# Relay Plans — Cross-Plan Reconciliation TODO The 6 relay plans + `PLAN_RELAY_INDEX.md` were generated and hardened in parallel (32-agent workflow: draft → security+architecture review → fix → re-review → global consistency). Each plan passed its own review (4–8 blocking findings fixed each). The **global consistency review then found 11 CRITICAL/HIGH cross-plan integration issues** — all one root cause: **plans drifted their SHARED contracts locally during hardening instead of routing changes through the frozen INDEX §4** (violating INDEX §2 rule 1). These must be reconciled BEFORE any plan's implementation is dispatched, or the plans will not compose (they'd type-mismatch / fail at integration). The fix is a **reconciliation pass**: freeze the drifted/missing contracts at the INDEX coordination point, then sync the consumer plans. Below, the 11 findings collapse into 6 concrete fixes. --- ## FIX 1 — `relay-contracts/` (W0) has no owning task *(GLOBAL CRITICAL)* Every plan imports `relay-contracts/` read-only and INDEX says it's "frozen before any plan's W1", but **no plan contains a task to AUTHOR it** (Zod schemas + TS types + the mux binary/CBOR codec). It's also where several still-unfrozen shapes must land. - **Do:** add an INDEX-owned **W0 task** that builds `relay-contracts/`: §4 Zod schemas + inferred types, `encodeMuxFrame`/`decodeMuxFrame` (+ CBOR), `GoAwayReason`/`decodeGoAwayReason`, the amended §4.4 shapes (Fix 2), account/session/pairing TS types, INV8 version-table DDL. Freeze before dispatching any W1. ## FIX 2 — §4.4 E2E contract drift (single `sessionKey: CryptoKey` → evolved shape) *(E2E HIGH, GLOBAL HIGH, FRONTEND HIGH)* P4 (E2E) evolved to: `AeadKey` (not `CryptoKey`), **synchronous** `sealFrame`/`openFrame`, `aad = directionLabel‖seq`, **direction-split `DirectionalKeys` (`c2h`/`h2c`)** wrapped in a stateful `E2ESession` (`.seal()`/`.open()`/`.rederive()`), handshake via `buildClientHandshake(ctx, hostId, aeadOffer)`. But INDEX §4.4, **P2 T15 `makeHostHello` (returns `sessionKey: CryptoKey`)**, and **P6 T8 (raw SubtleCrypto AES-GCM, single key)** still use the ORIGINAL shape → they can't type-check against what P4 exports. - **Do:** land P4 §9 Q4's amendments into **INDEX §4.4 / relay-contracts** first (`AeadKey`, sync sigs, `DirectionalKeys` c2h/h2c, `aad=directionLabel‖seq`, `ReplayKeyParams`). Then update **P2 T15** → `HandshakeResult` carrying `DirectionalKeys`; **P6 T8/T9** → consume `E2ESession`/`buildClientHandshake`/`AeadKey`. Closes P6 §8 Q4 by construction. ## FIX 3 — recoverable content-key (`K_content`/`hostContentSecret`) has no wiring or delivery *(E2E HIGH, GLOBAL HIGH)* The LOCKED "ciphertext ring-buffer survives reconnect" decision (EXPLORE §0 dec.2 / §4c) needs a **recoverable** key: live frames use the ephemeral `h2c` key, but replay/preview frames must use `K_content` derived from a host-scoped `hostContentSecret`. Problems: (a) P2 never calls `sealReplayFrame`; P6 T9 never calls `openReplayCiphertext` → T10/T11 ship as unconsumed library code; (b) `hostContentSecret` has **no delivery** — P2 T4 and P3 T8 `EnrollResult` return only `{hostId, subdomain, cert, caChain}`. - **Do:** add `hostContentSecret` (wrapped to the agent's identity) to the **§4.5 enrollment contract + `EnrollResult`**; assign minting/wrapping to **P3 or P5 at BIND**, receipt/keystore to **P2**. Add consumer tasks: **P2** seals every replay-bound host→client output with `sealReplayFrame`; **P6 T9** decrypts via `openReplayCiphertext`/`deriveContentKey`. Extend T10/T11's merge gate to include this wiring. ## FIX 4 — INV12 revocation teardown has 3 incompatible descriptions + no P1 subscriber *(AUTH HIGH, GLOBAL CRITICAL)* P5 T10 publishes `KillSignal` on Redis pub/sub `relay:revocations` and asserts "P1 subscribes and injects §4.1 RST/GOAWAY". P3 T13 pushes over an unowned "control→node channel (OQ4)". P1 T10/T11 imply P5 calls it directly. **`relay:revocations` + `KillSignal` are NOT in INDEX §4.2** (only `route:{host_id}` / `revoked:{jti}` are), and **P1 has no subscriber task**; `KillSignal` is defined P5-locally (would force P1→P5 import, against DAG). - **Do:** freeze ONE control→data-plane teardown contract in **INDEX §4** — `relay:revocations` pub/sub + `KillSignal` shape, promoted into `relay-contracts`. Assign **P1** an explicit task that subscribes and turns `KillSignal` → §4.1 CLOSE+RST/GOAWAY via `closeStream`/`closeTunnel`. Reconcile **P3 OQ4** drain channel to the same named channel. ## FIX 5 — capability-token WS subprotocol wire format diverges *(FRONTEND HIGH)* P6 T4: `new WebSocket(url, ["relay.capability.v1", token])` (raw token). P1 `extractCapabilityToken`: `['term.relay.v1', 'term.token.' + base64url(token)]` (prefixed + encoded, relay strips before verify; must echo only the app subprotocol). Neither is in INDEX §4.3 → invented independently and diverged → real upgrade 401s while isolated tests pass. - **Do:** freeze the exact app-subprotocol string + `term.token.` prefix + base64url encoding + "accepted subprotocol must be the app one, never the token" rule in **INDEX §4.3**. Update **P6 T4/T8** verbatim + assert `ws.protocol === 'term.relay.v1'` after open (tear down otherwise). ## FIX 6 — two more single-owner violations *(GLOBAL HIGH ×2)* - **6a. WS-upgrade auth implemented twice.** P1 T8 `authorizeUpgrade` re-implements the decision (Origin+token+cross-tenant+rights) while P5 T12 `onUpgrade` is the real gate (adds DPoP PoP, single-use burn, pre-auth throttle, step-up, per-tenant rate, audit). → Make **P1 T8 a thin adapter that DELEGATES to P5 `onUpgrade`/`onReattach`**; add DPoP + `activeSessionCount` to P1's `UpgradeRequest`. Authorization DECISION has exactly one owner (P5). - **6b. `deviceAuthProof` verifier has 3 signatures + ambiguous home.** P5 T2 owns `verifyDeviceAuthProof(proof, expectedAccountId, transcriptHash, now)`; P4 T8 consumes an injected `verifyDeviceProof(proof, {clientEphPub, clientNonce})`; P2 T15 imports it from `relay-e2e` (wrong package). → **P5 owns** issuance+verification (binding to `{clientEphPub, clientNonce}` per P4 Q3); **P4 consumes as injected dep**; **P2 gets it via P4's host-handshake wiring** (fix the import). Unify the binding param across P2/P4/P5. --- ## Reconciliation pass shape (recommended) 1. **Amend `PLAN_RELAY_INDEX.md`** (single agent) — land Fixes 1,2,3,4,5,6 contract decisions into §4 (+ the W0 relay-contracts task). This is the coordination point; do it first. 2. **Sync consumer plans** (parallel, file-disjoint): P1 TRANSPORT (Fix 4 subscriber, 6a delegate, 5 confirm), P2 AGENT (Fix 2 DirectionalKeys, 3 seal+receive secret, 6b import), P3 CONTROLPLANE (Fix 3 EnrollResult, 4 OQ4 reconcile), P6 FRONTEND (Fix 2 T8/T9, 3 T9, 5 T4/T8). P4/P5 mostly already correct — minor confirmations. 3. **Re-run the global consistency review** to confirm zero residual contract drift. Effort: ~7 agents (1 index + ~5 consumer syncs + 1 re-review) — far smaller than the generation workflow. Do it as its own step, ideally in a fresh conversation (read this file + the 7 plan docs).