@byollm/relay 0.1.0-alpha.83 → 0.1.0-alpha.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  > [!WARNING]
2
- > **Alpha (`0.1.0-alpha.83`) — under active development. Don't use this yet.**
2
+ > **Alpha (`0.1.0-alpha.84`) — under active development. Don't use this yet.**
3
3
  >
4
4
  > This is a walking skeleton. It routes real jobs between real daemons and real
5
5
  > sites, and it is the fixture byollm_009 freezes against — but it keeps its
@@ -79,16 +79,16 @@
79
79
  > ordering without the column it stores the grant in.
80
80
 
81
81
  <!-- release-note 0.1.0-alpha.40 -->
82
- **`byollm install` — stop keeping a terminal open.** The daemon can now run
82
+ **`byollm start` — stop keeping a terminal open.** The daemon can now run
83
83
  under your computer's own supervisor and restart itself if it stops: a launchd
84
84
  agent on macOS, a `systemd --user` unit on Linux, a logon task on Windows. All
85
- user-level — no root, no system directories, and `byollm uninstall` takes it
85
+ user-level — no root, no system directories, and `byollm stop` takes it
86
86
  away. `byollm status` gained a line saying whether it is actually supervised
87
87
  right now, including the state that matters most: installed but not running,
88
88
  which looks fine from an app's dashboard and serves nothing.
89
89
 
90
90
  If you are running via `npx`, install properly first (`npm install -g
91
- byollm@alpha`) — `install` refuses to supervise a copy in npx's cache, because
91
+ byollm@latest`) — `install` refuses to supervise a copy in npx's cache, because
92
92
  npm deletes that directory and the service would fail at some later boot.
93
93
 
94
94
  <!-- release-note 0.1.0-alpha.41 -->
package/dist/index.d.ts CHANGED
@@ -603,6 +603,20 @@ interface RelayOptions {
603
603
  * sent none refuses every job, and it would do so with no signal here.
604
604
  */
605
605
  readonly controlPlanePublic?: string | undefined;
606
+ /**
607
+ * A version this deployment offers daemons — B053, D1.
608
+ *
609
+ * Absent means no offer reaches anybody, which is how this ships: the code
610
+ * changes no byte on the wire until a deployment sets it deliberately.
611
+ */
612
+ readonly updateOffer?: string | undefined;
613
+ /**
614
+ * The oldest daemon this deployment serves — B052, D1.
615
+ *
616
+ * Absent means the floor refuses nobody. A floor picked optimistically on
617
+ * deploy day takes working machines down.
618
+ */
619
+ readonly daemonFloor?: string | undefined;
606
620
  /**
607
621
  * Whether a purpose can be satisfied for this person, asked at enqueue.
608
622
  *
package/dist/index.js CHANGED
@@ -27,6 +27,9 @@ import {
27
27
  keyId,
28
28
  verifyRequest,
29
29
  verifyPublicIdentity,
30
+ updateOfferFor,
31
+ checkDaemonFloor,
32
+ UPGRADE_COMMAND,
30
33
  PublicIdentity,
31
34
  ERROR_STATUS as ERROR_STATUS2
32
35
  } from "@byollm/protocol";
@@ -545,6 +548,21 @@ var DaemonPlane = class {
545
548
  body,
546
549
  HeartbeatRequest,
547
550
  async (request, device) => {
551
+ const belowFloor = this.#deps.daemonFloor === void 0 ? null : checkDaemonFloor({
552
+ daemonVersion: request.daemonVersion,
553
+ floor: this.#deps.daemonFloor,
554
+ upgradeCommand: UPGRADE_COMMAND
555
+ });
556
+ if (belowFloor !== null) {
557
+ return {
558
+ status: ERROR_STATUS2["daemon-below-floor"],
559
+ body: {
560
+ error: belowFloor.error,
561
+ message: belowFloor.message,
562
+ floor: belowFloor.floor
563
+ }
564
+ };
565
+ }
548
566
  const now = this.#deps.now();
549
567
  await this.#deps.state.sweep();
550
568
  await this.#deps.state.seen({
@@ -578,6 +596,10 @@ var DaemonPlane = class {
578
596
  return ok({
579
597
  sites,
580
598
  ...rotations,
599
+ ...updateOfferFor({
600
+ offer: this.#deps.updateOffer,
601
+ daemonVersion: request.daemonVersion
602
+ }),
581
603
  awaitingConsent,
582
604
  cancel: [],
583
605
  lost: request.activeLeases.map((lease) => ({
@@ -596,6 +618,21 @@ var DaemonPlane = class {
596
618
  return ok({
597
619
  sites,
598
620
  ...rotations,
621
+ /**
622
+ * The update offer — B053, and the ONLY way this field is set.
623
+ *
624
+ * `updateOfferFor` applies `mayOfferUpdate` inside itself and
625
+ * returns a spreadable object, so there is no `updateTo:` anywhere
626
+ * for a later hand to copy to a third return site. There are
627
+ * already two, which is how a remember-to-check rule fails.
628
+ *
629
+ * HeartbeatResponse is `.strict()`: a wrong emission is not a bad
630
+ * offer, it is every pre-.83 daemon rejecting every heartbeat.
631
+ */
632
+ ...updateOfferFor({
633
+ offer: this.#deps.updateOffer,
634
+ daemonVersion: request.daemonVersion
635
+ }),
599
636
  awaitingConsent,
600
637
  cancel,
601
638
  lost,
@@ -1377,7 +1414,9 @@ var Relay = class {
1377
1414
  pairingCodes: options.pairingCodes ?? new MemoryPairingCodes(() => this.#now()),
1378
1415
  ...options.verificationUrl === void 0 ? {} : { verificationUrl: options.verificationUrl },
1379
1416
  ...options.controlPlanePublic === void 0 ? {} : { controlPlanePublic: options.controlPlanePublic },
1380
- ...options.authorGrant === void 0 ? {} : { authorGrant: options.authorGrant }
1417
+ ...options.authorGrant === void 0 ? {} : { authorGrant: options.authorGrant },
1418
+ ...options.updateOffer === void 0 ? {} : { updateOffer: options.updateOffer },
1419
+ ...options.daemonFloor === void 0 ? {} : { daemonFloor: options.daemonFloor }
1381
1420
  });
1382
1421
  if (options.controlPlanePublic !== void 0 && options.authorGrant === void 0) {
1383
1422
  throw new Error(
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/daemon-plane.ts","../src/pairing-codes.ts","../src/refusals.ts","../src/debug.ts","../src/fixture.ts","../src/site-plane.ts"],"sourcesContent":["import {\n checkProtocolVersion,\n declaredVersion,\n type SignedGrant,\n} from \"@byollm/protocol\";\nimport {\n DaemonPlane,\n type GrantAuthor,\n type PlaneResult,\n} from \"./daemon-plane.js\";\nimport { debugPage } from \"./debug.js\";\nimport { Projection, type RelayFixture } from \"./fixture.js\";\nimport { MemoryPairingCodes, type PairingCodes } from \"./pairing-codes.js\";\nimport { SitePlane, type Satisfiable } from \"./site-plane.js\";\nimport { RelayState } from \"./state.js\";\nimport type { RoutingStore } from \"./store.js\";\n\n/**\n * `@byollm/relay` — the reference relay (cloud_004 §14).\n *\n * A blind relay between byollm sites and daemons: it routes stubs, hands over\n * sealed envelopes it cannot open, and knows who is online. It is the first\n * consumer of byollm_009's session layer that is neither the site nor the\n * device, which makes it the thing that proves the protocol's central claim.\n *\n * ## Why this ships open\n *\n * It is the conformance kit's reference relay, and the kit is public — so it\n * starts where it ends rather than being written closed and ported. A relay\n * that claims to be blind should be readable by the people trusting it, and a\n * third-party daemon testing hub mode should test against real code rather\n * than a mock of it. The production hub — multi-tenant routing, presence at\n * scale, billing, ops — is built on these same interfaces and is not this.\n *\n * ## Blind by construction, not by policy\n *\n * {@link RelayOptions} has no field that can hold a private key, and no type\n * in this package has one either. `RELAY_BLIND` is therefore not a rule the\n * code follows; it is a shape the code has. The only way to make this relay\n * able to read a payload is to change its types, which is a review someone\n * would have to justify rather than a line someone could slip in.\n */\n\n/**\n * What a control plane answers when a relay asks about one job.\n *\n * Declared here rather than imported, and deliberately narrower than what\n * `@byollm/control-plane` returns: a relay needs to know whether it got a\n * grant and whether a refusal is forever, and nothing else. Stating only that\n * keeps the two packages independent — a relay can be wired to any control\n * plane, and the reference engine satisfies this by having more, not less.\n *\n * `reason` is for the log. The relay never branches on it, because a relay\n * that acted differently per reason would be a second implementation of a\n * policy it does not own.\n */\nexport type GrantDecision =\n | { readonly granted: SignedGrant; readonly declined?: undefined }\n | {\n readonly granted?: undefined;\n readonly declined: {\n /** Never offer this job to this device again. */\n readonly permanent: boolean;\n readonly reason?: string;\n };\n };\n\nexport interface RelayOptions {\n /**\n * Which site this relay routes for.\n *\n * One, in the skeleton. Multi-tenant routing is the closed piece\n * (cloud_004 §9), and it replaces this field rather than extending it.\n */\n /** Consent and routing, projected from the control plane. */\n readonly fixture?: RelayFixture;\n /**\n * The control plane's grant-signing public key — Amendment J.\n *\n * Handed to daemons at pairing, and the thing every grant is checked\n * against. Configuring it without {@link RelayOptions.authorGrant} is\n * refused at construction: a device told to expect signed grants and then\n * sent none refuses every job, and it would do so with no signal here.\n */\n readonly controlPlanePublic?: string | undefined;\n /**\n * Whether a purpose can be satisfied for this person, asked at enqueue.\n *\n * The relay does not hold the answer and must not: one that filtered on\n * mappings would hold the mapping, which is the one thing it cannot have. So\n * it asks whoever does — in practice the control plane, which already\n * decides this at claim, a moment later.\n *\n * Optional. A relay without it refuses nothing, which is a supported\n * arrangement and one an operator must be able to see they are in: say so at\n * boot and on the health surface, because a check that quietly is not there\n * reads as a check that passed.\n */\n readonly satisfiable?: Satisfiable;\n /**\n * Author a grant for one claimed job — Amendment J.\n *\n * **The relay asks; it does not decide.** Everything a grant asserts —\n * whose job this is, whether they are still a member, which of the owner's\n * services their mapping resolves to — is the control plane's knowledge,\n * and this callback is the seam between the two. A relay wired to a\n * deployment that has no control plane simply has no callback, and its\n * devices serve their owners alone.\n *\n * Declining says whether the refusal is **permanent**, and that is the\n * whole reason this returns a shape rather than `SignedGrant | undefined`.\n * A relay releases a declined job, and a release can carry `refused`, which\n * means never offer this job to this device again. \"This person was removed\n * from the team\" is forever — removal stops queued claims, per hole 1.\n * \"Their mapping resolved to another of your machines\" is emphatically not:\n * marking that permanently would mean the job could never reach the device\n * it was always meant for, and nothing would ever report it.\n *\n * The capability matrix is passed because resolution needs it — the control\n * plane chooses from what this device actually advertised, never from a\n * name it invented. Until byollm_016 Amendment L lands, \"resolution\" is the\n * job's own selection or the device's default; after it, the user's\n * per-purpose mapping. The seam does not change.\n */\n readonly authorGrant?: GrantAuthor;\n /** How long a claim is good for. */\n readonly leaseMs?: number;\n /** Injectable clock, so tests move time instead of sleeping. */\n readonly now?: () => number;\n /**\n * Where pending pairing codes live — cloud_009's device-code flow.\n *\n * Defaults to an in-memory store, which is right for the reference relay\n * and wrong for a hub: two replicas mean a code minted on one must be\n * pollable on the other, the same reason the routing store is not a `Map`.\n */\n readonly pairingCodes?: PairingCodes;\n /**\n * Where a human approves a code. The control plane's own URL.\n *\n * Given rather than derived: the relay cannot approve anything, because\n * approving is looking at a fingerprint while signed in and that session\n * lives in the dashboard. Absent, the device-code flow is refused as\n * unsupported rather than pointed somewhere useless.\n */\n readonly verificationUrl?: string;\n /** Where the daemon plane is mounted. */\n readonly basePath?: string;\n /**\n * Serve `/debug`, which is off unless somebody asks for it.\n *\n * The page shows every routed job for a site, its state, who claimed it and\n * how long its timers have left. It shows no prompt or result text — the\n * relay does not have them — and it is genuinely useful when a route is\n * behaving strangely.\n *\n * It is also, on anything reachable from the internet, an anonymous read of\n * exactly the metadata the site plane exists to protect. That was finding\n * eleven, found by curling a deployed hub. The hub refuses the route\n * outright; this package used to serve it by default and leave `D005` to\n * warn whoever deployed it, which is a default that fails safe only if\n * somebody reads the audit.\n *\n * So: off, and per-site when on (cloud_009 §3 — the debug page is per-site\n * or it is nothing). `D005` still fails for a relay that turned it on,\n * which is the audit doing its job for an operator who made a choice rather\n * than warning everybody about a default.\n */\n readonly debug?: boolean;\n /**\n * Where routing state lives — cloud_006.\n *\n * Defaults to an in-process {@link RelayState}, which is correct for one\n * replica and is what this package ships. A hub running more than one\n * replica supplies a shared implementation of {@link RoutingStore} instead;\n * `packages/relay/test/two-replicas.test.ts` is why that is not optional.\n *\n * **The implementation is deliberately not in this package.** A Valkey\n * client is a dependency every consumer would carry to get a feature only a\n * multi-replica deployment uses, and the production hub is the closed piece\n * (cloud_001). What ships here is the interface, the reference\n * implementation, and the tests that say what an implementation must\n * guarantee.\n */\n readonly store?: RoutingStore;\n}\n\n/** A running relay: one fetch handler, two planes, one debug page. */\nexport class Relay {\n readonly state: RoutingStore;\n readonly projection: Projection;\n readonly #daemon: DaemonPlane;\n readonly #site: SitePlane;\n readonly #now: () => number;\n readonly #basePath: string;\n readonly #debug: boolean;\n\n constructor(options: RelayOptions) {\n this.state =\n options.store ?? new RelayState({ now: options.now ?? Date.now });\n this.projection = new Projection(options.fixture);\n this.#now = options.now ?? Date.now;\n this.#basePath = (options.basePath ?? \"/byollm\").replace(/\\/+$/, \"\");\n this.#debug = options.debug ?? false;\n this.#daemon = new DaemonPlane({\n state: this.state,\n projection: this.projection,\n now: this.#now,\n leaseMs: options.leaseMs ?? 60_000,\n pairingCodes:\n options.pairingCodes ?? new MemoryPairingCodes(() => this.#now()),\n ...(options.verificationUrl === undefined\n ? {}\n : { verificationUrl: options.verificationUrl }),\n ...(options.controlPlanePublic === undefined\n ? {}\n : { controlPlanePublic: options.controlPlanePublic }),\n ...(options.authorGrant === undefined\n ? {}\n : { authorGrant: options.authorGrant }),\n });\n /**\n * A relay that promises grants and cannot author them is refused here.\n *\n * `controlPlanePublic` is what tells a device to expect a signed grant\n * with every job. Without an author, every claim would arrive without\n * one, every job would be refused at the device, and the only symptom\n * would be a fleet that had quietly stopped working — with this process\n * reporting itself healthy throughout. It is a deployment mistake that\n * cannot be caught downstream, so it is caught at construction.\n */\n if (\n options.controlPlanePublic !== undefined &&\n options.authorGrant === undefined\n ) {\n throw new Error(\n \"controlPlanePublic is set but authorGrant is not: devices would be \" +\n \"told to expect signed grants that nothing here can produce, and \" +\n \"would refuse every job\",\n );\n }\n\n this.#site = new SitePlane({\n state: this.state,\n projection: this.projection,\n now: this.#now,\n ...(options.satisfiable === undefined\n ? {}\n : { satisfiable: options.satisfiable }),\n });\n }\n\n /** Replace the projection — a control-plane push, or a fixture edit. */\n project(fixture: RelayFixture): void {\n this.projection.replace(fixture);\n }\n\n /**\n * Fire due timers and report what moved.\n *\n * Exposed rather than run on an interval so a test can drive it, and so the\n * production hub can decide its own scheduling. The relay never needs a\n * timer to be *correct* — every read path sweeps first — but a job whose\n * site vanished should return to the queue without waiting for someone to\n * ask about it.\n */\n async sweep(): Promise<{ requeued: string[] }> {\n const requeued = await this.state.sweep();\n return { requeued: requeued.map((j) => j.id) };\n }\n\n /** The whole HTTP surface. */\n async handle(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const path = url.pathname;\n\n if (path === \"/debug\" || path === `${this.#basePath}/debug`) {\n // **Per-site or nothing** — cloud_009 §3, ratified. A page that reads\n // every tenant's state through one door is finding eleven wearing a\n // different hat: the anonymous read of who-is-online and\n // which-device-holds-what, rebuilt after being closed. So the site is a\n // parameter, and without one there is no page rather than a page\n // showing everything.\n const siteId = url.searchParams.get(\"site\");\n if (!this.#debug) {\n // No such route. `not-found` rather than `forbidden`, because there\n // is no credential that would work and saying \"forbidden\" advertises\n // a door that does not open.\n return new Response(JSON.stringify({ error: \"not-found\" }), {\n status: 404,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n if (siteId === null || this.projection.siteFor(siteId) === null) {\n // **A different answer, deliberately.** `D005` probes this path with\n // no site id, and a 404 here would tell it there is no debug surface\n // when there is one behind a parameter — a check passing for a reason\n // unrelated to the property, in the audit written to catch that.\n //\n // The same answer for \"no site named\" and \"a site I do not hold\", so\n // this cannot be used to ask which sites exist.\n return new Response(JSON.stringify({ error: \"bad-request\" }), {\n status: 400,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n return new Response(\n await debugPage(this.state, this.#now(), {\n siteId,\n consents: (owner) =>\n this.projection.consentFor(owner, siteId) !== null,\n }),\n { headers: { \"content-type\": \"text/html; charset=utf-8\" } },\n );\n }\n\n const rawBody = request.method === \"POST\" ? await request.text() : \"\";\n const body = rawBody === \"\" ? undefined : safeJson(rawBody);\n const endpoint = path.slice(path.lastIndexOf(\"/\") + 1);\n const auth = {\n endpoint,\n rawBody,\n signature: signatureFrom(request.headers, \"x-byollm-runner\"),\n };\n // **The handshake, before anything else** — byollm_009 §B.4.\n //\n // The direct server has done this since its own version defect was found;\n // this relay never did, so every mismatch on every endpoint arrived as a\n // bare `bad-request` from a `z.literal` buried in a schema — a daemon and\n // a relay discovering they disagreed by failing, with nothing in the\n // answer naming the disagreement or the fix.\n //\n // One helper, both planes, every endpoint. The first attempt at this\n // refused every site request in the suite, because the site plane's\n // requests carried no version at all: the check was right and the wire was\n // incomplete, which is why B.4 was written down rather than patched at\n // 4 a.m. The site plane declares a version now.\n //\n // Health and the debug page are above this line deliberately: they are not\n // protocol endpoints, and a probe that has to speak the version to ask\n // whether the process is alive is a probe that stops working on the day\n // the version moves.\n // **Protocol paths only.** An unknown path is a 404, not a lecture about\n // versions: written the other way first, and a request for `/healthz` —\n // or anything a scanner tries — came back with what this relay speaks and\n // how to upgrade. The handshake is part of the protocol, not of the HTTP\n // surface, and answering it for a path that does not exist both misleads\n // the caller and describes us to somebody who was only knocking.\n if (path.startsWith(\"/byollm/\") || path.startsWith(\"/relay/\")) {\n const refusal = checkProtocolVersion({\n protocolVersion: declaredVersion({ body, query: url.searchParams }),\n });\n if (refusal) return json({ status: 400, body: refusal });\n }\n\n // The caller header differs by plane, so a signature meant for one can\n // never be presented to the other by moving the request. The endpoint's\n // domain separator (`site/…`) already covers this; the header makes it\n // true at parse time rather than at verification time.\n const siteAuth = {\n endpoint,\n rawBody,\n signature: signatureFrom(request.headers, \"x-byollm-site\"),\n };\n\n // -- the site plane -----------------------------------------------------\n if (path === \"/relay/site/enqueue\") {\n return json(await this.#site.enqueue(siteAuth, body));\n }\n if (path === \"/relay/site/payload\") {\n return json(await this.#site.payload(siteAuth, body));\n }\n if (path === \"/relay/site/cancel\") {\n return json(await this.#site.cancel(siteAuth, body));\n }\n if (path === \"/relay/site/pending\") {\n return json(\n await this.#site.pending(\n siteAuth,\n url.searchParams.get(\"siteId\") ?? \"\",\n ),\n );\n }\n if (path === \"/relay/site/results\") {\n return json(\n await this.#site.results(\n siteAuth,\n url.searchParams.get(\"siteId\") ?? \"\",\n ),\n );\n }\n\n // -- the daemon plane ---------------------------------------------------\n if (!path.startsWith(`${this.#basePath}/`)) {\n return json({ status: 404, body: { error: \"not-found\" } });\n }\n switch (auth.endpoint) {\n case \"pair\":\n return json(await this.#daemon.pair(body));\n case \"claim\":\n return json(await this.#daemon.claim(auth, body));\n case \"fetch\":\n return json(await this.#daemon.fetch(auth, body));\n case \"result\":\n return json(await this.#daemon.result(auth, body));\n case \"heartbeat\":\n return json(await this.#daemon.heartbeat(auth, body));\n case \"release\":\n return json(await this.#daemon.release(auth, body));\n default:\n return json({ status: 404, body: { error: \"not-found\" } });\n }\n }\n}\n\nfunction safeJson(raw: string): unknown {\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n return undefined;\n }\n}\n\n/** Rebuild the signature from headers, refusing anything partial. */\nfunction signatureFrom(headers: Headers, callerHeader: string): unknown {\n const runnerId = headers.get(callerHeader);\n const issuedAt = headers.get(\"x-byollm-issued-at\");\n const signature = headers.get(\"x-byollm-signature\");\n // Checked before `Number()`, which turns a missing header into the epoch —\n // a stale-timestamp check that silently passes is worse than none.\n if (runnerId === null || issuedAt === null || signature === null) {\n return undefined;\n }\n return { runnerId, issuedAt: Number(issuedAt), signature };\n}\n\nconst json = (result: PlaneResult): Response =>\n new Response(JSON.stringify(result.body), {\n status: result.status,\n headers: { \"content-type\": \"application/json\" },\n });\n\nexport { Projection, RelayState, debugPage };\nexport type { RoutingStore } from \"./store.js\";\nexport type { RelayFixture };\nexport {\n ConsentRecord,\n DeviceRecord,\n RevocationRecord,\n RosterRecord,\n SiteRecord,\n RelayFixture as RelayFixtureSchema,\n EMPTY_FIXTURE,\n} from \"./fixture.js\";\nexport { AWAITING_PAYLOAD_MS } from \"./state.js\";\n/**\n * How a (site, owner) route is written — cloud_009 §3.\n *\n * Exported because a store in another repository builds the same set and has\n * to agree on the encoding. Spelling it out on both sides is two statements\n * of one format, which is the bug the routes set exists to remove one level\n * up: the hub's Valkey store and this package's memory store must match\n * character for character or a claim silently returns nothing.\n */\nexport { routeKey } from \"./state.js\";\n/**\n * Everything an implementer of {@link RoutingStore} needs.\n *\n * `ClaimInput` and `HolderRefusal` were missing from this list, which made the\n * interface unimplementable outside this package — found by writing the second\n * implementation, which is the only thing that could have found it. An\n * exported interface whose parameter types are private is a contract nobody\n * can sign.\n *\n * It happened again with `ReleaseReason` (cloud_008 §2.1), added to\n * `releaseLeases` and not to this list, and found the same way: the hub\n * failed to compile. A docstring recording a lesson is not a check, which is\n * why `store-contract.test-d.ts` implements `RoutingStore` from the package\n * entry point alone.\n *\n * **And a third time, with `Grant` (V1-3) — through the check.** Declaring a\n * `RoutingStore` only requires `RoutingStore` to be exported; a *return* type\n * is reachable structurally without being nameable, so the check caught\n * nothing and the hub caught it a release later. The restated signatures\n * below the declaration are the part that bites, and they were a list\n * somebody had to remember to extend.\n *\n * A list of signatures is the same shape as this list of exports: correct\n * until the next member. `store-contract.test-d.ts` now restates every method\n * whose parameters or results are named types, `renewLeases` and\n * `cancelRequests` included.\n */\nexport type {\n ClaimInput,\n HolderRefusal,\n Presence,\n ReleaseReason,\n RoutedJob,\n RoutedState,\n} from \"./state.js\";\n\n/** The grant a lease-scoped answer names — V1-3. */\nexport type { Grant } from \"./store.js\";\n\nexport {\n MAX_OUTSTANDING_PAIRINGS,\n MemoryPairingCodes,\n PAIRING_BUSY_MESSAGE,\n PAIRING_CODE_TTL_MS,\n newDeviceCode,\n newUserCode,\n type PairingCodes,\n type PendingPairing,\n // `put` returns it, so an implementor outside this package needs to be able\n // to name it. Four separate releases have shipped a public option whose type\n // was not exported; the type test next door is what makes the fifth fail\n // here instead of in somebody else's build.\n type PutResult,\n} from \"./pairing-codes.js\";\n","import {\n PairPollRequest,\n PairStartRequest,\n ClaimRequest,\n FetchRequest,\n HeartbeatRequest,\n MAX_ENVELOPE_BYTES,\n PROTOCOL_VERSION,\n ReleaseRequest,\n ResultRequest,\n RequestSignature,\n envelopeBytes,\n keyId,\n verifyRequest,\n verifyPublicIdentity,\n PublicIdentity,\n ERROR_STATUS,\n type CapabilityMatrix,\n type ClaimedStub,\n} from \"@byollm/protocol\";\nimport { z } from \"zod\";\nimport type { Projection } from \"./fixture.js\";\nimport type { GrantDecision } from \"./index.js\";\nimport {\n PAIRING_BUSY_MESSAGE,\n PAIRING_CODE_TTL_MS,\n newDeviceCode,\n newUserCode,\n type PairingCodes,\n type PendingPairing,\n} from \"./pairing-codes.js\";\nimport { tooLargeRefusal } from \"./refusals.js\";\nimport { RETRY_AFTER_MS, type HolderRefusal } from \"./state.js\";\nimport { clockSkewRefusal } from \"./refusals.js\";\nimport type { RoutingStore } from \"./store.js\";\n\n/**\n * The plane a daemon talks to — cloud_004 §2.\n *\n * To a daemon this is an upstream like any other: it claims, fetches, reports\n * and heartbeats exactly as it does against a direct site. That sameness is\n * the point of §9's \"the hub is a deployment of the open parts\" — hub mode is\n * not a second daemon code path, it is a second upstream.\n *\n * What differs is invisible from the daemon's side and total from ours: **this\n * upstream cannot seal.** A direct site answers `fetch` by opening its own\n * envelope and re-sealing to the claiming device. The relay has nothing to\n * open and nothing to seal with, so it answers `fetch` with whatever the site\n * left for that device, or with nothing yet.\n */\n\nexport interface PlaneResult {\n readonly status: number;\n readonly body: unknown;\n}\n\nconst ok = (body: unknown): PlaneResult => ({ status: 200, body });\n\n/**\n * A store refusal, in HTTP.\n *\n * The store says *why* in its own vocabulary and this decides what a daemon is\n * told, which keeps the two independent: a store that grows a reason does not\n * get to invent a status code, and a status code that changes does not reach\n * into the store.\n *\n * `not-ready` is the one that matters. It means claimed-but-not-yet-sealed, and\n * a daemon must retry rather than abandon — the job is legitimately still\n * theirs until the lease or the awaiting-payload clock says otherwise. It was\n * the protocol gap that produced the 409 in the first place.\n */\nconst REFUSALS: Record<HolderRefusal, PlaneResult> = {\n \"not-found\": {\n status: 404,\n body: { error: \"not-found\", message: \"unknown job\" },\n },\n // `forbidden`, not `unauthorized` — V1-13. Both of these are an\n // *identified* caller being refused, which is what 403 means and what the\n // table says `forbidden` is for; `unauthorized` is 401 and means \"we do not\n // know who you are\". Served as 403 with a 401's code, a revoked daemon and\n // an unsigned one looked alike in every log and every client branch, and\n // \"check your keys\" is the wrong advice for both in opposite directions.\n \"not-holder\": {\n status: 403,\n body: {\n error: \"forbidden\",\n message: \"this runner does not hold the job\",\n },\n },\n \"stale-lease\": {\n status: 403,\n body: { error: \"forbidden\", message: \"that lease is no longer current\" },\n },\n \"not-ready\": {\n status: 409,\n body: {\n error: \"not-ready\",\n message: \"the site has not sealed this job yet\",\n },\n },\n // Also a 409, and deliberately a different code: `not-ready` means keep\n // asking and this means stop. A daemon that read them as one would poll a\n // finished job until its lease ran out.\n terminal: {\n status: 409,\n body: { error: \"too-late\", message: \"this job has already finished\" },\n },\n};\nconst fail = (status: number, error: string, message: string): PlaneResult => ({\n status,\n body: { error, message },\n});\n\n/**\n * What the relay asks a control plane, as one named shape.\n *\n * Spelled out twice — here and on `RelayOptions` — until adding `siteKey`\n * made one of them wrong and the build caught it. That was luck: the two are\n * structurally compared, so a field added to the *caller's* copy alone would\n * have been accepted silently and the grant would have carried nothing.\n */\nexport type GrantAuthor = (input: {\n readonly job: ClaimedStub;\n /** The site's id in the control plane's namespace, for its policy read. */\n readonly siteId: string;\n /**\n * The same site as the stub names it — the key id the device pinned.\n *\n * Carried, never derived. This is the value that gets signed, and the one a\n * device can compare against `stub.site` without a lookup and without\n * trusting the party that routed it.\n */\n readonly siteKey: string;\n readonly purpose?: string;\n readonly owner: string;\n readonly runnerId: string;\n readonly capabilities: CapabilityMatrix;\n}) => Promise<GrantDecision> | GrantDecision;\n\nexport interface DaemonPlaneDeps {\n readonly state: RoutingStore;\n readonly projection: Projection;\n readonly now: () => number;\n readonly leaseMs: number;\n /**\n * Where pending pairing codes live — cloud_009.\n *\n * Optional so a relay that only serves pre-approved devices keeps working\n * unchanged; when absent, the device-code flow answers \"not supported\"\n * rather than pretending.\n */\n readonly pairingCodes?: PairingCodes | undefined;\n /**\n * The control plane's grant-signing public key — Amendment J.\n *\n * Handed to a daemon at pairing so it can check every grant this relay\n * later delivers. This is the one moment the relay tells a device whom to\n * believe, and it happens inside the ceremony where a human is already\n * comparing fingerprints — the alternative, trust-on-first-grant, would\n * hand the choice of authority to whoever controls delivery.\n *\n * Optional so a relay with no control plane behind it keeps working\n * unchanged: a daemon that receives none serves its owner alone through\n * this pairing.\n */\n readonly controlPlanePublic?: string | undefined;\n /**\n * Author a grant for one claimed job — Amendment J. See\n * {@link RelayOptions.authorGrant}; this plane only calls it.\n */\n readonly authorGrant?: GrantAuthor;\n /**\n * Where a human goes to approve a code — the control plane, always.\n *\n * The relay cannot approve anything: approving is looking at a fingerprint\n * while signed in, and the session that makes that meaningful lives in the\n * dashboard. So this is a URL the relay is *given*, not one it derives.\n */\n readonly verificationUrl?: string | undefined;\n /**\n * Which site this relay routes for.\n *\n * The skeleton relays for one site because that is all the freeze gate\n * needs. The production hub's multi-tenant router is the closed piece that\n * replaces this field (cloud_004 §9) — recorded here so the seam is visible\n * rather than assumed away.\n */\n}\n\nexport class DaemonPlane {\n readonly #deps: DaemonPlaneDeps;\n\n constructor(deps: DaemonPlaneDeps) {\n this.#deps = deps;\n }\n\n /**\n * Pair a device — cloud_004 §3, the key-exchange moment.\n *\n * The relay hands back **the site's** public identity, taken from the\n * consent projection, not its own. This is the sentence that makes hub mode\n * safe: the daemon pins the party that will actually seal its work, so an\n * envelope is verified against the site even though it arrived via us. A\n * relay that substituted its own identity here could inject work — and would\n * need a private key to do it, which is why it has none.\n */\n async pair(body: unknown): Promise<PlaneResult> {\n // The device-code flow — cloud_009, and the reason cloud pairing did not\n // work at all. `byollm connect` has always sent this shape; nothing on\n // this side accepted it, so every cloud user's first command failed\n // schema validation. Direct mode implemented it, the conformance kit\n // drove direct mode, and the seam between them was what nothing checked.\n const start = PairStartRequest.safeParse(body);\n if (start.success) return this.#pairStart(start.data);\n\n const poll = PairPollRequest.safeParse(body);\n if (poll.success) return this.#pairPoll(poll.data);\n\n const parsed = PairFixtureRequest.safeParse(body);\n if (!parsed.success) {\n return fail(400, \"bad-request\", \"pair request failed schema validation\");\n }\n if (!verifyPublicIdentity(parsed.data.device)) {\n return fail(400, \"bad-request\", \"the device identity is not consistent\");\n }\n\n // Every site this owner has consented to — cloud_009 §3. The relay is no\n // longer configured with one: it routes for whatever the projection\n // holds, which is the honest shape, and `RelayOptions.siteId` was always\n // a stand-in for the projection being single-site.\n //\n // The keys come from the site registry, which is their one home. They\n // used to be inlined on each consent record, which gave a site's key one\n // copy per consenting user and nothing to reconcile them against.\n const sites = this.#deps.projection.sitesFor(parsed.data.owner);\n if (sites.length === 0) {\n // CONSENT_BEFORE_ROUTE. There is no discovery path that creates one:\n // consent is a click somewhere else, and the relay only reads it.\n return fail(403, \"forbidden\", \"no consent record for this user\");\n }\n\n // The device must already be approved, by a human, in the control plane.\n //\n // It presented keys; that is an assertion, not an identity. Somebody had\n // to look at a fingerprint and say yes, and this is where that decision is\n // enforced. byollm_009's seventh finding stopped a daemon from *naming*\n // itself and this stops it from *keying* itself — otherwise the relay\n // would be the authority on who a machine is, which is precisely the role\n // a blind relay must not hold.\n //\n // Matched on the identity key rather than a claimed id: the key is what\n // the human approved and what every later signature is checked against.\n const approved = this.#deps.projection.deviceByFingerprint(\n parsed.data.device.identity,\n );\n if (!approved) {\n return fail(\n 403,\n \"forbidden\",\n \"this device has not been approved by its owner\",\n );\n }\n if (approved.owner !== parsed.data.owner) {\n // The device was approved by somebody else. Refused rather than\n // re-owned: an approval is for a person, not a key in general.\n return fail(403, \"forbidden\", \"this device belongs to another owner\");\n }\n\n // The id comes from the control plane, not from the device and not from\n // here — one authority for identity, and it is the one with the human in\n // it. The relay's own uuid minting was a stopgap for a fixture with no\n // devices in it.\n const runnerId = approved.runnerId;\n\n // No timestamp: the store stamps `lastSeenAt` from its own clock, so\n // presence and the deadlines that reason about it agree (cloud_006 §3.4).\n await this.#deps.state.seen({\n runnerId,\n owner: parsed.data.owner,\n device: parsed.data.device,\n // The fixture exchange carries no matrix — it models a consent that\n // arrived from a file, not a daemon describing itself. Empty until the\n // first heartbeat, which is seconds away and is the authority anyway.\n capabilities: [],\n withheld: [],\n });\n\n return ok({\n protocolVersion: PROTOCOL_VERSION,\n runnerId,\n /**\n * The *sites'* keys. See the note above — this is load-bearing.\n *\n * The set this owner has consented to, keyed by each site's identity\n * key id (cloud_009 §5). Paused sites are here: a paused consent keeps\n * its pin so re-consenting never costs a re-pair, and what it does not\n * do is route.\n */\n sites: Object.fromEntries(\n sites.map((record) => [keyId(record.site.identity), record.site]),\n ),\n // The key every later grant is checked against — Amendment J. Sent\n // here and nowhere else: pairing is the ceremony where a human is\n // already deciding whether to trust this upstream, so a key learned\n // here rides a decision that has been made rather than inventing one.\n ...(this.#deps.controlPlanePublic === undefined\n ? {}\n : { controlPlanePublic: this.#deps.controlPlanePublic }),\n });\n }\n\n /**\n * Mint a code, remember the keys it stands for, and send the human away.\n *\n * Nothing is decided here. The relay holds an assertion — \"this keypair\n * would like to be a machine\" — for ten minutes, and the decision happens\n * where the person is signed in.\n */\n async #pairStart(request: PairStartRequest): Promise<PlaneResult> {\n const codes = this.#deps.pairingCodes;\n const verificationUrl = this.#deps.verificationUrl;\n if (!codes || verificationUrl === undefined) {\n // Said plainly rather than answered with a schema error: a relay\n // without a code store cannot do this, and the daemon's user deserves\n // to know that rather than to read \"bad request\".\n return fail(\n 501,\n \"bad-request\",\n \"this relay does not offer device-code pairing\",\n );\n }\n if (!verifyPublicIdentity(request.device)) {\n return fail(400, \"bad-request\", \"the device identity is not consistent\");\n }\n\n const pending: PendingPairing = {\n deviceCode: newDeviceCode(),\n userCode: newUserCode(),\n device: request.device,\n label: request.daemon.label,\n platform: request.daemon.platform,\n capabilities: request.capabilities,\n expiresAt: this.#deps.now() + PAIRING_CODE_TTL_MS,\n };\n if ((await codes.put(pending)) === \"at-capacity\") {\n // The protocol already has one word for \"too much traffic, back off\",\n // and this is that. Nothing this caller did was wrong — the relay is\n // full of other people's pending pairings — but the instruction is the\n // same one `rate-limited` always carries, and a second vocabulary for\n // the same idea would be a worse answer than a slightly generous code.\n return fail(\n ERROR_STATUS[\"rate-limited\"],\n \"rate-limited\",\n PAIRING_BUSY_MESSAGE,\n );\n }\n\n return ok({\n deviceCode: pending.deviceCode,\n userCode: pending.userCode,\n verificationUrl,\n expiresAt: pending.expiresAt,\n // Two seconds: fast enough that approving feels immediate, slow enough\n // that a forgotten terminal is not a load generator.\n pollIntervalMs: 2_000,\n });\n }\n\n /**\n * Has anybody approved this keypair yet?\n *\n * The answer comes from the **projection** — the control plane's own record\n * of devices a human approved — and never from a flag set here. That is the\n * whole shape of the fence: the dashboard writes the approval to its own\n * database, the hub's projection catches up within a poll, and this notices.\n * No write crosses in either direction.\n */\n async #pairPoll(request: PairPollRequest): Promise<PlaneResult> {\n const codes = this.#deps.pairingCodes;\n if (!codes) {\n return fail(\n 501,\n \"bad-request\",\n \"this relay does not offer device-code pairing\",\n );\n }\n\n const pending = await codes.byDeviceCode(request.deviceCode);\n // Expired and never-existed answer the same way, deliberately: a poll\n // loop cannot tell them apart and does not need to, and distinguishing\n // them would let somebody test codes for existence.\n if (!pending) return ok({ status: \"expired\" });\n\n const approved = this.#deps.projection.deviceByFingerprint(\n pending.device.identity,\n );\n if (!approved) return ok({ status: \"pending\" });\n\n const sites = this.#deps.projection.sitesFor(approved.owner);\n // Approved with nothing consented is still approved: the machine exists,\n // it belongs to somebody, and the site set is a projection of consent\n // that changes on every heartbeat anyway. Refusing here would make a\n // brand-new account's first pairing fail for a reason it cannot act on\n // from a terminal.\n await this.#deps.state.seen({\n runnerId: approved.runnerId,\n owner: approved.owner,\n device: pending.device,\n // What this machine said it could run when it asked to pair, so the\n // approval screen and the machines page have an answer in the same\n // moment the device appears. The next heartbeat replaces it.\n capabilities: pending.capabilities,\n // A pairing request describes what a machine *can* run, not what it is\n // holding back — the daemon resolves defaults locally, and the first\n // heartbeat is where that answer arrives.\n withheld: [],\n });\n // Single use. The keypair is approved from here on and the code has no\n // further job; leaving it would be a second way to ask the same question.\n await codes.drop(pending.deviceCode);\n\n return ok({\n status: \"approved\",\n runnerId: approved.runnerId,\n owner: approved.owner,\n sites: Object.fromEntries(\n sites.map((record) => [keyId(record.site.identity), record.site]),\n ),\n // The key every later grant is checked against — Amendment J. Sent\n // here and nowhere else: pairing is the ceremony where a human is\n // already deciding whether to trust this upstream, so a key learned\n // here rides a decision that has been made rather than inventing one.\n ...(this.#deps.controlPlanePublic === undefined\n ? {}\n : { controlPlanePublic: this.#deps.controlPlanePublic }),\n });\n }\n\n /** Every authenticated call: signature first, then consent, then work. */\n async #authed<T>(\n input: { endpoint: string; rawBody: string; signature: unknown },\n body: unknown,\n schema: { safeParse: (v: unknown) => { success: boolean; data?: T } },\n run: (\n request: T,\n device: { runnerId: string; owner: string; device: PublicIdentity },\n ) => Promise<PlaneResult>,\n options: { allowRevoked?: boolean } = {},\n ): Promise<PlaneResult> {\n const signature = RequestSignature.safeParse(input.signature);\n if (!signature.success) {\n return fail(401, \"unauthorized\", \"this request is not signed\");\n }\n // **Presence is a cache, and a cache miss is not an answer about identity.**\n //\n // This used to refuse outright, which made a store blip into a fleet-wide\n // outage: the hub keeps presence in Valkey with no persistence and no\n // volume, so a reschedule — bin-packing on Autopilot, a node upgrade,\n // anything — dropped every record at once and every daemon alive was told\n // `this runner is not recognised` until a human re-paired it, one machine\n // at a time. The one-hour TTL was the same failure arriving more slowly.\n //\n // Who a runner is has never lived here. It lives in the projection, put\n // there by a person comparing a fingerprint, and this file already says so\n // three times over — revocation is asked of the projection rather than a\n // cached flag, for exactly this reason. So a miss is repaired from the\n // authority instead of being reported as a verdict.\n let known = await this.#deps.state.presence(signature.data.runnerId);\n let rebuilt = false;\n if (!known) {\n const approved = this.#deps.projection.deviceFor(signature.data.runnerId);\n // Still the honest refusal when the *projection* does not know it: no\n // human ever approved this machine, and no amount of signing changes\n // that.\n if (!approved) {\n return fail(401, \"unauthorized\", \"this runner is not recognised\");\n }\n known = {\n ...approved,\n lastSeenAt: this.#deps.now(),\n // Not invented. The heartbeat is the authority on what a machine can\n // run, and it is seconds away; claiming a matrix here would be this\n // file guessing about a backend it cannot see.\n capabilities: [],\n withheld: [],\n };\n rebuilt = true;\n }\n\n const failure = verifyRequest({\n identityPublic: known.device.identity,\n endpoint: input.endpoint,\n body: input.rawBody,\n signature: signature.data,\n now: this.#deps.now(),\n });\n if (failure === \"stale\") return this.#clockSkew();\n if (failure) return fail(401, \"unauthorized\", \"signature check failed\");\n\n // Written only now, and the order is the whole safety argument: a record\n // restored *before* the signature was checked would let anybody who knows\n // a runner id repopulate presence for a machine they do not hold the keys\n // to. A verified signature over this request is proof the caller holds the\n // key a human approved, which is the same proof pairing produced.\n if (rebuilt) {\n await this.#deps.state.seen({\n runnerId: known.runnerId,\n owner: known.owner,\n device: known.device,\n capabilities: [],\n // Nothing has described itself yet, so nothing is withheld — the\n // first heartbeat is the authority on both.\n withheld: [],\n });\n }\n\n // Asked of the projection, not of the cached flag.\n //\n // `known.revoked` is set by `heartbeat`, and enforcing on it made\n // revocation depend on the client calling an endpoint: a daemon that\n // simply never heartbeats would go on claiming after its consent was\n // withdrawn, forever. A well-behaved daemon beats every few seconds, which\n // is why the freeze gate's \"within one heartbeat\" demo passed and why this\n // was invisible — the guarantee held for every client that wanted it to.\n //\n // The cached flag survives as what heartbeat *reports* to the daemon. It\n // is a message, not an authority, and the authority is the projection.\n // Two copies of one value where one is a stale mirror of the other is this\n // project's most-repeated bug; here it was also an enforcement hole.\n // The whole relationship ending — cloud_008 finding 59, corrected by\n // V1-2. Per-site revocation is the *set* changing, which heartbeat\n // reports; this guard is the end of everything, and it is the only thing\n // that should refuse a call outright.\n //\n // It used to read \"nothing to serve\" as \"revoked\", which made an empty or\n // half-written projection indistinguishable from a human's decision — and\n // the daemon's answer to revocation is to delete its pairings file. One\n // bad push, every pin gone. `revokedOutright` asks for the evidence\n // instead: a revocation on record, and nothing left standing.\n const revoked = this.#deps.projection.revokedDevice(known.runnerId);\n if (revoked && options.allowRevoked !== true) {\n // Every endpoint, heartbeat included — V1-2. Heartbeat used to be\n // answerable by a revoked runner so it could be told through an empty\n // set; that inference is gone, because an empty set is also what a\n // half-written projection looks like. The refusal is the message now,\n // and it reaches even a daemon that never claims because no backend of\n // its own is running.\n return fail(403, \"revoked\", \"routing for this runner has been revoked\");\n }\n\n known.lastSeenAt = this.#deps.now();\n\n const parsed = schema.safeParse(body);\n if (!parsed.success || parsed.data === undefined) {\n return fail(400, \"bad-request\", \"request failed schema validation\");\n }\n return run(parsed.data, known);\n }\n\n /**\n * A clock too far from ours, said plainly and with the number to fix it by.\n *\n * Its own error code rather than a generic `unauthorized`, because it is the\n * one refusal a retry can never fix and an `ntpdate` always can — the same\n * reasoning `version-unsupported` already carries on the daemon side. A\n * daemon that reports this as a generic rejection sends its owner looking at\n * their network.\n *\n * `serverTime` is included so the far side can say *how far off* rather than\n * *that something is wrong*. It is not a disclosure: the heartbeat response\n * returns the same value, and so does every `Date` header.\n */\n #clockSkew(): PlaneResult {\n return clockSkewRefusal(this.#deps.now());\n }\n\n claim(\n auth: { endpoint: string; rawBody: string; signature: unknown },\n body: unknown,\n ): Promise<PlaneResult> {\n return this.#authed(auth, body, ClaimRequest, async (request, device) => {\n if (request.runnerId !== device.runnerId) {\n // The signature verified, so we know exactly who this is; the body\n // names somebody else. 403 with `forbidden` — V1-13. The site plane\n // has always answered its own version of this 403, and one wire\n // answering two ways is a difference a daemon cannot see the reason\n // for.\n return fail(403, \"forbidden\", \"runner id does not match the key\");\n }\n // One store call. The decision and its write are the store's, because a\n // caller that reads, filters and writes back cannot be made atomic once\n // the store is on a network (cloud_006 §3.2).\n const granted = await this.#deps.state.claim({\n runnerId: device.runnerId,\n owner: device.owner,\n device: device.device,\n // Every kind this device can run, from any of its services.\n //\n // This used to send only the *defaults* — the rows an unselected job\n // should reach — beside the whole menu as (kind, service) pairs. A\n // job names no service now, so there is no menu to match against and\n // no unselected case for a default to catch: a device answering a\n // kind at all is a candidate, and which of its services runs the work\n // is resolved from the person's mapping when the grant is signed.\n kinds: new Set(request.capabilities.map((c) => c.kind)),\n // The projection, collapsed to data the store can match on — a\n // predicate does not travel, and a set of (site, owner) pairs is\n // what a route is (cloud_009 §3).\n //\n // A paused consent (cloud_008 finding 48) drops its routes and\n // leaves the rest, which is the whole difference between \"we are\n // waiting for you to read something about one site\" and \"a human cut\n // you off from everything\".\n routes: this.#deps.projection.routesFor(device.owner),\n max: request.max,\n leaseMs: this.#deps.leaseMs,\n });\n\n /**\n * The grant, attached at claim and nowhere else — Amendment J.\n *\n * After the store's atomic claim, deliberately. A grant authored for a\n * job this device did not actually win would be a signed statement\n * about work somebody else is running, and the window between deciding\n * and writing is exactly where that goes wrong.\n *\n * A declined job is released here rather than sent bare. Sending it\n * would cost three round trips to reach an answer this side already\n * has, and a device refusing a job with no grant cannot tell \"the\n * control plane said no\" from \"the relay lost it\" — so it would report\n * the wrong thing.\n *\n * **Two release shapes, and the difference is not cosmetic.** A\n * permanent decline is released as `refused`, which means this job is\n * never offered to this device again — right for a person removed from\n * a team, because removal stops queued claims (hole 1).\n *\n * A transient one goes back in the queue **with a not-before**. Not\n * plainly: a plain release stays claimable by the same device, so it\n * would re-claim at once, be declined again, and spin — one control\n * plane read per turn, for a job that is not going to run there. A\n * mapping that resolved to another of the owner's machines, an\n * unfilled slot, or a store that was briefly unreachable are all\n * states the world can change, and \"ask again later\" needs a later.\n *\n * The relay does not read the reason. Branching on it here would be a\n * second implementation of a policy this process does not own.\n */\n const author = this.#deps.authorGrant;\n if (author === undefined) {\n return ok({ jobs: granted, leaseMs: this.#deps.leaseMs });\n }\n const withGrants: ClaimedStub[] = [];\n const refused: { jobId: string; leaseId: string }[] = [];\n const returned: { jobId: string; leaseId: string }[] = [];\n for (const job of granted) {\n const siteId = this.#deps.projection.siteIdForKey(job.site);\n const decision =\n siteId === null\n ? // A stub naming a site this projection cannot place. Transient\n // rather than permanent: the projection is what is behind, not\n // the job.\n { declined: { permanent: false, reason: \"unknown-site\" } }\n : await author({\n job,\n siteId,\n // Straight off the stub. A relay that computed this would be\n // choosing which site a grant says it is for, which is the\n // one thing the signature exists to take out of its hands.\n siteKey: job.site,\n // The site's own purpose, straight off the stub. A relay does\n // not interpret it — it does not hold the manifest and does\n // not hold the mapping; it carries the site's word to the one\n // party that can join them.\n ...(job.purpose === undefined ? {} : { purpose: job.purpose }),\n owner: device.owner,\n runnerId: device.runnerId,\n capabilities: request.capabilities,\n });\n if (decision.granted === undefined) {\n const lease = { jobId: job.id, leaseId: job.lease.id };\n (decision.declined.permanent ? refused : returned).push(lease);\n continue;\n }\n withGrants.push({ ...job, grant: decision.granted });\n }\n // Released rather than left leased. A job nobody may run should be back\n // in the queue for a device whose owner still may, not held by a lease\n // that has to time out first.\n if (refused.length > 0) {\n await this.#deps.state.releaseLeases({\n runnerId: device.runnerId,\n leases: refused,\n reason: \"refused\",\n });\n }\n if (returned.length > 0) {\n await this.#deps.state.releaseLeases({\n runnerId: device.runnerId,\n leases: returned,\n retryAfter: this.#deps.now() + RETRY_AFTER_MS,\n });\n }\n\n return ok({ jobs: withGrants, leaseMs: this.#deps.leaseMs });\n });\n }\n\n /**\n * Hand over the sealed payload, if the site has left one.\n *\n * The one endpoint whose behaviour differs from a direct site's, and the\n * difference is the whole design: a direct site seals here, on demand,\n * because it holds the keys. The relay waits. A `409` means \"claimed, not\n * yet sealed\" — a daemon should retry, not treat it as a refusal, because\n * the job is still legitimately theirs until the lease or the\n * awaiting-payload clock says otherwise.\n */\n fetch(\n auth: { endpoint: string; rawBody: string; signature: unknown },\n body: unknown,\n ): Promise<PlaneResult> {\n return this.#authed(auth, body, FetchRequest, async (request, device) => {\n const taken = await this.#deps.state.takePayload({\n jobId: request.jobId,\n runnerId: device.runnerId,\n leaseId: request.leaseId,\n });\n if (\"refused\" in taken) return REFUSALS[taken.refused];\n return ok({ envelope: taken.envelope });\n });\n }\n\n /**\n * Take a sealed result.\n *\n * The relay stores ciphertext and records the disposition so it can stop\n * dispatching. It cannot check the two against each other — that requires\n * opening the envelope, which is the site's job and the site's key. This is\n * the asymmetry byollm_009 §6.1 describes: the hint is actionable here and\n * only verifiable there.\n */\n result(\n auth: { endpoint: string; rawBody: string; signature: unknown },\n body: unknown,\n ): Promise<PlaneResult> {\n return this.#authed(auth, body, ResultRequest, async (request, device) => {\n // The same ceiling the site plane applies, on the way back — ratified\n // 2026-08-28. Both directions count against the pool and both are\n // refused by one limit; a rail that guarded only the inbound half would\n // be a relay a device could still fill.\n const bytes = envelopeBytes(request.envelope);\n if (bytes > MAX_ENVELOPE_BYTES) return tooLargeRefusal(bytes);\n\n const recorded = await this.#deps.state.complete({\n jobId: request.jobId,\n runnerId: device.runnerId,\n leaseId: request.leaseId,\n envelope: request.envelope,\n disposition: request.disposition,\n });\n if (\"refused\" in recorded) return REFUSALS[recorded.refused];\n return ok(recorded);\n });\n }\n\n heartbeat(\n auth: { endpoint: string; rawBody: string; signature: unknown },\n body: unknown,\n ): Promise<PlaneResult> {\n return this.#authed(\n auth,\n body,\n HeartbeatRequest,\n async (request, device) => {\n const now = this.#deps.now();\n await this.#deps.state.sweep();\n\n // **Presence is recorded here, and it was not before.**\n //\n // `seen()` was called from the two pairing paths and nowhere else, so\n // `lastSeenAt` was written once when a machine paired and never moved\n // again. Everything downstream read it as liveness — the debug page's\n // who-is-online, and the `/devices` endpoint about to serve a\n // machines page — and it was reporting the pairing time under the\n // name \"last seen\". A field that is quietly a different fact is worse\n // than a missing one.\n //\n // The matrix rides along because it is the same fact on the same\n // schedule: this is the request that re-sends it, and a machine that\n // stopped heartbeating has not stopped being capable, it has stopped\n // being reachable. Recorded before the no-sites branch below, because\n // a machine with nothing consented is still online and still has\n // something to show.\n await this.#deps.state.seen({\n runnerId: device.runnerId,\n owner: device.owner,\n device: device.device,\n capabilities: request.capabilities,\n // Arrives on the same beat as the matrix and goes stale with it.\n // A daemon that resolves a contended kind stops sending it here,\n // which is what retires the owner's prompt to choose.\n withheld: request.withheld,\n });\n\n // Revocation is a fixture edit, and this is where the daemon learns\n // of it — within one heartbeat, which is what the freeze gate times.\n // The set, not a boolean — cloud_008 finding 59. A site that leaves\n // it is revoked for that site; the daemon drops that pin and keeps\n // the rest. An empty set is what `revoked: true` used to mean, and\n // the daemon reads it for itself rather than being told twice.\n const pinned = this.#deps.projection.sitesFor(device.owner);\n const sites = Object.fromEntries(\n pinned.map((record) => [keyId(record.site.identity), record.site]),\n );\n // How each of those keys can be traced back to one the daemon already\n // holds — byollm_009 Amendment C. Composed here rather than folded\n // into `sites` because `sites` is the one statement of which key is\n // current and this is evidence about how it got there; the relay\n // distributes both and can mint neither.\n //\n // Only for sites that have actually rotated, so the field is absent\n // for every site today and a daemon that has never seen a rotation\n // never parses one.\n const successions = Object.fromEntries(\n pinned\n .filter((record) => (record.succeeds?.length ?? 0) > 0)\n .map((record) => [\n keyId(record.site.identity),\n {\n succeeds: record.succeeds ?? [],\n ...(record.retiringUntil === undefined\n ? {}\n : { retiringUntil: record.retiringUntil }),\n },\n ]),\n );\n const rotations =\n Object.keys(successions).length > 0 ? { successions } : {};\n // A subset: paused sites keep their pin and route nothing, so the\n // daemon can name what the user has to go and read.\n\n const awaitingConsent = pinned\n .filter(\n (record) =>\n !this.#deps.projection.mayRouteFor(device.owner, record.siteId),\n )\n .map((record) => keyId(record.site.identity));\n\n if (pinned.length === 0) {\n // Nothing left to serve. The daemon abandons the queue rather than\n // finishing it, exactly as the direct plane's revoked branch does:\n // a daemon cannot see which upstream it is talking to, and the rule\n // must not depend on that.\n return ok({\n sites,\n ...rotations,\n awaitingConsent,\n cancel: [],\n lost: request.activeLeases.map((lease) => ({\n jobId: lease.jobId,\n leaseId: lease.leaseId,\n })),\n serverTime: now,\n });\n }\n\n // Renewal and loss, from one read — cloud_008 §0.6. This used to\n // return `leases: []` unconditionally, which told a working daemon\n // every few seconds that nothing it held had been renewed while the\n // sweep requeued its work at `leaseMs`. Any job slower than a lease\n // was handed to a second device mid-flight.\n //\n // The renewal is the fix; reporting it back was not. §1.4b took that\n // field off the wire — no daemon ever read it, and `lost` answers the\n // same question in the direction a daemon can act on.\n // What the site withdrew — cloud_008 §2.2. This was the literal\n // `cancel: []`, so a site could not stop a job it had already\n // cancelled: the device went on running work whose result nobody\n // would accept, on somebody's own machine and at their expense.\n const cancel = await this.#deps.state.cancelRequests(device.runnerId);\n\n const { lost } = await this.#deps.state.renewLeases({\n runnerId: device.runnerId,\n leases: request.activeLeases,\n leaseMs: this.#deps.leaseMs,\n });\n\n return ok({\n sites,\n ...rotations,\n awaitingConsent,\n cancel,\n lost,\n serverTime: now,\n });\n },\n );\n }\n\n release(\n auth: { endpoint: string; rawBody: string; signature: unknown },\n body: unknown,\n ): Promise<PlaneResult> {\n return this.#authed(auth, body, ReleaseRequest, async (request, device) => {\n const released = await this.#deps.state.releaseLeases({\n runnerId: device.runnerId,\n leases: request.leases,\n // Was dropped here — cloud_008 §2.1. `reason` is on the wire, the\n // schema's own docstring says an upstream MUST record `refused`, and\n // this handler read every other field.\n reason: request.reason,\n });\n return ok({ released });\n });\n }\n}\n\n/**\n * Pairing, as the skeleton does it.\n *\n * Not the public `PairRequest`: that models a device-code exchange with a\n * human at a browser, and the skeleton's consent arrives from a fixture\n * instead (cloud_004 §14). The daemon-visible *outcome* is identical — a\n * runner id and the site's pinned public identity — so nothing downstream can\n * tell the difference, which is what makes this substitution honest rather\n * than a shortcut around the consent MUST.\n */\nconst PairFixtureRequest = z\n .object({\n protocolVersion: z.literal(PROTOCOL_VERSION),\n owner: z.string().min(1),\n device: PublicIdentity,\n })\n .strict();\n\n/** Exported so the debug page can name a device the way a human would. */\nexport const fingerprintOf = (identity: PublicIdentity): string =>\n keyId(identity.identity);\n","import { randomBytes } from \"node:crypto\";\nimport type { CapabilityMatrix, PublicIdentity } from \"@byollm/protocol\";\n\n/**\n * Pending pairing codes — cloud_009, the cloud-pairing flow.\n *\n * `byollm connect` speaks the device-code flow: ask for a code, show it, poll\n * while a human approves it in a browser. A relay had no way to hold that\n * pending state, so cloud pairing was never implemented — the hub accepted\n * only the shape where the device is *already* approved, and nothing in the\n * control plane created device rows at all. Every test passed because they\n * drive direct mode or seed the row with a service key: the checks proved the\n * parts and never the seam.\n *\n * ## Why the relay holds the code, and the control plane holds the decision\n *\n * The code is a short-lived handle on an *assertion* — \"this keypair would\n * like to be a machine\" — and the relay is allowed to hold assertions. The\n * approval is a human looking at a fingerprint, which belongs to the control\n * plane where that human is signed in.\n *\n * So nothing here approves anything. The daemon's poll asks whether the\n * control plane's projection now contains this device as approved, and the\n * answer comes from the projection rather than from a flag somebody set here.\n * That is what keeps the fence intact in both directions: the hub never\n * writes to the control plane, and the control plane never writes to the hub.\n *\n * ## What a code is worth on its own\n *\n * Nothing. Holding a device code lets you ask \"has anyone approved this\n * keypair yet\", and the answer is only ever yes for a keypair whose owner\n * approved it by eye. Stolen mid-flight it grants no access, which is why it\n * can be a URL-safe string a person reads aloud rather than a credential.\n */\n\n/** What the relay remembers between `start` and `poll`. */\nexport interface PendingPairing {\n /** The secret the daemon polls with. Never shown to a human. */\n readonly deviceCode: string;\n /** The short code a person reads and types into the dashboard. */\n readonly userCode: string;\n /** The keys the daemon presented. What a human is about to approve. */\n readonly device: PublicIdentity;\n /**\n * What the machine said it can run, as advertised when it asked to pair.\n *\n * Held so the approval screen can show a person what they are approving,\n * and so presence has an answer the moment the device appears rather than\n * one heartbeat later. It is a claim, like everything else in this record —\n * the heartbeat is the authority and replaces it within seconds.\n */\n readonly capabilities: CapabilityMatrix;\n /** Label the daemon offered, for the approval screen. */\n readonly label: string;\n readonly platform: string;\n /** Epoch ms. After this the code is gone, approved or not. */\n readonly expiresAt: number;\n}\n\n/**\n * What happened when a code was offered for storage.\n *\n * `put` can refuse, and the reason it can is the whole of the rate-limit\n * story on this surface: **anybody can ask to pair.** That is not a bug — a\n * machine with no pairing has no credential to present — but it means a\n * stranger with a script can mint pending codes in a loop, and each one\n * occupies memory in a shared store for ten minutes. Without a ceiling the\n * only limit is somebody's patience.\n *\n * So the store has a capacity and says so, and the daemon is told to try\n * again shortly rather than given a code that crowds out a real one. A cap is\n * a blunt instrument — under a flood, a person pairing a laptop is refused\n * alongside the attacker — but a refusal that resolves in ten minutes is a\n * better failure than a hub that stops routing. Per-IP limits belong at the\n * edge, where the IP actually is.\n */\nexport type PutResult = \"stored\" | \"at-capacity\";\n\n/**\n * What a caller is told when pairings are being refused for load.\n *\n * Exported because it is said in two places by two different limits. This\n * package says it when the store is at capacity; a deployment that adds a\n * per-IP budget in front (the hub does — cloud_014) says it when one source\n * has spent its share. **One sentence for one situation, whichever limit\n * produced it**: the person reading it in a terminal is told to try again\n * shortly, and which of the two bit is not a distinction they can act on.\n *\n * It lived inline here and the hub kept a copy, which is the one-value-two-\n * names defect this codebase keeps finding — and the copy that drifts would\n * drift silently, because both sentences would be plausible.\n */\nexport const PAIRING_BUSY_MESSAGE =\n \"too many pairings are in progress right now — try again in a few minutes\";\n\nexport interface PairingCodes {\n put(pending: PendingPairing): Promise<PutResult>;\n /** By the secret the daemon holds. */\n byDeviceCode(deviceCode: string): Promise<PendingPairing | undefined>;\n /** By the short code a human typed. */\n byUserCode(userCode: string): Promise<PendingPairing | undefined>;\n /** After a successful pairing, so a code is single-use. */\n drop(deviceCode: string): Promise<void>;\n}\n\n/**\n * Codes a person reads aloud, from an alphabet that survives being read aloud.\n *\n * Crockford's, minus the letters that become other letters over a phone: no\n * I/L/O/U. Eight characters in two groups — enough entropy that guessing is\n * not a strategy against a code that lives for ten minutes and grants nothing\n * on its own.\n */\nconst HUMAN_ALPHABET = \"0123456789ABCDEFGHJKMNPQRSTVWXYZ\";\n\nexport function newUserCode(): string {\n // `Array.from` rather than an indexed loop, so there is no index the\n // compiler cannot prove is in range and therefore no non-null assertion —\n // this codebase forbids them, and a `!` here would be one written for the\n // convenience of a loop rather than because anything was known.\n const groups = Array.from(randomBytes(8), (byte) =>\n HUMAN_ALPHABET.charAt(byte % HUMAN_ALPHABET.length),\n );\n return `${groups.slice(0, 4).join(\"\")}-${groups.slice(4).join(\"\")}`;\n}\n\n/** The secret half. Long and URL-safe; never shown to anybody. */\nexport const newDeviceCode = (): string =>\n randomBytes(32).toString(\"base64url\");\n\n/** How long a person has to walk to their browser and type eight characters. */\nexport const PAIRING_CODE_TTL_MS = 10 * 60 * 1000;\n\n/**\n * How many pairings may be in flight at once, across a whole relay.\n *\n * Sized against reality rather than fear: a pairing takes under a minute of\n * human attention, so five hundred outstanding at the same instant is a\n * number this product will not reach honestly for a long time — and one an\n * attacker reaches in a second. Small enough to bound the store, large enough\n * that nobody legitimate meets it.\n */\nexport const MAX_OUTSTANDING_PAIRINGS = 500;\n\n/**\n * The in-memory implementation, for the reference relay and its tests.\n *\n * The hub replaces it with one backed by Valkey, because a hub is two\n * replicas and a code minted on one must be pollable on the other — the same\n * reason its routing store is not a `Map`.\n */\nexport class MemoryPairingCodes implements PairingCodes {\n readonly #byDevice = new Map<string, PendingPairing>();\n readonly #now: () => number;\n readonly #capacity: number;\n\n constructor(\n now: () => number = Date.now,\n capacity: number = MAX_OUTSTANDING_PAIRINGS,\n ) {\n this.#now = now;\n this.#capacity = capacity;\n }\n\n #live(pending: PendingPairing | undefined): PendingPairing | undefined {\n if (!pending) return undefined;\n // Expiry is checked on read rather than swept: a code nobody asks about\n // costs nothing, and a sweep is a second place for the deadline to live.\n return pending.expiresAt > this.#now() ? pending : undefined;\n }\n\n put(pending: PendingPairing): Promise<PutResult> {\n // Expired entries are dropped before counting. Without this the cap would\n // latch: ten minutes of traffic would fill it and nothing would ever\n // pair again, which is a worse outage than the flood it defends against.\n const now = this.#now();\n for (const [code, held] of this.#byDevice) {\n if (held.expiresAt <= now) this.#byDevice.delete(code);\n }\n\n // One outstanding code per keypair. A daemon that restarts pairing —\n // a fat-fingered code, a second terminal — replaces its own pending\n // request instead of adding to the pile, so the code on screen is always\n // the live one. An attacker must mint a fresh keypair per code, which is\n // cheap; the ceiling below is what actually bounds them.\n const fingerprint = pending.device.identity;\n for (const [code, held] of this.#byDevice) {\n if (held.device.identity === fingerprint) this.#byDevice.delete(code);\n }\n\n if (this.#byDevice.size >= this.#capacity)\n return Promise.resolve(\"at-capacity\");\n\n this.#byDevice.set(pending.deviceCode, pending);\n return Promise.resolve(\"stored\");\n }\n\n byDeviceCode(deviceCode: string): Promise<PendingPairing | undefined> {\n return Promise.resolve(this.#live(this.#byDevice.get(deviceCode)));\n }\n\n byUserCode(userCode: string): Promise<PendingPairing | undefined> {\n const wanted = userCode.trim().toUpperCase();\n for (const pending of this.#byDevice.values()) {\n if (pending.userCode === wanted)\n return Promise.resolve(this.#live(pending));\n }\n return Promise.resolve(undefined);\n }\n\n drop(deviceCode: string): Promise<void> {\n this.#byDevice.delete(deviceCode);\n return Promise.resolve();\n }\n}\n","import {\n ERROR_STATUS,\n MAX_CLOCK_SKEW_MS,\n MAX_ENVELOPE_BYTES,\n} from \"@byollm/protocol\";\nimport type { PlaneResult } from \"./daemon-plane.js\";\n\n/**\n * The clock-skew refusal, in one place — cloud_008 §1.4, finding 17.\n *\n * Both planes verify a signature and both can fail it for a reason that is not\n * the key: a timestamp too far from ours to judge freshness. Only the daemon\n * plane said so. The site plane collapsed every `SignatureFailure` into\n * \"signature check failed\", so a site whose clock had drifted was told its\n * signature was wrong and sent to look at its keys — while `verifySiteRequest`\n * had already distinguished `stale` and thrown the distinction away.\n *\n * The remedy is the whole reason this is a separate code. \"Your key is wrong\"\n * and \"your clock is wrong\" are different problems with different fixes, and\n * only the server can tell them apart, because only the server holds the other\n * clock.\n *\n * One builder rather than one per plane, because two copies of a refusal is\n * how this one came to exist: the daemon plane's was written first and the\n * site plane's was written to a different standard three files away.\n */\nexport function clockSkewRefusal(now: number): PlaneResult {\n return {\n status: ERROR_STATUS[\"clock-skew\"],\n body: {\n error: \"clock-skew\",\n message:\n \"this request's timestamp is too far from the server's clock; \" +\n \"check the machine's time and try again\",\n // So the far side can say *how far off* rather than *that something is\n // wrong*. Not a disclosure: the heartbeat response returns the same\n // value, and so does every `Date` header.\n serverTime: now,\n maxSkewMs: MAX_CLOCK_SKEW_MS,\n },\n };\n}\n\n/**\n * The envelope is larger than the relay will hold — ratified 2026-08-28.\n *\n * A **relay-memory safety rail**, the same ceiling on every tier, and the same\n * refusal in both directions: a site attaching a payload and a device\n * returning a result reach it by different routes and hit one limit.\n *\n * ## Refused before acceptance, and nothing is written down\n *\n * That is the whole implementation, and it is why the cap needed no schema.\n * The size is known for the length of this check and is then gone — recording\n * a size in order to enforce a limit against it would be exactly the per-job\n * byte figure the metering ruling exists to not keep.\n *\n * ## `bad-request`, not a code of its own\n *\n * 413 is the semantically tidy status and a new `WireErrorCode` member is the\n * tidy code, and neither is worth what it costs here. Error codes are a\n * published enumeration that daemons and sites parse; a member added today\n * reaches a client shipped last month as an unrecognised value, and the\n * refusal it renders would be worse than the plain one. An over-size request\n * *is* a bad request, callers act on the code rather than the status, and the\n * right behaviour on both — do not retry, make it smaller — is the same.\n *\n * The message carries the limit and the remedy, because a ceiling somebody\n * cannot see the height of is a ceiling they hit twice.\n */\nexport function tooLargeRefusal(bytes: number): PlaneResult {\n /**\n * Rounded **up**, and only ever up.\n *\n * `toFixed` rounds to nearest, so a message one byte over the line printed\n * as \"this message is 10.0 MB and the limit is 10.0 MB\" — a refusal that\n * reads as a contradiction, given to somebody who now has no idea what to\n * change. Found by rendering the sentence rather than by asserting on it.\n *\n * Ceiling is also the honest direction. Understating how far over a message\n * is would send somebody to trim a hundred bytes off something that needs to\n * lose a megabyte; overstating by a tenth costs them nothing.\n */\n const mb = (n: number) =>\n `${(Math.ceil((n / (1024 * 1024)) * 10) / 10).toFixed(1)} MB`;\n const limit = `${(MAX_ENVELOPE_BYTES / (1024 * 1024)).toFixed(1)} MB`;\n return {\n status: ERROR_STATUS[\"bad-request\"],\n body: {\n error: \"bad-request\",\n message:\n `this message is ${mb(bytes)} and the limit is ` +\n `${limit} — every plan has the same ceiling, and it ` +\n \"is a limit on one message rather than on how many you send. Split \" +\n \"the work into smaller jobs and send them separately.\",\n },\n };\n}\n","import { fingerprintOf } from \"./daemon-plane.js\";\nimport type { RoutedJob } from \"./state.js\";\nimport type { RoutingStore } from \"./store.js\";\n\n/**\n * The debug page — cloud_004 §10.\n *\n * It exists because watching a stub get claimed beats reading store rows, and\n * it earns its keep from the first routed job rather than being a thing\n * someone builds later when routing is already hard to follow.\n *\n * One screen, no build step, no dependencies. It renders from the relay's own\n * state, so it cannot show anything the relay does not actually know — which\n * makes it an honest demonstration of blindness as well as a debugging tool.\n * There is no view here that could show a prompt, because there is no prompt\n * to show.\n */\n\nconst escape = (value: string): string =>\n value.replace(\n /[&<>\"]/g,\n (c) => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\" })[c] ?? c,\n );\n\nconst STATE_COLOUR: Record<string, string> = {\n queued: \"#8a8a8a\",\n \"awaiting-payload\": \"#c98a00\",\n ready: \"#0a7\",\n running: \"#06c\",\n done: \"#444\",\n};\n\nfunction jobRow(job: RoutedJob, now: number): string {\n const claimed = job.claimedBy;\n const waiting =\n job.state === \"awaiting-payload\" && job.awaitingUntil !== undefined\n ? `${String(Math.max(0, job.awaitingUntil - now))}ms left`\n : \"\";\n return `<tr>\n <td><code>${escape(job.id)}</code></td>\n <td>${escape(job.stub.kind)}</td>\n <td>${escape(job.stub.owner)}</td>\n <td>${escape(job.stub.audience)}</td>\n <td>${escape(job.stub.sizeClass)}</td>\n <td>${job.stub.streaming ? \"yes\" : \"no\"}</td>\n <td><b style=\"color:${STATE_COLOUR[job.state] ?? \"#000\"}\">${escape(job.state)}</b> <span class=\"dim\">${escape(waiting)}</span></td>\n <td>${claimed ? `<code>${escape(fingerprintOf(claimed.device))}</code>` : \"<span class='dim'>—</span>\"}</td>\n <td>${job.payload ? \"sealed\" : \"<span class='dim'>—</span>\"}</td>\n <td>${job.result ? escape(job.disposition ?? \"?\") : \"<span class='dim'>—</span>\"}</td>\n </tr>`;\n}\n\nexport async function debugPage(\n state: RoutingStore,\n now: number,\n /**\n * Asked whether each device's owner still consents — cloud_008 §2.3.\n *\n * The page used to read a `revoked` boolean off presence. That flag was a\n * stored copy of a fact the projection owns, and it is gone; the page asks\n * the authority instead, which is also the only thing that stays correct\n * when one daemon serves several sites.\n */\n routesFor?: { siteId: string; consents: (owner: string) => boolean },\n): Promise<string> {\n const jobs = await state.jobs();\n const devices = await state.everyone();\n\n return `<!doctype html>\n<html><head><meta charset=\"utf-8\"><title>byollm relay — debug</title>\n<meta http-equiv=\"refresh\" content=\"1\">\n<style>\n body{font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;margin:24px;color:#111;background:#fff}\n h1{font-size:15px;margin:0 0 4px} h2{font-size:13px;margin:24px 0 6px}\n table{border-collapse:collapse;width:100%;margin-top:4px}\n th,td{text-align:left;padding:4px 8px;border-bottom:1px solid #eee;vertical-align:top}\n th{font-weight:600;color:#666;border-bottom:1px solid #ccc}\n code{background:#f5f5f5;padding:1px 4px;border-radius:3px}\n .dim{color:#aaa} .note{color:#666;max-width:70ch;margin:8px 0 0}\n @media(prefers-color-scheme:dark){\n body{background:#111;color:#eee} th{color:#999;border-color:#333}\n td{border-color:#222} code{background:#1c1c1c} .note{color:#999}\n }\n</style></head><body>\n<h1>byollm relay — debug</h1>\n<p class=\"note\">Everything this relay knows, which is everything on this page.\nThere is no prompt or result text here because it holds none: payloads and\nresults are sealed to their endpoints and pass through as ciphertext.</p>\n\n<h2>Routed jobs (${String(jobs.length)})</h2>\n<table>\n<tr><th>job</th><th>kind</th><th>owner</th><th>audience</th><th>size</th>\n <th>stream</th><th>state</th><th>claimed by</th><th>payload</th><th>result</th></tr>\n${jobs.length ? jobs.map((j) => jobRow(j, now)).join(\"\\n\") : `<tr><td colspan=\"10\" class=\"dim\">nothing routed yet</td></tr>`}\n</table>\n\n<h2>Presence (${String(devices.length)})</h2>\n<table>\n<tr><th>runner</th><th>owner</th><th>fingerprint</th><th>last seen</th><th>routing</th></tr>\n${\n devices.length\n ? devices\n .map(\n (d) => `<tr>\n <td><code>${escape(d.runnerId)}</code></td>\n <td>${escape(d.owner)}</td>\n <td><code>${escape(fingerprintOf(d.device))}</code></td>\n <td>${String(Math.max(0, now - d.lastSeenAt))}ms ago</td>\n <td>${\n routesFor && !routesFor.consents(d.owner)\n ? \"<b style='color:#c00'>no consent</b>\"\n : \"active\"\n }</td>\n</tr>`,\n )\n .join(\"\\n\")\n : `<tr><td colspan=\"5\" class=\"dim\">no devices connected</td></tr>`\n}\n</table>\n</body></html>`;\n}\n","import {\n MAX_SUCCESSION_CHAIN,\n Succession,\n PublicIdentity,\n keyId as keyIdOf,\n} from \"@byollm/protocol\";\nimport { routeKey } from \"./state.js\";\nimport { z } from \"zod\";\n\n/**\n * What the relay is told about the world — cloud_004 §14.\n *\n * The relay decides nothing about who may talk to whom. It is handed a\n * projection of the control plane and routes according to it. Today that\n * projection is a file; later it is whatever the suite serves. Either way the\n * relay's own state is derived and disposable: delete it and the fixture\n * rebuilds it.\n *\n * ## This shape is a contract, not a test convenience\n *\n * cloud_004 §14 flags it and the flag is worth repeating here, where someone\n * will be tempted to add a field: **this is the projection contract.** The\n * first real control plane will be written to produce whatever this says, and\n * a field added carelessly now is a field the suite must produce forever.\n *\n * So two rules for anything added later:\n *\n * 1. **It must be something a control plane can actually know.** The relay\n * cannot be given facts that only a daemon or only a site holds — that is\n * how a blind relay stops being blind, one convenient field at a time.\n * 2. **It must be a decision, not a derivation.** Consent is a decision.\n * Presence is not: the relay learns that from heartbeats. Anything the\n * relay can observe does not belong in the projection.\n *\n * ## What is deliberately absent\n *\n * No private keys, of any party, ever. The relay holds public keys so it can\n * *verify* signatures and *tell a site who to seal to*. It holds no key that\n * can open anything, and {@link RelayFixture} has no field where one could be\n * put — `RELAY_BLIND` as a type, not as a promise.\n */\n\n/**\n * A site the control plane registered and domain-verified — cloud_004 §5.\n *\n * **The one authority for a site's public identity.** It used to be inlined on\n * every consent record, which meant a site's key had as many homes as it had\n * users and nothing checked they agreed — the exact shape this project has now\n * found in a version constant, a clock read, an envelope deadline, a reseal\n * implementation, a package list and a docs page. Consents now reference a\n * site by id and the key is looked up here.\n *\n * The relay needs it for two things it cannot do without:\n *\n * 1. **Telling a daemon who to pin** at pairing — the key that makes relayed\n * work unforgeable, since the relay holds no key that could produce it.\n * 2. **Authenticating the site plane.** A site calls a relay the way a daemon\n * does, signing with this identity, and this is the key those signatures\n * are checked against.\n */\nexport const SiteRecord = z\n .object({\n /** How the control plane names the site. */\n siteId: z.string().min(1),\n /**\n * The site's public identity.\n *\n * The relay distributes it and cannot use it: an identity key verifies\n * signatures and seals nothing. This is the key-exchange half of consent\n * (cloud_004 §3), and both endpoints pin what they receive.\n */\n site: PublicIdentity,\n /**\n * How this site's current key can be traced back to one a daemon holds —\n * byollm_009 Amendment C, ordered oldest last.\n *\n * A list rather than one predecessor because a daemon offline across two\n * rotations holds K1 and meets K3: with a single predecessor it could not\n * verify K3 without K2's record, so it would have to re-pair over\n * housekeeping it did not ask for. The proofs are small, self-verifying,\n * and kept indefinitely for the same reason.\n *\n * **The relay distributes these and cannot mint one.** Each is a signature\n * by a key it does not hold, which is what lets rotation be automatic\n * without becoming the substitution `SITES_LOCALLY_APPROVED` refuses.\n */\n succeeds: z.array(Succession).max(MAX_SUCCESSION_CHAIN).optional(),\n /**\n * Until when the retired key may still sign work — epoch ms.\n *\n * Absent on a site that has never rotated. The daemon holds its own clock\n * against this for the reason it holds its own allowlist: a projection\n * that could extend the window indefinitely would be a two-key site\n * forever, decided by the party the design does not trust.\n */\n retiringUntil: z.number().int().positive().optional(),\n })\n .strict();\nexport type SiteRecord = z.infer<typeof SiteRecord>;\n\n/**\n * A user's decision to let one site use their compute — cloud_004 §3.\n *\n * `CONSENT_BEFORE_ROUTE`: with no record here, the relay refuses to route,\n * and there is no discovery path that creates one. Consent is a click in the\n * control plane; the relay only ever reads the result.\n */\nexport const ConsentRecord = z\n .object({\n /** The user, as the control plane identifies them. */\n owner: z.string().min(1),\n /** Which site this consent is for. Scoped: consent is never global. */\n siteId: z.string().min(1),\n /**\n * The consent stands, and nothing routes under it — cloud_008 finding 48.\n *\n * The disclosure this user agreed to no longer describes their\n * arrangements: they read that their prompts stay on machines they own,\n * and they have since been added to a roster whose owner can read them.\n * Until they have been shown the other sentence and clicked, their work\n * does not move.\n *\n * **A third state, because the two we had are both wrong here.** Dropping\n * the consent makes `consentFor` return null, and the daemon plane reads\n * exactly that as revoked: heartbeat answers `revoked: true` with `lost:\n * all`, and the daemon prints \"this runner was revoked\" and *deletes its\n * pairing*. So a user whose team changed a setting would be told a human\n * cut them off, lose their pinned keys, and have to re-run `byollm\n * connect` after re-consenting. Under cloud_009 that is worse still: the\n * pairing is keyed by origin, so one stale consent would drop the pairing\n * for every other site reached through that hub.\n *\n * Reporting it as revoked is the same falsehood finding 48 exists to\n * delete, told one layer down. So the record stays, the relationship\n * stays, and the routing stops.\n */\n paused: z.boolean().default(false),\n })\n .strict();\nexport type ConsentRecord = z.infer<typeof ConsentRecord>;\n\n/**\n * A named group whose members may use a shared machine — cloud_004 §11.\n *\n * The roster lives here and **never reaches a site**. A site learns whether a\n * consenting user has reachable compute; it never learns who else is on the\n * roster. That is `ROSTERS_NEVER_LEAK` in cloud_004 §11.4, and the reason\n * this type has no outbound representation anywhere in this package.\n */\nexport const RosterRecord = z\n .object({\n /** Stable id for the group, used only inside the relay. */\n id: z.string().min(1),\n /** Who owns the shared compute. */\n owner: z.string().min(1),\n /** Members who may route to it. The owner is not implicitly a member. */\n members: z.array(z.string().min(1)),\n })\n .strict();\nexport type RosterRecord = z.infer<typeof RosterRecord>;\n\n/**\n * A device its owner has approved — cloud_005 §7.1.\n *\n * The relay refuses a device that is not here, and that refusal is the point.\n * byollm_009's seventh finding stopped a daemon from *naming* itself; this\n * stops it from *keying* itself. A device that presents keys nobody approved\n * is a device whose owner never saw a fingerprint, and pairing it would make\n * the relay the authority on identity — which is exactly what it must not be.\n *\n * The three-party shape consent already has, applied to identity: the device\n * asserts, a human confirms in the control plane, the relay checks.\n */\nexport const DeviceRecord = z\n .object({\n /** Who approved it. */\n owner: z.string().min(1),\n /** The id the control plane assigned — the device does not choose it. */\n runnerId: z.string().min(1),\n /** The keys a human compared a fingerprint of before approving. */\n device: PublicIdentity,\n /**\n * Whether this device — this one — has been revoked.\n *\n * Optional because a control plane that predates it says nothing, and\n * saying nothing must read as \"not revoked\": the alternative is a missing\n * field stopping every device on the fleet, which is the failure mode this\n * whole entry exists to end.\n *\n * Device-scoped by ruling (2026-09-03). Revocation used to be answered\n * from the owner's route-revocation list, so an account with any\n * revocation on record and no live site consents refused **every** device\n * it owned — including one paired thirty seconds earlier, whose daemon\n * then deleted its own pairings file. Revoking an experiment and pairing a\n * replacement, which is the ordinary first hour, killed the replacement.\n */\n revoked: z.boolean().optional(),\n })\n .strict();\nexport type DeviceRecord = z.infer<typeof DeviceRecord>;\n\n/** A revoked route, named by its parts. */\nexport const RevocationRecord = z\n .object({ owner: z.string().min(1), siteId: z.string().min(1) })\n .strict();\nexport type RevocationRecord = z.infer<typeof RevocationRecord>;\n\nexport const RelayFixture = z\n .object({\n /** Registered sites, by id. A consent for a site absent here routes not. */\n sites: z.array(SiteRecord).default([]),\n consents: z.array(ConsentRecord),\n devices: z.array(DeviceRecord).default([]),\n rosters: z.array(RosterRecord).default([]),\n /**\n * Routes that were revoked, as structured pairs.\n *\n * A separate list rather than deleting the consent record, because the\n * freeze gate needs revocation to be an observable *event* rather than an\n * absence — \"the row is gone\" and \"the row was revoked\" are different\n * answers to someone debugging why routing stopped.\n *\n * `{owner, siteId}` and never the composite string `\"owner:siteId\"`. A\n * composite key is a parser waiting to meet an id containing its\n * separator, which is the lesson the composite lease ids taught against\n * Postgres — applied here before it became a contract.\n */\n revoked: z.array(RevocationRecord).default([]),\n })\n .strict();\nexport type RelayFixture = z.infer<typeof RelayFixture>;\n\n/** An empty projection: nothing consented, so nothing routes. */\nexport const EMPTY_FIXTURE: RelayFixture = {\n sites: [],\n consents: [],\n devices: [],\n rosters: [],\n revoked: [],\n};\n\n/**\n * The relay's read-only view of the projection.\n *\n * Deliberately a handful of questions rather than the raw fixture: every\n * caller asking \"may this route?\" through one method is what makes\n * `CONSENT_BEFORE_ROUTE` reviewable, and it leaves room for the projection to\n * become a service without touching a single call site.\n */\nexport class Projection {\n #fixture: RelayFixture;\n\n constructor(fixture: RelayFixture = EMPTY_FIXTURE) {\n this.#fixture = RelayFixture.parse(fixture);\n }\n\n /** Replace the projection wholesale — the control plane pushed a new one. */\n replace(fixture: RelayFixture): void {\n this.#fixture = RelayFixture.parse(fixture);\n }\n\n /**\n * The site this id names, if the control plane registered it.\n *\n * The only source of a site's public identity in this package. Everything\n * that pins, verifies or seals to a site starts here.\n */\n siteFor(siteId: string): SiteRecord | null {\n return this.#fixture.sites.find((s) => s.siteId === siteId) ?? null;\n }\n\n /**\n * The device this runner id names, if a human approved it.\n *\n * Returns null for a device the control plane does not know, which is how\n * the relay refuses to be the authority on identity.\n */\n deviceFor(runnerId: string): DeviceRecord | null {\n return this.#fixture.devices.find((d) => d.runnerId === runnerId) ?? null;\n }\n\n /** The device approved for these exact keys, if any. */\n deviceByFingerprint(identityPublic: string): DeviceRecord | null {\n return (\n this.#fixture.devices.find((d) => d.device.identity === identityPublic) ??\n null\n );\n }\n\n /**\n * The consent binding this owner to this site, if it exists and stands.\n *\n * **Liveness, not routing.** A paused consent is returned here: the\n * relationship exists, the daemon is not revoked, the pairing stands. Ask\n * {@link Projection.mayRouteFor} before moving anybody's work — the two\n * questions have different answers and one method answering both is how a\n * paused user would quietly start routing again.\n */\n consentFor(owner: string, siteId: string): ConsentRecord | null {\n const revoked = this.#fixture.revoked.some(\n (r) => r.owner === owner && r.siteId === siteId,\n );\n if (revoked) return null;\n return (\n this.#fixture.consents.find(\n (c) => c.owner === owner && c.siteId === siteId,\n ) ?? null\n );\n }\n\n /**\n * Every site this owner may route with — cloud_009 §3.\n *\n * The set a pairing covers, and the set a claim will filter on. Consent\n * decides it, which is the sentence the whole design rests on: a site\n * appears here because a human clicked, never because a site asked to be\n * here and never because a daemon named it.\n *\n * **Paused sites are here, and that is deliberate** — cloud_008 finding 48\n * as ratified. A paused consent routes nothing and keeps its pin: the\n * relationship stands, the key the daemon compared a fingerprint of stays\n * pinned, and re-consenting never costs a re-pair. Written the other way\n * round first, and three of the paused tests failed by refusing to pair at\n * all — which is the trap the finding is about, arriving through the door\n * marked \"be stricter\".\n *\n * So this is the *pairing* set and `mayRouteFor` is the *routing* set. Two\n * questions with different answers, kept apart for the same reason\n * `consentFor` and `mayRouteFor` are: one method answering both is how a\n * paused user quietly starts routing again, or quietly loses their machine.\n *\n * Sorted by site id so two calls with the same projection produce the same\n * answer: this ends up in a pairings file and in a fingerprint list a human\n * compares by eye, and an order that drifts between polls is a diff nobody\n * can read.\n */\n sitesFor(owner: string): SiteRecord[] {\n return this.#fixture.sites\n .filter((site) => this.consentFor(owner, site.siteId) !== null)\n .sort((a, b) => (a.siteId < b.siteId ? -1 : 1));\n }\n\n /**\n * Which registered site owns this identity key id?\n *\n * A stub names its site by *key id* (Amendment A §A.3) so a daemon can\n * check it against a pinned key without a lookup. A control plane knows\n * sites by their account id. This is the one place that holds both, so it\n * is the one place that joins them — a control plane asked to accept key\n * ids would need its own copy of the registry.\n *\n * `null` for a key id no registered site carries, which is a projection\n * that is behind rather than a job that is wrong.\n */\n siteIdForKey(keyId: string): string | null {\n return (\n this.#fixture.sites.find(\n (record) => keyIdOf(record.site.identity) === keyId,\n )?.siteId ?? null\n );\n }\n\n /**\n * May this owner's work move for this site, right now?\n *\n * Consent exists, was not revoked, and is not paused. The routing question,\n * kept apart from {@link Projection.consentFor}'s liveness one so that a\n * caller has to pick which it means.\n */\n mayRouteFor(owner: string, siteId: string): boolean {\n const consent = this.consentFor(owner, siteId);\n return consent !== null && !consent.paused;\n }\n\n /**\n * Has *this device* been revoked — ruled 2026-09-03?\n *\n * This method has now been wrong in both directions, which is why it reads\n * the way it does.\n *\n * First it answered \"is there nothing to serve\", so an empty or half-written\n * projection was indistinguishable from a human's decision and cost every\n * daemon its pinned keys. The fix asked for evidence — a revocation on\n * record — but asked it of the **owner**, and added\n * `sitesFor(owner).length > 0` as a softener. That produced the opposite\n * failure: an account with any revocation and no live consents refused every\n * device it had, one paired seconds ago included.\n *\n * So: revocation is a fact about one device, never a mood about an owner.\n * This looks up the device that signed the request and reports what the\n * control plane says about *it*.\n *\n * The softener is gone with it. A guard whose answer changes with unrelated\n * state is not a guard — enabling a site must never be the thing that\n * un-revokes a machine, and under the old shape it was exactly that.\n *\n * A projection that knows nothing about a runner still says nothing here;\n * `deviceFor` is what refuses an unknown one, with 401, which is a different\n * sentence for a different situation.\n *\n * REVOCATION_IMMEDIATE is untouched: revoking device A still stops A on its\n * next call. It stops stopping B and C.\n */\n revokedDevice(runnerId: string): boolean {\n const device = this.#fixture.devices.find(\n (record) => record.runnerId === runnerId,\n );\n return device?.revoked === true;\n }\n\n /** Whether this pair is consented and paused — what heartbeat reports. */\n pausedFor(owner: string, siteId: string): boolean {\n return this.consentFor(owner, siteId)?.paused === true;\n }\n\n /**\n * Every (site, owner) route this device may run — cloud_009 §3.\n *\n * The claim filter, collapsed to data a store can match on. `routableOwners`\n * was this for one site; the hub needs it for the set, and the shape had to\n * change rather than repeat, because **a set of sites and a set of owners\n * multiply**. A device whose owner consented to site A, serving a roster\n * member who consented to site B, appears in both sets and has no consented\n * route between them. Pairs cannot express a route nobody agreed to.\n *\n * Both halves of the rule are here, and neither was enforced before finding\n * 48's work:\n *\n * - **This machine's owner** must have a live consent for the site, or\n * nothing of that site's runs here at all — including a roster member's\n * work. The roster says whose jobs may land on this machine; consent says\n * whether this machine is available to that site.\n * - **Each job's owner** must have one too. That check did not exist:\n * consent was enforced by the daemon plane's blanket revoked guard, which\n * asks only about the claiming device's owner, so a roster member who\n * never consented to a site could have their work claimed by their admin's\n * machine — `CONSENT_BEFORE_ROUTE` read the other way round.\n */\n routesFor(deviceOwner: string): Set<string> {\n const routes = new Set<string>();\n for (const site of this.#fixture.sites) {\n if (!this.mayRouteFor(deviceOwner, site.siteId)) continue;\n for (const owner of this.ownersRunnableBy(deviceOwner)) {\n if (!this.mayRouteFor(owner, site.siteId)) continue;\n routes.add(routeKey(site.siteId, owner));\n }\n }\n return routes;\n }\n\n /**\n * Every owner whose work this device's owner may run, as a list.\n *\n * The same question {@link mayRunFor} answers, asked in the direction a\n * *store* can use. That difference is the crux of making `claim` atomic\n * (cloud_006 §3.2).\n *\n * Today `claim` scans every job and calls `mayRunFor` per candidate, which\n * works because the projection is a local object. A shared routing store\n * cannot do that: the filter has to travel to the store, and a predicate\n * does not travel — you cannot send a closure to Valkey. So the projection\n * is collapsed to **data** here and handed over as a set the store can\n * match on.\n *\n * That the collapse is possible at all is a property of the design worth\n * noticing: `mayRunFor` is a finite lookup over consent and rosters, not a\n * computation over the jobs. If it ever became job-dependent — \"may run\n * work of this size\", say — an atomic claim would stop being expressible,\n * and that is the moment to argue rather than to add a parameter.\n *\n * The owner is always included: a device runs its owner's work, and the\n * relay checks that before it checks a roster.\n */\n ownersRunnableBy(deviceOwner: string): string[] {\n const owners = new Set([deviceOwner]);\n for (const roster of this.#fixture.rosters) {\n if (roster.owner !== deviceOwner) continue;\n for (const member of roster.members) owners.add(member);\n }\n return [...owners];\n }\n\n /**\n * May this device's owner run work belonging to `jobOwner`?\n *\n * The relay's half of `AUDIENCE_BOTH_SIDES`. It is only ever a *narrowing*:\n * the daemon re-checks its own allowlist locally and may still refuse, and\n * the site's audience already bounded who could be offered the job. A relay\n * that answered `true` for everyone would not widen anything — which is\n * exactly the property that lets it be blind.\n */\n mayRunFor(deviceOwner: string, jobOwner: string): boolean {\n if (deviceOwner === jobOwner) return true;\n return this.#fixture.rosters.some(\n (r) => r.owner === deviceOwner && r.members.includes(jobOwner),\n );\n }\n}\n","import {\n MAX_ENVELOPE_BYTES,\n PROTOCOL_VERSION,\n envelopeBytes,\n keyId,\n JobStub,\n RequestSignature,\n SealedEnvelope,\n verifySiteRequest,\n} from \"@byollm/protocol\";\nimport { z } from \"zod\";\nimport type { PlaneResult } from \"./daemon-plane.js\";\nimport type { Projection } from \"./fixture.js\";\nimport { clockSkewRefusal, tooLargeRefusal } from \"./refusals.js\";\nimport type { RoutingStore } from \"./store.js\";\n\n/**\n * The plane a site talks to.\n *\n * **Outbound from the site, like everything else in this product.** A relay\n * that called site webhooks would need every site publicly reachable, which is\n * the connectivity problem the hub exists to delete — and it would put the\n * relay in the position of initiating contact, which is the posture the whole\n * design avoids. So a site polls, exactly as a daemon does, and the relay\n * never opens a connection to anyone.\n *\n * ## The three-beat exchange\n *\n * A site cannot seal at enqueue: a payload is encrypted to the device that\n * claims it, and at enqueue nobody has. So enqueue publishes a **stub**, and\n * sealing happens later, on demand:\n *\n * 1. `enqueue` — here is a stub; route it.\n * 2. `pending` — who claimed anything of mine, and what key do I seal to?\n * 3. `payload` — here is the ciphertext for that device.\n *\n * Then `results` collects what comes back. Four endpoints, all polled, none of\n * which ever carries a plaintext or a private key.\n *\n * The gap between beats 2 and 3 is the `awaiting-payload` state, and the\n * reason it needs its own timeout: a site that dies between them leaves a\n * device holding a job whose work will never arrive.\n *\n * ## Every call is signed, and this plane once was not\n *\n * A site authenticates exactly as a daemon does: it signs each request with\n * the identity key the control plane registered for it, and the relay checks\n * that signature against the projection. Nothing here trusts a `siteId` in a\n * body or a query string.\n *\n * This was the ninth finding, and it was found by reading the code in\n * preparation for the first public deploy rather than by any test — the whole\n * plane took the caller's word for who it was. What that bought an anonymous\n * caller, against a relay reachable on the internet:\n *\n * - **`enqueue` as anyone.** Publish stubs in a site's name and consenting\n * users' machines claim them. The payload that follows is sealed by the real\n * site or not at all, so no forged *work* runs — but unsolicited dispatch to\n * private hardware is a product-level breach whatever the ciphertext does.\n * - **`payload` as anyone**, over a live claim: substitute an envelope the\n * daemon will refuse to open, and the job is burned rather than run.\n * - **`pending` and `results` as anyone**: a metadata read of who is online\n * for a site, which device claimed what, and every lease id in flight.\n *\n * `RELAY_BLIND` held throughout — none of it opens a payload, which is the\n * point of building it that way. But blind is not the same as safe, and the\n * distance between them is this file.\n */\n\nconst EnqueueRequest = z\n .object({\n protocolVersion: z.literal(PROTOCOL_VERSION),\n siteId: z.string().min(1),\n /**\n * Everything the relay learns about the job.\n *\n * `JobStub` is exhaustive by construction and asserted so in the protocol\n * package — a site that tried to attach a prompt here would be refused by\n * the schema, not by a reviewer.\n */\n stub: JobStub,\n })\n .strict();\n\nconst PayloadRequest = z\n .object({\n protocolVersion: z.literal(PROTOCOL_VERSION),\n siteId: z.string().min(1),\n jobId: z.string().min(1),\n /** Sealed to the claiming device. Opaque to us and to the schema. */\n envelope: SealedEnvelope,\n })\n .strict();\n\n/** A read: the site id arrives in the query and is signed as an empty body. */\n/** What a site sends to withdraw a job. */\nconst CancelRequest = z\n .object({\n protocolVersion: z.literal(PROTOCOL_VERSION),\n siteId: z.string().min(1),\n jobId: z.string().min(1),\n })\n .strict();\n\n// No version here: a read declares it in the query string, which is where\n// `declaredVersion` looks for a GET, and this object is built from that same\n// query rather than parsed from a body.\nconst QueryRequest = z.object({ siteId: z.string().min(1) }).strict();\n\nconst ok = (body: unknown): PlaneResult => ({ status: 200, body });\nconst fail = (status: number, error: string, message: string): PlaneResult => ({\n status,\n body: { error, message },\n});\n\n/**\n * Whether a purpose can be satisfied for this person, asked at enqueue.\n *\n * The relay does not hold the answer and must not: a relay that filtered on\n * mappings would hold the mapping, which is the one thing it cannot have. So\n * it asks — of the control plane, which already answers the same question at\n * claim, from the same authority, a moment later.\n *\n * Three replies, because three things are true at three different times.\n * `not-declared` is the site's own manifest and is fixed by the developer.\n * `unmapped` is the person's own dashboard and is fixed by them. `ok` covers\n * everything the transient path was always for: declared, mapped, and no\n * device able to claim right now.\n *\n * Optional, because a self-hosted relay may have no control plane. When it is\n * absent nothing is refused — and the relay says so at boot and on its health\n * surface, because a check that quietly is not there is the skipping-check law\n * wearing deployment.\n */\nexport interface SitePlaneDeps {\n readonly state: RoutingStore;\n readonly projection: Projection;\n readonly now: () => number;\n /**\n * Asked once per enqueue, when a control plane is present.\n *\n * What this teaches the relay is one bit it did not previously hold:\n * whether this owner has *a* mapping for this purpose. Existence, never\n * which service — that stays in the control plane, and this is recorded in\n * the enumerated-metadata commitment so the list stays exhaustive.\n */\n readonly satisfiable?: Satisfiable;\n /**\n * The one site this relay routes for.\n *\n * The same value the daemon plane holds, from the same option, because it is\n * the same fact — a relay that accepted enqueues for sites its daemons never\n * paired with would route work nobody can open.\n */\n}\n\n/**\n * The enqueue-time question, declared once.\n *\n * It was written out twice — here and on `RelayOptions` — and by the time\n * 019 added a fourth verdict the two copies disagreed, so a relay could be\n * handed an answer its own options type said was impossible. **A shape\n * declared in two places is two places for it to drift.**\n */\nexport type Satisfiable = (query: {\n readonly siteId: string;\n readonly owner: string;\n readonly purpose: string | undefined;\n readonly kind: string;\n}) => Promise<{\n readonly verdict: \"ok\" | \"not-declared\" | \"unmapped\" | \"waiting\";\n}>;\n\n/** What `Relay.handle` reconstructs from the request, for signature checking. */\nexport interface SiteAuth {\n /** The endpoint name alone — the domain separator is applied by protocol. */\n readonly endpoint: string;\n /** The exact bytes received, hashed into the signature. */\n readonly rawBody: string;\n /** From the headers, or undefined if any part was missing. */\n readonly signature: unknown;\n}\n\nexport class SitePlane {\n readonly #deps: SitePlaneDeps;\n\n constructor(deps: SitePlaneDeps) {\n this.#deps = deps;\n }\n\n /**\n * Signature first, then the site id, then the work.\n *\n * The caller is whoever the signature says, verified against the key the\n * control plane registered — never whoever the request claims. The `siteId`\n * every request carries is then required to *match* that caller, so the two\n * can never name different sites; a request that says one thing in its\n * signed material and another in its body is refused rather than reconciled.\n *\n * Every endpoint goes through here, including the reads. That is deliberate:\n * an authenticated write plane beside an open read plane would still hand a\n * stranger presence, claims and lease ids, and \"who is online right now\" is\n * exactly the fact a blind relay is otherwise so careful not to reveal.\n */\n async #authed<T>(\n auth: SiteAuth,\n body: unknown,\n schema: { safeParse: (v: unknown) => { success: boolean; data?: T } },\n siteIdOf: (request: T) => string,\n run: (request: T, siteId: string) => Promise<PlaneResult>,\n ): Promise<PlaneResult> {\n const signature = RequestSignature.safeParse(auth.signature);\n if (!signature.success) {\n return fail(401, \"unauthorized\", \"this request is not signed\");\n }\n // The signature's caller slot carries the site id (byollm_009 §4.2's\n // site-plane note). Resolving the key through the site registry rather\n // than the device registry is what keeps a device signature from ever\n // authenticating as a site.\n const siteId = signature.data.runnerId;\n const site = this.#deps.projection.siteFor(siteId);\n if (!site) {\n return fail(401, \"unauthorized\", \"this site is not registered\");\n }\n\n /**\n * Which keys may sign for this site right now — byollm_009 Amendment C.\n *\n * The current one, and — while a retirement window is open — the key it\n * just superseded. **Both keys route while the window is open** (C.2): a\n * site that rotated is a site with two processes mid-deploy and a queue of\n * work signed a minute ago, and refusing the old key the instant the\n * record moves makes rotation a flag day.\n *\n * The site id in the caller slot does not change across a rotation — it is\n * the control plane's name for the site, not a key id — so this cannot be\n * decided by looking at who is calling. It is decided by which key\n * verifies, which is the honest question.\n *\n * The predecessors come from the chain the site itself signed, so this\n * widens nothing: a key that can authenticate here is one the current key\n * has vouched for in a statement naming both. And the window is measured\n * against the relay's clock, because a window a caller could assert would\n * not be a window.\n */\n const acceptable = [\n site.site.identity,\n ...(site.retiringUntil !== undefined &&\n this.#deps.now() < site.retiringUntil\n ? (site.succeeds ?? []).map((link) => link.identity.identity)\n : []),\n ];\n\n // Tried in order, current key first, and the *last* failure is the one\n // reported: a site whose clock has drifted fails every key with `stale`,\n // and telling it its signature was wrong would send it looking in the one\n // place the problem is not — finding 17, which this loop could quietly\n // undo.\n // Seeded with a refusal rather than left unassigned: `acceptable` always\n // has at least the current key, but a loop that can be entered zero times\n // must not be able to fall through into an authenticated request.\n let failure: ReturnType<typeof verifySiteRequest> = \"bad-signature\";\n for (const identityPublic of acceptable) {\n failure = verifySiteRequest({\n identityPublic,\n endpoint: auth.endpoint,\n body: auth.rawBody,\n signature: signature.data,\n now: this.#deps.now(),\n });\n if (!failure) break;\n // A stale signature is stale against every key; no point asking again.\n if (failure === \"stale\") break;\n }\n // `stale` is not a bad signature — cloud_008 §1.4, finding 17.\n //\n // `verifySiteRequest` distinguishes the two and this used to throw the\n // distinction away, so a site whose clock had drifted was told its\n // signature was wrong. The daemon plane had said so correctly for weeks;\n // the site plane three files over had not, which is what two\n // implementations of one refusal looks like from the inside.\n // Ordered so a failure kind added later reports `unauthorized` rather\n // than claiming a clock problem nobody diagnosed.\n if (failure === \"stale\") return clockSkewRefusal(this.#deps.now());\n if (failure) return fail(401, \"unauthorized\", \"signature check failed\");\n\n const parsed = schema.safeParse(body);\n if (!parsed.success || parsed.data === undefined) {\n return fail(400, \"bad-request\", \"request failed schema validation\");\n }\n if (siteIdOf(parsed.data) !== siteId) {\n return fail(403, \"forbidden\", \"that is not your site\");\n }\n // This relay routes for exactly one site, and now says so.\n //\n // The daemon plane has always been single-tenant — `DaemonPlaneDeps.siteId`\n // is the field multi-tenancy replaces — but the site plane accepted any\n // registered site, and `claim` never looked at a job's `siteId` at all. A\n // second registered site's jobs would therefore be offered to a daemon\n // paired with the first, which pinned a different key and could only fail\n // to open the payload. Contained by the crypto, and still a job burned by\n // routing rather than by anything the device did.\n // Registered, rather than \"the one site this relay was configured with\"\n // — cloud_009 §3. Every registered site is routable now; what a relay\n // refuses is a caller naming a site its projection does not hold.\n //\n // Which is checked **above**, where the signature is resolved: a site the\n // projection does not hold has no key to verify against and is refused\n // 401 before reaching here. The second check that used to stand at this\n // line was dead — V1-17 — and dead guards are worse than absent ones:\n // they read as the enforcement, so the day somebody moves the real check\n // they leave this one behind and nothing looks different.\n return run(parsed.data, siteId);\n }\n\n enqueue(auth: SiteAuth, body: unknown): Promise<PlaneResult> {\n return this.#authed(\n auth,\n body,\n EnqueueRequest,\n (request) => request.siteId,\n async (request, siteId) => {\n // The stub names a site; the signature says who is asking. They have\n // to agree — Amendment A §A.3.\n //\n // Same rule the `siteId` in the body already follows, applied one\n // level in: a caller that could publish stubs naming *another* site\n // would be handing that site's daemons work sealed by the wrong key,\n // and every one of them would report a corrupt envelope rather than an\n // impersonation. `siteFor` is non-null here — `#authed` resolved the\n // caller through it — and the optional chain is what makes a later\n // edit to that invariant produce a refusal instead of a crash.\n const registered = this.#deps.projection.siteFor(siteId);\n if (\n !registered ||\n keyId(registered.site.identity) !== request.stub.site\n ) {\n return fail(\n 403,\n // V1-13, and one of the five the ruling itself named: an\n // identified site claiming another site's stub is `forbidden`.\n \"forbidden\",\n \"that stub does not name the site that signed it\",\n );\n }\n\n /**\n * Refused here, or never — the two answers a site can act on.\n *\n * Both are knowable now and neither becomes knowable later. A purpose\n * the manifest does not declare will not appear in it by waiting, and\n * a person who maps a slot thirty seconds from now is served by the\n * next job, which is the same thirty seconds. Queuing either would be\n * a poll wearing a promise — and worse, a job the site has already\n * fallen back on must never be served afterwards.\n *\n * The third case is the one the transient path was always for:\n * declared, mapped, and nothing able to claim it right now.\n */\n /**\n * A read that failed is not a negative answer — and not a 500 either.\n *\n * This call reaches the control plane's policy store. A blip there —\n * a connection reset, a failover, a pool exhausted — threw straight\n * out of `handle`, so every cloud-lane enqueue became `internal`\n * while the database caught its breath.\n *\n * The property that matters already held: nothing turns a failed read\n * into `not-declared` or `unmapped`, so no job was ever refused for a\n * reason nobody could check. What was wrong is what the site was\n * told. `internal` says \"we are broken and you should stop\"; this is\n * a transient condition, and the honest answer is ask again.\n *\n * 503 rather than a 409, deliberately. The enqueue endpoint's 409\n * class *is* the refusal class — an unknown code there is read as\n * `EnqueueRefused` and the job is abandoned. A transient failure\n * arriving in that class would tell a site to give up on a job the\n * relay never even evaluated.\n *\n * Not swallowed into \"satisfiable\" either, which would be the other\n * tempting shape: accepting the job and letting it expire is the\n * pre-alpha.65 behaviour, and the whole point of that release was\n * that a slot nobody can answer should not cost the site a TTL.\n */\n let answer;\n try {\n answer = await this.#deps.satisfiable?.({\n siteId,\n owner: request.stub.owner,\n purpose: request.stub.purpose,\n kind: request.stub.kind,\n });\n } catch {\n // The reason stays here. A site learns that we could not answer,\n // never that a database was the thing that could not.\n return fail(\n 503,\n \"server-error\",\n \"we could not check this just now — try again shortly\",\n );\n }\n if (answer?.verdict === \"not-declared\") {\n return fail(\n 409,\n \"purpose-not-declared\",\n `this site does not declare ${request.stub.purpose ?? \"that purpose\"} — ` +\n \"declare it on Developer Sites, and the people who have already \" +\n \"connected will each map the new slot before it routes\",\n );\n }\n if (answer?.verdict === \"unmapped\") {\n // One sentence, never why. Which service, whose device and whether\n // one exists are all the person's, and a site learns only that the\n // slot is unsatisfiable — the opacity is the promise, not a\n // side-effect of it.\n return fail(\n 409,\n \"slot-unsatisfiable\",\n \"nobody has chosen what answers this yet\",\n );\n }\n if (answer?.verdict === \"waiting\") {\n /**\n * The one bit beyond unsatisfiable a site may learn — 019 §6.3.\n *\n * **Does this need the person, or only time.** Above: somebody has\n * to go and choose a model, and no amount of waiting helps. Here:\n * the slot may recover with nobody acting, so retrying later is the\n * right fallback and sending the person to a settings page is not.\n *\n * A separate code rather than a field, and that is not a shortcut:\n * this endpoint's 409 class is the refusal class, and an unknown\n * code in it is already read as a refusal by every deployed client.\n * A site that has never heard of this one still learns its job was\n * refused, which is the fact it needs.\n *\n * Carries no duration — a duration leaks which block was hit, and\n * which block was hit says how much somebody has been working\n * today. Carries no cause: device asleep, service unhealthy and\n * account blocked are one sentence here, which is what makes the\n * bit safe. It is about the slot's future, not the person's day.\n */\n return fail(\n 409,\n \"slot-waiting\",\n \"nothing can answer this right now — try again later\",\n );\n }\n\n const job = await this.#deps.state.enqueue({\n id: request.stub.id,\n siteId,\n stub: request.stub,\n });\n // Idempotent by id **within a site** — cloud_008 finding 58. A known\n // id from the same site is that site's republish and returns what is\n // already routing; a known id from another site is refused rather\n // than answered with the other site's job.\n //\n // The comment here used to say the multi-tenant router would need a\n // collision check \"when it can be exercised\", on the argument that an\n // unreachable guard is a test that cannot fail. That was right about\n // the test and wrong about where the guard belongs: the store owns\n // idempotency, so the store owns the exception to it, and the check\n // is exercised there against both implementations. This is the wire's\n // half of it.\n if (\"refused\" in job) {\n // **Says nothing about why** — cloud_008 finding 58, second pass.\n //\n // \"that job id belongs to another site\" is a cross-tenant existence\n // oracle: a site that guessed or was leaked an id could confirm\n // another tenant holds it. Ids are random, so enumeration is not\n // practical, and a confirmation should still not be available for\n // the asking.\n //\n // This reduces the leak and does not remove it, which is worth\n // saying rather than claiming a fix: a site knows its own stub is\n // well-formed, so *any* refusal it can tell apart from success is\n // the confirmation, whatever the message says. The only real fix is\n // a collision that cannot happen — per-site keys, cloud_009 §3's\n // first store decision — and this refusal disappears with it.\n return fail(400, \"bad-request\", \"that stub was not accepted\");\n }\n return ok({ jobId: job.id, state: job.state });\n },\n );\n }\n\n /**\n * What needs sealing, and who to seal it to.\n *\n * The response carries the claiming device's **public** keys — which is the\n * entire reason a blind relay can exist. The relay is a directory here, not\n * a participant: it tells the site an address, and what the site sends to\n * that address is unreadable on the way through.\n */\n pending(auth: SiteAuth, siteId: string): Promise<PlaneResult> {\n return this.#authed(\n auth,\n { siteId },\n QueryRequest,\n (request) => request.siteId,\n async (_request, site) => {\n await this.#deps.state.sweep();\n const jobs = (await this.#deps.state.awaiting(site)).map((job) => ({\n jobId: job.id,\n // Non-null by construction: `awaiting` only returns claimed jobs.\n // The optional chain is here so a future state-machine edit that\n // broke that invariant would produce a missing field rather than a\n // crash on the routing path.\n device: job.claimedBy?.device,\n runnerId: job.claimedBy?.runnerId,\n leaseId: job.claimedBy?.leaseId,\n /** So a site can decline to seal for a claim about to expire. */\n awaitingUntil: job.awaitingUntil,\n /**\n * When the *grant* ends — cloud_008 §0.6.\n *\n * Distinct from `awaitingUntil`, which bounds how long this relay\n * waits for the site to seal. A site adopting the lease into its own\n * records needs the lease's clock; given the other one it recorded a\n * grant that expired in seconds, then refused the device's own\n * result for want of a matching lease.\n */\n leaseExpiresAt: job.claimedBy?.leaseExpiresAt,\n }));\n return ok({ jobs });\n },\n );\n }\n\n /**\n * The site withdraws a job — cloud_008 §2.2.\n *\n * Signed and site-scoped like every other site-plane call. A cancellation\n * is not a delete: a device already running the job has to be told, and it\n * hears at its next heartbeat.\n */\n cancel(auth: SiteAuth, body: unknown): Promise<PlaneResult> {\n return this.#authed(\n auth,\n body,\n CancelRequest,\n (request) => request.siteId,\n async (request, siteId) => {\n const cancelled = await this.#deps.state.cancel({\n jobId: request.jobId,\n siteId,\n });\n // Idempotent, and quiet about what it did not find: a site asking\n // twice is ordinary, and answering differently for \"already\n // cancelled\" and \"never existed\" would tell an unrelated caller\n // whether an id is real.\n return ok({ cancelled });\n },\n );\n }\n\n payload(auth: SiteAuth, body: unknown): Promise<PlaneResult> {\n return this.#authed(\n auth,\n body,\n PayloadRequest,\n (request) => request.siteId,\n async (request, siteId) => {\n // The ceiling, before acceptance — ratified 2026-08-28. Refused here\n // rather than after the store call, because the point of a\n // relay-memory rail is that the oversized thing is never held.\n const bytes = envelopeBytes(request.envelope);\n if (bytes > MAX_ENVELOPE_BYTES) return tooLargeRefusal(bytes);\n\n // One store call: the check and the write together. A site that read\n // \"awaiting-payload\" and then wrote would be racing the timeout that\n // makes the state mean anything.\n const sealed = await this.#deps.state.seal({\n jobId: request.jobId,\n siteId,\n envelope: request.envelope,\n });\n if (\"refused\" in sealed) {\n return sealed.refused === \"not-found\"\n ? fail(404, \"not-found\", \"unknown job\")\n : fail(\n 409,\n \"too-late\",\n `job is ${sealed.was ?? \"gone\"}, not awaiting payload`,\n );\n }\n return ok({ jobId: request.jobId, state: sealed.state });\n },\n );\n }\n\n /** Sealed results, for the site to open and verify. */\n results(auth: SiteAuth, siteId: string): Promise<PlaneResult> {\n return this.#authed(\n auth,\n { siteId },\n QueryRequest,\n (request) => request.siteId,\n async (_request, site) => {\n const jobs = (await this.#deps.state.finished(site)).map((job) => ({\n jobId: job.id,\n envelope: job.result,\n disposition: job.disposition,\n runnerId: job.claimedBy?.runnerId,\n /** The grant the site adopted, so it can complete against it. */\n leaseId: job.claimedBy?.leaseId,\n /**\n * Which device ran it, so the site can verify the signature against\n * the key it was told to seal to — and so `PROVENANCE_NAMES_DEVICE` can\n * name a foreign device rather than guessing (cloud_004 §11.2).\n */\n device: job.claimedBy?.device,\n /**\n * Whose machine ran it — cloud_008 §2.5, finding 41.\n *\n * The relay has held this since the claim: `claimedBy.owner` is the\n * owner id the *projection* supplied, in the same namespace the\n * direct plane's `runnerOwner` uses. The cloud lane was filling that\n * field with `keyId(device.identity)` instead — a key id where every\n * other plane puts a user id, so an app comparing provenance across\n * lanes compared two namespaces for equality and got `false` for\n * the same person.\n */\n runnerOwner: job.claimedBy?.owner,\n }));\n return ok({ jobs });\n },\n );\n }\n}\n"],"mappings":"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","names":["MAX_ENVELOPE_BYTES","ERROR_STATUS","ERROR_STATUS","MAX_ENVELOPE_BYTES","PublicIdentity","z","PublicIdentity","keyId","MAX_ENVELOPE_BYTES","PROTOCOL_VERSION","envelopeBytes","keyId","RequestSignature","z","z","PROTOCOL_VERSION","ok","fail","RequestSignature","keyId","envelopeBytes","MAX_ENVELOPE_BYTES"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/daemon-plane.ts","../src/pairing-codes.ts","../src/refusals.ts","../src/debug.ts","../src/fixture.ts","../src/site-plane.ts"],"sourcesContent":["import {\n checkProtocolVersion,\n declaredVersion,\n type SignedGrant,\n} from \"@byollm/protocol\";\nimport {\n DaemonPlane,\n type GrantAuthor,\n type PlaneResult,\n} from \"./daemon-plane.js\";\nimport { debugPage } from \"./debug.js\";\nimport { Projection, type RelayFixture } from \"./fixture.js\";\nimport { MemoryPairingCodes, type PairingCodes } from \"./pairing-codes.js\";\nimport { SitePlane, type Satisfiable } from \"./site-plane.js\";\nimport { RelayState } from \"./state.js\";\nimport type { RoutingStore } from \"./store.js\";\n\n/**\n * `@byollm/relay` — the reference relay (cloud_004 §14).\n *\n * A blind relay between byollm sites and daemons: it routes stubs, hands over\n * sealed envelopes it cannot open, and knows who is online. It is the first\n * consumer of byollm_009's session layer that is neither the site nor the\n * device, which makes it the thing that proves the protocol's central claim.\n *\n * ## Why this ships open\n *\n * It is the conformance kit's reference relay, and the kit is public — so it\n * starts where it ends rather than being written closed and ported. A relay\n * that claims to be blind should be readable by the people trusting it, and a\n * third-party daemon testing hub mode should test against real code rather\n * than a mock of it. The production hub — multi-tenant routing, presence at\n * scale, billing, ops — is built on these same interfaces and is not this.\n *\n * ## Blind by construction, not by policy\n *\n * {@link RelayOptions} has no field that can hold a private key, and no type\n * in this package has one either. `RELAY_BLIND` is therefore not a rule the\n * code follows; it is a shape the code has. The only way to make this relay\n * able to read a payload is to change its types, which is a review someone\n * would have to justify rather than a line someone could slip in.\n */\n\n/**\n * What a control plane answers when a relay asks about one job.\n *\n * Declared here rather than imported, and deliberately narrower than what\n * `@byollm/control-plane` returns: a relay needs to know whether it got a\n * grant and whether a refusal is forever, and nothing else. Stating only that\n * keeps the two packages independent — a relay can be wired to any control\n * plane, and the reference engine satisfies this by having more, not less.\n *\n * `reason` is for the log. The relay never branches on it, because a relay\n * that acted differently per reason would be a second implementation of a\n * policy it does not own.\n */\nexport type GrantDecision =\n | { readonly granted: SignedGrant; readonly declined?: undefined }\n | {\n readonly granted?: undefined;\n readonly declined: {\n /** Never offer this job to this device again. */\n readonly permanent: boolean;\n readonly reason?: string;\n };\n };\n\nexport interface RelayOptions {\n /**\n * Which site this relay routes for.\n *\n * One, in the skeleton. Multi-tenant routing is the closed piece\n * (cloud_004 §9), and it replaces this field rather than extending it.\n */\n /** Consent and routing, projected from the control plane. */\n readonly fixture?: RelayFixture;\n /**\n * The control plane's grant-signing public key — Amendment J.\n *\n * Handed to daemons at pairing, and the thing every grant is checked\n * against. Configuring it without {@link RelayOptions.authorGrant} is\n * refused at construction: a device told to expect signed grants and then\n * sent none refuses every job, and it would do so with no signal here.\n */\n readonly controlPlanePublic?: string | undefined;\n /**\n * A version this deployment offers daemons — B053, D1.\n *\n * Absent means no offer reaches anybody, which is how this ships: the code\n * changes no byte on the wire until a deployment sets it deliberately.\n */\n readonly updateOffer?: string | undefined;\n /**\n * The oldest daemon this deployment serves — B052, D1.\n *\n * Absent means the floor refuses nobody. A floor picked optimistically on\n * deploy day takes working machines down.\n */\n readonly daemonFloor?: string | undefined;\n /**\n * Whether a purpose can be satisfied for this person, asked at enqueue.\n *\n * The relay does not hold the answer and must not: one that filtered on\n * mappings would hold the mapping, which is the one thing it cannot have. So\n * it asks whoever does — in practice the control plane, which already\n * decides this at claim, a moment later.\n *\n * Optional. A relay without it refuses nothing, which is a supported\n * arrangement and one an operator must be able to see they are in: say so at\n * boot and on the health surface, because a check that quietly is not there\n * reads as a check that passed.\n */\n readonly satisfiable?: Satisfiable;\n /**\n * Author a grant for one claimed job — Amendment J.\n *\n * **The relay asks; it does not decide.** Everything a grant asserts —\n * whose job this is, whether they are still a member, which of the owner's\n * services their mapping resolves to — is the control plane's knowledge,\n * and this callback is the seam between the two. A relay wired to a\n * deployment that has no control plane simply has no callback, and its\n * devices serve their owners alone.\n *\n * Declining says whether the refusal is **permanent**, and that is the\n * whole reason this returns a shape rather than `SignedGrant | undefined`.\n * A relay releases a declined job, and a release can carry `refused`, which\n * means never offer this job to this device again. \"This person was removed\n * from the team\" is forever — removal stops queued claims, per hole 1.\n * \"Their mapping resolved to another of your machines\" is emphatically not:\n * marking that permanently would mean the job could never reach the device\n * it was always meant for, and nothing would ever report it.\n *\n * The capability matrix is passed because resolution needs it — the control\n * plane chooses from what this device actually advertised, never from a\n * name it invented. Until byollm_016 Amendment L lands, \"resolution\" is the\n * job's own selection or the device's default; after it, the user's\n * per-purpose mapping. The seam does not change.\n */\n readonly authorGrant?: GrantAuthor;\n /** How long a claim is good for. */\n readonly leaseMs?: number;\n /** Injectable clock, so tests move time instead of sleeping. */\n readonly now?: () => number;\n /**\n * Where pending pairing codes live — cloud_009's device-code flow.\n *\n * Defaults to an in-memory store, which is right for the reference relay\n * and wrong for a hub: two replicas mean a code minted on one must be\n * pollable on the other, the same reason the routing store is not a `Map`.\n */\n readonly pairingCodes?: PairingCodes;\n /**\n * Where a human approves a code. The control plane's own URL.\n *\n * Given rather than derived: the relay cannot approve anything, because\n * approving is looking at a fingerprint while signed in and that session\n * lives in the dashboard. Absent, the device-code flow is refused as\n * unsupported rather than pointed somewhere useless.\n */\n readonly verificationUrl?: string;\n /** Where the daemon plane is mounted. */\n readonly basePath?: string;\n /**\n * Serve `/debug`, which is off unless somebody asks for it.\n *\n * The page shows every routed job for a site, its state, who claimed it and\n * how long its timers have left. It shows no prompt or result text — the\n * relay does not have them — and it is genuinely useful when a route is\n * behaving strangely.\n *\n * It is also, on anything reachable from the internet, an anonymous read of\n * exactly the metadata the site plane exists to protect. That was finding\n * eleven, found by curling a deployed hub. The hub refuses the route\n * outright; this package used to serve it by default and leave `D005` to\n * warn whoever deployed it, which is a default that fails safe only if\n * somebody reads the audit.\n *\n * So: off, and per-site when on (cloud_009 §3 — the debug page is per-site\n * or it is nothing). `D005` still fails for a relay that turned it on,\n * which is the audit doing its job for an operator who made a choice rather\n * than warning everybody about a default.\n */\n readonly debug?: boolean;\n /**\n * Where routing state lives — cloud_006.\n *\n * Defaults to an in-process {@link RelayState}, which is correct for one\n * replica and is what this package ships. A hub running more than one\n * replica supplies a shared implementation of {@link RoutingStore} instead;\n * `packages/relay/test/two-replicas.test.ts` is why that is not optional.\n *\n * **The implementation is deliberately not in this package.** A Valkey\n * client is a dependency every consumer would carry to get a feature only a\n * multi-replica deployment uses, and the production hub is the closed piece\n * (cloud_001). What ships here is the interface, the reference\n * implementation, and the tests that say what an implementation must\n * guarantee.\n */\n readonly store?: RoutingStore;\n}\n\n/** A running relay: one fetch handler, two planes, one debug page. */\nexport class Relay {\n readonly state: RoutingStore;\n readonly projection: Projection;\n readonly #daemon: DaemonPlane;\n readonly #site: SitePlane;\n readonly #now: () => number;\n readonly #basePath: string;\n readonly #debug: boolean;\n\n constructor(options: RelayOptions) {\n this.state =\n options.store ?? new RelayState({ now: options.now ?? Date.now });\n this.projection = new Projection(options.fixture);\n this.#now = options.now ?? Date.now;\n this.#basePath = (options.basePath ?? \"/byollm\").replace(/\\/+$/, \"\");\n this.#debug = options.debug ?? false;\n this.#daemon = new DaemonPlane({\n state: this.state,\n projection: this.projection,\n now: this.#now,\n leaseMs: options.leaseMs ?? 60_000,\n pairingCodes:\n options.pairingCodes ?? new MemoryPairingCodes(() => this.#now()),\n ...(options.verificationUrl === undefined\n ? {}\n : { verificationUrl: options.verificationUrl }),\n ...(options.controlPlanePublic === undefined\n ? {}\n : { controlPlanePublic: options.controlPlanePublic }),\n ...(options.authorGrant === undefined\n ? {}\n : { authorGrant: options.authorGrant }),\n ...(options.updateOffer === undefined\n ? {}\n : { updateOffer: options.updateOffer }),\n ...(options.daemonFloor === undefined\n ? {}\n : { daemonFloor: options.daemonFloor }),\n });\n /**\n * A relay that promises grants and cannot author them is refused here.\n *\n * `controlPlanePublic` is what tells a device to expect a signed grant\n * with every job. Without an author, every claim would arrive without\n * one, every job would be refused at the device, and the only symptom\n * would be a fleet that had quietly stopped working — with this process\n * reporting itself healthy throughout. It is a deployment mistake that\n * cannot be caught downstream, so it is caught at construction.\n */\n if (\n options.controlPlanePublic !== undefined &&\n options.authorGrant === undefined\n ) {\n throw new Error(\n \"controlPlanePublic is set but authorGrant is not: devices would be \" +\n \"told to expect signed grants that nothing here can produce, and \" +\n \"would refuse every job\",\n );\n }\n\n this.#site = new SitePlane({\n state: this.state,\n projection: this.projection,\n now: this.#now,\n ...(options.satisfiable === undefined\n ? {}\n : { satisfiable: options.satisfiable }),\n });\n }\n\n /** Replace the projection — a control-plane push, or a fixture edit. */\n project(fixture: RelayFixture): void {\n this.projection.replace(fixture);\n }\n\n /**\n * Fire due timers and report what moved.\n *\n * Exposed rather than run on an interval so a test can drive it, and so the\n * production hub can decide its own scheduling. The relay never needs a\n * timer to be *correct* — every read path sweeps first — but a job whose\n * site vanished should return to the queue without waiting for someone to\n * ask about it.\n */\n async sweep(): Promise<{ requeued: string[] }> {\n const requeued = await this.state.sweep();\n return { requeued: requeued.map((j) => j.id) };\n }\n\n /** The whole HTTP surface. */\n async handle(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const path = url.pathname;\n\n if (path === \"/debug\" || path === `${this.#basePath}/debug`) {\n // **Per-site or nothing** — cloud_009 §3, ratified. A page that reads\n // every tenant's state through one door is finding eleven wearing a\n // different hat: the anonymous read of who-is-online and\n // which-device-holds-what, rebuilt after being closed. So the site is a\n // parameter, and without one there is no page rather than a page\n // showing everything.\n const siteId = url.searchParams.get(\"site\");\n if (!this.#debug) {\n // No such route. `not-found` rather than `forbidden`, because there\n // is no credential that would work and saying \"forbidden\" advertises\n // a door that does not open.\n return new Response(JSON.stringify({ error: \"not-found\" }), {\n status: 404,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n if (siteId === null || this.projection.siteFor(siteId) === null) {\n // **A different answer, deliberately.** `D005` probes this path with\n // no site id, and a 404 here would tell it there is no debug surface\n // when there is one behind a parameter — a check passing for a reason\n // unrelated to the property, in the audit written to catch that.\n //\n // The same answer for \"no site named\" and \"a site I do not hold\", so\n // this cannot be used to ask which sites exist.\n return new Response(JSON.stringify({ error: \"bad-request\" }), {\n status: 400,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n return new Response(\n await debugPage(this.state, this.#now(), {\n siteId,\n consents: (owner) =>\n this.projection.consentFor(owner, siteId) !== null,\n }),\n { headers: { \"content-type\": \"text/html; charset=utf-8\" } },\n );\n }\n\n const rawBody = request.method === \"POST\" ? await request.text() : \"\";\n const body = rawBody === \"\" ? undefined : safeJson(rawBody);\n const endpoint = path.slice(path.lastIndexOf(\"/\") + 1);\n const auth = {\n endpoint,\n rawBody,\n signature: signatureFrom(request.headers, \"x-byollm-runner\"),\n };\n // **The handshake, before anything else** — byollm_009 §B.4.\n //\n // The direct server has done this since its own version defect was found;\n // this relay never did, so every mismatch on every endpoint arrived as a\n // bare `bad-request` from a `z.literal` buried in a schema — a daemon and\n // a relay discovering they disagreed by failing, with nothing in the\n // answer naming the disagreement or the fix.\n //\n // One helper, both planes, every endpoint. The first attempt at this\n // refused every site request in the suite, because the site plane's\n // requests carried no version at all: the check was right and the wire was\n // incomplete, which is why B.4 was written down rather than patched at\n // 4 a.m. The site plane declares a version now.\n //\n // Health and the debug page are above this line deliberately: they are not\n // protocol endpoints, and a probe that has to speak the version to ask\n // whether the process is alive is a probe that stops working on the day\n // the version moves.\n // **Protocol paths only.** An unknown path is a 404, not a lecture about\n // versions: written the other way first, and a request for `/healthz` —\n // or anything a scanner tries — came back with what this relay speaks and\n // how to upgrade. The handshake is part of the protocol, not of the HTTP\n // surface, and answering it for a path that does not exist both misleads\n // the caller and describes us to somebody who was only knocking.\n if (path.startsWith(\"/byollm/\") || path.startsWith(\"/relay/\")) {\n const refusal = checkProtocolVersion({\n protocolVersion: declaredVersion({ body, query: url.searchParams }),\n });\n if (refusal) return json({ status: 400, body: refusal });\n }\n\n // The caller header differs by plane, so a signature meant for one can\n // never be presented to the other by moving the request. The endpoint's\n // domain separator (`site/…`) already covers this; the header makes it\n // true at parse time rather than at verification time.\n const siteAuth = {\n endpoint,\n rawBody,\n signature: signatureFrom(request.headers, \"x-byollm-site\"),\n };\n\n // -- the site plane -----------------------------------------------------\n if (path === \"/relay/site/enqueue\") {\n return json(await this.#site.enqueue(siteAuth, body));\n }\n if (path === \"/relay/site/payload\") {\n return json(await this.#site.payload(siteAuth, body));\n }\n if (path === \"/relay/site/cancel\") {\n return json(await this.#site.cancel(siteAuth, body));\n }\n if (path === \"/relay/site/pending\") {\n return json(\n await this.#site.pending(\n siteAuth,\n url.searchParams.get(\"siteId\") ?? \"\",\n ),\n );\n }\n if (path === \"/relay/site/results\") {\n return json(\n await this.#site.results(\n siteAuth,\n url.searchParams.get(\"siteId\") ?? \"\",\n ),\n );\n }\n\n // -- the daemon plane ---------------------------------------------------\n if (!path.startsWith(`${this.#basePath}/`)) {\n return json({ status: 404, body: { error: \"not-found\" } });\n }\n switch (auth.endpoint) {\n case \"pair\":\n return json(await this.#daemon.pair(body));\n case \"claim\":\n return json(await this.#daemon.claim(auth, body));\n case \"fetch\":\n return json(await this.#daemon.fetch(auth, body));\n case \"result\":\n return json(await this.#daemon.result(auth, body));\n case \"heartbeat\":\n return json(await this.#daemon.heartbeat(auth, body));\n case \"release\":\n return json(await this.#daemon.release(auth, body));\n default:\n return json({ status: 404, body: { error: \"not-found\" } });\n }\n }\n}\n\nfunction safeJson(raw: string): unknown {\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n return undefined;\n }\n}\n\n/** Rebuild the signature from headers, refusing anything partial. */\nfunction signatureFrom(headers: Headers, callerHeader: string): unknown {\n const runnerId = headers.get(callerHeader);\n const issuedAt = headers.get(\"x-byollm-issued-at\");\n const signature = headers.get(\"x-byollm-signature\");\n // Checked before `Number()`, which turns a missing header into the epoch —\n // a stale-timestamp check that silently passes is worse than none.\n if (runnerId === null || issuedAt === null || signature === null) {\n return undefined;\n }\n return { runnerId, issuedAt: Number(issuedAt), signature };\n}\n\nconst json = (result: PlaneResult): Response =>\n new Response(JSON.stringify(result.body), {\n status: result.status,\n headers: { \"content-type\": \"application/json\" },\n });\n\nexport { Projection, RelayState, debugPage };\nexport type { RoutingStore } from \"./store.js\";\nexport type { RelayFixture };\nexport {\n ConsentRecord,\n DeviceRecord,\n RevocationRecord,\n RosterRecord,\n SiteRecord,\n RelayFixture as RelayFixtureSchema,\n EMPTY_FIXTURE,\n} from \"./fixture.js\";\nexport { AWAITING_PAYLOAD_MS } from \"./state.js\";\n/**\n * How a (site, owner) route is written — cloud_009 §3.\n *\n * Exported because a store in another repository builds the same set and has\n * to agree on the encoding. Spelling it out on both sides is two statements\n * of one format, which is the bug the routes set exists to remove one level\n * up: the hub's Valkey store and this package's memory store must match\n * character for character or a claim silently returns nothing.\n */\nexport { routeKey } from \"./state.js\";\n/**\n * Everything an implementer of {@link RoutingStore} needs.\n *\n * `ClaimInput` and `HolderRefusal` were missing from this list, which made the\n * interface unimplementable outside this package — found by writing the second\n * implementation, which is the only thing that could have found it. An\n * exported interface whose parameter types are private is a contract nobody\n * can sign.\n *\n * It happened again with `ReleaseReason` (cloud_008 §2.1), added to\n * `releaseLeases` and not to this list, and found the same way: the hub\n * failed to compile. A docstring recording a lesson is not a check, which is\n * why `store-contract.test-d.ts` implements `RoutingStore` from the package\n * entry point alone.\n *\n * **And a third time, with `Grant` (V1-3) — through the check.** Declaring a\n * `RoutingStore` only requires `RoutingStore` to be exported; a *return* type\n * is reachable structurally without being nameable, so the check caught\n * nothing and the hub caught it a release later. The restated signatures\n * below the declaration are the part that bites, and they were a list\n * somebody had to remember to extend.\n *\n * A list of signatures is the same shape as this list of exports: correct\n * until the next member. `store-contract.test-d.ts` now restates every method\n * whose parameters or results are named types, `renewLeases` and\n * `cancelRequests` included.\n */\nexport type {\n ClaimInput,\n HolderRefusal,\n Presence,\n ReleaseReason,\n RoutedJob,\n RoutedState,\n} from \"./state.js\";\n\n/** The grant a lease-scoped answer names — V1-3. */\nexport type { Grant } from \"./store.js\";\n\nexport {\n MAX_OUTSTANDING_PAIRINGS,\n MemoryPairingCodes,\n PAIRING_BUSY_MESSAGE,\n PAIRING_CODE_TTL_MS,\n newDeviceCode,\n newUserCode,\n type PairingCodes,\n type PendingPairing,\n // `put` returns it, so an implementor outside this package needs to be able\n // to name it. Four separate releases have shipped a public option whose type\n // was not exported; the type test next door is what makes the fifth fail\n // here instead of in somebody else's build.\n type PutResult,\n} from \"./pairing-codes.js\";\n","import {\n PairPollRequest,\n PairStartRequest,\n ClaimRequest,\n FetchRequest,\n HeartbeatRequest,\n MAX_ENVELOPE_BYTES,\n PROTOCOL_VERSION,\n ReleaseRequest,\n ResultRequest,\n RequestSignature,\n envelopeBytes,\n keyId,\n verifyRequest,\n verifyPublicIdentity,\n updateOfferFor,\n checkDaemonFloor,\n UPGRADE_COMMAND,\n PublicIdentity,\n ERROR_STATUS,\n type CapabilityMatrix,\n type ClaimedStub,\n} from \"@byollm/protocol\";\nimport { z } from \"zod\";\nimport type { Projection } from \"./fixture.js\";\nimport type { GrantDecision } from \"./index.js\";\nimport {\n PAIRING_BUSY_MESSAGE,\n PAIRING_CODE_TTL_MS,\n newDeviceCode,\n newUserCode,\n type PairingCodes,\n type PendingPairing,\n} from \"./pairing-codes.js\";\nimport { tooLargeRefusal } from \"./refusals.js\";\nimport { RETRY_AFTER_MS, type HolderRefusal } from \"./state.js\";\nimport { clockSkewRefusal } from \"./refusals.js\";\nimport type { RoutingStore } from \"./store.js\";\n\n/**\n * The plane a daemon talks to — cloud_004 §2.\n *\n * To a daemon this is an upstream like any other: it claims, fetches, reports\n * and heartbeats exactly as it does against a direct site. That sameness is\n * the point of §9's \"the hub is a deployment of the open parts\" — hub mode is\n * not a second daemon code path, it is a second upstream.\n *\n * What differs is invisible from the daemon's side and total from ours: **this\n * upstream cannot seal.** A direct site answers `fetch` by opening its own\n * envelope and re-sealing to the claiming device. The relay has nothing to\n * open and nothing to seal with, so it answers `fetch` with whatever the site\n * left for that device, or with nothing yet.\n */\n\nexport interface PlaneResult {\n readonly status: number;\n readonly body: unknown;\n}\n\nconst ok = (body: unknown): PlaneResult => ({ status: 200, body });\n\n/**\n * A store refusal, in HTTP.\n *\n * The store says *why* in its own vocabulary and this decides what a daemon is\n * told, which keeps the two independent: a store that grows a reason does not\n * get to invent a status code, and a status code that changes does not reach\n * into the store.\n *\n * `not-ready` is the one that matters. It means claimed-but-not-yet-sealed, and\n * a daemon must retry rather than abandon — the job is legitimately still\n * theirs until the lease or the awaiting-payload clock says otherwise. It was\n * the protocol gap that produced the 409 in the first place.\n */\nconst REFUSALS: Record<HolderRefusal, PlaneResult> = {\n \"not-found\": {\n status: 404,\n body: { error: \"not-found\", message: \"unknown job\" },\n },\n // `forbidden`, not `unauthorized` — V1-13. Both of these are an\n // *identified* caller being refused, which is what 403 means and what the\n // table says `forbidden` is for; `unauthorized` is 401 and means \"we do not\n // know who you are\". Served as 403 with a 401's code, a revoked daemon and\n // an unsigned one looked alike in every log and every client branch, and\n // \"check your keys\" is the wrong advice for both in opposite directions.\n \"not-holder\": {\n status: 403,\n body: {\n error: \"forbidden\",\n message: \"this runner does not hold the job\",\n },\n },\n \"stale-lease\": {\n status: 403,\n body: { error: \"forbidden\", message: \"that lease is no longer current\" },\n },\n \"not-ready\": {\n status: 409,\n body: {\n error: \"not-ready\",\n message: \"the site has not sealed this job yet\",\n },\n },\n // Also a 409, and deliberately a different code: `not-ready` means keep\n // asking and this means stop. A daemon that read them as one would poll a\n // finished job until its lease ran out.\n terminal: {\n status: 409,\n body: { error: \"too-late\", message: \"this job has already finished\" },\n },\n};\nconst fail = (status: number, error: string, message: string): PlaneResult => ({\n status,\n body: { error, message },\n});\n\n/**\n * What the relay asks a control plane, as one named shape.\n *\n * Spelled out twice — here and on `RelayOptions` — until adding `siteKey`\n * made one of them wrong and the build caught it. That was luck: the two are\n * structurally compared, so a field added to the *caller's* copy alone would\n * have been accepted silently and the grant would have carried nothing.\n */\nexport type GrantAuthor = (input: {\n readonly job: ClaimedStub;\n /** The site's id in the control plane's namespace, for its policy read. */\n readonly siteId: string;\n /**\n * The same site as the stub names it — the key id the device pinned.\n *\n * Carried, never derived. This is the value that gets signed, and the one a\n * device can compare against `stub.site` without a lookup and without\n * trusting the party that routed it.\n */\n readonly siteKey: string;\n readonly purpose?: string;\n readonly owner: string;\n readonly runnerId: string;\n readonly capabilities: CapabilityMatrix;\n}) => Promise<GrantDecision> | GrantDecision;\n\nexport interface DaemonPlaneDeps {\n readonly state: RoutingStore;\n readonly projection: Projection;\n readonly now: () => number;\n readonly leaseMs: number;\n /**\n * A version this deployment wants daemons to move to — B053, D1.\n *\n * ABSENT BY DEFAULT, and that is the deploy condition rather than a\n * preference: a hub that has not been told a version says nothing to\n * anybody, so shipping this code changes no byte on the wire until\n * somebody sets it deliberately.\n *\n * Never read directly at a return site. {@link updateOfferFor} is the only\n * thing that turns it into a field, because it is the only thing that\n * applies the version fence.\n */\n readonly updateOffer?: string | undefined;\n /**\n * The oldest daemon this deployment will serve — B052, D1.\n *\n * ABSENT BY DEFAULT, and that is the second deploy condition rather than a\n * preference: a floor set optimistically on deploy day takes working\n * machines down, which is B052's opposite-boolean hazard arriving as an\n * ops mistake instead of a coding one. Shipping the code refuses nobody;\n * raising it is a separate, deliberate act.\n */\n readonly daemonFloor?: string | undefined;\n /**\n * Where pending pairing codes live — cloud_009.\n *\n * Optional so a relay that only serves pre-approved devices keeps working\n * unchanged; when absent, the device-code flow answers \"not supported\"\n * rather than pretending.\n */\n readonly pairingCodes?: PairingCodes | undefined;\n /**\n * The control plane's grant-signing public key — Amendment J.\n *\n * Handed to a daemon at pairing so it can check every grant this relay\n * later delivers. This is the one moment the relay tells a device whom to\n * believe, and it happens inside the ceremony where a human is already\n * comparing fingerprints — the alternative, trust-on-first-grant, would\n * hand the choice of authority to whoever controls delivery.\n *\n * Optional so a relay with no control plane behind it keeps working\n * unchanged: a daemon that receives none serves its owner alone through\n * this pairing.\n */\n readonly controlPlanePublic?: string | undefined;\n /**\n * Author a grant for one claimed job — Amendment J. See\n * {@link RelayOptions.authorGrant}; this plane only calls it.\n */\n readonly authorGrant?: GrantAuthor;\n /**\n * Where a human goes to approve a code — the control plane, always.\n *\n * The relay cannot approve anything: approving is looking at a fingerprint\n * while signed in, and the session that makes that meaningful lives in the\n * dashboard. So this is a URL the relay is *given*, not one it derives.\n */\n readonly verificationUrl?: string | undefined;\n /**\n * Which site this relay routes for.\n *\n * The skeleton relays for one site because that is all the freeze gate\n * needs. The production hub's multi-tenant router is the closed piece that\n * replaces this field (cloud_004 §9) — recorded here so the seam is visible\n * rather than assumed away.\n */\n}\n\nexport class DaemonPlane {\n readonly #deps: DaemonPlaneDeps;\n\n constructor(deps: DaemonPlaneDeps) {\n this.#deps = deps;\n }\n\n /**\n * Pair a device — cloud_004 §3, the key-exchange moment.\n *\n * The relay hands back **the site's** public identity, taken from the\n * consent projection, not its own. This is the sentence that makes hub mode\n * safe: the daemon pins the party that will actually seal its work, so an\n * envelope is verified against the site even though it arrived via us. A\n * relay that substituted its own identity here could inject work — and would\n * need a private key to do it, which is why it has none.\n */\n async pair(body: unknown): Promise<PlaneResult> {\n // The device-code flow — cloud_009, and the reason cloud pairing did not\n // work at all. `byollm connect` has always sent this shape; nothing on\n // this side accepted it, so every cloud user's first command failed\n // schema validation. Direct mode implemented it, the conformance kit\n // drove direct mode, and the seam between them was what nothing checked.\n const start = PairStartRequest.safeParse(body);\n if (start.success) return this.#pairStart(start.data);\n\n const poll = PairPollRequest.safeParse(body);\n if (poll.success) return this.#pairPoll(poll.data);\n\n const parsed = PairFixtureRequest.safeParse(body);\n if (!parsed.success) {\n return fail(400, \"bad-request\", \"pair request failed schema validation\");\n }\n if (!verifyPublicIdentity(parsed.data.device)) {\n return fail(400, \"bad-request\", \"the device identity is not consistent\");\n }\n\n // Every site this owner has consented to — cloud_009 §3. The relay is no\n // longer configured with one: it routes for whatever the projection\n // holds, which is the honest shape, and `RelayOptions.siteId` was always\n // a stand-in for the projection being single-site.\n //\n // The keys come from the site registry, which is their one home. They\n // used to be inlined on each consent record, which gave a site's key one\n // copy per consenting user and nothing to reconcile them against.\n const sites = this.#deps.projection.sitesFor(parsed.data.owner);\n if (sites.length === 0) {\n // CONSENT_BEFORE_ROUTE. There is no discovery path that creates one:\n // consent is a click somewhere else, and the relay only reads it.\n return fail(403, \"forbidden\", \"no consent record for this user\");\n }\n\n // The device must already be approved, by a human, in the control plane.\n //\n // It presented keys; that is an assertion, not an identity. Somebody had\n // to look at a fingerprint and say yes, and this is where that decision is\n // enforced. byollm_009's seventh finding stopped a daemon from *naming*\n // itself and this stops it from *keying* itself — otherwise the relay\n // would be the authority on who a machine is, which is precisely the role\n // a blind relay must not hold.\n //\n // Matched on the identity key rather than a claimed id: the key is what\n // the human approved and what every later signature is checked against.\n const approved = this.#deps.projection.deviceByFingerprint(\n parsed.data.device.identity,\n );\n if (!approved) {\n return fail(\n 403,\n \"forbidden\",\n \"this device has not been approved by its owner\",\n );\n }\n if (approved.owner !== parsed.data.owner) {\n // The device was approved by somebody else. Refused rather than\n // re-owned: an approval is for a person, not a key in general.\n return fail(403, \"forbidden\", \"this device belongs to another owner\");\n }\n\n // The id comes from the control plane, not from the device and not from\n // here — one authority for identity, and it is the one with the human in\n // it. The relay's own uuid minting was a stopgap for a fixture with no\n // devices in it.\n const runnerId = approved.runnerId;\n\n // No timestamp: the store stamps `lastSeenAt` from its own clock, so\n // presence and the deadlines that reason about it agree (cloud_006 §3.4).\n await this.#deps.state.seen({\n runnerId,\n owner: parsed.data.owner,\n device: parsed.data.device,\n // The fixture exchange carries no matrix — it models a consent that\n // arrived from a file, not a daemon describing itself. Empty until the\n // first heartbeat, which is seconds away and is the authority anyway.\n capabilities: [],\n withheld: [],\n });\n\n return ok({\n protocolVersion: PROTOCOL_VERSION,\n runnerId,\n /**\n * The *sites'* keys. See the note above — this is load-bearing.\n *\n * The set this owner has consented to, keyed by each site's identity\n * key id (cloud_009 §5). Paused sites are here: a paused consent keeps\n * its pin so re-consenting never costs a re-pair, and what it does not\n * do is route.\n */\n sites: Object.fromEntries(\n sites.map((record) => [keyId(record.site.identity), record.site]),\n ),\n // The key every later grant is checked against — Amendment J. Sent\n // here and nowhere else: pairing is the ceremony where a human is\n // already deciding whether to trust this upstream, so a key learned\n // here rides a decision that has been made rather than inventing one.\n ...(this.#deps.controlPlanePublic === undefined\n ? {}\n : { controlPlanePublic: this.#deps.controlPlanePublic }),\n });\n }\n\n /**\n * Mint a code, remember the keys it stands for, and send the human away.\n *\n * Nothing is decided here. The relay holds an assertion — \"this keypair\n * would like to be a machine\" — for ten minutes, and the decision happens\n * where the person is signed in.\n */\n async #pairStart(request: PairStartRequest): Promise<PlaneResult> {\n const codes = this.#deps.pairingCodes;\n const verificationUrl = this.#deps.verificationUrl;\n if (!codes || verificationUrl === undefined) {\n // Said plainly rather than answered with a schema error: a relay\n // without a code store cannot do this, and the daemon's user deserves\n // to know that rather than to read \"bad request\".\n return fail(\n 501,\n \"bad-request\",\n \"this relay does not offer device-code pairing\",\n );\n }\n if (!verifyPublicIdentity(request.device)) {\n return fail(400, \"bad-request\", \"the device identity is not consistent\");\n }\n\n const pending: PendingPairing = {\n deviceCode: newDeviceCode(),\n userCode: newUserCode(),\n device: request.device,\n label: request.daemon.label,\n platform: request.daemon.platform,\n capabilities: request.capabilities,\n expiresAt: this.#deps.now() + PAIRING_CODE_TTL_MS,\n };\n if ((await codes.put(pending)) === \"at-capacity\") {\n // The protocol already has one word for \"too much traffic, back off\",\n // and this is that. Nothing this caller did was wrong — the relay is\n // full of other people's pending pairings — but the instruction is the\n // same one `rate-limited` always carries, and a second vocabulary for\n // the same idea would be a worse answer than a slightly generous code.\n return fail(\n ERROR_STATUS[\"rate-limited\"],\n \"rate-limited\",\n PAIRING_BUSY_MESSAGE,\n );\n }\n\n return ok({\n deviceCode: pending.deviceCode,\n userCode: pending.userCode,\n verificationUrl,\n expiresAt: pending.expiresAt,\n // Two seconds: fast enough that approving feels immediate, slow enough\n // that a forgotten terminal is not a load generator.\n pollIntervalMs: 2_000,\n });\n }\n\n /**\n * Has anybody approved this keypair yet?\n *\n * The answer comes from the **projection** — the control plane's own record\n * of devices a human approved — and never from a flag set here. That is the\n * whole shape of the fence: the dashboard writes the approval to its own\n * database, the hub's projection catches up within a poll, and this notices.\n * No write crosses in either direction.\n */\n async #pairPoll(request: PairPollRequest): Promise<PlaneResult> {\n const codes = this.#deps.pairingCodes;\n if (!codes) {\n return fail(\n 501,\n \"bad-request\",\n \"this relay does not offer device-code pairing\",\n );\n }\n\n const pending = await codes.byDeviceCode(request.deviceCode);\n // Expired and never-existed answer the same way, deliberately: a poll\n // loop cannot tell them apart and does not need to, and distinguishing\n // them would let somebody test codes for existence.\n if (!pending) return ok({ status: \"expired\" });\n\n const approved = this.#deps.projection.deviceByFingerprint(\n pending.device.identity,\n );\n if (!approved) return ok({ status: \"pending\" });\n\n const sites = this.#deps.projection.sitesFor(approved.owner);\n // Approved with nothing consented is still approved: the machine exists,\n // it belongs to somebody, and the site set is a projection of consent\n // that changes on every heartbeat anyway. Refusing here would make a\n // brand-new account's first pairing fail for a reason it cannot act on\n // from a terminal.\n await this.#deps.state.seen({\n runnerId: approved.runnerId,\n owner: approved.owner,\n device: pending.device,\n // What this machine said it could run when it asked to pair, so the\n // approval screen and the machines page have an answer in the same\n // moment the device appears. The next heartbeat replaces it.\n capabilities: pending.capabilities,\n // A pairing request describes what a machine *can* run, not what it is\n // holding back — the daemon resolves defaults locally, and the first\n // heartbeat is where that answer arrives.\n withheld: [],\n });\n // Single use. The keypair is approved from here on and the code has no\n // further job; leaving it would be a second way to ask the same question.\n await codes.drop(pending.deviceCode);\n\n return ok({\n status: \"approved\",\n runnerId: approved.runnerId,\n owner: approved.owner,\n sites: Object.fromEntries(\n sites.map((record) => [keyId(record.site.identity), record.site]),\n ),\n // The key every later grant is checked against — Amendment J. Sent\n // here and nowhere else: pairing is the ceremony where a human is\n // already deciding whether to trust this upstream, so a key learned\n // here rides a decision that has been made rather than inventing one.\n ...(this.#deps.controlPlanePublic === undefined\n ? {}\n : { controlPlanePublic: this.#deps.controlPlanePublic }),\n });\n }\n\n /** Every authenticated call: signature first, then consent, then work. */\n async #authed<T>(\n input: { endpoint: string; rawBody: string; signature: unknown },\n body: unknown,\n schema: { safeParse: (v: unknown) => { success: boolean; data?: T } },\n run: (\n request: T,\n device: { runnerId: string; owner: string; device: PublicIdentity },\n ) => Promise<PlaneResult>,\n options: { allowRevoked?: boolean } = {},\n ): Promise<PlaneResult> {\n const signature = RequestSignature.safeParse(input.signature);\n if (!signature.success) {\n return fail(401, \"unauthorized\", \"this request is not signed\");\n }\n // **Presence is a cache, and a cache miss is not an answer about identity.**\n //\n // This used to refuse outright, which made a store blip into a fleet-wide\n // outage: the hub keeps presence in Valkey with no persistence and no\n // volume, so a reschedule — bin-packing on Autopilot, a node upgrade,\n // anything — dropped every record at once and every daemon alive was told\n // `this runner is not recognised` until a human re-paired it, one machine\n // at a time. The one-hour TTL was the same failure arriving more slowly.\n //\n // Who a runner is has never lived here. It lives in the projection, put\n // there by a person comparing a fingerprint, and this file already says so\n // three times over — revocation is asked of the projection rather than a\n // cached flag, for exactly this reason. So a miss is repaired from the\n // authority instead of being reported as a verdict.\n let known = await this.#deps.state.presence(signature.data.runnerId);\n let rebuilt = false;\n if (!known) {\n const approved = this.#deps.projection.deviceFor(signature.data.runnerId);\n // Still the honest refusal when the *projection* does not know it: no\n // human ever approved this machine, and no amount of signing changes\n // that.\n if (!approved) {\n return fail(401, \"unauthorized\", \"this runner is not recognised\");\n }\n known = {\n ...approved,\n lastSeenAt: this.#deps.now(),\n // Not invented. The heartbeat is the authority on what a machine can\n // run, and it is seconds away; claiming a matrix here would be this\n // file guessing about a backend it cannot see.\n capabilities: [],\n withheld: [],\n };\n rebuilt = true;\n }\n\n const failure = verifyRequest({\n identityPublic: known.device.identity,\n endpoint: input.endpoint,\n body: input.rawBody,\n signature: signature.data,\n now: this.#deps.now(),\n });\n if (failure === \"stale\") return this.#clockSkew();\n if (failure) return fail(401, \"unauthorized\", \"signature check failed\");\n\n // Written only now, and the order is the whole safety argument: a record\n // restored *before* the signature was checked would let anybody who knows\n // a runner id repopulate presence for a machine they do not hold the keys\n // to. A verified signature over this request is proof the caller holds the\n // key a human approved, which is the same proof pairing produced.\n if (rebuilt) {\n await this.#deps.state.seen({\n runnerId: known.runnerId,\n owner: known.owner,\n device: known.device,\n capabilities: [],\n // Nothing has described itself yet, so nothing is withheld — the\n // first heartbeat is the authority on both.\n withheld: [],\n });\n }\n\n // Asked of the projection, not of the cached flag.\n //\n // `known.revoked` is set by `heartbeat`, and enforcing on it made\n // revocation depend on the client calling an endpoint: a daemon that\n // simply never heartbeats would go on claiming after its consent was\n // withdrawn, forever. A well-behaved daemon beats every few seconds, which\n // is why the freeze gate's \"within one heartbeat\" demo passed and why this\n // was invisible — the guarantee held for every client that wanted it to.\n //\n // The cached flag survives as what heartbeat *reports* to the daemon. It\n // is a message, not an authority, and the authority is the projection.\n // Two copies of one value where one is a stale mirror of the other is this\n // project's most-repeated bug; here it was also an enforcement hole.\n // The whole relationship ending — cloud_008 finding 59, corrected by\n // V1-2. Per-site revocation is the *set* changing, which heartbeat\n // reports; this guard is the end of everything, and it is the only thing\n // that should refuse a call outright.\n //\n // It used to read \"nothing to serve\" as \"revoked\", which made an empty or\n // half-written projection indistinguishable from a human's decision — and\n // the daemon's answer to revocation is to delete its pairings file. One\n // bad push, every pin gone. `revokedOutright` asks for the evidence\n // instead: a revocation on record, and nothing left standing.\n const revoked = this.#deps.projection.revokedDevice(known.runnerId);\n if (revoked && options.allowRevoked !== true) {\n // Every endpoint, heartbeat included — V1-2. Heartbeat used to be\n // answerable by a revoked runner so it could be told through an empty\n // set; that inference is gone, because an empty set is also what a\n // half-written projection looks like. The refusal is the message now,\n // and it reaches even a daemon that never claims because no backend of\n // its own is running.\n return fail(403, \"revoked\", \"routing for this runner has been revoked\");\n }\n\n known.lastSeenAt = this.#deps.now();\n\n const parsed = schema.safeParse(body);\n if (!parsed.success || parsed.data === undefined) {\n return fail(400, \"bad-request\", \"request failed schema validation\");\n }\n return run(parsed.data, known);\n }\n\n /**\n * A clock too far from ours, said plainly and with the number to fix it by.\n *\n * Its own error code rather than a generic `unauthorized`, because it is the\n * one refusal a retry can never fix and an `ntpdate` always can — the same\n * reasoning `version-unsupported` already carries on the daemon side. A\n * daemon that reports this as a generic rejection sends its owner looking at\n * their network.\n *\n * `serverTime` is included so the far side can say *how far off* rather than\n * *that something is wrong*. It is not a disclosure: the heartbeat response\n * returns the same value, and so does every `Date` header.\n */\n #clockSkew(): PlaneResult {\n return clockSkewRefusal(this.#deps.now());\n }\n\n claim(\n auth: { endpoint: string; rawBody: string; signature: unknown },\n body: unknown,\n ): Promise<PlaneResult> {\n return this.#authed(auth, body, ClaimRequest, async (request, device) => {\n if (request.runnerId !== device.runnerId) {\n // The signature verified, so we know exactly who this is; the body\n // names somebody else. 403 with `forbidden` — V1-13. The site plane\n // has always answered its own version of this 403, and one wire\n // answering two ways is a difference a daemon cannot see the reason\n // for.\n return fail(403, \"forbidden\", \"runner id does not match the key\");\n }\n // One store call. The decision and its write are the store's, because a\n // caller that reads, filters and writes back cannot be made atomic once\n // the store is on a network (cloud_006 §3.2).\n const granted = await this.#deps.state.claim({\n runnerId: device.runnerId,\n owner: device.owner,\n device: device.device,\n // Every kind this device can run, from any of its services.\n //\n // This used to send only the *defaults* — the rows an unselected job\n // should reach — beside the whole menu as (kind, service) pairs. A\n // job names no service now, so there is no menu to match against and\n // no unselected case for a default to catch: a device answering a\n // kind at all is a candidate, and which of its services runs the work\n // is resolved from the person's mapping when the grant is signed.\n kinds: new Set(request.capabilities.map((c) => c.kind)),\n // The projection, collapsed to data the store can match on — a\n // predicate does not travel, and a set of (site, owner) pairs is\n // what a route is (cloud_009 §3).\n //\n // A paused consent (cloud_008 finding 48) drops its routes and\n // leaves the rest, which is the whole difference between \"we are\n // waiting for you to read something about one site\" and \"a human cut\n // you off from everything\".\n routes: this.#deps.projection.routesFor(device.owner),\n max: request.max,\n leaseMs: this.#deps.leaseMs,\n });\n\n /**\n * The grant, attached at claim and nowhere else — Amendment J.\n *\n * After the store's atomic claim, deliberately. A grant authored for a\n * job this device did not actually win would be a signed statement\n * about work somebody else is running, and the window between deciding\n * and writing is exactly where that goes wrong.\n *\n * A declined job is released here rather than sent bare. Sending it\n * would cost three round trips to reach an answer this side already\n * has, and a device refusing a job with no grant cannot tell \"the\n * control plane said no\" from \"the relay lost it\" — so it would report\n * the wrong thing.\n *\n * **Two release shapes, and the difference is not cosmetic.** A\n * permanent decline is released as `refused`, which means this job is\n * never offered to this device again — right for a person removed from\n * a team, because removal stops queued claims (hole 1).\n *\n * A transient one goes back in the queue **with a not-before**. Not\n * plainly: a plain release stays claimable by the same device, so it\n * would re-claim at once, be declined again, and spin — one control\n * plane read per turn, for a job that is not going to run there. A\n * mapping that resolved to another of the owner's machines, an\n * unfilled slot, or a store that was briefly unreachable are all\n * states the world can change, and \"ask again later\" needs a later.\n *\n * The relay does not read the reason. Branching on it here would be a\n * second implementation of a policy this process does not own.\n */\n const author = this.#deps.authorGrant;\n if (author === undefined) {\n return ok({ jobs: granted, leaseMs: this.#deps.leaseMs });\n }\n const withGrants: ClaimedStub[] = [];\n const refused: { jobId: string; leaseId: string }[] = [];\n const returned: { jobId: string; leaseId: string }[] = [];\n for (const job of granted) {\n const siteId = this.#deps.projection.siteIdForKey(job.site);\n const decision =\n siteId === null\n ? // A stub naming a site this projection cannot place. Transient\n // rather than permanent: the projection is what is behind, not\n // the job.\n { declined: { permanent: false, reason: \"unknown-site\" } }\n : await author({\n job,\n siteId,\n // Straight off the stub. A relay that computed this would be\n // choosing which site a grant says it is for, which is the\n // one thing the signature exists to take out of its hands.\n siteKey: job.site,\n // The site's own purpose, straight off the stub. A relay does\n // not interpret it — it does not hold the manifest and does\n // not hold the mapping; it carries the site's word to the one\n // party that can join them.\n ...(job.purpose === undefined ? {} : { purpose: job.purpose }),\n owner: device.owner,\n runnerId: device.runnerId,\n capabilities: request.capabilities,\n });\n if (decision.granted === undefined) {\n const lease = { jobId: job.id, leaseId: job.lease.id };\n (decision.declined.permanent ? refused : returned).push(lease);\n continue;\n }\n withGrants.push({ ...job, grant: decision.granted });\n }\n // Released rather than left leased. A job nobody may run should be back\n // in the queue for a device whose owner still may, not held by a lease\n // that has to time out first.\n if (refused.length > 0) {\n await this.#deps.state.releaseLeases({\n runnerId: device.runnerId,\n leases: refused,\n reason: \"refused\",\n });\n }\n if (returned.length > 0) {\n await this.#deps.state.releaseLeases({\n runnerId: device.runnerId,\n leases: returned,\n retryAfter: this.#deps.now() + RETRY_AFTER_MS,\n });\n }\n\n return ok({ jobs: withGrants, leaseMs: this.#deps.leaseMs });\n });\n }\n\n /**\n * Hand over the sealed payload, if the site has left one.\n *\n * The one endpoint whose behaviour differs from a direct site's, and the\n * difference is the whole design: a direct site seals here, on demand,\n * because it holds the keys. The relay waits. A `409` means \"claimed, not\n * yet sealed\" — a daemon should retry, not treat it as a refusal, because\n * the job is still legitimately theirs until the lease or the\n * awaiting-payload clock says otherwise.\n */\n fetch(\n auth: { endpoint: string; rawBody: string; signature: unknown },\n body: unknown,\n ): Promise<PlaneResult> {\n return this.#authed(auth, body, FetchRequest, async (request, device) => {\n const taken = await this.#deps.state.takePayload({\n jobId: request.jobId,\n runnerId: device.runnerId,\n leaseId: request.leaseId,\n });\n if (\"refused\" in taken) return REFUSALS[taken.refused];\n return ok({ envelope: taken.envelope });\n });\n }\n\n /**\n * Take a sealed result.\n *\n * The relay stores ciphertext and records the disposition so it can stop\n * dispatching. It cannot check the two against each other — that requires\n * opening the envelope, which is the site's job and the site's key. This is\n * the asymmetry byollm_009 §6.1 describes: the hint is actionable here and\n * only verifiable there.\n */\n result(\n auth: { endpoint: string; rawBody: string; signature: unknown },\n body: unknown,\n ): Promise<PlaneResult> {\n return this.#authed(auth, body, ResultRequest, async (request, device) => {\n // The same ceiling the site plane applies, on the way back — ratified\n // 2026-08-28. Both directions count against the pool and both are\n // refused by one limit; a rail that guarded only the inbound half would\n // be a relay a device could still fill.\n const bytes = envelopeBytes(request.envelope);\n if (bytes > MAX_ENVELOPE_BYTES) return tooLargeRefusal(bytes);\n\n const recorded = await this.#deps.state.complete({\n jobId: request.jobId,\n runnerId: device.runnerId,\n leaseId: request.leaseId,\n envelope: request.envelope,\n disposition: request.disposition,\n });\n if (\"refused\" in recorded) return REFUSALS[recorded.refused];\n return ok(recorded);\n });\n }\n\n heartbeat(\n auth: { endpoint: string; rawBody: string; signature: unknown },\n body: unknown,\n ): Promise<PlaneResult> {\n return this.#authed(\n auth,\n body,\n HeartbeatRequest,\n async (request, device) => {\n /**\n * Too old to serve — B052, checked here because here is where the\n * version is.\n *\n * The spec said \"connect/claim\". `ClaimRequest` carries no\n * `daemonVersion`, so claim cannot ask the question at all; the\n * heartbeat and the pairing are the two requests that name a\n * version, and a daemon heartbeats every few seconds, so this\n * refuses a straggler within one beat and it cannot claim without a\n * pairing anyway. Saying so rather than quietly enforcing somewhere\n * the spec did not mean.\n */\n const belowFloor =\n this.#deps.daemonFloor === undefined\n ? null\n : checkDaemonFloor({\n daemonVersion: request.daemonVersion,\n floor: this.#deps.daemonFloor,\n upgradeCommand: UPGRADE_COMMAND,\n });\n if (belowFloor !== null) {\n /* `floor` travels as a field, not only inside the sentence, so a\n surface can say \"two versions under\" without parsing English —\n the same rule the version handshake's `supported`/`minimum`\n follow, and the WireError refinement requires it on this code. */\n return {\n status: ERROR_STATUS[\"daemon-below-floor\"],\n body: {\n error: belowFloor.error,\n message: belowFloor.message,\n floor: belowFloor.floor,\n },\n };\n }\n\n const now = this.#deps.now();\n await this.#deps.state.sweep();\n\n // **Presence is recorded here, and it was not before.**\n //\n // `seen()` was called from the two pairing paths and nowhere else, so\n // `lastSeenAt` was written once when a machine paired and never moved\n // again. Everything downstream read it as liveness — the debug page's\n // who-is-online, and the `/devices` endpoint about to serve a\n // machines page — and it was reporting the pairing time under the\n // name \"last seen\". A field that is quietly a different fact is worse\n // than a missing one.\n //\n // The matrix rides along because it is the same fact on the same\n // schedule: this is the request that re-sends it, and a machine that\n // stopped heartbeating has not stopped being capable, it has stopped\n // being reachable. Recorded before the no-sites branch below, because\n // a machine with nothing consented is still online and still has\n // something to show.\n await this.#deps.state.seen({\n runnerId: device.runnerId,\n owner: device.owner,\n device: device.device,\n capabilities: request.capabilities,\n // Arrives on the same beat as the matrix and goes stale with it.\n // A daemon that resolves a contended kind stops sending it here,\n // which is what retires the owner's prompt to choose.\n withheld: request.withheld,\n });\n\n // Revocation is a fixture edit, and this is where the daemon learns\n // of it — within one heartbeat, which is what the freeze gate times.\n // The set, not a boolean — cloud_008 finding 59. A site that leaves\n // it is revoked for that site; the daemon drops that pin and keeps\n // the rest. An empty set is what `revoked: true` used to mean, and\n // the daemon reads it for itself rather than being told twice.\n const pinned = this.#deps.projection.sitesFor(device.owner);\n const sites = Object.fromEntries(\n pinned.map((record) => [keyId(record.site.identity), record.site]),\n );\n // How each of those keys can be traced back to one the daemon already\n // holds — byollm_009 Amendment C. Composed here rather than folded\n // into `sites` because `sites` is the one statement of which key is\n // current and this is evidence about how it got there; the relay\n // distributes both and can mint neither.\n //\n // Only for sites that have actually rotated, so the field is absent\n // for every site today and a daemon that has never seen a rotation\n // never parses one.\n const successions = Object.fromEntries(\n pinned\n .filter((record) => (record.succeeds?.length ?? 0) > 0)\n .map((record) => [\n keyId(record.site.identity),\n {\n succeeds: record.succeeds ?? [],\n ...(record.retiringUntil === undefined\n ? {}\n : { retiringUntil: record.retiringUntil }),\n },\n ]),\n );\n const rotations =\n Object.keys(successions).length > 0 ? { successions } : {};\n // A subset: paused sites keep their pin and route nothing, so the\n // daemon can name what the user has to go and read.\n\n const awaitingConsent = pinned\n .filter(\n (record) =>\n !this.#deps.projection.mayRouteFor(device.owner, record.siteId),\n )\n .map((record) => keyId(record.site.identity));\n\n if (pinned.length === 0) {\n // Nothing left to serve. The daemon abandons the queue rather than\n // finishing it, exactly as the direct plane's revoked branch does:\n // a daemon cannot see which upstream it is talking to, and the rule\n // must not depend on that.\n return ok({\n sites,\n ...rotations,\n ...updateOfferFor({\n offer: this.#deps.updateOffer,\n daemonVersion: request.daemonVersion,\n }),\n awaitingConsent,\n cancel: [],\n lost: request.activeLeases.map((lease) => ({\n jobId: lease.jobId,\n leaseId: lease.leaseId,\n })),\n serverTime: now,\n });\n }\n\n // Renewal and loss, from one read — cloud_008 §0.6. This used to\n // return `leases: []` unconditionally, which told a working daemon\n // every few seconds that nothing it held had been renewed while the\n // sweep requeued its work at `leaseMs`. Any job slower than a lease\n // was handed to a second device mid-flight.\n //\n // The renewal is the fix; reporting it back was not. §1.4b took that\n // field off the wire — no daemon ever read it, and `lost` answers the\n // same question in the direction a daemon can act on.\n // What the site withdrew — cloud_008 §2.2. This was the literal\n // `cancel: []`, so a site could not stop a job it had already\n // cancelled: the device went on running work whose result nobody\n // would accept, on somebody's own machine and at their expense.\n const cancel = await this.#deps.state.cancelRequests(device.runnerId);\n\n const { lost } = await this.#deps.state.renewLeases({\n runnerId: device.runnerId,\n leases: request.activeLeases,\n leaseMs: this.#deps.leaseMs,\n });\n\n return ok({\n sites,\n ...rotations,\n /**\n * The update offer — B053, and the ONLY way this field is set.\n *\n * `updateOfferFor` applies `mayOfferUpdate` inside itself and\n * returns a spreadable object, so there is no `updateTo:` anywhere\n * for a later hand to copy to a third return site. There are\n * already two, which is how a remember-to-check rule fails.\n *\n * HeartbeatResponse is `.strict()`: a wrong emission is not a bad\n * offer, it is every pre-.83 daemon rejecting every heartbeat.\n */\n ...updateOfferFor({\n offer: this.#deps.updateOffer,\n daemonVersion: request.daemonVersion,\n }),\n awaitingConsent,\n cancel,\n lost,\n serverTime: now,\n });\n },\n );\n }\n\n release(\n auth: { endpoint: string; rawBody: string; signature: unknown },\n body: unknown,\n ): Promise<PlaneResult> {\n return this.#authed(auth, body, ReleaseRequest, async (request, device) => {\n const released = await this.#deps.state.releaseLeases({\n runnerId: device.runnerId,\n leases: request.leases,\n // Was dropped here — cloud_008 §2.1. `reason` is on the wire, the\n // schema's own docstring says an upstream MUST record `refused`, and\n // this handler read every other field.\n reason: request.reason,\n });\n return ok({ released });\n });\n }\n}\n\n/**\n * Pairing, as the skeleton does it.\n *\n * Not the public `PairRequest`: that models a device-code exchange with a\n * human at a browser, and the skeleton's consent arrives from a fixture\n * instead (cloud_004 §14). The daemon-visible *outcome* is identical — a\n * runner id and the site's pinned public identity — so nothing downstream can\n * tell the difference, which is what makes this substitution honest rather\n * than a shortcut around the consent MUST.\n */\nconst PairFixtureRequest = z\n .object({\n protocolVersion: z.literal(PROTOCOL_VERSION),\n owner: z.string().min(1),\n device: PublicIdentity,\n })\n .strict();\n\n/** Exported so the debug page can name a device the way a human would. */\nexport const fingerprintOf = (identity: PublicIdentity): string =>\n keyId(identity.identity);\n","import { randomBytes } from \"node:crypto\";\nimport type { CapabilityMatrix, PublicIdentity } from \"@byollm/protocol\";\n\n/**\n * Pending pairing codes — cloud_009, the cloud-pairing flow.\n *\n * `byollm connect` speaks the device-code flow: ask for a code, show it, poll\n * while a human approves it in a browser. A relay had no way to hold that\n * pending state, so cloud pairing was never implemented — the hub accepted\n * only the shape where the device is *already* approved, and nothing in the\n * control plane created device rows at all. Every test passed because they\n * drive direct mode or seed the row with a service key: the checks proved the\n * parts and never the seam.\n *\n * ## Why the relay holds the code, and the control plane holds the decision\n *\n * The code is a short-lived handle on an *assertion* — \"this keypair would\n * like to be a machine\" — and the relay is allowed to hold assertions. The\n * approval is a human looking at a fingerprint, which belongs to the control\n * plane where that human is signed in.\n *\n * So nothing here approves anything. The daemon's poll asks whether the\n * control plane's projection now contains this device as approved, and the\n * answer comes from the projection rather than from a flag somebody set here.\n * That is what keeps the fence intact in both directions: the hub never\n * writes to the control plane, and the control plane never writes to the hub.\n *\n * ## What a code is worth on its own\n *\n * Nothing. Holding a device code lets you ask \"has anyone approved this\n * keypair yet\", and the answer is only ever yes for a keypair whose owner\n * approved it by eye. Stolen mid-flight it grants no access, which is why it\n * can be a URL-safe string a person reads aloud rather than a credential.\n */\n\n/** What the relay remembers between `start` and `poll`. */\nexport interface PendingPairing {\n /** The secret the daemon polls with. Never shown to a human. */\n readonly deviceCode: string;\n /** The short code a person reads and types into the dashboard. */\n readonly userCode: string;\n /** The keys the daemon presented. What a human is about to approve. */\n readonly device: PublicIdentity;\n /**\n * What the machine said it can run, as advertised when it asked to pair.\n *\n * Held so the approval screen can show a person what they are approving,\n * and so presence has an answer the moment the device appears rather than\n * one heartbeat later. It is a claim, like everything else in this record —\n * the heartbeat is the authority and replaces it within seconds.\n */\n readonly capabilities: CapabilityMatrix;\n /** Label the daemon offered, for the approval screen. */\n readonly label: string;\n readonly platform: string;\n /** Epoch ms. After this the code is gone, approved or not. */\n readonly expiresAt: number;\n}\n\n/**\n * What happened when a code was offered for storage.\n *\n * `put` can refuse, and the reason it can is the whole of the rate-limit\n * story on this surface: **anybody can ask to pair.** That is not a bug — a\n * machine with no pairing has no credential to present — but it means a\n * stranger with a script can mint pending codes in a loop, and each one\n * occupies memory in a shared store for ten minutes. Without a ceiling the\n * only limit is somebody's patience.\n *\n * So the store has a capacity and says so, and the daemon is told to try\n * again shortly rather than given a code that crowds out a real one. A cap is\n * a blunt instrument — under a flood, a person pairing a laptop is refused\n * alongside the attacker — but a refusal that resolves in ten minutes is a\n * better failure than a hub that stops routing. Per-IP limits belong at the\n * edge, where the IP actually is.\n */\nexport type PutResult = \"stored\" | \"at-capacity\";\n\n/**\n * What a caller is told when pairings are being refused for load.\n *\n * Exported because it is said in two places by two different limits. This\n * package says it when the store is at capacity; a deployment that adds a\n * per-IP budget in front (the hub does — cloud_014) says it when one source\n * has spent its share. **One sentence for one situation, whichever limit\n * produced it**: the person reading it in a terminal is told to try again\n * shortly, and which of the two bit is not a distinction they can act on.\n *\n * It lived inline here and the hub kept a copy, which is the one-value-two-\n * names defect this codebase keeps finding — and the copy that drifts would\n * drift silently, because both sentences would be plausible.\n */\nexport const PAIRING_BUSY_MESSAGE =\n \"too many pairings are in progress right now — try again in a few minutes\";\n\nexport interface PairingCodes {\n put(pending: PendingPairing): Promise<PutResult>;\n /** By the secret the daemon holds. */\n byDeviceCode(deviceCode: string): Promise<PendingPairing | undefined>;\n /** By the short code a human typed. */\n byUserCode(userCode: string): Promise<PendingPairing | undefined>;\n /** After a successful pairing, so a code is single-use. */\n drop(deviceCode: string): Promise<void>;\n}\n\n/**\n * Codes a person reads aloud, from an alphabet that survives being read aloud.\n *\n * Crockford's, minus the letters that become other letters over a phone: no\n * I/L/O/U. Eight characters in two groups — enough entropy that guessing is\n * not a strategy against a code that lives for ten minutes and grants nothing\n * on its own.\n */\nconst HUMAN_ALPHABET = \"0123456789ABCDEFGHJKMNPQRSTVWXYZ\";\n\nexport function newUserCode(): string {\n // `Array.from` rather than an indexed loop, so there is no index the\n // compiler cannot prove is in range and therefore no non-null assertion —\n // this codebase forbids them, and a `!` here would be one written for the\n // convenience of a loop rather than because anything was known.\n const groups = Array.from(randomBytes(8), (byte) =>\n HUMAN_ALPHABET.charAt(byte % HUMAN_ALPHABET.length),\n );\n return `${groups.slice(0, 4).join(\"\")}-${groups.slice(4).join(\"\")}`;\n}\n\n/** The secret half. Long and URL-safe; never shown to anybody. */\nexport const newDeviceCode = (): string =>\n randomBytes(32).toString(\"base64url\");\n\n/** How long a person has to walk to their browser and type eight characters. */\nexport const PAIRING_CODE_TTL_MS = 10 * 60 * 1000;\n\n/**\n * How many pairings may be in flight at once, across a whole relay.\n *\n * Sized against reality rather than fear: a pairing takes under a minute of\n * human attention, so five hundred outstanding at the same instant is a\n * number this product will not reach honestly for a long time — and one an\n * attacker reaches in a second. Small enough to bound the store, large enough\n * that nobody legitimate meets it.\n */\nexport const MAX_OUTSTANDING_PAIRINGS = 500;\n\n/**\n * The in-memory implementation, for the reference relay and its tests.\n *\n * The hub replaces it with one backed by Valkey, because a hub is two\n * replicas and a code minted on one must be pollable on the other — the same\n * reason its routing store is not a `Map`.\n */\nexport class MemoryPairingCodes implements PairingCodes {\n readonly #byDevice = new Map<string, PendingPairing>();\n readonly #now: () => number;\n readonly #capacity: number;\n\n constructor(\n now: () => number = Date.now,\n capacity: number = MAX_OUTSTANDING_PAIRINGS,\n ) {\n this.#now = now;\n this.#capacity = capacity;\n }\n\n #live(pending: PendingPairing | undefined): PendingPairing | undefined {\n if (!pending) return undefined;\n // Expiry is checked on read rather than swept: a code nobody asks about\n // costs nothing, and a sweep is a second place for the deadline to live.\n return pending.expiresAt > this.#now() ? pending : undefined;\n }\n\n put(pending: PendingPairing): Promise<PutResult> {\n // Expired entries are dropped before counting. Without this the cap would\n // latch: ten minutes of traffic would fill it and nothing would ever\n // pair again, which is a worse outage than the flood it defends against.\n const now = this.#now();\n for (const [code, held] of this.#byDevice) {\n if (held.expiresAt <= now) this.#byDevice.delete(code);\n }\n\n // One outstanding code per keypair. A daemon that restarts pairing —\n // a fat-fingered code, a second terminal — replaces its own pending\n // request instead of adding to the pile, so the code on screen is always\n // the live one. An attacker must mint a fresh keypair per code, which is\n // cheap; the ceiling below is what actually bounds them.\n const fingerprint = pending.device.identity;\n for (const [code, held] of this.#byDevice) {\n if (held.device.identity === fingerprint) this.#byDevice.delete(code);\n }\n\n if (this.#byDevice.size >= this.#capacity)\n return Promise.resolve(\"at-capacity\");\n\n this.#byDevice.set(pending.deviceCode, pending);\n return Promise.resolve(\"stored\");\n }\n\n byDeviceCode(deviceCode: string): Promise<PendingPairing | undefined> {\n return Promise.resolve(this.#live(this.#byDevice.get(deviceCode)));\n }\n\n byUserCode(userCode: string): Promise<PendingPairing | undefined> {\n const wanted = userCode.trim().toUpperCase();\n for (const pending of this.#byDevice.values()) {\n if (pending.userCode === wanted)\n return Promise.resolve(this.#live(pending));\n }\n return Promise.resolve(undefined);\n }\n\n drop(deviceCode: string): Promise<void> {\n this.#byDevice.delete(deviceCode);\n return Promise.resolve();\n }\n}\n","import {\n ERROR_STATUS,\n MAX_CLOCK_SKEW_MS,\n MAX_ENVELOPE_BYTES,\n} from \"@byollm/protocol\";\nimport type { PlaneResult } from \"./daemon-plane.js\";\n\n/**\n * The clock-skew refusal, in one place — cloud_008 §1.4, finding 17.\n *\n * Both planes verify a signature and both can fail it for a reason that is not\n * the key: a timestamp too far from ours to judge freshness. Only the daemon\n * plane said so. The site plane collapsed every `SignatureFailure` into\n * \"signature check failed\", so a site whose clock had drifted was told its\n * signature was wrong and sent to look at its keys — while `verifySiteRequest`\n * had already distinguished `stale` and thrown the distinction away.\n *\n * The remedy is the whole reason this is a separate code. \"Your key is wrong\"\n * and \"your clock is wrong\" are different problems with different fixes, and\n * only the server can tell them apart, because only the server holds the other\n * clock.\n *\n * One builder rather than one per plane, because two copies of a refusal is\n * how this one came to exist: the daemon plane's was written first and the\n * site plane's was written to a different standard three files away.\n */\nexport function clockSkewRefusal(now: number): PlaneResult {\n return {\n status: ERROR_STATUS[\"clock-skew\"],\n body: {\n error: \"clock-skew\",\n message:\n \"this request's timestamp is too far from the server's clock; \" +\n \"check the machine's time and try again\",\n // So the far side can say *how far off* rather than *that something is\n // wrong*. Not a disclosure: the heartbeat response returns the same\n // value, and so does every `Date` header.\n serverTime: now,\n maxSkewMs: MAX_CLOCK_SKEW_MS,\n },\n };\n}\n\n/**\n * The envelope is larger than the relay will hold — ratified 2026-08-28.\n *\n * A **relay-memory safety rail**, the same ceiling on every tier, and the same\n * refusal in both directions: a site attaching a payload and a device\n * returning a result reach it by different routes and hit one limit.\n *\n * ## Refused before acceptance, and nothing is written down\n *\n * That is the whole implementation, and it is why the cap needed no schema.\n * The size is known for the length of this check and is then gone — recording\n * a size in order to enforce a limit against it would be exactly the per-job\n * byte figure the metering ruling exists to not keep.\n *\n * ## `bad-request`, not a code of its own\n *\n * 413 is the semantically tidy status and a new `WireErrorCode` member is the\n * tidy code, and neither is worth what it costs here. Error codes are a\n * published enumeration that daemons and sites parse; a member added today\n * reaches a client shipped last month as an unrecognised value, and the\n * refusal it renders would be worse than the plain one. An over-size request\n * *is* a bad request, callers act on the code rather than the status, and the\n * right behaviour on both — do not retry, make it smaller — is the same.\n *\n * The message carries the limit and the remedy, because a ceiling somebody\n * cannot see the height of is a ceiling they hit twice.\n */\nexport function tooLargeRefusal(bytes: number): PlaneResult {\n /**\n * Rounded **up**, and only ever up.\n *\n * `toFixed` rounds to nearest, so a message one byte over the line printed\n * as \"this message is 10.0 MB and the limit is 10.0 MB\" — a refusal that\n * reads as a contradiction, given to somebody who now has no idea what to\n * change. Found by rendering the sentence rather than by asserting on it.\n *\n * Ceiling is also the honest direction. Understating how far over a message\n * is would send somebody to trim a hundred bytes off something that needs to\n * lose a megabyte; overstating by a tenth costs them nothing.\n */\n const mb = (n: number) =>\n `${(Math.ceil((n / (1024 * 1024)) * 10) / 10).toFixed(1)} MB`;\n const limit = `${(MAX_ENVELOPE_BYTES / (1024 * 1024)).toFixed(1)} MB`;\n return {\n status: ERROR_STATUS[\"bad-request\"],\n body: {\n error: \"bad-request\",\n message:\n `this message is ${mb(bytes)} and the limit is ` +\n `${limit} — every plan has the same ceiling, and it ` +\n \"is a limit on one message rather than on how many you send. Split \" +\n \"the work into smaller jobs and send them separately.\",\n },\n };\n}\n","import { fingerprintOf } from \"./daemon-plane.js\";\nimport type { RoutedJob } from \"./state.js\";\nimport type { RoutingStore } from \"./store.js\";\n\n/**\n * The debug page — cloud_004 §10.\n *\n * It exists because watching a stub get claimed beats reading store rows, and\n * it earns its keep from the first routed job rather than being a thing\n * someone builds later when routing is already hard to follow.\n *\n * One screen, no build step, no dependencies. It renders from the relay's own\n * state, so it cannot show anything the relay does not actually know — which\n * makes it an honest demonstration of blindness as well as a debugging tool.\n * There is no view here that could show a prompt, because there is no prompt\n * to show.\n */\n\nconst escape = (value: string): string =>\n value.replace(\n /[&<>\"]/g,\n (c) => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\" })[c] ?? c,\n );\n\nconst STATE_COLOUR: Record<string, string> = {\n queued: \"#8a8a8a\",\n \"awaiting-payload\": \"#c98a00\",\n ready: \"#0a7\",\n running: \"#06c\",\n done: \"#444\",\n};\n\nfunction jobRow(job: RoutedJob, now: number): string {\n const claimed = job.claimedBy;\n const waiting =\n job.state === \"awaiting-payload\" && job.awaitingUntil !== undefined\n ? `${String(Math.max(0, job.awaitingUntil - now))}ms left`\n : \"\";\n return `<tr>\n <td><code>${escape(job.id)}</code></td>\n <td>${escape(job.stub.kind)}</td>\n <td>${escape(job.stub.owner)}</td>\n <td>${escape(job.stub.audience)}</td>\n <td>${escape(job.stub.sizeClass)}</td>\n <td>${job.stub.streaming ? \"yes\" : \"no\"}</td>\n <td><b style=\"color:${STATE_COLOUR[job.state] ?? \"#000\"}\">${escape(job.state)}</b> <span class=\"dim\">${escape(waiting)}</span></td>\n <td>${claimed ? `<code>${escape(fingerprintOf(claimed.device))}</code>` : \"<span class='dim'>—</span>\"}</td>\n <td>${job.payload ? \"sealed\" : \"<span class='dim'>—</span>\"}</td>\n <td>${job.result ? escape(job.disposition ?? \"?\") : \"<span class='dim'>—</span>\"}</td>\n </tr>`;\n}\n\nexport async function debugPage(\n state: RoutingStore,\n now: number,\n /**\n * Asked whether each device's owner still consents — cloud_008 §2.3.\n *\n * The page used to read a `revoked` boolean off presence. That flag was a\n * stored copy of a fact the projection owns, and it is gone; the page asks\n * the authority instead, which is also the only thing that stays correct\n * when one daemon serves several sites.\n */\n routesFor?: { siteId: string; consents: (owner: string) => boolean },\n): Promise<string> {\n const jobs = await state.jobs();\n const devices = await state.everyone();\n\n return `<!doctype html>\n<html><head><meta charset=\"utf-8\"><title>byollm relay — debug</title>\n<meta http-equiv=\"refresh\" content=\"1\">\n<style>\n body{font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;margin:24px;color:#111;background:#fff}\n h1{font-size:15px;margin:0 0 4px} h2{font-size:13px;margin:24px 0 6px}\n table{border-collapse:collapse;width:100%;margin-top:4px}\n th,td{text-align:left;padding:4px 8px;border-bottom:1px solid #eee;vertical-align:top}\n th{font-weight:600;color:#666;border-bottom:1px solid #ccc}\n code{background:#f5f5f5;padding:1px 4px;border-radius:3px}\n .dim{color:#aaa} .note{color:#666;max-width:70ch;margin:8px 0 0}\n @media(prefers-color-scheme:dark){\n body{background:#111;color:#eee} th{color:#999;border-color:#333}\n td{border-color:#222} code{background:#1c1c1c} .note{color:#999}\n }\n</style></head><body>\n<h1>byollm relay — debug</h1>\n<p class=\"note\">Everything this relay knows, which is everything on this page.\nThere is no prompt or result text here because it holds none: payloads and\nresults are sealed to their endpoints and pass through as ciphertext.</p>\n\n<h2>Routed jobs (${String(jobs.length)})</h2>\n<table>\n<tr><th>job</th><th>kind</th><th>owner</th><th>audience</th><th>size</th>\n <th>stream</th><th>state</th><th>claimed by</th><th>payload</th><th>result</th></tr>\n${jobs.length ? jobs.map((j) => jobRow(j, now)).join(\"\\n\") : `<tr><td colspan=\"10\" class=\"dim\">nothing routed yet</td></tr>`}\n</table>\n\n<h2>Presence (${String(devices.length)})</h2>\n<table>\n<tr><th>runner</th><th>owner</th><th>fingerprint</th><th>last seen</th><th>routing</th></tr>\n${\n devices.length\n ? devices\n .map(\n (d) => `<tr>\n <td><code>${escape(d.runnerId)}</code></td>\n <td>${escape(d.owner)}</td>\n <td><code>${escape(fingerprintOf(d.device))}</code></td>\n <td>${String(Math.max(0, now - d.lastSeenAt))}ms ago</td>\n <td>${\n routesFor && !routesFor.consents(d.owner)\n ? \"<b style='color:#c00'>no consent</b>\"\n : \"active\"\n }</td>\n</tr>`,\n )\n .join(\"\\n\")\n : `<tr><td colspan=\"5\" class=\"dim\">no devices connected</td></tr>`\n}\n</table>\n</body></html>`;\n}\n","import {\n MAX_SUCCESSION_CHAIN,\n Succession,\n PublicIdentity,\n keyId as keyIdOf,\n} from \"@byollm/protocol\";\nimport { routeKey } from \"./state.js\";\nimport { z } from \"zod\";\n\n/**\n * What the relay is told about the world — cloud_004 §14.\n *\n * The relay decides nothing about who may talk to whom. It is handed a\n * projection of the control plane and routes according to it. Today that\n * projection is a file; later it is whatever the suite serves. Either way the\n * relay's own state is derived and disposable: delete it and the fixture\n * rebuilds it.\n *\n * ## This shape is a contract, not a test convenience\n *\n * cloud_004 §14 flags it and the flag is worth repeating here, where someone\n * will be tempted to add a field: **this is the projection contract.** The\n * first real control plane will be written to produce whatever this says, and\n * a field added carelessly now is a field the suite must produce forever.\n *\n * So two rules for anything added later:\n *\n * 1. **It must be something a control plane can actually know.** The relay\n * cannot be given facts that only a daemon or only a site holds — that is\n * how a blind relay stops being blind, one convenient field at a time.\n * 2. **It must be a decision, not a derivation.** Consent is a decision.\n * Presence is not: the relay learns that from heartbeats. Anything the\n * relay can observe does not belong in the projection.\n *\n * ## What is deliberately absent\n *\n * No private keys, of any party, ever. The relay holds public keys so it can\n * *verify* signatures and *tell a site who to seal to*. It holds no key that\n * can open anything, and {@link RelayFixture} has no field where one could be\n * put — `RELAY_BLIND` as a type, not as a promise.\n */\n\n/**\n * A site the control plane registered and domain-verified — cloud_004 §5.\n *\n * **The one authority for a site's public identity.** It used to be inlined on\n * every consent record, which meant a site's key had as many homes as it had\n * users and nothing checked they agreed — the exact shape this project has now\n * found in a version constant, a clock read, an envelope deadline, a reseal\n * implementation, a package list and a docs page. Consents now reference a\n * site by id and the key is looked up here.\n *\n * The relay needs it for two things it cannot do without:\n *\n * 1. **Telling a daemon who to pin** at pairing — the key that makes relayed\n * work unforgeable, since the relay holds no key that could produce it.\n * 2. **Authenticating the site plane.** A site calls a relay the way a daemon\n * does, signing with this identity, and this is the key those signatures\n * are checked against.\n */\nexport const SiteRecord = z\n .object({\n /** How the control plane names the site. */\n siteId: z.string().min(1),\n /**\n * The site's public identity.\n *\n * The relay distributes it and cannot use it: an identity key verifies\n * signatures and seals nothing. This is the key-exchange half of consent\n * (cloud_004 §3), and both endpoints pin what they receive.\n */\n site: PublicIdentity,\n /**\n * How this site's current key can be traced back to one a daemon holds —\n * byollm_009 Amendment C, ordered oldest last.\n *\n * A list rather than one predecessor because a daemon offline across two\n * rotations holds K1 and meets K3: with a single predecessor it could not\n * verify K3 without K2's record, so it would have to re-pair over\n * housekeeping it did not ask for. The proofs are small, self-verifying,\n * and kept indefinitely for the same reason.\n *\n * **The relay distributes these and cannot mint one.** Each is a signature\n * by a key it does not hold, which is what lets rotation be automatic\n * without becoming the substitution `SITES_LOCALLY_APPROVED` refuses.\n */\n succeeds: z.array(Succession).max(MAX_SUCCESSION_CHAIN).optional(),\n /**\n * Until when the retired key may still sign work — epoch ms.\n *\n * Absent on a site that has never rotated. The daemon holds its own clock\n * against this for the reason it holds its own allowlist: a projection\n * that could extend the window indefinitely would be a two-key site\n * forever, decided by the party the design does not trust.\n */\n retiringUntil: z.number().int().positive().optional(),\n })\n .strict();\nexport type SiteRecord = z.infer<typeof SiteRecord>;\n\n/**\n * A user's decision to let one site use their compute — cloud_004 §3.\n *\n * `CONSENT_BEFORE_ROUTE`: with no record here, the relay refuses to route,\n * and there is no discovery path that creates one. Consent is a click in the\n * control plane; the relay only ever reads the result.\n */\nexport const ConsentRecord = z\n .object({\n /** The user, as the control plane identifies them. */\n owner: z.string().min(1),\n /** Which site this consent is for. Scoped: consent is never global. */\n siteId: z.string().min(1),\n /**\n * The consent stands, and nothing routes under it — cloud_008 finding 48.\n *\n * The disclosure this user agreed to no longer describes their\n * arrangements: they read that their prompts stay on machines they own,\n * and they have since been added to a roster whose owner can read them.\n * Until they have been shown the other sentence and clicked, their work\n * does not move.\n *\n * **A third state, because the two we had are both wrong here.** Dropping\n * the consent makes `consentFor` return null, and the daemon plane reads\n * exactly that as revoked: heartbeat answers `revoked: true` with `lost:\n * all`, and the daemon prints \"this runner was revoked\" and *deletes its\n * pairing*. So a user whose team changed a setting would be told a human\n * cut them off, lose their pinned keys, and have to re-run `byollm\n * connect` after re-consenting. Under cloud_009 that is worse still: the\n * pairing is keyed by origin, so one stale consent would drop the pairing\n * for every other site reached through that hub.\n *\n * Reporting it as revoked is the same falsehood finding 48 exists to\n * delete, told one layer down. So the record stays, the relationship\n * stays, and the routing stops.\n */\n paused: z.boolean().default(false),\n })\n .strict();\nexport type ConsentRecord = z.infer<typeof ConsentRecord>;\n\n/**\n * A named group whose members may use a shared machine — cloud_004 §11.\n *\n * The roster lives here and **never reaches a site**. A site learns whether a\n * consenting user has reachable compute; it never learns who else is on the\n * roster. That is `ROSTERS_NEVER_LEAK` in cloud_004 §11.4, and the reason\n * this type has no outbound representation anywhere in this package.\n */\nexport const RosterRecord = z\n .object({\n /** Stable id for the group, used only inside the relay. */\n id: z.string().min(1),\n /** Who owns the shared compute. */\n owner: z.string().min(1),\n /** Members who may route to it. The owner is not implicitly a member. */\n members: z.array(z.string().min(1)),\n })\n .strict();\nexport type RosterRecord = z.infer<typeof RosterRecord>;\n\n/**\n * A device its owner has approved — cloud_005 §7.1.\n *\n * The relay refuses a device that is not here, and that refusal is the point.\n * byollm_009's seventh finding stopped a daemon from *naming* itself; this\n * stops it from *keying* itself. A device that presents keys nobody approved\n * is a device whose owner never saw a fingerprint, and pairing it would make\n * the relay the authority on identity — which is exactly what it must not be.\n *\n * The three-party shape consent already has, applied to identity: the device\n * asserts, a human confirms in the control plane, the relay checks.\n */\nexport const DeviceRecord = z\n .object({\n /** Who approved it. */\n owner: z.string().min(1),\n /** The id the control plane assigned — the device does not choose it. */\n runnerId: z.string().min(1),\n /** The keys a human compared a fingerprint of before approving. */\n device: PublicIdentity,\n /**\n * Whether this device — this one — has been revoked.\n *\n * Optional because a control plane that predates it says nothing, and\n * saying nothing must read as \"not revoked\": the alternative is a missing\n * field stopping every device on the fleet, which is the failure mode this\n * whole entry exists to end.\n *\n * Device-scoped by ruling (2026-09-03). Revocation used to be answered\n * from the owner's route-revocation list, so an account with any\n * revocation on record and no live site consents refused **every** device\n * it owned — including one paired thirty seconds earlier, whose daemon\n * then deleted its own pairings file. Revoking an experiment and pairing a\n * replacement, which is the ordinary first hour, killed the replacement.\n */\n revoked: z.boolean().optional(),\n })\n .strict();\nexport type DeviceRecord = z.infer<typeof DeviceRecord>;\n\n/** A revoked route, named by its parts. */\nexport const RevocationRecord = z\n .object({ owner: z.string().min(1), siteId: z.string().min(1) })\n .strict();\nexport type RevocationRecord = z.infer<typeof RevocationRecord>;\n\nexport const RelayFixture = z\n .object({\n /** Registered sites, by id. A consent for a site absent here routes not. */\n sites: z.array(SiteRecord).default([]),\n consents: z.array(ConsentRecord),\n devices: z.array(DeviceRecord).default([]),\n rosters: z.array(RosterRecord).default([]),\n /**\n * Routes that were revoked, as structured pairs.\n *\n * A separate list rather than deleting the consent record, because the\n * freeze gate needs revocation to be an observable *event* rather than an\n * absence — \"the row is gone\" and \"the row was revoked\" are different\n * answers to someone debugging why routing stopped.\n *\n * `{owner, siteId}` and never the composite string `\"owner:siteId\"`. A\n * composite key is a parser waiting to meet an id containing its\n * separator, which is the lesson the composite lease ids taught against\n * Postgres — applied here before it became a contract.\n */\n revoked: z.array(RevocationRecord).default([]),\n })\n .strict();\nexport type RelayFixture = z.infer<typeof RelayFixture>;\n\n/** An empty projection: nothing consented, so nothing routes. */\nexport const EMPTY_FIXTURE: RelayFixture = {\n sites: [],\n consents: [],\n devices: [],\n rosters: [],\n revoked: [],\n};\n\n/**\n * The relay's read-only view of the projection.\n *\n * Deliberately a handful of questions rather than the raw fixture: every\n * caller asking \"may this route?\" through one method is what makes\n * `CONSENT_BEFORE_ROUTE` reviewable, and it leaves room for the projection to\n * become a service without touching a single call site.\n */\nexport class Projection {\n #fixture: RelayFixture;\n\n constructor(fixture: RelayFixture = EMPTY_FIXTURE) {\n this.#fixture = RelayFixture.parse(fixture);\n }\n\n /** Replace the projection wholesale — the control plane pushed a new one. */\n replace(fixture: RelayFixture): void {\n this.#fixture = RelayFixture.parse(fixture);\n }\n\n /**\n * The site this id names, if the control plane registered it.\n *\n * The only source of a site's public identity in this package. Everything\n * that pins, verifies or seals to a site starts here.\n */\n siteFor(siteId: string): SiteRecord | null {\n return this.#fixture.sites.find((s) => s.siteId === siteId) ?? null;\n }\n\n /**\n * The device this runner id names, if a human approved it.\n *\n * Returns null for a device the control plane does not know, which is how\n * the relay refuses to be the authority on identity.\n */\n deviceFor(runnerId: string): DeviceRecord | null {\n return this.#fixture.devices.find((d) => d.runnerId === runnerId) ?? null;\n }\n\n /** The device approved for these exact keys, if any. */\n deviceByFingerprint(identityPublic: string): DeviceRecord | null {\n return (\n this.#fixture.devices.find((d) => d.device.identity === identityPublic) ??\n null\n );\n }\n\n /**\n * The consent binding this owner to this site, if it exists and stands.\n *\n * **Liveness, not routing.** A paused consent is returned here: the\n * relationship exists, the daemon is not revoked, the pairing stands. Ask\n * {@link Projection.mayRouteFor} before moving anybody's work — the two\n * questions have different answers and one method answering both is how a\n * paused user would quietly start routing again.\n */\n consentFor(owner: string, siteId: string): ConsentRecord | null {\n const revoked = this.#fixture.revoked.some(\n (r) => r.owner === owner && r.siteId === siteId,\n );\n if (revoked) return null;\n return (\n this.#fixture.consents.find(\n (c) => c.owner === owner && c.siteId === siteId,\n ) ?? null\n );\n }\n\n /**\n * Every site this owner may route with — cloud_009 §3.\n *\n * The set a pairing covers, and the set a claim will filter on. Consent\n * decides it, which is the sentence the whole design rests on: a site\n * appears here because a human clicked, never because a site asked to be\n * here and never because a daemon named it.\n *\n * **Paused sites are here, and that is deliberate** — cloud_008 finding 48\n * as ratified. A paused consent routes nothing and keeps its pin: the\n * relationship stands, the key the daemon compared a fingerprint of stays\n * pinned, and re-consenting never costs a re-pair. Written the other way\n * round first, and three of the paused tests failed by refusing to pair at\n * all — which is the trap the finding is about, arriving through the door\n * marked \"be stricter\".\n *\n * So this is the *pairing* set and `mayRouteFor` is the *routing* set. Two\n * questions with different answers, kept apart for the same reason\n * `consentFor` and `mayRouteFor` are: one method answering both is how a\n * paused user quietly starts routing again, or quietly loses their machine.\n *\n * Sorted by site id so two calls with the same projection produce the same\n * answer: this ends up in a pairings file and in a fingerprint list a human\n * compares by eye, and an order that drifts between polls is a diff nobody\n * can read.\n */\n sitesFor(owner: string): SiteRecord[] {\n return this.#fixture.sites\n .filter((site) => this.consentFor(owner, site.siteId) !== null)\n .sort((a, b) => (a.siteId < b.siteId ? -1 : 1));\n }\n\n /**\n * Which registered site owns this identity key id?\n *\n * A stub names its site by *key id* (Amendment A §A.3) so a daemon can\n * check it against a pinned key without a lookup. A control plane knows\n * sites by their account id. This is the one place that holds both, so it\n * is the one place that joins them — a control plane asked to accept key\n * ids would need its own copy of the registry.\n *\n * `null` for a key id no registered site carries, which is a projection\n * that is behind rather than a job that is wrong.\n */\n siteIdForKey(keyId: string): string | null {\n return (\n this.#fixture.sites.find(\n (record) => keyIdOf(record.site.identity) === keyId,\n )?.siteId ?? null\n );\n }\n\n /**\n * May this owner's work move for this site, right now?\n *\n * Consent exists, was not revoked, and is not paused. The routing question,\n * kept apart from {@link Projection.consentFor}'s liveness one so that a\n * caller has to pick which it means.\n */\n mayRouteFor(owner: string, siteId: string): boolean {\n const consent = this.consentFor(owner, siteId);\n return consent !== null && !consent.paused;\n }\n\n /**\n * Has *this device* been revoked — ruled 2026-09-03?\n *\n * This method has now been wrong in both directions, which is why it reads\n * the way it does.\n *\n * First it answered \"is there nothing to serve\", so an empty or half-written\n * projection was indistinguishable from a human's decision and cost every\n * daemon its pinned keys. The fix asked for evidence — a revocation on\n * record — but asked it of the **owner**, and added\n * `sitesFor(owner).length > 0` as a softener. That produced the opposite\n * failure: an account with any revocation and no live consents refused every\n * device it had, one paired seconds ago included.\n *\n * So: revocation is a fact about one device, never a mood about an owner.\n * This looks up the device that signed the request and reports what the\n * control plane says about *it*.\n *\n * The softener is gone with it. A guard whose answer changes with unrelated\n * state is not a guard — enabling a site must never be the thing that\n * un-revokes a machine, and under the old shape it was exactly that.\n *\n * A projection that knows nothing about a runner still says nothing here;\n * `deviceFor` is what refuses an unknown one, with 401, which is a different\n * sentence for a different situation.\n *\n * REVOCATION_IMMEDIATE is untouched: revoking device A still stops A on its\n * next call. It stops stopping B and C.\n */\n revokedDevice(runnerId: string): boolean {\n const device = this.#fixture.devices.find(\n (record) => record.runnerId === runnerId,\n );\n return device?.revoked === true;\n }\n\n /** Whether this pair is consented and paused — what heartbeat reports. */\n pausedFor(owner: string, siteId: string): boolean {\n return this.consentFor(owner, siteId)?.paused === true;\n }\n\n /**\n * Every (site, owner) route this device may run — cloud_009 §3.\n *\n * The claim filter, collapsed to data a store can match on. `routableOwners`\n * was this for one site; the hub needs it for the set, and the shape had to\n * change rather than repeat, because **a set of sites and a set of owners\n * multiply**. A device whose owner consented to site A, serving a roster\n * member who consented to site B, appears in both sets and has no consented\n * route between them. Pairs cannot express a route nobody agreed to.\n *\n * Both halves of the rule are here, and neither was enforced before finding\n * 48's work:\n *\n * - **This machine's owner** must have a live consent for the site, or\n * nothing of that site's runs here at all — including a roster member's\n * work. The roster says whose jobs may land on this machine; consent says\n * whether this machine is available to that site.\n * - **Each job's owner** must have one too. That check did not exist:\n * consent was enforced by the daemon plane's blanket revoked guard, which\n * asks only about the claiming device's owner, so a roster member who\n * never consented to a site could have their work claimed by their admin's\n * machine — `CONSENT_BEFORE_ROUTE` read the other way round.\n */\n routesFor(deviceOwner: string): Set<string> {\n const routes = new Set<string>();\n for (const site of this.#fixture.sites) {\n if (!this.mayRouteFor(deviceOwner, site.siteId)) continue;\n for (const owner of this.ownersRunnableBy(deviceOwner)) {\n if (!this.mayRouteFor(owner, site.siteId)) continue;\n routes.add(routeKey(site.siteId, owner));\n }\n }\n return routes;\n }\n\n /**\n * Every owner whose work this device's owner may run, as a list.\n *\n * The same question {@link mayRunFor} answers, asked in the direction a\n * *store* can use. That difference is the crux of making `claim` atomic\n * (cloud_006 §3.2).\n *\n * Today `claim` scans every job and calls `mayRunFor` per candidate, which\n * works because the projection is a local object. A shared routing store\n * cannot do that: the filter has to travel to the store, and a predicate\n * does not travel — you cannot send a closure to Valkey. So the projection\n * is collapsed to **data** here and handed over as a set the store can\n * match on.\n *\n * That the collapse is possible at all is a property of the design worth\n * noticing: `mayRunFor` is a finite lookup over consent and rosters, not a\n * computation over the jobs. If it ever became job-dependent — \"may run\n * work of this size\", say — an atomic claim would stop being expressible,\n * and that is the moment to argue rather than to add a parameter.\n *\n * The owner is always included: a device runs its owner's work, and the\n * relay checks that before it checks a roster.\n */\n ownersRunnableBy(deviceOwner: string): string[] {\n const owners = new Set([deviceOwner]);\n for (const roster of this.#fixture.rosters) {\n if (roster.owner !== deviceOwner) continue;\n for (const member of roster.members) owners.add(member);\n }\n return [...owners];\n }\n\n /**\n * May this device's owner run work belonging to `jobOwner`?\n *\n * The relay's half of `AUDIENCE_BOTH_SIDES`. It is only ever a *narrowing*:\n * the daemon re-checks its own allowlist locally and may still refuse, and\n * the site's audience already bounded who could be offered the job. A relay\n * that answered `true` for everyone would not widen anything — which is\n * exactly the property that lets it be blind.\n */\n mayRunFor(deviceOwner: string, jobOwner: string): boolean {\n if (deviceOwner === jobOwner) return true;\n return this.#fixture.rosters.some(\n (r) => r.owner === deviceOwner && r.members.includes(jobOwner),\n );\n }\n}\n","import {\n MAX_ENVELOPE_BYTES,\n PROTOCOL_VERSION,\n envelopeBytes,\n keyId,\n JobStub,\n RequestSignature,\n SealedEnvelope,\n verifySiteRequest,\n} from \"@byollm/protocol\";\nimport { z } from \"zod\";\nimport type { PlaneResult } from \"./daemon-plane.js\";\nimport type { Projection } from \"./fixture.js\";\nimport { clockSkewRefusal, tooLargeRefusal } from \"./refusals.js\";\nimport type { RoutingStore } from \"./store.js\";\n\n/**\n * The plane a site talks to.\n *\n * **Outbound from the site, like everything else in this product.** A relay\n * that called site webhooks would need every site publicly reachable, which is\n * the connectivity problem the hub exists to delete — and it would put the\n * relay in the position of initiating contact, which is the posture the whole\n * design avoids. So a site polls, exactly as a daemon does, and the relay\n * never opens a connection to anyone.\n *\n * ## The three-beat exchange\n *\n * A site cannot seal at enqueue: a payload is encrypted to the device that\n * claims it, and at enqueue nobody has. So enqueue publishes a **stub**, and\n * sealing happens later, on demand:\n *\n * 1. `enqueue` — here is a stub; route it.\n * 2. `pending` — who claimed anything of mine, and what key do I seal to?\n * 3. `payload` — here is the ciphertext for that device.\n *\n * Then `results` collects what comes back. Four endpoints, all polled, none of\n * which ever carries a plaintext or a private key.\n *\n * The gap between beats 2 and 3 is the `awaiting-payload` state, and the\n * reason it needs its own timeout: a site that dies between them leaves a\n * device holding a job whose work will never arrive.\n *\n * ## Every call is signed, and this plane once was not\n *\n * A site authenticates exactly as a daemon does: it signs each request with\n * the identity key the control plane registered for it, and the relay checks\n * that signature against the projection. Nothing here trusts a `siteId` in a\n * body or a query string.\n *\n * This was the ninth finding, and it was found by reading the code in\n * preparation for the first public deploy rather than by any test — the whole\n * plane took the caller's word for who it was. What that bought an anonymous\n * caller, against a relay reachable on the internet:\n *\n * - **`enqueue` as anyone.** Publish stubs in a site's name and consenting\n * users' machines claim them. The payload that follows is sealed by the real\n * site or not at all, so no forged *work* runs — but unsolicited dispatch to\n * private hardware is a product-level breach whatever the ciphertext does.\n * - **`payload` as anyone**, over a live claim: substitute an envelope the\n * daemon will refuse to open, and the job is burned rather than run.\n * - **`pending` and `results` as anyone**: a metadata read of who is online\n * for a site, which device claimed what, and every lease id in flight.\n *\n * `RELAY_BLIND` held throughout — none of it opens a payload, which is the\n * point of building it that way. But blind is not the same as safe, and the\n * distance between them is this file.\n */\n\nconst EnqueueRequest = z\n .object({\n protocolVersion: z.literal(PROTOCOL_VERSION),\n siteId: z.string().min(1),\n /**\n * Everything the relay learns about the job.\n *\n * `JobStub` is exhaustive by construction and asserted so in the protocol\n * package — a site that tried to attach a prompt here would be refused by\n * the schema, not by a reviewer.\n */\n stub: JobStub,\n })\n .strict();\n\nconst PayloadRequest = z\n .object({\n protocolVersion: z.literal(PROTOCOL_VERSION),\n siteId: z.string().min(1),\n jobId: z.string().min(1),\n /** Sealed to the claiming device. Opaque to us and to the schema. */\n envelope: SealedEnvelope,\n })\n .strict();\n\n/** A read: the site id arrives in the query and is signed as an empty body. */\n/** What a site sends to withdraw a job. */\nconst CancelRequest = z\n .object({\n protocolVersion: z.literal(PROTOCOL_VERSION),\n siteId: z.string().min(1),\n jobId: z.string().min(1),\n })\n .strict();\n\n// No version here: a read declares it in the query string, which is where\n// `declaredVersion` looks for a GET, and this object is built from that same\n// query rather than parsed from a body.\nconst QueryRequest = z.object({ siteId: z.string().min(1) }).strict();\n\nconst ok = (body: unknown): PlaneResult => ({ status: 200, body });\nconst fail = (status: number, error: string, message: string): PlaneResult => ({\n status,\n body: { error, message },\n});\n\n/**\n * Whether a purpose can be satisfied for this person, asked at enqueue.\n *\n * The relay does not hold the answer and must not: a relay that filtered on\n * mappings would hold the mapping, which is the one thing it cannot have. So\n * it asks — of the control plane, which already answers the same question at\n * claim, from the same authority, a moment later.\n *\n * Three replies, because three things are true at three different times.\n * `not-declared` is the site's own manifest and is fixed by the developer.\n * `unmapped` is the person's own dashboard and is fixed by them. `ok` covers\n * everything the transient path was always for: declared, mapped, and no\n * device able to claim right now.\n *\n * Optional, because a self-hosted relay may have no control plane. When it is\n * absent nothing is refused — and the relay says so at boot and on its health\n * surface, because a check that quietly is not there is the skipping-check law\n * wearing deployment.\n */\nexport interface SitePlaneDeps {\n readonly state: RoutingStore;\n readonly projection: Projection;\n readonly now: () => number;\n /**\n * Asked once per enqueue, when a control plane is present.\n *\n * What this teaches the relay is one bit it did not previously hold:\n * whether this owner has *a* mapping for this purpose. Existence, never\n * which service — that stays in the control plane, and this is recorded in\n * the enumerated-metadata commitment so the list stays exhaustive.\n */\n readonly satisfiable?: Satisfiable;\n /**\n * The one site this relay routes for.\n *\n * The same value the daemon plane holds, from the same option, because it is\n * the same fact — a relay that accepted enqueues for sites its daemons never\n * paired with would route work nobody can open.\n */\n}\n\n/**\n * The enqueue-time question, declared once.\n *\n * It was written out twice — here and on `RelayOptions` — and by the time\n * 019 added a fourth verdict the two copies disagreed, so a relay could be\n * handed an answer its own options type said was impossible. **A shape\n * declared in two places is two places for it to drift.**\n */\nexport type Satisfiable = (query: {\n readonly siteId: string;\n readonly owner: string;\n readonly purpose: string | undefined;\n readonly kind: string;\n}) => Promise<{\n readonly verdict: \"ok\" | \"not-declared\" | \"unmapped\" | \"waiting\";\n}>;\n\n/** What `Relay.handle` reconstructs from the request, for signature checking. */\nexport interface SiteAuth {\n /** The endpoint name alone — the domain separator is applied by protocol. */\n readonly endpoint: string;\n /** The exact bytes received, hashed into the signature. */\n readonly rawBody: string;\n /** From the headers, or undefined if any part was missing. */\n readonly signature: unknown;\n}\n\nexport class SitePlane {\n readonly #deps: SitePlaneDeps;\n\n constructor(deps: SitePlaneDeps) {\n this.#deps = deps;\n }\n\n /**\n * Signature first, then the site id, then the work.\n *\n * The caller is whoever the signature says, verified against the key the\n * control plane registered — never whoever the request claims. The `siteId`\n * every request carries is then required to *match* that caller, so the two\n * can never name different sites; a request that says one thing in its\n * signed material and another in its body is refused rather than reconciled.\n *\n * Every endpoint goes through here, including the reads. That is deliberate:\n * an authenticated write plane beside an open read plane would still hand a\n * stranger presence, claims and lease ids, and \"who is online right now\" is\n * exactly the fact a blind relay is otherwise so careful not to reveal.\n */\n async #authed<T>(\n auth: SiteAuth,\n body: unknown,\n schema: { safeParse: (v: unknown) => { success: boolean; data?: T } },\n siteIdOf: (request: T) => string,\n run: (request: T, siteId: string) => Promise<PlaneResult>,\n ): Promise<PlaneResult> {\n const signature = RequestSignature.safeParse(auth.signature);\n if (!signature.success) {\n return fail(401, \"unauthorized\", \"this request is not signed\");\n }\n // The signature's caller slot carries the site id (byollm_009 §4.2's\n // site-plane note). Resolving the key through the site registry rather\n // than the device registry is what keeps a device signature from ever\n // authenticating as a site.\n const siteId = signature.data.runnerId;\n const site = this.#deps.projection.siteFor(siteId);\n if (!site) {\n return fail(401, \"unauthorized\", \"this site is not registered\");\n }\n\n /**\n * Which keys may sign for this site right now — byollm_009 Amendment C.\n *\n * The current one, and — while a retirement window is open — the key it\n * just superseded. **Both keys route while the window is open** (C.2): a\n * site that rotated is a site with two processes mid-deploy and a queue of\n * work signed a minute ago, and refusing the old key the instant the\n * record moves makes rotation a flag day.\n *\n * The site id in the caller slot does not change across a rotation — it is\n * the control plane's name for the site, not a key id — so this cannot be\n * decided by looking at who is calling. It is decided by which key\n * verifies, which is the honest question.\n *\n * The predecessors come from the chain the site itself signed, so this\n * widens nothing: a key that can authenticate here is one the current key\n * has vouched for in a statement naming both. And the window is measured\n * against the relay's clock, because a window a caller could assert would\n * not be a window.\n */\n const acceptable = [\n site.site.identity,\n ...(site.retiringUntil !== undefined &&\n this.#deps.now() < site.retiringUntil\n ? (site.succeeds ?? []).map((link) => link.identity.identity)\n : []),\n ];\n\n // Tried in order, current key first, and the *last* failure is the one\n // reported: a site whose clock has drifted fails every key with `stale`,\n // and telling it its signature was wrong would send it looking in the one\n // place the problem is not — finding 17, which this loop could quietly\n // undo.\n // Seeded with a refusal rather than left unassigned: `acceptable` always\n // has at least the current key, but a loop that can be entered zero times\n // must not be able to fall through into an authenticated request.\n let failure: ReturnType<typeof verifySiteRequest> = \"bad-signature\";\n for (const identityPublic of acceptable) {\n failure = verifySiteRequest({\n identityPublic,\n endpoint: auth.endpoint,\n body: auth.rawBody,\n signature: signature.data,\n now: this.#deps.now(),\n });\n if (!failure) break;\n // A stale signature is stale against every key; no point asking again.\n if (failure === \"stale\") break;\n }\n // `stale` is not a bad signature — cloud_008 §1.4, finding 17.\n //\n // `verifySiteRequest` distinguishes the two and this used to throw the\n // distinction away, so a site whose clock had drifted was told its\n // signature was wrong. The daemon plane had said so correctly for weeks;\n // the site plane three files over had not, which is what two\n // implementations of one refusal looks like from the inside.\n // Ordered so a failure kind added later reports `unauthorized` rather\n // than claiming a clock problem nobody diagnosed.\n if (failure === \"stale\") return clockSkewRefusal(this.#deps.now());\n if (failure) return fail(401, \"unauthorized\", \"signature check failed\");\n\n const parsed = schema.safeParse(body);\n if (!parsed.success || parsed.data === undefined) {\n return fail(400, \"bad-request\", \"request failed schema validation\");\n }\n if (siteIdOf(parsed.data) !== siteId) {\n return fail(403, \"forbidden\", \"that is not your site\");\n }\n // This relay routes for exactly one site, and now says so.\n //\n // The daemon plane has always been single-tenant — `DaemonPlaneDeps.siteId`\n // is the field multi-tenancy replaces — but the site plane accepted any\n // registered site, and `claim` never looked at a job's `siteId` at all. A\n // second registered site's jobs would therefore be offered to a daemon\n // paired with the first, which pinned a different key and could only fail\n // to open the payload. Contained by the crypto, and still a job burned by\n // routing rather than by anything the device did.\n // Registered, rather than \"the one site this relay was configured with\"\n // — cloud_009 §3. Every registered site is routable now; what a relay\n // refuses is a caller naming a site its projection does not hold.\n //\n // Which is checked **above**, where the signature is resolved: a site the\n // projection does not hold has no key to verify against and is refused\n // 401 before reaching here. The second check that used to stand at this\n // line was dead — V1-17 — and dead guards are worse than absent ones:\n // they read as the enforcement, so the day somebody moves the real check\n // they leave this one behind and nothing looks different.\n return run(parsed.data, siteId);\n }\n\n enqueue(auth: SiteAuth, body: unknown): Promise<PlaneResult> {\n return this.#authed(\n auth,\n body,\n EnqueueRequest,\n (request) => request.siteId,\n async (request, siteId) => {\n // The stub names a site; the signature says who is asking. They have\n // to agree — Amendment A §A.3.\n //\n // Same rule the `siteId` in the body already follows, applied one\n // level in: a caller that could publish stubs naming *another* site\n // would be handing that site's daemons work sealed by the wrong key,\n // and every one of them would report a corrupt envelope rather than an\n // impersonation. `siteFor` is non-null here — `#authed` resolved the\n // caller through it — and the optional chain is what makes a later\n // edit to that invariant produce a refusal instead of a crash.\n const registered = this.#deps.projection.siteFor(siteId);\n if (\n !registered ||\n keyId(registered.site.identity) !== request.stub.site\n ) {\n return fail(\n 403,\n // V1-13, and one of the five the ruling itself named: an\n // identified site claiming another site's stub is `forbidden`.\n \"forbidden\",\n \"that stub does not name the site that signed it\",\n );\n }\n\n /**\n * Refused here, or never — the two answers a site can act on.\n *\n * Both are knowable now and neither becomes knowable later. A purpose\n * the manifest does not declare will not appear in it by waiting, and\n * a person who maps a slot thirty seconds from now is served by the\n * next job, which is the same thirty seconds. Queuing either would be\n * a poll wearing a promise — and worse, a job the site has already\n * fallen back on must never be served afterwards.\n *\n * The third case is the one the transient path was always for:\n * declared, mapped, and nothing able to claim it right now.\n */\n /**\n * A read that failed is not a negative answer — and not a 500 either.\n *\n * This call reaches the control plane's policy store. A blip there —\n * a connection reset, a failover, a pool exhausted — threw straight\n * out of `handle`, so every cloud-lane enqueue became `internal`\n * while the database caught its breath.\n *\n * The property that matters already held: nothing turns a failed read\n * into `not-declared` or `unmapped`, so no job was ever refused for a\n * reason nobody could check. What was wrong is what the site was\n * told. `internal` says \"we are broken and you should stop\"; this is\n * a transient condition, and the honest answer is ask again.\n *\n * 503 rather than a 409, deliberately. The enqueue endpoint's 409\n * class *is* the refusal class — an unknown code there is read as\n * `EnqueueRefused` and the job is abandoned. A transient failure\n * arriving in that class would tell a site to give up on a job the\n * relay never even evaluated.\n *\n * Not swallowed into \"satisfiable\" either, which would be the other\n * tempting shape: accepting the job and letting it expire is the\n * pre-alpha.65 behaviour, and the whole point of that release was\n * that a slot nobody can answer should not cost the site a TTL.\n */\n let answer;\n try {\n answer = await this.#deps.satisfiable?.({\n siteId,\n owner: request.stub.owner,\n purpose: request.stub.purpose,\n kind: request.stub.kind,\n });\n } catch {\n // The reason stays here. A site learns that we could not answer,\n // never that a database was the thing that could not.\n return fail(\n 503,\n \"server-error\",\n \"we could not check this just now — try again shortly\",\n );\n }\n if (answer?.verdict === \"not-declared\") {\n return fail(\n 409,\n \"purpose-not-declared\",\n `this site does not declare ${request.stub.purpose ?? \"that purpose\"} — ` +\n \"declare it on Developer Sites, and the people who have already \" +\n \"connected will each map the new slot before it routes\",\n );\n }\n if (answer?.verdict === \"unmapped\") {\n // One sentence, never why. Which service, whose device and whether\n // one exists are all the person's, and a site learns only that the\n // slot is unsatisfiable — the opacity is the promise, not a\n // side-effect of it.\n return fail(\n 409,\n \"slot-unsatisfiable\",\n \"nobody has chosen what answers this yet\",\n );\n }\n if (answer?.verdict === \"waiting\") {\n /**\n * The one bit beyond unsatisfiable a site may learn — 019 §6.3.\n *\n * **Does this need the person, or only time.** Above: somebody has\n * to go and choose a model, and no amount of waiting helps. Here:\n * the slot may recover with nobody acting, so retrying later is the\n * right fallback and sending the person to a settings page is not.\n *\n * A separate code rather than a field, and that is not a shortcut:\n * this endpoint's 409 class is the refusal class, and an unknown\n * code in it is already read as a refusal by every deployed client.\n * A site that has never heard of this one still learns its job was\n * refused, which is the fact it needs.\n *\n * Carries no duration — a duration leaks which block was hit, and\n * which block was hit says how much somebody has been working\n * today. Carries no cause: device asleep, service unhealthy and\n * account blocked are one sentence here, which is what makes the\n * bit safe. It is about the slot's future, not the person's day.\n */\n return fail(\n 409,\n \"slot-waiting\",\n \"nothing can answer this right now — try again later\",\n );\n }\n\n const job = await this.#deps.state.enqueue({\n id: request.stub.id,\n siteId,\n stub: request.stub,\n });\n // Idempotent by id **within a site** — cloud_008 finding 58. A known\n // id from the same site is that site's republish and returns what is\n // already routing; a known id from another site is refused rather\n // than answered with the other site's job.\n //\n // The comment here used to say the multi-tenant router would need a\n // collision check \"when it can be exercised\", on the argument that an\n // unreachable guard is a test that cannot fail. That was right about\n // the test and wrong about where the guard belongs: the store owns\n // idempotency, so the store owns the exception to it, and the check\n // is exercised there against both implementations. This is the wire's\n // half of it.\n if (\"refused\" in job) {\n // **Says nothing about why** — cloud_008 finding 58, second pass.\n //\n // \"that job id belongs to another site\" is a cross-tenant existence\n // oracle: a site that guessed or was leaked an id could confirm\n // another tenant holds it. Ids are random, so enumeration is not\n // practical, and a confirmation should still not be available for\n // the asking.\n //\n // This reduces the leak and does not remove it, which is worth\n // saying rather than claiming a fix: a site knows its own stub is\n // well-formed, so *any* refusal it can tell apart from success is\n // the confirmation, whatever the message says. The only real fix is\n // a collision that cannot happen — per-site keys, cloud_009 §3's\n // first store decision — and this refusal disappears with it.\n return fail(400, \"bad-request\", \"that stub was not accepted\");\n }\n return ok({ jobId: job.id, state: job.state });\n },\n );\n }\n\n /**\n * What needs sealing, and who to seal it to.\n *\n * The response carries the claiming device's **public** keys — which is the\n * entire reason a blind relay can exist. The relay is a directory here, not\n * a participant: it tells the site an address, and what the site sends to\n * that address is unreadable on the way through.\n */\n pending(auth: SiteAuth, siteId: string): Promise<PlaneResult> {\n return this.#authed(\n auth,\n { siteId },\n QueryRequest,\n (request) => request.siteId,\n async (_request, site) => {\n await this.#deps.state.sweep();\n const jobs = (await this.#deps.state.awaiting(site)).map((job) => ({\n jobId: job.id,\n // Non-null by construction: `awaiting` only returns claimed jobs.\n // The optional chain is here so a future state-machine edit that\n // broke that invariant would produce a missing field rather than a\n // crash on the routing path.\n device: job.claimedBy?.device,\n runnerId: job.claimedBy?.runnerId,\n leaseId: job.claimedBy?.leaseId,\n /** So a site can decline to seal for a claim about to expire. */\n awaitingUntil: job.awaitingUntil,\n /**\n * When the *grant* ends — cloud_008 §0.6.\n *\n * Distinct from `awaitingUntil`, which bounds how long this relay\n * waits for the site to seal. A site adopting the lease into its own\n * records needs the lease's clock; given the other one it recorded a\n * grant that expired in seconds, then refused the device's own\n * result for want of a matching lease.\n */\n leaseExpiresAt: job.claimedBy?.leaseExpiresAt,\n }));\n return ok({ jobs });\n },\n );\n }\n\n /**\n * The site withdraws a job — cloud_008 §2.2.\n *\n * Signed and site-scoped like every other site-plane call. A cancellation\n * is not a delete: a device already running the job has to be told, and it\n * hears at its next heartbeat.\n */\n cancel(auth: SiteAuth, body: unknown): Promise<PlaneResult> {\n return this.#authed(\n auth,\n body,\n CancelRequest,\n (request) => request.siteId,\n async (request, siteId) => {\n const cancelled = await this.#deps.state.cancel({\n jobId: request.jobId,\n siteId,\n });\n // Idempotent, and quiet about what it did not find: a site asking\n // twice is ordinary, and answering differently for \"already\n // cancelled\" and \"never existed\" would tell an unrelated caller\n // whether an id is real.\n return ok({ cancelled });\n },\n );\n }\n\n payload(auth: SiteAuth, body: unknown): Promise<PlaneResult> {\n return this.#authed(\n auth,\n body,\n PayloadRequest,\n (request) => request.siteId,\n async (request, siteId) => {\n // The ceiling, before acceptance — ratified 2026-08-28. Refused here\n // rather than after the store call, because the point of a\n // relay-memory rail is that the oversized thing is never held.\n const bytes = envelopeBytes(request.envelope);\n if (bytes > MAX_ENVELOPE_BYTES) return tooLargeRefusal(bytes);\n\n // One store call: the check and the write together. A site that read\n // \"awaiting-payload\" and then wrote would be racing the timeout that\n // makes the state mean anything.\n const sealed = await this.#deps.state.seal({\n jobId: request.jobId,\n siteId,\n envelope: request.envelope,\n });\n if (\"refused\" in sealed) {\n return sealed.refused === \"not-found\"\n ? fail(404, \"not-found\", \"unknown job\")\n : fail(\n 409,\n \"too-late\",\n `job is ${sealed.was ?? \"gone\"}, not awaiting payload`,\n );\n }\n return ok({ jobId: request.jobId, state: sealed.state });\n },\n );\n }\n\n /** Sealed results, for the site to open and verify. */\n results(auth: SiteAuth, siteId: string): Promise<PlaneResult> {\n return this.#authed(\n auth,\n { siteId },\n QueryRequest,\n (request) => request.siteId,\n async (_request, site) => {\n const jobs = (await this.#deps.state.finished(site)).map((job) => ({\n jobId: job.id,\n envelope: job.result,\n disposition: job.disposition,\n runnerId: job.claimedBy?.runnerId,\n /** The grant the site adopted, so it can complete against it. */\n leaseId: job.claimedBy?.leaseId,\n /**\n * Which device ran it, so the site can verify the signature against\n * the key it was told to seal to — and so `PROVENANCE_NAMES_DEVICE` can\n * name a foreign device rather than guessing (cloud_004 §11.2).\n */\n device: job.claimedBy?.device,\n /**\n * Whose machine ran it — cloud_008 §2.5, finding 41.\n *\n * The relay has held this since the claim: `claimedBy.owner` is the\n * owner id the *projection* supplied, in the same namespace the\n * direct plane's `runnerOwner` uses. The cloud lane was filling that\n * field with `keyId(device.identity)` instead — a key id where every\n * other plane puts a user id, so an app comparing provenance across\n * lanes compared two namespaces for equality and got `false` for\n * the same person.\n */\n runnerOwner: job.claimedBy?.owner,\n }));\n return ok({ jobs });\n },\n );\n }\n}\n"],"mappings":"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","names":["MAX_ENVELOPE_BYTES","ERROR_STATUS","ERROR_STATUS","MAX_ENVELOPE_BYTES","PublicIdentity","z","PublicIdentity","keyId","MAX_ENVELOPE_BYTES","PROTOCOL_VERSION","envelopeBytes","keyId","RequestSignature","z","z","PROTOCOL_VERSION","ok","fail","RequestSignature","keyId","envelopeBytes","MAX_ENVELOPE_BYTES"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byollm/relay",
3
- "version": "0.1.0-alpha.83",
3
+ "version": "0.1.0-alpha.84",
4
4
  "type": "module",
5
5
  "description": "The reference relay: routes sealed byollm jobs between sites and daemons, holding no decryption keys by construction.",
6
6
  "license": "MIT",
@@ -31,7 +31,7 @@
31
31
  ],
32
32
  "dependencies": {
33
33
  "zod": "^4.1.13",
34
- "@byollm/protocol": "0.1.0-alpha.83"
34
+ "@byollm/protocol": "0.1.0-alpha.84"
35
35
  },
36
36
  "publishConfig": {
37
37
  "access": "public"
@@ -39,10 +39,10 @@
39
39
  "devDependencies": {
40
40
  "@supabase/supabase-js": "^2.112.2",
41
41
  "vitest": "^4.1.10",
42
- "@byollm/server": "0.1.0-alpha.83",
43
- "byollm": "0.1.0-alpha.83",
44
- "@byollm/control-plane": "0.1.0-alpha.83",
45
- "@byollm/conformance": "0.1.0-alpha.83"
42
+ "@byollm/conformance": "0.1.0-alpha.84",
43
+ "byollm": "0.1.0-alpha.84",
44
+ "@byollm/control-plane": "0.1.0-alpha.84",
45
+ "@byollm/server": "0.1.0-alpha.84"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "vitest": ">=3"