---
title: "SDK"
description: "Reference for @oneie/sdk 0.14.13 — the client, which of the six verbs answer an outside caller, the 330-receiver registry, and all 29 subpath exports."
type: reference
audience: human
verified: 2026-09-06
canonical: https://one.ie/docs/sdk
---
`@oneie/sdk` is the TypeScript client for the ONE substrate. It ships
`SubstrateClient` (70 methods), the `RECEIVERS` registry of 330 typed receiver
contracts, and 29 subpath exports. It runs on Node, Bun, and Cloudflare Workers.

Related: [Receivers](/docs/receivers) · [API](/docs/api) ·
[Errors and limits](/docs/errors) · [Authority and access](/docs/authority) ·
[Quickstart](/docs/quickstart)

## Install

```bash
npm install @oneie/sdk
```

| Field | Value |
| --- | --- |
| Name | `@oneie/sdk` |
| Version | 0.14.13 |
| Module format | ESM only (`"type": "module"`) — no CommonJS build |
| Dependencies | `@noble/curves` ^2.2.0, `@noble/hashes` ^2.2.0, `@scure/base` ^2.2.0, `@scure/bip39` ^2.2.0, `better-auth` ^1.6.11, `zod` ^4.3.6 |
| Exports map | 30 entries — the root barrel plus 29 subpaths |

## The default base URL is localhost

`getApiUrl()` returns `http://localhost:4321` unless `process.env.NODE_ENV ===
"production"` (`src/urls.ts:7-16`). A script run in a normal shell with
`new SubstrateClient({ apiKey })` and no `baseUrl` will try to reach a local dev
server and fail with a connection error.

One branch is easy to miss: `getEnvironment()` returns `"production"` when
`process` is undefined at all (`src/urls.ts:8`). So the default turns on whether
the runtime exposes `process`, not on the runtime's name — where a `process`
shim exists but `NODE_ENV` is unset, the answer is still `http://localhost:4321`.
(`one.ie/web/wrangler.toml:8` sets `nodejs_compat` and declares no `NODE_ENV`,
so that worker takes the localhost branch.)

Pass `baseUrl` explicitly, or set `ONEIE_API_URL`:

```ts
import { SubstrateClient } from "@oneie/sdk";

const one = new SubstrateClient({
  apiKey: process.env.ONEIE_API_KEY,
  baseUrl: "https://one.ie",
});
```

Resolution order, from `src/urls.ts`:

| Setting | Order |
| --- | --- |
| Base URL | `cfg.baseUrl` → `ONEIE_API_URL` → `https://one.ie` when `NODE_ENV=production`, else `http://localhost:4321` |
| API key | `cfg.apiKey` → `ONEIE_API_KEY` → `ONE_API_KEY` |

The production host is `https://one.ie`. `PROD_API_URL` is set to that value at
`src/urls.ts:2`; the ask and auth routes are not served from `api.one.ie`.

## Client options

```ts
new SubstrateClient({
  apiKey?: string,
  baseUrl?: string,
  retry?: { maxAttempts?: number, backoff?: "exp"|"linear"|"fixed" },
  timeoutMs?: number,
  validate?: "strict" | "warn" | "off",
})
```

| Option | Default | Behaviour |
| --- | --- | --- |
| `apiKey` | env fallback | Sent as `Authorization: Bearer <key>` on every request |
| `baseUrl` | see above | Prefix for every path |
| `retry.maxAttempts` | `1` | Retries only on 429, 502, 503, 504, or a network error with no status |
| `retry.backoff` | `"exp"` | `exp` = `min(300 × 2^attempt, 30000)` ms · `linear` = `300 × attempt` ms · `fixed` = 300 ms |
| `timeoutMs` | `8000` | Per-request `AbortController` deadline; an abort throws `TimeoutError` |
| `validate` | `"warn"` | Payload validation mode, readable at `client.validateMode` |

`SubstrateClient.fromApiKey(key, baseUrl?)` is the two-argument shorthand.
`OneieClient` is an alias of `SubstrateClient`.

When an `apiKey` is present and telemetry is not disabled, the constructor fires
one fire-and-forget `POST /api/ask/sdk:init`. A 401 on any request clears the
persisted session token.

## The six verbs and which have an external door

Measured against `https://one.ie` on 2026-09-06 with a fresh key from
`POST /api/auth/agent`. Three of the six verbs return an error to a caller
holding a valid developer key.

| Verb | Method | Route | Agent key |
| --- | --- | --- | --- |
| ask | `ask(receiver, data?, timeoutMs?)` | `POST /api/ask/<receiver>` | **200** |
| signal | `signal(receiver, data?)` | `POST /api/signal/<receiver>` | **401** |
| mark | `mark(edge, strength?, opts?)` | `POST /api/mark/<edge>` | **403** |
| warn | `warn(edge, strength?, opts?)` | `POST /api/warn/<edge>` | **403** |
| fade | `fade(rate?)` | `POST /api/fade` | 200 |
| follow | `follow(tag)` | `GET /api/follow?tag=<tag>` | **200** |

Every row except `fade` was re-probed for this page. `fade` writes a decay
sweep across every path in the graph, so it was not fired against production
here; its 200 is the earlier probe's result. Its route gates on the same
`warn` role action that `mark` and `warn` pass.

`signal()` is 401 because `/api/signal/[...receiver].ts:67-74` requires the
Bearer token to byte-equal the server's `SERVER_SECRET` — a server-to-server
value, not a developer key. `api.one.ie` does not serve that path either. There
is no external entry point for `signal()` today.

`mark()` and `warn()` reach the route and are then refused by an ownership
check: `{"error":"forbidden","reason":"self_deal: cannot move an edge whose
source you do not own"}`. A malformed edge returns 400 with
`title: "edge must be source→target"` before the ownership check runs.

Two more methods a developer key cannot use: `discover()` returns 401
(`/api/agents/discover`) and `highways()` returns 403 (`/api/export/highways`).

`harden` is the sixth verb and has no client method. The only harden receiver in
the registry is `chat:harden` (`src/receivers.ts:2466`), called as
`ask("chat:harden", …)`. `client.know()` is `@deprecated` and returns `{}`;
`learning:know` is not in the registry.

Everything else on the surface is reachable through `ask()`. Of the 330
receivers, 310 declare `effect: "ask"` and 20 declare `effect: "signal"`.

## ask() and the outcome envelope

```ts
const cat = await one.ask("meta:catalog", {});
if (cat.kind === "result") {
  console.log((cat.result as unknown[]).length);
}
```

`Outcome<T>` is a discriminated union of four kinds (`src/types.ts:42-47`),
intersected with three optional backpressure fields.

| Kind | Shape |
| --- | --- |
| `result` | `{ kind: "result", result: T, latency: number }` |
| `timeout` | `{ kind: "timeout", timeout: true, latency: number }` |
| `dissolved` | `{ kind: "dissolved", dissolved: true, latency: number }` |
| `failure` | `{ kind: "failure", failure: true, latency: number }` |

Backpressure fields, present on any kind: `retryAfter` (seconds), `limit`,
`window` (seconds).

The wire envelope is different from the client union. The HTTP route sends
`{ outcome, signalId, receiver, … }`; the SDK computes `kind` from which key is
present in the response body, not from the `outcome` field
(`src/client.ts:229-233`). See Known limits for where the two disagree.

`signal()` returns `SignalResponse` — `{ ok, routed, result?, latency, success,
sui? }` — not an `Outcome`.

### Errors

Non-2xx responses throw. All classes extend `SubstrateError`, which carries
`.status`, `.code`, and `.body`.

| Status | Class | Code |
| --- | --- | --- |
| 401, 403 | `AuthError` | `auth_error` |
| 400, 422 | `ValidationError` | `validation_error` |
| 429 | `RateLimitError` (`.retryAfterMs`) | `rate_limit_error` |
| 408, 504, client abort | `TimeoutError` | `timeout_error` |
| any other, 402 included | `SubstrateError` | — |

That table is the whole of `throwForStatus` (`src/client.ts:67-74`). It has no
402 branch, so a payment-required response reaches the caller as a bare
`SubstrateError` with `.status === 402`.

Three more classes are exported from `@oneie/sdk/errors` but are never
constructed anywhere in `src/`, so the client does not throw them:
`DissolvedError` (`dissolved_error`), `InsufficientCreditsError`
(`insufficient_credits`, status 402) and `PaymentExceedsBudgetError`
(`payment_exceeds_budget`, status 402, carrying `.required` and `.max`).
`RateLimitError.retryAfterMs` is likewise declared but never populated —
`throwForStatus` constructs it with the message alone.

## Getting a key

```bash
curl -X POST https://one.ie/api/auth/agent \
  -H 'Content-Type: application/json' -d '{}'
```

Returns `{ uid, apiKey, wallet, group }`. No auth is required and the route is
rate-limited to 10 requests per minute per IP (`RATE_LIMIT`/`RATE_WINDOW`,
`one.ie/web/src/pages/api/auth/agent.ts:24-25`). `wallet` is `null` unless a Sui
address is supplied in the request body.

**The key is permanent.** `createKey()` inserts it with `valid_to` NULL —
"no forced expiry" — and the module docblock states it outright: "Agent keys are
permanent … No TTL expiry" (`one.ie/web/src/lib/agent-key.ts:10-11`,
`:100-106`). Verification treats a NULL `valid_to` as never-expiring
(`one.ie/web/src/lib/api-auth.ts:334`). Revocation is explicit: `world:revoke-key` runs `UPDATE world_keys SET
revoked_at = ?` (`one.ie/web/src/lib/world-receivers.ts:1086-1091`). The route's own docblock still says "24h key TTL"
(`agent.ts:8`); that line is stale, and the comment directly above the mint call
reads `// Mint permanent API key` (`agent.ts:123-124`).

There is no `agent` role. `MEMBER_ROLES` is `owner`, `admin`, `member`,
`viewer`, and `packages/sdk/src/role-tiers.ts:23` says "agent/auditor are actor
attributes, not roles". The route writes a `world_actors` row with
`type: 'agent'` and `role: 'member'` (`agent-key.ts:94-95`) and makes the new
actor `owner` of its own personal group (`agent-actor.ts:68`). At request time a
`one-`-prefixed key resolves to `role = "owner"` **scoped to `group:<uid>` and
nothing else** — naming any other group is refused 403,
`world-key cross-group access denied` (`api-auth.ts:350-355`).

From the SDK this is `client.authAgent(opts?)`, which posts to `/api/auth/agent`
directly. It is not `ask("auth:agent", …)` — that form returns 403
`direct substrate access not permitted` to an anonymous caller.

A fresh key on the free tier reads
`{"tier":"free","api_limit":1000,"agent_limit":5}` from
`ask("dashboard:usage", {})`.

## The receiver registry

`RECEIVERS` (`src/receivers.ts:177-5180`) is the capability catalog. Counts are
from evaluating the object, and match `meta:catalog` in production.

| Measure | Count |
| --- | --- |
| Receivers | 330 |
| Namespaces | 78 |
| `effect: "ask"` | 310 |
| `effect: "signal"` | 20 |
| Receivers with no `auth` label | 65 |
| Distinct `auth` label strings | 29 |

Largest families:

| Namespace | Receivers |
| --- | --- |
| `world` | 46 (45 prefixed `world:`, plus the bare `world` router) |
| `tasks` | 24 |
| `workflow` | 18 |
| `market` | 16 |
| `video` | 14 |
| `pages` | 10 |
| `booking` | 8 |
| `broadcast` | 8 |
| `wallet` | 7 |
| `foundation` | 7 |

Three receiver names carry no `family:` prefix: `world`, `notify`, and
`grant-capability`. `world` is the universal router — it takes tags and returns
who took the signal.

```ts
await one.ask("world", { tags: ["hello"] });
// → { kind: "result", outcome: "result", receiver: "world",
//     signalId: "…",
//     result: { ok: true, routed: "ceo", tags: ["hello"] } }
```

The address form `world:<tag-expr>` carries tags in the receiver name instead.

Each entry declares five required fields — `receiver`, `summary`, `request`,
`response`, `effect` — and optionally `auth`, `cost`, `reversible`,
`idempotent`, `settles`, `simulatable`, `roleAction`, `scope`, `version`,
`deprecated`, `surfaces`, and `examples` (`src/receivers.ts:61-142`). `auth` is
optional, which is why 65 receivers carry none.

### Typed access

`ask` and `signal` are conditional generics over `ReceiverName`. A declared
receiver infers its request type (`ReqOf<R>`) and response type (`ResOf<R>`)
from `RECEIVERS` with no codegen. A wrong payload to a known receiver is a
compile error. Any other string is the escape hatch, typed `unknown`.

```ts
import { RECEIVERS, RECIPES } from "@oneie/sdk/receivers";
import type { ReceiverName, ReqOf, ResOf } from "@oneie/sdk/receivers";

Object.keys(RECEIVERS).length; // 330
```

Exported types: `Receiver<Req, Res>`, `ReceiverName`, `ReqOf<R>`, `ResOf<R>`,
`ReceiverCost` (`"free" | "variable" | { fixed: number }`), `Settlement`
(`"none" | "offchain" | "onchain"`), `ReceiverEffect` (`"signal" | "ask"`),
`AuthLabel`, `RoleAction`, `RecipeName`. `receiver()` is the declaration helper.

`RECIPES` holds six named ordered journeys: `spine`, `build`, `trade`,
`transact`, `sop`, `a2a`.

```json
{ "spine": ["auth:agent", "world:create-key", "groups:join", "agents:sync"],
  "build": ["world:create-workspace", "world:create-group",
            "world:create-actor", "world:create-thing"] }
```

## Runtime discovery

Three `meta:` receivers, in four call forms, let a caller read the surface
instead of a document. (`meta:` holds six receivers in all — `meta:recall`,
`meta:reputation` and `meta:entitlements` are not projections of the catalog.)

| Call | Returns |
| --- | --- |
| `ask("meta:catalog", {})` | 330 rows, each with `receiver`, `summary`, `effect`, plus whichever of `auth`, `cost`, `reversible`, `settles`, `simulatable`, `idempotent` the receiver declared (measured on the live response: `idempotent` 275, `auth` 265, `cost` 216, `reversible` 201, `simulatable` 30, `settles` 20) |
| `ask("meta:catalog", { goal })` | the recipe for one of the six goals |
| `ask("meta:schema", { receiver })` | `{ request, response }` as JSON Schema draft 2020-12, generated from the zod contracts |
| `ask("meta:types", {})` | the workspace resource-type manifest plus built-in industry templates |

The first three run locally too, from `@oneie/sdk/meta`:
`metaCatalog({ canCall })`, `metaRecipe(goal)`, `metaSchema(receiver)`, plus
`ownerOnly` and the `CatalogEntry` type. `meta:types` has no local equivalent —
it reads the workspace, not the registry.

A malformed payload returns HTTP 400 with the field named:

```json
{ "error": "validation", "receiver": "agent:own-link",
  "field": "agent_id", "hint": "Invalid input", "expected": "string" }
```

## SubstrateClient methods

70 methods on the prototype, grouped. Plus one static (`fromApiKey`) and four
instance members: `pay` (`.accept`, `.request`, `.status`), `skills`
(`.import`), `subscribe` (an alias of `sub`), and `validateMode`.

| Family | Count | Methods |
| --- | --- | --- |
| Verbs | 7 | `ask` `signal` `mark` `warn` `markDims` `fade` `follow` |
| Identity | 4 | `authAgent` `register` `signOut` `walletFor` |
| Graph reads | 6 | `groups` `actors` `things` `paths` `events` `learning` |
| Groups and members | 9 | `createGroup` `listGroups` `joinGroup` `leaveGroup` `groupMembers` `inviteMember` `changeRole` `removeMember` `provisionClient` |
| Agents | 14 | `listAgents` `publishAgent` `pullAgent` `unpublishAgent` `agentHistory` `rollbackAgent` `syncAgent` `emitAgentEvent` `discover` `commend` `flag` `status` `capabilities` `claw` |
| Skills | 2 | `listSkills` `unimportSkill` |
| Market and money | 7 | `hire` `bounty` `bounties` `publish` `listMarket` `payWeight` `revenue` |
| Memory and learning | 10 | `recall` `recallHypotheses` `rememberHypothesis` `reveal` `forget` `frontier` `highways` `trail` `select` `know` |
| Observability | 6 | `stats` `health` `usage` `signals` `state` `exportData` |
| Other | 5 | `chat` `fn` `createLink` `inbox` `sub` |

The graph reads return async iterables and also expose `.get()`.

Signatures worth stating exactly, because they are printed wrongly elsewhere:

| Method | Signature |
| --- | --- |
| `signal` | `signal(receiver, data?)` — two arguments |
| `mark` | `mark(edge, strength = 1, opts?)` |
| `warn` | `warn(edge, strength = 1, opts?)` |
| `markDims` | `markDims(edge, { security, stability, simplicity, speed })` |
| `fade` | `fade(rate?)` — one argument |
| `frontier` | `frontier(limit = 50)` — a number |
| `highways` | `highways(limit = 50, from?)` → `HighwayEntry[]` |
| `know` | `know()` — deprecated, returns `{}` |

`health()` returns the live shape `{ status, agent, model, hasOpenRouter }`.

## Subpath exports

29 subpaths plus the root barrel. Each maps to one built module.

**Client and transport**

| Import | Exports |
| --- | --- |
| `@oneie/sdk/client` | `SubstrateClient` |
| `@oneie/sdk/brain` | `BrainClient`, `BrainEnv` |
| `@oneie/sdk/gateway` | `GatewayClient`, `GatewayEnv` |
| `@oneie/sdk/fetch` | `oneFetch`, `createAgent`, `PaymentError`, `BudgetExceededError`, `X402NetworkError` |
| `@oneie/sdk/urls` | `getApiUrl`, `getFrontendUrl`, `getEnvironment`, `resolveApiKey`, `resolveBaseUrl` |

**Contracts and types**

| Import | Exports |
| --- | --- |
| `@oneie/sdk/receivers` | `RECEIVERS`, `RECIPES`, `receiver()`, the receiver types |
| `@oneie/sdk/types` | `Outcome`, `SignalResponse`, `SdkConfig`, request/response interfaces |
| `@oneie/sdk/schemas` | zod schemas — `SignalResponseSchema`, `HealthSchema`, `AgentDefinitionSchema`, `WorkflowDiffSchema`, and others |
| `@oneie/sdk/meta` | `metaCatalog`, `metaRecipe`, `metaSchema`, `ownerOnly`, `CatalogEntry` |
| `@oneie/sdk/openapi` | `buildReceiverPayload`, `generatedRegion`, `injectRegion` |
| `@oneie/sdk/errors` | the eight error classes |

**Authority**

| Import | Exports |
| --- | --- |
| `@oneie/sdk/role-tiers` | `MEMBER_ROLES`, `rungOf`, `tierOf`, `minTier` |
| `@oneie/sdk/role-actions` | `ROLE_ACTIONS`, `isRoleAction` |
| `@oneie/sdk/receiver-action` | `roleActionFor` |

**Identity and session**

| Import | Exports |
| --- | --- |
| `@oneie/sdk/storage` | `getToken`, `setToken`, `clearToken` |
| `@oneie/sdk/wallet` | `generateWallet`, `recoverWallet`, `deriveWalletFromSeed`, `generateAgentKeypairs`, `suiAddressFromPubkey`, `evmAddressFromPubkey` — see [Wallets and custody](/docs/wallets) |
| `@oneie/sdk/launch` | `launchToken`, `LaunchOpts`, `LaunchResult` |

**Domain**

| Import | Exports |
| --- | --- |
| `@oneie/sdk/market` | `Deal`, `Rubric`, `Quote`, `quoteBlend`, `affiliateCommission`, `advance`, `passesBar`, `InvalidTransition` |
| `@oneie/sdk/billing` | `CREDITS_PER_USD`, `USD_PER_CREDIT`, `creditsForUsd`, `creditsForCents`, `rateFor`, `computeBurn` |
| `@oneie/sdk/work-contract` | `GRAMMAR`, `validateContract`, `hydrateScope`, `Verdict`, `Scope` |
| `@oneie/sdk/blocks` | `normalizeBlockProps`, `describeFields`, `variantsWithFields`, `CHROME_PROPS` |
| `@oneie/sdk/compile` | `parseAgentMd`, `compileAgent`, `priceToAccepts`, `ParsedAgent` |

**Templates and generated**

| Import | Exports |
| --- | --- |
| `@oneie/sdk/templates` | `TEMPLATES`, `TemplateName`, `manifestToTql` |
| `@oneie/sdk/fn-allowlist` | `FN_ALLOWLIST`, `isAllowedFn` — a readonly tuple, order is load-bearing |
| `@oneie/sdk/generated/fn-map` | `FN_MAP` — 270 generated schema functions — and `FN_MAP_VERSION` |
| `@oneie/sdk/generated/skins/engineering` | `asEpic`, `asStory`, `asBug`, `asChore`, `asSpike`, `asSprint` |

**Other**

| Import | Exports |
| --- | --- |
| `@oneie/sdk/telemetry` | `emit`, `isDisabled`, `getSessionId`, `emitEvolution` |
| `@oneie/sdk/testing` | `createMockSubstrate` |
| `@oneie/sdk/handoff` | `validateEthAddress`, `generateDeployLink` |

Some symbols exist only on the root barrel and have no subpath:
`createSdkAuthClient`, `trail`, `tagFingerprint`, `pluginBuild`,
`pluginBuildEnterprise`, `readWorkspaceSoul`, `buildSoulSuffix`,
`tqlToManifest`, `BASE_MANIFEST`, `SDK_VERSION`, and everything from `pay.ts`,
`skills.ts`, and `broadcast.ts`.

## Environments

| Runtime | Import style |
| --- | --- |
| Node | root barrel or any subpath |
| Bun | root barrel or any subpath |
| Cloudflare Workers | **subpaths only** |

In a Cloudflare Worker, import by subpath — `@oneie/sdk/brain`,
`@oneie/sdk/fn-allowlist`, `@oneie/sdk/role-actions`. Do not import the bare
barrel. The barrel pulls in modules that generate randomness at global scope,
which a Worker isolate refuses outside a request handler; the isolate fails to
initialise and the request returns 500. This is why `fn-allowlist`,
`role-actions` and `receiver-action` are subpath-only and are deliberately not
re-exported from `src/index.ts`.

The barrel-only symbols listed above are therefore Node and Bun only. `trail`
and `tagFingerprint` in particular have no subpath and cannot be imported from a
Worker.

The package is ESM only. There is no CommonJS build, so `require("@oneie/sdk")`
does not work.

## Known limits

**`signal()`, `mark()` and `warn()` have no external door.** Measured 2026-09-06
with a valid agent key: 401, 403, 403. `discover()` is 401 and `highways()` is
403. `signal()` requires `SERVER_SECRET`, which is not issued to developers, and
`api.one.ie` does not serve `/api/signal/*` either.

**`outcome.kind` is derived from key presence, not from the server's `outcome`
field.** `src/client.ts:229-233` computes `kind` as `"result" in raw ? "result"
: "timeout" in raw ? "timeout" : "dissolved" in raw ? "dissolved" : "failure"`.
Calling an unknown receiver returns the wire body
`{"outcome":"dissolved","signalId":…,"reason":"unknown_receiver"}`, which has no
`dissolved` key — so the SDK hands the caller `kind: "failure"` for a response
the server labelled dissolved. Read the raw `outcome` field when the distinction
matters.

**A self-minted agent key is permanent and scoped to one group.**
`POST /api/auth/agent` mints a non-expiring key (`valid_to` NULL) whose only
kill is explicit revocation. It carries `owner` authority over its own
`group:<uid>` and 403s on every other group, so it cannot read or write a
workspace you already own — mint a scoped key with `world:create-key` for that.
Treat the minted string as a standing credential, not a session token.

**`auth` labels do not reliably bind.** 65 of the 330 receivers declare no
`auth` label; the other 265 spread across 29 label strings that mix caller
classes (`public`, `session`, `agent_key`) with action names (`manage_clients`,
`read_corpus`, `mint_capability`). A receiver's declared label is not a
guarantee that a call will be refused — a key self-minted anonymously from
`POST /api/auth/agent` was observed completing a receiver labelled
`manage_clients`.

**`client.know()` is dead.** It is `@deprecated` and returns `{}`. There is no
`learning:know` receiver.

**The npm README has drifted.** `packages/sdk/README.md` is not in the
typechecked-snippet allowlist (`packages/sdk/tests/docs-snippets.test.ts:21-30`)
and its examples do not compile against the shipped SDK: a three-argument
`signal()`, a dims-object `mark()`, a two-argument `fade()`, a string-argument
`frontier()`, a `{ highways }` destructure, `know()` shown as live,
`ONEIE_API_URL` documented as defaulting to `https://api.one.ie`, and every
`ask()` example calling `tutor:explain`, which is not a receiver. Use the
signatures on this page.

**Two in-repo reference docs carry stale counts.**
`text/receivers-reference.md:58` says 120 receivers; `text/sdk-reference.md`
says 260+ at line 22 and pins version 0.14.8 in its frontmatter (line 4). The current numbers are 330 and 0.14.13.
`text/signals-catalog.md` is the one with a parity guard.