[
  {
    "pair_number": 1,
    "id_a": "REQ-HOSTING-AUTHORITY-CONTROLLABLE",
    "id_b": "REQ-RC-QUALIFIED-TARGET-CANONICAL",
    "req_a": {
      "title": "RC-RENDER-TRUTH v0.38.1 fast-follow leg 3 (hertz todlando immortal-hybrid trace, doyle fork ruling 2026-07-18 = controllable-authority): ONE hosting authority \u00e2\u20ac\u201d persisted `state` stays the durable endpoint TYPE (REQ-EP-6 open type system; establish_perch's prior-type preserve at startup.rs:346-350 is INTENTIONAL and stays), `controllable==Some(true)` is the source-definitive broker-PTY authority. FIELD ROOT (C1 coverage gap, not new family): an spt-hosted bind over a prior ready_agent perch preserves state=ready_agent while stamping controllable=true + online -> livehost restart_resume_gate (508-534) Skip's state!=live_agent so the orphan never resumes, and reconcile's C1 dead-pid hybrid heal predicate was scoped controllable=false so controllable=true escapes -> immortal dead-PID ready_agent hybrid, latch-driven Active projection (todlando field state; rc's no-session refusal was TRUTHFUL). FIX (hertz refinement, ratified): remove the state rejection from restart_resume_gate; reconcile routes only non-live_agent && controllable!=Some(true) through the PID-model hybrid heal, while controllable==Some(true) rows fall through BROKER-SESSION truth (orphan with ledger/adapter material =",
      "doc": "Requirements: <!-- --> ## Amendment (RC-RENDER-TRUTH v0.38.1 leg 3, doyle ruling 2026-07-19): one hosting authority \u00e2\u20ac\u201d the online-earn authority splits by hosting topology"
    },
    "req_b": {
      "title": "RC-RENDER-TRUTH W1 (ADR-0042 decision 4, hertz elevated-endpoints RCA core leg 2, doyle seam-confirmed rc.rs establish_attach): the resolver's canonical BARE endpoint id is carried separately from the user-facing qualified target \u00e2\u20ac\u201d AttachRequest.endpoint_id is always the bare id (today rc passes the ORIGINAL qualified string; the target's resolve_local_session compares verbatim vs the bare HostedSession.endpoint, so `spt rc id@node`/`subnet:id` dials the RIGHT node then gets a false no-live-session refusal). N-1-additive: bare-form callers are unchanged. Gate: impl \u00e2\u20ac\u201d canonical-id carry through establish_attach; unit \u00e2\u20ac\u201d Address::parse qualified forms yield bare wire id, user-facing copy keeps the qualified spelling; int \u00e2\u20ac\u201d bare + id@node + subnet:id ALL attach against a remote broker-hosted target, wire always carries the canonical bare id; doc \u00e2\u20ac\u201d ADR-0042.",
      "doc": "Decisions: <!-- --> 1. **`spt rc` consults the honest-session authority before the offline fast-fail.** Normal `spt rc <id>` runs the same bounded `SessionProbe::has_live_session_honest` gate `endpoint run` uses (ADR-0041 single liveness authority). An honest session exists \u00e2\u2020\u2019 attach via the session-confirmed path regardless of persisted status. No honest session \u00e2\u2020\u2019 the existing offline refusal stands. A claimed session with a dead client tree \u00e2\u2020\u2019 refusal/reap, never attach. Reuse `SessionProbe`; no new liveness heuristic. <!-- --> 2. **Resume stamps UNBOUND.** A resume launch transitions an exist"
    }
  },
  {
    "pair_number": 2,
    "id_a": "REQ-ADAPTER-PROOF-DIR-OVERRIDE",
    "id_b": "REQ-PICKER-WINDOW-TITLE",
    "req_a": {
      "title": "The author-time proof commands (`spt adapter digest-proof`, `spt adapter translate-proof`) gain a `--dir <path>` / `--manifest <file>` override so an author proofs a DEV binary against an on-disk manifest+install dir WITHOUT staging a full extracted GhReleaseManaged install (mirrors digest-proof's `--sample` pointing straight at a file). Fixes perri F-011: a bare-file-added gh_release adapter currently can't be resolved by the *-proof commands ('manifest is not present yet at <dir>'); un-stales the bare-file digest-proof int. (perri F-011, v0.13.x DX)",
      "doc": "Validate against the live binary: <!-- --> - **Proof a DEV build off disk \u00e2\u20ac\u201d `--dir` / `--manifest`.** Both `digest-proof` and `translate-proof` accept `--dir <install-dir>` (binaries resolve there, just like a registered install) or `--manifest <file>` (pins the manifest; its parent is the install dir) to proof an adapter that is **not registered** \u00e2\u20ac\u201d e.g. a freshly built binary beside a hand-written `manifest.toml`, or a bare-file `gh_release` adapter that was never staged into a full extracted install. `--dir` defaults the manifest to `<dir>/manifest.toml`; with neither flag the command resol"
    },
    "req_b": {
      "title": "B-5 (F029, operator): `spt endpoint run`'s interactive picker window/tab is untitled \u00e2\u20ac\u201d hard to find among many terminals. Set the window/tab title to `SPT Endpoint Picker`. Anchor picker/mod.rs:88 setup_terminal (crossterm SetTitle in the execute! chain). Set-only is acceptable (crossterm can't cheaply read the prior title to restore). Applies ONLY to the interactive picker path \u00e2\u20ac\u201d non-interactive/headless `endpoint run` (REQ-HOST-RUN-1) must NOT retitle the operator's terminal. See triage B-5.",
      "doc": ""
    }
  },
  {
    "pair_number": 3,
    "id_a": "REQ-MSG-DELIVERY-AXES",
    "id_b": "REQ-NOTIF-QUIET-DELIVERY",
    "req_a": {
      "title": "Activity-gated inbound delivery + per-message send control as THREE ORTHOGONAL AXES plus opaque metadata (ADR-0028; grilled w/ operator 2026-06-23). SUBSTRATE (the legacy-SPT parity gap, scaffolded-but-unwired today: `delivery::is_idle` + `resolve_inject_methods` exist but the result is discarded `let _methods`, and `broker::dispatch_endpoint_input` injects unconditionally \u00e2\u20ac\u201d its comment calls activity-gating 'a deferred follow wave'): an inbound message has an ACTIVE window (endpoint active \u00e2\u2020\u2019 spool for the receiver's hook-poll, non-disruptive) and an IDLE window (idle/idle-transition \u00e2\u2020\u2019 deliver immediately: translation binary spt-hosted \u00e2\u2020\u2019 relay-poll either topology \u00e2\u2020\u2019 spool, in fallback order). AXES (each composes; each defaults to its unrestricted value): (1) DELIVERY WINDOW \u00e2\u20ac\u201d default (both, first-to-fire) | `--idle-only` (idle window; immediate if already idle) | `--active-only` (active window only, never wakes; the RENAMED `--deferred` \u00e2\u20ac\u201d `deferred=1` spool column + `api poll --include-deferred` keep their names). (2) CHANNEL RESTRICTION \u00e2\u20ac\u201d unrestricted | `--prefer-native` (translation binary if running else fall back) | `--force-native` (binary ONLY, no fallback/no spool-to-other-m",
      "doc": "Activity-gated message delivery + send-modifier axes: <!-- -->"
    },
    "req_b": {
      "title": "The notify kind rides the active_only window UNCONDITIONALLY, rollback included: spool-only, never live TCP, never a wake, for EVERY producer; loudness = resurface-until-dismissed persistence, never PTY interruption; boundary resurface (clear/compact/new-session/wake) + the adapter safe-point drain are the surfacing paths; a future interrupting alert needs a new kind + its own ADR, not an exception here",
      "doc": "4. Delivery: the notify kind rides `active_only`, unconditionally: <!-- -->"
    }
  },
  {
    "pair_number": 4,
    "id_a": "REQ-HAZARD-CONTROLLER-LEASE",
    "id_b": "REQ-RC-IDENTITY",
    "req_a": {
      "title": "RC-RENDER-TRUTH W2 (KNOWN-HAZARDS 7.48 \u00e2\u20ac\u201d umbrella conformance seam for ADR-0044): at most one input-capable controller lease per PTY session; takeover revokes atomically and loudly; input is fenced to the active lease; node identity is never a lease. The full hertz 8-step deterministic two-loopback-client broker regression rides verbatim: A subscribes Control from node N and controls; B subscribes Take from the SAME node with a different lease; A receives Displaced{by:N} then terminal stream completion (rc exits via existing PumpEnd::Displaced); output post-takeover reaches B not A; A's Input+Resize post-takeover mutate nothing; B's both apply; controlled/driven_by metadata identifies B with exactly one controller slot; a separate equal-lease/equal-generation replay test proves genuine dispatcher recovery remains silent and never self-displaces. Gate: int \u00e2\u20ac\u201d the matrix; doc \u00e2\u20ac\u201d KNOWN-HAZARDS 7.48.",
      "doc": "7.47 The physical terminal is never mutated or terminated outside its renderer's ordered state model \u00e2\u20ac\u201d output before exit, owned baselines, unconditional teardown `[REQ-HAZARD-RENDER-LIFECYCLE]`: ### 7.48 At most one input-capable controller lease per PTY session \u00e2\u20ac\u201d takeover revokes atomically and loudly, input is fenced to the active lease, node identity is never a lease `[REQ-HAZARD-CONTROLLER-LEASE]` <!-- --> - **Failure (paid-for, hertz same-machine `--take` RCA, field repro 2026-07-16):** terminal A controlled an endpoint; terminal B on the SAME machine ran `spt rc --take`. Local loopback"
    },
    "req_b": {
      "title": "`spt rc` overlays a persistent endpoint-identity marker so the operator always groks which endpoint they control: a reserved TOP status row via a DECSTBM scroll-region (shrink the PTY's reported rows by 1, own the row), right-aligned `SUBNET : ENDPOINT_ID @ NODE`, CYAN text. Re-assert the margin + repaint on alt-screen enter / DECSTBM reset / resize (output-scanning, like the existing mouse_scanner). NO window title (the harness, e.g. CC, owns it for busyness \u00e2\u20ac\u201d OSC dropped). Resolve subnet/node/id once at attach (perch/registry read) and thread into the pump; subnet = the endpoint's home/primary (\"local\" if none). The literal floating rounded-rectangle corner box is DEFERRED to the future web-based GUI (not a grid-model rc \u00e2\u20ac\u201d that lift is better spent on the GUI). (v0.16.0)",
      "doc": ""
    }
  },
  {
    "pair_number": 5,
    "id_a": "REQ-HAZARD-CHILD-CONSOLE-FLASH",
    "id_b": "REQ-NOTIF-UPDATE-ROW-VERSION-RETIRE",
    "req_a": {
      "title": "Console-subsystem children of the console-less daemon spawn with CREATE_NO_WINDOW, or each spawn flashes a visible blank window on the user's desktop (5.8)",
      "doc": "5.7 Elevated commands spawn the daemon with the wrong token `[REQ-HAZARD-ELEVATED-DAEMON-SPAWN]`: <!-- --> ### 5.8 Console children of the console-less daemon flash visible windows `[REQ-HAZARD-CHILD-CONSOLE-FLASH]` - **Failure:** the daemon runs DETACHED (no console, 5.6/`detached_no_inherit`). Any console-subsystem child it spawns (`git`, `taskkill`, manifest hook commands) gets a **fresh conhost with a visible window** \u00e2\u20ac\u201d piped/null stdio does NOT prevent it. Field shape: the 60s sync pump's two git calls (`for-each-ref` + `rev-parse`) flashed two blank windows per minute on the user's deskt"
    },
    "req_b": {
      "title": "An update-available notif row minted by a node running PRE-0.40.0 spt is retired on the version the running node has ALREADY reached \u00e2\u20ac\u201d because the keyed catch-up dismissal is structurally blind to it. (DAEMON-LIFECYCLE W2 RIDER, operator-ordered; doyle root-caused end to end 2026-07-22 on the live box.) FIELD CHAIN, verified: GRAVITY-NVDA-PC (f15d837b, BIGNET) runs pre-0.40.0 spt, whose legacy producer mints the update notice SUBNET-scoped with NO coalesce key (the scoped+keyed producer shipped in 0.40.0). The row replicated fleet-wide. The modern catch-up dismissal (REQ-NOTIF-SEAM-DISMISS, pump/update.rs dismiss_staged_notif_if_caught_up) dismisses ONLY by coalesce key, so a keyless row can never be retired by it: a FULLY-UPDATED node holds a live 'v0.41.0 available' row forever, surfacing once per endpoint at every boundary (observed: todlando ~19:54 + Librarian/Athenaeum-Library; store showed seen=2, undismissed, the only undismissed update row in the whole history). FIX: a version-grounded retirement sweep at the EXISTING catch-up site, same per-tick per-subnet cadence, running ALONGSIDE the key path (which stays PRIMARY \u00e2\u20ac\u201d belt-and-braces, not a replacement): dismiss any UNDISM",
      "doc": ""
    }
  },
  {
    "pair_number": 6,
    "id_a": "REQ-HAZARD-SHELL-STALE-ONLINE",
    "id_b": "REQ-LIVENESS-ORACLE-SOUND",
    "req_a": {
      "title": "A shell instance's ONLINE-ness is DERIVED (recorded status AND its recorded `shell.pid` not provably dead), never the recorded `status` field alone \u00e2\u20ac\u201d an abruptly-dead binary (force-kill, crash, OOM: no link-break, so `close_shell`'s offline flip never runs) must not read online forever. The shell-side twin of REQ-HAZARD-DAEMON-HOSTED-LIVENESS, which gave AGENT perches exactly this resolver and which shells never got. PROVABLY DEAD is the narrow discriminant: `shell.pid` present AND parses non-zero AND `!is_process_alive` \u00e2\u20ac\u201d pid absent, unparseable, or 0 (a broker-hosted spawn whose backend exposed no pid records 0) reads ALIVE, the same interim-parity/fail-toward-alive stance `liveness.rs` already holds, so a pid-less backend is NEVER falsely declared dead. Recycled-pid caveat, accepted at mint: a reused pid reads alive, so the heal is missed, never mis-fired \u00e2\u20ac\u201d the failure direction is 'stays stale', never 'kills a live instance'. SITE CLASSIFICATION IS PART OF THE REQUIREMENT (authoritative cfg(test)-excluded census at mint = 8 status reads, 3 classes \u00e2\u20ac\u201d do NOT blanket-swap the predicate): (a) DERIVED \u00e2\u20ac\u201d relink's already-online refusal (the gate that made recovery impossible), the `s",
      "doc": "Conformance checklist (condensed): | # | Invariant | spt-core surface | |---|---|---| | 1.1 | Grace wait precedes INIT_SIGNOFF | daemon teardown | | 1.4/4.4 | Deferred rows excluded from event-stream drain | daemon spool drain | | 2.1/5.1 | Stable PID/broker-handle over ephemeral PID | liveness detection | | 2.3 | Handoff argv/IPC version-tolerant (newer brain \u00e2\u2020\u201d older broker) | broker\u00e2\u2020\u201dbrain IPC, self-update | | 2.4 | gen_start = now() on cold-start + handoff | per-instance generation | | 2.6 | A shell's ONLINE-ness is DERIVED (recorded status AND a not-provably-dead `shell.pid`) \u00e2\u20ac\u201d an abruptly-"
    },
    "req_b": {
      "title": "TEARDOWN-AUTHORITY W2 (todlando W1 gate-round-0 finding, doyle-scoped from the LANDED W1 code 2026-07-19): 'does this pid still exist' has ONE answer in spt-core and it is derived from the OS process table. TODAY spt-daemon/src/broker.rs session_is_zombie computes wrapper_alive from spt_store::proc::is_process_alive, which probes OpenProcess on Windows \u00e2\u20ac\u201d and OpenProcess keeps SUCCEEDING for a TERMINATED process while any parent holds an open handle, which the broker ALWAYS does (Arc<PtySession>) for every PTY child it spawned. A correctly-reaped harness therefore reads ALIVE, flipping zombie_verdict off its PRIMARY class (Some(false) = dead root + surviving record = always a zombie) onto the conditional arm, which additionally demands adapter_labeled && past_grace && !has_live_descendants. CONSEQUENCE, live today: a dead-root session that is NOT adapter-labeled is claimed LIVE indefinitely \u00e2\u20ac\u201d `endpoint run`'s dup-guard refuses ENDPOINT_ALREADY_LIVE over an already-dead tree and cmd_rest's Suspend alive_hint forces from=alive on the same false claim (both via has_live_session_honest, cli.rs:2014 and :3805). REQ-ENDPOINT-CYCLE-HONEST exists to give the cycle verbs ONE liveness authori",
      "doc": "7.50 The liveness oracle answers from the process table, never from a handle a caller still holds `[REQ-LIVENESS-ORACLE-SOUND]`: <!-- --> - **Failure (paid-for, found by todlando during TEARDOWN-AUTHORITY W1 gate round 0, 2026-07-19; latent in `session_is_zombie` since the cycle verbs were built):** `spt_store::proc::is_process_alive` probes `OpenProcess` on Windows, which keeps SUCCEEDING for a TERMINATED process while any parent still holds an open handle to it \u00e2\u20ac\u201d and the broker holds `Arc<PtySession>`, hence such a handle, for every PTY child it spawned. So a correctly-reaped harness reads A"
    }
  },
  {
    "pair_number": 7,
    "id_a": "REQ-HAZARD-PSYCHE-RESIDENCY-EXPECTATION",
    "id_b": "REQ-HAZARD-THRASH-GUARD-BLIND",
    "req_a": {
      "title": "W3 (F030 hazard; paid-for: hall-bf churn ordinal 6491+ + adapter v0.13.2 bad-ship brick 2026-07-04): a psyche failure of ANY shape must NOT remove or alter the parent endpoint's ready/hosted state, and hosting must NOT churn-respawn. The v0.13.2 shim-exit tripped the residency machinery (confirm_residency_or_unhost) which tore down the endpoint's hosted state \u00e2\u20ac\u201d ready marker removed, never re-stamped, every force-native gated leg=cli-gate-not-hosted PERMANENTLY (field brick). FIX: residency machinery retires with the resident child; the teardown that touches parent hosted state is DELETED \u00e2\u20ac\u201d psyche trouble stamps psyche fields only. REQ-HAZARD-LIVEHOST-NONRESIDENT's spirit transfers to the W1 failure budget (its entry gets a SUPERSEDED pointer here, LIVENESS-DECAY\u00e2\u2020\u2019SUPERSEDED pattern from C-1). Conformance int = the hall-bf shape: multi-subnet home, live endpoint, failing psyche \u00e2\u2020\u2019 parent stays deliverable, no rehost churn, error stamped (the wave's heart).",
      "doc": "7.29 Control/viewer stamps CONVERGE to broker session-table truth, not merely edge-trigger `[REQ-HAZARD-CONTROL-STAMP-CONVERGENCE]`: ### 7.30 A Psyche failure of ANY shape must NEVER remove or alter the parent endpoint's ready/hosted state `[REQ-HAZARD-PSYCHE-RESIDENCY-EXPECTATION]` <!-- --> - **Failure (paid-for, field brick 2026-07-04, adapter v0.13.2):** the pre-F-030 model kept a **resident** Psyche process the daemon supervised, with residency machinery (`confirm_residency_or_unhost`) that **un-hosted the parent endpoint** when the resident child went missing. A bad adapter ship (v0.13.2)"
    },
    "req_b": {
      "title": "W3 (F030 hazard; paid-for: evidence #6, hall-bf ~12/min re-host NEVER tripped the C3(b) thrash guard \u00e2\u20ac\u201d boot records were not ledger boundaries to the guard): the failure budget must count REAL attempts (ledger-derived: boot/turn records via the psyche perch ledger), not whatever it counted that let 12/min churn run invisibly. Red-first synthetic loop: a 12/min synthetic failure loop MUST trip the budget.",
      "doc": "7.30 A Psyche failure of ANY shape must NEVER remove or alter the parent endpoint's ready/hosted state `[REQ-HAZARD-PSYCHE-RESIDENCY-EXPECTATION]`: ### 7.31 The Psyche failure budget must count REAL per-event attempts \u00e2\u20ac\u201d a resident rate-guard is blind to per-event churn `[REQ-HAZARD-THRASH-GUARD-BLIND]` <!-- --> - **Failure (paid-for, field evidence 2026-07-04):** the pre-F-030 resident-hosting thrash guard keyed on ledger-rate boundaries that were NOT the real re-host attempts. On hall-bf a **~12/min** re-host churn ran completely **invisibly** \u00e2\u20ac\u201d the guard never tripped, never stamped, never c"
    }
  },
  {
    "pair_number": 8,
    "id_a": "REQ-ENDPOINT-LIST-PALETTE",
    "id_b": "REQ-PROJECT-INDEX-READER-CUTOVER",
    "req_a": {
      "title": "Bugs #11 + #15 (display): spt endpoint list renders status as plain text while the picker turns the same ResourceRow into the W5 colored EpDisplay palette. Fix: extract one shared ResourceRow-to-EpDisplay builder + make the picker display enums/helpers public, and have endpoint list render the same colored status squares (via helpfmt stdout_color, not ratatui Span). This also fixes #15 \u00e2\u20ac\u201d a lone warm detached instance renders as its online flavor (Dormant maps to online) instead of leaking the bare word Dormant through the text-only list (no resting.rs/CONTEXT model change; operator ruling display-only). Couples REQ-PICKER-NODE-GROUPING (both edit subnet_rows \u00e2\u20ac\u201d sequence the shared-builder extraction first). See docs/NEXT-MILESTONE-BUG-TRIAGE.md #11/#15.",
      "doc": ""
    },
    "req_b": {
      "title": "PROJECT-INDEX W3 (ADR-0037): endpoint list, the picker, and endpoint-info consume the materialized index \u00e2\u20ac\u201d NO git work in any user-facing read path (the O(PxB+C) fanout at cli.rs ~2954 / picker/data.rs ~456-518 dies). BEHAVIORAL PARITY is binding: precedence session-cwd -> origin-cwd -> context-recency and rendered project IDs/display names unchanged (parity suite vs the old derivation on a fixture); bare/partial run shares the indexed projection; fully-qualified --adapter+--id direct run stays picker-free; the direct-run 25s broker-session gate stays separately tested/observable. Degradation legs (git unavailable, branch malformed/locked, cwd deleted) keep fast reads. Manual latency acceptance on the 13-perch/7-branch fixture (~30s -> sub-second) + hertz field-verify on HFENDULEAM \u00e2\u20ac\u201d NOT a CI wall-clock gate. Gate: impl \u00e2\u20ac\u201d reader cutover; unit \u00e2\u20ac\u201d parity + degradation; int \u00e2\u20ac\u201d list/picker against a daemon-maintained index incl. counters proving zero reader git spawns; doc \u00e2\u20ac\u201d reference regen + CONTEXT avoid-list. Kin REQ-WHOAMI-IDENTITY-ONLY, REQ-PROJECT-INDEX-STORE/WRITER/INVALIDATION.",
      "doc": "Self-update: **project index** \u00e2\u20ac\u201d a node's endpoint\u00e2\u2020\u2019project attribution is DERIVED state, held as a **persistent materialized index** (ADR-0037): `spt-store` owns the versioned format + read path (daemon-offline reads = last persisted snapshot); the **daemon is the sole single-flight writer** (load-at-start, ready-without-warm, background batched reconcile, atomic replace, coalesced event-driven invalidation keyed on branch-tip fingerprints, last-known-good on failure). Readers \u00e2\u20ac\u201d list, picker, endpoint-info, hooks \u00e2\u20ac\u201d join index \u00c3\u2014 perch roster and **never run git**; stale renders last-known or `-"
    }
  },
  {
    "pair_number": 9,
    "id_a": "REQ-HAZARD-BRAIN-RESTART-LIFECYCLE-REHYDRATE",
    "id_b": "REQ-HAZARD-BRAIN-RESTART-PSYCHE-DUP",
    "req_a": {
      "title": "B4 (deepest): a bare brain restart (broker survives) REHYDRATES the live-agent lifecycle so post-restart endpoints are hosted + attachable. Today resume_sessions (brainproc.rs:186, brain.rs:797-809) re-subscribes to the broker's PTY sessions but ALL BrainLifecycle instances (lifecycle.rs:58-130; the ephemeral brain.rs:254-275) are LOST on restart \u00e2\u2020\u2019 a post-restart live endpoint gets no livehost \u00e2\u2020\u2019 its Psyche is never (re)hosted and new spawns die / can't attach until a FULL daemon reset (operator: perri's brain kill+restart wedged everything until a full daemon kill). FIX: on brain startup, rebuild a BrainLifecycle per resumed live-capable session \u00e2\u20ac\u201d load the manifest from the adapter registry \u00e2\u2020\u2019 instantiate \u00e2\u2020\u2019 start the pulse \u00e2\u20ac\u201d the rehydrate the resume no-op cannot do. Composes with B2 (the reconcile re-hosts from the honest on-disk status after rehydrate). (v0.12.0)",
      "doc": ""
    },
    "req_b": {
      "title": "A bare brain restart leaves EXACTLY ONE `{id}-psyche` process per endpoint \u00e2\u20ac\u201d no duplicate. On an abrupt brain death stop_host never runs (the LiveSet + owned child handles die with the brain) and Breap's job/group only reaps at DAEMON stop, so the PRIOR brain's Psyche stays ALIVE; the respawned brain's reconcile re-hosts a SECOND Psyche and overwrites the `{id}-psyche` perch pid, leaving the old one untracked + alive = a duplicate that lingers until daemon-stop (the operator's 'brain kill+restart wedged everything'). FIX: at brain start, BEFORE the first reconcile re-hosts, reap any pre-existing `{id}-psyche` orphan \u00e2\u20ac\u201d ID-SPECIFICALLY (recycle-safe on the shared box, where sibling agents share the `claude` basename): scoped-kill the recorded pid ONLY IF it is alive AND its exe basename == the adapter's psyche program (normalize_basename) AND its COMMAND LINE contains the full psyche id `<id>-psyche` (baked via {id}); a sibling never carries THIS id, and any unreadable signal FAILS SAFE (decline to reap \u00e2\u20ac\u201d a missed dup is bounded by Breap, a wrong-kill is catastrophic). CAVEAT: the cmdline carries `<id>-psyche` only when the adapter's psyche_init.command uses {id} (the norm); a non-{i",
      "doc": ""
    }
  },
  {
    "pair_number": 10,
    "id_a": "REQ-HAZARD-RC-ATTACH-TRUTH",
    "id_b": "REQ-RC-CROSS-NODE-ATTACH",
    "req_a": {
      "title": "RC-RENDER-TRUTH W1 (KNOWN-HAZARDS 7.46 \u00e2\u20ac\u201d umbrella conformance seam for ADR-0042): an rc surface answers from live session authority, never a stale persisted projection; a resuming perch is UNBOUND, not offline. Regression matrix from the hertz RCAs + operator field recovery: offline-row-over-honest-session attaches; offline-no-session refuses; zombie refuses/reaps never attaches; resume-never-bound reads UNBOUND and is attachable; harness-only refuses truthfully pre-stream; qualified targets attach with bare wire id. HEAVY nextest group at birth for any leg spawning a daemon tree (standing CI lint). Gate: int \u00e2\u20ac\u201d the matrix; doc \u00e2\u20ac\u201d KNOWN-HAZARDS 7.46.",
      "doc": "7.45 Endpoint lifecycle state converges to truth from every death path \u00e2\u20ac\u201d no optimistic online without authority, no surviving control stamps, no immortal wake intent, no untruthful create `[REQ-HAZARD-ENDPOINT-LIFECYCLE]`: ### 7.46 An rc surface answers from live session authority, never a stale persisted projection \u00e2\u20ac\u201d and a resuming perch is UNBOUND, not offline `[REQ-HAZARD-RC-ATTACH-TRUTH]` <!-- --> - **Failure (paid-for, hertz perri contradiction RCA 2026-07-17/18 + operator field recovery):** the broker hosted an honest live session (client tree alive, `SessionProbe::has_live_session_hones"
    },
    "req_b": {
      "title": "Bug #4: spt rc to a remote endpoint fails with 'no live session' though endpoint list shows it Active \u00e2\u20ac\u201d rc.rs:1063 resolves only the LOCAL broker session table and always dials loopback, never consulting the registry or dialing the owning node (the cross-node attach transport exists in the broker; only the client leg is missing). Fix: on a local resolve miss, resolve the owning node from the registry (reuse resolve_across_visible), net_dial that node, and run a remote session-resolve + serve_attach round-trip (mirror the wansend resolve-dial-round-trip pattern). Shares the resolve-owning-node primitive with REQ-WAN-SEND-DELIVERY. See docs/NEXT-MILESTONE-BUG-TRIAGE.md #4.",
      "doc": "Instances: <!-- --> **Remote-control vs local operation (two distinct modes \u00e2\u20ac\u201d not the same as instances):** - **Operate locally:** drive the native instance on *your* machine (its local files, its synced mind). The normal case. - **Remote-control (Shell-like):** attach a control/view surface to an instance *running on another node* \u00e2\u20ac\u201d compute + files stay remote; you are a viewport (the byte-stream terminal attach, daemon-to-daemon over Iroh). Used when you specifically want *that machine's* environment. This is effectively a Shell (a driven surface, user\u00e2\u2020\u2019agent direction), separate from the ins"
    }
  },
  {
    "pair_number": 11,
    "id_a": "REQ-HAZARD-RC-INPUT-KEY-ENCODING",
    "id_b": "REQ-INPUT-CONTROLLER-FENCE",
    "req_a": {
      "title": "An `spt rc` session forwards the Backspace key as the VT DEL byte (0x7f), so the hosted TUI (Claude Code) deletes ONE character \u00e2\u20ac\u201d never a whole word. SYMPTOM (operator dogfooding): Backspace in an rc session always behaves like ctrl+Backspace \u00e2\u20ac\u201d deletes the entire last word. ROOT (doyle /diagnose, code-grounded, byte PENDING HITL confirm): rc is a RAW VERBATIM byte pump \u00e2\u20ac\u201d spawn_stdin_reader (rc.rs:152) reads std::io::stdin() bytes under crossterm raw mode and forwards them unchanged (parse_stdin_chunk only intercepts the ctrl-b detach prefix); there is NO key-event encoding and NO 0x08\u00e2\u2020\u201d0x7f normalization ANYWHERE in the tree (grep: zero SetConsoleMode / ENABLE_VIRTUAL_TERMINAL_INPUT). On Windows, crossterm enable_raw_mode does NOT set ENABLE_VIRTUAL_TERMINAL_INPUT, so the LEGACY console delivers ^H (0x08, ctrl+h) for Backspace instead of VT DEL (0x7f); Claude Code maps ^H \u00e2\u2020\u2019 backward-kill-word \u00e2\u2020\u2019 the observed whole-word delete. CONFIRM-FIRST (build the loop): an env-gated hexdump in spawn_stdin_reader (SPT_RC_DEBUG_KEYS) prints the forwarded byte; operator presses Backspace + ctrl+Backspace in a real rc session. FIX CANDIDATES: (a) enable ENABLE_VIRTUAL_TERMINAL_INPUT on the rc stdin ",
      "doc": ""
    },
    "req_b": {
      "title": "RC-RENDER-TRUTH W2 (ADR-0044 decision 3, hertz same-machine --take split-brain RCA P0-C + scope clarification, doyle seam-verified broker.rs dispatch_input 3920-3935 session-addressed unfenced): broker-enforced input fencing SCOPED TO RC-ORIGIN INPUT \u00e2\u20ac\u201d RC Input/Resize bind to the ACTIVE controller lease (or originating broker connection as the N-1 surrogate); commands from a displaced/stale lease are rejected/dropped after replacement. Do NOT globally gate generic KIND_INPUT: shell/system injection legitimately sends InputReq from non-controller connections (Minter::Shell, shellchan seam) \u00e2\u20ac\u201d fence keys on an additive controller-ownership token validated only for token-bearing/Minter::Rc requests, or a dedicated guarded RC-input verb; token optional/default-none preserves generic injection exactly. REQUIRED DEFENSE, not optional hardening: this is what makes the at-most-one-input-capable-controller invariant TRUE even when the Displaced notification is delayed or lost (today the displaced window keeps typing into the PTY indefinitely \u00e2\u20ac\u201d the field split-brain). Gate: impl \u00e2\u20ac\u201d token/verb + lease-bound validation on the RC input path; unit \u00e2\u20ac\u201d stale-lease RC input rejected post-replacement, ",
      "doc": "Decisions: Across DIFFERENT `by` identities the intent split stands: `Control` = Busy, `Take` = loud revoke. Deliberate, documented UX consequence: a second same-node window's plain `rc` now LOUDLY displaces the first (newest viewport wins within one identity) \u00e2\u20ac\u201d the pre-W2 behavior was the same replacement done SILENTLY with the loser left interactive and blind; loud + fenced is strictly better on every axis, and `--view` remains the coexistence path. Every ruled invariant holds: at most one input-capable lease, the incumbent always ends terminally, a displaced window can never type, equal-gen"
    }
  },
  {
    "pair_number": 12,
    "id_a": "REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE",
    "id_b": "REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT",
    "req_a": {
      "title": "A SLOW controller must not starve a concurrent `rc --view` VIEWER. W1 (REQ-HAZARD-INJECT-CONTROL-COEXIST) moved the controller SOCKET WRITE off the drain thread onto controller_writer, but left the bounded HANDOFF (ControllerJob::deliver) as an INLINE try_send SLEEP-POLL on the drain (broker.rs:1450-1457 \u00e2\u2020\u2019 deliver:669-685, up to CONTROLLER_WRITE_DEADLINE=5s). So when a controller drains slower than the PTY floods, its CONTROLLER_CHANNEL_DEPTH(4096) channel fills, deliver() polls inline, and the DRAIN THREAD is throttled to the controller's read rate \u00e2\u2020\u2019 OutputLog::append's viewer fan-out (try_send) stops running \u00e2\u2020\u2019 a concurrent VIEWER receives only the initial replay then nothing (root 'b4', warm forkpty: a_journaled c1=0/EVICT=0/got_output=FALSE; steady-state-near-full = no recovery; forkpty-only, floods harder than Windows ConPTY). The viewer-not-starved-by-a-busy-session property is legitimate (rc --view of a noisy session must show LIVE output). FIX: the controller becomes a SINGLE NON-BLOCKING try_send (like a viewer), done IN append() under the log lock; deliver()'s sleep-poll DELETED; the drain NEVER sleeps. ControllerSink gains a stateful last_ok deadline \u00e2\u2020\u2019 a TRULY-stalled con",
      "doc": ""
    },
    "req_b": {
      "title": "A `rc --view` VIEWER that overflows its broker subscription queue and is EVICTED (OutputLog::append try_send Full \u00e2\u2020\u2019 viewers.remove, REQ-HAZARD-VIEWER-ISOLATION session-protection) must SKIP TO LIVE, not die silently. ROOT (v0.13.0, b4 JIT item 2 = p0_paste + post-b4 a_journaled-Linux, ONE root): serve_attach forwards each frame (read_event\u00e2\u2020\u2019b64decode\u00e2\u2020\u2019re-encode AttachRecord\u00e2\u2020\u2019net_stream_send) SLOWER than the drain fans out under flood \u00e2\u2020\u2019 its VIEWER_CHANNEL_DEPTH(256) channel overflows \u00e2\u2020\u2019 the drain evicts (viewers.remove drops the ViewerSink \u00e2\u2020\u2019 drops tx \u00e2\u2020\u2019 viewer_writer's rx.recv() Err \u00e2\u2020\u2019 the writer returns WRITING NOTHING) \u00e2\u2020\u2019 serve_attach's brain.read_event() just STOPS getting Output (no EOF, no error) \u00e2\u2020\u2019 serve_attach blocks forever \u00e2\u2020\u2019 the operator receives nothing (attach_received_output=FALSE). Eviction-of-a-hopelessly-behind-viewer is CORRECT session-protection (keep it); SILENT+PERMANENT eviction is the bug. VIEWER-only \u00e2\u2020\u2019 B2-SAFE (a viewer never advances delivered_through / is not authoritative / exposes no resume cursor). FIX (doyle-gated, skip-to-live = tail -f reconnect): (1) explicit broker\u00e2\u2020\u2019viewer EVICTION SIGNAL (KIND_VIEWER_EVICTED, written in the viewer_writer thread OFF the log lo",
      "doc": "Shell sleep/wake (offline \u00e2\u2020\u201d online): <!-- --> **BUILT (M12 W2.5).** The controller/viewer model is implemented end-to-end. Attach intent is **three-valued** (`AttachIntent = Viewer | Control | Take`, wire-default `Control`): `Control` to a FREE endpoint becomes controller; `Control` to a CONTROLLED endpoint is **refused with guidance** (`--view` to watch, `--take` to control) \u00e2\u20ac\u201d never auto-viewer, never silent-displace; `Take` (`spt rc --take` / picker \"Kick\") kicks the incumbent with a **loud `Displaced{by}` notice** and full detach (not demote). The broker's per-session `OutputLog` is the fan"
    }
  },
  {
    "pair_number": 13,
    "id_a": "REQ-HAZARD-DEFERRED-MANIFEST",
    "id_b": "REQ-MANIFEST-6",
    "req_a": {
      "title": "A pointer-mode (delegated / GhReleaseManaged) adapter whose binary/manifest is not yet extracted is reported with a CLEAR diagnostic, never silently dropped. Today such an adapter reads its manifest LIVE from source_dir (registry.rs manifest_dir ~146/149); a deferred / un-extracted install makes load_manifest fail \u00e2\u2020\u2019 registered() (~410, filter_map(.ok())) SILENTLY DROPS the row \u00e2\u2020\u2019 downstream ADAPTER_UNRESOLVED + a cryptic os-error-2 on `spt adapter use`. FIX: surface a clear diagnostic at the resolver + at `adapter use` (name the adapter + the deferred/missing-manifest cause + the fix), not a silent filter-drop and not a bare os-error-2; consider an eager manifest copy at register time so host_binaries survive before the binary download completes. doyle Finding A. (post-v0.10.0)",
      "doc": ""
    },
    "req_b": {
      "title": "Cross-adapter fallback target addressing (M12-W3-T3.2): a cross-adapter fallback target is addressed as `<adapter>:<profile>` (not just a bare adapter_name), resolved through the one composite-addressing resolver (registry::resolve_option) at every adapter-option read site so a fallback may select a shipped/local profile (e.g. a `ccs` profile). CONTEXT.md \u00c2\u00a7cross-adapter-fallback reconciled (\"ccs is a profile; cross-adapter fallback may target <adapter>:<profile>\"). Contract-only this milestone: the node-wide fallback SETTING + its rate-limit invocation are deferred to the consuming milestone (the runtime path does not exist yet); this REQ guarantees the ADDRESSING resolves.",
      "doc": "Manifest seams (outbound contract, detailed): - **Command templates are opaque.** spt-core never parses out a model/tool/flag \u00e2\u20ac\u201d the adapter writes the whole command line; spt-core fills substitution keys and runs it. - **A command template's program token resolves against the adapter install dir before PATH (since v0.8.0).** A `.spt` adapter ships its built binaries to the adapter's install dir (`adapters/_github/<safe>/` via `--release`/`--github`, or the record's `source_dir` under copy-mode), so a bare program name (e.g. `claude-spt-digest \u00e2\u20ac\u00a6`) binds to the shipped binary first and falls bac"
    }
  },
  {
    "pair_number": 14,
    "id_a": "REQ-UPD-3",
    "id_b": "REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL",
    "req_a": {
      "title": "No endpoint process terminates/suspends during self-update",
      "doc": ""
    },
    "req_b": {
      "title": "W3 (LIFECYCLE-TRUTH): daemon restart no longer massacres hosted endpoints \u00e2\u20ac\u201d daemon start RE-RUNS previously-online spt-hosted endpoints. ROOT rig-proven: daemon stop+start (the apply notice's OWN instruction) kills every hosted endpoint; they stay OFFLINE after start (no resurrection) though records exist (info.json status + adapter + cwd). SCOPE RULING (doyle): re-run-on-start, marked start-reason=daemon-restart; agents' minds ride psyche re-host as today. Int: endpoint online -> daemon stop -> start -> endpoint back ONLINE, same id, harness respawned.",
      "doc": ""
    }
  },
  {
    "pair_number": 15,
    "id_a": "REQ-ACTIVITY-LIST-JSON",
    "id_b": "REQ-DIGEST-JSON-SELF-CONTAINED",
    "req_a": {
      "title": "`spt endpoint list --json` carries a per-endpoint `activity` (busy|idle) key, for consumers surveying the idle/busy state of many endpoints at once (ADR-0048 decision 1, roster pull avenue; operator addition 2026-07-24). Additive key, N-1-safe; kin the flynn 2026-07-06 last-active/description/adapter list-enrichment seed (same additive posture, may ride together).",
      "doc": "`--json` catalog: | Command | Top-level shape | |---|---| | `endpoint list` | `{ self, subnets[], local[] }` \u00e2\u20ac\u201d `self`: `{id, status, ready, alive, unbound, description, psyche_host_error, translation_fault?}`; `subnets[]`: `{name, endpoints[]}` where each endpoint is `{id, node, node_label, status, resources, endpoint_type?, project?}`; `local[]`: `{id, state, address, ready, alive, unbound, project?, activity?}`. *(Since v0.33.0 the local `project` field reads the daemon-maintained project index \u00e2\u20ac\u201d answers are immediate and may lag a just-changed project by moments; absent while the index has"
    },
    "req_b": {
      "title": "TEARDOWN-AUTHORITY W4 (perri adapter-surface finding 2026-07-19, doyle-grounded and RE-SCOPED; title AMENDED at gate 2026-07-19 \u00e2\u20ac\u201d the original welded version to --after polling, todlando falsified it from digesthub.rs/cli.rs and doyle ruled the amendment rides the wave: fifth instance of the claim-keyed-on-the-wrong-thing class, this one in the REQ registry itself): `spt endpoint digest <id> --json` must be self-contained on stdout. Today the digest snapshot version is NOT a field of the --json object at all \u00e2\u20ac\u201d it exists ONLY in the DIGEST:<id> version=N trailer that cmd_digest eprintln!s at cli.rs:1619 \u00e2\u20ac\u201d so a JSON consumer that wants it is FORCED to parse stderr. NUMBER-SPACE TRUTH (the amendment): version is digesthub's monotonic PROJECTION counter \u00e2\u20ac\u201d it bumps when the projected digest CHANGES, serves as the --follow from_version floor and a change-detection cursor, and is NOT valid --after input; --after filters on entry seq ((ledger_ordinal<<32)|line_idx), a different number space, so a version passed as --after predates the window every time. NOTE the corrected history (perri's original RCA framed this as a fleet outage and doyle falsified it; perri confirmed): the trailer has b",
      "doc": "Session digest \u00e2\u20ac\u201d `endpoint digest --json`: <!-- the self-contained --json contract: top-level integer version cursor, stderr-clean under --json, the complete entry-kind enum with per-kind agent-produced vs spt-injected provenance, and the seq/cursor asymmetry -->"
    }
  },
  {
    "pair_number": 16,
    "id_a": "REQ-INJECT-MULTILINE-INTEGRITY",
    "id_b": "REQ-MSG-IDLE-TRANSLATION-BINARY",
    "req_a": {
      "title": "W5 (LIFECYCLE-TRUTH): the idle-inject TYPED delivery leg delivers multi-line bodies byte-complete. ROOT (4 field instances + spool diff): the typed leg eats HEAD bytes nondeterministically \u00e2\u20ac\u201d spool rows complete (1669B) vs ~322B received suffix; mid-turn poll envelopes always intact; a 1854B body later rode the same leg intact => timing race (terminal-readiness / enter-coalescing settle class), NOT a size cap. FIX DIRECTION (todlando proposes on the broker/translate typed-inject seam): settle-before-head, bracketed-paste where the harness supports it, or chunked write with echo-verify. STAKES: live-SENT injects leave NO spool copy \u00e2\u20ac\u201d truncation there is unrecoverable. Int: repeated large multi-line injects into a real PTY session arrive byte-complete (loop N times \u00e2\u20ac\u201d the race is timing-dependent, single-shot green is not proof).",
      "doc": ""
    },
    "req_b": {
      "title": "spt-hosted idle message delivery via an adapter TRANSLATION BINARY (ADR-0022). New opt-in manifest section `[message-idle-translation-binary]` = a TABLE carrying a `path` scalar (doyle OPT-B ruling: modeled as a table, not a bare top-level scalar, so a preceding section cannot silently absorb it + N+1 extensible; spt-core does NOT deny_unknown_fields, so a future key degrades gracefully); spt-core LIFECYCLE-manages it (spawn when the endpoint comes up, terminate when it goes down). The binary is a PURE stdin\u00e2\u2020\u2019stdout filter; spt-core owns EVERY PTY write. stdin (JSON-lines): `{type:\"init\",endpoint_id,node}` first \u00c2\u00b7 `{type:\"event\",envelope:\"<EVENT\u00e2\u20ac\u00a6>\"}` per inbound message (ADR-0020 envelope) \u00c2\u00b7 `{type:\"input\"}` content-free ping on each operator keystroke (binary tracks user-idle for its own idle-gated buffering; PTY input content NOT duplicated). stdout (JSON-lines): keystroke-commands `{key:\u00e2\u20ac\u00a6}`/`{delay_ms:\u00e2\u20ac\u00a6}`/`{text:\u00e2\u20ac\u00a6}` (extensible). spt-core applies the emitted sequence to the broker PTY ATOMICALLY (the W1 coordination \u00e2\u20ac\u201d REQ-HAZARD-INJECT-CONTROL-COEXIST). The daemon poll feed is the ONE idle substrate for both topologies (Q1=A): harness-hosted consumer = the Monitor child, spt-host",
      "doc": "`[message-idle-translation-binary]` \u00e2\u20ac\u201d spt-hosted idle delivery: <!-- --> // `[inject]` \u00e2\u20ac\u201d inject-input methods: <!-- --> ### `[message-idle-translation-binary]` \u00e2\u20ac\u201d spt-hosted idle delivery (ADR-0022) Opt-in. The adapter's **idle-delivery translation binary**: a pure stdin\u00e2\u2020\u2019stdout JSON-lines filter spt-core lifecycle-manages (spawned when the spt-hosted endpoint comes up, terminated when it goes down). spt-core feeds it the inbound `<EVENT>` feed and reads back keystroke-commands, which spt-core applies to the broker-held PTY **atomically** \u00e2\u20ac\u201d controller input is buffered during the emitted sequenc"
    }
  },
  {
    "pair_number": 17,
    "id_a": "REQ-HAZARD-GRACE-BEFORE-SIGNOFF",
    "id_b": "REQ-HAZARD-ECHO-BEFORE-SIGNOFF",
    "req_a": {
      "title": "Grace-period wait completes before composing INIT_SIGNOFF (1.1)",
      "doc": ""
    },
    "req_b": {
      "title": "Echo-commune fires before INIT_SIGNOFF on orphan teardown (3.3)",
      "doc": ""
    }
  },
  {
    "pair_number": 18,
    "id_a": "REQ-ENDPOINT-LIST-NODE-IDENT",
    "id_b": "REQ-WHOAMI-1",
    "req_a": {
      "title": "Bug #5: spt endpoint list local section header is the hardcoded literal LOCAL (this node) (render_local_section cli.rs:4359). Change to 'This node: <node-id>' using the existing node-ident idiom (os_hostname + nodeid public-key prefix, cli.rs:5531 \u00e2\u20ac\u201d factor a node_ident_display helper); compute in the impure print_local_section, pass into the pure renderer. Update the two test assertions (cli.rs:10711/10716). See docs/NEXT-MILESTONE-BUG-TRIAGE.md #5.",
      "doc": ""
    },
    "req_b": {
      "title": "The `endpoint list` SELF pin carries the Self endpoint's authored `endpoint description` (info::read_info(...).resources) when present, inline after the liveness state; whoami stays a top-level hot-path verb (parse unchanged, REQ-MSG-9) and renders the same description-carrying SELF pin. HISTORY: originally minted whoami as a thin ALIAS of `spt endpoint list` \u00e2\u20ac\u201d that alias premise is SUPERSEDED by REQ-WHOAMI-IDENTITY-ONLY (PROJECT-INDEX W1, 2026-07-15): the alias inherited the list's O(perches x branches) git fanout onto hook paths (the 2026-07-15 message-delivery incident), so whoami is now identity-only over the shared render_self_pin. The pin render + parse evidence here stands; the full-roster surface lives solely on `endpoint list`.",
      "doc": "**whoami** (alias for endpoint list): <!-- --> `spt whoami` is a thin **alias for `spt endpoint list`** \u00e2\u20ac\u201d it prints the full view with the session's own endpoint **SELF-pinned first**, that pin carrying the endpoint's id, liveness state, and its authored **endpoint description** (the \"who am I\" answer). There is no separate bare-id command: nothing captured `id=$(spt whoami)` (environment variables don't persist between an agent's tool calls), so there is no scripting contract to preserve. `whoami` stays a top-level hot-path verb (its parse is unchanged, REQ-MSG-9); only the SELF pin's new des"
    }
  },
  {
    "pair_number": 19,
    "id_a": "REQ-MSG-3",
    "id_b": "REQ-MSG-ENVELOPE",
    "req_a": {
      "title": "Ready-agent lifecycle: register perch (info.json + listener + registry address) on ready, drain spooled backlog on startup, clean teardown",
      "doc": ""
    },
    "req_b": {
      "title": "The <EVENT type=\"msg\" from=\u00e2\u20ac\u00a6>body</EVENT> envelope (spt-proto::event, the ADR-0001 grammar) is the SOLE canonical arriving-message format at EVERY harness arriving-message surface on an AGENT perch \u00e2\u20ac\u201d api listen AND api poll/worker-poll, byte-identical (reverses REQ-MSG-4's 'hook drains keep the raw frame by contract'). SCOPE CARVE-OUT: the shell-command relay (api poll <shell-id> --link, cmd_poll_shell) is a distinct internal transport carrying RAW MAC'd stamped frames the shell child consumes verbatim \u00e2\u20ac\u201d NOT an arriving-message surface, deliberately EXEMPT from <EVENT> composition (notify_shell_e2e guards this boundary). __REPLY_TO__ \u00e2\u20ac\u201d mis-elevated during the clean-room port to a fake ADR-0001 'stable wire format' (spt-msg/wire.rs, lib.rs) \u00e2\u20ac\u201d is REMOVED entirely (spool format_row, the spt-msg TCP frame, emit parse_frame); (from, body) carried structurally, <EVENT> composed once at the delivery boundary. No legacy sister-interop (spt-core never required it). Reply-correlation rebinds onto the structural from / <EVENT from=\u00e2\u20ac\u00a6> attribute (ADR-0009 access-gate + ADR-0012 Psyche/spt-live reply-target). Self-delimiting by construction \u00e2\u2020\u2019 finding F-002 (non-self-delimiting multi-message poll",
      "doc": "Decision: <!-- -->"
    }
  },
  {
    "pair_number": 20,
    "id_a": "REQ-MANIFEST-2",
    "id_b": "REQ-MANIFEST-3",
    "req_a": {
      "title": "Adapter profiles \u00e2\u20ac\u201d sparse leaf-replace overlays (shipped + local), composite <adapter>:<profile> addressing, shadow-refusal, tighten-only consent floors",
      "doc": "Runtime model: <!-- --> **adapter profile** (ratified 2026-06-11, Gateway grill; future spt-core milestone \u00e2\u20ac\u201d first beneficiaries `spt-claude-code` and the usbip shell): A named **sparse overlay** on its parent adapter manifest. Merge semantics are **leaf-replace**: a profile key replaces the whole value at that path (arrays included \u00e2\u20ac\u201d never spliced or appended). The merged result is a complete manifest, and the profile behaves as a distinct adapter option everywhere: canonical addressing is the composite **`<adapter>:<profile>`** (`claude-spt:work`, `spt-usbip-driver:hid-only`) in every place"
    },
    "req_b": {
      "title": "Adapter strings \u00e2\u20ac\u201d [strings] KV tree, dot-path get-string resolving through the profile leaf-replace overlay, set-string editing a local profile's [strings] only; data-only (nothing executes a string)",
      "doc": "Runtime model: **adapter strings** (ratified 2026-06-11, Gateway grill): <!-- --> A `[strings]` manifest section \u00e2\u20ac\u201d an adapter-authored JSON/TOML KV tree, dot-path-readable by anything on the node via `spt adapter get-string <adapter-option> <key.path>` (e.g. a harness hook fetching per-profile `additionalContext` \u00e2\u20ac\u201d one hook script serves every profile, only the data differs). Resolution rides the **same leaf-replace profile overlay** as the rest of the manifest: a shipped or local profile may override base strings; `get-string` returns the merged view for the named adapter option. **Strings ar"
    }
  },
  {
    "pair_number": 21,
    "id_a": "REQ-HAZARD-WIN-PTY-PROGRAM-RESOLVE",
    "id_b": "REQ-SUBNET-2",
    "req_a": {
      "title": "Native-PTY spawn must resolve a bare program name with PATHEXT precedence and run a non-PE target through its interpreter: portable-pty's own `which` takes the FIRST PATH match \u00e2\u20ac\u201d an extensionless shebang shim (e.g. a node CLI `ccs` shipped beside `ccs.cmd`) \u00e2\u20ac\u201d and CreateProcessW then rejects the non-PE file with os error 193 ('not a valid Win32 application'); spt-term resolves the program itself (PATHEXT order prefers .EXE over .CMD; .cmd/.bat \u00e2\u2020\u2019 cmd.exe /d /c, .ps1 \u00e2\u2020\u2019 powershell -NoProfile -File) so a bare harness/shell [session.self] command actually launches on Windows. Unix is a passthrough (execve honours the shebang).",
      "doc": "5.11 Self-elevating re-launch must re-run verbatim, never widen / inject / loop `[REQ-HAZARD-SELF-ELEVATE]`: <!-- --> ### 5.12 Native-PTY spawn of a bare program runs the wrong (non-PE) file on Windows `[REQ-HAZARD-WIN-PTY-PROGRAM-RESOLVE]` - **Failure:** `portable-pty`'s ConPTY spawn resolves a bare program name with a `which` that takes the FIRST `PATH` match. A node/npm CLI installs as BOTH an extensionless shebang shim (`ccs`, for Git Bash) and a Windows launcher (`ccs.cmd`) in the same dir; portable-pty picks the extensionless `ccs`, and `CreateProcessW` then tries to execute that non-PE"
    },
    "req_b": {
      "title": "Guided join e2e: spt subnet join CLI initiator + always-on daemon pairing responder",
      "doc": "Product-surface amendment (2026-06-05 \u00e2\u20ac\u201d M7 D3): <!-- -->"
    }
  },
  {
    "pair_number": 22,
    "id_a": "REQ-HAZARD-BIND-REST-STATE-CARRY",
    "id_b": "REQ-HAZARD-PSYCHE-RESIDENCY-EXPECTATION",
    "req_a": {
      "title": "GATEWAY-LIVENESS DEFECT A (flynn field bug 2026-07-09, confirmed independent): a re-bind MUST preserve the daemon-owned resting intent (rest_state, D9-2/REQ-INST-3) \u00e2\u20ac\u201d the same carry-forward discipline establish_perch already applies to cwd/controllable/adapter/read_env. ROOT: establish_perch's record build (crates/spt/src/api/startup.rs, the build closure) constructs a fresh InfoJson via InfoJson::new (defaults rest_state None) and carries cwd/controllable/read_env forward from prior but NOT rest_state -> a re-bind WIPES the wake intent (flynn tick11 rest_state:active vanish). FIX: carry prior.rest_state (and its paired dormant_since_ms anchor, present iff dormant) forward on re-bind, like the sibling fields. Gate: a re-bind over a prior record with rest_state set preserves it (unit \u00e2\u20ac\u201d the build closure carries rest_state + dormant_since_ms). KNOWN-HAZARDS entry on landing. Kin REQ-HAZARD-BIND-CWD-UNSET / REQ-PICKER-1 + REQ-INST-3.",
      "doc": ""
    },
    "req_b": {
      "title": "W3 (F030 hazard; paid-for: hall-bf churn ordinal 6491+ + adapter v0.13.2 bad-ship brick 2026-07-04): a psyche failure of ANY shape must NOT remove or alter the parent endpoint's ready/hosted state, and hosting must NOT churn-respawn. The v0.13.2 shim-exit tripped the residency machinery (confirm_residency_or_unhost) which tore down the endpoint's hosted state \u00e2\u20ac\u201d ready marker removed, never re-stamped, every force-native gated leg=cli-gate-not-hosted PERMANENTLY (field brick). FIX: residency machinery retires with the resident child; the teardown that touches parent hosted state is DELETED \u00e2\u20ac\u201d psyche trouble stamps psyche fields only. REQ-HAZARD-LIVEHOST-NONRESIDENT's spirit transfers to the W1 failure budget (its entry gets a SUPERSEDED pointer here, LIVENESS-DECAY\u00e2\u2020\u2019SUPERSEDED pattern from C-1). Conformance int = the hall-bf shape: multi-subnet home, live endpoint, failing psyche \u00e2\u2020\u2019 parent stays deliverable, no rehost churn, error stamped (the wave's heart).",
      "doc": "7.29 Control/viewer stamps CONVERGE to broker session-table truth, not merely edge-trigger `[REQ-HAZARD-CONTROL-STAMP-CONVERGENCE]`: ### 7.30 A Psyche failure of ANY shape must NEVER remove or alter the parent endpoint's ready/hosted state `[REQ-HAZARD-PSYCHE-RESIDENCY-EXPECTATION]` <!-- --> - **Failure (paid-for, field brick 2026-07-04, adapter v0.13.2):** the pre-F-030 model kept a **resident** Psyche process the daemon supervised, with residency machinery (`confirm_residency_or_unhost`) that **un-hosted the parent endpoint** when the resident child went missing. A bad adapter ship (v0.13.2)"
    }
  },
  {
    "pair_number": 23,
    "id_a": "REQ-MSG-CLI-ORIGIN",
    "id_b": "REQ-SEND-STAMP-AGENT-ID",
    "req_a": {
      "title": "A bare non-perch CLI `spt send` (no owning perch to name as origin) stamps from = `cli@<node-label>` at compose time (bare `cli` when no node label is known \u00e2\u20ac\u201d never a dangling `cli@`), and WAN ingress renders an EMPTY from as the origin node DISPLAY (`node_label_display(origin_node, None)` = the QUIC-proven origin node's key-prefix; never blank) \u00e2\u20ac\u201d a delivered message NEVER shows a blank sender. Scoped to `spt send`: a from-less send is LEGAL (stamped, never refused), while `spt ring` keeps its NO_SELF refusal (a ring needs a routable self for the reply leg; `cli@<node>` is a display origin, not a perch address). (F-024C item 3, doyle ruled)",
      "doc": ""
    },
    "req_b": {
      "title": "MSG-IDENTITY W4 / endpoint-identity (operator-flagged 2026-07-09 + live-confirmed on flynn's F-038 ask arriving 'cli@HFENDULEAM'): a live agent's own CLI `spt send <target>` MUST stamp from_id with the AGENT id (e.g. 'doyle'), not the node fallback 'cli@<node>' \u00e2\u20ac\u201d today the agent-id stamp rides ONLY the adapter/perch shortform path, so any agent shelling out `spt send` (the DOCUMENTED reach-another-agent form) presents to recipients as an anonymous node CLI: replies mis-route (recipients answer cli@node \u00e2\u20ac\u201d no perch \u00e2\u20ac\u201d instead of the sender), and the F-036 victim-effect ('sends downgraded to from:cli@node') is indistinguishable from normal CLI traffic. FIX: send-time self-resolve \u00e2\u20ac\u201d when the calling process/session maps to a bound live perch on this node (the session-pin/seed machinery already resolves this for bind), stamp that endpoint id as from_id; a genuinely perchless CLI keeps 'cli@<node>'. Gate: unit \u00e2\u20ac\u201d a send from a session bound to a live perch stamps the endpoint id; a perchless shell keeps the node stamp; int \u00e2\u20ac\u201d recipient's EVENT from= carries the agent id for a shelled-out send from a live session. Kin F-036 victim effects, EVENT envelope (ADR-0020), [[owl-send-not-legacy-spt",
      "doc": ""
    }
  },
  {
    "pair_number": 24,
    "id_a": "REQ-CONN-BLACKHOLE-LIFECYCLE-HARNESS",
    "id_b": "REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE",
    "req_a": {
      "title": "MSG-IDENTITY W6 / F-039 leg e (doyle W6 LOCK 2026-07-10, minted per amendment 3 \u00e2\u20ac\u201d hertz's five invariants VERBATIM from his RCA fix-shape item 5): 'Build a deterministic black-holed-controller harness against current v0.30.6 semantics and assert: unrelated sessions continue; the bad physical connection is canceled/closed within the bound; its writer exits; a fresh viewer can attach; no lock or task remains owned by the retired connection.' The harness is the standing conformance rig for the r4 SHAREDSEND fix-class \u00e2\u20ac\u201d hertz's RCA discipline: only after a timestamped incident maps to a FAILING lifecycle invariant does an ownership/cancellation defect get fixed (the likely shape being complete physical-connection cancellation and writer-task join/retirement, never a broader timeout increase). Consumes REQ-CONN-POISON-ATTRIBUTION's records (conn id + lifecycle events are what make the five assertions checkable deterministically). Kin REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK (the invariant class under test \u00e2\u20ac\u201d its brain_decouple int stays the Windows-mandatory gate leg), REQ-CONN-POISON-DIAL-SCOPE.",
      "doc": ""
    },
    "req_b": {
      "title": "W2 (LIFECYCLE-TRUTH, KNOWN-HAZARDS, flagship \u00e2\u20ac\u201d the update wedge): PTY viewer fan-out and control mutations must not depend synchronously on a live draining brain. ROOT rig-CONFIRMED (NtSuspendProcess on the brain, no update involved): brain-subscriber session-output writes ride UNDER the per-session log lock (broker.rs:19-20); failed writes are handled (cursor freeze + detach :3486) but BLOCKED writes are not. Suspended brain => within seconds attached rc output freezes; detach does NOT release the control stamp (release routes through the brain); reattach REFUSED (controlled-by); rc --take hangs; daemon status stays healthy. Field: every brain cycle (incl. every update apply) has a freeze window; a stalled/slow-draining new brain = permanent wedge until bounce; brain.ready != subscribers drained. FIX SHAPE (todlando proposes, doyle RULES BEFORE IMPL): subscriber writes move OFF the log lock (bounded/nonblocking, stall => detach-subscriber like viewer eviction \u00e2\u20ac\u201d broker already buffers + replays on re-attach, so a detached-stalled brain self-heals by rewind); control stamp release/take completes against the BROKER without brain round-trip (or bounded with loud timeout). doc = KNOWN-",
      "doc": "7.35 The cached ceremony-clock NTP offset must NOT survive an OS clock STEP \u00e2\u20ac\u201d an offset measured against the pre-step clock strands every pairing for the TTL `[REQ-HAZARD-CEREMONY-CLOCK-STEP]`: ### 7.36 The broker control plane and PTY fan-out must NEVER block on a single subscriber connection \u00e2\u20ac\u201d a suspended brain conn must not wedge control `[REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]` <!-- --> - **Failure (paid-for, rig-CONFIRMED 2026-07-06/07 \u00e2\u20ac\u201d `NtSuspendProcess` on the brain, no update involved):** a controller's writer thread does a BLOCKING socket write to its brain subscriber conn. When th"
    }
  },
  {
    "pair_number": 25,
    "id_a": "REQ-RC-HONEST-SESSION-AUTHORITY",
    "id_b": "REQ-SESSIONS-LOG-ENDPOINT-ATTRIBUTION",
    "req_a": {
      "title": "RC-RENDER-TRUTH W1 (ADR-0042 decision 1, hertz perri-contradiction RCA): normal `spt rc` consults the ADR-0041 single honest-session authority BEFORE the persisted-offline fast-fail \u00e2\u20ac\u201d run the bounded SessionProbe::has_live_session_honest gate; honest session exists means attach via run_attach_session_confirmed regardless of persisted status; no honest session means the existing offline refusal stands; claimed session with dead client tree means refusal/reap, NEVER attach. Reuse SessionProbe \u00e2\u20ac\u201d no new liveness heuristic. Kills the authority split where rc refused ('offline \u00e2\u20ac\u201d nothing to attach to') while endpoint run --resume reattached to the same live session. Gate: impl \u00e2\u20ac\u201d the pre-fast-fail probe + session-confirmed routing; unit \u00e2\u20ac\u201d probe-true routes to session-confirmed attach, probe-false keeps the refusal, dead-tree claim refuses; int \u00e2\u20ac\u201d the 3-row regression matrix: offline persisted row + honest live broker session => rc attaches; offline + no session => existing refusal; zombie/dead client tree => refusal, never attach; doc \u00e2\u20ac\u201d ADR-0042.",
      "doc": "Decisions: <!-- --> 1. **`spt rc` consults the honest-session authority before the offline fast-fail.** Normal `spt rc <id>` runs the same bounded `SessionProbe::has_live_session_honest` gate `endpoint run` uses (ADR-0041 single liveness authority). An honest session exists \u00e2\u2020\u2019 attach via the session-confirmed path regardless of persisted status. No honest session \u00e2\u2020\u2019 the existing offline refusal stands. A claimed session with a dead client tree \u00e2\u2020\u2019 refusal/reap, never attach. Reuse `SessionProbe`; no new liveness heuristic. <!-- --> 2. **Resume stamps UNBOUND.** A resume launch transitions an exist"
    },
    "req_b": {
      "title": "C2 (F028, infra; ROOT-CAUSED + severity-upgraded doyle RCA 2026-07-03): cross-endpoint perch contamination \u00e2\u20ac\u201d a foreign psyche's SessionStart hook REBINDS a victim perch's IDENTITY, not merely its ledger. Evidence: hall-a's info.json.session_id IS f015b-probe-psyche's session (359d7bd7) + hall-a's ledger holds the foreign psyche session; same class as hall-b's dead-pid stamp (141556). This REQ = the OBSERVABLE (foreign session_id in a perch's ledger/info.json + resume offering foreign sessions) and its belt-braces: (iii) filter owlery-cwd rows OUT of resume_rows; (iv) one-time repair for already-contaminated perches (hall-a on HFENDULEAM) or self-heal on next legitimate session-start. The ROOT (identity pinned at spawn + honest bind + nested self-resolve) is REQ-BIND-HONEST-SELF-STAMP \u00e2\u20ac\u201d C2 is UPSTREAM of B3 (presence/CONTROLLED read the very stamps this corrupts). See triage C2.",
      "doc": ""
    }
  },
  {
    "pair_number": 26,
    "id_a": "REQ-HAZARD-ENV-SUBST",
    "id_b": "REQ-PSYCHE-SPAWN-ENV-PARITY",
    "req_a": {
      "title": "`spt endpoint run` HONORS manifest [env.<VAR>] direction=inject values (with {key} substitution) on the spt-hosted spawn. Today only the [session.self] command ARGV is {id}-substituted; the [env] inject value is NEITHER substituted NOR applied \u00e2\u20ac\u201d manifest.schema.json promises EnvVar.value = 'Value to inject (with substitution)' but prepare_harness_spawn fills only argv and SpawnReq carries no env, so a [env.SPT_ENDPOINT_ID].value='{id}' arrives EMPTY. A FLAGLESS harness (bare `claude`, no argv slot for {id}) then routes the id via [env] \u00e2\u2020\u2019 empty \u00e2\u2020\u2019 SessionStart sees empty $SPT_ENDPOINT_ID \u00e2\u2020\u2019 seeds-by-PPID instead of binding \u00e2\u2020\u2019 ZERO perch \u00e2\u2020\u2019 NO_PERCH (the actual wall-b bind blocker; perri hard-repro'd). SILENT failure (empty inject, no error). FIX (doyle ruled a): fill every [env] inject value from the SAME {key} catalog as argv/role (mirror F-009 TEMPLATE fill, whole-string fill_template for an env value), thread it through SpawnReq.env \u00e2\u2020\u2019 the broker sets it on the spawned PTY child. Correctness fix \u00e2\u20ac\u201d schema already promises it, NO manifest change, NO new binary. PAIRS with REQ-SEND-SPT-HOSTED to make endpoint run fully work. doyle F-013. (post-v0.10.0)",
      "doc": ""
    },
    "req_b": {
      "title": "P-2 (WORKER-TRUTH triage addendum, perri-filed field finding 2026-07-06): the per-event psyche_resume spawn threads the perch record's CAPTURED read_env stamps into the spawn ENVIRONMENT \u00e2\u20ac\u201d the F-027 Half-B env-parity contract (BINDING, design-frozen: read_env captured at creation + stamped on the record + threaded IDENTICALLY to every session spawn; the spawn never reads its own process env for a stamped var) extended to the psyche role the design predates. Field driver: flynn (claude-spt:ccs) \u00e2\u20ac\u201d the ccs wrapper relocates the account root via CLAUDE_CONFIG_DIR at PARENT launch and the perch record correctly captured it, but the daemon spawns psyche_resume with bare env \u00e2\u2020\u2019 default ~/.claude root \u00e2\u2020\u2019 headless 'Not logged in' exit-1 \u00e2\u2020\u2019 strike loop; psyche + parent land in DIFFERENT account roots (auth AND root-scoped continuity both break). Core stays harness-agnostic (threads whatever [env] direction=read captured \u00e2\u20ac\u201d knows nothing of CLAUDE_CONFIG_DIR). Scope note: this is the URGENT psyche leg of F-027 Half B; the full pre_spawn seam + endpoint-session env threading stays design-parked (F-027-ENDPOINT-SPAWN-FAIL-DESIGN.md) unless operator pulls it forward.",
      "doc": ""
    }
  },
  {
    "pair_number": 27,
    "id_a": "REQ-WHOAMI-EXPLICIT-SID-REFUSAL",
    "id_b": "REQ-WHOAMI-IDENTITY-ONLY",
    "req_a": {
      "title": "RULED DESIGN, delivery unowned (doyle 2026-07-26): when a caller hands identity resolution an EXPLICIT non-empty $OWL_SESSION_ID that resolves to NO perch, core must REFUSE identity (unresolved, exit 1, loud distinct diagnostic) rather than fall through to an ambient/inherited one \u00e2\u20ac\u201d today `detect_self_id` (roster.rs, legs a\u00e2\u2020\u2019b\u00e2\u2020\u2019b2\u00e2\u2020\u2019c) treats sid-UNMATCHED identically to sid-ABSENT, so the fallback chain re-adopts precisely the identity a sharper claim just failed to prove. MEASURED (perri, this node, 2026-07-26, three read-only whoami calls from a genuine descendant of the perri host process): (1) all SPT_*/OWL_* scrubbed \u00e2\u2020\u2019 id null, exit 1 \u00e2\u20ac\u201d ancestry resolved nothing (caveat honored from the probe: the perch's recorded pid was not in the caller's chain, so this run refutes lineage-as-the-mechanism for probe v1 without disproving a lineage path in general); (2) inherited SPT_ENDPOINT_ID=perri + explicit OWL_SESSION_ID matching no perch \u00e2\u2020\u2019 perri, exit 0 \u00e2\u20ac\u201d the mismatch datum was IN HAND (core had already scanned and failed to match the explicit sid) and the ambient id won anyway; (3) real OWL_SESSION_ID with endpoint id scrubbed \u00e2\u2020\u2019 correct self \u00e2\u20ac\u201d the healthy path any fix must leave untouch",
      "doc": ""
    },
    "req_b": {
      "title": "PROJECT-INDEX W1 (F-040, perri filing claude-spt docs/SPT-CORE-FINDINGS.md @d775b38; correctness-critical opener \u00e2\u20ac\u201d the 2026-07-15 message-bodies incident root): a core IDENTITY-ONLY resolution \u00e2\u20ac\u201d session -> endpoint|null \u00e2\u20ac\u201d that touches NO list/registry/project/git/network path, and `spt whoami` DE-ALIASED from cmd_endpoint_list (cli.rs ~6609 aliases the full list = 100+ git children under hook deadlines). endpoint-info is DISQUALIFIED as the carrier (runs latest_project_ref). Adapters/hooks get a bounded-time identity verb; the harness-hosted adapter fallback stays deadline-vulnerable until this ships. Gate: impl \u00e2\u20ac\u201d the resolver + whoami de-alias; unit \u00e2\u20ac\u201d resolver returns endpoint|null with zero project derivation (assert no git spawn seam); int \u00e2\u20ac\u201d whoami on a multi-perch home answers fast-path without touching context branches; doc \u00e2\u20ac\u201d harness-contract api.md names the identity verb + its no-derivation bound. Kin REQ-PROJECT-INDEX-READER-CUTOVER (list-shaped verbs), REQ-WHOAMI-1, docs/PROJECT-INDEX-TRIAGE.md.",
      "doc": "`spt whoami` \u00e2\u20ac\u201d the identity verb *(identity-only since v0.33.0)*: <!-- --> The bounded-time \"which endpoint am I?\" answer for hooks and adapter glue: resolves the calling session to its endpoint (`$OWL_SESSION_ID` / `$SPT_AGENT_ID` / process ancestry) and prints that ONE endpoint's SELF line \u00e2\u20ac\u201d id, liveness, description. **The no-derivation bound is the contract**: whoami never enumerates the roster, never derives projects, never runs git, never touches the network \u00e2\u20ac\u201d safe to call from deadline-bounded hook paths (the class that previously timed out and black-holed message delivery). Unresolved"
    }
  },
  {
    "pair_number": 28,
    "id_a": "REQ-ARCH-3",
    "id_b": "REQ-HAZARD-HANDOFF-ARGV-COMPAT",
    "req_a": {
      "title": "Wire-protocol version independent of crate semver, N-1 compat window",
      "doc": ""
    },
    "req_b": {
      "title": "Broker/brain IPC + handoff argv version-tolerant (2.3)",
      "doc": ""
    }
  },
  {
    "pair_number": 29,
    "id_a": "REQ-ADAPTER-LIVE-UPDATE",
    "id_b": "REQ-UPDATE-ADAPTERS-VERB",
    "req_a": {
      "title": "An adapter update is live and daemon-coordinated (the adapter analog of brain self-update, ADR-0004): for an endpoint with a running RESIDENT adapter binary (today the `[message-idle-translation-binary]`), the CLI keeps fetch+verify and hands the APPLY to the daemon over IPC, which per affected endpoint (1) STOPS the resident binary -> releases the OS file lock (fixes the Windows 'Access denied (os error 5)' overwrite failure), (2) swaps on disk ONLY files whose CRC differs from the staged archive (unchanged files + their still-running binaries untouched), (3) RE-CLONES the new on-disk manifest into the running `BrainLifecycle` (the in-memory manifest is cached at bringup and otherwise goes stale -> binaries+manifest back on the same page), (4) RESTARTS the resident binary from the new files. An endpoint NOT running -> CLI swaps directly (no lock, no cache). Only the resident class is cycled; ephemeral adapter binaries (Psyche loop, `[digest]` extractor, `[session.*]` runners, hooks) self-heal on next spawn and are excluded. The daemon keeps a per-endpoint registry of resident adapter children. (ADR-0025, v0.13.2)",
      "doc": "Live, daemon-coordinated adapter update: <!-- --> // Amendment (W3 build, 2026-06-22): <!-- --> <!-- -->"
    },
    "req_b": {
      "title": "THE-FORKENING W4 (operator-grilled 2026-07-14): `spt update adapters [<a>[,<b>...]]` = thin ALIAS over the existing `spt adapter update` engine (cli.rs:748 gh_release avenue; the old verb STAYS \u00e2\u20ac\u201d published surface) + comma-list accepted on BOTH forms. Semantics: no names -> all gh_release-avenue registrations; names validated FAIL-FAST against the registry BEFORE any update starts (a typo must not leave a half-updated set); per-adapter failure ISOLATION (one failure doesn't stop the rest) with a per-adapter summary line; nonzero exit if any failed; local-path/dev registrations SKIP loud (not error). Gate: unit \u00e2\u20ac\u201d name validation, list parsing, isolation + exit-code aggregation, local-path skip; doc \u00e2\u20ac\u201d reference regen (drift-gated). Kin REQ-UPDATE-DEFAULT-COMPOSITE (the caller), REQ-ADAPTER-UPDATE-MESSAGE (per-adapter apply notices ride the summary).",
      "doc": "Self-update: **update composite (`spt update`)** \u00e2\u20ac\u201d the plain verb is the primary form: `update fetch --apply` then `update adapters` (core-first order); with core already current, only adapters update. `--core-only`/`-c` skips adapters; `spt update adapters [<a>[,<b>\u00e2\u20ac\u00a6]]` is the adapters leg alone (alias over `spt adapter update`). The composite's invoker always survives, because a routine apply cycles only the **brain** \u00e2\u20ac\u201d the *restart-required* message on broker-side releases is a notice, not a restart. `spt update --restart` is the one-step **full cycle**: fetch \u00e2\u2020\u2019 adapters \u00e2\u2020\u2019 `apply --finish`"
    }
  },
  {
    "pair_number": 30,
    "id_a": "REQ-HAZARD-LOCAL-API-AUTH",
    "id_b": "REQ-HAZARD-WAN-ORIGIN-AUTH",
    "req_a": {
      "title": "Every local `api` mutation authenticated to an endpoint/session (codex #13)",
      "doc": ""
    },
    "req_b": {
      "title": "WAN-inbound origin is transport truth, never payload: the access gate's subject (ADR-0009 origin-node whitelist) is the QUIC handshake-proven remote node id from the broker's conn/stream table \u00e2\u20ac\u201d a forged origin/node field inside record bytes is inert (7.5)",
      "doc": "7.4 Per-agent pulse/psyche/echo scheduling must not serialize across agents `[REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]`: ### 7.5 WAN-inbound origin is transport truth, never payload `[REQ-HAZARD-WAN-ORIGIN-AUTH]` <!-- --> - **Failure:** the ADR-0009 access whitelist gates **unsolicited wire inbound by origin node**. If the gate's subject is read from record bytes (an `origin_node`/`from`/`node` field a sender wrote), any sender forges any origin and the whitelist is decoration \u00e2\u20ac\u201d same spoof class as 7.3's Psyche-supplied `from=`, now on the cross-node surface. - **Invariant:** the origin the gate ("
    }
  },
  {
    "pair_number": 31,
    "id_a": "REQ-HAZARD-EBUSY-RENAME",
    "id_b": "REQ-RESUME-HARNESS-SESSION-ID",
    "req_a": {
      "title": "tmp-write + atomic-rename + retry on Windows EBUSY (5.2)",
      "doc": ""
    },
    "req_b": {
      "title": "B2 (F028, hall-b diagnosis, verified 0.22.0): respawn/`--resume` feeds the SPT session id to `claude -r`. After the 02:05 daemon bounce respawn built `claude.exe -r 70b5bfa40901b7d4` \u00e2\u20ac\u201d an spt session id in claude's OWN session-id namespace -> claude hangs forever at a 'No sessions match' resume-picker while the endpoint reads online. Hits after EVERY daemon bounce + every picker Resume. The HARNESS session id (claude UUID, stamped in sessions.log/info.json by the hooks) is what {session_id} must mean in the adapter's [session.resume] command; the spt sid must not leak. FIX: substitute the HARNESS session id in the resume template (spt-core substitution-key semantics + LIKELY claude-spt manifest coordination \u00e2\u20ac\u201d FLAG perri BEFORE touching the manifest, adapter-boundary rule). Int: resume template receives the ledger UUID, not the spt sid. See triage B2.",
      "doc": ""
    }
  },
  {
    "pair_number": 32,
    "id_a": "REQ-RESIDENT-SERVICE",
    "id_b": "REQ-SHELL-ADAPTER-OWNED-DETACHED-SERVICE",
    "req_a": {
      "title": "ResidentService substrate (ADR-0049, design ratified 2026-07-26): a daemon-supervised binary an adapter declares via a `[service]` manifest section \u00e2\u20ac\u201d core-owned from birth, NO perch/identity/address. SPAWN: the daemon launches it job-neutrally (detached_no_inherit + the cold-start ladder posture), so it is never a shell's child (`/T` tree-kill cannot reach it; the shell-descendant hazard class of REQ-SHELL-ADAPTER-OWNED-DETACHED-SERVICE never arises) and never inside a launching terminal's Job Object (the REQ-SHELL-CLI-SPAWN-JOB-EXPOSURE service half closes by construction). START TRIGGER declared in the manifest: start = 'boot' or start = 'bind'; supervised identically once running, with the wake-watcher scaffolding (backoff, give-up latch, one-per-instance lock, orphan-kill, brain-side reconcile) minus the offline-only flip. 'boot' is DESIRED-STATE-RUNNING, not an event: the supervisor reconciles a boot service toward running at EVERY opportunity \u00e2\u20ac\u201d daemon boot, ADAPTER REGISTRATION while the daemon is live (operator addition 2026-07-26: installing or registering an adapter whose manifest declares a boot service starts it THEN \u00e2\u20ac\u201d spt itself is never restarted to bring a new adapter",
      "doc": "`[service]` \u00e2\u20ac\u201d a daemon-supervised resident service (ADR-0049): <!-- --> - **`command`** \u00e2\u20ac\u201d an **opaque** command string (program token plus args), like every other command seam. Its program token resolves against the adapter **install dir** before PATH (REQ-INSTALL-11), and args support adapter-static `{adapter_dir}` / `{adapter_name}` substitution only. Must be non-empty: a declared service means spt-core owns and supervises a process. - **`start`** \u00e2\u20ac\u201d **required**, no default. `\"boot\"` is **desired-state-running, not an event**: the supervisor reconciles the service toward running at daemon bo"
    },
    "req_b": {
      "title": "RULING OWED (unratified \u00e2\u20ac\u201d do NOT satisfy without one): may a shell adapter own a DETACHED process that deliberately OUTLIVES its shell's link-break? Today the answer is 'no, silently, on Windows only'. `shellhost::kill_shell_pid` force-kills with `taskkill /PID <pid> /T /F` \u00e2\u20ac\u201d /T is a TREE kill, so `close_shell` takes the shell's descendants with it; the Unix arm is `kill -9 <pid>`, a SINGLE process. So the same adapter's detached child dies on Windows and survives on Linux, undocumented either way. Two ratified positions collide here and neither may be quietly discarded: (a) CONTEXT \u00c2\u00a7Shell model lifecycle states flatly 'the binary never survives a link-break', and the KNOWN-HAZARDS 'stop lies' RCA (two field cases 2026-07-19, one on doyle's own production endpoint) was paid for precisely because a surviving GRANDCHILD was orphaned where 'even a direct-child kill would have missed it' \u00e2\u20ac\u201d that hazard is the standing argument FOR /T; (b) a node-singleton service that must not drop every time a shell relinks is a legitimate shape, and flynn's alchemy ships one today (Hub Daemon, CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP, lockfile-guarded), depending on surviving link-breaks to keep a ",
      "doc": ""
    }
  },
  {
    "pair_number": 33,
    "id_a": "REQ-ENDPOINT-CYCLE-HONEST",
    "id_b": "REQ-ENDPOINT-ONLINE-TRUTH",
    "req_a": {
      "title": "REGISTRY-LIFECYCLE W3 (ADR-0041 decision 6, operator deployah stop/run wedge): cycle verbs share ONE liveness authority \u00e2\u20ac\u201d the ALREADY_LIVE dup-guard liveness-probes the claimed session client tree before refusing (dead tree means reap + respawn honestly, never a refusal citing a zombie); the shutdown state machine consults the same source so is-it-live has one answer (no ALREADY_LIVE / list-OFFLINE / shutdown-NO_EDGE three-way contradiction on the same endpoint). Gate: impl \u00e2\u20ac\u201d probing dup-guard + unified authority; unit \u00e2\u20ac\u201d dead-tree claim probes and reaps, live claim still refuses; int \u00e2\u20ac\u201d controlled zombie (killed client tree, surviving hosted record) leads to endpoint run succeeding honestly end-to-end; doc \u00e2\u20ac\u201d ADR-0041.",
      "doc": "Decision: <!-- --> 1. **Online is earned, not declared.** A creator may stamp `status=online` only from actual persisted state + hosting authority \u00e2\u20ac\u201d never from manifest capability alone. Legacy hybrid rows self-heal at reconcile, but only after a SUCCESSFUL broker query: a broker failure is never interpreted as an empty session set (no mass-offline on a hiccup). 2. **Control cleanup splits from offline classification.** Reconcile clears `controlled`/`driven_by`/`viewer_count` for EVERY endpoint absent from session truth \u00e2\u20ac\u201d regardless of state or controllability \u00e2\u20ac\u201d while offline classification ke"
    },
    "req_b": {
      "title": "REGISTRY-LIFECYCLE W2 (ADR-0041 decisions 1+2, emphasys C1 P0): ONLINE is earned, not declared \u00e2\u20ac\u201d cmd_listen stamps status=online only from actual persisted state + hosting authority, never from manifest psyche_init capability alone (no more ready_agent/controllable=false hybrid rows born online-authoritative); livehost reconcile SPLITS control cleanup (clear controlled/driven_by/viewer_count for EVERY endpoint absent from session truth, regardless of state/controllable) from offline classification (live_agent+controllable gate unchanged); legacy hybrid rows self-heal after a SUCCESSFUL broker query only (broker failure is never interpreted as an empty session set); terminal signoff/owner-loss = atomic CAS-guarded offline + ready/address removal WITHOUT overloading soft api session-end (/clear preserves the live listener). Gate: impl \u00e2\u20ac\u201d creator gate + reconcile split + self-heal + terminal path; unit \u00e2\u20ac\u201d creator refuses capability-only online, cleanup clears stamps on state-quirk rows, broker-failure never mass-offlines; int \u00e2\u20ac\u201d dead-PID hybrid row does NOT survive a reconcile cycle (the immortal-row regression); doc \u00e2\u20ac\u201d ADR-0041.",
      "doc": "Decision: <!-- --> 1. **Online is earned, not declared.** A creator may stamp `status=online` only from actual persisted state + hosting authority \u00e2\u20ac\u201d never from manifest capability alone. Legacy hybrid rows self-heal at reconcile, but only after a SUCCESSFUL broker query: a broker failure is never interpreted as an empty session set (no mass-offline on a hiccup). 2. **Control cleanup splits from offline classification.** Reconcile clears `controlled`/`driven_by`/`viewer_count` for EVERY endpoint absent from session truth \u00e2\u20ac\u201d regardless of state or controllability \u00e2\u20ac\u201d while offline classification ke"
    }
  },
  {
    "pair_number": 34,
    "id_a": "REQ-HAZARD-REDISPATCH-STALL",
    "id_b": "REQ-HAZARD-REGISTRY-STALL",
    "req_a": {
      "title": "REDISPATCH-STALL W1 (KNOWN-HAZARDS 7.43, hertz v0.34 field RCA 2026-07-16 \u00e2\u20ac\u201d recurrent 20-30s PTY/RC freezes, 17-62s DISPATCH tails): one wedged stream subscriber must NEVER stall stream serving, and recovery machinery must not manufacture new replay victims. Today: claim retries x the broad Err(_) opener fallback (dispatch.rs:414) install throwaway peek subscribers whose StreamLog::attach replays the entire retained ring UNDER the per-stream mutex with discarded write errors and the poisoned subscriber left installed \u00e2\u20ac\u201d serial 15s bounded-write poison windows (33 observed, all 15,000-15,154ms) composing into the field stalls. Gate: int \u00e2\u20ac\u201d production-path regression at the REAL run_dispatch_loop + StreamLog + serve_attach seams: wedge one subscriber conn, prove producer appends and unrelated streams stay flat while the poisoned subscriber is removed and the stream recovers (no abandonment); doc \u00e2\u20ac\u201d KNOWN-HAZARDS 7.43. Binding: redispatch D1/D1b stay green every leg. HEAVY nextest group at birth. Kin REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE + REQ-DISPATCH-FALLBACK-CIRCUIT (the mechanisms), REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK (the deadline that fires), ADR-0038 Amendment.",
      "doc": "7.42 A node holding a valid roster address for a peer is NEVER route-less \u00e2\u20ac\u201d a failed dial must not delete the only bootstrap route `[REQ-HAZARD-MESH-BOOTSTRAP-TRAP]`: ### 7.43 One wedged stream subscriber must NEVER stall stream serving \u00e2\u20ac\u201d replay halts at the first failed write, a poisoned subscriber is removed, and recovery machinery must not manufacture new replay victims `[REQ-HAZARD-REDISPATCH-STALL]` <!-- --> - **Failure (paid-for, hertz field RCA 2026-07-16 \u00e2\u20ac\u201d live v0.34 boxes, recurrent 20\u00e2\u20ac\u201c30s PTY/RC freezes, DISPATCH tails 17\u00e2\u20ac\u201c62s):** a COMPOSITION, not one new timer. The dispatcher's ret"
    },
    "req_b": {
      "title": "REGISTRY-LIFECYCLE W1 (KNOWN-HAZARDS 7.44, hertz post-close v0.36 RCA \u00e2\u20ac\u201d the umbrella conformance seam): streams and seats on a long-lived connection have BOUNDED lifetime. The hertz regression seam verbatim: real long-lived pump + dispatcher integration, N registry-only rounds over ONE persistent connection, asserting (a) dispatcher-eligible Registry rows plateau O(active) not O(N); (b) physical stream/subscriber/seat counts plateau after completion CROSS-FAMILY (sync/update seats included, not just Registry); (c) snapshot writes O(feeds) not O(chunks x record-kinds); (d) brain refresh produces ZERO historical Registry replay subscriptions; (e) zero CONN_WRITE_POISONED / replay-write-failed events; (f) broker thread count returns to a bounded baseline. HEAVY nextest group at birth (spawns a real daemon tree). Binding: redispatch D1/D1b + REDISPATCH-STALL T1-T7 + mesh-recovery legs green every leg (retire machinery + registry gate = substrate). Gate: int \u00e2\u20ac\u201d the seam above; doc \u00e2\u20ac\u201d KNOWN-HAZARDS 7.44.",
      "doc": "7.43 One wedged stream subscriber must NEVER stall stream serving \u00e2\u20ac\u201d replay halts at the first failed write, a poisoned subscriber is removed, and recovery machinery must not manufacture new replay victims `[REQ-HAZARD-REDISPATCH-STALL]`: ### 7.44 Streams and seats on a long-lived connection must have bounded lifetime \u00e2\u20ac\u201d one-way rows terminal at FIN, seats released at serve completion, no per-chunk full-state rewrites in a drain loop `[REQ-HAZARD-REGISTRY-STALL]` <!-- --> - **Failure (paid-for, hertz post-close v0.36 field RCA 2026-07-17 \u00e2\u20ac\u201d live prod box):** FOUR compounding consequences of \"phys"
    }
  },
  {
    "pair_number": 35,
    "id_a": "REQ-HAZARD-SOFT-CLEANUP",
    "id_b": "REQ-HAZARD-EPHEMERAL-CLEANUP",
    "req_a": {
      "title": "Soft-cleanup preserves state, removes only the ready marker (6.2)",
      "doc": ""
    },
    "req_b": {
      "title": "Ephemeral perch cleanup on every ring exit path (3.1)",
      "doc": ""
    }
  },
  {
    "pair_number": 36,
    "id_a": "REQ-HAZARD-UNC-PATH-STRIP",
    "id_b": "REQ-UPDATE-FINISH-COMMUNE-FLUSH",
    "req_a": {
      "title": "Strip Windows UNC prefix on serialized paths (5.4)",
      "doc": ""
    },
    "req_b": {
      "title": "DEFERRED (post-LIFECYCLE-TRUTH, operator-ruled 2026-07-07 \u00e2\u20ac\u201d mint now, impl a FUTURE milestone): make the update swap LOSSLESS for live hosted endpoints by flushing a final echo-commune per endpoint BEFORE the brain-subtree reap. ROOT (operator-surfaced probing --finish): `update apply --finish` = daemonless swap -> daemon RESTART; the graceful `daemon stop` path (daemon.rs:316-325) raises brain_stop then reaper.reap() KILLS the brain subtree (brain + shellwake watchers + detached Psyches) as one unit \u00e2\u20ac\u201d there is NO per-endpoint final commune before the kill. ENDPOINT-SURVIVAL (REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL) then RESPAWNS each orphaned online spt-hosted endpoint, but from its LAST commune (whatever the ongoing per-event echo-commune cadence last saved), NOT an as-of-swap checkpoint \u00e2\u20ac\u201d so mid-turn / uncommuned work is lost across the bounce. Today's mitigation is operator discipline: commune-before-swap. FIX (future): the stop/finish path, before reap, drives each LIVE hosted endpoint's final echo-commune (fire_echo final context save) so the respawn resumes from a swap-fresh checkpoint. Composes with ENDPOINT-SURVIVAL (commune -> reap -> respawn) and the W1 echo pipeline (REQ-EC",
      "doc": ""
    }
  },
  {
    "pair_number": 37,
    "id_a": "REQ-ENSURE-DAEMON-STOP-INHIBIT",
    "id_b": "REQ-START-3",
    "req_a": {
      "title": "An operator stop outranks every implicit daemon ensure. (ADR-0047 decision 2, AMENDING REQ-DAEMON-3's anchor; hertz v0.39.4 field bug 2, RCA accepted 2026-07-22.) TODAY: every `spt api` invocation runs unconditional ensure_daemon() (api/mod.rs run()), so on a box with live adapter sessions a `daemon stop --force` loses the race to hook-driven api calls \u00e2\u20ac\u201d respawn convoy (5-10 ephemeral spawner windows), several stops to stay down; the rc-side twins were fixed earlier, the api anchor stayed armed. FIX: `daemon stop` records a durable machine-scoped STOP INHIBIT before teardown begins; ensure_daemon()/ensure_running() consult it and DECLINE with one honest line naming the remedy ('daemon stopped by operator \u00e2\u20ac\u201d spt daemon start to resume'); cleared by intent verbs ONLY (explicit `daemon start`; update paths that restart by design) \u00e2\u20ac\u201d NO TTL (rejected: a timeout is the surprise respawn again, later); implicit autostart additionally takes a machine-wide lock around probe-and-spawn so N concurrent callers never launch N daemons. Gate: doc \u00e2\u20ac\u201d the stop/start contract on the daemon CLI docs (stop now sticks; the refusal line + remedy named); impl \u00e2\u20ac\u201d inhibit mint in cmd_stop + consult in both imp",
      "doc": "The daemon: broker and brain: <!-- --> \u00e2\u20ac\u00a6with one exception, because you are allowed to mean it: **`spt daemon stop` sticks.** Auto-start is a convenience, and a convenience never overrules an explicit instruction. Once you stop the daemon, the implicit auto-start that every `spt` invocation performs *declines* to bring it back, printing one line that names the way out // 2. An operator stop outranks every implicit ensure: <!-- -->"
    },
    "req_b": {
      "title": "spt-hosted startup: spawn-session then api bind (no file)",
      "doc": ""
    }
  },
  {
    "pair_number": 38,
    "id_a": "REQ-HAZARD-REGISTRY-DIR-CREATE",
    "id_b": "REQ-INSTALL-BOOTSTRAP-VERB",
    "req_a": {
      "title": "SQLite store opens create their parent dir themselves \u00e2\u20ac\u201d a fresh-home registry op must not SQLITE_CANTOPEN (4.9)",
      "doc": "4.8 Registry merge ordered by epoch, never wall-clock (red-team #8): ### 4.9 SQLite stores must create their parent dir \u00e2\u20ac\u201d SQLite won't <!-- --> - **Failure:** `Connection::open` creates the database FILE but never its parent DIRECTORY. On a fresh home (first boot, fresh CI `_work` dir) a registry op that runs before any perch-creating op (`create_dir_all` side effects) fails `SQLITE_CANTOPEN` \u00e2\u20ac\u201d \"unable to open database file \u00e2\u20ac\u00a6owlery\\.registry\". Timing-dependent: whichever code path touches the home first decides the outcome, so it surfaces as a parallel-test flake (bind-first tests losing the d"
    },
    "req_b": {
      "title": "THE-FORKENING W1 (ADR-0036 \u00c2\u00a73, bootstrap shape b operator-ruled): virgin-box install = `gh release download` the platform binary + ONE self-install verb in the binary itself \u00e2\u20ac\u201d the verb places the binary at the canonical install path (the path self-update already respawns from, v0.4.2 lesson), registers user PATH, and leaves first-run identity/daemon-start to the existing idempotent first-run; hosted one-liner install scripts (curl|sh / irm|iex at the dead Pages URL) are RETIRED. Non-interactive (CONTEXT.md Installation: the install path doubles as every adapter's pack-in on-demand install). Windows UAC-740 gotcha binding: the downloaded exe keeps the `spt-*` asset name and the verb lives INSIDE spt \u00e2\u20ac\u201d no installer-detection trigger words in exe names. README (bs-core) documents: install gh -> gh auth login -> gh release download -> the verb. Gate: unit \u00e2\u20ac\u201d verb places/registers idempotently, refuses cross-platform binaries (platform-stamp check exists, v0.3.2); int \u00e2\u20ac\u201d from a clean SPT_HOME+PATH sim, downloaded-binary self-install yields a working `spt` on PATH whose `spt update fetch` then speaks the gh channel; doc \u00e2\u20ac\u201d README install section rewritten. Kin REQ-UPDATE-GH-TRANSPORT, REQ-I",
      "doc": "Installation: **Installer form (gh bootstrap, ADR-0036):** install gh \u00e2\u2020\u2019 `gh auth login` (org membership) \u00e2\u2020\u2019 `gh release download` the platform binary from the private channel \u00e2\u2020\u2019 one **self-install verb** in the binary places it at the canonical install path and registers the *user* PATH (so adapters call `spt api \u00e2\u20ac\u00a6` cross-OS); first-run identity gen + daemon start stay the existing idempotent unattended first-run. Hosted one-liner scripts are retired with the public channel; first-fetch trust = gh's authenticated TLS + org membership (full ed25519 verification is `spt update`'s job thereafter)."
    }
  },
  {
    "pair_number": 39,
    "id_a": "REQ-HAZARD-BRAIN-RESTART-LIFECYCLE-REHYDRATE",
    "id_b": "REQ-HAZARD-WAN-ORIGIN-AUTH",
    "req_a": {
      "title": "B4 (deepest): a bare brain restart (broker survives) REHYDRATES the live-agent lifecycle so post-restart endpoints are hosted + attachable. Today resume_sessions (brainproc.rs:186, brain.rs:797-809) re-subscribes to the broker's PTY sessions but ALL BrainLifecycle instances (lifecycle.rs:58-130; the ephemeral brain.rs:254-275) are LOST on restart \u00e2\u2020\u2019 a post-restart live endpoint gets no livehost \u00e2\u2020\u2019 its Psyche is never (re)hosted and new spawns die / can't attach until a FULL daemon reset (operator: perri's brain kill+restart wedged everything until a full daemon kill). FIX: on brain startup, rebuild a BrainLifecycle per resumed live-capable session \u00e2\u20ac\u201d load the manifest from the adapter registry \u00e2\u2020\u2019 instantiate \u00e2\u2020\u2019 start the pulse \u00e2\u20ac\u201d the rehydrate the resume no-op cannot do. Composes with B2 (the reconcile re-hosts from the honest on-disk status after rehydrate). (v0.12.0)",
      "doc": ""
    },
    "req_b": {
      "title": "WAN-inbound origin is transport truth, never payload: the access gate's subject (ADR-0009 origin-node whitelist) is the QUIC handshake-proven remote node id from the broker's conn/stream table \u00e2\u20ac\u201d a forged origin/node field inside record bytes is inert (7.5)",
      "doc": "7.4 Per-agent pulse/psyche/echo scheduling must not serialize across agents `[REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]`: ### 7.5 WAN-inbound origin is transport truth, never payload `[REQ-HAZARD-WAN-ORIGIN-AUTH]` <!-- --> - **Failure:** the ADR-0009 access whitelist gates **unsolicited wire inbound by origin node**. If the gate's subject is read from record bytes (an `origin_node`/`from`/`node` field a sender wrote), any sender forges any origin and the whitelist is decoration \u00e2\u20ac\u201d same spoof class as 7.3's Psyche-supplied `from=`, now on the cross-node surface. - **Invariant:** the origin the gate ("
    }
  },
  {
    "pair_number": 40,
    "id_a": "REQ-PICKER-START-PROJECT-CHOICE",
    "id_b": "REQ-RUN-PICKER-HOME",
    "req_a": {
      "title": "#5: after 'Start now' in the endpoint picker, swap the bottom Options panel to a 'Choose project' list: (1) the endpoint's most recent project dir, (2) 'Here: <dir>' \u00e2\u20ac\u201d the spt endpoint run cwd (only if different), (3) all other project-history dirs newest->oldest. Fire the step ONLY when (A) the run cwd mismatches a singular history entry, or (B) history has >1 entry; otherwise start immediately (today's behavior). Depends on REQ-PICKER-PROJECT-HISTORY-TRUTH (needs full DIRS from sessions.log, owlery-internal exclusion applies). Start-now is local-only (no gossip). See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #5.",
      "doc": ""
    },
    "req_b": {
      "title": "Home-subnet selection LAYER in the `spt endpoint run` ratatui Create-new picker (v0.14.1; the deferred half of REQ-RUN-MULTISUBNET-HOME's interactive path \u00e2\u20ac\u201d ADR-0026 \u00c2\u00a73 'the interactive picker lists subnets MRU-ordered'). On a MULTI-SUBNET node the Create-new flow gains a `CreateHome` screen (CreateAdapter \u00e2\u2020\u2019 CreateId \u00e2\u2020\u2019 CreateHome \u00e2\u2020\u2019 Confirm) that lists the node's MEMBER subnets MRU-ordered (reusing recent_home::mru_preference + order_by_mru), default cursor = MRU head; the chosen subnet rides Outcome::Run{subnet} into cmd_endpoint_run's --subnet, so decide_run_home resolves Home directly and the post-TUI `Ok to proceed? Y/n` confirm NEVER fires for the picker path. Single-subnet / local-only nodes SKIP the layer (assign_home auto-homes; CreateId \u00e2\u2020\u2019 Confirm unchanged). The CLI / flagged `endpoint run` path KEEPS the decide_run_home Y/n confirm + the non-interactive MULTI_SUBNET_HOME refuse (operator: the confirm stays useful for CLI-only bringup, just not in the TUI). Esc backs CreateHome \u00e2\u2020\u2019 CreateId; Enter selects \u00e2\u2020\u2019 Confirm. Pure front-end invariant preserved: the layer only collects --subnet, routes through the one bringup core.",
      "doc": "Shell sleep/wake (offline \u00e2\u2020\u201d online): **spt-hosted bringup picker (`spt endpoint run`)** (M12-W2): <!-- --> The user-facing bringup flow for spt-hosted endpoints. **Bare `spt endpoint run`** (no `--adapter`/`--id`) opens an in-process **ratatui picker**; the **flagged** form is the non-interactive bringup path (`--adapter <a[:profile]> --id <id> --create|--resume <session> --start|--attach|--view`), untouched \u00e2\u20ac\u201d a picker selection bakes exactly that path. **Layer 1** picks the kind (*Create new* | *Pick existing*). **Create-new** chooses a registered `kind=\"harness\"` adapter with its shipped+loc"
    }
  },
  {
    "pair_number": 41,
    "id_a": "REQ-HAZARD-SPOOL-SENTINEL-CREATE-FAIL",
    "id_b": "REQ-HAZARD-STALE-SIGNOFF-SENTINEL",
    "req_a": {
      "title": "A persistent failure to (re)create the spool has-messages sentinel is SURFACED, never silently swallowed. touch_has_messages (spt-store/src/spool.rs:147) does `let _ = File::create(...)` \u00e2\u20ac\u201d a live field defect on ENLYZEAM left a stale .has-messages (2026-06-29) beside a fresh spool.db insert (06:59:17Z) in ONE directory, i.e. the create silently failed while rows accumulated (suspected read-only-attrib / share-lock). FIX: on File::create failure emit a LOUD-ONCE-per-perch stderr diagnostic naming the concrete io::Error (self-identifying regardless of kind); do NOT make it fatal (spool writes still proceed). (F-024C item 2, doyle)",
      "doc": ""
    },
    "req_b": {
      "title": "Stale signoff sentinel does not kill a fresh start (3.2)",
      "doc": ""
    }
  },
  {
    "pair_number": 42,
    "id_a": "REQ-ENDPOINT-TEARDOWN-AUTHORITY",
    "id_b": "REQ-HAZARD-CASCADE-WIPE-GUARD",
    "req_a": {
      "title": "TEARDOWN-AUTHORITY W1 (ADR-0045; two hertz field RCAs 2026-07-19, doyle code-verified + ruled): ONE shared topology-aware broker-teardown primitive behind BOTH `endpoint shutdown` and `endpoint stop`. Today both verbs stamp state they never cause: cmd_shutdown (cli.rs:3979) = remove ready marker + cmd_rest(Suspend) \u00e2\u20ac\u201d the Suspend edge (resting.rs daemon_rest_event_with_liveness -> apply_event -> cascade_shells_on_edge -> advertise) fires echo + shell cascade + advertise and NEVER touches a session, even though the verb already probed broker-session truth (cli.rs:3797-3804 has_live_session_honest) to force from=alive; cmd_stop (cli.rs:7071) = marker + unregister_address + terminal_normalize + advertise, whose own comment calls it a DEFINITIVE death observation it fabricates. Field: broker retains the whole subtree (adapter -> node -> harness -> nested resumed harness + MCP children; the surviving `spt api listen` is a DESCENDANT, so a direct-child kill misses it). PRIMITIVE (ordered, ADR-0045 decisions 1/6/7/9): resolve broker SessionInfo -> dedicated sid/endpoint-keyed broker kill claiming NO controller (NOT Brain::attach()+kill_session(), brain.rs:622 require_session() = controller",
      "doc": "7.48 At most one input-capable controller lease per PTY session \u00e2\u20ac\u201d takeover revokes atomically and loudly, input is fenced to the active lease, node identity is never a lease `[REQ-HAZARD-CONTROLLER-LEASE]`: ### 7.49 A teardown verb never stamps a terminal or resting state it has not caused \u00e2\u20ac\u201d and two individually-correct verbs must not compose into a lifecycle dead end `[REQ-HAZARD-TEARDOWN-DEADEND]` <!-- --> <!-- --> - **Failure (paid-for, two hertz field RCAs 2026-07-19, both doyle code-verified the same day; the second hit doyle's OWN live production endpoint):** `endpoint shutdown` reported"
    },
    "req_b": {
      "title": "No hard-delete of a parent hosting non-empty children (6.3)",
      "doc": ""
    }
  },
  {
    "pair_number": 43,
    "id_a": "REQ-NODE-IDENTITY",
    "id_b": "REQ-RC-IDENTITY",
    "req_a": {
      "title": "Ed25519 identity primitive: keypair, detached sign/verify, stable pubkey<->hex",
      "doc": ""
    },
    "req_b": {
      "title": "`spt rc` overlays a persistent endpoint-identity marker so the operator always groks which endpoint they control: a reserved TOP status row via a DECSTBM scroll-region (shrink the PTY's reported rows by 1, own the row), right-aligned `SUBNET : ENDPOINT_ID @ NODE`, CYAN text. Re-assert the margin + repaint on alt-screen enter / DECSTBM reset / resize (output-scanning, like the existing mouse_scanner). NO window title (the harness, e.g. CC, owns it for busyness \u00e2\u20ac\u201d OSC dropped). Resolve subnet/node/id once at attach (perch/registry read) and thread into the pump; subnet = the endpoint's home/primary (\"local\" if none). The literal floating rounded-rectangle corner box is DEFERRED to the future web-based GUI (not a grid-model rc \u00e2\u20ac\u201d that lift is better spent on the GUI). (v0.16.0)",
      "doc": ""
    }
  },
  {
    "pair_number": 44,
    "id_a": "REQ-BRAIN-RESUME-NO-CONTROL-STEAL",
    "id_b": "REQ-CONTROLLER-LIVENESS-REAP",
    "req_a": {
      "title": "UPDATE-WEDGE round 2 (v0.30.4, doyle-ruled 2026-07-09 \u00e2\u20ac\u201d field incident on the counter-54 fetch--apply): a brain-respawn must NEVER steal, then stall-evict, the controller of a broker PTY session the daemon brain does not DRIVE. ROOT (field-pinned + SME, docs/UPDATE-WEDGE-2-RCA.md + docs/UPDATE-WEDGE-2-SME-todlando.md): `resume_sessions` (brain.rs:985) re-attaches EVERY session `KIND_SESSIONS` returns via `subscribe` = `subscribe_with(AttachIntent::Control, by:None)` (brain.rs:1450-1455). The docstring's 'a None identity never displaces (falls back to viewer)' is a MYTH for FREE / SAME-LOCAL-IDENTITY slots: `resolve_subscribe` (broker.rs:1134-1153) stall-evicts FIRST, then `become_controller` if the slot is now free OR the incumbent is also local (None==None) \u00e2\u20ac\u201d viewer-fallback fires ONLY when a DIFFERENT REMOTE controls. So on a box with N spt-hosted broker PTYs, a brain-respawn STEALS the by:None controller of every free/local-controlled session (incl. the operator's LOCAL `spt rc`), which the daemon brain never drains (it hosts no PTY sessions \u00e2\u20ac\u201d brainproc.rs:184) \u00e2\u2020\u2019 15s later `stall_evict_controller` (broker.rs:1039) releases driven_by \u00e2\u2020\u2019 the session is UNCONTROLLABLE (Failure A). I",
      "doc": "Self-update: **resume re-attach is view-only for non-driven sessions** \u00e2\u20ac\u201d on respawn the new brain queries the broker for every hosted session and re-attaches to rebuild output-continuity cursors, but it re-attaches as a **viewer**, never a controller, for any session it does not itself drive (which is *all* of them today \u00e2\u20ac\u201d the supervised daemon brain hosts no PTY sessions; spt-hosted PTYs are driven by the operator's attach or the endpoint's own loop). Re-attaching as a controller would seize the controller slot of every free/local-controlled session \u00e2\u20ac\u201d including the operator's local `spt rc` \u00e2\u20ac\u201d"
    },
    "req_b": {
      "title": "B-2 (REMOTE-TRUTH triage \u00c2\u00a7B-2, REDUCED @bdc1242): a stale ONLINE+CONTROLLED stamp on a live-session perch self-heals \u00e2\u20ac\u201d the info.json driven_by/controlled RECORD is made to match the broker's SINK-TABLE TRUTH. ROOT (persisted-stale-stamp class, doyle Q1): the livehost control reap gates on !has_session (reconcile_hosted_liveness), and a brain-only update KEEPS the session (REQ-UPD-3), so a controller stamp that went stale WHILE the session lived was never re-derived from broker truth. NOT a transport bug: (a) a persisted conn is the REQ-UPD-3 feature; (b) an idle-severed conn eventually EOFs via QUIC keepalive \u00e2\u2020\u2019 handle_conn detach (path 1) \u00e2\u20ac\u201d and the reason paths 1-3 previously failed to clean up was the B-1 broker floor-lock POISON WEDGE (cleanup panicked under the poisoned lock), now fixed. REDUCTION (doyle, my ground-truth): the prescription was 80% pre-built \u00e2\u20ac\u201d converge_perch_stamps (broker.rs, REQ-HAZARD-CONTROL-STAMP-CONVERGENCE) ALREADY converges info.json driven_by/controlled to the broker's controller_by/has_controller on EVERY KIND_SESSIONS poll, and the livehost reconcile already TRIGGERS that poll per tick (query_live_session_endpoints). So NO new IPC query, NO new livehos",
      "doc": ""
    }
  },
  {
    "pair_number": 45,
    "id_a": "REQ-HAZARD-CONTROL-STAMP-CONVERGENCE",
    "id_b": "REQ-STAMP-CONVERGENCE-ORDER",
    "req_a": {
      "title": "Control/viewer stamps must CONVERGE to broker session-table truth for every session-backed endpoint, not merely edge-trigger \u00e2\u20ac\u201d the UPWARD companion to the DOWNWARD edge-clear REQ-HAZARD-CONTROL-STAMP-LIFETIME (7.27); same 'stamps == broker truth' family. ROOT (F-026 stamp-gap, hall-b/ball-b): a picker-created endpoint's broker spawn become_controller->stamp_driven_by->set_controlled(true) fires BEFORE the adapter binds its perch (a fresh endpoint has no perch until claude boots + binds), so mutate_info returns NotFound and the edge stamp is SWALLOWED (let _); the adapter's bind then writes InfoJson::new with controlled:false DEFAULT and no later edge re-stamps -> the endpoint reads uncontrolled FOREVER while driven (the #3 display fix is correct but datum-starved on this creation path). FIX: the broker (SINGLE WRITER) re-asserts each live session's control/viewer stamps to session-table truth, DIVERGENCE-GATED (read info; compare driven_by/controlled/viewer_count; write ONLY on diff \u00e2\u20ac\u201d no per-poll fsync storm), on the KIND_SESSIONS handler (piggyback: the daemon reconcile + picker poll it, so a fresh perch converges within one reconcile-poll window after bind = the BOUNDED window, n",
      "doc": "7.28 A relative manifest path resolves against the ENDPOINT, never the daemon `[REQ-STORE-CONTEXT-BRANCH-FILL]`: <!-- --> ### 7.29 Control/viewer stamps CONVERGE to broker session-table truth, not merely edge-trigger `[REQ-HAZARD-CONTROL-STAMP-CONVERGENCE]` - **Failure (F-026 stamp-gap, hall-b + the original ball-b):** a picker-created endpoint (`endpoint run` \u00e2\u2020\u2019 new) read plain `ONLINE` in the list + picker while genuinely driven \u00e2\u20ac\u201d `info.json` `controlled:false` throughout. ROOT (the UPWARD companion to 7.27's downward edge-clear): the broker spawn path's `become_controller` \u00e2\u2020\u2019 `stamp_driven_by"
    },
    "req_b": {
      "title": "RETIRED at W2 verify-first (doyle 2026-07-22): the C3 poll-vs-reap ordering race (hertz emphasys RCA 2026-07-16, P1) was ALREADY CLOSED by REQ-CONTROL-STAMP-CONVERGENCE (ADR-0041 decision 4, REGISTRY-LIFECYCLE W2 build todlando 2026-07-17 \u00e2\u20ac\u201d one day after the RCA). Code-verified chain: the exit-waiter reap (broker.rs stamp_reaped) bumps a per-endpoint StampSlot generation UNDER the stamp-write serial then clears (the comment names the emphasys C3 window); the KIND_SESSIONS handler snapshots stamp_gen under the log lock; converge_perch_stamps validates snap_gen under the same write serial and REFUSES stale (STAMP_STALE_SKIP) \u00e2\u20ac\u201d a pre-reap snapshot can never relatch controlled=true. Full stage coverage rides that REQ: unit stale_snapshot_stamp_write_refused_after_reap_generation_bump + int endpoint_lifecycle.rs:488. Kept as a stable pointer; no build owed.",
      "doc": ""
    }
  },
  {
    "pair_number": 46,
    "id_a": "REQ-HAZARD-DAEMON-HOSTED-LIVENESS",
    "id_b": "REQ-HAZARD-HOSTED-LIVENESS-RECONCILE",
    "req_a": {
      "title": "Daemon-hosted perches (Psyche, spt-hosted Self) derive liveness from the daemon endpoint table + info.json status, never is_process_alive(info.pid) (2.5)",
      "doc": ""
    },
    "req_b": {
      "title": "B2 KEYSTONE: a daemon-hosted (spt-hosted) endpoint's info.json status is RECONCILED to real liveness, not left latched online. The broker exit-waiter (broker.rs:889-910) reaps its in-mem session table + emits ExitEvent but NEVER touches info.json; lifecycle::mark_offline only fires on Psyche teardown \u00e2\u20ac\u201d so a dead/exited harness (operator closed the tab) stays status=online forever (is_perch_alive returns ONLINE for daemon-hosted, liveness.rs:80-93). FIX (doyle ruled PULL-PRIMARY \u00e2\u20ac\u201d the live-status analog of REQ-HAZARD-ROSTER-GHOST): the livehost reconcile loop (reconcile_once livehost.rs:226-313) queries the broker's live session set (KIND_SESSIONS) each tick and, for any status=online live_agent perch PAST the boot grace whose endpoint has NO live broker session, marks it offline (lifecycle::mark_offline \u00e2\u2020\u2019 status=offline \u00e2\u2020\u2019 is_perch_alive=false). GATED on spt-hosted (controllable==Some(true)) so a HARNESS-HOSTED relay live agent (api listen, legitimately online with no broker session) is NEVER mis-marked. Crash-robust + self-healing on the next tick (clear-on-event is not crash-robust alone). PUSH (brain ExitEvent\u00e2\u2020\u2019mark_offline) is an OPTIONAL fast-path only if the daemon brain is rel",
      "doc": ""
    }
  },
  {
    "pair_number": 47,
    "id_a": "REQ-EP-1",
    "id_b": "REQ-EP-2",
    "req_a": {
      "title": "Day-one endpoint types; open type system",
      "doc": ""
    },
    "req_b": {
      "title": "Agent endpoints vs Shells distinction in the type model",
      "doc": ""
    }
  },
  {
    "pair_number": 48,
    "id_a": "REQ-PICKER-PURGE-SHORTCUT",
    "id_b": "REQ-PICKER-START-PROJECT-CHOICE",
    "req_a": {
      "title": "C-3 (REMOTE-TRUTH triage \u00c2\u00a7C-3 #8): the pick-existing list gains an `x` purge shortcut \u00e2\u20ac\u201d on an OFFLINE LOCAL highlight, `x` opens a small in-TUI confirm screen (Screen::ConfirmPurge, the B-2 ChangeAdapterPick shape) and Enter purges via the ONE existing purge core `cmd_endpoint_purge(id, yes=true, force=false)` \u00e2\u20ac\u201d NEVER the core's stdin [y/N] (it fights the picker's raw mode; the confirm screen IS the confirm). The shortcut INHERITS both purge gates (offline-only + node-local, cli.rs cmd_endpoint_purge / CONTEXT:189): gated-off presses stay on the list and FLASH WHY (online \u00e2\u2020\u2019 offline-only, remote \u00e2\u2020\u2019 local-only). force=false is deliberate \u00e2\u20ac\u201d the model gate is advisory; the core's own offline check is the authority, and a race to online between gate and purge must REFUSE, never stop-then-purge. After a successful purge the picker STAYS (inline outcome, like Shortcut/ChangeAdapter): the row leaves the in-memory list (remove_endpoint, cursor re-clamped) + flash PURGED:{id}. Hint truth (B-1/F029 discipline): the pick legend renders `x purge` ONLY when purge_key_live() \u00e2\u20ac\u201d the same predicate the handler gates on. Red-first: purge outcome reachable ONLY from an offline LOCAL highlight (online/r",
      "doc": ""
    },
    "req_b": {
      "title": "#5: after 'Start now' in the endpoint picker, swap the bottom Options panel to a 'Choose project' list: (1) the endpoint's most recent project dir, (2) 'Here: <dir>' \u00e2\u20ac\u201d the spt endpoint run cwd (only if different), (3) all other project-history dirs newest->oldest. Fire the step ONLY when (A) the run cwd mismatches a singular history entry, or (B) history has >1 entry; otherwise start immediately (today's behavior). Depends on REQ-PICKER-PROJECT-HISTORY-TRUTH (needs full DIRS from sessions.log, owlery-internal exclusion applies). Start-now is local-only (no gossip). See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #5.",
      "doc": ""
    }
  },
  {
    "pair_number": 49,
    "id_a": "REQ-GOSSIP-ADAPTER-PROJECTS",
    "id_b": "REQ-PICKER-PROJECT-HISTORY-TRUTH",
    "req_a": {
      "title": "#4: remote endpoint details (harness + project history) are gossiped, not faked. Today from_resource_row (crates/spt/src/picker/model.rs:340) hardcodes project_history=Vec::new() for every remote row and passes adapter_profile=row.resources (the blurb masquerading as the harness), and Instance/ResourceRow (crates/spt-net/src/net/registry.rs:457) carry no adapter field and no project list. Fix: additive gossip fields N-1-safe exactly like endpoint_type \u00e2\u20ac\u201d Instance.adapter (composite <adapter>[:profile]) + Instance.recent_projects (bounded, newest-first, project IDs only) -> thread to ResourceRow -> from_resource_row stops faking. Pre-field remote rows render '-'. Project IDs only + REQ-PICKER-PROJECT-HISTORY-TRUTH's disambiguation. See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #4.",
      "doc": ""
    },
    "req_b": {
      "title": "#1: picker project history is derived from sessions.log cwds (newest->oldest, deduped by project_id_for_dir) UNION context-store branches, EXCLUDING owlery-internal paths (any cwd under spt_home()/owlery) everywhere a project is displayed or inferred. Fixes three stacked defects (crates/spt/src/picker/data.rs): (1a) project_history_for (data.rs:372) reads ONLY context-store p-* branches, which are empty on this box -> history []; (1b) the fallback origin project (data.rs:207) is derived from info.json.cwd = latest-boot-cwd (rewritten every rebind), not origin; (1c) psyche-host sessions bind owlery-internal cwds that pollute history. Full DIRS stay available in the model (feature #5 needs them). PROJECT REPRESENTATION RULING (operator 2026-07-03): project IDs ONLY, EVERYWHERE incl local display; on ID collision disambiguate minimally via a PURE disambiguate_project_ids(entries)->display-names fn (append one-level-up parent folder and/or root drive letter, e.g. 'spt-core (projects)' vs 'spt-core (D:)'). See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #1.",
      "doc": ""
    }
  },
  {
    "pair_number": 50,
    "id_a": "REQ-MSG-DELIVERY-AXES",
    "id_b": "REQ-MSG-IDLE-EDGE-DRAIN",
    "req_a": {
      "title": "Activity-gated inbound delivery + per-message send control as THREE ORTHOGONAL AXES plus opaque metadata (ADR-0028; grilled w/ operator 2026-06-23). SUBSTRATE (the legacy-SPT parity gap, scaffolded-but-unwired today: `delivery::is_idle` + `resolve_inject_methods` exist but the result is discarded `let _methods`, and `broker::dispatch_endpoint_input` injects unconditionally \u00e2\u20ac\u201d its comment calls activity-gating 'a deferred follow wave'): an inbound message has an ACTIVE window (endpoint active \u00e2\u2020\u2019 spool for the receiver's hook-poll, non-disruptive) and an IDLE window (idle/idle-transition \u00e2\u2020\u2019 deliver immediately: translation binary spt-hosted \u00e2\u2020\u2019 relay-poll either topology \u00e2\u2020\u2019 spool, in fallback order). AXES (each composes; each defaults to its unrestricted value): (1) DELIVERY WINDOW \u00e2\u20ac\u201d default (both, first-to-fire) | `--idle-only` (idle window; immediate if already idle) | `--active-only` (active window only, never wakes; the RENAMED `--deferred` \u00e2\u20ac\u201d `deferred=1` spool column + `api poll --include-deferred` keep their names). (2) CHANNEL RESTRICTION \u00e2\u20ac\u201d unrestricted | `--prefer-native` (translation binary if running else fall back) | `--force-native` (binary ONLY, no fallback/no spool-to-other-m",
      "doc": "Activity-gated message delivery + send-modifier axes: <!-- -->"
    },
    "req_b": {
      "title": "On an endpoint's ACTIVE\u00e2\u2020\u2019IDLE transition the daemon DRAINS its pending spool (deferred AND non-deferred) through the same shared spt-hosted inject leg \u00e2\u20ac\u201d closing the SECOND F-023 gap: no idle-edge drain exists anywhere, so an spt-hosted endpoint (which has no api-listen relay to wake it) strands BOTH message classes ('ACTIVE \u00e2\u2020\u2019 spool deferred for hook-poll' and 'IDLE+no-binary \u00e2\u2020\u2019 non-deferred for a relay that does not exist'). FIX: on the state ACTIVE\u00e2\u2020\u2019IDLE edge, offer the pending spool through the shared inject leg; REUSE the hook-poll drain's take/ack machinery so a concurrent `api poll` cannot double-deliver \u00e2\u20ac\u201d ONE drain path, TWO triggers (hook-poll + idle-edge). v0.14.3 LAW holds on BOTH triggers: translation-binary-ONLY, a no-binary idle drain SPOOLS LOUD, never writes the PTY. (F-023, BUILD-F023-WANIDLE)",
      "doc": ""
    }
  },
  {
    "pair_number": 51,
    "id_a": "REQ-INSTALL-10",
    "id_b": "REQ-INSTALL-8",
    "req_a": {
      "title": "Windows at-logon autostart runs the daemon in the background with no persistent window: the scheduled task launches `spt daemon start` (which spawn_detaches a console-less DETACHED_PROCESS daemon and exits) rather than the foreground `spt daemon run` \u00e2\u20ac\u201d Task Scheduler's interactive ONLOGON launch of a long-lived console process otherwise leaves a visible console window for the daemon's whole lifetime (v0.7.4)",
      "doc": ""
    },
    "req_b": {
      "title": "OS-service registration (REQ-INSTALL-1's deferred third leg): Linux systemd USER service + loginctl enable-linger (linger rides the elevated install leg; daemon starts at boot pre-login, user universe per KH 5.7, systemctl --user managed); Windows scheduled task at-logon (interactive session, no stored credentials); a node is reachable after reboot without any manual spt invocation (M8 decision 17)",
      "doc": ""
    }
  },
  {
    "pair_number": 52,
    "id_a": "REQ-PICKER-1",
    "id_b": "REQ-RUN-PICKER-HOME",
    "req_a": {
      "title": "The picker renders a FOUR-state endpoint status (extending the W2 online/offline duality): the list-item square AND a color-coded STATUS line at the top of the pick-existing right-side details both show \u00e2\u20ac\u201d gray OFFLINE; green ONLINE (online + PTY-controllable spt-hosted, not controlled); amber 'ONLINE - HARNESS ONLY' (online but NOT broker-PTY-controllable = harness-hosted, no broker PTY seat \u00e2\u20ac\u201d today mis-shows green); blue 'ONLINE + CONTROLLED' (online + driven_by.is_some()). Derived on EndpointRow from {offline | controllable | driven_by} with precedence offline\u00e2\u2020\u2019gray, else driven_by\u00e2\u2020\u2019blue, else !controllable\u00e2\u2020\u2019amber, else green (driven_by outranks harness-only; mutually exclusive in practice \u00e2\u20ac\u201d a harness-only endpoint has no broker PTY to control). The controllable discriminator is a NEW InfoJson.controllable: Option<bool> (serde-default, N-1-safe), stamped at the establish seam \u00e2\u20ac\u201d cmd_listen (harness-hosted relay, no broker PTY) \u00e2\u2020\u2019 Some(false); cmd_bind live_agent (spt-hosted broker PTY) \u00e2\u2020\u2019 Some(true); absent \u00e2\u2020\u2019 not-controllable (amber) default (harness-hosted is the common mis-reported case; one bind self-corrects). Store-projection-only (no live daemon query \u00e2\u20ac\u201d doyle ruling). (v0.10.0)",
      "doc": ""
    },
    "req_b": {
      "title": "Home-subnet selection LAYER in the `spt endpoint run` ratatui Create-new picker (v0.14.1; the deferred half of REQ-RUN-MULTISUBNET-HOME's interactive path \u00e2\u20ac\u201d ADR-0026 \u00c2\u00a73 'the interactive picker lists subnets MRU-ordered'). On a MULTI-SUBNET node the Create-new flow gains a `CreateHome` screen (CreateAdapter \u00e2\u2020\u2019 CreateId \u00e2\u2020\u2019 CreateHome \u00e2\u2020\u2019 Confirm) that lists the node's MEMBER subnets MRU-ordered (reusing recent_home::mru_preference + order_by_mru), default cursor = MRU head; the chosen subnet rides Outcome::Run{subnet} into cmd_endpoint_run's --subnet, so decide_run_home resolves Home directly and the post-TUI `Ok to proceed? Y/n` confirm NEVER fires for the picker path. Single-subnet / local-only nodes SKIP the layer (assign_home auto-homes; CreateId \u00e2\u2020\u2019 Confirm unchanged). The CLI / flagged `endpoint run` path KEEPS the decide_run_home Y/n confirm + the non-interactive MULTI_SUBNET_HOME refuse (operator: the confirm stays useful for CLI-only bringup, just not in the TUI). Esc backs CreateHome \u00e2\u2020\u2019 CreateId; Enter selects \u00e2\u2020\u2019 Confirm. Pure front-end invariant preserved: the layer only collects --subnet, routes through the one bringup core.",
      "doc": "Shell sleep/wake (offline \u00e2\u2020\u201d online): **spt-hosted bringup picker (`spt endpoint run`)** (M12-W2): <!-- --> The user-facing bringup flow for spt-hosted endpoints. **Bare `spt endpoint run`** (no `--adapter`/`--id`) opens an in-process **ratatui picker**; the **flagged** form is the non-interactive bringup path (`--adapter <a[:profile]> --id <id> --create|--resume <session> --start|--attach|--view`), untouched \u00e2\u20ac\u201d a picker selection bakes exactly that path. **Layer 1** picks the kind (*Create new* | *Pick existing*). **Create-new** chooses a registered `kind=\"harness\"` adapter with its shipped+loc"
    }
  },
  {
    "pair_number": 53,
    "id_a": "REQ-HAZARD-STALE-SIGNOFF-SENTINEL",
    "id_b": "REQ-HAZARD-TEARDOWN-DEADEND",
    "req_a": {
      "title": "Stale signoff sentinel does not kill a fresh start (3.2)",
      "doc": ""
    },
    "req_b": {
      "title": "TEARDOWN-AUTHORITY W1 HAZARD (ADR-0045 decision 8; hertz RCA 2 \u00e2\u20ac\u201d hit doyle's OWN live production endpoint, recoverable only by out-of-band scoped kill): `endpoint stop` followed by `endpoint run --id <same>` MUST succeed (spawn or resume) and must NEVER answer ENDPOINT_CREATE_CONFLICT about a session that stop claimed to end. TRAP SHAPE (two individually-CORRECT behaviors composing into a lifecycle DEAD END with no in-band exit): (1) stop leaves the broker session alive (REQ-ENDPOINT-TEARDOWN-AUTHORITY), and (2) `endpoint run` correctly REFUSES ENDPOINT_CREATE_CONFLICT rather than silently reattaching (v0.37.0 no-silent-reattach rule, deliberately chosen). The survivor is therefore simultaneously what stop claims to have killed AND what run refuses to work around; with a wedged harness every in-band verb is exhausted (stop lies, run refuses, rc replays a dead PTY, rc --take controls a process that never answers). Neither behavior is individually wrong \u00e2\u20ac\u201d the COMPOSITION is the hazard, so the regression must assert the composition, not either verb alone. This single assertion is the whole user-visible point of W1. Gate: impl \u00e2\u20ac\u201d covered by the shared primitive; int \u00e2\u20ac\u201d start a real broke",
      "doc": "7.48 At most one input-capable controller lease per PTY session \u00e2\u20ac\u201d takeover revokes atomically and loudly, input is fenced to the active lease, node identity is never a lease `[REQ-HAZARD-CONTROLLER-LEASE]`: ### 7.49 A teardown verb never stamps a terminal or resting state it has not caused \u00e2\u20ac\u201d and two individually-correct verbs must not compose into a lifecycle dead end `[REQ-HAZARD-TEARDOWN-DEADEND]` <!-- --> <!-- --> - **Failure (paid-for, two hertz field RCAs 2026-07-19, both doyle code-verified the same day; the second hit doyle's OWN live production endpoint):** `endpoint shutdown` reported"
    }
  },
  {
    "pair_number": 54,
    "id_a": "REQ-HAZARD-BROKER-SEED-WIRE-SKEW",
    "id_b": "REQ-NOTIF-SEAM-DISMISS",
    "req_a": {
      "title": "A daemon-state wire-format change (e.g. the v0.9.0 adapter-agnostic Seed) does NOT take effect until a DELIBERATE full broker restart: the broker serves the seed-control channel and is RESIDENT across a brain-only self-update (ADR-0004 no-terminate-during-update forbids auto-killing it), so a NEW-version CLI talking to a still-resident OLD broker fails the seed handshake \u00e2\u20ac\u201d the old broker cannot deserialize the new Seed (its formerly-required `adapter` field is gone) and drops the conn without an ack, which surfaces to the CLI as a raw UnexpectedEof 'failed to fill whole buffer'. spt-core must (a) surface an ACTIONABLE diagnostic on that seed-ack EOF (name the stale-broker cause + the `spt daemon stop` fix \u00e2\u20ac\u201d the broker restarts on the next api call), never the cryptic io error; and (b) document the operational rule (a deliberate broker restart is required on any daemon-state wire change \u00e2\u20ac\u201d NOT automatic) + the FORWARD discipline (daemon-state/Seed schema changes stay additive + serde-default so a resident OLD broker tolerates a NEW CLI across a brain-only update; note this would NOT have rescued 0.9.0 itself, since the old broker's `adapter` was a required field). perri PREP-4 FINDIN",
      "doc": "7.8 The broker must never make a brain wait UNBOUNDED on a QUIC op (the pump-IPC-deadline B-half) `[REQ-HAZARD-BROKER-QUIC-DEADLINE]`: ### 7.9 A daemon-state wire change needs a deliberate BROKER restart (the broker is resident across a brain self-update) `[REQ-HAZARD-BROKER-SEED-WIRE-SKEW]` <!-- --> - **Failure:** the broker serves the seed-control channel and is RESIDENT across a brain-only self-update (ADR-0004's no-terminate-during-update pillar forbids auto-killing it \u00e2\u20ac\u201d 6.7). A self-update that changes a daemon-state WIRE FORMAT \u00e2\u20ac\u201d e.g. the v0.9.0 adapter-agnostic `Seed` (the `adapter` fie"
    },
    "req_b": {
      "title": "Staleness is dismissed at the seam that knows, never evaluated at surface: a successful update apply dismisses spt-core:update-staged; the update worker's next check, seeing running >= staged, dismisses it too (covers out-of-band installs); a later successful update dismisses a rollback row; the primitive stores rows and latches \u00e2\u20ac\u201d NO relevance predicates, nothing evaluated at surface time",
      "doc": "3. Staleness is dismissed at the seam that knows, never evaluated at surface: <!-- -->"
    }
  },
  {
    "pair_number": 55,
    "id_a": "REQ-IDLE-PARKED-DELIVERY",
    "id_b": "REQ-LIVE-AGENT-NO-INJECT-DELIVERY",
    "req_a": {
      "title": "W5 (LIFECYCLE-TRUTH): a message QUEUED to an ALREADY-idle spt-hosted endpoint is delivered without an operator poke. ROOT (live during the milestone dispatch 2026-07-07): the idle-edge drain (F-023 leg 2) fires only on the ACTIVE->IDLE transition; no new edge ever comes for a parked session, and the send-time inject didn't carry it \u00e2\u20ac\u201d both doyle->todlando dispatches sat delivered=0 in the spool while the endpoint showed ONLINE. FIX: send-time inject fires for an already-idle spt-hosted target (activity sense says idle => inject now, not spool), and/or a bounded spool sweep re-offers pending rows to idle endpoints (piggyback the pulse tick, no new loop). Int: send to a session idle for N minutes -> delivered without any operator poke.",
      "doc": ""
    },
    "req_b": {
      "title": "F-033 RE-SCOPED (doyle re-ruling 2026-07-10 after the #82 gate falsified the original premise \u00e2\u20ac\u201d the hosting-mode class split is BINDING context): 'state:live_agent has a self-delivery reader' CONFLATED two hosting modes. (A) HARNESS-hosted live agents (api listen path) DO deliver via adapter channels only \u00e2\u20ac\u201d and are ALREADY structurally excluded from inject: bind_from_seed stamps controllable=Some(false), no broker PTY exists. (B) SPT-HOSTED/CONTROLLED live agents' inject leg IS their delivery reader (broker PTY + translation binary \u00e2\u20ac\u201d doyle's own endpoint is field proof: ENDPOINT_INJECT + IDLE_PARKED_DRAIN alongside hook-poll); the original blanket state:live_agent exclusion broke REQ-MSG-IDLE-EDGE-DRAIN + the v0.14.3 LAW on both CI platforms and was REVERTED (predicate stays controllable-gated). perri's adapter-channels-only model (F-dupmsg-adapter-confirm.md) holds for class (A) only \u00e2\u20ac\u201d do not re-seed the conflation. The F-033 DUPLICATE mechanism (hook-poll + idle-inject both delivering one row, operator spool row 156) is closed structurally by REQ-CARRIER-CLAIM-EXCLUSIVE's atomic cross-carrier take. REMAINING LEGS OF THIS REQ: (a) unit \u00e2\u20ac\u201d a harness-hosted live agent (controllable S",
      "doc": ""
    }
  },
  {
    "pair_number": 56,
    "id_a": "REQ-PSYCHE-NESTED-RESOLUTION",
    "id_b": "REQ-PSYCHE-SPAWN-ENV-PARITY",
    "req_a": {
      "title": "W4 (F030, design \u00c2\u00a73; F-017 sibling, general not psyche-only): nested {parent}/{nested} ids resolve under the PARENT's home \u00e2\u20ac\u201d subnet-less resolution for nested perches (the home-ASSIGNMENT seam, not perch-path which is already subnet-less) \u00e2\u20ac\u201d so child-side verbs acting on nested perches need no --subnet the child cannot know. ROOT: home::assign_home returns Ambiguous on a multi-subnet node w/o --subnet; a nested id must instead derive home from its parent perch. Additionally expose a SINGLE {subnet} base_keys fill WHEN KNOWN (own_subnet = home-subnet label, 'local' when unhomed; absent \u00e2\u2020\u2019 LOUD missing-key fail, the {node} precedent). NO {home} key (doyle W4 Q1: the endpoint home subnet is ONE concept per CONTEXT.md:640, and 'home:' is already the subnet-name qualifier position CONTEXT.md:652 \u00e2\u20ac\u201d a {home} template key invites meaning-drift). Red-first = evidence #3's exact repro: 2-subnet home, nested-id verb, must succeed (was: `spt ready <id> --once` \u00e2\u2020\u2019 exit 1 READY_FAIL \u00e2\u20ac\u00a6 pass --subnet).",
      "doc": "then it exits \u00e2\u20ac\u201d no resident process, no detach.: <!-- --> **`{subnet}` \u00e2\u20ac\u201d the endpoint's home-subnet label.** A single `{subnet}` key fills to this endpoint's home-subnet label (`local` when unhomed), supplied whenever it is known so a psyche turn need never resolve a `--subnet` it cannot know; when no subnet is known the key is left unfilled so a referencing template fails **loud** (the `{node}` precedent). There is deliberately **no `{home}` key** \u00e2\u20ac\u201d the endpoint home subnet is one concept, and `home:` is already the subnet-name qualifier position, so a `{home}` template key would only invite"
    },
    "req_b": {
      "title": "P-2 (WORKER-TRUTH triage addendum, perri-filed field finding 2026-07-06): the per-event psyche_resume spawn threads the perch record's CAPTURED read_env stamps into the spawn ENVIRONMENT \u00e2\u20ac\u201d the F-027 Half-B env-parity contract (BINDING, design-frozen: read_env captured at creation + stamped on the record + threaded IDENTICALLY to every session spawn; the spawn never reads its own process env for a stamped var) extended to the psyche role the design predates. Field driver: flynn (claude-spt:ccs) \u00e2\u20ac\u201d the ccs wrapper relocates the account root via CLAUDE_CONFIG_DIR at PARENT launch and the perch record correctly captured it, but the daemon spawns psyche_resume with bare env \u00e2\u2020\u2019 default ~/.claude root \u00e2\u2020\u2019 headless 'Not logged in' exit-1 \u00e2\u2020\u2019 strike loop; psyche + parent land in DIFFERENT account roots (auth AND root-scoped continuity both break). Core stays harness-agnostic (threads whatever [env] direction=read captured \u00e2\u20ac\u201d knows nothing of CLAUDE_CONFIG_DIR). Scope note: this is the URGENT psyche leg of F-027 Half B; the full pre_spawn seam + endpoint-session env threading stays design-parked (F-027-ENDPOINT-SPAWN-FAIL-DESIGN.md) unless operator pulls it forward.",
      "doc": ""
    }
  },
  {
    "pair_number": 57,
    "id_a": "REQ-HAZARD-CONPTY-DSR",
    "id_b": "REQ-HAZARD-EBUSY-RENAME",
    "req_a": {
      "title": "ConPTY reader must auto-answer DSR (ESC[6n) or all child output stalls (5.5)",
      "doc": ""
    },
    "req_b": {
      "title": "tmp-write + atomic-rename + retry on Windows EBUSY (5.2)",
      "doc": ""
    }
  },
  {
    "pair_number": 58,
    "id_a": "REQ-NOTIF-COALESCE",
    "id_b": "REQ-NOTIF-TTL",
    "req_a": {
      "title": "Coalesce-key supersession: producer-stamped key REQUIRED in namespaced form <owner>:<key> (e.g. spt-core:update-staged) \u00e2\u20ac\u201d the produce front door rejects un-namespaced keys; producing a new row with the same (scope-target, kind, key) auto-dismisses the prior rows, latest-wins; supersession expresses through the existing dismissed one-way latch (semilattice unchanged)",
      "doc": "2. Supersession: a required-namespaced coalesce key, latest-wins: <!-- -->"
    },
    "req_b": {
      "title": "Producer-optional TTL for informational kinds with no dismissal seam (node-paired, agent-issued): expired row auto-dismissed instead of surfaced \u00e2\u20ac\u201d a timestamp compare, not a predicate; expiry IGNORES seen-state (TTL means stale-after-this; a producer that cannot accept silent expiry must not set one); no global default",
      "doc": "5. TTL: producer-optional, expiry ignores seen-state: <!-- -->"
    }
  },
  {
    "pair_number": 59,
    "id_a": "REQ-HAZARD-ENDPOINT-LIFECYCLE",
    "id_b": "REQ-HAZARD-STOP-PATH-PSYCHE-ORPHAN-REAP",
    "req_a": {
      "title": "REGISTRY-LIFECYCLE W2 (KNOWN-HAZARDS 7.45 \u00e2\u20ac\u201d the umbrella conformance seam for ADR-0041): endpoint lifecycle state converges to truth from EVERY death path. Regression matrix from the three hertz reports + operator field: dead-PID hybrid row does not survive reconcile; raw viewport close frees the controller (full chain, broker restart included \u00e2\u20ac\u201d shared with REQ-STREAM-LEASE-CLASSES int); definitive death means offline+suspended atomically and the next reconcile emits no WAKE_RESUME; explicit Wake still launches exactly once; poll-vs-reap interleave converges to cleared stamps. HEAVY nextest group at birth for any leg spawning a daemon tree. Gate: int \u00e2\u20ac\u201d the matrix; doc \u00e2\u20ac\u201d KNOWN-HAZARDS 7.45.",
      "doc": "7.44 Streams and seats on a long-lived connection must have bounded lifetime \u00e2\u20ac\u201d one-way rows terminal at FIN, seats released at serve completion, no per-chunk full-state rewrites in a drain loop `[REQ-HAZARD-REGISTRY-STALL]`: ### 7.45 Endpoint lifecycle state converges to truth from every death path \u00e2\u20ac\u201d no optimistic online without authority, no surviving control stamps, no immortal wake intent, no untruthful create `[REQ-HAZARD-ENDPOINT-LIFECYCLE]` <!-- --> - **Failure (paid-for, three hertz reports + operator field 2026-07-16):** four families, one root shape \u00e2\u20ac\u201d lifecycle state written by multip"
    },
    "req_b": {
      "title": "Endpoint-stop and brain-death reconcile MUST reap a brain-less perch's orphan detached Psyche via the cmdline-scoped guard (`psyche_orphan_should_reap`) \u00e2\u20ac\u201d the handle-reap (`LiveSet::stop_host`, REQ-HAZARD-UNHOST-PSYCHE-REAP) CANNOT, because the owning brain is gone (its `psyche_child` handle died with it), and the brain-start scoped-reap (REQ-HAZARD-BRAIN-RESTART-PSYCHE-DUP) never fires for a perch being STOPPED rather than re-hosted. So the live-host calls the scoped reap after `stop_host` at the reconcile stop-side AND in `confirm_residency_or_unhost`. Preserves fail-safe-decline (pid-alive AND exe-basename==psyche-program AND cmdline contains `<id>-psyche`; any unreadable signal DECLINES \u00e2\u20ac\u201d a missed dup is bounded, a wrong-kill is catastrophic). This is the orphan-leak half of the perri F-010xF-015 field bug (the unsupervised install-dir Psyche that locked an update); the other half is the psyche own-copy (ADR-0025 amendment). (v0.13.2 W3 (a))",
      "doc": "Conformance checklist (condensed): | # | Invariant | spt-core surface | |---|---|---| | 1.1 | Grace wait precedes INIT_SIGNOFF | daemon teardown | | 1.4/4.4 | Deferred rows excluded from event-stream drain | daemon spool drain | | 2.1/5.1 | Stable PID/broker-handle over ephemeral PID | liveness detection | | 2.3 | Handoff argv/IPC version-tolerant (newer brain \u00e2\u2020\u201d older broker) | broker\u00e2\u2020\u201dbrain IPC, self-update | | 2.4 | gen_start = now() on cold-start + handoff | per-instance generation | | 2.6 | A shell's ONLINE-ness is DERIVED (recorded status AND a not-provably-dead `shell.pid`) \u00e2\u20ac\u201d an abruptly-"
    }
  },
  {
    "pair_number": 60,
    "id_a": "REQ-ARCH-1",
    "id_b": "REQ-ARCH-2",
    "req_a": {
      "title": "Many small acyclically-layered crates",
      "doc": ""
    },
    "req_b": {
      "title": "Public SDK surface is spt-proto, spt-runtime, spt-msg",
      "doc": ""
    }
  },
  {
    "pair_number": 61,
    "id_a": "REQ-CONV-1",
    "id_b": "REQ-CONV-2",
    "req_a": {
      "title": "Peer address seeding, both cold starts: durable peer-addrs.json (identity dir) maps peer pubkey \u00e2\u2020\u2019 last-known dialable address; the pump's resolver consults it FIRST with id-only discovery fallback on miss or dial failure (a stale addr never strands a peer); written by the pairing ceremony (both sides, from the live connection) and by the pump on successful connect; post-join first sync and post-restart resync converge in seconds, not ~1 min (M8 decisions 14, 20)",
      "doc": ""
    },
    "req_b": {
      "title": "Event-driven advertisement: endpoint online/offline transitions (ready-listener start/stop, rest-state transition, perch death) trigger an immediate advertise_local + peer push as a WAKE of the existing pump loop (no second advertisement path \u00e2\u20ac\u201d epoch lease + visibility gates ride unchanged); the cadence stays the steady-state floor (M8 decision 15)",
      "doc": ""
    }
  },
  {
    "pair_number": 62,
    "id_a": "REQ-HAZARD-ROLLBACK-STATE-COMPAT",
    "id_b": "REQ-UPDATE-TRIAL-DRAIN-DRIVE",
    "req_a": {
      "title": "A brain must not irreversibly migrate durable state before update ready-promotion: the readiness-gated auto-rollback (ADR-0018 Q7) spawns the N-1 binary against durable state the new brain may have written, so every pre-ready write must stay N-1-readable (schema migrations gated behind ready-promotion, or written N-1-tolerant/additive). Else the first in-place schema migration silently bricks rollback (KNOWN-HAZARDS 6.8). Free now \u00e2\u20ac\u201d a 2026-06-09 audit confirmed zero state-migration code exists; unmintable retroactively once a migration ships.",
      "doc": "6.7 Broker and brain MUST be separate processes (in-process collapse silently breaks no-endpoint-drop update) `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]`: ### 6.8 No irreversible durable-state migration before update ready-promotion `[REQ-HAZARD-ROLLBACK-STATE-COMPAT]` - **Failure:** the readiness-gated auto-rollback (ADR-0018 Q7) spawns the *previous* binary against durable state the *new* brain already wrote. The first release that migrates a durable-state schema in place would make the old binary unable to read it \u00e2\u20ac\u201d silently bricking rollback exactly when it is needed (a logic-bricking update t"
    },
    "req_b": {
      "title": "UPDATE-WEDGE (counter-54, doyle-ruled 2026-07-09 \u00e2\u20ac\u201d regression of the v0.29.0 seamless brain-swap): a brain generation DRIVES the broker's controller-liveness reap (a KIND_SESSIONS poll) each heartbeat throughout its boot/trial loop, so a hard-KILLED prior generation's black-holed LOCAL controller conn (by:None) is stall-evicted within the trial window and can never permanently strand the promotion DRAINED gate. ROOT (2026-07-09 field freeze, `spt update fetch --apply` v0.30.0->v0.30.2 froze all 7 live PTYs ~30s then rolled back): the promote gate (run_trial, brainproc.rs:657-661) needs BOTH `ready_generation==gen` AND `old_gen_drained()`; `old_gen_drained()` = `!any_local_controller_wedged()` (brainproc.rs:534) is a PURE READ of `write_blocked_since` (broker.rs:2703) \u00e2\u20ac\u201d it never DRIVES the evict. The evict (`stall_evict_controller`, broker.rs:1039, same 15s `brain_write_deadline` the wedge-read uses) only runs via `reap_dead_controller` (broker.rs:967, severed->drop ELSE stall-evict) inside the KIND_SESSIONS snapshot closure (broker.rs:2879). During the isolated brain-trial window NOTHING polls KIND_SESSIONS: the old brain was hard-killed (`child.kill()`, brainproc.rs:851) so its lo",
      "doc": "Self-update: **brain-trial promotion (readiness + drained)** \u00e2\u20ac\u201d the broker supervises the swapped-in brain through a bounded readiness **trial** and *promotes* the new binary only when it both signals ready for its own generation **and** the OUTGOING generation's control plane has **drained** \u00e2\u20ac\u201d the old brain's local (brain-owned) controller connection is closed or stall-evicted, never still holding blocked writes. A hard-killed prior generation leaves that connection **black-holed** (its hosted PTYs keep producing output the broker's writer blocks on, since a killed peer's pipe blocks rather th"
    }
  },
  {
    "pair_number": 63,
    "id_a": "REQ-PSYCHE-SID-CUSTODY",
    "id_b": "REQ-RESUME-CUSTODY-IDENTITY",
    "req_a": {
      "title": "W2 (F030, design \u00c2\u00a73): the psyche mints and keeps its OWN session id, stored in the nested {id}-psyche perch record \u00e2\u20ac\u201d {session_id} in psyche role templates becomes the psyche's sid, never the parent's (today's fill at livehost.rs:518 is the PARENT's \u00e2\u20ac\u201d the custody bug). Parent boundary (/clear, /compact) does NOT rotate the psyche sid (the psyche's conversational thread survives parent resets \u00e2\u20ac\u201d its job). resume_psyche validates the custody key before spawn (resume.rs:183). Reseed path: psyche session lost/invalid \u00e2\u2020\u2019 ResumeMode::FreshWithPreload (download_psyche_context composes role/live/project into {psyche_context}, resume.rs:100) + LOUD PSYCHE_RESEED:{id} marker (custody-loss loop visible; W1 budget bounds it). If the parent sid is still needed by a template it gets its OWN explicit key {parent_session_id} \u00e2\u20ac\u201d never aliased. Red-first: parent `api boundary clear` \u00e2\u2020\u2019 nested perch sid UNCHANGED (today it is the parent's \u00e2\u20ac\u201d guard-revert reproduces).",
      "doc": "The role spt-core actually drives \u00e2\u20ac\u201d one bounded turn per Psyche event.: <!-- --> <!-- --> // then it exits \u00e2\u20ac\u201d no resident process, no detach.: <!-- --> **Custody sid \u00e2\u20ac\u201d `{session_id}` is the Psyche's OWN id.** In a psyche role template `{session_id}` is the **Psyche's own minted session id**, kept in its nested `<parent>-psyche` perch record \u00e2\u20ac\u201d **not** the parent's. A parent boundary (`/clear`, `/compact`) rotates the *parent's* sid but does **NOT** rotate the psyche sid: the Psyche's conversational thread survives parent resets (that is its job). When a template still needs the parent's sid it t"
    },
    "req_b": {
      "title": "Resume custody is an identity pair (pid + process creation time), never a bare PID. (ADR-0047 decision 1; hertz v0.39.4 field bug 1, RCA accepted 2026-07-22.) TODAY: livehost's restart gate and liveness-reconcile DEFER both consume `read_resume_pid(..).is_some_and(is_process_alive)` \u00e2\u20ac\u201d zero identity binding, so a dead wake-resume spawn's pid recycled onto an unrelated process (field: resume.pid=29456 -> cmd.exe) reads as a live resume forever: reconcile defers every tick, the row stays online-authoritative, FALSE-ONLINE with no self-repair. FIX: the custody record stores (pid, creation_time) written atomically at spawn-mint; every consumer tests the PAIR; mismatch = NOT OURS -> the discovering reader DELETES the record and proceeds (self-heal, not error); successful bind and spawn-reap clear custody atomically with their own outcome. Creation time from the process SNAPSHOT, never a retained handle (KH 7.50); platform without a snapshot -> unproven -> defer one tick, never a manufactured verdict. Gate: impl \u00e2\u20ac\u201d the paired custody record + both livehost consumers on the pair test; unit \u00e2\u20ac\u201d pair mismatch reads NOT-OURS + record deleted, pair match reads OURS, absent-snapshot defers; int \u00e2\u20ac\u201d ",
      "doc": "1. Process custody is an identity, never a bare PID: <!-- -->"
    }
  },
  {
    "pair_number": 64,
    "id_a": "REQ-HAZARD-REDISPATCH-STALL",
    "id_b": "REQ-REDISPATCH-FINISHED-RETIRE",
    "req_a": {
      "title": "REDISPATCH-STALL W1 (KNOWN-HAZARDS 7.43, hertz v0.34 field RCA 2026-07-16 \u00e2\u20ac\u201d recurrent 20-30s PTY/RC freezes, 17-62s DISPATCH tails): one wedged stream subscriber must NEVER stall stream serving, and recovery machinery must not manufacture new replay victims. Today: claim retries x the broad Err(_) opener fallback (dispatch.rs:414) install throwaway peek subscribers whose StreamLog::attach replays the entire retained ring UNDER the per-stream mutex with discarded write errors and the poisoned subscriber left installed \u00e2\u20ac\u201d serial 15s bounded-write poison windows (33 observed, all 15,000-15,154ms) composing into the field stalls. Gate: int \u00e2\u20ac\u201d production-path regression at the REAL run_dispatch_loop + StreamLog + serve_attach seams: wedge one subscriber conn, prove producer appends and unrelated streams stay flat while the poisoned subscriber is removed and the stream recovers (no abandonment); doc \u00e2\u20ac\u201d KNOWN-HAZARDS 7.43. Binding: redispatch D1/D1b stay green every leg. HEAVY nextest group at birth. Kin REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE + REQ-DISPATCH-FALLBACK-CIRCUIT (the mechanisms), REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK (the deadline that fires), ADR-0038 Amendment.",
      "doc": "7.42 A node holding a valid roster address for a peer is NEVER route-less \u00e2\u20ac\u201d a failed dial must not delete the only bootstrap route `[REQ-HAZARD-MESH-BOOTSTRAP-TRAP]`: ### 7.43 One wedged stream subscriber must NEVER stall stream serving \u00e2\u20ac\u201d replay halts at the first failed write, a poisoned subscriber is removed, and recovery machinery must not manufacture new replay victims `[REQ-HAZARD-REDISPATCH-STALL]` <!-- --> - **Failure (paid-for, hertz field RCA 2026-07-16 \u00e2\u20ac\u201d live v0.34 boxes, recurrent 20\u00e2\u20ac\u201c30s PTY/RC freezes, DISPATCH tails 17\u00e2\u20ac\u201c62s):** a COMPOSITION, not one new timer. The dispatcher's ret"
    },
    "req_b": {
      "title": "REDISPATCH-TRUTH W1 (ADR-0038, hertz fix A): finished/terminal stream rows are RETIRED from redispatch eligibility \u00e2\u20ac\u201d NetShared.streams today has NO removal path (single insert nethost.rs ~649; StreamLog::finish only marks) so every dispatcher generation re-enumerates every historical stream forever. Retire terminal rows from the enumeration the dispatcher claims from (remove, or lifecycle-exclude), preserving only the post-EOF state genuinely needed by other readers (presence/log reads); bounded growth replaces forever-discoverable rows. Clearing the whole table on brain restart is REJECTED (destroys live streams' reconstruction facts). Gate: impl \u00e2\u20ac\u201d the retirement path; unit \u00e2\u20ac\u201d a finished stream is invisible to the dispatch enumeration while an active one stays claimable + post-EOF reader state survives retirement; doc \u00e2\u20ac\u201d rides ADR-0038 + the triage doc. Kin REQ-HAZARD-REDISPATCH-CONTROL-STEAL (the invariant it satisfies), REQ-STREAM-OPENER-DURABLE.",
      "doc": "Context: ## Decision <!-- -->"
    }
  },
  {
    "pair_number": 65,
    "id_a": "REQ-DOC-ENDPOINT-DROP-RESOLUTION",
    "id_b": "REQ-SHELL-PERCH-DIR",
    "req_a": {
      "title": "D1 (F028, perri F-c; docs/truth): SI-1's resolution rule \u00e2\u20ac\u201d a RELATIVE watched drop dir resolves against the ENDPOINT's cwd, never the daemon's (KH 7.28, shipped v0.22.0) \u00e2\u20ac\u201d is documented NOWHERE public. Add it to harness-contract/manifest.md + the manifest schema field descriptions so an adapter author knows a relative commune_dir/signoff_dir is endpoint-resolved. docs-drift gate applies. See triage D1.",
      "doc": ""
    },
    "req_b": {
      "title": "A shell binary can mechanically resolve where its files land. (flynn spt-alchemy clean-room audit 2026-07-21, doyle code-verified, seed pair item 2, P1 \u00e2\u20ac\u201d HARD-GATES flynn's alchemy W4.) TODAY: `spt shell send --file` lands the blob at <shell-perch>/files/<xfer-id>-<name> and the shell_file frame's path attr is PERCH-RELATIVE (files/...) \u00e2\u20ac\u201d but the spawn template substitution keys are ONLY {id}/{adapter_name}/{link_token} (shellhost.rs fill_spawn_command) and the spawned child inherits the BROKER's cwd, so no mechanical perch resolution exists: the binary cannot turn the frame's path into a real file without guessing SPT_HOME layout. DOYLE RULING, both halves binding: the frame KEEPS the perch-relative path (an absolute path in a spooled frame LIES across perch moves and node boundaries \u00e2\u20ac\u201d frames outlive layouts); the fix is an ADDITIVE {perch_dir} spawn-template substitution key (opt-in \u00e2\u20ac\u201d templates that do not use it are byte-identical, N-1-safe by construction) filled with the shell perch dir so the binary receives its root at spawn and joins the frame's relative path against it. REFUSED, recorded so it is not re-proposed: blessing SPT_HOME layout guessing as an interim contract. Pu",
      "doc": "`shell_text` \u00e2\u20ac\u201d free text: <!-- --> ## `shell_file` \u00e2\u20ac\u201d a landed file"
    }
  },
  {
    "pair_number": 66,
    "id_a": "REQ-HAZARD-CONTROLLER-WRITER-REORDER",
    "id_b": "REQ-HAZARD-INFO-RMW-LOST-UPDATE",
    "req_a": {
      "title": "Two `controller_writer` threads must never race ONE brain\u00e2\u2020\u201dbroker connection's socket. ROOT (doyle, instrumented RACEDIAG repro on kitsubito): on a brain-restart re-serve the handoff brain registers as controller on the SAME session TWICE over the SAME `Brain::conn` socket \u00e2\u20ac\u201d (1) `Brain::handoff` eagerly `subscribe(prior.session_id, prior.next_seq=1)` \u00e2\u2020\u2019 `become_controller(from_seq=1)`, initial=[1], spawns writer-A (writes seq 1); (2) `serve_attach` re-handles the replayed `Request{from_seq:0}` \u00e2\u2020\u2019 `attach_as(sid,0)` \u00e2\u2020\u2019 `become_controller(from_seq=0)`, initial=[0,1], spawns writer-B (writes 0 then 1). `become_controller` (broker.rs) drops the prior `ControllerSink` (its `tx`) but does NOT stop the prior writer thread \u00e2\u20ac\u201d writer-A keeps flushing its owned `initial` batch, and both writers hold clones of the same `SharedSend` (`Arc<Mutex<socket>>`) with NO inter-thread ordering. When writer-A's seq 1 wins the socket before writer-B's seq 0, the strict legacy consumer (brain.rs read_event reject-gap path) sees `output gap: got seq 1 want 0` \u00e2\u2020\u2019 the test `attach_survives_target_brain_restart_exactly_once` panics at `.expect(\"re-serve\")` OR HANGS in `render_until` (serve thread died on the gap \u00e2\u2020\u2019 ",
      "doc": "7.20 `spt rc` must forward the scroll wheel to the harness (our mouse capture steals WT's native scroll) `[REQ-RC-MOUSE-FORWARD]`: <!-- --> ### 7.21 Exactly ONE `controller_writer` per brain\u00e2\u2020\u201dbroker connection \u00e2\u20ac\u201d a superseded writer must write nothing further `[REQ-HAZARD-CONTROLLER-WRITER-REORDER]` - **Failure (doyle instrumented RACEDIAG repro, kitsubito):** on a brain-restart re-serve the handoff brain registered as controller on the SAME session TWICE over the SAME socket \u00e2\u20ac\u201d `Brain::handoff` eagerly `subscribe(prior.next_seq=1)` \u00e2\u2020\u2019 `become_controller(from_seq=1)`, spawning writer-A (writes seq"
    },
    "req_b": {
      "title": "Concurrent info.json writers must serialize under the per-perch lock (5.16): an unlocked whole-record write racing a locked RMW is a silent lost update",
      "doc": "5.15 Fixed atomic-write tmp name \u00e2\u2020\u2019 concurrent writers collide (loser renames a consumed file) `[REQ-HAZARD-ATOMIC-TMP-COLLISION]`: <!-- --> ### 5.16 Unlocked whole-record info.json write races a locked RMW \u00e2\u2020\u2019 silent lost update `[REQ-HAZARD-INFO-RMW-LOST-UPDATE]` - **Failure:** `mutate_info` serializes its read\u00e2\u2020\u2019mutate\u00e2\u2020\u2019write under the per-perch `.info.lock` sentinel, but `establish_perch` (`spt::api::startup`) did read\u00e2\u2020\u2019conflict-check\u00e2\u2020\u2019`write_info` with **no lock**. At bind the two writers race (~700\u00c2\u00b5s apart): the daemon RMW reads the PRE-BIND record, bind's `write_info` renames the full record in"
    }
  },
  {
    "pair_number": 67,
    "id_a": "REQ-ONEWAY-STREAM-TERMINAL",
    "id_b": "REQ-STREAM-LIFETIME-CLASS",
    "req_a": {
      "title": "REGISTRY-LIFECYCLE W1 (ADR-0040 decision 1, hertz defect B leg 1): a one-way fire-and-forget stream family is TERMINAL at successful FIN, sender-side \u00e2\u20ac\u201d the registry pump retires its OWN feed row after write+FIN via the existing net-stream-retire verb (best-effort on N-1 brokers per ADR-0038 A). Sender history on the long-lived pump conn stops accumulating: steady-state row population is O(active exchanges), not O(feeds since conn start). Gate: impl \u00e2\u20ac\u201d pump push_feed retire-after-FIN; unit \u00e2\u20ac\u201d successful feed retires its row, failed/unFINed feed does not, retire failure is best-effort non-fatal; int \u00e2\u20ac\u201d rides REQ-HAZARD-REGISTRY-STALL plateau seam (eligible rows plateau O(active) over N rounds); doc \u00e2\u20ac\u201d ADR-0040.",
      "doc": "Decision: <!-- --> 1. **One-way (fire-and-forget) stream families are terminal at FIN, sender-side.** The registry feed pump retires its own row after successful write+FIN via the existing `net-stream-retire` verb (best-effort on N-1 brokers, per ADR-0038 A). A one-way family's exchange is definitionally over at FIN; keeping the row eligible reproduces the O(history) defect forever. 2. **Eligibility filtering is server-side.** `stream_infos` excludes `initiated_locally` rows (alongside `retired`) before serializing. Consumers keep their client-side guards (double-filter harmless; N-1 compatibl"
    },
    "req_b": {
      "title": "RESCOPED at W2 activation 2026-07-22 (doyle verify-first, C3-retirement precedent): the CLEAN-CASE chain this REQ was minted for is ALREADY SHIPPED under REQ-STREAM-LEASE-CLASSES / ADR-0040 decision 6, verified against @7c0f12d \u00e2\u20ac\u201d StreamLifetime is opener-declared and on the wire, serde-default Durable so N-1 openers keep exact today-semantics (msg.rs:810-836 + the additive-wire unit @msg.rs:1408); rc attach/view is the SOLE ConnectionBound opener (attach.rs:667-677); the broker binds the class at open (broker.rs:5689) and nethost carries it per StreamEntry (nethost.rs:1059-1076); the conn-exit sweep FINs each ConnectionBound stream toward its target and terminal-retires the local row while Durable rows never enter it (broker.rs:4118-4123/4227-4235/4429-4446); the target's serve_attach EOF arm runs detach_session_gen(serve_gen) so controller/viewer stamps clear on exactly one generation (attach.rs:580-597); the late-close gen guard (broker.rs:2179-2192, unit @8416) and converge_perch_stamps close the race; int coverage rides endpoint_lifecycle.rs:241. Building that again would re-implement shipped code \u00e2\u20ac\u201d the C3 lesson. WHAT REMAINS, and what this REQ now owns: RESTART-REPLAY RE-ESTA",
      "doc": "Amendment 1 (2026-07-22, DAEMON-LIFECYCLE W2) \u00e2\u20ac\u201d teardown authority is opener-declared class PLUS transport liveness, enforced at the three places decision 6 could not see: <!-- --> <!-- -->"
    }
  },
  {
    "pair_number": 68,
    "id_a": "REQ-HAZARD-CASCADE-WIPE-GUARD",
    "id_b": "REQ-PAIR-NTP-MULTIHOME",
    "req_a": {
      "title": "No hard-delete of a parent hosting non-empty children (6.3)",
      "doc": ""
    },
    "req_b": {
      "title": "W1/D1 (JOIN-TRUTH): the ceremony NTP query reaches a server on EITHER IP family \u00e2\u20ac\u201d `query_unix_secs` (ntp.rs) must iterate every address `to_socket_addrs()` resolves (not just the first) and bind a socket of the matching family per candidate (IPv4 addr \u00e2\u2020\u2019 bind 0.0.0.0:0; IPv6 addr \u00e2\u2020\u2019 bind [::]:0), first successful answer wins. ROOT (proven 3/3-FAIL via our exact code on enlyzeam): today `UdpSocket::bind((\"0.0.0.0\",0))` is v4-only and `send_to(&packet, server)` sends ONLY to the FIRST resolved addr \u00e2\u20ac\u201d time.google.com resolves 4\u00c3\u2014AAAA before any A on a v6-first dual-stack box \u00e2\u2020\u2019 the primary server is PERMANENTLY unreachable via our code (w32tm reaches it over v6), silently halving NTP redundancy (pool.ntp.org v4 carried everything; a DNS rotation making BOTH v6-first would zero it). Fix keeps the lazy-cache/TTL/fallback contract of REQ-PAIR-8 unchanged \u00e2\u20ac\u201d only the socket/resolve leg changes.",
      "doc": ""
    }
  },
  {
    "pair_number": 69,
    "id_a": "REQ-PICKER-5",
    "id_b": "REQ-PICKER-CURRENT-DIR-LABEL",
    "req_a": {
      "title": "`spt endpoint list` (bare/subnet view) renders an ALIGNED table with canonical node labels: cmd_endpoint_list prints subnet rows with `\\t` TAB separators (cli.rs:~1651-1662) so variable-width endpoint_ids snap fields to different tab-stops \u00e2\u2020\u2019 a RAGGED status column (operator screenshot: X/help statuses misaligned vs rt-*/sptc-*/wall-a); and it calls the node renderer with no label \u00e2\u2020\u2019 bare key-hex for every row (SAME ResourceRow-drops-node_label root as REQ-PICKER-4). FIX: max-width per-column padding (mirror render_node_rows' pad, pad by char count not byte len \u00e2\u20ac\u201d '\u00e2\u20ac\u00a6' is multibyte) replacing the tabs, and render the node via the shared node_label_display now that ResourceRow carries node_label (REQ-PICKER-4). Extract a pure row-formatter seam so the alignment+label is unit-testable. ALSO: the bare list is the SUBNET view (a just-run LOCAL perch is invisible cross-subnet until the next advertise tick), so emit a `--local` hint line so a freshly-run endpoint isn't perceived as lost. (v0.10.0; operator-flagged + doyle dispatch 2026-06-17)",
      "doc": ""
    },
    "req_b": {
      "title": "A-2/A-3 (F029, operator, semantic pair): the Choose-project rows must self-identify the CURRENT DIR. build_project_choices (picker/model.rs:322-352). A-2: when the run cwd IS already a history dir the `Here:` row is (correctly) suppressed by the dedup (REQ-PICKER-CHOOSE-DEDUP-ALL), but the matching history row rendered bare `r.display` with no cwd affordance \u00e2\u20ac\u201d mark it `<display> (CURRENT DIR)`. A-3: the not-in-history current-dir row changes from `Here: <run_cwd>` to `CURRENT DIR --> <project>`, deriving the display the SAME way the history refs do (folder tail; honest fallback to the raw path when underivable). `cwd` payload unchanged. Grep-tests rule: 3 `starts_with(\"Here: \")` asserts (model.rs) + a `Here: /here` render assert (view.rs) are behavior assertions on the OLD label. See triage A-2/A-3.",
      "doc": ""
    }
  },
  {
    "pair_number": 70,
    "id_a": "REQ-API-3",
    "id_b": "REQ-HAZARD-ENVELOPE-PARSER-SAFE",
    "req_a": {
      "title": "commune/signoff are file-drops, not commands",
      "doc": ""
    },
    "req_b": {
      "title": "Two-slice envelope parser is panic-free and tolerant (4.2)",
      "doc": ""
    }
  },
  {
    "pair_number": 71,
    "id_a": "REQ-PROJECT-INDEX-STORE",
    "id_b": "REQ-PROJECT-INDEX-WRITER",
    "req_a": {
      "title": "PROJECT-INDEX W1 (ADR-0037, RCA .claude/reports/2026-07-10-hertz-session/03): spt-store owns the VERSIONED materialized project-index format + read path. Reader contract: read one compact versioned index, join with the local perch roster, return immediately; stale/missing renders last-known-good or '-'; NEVER fall back to synchronous git enrichment; daemon-offline readers consume the last persisted snapshot; truncated/schema-mismatched index degrades to fast reads + last-known-good, never an error stall. Gate: impl \u00e2\u20ac\u201d format + store read path; unit \u00e2\u20ac\u201d version/schema-mismatch/truncation degradation legs + join semantics; doc \u00e2\u20ac\u201d CONTEXT.md project-index entry + STORAGE.md section. Kin REQ-PROJECT-INDEX-WRITER (the producer), ADR-0037.",
      "doc": "Self-update: **project index** \u00e2\u20ac\u201d a node's endpoint\u00e2\u2020\u2019project attribution is DERIVED state, held as a **persistent materialized index** (ADR-0037): `spt-store` owns the versioned format + read path (daemon-offline reads = last persisted snapshot); the **daemon is the sole single-flight writer** (load-at-start, ready-without-warm, background batched reconcile, atomic replace, coalesced event-driven invalidation keyed on branch-tip fingerprints, last-known-good on failure). Readers \u00e2\u20ac\u201d list, picker, endpoint-info, hooks \u00e2\u20ac\u201d join index \u00c3\u2014 perch roster and **never run git**; stale renders last-known or `-"
    },
    "req_b": {
      "title": "PROJECT-INDEX W2 (ADR-0037): the daemon is the SOLE single-flight project-index writer: load persisted index at startup; ready WITHOUT warm (cold start = daemon ready + CLI fast before background completes); background reconcile with BATCHED complexity O(P+B+F+C) \u00e2\u20ac\u201d enumerate branches ONCE, <=1 tree scan per changed branch, ONE derivation per distinct normalized cwd (in-process BranchStore traversal or fixed plumbing calls; backgrounding the existing 100+ process loop is REJECTED); atomic replace; last-known-good preserved on any failure; warm start with unchanged generation performs NO scan. Observability surface: generated time, source generation, pending refresh, last duration/error, endpoint/project/cwd counts, cache hits/misses, stale reads, repair count \u00e2\u20ac\u201d index presence alone is not health. Gate: impl \u00e2\u20ac\u201d writer + observability; unit \u00e2\u20ac\u201d single-flight, atomic-replace, last-known-good, no-scan-on-unchanged-generation; int \u00e2\u20ac\u201d cold+warm start legs against a real store; COMPLEXITY COUNTERS are the CI gate (wall-clock = manual acceptance ONLY, shared-runner flake class); doc \u00e2\u20ac\u201d daemon docs writer-duty section. Kin REQ-PROJECT-INDEX-STORE, REQ-PROJECT-INDEX-INVALIDATION.",
      "doc": "Self-update: **index writer duty (daemon)** \u00e2\u20ac\u201d the brain hosts ONE writer thread (`projwriter`, spawned beside the live host; single-flight by construction). Batched complexity is contract, `O(P+B+F+C)`: ONE branch enumeration per cycle (`for-each-ref` carries recency + tips), \u00e2\u2030\u00a41 tree scan per **changed** `p-*` branch (`ls-tree` at tip, membership cached by tip), ONE derivation per distinct normalized cwd (cache stamped on the repo-identity marker \u00e2\u20ac\u201d `.git/config` / the `.git` gitfile \u00e2\u20ac\u201d so **ordinary commits are a no-op by construction**); backgrounding the legacy 100+ process loop is REJECTED."
    }
  },
  {
    "pair_number": 72,
    "id_a": "REQ-HAZARD-STALE-INDEX-LOCK",
    "id_b": "REQ-HAZARD-REGISTRY-STALE-CLEAN",
    "req_a": {
      "title": "Sweep stale lockfiles on daemon boot (1.3)",
      "doc": ""
    },
    "req_b": {
      "title": "Stale registry entries degrade to fallback, never hard-fail (4.3)",
      "doc": ""
    }
  },
  {
    "pair_number": 73,
    "id_a": "REQ-REL-3",
    "id_b": "REQ-RELEASE-CHANNEL-PRIVATE",
    "req_a": {
      "title": "Two-key release-signing trust anchor: primary + offline never-used recovery, both pubkeys embedded in the binary's trusted set, manual local signing (ADR-0015)",
      "doc": ""
    },
    "req_b": {
      "title": "THE-FORKENING W2 (ADR-0036 \u00c2\u00a72): the publish pipeline targets `BigscreenVR/spt-bs-releases` \u00e2\u20ac\u201d release.yml assemble/draft/flip retargeted (draft lands on the bs releases repo, untagged, per the existing spt-releases pattern); docs-publish.yml RETIRED (no public docs, ADR-0014 superseded) with the mdbook build folded into ci.yml as the drift gate (CLAUDE.md mandate: doc generation stays CI-gated); counter + signing key + update-set format CONTINUE unchanged (trust anchor is the continuity, carrier is not). Gate: impl \u00e2\u20ac\u201d workflow retarget + drift-gate fold; int \u00e2\u20ac\u201d a full release dry-run assembles binaries + docs asset + signed update-set against the private channel. Kin REQ-DOCS-RELEASE-ASSET, REQ-UPDATE-GH-TRANSPORT, ADR-0036.",
      "doc": ""
    }
  },
  {
    "pair_number": 74,
    "id_a": "REQ-ENDPOINT-PURGE",
    "id_b": "REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL",
    "req_a": {
      "title": "`spt endpoint purge <id>` fully removes an endpoint AND every record keyed on it \u00e2\u20ac\u201d the formal teardown devs/CI need for clean test setup/reset. NOT consent-gated (a local dev/test op \u00e2\u20ac\u201d no peer consent). OFFLINE-ONLY: refuses while the endpoint is online / daemon-hosted (deleting records out from under a live host risks the daemon re-creating or re-hosting mid-purge); `--force` STOPS it first (endpoint stop \u00e2\u2020\u2019 wait for the daemon reconcile to un-host + reap the Psyche) THEN purges. Confirms interactively unless `--yes` (the CI path). Refuses purging the CALLER's OWN running id. All LOCAL \u00e2\u20ac\u201d purge reaches only THIS node's records; a remote endpoint's records can't be touched, and its subnet-registry rows decay via the epoch-lease eviction (REQ-HAZARD-REGISTRY-DECAY). Removes: (1) the perch dir TREE recursively \u00e2\u20ac\u201d owlery/<id>/ incl every nested {id}-psyche / {id}-w* / shells child (info.json, ready marker, sessions.log ledger, spool.db, inbox, .idle/.more-done sentinels, auth token); (2) the registry address (registry::unregister_address); (3) the context store \u00e2\u20ac\u201d ContextStore::remove_endpoint(id): the a-<id> branch+worktree + the <id>/ rows from every p-<project> branch (the same fn `for",
      "doc": "Inbound `api` surface (detailed): **`spt endpoint purge <id>`** (CLI, not `api`) \u00e2\u20ac\u201d the standalone, formal **full teardown**: wipe an endpoint and *every* record keyed on it. It is the dev/CI sibling of `api session-end --erase` (which is adapter-triggered at session end); `purge` is the explicit operator/test command for clean setup-and-reset. **Deliberately NOT consent-gated** \u00e2\u20ac\u201d a local dev/test op, never a peer-visible action. **Offline-only**: it refuses a live / daemon-hosted endpoint (deleting records out from under a running host would let the daemon re-create or re-host mid-purge); **`-"
    },
    "req_b": {
      "title": "W3 (LIFECYCLE-TRUTH): daemon restart no longer massacres hosted endpoints \u00e2\u20ac\u201d daemon start RE-RUNS previously-online spt-hosted endpoints. ROOT rig-proven: daemon stop+start (the apply notice's OWN instruction) kills every hosted endpoint; they stay OFFLINE after start (no resurrection) though records exist (info.json status + adapter + cwd). SCOPE RULING (doyle): re-run-on-start, marked start-reason=daemon-restart; agents' minds ride psyche re-host as today. Int: endpoint online -> daemon stop -> start -> endpoint back ONLINE, same id, harness respawned.",
      "doc": ""
    }
  },
  {
    "pair_number": 75,
    "id_a": "REQ-PICKER-5",
    "id_b": "REQ-PICKER-UX-V013",
    "req_a": {
      "title": "`spt endpoint list` (bare/subnet view) renders an ALIGNED table with canonical node labels: cmd_endpoint_list prints subnet rows with `\\t` TAB separators (cli.rs:~1651-1662) so variable-width endpoint_ids snap fields to different tab-stops \u00e2\u2020\u2019 a RAGGED status column (operator screenshot: X/help statuses misaligned vs rt-*/sptc-*/wall-a); and it calls the node renderer with no label \u00e2\u2020\u2019 bare key-hex for every row (SAME ResourceRow-drops-node_label root as REQ-PICKER-4). FIX: max-width per-column padding (mirror render_node_rows' pad, pad by char count not byte len \u00e2\u20ac\u201d '\u00e2\u20ac\u00a6' is multibyte) replacing the tabs, and render the node via the shared node_label_display now that ResourceRow carries node_label (REQ-PICKER-4). Extract a pure row-formatter seam so the alignment+label is unit-testable. ALSO: the bare list is the SUBNET view (a just-run LOCAL perch is invisible cross-subnet until the next advertise tick), so emit a `--local` hint line so a freshly-run endpoint isn't perceived as lost. (v0.10.0; operator-flagged + doyle dispatch 2026-06-17)",
      "doc": ""
    },
    "req_b": {
      "title": "`spt endpoint run` picker UX (v0.13.0 operator dogfooding): (1) SKIP the first screen \u00e2\u20ac\u201d open directly on 'Pick existing'; `n` jumps to 'Create new'. (2) AUTO-ATTACH after both Start-new AND Resume-from-history (both currently don't attach and show no stdout); add an `h` shortcut to run headless (no attach). (3) 'controlled by' shows the node NAME (node_label_display), not the raw hex. (4) Clean up Start-new output \u00e2\u20ac\u201d drop the Rust `pid=Some(142748)` leak and the 'harness binds its perch on startup' internals; user-friendly, not a process log. (v0.13.0)",
      "doc": ""
    }
  },
  {
    "pair_number": 76,
    "id_a": "REQ-MSG-5",
    "id_b": "REQ-MSG-INJECT-LEG-DROP-VISIBLE",
    "req_a": {
      "title": "user-msg envelope kind + daemon identity gate: a Gateway endpoint / the local user's CLI author user-msg (the user's authority); agent-family senders re-stamped to plain msg; identity-gated never payload-trusted (KH 7.3/7.5); wire-additive (N-1 receivers tolerate the new type)",
      "doc": "Endpoint types: <!-- --> A message sent from a Gateway carries **the user's authority** \u00e2\u20ac\u201d it *is* the user speaking through a device \u00e2\u20ac\u201d and is delivered typed **`user-msg`** (ratified 2026-06-12) so receiving agents weight it as user instruction, not peer-agent chatter. The type is **identity-gated, never payload-trusted** (the KH 7.3/7.5 posture): the daemon permits `user-msg` only from user-backed origins (a Gateway endpoint, the local user's own CLI) and re-stamps an agent-family sender's `user-msg` down to plain `msg` \u00e2\u20ac\u201d authority comes from who you are, not what you wrote."
    },
    "req_b": {
      "title": "SEED (inactive \u00e2\u20ac\u201d observability): a silently-dropped delivery leg must be DISTINGUISHABLE from an honestly-offline endpoint on a status surface. The spt-hosted inject leg (spt-daemon inject.rs `try_spt_hosted_inject` \u00e2\u20ac\u201d the ONE shared implementation behind local cmd_send, the WAN ingress, the idle-edge drain, and the parked-idle/pulse re-offer belts) gates on `is_spt_hosted_no_relay` \u00e2\u2020\u2019 `deliver::is_online` \u00e2\u2020\u2019 `liveness::is_perch_alive`: a perch PINNED TO A DEAD SESSION (the KH 7.25 wedge class \u00e2\u20ac\u201d dead owner, record not yet healed by the next auth touch) makes EVERY belt on that leg silently return None \u00e2\u2020\u2019 messages spool as if the endpoint were ordinarily offline, while the operator-facing view can keep reading the recorded state. Nothing anywhere surfaces 'the inject leg stopped firing for this endpoint' \u00e2\u20ac\u201d the idle-window injections just stop, which from the outside is indistinguishable from 'no messages arrived' (field shape: IDLE-EDGE W1 field-verify, perri's killed-resident rig \u00e2\u20ac\u201d their 'so it did not repeat' conclusion was exactly this invisibility, self-corrected only by re-rigging with a live resident). Shape at activation: a status/list surface DERIVES and reports the inject-leg v",
      "doc": ""
    }
  },
  {
    "pair_number": 77,
    "id_a": "REQ-HAZARD-DAEMON-HOSTED-LIVENESS",
    "id_b": "REQ-HAZARD-PSYCHE-RESIDENCY-EXPECTATION",
    "req_a": {
      "title": "Daemon-hosted perches (Psyche, spt-hosted Self) derive liveness from the daemon endpoint table + info.json status, never is_process_alive(info.pid) (2.5)",
      "doc": ""
    },
    "req_b": {
      "title": "W3 (F030 hazard; paid-for: hall-bf churn ordinal 6491+ + adapter v0.13.2 bad-ship brick 2026-07-04): a psyche failure of ANY shape must NOT remove or alter the parent endpoint's ready/hosted state, and hosting must NOT churn-respawn. The v0.13.2 shim-exit tripped the residency machinery (confirm_residency_or_unhost) which tore down the endpoint's hosted state \u00e2\u20ac\u201d ready marker removed, never re-stamped, every force-native gated leg=cli-gate-not-hosted PERMANENTLY (field brick). FIX: residency machinery retires with the resident child; the teardown that touches parent hosted state is DELETED \u00e2\u20ac\u201d psyche trouble stamps psyche fields only. REQ-HAZARD-LIVEHOST-NONRESIDENT's spirit transfers to the W1 failure budget (its entry gets a SUPERSEDED pointer here, LIVENESS-DECAY\u00e2\u2020\u2019SUPERSEDED pattern from C-1). Conformance int = the hall-bf shape: multi-subnet home, live endpoint, failing psyche \u00e2\u2020\u2019 parent stays deliverable, no rehost churn, error stamped (the wave's heart).",
      "doc": "7.29 Control/viewer stamps CONVERGE to broker session-table truth, not merely edge-trigger `[REQ-HAZARD-CONTROL-STAMP-CONVERGENCE]`: ### 7.30 A Psyche failure of ANY shape must NEVER remove or alter the parent endpoint's ready/hosted state `[REQ-HAZARD-PSYCHE-RESIDENCY-EXPECTATION]` <!-- --> - **Failure (paid-for, field brick 2026-07-04, adapter v0.13.2):** the pre-F-030 model kept a **resident** Psyche process the daemon supervised, with residency machinery (`confirm_residency_or_unhost`) that **un-hosted the parent endpoint** when the resident child went missing. A bad adapter ship (v0.13.2)"
    }
  },
  {
    "pair_number": 78,
    "id_a": "REQ-MESH-3",
    "id_b": "REQ-MSG-ENVELOPE",
    "req_a": {
      "title": "Mesh row fan-out: registry rows stay OWN-AUTHORED; the only change is the push target widens from directly-paired peers to ALL roster members (a wider DIRECT fan-out, never a third-party relay). Every row/message still arrives from its author over a handshake \u00e2\u2020\u2019 KNOWN-HAZARDS 7.5 (origin = handshake node) and 4.10 (eviction lease: any future update comes from that node itself, alive) PRESERVED VERBATIM. Closes the staggered A\u00e2\u2020\u2019B\u00e2\u2020\u2019C repro: C (roster-seeded with A at pairing) initiates to A, seed-proof admits C unpaired, A learns C, both push directly.",
      "doc": ""
    },
    "req_b": {
      "title": "The <EVENT type=\"msg\" from=\u00e2\u20ac\u00a6>body</EVENT> envelope (spt-proto::event, the ADR-0001 grammar) is the SOLE canonical arriving-message format at EVERY harness arriving-message surface on an AGENT perch \u00e2\u20ac\u201d api listen AND api poll/worker-poll, byte-identical (reverses REQ-MSG-4's 'hook drains keep the raw frame by contract'). SCOPE CARVE-OUT: the shell-command relay (api poll <shell-id> --link, cmd_poll_shell) is a distinct internal transport carrying RAW MAC'd stamped frames the shell child consumes verbatim \u00e2\u20ac\u201d NOT an arriving-message surface, deliberately EXEMPT from <EVENT> composition (notify_shell_e2e guards this boundary). __REPLY_TO__ \u00e2\u20ac\u201d mis-elevated during the clean-room port to a fake ADR-0001 'stable wire format' (spt-msg/wire.rs, lib.rs) \u00e2\u20ac\u201d is REMOVED entirely (spool format_row, the spt-msg TCP frame, emit parse_frame); (from, body) carried structurally, <EVENT> composed once at the delivery boundary. No legacy sister-interop (spt-core never required it). Reply-correlation rebinds onto the structural from / <EVENT from=\u00e2\u20ac\u00a6> attribute (ADR-0009 access-gate + ADR-0012 Psyche/spt-live reply-target). Self-delimiting by construction \u00e2\u2020\u2019 finding F-002 (non-self-delimiting multi-message poll",
      "doc": "Decision: <!-- -->"
    }
  },
  {
    "pair_number": 79,
    "id_a": "REQ-PICKER-3",
    "id_b": "REQ-PICKER-CHOOSE-DEDUP-ALL",
    "req_a": {
      "title": "A self-owned subnet row reconciles its status to the LIVE roster: a Subnet-category row whose endpoint_id overlaps a local (is_local) roster id is self-owned (this node hosts it), so its status square is OVERRIDDEN with the live roster status \u00e2\u20ac\u201d the WAN registry snapshot (wansend::load_snapshots) is a periodically-advertised, independently-stale projection, while the local roster (p.alive) is ground truth for an endpoint this node hosts. One status square per endpoint (CONTEXT.md:348-350 \u00e2\u20ac\u201d nothing licenses opposite squares for one endpoint across its Local vs Subnet listings). A reconcile pass in data.rs after the local_rows + subnet_rows gather; BOTH category listings are preserved (Local + Subnet are legitimately distinct views \u00e2\u20ac\u201d you are in your own subnet), only the STATUS is unified. (v0.10.0)",
      "doc": ""
    },
    "req_b": {
      "title": "A4 (F028, operator #5): choose-project duplicate rows. model.rs:314-337 build_project_choices dedups the `Here: <run_cwd>` row only against the HEAD ref's dir (line 324) \u00e2\u20ac\u201d an OLDER history ref with the SAME dir still renders, giving `Here: C:\\...\\projects` + `projects` as two rows for one project (operator screenshot). FIX: dedupe `Here` against ALL history dirs, and skip rest-rows whose dir == run_cwd when Here is present. Extend the model.rs:1847 choose-project test. See triage A4.",
      "doc": ""
    }
  },
  {
    "pair_number": 80,
    "id_a": "REQ-PICKER-HISTORY-FRESH",
    "id_b": "REQ-WHOAMI-IDENTITY-ONLY",
    "req_a": {
      "title": "The `spt endpoint run` picker shows project history for FRESH endpoints (operator-raised v0.12.0 real-harness finding). Symptom: a fresh endpoint shows no project history in the picker. ROOT TBD \u00e2\u20ac\u201d investigate the project-history loader (v0.10.0 PICKER-2, picker/data.rs) before fixing: distinguish a real loader bug from 'fresh = no history yet' semantics. (v0.12.1)",
      "doc": ""
    },
    "req_b": {
      "title": "PROJECT-INDEX W1 (F-040, perri filing claude-spt docs/SPT-CORE-FINDINGS.md @d775b38; correctness-critical opener \u00e2\u20ac\u201d the 2026-07-15 message-bodies incident root): a core IDENTITY-ONLY resolution \u00e2\u20ac\u201d session -> endpoint|null \u00e2\u20ac\u201d that touches NO list/registry/project/git/network path, and `spt whoami` DE-ALIASED from cmd_endpoint_list (cli.rs ~6609 aliases the full list = 100+ git children under hook deadlines). endpoint-info is DISQUALIFIED as the carrier (runs latest_project_ref). Adapters/hooks get a bounded-time identity verb; the harness-hosted adapter fallback stays deadline-vulnerable until this ships. Gate: impl \u00e2\u20ac\u201d the resolver + whoami de-alias; unit \u00e2\u20ac\u201d resolver returns endpoint|null with zero project derivation (assert no git spawn seam); int \u00e2\u20ac\u201d whoami on a multi-perch home answers fast-path without touching context branches; doc \u00e2\u20ac\u201d harness-contract api.md names the identity verb + its no-derivation bound. Kin REQ-PROJECT-INDEX-READER-CUTOVER (list-shaped verbs), REQ-WHOAMI-1, docs/PROJECT-INDEX-TRIAGE.md.",
      "doc": "`spt whoami` \u00e2\u20ac\u201d the identity verb *(identity-only since v0.33.0)*: <!-- --> The bounded-time \"which endpoint am I?\" answer for hooks and adapter glue: resolves the calling session to its endpoint (`$OWL_SESSION_ID` / `$SPT_AGENT_ID` / process ancestry) and prints that ONE endpoint's SELF line \u00e2\u20ac\u201d id, liveness, description. **The no-derivation bound is the contract**: whoami never enumerates the roster, never derives projects, never runs git, never touches the network \u00e2\u20ac\u201d safe to call from deadline-bounded hook paths (the class that previously timed out and black-holed message delivery). Unresolved"
    }
  },
  {
    "pair_number": 81,
    "id_a": "REQ-SEND-REPLYTO-REMOVE",
    "id_b": "REQ-TEST-TMPDIR-HYGIENE",
    "req_a": {
      "title": "Remove `--reply-to` from `spt send` \u00e2\u20ac\u201d a target-fallback + REPLIED-label nicety that confuses agents, with no wire effect (ADR-0020 already made messages structural (from,body), no __REPLY_TO__). Hard-remove (no deprecation shim): the clap flag, the is_reply/REPLIED label branch (always SENT/QUEUED), the `send` how-to --reply-to example, and the reply-to mention in REQ-DOCS-6's send topic. Reply-correlation stays on the structural `from` attribute. (v0.16.0)",
      "doc": ""
    },
    "req_b": {
      "title": "Windows test fixtures MUST NOT leak their temp dirs \u00e2\u20ac\u201d find and fix the leak that accumulated 14,319 `.tmp*` dirs (163 GB) in %TEMP% on hfenduleam over 2026-07-19\u00e2\u2020\u201926 and ate the box to 0.00 GB free during the v0.44.0 cut (find credit: deployah). The class presents as resource exhaustion wearing a timing-flake mask: the release-blocking red was spt-daemon::sync `two_tier_sync_lands_and_gate_refuses_server_side` dying 'No space left on device' INSIDE its own leaked-class tmpdir, and the PRIOR red (digest_cross_node brain-IPC deadline elapse) was the SAME root through a timing probe \u00e2\u20ac\u201d burns release windows and invites false environmental discharges (deployah self-corrected his own). ROOT IS UNPROVEN AT MINT (investigate, don't assume): tempfile-crate dirs self-clean on Drop, so something defeats Drop \u00e2\u20ac\u201d candidates from the incident record: process killed mid-drop (fixture children force-killed while owning the dir), detached/leaked children pinning the dir cwd so removal fails, panic paths that never unwind. Deliverable: name the leaking fixture path(s) with evidence, fix the cleanup (guard/finalizer that survives the kill path, or a fixture-scoped reaper), and prove it with a before/af",
      "doc": ""
    }
  },
  {
    "pair_number": 82,
    "id_a": "REQ-DRIVEN-BY-OWN-NODE-NORMALIZE",
    "id_b": "REQ-RC-HONEST-SESSION-AUTHORITY",
    "req_a": {
      "title": "RC-RENDER-TRUTH v0.38.1 fast-follow leg 1 \u00e2\u20ac\u201d RESHAPED by operator/hertz correction + CONTEXT.md:382-389 grounding (doyle ruling v3, 2026-07-19; the original own-node NORMALIZATION is WITHDRAWN): plain `spt rc` on a CONTROLLED endpoint is refused-with-guidance BY DESIGN (CONTEXT:386 \u00e2\u20ac\u201d never silent-displace; --take is the opt-in kick), and `driven_by` = the CONTROLLING NODE including the own node (CONTEXT:386 single-writer datum \u00e2\u20ac\u201d the stamp comment's remote-only claim was a legacy-path artifact, NOT the model). v0.38.0 shipped behavior is CORRECT: the own-hex latch + client guidance blocking a second same-node plain rc IS the documented refusal, and the W2 generation ladder stays reachable exactly where it belongs \u00e2\u20ac\u201d the recovery seams (rc reconnect re-drive + dispatcher re-serve bypass the client gate; the hertz field FAIL leg is reclassified NOT-A-DEFECT). Remaining work = truth/cosmetics across ALL own-node display surfaces (doyle gate finding 2026-07-19 \u00e2\u20ac\u201d the own-hex latch is truthful but only rc.rs first learned to HUMANIZE it; the picker pin + endpoint-info attached_node still read the own-hex stamp as a foreign remote driver and print raw hex): (a) rc.rs guidance copy names the ",
      "doc": "Consequences: <!-- --> ### v0.38.1 consequence note \u00e2\u20ac\u201d `driven_by` own-node truth (ruling v3)"
    },
    "req_b": {
      "title": "RC-RENDER-TRUTH W1 (ADR-0042 decision 1, hertz perri-contradiction RCA): normal `spt rc` consults the ADR-0041 single honest-session authority BEFORE the persisted-offline fast-fail \u00e2\u20ac\u201d run the bounded SessionProbe::has_live_session_honest gate; honest session exists means attach via run_attach_session_confirmed regardless of persisted status; no honest session means the existing offline refusal stands; claimed session with dead client tree means refusal/reap, NEVER attach. Reuse SessionProbe \u00e2\u20ac\u201d no new liveness heuristic. Kills the authority split where rc refused ('offline \u00e2\u20ac\u201d nothing to attach to') while endpoint run --resume reattached to the same live session. Gate: impl \u00e2\u20ac\u201d the pre-fast-fail probe + session-confirmed routing; unit \u00e2\u20ac\u201d probe-true routes to session-confirmed attach, probe-false keeps the refusal, dead-tree claim refuses; int \u00e2\u20ac\u201d the 3-row regression matrix: offline persisted row + honest live broker session => rc attaches; offline + no session => existing refusal; zombie/dead client tree => refusal, never attach; doc \u00e2\u20ac\u201d ADR-0042.",
      "doc": "Decisions: <!-- --> 1. **`spt rc` consults the honest-session authority before the offline fast-fail.** Normal `spt rc <id>` runs the same bounded `SessionProbe::has_live_session_honest` gate `endpoint run` uses (ADR-0041 single liveness authority). An honest session exists \u00e2\u2020\u2019 attach via the session-confirmed path regardless of persisted status. No honest session \u00e2\u2020\u2019 the existing offline refusal stands. A claimed session with a dead client tree \u00e2\u2020\u2019 refusal/reap, never attach. Reuse `SessionProbe`; no new liveness heuristic. <!-- --> 2. **Resume stamps UNBOUND.** A resume launch transitions an exist"
    }
  },
  {
    "pair_number": 83,
    "id_a": "REQ-ENDPOINT-ONLINE-TRUTH",
    "id_b": "REQ-HAZARD-BRAIN-RESTART-PSYCHE-DUP",
    "req_a": {
      "title": "REGISTRY-LIFECYCLE W2 (ADR-0041 decisions 1+2, emphasys C1 P0): ONLINE is earned, not declared \u00e2\u20ac\u201d cmd_listen stamps status=online only from actual persisted state + hosting authority, never from manifest psyche_init capability alone (no more ready_agent/controllable=false hybrid rows born online-authoritative); livehost reconcile SPLITS control cleanup (clear controlled/driven_by/viewer_count for EVERY endpoint absent from session truth, regardless of state/controllable) from offline classification (live_agent+controllable gate unchanged); legacy hybrid rows self-heal after a SUCCESSFUL broker query only (broker failure is never interpreted as an empty session set); terminal signoff/owner-loss = atomic CAS-guarded offline + ready/address removal WITHOUT overloading soft api session-end (/clear preserves the live listener). Gate: impl \u00e2\u20ac\u201d creator gate + reconcile split + self-heal + terminal path; unit \u00e2\u20ac\u201d creator refuses capability-only online, cleanup clears stamps on state-quirk rows, broker-failure never mass-offlines; int \u00e2\u20ac\u201d dead-PID hybrid row does NOT survive a reconcile cycle (the immortal-row regression); doc \u00e2\u20ac\u201d ADR-0041.",
      "doc": "Decision: <!-- --> 1. **Online is earned, not declared.** A creator may stamp `status=online` only from actual persisted state + hosting authority \u00e2\u20ac\u201d never from manifest capability alone. Legacy hybrid rows self-heal at reconcile, but only after a SUCCESSFUL broker query: a broker failure is never interpreted as an empty session set (no mass-offline on a hiccup). 2. **Control cleanup splits from offline classification.** Reconcile clears `controlled`/`driven_by`/`viewer_count` for EVERY endpoint absent from session truth \u00e2\u20ac\u201d regardless of state or controllability \u00e2\u20ac\u201d while offline classification ke"
    },
    "req_b": {
      "title": "A bare brain restart leaves EXACTLY ONE `{id}-psyche` process per endpoint \u00e2\u20ac\u201d no duplicate. On an abrupt brain death stop_host never runs (the LiveSet + owned child handles die with the brain) and Breap's job/group only reaps at DAEMON stop, so the PRIOR brain's Psyche stays ALIVE; the respawned brain's reconcile re-hosts a SECOND Psyche and overwrites the `{id}-psyche` perch pid, leaving the old one untracked + alive = a duplicate that lingers until daemon-stop (the operator's 'brain kill+restart wedged everything'). FIX: at brain start, BEFORE the first reconcile re-hosts, reap any pre-existing `{id}-psyche` orphan \u00e2\u20ac\u201d ID-SPECIFICALLY (recycle-safe on the shared box, where sibling agents share the `claude` basename): scoped-kill the recorded pid ONLY IF it is alive AND its exe basename == the adapter's psyche program (normalize_basename) AND its COMMAND LINE contains the full psyche id `<id>-psyche` (baked via {id}); a sibling never carries THIS id, and any unreadable signal FAILS SAFE (decline to reap \u00e2\u20ac\u201d a missed dup is bounded by Breap, a wrong-kill is catastrophic). CAVEAT: the cmdline carries `<id>-psyche` only when the adapter's psyche_init.command uses {id} (the norm); a non-{i",
      "doc": ""
    }
  },
  {
    "pair_number": 84,
    "id_a": "REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT",
    "id_b": "REQ-IDLE-PARKED-DELIVERY",
    "req_a": {
      "title": "An spt-hosted endpoint driven by a REMOTE controller whose remote is gone but whose broker connection stays OPEN (a wedged/lost pump that never delivers the detach) AND whose session is IDLE (no output) stays latched ONLINE+CONTROLLED forever: the W1 drain-evict only fires on OUTPUT (CONTROLLER_WRITE_DEADLINE on a backed-up write), a clean disconnect self-heals via detach_if\u00e2\u2020\u2019clear_controller, but an idle session with a half-open/wedged controller connection produces neither signal. PROVED repro-first on a real broker (v0.13.0 W5, inject_control_wedge.rs w5_a2): controller_by STAYS Some(origin) and driven_by STAYS Some after the remote is abandoned without a clean EOF on an idle session \u00e2\u20ac\u201d so the brain reconcile CANNOT detect it from KIND_SESSIONS controller_by (the broker still reports it controlled). FIX DIRECTION (doyle ruling 2026-06-19, broker-side single-writer \u00e2\u20ac\u201d the broker owns driven_by/clear_controller): wire the EXISTING D4c NetPresence connection-disconnect event \u00e2\u2020\u2019 clear_controller for any session whose controller identity == the dead origin (become_controller already stores Some(origin); presence events already exist \u00e2\u20ac\u201d modest wiring, NOT a new probe). The liveness ORACLE ",
      "doc": "Amendment 1 (2026-07-22, DAEMON-LIFECYCLE W2) \u00e2\u20ac\u201d teardown authority is opener-declared class PLUS transport liveness, enforced at the three places decision 6 could not see: <!-- --> <!-- -->"
    },
    "req_b": {
      "title": "W5 (LIFECYCLE-TRUTH): a message QUEUED to an ALREADY-idle spt-hosted endpoint is delivered without an operator poke. ROOT (live during the milestone dispatch 2026-07-07): the idle-edge drain (F-023 leg 2) fires only on the ACTIVE->IDLE transition; no new edge ever comes for a parked session, and the send-time inject didn't carry it \u00e2\u20ac\u201d both doyle->todlando dispatches sat delivered=0 in the spool while the endpoint showed ONLINE. FIX: send-time inject fires for an already-idle spt-hosted target (activity sense says idle => inject now, not spool), and/or a bounded spool sweep re-offers pending rows to idle endpoints (piggyback the pulse tick, no new loop). Int: send to a session idle for N minutes -> delivered without any operator poke.",
      "doc": ""
    }
  },
  {
    "pair_number": 85,
    "id_a": "REQ-UPD-4",
    "id_b": "REQ-UPDATE-APPLY-RESTART-NOTICE",
    "req_a": {
      "title": "Update gated on user confirmation by default; opt-in full-auto",
      "doc": ""
    },
    "req_b": {
      "title": "`spt update apply` prints a LOUD restart-required notice whenever the surviving broker will keep running the pre-apply image (which, until broker-restart choreography exists, is ALWAYS on a successful apply). Public wording, no internal CODE:RESULT markers (composes with REQ-ADAPTER-UPDATE-MESSAGE / the update-apply-confident-message rule) \u00e2\u20ac\u201d name the user-visible CONSEQUENCE ('daemon-coordinated features run the previous version until the daemon restarts'), not the broker/brain internals. Composes with REQ-UPDATE-RUNNING-IMAGE-SURFACE (the notice tells the user what the version-surface will then show, and how to clear it). (F-025)",
      "doc": ""
    }
  },
  {
    "pair_number": 86,
    "id_a": "REQ-HOST-RUN-2",
    "id_b": "REQ-RUN-MULTISUBNET-HOME",
    "req_a": {
      "title": "Project-scoped working directory for spt-hosted bringup: `spt endpoint run` lands the broker-spawned harness PTY in the user's PROJECT cwd, not the daemon's, via an additive `SpawnReq.cwd` field carried through the broker PTY spawn (portable-pty CommandBuilder cwd). N-1-safe wire change (additive, defaulted). Required because the consumer (Claude Code) is project-scoped: broker-inherited cwd = the daemon's cwd = the wrong `.claude`, wrong session history, wrong digest source; `cc <id>` at a project root MUST land the harness in that project. W1 ships broker-inherited cwd as a bringup-proof shortcut only; this REQ must land before the M12 gate (doyle, 2026-06-14).",
      "doc": ""
    },
    "req_b": {
      "title": "`spt endpoint run` resolves the home subnet at the skeleton-create step and pre-creates the skeleton perch carrying it, so the harness `bind` inherits home via establish_perch's immutable prior-branch (no hook change, no env injection). Resolution: sole-subnet auto; multi-subnet + no --subnet + NON-interactive terminal -> refuse early with MRU-ordered --subnet guidance (never the silent 25s online-timeout); multi-subnet + no --subnet + INTERACTIVE -> print proposed config (id/project/adapter[:profile]/home=MRU-default) + 'Ok to proceed? Y/n', n -> --subnet guidance; --subnet overrides + validates membership. MRU = ordered move-to-front LISTs at two levels (per-project + always-updated node-global fallback). Home stays IMMUTABLE (ADR-0010). Fixes the LATENT multi-subnet bringup gap (perri, not a regression \u00e2\u20ac\u201d HOME_REFUSED established >=0.11.0; exposed by the node crossing 1->2 subnets). (ADR-0026)",
      "doc": "Multi-subnet home resolution at `endpoint run` creation: <!-- -->"
    }
  },
  {
    "pair_number": 87,
    "id_a": "REQ-ENDPOINT-LIST-RENDER-POLISH",
    "id_b": "REQ-PICKER-5",
    "req_a": {
      "title": "A6 (F028, operator, 4 asks): `spt endpoint list` render polish. (a) the 'Shared subnets' line is NOT dim \u00e2\u20ac\u201d LIGHT_GRAY = \"37\" (cli.rs:3019) is standard-palette WHITE, indistinguishable from row text; use SGR 90 (bright-black/gray) for the dim intent. (b) the `Total:` line takes the same dim color. (c) move the status glyph ADJACENT to the endpoint name (operator: 'right behind the endpoint name'), mirroring the picker's glyph-beside-name presentation (today the glyph sits at the end next to the status word). (d) color the status WORD like the picker TUI (green ONLINE / gray OFFLINE / blue when driven, matching picker glyph semantics). All in render_node_grouped/render_instance_row (cli.rs ~3000s); pure render with an injected color decision \u00e2\u20ac\u201d unit-testable off a tty. See triage A6.",
      "doc": ""
    },
    "req_b": {
      "title": "`spt endpoint list` (bare/subnet view) renders an ALIGNED table with canonical node labels: cmd_endpoint_list prints subnet rows with `\\t` TAB separators (cli.rs:~1651-1662) so variable-width endpoint_ids snap fields to different tab-stops \u00e2\u2020\u2019 a RAGGED status column (operator screenshot: X/help statuses misaligned vs rt-*/sptc-*/wall-a); and it calls the node renderer with no label \u00e2\u2020\u2019 bare key-hex for every row (SAME ResourceRow-drops-node_label root as REQ-PICKER-4). FIX: max-width per-column padding (mirror render_node_rows' pad, pad by char count not byte len \u00e2\u20ac\u201d '\u00e2\u20ac\u00a6' is multibyte) replacing the tabs, and render the node via the shared node_label_display now that ResourceRow carries node_label (REQ-PICKER-4). Extract a pure row-formatter seam so the alignment+label is unit-testable. ALSO: the bare list is the SUBNET view (a just-run LOCAL perch is invisible cross-subnet until the next advertise tick), so emit a `--local` hint line so a freshly-run endpoint isn't perceived as lost. (v0.10.0; operator-flagged + doyle dispatch 2026-06-17)",
      "doc": ""
    }
  },
  {
    "pair_number": 88,
    "id_a": "REQ-ENDPOINT-CYCLE-HONEST",
    "id_b": "REQ-RC-HONEST-SESSION-AUTHORITY",
    "req_a": {
      "title": "REGISTRY-LIFECYCLE W3 (ADR-0041 decision 6, operator deployah stop/run wedge): cycle verbs share ONE liveness authority \u00e2\u20ac\u201d the ALREADY_LIVE dup-guard liveness-probes the claimed session client tree before refusing (dead tree means reap + respawn honestly, never a refusal citing a zombie); the shutdown state machine consults the same source so is-it-live has one answer (no ALREADY_LIVE / list-OFFLINE / shutdown-NO_EDGE three-way contradiction on the same endpoint). Gate: impl \u00e2\u20ac\u201d probing dup-guard + unified authority; unit \u00e2\u20ac\u201d dead-tree claim probes and reaps, live claim still refuses; int \u00e2\u20ac\u201d controlled zombie (killed client tree, surviving hosted record) leads to endpoint run succeeding honestly end-to-end; doc \u00e2\u20ac\u201d ADR-0041.",
      "doc": "Decision: <!-- --> 1. **Online is earned, not declared.** A creator may stamp `status=online` only from actual persisted state + hosting authority \u00e2\u20ac\u201d never from manifest capability alone. Legacy hybrid rows self-heal at reconcile, but only after a SUCCESSFUL broker query: a broker failure is never interpreted as an empty session set (no mass-offline on a hiccup). 2. **Control cleanup splits from offline classification.** Reconcile clears `controlled`/`driven_by`/`viewer_count` for EVERY endpoint absent from session truth \u00e2\u20ac\u201d regardless of state or controllability \u00e2\u20ac\u201d while offline classification ke"
    },
    "req_b": {
      "title": "RC-RENDER-TRUTH W1 (ADR-0042 decision 1, hertz perri-contradiction RCA): normal `spt rc` consults the ADR-0041 single honest-session authority BEFORE the persisted-offline fast-fail \u00e2\u20ac\u201d run the bounded SessionProbe::has_live_session_honest gate; honest session exists means attach via run_attach_session_confirmed regardless of persisted status; no honest session means the existing offline refusal stands; claimed session with dead client tree means refusal/reap, NEVER attach. Reuse SessionProbe \u00e2\u20ac\u201d no new liveness heuristic. Kills the authority split where rc refused ('offline \u00e2\u20ac\u201d nothing to attach to') while endpoint run --resume reattached to the same live session. Gate: impl \u00e2\u20ac\u201d the pre-fast-fail probe + session-confirmed routing; unit \u00e2\u20ac\u201d probe-true routes to session-confirmed attach, probe-false keeps the refusal, dead-tree claim refuses; int \u00e2\u20ac\u201d the 3-row regression matrix: offline persisted row + honest live broker session => rc attaches; offline + no session => existing refusal; zombie/dead client tree => refusal, never attach; doc \u00e2\u20ac\u201d ADR-0042.",
      "doc": "Decisions: <!-- --> 1. **`spt rc` consults the honest-session authority before the offline fast-fail.** Normal `spt rc <id>` runs the same bounded `SessionProbe::has_live_session_honest` gate `endpoint run` uses (ADR-0041 single liveness authority). An honest session exists \u00e2\u2020\u2019 attach via the session-confirmed path regardless of persisted status. No honest session \u00e2\u2020\u2019 the existing offline refusal stands. A claimed session with a dead client tree \u00e2\u2020\u2019 refusal/reap, never attach. Reuse `SessionProbe`; no new liveness heuristic. <!-- --> 2. **Resume stamps UNBOUND.** A resume launch transitions an exist"
    }
  },
  {
    "pair_number": 89,
    "id_a": "REQ-DIGEST-PROFILE-ENV",
    "id_b": "REQ-INST-1",
    "req_a": {
      "title": "Bug #17: spt endpoint digest returns NO_DIGEST for a ccs-profile endpoint (claude-spt:ccs) though [digest] is wired and the transcript exists \u00e2\u20ac\u201d under .ccs (CLAUDE_CONFIG_DIR relocation) not .claude. The on-demand digest runs the extractor in the daemon context WITHOUT the endpoint profile transcript-location env, so the env-aware resolver cannot find the relocated transcript. Fix: propagate/persist the endpoint profile transcript-location env (e.g. the ccs CLAUDE_CONFIG_DIR) to the on-demand digest extractor so a profile-relocated transcript resolves; confirm the exact extractor verdict via spt adapter digest-proof. Ownership spt-core (digest env/profile propagation), possibly with a claude-spt extractor-resolver assist. See docs/NEXT-MILESTONE-BUG-TRIAGE.md #17.",
      "doc": "`[env]` \u00e2\u20ac\u201d env-var table: **`direction = \"read\"` \u00e2\u20ac\u201d capture a launch-env var for template substitution.** <!-- --> A `read` directive names an environment variable spt-core **captures from the session's launch environment at bind** and then exposes as a `{VAR}` substitution key in `[digest].source` and `[history].locate_template`. This is how an adapter whose harness stores its transcript under a **relocatable root** (e.g. Claude Code's `CLAUDE_CONFIG_DIR`, which a profile like `ccs` repoints) makes that root resolve at digest time \u00e2\u20ac\u201d the on-demand digest runs later in the daemon context where th"
    },
    "req_b": {
      "title": "endpoint ID vs instance split (adapter-agnostic ID)",
      "doc": ""
    }
  },
  {
    "pair_number": 90,
    "id_a": "REQ-ACTIVITY-LIST-JSON",
    "id_b": "REQ-LIVENESS-ORACLE-SOUND",
    "req_a": {
      "title": "`spt endpoint list --json` carries a per-endpoint `activity` (busy|idle) key, for consumers surveying the idle/busy state of many endpoints at once (ADR-0048 decision 1, roster pull avenue; operator addition 2026-07-24). Additive key, N-1-safe; kin the flynn 2026-07-06 last-active/description/adapter list-enrichment seed (same additive posture, may ride together).",
      "doc": "`--json` catalog: | Command | Top-level shape | |---|---| | `endpoint list` | `{ self, subnets[], local[] }` \u00e2\u20ac\u201d `self`: `{id, status, ready, alive, unbound, description, psyche_host_error, translation_fault?}`; `subnets[]`: `{name, endpoints[]}` where each endpoint is `{id, node, node_label, status, resources, endpoint_type?, project?}`; `local[]`: `{id, state, address, ready, alive, unbound, project?, activity?}`. *(Since v0.33.0 the local `project` field reads the daemon-maintained project index \u00e2\u20ac\u201d answers are immediate and may lag a just-changed project by moments; absent while the index has"
    },
    "req_b": {
      "title": "TEARDOWN-AUTHORITY W2 (todlando W1 gate-round-0 finding, doyle-scoped from the LANDED W1 code 2026-07-19): 'does this pid still exist' has ONE answer in spt-core and it is derived from the OS process table. TODAY spt-daemon/src/broker.rs session_is_zombie computes wrapper_alive from spt_store::proc::is_process_alive, which probes OpenProcess on Windows \u00e2\u20ac\u201d and OpenProcess keeps SUCCEEDING for a TERMINATED process while any parent holds an open handle, which the broker ALWAYS does (Arc<PtySession>) for every PTY child it spawned. A correctly-reaped harness therefore reads ALIVE, flipping zombie_verdict off its PRIMARY class (Some(false) = dead root + surviving record = always a zombie) onto the conditional arm, which additionally demands adapter_labeled && past_grace && !has_live_descendants. CONSEQUENCE, live today: a dead-root session that is NOT adapter-labeled is claimed LIVE indefinitely \u00e2\u20ac\u201d `endpoint run`'s dup-guard refuses ENDPOINT_ALREADY_LIVE over an already-dead tree and cmd_rest's Suspend alive_hint forces from=alive on the same false claim (both via has_live_session_honest, cli.rs:2014 and :3805). REQ-ENDPOINT-CYCLE-HONEST exists to give the cycle verbs ONE liveness authori",
      "doc": "7.50 The liveness oracle answers from the process table, never from a handle a caller still holds `[REQ-LIVENESS-ORACLE-SOUND]`: <!-- --> - **Failure (paid-for, found by todlando during TEARDOWN-AUTHORITY W1 gate round 0, 2026-07-19; latent in `session_is_zombie` since the cycle verbs were built):** `spt_store::proc::is_process_alive` probes `OpenProcess` on Windows, which keeps SUCCEEDING for a TERMINATED process while any parent still holds an open handle to it \u00e2\u20ac\u201d and the broker holds `Arc<PtySession>`, hence such a handle, for every PTY child it spawned. So a correctly-reaped harness reads A"
    }
  },
  {
    "pair_number": 91,
    "id_a": "REQ-HAZARD-ENVELOPE-DECODE-ORDER",
    "id_b": "REQ-HAZARD-ENVELOPE-PARSER-SAFE",
    "req_a": {
      "title": "Envelope decode order, ampersand decoded last (4.1)",
      "doc": "Body and attribute encoding: <!-- --> **Decode order is binding.** Decode a *body* as: `<br>` \u00e2\u2020\u2019 `\\n` **first**, then `&lt;`/`&gt;`/`&quot;`, then `&amp;` \u00e2\u2020\u2019 `&` **last**. Decode an *attribute value* the same way minus the `<br>` step. Amp-last is the invariant that prevents double-decoding: a body carrying the literal text `&lt;` arrives as `&amp;lt;`, and decoding the ampersand first would turn it into `<` instead of `&lt;`. And decode **only the extracted body or attribute substring** \u00e2\u20ac\u201d never run the unescape over the full envelope line, or the framing tokens themselves get rewritten."
    },
    "req_b": {
      "title": "Two-slice envelope parser is panic-free and tolerant (4.2)",
      "doc": ""
    }
  },
  {
    "pair_number": 92,
    "id_a": "REQ-HAZARD-RC-ATTACH-FAILFAST",
    "id_b": "REQ-MESH-1",
    "req_a": {
      "title": "B1: `spt rc <id>` to a DEAD or non-streaming session fails fast with a clear message, never an INFINITE blank screen. Today rc.rs run_attach (209-231) + pump spawns PUMP_IPC_READER and blocks: the poll times out each slice but the stream never produces output, so the operator sees a permanent blank (operator: fresh wall-f attached, closed tab, then `spt rc wall-f` HUNG \u00e2\u20ac\u201d the broker still resolved a session for it). FIX: (a) once B2 lands, gate attach on is_online/status \u00e2\u20ac\u201d an offline endpoint yields a clean 'endpoint offline, start it' not an attach; (b) fail-fast \u00e2\u20ac\u201d if the attach-open ack / first output does not arrive within a bound, surface a clear message, never an infinite blank; (c) the broker EOFs the attach stream when the session's child is dead, so rc's existing PumpEnd::BrokerGone graceful path (REQ-HAZARD-RC-EOF) catches it. PIN the exact sub-mechanism with a repro test FIRST (dead-session-lingers-in-broker vs reaped-but-rc-waits vs alive-resting-no-wake \u00e2\u20ac\u201d the wall-f Windows tab-close: child alive-silent vs dead-not-reaped). (v0.12.0)",
      "doc": ""
    },
    "req_b": {
      "title": "Membership proof (seed-proof): symmetric current-epoch seed-knowledge replaces is_trusted at EVERY inbound gate (registry apply, WAN receive, sync, notif, connection accept). MK = HKDF(seed, domain \u00e2\u20ac\u2013 subnet_id \u00e2\u20ac\u2013 seed_epoch); mutual channel-bound challenge-response at connect (transcript binds both handshake-proven node pubkeys, both nonces, subnet_id, seed_epoch, role); verified once per connection, cached on the broker ConnEntry, kept warm via QUIC keep-alive so re-proof is restart/partition/rotation-only. Exact-epoch match (re-seed is the sole N-1 exception). SECURITY INVARIANTS: channel-bound (no cross-connection replay), mutual, accepts a member it never paired (the mesh property).",
      "doc": ""
    }
  },
  {
    "pair_number": 93,
    "id_a": "REQ-HAZARD-DEFERRED-DRAIN",
    "id_b": "REQ-HAZARD-DEFERRED-SURVIVE-DRAIN",
    "req_a": {
      "title": "Deferred spool rows excluded from the event-stream drain (1.4)",
      "doc": ""
    },
    "req_b": {
      "title": "Deferred rows survive poll drain (4.4)",
      "doc": ""
    }
  },
  {
    "pair_number": 94,
    "id_a": "REQ-RESUME-HARNESS-SESSION-ID",
    "id_b": "REQ-RESUME-UNBOUND-STAMP",
    "req_a": {
      "title": "B2 (F028, hall-b diagnosis, verified 0.22.0): respawn/`--resume` feeds the SPT session id to `claude -r`. After the 02:05 daemon bounce respawn built `claude.exe -r 70b5bfa40901b7d4` \u00e2\u20ac\u201d an spt session id in claude's OWN session-id namespace -> claude hangs forever at a 'No sessions match' resume-picker while the endpoint reads online. Hits after EVERY daemon bounce + every picker Resume. The HARNESS session id (claude UUID, stamped in sessions.log/info.json by the hooks) is what {session_id} must mean in the adapter's [session.resume] command; the spt sid must not leak. FIX: substitute the HARNESS session id in the resume template (spt-core substitution-key semantics + LIKELY claude-spt manifest coordination \u00e2\u20ac\u201d FLAG perri BEFORE touching the manifest, adapter-boundary rule). Int: resume template receives the ledger UUID, not the spt sid. See triage B2.",
      "doc": ""
    },
    "req_b": {
      "title": "RC-RENDER-TRUTH W1 (ADR-0042 decision 2, operator-spotted second root seam): resume launch transitions an existing offline perch to UNBOUND before/with the broker spawn \u00e2\u20ac\u201d UNBOUND semantics are fresh/resume-invariant (broker session exists + harness not bound = UNBOUND); generation/session-safe ROLLBACK to offline on spawn failure or session death; bind owns UNBOUND->ONLINE. Supersedes the rc.rs 'resume gets no UNBOUND stamp \u00e2\u20ac\u201d accepted' boundary note (the pre-bind window can be PERMANENT: stuck native resume, SessionStart never fires \u00e2\u20ac\u201d field-proven; truthful UNBOUND is the operator-recovery surface that let `spt rc` reach the wedged TUI). Writer-truth complement to REQ-RC-HONEST-SESSION-AUTHORITY \u00e2\u20ac\u201d both land, neither substitutes. Gate: impl \u00e2\u20ac\u201d UNBOUND stamp at resume spawn + rollback + bind transition; unit \u00e2\u20ac\u201d stamp fires on existing-offline perch resume, rollback on spawn-fail restores offline, generation guard refuses a stale rollback over a newer bind; int \u00e2\u20ac\u201d resumed-but-never-bound endpoint reads UNBOUND (not offline) and `spt rc` attaches to its live session; doc \u00e2\u20ac\u201d ADR-0042.",
      "doc": "Decisions: <!-- --> 1. **`spt rc` consults the honest-session authority before the offline fast-fail.** Normal `spt rc <id>` runs the same bounded `SessionProbe::has_live_session_honest` gate `endpoint run` uses (ADR-0041 single liveness authority). An honest session exists \u00e2\u2020\u2019 attach via the session-confirmed path regardless of persisted status. No honest session \u00e2\u2020\u2019 the existing offline refusal stands. A claimed session with a dead client tree \u00e2\u2020\u2019 refusal/reap, never attach. Reuse `SessionProbe`; no new liveness heuristic. <!-- --> 2. **Resume stamps UNBOUND.** A resume launch transitions an exist"
    }
  },
  {
    "pair_number": 95,
    "id_a": "REQ-HAZARD-INBOX-NO-DOUBLE",
    "id_b": "REQ-HAZARD-RESTART-IDEMPOTENT",
    "req_a": {
      "title": "No double-delivery via legacy inbox (4.5)",
      "doc": ""
    },
    "req_b": {
      "title": "Idempotent/exactly-once delivery across brain restart at every broker boundary (codex #14)",
      "doc": ""
    }
  },
  {
    "pair_number": 96,
    "id_a": "REQ-KICK-1",
    "id_b": "REQ-TERM-ECHO-CLAMP-WINDOW",
    "req_a": {
      "title": "Explicit, loud controller displacement: `spt rc kick <target>` / `--take` (Take intent) kicks the incumbent controller and becomes controller; the displaced controller receives a LOUD `Displaced{by}` notice and is FULLY DETACHED (not demoted to a viewer). A default attach to a controlled endpoint is NEVER a silent displace (it is the Control busy-refusal). An old (N-1) rc omits intent \u00e2\u2020\u2019 Control, so it can drive a free endpoint but CANNOT `--take` \u00e2\u20ac\u201d it can never silently steal, and gets a clean busy-refusal instead. Taking control rides the same access_check(endpoint, origin, Unsolicited) as a normal control attach (if you may drive, you may take \u00e2\u20ac\u201d no elevated kick policy). The picker surfaces 'Kick <node> and attach' (Take) only on a controlled (blue \u00e2\u2013\u00a0) endpoint, via the existing attach dispatch (single-bringup-path: intent is a parameter).",
      "doc": "Shell sleep/wake (offline \u00e2\u2020\u201d online): <!-- --> **BUILT (M12 W2.5).** The controller/viewer model is implemented end-to-end. Attach intent is **three-valued** (`AttachIntent = Viewer | Control | Take`, wire-default `Control`): `Control` to a FREE endpoint becomes controller; `Control` to a CONTROLLED endpoint is **refused with guidance** (`--view` to watch, `--take` to control) \u00e2\u20ac\u201d never auto-viewer, never silent-displace; `Take` (`spt rc --take` / picker \"Kick\") kicks the incumbent with a **loud `Displaced{by}` notice** and full detach (not demote). The broker's per-session `OutputLog` is the fan"
    },
    "req_b": {
      "title": "SEED (DAEMON-LIFECYCLE W3 rideout, field-grounded 2026-07-22): the hosted-TUI echo CLAMP WINDOW \u00e2\u20ac\u201d a Windows pseudoconsole boots with echo/line input ON (in=0x1f7, measured by the 7.55 instrument @0f74bba) and it is the hosted CHILD that clamps them, so any window in which the TUI has not yet (re-)clamped echoes typed bytes into PTY output SERVER-SIDE with no seam re-enabling anything. FIELD GROUNDING (hertz capture-2 byte timeline, output-only taps): echo onset is NOT resize-instant \u00e2\u20ac\u201d first echoed key lands 6003ms after the first resize-associated repaint burst, IMMEDIATELY after a 3225-byte TUI-reinitialization-shaped absolute repaint (no alt-screen/mode CSI anywhere \u00e2\u20ac\u201d cursor hide/show + HOME/absolute repaints only); echo CEASES mid-input (isolated c/o/n/f/i, NO g) directly after a 535-byte TUI diff \u00e2\u20ac\u201d consistent with a late clamp landing (a WinAPI mode call is invisible to a byte tap). The resize seam itself is measured MODE-PRESERVING (REQ-RESIZE-INPUT-MODE-INTEGRITY rig, four sample points). OPEN AT TRIAGE: (1) whether the clamp is the TUI's own SetConsoleMode or portable-pty/ConPTY-internal; (2) whether spt-core CAN mitigate at all \u00e2\u20ac\u201d the daemon structurally cannot read or set t",
      "doc": ""
    }
  },
  {
    "pair_number": 97,
    "id_a": "REQ-REL-2",
    "id_b": "REQ-UPD-2",
    "req_a": {
      "title": "Release asset set consumable by the self-updater: platform binaries, SHA256SUMS, SignedRelease metadata, manifest schema, mock-adapter zip; tag-triggered cross-repo pipeline",
      "doc": "Release runbook: <!-- -->"
    },
    "req_b": {
      "title": "All binaries signature-verified before handoff",
      "doc": ""
    }
  },
  {
    "pair_number": 98,
    "id_a": "REQ-UPDATE-PROMOTE-DRAINED",
    "id_b": "REQ-UPDATE-TRIAL-DRAIN-DRIVE",
    "req_a": {
      "title": "W3 (LIFECYCLE-TRUTH, mechanic-d MOVED FROM W2 per doyle gate verdict @e5ae7a9 \u00e2\u20ac\u201d binding): the update-apply brain-generation promotion completes only when the OLD generation's broker subscriber connection is CLOSED or stall-EVICTED \u00e2\u20ac\u201d never while blocked writes still pend on it. ROOT: `brain.ready` != subscribers drained; W2's stall-evict (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE) only BOUNDS the false-promote window to BRAIN_WRITE_DEADLINE (15s), it does NOT close it \u00e2\u20ac\u201d a new brain can signal ready inside that window while the old gen's conn is still wedged, so the apply 'promotes' onto a still-frozen control plane (the 22:47 incident-night false-promote). FIX: the promotion gate (ADR-0018 brain-trial, brainproc.rs) adds an explicit DRAINED precondition \u00e2\u20ac\u201d promote only on ready AND old-gen-subscriber-drained (conn closed OR stall-evicted); the drained signal reads broker truth (the W2 stall-evict tally / the old conn's liveness), no brain round-trip. The residual W2 left open, now closed. Int = a FALSE-PROMOTE rig that exercises the promotion path itself: an old-gen subscriber conn held wedged past ready must NOT promote until it drains (RED-first: ready-alone promotes).",
      "doc": ""
    },
    "req_b": {
      "title": "UPDATE-WEDGE (counter-54, doyle-ruled 2026-07-09 \u00e2\u20ac\u201d regression of the v0.29.0 seamless brain-swap): a brain generation DRIVES the broker's controller-liveness reap (a KIND_SESSIONS poll) each heartbeat throughout its boot/trial loop, so a hard-KILLED prior generation's black-holed LOCAL controller conn (by:None) is stall-evicted within the trial window and can never permanently strand the promotion DRAINED gate. ROOT (2026-07-09 field freeze, `spt update fetch --apply` v0.30.0->v0.30.2 froze all 7 live PTYs ~30s then rolled back): the promote gate (run_trial, brainproc.rs:657-661) needs BOTH `ready_generation==gen` AND `old_gen_drained()`; `old_gen_drained()` = `!any_local_controller_wedged()` (brainproc.rs:534) is a PURE READ of `write_blocked_since` (broker.rs:2703) \u00e2\u20ac\u201d it never DRIVES the evict. The evict (`stall_evict_controller`, broker.rs:1039, same 15s `brain_write_deadline` the wedge-read uses) only runs via `reap_dead_controller` (broker.rs:967, severed->drop ELSE stall-evict) inside the KIND_SESSIONS snapshot closure (broker.rs:2879). During the isolated brain-trial window NOTHING polls KIND_SESSIONS: the old brain was hard-killed (`child.kill()`, brainproc.rs:851) so its lo",
      "doc": "Self-update: **brain-trial promotion (readiness + drained)** \u00e2\u20ac\u201d the broker supervises the swapped-in brain through a bounded readiness **trial** and *promotes* the new binary only when it both signals ready for its own generation **and** the OUTGOING generation's control plane has **drained** \u00e2\u20ac\u201d the old brain's local (brain-owned) controller connection is closed or stall-evicted, never still holding blocked writes. A hard-killed prior generation leaves that connection **black-holed** (its hosted PTYs keep producing output the broker's writer blocks on, since a killed peer's pipe blocks rather th"
    }
  },
  {
    "pair_number": 99,
    "id_a": "REQ-DAEMON-2",
    "id_b": "REQ-DAEMON-BITS-AMBIGUITY",
    "req_a": {
      "title": "Broker/brain split for seamless self-update",
      "doc": "Restoration field-run evidence (D7-4) \u00e2\u20ac\u201d the seamless-update acceptance: <!-- -->"
    },
    "req_b": {
      "title": "SEED (inactive, RCA-first \u00e2\u20ac\u201d do NOT close on agreement): nothing on a node surfaces WHICH BITS ARE SERVING, and the same silent wrong-state shape bit twice in one day (2026-07-25). Case 1 (adapter-side echo): a post-reboot ensure race left a dev-build alchemy Hub Daemon serving release shells \u00e2\u20ac\u201d version skew visible only by manually comparing process image paths. Case 2 (core, field-measured by flynn): TWO spt daemons resident with live brains on one node \u00e2\u20ac\u201d the installed main daemon (owning ALL sockets: the 5474 listeners and every established connection, single home_tag pipe family) and an orphaned scratchpad-built daemon (auto-started into the node by ensure_running from a stray dev-binary invocation at 16:18, holding zero sockets, resident for hours) \u00e2\u20ac\u201d while `spt --version` on any binary file answers nothing about which process is answering. Measured sharp edges to carry into the RCA: (a) exe path and resolved HOME are independent \u00e2\u20ac\u201d the orphan ran scratchpad bits against the DEFAULT home, so 'where the binary lives' predicts nothing about 'whose state it mutates'; (b) the brain.ready breadcrumb is ONE FILE PER HOME, LAST-WRITER-WINS, keyed by generation \u00e2\u20ac\u201d with two brains in one ho",
      "doc": ""
    }
  },
  {
    "pair_number": 100,
    "id_a": "REQ-PICKER-CHOOSE-DEDUP-ALL",
    "id_b": "REQ-PICKER-PURGE-STRUCTURED",
    "req_a": {
      "title": "A4 (F028, operator #5): choose-project duplicate rows. model.rs:314-337 build_project_choices dedups the `Here: <run_cwd>` row only against the HEAD ref's dir (line 324) \u00e2\u20ac\u201d an OLDER history ref with the SAME dir still renders, giving `Here: C:\\...\\projects` + `projects` as two rows for one project (operator screenshot). FIX: dedupe `Here` against ALL history dirs, and skip rest-rows whose dir == run_cwd when Here is present. Extend the model.rs:1847 choose-project test. See triage A4.",
      "doc": ""
    },
    "req_b": {
      "title": "RC-RENDER-TRUTH W3 (ADR-0043 decision 3, hertz stale-glyphs RCA leg 3 P0): core verbs invoked from inside an active TUI return STRUCTURED outcomes and write NOTHING to the terminal \u00e2\u20ac\u201d the picker purge path calls a structured-outcome purge core (no stdout/stderr under the live alternate screen) and remains the sole renderer via model.flash (today cmd_endpoint_purge writes diagnostics/PURGED to stderr while ratatui owns the alt screen, mutating the physical screen behind the previous-Buffer diff baseline => later draws skip 'already blank' cells and stderr glyph fragments persist \u00e2\u20ac\u201d the x-purge symptom). Baseline-desync regression REQUIRES a stateful/recording backend (pure TestBackend view snapshots cannot catch it). Gate: impl \u00e2\u20ac\u201d structured purge outcome + silent-under-TUI routing; unit \u00e2\u20ac\u201d purge core emits no terminal bytes in structured mode, picker converts outcomes to flash; int \u00e2\u20ac\u201d recording backend: draw ConfirmPurge, inject an external display mutation, transition back => next frame reconstructs the COMPLETE target screen; doc \u00e2\u20ac\u201d ADR-0043.",
      "doc": "Decisions: <!-- --> 1. **One FIFO sequencer per attach sink.** The PTY drain/output writer is the sole sequencer for terminal Output and Exit: Exit is enqueued behind all prior output for each sink (drain EOF/completion first, then Exit). A mutex alone is insufficient \u00e2\u20ac\u201d producer order is the contract. Output-before-Exit is a production-path invariant, regression-proven end-to-end (broker \u00e2\u2020\u2019 attach \u00e2\u2020\u2019 rc). <!-- --> 2. **rc display teardown is unconditional, idempotent, and separate from input teardown.** A display RAII guard (distinct from the OS input/raw-mode guard) runs on every exit path incl"
    }
  },
  {
    "pair_number": 101,
    "id_a": "REQ-HAZARD-CONTROLLER-RETAKE-FLOOR",
    "id_b": "REQ-HOSTING-AUTHORITY-CONTROLLABLE",
    "req_a": {
      "title": "`become_controller` should STRUCTURALLY refuse a controller re-take whose `from_seq` falls below the connection's already-delivered contiguous floor \u00e2\u20ac\u201d making the P1c reorder invariant un-reintroducible by a future caller, not just removed at the one caller. ROOT/SCOPE (doyle proposed, P1c gate dialogue): P1c fixes REQ-HAZARD-CONTROLLER-WRITER-REORDER three ways (handoff single-take + epoch-gate-under-lock + session_cursors seed), removing the one decreasing-floor double-take and bounding any other to already-committed-only. A self-enforcing broker guard would refuse the bad SHAPE outright. BLOCKER: the obvious predicate (`from_seq >= delivered_through`) is UNSAFE because `delivered_through` is SESSION-WIDE (the `Arc<AtomicU64>` on `OutputLog`, shared by all controllers/viewers, advanced monotonic-MAX; `resume_seq` reads it) \u00e2\u20ac\u201d a normal fresh-operator `from_seq=0` attach to a producing session legitimately sits below it (full ring replay + consumer dedup-below/snap-above), and monotonic-MAX can't distinguish the hazard (a `seq1`-without-`seq0` write reads as `2`). The structurally-correct guard needs a NEW per-connection contiguous-sent cursor (the true highest-contiguous seq this so",
      "doc": ""
    },
    "req_b": {
      "title": "RC-RENDER-TRUTH v0.38.1 fast-follow leg 3 (hertz todlando immortal-hybrid trace, doyle fork ruling 2026-07-18 = controllable-authority): ONE hosting authority \u00e2\u20ac\u201d persisted `state` stays the durable endpoint TYPE (REQ-EP-6 open type system; establish_perch's prior-type preserve at startup.rs:346-350 is INTENTIONAL and stays), `controllable==Some(true)` is the source-definitive broker-PTY authority. FIELD ROOT (C1 coverage gap, not new family): an spt-hosted bind over a prior ready_agent perch preserves state=ready_agent while stamping controllable=true + online -> livehost restart_resume_gate (508-534) Skip's state!=live_agent so the orphan never resumes, and reconcile's C1 dead-pid hybrid heal predicate was scoped controllable=false so controllable=true escapes -> immortal dead-PID ready_agent hybrid, latch-driven Active projection (todlando field state; rc's no-session refusal was TRUTHFUL). FIX (hertz refinement, ratified): remove the state rejection from restart_resume_gate; reconcile routes only non-live_agent && controllable!=Some(true) through the PID-model hybrid heal, while controllable==Some(true) rows fall through BROKER-SESSION truth (orphan with ledger/adapter material =",
      "doc": "Requirements: <!-- --> ## Amendment (RC-RENDER-TRUTH v0.38.1 leg 3, doyle ruling 2026-07-19): one hosting authority \u00e2\u20ac\u201d the online-earn authority splits by hosting topology"
    }
  },
  {
    "pair_number": 102,
    "id_a": "REQ-HOST-RUN-1",
    "id_b": "REQ-RESUME-HARNESS-SESSION-ID",
    "req_a": {
      "title": "spt-hosted harness bringup: `spt endpoint run` spawns an adapter's `[session.self]` command template into a broker-held PTY (the spawn-session seam, brain.rs spawn_session_pid \u00e2\u20ac\u201d same broker path shellhost.rs launch_shell_brokered_in uses for shells, now for kind=\"harness\" self-role), registers the perch under the given endpoint id, returns the id. Reverses today's harness-hosted-only launch (external launcher \u00e2\u2020\u2019 `api bind`). Non-interactive flag set (--adapter <a[:profile]> --id <id> --create --resume <session> --attach|--start|--view) covers every terminal action of the W2 interactive picker so shortcuts (cc-<id>) bake fully non-interactive launches; composite adapter:profile resolves via registry::resolve_option leaf-replace overlay.",
      "doc": ""
    },
    "req_b": {
      "title": "B2 (F028, hall-b diagnosis, verified 0.22.0): respawn/`--resume` feeds the SPT session id to `claude -r`. After the 02:05 daemon bounce respawn built `claude.exe -r 70b5bfa40901b7d4` \u00e2\u20ac\u201d an spt session id in claude's OWN session-id namespace -> claude hangs forever at a 'No sessions match' resume-picker while the endpoint reads online. Hits after EVERY daemon bounce + every picker Resume. The HARNESS session id (claude UUID, stamped in sessions.log/info.json by the hooks) is what {session_id} must mean in the adapter's [session.resume] command; the spt sid must not leak. FIX: substitute the HARNESS session id in the resume template (spt-core substitution-key semantics + LIKELY claude-spt manifest coordination \u00e2\u20ac\u201d FLAG perri BEFORE touching the manifest, adapter-boundary rule). Int: resume template receives the ledger UUID, not the spt sid. See triage B2.",
      "doc": ""
    }
  },
  {
    "pair_number": 103,
    "id_a": "REQ-PAIR-5",
    "id_b": "REQ-RELAY-NO-BUSY-DELIVER",
    "req_a": {
      "title": "Multi-subnet pairing: subnet-name discovery input, create-new-names-up-front, rendezvous-token hashing",
      "doc": ""
    },
    "req_b": {
      "title": "MSG-IDENTITY W4 (operator self-send probe 2026-07-09, companion leg): the relay/idle-inject carrier MUST NOT fire for an ACTIVE endpoint \u00e2\u20ac\u201d the daemon already guards send-time (daemon.stderr.log: 'ENDPOINT_INJECT: endpoint ACTIVE -> spool (deferred hint), not injected') yet the field row ended taken_leg='idle-inject' for a message sent while the endpoint was ACTIVE, so the guard is bypassed somewhere on the busy-to-idle EDGE (the parked-drain idle-injects rows that arrived during busy without re-checking whether a poll is concurrently draining them \u00e2\u20ac\u201d the edge itself is when BOTH carriers are plausibly live). RCA-FIRST with REQ-CARRIER-CLAIM-EXCLUSIVE (same rig, same instrument); if the exclusive claim alone closes the double-delivery this leg may reduce to an ordering assertion \u00e2\u20ac\u201d rule at RCA lock, don't build blind. Gate: unit \u00e2\u20ac\u201d the idle-edge drain re-verifies activity (or defers to the claim) before idle-injecting; a row taken by a poll is never idle-injected. Kin REQ-IDLE-PARKED-DELIVERY (the drain this guards), REQ-MSG-IDLE-EDGE-DRAIN, F-023 anti-starvation gate (do NOT break the already-idle delivery class).",
      "doc": ""
    }
  },
  {
    "pair_number": 104,
    "id_a": "REQ-RESUME-CUSTODY-IDENTITY",
    "id_b": "REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL",
    "req_a": {
      "title": "Resume custody is an identity pair (pid + process creation time), never a bare PID. (ADR-0047 decision 1; hertz v0.39.4 field bug 1, RCA accepted 2026-07-22.) TODAY: livehost's restart gate and liveness-reconcile DEFER both consume `read_resume_pid(..).is_some_and(is_process_alive)` \u00e2\u20ac\u201d zero identity binding, so a dead wake-resume spawn's pid recycled onto an unrelated process (field: resume.pid=29456 -> cmd.exe) reads as a live resume forever: reconcile defers every tick, the row stays online-authoritative, FALSE-ONLINE with no self-repair. FIX: the custody record stores (pid, creation_time) written atomically at spawn-mint; every consumer tests the PAIR; mismatch = NOT OURS -> the discovering reader DELETES the record and proceeds (self-heal, not error); successful bind and spawn-reap clear custody atomically with their own outcome. Creation time from the process SNAPSHOT, never a retained handle (KH 7.50); platform without a snapshot -> unproven -> defer one tick, never a manufactured verdict. Gate: impl \u00e2\u20ac\u201d the paired custody record + both livehost consumers on the pair test; unit \u00e2\u20ac\u201d pair mismatch reads NOT-OURS + record deleted, pair match reads OURS, absent-snapshot defers; int \u00e2\u20ac\u201d ",
      "doc": "1. Process custody is an identity, never a bare PID: <!-- -->"
    },
    "req_b": {
      "title": "W3 (LIFECYCLE-TRUTH): daemon restart no longer massacres hosted endpoints \u00e2\u20ac\u201d daemon start RE-RUNS previously-online spt-hosted endpoints. ROOT rig-proven: daemon stop+start (the apply notice's OWN instruction) kills every hosted endpoint; they stay OFFLINE after start (no resurrection) though records exist (info.json status + adapter + cwd). SCOPE RULING (doyle): re-run-on-start, marked start-reason=daemon-restart; agents' minds ride psyche re-host as today. Int: endpoint online -> daemon stop -> start -> endpoint back ONLINE, same id, harness respawned.",
      "doc": ""
    }
  },
  {
    "pair_number": 105,
    "id_a": "REQ-PICKER-PROJECT-HISTORY-TRUTH",
    "id_b": "REQ-RESUME-ROW-PER-PROJECT",
    "req_a": {
      "title": "#1: picker project history is derived from sessions.log cwds (newest->oldest, deduped by project_id_for_dir) UNION context-store branches, EXCLUDING owlery-internal paths (any cwd under spt_home()/owlery) everywhere a project is displayed or inferred. Fixes three stacked defects (crates/spt/src/picker/data.rs): (1a) project_history_for (data.rs:372) reads ONLY context-store p-* branches, which are empty on this box -> history []; (1b) the fallback origin project (data.rs:207) is derived from info.json.cwd = latest-boot-cwd (rewritten every rebind), not origin; (1c) psyche-host sessions bind owlery-internal cwds that pollute history. Full DIRS stay available in the model (feature #5 needs them). PROJECT REPRESENTATION RULING (operator 2026-07-03): project IDs ONLY, EVERYWHERE incl local display; on ID collision disambiguate minimally via a PURE disambiguate_project_ids(entries)->display-names fn (append one-level-up parent folder and/or root drive letter, e.g. 'spt-core (projects)' vs 'spt-core (D:)'). See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #1.",
      "doc": ""
    },
    "req_b": {
      "title": "A5 (F028, operator #6): resume-from-history labels EVERY session with the endpoint's newest project. data.rs:480-496 resume_rows_for clones project_history.first() onto every ResumeRow (line 481/488), so all sessions read as the head project (the ghost). The per-row e.cwd is already carried for launch-into-dir. FIX: derive per-row project_id_for_dir(e.cwd) (owlery-excluded -> fall back to trigger token), rendered through A1's display-name path. See triage A5.",
      "doc": ""
    }
  },
  {
    "pair_number": 106,
    "id_a": "REQ-HAZARD-DROP-FILE-SINGLE-WRITER",
    "id_b": "REQ-HAZARD-DIRECT-WRITE-PRECEDENCE",
    "req_a": {
      "title": "Drop files are daemon-owned single-writer (6.4)",
      "doc": ""
    },
    "req_b": {
      "title": "Direct-write precedence marker (with node id) guards stale overwrite (6.5)",
      "doc": ""
    }
  },
  {
    "pair_number": 107,
    "id_a": "REQ-ENDPOINT-LIST-NODE-GROUPED",
    "id_b": "REQ-PICKER-4",
    "req_a": {
      "title": "spt endpoint list is node-grouped over unique INSTANCES, not subnet-grouped over duplicated rows: one row per (id,node) instance \u00e2\u20ac\u201d subnet duplication collapsed (ADR-0006 \u00c2\u00a71: subnet is never an identity axis), freshest-epoch wins a cross-subnet status disagreement (the resolve_among twin rule) \u00e2\u20ac\u201d grouped under 'This node: <label>' (cyan; local roster is the status truth, advertised-status vocabulary, corrupt = suspended + corrupt annotation) then remote nodes alphabetical (node name orange 38;5;208, the legacy $LIVE orange), each node carrying a light-gray 'Shared subnets:' line (subnets among our memberships where that node gossips any visible row; per-instance subnet detail stays in --json/--detail), instance rows [id, endpoint_type, glyph, status] (endpoint_type threaded from Instance.endpoint_type through ResourceRow \u00e2\u20ac\u201d additive, pre-field rows render '-'), per-node 'Total:' lines with NO grand total (the stderr ENDPOINTS:<n> line is REMOVED \u00e2\u20ac\u201d it counted subnet rows, so the same instance in N subnets counted N times), SELF pin kept first with a '(self @ <node>)' marker (REQ-WHOAMI-1 alias; self also appears as a This-node row so the node total stays honest), remote nodes with zero",
      "doc": ""
    },
    "req_b": {
      "title": "The picker's Subnet category renders the canonical node LABEL, not bare key-hex: a subnet row's node renders as 'LABEL (keyprefix\u00e2\u20ac\u00a6)' (e.g. 'HFENDULEAM (bcead52b\u00e2\u20ac\u00a6)') per CONTEXT.md:650 + Instance.node_label, NOT the raw node key-hex (SPT_DEV:14efb80cb\u00e2\u20ac\u00a6 \u00e2\u20ac\u201d a picker-only regression because resource_projection\u00e2\u2020\u2019ResourceRow drops node_label, so data.rs subnet_rows uses the raw row.node). Thread node_label into the picker subnet path (ResourceRow gains node_label, or subnet_rows looks it up via the registry's node_labels) and REUSE the one canonical render (format!(\"{l} ({}\u00e2\u20ac\u00a6)\", key_prefix) \u00e2\u20ac\u201d cli.rs / wansend.rs), never a re-implementation. (v0.10.0)",
      "doc": ""
    }
  },
  {
    "pair_number": 108,
    "id_a": "REQ-DAEMON-5",
    "id_b": "REQ-HAZARD-INJECT-WORKER-POISON",
    "req_a": {
      "title": "Pump liveness: the peer pump writes a last-tick heartbeat consumed by daemon status / subnet status (decision 23 render legs in REQ-CLI-2/REQ-SUBNET-8); the daemon supervises the pump task \u00e2\u20ac\u201d a panic is caught, logged loudly, and the pump restarts with capped backoff (\u00e2\u2030\u00a45 min), so a 5.9-class death self-heals visibly instead of silently halving the daemon (M8 decision 23; field motivation: hfenduleam 2026-06-07 half-death)",
      "doc": ""
    },
    "req_b": {
      "title": "The per-session inject-worker floor Mutex is SHARED by the inject worker (open/flush) and the controller-input path (dispatch_input Layer C buffer_if_held); a panic under the lock on EITHER poisons it, and a bare .lock().unwrap() at the next site then panics too \u00e2\u20ac\u201d a panic in the inject WORKER kills its thread WITHOUT reaping the translation child, orphaning a live binary while event_tx.send fails, so force-native reports 'worker-gone' delivered=false FOREVER (the F-e generic-miss shape). HARDENING, NOT the F-e incident root (perri's matrix exonerated poison under thrash/dormancy/churn): (i) poison-tolerant floor lock (unwrap_or_else into_inner) at all 3 sites so one panic can't cascade the session's delivery dead; (ii) a panic-resilient inject worker (catch_unwind -> fault+terminate the child on a worker panic) so a dead worker never orphans a live binary + strands delivery. Poison-tolerance class of REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE / bug #16.",
      "doc": ""
    }
  },
  {
    "pair_number": 109,
    "id_a": "REQ-DISPATCH-FALLBACK-CIRCUIT",
    "id_b": "REQ-UPDATE-APPLY-RESTART-NOTICE",
    "req_a": {
      "title": "REDISPATCH-STALL W1 (ADR-0038 Amendment, fixes 1+5): the opener ring-peek fallback fires ONLY on the explicit UnsupportedVerb/old-broker answer \u00e2\u20ac\u201d transport timeout/EOF/poison classify Failed and requeue bounded, NEVER a second replay subscriber (today first_line's Err(_) arm at dispatch.rs:414 catches everything; the comment intends old-broker-only). Retries are CLASSIFIED: pre-setup transient may retry; a deadline-poisoned replay is CIRCUIT-BROKEN (global backoff) and a replacement subscriber is never installed until the prior subscriber is fully gone. NOT a revert to the v0.33 burn-the-claim abandonment (rejected): the stream must recover after the breaker window \u00e2\u20ac\u201d show it in test. Gate: impl \u00e2\u20ac\u201d narrowed fallback arm + breaker; unit \u00e2\u20ac\u201d timeout/EOF/poison never reach the peek path while unsupported-verb does + breaker trips and resets + no-reinstall-until-gone; int \u00e2\u20ac\u201d T2 (no peek subscriber created on transport errors) + T5 (breaker recovery, the not-abandonment discriminator) + T7 mixed-image N-1 with REAL traffic-carrying streams (handshake-only insufficient); doc \u00e2\u20ac\u201d ADR-0038 Amendment. Kin REQ-DISPATCH-CLAIM-RETRY (upgraded, not reverted), REQ-STREAM-OPENER-DURABLE (the N-1 window ",
      "doc": "Consequences: ## Amendment \u00e2\u20ac\u201d REDISPATCH-STALL (2026-07-16) <!-- --> <!-- --> <!-- --> <!-- -->"
    },
    "req_b": {
      "title": "`spt update apply` prints a LOUD restart-required notice whenever the surviving broker will keep running the pre-apply image (which, until broker-restart choreography exists, is ALWAYS on a successful apply). Public wording, no internal CODE:RESULT markers (composes with REQ-ADAPTER-UPDATE-MESSAGE / the update-apply-confident-message rule) \u00e2\u20ac\u201d name the user-visible CONSEQUENCE ('daemon-coordinated features run the previous version until the daemon restarts'), not the broker/brain internals. Composes with REQ-UPDATE-RUNNING-IMAGE-SURFACE (the notice tells the user what the version-surface will then show, and how to clear it). (F-025)",
      "doc": ""
    }
  },
  {
    "pair_number": 110,
    "id_a": "REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE",
    "id_b": "REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT",
    "req_a": {
      "title": "W2 (LIFECYCLE-TRUTH, KNOWN-HAZARDS, flagship \u00e2\u20ac\u201d the update wedge): PTY viewer fan-out and control mutations must not depend synchronously on a live draining brain. ROOT rig-CONFIRMED (NtSuspendProcess on the brain, no update involved): brain-subscriber session-output writes ride UNDER the per-session log lock (broker.rs:19-20); failed writes are handled (cursor freeze + detach :3486) but BLOCKED writes are not. Suspended brain => within seconds attached rc output freezes; detach does NOT release the control stamp (release routes through the brain); reattach REFUSED (controlled-by); rc --take hangs; daemon status stays healthy. Field: every brain cycle (incl. every update apply) has a freeze window; a stalled/slow-draining new brain = permanent wedge until bounce; brain.ready != subscribers drained. FIX SHAPE (todlando proposes, doyle RULES BEFORE IMPL): subscriber writes move OFF the log lock (bounded/nonblocking, stall => detach-subscriber like viewer eviction \u00e2\u20ac\u201d broker already buffers + replays on re-attach, so a detached-stalled brain self-heals by rewind); control stamp release/take completes against the BROKER without brain round-trip (or bounded with loud timeout). doc = KNOWN-",
      "doc": "7.35 The cached ceremony-clock NTP offset must NOT survive an OS clock STEP \u00e2\u20ac\u201d an offset measured against the pre-step clock strands every pairing for the TTL `[REQ-HAZARD-CEREMONY-CLOCK-STEP]`: ### 7.36 The broker control plane and PTY fan-out must NEVER block on a single subscriber connection \u00e2\u20ac\u201d a suspended brain conn must not wedge control `[REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]` <!-- --> - **Failure (paid-for, rig-CONFIRMED 2026-07-06/07 \u00e2\u20ac\u201d `NtSuspendProcess` on the brain, no update involved):** a controller's writer thread does a BLOCKING socket write to its brain subscriber conn. When th"
    },
    "req_b": {
      "title": "An spt-hosted endpoint driven by a REMOTE controller whose remote is gone but whose broker connection stays OPEN (a wedged/lost pump that never delivers the detach) AND whose session is IDLE (no output) stays latched ONLINE+CONTROLLED forever: the W1 drain-evict only fires on OUTPUT (CONTROLLER_WRITE_DEADLINE on a backed-up write), a clean disconnect self-heals via detach_if\u00e2\u2020\u2019clear_controller, but an idle session with a half-open/wedged controller connection produces neither signal. PROVED repro-first on a real broker (v0.13.0 W5, inject_control_wedge.rs w5_a2): controller_by STAYS Some(origin) and driven_by STAYS Some after the remote is abandoned without a clean EOF on an idle session \u00e2\u20ac\u201d so the brain reconcile CANNOT detect it from KIND_SESSIONS controller_by (the broker still reports it controlled). FIX DIRECTION (doyle ruling 2026-06-19, broker-side single-writer \u00e2\u20ac\u201d the broker owns driven_by/clear_controller): wire the EXISTING D4c NetPresence connection-disconnect event \u00e2\u2020\u2019 clear_controller for any session whose controller identity == the dead origin (become_controller already stores Some(origin); presence events already exist \u00e2\u20ac\u201d modest wiring, NOT a new probe). The liveness ORACLE ",
      "doc": "Amendment 1 (2026-07-22, DAEMON-LIFECYCLE W2) \u00e2\u20ac\u201d teardown authority is opener-declared class PLUS transport liveness, enforced at the three places decision 6 could not see: <!-- --> <!-- -->"
    }
  },
  {
    "pair_number": 111,
    "id_a": "REQ-ADAPTER-VERSION-CMD",
    "id_b": "REQ-MSG-ENVELOPE",
    "req_a": {
      "title": "`spt adapter version <name>` prints a registered adapter's declared version \u00e2\u20ac\u201d the EXISTING mandatory `[adapter].version` manifest field (manifest.rs already requires it; NOT a `[strings].version`, NOT `get-string`, no second version source). A new `AdapterCmd::Version{option}` resolves the option's merged view via `registry::resolve_option` like the sibling adapter subcommands and prints `manifest.adapter.version`; an unresolvable option errors (exit 1) the same way. (v0.13.2 W6)",
      "doc": "`[adapter]` \u00e2\u20ac\u201d header: <!-- --> `version` is **mandatory**. It is the single declared adapter version \u00e2\u20ac\u201d read before any update (the `gh_release` compare point), surfaced by `spt adapter version <name>` (which prints this `[adapter].version` of a registered adapter; exit 1 if unregistered), and the value the post-update `message` gate keys on. There is no second version source \u00e2\u20ac\u201d no `[strings].version`, no `get-string` convention."
    },
    "req_b": {
      "title": "The <EVENT type=\"msg\" from=\u00e2\u20ac\u00a6>body</EVENT> envelope (spt-proto::event, the ADR-0001 grammar) is the SOLE canonical arriving-message format at EVERY harness arriving-message surface on an AGENT perch \u00e2\u20ac\u201d api listen AND api poll/worker-poll, byte-identical (reverses REQ-MSG-4's 'hook drains keep the raw frame by contract'). SCOPE CARVE-OUT: the shell-command relay (api poll <shell-id> --link, cmd_poll_shell) is a distinct internal transport carrying RAW MAC'd stamped frames the shell child consumes verbatim \u00e2\u20ac\u201d NOT an arriving-message surface, deliberately EXEMPT from <EVENT> composition (notify_shell_e2e guards this boundary). __REPLY_TO__ \u00e2\u20ac\u201d mis-elevated during the clean-room port to a fake ADR-0001 'stable wire format' (spt-msg/wire.rs, lib.rs) \u00e2\u20ac\u201d is REMOVED entirely (spool format_row, the spt-msg TCP frame, emit parse_frame); (from, body) carried structurally, <EVENT> composed once at the delivery boundary. No legacy sister-interop (spt-core never required it). Reply-correlation rebinds onto the structural from / <EVENT from=\u00e2\u20ac\u00a6> attribute (ADR-0009 access-gate + ADR-0012 Psyche/spt-live reply-target). Self-delimiting by construction \u00e2\u2020\u2019 finding F-002 (non-self-delimiting multi-message poll",
      "doc": "Decision: <!-- -->"
    }
  },
  {
    "pair_number": 112,
    "id_a": "REQ-LIST-JSON-LIVENESS-PARITY",
    "id_b": "REQ-STREAM-OPENER-DURABLE",
    "req_a": {
      "title": "GATEWAY-LIVENESS (flynn field bug 2026-07-09, RCA reader-divergence root): `spt endpoint list` (human) and `endpoint list --json` MUST report an IDENTICAL status for a locally-hosted endpoint \u00e2\u20ac\u201d especially a pid-alive, status-ABSENT gateway (no psyche_init). ROOT (todlando RCA STEP-1, doyle-verified): the --json builder (crates/spt/src/cli.rs cmd_endpoint_list) emits each subnet row's status straight from resource_projection (spt-net registry.rs:566, passes instance.status through verbatim :592 \u00e2\u20ac\u201d the persisted WAN snapshot, a lagged gossip that can carry a stale/crash-time Suspended) and NEVER applies the self-owned reconcile the human/picker path applies (reconcile_self_owned, crates/spt/src/picker/data.rs:160 via gather_endpoints :112). So a pid-alive self-owned gateway reads Suspended on --json but ONLINE on human (roster::enumerate spt/src/roster.rs:38 -> is_perch_alive pid-fallback spt-store/liveness.rs:136); the adapter suspend-poll (parse_endpoint_status over endpoint list --json --show-all) reads the divergent --json status -> self-suspends a pid-alive gateway. Candidates REFUTED: resource_projection does NOT re-derive liveness (copies instance.status, only skips !routable :",
      "doc": ""
    },
    "req_b": {
      "title": "REDISPATCH-TRUTH W1 (ADR-0038, hertz fix B): stream classification identity is RESTART-DURABLE and independent of the evictable data ring \u00e2\u20ac\u201d today reconstruction classifies via peek_first_line from ring seq 0, but StreamLog is a bounded 4096-transport-chunk ring (DEFAULT_STREAM_RING_CHUNKS, nethost.rs:111), so a high-traffic ACTIVE stream's opener/Request record evicts and replay classifies Unknown/Failed = active stream permanently abandoned. Pin an immutable bounded opener/classification fact (the complete first NDJSON record, or its derived family+cursor facts) OUTSIDE the data ring per broker-held inbound stream, held until stream close; recovery classifies from that metadata, never ring seq 0. Ring semantics and size untouched (enlarging the ring for a correctness fact is REJECTED). Gate: impl \u00e2\u20ac\u201d the pinned opener fact + classification cutover; unit \u00e2\u20ac\u201d classification survives full ring roll-over; int \u00e2\u20ac\u201d production-path regression D2: push >4096 transport chunks on an active Attach (opener evicted), restart dispatcher, prove durable classification resumes the SAME operator stream. HEAVY nextest group at birth. Kin REQ-DISPATCH-CLAIM-RETRY, REQ-HAZARD-REDISPATCH-CONTROL-STEAL.",
      "doc": ""
    }
  },
  {
    "pair_number": 113,
    "id_a": "REQ-MESH-3",
    "id_b": "REQ-WAN-SEND-DELIVERY",
    "req_a": {
      "title": "Mesh row fan-out: registry rows stay OWN-AUTHORED; the only change is the push target widens from directly-paired peers to ALL roster members (a wider DIRECT fan-out, never a third-party relay). Every row/message still arrives from its author over a handshake \u00e2\u2020\u2019 KNOWN-HAZARDS 7.5 (origin = handshake node) and 4.10 (eviction lease: any future update comes from that node itself, alive) PRESERVED VERBATIM. Closes the staggered A\u00e2\u2020\u2019B\u00e2\u2020\u2019C repro: C (roster-seeded with A at pairing) initiates to A, seed-proof admits C unpaired, A learns C, both push directly.",
      "doc": ""
    },
    "req_b": {
      "title": "Bug #9/#10: cross-node spt send reports SENT(WAN) but does not deliver, even on stable-IP pairs. Real root: spt send resolves the dial with id-only addr_for_node_hex (endpoint.rs:538) which forces a fresh iroh discovery round-trip every send, while the gossip pump uses cached direct addresses (dial_seeded/PeerAddrStore) so gossip stays green but send rides a marginal discovery path that cannot carry the fire-and-forget payload; the handshake completes so SENT(WAN) prints falsely. Fix: (1) route the WAN dial through the pump seeded-direct-address resolution (PeerAddrStore first, id-only fallback); (2) receiver writes its WanOutcome back so the sender confirms delivery under the QUIC deadline and only reports SENT on confirmed delivery, honest failure otherwise. Access-gate/perch/spool all verified correct (ruled out). See docs/NEXT-MILESTONE-BUG-TRIAGE.md #9-10.",
      "doc": ""
    }
  },
  {
    "pair_number": 114,
    "id_a": "REQ-REST-TERMINAL-NORMALIZE",
    "id_b": "REQ-SOFT-END-PRESERVES-LIVE-LISTENER",
    "req_a": {
      "title": "REGISTRY-LIFECYCLE W2 (ADR-0041 decision 3, rest-normalize P0): definitive hosted-session loss normalizes TERMINALLY and ATOMICALLY \u00e2\u20ac\u201d one store-level mutation writes status=offline + rest_state=suspended + clears dormant_since_ms in the SAME info.json write, invoked at authoritative broker-session-loss/liveness-reap + cmd_stop. NOT daemon_rest_event(Suspend) (no edge when effective already Suspended \u00e2\u20ac\u201d raw Active intent survives); NOT reader-side blanket offline-implies-suspended (destroys explicit-Wake semantics: wake writes intent first, reconcile consumes). Graceful shutdown keeps echo-before-teardown. Kills the zombie WAKE_RESUME loop (perri field: repeated cross-generation resumes of a dead session). Gate: impl \u00e2\u20ac\u201d atomic terminal-normalize mutation + call sites; unit \u00e2\u20ac\u201d store-level atomic pair never mixed + endpoint_stop covers already-offline/raw-Active input; int \u00e2\u20ac\u201d session vanish means offline+suspended and the NEXT reconcile emits NO WAKE_RESUME, explicit suspended/offline->Wake->Active launches EXACTLY once, RefuseLivePid-then-valid-bind custody race leaves the revived seat unsuspended; doc \u00e2\u20ac\u201d ADR-0041.",
      "doc": "Decision: <!-- --> 1. **Online is earned, not declared.** A creator may stamp `status=online` only from actual persisted state + hosting authority \u00e2\u20ac\u201d never from manifest capability alone. Legacy hybrid rows self-heal at reconcile, but only after a SUCCESSFUL broker query: a broker failure is never interpreted as an empty session set (no mass-offline on a hiccup). 2. **Control cleanup splits from offline classification.** Reconcile clears `controlled`/`driven_by`/`viewer_count` for EVERY endpoint absent from session truth \u00e2\u20ac\u201d regardless of state or controllability \u00e2\u20ac\u201d while offline classification ke"
    },
    "req_b": {
      "title": "F-2 (REMOTE-TRUTH triage \u00c2\u00a7F-2, field-repro'd hall-bf 2026-07-04): a /clear must not sever a SURVIVING poll listener's relay address \u00e2\u20ac\u201d post-clear owl-path send hit NO_PERCH while ready was present and the inject path healthy. ROOT (source-certain): the relay registry row (id\u00e2\u2020\u2019addr + owning pid, registered by the LISTENER process itself at PollListener::bind, listener.rs:109) is DELETED by the adapter's soft `api session-end` (reporting.rs:231) fired for the DEPARTING session at /clear; but the poll listener SURVIVES /clear (a session-independent process, still bound on its port), so the deletion destroys a TRUE row. The C-2 boundary re-stamp (REQ-HAZARD-BOUNDARY-READY-STRAND) restores ready + status online but CANNOT re-register \u00e2\u20ac\u201d only the listener process knows its socket addr \u00e2\u20ac\u201d so every subsequent send lookup misses \u00e2\u2020\u2019 NO_PERCH forever (until a listener restart re-binds). FIX: the SOFT arm of cmd_session_end unregisters CONDITIONALLY through the single liveness resolver (liveness::is_registry_entry_alive \u00e2\u20ac\u201d the KH 2.5-aware resolver clean_stale_entries routes through): a row whose owner is still ALIVE is PRESERVED (the row is LISTENER-scoped truth, not session-scoped; the listener ou",
      "doc": ""
    }
  },
  {
    "pair_number": 115,
    "id_a": "REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT",
    "id_b": "REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT",
    "req_a": {
      "title": "An spt-hosted endpoint driven by a REMOTE controller whose remote is gone but whose broker connection stays OPEN (a wedged/lost pump that never delivers the detach) AND whose session is IDLE (no output) stays latched ONLINE+CONTROLLED forever: the W1 drain-evict only fires on OUTPUT (CONTROLLER_WRITE_DEADLINE on a backed-up write), a clean disconnect self-heals via detach_if\u00e2\u2020\u2019clear_controller, but an idle session with a half-open/wedged controller connection produces neither signal. PROVED repro-first on a real broker (v0.13.0 W5, inject_control_wedge.rs w5_a2): controller_by STAYS Some(origin) and driven_by STAYS Some after the remote is abandoned without a clean EOF on an idle session \u00e2\u20ac\u201d so the brain reconcile CANNOT detect it from KIND_SESSIONS controller_by (the broker still reports it controlled). FIX DIRECTION (doyle ruling 2026-06-19, broker-side single-writer \u00e2\u20ac\u201d the broker owns driven_by/clear_controller): wire the EXISTING D4c NetPresence connection-disconnect event \u00e2\u2020\u2019 clear_controller for any session whose controller identity == the dead origin (become_controller already stores Some(origin); presence events already exist \u00e2\u20ac\u201d modest wiring, NOT a new probe). The liveness ORACLE ",
      "doc": "Amendment 1 (2026-07-22, DAEMON-LIFECYCLE W2) \u00e2\u20ac\u201d teardown authority is opener-declared class PLUS transport liveness, enforced at the three places decision 6 could not see: <!-- --> <!-- -->"
    },
    "req_b": {
      "title": "A `rc --view` VIEWER that overflows its broker subscription queue and is EVICTED (OutputLog::append try_send Full \u00e2\u2020\u2019 viewers.remove, REQ-HAZARD-VIEWER-ISOLATION session-protection) must SKIP TO LIVE, not die silently. ROOT (v0.13.0, b4 JIT item 2 = p0_paste + post-b4 a_journaled-Linux, ONE root): serve_attach forwards each frame (read_event\u00e2\u2020\u2019b64decode\u00e2\u2020\u2019re-encode AttachRecord\u00e2\u2020\u2019net_stream_send) SLOWER than the drain fans out under flood \u00e2\u2020\u2019 its VIEWER_CHANNEL_DEPTH(256) channel overflows \u00e2\u2020\u2019 the drain evicts (viewers.remove drops the ViewerSink \u00e2\u2020\u2019 drops tx \u00e2\u2020\u2019 viewer_writer's rx.recv() Err \u00e2\u2020\u2019 the writer returns WRITING NOTHING) \u00e2\u2020\u2019 serve_attach's brain.read_event() just STOPS getting Output (no EOF, no error) \u00e2\u2020\u2019 serve_attach blocks forever \u00e2\u2020\u2019 the operator receives nothing (attach_received_output=FALSE). Eviction-of-a-hopelessly-behind-viewer is CORRECT session-protection (keep it); SILENT+PERMANENT eviction is the bug. VIEWER-only \u00e2\u2020\u2019 B2-SAFE (a viewer never advances delivered_through / is not authoritative / exposes no resume cursor). FIX (doyle-gated, skip-to-live = tail -f reconnect): (1) explicit broker\u00e2\u2020\u2019viewer EVICTION SIGNAL (KIND_VIEWER_EVICTED, written in the viewer_writer thread OFF the log lo",
      "doc": "Shell sleep/wake (offline \u00e2\u2020\u201d online): <!-- --> **BUILT (M12 W2.5).** The controller/viewer model is implemented end-to-end. Attach intent is **three-valued** (`AttachIntent = Viewer | Control | Take`, wire-default `Control`): `Control` to a FREE endpoint becomes controller; `Control` to a CONTROLLED endpoint is **refused with guidance** (`--view` to watch, `--take` to control) \u00e2\u20ac\u201d never auto-viewer, never silent-displace; `Take` (`spt rc --take` / picker \"Kick\") kicks the incumbent with a **loud `Displaced{by}` notice** and full detach (not demote). The broker's per-session `OutputLog` is the fan"
    }
  },
  {
    "pair_number": 116,
    "id_a": "REQ-ENDPOINT-STOP-OFFLINE",
    "id_b": "REQ-HAZARD-DAEMON-STOP-BARRIER",
    "req_a": {
      "title": "H3: `spt endpoint stop <id>` marks the endpoint OFFLINE (alive=false), not merely de-readied. cmd_stop (cli.rs:2994-3010) removes the ready marker + unregisters the address but does NOT set status offline, so a stopped daemon-hosted endpoint still reports alive=true (status=online latch). FIX: add set_status(perch, STATUS_OFFLINE) to cmd_stop \u00e2\u20ac\u201d folds with B2 (same setter). Unit: stop \u00e2\u2020\u2019 is_perch_alive=false / alive=false. (v0.12.0)",
      "doc": ""
    },
    "req_b": {
      "title": "B3: `spt daemon stop` then an immediate `spt daemon start` does NOT race \u00e2\u20ac\u201d stop fully completes before it returns. Today request_stop (seedmap.rs:240-255) returns on the KIND_STOPPING ack (sent seedmap.rs:174-176) BEFORE the seed socket unbinds, so a following is_running ping (daemon.rs:375) wins the exit window and start reports ALREADY_RUNNING (operator: daemon stop \u00e2\u2020\u2019 STOPPED then start \u00e2\u2020\u2019 ALREADY_RUNNING). FIX: unbind/stop-gate the seed socket BEFORE acking KIND_STOPPING, OR request_stop waits for a ping-to-fail before returning. Unit: stop then immediate is_running()==false. (v0.12.0)",
      "doc": ""
    }
  },
  {
    "pair_number": 117,
    "id_a": "REQ-BRAIN-RESUME-NO-CONTROL-STEAL",
    "id_b": "REQ-RC-HONEST-SESSION-AUTHORITY",
    "req_a": {
      "title": "UPDATE-WEDGE round 2 (v0.30.4, doyle-ruled 2026-07-09 \u00e2\u20ac\u201d field incident on the counter-54 fetch--apply): a brain-respawn must NEVER steal, then stall-evict, the controller of a broker PTY session the daemon brain does not DRIVE. ROOT (field-pinned + SME, docs/UPDATE-WEDGE-2-RCA.md + docs/UPDATE-WEDGE-2-SME-todlando.md): `resume_sessions` (brain.rs:985) re-attaches EVERY session `KIND_SESSIONS` returns via `subscribe` = `subscribe_with(AttachIntent::Control, by:None)` (brain.rs:1450-1455). The docstring's 'a None identity never displaces (falls back to viewer)' is a MYTH for FREE / SAME-LOCAL-IDENTITY slots: `resolve_subscribe` (broker.rs:1134-1153) stall-evicts FIRST, then `become_controller` if the slot is now free OR the incumbent is also local (None==None) \u00e2\u20ac\u201d viewer-fallback fires ONLY when a DIFFERENT REMOTE controls. So on a box with N spt-hosted broker PTYs, a brain-respawn STEALS the by:None controller of every free/local-controlled session (incl. the operator's LOCAL `spt rc`), which the daemon brain never drains (it hosts no PTY sessions \u00e2\u20ac\u201d brainproc.rs:184) \u00e2\u2020\u2019 15s later `stall_evict_controller` (broker.rs:1039) releases driven_by \u00e2\u2020\u2019 the session is UNCONTROLLABLE (Failure A). I",
      "doc": "Self-update: **resume re-attach is view-only for non-driven sessions** \u00e2\u20ac\u201d on respawn the new brain queries the broker for every hosted session and re-attaches to rebuild output-continuity cursors, but it re-attaches as a **viewer**, never a controller, for any session it does not itself drive (which is *all* of them today \u00e2\u20ac\u201d the supervised daemon brain hosts no PTY sessions; spt-hosted PTYs are driven by the operator's attach or the endpoint's own loop). Re-attaching as a controller would seize the controller slot of every free/local-controlled session \u00e2\u20ac\u201d including the operator's local `spt rc` \u00e2\u20ac\u201d"
    },
    "req_b": {
      "title": "RC-RENDER-TRUTH W1 (ADR-0042 decision 1, hertz perri-contradiction RCA): normal `spt rc` consults the ADR-0041 single honest-session authority BEFORE the persisted-offline fast-fail \u00e2\u20ac\u201d run the bounded SessionProbe::has_live_session_honest gate; honest session exists means attach via run_attach_session_confirmed regardless of persisted status; no honest session means the existing offline refusal stands; claimed session with dead client tree means refusal/reap, NEVER attach. Reuse SessionProbe \u00e2\u20ac\u201d no new liveness heuristic. Kills the authority split where rc refused ('offline \u00e2\u20ac\u201d nothing to attach to') while endpoint run --resume reattached to the same live session. Gate: impl \u00e2\u20ac\u201d the pre-fast-fail probe + session-confirmed routing; unit \u00e2\u20ac\u201d probe-true routes to session-confirmed attach, probe-false keeps the refusal, dead-tree claim refuses; int \u00e2\u20ac\u201d the 3-row regression matrix: offline persisted row + honest live broker session => rc attaches; offline + no session => existing refusal; zombie/dead client tree => refusal, never attach; doc \u00e2\u20ac\u201d ADR-0042.",
      "doc": "Decisions: <!-- --> 1. **`spt rc` consults the honest-session authority before the offline fast-fail.** Normal `spt rc <id>` runs the same bounded `SessionProbe::has_live_session_honest` gate `endpoint run` uses (ADR-0041 single liveness authority). An honest session exists \u00e2\u2020\u2019 attach via the session-confirmed path regardless of persisted status. No honest session \u00e2\u2020\u2019 the existing offline refusal stands. A claimed session with a dead client tree \u00e2\u2020\u2019 refusal/reap, never attach. Reuse `SessionProbe`; no new liveness heuristic. <!-- --> 2. **Resume stamps UNBOUND.** A resume launch transitions an exist"
    }
  },
  {
    "pair_number": 118,
    "id_a": "REQ-ENDPOINT-LIST-NODE-GROUPED",
    "id_b": "REQ-ENDPOINT-LIST-REST-FILTER",
    "req_a": {
      "title": "spt endpoint list is node-grouped over unique INSTANCES, not subnet-grouped over duplicated rows: one row per (id,node) instance \u00e2\u20ac\u201d subnet duplication collapsed (ADR-0006 \u00c2\u00a71: subnet is never an identity axis), freshest-epoch wins a cross-subnet status disagreement (the resolve_among twin rule) \u00e2\u20ac\u201d grouped under 'This node: <label>' (cyan; local roster is the status truth, advertised-status vocabulary, corrupt = suspended + corrupt annotation) then remote nodes alphabetical (node name orange 38;5;208, the legacy $LIVE orange), each node carrying a light-gray 'Shared subnets:' line (subnets among our memberships where that node gossips any visible row; per-instance subnet detail stays in --json/--detail), instance rows [id, endpoint_type, glyph, status] (endpoint_type threaded from Instance.endpoint_type through ResourceRow \u00e2\u20ac\u201d additive, pre-field rows render '-'), per-node 'Total:' lines with NO grand total (the stderr ENDPOINTS:<n> line is REMOVED \u00e2\u20ac\u201d it counted subnet rows, so the same instance in N subnets counted N times), SELF pin kept first with a '(self @ <node>)' marker (REQ-WHOAMI-1 alias; self also appears as a This-node row so the node total stays honest), remote nodes with zero",
      "doc": ""
    },
    "req_b": {
      "title": "spt endpoint list hides SUSPENDED instances by default; a new --show-all flag reveals them. Status-first row ordering with fixed precedence ONLINE > CONTROLLED > UNBOUND > SUSPENDED (when shown) > corrupt last, alphabetical by id within each band. Two invariants: (1) CORRUPT rows ALWAYS render regardless of filters \u00e2\u20ac\u201d corrupt is a record condition demanding operator action (purge/re-mint), not resting clutter; hiding it would re-create counter-39 bug #3 (cross-ref REQ-HAZARD-CORRUPT-PERCH-COHERENCE, CONTEXT.md instance-state _Also avoid_); (2) the per-node Total line DISCLOSES the filter \u00e2\u20ac\u201d 'Total: N (+M suspended hidden)' \u00e2\u20ac\u201d so nothing silently vanishes. Registry-Offline rows stay excluded by projection law (resource_projection skips unroutable; unchanged). Grill-with-docs ruling 2026-07-02 (operator + doyle).",
      "doc": ""
    }
  },
  {
    "pair_number": 119,
    "id_a": "REQ-NOTIF-SCOPE",
    "id_b": "REQ-NOTIF-SEAM-DISMISS",
    "req_a": {
      "title": "Notif scope is a per-row producer-chosen attribute node|subnet: node-scoped rows (update/consent/rollback kinds) live and die on their node and NEVER enter a replication feed; subnet-scoped rows keep the full ADR-0007 machinery (replication, cross-node dismiss, semilattice join); scope determines the first-fire candidate set (node-scoped targets the most-recently-active endpoint ON that node). New row fields additive serde-defaulted (host_binaries pattern) \u00e2\u20ac\u201d old peers parse clean, schema regen drift-gated",
      "doc": "1. Scope is a per-row, producer-chosen attribute: `node` | `subnet`: <!-- -->"
    },
    "req_b": {
      "title": "Staleness is dismissed at the seam that knows, never evaluated at surface: a successful update apply dismisses spt-core:update-staged; the update worker's next check, seeing running >= staged, dismisses it too (covers out-of-band installs); a later successful update dismisses a rollback row; the primitive stores rows and latches \u00e2\u20ac\u201d NO relevance predicates, nothing evaluated at surface time",
      "doc": "3. Staleness is dismissed at the seam that knows, never evaluated at surface: <!-- -->"
    }
  },
  {
    "pair_number": 120,
    "id_a": "REQ-RC-CROSS-NODE-ATTACH",
    "id_b": "REQ-RC-HONEST-SESSION-AUTHORITY",
    "req_a": {
      "title": "Bug #4: spt rc to a remote endpoint fails with 'no live session' though endpoint list shows it Active \u00e2\u20ac\u201d rc.rs:1063 resolves only the LOCAL broker session table and always dials loopback, never consulting the registry or dialing the owning node (the cross-node attach transport exists in the broker; only the client leg is missing). Fix: on a local resolve miss, resolve the owning node from the registry (reuse resolve_across_visible), net_dial that node, and run a remote session-resolve + serve_attach round-trip (mirror the wansend resolve-dial-round-trip pattern). Shares the resolve-owning-node primitive with REQ-WAN-SEND-DELIVERY. See docs/NEXT-MILESTONE-BUG-TRIAGE.md #4.",
      "doc": "Instances: <!-- --> **Remote-control vs local operation (two distinct modes \u00e2\u20ac\u201d not the same as instances):** - **Operate locally:** drive the native instance on *your* machine (its local files, its synced mind). The normal case. - **Remote-control (Shell-like):** attach a control/view surface to an instance *running on another node* \u00e2\u20ac\u201d compute + files stay remote; you are a viewport (the byte-stream terminal attach, daemon-to-daemon over Iroh). Used when you specifically want *that machine's* environment. This is effectively a Shell (a driven surface, user\u00e2\u2020\u2019agent direction), separate from the ins"
    },
    "req_b": {
      "title": "RC-RENDER-TRUTH W1 (ADR-0042 decision 1, hertz perri-contradiction RCA): normal `spt rc` consults the ADR-0041 single honest-session authority BEFORE the persisted-offline fast-fail \u00e2\u20ac\u201d run the bounded SessionProbe::has_live_session_honest gate; honest session exists means attach via run_attach_session_confirmed regardless of persisted status; no honest session means the existing offline refusal stands; claimed session with dead client tree means refusal/reap, NEVER attach. Reuse SessionProbe \u00e2\u20ac\u201d no new liveness heuristic. Kills the authority split where rc refused ('offline \u00e2\u20ac\u201d nothing to attach to') while endpoint run --resume reattached to the same live session. Gate: impl \u00e2\u20ac\u201d the pre-fast-fail probe + session-confirmed routing; unit \u00e2\u20ac\u201d probe-true routes to session-confirmed attach, probe-false keeps the refusal, dead-tree claim refuses; int \u00e2\u20ac\u201d the 3-row regression matrix: offline persisted row + honest live broker session => rc attaches; offline + no session => existing refusal; zombie/dead client tree => refusal, never attach; doc \u00e2\u20ac\u201d ADR-0042.",
      "doc": "Decisions: <!-- --> 1. **`spt rc` consults the honest-session authority before the offline fast-fail.** Normal `spt rc <id>` runs the same bounded `SessionProbe::has_live_session_honest` gate `endpoint run` uses (ADR-0041 single liveness authority). An honest session exists \u00e2\u2020\u2019 attach via the session-confirmed path regardless of persisted status. No honest session \u00e2\u2020\u2019 the existing offline refusal stands. A claimed session with a dead client tree \u00e2\u2020\u2019 refusal/reap, never attach. Reuse `SessionProbe`; no new liveness heuristic. <!-- --> 2. **Resume stamps UNBOUND.** A resume launch transitions an exist"
    }
  },
  {
    "pair_number": 121,
    "id_a": "REQ-BRAIN-UPDATE-RESTART-CLEAN-CLOSE",
    "id_b": "REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE",
    "req_a": {
      "title": "SEED (DEFERRED, doyle 2026-07-09 \u00e2\u20ac\u201d post-counter-54 root-hardening for UPDATE-WEDGE; mint now, impl a FUTURE milestone): on a PLANNED brain-restart (`BRAIN_UPDATE_RESTART`, the seamless update-apply brain-cycle), the outgoing brain's LOCAL (by:None) controller conns are GRACEFULLY CLEAN-CLOSED as the brain is cycled, instead of hard-killed and left to black-hole. ROOT (field-pinned 2026-07-09, daemon.stderr.log L24277-24303): the update-restart path hard-kills the outgoing brain (`child.kill()`, brainproc.rs:851); its live-agent controller conns then block on dead pipes (never EOF) \u00e2\u2020\u2019 the broker reads them WEDGED (broker.rs:2695-2700) \u00e2\u2020\u2019 the new candidate's promotion DRAINED gate (`any_local_controller_wedged`, broker.rs:2704) stays true until the W2 stall-evict matures (~15s). REQ-UPDATE-TRIAL-DRAIN-DRIVE (counter-54) makes the candidate DRIVE that reap so it promotes within the 30s window \u00e2\u20ac\u201d but at a ~15s wedge-maturity hitch (frozen PTYs during the swap). A CLEAN close makes the conn 'simply absent \u00e2\u2020\u2019 drained=false AT ONCE \u00e2\u2020\u2019 fast promote' (broker.rs:2699-2700), ELIMINATING the hitch = truly seamless (honors the paradigm the field freeze broke). SUPERSEDES the earlier livehost-reattac",
      "doc": ""
    },
    "req_b": {
      "title": "W2 (LIFECYCLE-TRUTH, KNOWN-HAZARDS, flagship \u00e2\u20ac\u201d the update wedge): PTY viewer fan-out and control mutations must not depend synchronously on a live draining brain. ROOT rig-CONFIRMED (NtSuspendProcess on the brain, no update involved): brain-subscriber session-output writes ride UNDER the per-session log lock (broker.rs:19-20); failed writes are handled (cursor freeze + detach :3486) but BLOCKED writes are not. Suspended brain => within seconds attached rc output freezes; detach does NOT release the control stamp (release routes through the brain); reattach REFUSED (controlled-by); rc --take hangs; daemon status stays healthy. Field: every brain cycle (incl. every update apply) has a freeze window; a stalled/slow-draining new brain = permanent wedge until bounce; brain.ready != subscribers drained. FIX SHAPE (todlando proposes, doyle RULES BEFORE IMPL): subscriber writes move OFF the log lock (bounded/nonblocking, stall => detach-subscriber like viewer eviction \u00e2\u20ac\u201d broker already buffers + replays on re-attach, so a detached-stalled brain self-heals by rewind); control stamp release/take completes against the BROKER without brain round-trip (or bounded with loud timeout). doc = KNOWN-",
      "doc": "7.35 The cached ceremony-clock NTP offset must NOT survive an OS clock STEP \u00e2\u20ac\u201d an offset measured against the pre-step clock strands every pairing for the TTL `[REQ-HAZARD-CEREMONY-CLOCK-STEP]`: ### 7.36 The broker control plane and PTY fan-out must NEVER block on a single subscriber connection \u00e2\u20ac\u201d a suspended brain conn must not wedge control `[REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]` <!-- --> - **Failure (paid-for, rig-CONFIRMED 2026-07-06/07 \u00e2\u20ac\u201d `NtSuspendProcess` on the brain, no update involved):** a controller's writer thread does a BLOCKING socket write to its brain subscriber conn. When th"
    }
  },
  {
    "pair_number": 122,
    "id_a": "REQ-SHELL-4",
    "id_b": "REQ-SHELL-PERCH-DIR",
    "req_a": {
      "title": "Shell tunnel (reliable-ordered opaque byte stream): an owner<->shell link may hold a long-lived, reliable-ordered, link-bound QUIC stream pair carrying opaque wire protocol traffic the channel taxonomy must NOT reinterpret (first consumer usbip URB) \u00e2\u20ac\u201d manifest opt-in, not enveloped, not MAC-framed, not spooled; the link lifecycle governs it (a link-break closes the tunnel). Reliable-ordered \u00e2\u2021\u2019 congestion surfaces as lag never loss \u00e2\u2021\u2019 acceptable only on-LAN: the on-LAN posture is documented and the tunnel is NOT proven cross-WAN (CONTEXT:262, minted 2026-06-11 Gateway grill; doyle gate C2).",
      "doc": "Shell model (detailed): <!-- shell tunnel: a long-lived reliable-ordered link-bound QUIC stream pair carrying opaque bytes the taxonomy never reinterprets; manifest opt-in, not enveloped/MAC-framed/spooled; link-break closes it; reliable-ordered \u00e2\u2021\u2019 on-LAN posture --> Channels carry typed, taxonomy-interpreted payloads. Distinct from them, an owner\u00e2\u2020\u201dshell link may also hold a **shell tunnel**: a long-lived, **reliable, ordered** byte stream (a dedicated QUIC stream pair bound to the link) for protocol traffic the channel taxonomy must NOT reinterpret \u00e2\u20ac\u201d opaque wire protocols spoken end-to-end (fir"
    },
    "req_b": {
      "title": "A shell binary can mechanically resolve where its files land. (flynn spt-alchemy clean-room audit 2026-07-21, doyle code-verified, seed pair item 2, P1 \u00e2\u20ac\u201d HARD-GATES flynn's alchemy W4.) TODAY: `spt shell send --file` lands the blob at <shell-perch>/files/<xfer-id>-<name> and the shell_file frame's path attr is PERCH-RELATIVE (files/...) \u00e2\u20ac\u201d but the spawn template substitution keys are ONLY {id}/{adapter_name}/{link_token} (shellhost.rs fill_spawn_command) and the spawned child inherits the BROKER's cwd, so no mechanical perch resolution exists: the binary cannot turn the frame's path into a real file without guessing SPT_HOME layout. DOYLE RULING, both halves binding: the frame KEEPS the perch-relative path (an absolute path in a spooled frame LIES across perch moves and node boundaries \u00e2\u20ac\u201d frames outlive layouts); the fix is an ADDITIVE {perch_dir} spawn-template substitution key (opt-in \u00e2\u20ac\u201d templates that do not use it are byte-identical, N-1-safe by construction) filled with the shell perch dir so the binary receives its root at spawn and joins the frame's relative path against it. REFUSED, recorded so it is not re-proposed: blessing SPT_HOME layout guessing as an interim contract. Pu",
      "doc": "`shell_text` \u00e2\u20ac\u201d free text: <!-- --> ## `shell_file` \u00e2\u20ac\u201d a landed file"
    }
  },
  {
    "pair_number": 123,
    "id_a": "REQ-ENDPOINT-LIST-NODE-GROUPED",
    "id_b": "REQ-ENDPOINT-MESSAGE-ONLY-DISPLAY",
    "req_a": {
      "title": "spt endpoint list is node-grouped over unique INSTANCES, not subnet-grouped over duplicated rows: one row per (id,node) instance \u00e2\u20ac\u201d subnet duplication collapsed (ADR-0006 \u00c2\u00a71: subnet is never an identity axis), freshest-epoch wins a cross-subnet status disagreement (the resolve_among twin rule) \u00e2\u20ac\u201d grouped under 'This node: <label>' (cyan; local roster is the status truth, advertised-status vocabulary, corrupt = suspended + corrupt annotation) then remote nodes alphabetical (node name orange 38;5;208, the legacy $LIVE orange), each node carrying a light-gray 'Shared subnets:' line (subnets among our memberships where that node gossips any visible row; per-instance subnet detail stays in --json/--detail), instance rows [id, endpoint_type, glyph, status] (endpoint_type threaded from Instance.endpoint_type through ResourceRow \u00e2\u20ac\u201d additive, pre-field rows render '-'), per-node 'Total:' lines with NO grand total (the stderr ENDPOINTS:<n> line is REMOVED \u00e2\u20ac\u201d it counted subnet rows, so the same instance in N subnets counted N times), SELF pin kept first with a '(self @ <node>)' marker (REQ-WHOAMI-1 alias; self also appears as a This-node row so the node total stays honest), remote nodes with zero",
      "doc": ""
    },
    "req_b": {
      "title": "An online agent-family endpoint with NO session surface reads as message-reachable, not as a harness-hosted live agent and not as a plain ONLINE. (hertz v0.39.0 field report 2026-07-21; doyle PRODUCT RULING \u00e2\u20ac\u201d deliberately NEITHER of the two options offered.) OBSERVED: an adapterless `spt ready` perch (type=ready_agent, adapter=null, ready/alive true) displays plain ONLINE with '(unknown adapter)'. SOURCE: picker/model.rs display_status returns Online for every non-live_agent type (~680-681) BEFORE consulting controllable, and amber HarnessOnly is live-agent-only (~683-687). RULING: that type gate is CORRECT and STAYS. 'ONLINE - HARNESS ONLY' means one specific thing \u00e2\u20ac\u201d a LIVE AGENT whose session surface is owned by a harness rather than a broker PTY. Broadening it to 'online non-controllable agent-family' would make one label mean two different things, which is how a status label starts lying. An adapterless ready receiver is a THIRD truth: message-reachable, no session surface at all, nothing to attach to ever. So: a DISTINCT display state (working name 'ONLINE - MESSAGE ONLY') keyed on the endpoint TYPE (ready_agent), never on absence-of-adapter, and never an invented adapter name",
      "doc": ""
    }
  },
  {
    "pair_number": 124,
    "id_a": "REQ-BROKER-OUTPUT-BEFORE-EXIT",
    "id_b": "REQ-HAZARD-ECHO-BEFORE-SIGNOFF",
    "req_a": {
      "title": "RC-RENDER-TRUTH W3 (ADR-0043 decision 1, hertz stale-glyphs RCA leg 1 P0): the PTY drain/output writer is the SOLE FIFO sequencer for terminal Output + Exit per attach sink \u00e2\u20ac\u201d Exit is enqueued BEHIND all prior output (drain EOF/completion first, then Exit); the exit waiter never direct-writes KIND_EXIT around the queued output path (a mutex serializes bytes, not producer order). Kills the stranded-final-frame race (final EL/SGR-reset/cursor-show/?1049l lost when Exit overtakes Output \u00e2\u20ac\u201d already admitted and compensated in the broker test suite, never fixed in production rc). Gate: impl \u00e2\u20ac\u201d single sequencer, exit-behind-output enqueue; unit \u00e2\u20ac\u201d ordering invariant on the writer queue (exit never precedes queued output for a sink); int \u00e2\u20ac\u201d short-lived child emits 'XXXX ESC[2K ESC[?25h ESC[?1049l' then exits => that exact Output precedes Exit through the PRODUCTION broker->attach->rc path; doc \u00e2\u20ac\u201d ADR-0043.",
      "doc": "Decisions: <!-- --> 1. **One FIFO sequencer per attach sink.** The PTY drain/output writer is the sole sequencer for terminal Output and Exit: Exit is enqueued behind all prior output for each sink (drain EOF/completion first, then Exit). A mutex alone is insufficient \u00e2\u20ac\u201d producer order is the contract. Output-before-Exit is a production-path invariant, regression-proven end-to-end (broker \u00e2\u2020\u2019 attach \u00e2\u2020\u2019 rc). <!-- --> 2. **rc display teardown is unconditional, idempotent, and separate from input teardown.** A display RAII guard (distinct from the OS input/raw-mode guard) runs on every exit path incl"
    },
    "req_b": {
      "title": "Echo-commune fires before INIT_SIGNOFF on orphan teardown (3.3)",
      "doc": ""
    }
  },
  {
    "pair_number": 125,
    "id_a": "REQ-HAZARD-TEARDOWN-DEADEND",
    "id_b": "REQ-TEARDOWN-UNCOOPERATIVE-HOST",
    "req_a": {
      "title": "TEARDOWN-AUTHORITY W1 HAZARD (ADR-0045 decision 8; hertz RCA 2 \u00e2\u20ac\u201d hit doyle's OWN live production endpoint, recoverable only by out-of-band scoped kill): `endpoint stop` followed by `endpoint run --id <same>` MUST succeed (spawn or resume) and must NEVER answer ENDPOINT_CREATE_CONFLICT about a session that stop claimed to end. TRAP SHAPE (two individually-CORRECT behaviors composing into a lifecycle DEAD END with no in-band exit): (1) stop leaves the broker session alive (REQ-ENDPOINT-TEARDOWN-AUTHORITY), and (2) `endpoint run` correctly REFUSES ENDPOINT_CREATE_CONFLICT rather than silently reattaching (v0.37.0 no-silent-reattach rule, deliberately chosen). The survivor is therefore simultaneously what stop claims to have killed AND what run refuses to work around; with a wedged harness every in-band verb is exhausted (stop lies, run refuses, rc replays a dead PTY, rc --take controls a process that never answers). Neither behavior is individually wrong \u00e2\u20ac\u201d the COMPOSITION is the hazard, so the regression must assert the composition, not either verb alone. This single assertion is the whole user-visible point of W1. Gate: impl \u00e2\u20ac\u201d covered by the shared primitive; int \u00e2\u20ac\u201d start a real broke",
      "doc": "7.48 At most one input-capable controller lease per PTY session \u00e2\u20ac\u201d takeover revokes atomically and loudly, input is fenced to the active lease, node identity is never a lease `[REQ-HAZARD-CONTROLLER-LEASE]`: ### 7.49 A teardown verb never stamps a terminal or resting state it has not caused \u00e2\u20ac\u201d and two individually-correct verbs must not compose into a lifecycle dead end `[REQ-HAZARD-TEARDOWN-DEADEND]` <!-- --> <!-- --> - **Failure (paid-for, two hertz field RCAs 2026-07-19, both doyle code-verified the same day; the second hit doyle's OWN live production endpoint):** `endpoint shutdown` reported"
    },
    "req_b": {
      "title": "TEARDOWN-AUTHORITY W1 (ADR-0045 decision 4): the teardown kill NEVER depends on harness cooperation \u00e2\u20ac\u201d no graceful-input path, no waiting on PTY EOF, no ask-it-to-exit-first step. A WEDGED host is the design case, not the edge case: it is the situation that produced the ADR (doyle's own endpoint \u00e2\u20ac\u201d read-only rc replayed the retained PTY and `rc --take` acquired control, but a direct prompt produced no output for 30+s because the retained harness subtree itself was nonresponsive while the broker correctly preserved and replayed its last PTY state). Any cooperation-dependent step re-imports the exact hang the verb exists to break. Gate: impl \u00e2\u20ac\u201d the kill path proves no dependency on session responsiveness; int \u00e2\u20ac\u201d a deliberately nonresponsive/wedged hosted harness is still reaped within the bound (broker row gone + subtree gone), asserted against a real broker-hosted session.",
      "doc": "Decisions: <!-- --> **4. The kill never depends on harness cooperation** \u00e2\u20ac\u201d no graceful-input path, no waiting on PTY EOF, no \"ask it to exit first\". A wedged host is the design case, not the edge case: it is the situation that produced this ADR."
    }
  },
  {
    "pair_number": 126,
    "id_a": "REQ-HAZARD-CONTROL-STAMP-LIFETIME",
    "id_b": "REQ-HAZARD-VIEWER-RING-ROLL-SNAP",
    "req_a": {
      "title": "#2: a control/viewer stamp never outlives its session \u00e2\u20ac\u201d every teardown path clears what attach stamped. The broker exit-waiter (broker.rs ~:1844) sends the exit frame + sessions.remove(&id) but does NOT clear the perch's controller/viewer stamps; clear_controller()->stamp_driven_by() (clears driven_by+controlled) runs ONLY on controller-detach/evict/displace. /exit kills the CHILD not the controller conn, so the OutputLog drops with controlled:true, viewer_count, (and driven_by for a remote controller) latched in info.json forever \u00e2\u20ac\u201d and hfenduleam keeps gossiping controller_node=self cross-node. Fix: on session reap, clear the perch's controller/viewer stamps (set_driven_by(None)+set_controlled(false)+set_viewer_count(0) via the known endpoint id) \u00e2\u20ac\u201d broker stays the single writer. KNOWN-HAZARDS invariant. See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #2.",
      "doc": "7.26 Concurrent first-touch of ONE fresh BranchStore must ALL succeed \u00e2\u20ac\u201d a non-atomic `git init` race must never strand a first-toucher `[REQ-HAZARD-STORE-INIT-RACE]`: <!-- --> ### 7.27 A control/viewer stamp must NEVER outlive its session \u00e2\u20ac\u201d every teardown path clears what attach stamped `[REQ-HAZARD-CONTROL-STAMP-LIFETIME]` - **Failure (F-026 #2, live evidence HFENDULEAM):** an spt-hosted endpoint stayed `ONLINE+CONTROLLED` after the operator's RC `/exit` \u00e2\u20ac\u201d hours later hall-a's `info.json` still read `controlled:true` (status offline, dormant) and hfenduleam still GOSSIPED `controller_node=sel"
    },
    "req_b": {
      "title": "A read-only rc --view VIEWER whose serving brain falls behind the live ring under a hard flood and receives a FORWARD Output seq gap (the ring rolled frames out between reads, BEFORE any channel-overflow eviction \u00e2\u2020\u2019 NO KIND_VIEWER_EVICTED marker) must SNAP TO LIVE (accept-and-advance via dedup-below + snap-above), NOT fatal with output gap (brain.rs:624/628 legacy reject-gap). ROOT (v0.13.0 forkpty, post-b4+skip-to-live): serve_attach subscribes a viewer via brain.attach_as(Viewer) leaving session_cursors EMPTY \u00e2\u2020\u2019 the viewer serve-brain uses the LEGACY reject-gap \u00e2\u2020\u2019 a PRE-eviction ring-roll forward-gap FATALS read_event \u00e2\u2020\u2019 serve_attach returns \u00e2\u2020\u2019 forwarding stops \u00e2\u2020\u2019 attach_received_pty_output=FALSE (a_journaled / p0_paste / attach.rs:1071 wedged_viewer, Linux forkpty; Windows ConPTY floods slower \u00e2\u2020\u2019 MASKED false-green). DISTINCT from REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT (the POST-eviction re-subscribe-from-floor): this is PRE-eviction gap-tolerance while STILL subscribed. VIEWER-only \u00e2\u2020\u2019 B2-SAFE (a viewer never advances delivered_through / is not authoritative); the CONTROLLER keeps strict reject-gap (exactly-once resume). FIX: arm snap-above at initial viewer attach (attach_as_viewer_snap = at",
      "doc": "Shell sleep/wake (offline \u00e2\u2020\u201d online): <!-- --> **BUILT (M12 W2.5).** The controller/viewer model is implemented end-to-end. Attach intent is **three-valued** (`AttachIntent = Viewer | Control | Take`, wire-default `Control`): `Control` to a FREE endpoint becomes controller; `Control` to a CONTROLLED endpoint is **refused with guidance** (`--view` to watch, `--take` to control) \u00e2\u20ac\u201d never auto-viewer, never silent-displace; `Take` (`spt rc --take` / picker \"Kick\") kicks the incumbent with a **loud `Displaced{by}` notice** and full detach (not demote). The broker's per-session `OutputLog` is the fan"
    }
  },
  {
    "pair_number": 127,
    "id_a": "REQ-SUBNET-6",
    "id_b": "REQ-SUBNET-7",
    "req_a": {
      "title": "Trust lifecycle verbs, elevation-gated: spt subnet leave <NAME> (membership exit) and spt subnet prune <node> (removes a dead identity's trust + registry rows, killing its dead dials; trust mutation = security surface, REQ-PAIR-6 gate machinery) (M8 decisions 6-7)",
      "doc": ""
    },
    "req_b": {
      "title": "Per-machine re-pair trust overwrite: registry rows carry a hashed stable machine identifier (OS machine id /etc/machine-id|MachineGuid, domain-separated SHA-256 before gossip, spt-minted persisted UUID fallback; additive serde-default field \u00e2\u20ac\u201d old rows parse clean); a COMPLETED pairing ceremony presenting the same node label AND machine id as an existing trusted row evicts the superseded identity's trust + registry rows on the seed-holder and replicates the eviction; a gossiped claim alone never evicts trust (M8 decisions 13, 22)",
      "doc": ""
    }
  },
  {
    "pair_number": 128,
    "id_a": "REQ-ENDPOINT-ONLINE-TRUTH",
    "id_b": "REQ-HAZARD-DAEMON-HOSTED-LIVENESS",
    "req_a": {
      "title": "REGISTRY-LIFECYCLE W2 (ADR-0041 decisions 1+2, emphasys C1 P0): ONLINE is earned, not declared \u00e2\u20ac\u201d cmd_listen stamps status=online only from actual persisted state + hosting authority, never from manifest psyche_init capability alone (no more ready_agent/controllable=false hybrid rows born online-authoritative); livehost reconcile SPLITS control cleanup (clear controlled/driven_by/viewer_count for EVERY endpoint absent from session truth, regardless of state/controllable) from offline classification (live_agent+controllable gate unchanged); legacy hybrid rows self-heal after a SUCCESSFUL broker query only (broker failure is never interpreted as an empty session set); terminal signoff/owner-loss = atomic CAS-guarded offline + ready/address removal WITHOUT overloading soft api session-end (/clear preserves the live listener). Gate: impl \u00e2\u20ac\u201d creator gate + reconcile split + self-heal + terminal path; unit \u00e2\u20ac\u201d creator refuses capability-only online, cleanup clears stamps on state-quirk rows, broker-failure never mass-offlines; int \u00e2\u20ac\u201d dead-PID hybrid row does NOT survive a reconcile cycle (the immortal-row regression); doc \u00e2\u20ac\u201d ADR-0041.",
      "doc": "Decision: <!-- --> 1. **Online is earned, not declared.** A creator may stamp `status=online` only from actual persisted state + hosting authority \u00e2\u20ac\u201d never from manifest capability alone. Legacy hybrid rows self-heal at reconcile, but only after a SUCCESSFUL broker query: a broker failure is never interpreted as an empty session set (no mass-offline on a hiccup). 2. **Control cleanup splits from offline classification.** Reconcile clears `controlled`/`driven_by`/`viewer_count` for EVERY endpoint absent from session truth \u00e2\u20ac\u201d regardless of state or controllability \u00e2\u20ac\u201d while offline classification ke"
    },
    "req_b": {
      "title": "Daemon-hosted perches (Psyche, spt-hosted Self) derive liveness from the daemon endpoint table + info.json status, never is_process_alive(info.pid) (2.5)",
      "doc": ""
    }
  },
  {
    "pair_number": 129,
    "id_a": "REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT",
    "id_b": "REQ-HAZARD-RC-ATTACH-ONLINE-RACE",
    "req_a": {
      "title": "A clean `spt rc` attach to a LIVE spt-hosted (`endpoint run`) harness must DELIVER the harness's PTY output. KEYSTONE \u00e2\u20ac\u201d the operator's central 'attach shows no output' symptom, reproduced on the real dummy-harness fixture (v0.12.1 Wave 1) with NO death and NO wedge: bringup succeeds (online, harness pid alive + heartbeating, psyche hosted), the attach CONNECTS (PUMP_IPC_READER spawned, no RC_FAIL, holds the full window) \u00e2\u20ac\u201d but receives EXACTLY 0 bytes over 10s of the harness's flushed [session.self] stdout. DISTINCT from REQ-HAZARD-VIEWER-CLOSE-DETACH (death) and REQ-HAZARD-ATTACH-WEDGE (dead-child backpressure): here the harness is ALIVE and the attach is a clean first subscribe. This BLOCKS the 'view is independent' invariant \u00e2\u20ac\u201d re-attach is meaningless if a live endpoint-run harness shows nothing. KNOWN-GOOD (rules out 'no drain'): attach.rs `local_attach_via_loopback_conn_rides_the_same_pump` + `broker_spawns_the_pty_child_in_the_requested_cwd` prove the broker DOES drain+fan a `spawn_session` PTY child to a loopback attach over the SAME transport rc uses. Both spawn_session and endpoint-run's spawn_session_pid send KIND_SPAWN \u00e2\u2020\u2019 the same dispatch_spawn (broker.rs:706/835) which s",
      "doc": ""
    },
    "req_b": {
      "title": "`spt endpoint run` in an ATTACH/VIEW terminal action attaches BEFORE the freshly-spawned endpoint is online, so the attach races (or outright loses to) the harness bind. ROOT (doyle /diagnose, code-grounded): cmd_endpoint_run (cli.rs) does launch_harness_brokered_in -> (if start: return) -> run_attach with NO await-online between them. launch_harness_brokered_in returns once the harness PROCESS is spawned, but the broker-PTY bind (info status -> STATUS_ONLINE + the live session) lands ASYNC. Both picker attach paths route here with start=false (RunMode::Attach -> cmd_endpoint_run start=false,view=false): Start-now catches the endpoint mid-bringup -> run_attach attempts + loses the handshake race; Resume-from-history catches it still fully OFFLINE -> run_attach's status-gate (REQ-HAZARD-RC-ATTACH-FAILFAST) short-circuits 'offline - nothing to attach' and NEVER attempts. SAME root, two faces (the W4 attach-by-default surfaced both; an online endpoint is unaffected - the picker returns Outcome::Attach, not Run). FIX: in cmd_endpoint_run, when the terminal action is attach/view (NOT start), AWAIT the endpoint online between launch_harness_brokered_in success and run_attach - poll spt_s",
      "doc": ""
    }
  },
  {
    "pair_number": 130,
    "id_a": "REQ-HAZARD-CASCADE-WIPE-GUARD",
    "id_b": "REQ-HAZARD-SINGLE-PATH-SOURCE",
    "req_a": {
      "title": "No hard-delete of a parent hosting non-empty children (6.3)",
      "doc": ""
    },
    "req_b": {
      "title": "Single path/registry source of truth; no layout ambiguity (6.1)",
      "doc": ""
    }
  },
  {
    "pair_number": 131,
    "id_a": "REQ-GOSSIP-ADAPTER-PROJECTS",
    "id_b": "REQ-RESUME-ROW-PER-PROJECT",
    "req_a": {
      "title": "#4: remote endpoint details (harness + project history) are gossiped, not faked. Today from_resource_row (crates/spt/src/picker/model.rs:340) hardcodes project_history=Vec::new() for every remote row and passes adapter_profile=row.resources (the blurb masquerading as the harness), and Instance/ResourceRow (crates/spt-net/src/net/registry.rs:457) carry no adapter field and no project list. Fix: additive gossip fields N-1-safe exactly like endpoint_type \u00e2\u20ac\u201d Instance.adapter (composite <adapter>[:profile]) + Instance.recent_projects (bounded, newest-first, project IDs only) -> thread to ResourceRow -> from_resource_row stops faking. Pre-field remote rows render '-'. Project IDs only + REQ-PICKER-PROJECT-HISTORY-TRUTH's disambiguation. See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #4.",
      "doc": ""
    },
    "req_b": {
      "title": "A5 (F028, operator #6): resume-from-history labels EVERY session with the endpoint's newest project. data.rs:480-496 resume_rows_for clones project_history.first() onto every ResumeRow (line 481/488), so all sessions read as the head project (the ghost). The per-row e.cwd is already carried for launch-into-dir. FIX: derive per-row project_id_for_dir(e.cwd) (owlery-excluded -> fall back to trigger token), rendered through A1's display-name path. See triage A5.",
      "doc": ""
    }
  },
  {
    "pair_number": 132,
    "id_a": "REQ-ADAPTER-UPDATE-MESSAGE",
    "id_b": "REQ-ADAPTER-UPDATE-POST",
    "req_a": {
      "title": "An adapter manifest may declare `[update].message` \u00e2\u20ac\u201d a plain (multi-line) human notice surfaced to stdout, markdown-rendered (the v0.13.0 helpfmt prose path), ONLY when `spt adapter update` actually APPLIES an update (version changed), not on a no-op. Read from the newly-installed manifest; avenue-agnostic (gh_release/delegated/file_pull). No `{key}` substitution. Use: an adapter telling the operator a post-update action, e.g. spt-claude-code's \"run `/reload-plugins` in any ongoing sessions\". (v0.13.2)",
      "doc": "Runtime model: **adapter packaging & live update** (v0.13.2; ADR-0024, ADR-0025): <!-- --> A `.spt` may be **multi-platform**: shared `manifest.toml` + `strings/` at the root, role binaries under per-target-triple subdirectories (`x86_64-pc-windows-msvc/`, \u00e2\u20ac\u00a6); install/update extracts the shared root plus only the current node's triple, flattened into `install_dir`, so flat `<install_dir>/<program>` resolution is unchanged. It stays one signed asset (`adapter.spt`, plain-tar or gzip); a multi-platform archive missing the recipient's triple is a typed `NoArtifactForPlatform`. Large adapters may"
    },
    "req_b": {
      "title": "Composite adapter update \u00e2\u20ac\u201d an avenue-agnostic `[update.post]` sub-table `{ command, self_verifies }` run AFTER the primary avenue (gh_release/file_pull/delegated) resolves, in the same `spt adapter update` (ADR-0029). Runs UNCONDITIONALLY (even on an adapter version no-op \u00e2\u20ac\u201d the post-step's own idempotent check decides). PUBLISHED stdin JSON seam: one line `{adapter_applied, adapter_name, profile_name, version, previous_version, adapter_dir}` (additive keys; post-step ignores unknown). stdout decides the notice: custom text SUPERSEDES [update].message; a reserved sentinel fires the static [update].message; empty = no notice. exit code orthogonal (0 ok / nonzero failed). Precedence: dynamic-stdout > sentinel/manifest-message > nothing. NO [update.post] declared \u00e2\u2021\u2019 today's adapter_applied\u00e2\u2020\u2019[update].message unchanged; post-step FAILS \u00e2\u2021\u2019 loud warning + fall back to adapter_applied\u00e2\u2020\u2019message. FAILURE-ISOLATED: a committed gh_release pull is never rolled back if the post-step fails (independent channels). (v0.16.0)",
      "doc": "`[update.post]` \u00e2\u20ac\u201d the composite post-step (since v0.16.0): <!-- --> // file_pull: repo + path_regex: <!-- --> **Composite update \u00e2\u20ac\u201d `[update.post]` (since v0.16.0).** An optional **avenue-agnostic** sub-table that runs a delegated **post-step** *after* the primary update avenue resolves, in the same `spt adapter update`. It lets an adapter pull its `.spt` from `gh_release` **and** run a second, adapter-owned step (e.g. an in-harness plugin sync) under one lever."
    }
  }
]