[
  {
    "titleB": "`<basename>-<id>` launcher shortcut generation (picker `s` keybind, M12-W2-T2.4): from any pre-start options set the picker writes/updates a `<basename>-<id>` launcher at the project root baking the current selection's non-interactive `spt endpoint run` flags (terminal actions only: adapter[:profile] + id + (create|resume) + (start|attach|view); Kick/Instantiate/Change-adapter/Fork are interactive-only, not bakeable). BASENAME IS A PARAMETER (operator rev. 2026-06-14): harness-agnostic spt-core defaults to `spt` (→ `spt-<id>`); an adapter/flow OVERRIDES it (spt-claude-code → `cc`), so spt-core NEVER bakes `cc` (a harness name) into itself. The basename must be a DISTINCT token, never bare `spt` (a `spt.cmd` would shadow the real `spt.exe` only under cmd.exe cwd-first search, silently no-op in PowerShell/Unix, and self-recurse). The script is the CURRENT OS's native form — `.cmd` on Windows (NOT `.ps1`: default PATHEXT excludes `.ps1` so a bare/ext-less name never resolves one; `.cmd` is PATHEXT-resolvable), POSIX `sh` (+chmod +x) on Unix (a single portable form can't be both). The generated header documents the invocation reality (cmd.exe bare `<name>` in the project dir / PowerShe",
    "docA": "",
    "b": "REQ-RUN-SHORTCUT",
    "docB": "**`spt-<id>` shortcut** (picker `s` keybind, M12-W2): <!-- --> From any pre-start options set, `s` writes (or updates) a **`<basename>-<id>` launcher** at the project root that bakes the current selection's **non-interactive** flags (terminal actions only: adapter[:profile] + id + create|resume + start|attach|view; the interactive-only branches — Kick/Instantiate/Change-adapter/Fork — are not bakeable). The **basename is a parameter**: harness-agnostic spt-core defaults to **`spt`** (→ `spt-<id>`, e.g. `spt-doyle`); an adapter/flow **overrides** it (spt-claude-code → `cc`, giving `cc-<id>`) —",
    "a": "REQ-PLATFORM-MUSL",
    "titleA": "MUSL-TIER W2 (register the musl platform, self-IDENTIFY): add x86_64-unknown-linux-musl to the W1 registry — a current_platform() cfg arm (target_arch=x86_64, target_os=linux, target_env=musl -> the musl triple, NO more 'unknown' fallback), asset name spt-x86_64-linux-musl, triple-map entry. PROVEN (doyle /diagnose 2026-07-08, backlog #14): the current tree builds+runs static musl with ZERO source changes (rustls not openssl; aws-lc-sys+bundled-sqlite clean under musl-gcc; openpty; DNS/HTTPS works statically) — so W2 is registry DATA + cfg + asset map, NO dependency changes. Gate: a musl-built spt self-reports x86_64-unknown-linux-musl (not 'unknown'); registry-membership unit (the cfg arm is cross-target, unit the registry not the arm); build the musl target in-gate (kitsubito toolchain) and assert current_platform."
  },
  {
    "titleB": "Release asset set consumable by the self-updater: platform binaries, SHA256SUMS, SignedRelease metadata, manifest schema, mock-adapter zip; tag-triggered cross-repo pipeline",
    "docA": "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/`, …); 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",
    "b": "REQ-REL-2",
    "docB": "Release runbook: <!-- -->",
    "a": "REQ-ADAPTER-UPDATE-MESSAGE",
    "titleA": "An adapter manifest may declare `[update].message` — 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)"
  },
  {
    "titleB": "W3 (LIFECYCLE-TRUTH): daemon restart no longer massacres hosted endpoints — 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.",
    "docA": "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 ↔ older broker) | broker↔brain 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`) — an abruptly-",
    "b": "REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL",
    "docB": "",
    "a": "REQ-HAZARD-STOP-PATH-PSYCHE-ORPHAN-REAP",
    "titleA": "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`) — 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 — 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))"
  },
  {
    "titleB": "F-034 leg b (perri/hertz field finding 2026-07-09, hertz's HEADLINE): `api listen` must NOT consume the consume-once ephemeral seed on a PRE-BIND refusal — validate (adapter resolvable, home/subnet) and BIND first, THEN consume the seed. ROOT: today `api listen` burns the consume-once seed BEFORE it validates home/subnet, so on a multi-subnet node HOME_REFUSED (needs --subnet) fires AFTER the seed is already gone → the corrected retry (adding --subnet) on the SAME pid hits NO_SEED, a dead end (plausibly ADAPTER_UNRESOLVED burns it the same way). A refusal that never bound must leave the seed intact for the corrected retry. Same EFFECT-BEFORE-IRREVERSIBLE-CONSUME ordering class as F-032 (commune commit-before-delete) — the irreversible consume must follow the successful effect, never precede a refusal. Gate: a pre-bind refusal (HOME_REFUSED on a multi-subnet node without --subnet; ADAPTER_UNRESOLVED) leaves the seed CONSUMABLE — the corrected retry on the same pid binds (no NO_SEED); a SUCCESSFUL bind still consumes the seed exactly once (no double-bind). Files: the api-listen bind path (seed consume ordering). Kin F-032 [[spt-core-findings-backlog]].",
    "docA": "",
    "b": "REQ-LISTEN-SEED-CONSUME-AFTER-BIND",
    "docB": "`api listen <id> [--once] [--parent-pid <pid>] [--subnet <name>] [--session-id <sid>]`: <!-- --> **Recoverable refusals do not consume the seed.** The seed is consumed by a **successful bind** — or by a refusal that proves the seed itself dead (see spend-vs-restore below). A recoverable refusal that never bound — `HOME_REFUSED` on a multi-subnet node without `--subnet`, `ADAPTER_UNRESOLVED`, a live-perch conflict — leaves the seed consumable, so the corrected retry on the same pid binds instead of dead-ending on `NO_SEED`. (Effect before irreversible consume: the destructive step follows the s",
    "a": "REQ-LISTEN-PRESERVES-HOSTING-TOPOLOGY",
    "titleA": "`api listen` must not ASSERT hosting topology it does not know. (hertz v0.39.0 field RCA 2026-07-21, doyle re-grounded at source.) OBSERVED: the published adapter sequence `api bind` then identity-preserving `api listen --session-id` produces a self-contradictory live record — controlled=true AND controllable=false on a broker-hosted PTY endpoint — which controlled-precedence masks blue while attached and which a DETACH then unmasks as amber HARNESS ONLY. Detach is not the root; it only reveals the bad stamp. SOURCE: api/startup.rs passes controllable=Some(false) UNCONDITIONALLY on the relay/listen path (~191-195, reasoning 'the harness owns the process, so there is no broker PTY'), and establish_perch resolves controllable = controllable.or_else(|| prior…) (~379) — explicit wins, so that Some(false) OVERWRITES the Some(true) an earlier `api bind` EARNED. The carry-forward discipline that protects cwd/adapter/rest_state does not protect this field precisely BECAUSE the listen path is not silent about it. The defect is an ASSUMPTION about hosting authority made by a path that does not know the answer. FIX (preferred): represent LISTENER CUSTODY separately from PTY HOSTING AUTHORITY,"
  },
  {
    "titleB": "C2 (F028, infra; ROOT-CAUSED + severity-upgraded doyle RCA 2026-07-03): cross-endpoint perch contamination — 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 — C2 is UPSTREAM of B3 (presence/CONTROLLED read the very stamps this corrupts). See triage C2.",
    "docA": "**`spt endpoint run` is the spt-hosted bringup for BOTH endpoint types** (v0.12.0): <!-- --> The bringup core is **type-agnostic** — the endpoint TYPE is the adapter manifest's concern, not a separate bringup mode. A manifest declaring `[session.psyche_init]` brings up a **LiveAgent** (the daemon reconcile hosts its Psyche); a manifest *without* it brings up a **ReadyAgent** (a poll listener, no Psyche — see *ReadyAgent* and the harness-hosted ready bind at the *seed + bind-time resolution* note above). No `--adapter`/picker branch distinguishes them: the daemon live-host reconcile hosts only",
    "b": "REQ-SESSIONS-LOG-ENDPOINT-ATTRIBUTION",
    "docB": "",
    "a": "REQ-READY-AGENT-RESUME",
    "titleA": "An offline ReadyAgent shows in `spt endpoint run`'s picker Resume-from-history and resumes correctly — closing the gap that today only LiveAgents do. ROOT: a harness-hosted ready bind (ReadyAgent::start_homed, ready.rs) writes info.json DIRECTLY and never appends the session ledger (unlike the shared establish_perch:250 live path), so a ready agent — though it has a session_id — produces ZERO ledger rows → the picker's offline+local Resume-from-history (which gates on ledger rows) never offers it. FIX (1): ledger the ready bind (ReadyAgent::start_homed → sessions::append Boot, mirroring establish_perch). FIX (2): `spt endpoint run --resume <session>` honors the adapter MANIFEST's endpoint TYPE — a ReadyAgent manifest (no [session.psyche_init]) resumes as a ready endpoint (poll listener, NO psyche-host); a LiveAgent (with psyche_init) as live. NO new bringup mode + NO picker changes (operator 2026-06-18): `spt endpoint run` is the spt-hosted ENDPOINT bringup for BOTH types, the type IS the adapter-manifest's concern (psyche-host already keys on psyche_init presence) — so (2) likely already holds; VERIFY at code, build only the residual. (v0.12.0)"
  },
  {
    "titleB": "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` — 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`.",
    "docA": "",
    "b": "REQ-WHOAMI-1",
    "docB": "**whoami** (alias for endpoint list): <!-- --> `spt whoami` is a thin **alias for `spt endpoint list`** — 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",
    "a": "REQ-EP-4",
    "titleA": "PresenceChannel broker endpoint (seam day-one)"
  },
  {
    "titleB": "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.",
    "docA": "Activity-gated message delivery + send-modifier axes: <!-- -->",
    "b": "REQ-WAN-SEND-DELIVERY",
    "docB": "",
    "a": "REQ-MSG-DELIVERY-AXES",
    "titleA": "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 — its comment calls activity-gating 'a deferred follow wave'): an inbound message has an ACTIVE window (endpoint active → spool for the receiver's hook-poll, non-disruptive) and an IDLE window (idle/idle-transition → deliver immediately: translation binary spt-hosted → relay-poll either topology → spool, in fallback order). AXES (each composes; each defaults to its unrestricted value): (1) DELIVERY WINDOW — default (both, first-to-fire) | `--idle-only` (idle window; immediate if already idle) | `--active-only` (active window only, never wakes; the RENAMED `--deferred` — `deferred=1` spool column + `api poll --include-deferred` keep their names). (2) CHANNEL RESTRICTION — unrestricted | `--prefer-native` (translation binary if running else fall back) | `--force-native` (binary ONLY, no fallback/no spool-to-other-m"
  },
  {
    "titleB": "`spt update apply` classifies an already-staged / already-applied state as a friendly exit-0 no-op instead of dying at the binary-aside rename with 'Access is denied (os error 5)'. ROOT (F-025): a second apply on an already-applied staged version reaches the two-phase binary-aside rename and fails os-error-5, reading as a hard failure when the machine is simply up to date. FIX: apply gains the same pure classifier `fetch` got in v0.18.0 (REQ-UPDATE-FETCH-CURRENT-UX) — already-applied → clear message + exit 0, and the flow MUST short-circuit BEFORE the binary-aside rename in that state; mirror the classifier at ALL apply reject/entry sites the way the fetch fix covered its three. Genuine errors (bad signature, wrong platform, true downgrade, network) still propagate nonzero. (F-025)",
    "docA": "7.44 Streams and seats on a long-lived connection must have bounded lifetime — 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 — 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 — lifecycle state written by multip",
    "b": "REQ-UPDATE-APPLY-ALREADY-APPLIED",
    "docB": "",
    "a": "REQ-HAZARD-ENDPOINT-LIFECYCLE",
    "titleA": "REGISTRY-LIFECYCLE W2 (KNOWN-HAZARDS 7.45 — 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 — 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 — the matrix; doc — KNOWN-HAZARDS 7.45."
  },
  {
    "titleB": "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 — format + store read path; unit — version/schema-mismatch/truncation degradation legs + join semantics; doc — CONTEXT.md project-index entry + STORAGE.md section. Kin REQ-PROJECT-INDEX-WRITER (the producer), ADR-0037.",
    "docA": "",
    "b": "REQ-PROJECT-INDEX-STORE",
    "docB": "Self-update: **project index** — a node's endpoint→project 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 — list, picker, endpoint-info, hooks — join index × perch roster and **never run git**; stale renders last-known or `-",
    "a": "REQ-DAEMON-1",
    "titleA": "One per-machine spt-daemon owning all per-machine state"
  },
  {
    "titleB": "Marketplace-repackaging-friendly install",
    "docA": "7.52 An operator stop outranks every implicit ensure — no convenience path resurrects what the operator just killed `[REQ-HAZARD-STOP-RESPAWN-CONVOY]`: <!-- --> - **Failure (paid-for, hertz v0.39.4 field RCA 2026-07-22, doyle code-verified same day):** `spt daemon stop --force` is non-terminal on a box hosting live adapter sessions: every `spt api` invocation runs an unconditional `ensure_daemon()` (REQ-DAEMON-3's anchor), hook-driven api calls arrive continuously, so the stop is a race the operator loses — 5–10 ephemeral spawner windows flash and the daemon is back; several force-stops to sta",
    "b": "REQ-INSTALL-2",
    "docB": "Installation: <!-- the two-paths model + the one-line script half (v0.1 phasing below; OS-service leg = docs/DEFERRED.md) --> <!-- the marketplace-repackaging stance: relocatable binary + minimal, non-OS-entangled install logic --> spt-core is per-machine and harness-independent, so it installs *before* and *independent of* any adapter.",
    "a": "REQ-HAZARD-STOP-RESPAWN-CONVOY",
    "titleA": "KNOWN-HAZARDS 7.52: an operator stop outranks every implicit ensure — no convenience path resurrects what the operator just killed. The hazard-conformance twin of REQ-ENSURE-DAEMON-STOP-INHIBIT: its int-stage convoy rig IS this hazard's required test (stop under api-call storm -> down + zero respawns + honest refusal; start clears; one-daemon race). Registered separately per CLAUDE.md rule 4 — evidence may tag the same rig."
  },
  {
    "titleB": "MSG-IDENTITY W1 / F-036 leg a (perri field RCA 2026-07-09/10, psyche seat-theft — doyle ACCEPTED primary fix): the daemon MUST sanitize inherited per-session identity env (SPT_ENDPOINT_ID / OWL_SESSION_ID / SPT_AGENT_ID) at startup AND before EVERY role spawn — these are per-session identity and are NEVER correct inherited state for a daemon or its role children. ROOT: a daemon restarted from inside an agent session (routine during core dev / `spt update apply`) carries the session's SPT_ENDPOINT_ID and passes it verbatim to every [session.psyche_resume] spawn; core only strips each role's DECLARED env_remove list (runtime.rs:728), so ONE adapter env_remove miss infects the whole node — every psyche claude turn fires SessionStart, the adapter hook sees the endpoint id, takes the bind path, and ROTATES the victim's perch to the psyche's own sid with a valid prior-sid proof, every pulse (field: lia/deployah/doyle psyches ALL briefed as <sptc-active-perch id=doyle>; 37 peer msgs drained into lia's psyche transcript; victim deliveries eaten, communes dark, sends downgraded from:cli@node). Adapter half FIXED v0.18.8 (env_remove += SPT_ENDPOINT_ID + shim scrub + SPT_PSYCHE_TURN hook-bail",
    "docA": "Decision: <!-- --> 1. **Online is earned, not declared.** A creator may stamp `status=online` only from actual persisted state + hosting authority — 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 — regardless of state or controllability — while offline classification ke",
    "b": "REQ-HAZARD-DAEMON-IDENTITY-ENV-SANITIZE",
    "docB": "7.38 EVERY write on a physical broker connection is bounded + cancelable + poison-on-failure — no writer holds the send gate across an UNBOUNDED OS write `[REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]`: ### 7.39 Per-session identity env (`SPT_ENDPOINT_ID`/`OWL_SESSION_ID`/`SPT_AGENT_ID`) is NEVER inherited — the daemon scrubs it at startup AND on every role spawn, regardless of any role's declared `env_remove` `[REQ-HAZARD-DAEMON-IDENTITY-ENV-SANITIZE]` <!-- --> - **Failure (paid-for, perri field RCA 2026-07-09/10 — F-036 psyche seat-theft):** a daemon restarted from inside an agent ses",
    "a": "REQ-ENDPOINT-ONLINE-TRUTH",
    "titleA": "REGISTRY-LIFECYCLE W2 (ADR-0041 decisions 1+2, emphasys C1 P0): ONLINE is earned, not declared — 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 — creator gate + reconcile split + self-heal + terminal path; unit — creator refuses capability-only online, cleanup clears stamps on state-quirk rows, broker-failure never mass-offlines; int — dead-PID hybrid row does NOT survive a reconcile cycle (the immortal-row regression); doc — ADR-0041."
  },
  {
    "titleB": "REGISTRY-LIFECYCLE W2 (ADR-0041 decision 3, rest-normalize P0): definitive hosted-session loss normalizes TERMINALLY and ATOMICALLY — 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 — 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 — atomic terminal-normalize mutation + call sites; unit — store-level atomic pair never mixed + endpoint_stop covers already-offline/raw-Active input; int — 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 — ADR-0041.",
    "docA": "",
    "b": "REQ-REST-TERMINAL-NORMALIZE",
    "docB": "Decision: <!-- --> 1. **Online is earned, not declared.** A creator may stamp `status=online` only from actual persisted state + hosting authority — 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 — regardless of state or controllability — while offline classification ke",
    "a": "REQ-PRESENCE-CONTROL-REAP-ON-EXIT",
    "titleA": "B3 (F028, hall-b diagnosis + deferred #11 seed; BROADENED perri F-b CONFIRMED): dead-pid ONLINE decay window + sticky CONTROLLED stamp. Repro: /exit -> all endpoint processes dead -> `endpoint list` stays ■ ONLINE for a decay window before OFFLINE. Sticky CONTROLLED: perri confirmed controlled=true + attached_node SET while alive=false/OFFLINE, persisting >20min AND ACROSS A DAEMON RESTART (hall-b) — worse than the SIGKILL>=5min original (CAVEAT still: may reflect claude's --remote-control channel not the PTY attach — DISAMBIGUATE first). This is the deferred #11; RCA belongs to this wave. FIX: reap must clear presence AND control stamps promptly across FOUR paths — (i) clean exit, (ii) serve conn-drop, (iii) session-died-without-exit (crash/bounce), (iv) a BOOT-TIME sweep so a restarted daemon does NOT resurrect control stamps for endpoints it can see are dead. Int tests per edge. Closes A2(b). See triage B3 (broadened)."
  },
  {
    "titleB": "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.",
    "docA": "Always-on endpoints: resident supervised adapter binaries, `#`-sigil addressed: <!-- -->",
    "b": "REQ-WAN-SEND-DELIVERY",
    "docB": "",
    "a": "REQ-EP-9",
    "titleA": "`#` always-on address sigil: a reserved LEADING sigil marking an AlwaysOnEndpoint, extending the REQ-INST-10 grammar to `[subnet:]#id[@node]`. Mandatory + bijective — `#name` ⟺ always-on endpoint, bare `name` ⟺ agent endpoint — so the router resolves endpoint class from the address alone, before any registry lookup. Sits ABOVE REQ-HAZARD-ID-CHARSET: the address parser strips the single leading `#` before id validation, so the bare/stored id stays charset-clean and a mid-id `#` remains rejected (the charset contract is unchanged)."
  },
  {
    "titleB": "File-backed adapter [strings] (M12-W3-T3.1): a [strings] dot-path value MAY be an inline-table FILE POINTER `key = { file = \"rel/path\" }` resolved to the file's contents at get-string time, keeping large bodies (skill-instructions, hint text) out of the manifest. A value-position table with a `file` key IS the pointer form (reserved — cannot double as data). Per-adapter aux storage `adapters/<adapter>/strings/`; pointers resolve relative to it with CONTAINMENT (reject `..`/absolute escaping the dir). UPDATE-SAFETY: a LOCAL profile's file-pointers resolve relative to the user-owned local-profile dir (NOT adapter-shipped strings/, which adapter updates overwrite), or the local profile inlines. Validate-at-register (fail-fast on a bad/escaping/missing pointer) + LAZY read at get-string (live file edits reflect, no re-register) + skip-diagnostics on missing-at-read (no hard-crash, mirrors [digest]). Rides the same leaf-replace profile overlay as the rest of [strings].",
    "docA": "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 ↔ older broker) | broker↔brain 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`) — an abruptly-",
    "b": "REQ-MANIFEST-5",
    "docB": "Runtime model: **adapter strings** (ratified 2026-06-11, Gateway grill): <!-- --> A `[strings]` manifest section — 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` — 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",
    "a": "REQ-HAZARD-SHELL-STALE-ONLINE",
    "titleA": "A shell instance's ONLINE-ness is DERIVED (recorded status AND its recorded `shell.pid` not provably dead), never the recorded `status` field alone — 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` — 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 — 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 — do NOT blanket-swap the predicate): (a) DERIVED — relink's already-online refusal (the gate that made recovery impossible), the `s"
  },
  {
    "titleB": "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`) — 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 — 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))",
    "docA": "",
    "b": "REQ-HAZARD-STOP-PATH-PSYCHE-ORPHAN-REAP",
    "docB": "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 ↔ older broker) | broker↔brain 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`) — an abruptly-",
    "a": "REQ-HAZARD-PAIR-TRANSCRIPT-BIND",
    "titleA": "Pairing transcript binds roles, both node pubkeys, subnet ID, seed epoch, TOTP time-step, and confirmation MACs — or unknown-key-share/reflection/wrong-subnet/replay pairing remain possible (ADR-0005 #12)"
  },
  {
    "titleB": "tmp-write + atomic-rename + retry on Windows EBUSY (5.2)",
    "docA": "",
    "b": "REQ-HAZARD-EBUSY-RENAME",
    "docB": "",
    "a": "REQ-HAZARD-CONPTY-DSR",
    "titleA": "ConPTY reader must auto-answer DSR (ESC[6n) or all child output stalls (5.5)"
  },
  {
    "titleB": "Public SDK surface is spt-proto, spt-runtime, spt-msg",
    "docA": "",
    "b": "REQ-ARCH-2",
    "docB": "",
    "a": "REQ-ARCH-1",
    "titleA": "Many small acyclically-layered crates"
  },
  {
    "titleB": "Stale registry entries degrade to fallback, never hard-fail (4.3)",
    "docA": "",
    "b": "REQ-HAZARD-REGISTRY-STALE-CLEAN",
    "docB": "",
    "a": "REQ-HAZARD-STALE-INDEX-LOCK",
    "titleA": "Sweep stale lockfiles on daemon boot (1.3)"
  },
  {
    "titleB": "A node's gossiped per-endpoint registry Status reflects real liveness, so a remote viewer never paints a DEAD endpoint as ONLINE. ROOT (confirmed + gated vs CONTEXT.md): registryhost.rs:397-405 advertises a NOT-alive perch as Status::Dormant (the `else` of is_perch_alive), re-stamped every gossip round (never ages to Offline). Design intent GATED vs resting.rs + CONTEXT.md: active/dormant is the MULTI-INSTANCE routing differentiator (active = the bare-id routing target; dormant = a WARM non-target sibling — 'driving ling@laptop makes ling@desktop dormant', resting.rs:97; transitions active→dormant on AttentionShift/Detach). suspended = COLD (session closed, resumable-on-wake) while its NODE is UP. offline = NODE DOWN — NEVER self-gossiped (RestState has no Offline; a live node only ever gossips active/dormant/suspended), remote-inferred via epoch-lease eviction. So labeling a NOT-running perch Dormant is the DEFECT (dormant requires warm/running). PRIMARY FIX (registryhost `else`, not-bound-alive): live-but-UNBOUND (has a live broker session; is_perch_alive is bound-gated) → Active/Dormant (still warm); else (cold, no live session, but its node is up because this very daemon is gos",
    "docA": "",
    "b": "REQ-PRESENCE-LIVENESS-TRUTH",
    "docB": "",
    "a": "REQ-DAEMON-5",
    "titleA": "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 — a panic is caught, logged loudly, and the pump restarts with capped backoff (≤5 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)"
  },
  {
    "titleB": "REGISTRY-LIFECYCLE W1 (ADR-0040 decision 5, hertz defect B leg 5 CROSS-FAMILY per the final attribution correction — my_stream_subs releases only at conn-loop exit, NO unsubscribe verb existed, ~546/589 broker threads were unnamed parked SubscriberSeat writers on completed Registry/sync/update seats): new KIND_NET_STREAM_UNSUBSCRIBE broker verb removes the SubscriberSeat, stops AND JOINS its writer thread, drops its cursor; every dispatch worker calls it on serve completion (ALL families, success or failure); the retire sweep calls it for retired rows. N-1: unknown-kind on an older broker = tolerated send_error path, caller best-effort. Rider: spawned daemon threads NAMED (SubscriberSeat writer sub-writer-s<id> minimum, conn accept handlers) — field censuses attribute, not infer. Gate: impl — verb + worker/sweep call sites + thread naming; unit — unsubscribe removes seat/joins writer/drops cursor + idempotent + unknown-stream tolerated; int — rides REQ-HAZARD-REGISTRY-STALL seam (physical seat/thread counts plateau CROSS-FAMILY after completion); doc — ADR-0040.",
    "docA": "5.14 Corrupt info.json read as ABSENT → fail-open readers gossip a wiped perch ONLINE `[REQ-HAZARD-CORRUPT-PERCH-COHERENCE]`: <!-- --> ### 5.15 Fixed atomic-write tmp name → concurrent writers collide (loser renames a consumed file) `[REQ-HAZARD-ATOMIC-TMP-COLLISION]` - **Failure:** `atomic_write_bytes` staged every write under a **fixed** sibling `{name}.tmp`. Two processes writing the SAME target concurrently — traced at bind ~700µs apart: the daemon's `mutate_info` RMW and `spt api bind`'s `establish_perch` — both create the same `info.json.tmp`; whichever renames first CONSUMES it, and the",
    "b": "REQ-SEAT-LIFETIME-BOUNDED",
    "docB": "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",
    "a": "REQ-HAZARD-ATOMIC-TMP-COLLISION",
    "titleA": "Concurrent atomic writers to the same target must not share a tmp name (5.15): a fixed tmp sibling makes one writer's rename consume the other's staged file (os-error-2 loser)"
  },
  {
    "titleB": "spt binary CLI surface: send/ring/ready(+--once)/list/stop/whoami, stable arg shapes + exit codes",
    "docA": "",
    "b": "REQ-MSG-2",
    "docB": "",
    "a": "REQ-ENDPOINT-STOP-RESOLVES",
    "titleA": "`spt endpoint stop <id>` REFUSES an id that nothing on the node knows, instead of stamping success on a no-op — an unconditional-success verb is a lying instrument (find: liam via flynn's discriminating repro, mechanism corrected by flynn 2026-07-26 superseding the original shell-half-action framing; shells aren't endpoints and the verb correctly never tried to resolve one — it then answered incorrectly). TODAY (cli.rs `stop_endpoint_core`, read at mint): ready-marker removal is `.is_ok()`-best-effort, `teardown_hosted_session` topology-gates on a `controllable` flag a nonexistent perch cannot have and falls through, `unregister_address` is `let _`, `terminal_normalize` silently skips a recordless perch — so EVERY string returns `Stopped{removed:false}` → `STOPPED:<id> (no ready marker; address unregistered)` exit 0, and the 'address unregistered' clause prints whether or not any address existed to unregister. FIX SHAPE: resolve FIRST — an id with ZERO evidence on this node (no ready marker, no perch record, no registered address, no broker session row) is REFUSED with a non-zero exit and a line naming that nothing by that id exists here; ANY evidence → proceed EXACTLY as today (st"
  },
  {
    "titleB": "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 → 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 → the viewer serve-brain uses the LEGACY reject-gap → a PRE-eviction ring-roll forward-gap FATALS read_event → serve_attach returns → forwarding stops → attach_received_pty_output=FALSE (a_journaled / p0_paste / attach.rs:1071 wedged_viewer, Linux forkpty; Windows ConPTY floods slower → 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 → 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",
    "docA": "Amendment 1 (2026-07-22, DAEMON-LIFECYCLE W2) — teardown authority is opener-declared class PLUS transport liveness, enforced at the three places decision 6 could not see: <!-- --> <!-- -->",
    "b": "REQ-HAZARD-VIEWER-RING-ROLL-SNAP",
    "docB": "Shell sleep/wake (offline ↔ 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) — 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",
    "a": "REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT",
    "titleA": "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→clear_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 — 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 — the broker owns driven_by/clear_controller): wire the EXISTING D4c NetPresence connection-disconnect event → clear_controller for any session whose controller identity == the dead origin (become_controller already stores Some(origin); presence events already exist — modest wiring, NOT a new probe). The liveness ORACLE "
  },
  {
    "titleB": "Endpoint visibility per-(endpoint,subnet): excluded semantics, OR-of-defaults + override, gates sync",
    "docA": "",
    "b": "REQ-INST-12",
    "docB": "",
    "a": "REQ-INST-10",
    "titleA": "Qualified addressing [subnet:]id[@node] + ambiguity forces qualification"
  },
  {
    "titleB": "No endpoint process terminates/suspends during self-update",
    "docA": "Self-update: **daemon refresh (`spt daemon refresh`)** — restart the **brain** in place, no binary swap, broker and every held PTY untouched: the routine-update handoff path minus the swap. The recovery verb for wedged brain-held state (broken endpoint bringup, a downed hosted agent) that previously required a full daemon bounce. <!-- -->",
    "b": "REQ-UPD-3",
    "docB": "",
    "a": "REQ-DAEMON-REFRESH",
    "titleA": "THE-FORKENING W4 (operator add 2026-07-14): `spt daemon refresh` — restart the daemon BRAIN without a binary swap and WITHOUT touching the broker: exactly the apply_staged brain-cycle path (brain stop -> respawn -> readiness trial -> promote, incl. the trial-drain drive REQ-UPDATE-TRIAL-DRAIN-DRIVE and viewer-only resume REQ-BRAIN-RESUME-NO-CONTROL-STEAL) minus the swap. Recovery verb for wedged brain-held state (field motivator 2026-07-14: endpoint bringup broken on a live daemon + deployah down — today's only remedy is a full daemon bounce that kills every PTY). Broker + PTYs survive by construction (handoff invariant). Failure = the existing trial rollback semantics (old brain resumes; refresh reports loud). Gate: unit — verb routes the brain-cycle without staging/swap preconditions; int — refresh on a live daemon with a hosted PTY: brain generation changes, PTY survives, endpoint stays attached; doc — daemon docs name refresh next to stop/start. Kin apply_staged (the path it reuses), REQ-UPDATE-TRIAL-DRAIN-DRIVE, REQ-BRAIN-RESUME-NO-CONTROL-STEAL."
  },
  {
    "titleB": "`spt` surfaces the RUNNING broker image version beside the on-disk version so an updated-looking node reveals broker-side dormancy. ROOT (F-025): `spt update apply` restarts the BRAIN only (ADR-0018 D3-3) — the BROKER process survives and keeps running its pre-apply compiled image, so every broker-side surface of a freshly-applied release (the F015B live-apply matcher, dispatch inject legs, etc.) is silently dormant until a full daemon bounce, with nothing in the CLI revealing the split. FIX: the running broker SELF-REPORTS its compiled image version over IPC (a new request KIND answered by the live broker process from its own compiled build constant) — HARD CONSTRAINT: the version comes FROM the running broker process, NEVER inferred from disk bytes, install manifest, or file timestamps, since the disk is exactly the half that is already ahead; a version-surface command (`spt version` and/or `spt daemon status`) prints the running-broker version beside the on-disk/product version and flags a mismatch. Keeps read-side truth independent of write-side claims (the field lesson from F015B). (F-025)",
    "docA": "",
    "b": "REQ-UPDATE-RUNNING-IMAGE-SURFACE",
    "docB": "",
    "a": "REQ-UPDATE-APPLY-RESTART-NOTICE",
    "titleA": "`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) — 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)"
  },
  {
    "titleB": "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 — 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) — 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 — the field split-brain). Gate: impl — token/verb + lease-bound validation on the RC input path; unit — stale-lease RC input rejected post-replacement, ",
    "docA": "",
    "b": "REQ-INPUT-CONTROLLER-FENCE",
    "docB": "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) — 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",
    "a": "REQ-DAEMON-STDERR-PERSIST",
    "titleA": "W3 (LIFECYCLE-TRUTH, observability): broker + brain stderr tee to a rotating size-capped file under SPT_HOME (e.g. 2x5MB), stamped per generation. ROOT: detached daemon nulls stdio -> the 2026-07-06/07 incident window left ZERO logs (both RCAs ran blind; rigs had to recreate everything). KNOWN-HAZARDS note: never inherit handles (REQ-HAZARD-DETACHED-DAEMON-STDIO) — open the file in-process, don't pipe."
  },
  {
    "titleB": "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 — 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 — 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 :",
    "docA": "",
    "b": "REQ-LIST-JSON-LIVENESS-PARITY",
    "docB": "",
    "a": "REQ-DAEMON-6",
    "titleA": "Service-aware `daemon start`/`stop`: when an OS service manager has a registered spt-daemon for this user, `spt daemon start` and `spt daemon stop` drive THAT service (so stop doesn't IPC-kill a unit that auto-restart-fights for the broker socket — the kitsubito 2026-06-08 loop). `start` graduates from a `run` alias to a first-class background verb (ensure-up, idempotent, non-blocking); stop routes managed→manager, manual→IPC. Linux=systemd user unit (`systemctl --user start|stop|is-active spt-daemon`, detected by unit-file presence); Windows=no controllable manager (the logon task is boot-only), so start=detached spawn / stop=IPC."
  },
  {
    "titleB": "REGISTRY-LIFECYCLE W2 (ADR-0040 decision 6 + ADR-0041, emphasys C2 P0): streams declare a lifetime class at open — RC attach/view streams are ConnectionBound (opener conn EOF means the target sees FIN, serve_attach runs detach_session, controller slot + CONTROLLED stamps clear — a dead viewer can never pin a controller across its own connection death; today the raw-closed viewport attach stream is restart-durable forever); inter-brain streams stay Durable (NEVER globally retire on Brain disconnect — brain-swap correctness depends on it). Late-close identity validated (stale opener A close cannot evict newer controller B — rides ADR-0038 Amendment fix-6 generation tokens + W1 seat teardown machinery; same neighborhood, built once per the standing C2 coordination ruling). Lifetime class = additive open field, absent = Durable (N-1 openers keep exact current semantics). Gate: impl — class at open + ConnectionBound EOF chain; unit — class routing + absent-defaults-Durable + late-close identity refusal; int — raw viewport close frees the controller full-chain incl. across broker restart, brain_swap/daemon_refresh/redispatch legs stay green; doc — ADR-0040/0041.",
    "docA": "7.43 One wedged stream subscriber must NEVER stall stream serving — 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 — 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 — live prod box):** FOUR compounding consequences of \"phys",
    "b": "REQ-STREAM-LEASE-CLASSES",
    "docB": "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",
    "a": "REQ-HAZARD-REGISTRY-STALL",
    "titleA": "REGISTRY-LIFECYCLE W1 (KNOWN-HAZARDS 7.44, hertz post-close v0.36 RCA — 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 — the seam above; doc — KNOWN-HAZARDS 7.44."
  },
  {
    "titleB": "spt notify (agent-issued subnet notif) + notif_command manifest seam (harness + shell adapters)",
    "docA": "",
    "b": "REQ-NOTIF-2",
    "docB": "Subnet notifications: <!-- --> **`spt subnet notify` (agent-issued subnet notif; bare `spt notify` moved under the subnet noun at M8-D1)**: A command letting any agent **issue a subnet-wide notif** to the user. v1: it **reaches the user on their active endpoint from any agent** (the default broadcast-to-user). *Forward:* gains targeting — **all subnet users (default) or specific subnet users** — once the per-(subnet, user) model lands; the `from` field is what makes targeted/attributed delivery possible. // {node} fills as one argv token — the node's advertised label (its hostname).: <!-- -->",
    "a": "REQ-NOTIF-1",
    "titleA": "Notification primitive: per-subnet replicated spool, seen/dismissed, resurface-at-boundary, subsumes update+consent prompts"
  },
  {
    "titleB": "A `rc --view` VIEWER that overflows its broker subscription queue and is EVICTED (OutputLog::append try_send Full → 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→b64decode→re-encode AttachRecord→net_stream_send) SLOWER than the drain fans out under flood → its VIEWER_CHANNEL_DEPTH(256) channel overflows → the drain evicts (viewers.remove drops the ViewerSink → drops tx → viewer_writer's rx.recv() Err → the writer returns WRITING NOTHING) → serve_attach's brain.read_event() just STOPS getting Output (no EOF, no error) → serve_attach blocks forever → 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 → 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→viewer EVICTION SIGNAL (KIND_VIEWER_EVICTED, written in the viewer_writer thread OFF the log lo",
    "docA": "",
    "b": "REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT",
    "docB": "Shell sleep/wake (offline ↔ 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) — 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",
    "a": "REQ-MSG-INJECT-LEG-DROP-VISIBLE",
    "titleA": "SEED (inactive — 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` — 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` → `deliver::is_online` → `liveness::is_perch_alive`: a perch PINNED TO A DEAD SESSION (the KH 7.25 wedge class — dead owner, record not yet healed by the next auth touch) makes EVERY belt on that leg silently return None → 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' — 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 — 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"
  },
  {
    "titleB": "Guided join e2e: spt subnet join CLI initiator + always-on daemon pairing responder",
    "docA": "",
    "b": "REQ-SUBNET-2",
    "docB": "Product-surface amendment (2026-06-05 — M7 D3): <!-- -->",
    "a": "REQ-PAIR-4",
    "titleA": "Subnet naming on first pairing"
  },
  {
    "titleB": "Ready-agent lifecycle: register perch (info.json + listener + registry address) on ready, drain spooled backlog on startup, clean teardown",
    "docA": "",
    "b": "REQ-MSG-3",
    "docB": "",
    "a": "REQ-HAZARD-BRAIN-RESTART-PSYCHE-DUP",
    "titleA": "A bare brain restart leaves EXACTLY ONE `{id}-psyche` process per endpoint — 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 — 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 — 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"
  },
  {
    "titleB": "Producer-optional TTL for informational kinds with no dismissal seam (node-paired, agent-issued): expired row auto-dismissed instead of surfaced — 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",
    "docA": "4. Delivery: the notify kind rides `active_only`, unconditionally: <!-- -->",
    "b": "REQ-NOTIF-TTL",
    "docB": "5. TTL: producer-optional, expiry ignores seen-state: <!-- -->",
    "a": "REQ-NOTIF-QUIET-DELIVERY",
    "titleA": "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"
  },
  {
    "titleB": "spt-hosted startup: spawn-session then api bind (no file)",
    "docA": "",
    "b": "REQ-START-3",
    "docB": "",
    "a": "REQ-SEAM-PSYCHE",
    "titleA": "spawn-psyche seam (fresh + resume templates)"
  },
  {
    "titleB": "A `rc --view` VIEWER that overflows its broker subscription queue and is EVICTED (OutputLog::append try_send Full → 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→b64decode→re-encode AttachRecord→net_stream_send) SLOWER than the drain fans out under flood → its VIEWER_CHANNEL_DEPTH(256) channel overflows → the drain evicts (viewers.remove drops the ViewerSink → drops tx → viewer_writer's rx.recv() Err → the writer returns WRITING NOTHING) → serve_attach's brain.read_event() just STOPS getting Output (no EOF, no error) → serve_attach blocks forever → 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 → 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→viewer EVICTION SIGNAL (KIND_VIEWER_EVICTED, written in the viewer_writer thread OFF the log lo",
    "docA": "Consequences: <!-- --> ### v0.38.1 consequence note — `driven_by` own-node truth (ruling v3)",
    "b": "REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT",
    "docB": "Shell sleep/wake (offline ↔ 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) — 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",
    "a": "REQ-DRIVEN-BY-OWN-NODE-NORMALIZE",
    "titleA": "RC-RENDER-TRUTH v0.38.1 fast-follow leg 1 — 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 — never silent-displace; --take is the opt-in kick), and `driven_by` = the CONTROLLING NODE including the own node (CONTEXT:386 single-writer datum — 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 — 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 — 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 "
  },
  {
    "titleB": "Single path/registry source of truth; no layout ambiguity (6.1)",
    "docA": "7.44 Streams and seats on a long-lived connection must have bounded lifetime — 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 — 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 — lifecycle state written by multip",
    "b": "REQ-HAZARD-SINGLE-PATH-SOURCE",
    "docB": "",
    "a": "REQ-HAZARD-ENDPOINT-LIFECYCLE",
    "titleA": "REGISTRY-LIFECYCLE W2 (KNOWN-HAZARDS 7.45 — 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 — 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 — the matrix; doc — KNOWN-HAZARDS 7.45."
  },
  {
    "titleB": "SEED (inactive — 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` — 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` → `deliver::is_online` → `liveness::is_perch_alive`: a perch PINNED TO A DEAD SESSION (the KH 7.25 wedge class — dead owner, record not yet healed by the next auth touch) makes EVERY belt on that leg silently return None → 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' — 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 — 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",
    "docA": "7.23 A message that has REACHED a node's spool must NEVER depend on an adapter hook-poll cadence to reach an spt-hosted (relay-less) endpoint — the daemon drives delivery on the events it owns `[REQ-HAZARD-DELIVERY-STARVATION]`: ### 7.24 A delegated live adapter apply must NEVER report success without swapping, and the live-update seam must use ONE parent-aware adapter matcher `[REQ-HAZARD-ADAPTER-APPLY-SILENT-NOOP]` - **Failure (F015B / BUILD-F015B-APPLYMATCH):** a live adapter update to a PROFILE-COMPOSITE endpoint (`--adapter cc:ccs`) silently no-oped — `LIVE` then `DONE` printed, but the v",
    "b": "REQ-MSG-INJECT-LEG-DROP-VISIBLE",
    "docB": "",
    "a": "REQ-HAZARD-ADAPTER-APPLY-SILENT-NOOP",
    "titleA": "A DELEGATED live adapter apply MUST NEVER report success without performing the swap, and the live-update seam MUST use ONE parent-aware adapter matcher across all its comparators. TWO defects made the field repro (BUILD-F015B-APPLYMATCH: `--adapter cc:ccs` live update silently no-ops): (D1, matcher skew) the broker's dispatch_adapter_apply filtered sessions by EXACT `s.adapter == req.adapter`, but a `--adapter <adapter>:<profile>` endpoint stores the COMPOSITE `cc:ccs` while the apply carries the PARENT record name `cc` — so every :profile endpoint fell out to affected=[]; select_endpoints_running_adapter had the same `adp == adapter` skew, while the CLI live-gate (adapter_has_live_endpoint) already parent-matched — divergent rules on ONE seam. (D2, silent success) the affected.is_empty() branch replied KIND_APPLIED and RETURNED WITHOUT SWAPPING; once the CLI delegates the apply there is no CLI-side fallback swap, so success-without-swap = the update never lands (re-register re-reads the OLD manifest, version-of-truth honestly says old). FIX: (1) ONE shared spt_runtime::profile::adapter_parent_matches(session_adapter, parent) used by the live-gate + broker apply-filter + select_en"
  },
  {
    "titleB": "`spt subnet join` is two-phase (meet-before-code) so the entered code is FRESH at the ceremony regardless of discovery time (ADR-0030). The meet selector is the PUBLIC `(subnet-name, TOTP-epoch)` (rendezvous_token = SHA256(domain ‖ name ‖ totp_step)); the secret TOTP-code is the SPAKE2 password ONLY, never a discovery input — so the code is collected AFTER a member is found. Extend the brain.pair_join event stream (brain.rs:1009): CLI PairMeetReq{subnet} → daemon meets (name, current-epoch) resolving the seed-holder's REAL stable pairing address → MetMember event → CLI prompts the code (cli.rs cmd_subnet_join :6236) → PairCodeSubmit{code} → daemon dials the held real-address on SPT_PAIR_ALPN + SPAKE2 → PairJoined/PairFail. Daemon holds the real-address between phases, bounded by a 5-MINUTE wait-for-code timeout; a wrong-code retry re-runs the CEREMONY ONLY against the held address (no re-search). The non-interactive `--code` path stays one-shot (no prompt; relies on REQ-NET-FAMILY-GATE fast discovery, fails loudly per REQ-JOIN-DIAGNOSTICS on staleness). Security unchanged — the meet is pre-trust/unauthenticated (SPT_PAIR_MEET_ALPN); auth stays in SPAKE2. (next milestone)",
    "docA": "",
    "b": "REQ-JOIN-TWO-PHASE",
    "docB": "Robust WAN subnet join: meet-before-code + per-family bind gate: <!-- -->",
    "a": "REQ-HAZARD-PAIR-TRANSCRIPT-BIND",
    "titleA": "Pairing transcript binds roles, both node pubkeys, subnet ID, seed epoch, TOTP time-step, and confirmation MACs — or unknown-key-share/reflection/wrong-subnet/replay pairing remain possible (ADR-0005 #12)"
  },
  {
    "titleB": "Ephemeral perch cleanup on every ring exit path (3.1)",
    "docA": "",
    "b": "REQ-HAZARD-EPHEMERAL-CLEANUP",
    "docB": "",
    "a": "REQ-HAZARD-SOFT-CLEANUP",
    "titleA": "Soft-cleanup preserves state, removes only the ready marker (6.2)"
  },
  {
    "titleB": "Platform-safe `spt update fetch` + apply platform-guard (v0.3.1 cross-OS brick fix): `spt update fetch` stages the signed multi-platform `SignedUpdateSet` (`update-set.json` + every platform artifact it names), never a platform-blind single `SignedRelease`, so local apply selects `current_platform()` and P2P re-serve lets each peer select ITS own platform. Defense-in-depth: `apply_staged` REFUSES a staged single-release artifact unless it is platform-stamped for THIS node (an unstamped pre-v0.3.2 single, or a single stamped for another OS, fail-safe refuses — the guard that alone prevents the v0.3.1 brick where a Linux ELF was applied as `spt.exe`). UX: a friendly post-apply message (`Updated spt-core to vX.Y.Z.` + changelog URL) driven by an additive `product_version` metadata field, with a release-counter fallback when absent.",
    "docA": "Live, daemon-coordinated adapter update: <!-- --> // Amendment (W3 build, 2026-06-22): <!-- --> <!-- -->",
    "b": "REQ-UPD-8",
    "docB": "",
    "a": "REQ-ADAPTER-LIVE-UPDATE",
    "titleA": "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)"
  },
  {
    "titleB": "`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 — HOME_REFUSED established >=0.11.0; exposed by the node crossing 1->2 subnets). (ADR-0026)",
    "docA": "",
    "b": "REQ-RUN-MULTISUBNET-HOME",
    "docB": "Multi-subnet home resolution at `endpoint run` creation: <!-- -->",
    "a": "REQ-PICKER-NODE-GROUPING",
    "titleA": "Bug #13: the endpoint run Subnet tab shows a machine once PER shared subnet (subnet_rows data.rs:253 iterates per-subnet, groups by subnet:node, no cross-subnet dedup). Fix: dedup by (node, endpoint_id) across the subnet loop, collect the set of shared subnet names per endpoint, emit one group per MACHINE (group = node_display) with its shared subnets listed beneath the machine name; reconcile per-endpoint status across subnets (most-alive). Couples REQ-ENDPOINT-LIST-PALETTE (both edit subnet_rows). See docs/NEXT-MILESTONE-BUG-TRIAGE.md #13."
  },
  {
    "titleB": "TEARDOWN-AUTHORITY W1 HAZARD (ADR-0045 decision 8; hertz RCA 2 — 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 — 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 — covered by the shared primitive; int — start a real broke",
    "docA": "7.47 The physical terminal is never mutated or terminated outside its renderer's ordered state model — output before exit, owned baselines, unconditional teardown `[REQ-HAZARD-RENDER-LIFECYCLE]`: ### 7.48 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 `[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",
    "b": "REQ-HAZARD-TEARDOWN-DEADEND",
    "docB": "7.48 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 `[REQ-HAZARD-CONTROLLER-LEASE]`: ### 7.49 A teardown verb never stamps a terminal or resting state it has not caused — 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",
    "a": "REQ-HAZARD-CONTROLLER-LEASE",
    "titleA": "RC-RENDER-TRUTH W2 (KNOWN-HAZARDS 7.48 — 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 — the matrix; doc — KNOWN-HAZARDS 7.48."
  },
  {
    "titleB": "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` — 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)",
    "docA": "",
    "b": "REQ-INSTALL-10",
    "docB": "",
    "a": "REQ-DAEMON-SERVICE-INSTALL",
    "titleA": "F-038 RIDER (flynn nice-to-have, QUEUED not activated): a documented OS-service registration recipe or `spt daemon install-service` verb so the daemon itself survives box reboot (flynn's box runs managed_by:null = daemon-at-boot unprovisioned; kitsubito's hand-rolled systemd --user unit = prior art). NOT in MSG-IDENTITY scope — REQ-ENDPOINT-AUTOSTART covers the daemon-start-to-endpoint leg; the boot-to-daemon leg stays interim (logon scheduled task / systemd unit). Activate at an infra-provisioning milestone; shape (recipe doc vs verb) ruled then. Kin [[daemon-service-detection-gotcha]] (global-OS-state detection blind on dev box — a verb must not regress that), [[kitsubito-linux-rig]]."
  },
  {
    "titleB": "Adapter registration lifecycle: spt adapter add (--github, manifest-first, install-is-first-update) + soft-deregister remove + optional manifest uninstall template; node-local registered-adapter set self-update ripples over",
    "docA": "",
    "b": "REQ-INSTALL-4",
    "docB": "",
    "a": "REQ-EP-1",
    "titleA": "Day-one endpoint types; open type system"
  },
  {
    "titleB": "Adapter-declared digest extractor seam: a `[digest]` manifest section declaring an imperative extractor (native harness log -> the {role,text,tool,ts} contract; defaults to the [history] source files with an own-source escape hatch), `api digest-entry` push fallback, register-time validation of the section, adapter-declared presentation defaults (window depth, arg-truncation, sprint-collapse) that any consumer may override, and a `spt adapter digest-proof` author tool plus runtime skip-diagnostics (no silent drop). Reverses M9's no-manifest-seam stance; no declarative DSL.",
    "docA": "Startup flows (the two topologies): **Bind-time adapter/profile resolution (ADR-0021).** Because the seed is adapter-agnostic, `listen`/`poll` resolve the owning adapter/profile when they bind, as a pure read — never a seed-time snapshot that could drift. `--adapter <name[:profile]>` is an **optional override** on the `api` group (an explicit choice for adapter dev/iteration); omitted, resolution runs: 1. the seed's `parent_pid` → that process's **executable basename** (case-insensitive, `.exe`-stripped); 2. **candidate adapters** = registered `kind=\"harness\"` adapters whose **`host_binaries`*",
    "b": "REQ-TERM-5",
    "docB": "Session digest — the published digest-record contract (ADR-0019): <!-- The session digest is a PROJECTION of the endpoint's session logs, never a PTY-byte parse (the superseded source mechanism). ADR-0019 gives it its OWN manifest seam — the `[digest]` extractor above — distinct from `[history]` (which stays opaque + single-session, feeding the echo-commune verbatim). The M9 \"no manifest seam / rides `[history]`\" stance is REVERSED: one `[history]` normalizer cannot serve both the opaque echo consumer and the contract-typed digest. What is published here is the digest-record CONTRACT: the smal",
    "a": "REQ-MANIFEST-8",
    "titleA": "[adapter] host_binaries declares the harness executable basenames a kind=\"harness\" adapter hosts agents inside (e.g. host_binaries = [\"claude\"]); bind-time pid→exe-basename match (case-insensitive, .exe-stripped) over the seed's parent_pid selects the candidate adapter set; zero matches → a friendly error naming the binary + the --adapter escape hatch. Additive + N-1-safe: optional Vec<String>, #[serde(default, skip_serializing_if = \"Vec::is_empty\")] (omitted-serialized like shortcut_basename, old manifests parse clean); manifest.schema.json regenerated from the derive (ADR-0001, CI drift-gated). The match-key for ADR-0021 adapter-agnostic bind-time resolution. (v0.9.0)"
  },
  {
    "titleB": "B3: `spt daemon stop` then an immediate `spt daemon start` does NOT race — 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 → STOPPED then start → 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)",
    "docA": "",
    "b": "REQ-HAZARD-DAEMON-STOP-BARRIER",
    "docB": "",
    "a": "REQ-BRAIN-UPDATE-RESTART-CLEAN-CLOSE",
    "titleA": "SEED (DEFERRED, doyle 2026-07-09 — 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) → the broker reads them WEDGED (broker.rs:2695-2700) → 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 — but at a ~15s wedge-maturity hitch (frozen PTYs during the swap). A CLEAN close makes the conn 'simply absent → drained=false AT ONCE → fast promote' (broker.rs:2699-2700), ELIMINATING the hitch = truly seamless (honors the paradigm the field freeze broke). SUPERSEDES the earlier livehost-reattac"
  },
  {
    "titleB": "Direct-write precedence marker (with node id) guards stale overwrite (6.5)",
    "docA": "",
    "b": "REQ-HAZARD-DIRECT-WRITE-PRECEDENCE",
    "docB": "",
    "a": "REQ-HAZARD-DROP-FILE-SINGLE-WRITER",
    "titleA": "Drop files are daemon-owned single-writer (6.4)"
  },
  {
    "titleB": "post-spawn / api bind seam with boot nonce",
    "docA": "",
    "b": "REQ-SEAM-POSTSPAWN",
    "docB": "",
    "a": "REQ-SEAM-INJECT",
    "titleA": "inject-input methods configurable per activity-state"
  },
  {
    "titleB": "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.",
    "docA": "7.22 An idle delivery with no working translation binary must SPOOL, never raw-inject a pseudo-delivery reported as delivered `[REQ-HAZARD-IDLE-SILENT-NONDELIVERY]`: ### 7.23 A message that has REACHED a node's spool must NEVER depend on an adapter hook-poll cadence to reach an spt-hosted (relay-less) endpoint — the daemon drives delivery on the events it owns `[REQ-HAZARD-DELIVERY-STARVATION]` - **Failure (F-023, RCA @ `f023-f024-wan-idle-starvation`):** a message that has already landed in a node's spool — WAN-arrived, or locally spooled-while-active — stranded INDEFINITELY on an spt-hosted,",
    "b": "REQ-WAN-SEND-DELIVERY",
    "docB": "",
    "a": "REQ-HAZARD-DELIVERY-STARVATION",
    "titleA": "A message that has REACHED a node's spool (WAN-arrived or locally spooled-while-active) is NEVER dependent on an adapter HOOK-POLL cadence for its eventual delivery to an spt-hosted (relay-less) endpoint — the daemon itself drives delivery on the events it owns (WAN ingress + the ACTIVE→IDLE edge). Hazard class: delivery starvation. Without this, cross-node and post-active messages to an spt-hosted perch strand indefinitely whenever the adapter's hooks are quiet (idle session, no user turns), presenting as 'sent but never lands' with a healthy binary and an idle perch (F-023). Guarded by REQ-WAN-SPT-HOSTED-DELIVERY (WAN ingress leg) + REQ-MSG-IDLE-EDGE-DRAIN (idle-edge drain). (F-023)"
  },
  {
    "titleB": "Origin-source update bootstrap (`spt update fetch`): pull the latest signed release directly from the GitHub release origin (`SaberMage/spt-releases`) — the per-platform artifact + its `<asset>.release.json` SignedRelease metadata — and stage it through the EXISTING verify→stage pipeline (the same `plan_verified` gate: two-key signature + channel + monotonic rollback floor + SHA-256), after which the normal consent-notif / `spt update apply` flow is unchanged. Closes the peer-only-discovery gap (REQ-UPD-1): a first-in-fleet / isolated node can update with no peer to pull from. The signed-release anchor keeps the GitHub transport untrusted-but-verified.",
    "docA": "Installation: **adapter registration (`spt adapter add`)**: How a node comes to *know* an adapter — harness or shell. An explicit **`spt adapter add <path>`** (or **`--github <user/repo>`**) validates the manifest against the published JSON Schema and writes a registration record under `{SPT_HOME}/…/adapters/` — a **copy** of the files for `file_pull`-update adapters (spt-core owns what it later swaps) or a **pointer** for `delegated`-update adapters (the plugin owns + updates its own files). One command + one dir for both `kind=\"harness\"` and `kind=\"shell\"`; the `kind` field differentiates. T",
    "b": "REQ-UPD-7",
    "docB": "",
    "a": "REQ-INSTALL-9",
    "titleA": "Adapter add from a GitHub release archive: `spt adapter add --release <user/repo> [--tag <tag>] [--asset <name>]` fetches a `.spt` tar asset over HTTPS+GitHub trust, extracts it to the durable adapters/_github home, and registers the root — ships built binaries source-free and versioned (the distribution path for an adapter whose dev repo is a monorepo subdir, where --github root-only clone does not fit)"
  },
  {
    "titleB": "Broker/brain IPC + handoff argv version-tolerant (2.3)",
    "docA": "",
    "b": "REQ-HAZARD-HANDOFF-ARGV-COMPAT",
    "docB": "",
    "a": "REQ-ARCH-3",
    "titleA": "Wire-protocol version independent of crate semver, N-1 compat window"
  },
  {
    "titleB": "`[message-idle-translation-binary]` accepts a `command` (opaque; args + ADAPTER-STATIC {adapter_dir}/{adapter_name} substitution ONLY — ratified v0.16.0 W1, NOT session {key}: the translation binary is a persistent process serving all sessions on the endpoint (session/event ctx arrives per-message via the stdin Init/Event protocol, never the spawn argv) and the live-update respawn site has no session ctx (a {id}-bearing command would MissingKey→spool); program token resolved against install_dir like [digest].extractor/[session.psyche_init]) in addition to the bare `path`. `path` is DEPRECATED — keeps parsing (manifest forward/back-compat) but emits a registration warning steering to command. Exactly one of {path, command} (both-set refused at registration; neither = no translation binary). The spawn lifecycle + stdin/stdout JSON-lines protocol (Init/Event/Input → key/text/delay_ms/commit) are UNCHANGED — command alters only how the executable+args are located/launched (read_translation_path → read_translation_command). Unblocks folding `claude-spt translate` into the one consolidated binary (downstream ADR-0006). (v0.16.0)",
    "docA": "`[message-idle-translation-binary]` — spt-hosted idle delivery: <!-- --> // `[inject]` — inject-input methods: <!-- --> ### `[message-idle-translation-binary]` — spt-hosted idle delivery (ADR-0022) Opt-in. The adapter's **idle-delivery translation binary**: a pure stdin→stdout 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** — controller input is buffered during the emitted sequenc",
    "b": "REQ-TRANSLATE-COMMAND",
    "docB": "path = \"cc-spt-idle-translate\" # DEPRECATED bare-program form (still parses; warns at registration): <!-- --> - **`command` (preferred, since v0.16.0)** — an **opaque** command string (a program token plus args), exactly like the other command seams. Its program token resolves against the adapter **install dir** (REQ-INSTALL-11), like `[digest].extractor` / `[session.psyche_resume]`: a bare/relative program (e.g. `claude-spt`) resolves to `<install_dir>/<program>(.exe)` before PATH. Args support **adapter-static `{adapter_dir}` / `{adapter_name}` substitution only** — **not** session keys. The",
    "a": "REQ-MSG-IDLE-TRANSLATION-BINARY",
    "titleA": "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→stdout filter; spt-core owns EVERY PTY write. stdin (JSON-lines): `{type:\"init\",endpoint_id,node}` first · `{type:\"event\",envelope:\"<EVENT…>\"}` per inbound message (ADR-0020 envelope) · `{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:…}`/`{delay_ms:…}`/`{text:…}` (extensible). spt-core applies the emitted sequence to the broker PTY ATOMICALLY (the W1 coordination — 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"
  },
  {
    "titleB": "A2 (F028, operator #2): the picker confirm-panel 'controlled locally' line renders for OFFLINE endpoints. view.rs:425-436 builds control_line from ep.controlled with NO status gate; an offline endpoint with a stale controlled stamp shows 'controlled locally' (operator screenshot: hall-a offline + controlled locally). RENDER HALF (this REQ): control_line MUST be empty when status != Online. The upstream STICKY-stamp half (stamp survives client SIGKILL >=5min) is B3/REQ-PRESENCE-CONTROL-REAP-ON-EXIT. FIX: gate the render. See triage A2(a).",
    "docA": "",
    "b": "REQ-PICKER-CONTROL-LINE-STATUS-GATE",
    "docB": "",
    "a": "REQ-INST-12",
    "titleA": "Endpoint visibility per-(endpoint,subnet): excluded semantics, OR-of-defaults + override, gates sync"
  },
  {
    "titleB": "post-spawn / api bind seam with boot nonce",
    "docA": "Pieces the Instances model requires: <!-- --> **Unbound endpoint**: The lifecycle point between *spawn* and *bind*: an spt-hosted endpoint whose broker **session + PTY are live** but whose harness has **not yet bound** its perch (the *post-spawn seam* hasn't fired — e.g. the harness is waiting on a startup prompt). On-disk status `unbound` (spawn → `unbound`; bind → `online`; session death → `offline`). An Unbound endpoint is **attachable** (a live PTY — `spt rc` and the `endpoint run` attach reach it, so an operator can see and drive the harness, including clearing a bind-gating prompt) but *",
    "b": "REQ-SEAM-POSTSPAWN",
    "docB": "",
    "a": "REQ-ENDPOINT-UNBOUND-ATTACH",
    "titleA": "An spt-hosted endpoint is ATTACHABLE between spawn and bind: gate the attach on the broker SESSION being attachable (session+PTY+OutputLog exist at spawn, before bind), not on perch STATUS_ONLINE (bind). cmd_endpoint_run + `spt rc <id>` attach to a live broker session regardless of perch status (headless bringups too; lets an operator clear a bind-gating prompt) -- replaces await_endpoint_online; preserves REQ-HAZARD-RC-ATTACH-ONLINE-RACE's 'no attach before a session' intent at the earlier session-exists point; source = the broker sessions map (ADR-0025 W3a); local-only. New on-disk status STATUS_UNBOUND (spawn->unbound, bind->online, death->offline); lifecycle reuses the existing exit-waiter/reconcile (session death->offline); unbound is attachable but NOT message-addressable (messaging stays online/bound-gated). EpDisplay gains Unbound = HOLLOW (+ hollow-controlled variant) -- amber=HarnessOnly is taken + means not-controllable (the opposite of attachable). (ADR-0027)"
  },
  {
    "titleB": "A-2 (REMOTE-TRUTH triage §A-2 + ADR-0033): the daemon reconcile gains a WAKE-RESUME LEG — an endpoint whose rest INTENT is Active but whose harness session is COLD (status != online) is resumed by the daemon via the adapter's [session.resume] template using the LAST LEDGER session id, so a bare `spt wake <id>` on a suspended live agent actually brings it back (today: reconcile_once start-arm hosts ONLY status==online (livehost.rs:199), so a woken-but-unbound endpoint is skipped forever — neither status reaches online nor does reconcile re-host). This is the ADR-0033 LIFT: the thin `spt wake` edge writes rest intent, the DAEMON does the work. Mirrors shellwake::resolve_wake (read rest state, live-pid double-launch guard, launch, NEVER flip status — the harness self-binds → online). The leg reads the recorded adapter (D-2, REQ-SESSION-ADAPTER-RECORDED); an UNREGISTERED recorded adapter is the Q5 daemon-variant refuse: do NOT spawn, record a LOUD host_error report (F-1 naming the adapter + `spt adapter add`), never silent, never fallback-spawn on a different adapter. BINDS: (1) status=online is set ONLY by a real bind — the resume leg NEVER stamps it (CONTEXT liveness truth; the A-1 e",
    "docA": "",
    "b": "REQ-WAKE-RESUME-LEG",
    "docB": "",
    "a": "REQ-RUN-NO-DUP-SESSION",
    "titleA": "B1 (F028, hall-b diagnosis, verified 0.22.0): `endpoint run --id X --create` on an endpoint with a LIVE session mints a silent DUPLICATE session — and attach output can CROSS sessions (second create for diag-hallc minted a new session while the old ran; the new run's attach viewport rendered the OLD session's screen — claude resume-picker UI of pid 84512 while new claude 356020 had no -r). ROOT CLASS of the 0.21.0 attach-stall (zero events in FIRST_EVENT_GRACE rc.rs:1402 = attach bound to dead/wrong same-id slot); also the triplicate `launch --id ball-b` on ENLYZEAM. FIX: (i) run-on-live-session must REFUSE or REATTACH, never silently duplicate; (ii) RCA the attach/output routing that let frames cross same-id sessions (broker session-slot keying, dispatch_adapter vs serve_attach resolution). Int: two sessions one endpoint id -> each attach sees only its own frames. See triage B1."
  },
  {
    "titleB": "D2 (F028, perri F-d): `daemon status --json` truth drift. managed_by/managed_active read null in JSON while the HUMAN view says 'managed-by: manual — at-logon task registered' (the two surfaces disagree); and pump staleness (the STALLED diagnosis, B5) is NOT computable from JSON — only a raw pump_heartbeat_ms is emitted, no derived staleness/stalled field. FIX: JSON managed_by/active match the human render, and add a derived pump-staleness/stalled field so B5's condition is machine-observable. See triage D2.",
    "docA": "",
    "b": "REQ-DAEMON-STATUS-JSON-TRUTH",
    "docB": "",
    "a": "REQ-CLI-JSON",
    "titleA": "A global `--json` flag (clap global=true) honored by the READ/STATUS command set: endpoint list/whoami, daemon status, subnet status/show-code, endpoint description/role, adapter list/version, notif list, grant list, access list, shell list, how-to (endpoint digest already has it). Action commands do not honor it. A shared print_json() helper + a coverage TEST asserting every command in the set emits valid JSON (guards against the missing-shared-formatter drift). Output uses explicit per-command output DTOs with committed field names — internal structs are NOT serialized verbatim (their fields would become a public contract; JSON is a consumed wire-parity surface). (v0.16.0)"
  },
  {
    "titleB": "Keyword hints — [[hints]] {keywords (literal/regex), text}; spt api hint --session emits at most one matched hint per message, once per session (seen-set), declaration-order first match; profiles overlay [[hints]] by leaf-replace",
    "docA": "`[update.post]` — the composite post-step (since v0.16.0): <!-- --> // file_pull: repo + path_regex: <!-- --> **Composite update — `[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.",
    "b": "REQ-MANIFEST-4",
    "docB": "Runtime model: **keyword hints** (ratified 2026-06-12 — core milestone A): <!-- --> Once-per-session usage/syntax hints, a first-class adapter feature: the manifest's `[hints]` section declares entries of `{keywords (literal default, regex opt-in), text}`; the adapter's user-prompt hook pipes the **full user message** to `spt api hint --session <id>` (stdin) and receives matched hint lines (`keyword hint for SPT adapter <name>: \"<kw>\"-->{text}`) for its context-injection channel. The daemon keeps a per-session seen-set — each hint fires **once per session** (a `/clear` mints a new session, nat",
    "a": "REQ-ADAPTER-UPDATE-POST",
    "titleA": "Composite adapter update — 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 — 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 ⇒ today's adapter_applied→[update].message unchanged; post-step FAILS ⇒ loud warning + fall back to adapter_applied→message. FAILURE-ISOLATED: a committed gh_release pull is never rolled back if the post-step fails (independent channels). (v0.16.0)"
  },
  {
    "titleB": "HARDENING SEED (inactive — no defect claimed): a CLI-side `spt shell spawn` rises inside the launching terminal's Job Object when that job denies breakaway. Launch-path census (2026-07-25): `spt shell spawn` calls `shellhost::launch_shell` IN the CLI process, so the shell binary is spawned by a terminal-resident process and is subject to whatever job the terminal wrapped the CLI in; daemon-side (re)launches (`shellwake` relaunch-on-wake, linkhost relink) spawn from the daemon, which is job-neutral once its own cold-start ladder (WMI → schtasks → breakaway → in-job; REQ-HAZARD-VIEWER-CLOSE-DETACH) escaped. The shared spawn primitive (`daemon::detached_no_inherit`) requests CREATE_BREAKAWAY_FROM_JOB best-effort: a job without JOB_OBJECT_LIMIT_BREAKAWAY_OK denies it (ERROR_ACCESS_DENIED) and the DELIBERATE fallback spawns IN-JOB with the loud diagnostic 'DETACH_BREAKAWAY_DENIED: launching Job Object forbids breakaway; spawned IN-JOB (may be reaped if the launching terminal closes)' — chosen so a spawn that once worked never regresses to failure. spt CANNOT grant itself breakaway: BREAKAWAY_OK is a limit only the job's CREATOR (the terminal) sets on the job; a member process has no say",
    "docA": "Self-update: **docs bundle** — every release ships a platform-independent archive of the **built docs** (HTML + `llms.txt` + `llms-full.txt` + raw markdown + `manifest.schema.json`) as a **signed update-set asset**; apply lands it at `$SPT_HOME/docs`, so a node's docs always match its installed version. A docs-asset failure never fails the binary update (skip loud, retry next fetch). Consumed by the *docs server* (below). <!-- -->",
    "b": "REQ-SHELL-CLI-SPAWN-JOB-EXPOSURE",
    "docB": "",
    "a": "REQ-DOCS-RELEASE-ASSET",
    "titleA": "THE-FORKENING W2 (ADR-0036 §4): every release ships a platform-independent docs bundle `spt-docs.tar.gz` (BUILT mdbook output: HTML + llms.txt + llms-full.txt + raw .md + manifest.schema.json) as a release asset WITH an entry in the SIGNED update-set (sha256, same integrity chain as binaries — docs describe the security-relevant contract surface, they do not ride unverified). Apply lands/refreshes $SPT_HOME/docs (single current copy = docs always match the installed binary). FAILURE ISOLATION binding: a docs-asset failure NEVER fails the binary update — UPDATE_DOCS_SKIPPED loud, retried next fetch. Gate: unit — update-set entry + sha256 verify + skip-loud isolation; int — a fetch+apply lands version-matched docs at $SPT_HOME/docs; doc — self-update docs name the bundle. Kin REQ-DOCS-LOCAL-SERVER (the consumer), REQ-RELEASE-CHANNEL-PRIVATE (the assemble leg), ADR-0036."
  },
  {
    "titleB": "W5 (LIFECYCLE-TRUTH, RCA cost: proving WHO took delivered=1 rows burned an hour): the spool records the taker per row — leg enum (relay-backlog / hook-poll / idle-inject / psyche) + sid/pid + taken_at ms — surfaced by a --json debug read. Additive column, no schema break (delivered rows already retained).",
    "docA": "then it exits — no resident process, no detach.: <!-- --> **Psyche-download — `{psyche_context_file}` (file-always, replaces `{psyche_context}`).** The composed Psyche mind rides a **file**, never the command argv: before each turn spt-core writes the mind into the nested psyche perch dir and fills a single **`{psyche_context_file}` = that path** (argv-cap-immune — a real ~20 KB mind exceeds the win32 command-line cap and would brick the spawn). The soft **fresh-vs-continue** discriminator is the file's **content**, not key presence: a **fresh** (first / reseeded) turn writes the composed mind",
    "b": "REQ-SPOOL-TAKE-AUDIT",
    "docB": "",
    "a": "REQ-PSYCHE-CONTEXT-FILE-INDIRECTION",
    "titleA": "W4 (F030; doyle Q2 ruling + perri file-always freeze, 2026-07-04): the composed psyche mind ({psyche_context}) rides the SHIM argv today — a real ~20KB doyle psyche-download exceeds the win32 CreateProcess lpCommandLine ~32k cap → the shim spawn BRICKS. FIX (file-always, replaces {psyche_context} outright — no size-branch, no argv cliff, one path): core writes the mind to a file in the psyche's NESTED perch dir BEFORE each turn spawn and fills a single {psyche_context_file} = that PATH (argv-cap-immune). The soft fresh/continue discriminator moves from KEY-presence to FILE-CONTENT: FreshWithPreload writes the composed mind NON-EMPTY (the <fresh-psyche/> never-empty guarantee carries to the file content); ContinueExisting writes it TRULY 0-BYTE (perri BINDING PIN 1 — NO sentinel/placeholder EVER, else her non-empty=fresh discriminator misfires a spurious --session-id adopt). Core owns the file lifecycle: write-before-spawn each turn, overwrite in place, persists between turns in the nested perch (debuggability); never deleted per turn. perri shim delta: --psyche-context-file <path> arg, read-file prefix, TRIM-based emptiness (her tolerance, NOT core's license — core writes exactly 0"
  },
  {
    "titleB": "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` — 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)",
    "docA": "",
    "b": "REQ-INSTALL-10",
    "docB": "",
    "a": "REQ-HAZARD-SELF-ELEVATE",
    "titleA": "Self-elevation (REQ-ELEVATE-1) re-runs the EXACT original invocation with the binary's ABSOLUTE exe path — never widening privilege scope, never adding/altering args, never via a PATH-resolved bare name, never via a shell-interpolated command string (argv-array only, no `sh -c`); the elevated child drops state back to the user (composes with the 5.7 de-elevation) and NEVER re-elevates (loop-safe: decide_elevation_path returns AlreadyElevated whenever the process is already Elevated, on every OS). The user's UAC/polkit/sudo prompt is the only consent gate — we never bypass it; the print-hint floor prints the absolute-path command too. The unprivileged parent never depends on (pipes/captures) the privileged child's stdout."
  },
  {
    "titleB": "Single path/registry source of truth; no layout ambiguity (6.1)",
    "docA": "",
    "b": "REQ-HAZARD-SINGLE-PATH-SOURCE",
    "docB": "",
    "a": "REQ-HAZARD-CASCADE-WIPE-GUARD",
    "titleA": "No hard-delete of a parent hosting non-empty children (6.3)"
  },
  {
    "titleB": "B5: `spt daemon start` does NOT revive phantom Psyches for dead-but-online-latched perches. Today reconcile_once (livehost.rs:285) spawns a Psyche per status=online live_agent perch at boot WITHOUT verifying the harness child / {id}-psyche is actually alive — so a Cold start after an unclean stop revives N psyches for N dead-but-latched perches (3 psyches for 3 dead perches). FIX: gate the boot psyche-spawn on real child-liveness — a perch with NO live broker session (the B2 reconcile signal) is marked OFFLINE at boot instead of hosted, so a dead-harness perch is never revived. Shares the B2 reconcile loop (this is its boot-gate arm); composes with B2's honest latch. Also closes wall-a's psyche_host_error gap (residency-confirm does not run at boot tick-1, livehost.rs:395-441 / 257-263). (v0.12.0)",
    "docA": "",
    "b": "REQ-HAZARD-LIVEHOST-BOOT-LIVENESS-GATE",
    "docB": "",
    "a": "REQ-HAZARD-BRAIN-RESTART-PSYCHE-DUP",
    "titleA": "A bare brain restart leaves EXACTLY ONE `{id}-psyche` process per endpoint — 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 — 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 — 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"
  },
  {
    "titleB": "Qualified addressing [subnet:]id[@node] + ambiguity forces qualification",
    "docA": "Always-on endpoints: resident supervised adapter binaries, `#`-sigil addressed: <!-- -->",
    "b": "REQ-INST-10",
    "docB": "",
    "a": "REQ-EP-9",
    "titleA": "`#` always-on address sigil: a reserved LEADING sigil marking an AlwaysOnEndpoint, extending the REQ-INST-10 grammar to `[subnet:]#id[@node]`. Mandatory + bijective — `#name` ⟺ always-on endpoint, bare `name` ⟺ agent endpoint — so the router resolves endpoint class from the address alone, before any registry lookup. Sits ABOVE REQ-HAZARD-ID-CHARSET: the address parser strips the single leading `#` before id validation, so the bare/stored id stays charset-clean and a mid-id `#` remains rejected (the charset contract is unchanged)."
  },
  {
    "titleB": "THE-FORKENING W2 (ADR-0036 §4): every release ships a platform-independent docs bundle `spt-docs.tar.gz` (BUILT mdbook output: HTML + llms.txt + llms-full.txt + raw .md + manifest.schema.json) as a release asset WITH an entry in the SIGNED update-set (sha256, same integrity chain as binaries — docs describe the security-relevant contract surface, they do not ride unverified). Apply lands/refreshes $SPT_HOME/docs (single current copy = docs always match the installed binary). FAILURE ISOLATION binding: a docs-asset failure NEVER fails the binary update — UPDATE_DOCS_SKIPPED loud, retried next fetch. Gate: unit — update-set entry + sha256 verify + skip-loud isolation; int — a fetch+apply lands version-matched docs at $SPT_HOME/docs; doc — self-update docs name the bundle. Kin REQ-DOCS-LOCAL-SERVER (the consumer), REQ-RELEASE-CHANNEL-PRIVATE (the assemble leg), ADR-0036.",
    "docA": "Harness integration checklist: <!-- the harness-author checklist: every contract surface a harness touches, grouped by necessity, mapped to the interaction lifecycle, with the modern Claude Code adapter (spt-claude-code) as the worked example --> // Quickstart: build an adapter: <!-- the dev-agent killer quickstart: minimal adapter satisfying the manifest + api contract, walked via the shipped mock adapter --> The \"build a harness for spt-core\" hello-world: take the reference **mock adapter** apart, register it, drive the contract with real commands, then swap in your own harness. No spt-core",
    "b": "REQ-DOCS-RELEASE-ASSET",
    "docB": "Self-update: **docs bundle** — every release ships a platform-independent archive of the **built docs** (HTML + `llms.txt` + `llms-full.txt` + raw markdown + `manifest.schema.json`) as a **signed update-set asset**; apply lands it at `$SPT_HOME/docs`, so a node's docs always match its installed version. A docs-asset failure never fails the binary update (skip loud, retry next fetch). Consumed by the *docs server* (below). <!-- -->",
    "a": "REQ-DOCS-2",
    "titleA": "Sub-10-minute runnable killer quickstart per audience"
  },
  {
    "titleB": "REDISPATCH-TRUTH W1 (KNOWN-HAZARDS 7.41, hertz field RCA 2026-07-16 — 4/5 endpoints frozen per brain cycle): a fresh dispatcher must NEVER re-serve a terminal stream — a replayed historical Attach must not steal (same-identity silent become_controller, no Displaced) or clear (replayed-EOF detach_session) a LIVE controller. The legitimate same-by successor re-take after a brain restart still silently re-takes: the discriminator is stream LIFECYCLE, never origin identity. Gate: int — production-path regression D1: finished historical Attach + current active Attach, same endpoint/origin; restart target brain only (real run_dispatch_loop rediscovery, NO manual re-serve — the pre-fix e2e bypass is the lesson); prove the historical stream neither takes nor clears the current controller and current input/output stays exactly-once without detach; doc — KNOWN-HAZARDS 7.41. HEAVY nextest group at birth (FLAKE-LEDGER #15). Kin REQ-REDISPATCH-FINISHED-RETIRE (the mechanism), REQ-BRAIN-RESUME-NO-CONTROL-STEAL (the CLOSED session-cursor sibling — different leg), ADR-0038.",
    "docA": "Decisions: <!-- --> 1. **Distinct viewport/lease identity.** Each rc invocation/attach stream mints a unique controller lease id, carried through SubscribeReq, the controller slot, and Input/Resize. Node identity is kept separately for display/access policy. The ONLY silent successor/replay case is same-lease + equal-or-newer generation (the ADR-0038 fix-6 dispatcher-recovery contract, preserved exactly). Same node but different lease is a DISTINCT controller. 2. **Explicit Take always revokes a distinct incumbent loudly — and revocation is authoritative, notification is not.** If `intent == T",
    "b": "REQ-HAZARD-REDISPATCH-CONTROL-STEAL",
    "docB": "7.40 A commune/signoff drop is deleted ONLY after every applicable tier is durably committed — an un-committable slice preserves the drop, never delete-then-lose `[REQ-HAZARD-COMMUNE-INGEST-BLACKHOLE]`: ### 7.41 A fresh dispatcher must NEVER re-serve a terminal stream — historical replay must not steal or clear a live controller `[REQ-HAZARD-REDISPATCH-CONTROL-STEAL]` <!-- --> - **Failure (paid-for, hertz field RCA 2026-07-16 — 4/5 endpoints frozen, twice in one day):** every brain cycle (`spt daemon refresh`, `spt update` apply — same `applyhost.rs` path) launches a fresh `dispatch::run_dispa",
    "a": "REQ-CONTROLLER-LEASE-IDENTITY",
    "titleA": "RC-RENDER-TRUTH W2 (ADR-0044 decisions 1+2, hertz same-machine --take split-brain RCA P0-A/B, doyle seam-verified broker.rs resolve_subscribe 1296-1318 + 1334-1347): each rc invocation/attach stream mints a UNIQUE controller lease id carried through SubscribeReq, the controller slot, and Input/Resize; node identity stays separate as attribution/access policy only. The ONLY silent successor/replay case is same-lease + equal-or-newer generation (ADR-0038 fix-6 dispatcher-recovery contract preserved exactly); same node + different lease = DISTINCT controller. Explicit Take on a distinct incumbent lease ALWAYS revokes loudly AND authoritatively — atomically revoke/fence the old lease and FORCE its attach stream closed, then install the taker, regardless of whether by-node strings match; the Displaced notice is best-effort (today old.tx.try_send at broker.rs:1342-1345 can DROP the notice on a Full queue while become_controller still replaces — the revoke/close must land even when notice enqueue fails; the closed stream is itself the terminal signal rc's PumpEnd::Displaced/EOF path handles). Today same_identity keys on controller_by()==by alone, intent never consulted; two same-machine w"
  },
  {
    "titleB": "spt-store::BranchStore (git branch as versioned KV; commit=checkpoint/tip=resume, atomic multi-key, merge-native sync) is the substrate for coarse/durable/audited state (context, registry snapshot+distribution, daemon checkpoint); hot paths (B5 fsync journal) + indexed queries (SQLite spool) excluded (ADR-0011)",
    "docA": "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 — silently bricking rollback exactly when it is needed (a logic-bricking update t",
    "b": "REQ-STORE-1",
    "docB": "",
    "a": "REQ-HAZARD-ROLLBACK-STATE-COMPAT",
    "titleA": "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 — a 2026-06-09 audit confirmed zero state-migration code exists; unmintable retroactively once a migration ships."
  },
  {
    "titleB": "An `spt rc` session forwards the Backspace key as the VT DEL byte (0x7f), so the hosted TUI (Claude Code) deletes ONE character — never a whole word. SYMPTOM (operator dogfooding): Backspace in an rc session always behaves like ctrl+Backspace — deletes the entire last word. ROOT (doyle /diagnose, code-grounded, byte PENDING HITL confirm): rc is a RAW VERBATIM byte pump — 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↔0x7f 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 → backward-kill-word → 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 ",
    "docA": "",
    "b": "REQ-HAZARD-RC-INPUT-KEY-ENCODING",
    "docB": "",
    "a": "REQ-HAZARD-RC-EOF",
    "titleA": "A severed broker stream during a live rc session surfaces GRACEFULLY, never as a raw io error that crashes the PTY. The rc read-loop (rc.rs:352-362) continues only on WouldBlock/TimedOut; ANY other read_event_until error — including UnexpectedEof 'failed to fill whole buffer' — returns Err → RC_FAIL → the PTY 'crashes' from the user's view. Confirmed trigger: a deliberate `spt daemon stop` (broker bounce) severs an active rc (perri stopped the daemon to release owlery watch handles). Same severed-broker-stream EOF class as the v0.9.1 seed fix (seed_fail_message) and the listener-death case — spt-core must classify a broker-gone EOF and (a) surface a CLEAR actionable message ('daemon stopped/restarted — re-run / reconnect'), never the raw buffer error, and ideally (b) AUTO-REATTACH to the same session on the fresh broker (the broker is the daemon-lifetime anchor; it returns on the next `spt api` call). FOLD two side-observations: (1) `spt daemon stop` SILENTLY drops active rc/live sessions — warn ('N active session(s) will drop') or graceful-detach on stop; (2) the daemon holds owlery WATCH HANDLES on perch dirs so a torn-down perch dir stays 'Device busy' until a full daemon stop r"
  },
  {
    "titleB": "W-2 (WORKER-TRUTH triage, operator-ruled 2026-07-06): worker verbs go sid-symmetric with every sibling id-scoped verb — worker-start mints NO token and worker-stop takes NONE (token custody is undue adapter burden, ruling via perri). Registration STORES the sid it authenticated (the parent's sid at start; today cmd_worker_start hardcodes session_id=\"\" — worker.rs:44 — so a sid-authed stop compares against empty and refuses 100%). Stop accepts the parent's CURRENT sid OR the stored registration sid (a /clear between start and stop rotates the parent's sid; either rotation endpoint is honest custody — the REQ-PSYCHE-SID-CUSTODY rotation reasoning). Under the ruling the field adapter's existing emission (worker-stop <id> --session-id <parent sid>) becomes contract-correct as-is. Publish the frozen verb shape to the docs-site with the landing wave (perri blind-builds from published docs).",
    "docA": "",
    "b": "REQ-WORKER-SID-SYMMETRIC-AUTH",
    "docB": "`api worker-start <parent> [--agent-id <id>] [--agent-type <type>]`: <!-- --> ### `api worker-stop <id> --session-id <sid>` · `api worker-poll <id> --session-id <sid>`",
    "a": "REQ-CONSENT-1",
    "titleA": "Consent grant store: capability x subject-agent x target-node rows, enforced at the target node, subnet-settable (replicates as security material near the trust store), revocable; gated-capability ids (remote-exec, instantiate-anywhere) reserved-but-refusing; v1 consumers are the shell spawn gates (CONTEXT Consent & security gates)"
  },
  {
    "titleB": "C2 (F028, infra; ROOT-CAUSED + severity-upgraded doyle RCA 2026-07-03): cross-endpoint perch contamination — 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 — C2 is UPSTREAM of B3 (presence/CONTROLLED read the very stamps this corrupts). See triage C2.",
    "docA": "",
    "b": "REQ-SESSIONS-LOG-ENDPOINT-ATTRIBUTION",
    "docB": "",
    "a": "REQ-SEC-1",
    "titleA": "Per-endpoint access whitelist: origin-node gate, stateful-firewall (reply/outbound exempt), node-now/user-later, outer gate before grants"
  },
  {
    "titleB": "The Create-new adapter-CHOICE screen of `spt endpoint run`'s picker shows a right-hand Description panel (like the Pick-existing endpoint picker's two-pane) surfacing per-adapter detail: install date, last-updated, adapter TYPE / the endpoint types it hosts, and the adapter description — so the user can see WHAT each adapter is before choosing it (today the selector lists bare names). DEFERRED fast-follow to v0.12.0 (operator 2026-06-18). (post-v0.12.0)",
    "docA": "",
    "b": "REQ-PICKER-ADAPTER-DESCRIPTION",
    "docB": "",
    "a": "REQ-ENDPOINT-LIST-REST-FILTER",
    "titleA": "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 — 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 — 'Total: N (+M suspended hidden)' — 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)."
  },
  {
    "titleB": "An inbound `spt send` is DELIVERED to an spt-hosted endpoint (brought up via `spt endpoint run` → `api bind`, broker holds its PTY, NO `api listen` relay). Today cmd_bind→establish_perch (api/startup.rs ~441) writes info.json + ready marker + controllable=Some(true) but registers NO message-listener / NO address, so deliver.rs resolve_address→None→spool (deliver.rs:132-140) and the message NEVER reaches the live PTY — the endpoint reads 'online' (ready marker) yet `spt send` silently SPOOLS ('online but not deliverable' lie). Per CONTEXT:187-188 the daemon owns the PTY and delivers, manifest-configurable per activity-state (direct PTY injection / relay / HTTP). FIX: route an inbound send for an spt-hosted target through the daemon → broker InputReq → session.write_input PTY-inject (broker.rs dispatch_input/write_input ~988-1022), the same path the brain uses; the live-delivery handshake must report Sent (not Queued) and stop the spool-only fallback for a broker-hosted, PTY-resident endpoint. Detection is local: controllable==Some(true) + spt-hosted state + resolve_address==None. = the spt-core HALF of the wall-b finding (perri owns the adapter half: bind-hook fired-zero-perch + the",
    "docA": "",
    "b": "REQ-SEND-SPT-HOSTED",
    "docB": "",
    "a": "REQ-RELAY-NO-BUSY-DELIVER",
    "titleA": "MSG-IDENTITY W4 (operator self-send probe 2026-07-09, companion leg): the relay/idle-inject carrier MUST NOT fire for an ACTIVE endpoint — 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 — 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 — rule at RCA lock, don't build blind. Gate: unit — 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)."
  },
  {
    "titleB": "#6: the 'Resume from history' view keeps the endpoint's 'Confirm selection' top panel and swaps ONLY the bottom panel to 'Resume from a prior session' — the user stays contextually informed about what they're picking (today the resume view replaces the whole screen). crates/spt/src/picker/view.rs (resume screen) + model screen state. See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #6.",
    "docA": "",
    "b": "REQ-PICKER-RESUME-CONTEXT-PANEL",
    "docB": "",
    "a": "REQ-PICKER-REMOTE-WAKE",
    "titleA": "C-2 (REMOTE-TRUTH triage §C-2 #4 + addendum @3442ce5): a REMOTE suspended picker row offers `Wake now` — waking the endpoint THROUGH its owning node's rest edge (the A-2 daemon resume leg) — instead of a bare `Start now` that silently cold-starts a COLLIDING LOCAL instance of a remote id (node-anchored identity violation, ADR-0003/0023). ROOT (certain): confirm_options collapsed Suspended into the offline action set = [Start,…]; on a REMOTE row `Start` bakes Outcome::Run with NO node → picker dispatch → cmd_endpoint_run creates a fresh LOCAL perch of the remote id (model.rs confirm_terminal / mod.rs dispatch). Remote rows are only Online/Suspended, so remote+offline == remote-suspended. FIX: confirm_options splits the offline arm on is_local — remote → vec![Wake] (a new ConfirmOption::Wake), local → vec![Start] UNCHANGED; confirm_terminal(Wake) → a new Outcome::Wake{id,node} carrying the RAW node hex; dispatch routes crate::cli::cmd_endpoint_wake_remote(id,node) → cmd_rest(id@node, RestEvent::Wake) = the EXISTING WAN rest arm (dispatch_wan_rest → wan_rest), and A-2's resume leg revives the session async (the full loop the operator wanted). `Instantiate locally` stays the separate d"
  },
  {
    "titleB": "Two-slice envelope parser is panic-free and tolerant (4.2)",
    "docA": "",
    "b": "REQ-HAZARD-ENVELOPE-PARSER-SAFE",
    "docB": "",
    "a": "REQ-API-3",
    "titleA": "commune/signoff are file-drops, not commands"
  },
  {
    "titleB": "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 — 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 — the C3 lesson. WHAT REMAINS, and what this REQ now owns: RESTART-REPLAY RE-ESTA",
    "docA": "`api listen <id> [--once] [--parent-pid <pid>] [--subnet <name>] [--session-id <sid>]`: <!-- --> **Recoverable refusals do not consume the seed.** The seed is consumed by a **successful bind** — or by a refusal that proves the seed itself dead (see spend-vs-restore below). A recoverable refusal that never bound — `HOME_REFUSED` on a multi-subnet node without `--subnet`, `ADAPTER_UNRESOLVED`, a live-perch conflict — leaves the seed consumable, so the corrected retry on the same pid binds instead of dead-ending on `NO_SEED`. (Effect before irreversible consume: the destructive step follows the s",
    "b": "REQ-STREAM-LIFETIME-CLASS",
    "docB": "Amendment 1 (2026-07-22, DAEMON-LIFECYCLE W2) — teardown authority is opener-declared class PLUS transport liveness, enforced at the three places decision 6 could not see: <!-- --> <!-- -->",
    "a": "REQ-LISTEN-SEED-CONSUME-AFTER-BIND",
    "titleA": "F-034 leg b (perri/hertz field finding 2026-07-09, hertz's HEADLINE): `api listen` must NOT consume the consume-once ephemeral seed on a PRE-BIND refusal — validate (adapter resolvable, home/subnet) and BIND first, THEN consume the seed. ROOT: today `api listen` burns the consume-once seed BEFORE it validates home/subnet, so on a multi-subnet node HOME_REFUSED (needs --subnet) fires AFTER the seed is already gone → the corrected retry (adding --subnet) on the SAME pid hits NO_SEED, a dead end (plausibly ADAPTER_UNRESOLVED burns it the same way). A refusal that never bound must leave the seed intact for the corrected retry. Same EFFECT-BEFORE-IRREVERSIBLE-CONSUME ordering class as F-032 (commune commit-before-delete) — the irreversible consume must follow the successful effect, never precede a refusal. Gate: a pre-bind refusal (HOME_REFUSED on a multi-subnet node without --subnet; ADAPTER_UNRESOLVED) leaves the seed CONSUMABLE — the corrected retry on the same pid binds (no NO_SEED); a SUCCESSFUL bind still consumes the seed exactly once (no double-bind). Files: the api-listen bind path (seed consume ordering). Kin F-032 [[spt-core-findings-backlog]]."
  },
  {
    "titleB": "Idempotent/exactly-once delivery across brain restart at every broker boundary (codex #14)",
    "docA": "",
    "b": "REQ-HAZARD-RESTART-IDEMPOTENT",
    "docB": "",
    "a": "REQ-HAZARD-INBOX-NO-DOUBLE",
    "titleA": "No double-delivery via legacy inbox (4.5)"
  },
  {
    "titleB": "Bug #17 (W6b, closes eel-a end-to-end): [digest] gains a `fetcher` strategy mirroring [history]'s locate/normalize split (CONTEXT §history: [digest] mirrors history's two strategies — locate ownership). ROOT: the pre-W6b [digest] had only the locate_normalize analog (spt-core resolves ONE `source` template + pre-reads the file), which CANNOT express a PARTITIONED transcript layout — CC's projects/<munge(cwd)>/<session_id>.jsonl or a date-globbed rollout tree — the exact case CONTEXT already assigns to the adapter. spt-core (correctly) provides NO {project}/slug key (harness-specific cwd munging = the charter violation FIX-A was rejected for). Fix: strategy = fetcher makes the ADAPTER's extractor locate + read + emit normalized records; spt-core runs it bounded (no locate, no pre-read, no stdin) and consumes stdout, feeding only the harness-NEUTRAL inputs it owns — {session_id}, the perch-bound {cwd} (info.json.cwd), and the captured [env] direction=read vars (W6/REQ-DIGEST-PROFILE-ENV) — so the extractor globs the unique {session_id} under {read-var-root}/projects/ with no slug. Keeps locate_normalize (default, back-compat) for a trivial single-file harness. Distinct capability fro",
    "docA": "Author-time proof: `spt adapter translate-proof`: <!-- -->",
    "b": "REQ-DIGEST-FETCHER-STRATEGY",
    "docB": "`[digest]` — session-digest extractor: <!-- --> `[digest]` supports the same two locate strategies as `[history]` — pick with `strategy` // native: adapter pushes via `api history-log`; spt-core stores (also used for Shells): ### `[digest]` — session-digest extractor (ADR-0019) The session digest's own seam — **distinct from `[history]`** (which stays opaque and single-session, feeding the echo-commune verbatim). Declares an **imperative extractor** that maps the harness's native log → the digest-record contract. ```toml [digest] extractor = \"claude-spt-digest --session {session_id} --in {sour",
    "a": "REQ-ADAPTER-TRANSLATE-PROOF",
    "titleA": "`spt adapter translate-proof <adapter> --event <envelope> [--session <id>]` — the author-time EMIT-half proof tool for `[message-idle-translation-binary]` (ADR-0022), symmetric to `spt adapter digest-proof` (REQ-TERM-5). It spawns and feeds the adapter's declared translation binary EXACTLY as the daemon does at idle-delivery — running the REAL `spt_daemon::translation` driver VERBATIM (no protocol reimplementation): `TranslationChild::spawn` the binary, send the `{type:\"init\",endpoint_id,node}` line then the `{type:\"event\",envelope}` line, and read back the emitted `{key}`/`{text}`/`{delay_ms}`/`{commit}` keystroke-command stream — then prints it author-readable (each Key with its `key_to_bytes` rendering, Text quoted, Delay in ms, Commit marker) with counts. It fills the SAME `{id}`→option and `{session_id}`→(--session, else a placeholder) keys into the `--event` envelope the daemon fills at runtime, so an envelope that proofs here feeds faithfully live. EMIT-half ONLY: it proves the binary's spawn+feed+emit contract; it does NOT exercise the daemon's atomic PTY apply / controller-buffering (that stays covered by the W2 inject_control_wedge int gate) — `--help` says so. Exit codes"
  },
  {
    "titleB": "Adapter strings — [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)",
    "docA": "",
    "b": "REQ-MANIFEST-3",
    "docB": "Runtime model: **adapter strings** (ratified 2026-06-11, Gateway grill): <!-- --> A `[strings]` manifest section — 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` — 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",
    "a": "REQ-DAEMON-1",
    "titleA": "One per-machine spt-daemon owning all per-machine state"
  },
  {
    "titleB": "PUMP-TRUTH W2 (architectural, operator ruling 2026-07-08): one peer must NOT block or poison all others -- peer discovery is async / per-peer-independent. Two coupled defects in run_peer_pump: (1) SEQUENTIAL fan-out (for peer in fan_targets dials one-at-a-time, each up to the bound -> peer N+1 waits behind peer N); (2) WHOLE-ROUND POISON (peer_outcome(...)? -- one TimedOut aborts the ENTIRE round via ? -> supervise_pump doubling-backoff restart, resetting ALL conns). FIX: per-peer concurrency + fault isolation -- the pump issues non-blocking dial requests; the broker (already async tokio+iroh) returns connection/presence results as async events (the D4c presence seam), no serial per-peer block; a peer TimedOut drops + reschedules ONLY that peer, NEVER aborts the round or restarts the pump. Supervised-restart is RESERVED for a dead BROKER conn, not a dead peer (the single-thread+bounded-read A-half REQ-HAZARD-PUMP-IPC-DEADLINE was defensive -- it stopped the infinite wedge but coupled every peer's fate; this decouples). Gate: a mixed roster (1 live + N offline peers) -- the live peer connects AND this node advertises presence in the SAME round the offline peers fail; heartbeat advan",
    "docA": "",
    "b": "REQ-PUMP-PEER-ISOLATION",
    "docB": "",
    "a": "REQ-NET-3",
    "titleA": "Cross-node Psyche sync over P2P replaces gh-repo-sync"
  },
  {
    "titleB": "A-4b (REMOTE-TRUTH triage §A + ADR-0034 Decision 2): the tracing-only op families auto-retry ONCE with a fresh op on the typed no-longer-held error, so a broker-restart-dropped conn/stream self-heals instead of surfacing core lingo to the user. ROOT: net/rest.rs declares rest op-ids 'tracing/correlation only … redelivery needs reporting, not dedup', yet the journal enforced exactly-once on them — a rest/attach op whose conn the broker no longer holds (post-restart) returned the typed 'already applied … no longer held … retry with a fresh op_id' (broker.rs:2796 net-dial / 3047 stream-open) straight to the user (WOKE_FAIL / rc attach fail). FIX (Decision 2, scoped): the rest family (request_rest) + rc attach stream-open (request_attach_endpoint) — and ONLY those — catch the typed no-longer-held error INTERNALLY and re-issue ONCE with a FRESH op minted from the SAME minter (A-4a MintedOp; same producer, new seq). sync/update pull families (request_sync/request_update, durable open_op) are NOT wrapped — their exactly-once dedup is load-bearing (negative control). The typed op error becomes internal-only; if the retry ALSO fails, the user-facing line names the observable situation + nex",
    "docA": "Decision's minter list did not match the code): <!-- --> Executor recon verified the actual journaled-op surface against HEAD; the Decision-1 tag list was wrong in both directions. Corrections, binding",
    "b": "REQ-OPID-TRACING-RETRY",
    "docB": "no-longer-held error by a SHARED CONST MARKER, not a typed wire variant): <!-- --> Decision 3's \"typed op error\" is realized as a single `pub const` marker phrase, not a new typed variant on the KIND_ERROR wire path",
    "a": "REQ-OPID-MINTER-NAMESPACE",
    "titleA": "A-4a (REMOTE-TRUTH triage §A + ADR-0034 Decision 1 + Amendments 1 & 2): the broker effect journal's dedup key gains a minter dimension so ops minted by independent counters can never collide. ROOT (high, ground-truthed vs HEAD): the journaled-op producers key into ONE journal namespace (NET_EFFECT_SESSION|shell_sid, op) at broker.rs (EffectKey=(u64,u64)); a CLI wake op colliding with an already-journaled daemon op reproduces the typed 'already applied … retry with a fresh op_id' with NO broker restart (field-hit: spt endpoint wake id@node WOKE_FAIL). Same latent class: nethost dial_ops/stream_ops HashMap<u64,u64> ('Shares the one net op-id namespace') would re-clobber even after the journal separates them; AND shellchan::deliver_stdin_pending journals (shell_sid, row_id) so an rc operator's ops on the same shell_sid collide with spool row ids (dropped keystroke OR dropped spool row). Amendment 2 corrected the minter set: the REAL journal minters are {cli, pump, rc, shell} + legacy — psyche/epoch are the EpochSource notif/lease counter domain, NEVER submit to apply_once, DROPPED from the journal enum (a tag with no stamp site = doc'd-but-dead knob). FIX (Decision 1 + Amdt 2): ONE ca"
  },
  {
    "titleB": "A VIEW is independent from the endpoint: closing the tab/window where `spt endpoint run` was invoked must detach ONLY the `spt rc` attach pump — the daemon-hosted harness keeps running and stays re-attachable via `spt rc <id>`. ROOT (Windows, v0.12.0 real-harness defect): the daemon never breaks away from the launching terminal's Job Object. Windows Terminal / VS Code place the launched shell AND every descendant into a Job Object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; closing the tab drops the terminal's last job handle → the OS terminates every process still in that job. A child escapes only with CREATE_BREAKAWAY_FROM_JOB — used NOWHERE in the tree. Both daemon spawn paths (daemon.rs:707 detached_no_inherit = DETACHED_PROCESS|CREATE_NEW_PROCESS_GROUP|CREATE_NO_WINDOW; deelevate.rs:519 elevated = CREATE_NEW_CONSOLE|...) drop the CONSOLE but NOT job membership, so the daemon's freshly broker-spawned ConPTY harness subtree is reaped on tab-close. The ConPTY/pseudoconsole isolation itself is CORRECT (portable-pty builds the pseudoconsole in the daemon; no console signal / handle leak) — the leaking lifetime binding is the Job Object, not the console. FIX: add CREATE_BREAKAWAY_FROM_",
    "docA": "",
    "b": "REQ-HAZARD-VIEWER-CLOSE-DETACH",
    "docB": "Terminal wrapper: **A view is independent from the endpoint** (invariant): <!-- --> An spt-hosted endpoint runs in a **daemon-owned PTY, decoupled from whatever terminal launched it**. Closing the tab/window where `spt endpoint run` was invoked detaches only the `spt rc` attach pump — the endpoint keeps running under the daemon and stays re-attachable via `spt rc <id>`. A view is a transient frontend over a daemon-owned session, never the session's lifeline. *Implementation:* the daemon must never live inside the launching terminal's process grouping — on Windows the cold-started daemon is lau",
    "a": "REQ-HAZARD-ATTACH-WEDGE",
    "titleA": "A legitimately dead PTY child (real crash/kill) + an undrained operator pump must NOT wedge the broker for all other clients. ROOT (v0.12.0 real-harness defect): loopback attach output is a blocking write_all into a bounded 64KB tokio duplex (nethost.rs:1040,1090); when the operator's rc pump stops draining (tab closed) the buffer fills and write_all blocks forever (the 'loopback never hangs' assumption at nethost.rs:1103 is false), parking a worker in the 2-worker net runtime (nethost.rs:640); a couple of these saturate BOTH workers → every new attach / `endpoint run` stalls right after 'PUMP_IPC_READER: spawned' → 30s FIRST_EVENT_GRACE → 'no output / dead or wedged'; `daemon stop` cannot join the stuck workers. DISTINCT from the removed B1 path-(c) mutex deadlock. DISPOSITION = PROVE-DON'T-CHANGE (doyle GATE-PASS @e883f45, 2026-06-18): this ROOT is the SUPERSEDED v0.12.0 hypothesis — the post-L0 code ALREADY prevents the wedge, so NO fail-fast / worker-count code was added. serve_attach forwards fire-and-forget (net_stream_send op_id=None) and the broker-side send_stream is already BROKER-QUIC-DEADLINE-bounded (bounded_block_on, 10s); the loopback duplex is drained broker-INTERNA"
  },
  {
    "titleB": "`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) — 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)",
    "docA": "Live, daemon-coordinated adapter update: <!-- --> // Amendment (W3 build, 2026-06-22): <!-- --> <!-- -->",
    "b": "REQ-UPDATE-APPLY-RESTART-NOTICE",
    "docB": "",
    "a": "REQ-ADAPTER-LIVE-UPDATE",
    "titleA": "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)"
  },
  {
    "titleB": "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.",
    "docA": "",
    "b": "REQ-WAN-SEND-DELIVERY",
    "docB": "",
    "a": "REQ-LIVE-AGENT-NO-INJECT-DELIVERY",
    "titleA": "F-033 RE-SCOPED (doyle re-ruling 2026-07-10 after the #82 gate falsified the original premise — 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 — 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 — 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 — 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 — a harness-hosted live agent (controllable S"
  },
  {
    "titleB": "An spt-hosted endpoint's ONLINE+CONTROLLED state (`driven_by`) must CLEAR even when the detach IPC is lost — do NOT rely on the detach signal (same lesson as REQ-HAZARD-HOSTED-LIVENESS-RECONCILE B2): the reconcile loop clears `driven_by` when the endpoint has no live controller/session. Today a wedged or lost pump never delivers the detach, so the endpoint stays latched CONTROLLED forever. Composes with W1 (the wedge no longer blocks the detach) and rides the same pull-primary reconcile substrate as B2. (v0.13.0)",
    "docA": "",
    "b": "REQ-HAZARD-DRIVEN-BY-SELFHEAL",
    "docB": "",
    "a": "REQ-HAZARD-BIND-REST-STATE-CARRY",
    "titleA": "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) — 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 — 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."
  },
  {
    "titleB": "Revoke + timeboxed seed rotation + re-seed grace: `spt subnet revoke <node>...` (list, elevation-gated, revoke-only) writes roster tombstones immediately, then schedules ONE seed rotation (re-mint seed, bump seed_epoch, push new seed CONFIDENTIALLY over member-auth'd TLS connections — never in roster/registry gossip — force-drop revokees) at the close of a coalescing window (default 1h); further revokes in the window join the same rotation (one epoch bump). `--force-rotate-seed` rotates immediately (compromised-node path). RE-SEED GRACE: a node proving the immediately-prior epoch (N-1) AND still on the roster gets a re-seed-only restricted connection (auto-heals a benign offliner); revoked/off-roster denied; ≥2 stale → re-pair.",
    "docA": "",
    "b": "REQ-MESH-4",
    "docB": "",
    "a": "REQ-INST-9",
    "titleA": "Multi-subnet membership (same-user N subnets; cross-user seam)"
  },
  {
    "titleB": "Echo-commune fires before INIT_SIGNOFF on orphan teardown (3.3)",
    "docA": "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 — producer order is the contract. Output-before-Exit is a production-path invariant, regression-proven end-to-end (broker → attach → 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",
    "b": "REQ-HAZARD-ECHO-BEFORE-SIGNOFF",
    "docB": "",
    "a": "REQ-BROKER-OUTPUT-BEFORE-EXIT",
    "titleA": "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 — 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 — already admitted and compensated in the broker test suite, never fixed in production rc). Gate: impl — single sequencer, exit-behind-output enqueue; unit — ordering invariant on the writer queue (exit never precedes queued output for a sink); int — 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 — ADR-0043."
  },
  {
    "titleB": "A-2 (REMOTE-TRUTH triage §A-2 + ADR-0033): the daemon reconcile gains a WAKE-RESUME LEG — an endpoint whose rest INTENT is Active but whose harness session is COLD (status != online) is resumed by the daemon via the adapter's [session.resume] template using the LAST LEDGER session id, so a bare `spt wake <id>` on a suspended live agent actually brings it back (today: reconcile_once start-arm hosts ONLY status==online (livehost.rs:199), so a woken-but-unbound endpoint is skipped forever — neither status reaches online nor does reconcile re-host). This is the ADR-0033 LIFT: the thin `spt wake` edge writes rest intent, the DAEMON does the work. Mirrors shellwake::resolve_wake (read rest state, live-pid double-launch guard, launch, NEVER flip status — the harness self-binds → online). The leg reads the recorded adapter (D-2, REQ-SESSION-ADAPTER-RECORDED); an UNREGISTERED recorded adapter is the Q5 daemon-variant refuse: do NOT spawn, record a LOUD host_error report (F-1 naming the adapter + `spt adapter add`), never silent, never fallback-spawn on a different adapter. BINDS: (1) status=online is set ONLY by a real bind — the resume leg NEVER stamps it (CONTEXT liveness truth; the A-1 e",
    "docA": "Requirements: <!-- --> ## Amendment (RC-RENDER-TRUTH v0.38.1 leg 3, doyle ruling 2026-07-19): one hosting authority — the online-earn authority splits by hosting topology",
    "b": "REQ-WAKE-RESUME-LEG",
    "docB": "",
    "a": "REQ-HOSTING-AUTHORITY-CONTROLLABLE",
    "titleA": "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 — 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 ="
  },
  {
    "titleB": "Hostable endpoint-types capability declaration",
    "docA": "Manifest seams (outbound contract, detailed): **resume-session seam** — two distinct forms: - **fresh-with-preload:** resume with *cleared* context (a fresh session) + psyche-download. Accepts a `$psyche-context` key to launch the fresh session with the psyche-download preloaded — or the adapter instead pulls it via an spt-core command in its SessionStart hook. <!-- --> That command is **`spt api psyche-download <id> [--session-id <sid>]`**: it emits the durable resume brief (role → live-context → project-context, project resolved from the perch's bound cwd) to stdout for the adapter's Session",
    "b": "REQ-SEAM-CAPABILITY",
    "docB": "",
    "a": "REQ-RESUME-CONTEXT-PULL",
    "titleA": "Adapter-callable resume-context pull verb + not-yet-synthesized commune/signoff drop append (legacy-SPT parity, operator-directed 2026-06-24). GAP: spt-core exposes NO verb for a harness adapter's SessionStart hook to pull an agent's resume context — `resume::download_psyche_context` (spt-live/src/resume.rs:88, composes <live-role>+<live-context>+<project-context> from the durable two-tier store) is INTERNAL with ZERO spt callers and no ApiCmd verb (api/mod.rs ApiCmd enum has none); resume.rs:9 documents the intended 'adapter pulls it in its SessionStart hook' path but it was NEVER wired. Result: a harness adapter cannot inject the agent's durable mind on resume at all (claude-spt today runs only `api boundary` session-rotation + an identity brief — the agent resumes WITHOUT its mind). TIER-1 SCOPE (operator-approved; Tier-2 = drift-stamp/<current>/drift-directive + <memformat> + Pulse-Log DEFERRED to a separate parity item, NOT v0.15.0 — the legacy download_payload [claude_skill_owl context.rs:344] is richer but memformat is roadmap-deferred + drift-stamp is an orthogonal cross-machine-drift feature). TWO PARTS: (1) EXPOSE `spt api psyche-download <id> [--session-id <sid>]` -> std"
  },
  {
    "titleB": "Stale signoff sentinel does not kill a fresh start (3.2)",
    "docA": "",
    "b": "REQ-HAZARD-STALE-SIGNOFF-SENTINEL",
    "docB": "",
    "a": "REQ-HAZARD-REGISTRY-STALE-CLEAN",
    "titleA": "Stale registry entries degrade to fallback, never hard-fail (4.3)"
  },
  {
    "titleB": "Registry merge ordered by per-node monotonic epoch, never wall-clock — a stale Active can't clobber a newer Offline (4.8, red-team #8)",
    "docA": "4.6 Addressable-id charset reserves the address delimiters: ### 4.7 Concurrent SQLite openers must not fail with \"database is locked\" <!-- --> - **Failure:** two endpoints on one machine open the same SQLite store at once (e.g. two `ReadyAgent::start` calls registering simultaneously) and one fails outright with `SQLITE_BUSY` / \"database is locked\" → spurious registration/spool failure. Surfaced as a parallel-test flake in `two_agents_exchange_message_tcp_and_spool`, but the bug is real concurrency, not test-only. - **Invariant:** `busy_timeout` is set **before** any lock-taking statement on e",
    "b": "REQ-HAZARD-REGISTRY-EPOCH-LEASE",
    "docB": "4.7 Concurrent SQLite openers must not fail with \"database is locked\": ### 4.8 Registry merge ordered by epoch, never wall-clock (red-team #8) <!-- --> - **Failure:** the per-subnet registry replicates `endpoint_id → [instances]` eventually-consistently across nodes. Under a partition or clock skew, a lagging node re-announces a stale `Active` for an endpoint that has actually gone `Offline`. If the merge ordered updates by wall-clock (or \"last write wins\"), the stale `Active` overwrites the newer `Offline` and resolution routes a message to a dead/wrong instance. - **Invariant:** the merge pr",
    "a": "REQ-HAZARD-REGISTRY-CONCURRENT",
    "titleA": "Concurrent SQLite openers (registry/spool) must not fail with 'database is locked' (4.7)"
  },
  {
    "titleB": "Two-slice envelope parser is panic-free and tolerant (4.2)",
    "docA": "Body and attribute encoding: <!-- --> **Carriage returns are unrepresentable.** The encoder normalizes `\\r\\n` and lone `\\r` to `\\n` *before* the `<br>` encoding, so no frame ever carries a raw CR and a decoder always receives `\\n` newlines. Do not expect `\\r` to round-trip — content that needs CRs preserved does not fit this codec.",
    "b": "REQ-HAZARD-ENVELOPE-PARSER-SAFE",
    "docB": "",
    "a": "REQ-HAZARD-ENVELOPE-CR-LINESAFE",
    "titleA": "Envelope CR-linesafety (4.1): the line-framed EVENT codec must neutralize raw carriage returns — `event_body_escape` folds CRLF/lone-CR to the codec's representable linebreak (`\\n`→`<br>`) BEFORE framing, so a body carrying `\\r` (Windows `echo`/CRLF text crossing nodes) cannot survive into the single-line envelope and trigger a receiver terminal CR→col0 overwrite that corrupts the frame. Robustness on unrepresentable input, NOT a wire-format change (decoder untouched, amp-last invariant held). Belt-and-suspenders: `spt send`/`ring` also trim stdin (parity with `notify`)."
  },
  {
    "titleB": "A VIEW is independent from the endpoint: closing the tab/window where `spt endpoint run` was invoked must detach ONLY the `spt rc` attach pump — the daemon-hosted harness keeps running and stays re-attachable via `spt rc <id>`. ROOT (Windows, v0.12.0 real-harness defect): the daemon never breaks away from the launching terminal's Job Object. Windows Terminal / VS Code place the launched shell AND every descendant into a Job Object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; closing the tab drops the terminal's last job handle → the OS terminates every process still in that job. A child escapes only with CREATE_BREAKAWAY_FROM_JOB — used NOWHERE in the tree. Both daemon spawn paths (daemon.rs:707 detached_no_inherit = DETACHED_PROCESS|CREATE_NEW_PROCESS_GROUP|CREATE_NO_WINDOW; deelevate.rs:519 elevated = CREATE_NEW_CONSOLE|...) drop the CONSOLE but NOT job membership, so the daemon's freshly broker-spawned ConPTY harness subtree is reaped on tab-close. The ConPTY/pseudoconsole isolation itself is CORRECT (portable-pty builds the pseudoconsole in the daemon; no console signal / handle leak) — the leaking lifetime binding is the Job Object, not the console. FIX: add CREATE_BREAKAWAY_FROM_",
    "docA": "F-019 diagnosis lesson — confirm an adapter binary actually SPAWNED before behavioral diagnosis `[REQ-INSTALL-11]`: ### 7.25 A perch PINNED to a DEAD session self-heals (dead-owner re-pin) instead of wedging forever; a LIVE-owner rotation still refuses `[REQ-HAZARD-SESSION-PIN-WEDGE]` - **Failure (F-024C/F-024D, ENLYZEAM field + clean-room repro 2026-07-02):** `authenticate()` (auth.rs) gates `api poll`/`state`/`boundary` on `proof.session_id == info.json.session_id`. If ONE boundary rotation is LOST — the departing session dies (crash / tab-close) or its `/clear`-era `boundary` call never lan",
    "b": "REQ-HAZARD-VIEWER-CLOSE-DETACH",
    "docB": "Terminal wrapper: **A view is independent from the endpoint** (invariant): <!-- --> An spt-hosted endpoint runs in a **daemon-owned PTY, decoupled from whatever terminal launched it**. Closing the tab/window where `spt endpoint run` was invoked detaches only the `spt rc` attach pump — the endpoint keeps running under the daemon and stays re-attachable via `spt rc <id>`. A view is a transient frontend over a daemon-owned session, never the session's lifeline. *Implementation:* the daemon must never live inside the launching terminal's process grouping — on Windows the cold-started daemon is lau",
    "a": "REQ-HAZARD-SESSION-PIN-WEDGE",
    "titleA": "A perch PINNED to a DEAD session-id self-heals instead of wedging forever. authenticate() (spt/src/api/auth.rs:78) gates api poll/state/boundary on proof-sid == info.json.session_id; if ONE boundary rotation is lost (transient env corruption kills the /clear-era hook), the perch stays pinned to the dead sid and EVERY id-scoped hook call refuses — INCLUDING boundary itself (it presents the new sid), a permanent strand (ready:false, stale .idle, drain no-ops, WAN spool sleeps forever; AUTH_REFUSED is stderr-only = invisible inside a hook). FIX: authenticate() gains a DEAD-OWNER fallback — when the sid MISMATCHES AND the perch's recorded pid is dead (proc::is_process_alive==false), ACCEPT the caller's sid and RE-PIN (rotate session_id + log SESSION_REPIN loud). Same trust model as establish_perch's conflict gate (api/startup.rs:207-210), which already allows rebind exactly when owner_alive==false (an orphaned perch accepts a new LOCAL owner). A LIVE-owner mismatch STILL refuses (squat protection UNCHANGED). ADDITIVE to token auth — the existing token-auth recovery path is UNTOUCHED; the new branch fires only on (no token) AND (sid mismatch) AND (owner dead). COVERAGE SPLIT (explicit, "
  },
  {
    "titleB": "TOTP-seeded SPAKE2 pairing",
    "docA": "Robust WAN subnet join: meet-before-code + per-family bind gate: <!-- -->",
    "b": "REQ-PAIR-1",
    "docB": "",
    "a": "REQ-JOIN-TWO-PHASE",
    "titleA": "`spt subnet join` is two-phase (meet-before-code) so the entered code is FRESH at the ceremony regardless of discovery time (ADR-0030). The meet selector is the PUBLIC `(subnet-name, TOTP-epoch)` (rendezvous_token = SHA256(domain ‖ name ‖ totp_step)); the secret TOTP-code is the SPAKE2 password ONLY, never a discovery input — so the code is collected AFTER a member is found. Extend the brain.pair_join event stream (brain.rs:1009): CLI PairMeetReq{subnet} → daemon meets (name, current-epoch) resolving the seed-holder's REAL stable pairing address → MetMember event → CLI prompts the code (cli.rs cmd_subnet_join :6236) → PairCodeSubmit{code} → daemon dials the held real-address on SPT_PAIR_ALPN + SPAKE2 → PairJoined/PairFail. Daemon holds the real-address between phases, bounded by a 5-MINUTE wait-for-code timeout; a wrong-code retry re-runs the CEREMONY ONLY against the held address (no re-search). The non-interactive `--code` path stays one-shot (no prompt; relies on REQ-NET-FAMILY-GATE fast discovery, fails loudly per REQ-JOIN-DIAGNOSTICS on staleness). Security unchanged — the meet is pre-trust/unauthenticated (SPT_PAIR_MEET_ALPN); auth stays in SPAKE2. (next milestone)"
  },
  {
    "titleB": "[adapter] host_binaries declares the harness executable basenames a kind=\"harness\" adapter hosts agents inside (e.g. host_binaries = [\"claude\"]); bind-time pid→exe-basename match (case-insensitive, .exe-stripped) over the seed's parent_pid selects the candidate adapter set; zero matches → a friendly error naming the binary + the --adapter escape hatch. Additive + N-1-safe: optional Vec<String>, #[serde(default, skip_serializing_if = \"Vec::is_empty\")] (omitted-serialized like shortcut_basename, old manifests parse clean); manifest.schema.json regenerated from the derive (ADR-0001, CI drift-gated). The match-key for ADR-0021 adapter-agnostic bind-time resolution. (v0.9.0)",
    "docA": "Requirements: <!-- --> ## Amendment (RC-RENDER-TRUTH v0.38.1 leg 3, doyle ruling 2026-07-19): one hosting authority — the online-earn authority splits by hosting topology",
    "b": "REQ-MANIFEST-8",
    "docB": "Startup flows (the two topologies): **Bind-time adapter/profile resolution (ADR-0021).** Because the seed is adapter-agnostic, `listen`/`poll` resolve the owning adapter/profile when they bind, as a pure read — never a seed-time snapshot that could drift. `--adapter <name[:profile]>` is an **optional override** on the `api` group (an explicit choice for adapter dev/iteration); omitted, resolution runs: 1. the seed's `parent_pid` → that process's **executable basename** (case-insensitive, `.exe`-stripped); 2. **candidate adapters** = registered `kind=\"harness\"` adapters whose **`host_binaries`*",
    "a": "REQ-HOSTING-AUTHORITY-CONTROLLABLE",
    "titleA": "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 — 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 ="
  },
  {
    "titleB": "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 → Control, so it can drive a free endpoint but CANNOT `--take` — 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 — no elevated kick policy). The picker surfaces 'Kick <node> and attach' (Take) only on a controlled (blue ■) endpoint, via the existing attach dispatch (single-bringup-path: intent is a parameter).",
    "docA": "7.47 The physical terminal is never mutated or terminated outside its renderer's ordered state model — output before exit, owned baselines, unconditional teardown `[REQ-HAZARD-RENDER-LIFECYCLE]`: ### 7.48 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 `[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",
    "b": "REQ-KICK-1",
    "docB": "Shell sleep/wake (offline ↔ 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) — 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",
    "a": "REQ-HAZARD-CONTROLLER-LEASE",
    "titleA": "RC-RENDER-TRUTH W2 (KNOWN-HAZARDS 7.48 — 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 — the matrix; doc — KNOWN-HAZARDS 7.48."
  },
  {
    "titleB": "W1/D2 (JOIN-TRUTH): total NTP failure (no server on any family answered) is LOUD, not silent — a node running the ceremony on its raw skewed system clock must be visible. ROOT: current_offset_secs (ntp.rs) does `query_offset_secs().unwrap_or(0)` and eprintln's ONLY on a nonzero success, so an all-servers-unreachable refresh is indistinguishable from 'clock agrees'. Fix: log the TRANSITION into all-servers-failed once per refresh (suggested `NTP_TOTP_UNCORRECTED: all NTP servers unreachable — ceremony clock = raw system clock`) and the recovery transition back to corrected; the OFFSET_TTL already bounds refresh cadence so no per-call spam. Fallback behavior (offset 0 → system clock) is UNCHANGED — this adds observability only.",
    "docA": "",
    "b": "REQ-PAIR-NTP-LOUD-FAIL",
    "docB": "",
    "a": "REQ-HAZARD-RC-ATTACH-FAILFAST",
    "titleA": "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 — the broker still resolved a session for it). FIX: (a) once B2 lands, gate attach on is_online/status — an offline endpoint yields a clean 'endpoint offline, start it' not an attach; (b) fail-fast — 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 — the wall-f Windows tab-close: child alive-silent vs dead-not-reaped). (v0.12.0)"
  },
  {
    "titleB": "Per-endpoint access whitelist: origin-node gate, stateful-firewall (reply/outbound exempt), node-now/user-later, outer gate before grants",
    "docA": "Requirements: <!-- --> ## Amendment (RC-RENDER-TRUTH v0.38.1 leg 3, doyle ruling 2026-07-19): one hosting authority — the online-earn authority splits by hosting topology",
    "b": "REQ-SEC-1",
    "docB": "",
    "a": "REQ-HOSTING-AUTHORITY-CONTROLLABLE",
    "titleA": "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 — 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 ="
  },
  {
    "titleB": "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) — 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",
    "docA": "Startup defaults (`endpoint run --save`): <!-- --> Infrastructure endpoints (a gateway the phone treats as always-there) should not need hands-on bringup after a box reboot or daemon restart. `spt endpoint run … --save` persists the run — endpoint id, adapter option, and working directory — as a **startup default** in `daemon.json`; the daemon **replays** every saved default when it starts, as a fresh session with the adapter re-resolved at replay time. One entry per endpoint id (a re-save replaces the prior one); remove the entry from `daemon.json`'s `startup_endpoints` to stop auto-starting",
    "b": "REQ-ENDPOINT-TEARDOWN-AUTHORITY",
    "docB": "7.48 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 `[REQ-HAZARD-CONTROLLER-LEASE]`: ### 7.49 A teardown verb never stamps a terminal or resting state it has not caused — 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",
    "a": "REQ-ENDPOINT-AUTOSTART",
    "titleA": "MSG-IDENTITY W5 / F-038 (flynn operator-directed ask 2026-07-10, SPT-CORE-NEEDS #7 + deployah field-confirm same day: mobile-gw alive=false after the v0.30.6 full daemon restart = this feature's absence, live): an endpoint can be marked a STARTUP DEFAULT so the daemon brings it back up at daemon start — Gateway-class endpoints are infra (the phone treats mobile-gw as always-there; box reboot / daemon cold start currently leaves it down until hands-on). SHAPE RULED (doyle, dispatch): (a) `spt endpoint run --save` persists the run (id + adapter/profile + args) as a startup default REPLAYED at daemon start, symmetric with the shipped `subnet attach/detach --save` precedent — smallest orthogonal cut, explicit operator intent, no interaction with effective_rest_state/F-035 reader-parity semantics (shape (c) restore-what-was-up REJECTED for now: principled but couples to the rest_state neighborhood that just churned; revisit if --save proves insufficient in the field). A saved endpoint that fails to come up logs loud + does not block daemon start or other replays. flynn docs sweep confirmed missing-feature not docs-gap (rest/wake manual-only; no endpoint analog of subnet --save; no manif"
  },
  {
    "titleB": "Bug #12: `spt rc` to an endpoint renders ANSI escapes LITERALLY (raw ←[K / color codes) on a Win10 conhost console, garbling the viewport — while `endpoint run --attach` in the SAME env renders fine and Win11 Windows Terminal is unaffected. Root (code-grounded, doyle): rc.rs RawGuard::enable calls only crossterm enable_raw_mode (INPUT raw mode) and NEVER enables ENABLE_VIRTUAL_TERMINAL_PROCESSING on the OUTPUT handle; Win10 conhost defaults VT-output OFF so escapes print literally, whereas the picker/alt-screen setup on the endpoint-run path enters crossterm's VT-enabling console setup first (leaving VT-output on) — so it is the rc-attach CLIENT PATH specifically, and --attach-works-same-env confirms (not refutes) the VT-out theory. Fix: in the rc attach path (RawGuard), on cfg(windows) + interactive console (mirror the windows_mouse_wanted guard so piped stdin/stdout keeps clean bytes for the e2e byte tests), SetConsoleMode STD_OUTPUT_HANDLE |= ENABLE_VIRTUAL_TERMINAL_PROCESSING|ENABLE_PROCESSED_OUTPUT, capture the prior mode, restore on Drop. cfg(windows)-only, client-side, independent of #4/#6. See docs/NEXT-MILESTONE-BUG-TRIAGE.md #12.",
    "docA": "7.45 Endpoint lifecycle state converges to truth from every death path — 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 — 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",
    "b": "REQ-RC-WIN-VT-OUTPUT",
    "docB": "",
    "a": "REQ-HAZARD-RC-ATTACH-TRUTH",
    "titleA": "RC-RENDER-TRUTH W1 (KNOWN-HAZARDS 7.46 — 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 — the matrix; doc — KNOWN-HAZARDS 7.46."
  },
  {
    "titleB": "`api listen` must not ASSERT hosting topology it does not know. (hertz v0.39.0 field RCA 2026-07-21, doyle re-grounded at source.) OBSERVED: the published adapter sequence `api bind` then identity-preserving `api listen --session-id` produces a self-contradictory live record — controlled=true AND controllable=false on a broker-hosted PTY endpoint — which controlled-precedence masks blue while attached and which a DETACH then unmasks as amber HARNESS ONLY. Detach is not the root; it only reveals the bad stamp. SOURCE: api/startup.rs passes controllable=Some(false) UNCONDITIONALLY on the relay/listen path (~191-195, reasoning 'the harness owns the process, so there is no broker PTY'), and establish_perch resolves controllable = controllable.or_else(|| prior…) (~379) — explicit wins, so that Some(false) OVERWRITES the Some(true) an earlier `api bind` EARNED. The carry-forward discipline that protects cwd/adapter/rest_state does not protect this field precisely BECAUSE the listen path is not silent about it. The defect is an ASSUMPTION about hosting authority made by a path that does not know the answer. FIX (preferred): represent LISTENER CUSTODY separately from PTY HOSTING AUTHORITY,",
    "docA": "",
    "b": "REQ-LISTEN-PRESERVES-HOSTING-TOPOLOGY",
    "docB": "",
    "a": "REQ-HAZARD-RC-ATTACH-ONLINE-RACE",
    "titleA": "`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"
  },
  {
    "titleB": "RC-RENDER-TRUTH W1 (ADR-0042 decision 3, hertz elevated-endpoints RCA core leg 1): direct `spt rc <id>` preflights the already-known harness_only/controllable=Some(false) fact (local + gossiped rows both carry it) and refuses BEFORE any attach/stream with truthful copy naming the actual state ('online but harness-hosted; spt does not own its terminal') — never the stale-row guess ('it may have ended, or the registry row is stale'). Reproduced deterministically 2x live on an ONLINE - HARNESS ONLY endpoint. RC support for harness-hosted endpoints is NOT claimed until the elevated PTY-host satellite exists (standing ruling). Gate: impl — pre-stream preflight + truthful copy; unit — harness-only row refuses pre-dial with the new copy, non-harness rows unaffected; int — live remote Instance{harness_only:true} => direct rc refuses pre-stream, names harness-hosted, never says stale; doc — ADR-0042.",
    "docA": "7.45 Endpoint lifecycle state converges to truth from every death path — 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 — 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",
    "b": "REQ-RC-HARNESS-ONLY-REFUSAL",
    "docB": "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 → attach via the session-confirmed path regardless of persisted status. No honest session → the existing offline refusal stands. A claimed session with a dead client tree → refusal/reap, never attach. Reuse `SessionProbe`; no new liveness heuristic. <!-- --> 2. **Resume stamps UNBOUND.** A resume launch transitions an exist",
    "a": "REQ-HAZARD-RC-ATTACH-TRUTH",
    "titleA": "RC-RENDER-TRUTH W1 (KNOWN-HAZARDS 7.46 — 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 — the matrix; doc — KNOWN-HAZARDS 7.46."
  },
  {
    "titleB": "Echo-commune fires before INIT_SIGNOFF on orphan teardown (3.3)",
    "docA": "",
    "b": "REQ-HAZARD-ECHO-BEFORE-SIGNOFF",
    "docB": "",
    "a": "REQ-HAZARD-GRACE-BEFORE-SIGNOFF",
    "titleA": "Grace-period wait completes before composing INIT_SIGNOFF (1.1)"
  },
  {
    "titleB": "Agent endpoints vs Shells distinction in the type model",
    "docA": "",
    "b": "REQ-EP-2",
    "docB": "",
    "a": "REQ-EP-1",
    "titleA": "Day-one endpoint types; open type system"
  },
  {
    "titleB": "MESH-RECOVERY W1 (ADR-0039, RCA wave 3 — the acceptance surface, same contract): peer-failure telemetry is STAGE-SPLIT and health is USER-MEANINGFUL. The single 10s PUMP_PEER_FAIL token splits into attributed stages — address-resolution, QUIC connect, ALPN, seed-proof send, seed-proof receive/verify, roster exchange — each failure stamped (wall+mono) and peer-attributed (subsumes the 2026-07-14 PUMP_PEER_FAIL-unstamped seed). daemon status / subnet status report: live peer count, last successful peer dial, last admitted registry update, duration of any all-peer failure; the incident fingerprint (all dials failing + heartbeat fresh + net_up true) MUST render degraded — no green without real peer progress. New fields ADDITIVE (N-1 readers unaffected). Gate: impl — stage split + status surfaces; unit — stage classification + health state machine (degraded on all-peer failure, healthy only on real progress, not on heartbeat/time); int — health flips degraded/healthy across a real peer outage/restore; doc — reference regen (CLI surface change → xtask gen, no internal codes in clap help). Kin REQ-PEER-ROUTE-CHAIN, REQ-DAEMON-5 (heartbeat — answers liveness, not reachability), REQ-CLI-2/R",
    "docA": "",
    "b": "REQ-PUMP-STAGE-TRUTH",
    "docB": "Context: ## Decision <!-- --> <!-- --> <!-- -->",
    "a": "REQ-HAZARD-DRIVEN-BY-SELFHEAL",
    "titleA": "An spt-hosted endpoint's ONLINE+CONTROLLED state (`driven_by`) must CLEAR even when the detach IPC is lost — do NOT rely on the detach signal (same lesson as REQ-HAZARD-HOSTED-LIVENESS-RECONCILE B2): the reconcile loop clears `driven_by` when the endpoint has no live controller/session. Today a wedged or lost pump never delivers the detach, so the endpoint stays latched CONTROLLED forever. Composes with W1 (the wedge no longer blocks the detach) and rides the same pull-primary reconcile substrate as B2. (v0.13.0)"
  },
  {
    "titleB": "spt-hosted startup: spawn-session then api bind (no file)",
    "docA": "Server-side **screen grid** — the clean-room render model behind the clean repaint on attach (field bug #6 / `REQ-BROKER-SCREEN-GRID`, ADR-0031). A cold attach used to stream the entire retained raw-byte ring as its initial batch. For an alt-screen TUI that ring is a *rendering protocol mid-stream* (alt-screen enter/exit, absolute cursor moves, repaints, a torn oldest chunk); replayed raw into a fresh terminal it corrupts scrollback (#6) and leaves stray cells (#7) / resize debris (#8). The [`ScreenGrid`] interprets the same byte stream into an authoritative **current screen** and synthesizes",
    "b": "REQ-START-3",
    "docB": "",
    "a": "REQ-SCREENGRID-WIDTH",
    "titleA": "TEARDOWN-AUTHORITY W3 (hertz field RCA 2026-07-19, doyle-confirmed at source): ScreenGrid models every character as ONE display column, so wide characters (CJK, emoji, and other 2-column glyphs) shift subsequent text left and leave stale scraps at the right margin — field repro on the Claude settings UI (left-shifted rows + To/Wh/Es left-margin scraps). SOURCE: spt-term/src/screen.rs Cell { ch: char, pen } (~127) carries NO width datum, and put_char (~327-343) unconditionally advances col += 1, with pending_wrap likewise advancing a single display cell. FIX: give the grid a real display-width model — a wide glyph occupies its leading cell plus a continuation cell that renders nothing and is never independently addressable; cursor motion, wrap, erase, and scroll all reckon in DISPLAY columns. WIDTH POLICY (hertz-proposed, doyle-ratified — pinned so the renderer and the emulator can never disagree): share ratatui's pinned unicode-width 0.2.0 as a WORKSPACE dependency, ambiguous-width = 1, no CJK-context override. A second width authority in the tree is the defect this policy exists to prevent. Gate: doc — the width policy stated where the grid is documented; impl — width-aware Cell/p"
  },
  {
    "titleB": "Deferred rows survive poll drain (4.4)",
    "docA": "",
    "b": "REQ-HAZARD-DEFERRED-SURVIVE-DRAIN",
    "docB": "",
    "a": "REQ-HAZARD-DEFERRED-DRAIN",
    "titleA": "Deferred spool rows excluded from the event-stream drain (1.4)"
  },
  {
    "titleB": "W3 (LIFECYCLE-TRUTH): update apply works daemonless and one command finishes the cycle. ROOT (operator wart): update fetch/apply run ensure_daemon_announced (cli.rs:4386) -> on a stopped box they BOOT THE OLD broker pre-swap, guaranteeing the mixed old-broker/new-brain pair + a manual bounce. FIX: apply works daemonless (swap + record, next start runs new bytes); `update apply --finish` (name subject to docs-token gate) completes the cycle: swap -> brain cycle -> broker restart onto new bytes (rides REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL so the restart is not a massacre). CLI change -> xtask docs gen, no internal codes in clap ///.",
    "docA": "",
    "b": "REQ-UPDATE-ONE-SHOT-FINISH",
    "docB": "",
    "a": "REQ-DAEMON-RESTART-SELF-INTERRUPTION",
    "titleA": "SEED (inactive — operator-workflow trap, field-paid 2026-07-25): an spt-hosted context CANNOT restart the daemon, because the restart's STOP phase tears down the very context running the restart command — the START half never executes and the daemon stays down until an operator starts it by hand. Field case (this node, 17:47–17:50): an agent-kicked restart's stop-begin swept the initiating context and everything sharing its Job Object (flynn's shell + the relay-hopped alchemy Hub, cross-context job members — see REQ-SHELL-CLI-SPAWN-JOB-EXPOSURE's field-firing record); the broker drained ~2m10s to final exit (stderr ends abruptly at 17:50:05.8 amid routine traffic, no shutdown markers); the START was already dead; the operator manually started whatever binary was installed (v0.42.0), with no channel conveying which bits the restarting agent had intended — binary choice silently transferred to whoever brings the daemon up (kin: REQ-DAEMON-BITS-AMBIGUITY, the same which-bits-serve blindness). CONSEQUENCES: (a) every RESTART-REQUIRED apply (broker update, seed-skew restart per hazard 7.9) is operator-only today, and nothing documents that; (b) stop-begin kills bound shells with `close_"
  },
  {
    "titleB": "A WAN-ARRIVED `spt send` is DELIVERED to an spt-hosted endpoint (broker holds its PTY, NO api-listen relay), not spooled-forever. Today receive_wan (spt-daemon/wan.rs:271-276) tries deliver_tcp (the harness-hosted relay leg) then falls to spool — it has NO spt-hosted broker-inject leg, which exists ONLY in local cmd_send (REQ-SEND-SPT-HOSTED, Brain::inject_endpoint → KIND_ENDPOINT_INPUT → broker dispatch_endpoint_input → translation-binary idle-inject). So a WAN arrival to an idle spt-hosted perch with a live translation binary ALWAYS sleeps in spool until an adapter hook polls (F-023: perch verifiably idle 7min, binary healthy, zero injection). FIX: factor cmd_send's spt-hosted delivery leg into a SHARED fn; receive_wan calls it after the replay-check (wan_seen_at) + restamp (restamp_wan_user_msg), BEFORE the spool fallback. Claim discipline UNCHANGED: inject delivered=true → wan_mark_seen_at then return the existing 'delivered' wire token (no wire change); delivered=false → the existing spool-with-claim transaction. v0.14.3 LAW: the shared leg is translation-binary-ONLY, NO raw-PTY fallback — a no-binary arrival SPOOLS LOUD, never writes the PTY. (F-023, BUILD-F023-WANIDLE)",
    "docA": "",
    "b": "REQ-WAN-SPT-HOSTED-DELIVERY",
    "docB": "",
    "a": "REQ-SPOOL-TAKE-AUDIT",
    "titleA": "W5 (LIFECYCLE-TRUTH, RCA cost: proving WHO took delivered=1 rows burned an hour): the spool records the taker per row — leg enum (relay-backlog / hook-poll / idle-inject / psyche) + sid/pid + taken_at ms — surfaced by a --json debug read. Additive column, no schema break (delivered rows already retained)."
  },
  {
    "titleB": "On Windows, `spt rc` must FORWARD scroll-wheel events to the harness when the harness has mouse reporting on. ROOT (operator HITL): P1's RawGuard EnableMouseCapture (added for right-click paste, REQ-RC-WIN-PASTE) makes Windows Terminal forward ALL mouse — including the scroll wheel — to rc instead of scrolling its own buffer, but the rc mouse handler dropped everything except right-button-down → scroll DIED (and WT's native scrollback is stolen by the capture). Operator ruling: keep mouse capture + right-click bracketed paste AND forward scroll to the harness. FIX (doyle design, cfg(windows), folds into the rc mouse handler): TRACK the harness's mouse-reporting mode by scanning its OUTPUT stream for the DECSET set/reset — ESC[?1000h/1002h/1003h (mouse on) + ESC[?1006h (SGR ext) and their ...l (off) — into a shared MouseMode{enabled,sgr} (pump writes from output, stdin reader reads); the scan survives a sequence SPLIT across output chunks (a bounded carry buffer). The mouse handler: right-button-DOWN -> bracketed clipboard paste (unchanged, REQ-RC-WIN-PASTE); ScrollUp/Down -> translate to an xterm SGR mouse report (ESC[<64;col+1;row+1M up / ESC[<65;..M down, 0-based crossterm -> 1-b",
    "docA": "Runtime model: **adapter strings** (ratified 2026-06-11, Gateway grill): <!-- --> A `[strings]` manifest section — 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` — 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",
    "b": "REQ-RC-MOUSE-FORWARD",
    "docB": "7.19 An operator input FLOOD must not deadlock the broker via the applied-ack on the same conn `[REQ-HAZARD-INPUT-ACK-BACKPRESSURE]`: <!-- --> ### 7.20 `spt rc` must forward the scroll wheel to the harness (our mouse capture steals WT's native scroll) `[REQ-RC-MOUSE-FORWARD]` - **Failure (operator HITL):** scroll stopped working in `spt rc`. P1's `EnableMouseCapture` (for right-click paste, 7.18) makes Windows Terminal forward ALL mouse — including the wheel — to rc instead of scrolling its own buffer, but the rc mouse handler dropped everything except right-button-down → scroll DIED (and WT's",
    "a": "REQ-MANIFEST-5",
    "titleA": "File-backed adapter [strings] (M12-W3-T3.1): a [strings] dot-path value MAY be an inline-table FILE POINTER `key = { file = \"rel/path\" }` resolved to the file's contents at get-string time, keeping large bodies (skill-instructions, hint text) out of the manifest. A value-position table with a `file` key IS the pointer form (reserved — cannot double as data). Per-adapter aux storage `adapters/<adapter>/strings/`; pointers resolve relative to it with CONTAINMENT (reject `..`/absolute escaping the dir). UPDATE-SAFETY: a LOCAL profile's file-pointers resolve relative to the user-owned local-profile dir (NOT adapter-shipped strings/, which adapter updates overwrite), or the local profile inlines. Validate-at-register (fail-fast on a bad/escaping/missing pointer) + LAZY read at get-string (live file edits reflect, no re-register) + skip-diagnostics on missing-at-read (no hard-crash, mirrors [digest]). Rides the same leaf-replace profile overlay as the rest of [strings]."
  },
  {
    "titleB": "Drop files are daemon-owned single-writer (6.4)",
    "docA": "",
    "b": "REQ-HAZARD-DROP-FILE-SINGLE-WRITER",
    "docB": "",
    "a": "REQ-HAZARD-BRAIN-RESTART-PSYCHE-DUP",
    "titleA": "A bare brain restart leaves EXACTLY ONE `{id}-psyche` process per endpoint — 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 — 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 — 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"
  },
  {
    "titleB": "W1/D3 (JOIN-TRUTH, KNOWN-HAZARDS): the cached ceremony offset goes stale-WRONG when the OS clock STEPS under a live daemon — an offset measured against the OLD clock keeps applying for up to the 15-min TTL. Field-proven timeline (enlyzeam): refresh cadence 15:08/15:23/15:38/15:53; operator `w32tm /resync` stepped the clock −210s at 15:45:46; every `subnet join` returned NO_SEED_HOLDER until a daemon bounce forced a fresh query. Fix: the cache snapshot stores an (Instant, SystemTime) PAIR; on read, if |wall-elapsed − mono-elapsed| > ~2s the clock stepped ⇒ force an immediate refresh (offset recomputed against the new clock). PLUS: `meet_seed_holder` (pairhost.rs), on search-deadline exhaustion, forces ONE fresh NTP refresh + one final sweep before returning NO_SEED_HOLDER — so a stepped-clock join self-heals without a bounce. Clock reads must be seam-injectable for the hazard unit (inject the (mono,wall) pair — do NOT sleep 15 min).",
    "docA": "",
    "b": "REQ-HAZARD-CEREMONY-CLOCK-STEP",
    "docB": "",
    "a": "REQ-HAZARD-BIND-CWD-UNSET",
    "titleA": "A bound endpoint's `info.cwd` is SET at bind so a freshly-created perch appears under its own project tab. ROOT (found, v0.13.0): `info.cwd` is NEVER set on bind — `cmd_bind` (spt-hosted) and `bind_from_seed` (harness-hosted) never thread cwd into `establish_perch`/`rec.cwd`. FIX: `cmd_bind` reads its own `current_dir` (the broker spawned it in `project_cwd`); `bind_from_seed` passes `seed.cwd` (already captured at seed time, currently DISCARDED). DISTINCT from REQ-PICKER-HISTORY-FRESH (v0.12.1) — that unioned cwd-origin into picker MEMBERSHIP but tested merge_origin_project with a PROVIDED origin; it never asserted `info.cwd` is actually set on bind, so a real `endpoint run` perch still had an empty cwd and the union had nothing to union. This is the v0.12.1 P1 'appears under its own project right away' claim that was REFUTED in the changelog — delivered for real here. (v0.13.0)"
  },
  {
    "titleB": "Adapter-update avenue (file-pull / delegated command)",
    "docA": "",
    "b": "REQ-SEAM-UPDATE",
    "docB": "",
    "a": "REQ-SEAM-HISTORY",
    "titleA": "History subsystem (fetcher / locate-normalize / native store)"
  },
  {
    "titleB": "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 — 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 — 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 → status=offline → 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→mark_offline) is an OPTIONAL fast-path only if the daemon brain is rel",
    "docA": "",
    "b": "REQ-HAZARD-HOSTED-LIVENESS-RECONCILE",
    "docB": "",
    "a": "REQ-DAEMON-STATUS-JSON-TRUTH",
    "titleA": "D2 (F028, perri F-d): `daemon status --json` truth drift. managed_by/managed_active read null in JSON while the HUMAN view says 'managed-by: manual — at-logon task registered' (the two surfaces disagree); and pump staleness (the STALLED diagnosis, B5) is NOT computable from JSON — only a raw pump_heartbeat_ms is emitted, no derived staleness/stalled field. FIX: JSON managed_by/active match the human render, and add a derived pump-staleness/stalled field so B5's condition is machine-observable. See triage D2."
  },
  {
    "titleB": "`spt endpoint run` / bare `spt` routes a TOTALLY-EMPTY scope straight to the endpoint-creation flow: when gather_endpoints() is empty (nothing attachable, local OR subnet) PickerModel::new opens on Screen::CreateAdapter instead of PickExisting (today it always opens PickExisting + renders a blank list). A node WITH subnet endpoints but no local ones still has things to pick → stays on the picker. Extends REQ-RUN-PICKER. (v0.16.0)",
    "docA": "",
    "b": "REQ-RUN-EMPTY-CREATE",
    "docB": "",
    "a": "REQ-PICKER-PURGE-SHORTCUT",
    "titleA": "C-3 (REMOTE-TRUTH triage §C-3 #8): the pick-existing list gains an `x` purge shortcut — 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)` — 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 → offline-only, remote → local-only). force=false is deliberate — 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() — the same predicate the handler gates on. Red-first: purge outcome reachable ONLY from an offline LOCAL highlight (online/r"
  },
  {
    "titleB": "Prefer stable parent PID / broker handle over ephemeral PID (2.1)",
    "docA": "",
    "b": "REQ-HAZARD-PARENT-PID-PREFER",
    "docB": "",
    "a": "REQ-HAZARD-DAEMON-HOSTED-LIVENESS",
    "titleA": "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)"
  },
  {
    "titleB": "THE-FORKENING W1 (ADR-0036, operator-ruled 2026-07-14): the release channel is PRIVATE (`BigscreenVR/spt-bs-releases`) and the gh CLI is the mandated carrier — release discovery (`releases/latest`, cli.rs:9717) and asset download (cli.rs:4861 public browser URLs) move to deadline-wrapped `gh` subprocess calls (`gh api`, `gh release download`; run_git pattern). WHY gh not token+HTTP: private-repo `browser_download_url` 404s even with a valid token — the API asset-id dance is gh's job. Default repo flips via the existing SPT_INSTALL_REPO seam (cli.rs:5363) + xtask REPO const (main.rs:729) + notif.rs consent-changelog URL rider. Loud failure classes: gh missing -> UPDATE_FETCH_REJECTED:GhCliRequired with OS-SPECIFIC install hints (winget/apt/brew); gh unauthed -> distinct GhAuthRequired pointing at `gh auth login`. Signature verification unchanged — bytes verified after download, carrier-independent (update-set/counter/anchor continuity per ADR-0036 §2). release_verify_e2e reworked to the gh carrier. Gate: unit — url/invocation construction + both failure classes render OS-correct hints; int — fetch against a real gh-authed channel resolves latest + downloads and verifies an asset; do",
    "docA": "",
    "b": "REQ-UPDATE-GH-TRANSPORT",
    "docB": "Self-update: **release channel (private, gh-carried)** — the release channel is a **private** GitHub repo (`BigscreenVR/spt-bs-releases`, ADR-0036); the **gh CLI is the mandated carrier** for release discovery and asset download (each node authenticates via org membership). A node without an authed `gh` cannot fetch — refused loud with OS-specific install hints, never a silent hang. Signature verification is carrier-independent: bytes are verified after download exactly as before; counter, signing key, and update-set format are unchanged from the public-channel era. <!-- --> // How updates mov",
    "a": "REQ-SEAM-UPDATE",
    "titleA": "Adapter-update avenue (file-pull / delegated command)"
  },
  {
    "titleB": "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 — 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 — no new liveness heuristic. Kills the authority split where rc refused ('offline — nothing to attach to') while endpoint run --resume reattached to the same live session. Gate: impl — the pre-fast-fail probe + session-confirmed routing; unit — probe-true routes to session-confirmed attach, probe-false keeps the refusal, dead-tree claim refuses; int — 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 — ADR-0042.",
    "docA": "Self-update: **resume re-attach is view-only for non-driven sessions** — 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 — 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 — including the operator's local `spt rc` —",
    "b": "REQ-RC-HONEST-SESSION-AUTHORITY",
    "docB": "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 → attach via the session-confirmed path regardless of persisted status. No honest session → the existing offline refusal stands. A claimed session with a dead client tree → refusal/reap, never attach. Reuse `SessionProbe`; no new liveness heuristic. <!-- --> 2. **Resume stamps UNBOUND.** A resume launch transitions an exist",
    "a": "REQ-BRAIN-RESUME-NO-CONTROL-STEAL",
    "titleA": "UPDATE-WEDGE round 2 (v0.30.4, doyle-ruled 2026-07-09 — 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) — 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 — brainproc.rs:184) → 15s later `stall_evict_controller` (broker.rs:1039) releases driven_by → the session is UNCONTROLLABLE (Failure A). I"
  },
  {
    "titleB": "Deferred spool rows excluded from the event-stream drain (1.4)",
    "docA": "",
    "b": "REQ-HAZARD-DEFERRED-DRAIN",
    "docB": "",
    "a": "REQ-HAZARD-CONTROLLER-IRRECOVERABLE-BEHIND",
    "titleA": "DEFERRED EDGE of REQ-HAZARD-CONTROLLER-GAP-RESUME: when a serving controller falls behind the live ring FURTHER than the ring holds (tail - delivered_through > cap_chunks, the ring trim being delivered_through-blind, broker.rs:327-330), the dropped frames have rolled OUT of the ring and an exactly-once re-fetch is IMPOSSIBLE. v0.13.0 DETECTS this (resume floor unchanged across two consecutive resumes) and surfaces a MARKED truncation notice to the operator then ends the attach cleanly — it never silent-skips (a B2 lie) and never spins. FULL graceful handling (a clearly-marked snap-with-data-loss that keeps the operator on the live tail, or a structured truncation record the rc renders distinctly, plus the controller-too-slow + ring-too-small backpressure/sizing policy) is DEFERRED — staging it needs a netsplit / deep-behind harness (the in-process loopback rig keeps up; wedged_viewer's gap is recoverable at ~1492 < 4096). (v0.13.0+)"
  },
  {
    "titleB": "Resuming an endpoint session that HAS conversation history brings up a BLANK session. ROOT (doyle, code-grounded + CONTEXT — case-3 spt-core MISSING feature, NOT a perri docs-miss): CONTEXT L127-129 already defines the resume-session seam ('continue-existing: resume an existing harness session under the adapter — its NATIVE resume'), and the manifest already has the resume-variant pattern (Session has BOTH psyche_init AND psyche_resume, manifest.rs:217-219) — but the agent's own session has ONLY self_ (`[session.self]`, no resume sibling). cmd_endpoint_run (cli.rs:1304) re-passes the session_id through `[session.self]` on resume (resume.unwrap_or_else(mint_session_id)), so the adapter's FRESH command (e.g. `claude --session-id ..`) runs again instead of the harness NATIVE resume (`claude -r ..`) -> CC starts a fresh transcript -> blank. spt-core forwards session_id + cwd faithfully; it just has no way to express the native-resume invocation. SECOND GAP: CC resolves a transcript by session_id + cwd, but the session ledger records only {ts, session_id, trigger} (no cwd), so picker Resume-from-history (cross-project rows) can't restore the right cwd. FIX (doyle design, V0.13.0-P2-SESS",
    "docA": "",
    "b": "REQ-SESSION-RESUME-TEMPLATE",
    "docB": "`[session.<role>]` — outbound templates: <!-- --> **Resuming an existing harness session (since v0.13.0).** `[session.self]` is the *fresh* bringup; `[session.resume]` is the **native-resume** sibling. spt-core selects `[session.resume]` over `[session.self]` only when a bringup carries a prior session (`spt endpoint run --resume <session>`, or the picker's *Resume from history*) **and** your manifest declares the role. Declare it with your harness's native-resume verb — if your harness resumes a transcript by id, use that form (Claude Code: `claude -r {session_id} …`), **not** the fresh creat",
    "a": "REQ-PICKER-RESUME-CONTEXT-PANEL",
    "titleA": "#6: the 'Resume from history' view keeps the endpoint's 'Confirm selection' top panel and swaps ONLY the bottom panel to 'Resume from a prior session' — the user stays contextually informed about what they're picking (today the resume view replaces the whole screen). crates/spt/src/picker/view.rs (resume screen) + model screen state. See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #6."
  },
  {
    "titleB": "Per-subnet serve-state: spt subnet detach <NAME> [--save] / attach <NAME> [--save] — daemon keeps running, stops/starts advertising + connecting for that subnet (peer pump + responder selective); --save persists the startup default in daemon config; the all-attached banner gains per-subnet states (M8 decision 6, --save renamed from --auto per decision 25 session)",
    "docA": "",
    "b": "REQ-SUBNET-5",
    "docB": "",
    "a": "REQ-SUBNET-1",
    "titleA": "spt subnet noun namespace: status view (bare + status [NAME] [--nodes]), create (QR/otpauth), show-code; spt pair deleted"
  },
  {
    "titleB": "Concurrent atomic writers to the same target must not share a tmp name (5.15): a fixed tmp sibling makes one writer's rename consume the other's staged file (os-error-2 loser)",
    "docA": "`api boundary <clear|compact> <id> --to-session-id <new-sid> --session-id <prior-sid>`: <!-- --> // Boundary rotation credential: the departed-session catch-22 and the parent_pid anchor: <!-- -->",
    "b": "REQ-HAZARD-ATOMIC-TMP-COLLISION",
    "docB": "5.14 Corrupt info.json read as ABSENT → fail-open readers gossip a wiped perch ONLINE `[REQ-HAZARD-CORRUPT-PERCH-COHERENCE]`: <!-- --> ### 5.15 Fixed atomic-write tmp name → concurrent writers collide (loser renames a consumed file) `[REQ-HAZARD-ATOMIC-TMP-COLLISION]` - **Failure:** `atomic_write_bytes` staged every write under a **fixed** sibling `{name}.tmp`. Two processes writing the SAME target concurrently — traced at bind ~700µs apart: the daemon's `mutate_info` RMW and `spt api bind`'s `establish_perch` — both create the same `info.json.tmp`; whichever renames first CONSUMES it, and the",
    "a": "REQ-BOUNDARY-ROTATION-CREDENTIAL",
    "titleA": "api boundary's rotation credential is designed, documented, and eventually anchor-proven (ADR-0032): the proof is the DEPARTED session's (prior sid / token) — --to-session-id is payload, never proof; the published surface documents the adapter prior-sid persistence pattern + loud-refusal requirement; the design-true end-state additionally accepts an OS-verified parent_pid-anchor ancestry proof making adapter sid-state optional"
  },
  {
    "titleB": "`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",
    "docA": "Always-on endpoints: resident supervised adapter binaries, `#`-sigil addressed: <!-- -->",
    "b": "REQ-HAZARD-RC-ATTACH-ONLINE-RACE",
    "docB": "",
    "a": "REQ-EP-8",
    "titleA": "AlwaysOnEndpoint: a resident, addressable, mindless endpoint whose adapter binary the daemon supervises continuously — register-triggered by an adapter-option's `[always-on]` manifest section, one supervised binary per `<adapter>[:profile]`, running independent of agent liveness. It self-manages its `#`-addressed channel endpoints via the existing `api bind` (one connection fronts many). The SECOND class of spt-core-boot-launched third-party binary (after the shell wake-watcher); supervision reuses the wake-watcher scaffolding (backoff / give-up latch / one-per-instance lock / orphan-kill / brain-side reconcile) MINUS the offline-only flip — always online, never resting (no dormant/suspended states). Two-way: agents message it; it may call `endpoint wake <id>`, target-side authorized (REQ-INST-3/6 wake resolution + access whitelist + shell_wake_spawn_anywhere — no caller-ownership gate). First consumer downstream: spt-discord."
  },
  {
    "titleB": "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) — 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.",
    "docA": "`api boundary <clear|compact> <id> --to-session-id <new-sid> --session-id <prior-sid>`: <!-- --> // Boundary rotation credential: the departed-session catch-22 and the parent_pid anchor: <!-- -->",
    "b": "REQ-PICKER-CHOOSE-DEDUP-ALL",
    "docB": "",
    "a": "REQ-BOUNDARY-ROTATION-CREDENTIAL",
    "titleA": "api boundary's rotation credential is designed, documented, and eventually anchor-proven (ADR-0032): the proof is the DEPARTED session's (prior sid / token) — --to-session-id is payload, never proof; the published surface documents the adapter prior-sid persistence pattern + loud-refusal requirement; the design-true end-state additionally accepts an OS-verified parent_pid-anchor ancestry proof making adapter sid-state optional"
  },
  {
    "titleB": "KNOWN-HAZARDS 7.52: an operator stop outranks every implicit ensure — no convenience path resurrects what the operator just killed. The hazard-conformance twin of REQ-ENSURE-DAEMON-STOP-INHIBIT: its int-stage convoy rig IS this hazard's required test (stop under api-call storm -> down + zero respawns + honest refusal; start clears; one-daemon race). Registered separately per CLAUDE.md rule 4 — evidence may tag the same rig.",
    "docA": "",
    "b": "REQ-HAZARD-STOP-RESPAWN-CONVOY",
    "docB": "7.52 An operator stop outranks every implicit ensure — no convenience path resurrects what the operator just killed `[REQ-HAZARD-STOP-RESPAWN-CONVOY]`: <!-- --> - **Failure (paid-for, hertz v0.39.4 field RCA 2026-07-22, doyle code-verified same day):** `spt daemon stop --force` is non-terminal on a box hosting live adapter sessions: every `spt api` invocation runs an unconditional `ensure_daemon()` (REQ-DAEMON-3's anchor), hook-driven api calls arrive continuously, so the stop is a race the operator loses — 5–10 ephemeral spawner windows flash and the daemon is back; several force-stops to sta",
    "a": "REQ-HAZARD-LIVEHOST-BOOT-LIVENESS-GATE",
    "titleA": "B5: `spt daemon start` does NOT revive phantom Psyches for dead-but-online-latched perches. Today reconcile_once (livehost.rs:285) spawns a Psyche per status=online live_agent perch at boot WITHOUT verifying the harness child / {id}-psyche is actually alive — so a Cold start after an unclean stop revives N psyches for N dead-but-latched perches (3 psyches for 3 dead perches). FIX: gate the boot psyche-spawn on real child-liveness — a perch with NO live broker session (the B2 reconcile signal) is marked OFFLINE at boot instead of hosted, so a dead-harness perch is never revived. Shares the B2 reconcile loop (this is its boot-gate arm); composes with B2's honest latch. Also closes wall-a's psyche_host_error gap (residency-confirm does not run at boot tick-1, livehost.rs:395-441 / 257-263). (v0.12.0)"
  },
  {
    "titleB": "REGISTRY-LIFECYCLE W1 (KNOWN-HAZARDS 7.44, hertz post-close v0.36 RCA — 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 — the seam above; doc — KNOWN-HAZARDS 7.44.",
    "docA": "7.44 Streams and seats on a long-lived connection must have bounded lifetime — 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 — 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 — lifecycle state written by multip",
    "b": "REQ-HAZARD-REGISTRY-STALL",
    "docB": "7.43 One wedged stream subscriber must NEVER stall stream serving — 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 — 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 — live prod box):** FOUR compounding consequences of \"phys",
    "a": "REQ-HAZARD-ENDPOINT-LIFECYCLE",
    "titleA": "REGISTRY-LIFECYCLE W2 (KNOWN-HAZARDS 7.45 — 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 — 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 — the matrix; doc — KNOWN-HAZARDS 7.45."
  },
  {
    "titleB": "Node labels: hostname-default, gossiped, addressable in @node qualifiers (refuse-on-ambiguity)",
    "docA": "",
    "b": "REQ-SUBNET-3",
    "docB": "",
    "a": "REQ-PICKER-4",
    "titleA": "The picker's Subnet category renders the canonical node LABEL, not bare key-hex: a subnet row's node renders as 'LABEL (keyprefix…)' (e.g. 'HFENDULEAM (bcead52b…)') per CONTEXT.md:650 + Instance.node_label, NOT the raw node key-hex (SPT_DEV:14efb80cb… — a picker-only regression because resource_projection→ResourceRow 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} ({}…)\", key_prefix) — cli.rs / wansend.rs), never a re-implementation. (v0.10.0)"
  },
  {
    "titleB": "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 — its comment calls activity-gating 'a deferred follow wave'): an inbound message has an ACTIVE window (endpoint active → spool for the receiver's hook-poll, non-disruptive) and an IDLE window (idle/idle-transition → deliver immediately: translation binary spt-hosted → relay-poll either topology → spool, in fallback order). AXES (each composes; each defaults to its unrestricted value): (1) DELIVERY WINDOW — default (both, first-to-fire) | `--idle-only` (idle window; immediate if already idle) | `--active-only` (active window only, never wakes; the RENAMED `--deferred` — `deferred=1` spool column + `api poll --include-deferred` keep their names). (2) CHANNEL RESTRICTION — unrestricted | `--prefer-native` (translation binary if running else fall back) | `--force-native` (binary ONLY, no fallback/no spool-to-other-m",
    "docA": "",
    "b": "REQ-MSG-DELIVERY-AXES",
    "docB": "Activity-gated message delivery + send-modifier axes: <!-- -->",
    "a": "REQ-HAZARD-STDIN-SESSION-ID",
    "titleA": "Stdin session_id precedence over env (2.2)"
  },
  {
    "titleB": "AlwaysOnEndpoint: a resident, addressable, mindless endpoint whose adapter binary the daemon supervises continuously — register-triggered by an adapter-option's `[always-on]` manifest section, one supervised binary per `<adapter>[:profile]`, running independent of agent liveness. It self-manages its `#`-addressed channel endpoints via the existing `api bind` (one connection fronts many). The SECOND class of spt-core-boot-launched third-party binary (after the shell wake-watcher); supervision reuses the wake-watcher scaffolding (backoff / give-up latch / one-per-instance lock / orphan-kill / brain-side reconcile) MINUS the offline-only flip — always online, never resting (no dormant/suspended states). Two-way: agents message it; it may call `endpoint wake <id>`, target-side authorized (REQ-INST-3/6 wake resolution + access whitelist + shell_wake_spawn_anywhere — no caller-ownership gate). First consumer downstream: spt-discord.",
    "docA": "Startup defaults (`endpoint run --save`): <!-- --> Infrastructure endpoints (a gateway the phone treats as always-there) should not need hands-on bringup after a box reboot or daemon restart. `spt endpoint run … --save` persists the run — endpoint id, adapter option, and working directory — as a **startup default** in `daemon.json`; the daemon **replays** every saved default when it starts, as a fresh session with the adapter re-resolved at replay time. One entry per endpoint id (a re-save replaces the prior one); remove the entry from `daemon.json`'s `startup_endpoints` to stop auto-starting",
    "b": "REQ-EP-8",
    "docB": "Always-on endpoints: resident supervised adapter binaries, `#`-sigil addressed: <!-- -->",
    "a": "REQ-ENDPOINT-AUTOSTART",
    "titleA": "MSG-IDENTITY W5 / F-038 (flynn operator-directed ask 2026-07-10, SPT-CORE-NEEDS #7 + deployah field-confirm same day: mobile-gw alive=false after the v0.30.6 full daemon restart = this feature's absence, live): an endpoint can be marked a STARTUP DEFAULT so the daemon brings it back up at daemon start — Gateway-class endpoints are infra (the phone treats mobile-gw as always-there; box reboot / daemon cold start currently leaves it down until hands-on). SHAPE RULED (doyle, dispatch): (a) `spt endpoint run --save` persists the run (id + adapter/profile + args) as a startup default REPLAYED at daemon start, symmetric with the shipped `subnet attach/detach --save` precedent — smallest orthogonal cut, explicit operator intent, no interaction with effective_rest_state/F-035 reader-parity semantics (shape (c) restore-what-was-up REJECTED for now: principled but couples to the rest_state neighborhood that just churned; revisit if --save proves insufficient in the field). A saved endpoint that fails to come up logs loud + does not block daemon start or other replays. flynn docs sweep confirmed missing-feature not docs-gap (rest/wake manual-only; no endpoint analog of subnet --save; no manif"
  },
  {
    "titleB": "`become_controller` should STRUCTURALLY refuse a controller re-take whose `from_seq` falls below the connection's already-delivered contiguous floor — 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) — 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",
    "docA": "7.47 The physical terminal is never mutated or terminated outside its renderer's ordered state model — output before exit, owned baselines, unconditional teardown `[REQ-HAZARD-RENDER-LIFECYCLE]`: ### 7.48 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 `[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",
    "b": "REQ-HAZARD-CONTROLLER-RETAKE-FLOOR",
    "docB": "",
    "a": "REQ-HAZARD-CONTROLLER-LEASE",
    "titleA": "RC-RENDER-TRUTH W2 (KNOWN-HAZARDS 7.48 — 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 — the matrix; doc — KNOWN-HAZARDS 7.48."
  },
  {
    "titleB": "W5 (LIFECYCLE-TRUTH, RCA cost: proving WHO took delivered=1 rows burned an hour): the spool records the taker per row — leg enum (relay-backlog / hook-poll / idle-inject / psyche) + sid/pid + taken_at ms — surfaced by a --json debug read. Additive column, no schema break (delivered rows already retained).",
    "docA": "",
    "b": "REQ-SPOOL-TAKE-AUDIT",
    "docB": "",
    "a": "REQ-CARRIER-CLAIM-EXCLUSIVE",
    "titleA": "MSG-IDENTITY W4 / F-033 + operator self-send probe 2026-07-09 (dup-delivery cluster, RCA-FIRST): a spooled message row is delivered by EXACTLY ONE carrier — the first carrier to take a row (hook-poll drain, relay idle-inject, relay-backlog, psyche) atomically CLAIMS it so no other carrier can re-deliver the same row. FIELD EVIDENCE (authoritative, operator-observed): a default-window doyle-to-doyle send while doyle was BUSY delivered on BOTH the busy POLL path AND the idle RELAY path; spool row 156: window='default', delivered=1, taken_leg='idle-inject' — the relay claimed a row a poll also surfaced (REQ-SPOOL-TAKE-AUDIT instrument, already shipped, is the RCA tool: taken_leg/taken_sid/taken_at per row). PRIOR: F-033 (perri 2026-07-08) — the psyche-download filing arrived as TWO copies, dup-delivery live-confirmed. RCA-FIRST (report-before-fix): pin whether the poll drains before/after the relay's delivered=1 mark; whether the busy-to-idle edge re-offers a row a poll already took; whether take-marking is atomic per carrier or check-then-mark racy. DISTINCT from F-035 (that = active_only window honor, spt-core exonerated; THIS = a default msg on both carriers — F-035's lock never as"
  },
  {
    "titleB": "SEED (DEFERRED investigation, doyle 2026-07-09 — UPDATE-WEDGE follow-up): determine WHY the trial/rollback brain's livehost reconcile loop did NOT drive the broker controller-reap (nor re-host the live agents) during the ~30s field update-trial window, when livehost polls `query_live_session_endpoints()` → `brain.sessions()` (KIND_SESSIONS) UNCONDITIONALLY every `LIVE_RECONCILE_INTERVAL_MS`=5000ms (livehost.rs:1026). CONTEXT (surfaced building the counter-54 rig): livehost's 5s KIND_SESSIONS poll drives the SAME broker `reap_dead_controller` sweep the fix drives — so it would otherwise reap the 15s-matured wedge by ~T20 < the 30s trial and SELF-HEAL. It didn't (field froze 30s → rollback), so the field trial-brain livehost was silent/delayed (PIN Q2: no `DAEMON_RESTART_RESUME` under gen-1/gen-2; the 30s kill landed before/around livehost's first reconcile tick). The counter-54 fix (REQ-UPDATE-TRIAL-DRAIN-DRIVE) puts a RELIABLE 500ms reap-driver in run_brain's CORE heartbeat loop, making the wedge-reap INDEPENDENT of livehost — so this does NOT block counter-54. But the livehost silence is a latent anomaly with a SECOND consequence: live-agent HARNESS re-hosting was also delayed ~30",
    "docA": "",
    "b": "REQ-LIVEHOST-RECONCILE-TRIAL-SILENT",
    "docB": "",
    "a": "REQ-HAZARD-HOSTED-LIVENESS-RECONCILE",
    "titleA": "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 — 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 — 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 → status=offline → 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→mark_offline) is an OPTIONAL fast-path only if the daemon brain is rel"
  },
  {
    "titleB": "`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 — HOME_REFUSED established >=0.11.0; exposed by the node crossing 1->2 subnets). (ADR-0026)",
    "docA": "",
    "b": "REQ-RUN-MULTISUBNET-HOME",
    "docB": "Multi-subnet home resolution at `endpoint run` creation: <!-- -->",
    "a": "REQ-HAZARD-PAIR-SEED-ROTATION",
    "titleA": "Removing a node rotates the subnet seed (epoch bump) so an old node/old seed cannot rejoin; trust-store delete alone is NOT revocation because the seed is replicated to every trusted node (ADR-0005 #10)"
  },
  {
    "titleB": "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`) — 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 — 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))",
    "docA": "",
    "b": "REQ-HAZARD-STOP-PATH-PSYCHE-ORPHAN-REAP",
    "docB": "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 ↔ older broker) | broker↔brain 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`) — an abruptly-",
    "a": "REQ-ENDPOINT-STOP-OFFLINE",
    "titleA": "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 — folds with B2 (same setter). Unit: stop → is_perch_alive=false / alive=false. (v0.12.0)"
  }
]
