{
  "REQ-ACTIVITY-INFO-PULL": {
    "title": "The endpoint's current activity state (busy|idle) is readable via `spt api endpoint-info` \u00e2\u20ac\u201d a point-in-time read of the perch idle sentinel, for consumers that need a check rather than a stream (ADR-0048 decision 1, pull avenue; operator-ruled 2026-07-24). Additive key, N-1-safe per the additive-evolution posture.",
    "doc": "1. Activity observation is a first-class surface with two avenues \u00e2\u20ac\u201d and the digest is not one of them: <!-- --> <!-- -->"
  },
  "REQ-ADAPTER-FLOOR-ENFORCE": {
    "title": "F-5 (REMOTE-TRUTH triage \u00c2\u00a7F-5 + doyle rulings 2026-07-05): BOTH adapter acquisition verbs (spt adapter add + spt adapter update) REFUSE when the installed spt-core is BELOW the adapter's declared [adapter].min_spt_core_version floor \u00e2\u20ac\u201d with an F-1 operator refusal naming the installed core, the floor, and the next action (update spt-core first). ROOT: the floor was PARSED + required (manifest.rs) but never compared to the running core \u00e2\u20ac\u201d dead enforcement; and the [update].version_check knob that gated it was DOC'D-BUT-DEAD (never read by any production path \u00e2\u20ac\u201d a contract lie). RULINGS: RETIRE version_check (drop the manifest field + schema + docs + the cfg(test) literals; a pre-existing manifest still setting it deserializes fine \u00e2\u20ac\u201d serde ignores the unknown key, no deny_unknown_fields, so retiring is back-compatible); SEMVER-compare NOT string-compare (the 0.9.0 < 0.25.0 lexical trap); enforce on BOTH verbs; nothing installs / registry untouched on refuse (binds both verbs, no residuals). FIX: (1) a pure spt-runtime version_meets_floor(core, floor) -> bool (numeric per-component: split '.', u64, missing\u00e2\u2020\u20190, non-numeric\u00e2\u2020\u20190, first-diff decides, equal-when-zero-padded \u00e2\u2021\u2019 satisfied) \u00e2\u20ac\u201d mirror",
    "doc": "`[adapter]` \u00e2\u20ac\u201d header: <!-- --> `min_spt_core_version` is the **enforced** compatibility floor. Both acquisition verbs \u00e2\u20ac\u201d `spt adapter add` and `spt adapter update` \u00e2\u20ac\u201d REFUSE when the installed spt-core is below this version, naming the installed core, the floor, and the next action (update spt-core first). The check is a numeric per-component compare (so `0.9.0 < 0.25.0`), and it fires **before** anything is written: a refused add leaves the registry untouched, and a refused update leaves the live install byte-untouched. The enforcement is unconditional \u00e2\u20ac\u201d there is **no** opt-in flag (the former"
  },
  "REQ-ADAPTER-GH-TRANSPORT": {
    "title": "The `gh_release` avenue (and `spt adapter add --release`) gain a fetch `transport`: `https` (current reqwest direct, public), `gh` (shell the pre-authorized `gh` CLI \u00e2\u20ac\u201d the private-repo path; `gh` honors OAuth and `GH_TOKEN`, so spt custodies no token), or `auto` (default: prefer `gh` when installed+authed, else HTTPS). `--gh`/`--https` force it on `add`. Additive over the existing fetch path; verify->extract->register downstream is unchanged. (v0.13.2)",
    "doc": "Runtime model: **adapter packaging & live update** (v0.13.2; ADR-0024, ADR-0025): <!-- --> A `.spt` may be **multi-platform**: shared `manifest.toml` + `strings/` at the root, role binaries under per-target-triple subdirectories (`x86_64-pc-windows-msvc/`, \u00e2\u20ac\u00a6); install/update extracts the shared root plus only the current node's triple, flattened into `install_dir`, so flat `<install_dir>/<program>` resolution is unchanged. It stays one signed asset (`adapter.spt`, plain-tar or gzip); a multi-platform archive missing the recipient's triple is a typed `NoArtifactForPlatform`. Large adapters may"
  },
  "REQ-ADAPTER-MULTIPLATFORM-SPT": {
    "title": "A `.spt` adapter archive may pack multiple platforms in one signed asset: shared `manifest.toml` + `strings/` at the root, role binaries under per-Rust-target-triple subdirectories (ADR-0016 triple vocabulary, e.g. `x86_64-pc-windows-msvc/`); install/update extracts the shared root plus ONLY `current_platform()`'s triple subdir, flattened into `install_dir` so flat `<install_dir>/<program>` resolution (REQ-INSTALL-11) is unchanged. Name stays `adapter.spt` (plain-tar or gzip, `--asset` optional default); one whole-archive Ed25519 signature over the fat archive (REQ-UPD-9 single-artifact verify). A legacy flat archive (no triple subdirs) extracts as today (free back-compat); a multi-platform archive sets `min_spt_core_version >= 0.13.2` (forward-compat gate, readable before extract); a multi-platform archive missing the recipient's triple -> typed `NoArtifactForPlatform`, never a silent no-op. Large adapters may still split per-platform (single-triple archives via `--asset`, or ADR-0016 update-set machinery). (ADR-0024, v0.13.2)",
    "doc": "Multi-platform adapter `.spt` packaging: <!-- -->"
  },
  "REQ-ADAPTER-UPDATE-MESSAGE": {
    "title": "An adapter manifest may declare `[update].message` \u00e2\u20ac\u201d a plain (multi-line) human notice surfaced to stdout, markdown-rendered (the v0.13.0 helpfmt prose path), ONLY when `spt adapter update` actually APPLIES an update (version changed), not on a no-op. Read from the newly-installed manifest; avenue-agnostic (gh_release/delegated/file_pull). No `{key}` substitution. Use: an adapter telling the operator a post-update action, e.g. spt-claude-code's \"run `/reload-plugins` in any ongoing sessions\". (v0.13.2)",
    "doc": "Runtime model: **adapter packaging & live update** (v0.13.2; ADR-0024, ADR-0025): <!-- --> A `.spt` may be **multi-platform**: shared `manifest.toml` + `strings/` at the root, role binaries under per-target-triple subdirectories (`x86_64-pc-windows-msvc/`, \u00e2\u20ac\u00a6); install/update extracts the shared root plus only the current node's triple, flattened into `install_dir`, so flat `<install_dir>/<program>` resolution is unchanged. It stays one signed asset (`adapter.spt`, plain-tar or gzip); a multi-platform archive missing the recipient's triple is a typed `NoArtifactForPlatform`. Large adapters may"
  },
  "REQ-API-3": {
    "title": "commune/signoff are file-drops, not commands",
    "doc": ""
  },
  "REQ-ARCH-1": {
    "title": "Many small acyclically-layered crates",
    "doc": ""
  },
  "REQ-ARCH-2": {
    "title": "Public SDK surface is spt-proto, spt-runtime, spt-msg",
    "doc": ""
  },
  "REQ-ARCH-3": {
    "title": "Wire-protocol version independent of crate semver, N-1 compat window",
    "doc": ""
  },
  "REQ-ATTACH-IDEMPOTENT-REPLAY": {
    "title": "The equal-generation lease rung is idempotent for the same connection. (ADR-0047 decision 3, AMENDING ADR-0044's ladder inside the equal-gen rung; hertz v0.39.4 field bug 4, PINNED via OBS breadcrumbs on authorized same-seam `daemon refresh` 2026-07-22 \u00e2\u20ac\u201d gen+1 premise FALSIFIED.) TODAY: a Control/Take subscribe with same identity + same nonzero gen classifies 'same lease, silent re-take' (broker.rs equal-gen branch \u00e2\u20ac\u201d correct, no revoke) but re-take = become_controller, which unconditionally takes+drops the prior seat (writer exits channel-closed) with NO same-conn check \u00e2\u20ac\u201d designed for the dead-seat dispatcher-restart successor, it also fires against the SAME LIVE conn re-served 15ms apart by post-cycle dispatcher replay: the lease kills its own writer, the rc viewer freezes until detach+re-attach (the field 'update freezes PTYs'). FIX: keyed (endpoint/session, by, conn, gen) \u00e2\u20ac\u201d same-conn equal-gen = IDEMPOTENT REPLAY: seat + writer PRESERVED, no controller-replaced, no second initial batch; breadcrumb answers decision=idempotent (additive vocab). Equal-gen DIFFERENT-conn keeps today's silent swap (the ADR-0038 fix-6 successor \u00e2\u20ac\u201d must not regress); strictly-newer keeps loud supersessi",
    "doc": "3. The equal-generation rung is idempotent for the same connection: <!-- -->"
  },
  "REQ-BIND-HONEST-SELF-STAMP": {
    "title": "C2-ROOT (F028, doyle RCA 2026-07-03): the identity-attribution ROOT behind REQ-SESSIONS-LOG-ENDPOINT-ATTRIBUTION \u00e2\u20ac\u201d three composing defects let a psyche-hosted SessionStart hook stamp a FOREIGN perch: (1) roster::detect_self_id leg (a) scans the owlery ONE level (roster.rs:107 read_dir(owlery)) so a NESTED psyche perch owlery/<parent>/nested/<id>-psyche can never self-resolve; (2) [session.psyche_init] (claude-spt manifest ~L347) spawns the psyche with NO env_remove + no pinned child identity, so whatever OWL_SESSION_ID/SPT_AGENT_ID reaches the child resolves to a foreign top-level perch; (3) the hook then writes info.json (session_id/pid rebind) + sessions.log on the mis-resolved victim. FIX (spt-core half): (i) identity PINNED at spawn \u00e2\u20ac\u201d ManifestRuntime role spawns inject the child's OWN SPT_AGENT_ID=<child perch id> + OWL_SESSION_ID=<child session>, AND detect_self_id enumerates NESTED perches (fix the one-level owlery scan); (ii) BIND HONESTY \u00e2\u20ac\u201d a session-start stamp may only write a perch whose resolved id AFFIRMATIVELY matches; never a fallback pick; refuse + loud-skip when unresolved (kin REQ-MSG-CLI-ORIGIN honest-default + #9 ancestry). ADAPTER half = PERRI touchpoint (psyche",
    "doc": ""
  },
  "REQ-BOUNDARY-ROTATION-CREDENTIAL": {
    "title": "api boundary's rotation credential is designed, documented, and eventually anchor-proven (ADR-0032): the proof is the DEPARTED session's (prior sid / token) \u00e2\u20ac\u201d --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",
    "doc": "`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: <!-- -->"
  },
  "REQ-CARRIER-CLAIM-EXCLUSIVE": {
    "title": "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 \u00e2\u20ac\u201d 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' \u00e2\u20ac\u201d 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) \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d F-035's lock never as",
    "doc": ""
  },
  "REQ-CLI-2": {
    "title": "spt daemon noun: run|stop|status (hidden daemon verb becomes daemon run; agent-endpoint shutdown keeps its name under endpoint); daemon status renders the pump heartbeat (last-tick recency) so a half-dead daemon is never rendered implied-healthy (M8 decisions 5, 23)",
    "doc": ""
  },
  "REQ-CLI-3": {
    "title": "Agent hot path stays flat across the M8 reorg: send/ring/ready/whoami/how-to unchanged; notify moves to subnet notify while notif stays top-level; breaking renames land clean with no deprecation shims (zero external CLI consumers pre-spt-claude-code) (M8 decisions 3-4, 9)",
    "doc": ""
  },
  "REQ-CONN-POISON-ATTRIBUTION": {
    "title": "MSG-IDENTITY W6 / F-039 legs b-d (doyle W6 LOCK 2026-07-10, minted per amendment 3): every broker-conn lifecycle record is ATTRIBUTABLE \u00e2\u20ac\u201d the W6 RCA's terminal undecidability (per-line 1:1 CONN_WRITE_POISONED churn = fresh-carrier churn OR stderr interleave artifact) exists because records carry no stable conn identity, no role/endpoint/session context, and no timestamps, and the once-per-conn poison latch hides multiplicity. THREE LEGS. (b) IDENTITY: mint a stable per-physical-conn id (monotonic u64 at conn construction \u00e2\u20ac\u201d Arc::ptr_eq is the only identity today and it does not survive a log line) plus subscriber role and endpoint/session where known, stamped on CONN_WRITE_POISONED, CONN_WRITE_RETIRED, logical stall-evict, attach/resume/detach, and write-retirement records (RCA attach sites: presence nethost.rs:379, stream nethost.rs:258, controller broker.rs:891, viewer broker.rs:1073). (c) TIME: daemon stderr correlation records carry wall-clock AND monotonic timestamps (stderrlog has neither; broker+brain share one file \u00e2\u20ac\u201d interleave is unresolvable without them). (d) LIFECYCLE (doyle-confirmed UNCONDITIONAL, not debug-gated): one BOUNDED set of per-conn lifecycle events \u00e2\u20ac\u201d write s",
    "doc": ""
  },
  "REQ-CONTROL-STAMP-CONVERGENCE": {
    "title": "REGISTRY-LIFECYCLE W2 (ADR-0041 decision 4, emphasys C3 P1): control-stamp writes are session/generation-VALIDATED (or per-endpoint stamp transitions serialized) so a pre-reap KIND_SESSIONS snapshot can never relatch controlled=true after the exit-waiter reap; no fs I/O under global locks (KH 7.12); every no-session path clears all three control fields (controlled/driven_by/viewer_count). Gate: impl \u00e2\u20ac\u201d validated/serialized stamp transitions; unit \u00e2\u20ac\u201d stale-snapshot write refused after a newer no-session truth, no-session paths clear all three; int \u00e2\u20ac\u201d poll-vs-reap interleave converges to cleared stamps (the relatch regression); doc \u00e2\u20ac\u201d ADR-0041.",
    "doc": "Decision: <!-- --> 1. **Online is earned, not declared.** A creator may stamp `status=online` only from actual persisted state + hosting authority \u00e2\u20ac\u201d never from manifest capability alone. Legacy hybrid rows self-heal at reconcile, but only after a SUCCESSFUL broker query: a broker failure is never interpreted as an empty session set (no mass-offline on a hiccup). 2. **Control cleanup splits from offline classification.** Reconcile clears `controlled`/`driven_by`/`viewer_count` for EVERY endpoint absent from session truth \u00e2\u20ac\u201d regardless of state or controllability \u00e2\u20ac\u201d while offline classification ke"
  },
  "REQ-CONTROLLER-LEASE-IDENTITY": {
    "title": "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 \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d 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",
    "doc": "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 \u00e2\u20ac\u201d and revocation is authoritative, notification is not.** If `intent == T"
  },
  "REQ-CONTROLLER-LIVENESS-REAP": {
    "title": "B-2 (REMOTE-TRUTH triage \u00c2\u00a7B-2, REDUCED @bdc1242): a stale ONLINE+CONTROLLED stamp on a live-session perch self-heals \u00e2\u20ac\u201d the info.json driven_by/controlled RECORD is made to match the broker's SINK-TABLE TRUTH. ROOT (persisted-stale-stamp class, doyle Q1): the livehost control reap gates on !has_session (reconcile_hosted_liveness), and a brain-only update KEEPS the session (REQ-UPD-3), so a controller stamp that went stale WHILE the session lived was never re-derived from broker truth. NOT a transport bug: (a) a persisted conn is the REQ-UPD-3 feature; (b) an idle-severed conn eventually EOFs via QUIC keepalive \u00e2\u2020\u2019 handle_conn detach (path 1) \u00e2\u20ac\u201d and the reason paths 1-3 previously failed to clean up was the B-1 broker floor-lock POISON WEDGE (cleanup panicked under the poisoned lock), now fixed. REDUCTION (doyle, my ground-truth): the prescription was 80% pre-built \u00e2\u20ac\u201d converge_perch_stamps (broker.rs, REQ-HAZARD-CONTROL-STAMP-CONVERGENCE) ALREADY converges info.json driven_by/controlled to the broker's controller_by/has_controller on EVERY KIND_SESSIONS poll, and the livehost reconcile already TRIGGERS that poll per tick (query_live_session_endpoints). So NO new IPC query, NO new livehos",
    "doc": ""
  },
  "REQ-CONV-2": {
    "title": "Event-driven advertisement: endpoint online/offline transitions (ready-listener start/stop, rest-state transition, perch death) trigger an immediate advertise_local + peer push as a WAKE of the existing pump loop (no second advertisement path \u00e2\u20ac\u201d epoch lease + visibility gates ride unchanged); the cadence stays the steady-state floor (M8 decision 15)",
    "doc": ""
  },
  "REQ-DAEMON-2": {
    "title": "Broker/brain split for seamless self-update",
    "doc": "Restoration field-run evidence (D7-4) \u00e2\u20ac\u201d the seamless-update acceptance: <!-- -->"
  },
  "REQ-DAEMON-9": {
    "title": "Net-bind boot-race resilience: a daemon that comes up net-less (NetHost::start failed \u00e2\u20ac\u201d e.g. the systemd unit autostarted before the network/DNS stack was ready, `Failed to create an address lookup service`) must SELF-HEAL \u00e2\u20ac\u201d retry the net bring-up in the background with capped backoff and, on success, attach net to the broker + spawn the dispatcher/peer-pump (which today are gated on `net_up` at boot and so never start, leaving the node silently unreachable until a manual restart \u00e2\u20ac\u201d kitsubito 2026-06-08). Status surfaces the net-less state honestly (a net-less broker renders as 'no connection', not only a pump-STALLED line with a bogus pre-boot heartbeat age). The installer's autostart unit waits for the network (`Wants=/After=network-online.target`) as belt-and-suspenders.",
    "doc": ""
  },
  "REQ-DAEMON-BITS-AMBIGUITY": {
    "title": "SEED (inactive, RCA-first \u00e2\u20ac\u201d do NOT close on agreement): nothing on a node surfaces WHICH BITS ARE SERVING, and the same silent wrong-state shape bit twice in one day (2026-07-25). Case 1 (adapter-side echo): a post-reboot ensure race left a dev-build alchemy Hub Daemon serving release shells \u00e2\u20ac\u201d version skew visible only by manually comparing process image paths. Case 2 (core, field-measured by flynn): TWO spt daemons resident with live brains on one node \u00e2\u20ac\u201d the installed main daemon (owning ALL sockets: the 5474 listeners and every established connection, single home_tag pipe family) and an orphaned scratchpad-built daemon (auto-started into the node by ensure_running from a stray dev-binary invocation at 16:18, holding zero sockets, resident for hours) \u00e2\u20ac\u201d while `spt --version` on any binary file answers nothing about which process is answering. Measured sharp edges to carry into the RCA: (a) exe path and resolved HOME are independent \u00e2\u20ac\u201d the orphan ran scratchpad bits against the DEFAULT home, so 'where the binary lives' predicts nothing about 'whose state it mutates'; (b) the brain.ready breadcrumb is ONE FILE PER HOME, LAST-WRITER-WINS, keyed by generation \u00e2\u20ac\u201d with two brains in one ho",
    "doc": ""
  },
  "REQ-DAEMON-REFRESH": {
    "title": "THE-FORKENING W4 (operator add 2026-07-14): `spt daemon refresh` \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d verb routes the brain-cycle without staging/swap preconditions; int \u00e2\u20ac\u201d refresh on a live daemon with a hosted PTY: brain generation changes, PTY survives, endpoint stays attached; doc \u00e2\u20ac\u201d 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.",
    "doc": "Self-update: **daemon refresh (`spt daemon refresh`)** \u00e2\u20ac\u201d 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. <!-- -->"
  },
  "REQ-DOCS-6": {
    "title": "spt how-to <topic>: in-binary task-oriented agent instructions (anti-drift; quickstart prompts point agents at it)",
    "doc": "Quickstart: two agents exchange a message: <!-- the human killer quickstart: install -> two agents message, real captured outputs, <10 min; CI-run step for step by quickstart_e2e.rs --> <!-- the agent prompt blocks point agents at the in-binary `spt how-to` topics; this page never duplicates their text --> End to end in under 10 minutes. The roles matter here: **you** install (and optionally pair machines); **your agents** exchange the messages. You hand each agent a short prompt; the binary itself teaches them the rest."
  },
  "REQ-DRIVEN-BY-OWN-NODE-NORMALIZE": {
    "title": "RC-RENDER-TRUTH v0.38.1 fast-follow leg 1 \u00e2\u20ac\u201d RESHAPED by operator/hertz correction + CONTEXT.md:382-389 grounding (doyle ruling v3, 2026-07-19; the original own-node NORMALIZATION is WITHDRAWN): plain `spt rc` on a CONTROLLED endpoint is refused-with-guidance BY DESIGN (CONTEXT:386 \u00e2\u20ac\u201d never silent-displace; --take is the opt-in kick), and `driven_by` = the CONTROLLING NODE including the own node (CONTEXT:386 single-writer datum \u00e2\u20ac\u201d the stamp comment's remote-only claim was a legacy-path artifact, NOT the model). v0.38.0 shipped behavior is CORRECT: the own-hex latch + client guidance blocking a second same-node plain rc IS the documented refusal, and the W2 generation ladder stays reachable exactly where it belongs \u00e2\u20ac\u201d the recovery seams (rc reconnect re-drive + dispatcher re-serve bypass the client gate; the hertz field FAIL leg is reclassified NOT-A-DEFECT). Remaining work = truth/cosmetics across ALL own-node display surfaces (doyle gate finding 2026-07-19 \u00e2\u20ac\u201d the own-hex latch is truthful but only rc.rs first learned to HUMANIZE it; the picker pin + endpoint-info attached_node still read the own-hex stamp as a foreign remote driver and print raw hex): (a) rc.rs guidance copy names the ",
    "doc": "Consequences: <!-- --> ### v0.38.1 consequence note \u00e2\u20ac\u201d `driven_by` own-node truth (ruling v3)"
  },
  "REQ-ENDPOINT-AUTOSTART": {
    "title": "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 \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d 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",
    "doc": "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 \u00e2\u20ac\u00a6 --save` persists the run \u00e2\u20ac\u201d endpoint id, adapter option, and working directory \u00e2\u20ac\u201d 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"
  },
  "REQ-ENDPOINT-CYCLE-HONEST": {
    "title": "REGISTRY-LIFECYCLE W3 (ADR-0041 decision 6, operator deployah stop/run wedge): cycle verbs share ONE liveness authority \u00e2\u20ac\u201d the ALREADY_LIVE dup-guard liveness-probes the claimed session client tree before refusing (dead tree means reap + respawn honestly, never a refusal citing a zombie); the shutdown state machine consults the same source so is-it-live has one answer (no ALREADY_LIVE / list-OFFLINE / shutdown-NO_EDGE three-way contradiction on the same endpoint). Gate: impl \u00e2\u20ac\u201d probing dup-guard + unified authority; unit \u00e2\u20ac\u201d dead-tree claim probes and reaps, live claim still refuses; int \u00e2\u20ac\u201d controlled zombie (killed client tree, surviving hosted record) leads to endpoint run succeeding honestly end-to-end; doc \u00e2\u20ac\u201d ADR-0041.",
    "doc": "Decision: <!-- --> 1. **Online is earned, not declared.** A creator may stamp `status=online` only from actual persisted state + hosting authority \u00e2\u20ac\u201d never from manifest capability alone. Legacy hybrid rows self-heal at reconcile, but only after a SUCCESSFUL broker query: a broker failure is never interpreted as an empty session set (no mass-offline on a hiccup). 2. **Control cleanup splits from offline classification.** Reconcile clears `controlled`/`driven_by`/`viewer_count` for EVERY endpoint absent from session truth \u00e2\u20ac\u201d regardless of state or controllability \u00e2\u20ac\u201d while offline classification ke"
  },
  "REQ-ENDPOINT-LIST-MERGE-LOCAL": {
    "title": "`spt endpoint list` always merges this node's LOCAL (unadvertised) perches into the view; the `--local` flag is REMOVED (operator decision 2026-06-17). Rationale: `spt whoami` is a thin alias of `endpoint list` \u00e2\u20ac\u201d a just-online agent running `whoami` must see its OWN perch, or it gets an omitted-self view ('chaos'). FIX: drop the `--local` flag + its `--detail` conflict test + the v0.10.0 REQ-PICKER-5 hint line (cli.rs:1678) + cmd_list_local; the bare list merges local perches into the subnet view; fix the whoami alias path accordingly. Run `cargo run -p xtask -- gen` (docs-drift, DEFAULT target). (v0.12.1)",
    "doc": "**endpoint list always merges local perches**: <!-- --> `spt endpoint list` (and therefore `whoami`) **always** appends this node's **LOCAL perch roster** as a trailing section, in addition to the SELF pin and the subnet groups. The subnet groups are the WAN registry snapshot, which lags a just-bound perch by a pump cadence \u00e2\u20ac\u201d so without the merge a freshly-online endpoint (or the caller's own, under `whoami`) could be **absent** from its own listing, which reads as lost. The earlier `--local` flag (a separate this-node-only view) is **removed**: the local view is no longer a mode, it is uncond"
  },
  "REQ-ENDPOINT-LIST-RENDER-POLISH": {
    "title": "A6 (F028, operator, 4 asks): `spt endpoint list` render polish. (a) the 'Shared subnets' line is NOT dim \u00e2\u20ac\u201d LIGHT_GRAY = \"37\" (cli.rs:3019) is standard-palette WHITE, indistinguishable from row text; use SGR 90 (bright-black/gray) for the dim intent. (b) the `Total:` line takes the same dim color. (c) move the status glyph ADJACENT to the endpoint name (operator: 'right behind the endpoint name'), mirroring the picker's glyph-beside-name presentation (today the glyph sits at the end next to the status word). (d) color the status WORD like the picker TUI (green ONLINE / gray OFFLINE / blue when driven, matching picker glyph semantics). All in render_node_grouped/render_instance_row (cli.rs ~3000s); pure render with an injected color decision \u00e2\u20ac\u201d unit-testable off a tty. See triage A6.",
    "doc": ""
  },
  "REQ-ENDPOINT-ONLINE-TRUTH": {
    "title": "REGISTRY-LIFECYCLE W2 (ADR-0041 decisions 1+2, emphasys C1 P0): ONLINE is earned, not declared \u00e2\u20ac\u201d cmd_listen stamps status=online only from actual persisted state + hosting authority, never from manifest psyche_init capability alone (no more ready_agent/controllable=false hybrid rows born online-authoritative); livehost reconcile SPLITS control cleanup (clear controlled/driven_by/viewer_count for EVERY endpoint absent from session truth, regardless of state/controllable) from offline classification (live_agent+controllable gate unchanged); legacy hybrid rows self-heal after a SUCCESSFUL broker query only (broker failure is never interpreted as an empty session set); terminal signoff/owner-loss = atomic CAS-guarded offline + ready/address removal WITHOUT overloading soft api session-end (/clear preserves the live listener). Gate: impl \u00e2\u20ac\u201d creator gate + reconcile split + self-heal + terminal path; unit \u00e2\u20ac\u201d creator refuses capability-only online, cleanup clears stamps on state-quirk rows, broker-failure never mass-offlines; int \u00e2\u20ac\u201d dead-PID hybrid row does NOT survive a reconcile cycle (the immortal-row regression); doc \u00e2\u20ac\u201d ADR-0041.",
    "doc": "Decision: <!-- --> 1. **Online is earned, not declared.** A creator may stamp `status=online` only from actual persisted state + hosting authority \u00e2\u20ac\u201d never from manifest capability alone. Legacy hybrid rows self-heal at reconcile, but only after a SUCCESSFUL broker query: a broker failure is never interpreted as an empty session set (no mass-offline on a hiccup). 2. **Control cleanup splits from offline classification.** Reconcile clears `controlled`/`driven_by`/`viewer_count` for EVERY endpoint absent from session truth \u00e2\u20ac\u201d regardless of state or controllability \u00e2\u20ac\u201d while offline classification ke"
  },
  "REQ-ENDPOINT-STOP-OFFLINE": {
    "title": "H3: `spt endpoint stop <id>` marks the endpoint OFFLINE (alive=false), not merely de-readied. cmd_stop (cli.rs:2994-3010) removes the ready marker + unregisters the address but does NOT set status offline, so a stopped daemon-hosted endpoint still reports alive=true (status=online latch). FIX: add set_status(perch, STATUS_OFFLINE) to cmd_stop \u00e2\u20ac\u201d folds with B2 (same setter). Unit: stop \u00e2\u2020\u2019 is_perch_alive=false / alive=false. (v0.12.0)",
    "doc": ""
  },
  "REQ-ENDPOINT-UNBOUND-ATTACH": {
    "title": "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)",
    "doc": "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 \u00e2\u20ac\u201d e.g. the harness is waiting on a startup prompt). On-disk status `unbound` (spawn \u00e2\u2020\u2019 `unbound`; bind \u00e2\u2020\u2019 `online`; session death \u00e2\u2020\u2019 `offline`). An Unbound endpoint is **attachable** (a live PTY \u00e2\u20ac\u201d `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 *"
  },
  "REQ-EP-1": {
    "title": "Day-one endpoint types; open type system",
    "doc": ""
  },
  "REQ-EP-2": {
    "title": "Agent endpoints vs Shells distinction in the type model",
    "doc": ""
  },
  "REQ-EP-5": {
    "title": "Concrete shell instantiation model: spawn-mints-instance (vs relink/online), registered-on-node permission + broadcast-is-discovery, per-shell require_approval gate, max_instances_per_owner + over_cap, instance aliasing, discovery scope",
    "doc": ""
  },
  "REQ-EP-6": {
    "title": "Gateway type acceptance: a Gateway-typed perch binds (api bind --type, open type system \u00e2\u20ac\u201d un-hardcode the live_agent default), advertises/addressable like any endpoint, owns shells (owner validation not agent-family-gated), subscribes to digests, and is the user-msg identity gate's user-backed origin (REQ-MSG-5); in-tree mock-gateway fixture (R-DOCS-2 pattern, no downstream adapter code). Cross-node WAN Gateway-origin (registry endpoint_type trust) tracked by REQ-MSG-6",
    "doc": "Endpoint types: <!-- --> **Gateway** (concept ratified 2026-06-11; registered via the open type system, first instance downstream): A **human-backed endpoint** \u00e2\u20ac\u201d a user's specialized window into the subnet from a device or surface with no conventional-harness compatibility. Nothing LLM-shaped runs there; the intelligence at the endpoint is the **user**. Addressable like any endpoint (receives digests/messages, sends via the normal verbs) and may **own Shells** (it is an owning endpoint \u00e2\u20ac\u201d see \u00c2\u00a7Shell model). Distinct from a Shell: a Shell is *driven from elsewhere*; a Gateway *originates* intera"
  },
  "REQ-EP-7": {
    "title": "Durable live-role.md: a per-agent broad-purpose statement in tracked/agents/<id>/ beside live-context.md (replicates with the mind on the same a-<id> branch); renders FIRST at start-transition context injection (role -> live-context -> project-context); SOLE writer `spt endpoint role --overwrite <file>` \u00e2\u20ac\u201d mechanical no-automated-writer guarantee (echo-commune ingest / signoff / Psyche reconcile structurally exclude it). The user-backed-origin hard gate on the writer is a deferred later tightening (rides the user-msg identity plumbing)",
    "doc": "Cross-node Psyche sync: <!-- --> **live role** (`live-role.md`, ratified 2026-06-12 \u00e2\u20ac\u201d core milestone A): A durable statement of an agent's **broad purpose** \u00e2\u20ac\u201d rarely modified, and only at deliberate user instruction. Lives in `tracked/` (the mind) beside `live-context.md`, so it replicates with the mind and follows the agent across nodes. At start-transition context injection it renders **first** (role, then live context, then project context). The guarantee is **mechanical**: no automated writer exists \u00e2\u20ac\u201d Psyche reconcile, echo-communes, and signoff structurally never touch it; the sole writer"
  },
  "REQ-GOSSIP-CONTROLLED-ANY": {
    "title": "Bug #3: a locally-controlled endpoint gossips controller_node = None so remote viewers show it free to control. Root: driven_by is stamped Some(node) only for a REMOTE WAN attach (attach.rs:337); a local controller is by=None by design (broker.rs:1750, KH 7.15 \u00e2\u20ac\u201d a local-only controller must not latch driven_by). Fix: broker stamps a SEPARATE any-controller datum (true/Some(host) for a local OR remote controller) alongside stamp_driven_by, and advertise_local gossips Instance controller_node from it, leaving the remote-only driven_by untouched (do not trip REQ-HAZARD-DRIVEN-BY-SELFHEAL). node-refresh is NOT the fix (data is absent at source). See docs/NEXT-MILESTONE-BUG-TRIAGE.md #3.",
    "doc": ""
  },
  "REQ-HAZARD-ADAPTER-APPLY-SILENT-NOOP": {
    "title": "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` \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d 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",
    "doc": "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 \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d `LIVE` then `DONE` printed, but the v"
  },
  "REQ-HAZARD-BIND-REST-STATE-CARRY": {
    "title": "GATEWAY-LIVENESS DEFECT A (flynn field bug 2026-07-09, confirmed independent): a re-bind MUST preserve the daemon-owned resting intent (rest_state, D9-2/REQ-INST-3) \u00e2\u20ac\u201d the same carry-forward discipline establish_perch already applies to cwd/controllable/adapter/read_env. ROOT: establish_perch's record build (crates/spt/src/api/startup.rs, the build closure) constructs a fresh InfoJson via InfoJson::new (defaults rest_state None) and carries cwd/controllable/read_env forward from prior but NOT rest_state -> a re-bind WIPES the wake intent (flynn tick11 rest_state:active vanish). FIX: carry prior.rest_state (and its paired dormant_since_ms anchor, present iff dormant) forward on re-bind, like the sibling fields. Gate: a re-bind over a prior record with rest_state set preserves it (unit \u00e2\u20ac\u201d the build closure carries rest_state + dormant_since_ms). KNOWN-HAZARDS entry on landing. Kin REQ-HAZARD-BIND-CWD-UNSET / REQ-PICKER-1 + REQ-INST-3.",
    "doc": ""
  },
  "REQ-HAZARD-BRAIN-RESPAWN-PATH": {
    "title": "The broker respawns the brain onto the APPLIED bytes, not the renamed old binary: the candidate-binary default is the canonical exe path captured ONCE at broker start, never a per-spawn std::env::current_exe() \u00e2\u20ac\u201d on Linux current_exe (readlink /proc/self/exe) is inode-tracking and follows the `apply` rename (spt -> spt.old-N), so a resident broker would respawn the brain onto OLD bytes while recording `applied` (Windows GetModuleFileName is path-at-start, so Windows was green; ADR-0018 Q3 silently assumed path-string semantics). Backstop: promotion gates on bytes \u00e2\u20ac\u201d a trial promotes only if brain.ready exe_hash == the staged artifact hash for this platform, else auto-rollback + loud notif (readiness != new-bytes was the false-success that recorded applied:8 over a v0.4.0 brain on kitsubito, 2026-06-11). KNOWN-HAZARDS 6.11.",
    "doc": "6.10 Phase-significant loop timing must be a durable absolute-deadline grid, not phase-relative sleep `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]`: ### 6.11 Brain respawn must exec the APPLIED bytes, not the renamed old binary (Linux `current_exe` follows the apply-rename; readiness \u00e2\u2030\u00a0 new-bytes) `[REQ-HAZARD-BRAIN-RESPAWN-PATH]` - **Failure:** the broker respawns the brain candidate from `std::env::current_exe()` resolved **per spawn** (`brainproc.rs:817`). `spt update apply` swaps the binary by renaming the running file `spt` \u00e2\u2020\u2019 `spt.old-N` and writing the new bytes at `spt`. On **Linux**, `current"
  },
  "REQ-HAZARD-BRAIN-RESTART-LIFECYCLE-REHYDRATE": {
    "title": "B4 (deepest): a bare brain restart (broker survives) REHYDRATES the live-agent lifecycle so post-restart endpoints are hosted + attachable. Today resume_sessions (brainproc.rs:186, brain.rs:797-809) re-subscribes to the broker's PTY sessions but ALL BrainLifecycle instances (lifecycle.rs:58-130; the ephemeral brain.rs:254-275) are LOST on restart \u00e2\u2020\u2019 a post-restart live endpoint gets no livehost \u00e2\u2020\u2019 its Psyche is never (re)hosted and new spawns die / can't attach until a FULL daemon reset (operator: perri's brain kill+restart wedged everything until a full daemon kill). FIX: on brain startup, rebuild a BrainLifecycle per resumed live-capable session \u00e2\u20ac\u201d load the manifest from the adapter registry \u00e2\u2020\u2019 instantiate \u00e2\u2020\u2019 start the pulse \u00e2\u20ac\u201d the rehydrate the resume no-op cannot do. Composes with B2 (the reconcile re-hosts from the honest on-disk status after rehydrate). (v0.12.0)",
    "doc": ""
  },
  "REQ-HAZARD-BRAIN-RESTART-PSYCHE-DUP": {
    "title": "A bare brain restart leaves EXACTLY ONE `{id}-psyche` process per endpoint \u00e2\u20ac\u201d no duplicate. On an abrupt brain death stop_host never runs (the LiveSet + owned child handles die with the brain) and Breap's job/group only reaps at DAEMON stop, so the PRIOR brain's Psyche stays ALIVE; the respawned brain's reconcile re-hosts a SECOND Psyche and overwrites the `{id}-psyche` perch pid, leaving the old one untracked + alive = a duplicate that lingers until daemon-stop (the operator's 'brain kill+restart wedged everything'). FIX: at brain start, BEFORE the first reconcile re-hosts, reap any pre-existing `{id}-psyche` orphan \u00e2\u20ac\u201d ID-SPECIFICALLY (recycle-safe on the shared box, where sibling agents share the `claude` basename): scoped-kill the recorded pid ONLY IF it is alive AND its exe basename == the adapter's psyche program (normalize_basename) AND its COMMAND LINE contains the full psyche id `<id>-psyche` (baked via {id}); a sibling never carries THIS id, and any unreadable signal FAILS SAFE (decline to reap \u00e2\u20ac\u201d a missed dup is bounded by Breap, a wrong-kill is catastrophic). CAVEAT: the cmdline carries `<id>-psyche` only when the adapter's psyche_init.command uses {id} (the norm); a non-{i",
    "doc": ""
  },
  "REQ-HAZARD-BROKER-QUIC-DEADLINE": {
    "title": "The broker bounds every brain-waiting QUIC op (dial / open_stream / send_stream) so a black-holed or dead peer fails PROMPTLY with an ORDINARY error the broker REPLIES, never an unbounded await. The bound (< the brain's 30s PUMP_PEER_IO_TIMEOUT so the BROKER fires first) surfaces to the pump as a normal broker error reply \u00e2\u2020\u2019 peer_outcome's non-TimedOut arm \u00e2\u2020\u2019 drop conn + redial next tick, the round CONTINUES and the heartbeat keeps advancing \u00e2\u20ac\u201d it must NEVER manifest as the brain's own read-deadline (the A-half poison \u00e2\u2020\u2019 supervised-restart path REQ-HAZARD-PUMP-IPC-DEADLINE guards). Exactly-once is preserved: a timed-out journaled op fails INSIDE its apply_once closure so no phantom conn_id/stream_id is recorded and a fresh tick re-dials cleanly. The happy path is unchanged (a live peer completes with zero added latency; the bound only bites a non-responsive peer). This is the ROOT-cause cure for the 2.2h hfenduleam pump wedge \u00e2\u20ac\u201d a dead roster peer whose QUIC path the broker awaited unbounded \u00e2\u20ac\u201d recurring on hfenduleam 2026-06-16.",
    "doc": "7.7 A slow/dead/hostile remote VIEWER must never stall the controller, child, or drain `[REQ-HAZARD-VIEWER-ISOLATION]`: ### 7.8 The broker must never make a brain wait UNBOUNDED on a QUIC op (the pump-IPC-deadline B-half) `[REQ-HAZARD-BROKER-QUIC-DEADLINE]` <!-- --> - **Failure:** the broker's brain-facing QUIC handlers (`dispatch_net_dial` / `dispatch_net_stream_open` / `dispatch_net_stream_send`) call into `NetHost::dial` / `open_stream` / `send_stream`, whose iroh awaits (`endpoint.connect` + `prove_membership`; `open_bi`; `write_all`/`finish`) had NO bound of their own. A dead/black-holed"
  },
  "REQ-HAZARD-BROKER-SEED-WIRE-SKEW": {
    "title": "A daemon-state wire-format change (e.g. the v0.9.0 adapter-agnostic Seed) does NOT take effect until a DELIBERATE full broker restart: the broker serves the seed-control channel and is RESIDENT across a brain-only self-update (ADR-0004 no-terminate-during-update forbids auto-killing it), so a NEW-version CLI talking to a still-resident OLD broker fails the seed handshake \u00e2\u20ac\u201d the old broker cannot deserialize the new Seed (its formerly-required `adapter` field is gone) and drops the conn without an ack, which surfaces to the CLI as a raw UnexpectedEof 'failed to fill whole buffer'. spt-core must (a) surface an ACTIONABLE diagnostic on that seed-ack EOF (name the stale-broker cause + the `spt daemon stop` fix \u00e2\u20ac\u201d the broker restarts on the next api call), never the cryptic io error; and (b) document the operational rule (a deliberate broker restart is required on any daemon-state wire change \u00e2\u20ac\u201d NOT automatic) + the FORWARD discipline (daemon-state/Seed schema changes stay additive + serde-default so a resident OLD broker tolerates a NEW CLI across a brain-only update; note this would NOT have rescued 0.9.0 itself, since the old broker's `adapter` was a required field). perri PREP-4 FINDIN",
    "doc": "7.8 The broker must never make a brain wait UNBOUNDED on a QUIC op (the pump-IPC-deadline B-half) `[REQ-HAZARD-BROKER-QUIC-DEADLINE]`: ### 7.9 A daemon-state wire change needs a deliberate BROKER restart (the broker is resident across a brain self-update) `[REQ-HAZARD-BROKER-SEED-WIRE-SKEW]` <!-- --> - **Failure:** the broker serves the seed-control channel and is RESIDENT across a brain-only self-update (ADR-0004's no-terminate-during-update pillar forbids auto-killing it \u00e2\u20ac\u201d 6.7). A self-update that changes a daemon-state WIRE FORMAT \u00e2\u20ac\u201d e.g. the v0.9.0 adapter-agnostic `Seed` (the `adapter` fie"
  },
  "REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE": {
    "title": "W2 (LIFECYCLE-TRUTH, KNOWN-HAZARDS, flagship \u00e2\u20ac\u201d the update wedge): PTY viewer fan-out and control mutations must not depend synchronously on a live draining brain. ROOT rig-CONFIRMED (NtSuspendProcess on the brain, no update involved): brain-subscriber session-output writes ride UNDER the per-session log lock (broker.rs:19-20); failed writes are handled (cursor freeze + detach :3486) but BLOCKED writes are not. Suspended brain => within seconds attached rc output freezes; detach does NOT release the control stamp (release routes through the brain); reattach REFUSED (controlled-by); rc --take hangs; daemon status stays healthy. Field: every brain cycle (incl. every update apply) has a freeze window; a stalled/slow-draining new brain = permanent wedge until bounce; brain.ready != subscribers drained. FIX SHAPE (todlando proposes, doyle RULES BEFORE IMPL): subscriber writes move OFF the log lock (bounded/nonblocking, stall => detach-subscriber like viewer eviction \u00e2\u20ac\u201d broker already buffers + replays on re-attach, so a detached-stalled brain self-heals by rewind); control stamp release/take completes against the BROKER without brain round-trip (or bounded with loud timeout). doc = KNOWN-",
    "doc": "7.35 The cached ceremony-clock NTP offset must NOT survive an OS clock STEP \u00e2\u20ac\u201d an offset measured against the pre-step clock strands every pairing for the TTL `[REQ-HAZARD-CEREMONY-CLOCK-STEP]`: ### 7.36 The broker control plane and PTY fan-out must NEVER block on a single subscriber connection \u00e2\u20ac\u201d a suspended brain conn must not wedge control `[REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]` <!-- --> - **Failure (paid-for, rig-CONFIRMED 2026-07-06/07 \u00e2\u20ac\u201d `NtSuspendProcess` on the brain, no update involved):** a controller's writer thread does a BLOCKING socket write to its brain subscriber conn. When th"
  },
  "REQ-HAZARD-CASCADE-WIPE-GUARD": {
    "title": "No hard-delete of a parent hosting non-empty children (6.3)",
    "doc": ""
  },
  "REQ-HAZARD-CHILD-CONSOLE-FLASH": {
    "title": "Console-subsystem children of the console-less daemon spawn with CREATE_NO_WINDOW, or each spawn flashes a visible blank window on the user's desktop (5.8)",
    "doc": "5.7 Elevated commands spawn the daemon with the wrong token `[REQ-HAZARD-ELEVATED-DAEMON-SPAWN]`: <!-- --> ### 5.8 Console children of the console-less daemon flash visible windows `[REQ-HAZARD-CHILD-CONSOLE-FLASH]` - **Failure:** the daemon runs DETACHED (no console, 5.6/`detached_no_inherit`). Any console-subsystem child it spawns (`git`, `taskkill`, manifest hook commands) gets a **fresh conhost with a visible window** \u00e2\u20ac\u201d piped/null stdio does NOT prevent it. Field shape: the 60s sync pump's two git calls (`for-each-ref` + `rev-parse`) flashed two blank windows per minute on the user's deskt"
  },
  "REQ-HAZARD-CONPTY-DSR": {
    "title": "ConPTY reader must auto-answer DSR (ESC[6n) or all child output stalls (5.5)",
    "doc": ""
  },
  "REQ-HAZARD-CONTROL-STAMP-CONVERGENCE": {
    "title": "Control/viewer stamps must CONVERGE to broker session-table truth for every session-backed endpoint, not merely edge-trigger \u00e2\u20ac\u201d the UPWARD companion to the DOWNWARD edge-clear REQ-HAZARD-CONTROL-STAMP-LIFETIME (7.27); same 'stamps == broker truth' family. ROOT (F-026 stamp-gap, hall-b/ball-b): a picker-created endpoint's broker spawn become_controller->stamp_driven_by->set_controlled(true) fires BEFORE the adapter binds its perch (a fresh endpoint has no perch until claude boots + binds), so mutate_info returns NotFound and the edge stamp is SWALLOWED (let _); the adapter's bind then writes InfoJson::new with controlled:false DEFAULT and no later edge re-stamps -> the endpoint reads uncontrolled FOREVER while driven (the #3 display fix is correct but datum-starved on this creation path). FIX: the broker (SINGLE WRITER) re-asserts each live session's control/viewer stamps to session-table truth, DIVERGENCE-GATED (read info; compare driven_by/controlled/viewer_count; write ONLY on diff \u00e2\u20ac\u201d no per-poll fsync storm), on the KIND_SESSIONS handler (piggyback: the daemon reconcile + picker poll it, so a fresh perch converges within one reconcile-poll window after bind = the BOUNDED window, n",
    "doc": "7.28 A relative manifest path resolves against the ENDPOINT, never the daemon `[REQ-STORE-CONTEXT-BRANCH-FILL]`: <!-- --> ### 7.29 Control/viewer stamps CONVERGE to broker session-table truth, not merely edge-trigger `[REQ-HAZARD-CONTROL-STAMP-CONVERGENCE]` - **Failure (F-026 stamp-gap, hall-b + the original ball-b):** a picker-created endpoint (`endpoint run` \u00e2\u2020\u2019 new) read plain `ONLINE` in the list + picker while genuinely driven \u00e2\u20ac\u201d `info.json` `controlled:false` throughout. ROOT (the UPWARD companion to 7.27's downward edge-clear): the broker spawn path's `become_controller` \u00e2\u2020\u2019 `stamp_driven_by"
  },
  "REQ-HAZARD-CONTROL-STAMP-LIFETIME": {
    "title": "#2: a control/viewer stamp never outlives its session \u00e2\u20ac\u201d every teardown path clears what attach stamped. The broker exit-waiter (broker.rs ~:1844) sends the exit frame + sessions.remove(&id) but does NOT clear the perch's controller/viewer stamps; clear_controller()->stamp_driven_by() (clears driven_by+controlled) runs ONLY on controller-detach/evict/displace. /exit kills the CHILD not the controller conn, so the OutputLog drops with controlled:true, viewer_count, (and driven_by for a remote controller) latched in info.json forever \u00e2\u20ac\u201d and hfenduleam keeps gossiping controller_node=self cross-node. Fix: on session reap, clear the perch's controller/viewer stamps (set_driven_by(None)+set_controlled(false)+set_viewer_count(0) via the known endpoint id) \u00e2\u20ac\u201d broker stays the single writer. KNOWN-HAZARDS invariant. See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #2.",
    "doc": "7.26 Concurrent first-touch of ONE fresh BranchStore must ALL succeed \u00e2\u20ac\u201d a non-atomic `git init` race must never strand a first-toucher `[REQ-HAZARD-STORE-INIT-RACE]`: <!-- --> ### 7.27 A control/viewer stamp must NEVER outlive its session \u00e2\u20ac\u201d every teardown path clears what attach stamped `[REQ-HAZARD-CONTROL-STAMP-LIFETIME]` - **Failure (F-026 #2, live evidence HFENDULEAM):** an spt-hosted endpoint stayed `ONLINE+CONTROLLED` after the operator's RC `/exit` \u00e2\u20ac\u201d hours later hall-a's `info.json` still read `controlled:true` (status offline, dormant) and hfenduleam still GOSSIPED `controller_node=sel"
  },
  "REQ-HAZARD-CONTROLLER-GAP-RESUME": {
    "title": "A serving CONTROLLER whose serve-brain hits a b4 drop-don't-block FORWARD output gap must RESUME-FROM-FLOOR (re-subscribe from delivered_through and re-fetch the dropped frames from the ring), NOT snap-above and NOT fatal. ROOT (v0.13.0 forkpty re-run, post-keystone): b4 made the controller a non-blocking try_send that DROPS frames when its bounded channel fills (a controller that falls behind its OWN echo under a hard flood), so the next read is a forward gap the strict reject-gap (brain.rs:624/628, B2 exactly-once) FATALS \u00e2\u20ac\u201d wedged_viewer_does_not_stall_controller (attach.rs:1048) drove ctrl.read_event() raw and fataled on `output gap got 6134 want 4643`. Pre-b4 the inline sleep-poll BLOCKED the drain to the controller's rate (no drops, no gaps); this is a b4 SIDE-EFFECT, not a new class. A controller CANNOT snap (it is authoritative \u00e2\u20ac\u201d advances delivered_through; skipping rolled frames = not-exactly-once = B2 violation), so REQ-HAZARD-VIEWER-RING-ROLL-SNAP does NOT apply. B2 INVARIANT (doyle, broker.rs:327-330): the ring trim is delivered_through-BLIND (`while ring.len() > cap_chunks { pop_front() }`), so re-fetch is exactly-once IFF tail - delivered_through <= cap_chunks (4096) \u00e2\u20ac\u201d",
    "doc": ""
  },
  "REQ-HAZARD-CONTROLLER-LEASE": {
    "title": "RC-RENDER-TRUTH W2 (KNOWN-HAZARDS 7.48 \u00e2\u20ac\u201d umbrella conformance seam for ADR-0044): at most one input-capable controller lease per PTY session; takeover revokes atomically and loudly; input is fenced to the active lease; node identity is never a lease. The full hertz 8-step deterministic two-loopback-client broker regression rides verbatim: A subscribes Control from node N and controls; B subscribes Take from the SAME node with a different lease; A receives Displaced{by:N} then terminal stream completion (rc exits via existing PumpEnd::Displaced); output post-takeover reaches B not A; A's Input+Resize post-takeover mutate nothing; B's both apply; controlled/driven_by metadata identifies B with exactly one controller slot; a separate equal-lease/equal-generation replay test proves genuine dispatcher recovery remains silent and never self-displaces. Gate: int \u00e2\u20ac\u201d the matrix; doc \u00e2\u20ac\u201d KNOWN-HAZARDS 7.48.",
    "doc": "7.47 The physical terminal is never mutated or terminated outside its renderer's ordered state model \u00e2\u20ac\u201d output before exit, owned baselines, unconditional teardown `[REQ-HAZARD-RENDER-LIFECYCLE]`: ### 7.48 At most one input-capable controller lease per PTY session \u00e2\u20ac\u201d takeover revokes atomically and loudly, input is fenced to the active lease, node identity is never a lease `[REQ-HAZARD-CONTROLLER-LEASE]` <!-- --> - **Failure (paid-for, hertz same-machine `--take` RCA, field repro 2026-07-16):** terminal A controlled an endpoint; terminal B on the SAME machine ran `spt rc --take`. Local loopback"
  },
  "REQ-HAZARD-DAEMON-STOP-BARRIER": {
    "title": "B3: `spt daemon stop` then an immediate `spt daemon start` does NOT race \u00e2\u20ac\u201d stop fully completes before it returns. Today request_stop (seedmap.rs:240-255) returns on the KIND_STOPPING ack (sent seedmap.rs:174-176) BEFORE the seed socket unbinds, so a following is_running ping (daemon.rs:375) wins the exit window and start reports ALREADY_RUNNING (operator: daemon stop \u00e2\u2020\u2019 STOPPED then start \u00e2\u2020\u2019 ALREADY_RUNNING). FIX: unbind/stop-gate the seed socket BEFORE acking KIND_STOPPING, OR request_stop waits for a ping-to-fail before returning. Unit: stop then immediate is_running()==false. (v0.12.0)",
    "doc": ""
  },
  "REQ-HAZARD-DEFERRED-DRAIN": {
    "title": "Deferred spool rows excluded from the event-stream drain (1.4)",
    "doc": ""
  },
  "REQ-HAZARD-DEFERRED-MANIFEST": {
    "title": "A pointer-mode (delegated / GhReleaseManaged) adapter whose binary/manifest is not yet extracted is reported with a CLEAR diagnostic, never silently dropped. Today such an adapter reads its manifest LIVE from source_dir (registry.rs manifest_dir ~146/149); a deferred / un-extracted install makes load_manifest fail \u00e2\u2020\u2019 registered() (~410, filter_map(.ok())) SILENTLY DROPS the row \u00e2\u2020\u2019 downstream ADAPTER_UNRESOLVED + a cryptic os-error-2 on `spt adapter use`. FIX: surface a clear diagnostic at the resolver + at `adapter use` (name the adapter + the deferred/missing-manifest cause + the fix), not a silent filter-drop and not a bare os-error-2; consider an eager manifest copy at register time so host_binaries survive before the binary download completes. doyle Finding A. (post-v0.10.0)",
    "doc": ""
  },
  "REQ-HAZARD-DEFERRED-SURVIVE-DRAIN": {
    "title": "Deferred rows survive poll drain (4.4)",
    "doc": ""
  },
  "REQ-HAZARD-DELIVERY-STARVATION": {
    "title": "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 \u00e2\u20ac\u201d the daemon itself drives delivery on the events it owns (WAN ingress + the ACTIVE\u00e2\u2020\u2019IDLE 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)",
    "doc": "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 \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d WAN-arrived, or locally spooled-while-active \u00e2\u20ac\u201d stranded INDEFINITELY on an spt-hosted,"
  },
  "REQ-HAZARD-DETACHED-DAEMON-STDIO": {
    "title": "A daemon DETACHED-IN-FACT (no interactive console, or an inherited stderr PIPE nobody drains) that never nulled its std handles will BLOCK on stdio writes when the pipe fills, and/or pop a visible conhost window (REQ-HAZARD-WMI-DAEMON-WINDOW is a covered surface of this hazard). detach_console nulls the 3 handles only under the --detached flag; a rung that omits it (the bare line-82 elevated->deelevated respawn; a STALE installer at-logon task registered as bare `daemon run`, confirmed live field-drift on ENLYZEAM) is exposed. FIX: (load-bearing) inside `daemon run`, null the 3 std handles when stderr GetFileType==FILE_TYPE_PIPE \u00e2\u20ac\u201d a pipe is the ONLY std sink that BLOCKS the daemon when it fills; catches every rung whose inherited stderr is an undrained pipe, independent of whether each caller passed --detached, while a FILE (2>run.log AND every int-test Stdio::from(file) brain-log capture), a CONSOLE (scrolls), and a NULL/absent handle (DETACHED_PROCESS rungs, already discard) all SURVIVE. DELIBERATELY NOT gated on GetConsoleWindow==NULL: a CREATE_NO_WINDOW daemon has no console window yet a drained FILE stderr \u00e2\u20ac\u201d nulling it would blank the capture for ZERO safety gain (a file never",
    "doc": ""
  },
  "REQ-HAZARD-DIRECT-WRITE-PRECEDENCE": {
    "title": "Direct-write precedence marker (with node id) guards stale overwrite (6.5)",
    "doc": ""
  },
  "REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT": {
    "title": "An spt-hosted endpoint driven by a REMOTE controller whose remote is gone but whose broker connection stays OPEN (a wedged/lost pump that never delivers the detach) AND whose session is IDLE (no output) stays latched ONLINE+CONTROLLED forever: the W1 drain-evict only fires on OUTPUT (CONTROLLER_WRITE_DEADLINE on a backed-up write), a clean disconnect self-heals via detach_if\u00e2\u2020\u2019clear_controller, but an idle session with a half-open/wedged controller connection produces neither signal. PROVED repro-first on a real broker (v0.13.0 W5, inject_control_wedge.rs w5_a2): controller_by STAYS Some(origin) and driven_by STAYS Some after the remote is abandoned without a clean EOF on an idle session \u00e2\u20ac\u201d so the brain reconcile CANNOT detect it from KIND_SESSIONS controller_by (the broker still reports it controlled). FIX DIRECTION (doyle ruling 2026-06-19, broker-side single-writer \u00e2\u20ac\u201d the broker owns driven_by/clear_controller): wire the EXISTING D4c NetPresence connection-disconnect event \u00e2\u2020\u2019 clear_controller for any session whose controller identity == the dead origin (become_controller already stores Some(origin); presence events already exist \u00e2\u20ac\u201d modest wiring, NOT a new probe). The liveness ORACLE ",
    "doc": "Amendment 1 (2026-07-22, DAEMON-LIFECYCLE W2) \u00e2\u20ac\u201d teardown authority is opener-declared class PLUS transport liveness, enforced at the three places decision 6 could not see: <!-- --> <!-- -->"
  },
  "REQ-HAZARD-DRIVEN-BY-SELFHEAL": {
    "title": "An spt-hosted endpoint's ONLINE+CONTROLLED state (`driven_by`) must CLEAR even when the detach IPC is lost \u00e2\u20ac\u201d 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)",
    "doc": ""
  },
  "REQ-HAZARD-DROP-FILE-SINGLE-WRITER": {
    "title": "Drop files are daemon-owned single-writer (6.4)",
    "doc": ""
  },
  "REQ-HAZARD-EBUSY-RENAME": {
    "title": "tmp-write + atomic-rename + retry on Windows EBUSY (5.2)",
    "doc": ""
  },
  "REQ-HAZARD-ECHO-BEFORE-SIGNOFF": {
    "title": "Echo-commune fires before INIT_SIGNOFF on orphan teardown (3.3)",
    "doc": ""
  },
  "REQ-HAZARD-ELEVATED-DAEMON-SPAWN": {
    "title": "The daemon always runs unelevated in the invoking user's universe, regardless of which command spawns it: an elevated spawner de-elevates (Windows: UAC linked token via CreateProcessWithTokenW; Linux: drop to SUDO_UID/SUDO_GID + the invoker's HOME) \u00e2\u20ac\u201d an elevated daemon's pipes deny unelevated clients (every later spt reads not-running\u00e2\u2020\u2019spawn\u00e2\u2020\u2019bind Access-denied) and a sudo'd daemon roots the user's state universe (5.7)",
    "doc": "5.6 Windows detached children inherit a captured caller's pipe `[REQ-HAZARD-DETACHED-PIPE-INHERIT]`: ### 5.7 Elevated commands spawn the daemon with the wrong token `[REQ-HAZARD-ELEVATED-DAEMON-SPAWN]` <!-- --> - **Failure:** membership-implies-reachability made *every* `spt` invocation a potential daemon spawner (`ensure_running`), including the elevation-gated ones (`subnet create`/`join`, REQ-SUBNET-4). The spawned daemon inherits the spawner's token. **Windows:** an elevated `subnet create` auto-starts an ELEVATED daemon whose named pipes deny unelevated clients \u00e2\u20ac\u201d every subsequent unelevat"
  },
  "REQ-HAZARD-ENVELOPE-CR-LINESAFE": {
    "title": "Envelope CR-linesafety (4.1): the line-framed EVENT codec must neutralize raw carriage returns \u00e2\u20ac\u201d `event_body_escape` folds CRLF/lone-CR to the codec's representable linebreak (`\\n`\u00e2\u2020\u2019`<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\u00e2\u2020\u2019col0 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`).",
    "doc": "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 \u00e2\u20ac\u201d content that needs CRs preserved does not fit this codec."
  },
  "REQ-HAZARD-ENVELOPE-PARSER-SAFE": {
    "title": "Two-slice envelope parser is panic-free and tolerant (4.2)",
    "doc": ""
  },
  "REQ-HAZARD-EPHEMERAL-CLEANUP": {
    "title": "Ephemeral perch cleanup on every ring exit path (3.1)",
    "doc": ""
  },
  "REQ-HAZARD-GRACE-BEFORE-SIGNOFF": {
    "title": "Grace-period wait completes before composing INIT_SIGNOFF (1.1)",
    "doc": ""
  },
  "REQ-HAZARD-HANDOFF-ARGV-COMPAT": {
    "title": "Broker/brain IPC + handoff argv version-tolerant (2.3)",
    "doc": ""
  },
  "REQ-HAZARD-HOSTED-LIVENESS-RECONCILE": {
    "title": "B2 KEYSTONE: a daemon-hosted (spt-hosted) endpoint's info.json status is RECONCILED to real liveness, not left latched online. The broker exit-waiter (broker.rs:889-910) reaps its in-mem session table + emits ExitEvent but NEVER touches info.json; lifecycle::mark_offline only fires on Psyche teardown \u00e2\u20ac\u201d so a dead/exited harness (operator closed the tab) stays status=online forever (is_perch_alive returns ONLINE for daemon-hosted, liveness.rs:80-93). FIX (doyle ruled PULL-PRIMARY \u00e2\u20ac\u201d the live-status analog of REQ-HAZARD-ROSTER-GHOST): the livehost reconcile loop (reconcile_once livehost.rs:226-313) queries the broker's live session set (KIND_SESSIONS) each tick and, for any status=online live_agent perch PAST the boot grace whose endpoint has NO live broker session, marks it offline (lifecycle::mark_offline \u00e2\u2020\u2019 status=offline \u00e2\u2020\u2019 is_perch_alive=false). GATED on spt-hosted (controllable==Some(true)) so a HARNESS-HOSTED relay live agent (api listen, legitimately online with no broker session) is NEVER mis-marked. Crash-robust + self-healing on the next tick (clear-on-event is not crash-robust alone). PUSH (brain ExitEvent\u00e2\u2020\u2019mark_offline) is an OPTIONAL fast-path only if the daemon brain is rel",
    "doc": ""
  },
  "REQ-HAZARD-INBOX-NO-DOUBLE": {
    "title": "No double-delivery via legacy inbox (4.5)",
    "doc": ""
  },
  "REQ-HAZARD-LIVEHOST-BOOT-RACE": {
    "title": "The brain's daemon-hosted Psyche lifecycle surfaces a host-FAILURE on the live perch (harness-diagnosable) and runs net-INDEPENDENTLY. When reconcile_once\u00e2\u2020\u2019host_one\u00e2\u2020\u2019spawn_psyche fails for a state=live_agent+status=online endpoint (e.g. the adapter's psyche binary absent from its install dir, REQ-INSTALL-11), the failure MUST be written to the perch info.json as a CURRENT-STATE field (reason + ts + attempt count; overwritten each 5s retry, CLEARED on successful host) and surfaced by `spt endpoint list`/status \u00e2\u20ac\u201d never left as an eprintln on the brain's invisible stderr where a harness reading only perch state is blind. status=online stays authoritative (agent reachable; only the Psyche is missing \u00e2\u20ac\u201d brain-restart rehydrate legitimately has online-without-Psyche windows), so this is a SEPARATE psyche-host-health field, never a status de-stamp. Net-independence is a locked-in invariant: spawn_live_host (brainproc.rs:230) reaches the reconcile and hosts the Psyche on a net-less/unpaired/peer-pump-STALLED node, proven by a REAL detached-daemon E2E (real broker\u00e2\u2020\u2019brain-child, real api seed+listen, real install-dir psyche binary). spt-core SURFACES the failure; the adapter owns fixing its pack",
    "doc": ""
  },
  "REQ-HAZARD-LOCAL-API-AUTH": {
    "title": "Every local `api` mutation authenticated to an endpoint/session (codex #13)",
    "doc": ""
  },
  "REQ-HAZARD-PAIR-SEED-ROTATION": {
    "title": "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)",
    "doc": ""
  },
  "REQ-HAZARD-PSYCHE-OUTBOUND-PROXY": {
    "title": "Psyche outbound captured + sanitized: the live-Psyche turn driver captures stdout (never Stdio::null), and the daemon strips/re-stamps Psyche-supplied from=/target and constrains routing (reply\u00e2\u2020\u2019__REPLY_TO__ sender, notify\u00e2\u2020\u2019own user/subnet) (7.3)",
    "doc": ""
  },
  "REQ-HAZARD-RC-ATTACH-FAILFAST": {
    "title": "B1: `spt rc <id>` to a DEAD or non-streaming session fails fast with a clear message, never an INFINITE blank screen. Today rc.rs run_attach (209-231) + pump spawns PUMP_IPC_READER and blocks: the poll times out each slice but the stream never produces output, so the operator sees a permanent blank (operator: fresh wall-f attached, closed tab, then `spt rc wall-f` HUNG \u00e2\u20ac\u201d the broker still resolved a session for it). FIX: (a) once B2 lands, gate attach on is_online/status \u00e2\u20ac\u201d an offline endpoint yields a clean 'endpoint offline, start it' not an attach; (b) fail-fast \u00e2\u20ac\u201d if the attach-open ack / first output does not arrive within a bound, surface a clear message, never an infinite blank; (c) the broker EOFs the attach stream when the session's child is dead, so rc's existing PumpEnd::BrokerGone graceful path (REQ-HAZARD-RC-EOF) catches it. PIN the exact sub-mechanism with a repro test FIRST (dead-session-lingers-in-broker vs reaped-but-rc-waits vs alive-resting-no-wake \u00e2\u20ac\u201d the wall-f Windows tab-close: child alive-silent vs dead-not-reaped). (v0.12.0)",
    "doc": ""
  },
  "REQ-HAZARD-RC-ATTACH-TRUTH": {
    "title": "RC-RENDER-TRUTH W1 (KNOWN-HAZARDS 7.46 \u00e2\u20ac\u201d umbrella conformance seam for ADR-0042): an rc surface answers from live session authority, never a stale persisted projection; a resuming perch is UNBOUND, not offline. Regression matrix from the hertz RCAs + operator field recovery: offline-row-over-honest-session attaches; offline-no-session refuses; zombie refuses/reaps never attaches; resume-never-bound reads UNBOUND and is attachable; harness-only refuses truthfully pre-stream; qualified targets attach with bare wire id. HEAVY nextest group at birth for any leg spawning a daemon tree (standing CI lint). Gate: int \u00e2\u20ac\u201d the matrix; doc \u00e2\u20ac\u201d KNOWN-HAZARDS 7.46.",
    "doc": "7.45 Endpoint lifecycle state converges to truth from every death path \u00e2\u20ac\u201d no optimistic online without authority, no surviving control stamps, no immortal wake intent, no untruthful create `[REQ-HAZARD-ENDPOINT-LIFECYCLE]`: ### 7.46 An rc surface answers from live session authority, never a stale persisted projection \u00e2\u20ac\u201d and a resuming perch is UNBOUND, not offline `[REQ-HAZARD-RC-ATTACH-TRUTH]` <!-- --> - **Failure (paid-for, hertz perri contradiction RCA 2026-07-17/18 + operator field recovery):** the broker hosted an honest live session (client tree alive, `SessionProbe::has_live_session_hones"
  },
  "REQ-HAZARD-RC-EOF": {
    "title": "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 \u00e2\u20ac\u201d including UnexpectedEof 'failed to fill whole buffer' \u00e2\u20ac\u201d returns Err \u00e2\u2020\u2019 RC_FAIL \u00e2\u2020\u2019 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 \u00e2\u20ac\u201d spt-core must classify a broker-gone EOF and (a) surface a CLEAR actionable message ('daemon stopped/restarted \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d 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",
    "doc": ""
  },
  "REQ-HAZARD-REGISTRY-EPOCH-LEASE": {
    "title": "Registry merge ordered by per-node monotonic epoch, never wall-clock \u00e2\u20ac\u201d a stale Active can't clobber a newer Offline (4.8, red-team #8)",
    "doc": "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 \u00e2\u2020\u2019 [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"
  },
  "REQ-HAZARD-REGISTRY-STALE-CLEAN": {
    "title": "Stale registry entries degrade to fallback, never hard-fail (4.3)",
    "doc": ""
  },
  "REQ-HAZARD-RENDER-LIFECYCLE": {
    "title": "RC-RENDER-TRUTH W3 (KNOWN-HAZARDS 7.47 \u00e2\u20ac\u201d umbrella conformance seam for ADR-0043): the physical terminal is never mutated or terminated outside its renderer's ordered state model. Regression matrix from the hertz RCA: output-before-exit through the production path; unconditional display teardown across every rc exit class; TUI baseline reconstructs whole after out-of-band mutation (recording backend); repaint replays tracked modes. Deferred P2 seeds recorded, NOT this milestone: Exit{after_seq} watermark defense; per-client semantic baseline (only if spt ever transforms live frames). Gate: int \u00e2\u20ac\u201d the matrix; doc \u00e2\u20ac\u201d KNOWN-HAZARDS 7.47.",
    "doc": "7.46 An rc surface answers from live session authority, never a stale persisted projection \u00e2\u20ac\u201d and a resuming perch is UNBOUND, not offline `[REQ-HAZARD-RC-ATTACH-TRUTH]`: ### 7.47 The physical terminal is never mutated or terminated outside its renderer's ordered state model \u00e2\u20ac\u201d output before exit, owned baselines, unconditional teardown `[REQ-HAZARD-RENDER-LIFECYCLE]` <!-- --> - **Failure (paid-for, hertz stale-glyphs RCA 2026-07-18, all legs doyle seam-verified):** four render-lifecycle defects presenting as \"missing whitespace\"/stale glyphs. (a) The broker exit waiter direct-writes `KIND_EXIT`"
  },
  "REQ-HAZARD-RESTART-IDEMPOTENT": {
    "title": "Idempotent/exactly-once delivery across brain restart at every broker boundary (codex #14)",
    "doc": ""
  },
  "REQ-HAZARD-ROSTER-GHOST": {
    "title": "A LOCAL subnet roster entry whose backing perch is erased does NOT keep advertising Active (no phantom perch-less endpoint). `api session-end <id> --erase` removes the perch (owlery dir gone) but the subnet roster (identity/registry/<subnet>.json) keeps the endpoint's instance row ACTIVE with no backing perch; `endpoint stop` says 'address unregistered' yet the line persists; no CLI verb forgets a roster entry, and a hand-edit is re-added by the single-writer daemon advertiser. FIX: daemon-side self-heal \u00e2\u20ac\u201d the advertiser DROPS/forgets a LOCAL roster entry whose backing perch no longer exists (stops advertising it Active), and/or a `forget`/evict verb; verify whether the epoch lease eventually evicts it (slow-self-heal) vs a real leak and scope accordingly. doyle secondary finding (perri). (post-v0.10.0)",
    "doc": ""
  },
  "REQ-HAZARD-SEEDMAP-CONNECT-UNBOUNDED": {
    "title": "SEED (doyle-filed, 2026-07-05, from the REQ-HAZARD-DAEMON-STOP-BARRIER B2 fix): the PRODUCTION seedmap connect callers (put / take / is_running) inherit the SAME interprocess WaitNamedPipeW-forever hazard the stop path just bounded \u00e2\u20ac\u201d a Windows named-pipe connect to a name that EXISTS but has NO accepting instance parks in NMPWAIT_WAIT_FOREVER, so a slow / half-dead seed daemon could wedge a live `api seed` / `api listen` / `daemon start`. UPDATE (2026-07-05, doyle reversed the stop-path scope-guard): request_stop's OWN initial connect became load-bearing (a stop-guard re-dialing an already-dying name parked forever, resurrecting the convoy) \u00e2\u2020\u2019 it is now bounded via connect_bounded under REQ-HAZARD-DAEMON-STOP-BARRIER (every dial on the STOP path is bounded). REMAINING deferred here = the put / take / is_running production clients. FIX (deferred, needs its own ruling): a shared bounded seed-control connect for those \u00e2\u20ac\u201d but a 2s-style cap on a legitimately slow daemon-start connect is a real behavior change (a slow-but-fine start could become a spurious failure), so the timeout + degrade semantics need design first. NOT built \u00e2\u20ac\u201d activate when scoped.",
    "doc": ""
  },
  "REQ-HAZARD-SESSION-PIN-WEDGE": {
    "title": "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 \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d 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, ",
    "doc": "F-019 diagnosis lesson \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d the departing session dies (crash / tab-close) or its `/clear`-era `boundary` call never lan"
  },
  "REQ-HAZARD-SHELL-STALE-ONLINE": {
    "title": "A shell instance's ONLINE-ness is DERIVED (recorded status AND its recorded `shell.pid` not provably dead), never the recorded `status` field alone \u00e2\u20ac\u201d an abruptly-dead binary (force-kill, crash, OOM: no link-break, so `close_shell`'s offline flip never runs) must not read online forever. The shell-side twin of REQ-HAZARD-DAEMON-HOSTED-LIVENESS, which gave AGENT perches exactly this resolver and which shells never got. PROVABLY DEAD is the narrow discriminant: `shell.pid` present AND parses non-zero AND `!is_process_alive` \u00e2\u20ac\u201d pid absent, unparseable, or 0 (a broker-hosted spawn whose backend exposed no pid records 0) reads ALIVE, the same interim-parity/fail-toward-alive stance `liveness.rs` already holds, so a pid-less backend is NEVER falsely declared dead. Recycled-pid caveat, accepted at mint: a reused pid reads alive, so the heal is missed, never mis-fired \u00e2\u20ac\u201d the failure direction is 'stays stale', never 'kills a live instance'. SITE CLASSIFICATION IS PART OF THE REQUIREMENT (authoritative cfg(test)-excluded census at mint = 8 status reads, 3 classes \u00e2\u20ac\u201d do NOT blanket-swap the predicate): (a) DERIVED \u00e2\u20ac\u201d relink's already-online refusal (the gate that made recovery impossible), the `s",
    "doc": "Conformance checklist (condensed): | # | Invariant | spt-core surface | |---|---|---| | 1.1 | Grace wait precedes INIT_SIGNOFF | daemon teardown | | 1.4/4.4 | Deferred rows excluded from event-stream drain | daemon spool drain | | 2.1/5.1 | Stable PID/broker-handle over ephemeral PID | liveness detection | | 2.3 | Handoff argv/IPC version-tolerant (newer brain \u00e2\u2020\u201d older broker) | broker\u00e2\u2020\u201dbrain IPC, self-update | | 2.4 | gen_start = now() on cold-start + handoff | per-instance generation | | 2.6 | A shell's ONLINE-ness is DERIVED (recorded status AND a not-provably-dead `shell.pid`) \u00e2\u20ac\u201d an abruptly-"
  },
  "REQ-HAZARD-SINGLE-PATH-SOURCE": {
    "title": "Single path/registry source of truth; no layout ambiguity (6.1)",
    "doc": ""
  },
  "REQ-HAZARD-SOFT-CLEANUP": {
    "title": "Soft-cleanup preserves state, removes only the ready marker (6.2)",
    "doc": ""
  },
  "REQ-HAZARD-STALE-INDEX-LOCK": {
    "title": "Sweep stale lockfiles on daemon boot (1.3)",
    "doc": ""
  },
  "REQ-HAZARD-STDIN-SESSION-ID": {
    "title": "Stdin session_id precedence over env (2.2)",
    "doc": ""
  },
  "REQ-HAZARD-STOP-PATH-PSYCHE-ORPHAN-REAP": {
    "title": "Endpoint-stop and brain-death reconcile MUST reap a brain-less perch's orphan detached Psyche via the cmdline-scoped guard (`psyche_orphan_should_reap`) \u00e2\u20ac\u201d the handle-reap (`LiveSet::stop_host`, REQ-HAZARD-UNHOST-PSYCHE-REAP) CANNOT, because the owning brain is gone (its `psyche_child` handle died with it), and the brain-start scoped-reap (REQ-HAZARD-BRAIN-RESTART-PSYCHE-DUP) never fires for a perch being STOPPED rather than re-hosted. So the live-host calls the scoped reap after `stop_host` at the reconcile stop-side AND in `confirm_residency_or_unhost`. Preserves fail-safe-decline (pid-alive AND exe-basename==psyche-program AND cmdline contains `<id>-psyche`; any unreadable signal DECLINES \u00e2\u20ac\u201d a missed dup is bounded, a wrong-kill is catastrophic). This is the orphan-leak half of the perri F-010xF-015 field bug (the unsupervised install-dir Psyche that locked an update); the other half is the psyche own-copy (ADR-0025 amendment). (v0.13.2 W3 (a))",
    "doc": "Conformance checklist (condensed): | # | Invariant | spt-core surface | |---|---|---| | 1.1 | Grace wait precedes INIT_SIGNOFF | daemon teardown | | 1.4/4.4 | Deferred rows excluded from event-stream drain | daemon spool drain | | 2.1/5.1 | Stable PID/broker-handle over ephemeral PID | liveness detection | | 2.3 | Handoff argv/IPC version-tolerant (newer brain \u00e2\u2020\u201d older broker) | broker\u00e2\u2020\u201dbrain IPC, self-update | | 2.4 | gen_start = now() on cold-start + handoff | per-instance generation | | 2.6 | A shell's ONLINE-ness is DERIVED (recorded status AND a not-provably-dead `shell.pid`) \u00e2\u20ac\u201d an abruptly-"
  },
  "REQ-HAZARD-STOP-RESPAWN-CONVOY": {
    "title": "KNOWN-HAZARDS 7.52: an operator stop outranks every implicit ensure \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d evidence may tag the same rig.",
    "doc": "7.52 An operator stop outranks every implicit ensure \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d 5\u00e2\u20ac\u201c10 ephemeral spawner windows flash and the daemon is back; several force-stops to sta"
  },
  "REQ-HAZARD-TEARDOWN-DEADEND": {
    "title": "TEARDOWN-AUTHORITY W1 HAZARD (ADR-0045 decision 8; hertz RCA 2 \u00e2\u20ac\u201d hit doyle's OWN live production endpoint, recoverable only by out-of-band scoped kill): `endpoint stop` followed by `endpoint run --id <same>` MUST succeed (spawn or resume) and must NEVER answer ENDPOINT_CREATE_CONFLICT about a session that stop claimed to end. TRAP SHAPE (two individually-CORRECT behaviors composing into a lifecycle DEAD END with no in-band exit): (1) stop leaves the broker session alive (REQ-ENDPOINT-TEARDOWN-AUTHORITY), and (2) `endpoint run` correctly REFUSES ENDPOINT_CREATE_CONFLICT rather than silently reattaching (v0.37.0 no-silent-reattach rule, deliberately chosen). The survivor is therefore simultaneously what stop claims to have killed AND what run refuses to work around; with a wedged harness every in-band verb is exhausted (stop lies, run refuses, rc replays a dead PTY, rc --take controls a process that never answers). Neither behavior is individually wrong \u00e2\u20ac\u201d the COMPOSITION is the hazard, so the regression must assert the composition, not either verb alone. This single assertion is the whole user-visible point of W1. Gate: impl \u00e2\u20ac\u201d covered by the shared primitive; int \u00e2\u20ac\u201d start a real broke",
    "doc": "7.48 At most one input-capable controller lease per PTY session \u00e2\u20ac\u201d takeover revokes atomically and loudly, input is fenced to the active lease, node identity is never a lease `[REQ-HAZARD-CONTROLLER-LEASE]`: ### 7.49 A teardown verb never stamps a terminal or resting state it has not caused \u00e2\u20ac\u201d and two individually-correct verbs must not compose into a lifecycle dead end `[REQ-HAZARD-TEARDOWN-DEADEND]` <!-- --> <!-- --> - **Failure (paid-for, two hertz field RCAs 2026-07-19, both doyle code-verified the same day; the second hit doyle's OWN live production endpoint):** `endpoint shutdown` reported"
  },
  "REQ-HAZARD-UPDATE-ROLLBACK": {
    "title": "Self-update rejects version rollback; metadata expiry + adapter content signing (codex #5)",
    "doc": ""
  },
  "REQ-HAZARD-VIEWER-CLOSE-DETACH": {
    "title": "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 \u00e2\u20ac\u201d 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 \u00e2\u2020\u2019 the OS terminates every process still in that job. A child escapes only with CREATE_BREAKAWAY_FROM_JOB \u00e2\u20ac\u201d 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) \u00e2\u20ac\u201d the leaking lifetime binding is the Job Object, not the console. FIX: add CREATE_BREAKAWAY_FROM_",
    "doc": "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 \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d on Windows the cold-started daemon is lau"
  },
  "REQ-HAZARD-VIEWER-RING-ROLL-SNAP": {
    "title": "A read-only rc --view VIEWER whose serving brain falls behind the live ring under a hard flood and receives a FORWARD Output seq gap (the ring rolled frames out between reads, BEFORE any channel-overflow eviction \u00e2\u2020\u2019 NO KIND_VIEWER_EVICTED marker) must SNAP TO LIVE (accept-and-advance via dedup-below + snap-above), NOT fatal with output gap (brain.rs:624/628 legacy reject-gap). ROOT (v0.13.0 forkpty, post-b4+skip-to-live): serve_attach subscribes a viewer via brain.attach_as(Viewer) leaving session_cursors EMPTY \u00e2\u2020\u2019 the viewer serve-brain uses the LEGACY reject-gap \u00e2\u2020\u2019 a PRE-eviction ring-roll forward-gap FATALS read_event \u00e2\u2020\u2019 serve_attach returns \u00e2\u2020\u2019 forwarding stops \u00e2\u2020\u2019 attach_received_pty_output=FALSE (a_journaled / p0_paste / attach.rs:1071 wedged_viewer, Linux forkpty; Windows ConPTY floods slower \u00e2\u2020\u2019 MASKED false-green). DISTINCT from REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT (the POST-eviction re-subscribe-from-floor): this is PRE-eviction gap-tolerance while STILL subscribed. VIEWER-only \u00e2\u2020\u2019 B2-SAFE (a viewer never advances delivered_through / is not authoritative); the CONTROLLER keeps strict reject-gap (exactly-once resume). FIX: arm snap-above at initial viewer attach (attach_as_viewer_snap = at",
    "doc": "Shell sleep/wake (offline \u00e2\u2020\u201d online): <!-- --> **BUILT (M12 W2.5).** The controller/viewer model is implemented end-to-end. Attach intent is **three-valued** (`AttachIntent = Viewer | Control | Take`, wire-default `Control`): `Control` to a FREE endpoint becomes controller; `Control` to a CONTROLLED endpoint is **refused with guidance** (`--view` to watch, `--take` to control) \u00e2\u20ac\u201d never auto-viewer, never silent-displace; `Take` (`spt rc --take` / picker \"Kick\") kicks the incumbent with a **loud `Displaced{by}` notice** and full detach (not demote). The broker's per-session `OutputLog` is the fan"
  },
  "REQ-HAZARD-WAN-ORIGIN-AUTH": {
    "title": "WAN-inbound origin is transport truth, never payload: the access gate's subject (ADR-0009 origin-node whitelist) is the QUIC handshake-proven remote node id from the broker's conn/stream table \u00e2\u20ac\u201d a forged origin/node field inside record bytes is inert (7.5)",
    "doc": "7.4 Per-agent pulse/psyche/echo scheduling must not serialize across agents `[REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]`: ### 7.5 WAN-inbound origin is transport truth, never payload `[REQ-HAZARD-WAN-ORIGIN-AUTH]` <!-- --> - **Failure:** the ADR-0009 access whitelist gates **unsolicited wire inbound by origin node**. If the gate's subject is read from record bytes (an `origin_node`/`from`/`node` field a sender wrote), any sender forges any origin and the whitelist is decoration \u00e2\u20ac\u201d same spoof class as 7.3's Psyche-supplied `from=`, now on the cross-node surface. - **Invariant:** the origin the gate ("
  },
  "REQ-HAZARD-WINDOWS-PID-RECYCLE": {
    "title": "Windows PID-recycling false positives guarded (5.1)",
    "doc": ""
  },
  "REQ-HOST-RUN-1": {
    "title": "spt-hosted harness bringup: `spt endpoint run` spawns an adapter's `[session.self]` command template into a broker-held PTY (the spawn-session seam, brain.rs spawn_session_pid \u00e2\u20ac\u201d same broker path shellhost.rs launch_shell_brokered_in uses for shells, now for kind=\"harness\" self-role), registers the perch under the given endpoint id, returns the id. Reverses today's harness-hosted-only launch (external launcher \u00e2\u2020\u2019 `api bind`). Non-interactive flag set (--adapter <a[:profile]> --id <id> --create --resume <session> --attach|--start|--view) covers every terminal action of the W2 interactive picker so shortcuts (cc-<id>) bake fully non-interactive launches; composite adapter:profile resolves via registry::resolve_option leaf-replace overlay.",
    "doc": ""
  },
  "REQ-IDLE-PARKED-DELIVERY": {
    "title": "W5 (LIFECYCLE-TRUTH): a message QUEUED to an ALREADY-idle spt-hosted endpoint is delivered without an operator poke. ROOT (live during the milestone dispatch 2026-07-07): the idle-edge drain (F-023 leg 2) fires only on the ACTIVE->IDLE transition; no new edge ever comes for a parked session, and the send-time inject didn't carry it \u00e2\u20ac\u201d both doyle->todlando dispatches sat delivered=0 in the spool while the endpoint showed ONLINE. FIX: send-time inject fires for an already-idle spt-hosted target (activity sense says idle => inject now, not spool), and/or a bounded spool sweep re-offers pending rows to idle endpoints (piggyback the pulse tick, no new loop). Int: send to a session idle for N minutes -> delivered without any operator poke.",
    "doc": ""
  },
  "REQ-INFRA-1": {
    "title": "GitHub issue tracking for v1; tangled.org as migration target",
    "doc": ""
  },
  "REQ-INST-13": {
    "title": "Subnet-exclusive sync + per-endpoint subnet-membership list",
    "doc": ""
  },
  "REQ-INST-6": {
    "title": "Deferred messages not delivered to dormant/suspended instances",
    "doc": "Deferred Features: | Feature | Cut from | Why deferred | Trigger to revisit | |---|---|---|---| | Scrollback on-disk spillover | terminal wrapper v1 | In-memory ring covers the common case; spillover adds a persistence/rotation story | First long-running session that overflows the ring usefully, or any \"scroll back further than the buffer\" user need | | Sidecar adapter process (long-running, wire-protocol) | harness contract v1 | Manifest + `spt.exe` subcommand surface covers v1 harnesses; sidecar only earns its keep for streaming / in-memory cross-event state | A harness outgrows manifest+hoo"
  },
  "REQ-INST-7": {
    "title": "Subnet registry + bare-id resolution policy",
    "doc": ""
  },
  "REQ-INSTALL-13": {
    "title": "Adapter add is non-destructive & idempotent-safe (F-018): `spt adapter add --github|--release` REFUSES when the target `_github/<safe>` home already backs an ACTIVE registered record \u00e2\u20ac\u201d emitting an actionable code (ADAPTER_ADD_ALREADY_REGISTERED) that routes to `spt adapter update <name>` (refresh in place) or `spt adapter remove <name>` then re-add (replace) \u00e2\u20ac\u201d instead of clobbering the live install (the perri footgun: `add --github` over a `--release` pointer git-cloned a source tree over the extracted built binaries \u00e2\u2020\u2019 registered pointer dangled \u00e2\u2020\u2019 cryptic `os error 2`). And when it DOES (re)populate the home it STAGES-THEN-SWAPS (clone/extract to a sibling staging dir, swap into place only on success) so a failed fetch/clone never strands the previously-extracted manifest+binaries as a dangling pointer (the os-2 / DeferredManifest class). Mirrors the safe stage-then-swap `adapter update` already uses (REQ-UPD-9, apply_release_crc_swap). (v0.14.1)",
    "doc": "Installation: **adapter registration (`spt adapter add`)**: How a node comes to *know* an adapter \u00e2\u20ac\u201d 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}/\u00e2\u20ac\u00a6/adapters/` \u00e2\u20ac\u201d 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"
  },
  "REQ-INSTALL-3": {
    "title": "Idempotent + interactive-optional first run",
    "doc": ""
  },
  "REQ-INSTALL-9": {
    "title": "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 \u00e2\u20ac\u201d 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)",
    "doc": "Installation: **adapter registration (`spt adapter add`)**: How a node comes to *know* an adapter \u00e2\u20ac\u201d 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}/\u00e2\u20ac\u00a6/adapters/` \u00e2\u20ac\u201d 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"
  },
  "REQ-INSTALL-BOOTSTRAP-VERB": {
    "title": "THE-FORKENING W1 (ADR-0036 \u00c2\u00a73, bootstrap shape b operator-ruled): virgin-box install = `gh release download` the platform binary + ONE self-install verb in the binary itself \u00e2\u20ac\u201d the verb places the binary at the canonical install path (the path self-update already respawns from, v0.4.2 lesson), registers user PATH, and leaves first-run identity/daemon-start to the existing idempotent first-run; hosted one-liner install scripts (curl|sh / irm|iex at the dead Pages URL) are RETIRED. Non-interactive (CONTEXT.md Installation: the install path doubles as every adapter's pack-in on-demand install). Windows UAC-740 gotcha binding: the downloaded exe keeps the `spt-*` asset name and the verb lives INSIDE spt \u00e2\u20ac\u201d no installer-detection trigger words in exe names. README (bs-core) documents: install gh -> gh auth login -> gh release download -> the verb. Gate: unit \u00e2\u20ac\u201d verb places/registers idempotently, refuses cross-platform binaries (platform-stamp check exists, v0.3.2); int \u00e2\u20ac\u201d from a clean SPT_HOME+PATH sim, downloaded-binary self-install yields a working `spt` on PATH whose `spt update fetch` then speaks the gh channel; doc \u00e2\u20ac\u201d README install section rewritten. Kin REQ-UPDATE-GH-TRANSPORT, REQ-I",
    "doc": "Installation: **Installer form (gh bootstrap, ADR-0036):** install gh \u00e2\u2020\u2019 `gh auth login` (org membership) \u00e2\u2020\u2019 `gh release download` the platform binary from the private channel \u00e2\u2020\u2019 one **self-install verb** in the binary places it at the canonical install path and registers the *user* PATH (so adapters call `spt api \u00e2\u20ac\u00a6` cross-OS); first-run identity gen + daemon start stay the existing idempotent unattended first-run. Hosted one-liner scripts are retired with the public channel; first-fetch trust = gh's authenticated TLS + org membership (full ed25519 verification is `spt update`'s job thereafter)."
  },
  "REQ-JOIN-DEFERRED-ELEVATION": {
    "title": "W2 (JOIN-TRUTH, operator UX ruling verbatim): 'if --code was not supplied, don't spawn the elevated subnet-join window until a target machine is discovered \u00e2\u2020\u2019 just in time for the code prompt.' When --code is ABSENT and the process is UNELEVATED, run the name prompt + ALREADY_MEMBER check + ensure_daemon + the MEET phase (brain.pair_meet) UNELEVATED; only on MetMember spawn the elevated window via the EXISTING try_auto_elevate machinery (the elevated re-run re-executes the join flow \u00e2\u20ac\u201d its second meet is cheap, the member is proven present). A FAILED search must NEVER show a UAC/sudo/pkexec prompt. The --code path is UNCHANGED (gate-first, one-shot). The unelevated phase performs ZERO trust mutation \u00e2\u20ac\u201d meet is pre-trust per REQ-JOIN-TWO-PHASE/ADR-0030. RULED OUT (doyle): cross-elevation hold-session adoption (passing the daemon-held pair session_id into the elevated process) \u00e2\u20ac\u201d a new security seam we don't need; the elevated re-run re-meets instead. The elevation gate MOVES from command-entry to the enrollment boundary; discovery is read-only pre-trust.",
    "doc": "Deferred-elevation amendment (2026-07-06 \u00e2\u20ac\u201d JOIN-TRUTH W2): <!-- -->"
  },
  "REQ-LIST-JSON-LIVENESS-PARITY": {
    "title": "GATEWAY-LIVENESS (flynn field bug 2026-07-09, RCA reader-divergence root): `spt endpoint list` (human) and `endpoint list --json` MUST report an IDENTICAL status for a locally-hosted endpoint \u00e2\u20ac\u201d especially a pid-alive, status-ABSENT gateway (no psyche_init). ROOT (todlando RCA STEP-1, doyle-verified): the --json builder (crates/spt/src/cli.rs cmd_endpoint_list) emits each subnet row's status straight from resource_projection (spt-net registry.rs:566, passes instance.status through verbatim :592 \u00e2\u20ac\u201d the persisted WAN snapshot, a lagged gossip that can carry a stale/crash-time Suspended) and NEVER applies the self-owned reconcile the human/picker path applies (reconcile_self_owned, crates/spt/src/picker/data.rs:160 via gather_endpoints :112). So a pid-alive self-owned gateway reads Suspended on --json but ONLINE on human (roster::enumerate spt/src/roster.rs:38 -> is_perch_alive pid-fallback spt-store/liveness.rs:136); the adapter suspend-poll (parse_endpoint_status over endpoint list --json --show-all) reads the divergent --json status -> self-suspends a pid-alive gateway. Candidates REFUTED: resource_projection does NOT re-derive liveness (copies instance.status, only skips !routable :",
    "doc": ""
  },
  "REQ-LISTEN-PRESERVES-HOSTING-TOPOLOGY": {
    "title": "`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 \u00e2\u20ac\u201d controlled=true AND controllable=false on a broker-hosted PTY endpoint \u00e2\u20ac\u201d 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\u00e2\u20ac\u00a6) (~379) \u00e2\u20ac\u201d 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,",
    "doc": ""
  },
  "REQ-LIVEHOST-RECONCILE-TRIAL-SILENT": {
    "title": "SEED (DEFERRED investigation, doyle 2026-07-09 \u00e2\u20ac\u201d 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()` \u00e2\u2020\u2019 `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 \u00e2\u20ac\u201d so it would otherwise reap the 15s-matured wedge by ~T20 < the 30s trial and SELF-HEAL. It didn't (field froze 30s \u00e2\u2020\u2019 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 \u00e2\u20ac\u201d 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",
    "doc": ""
  },
  "REQ-MANIFEST-4": {
    "title": "Keyword hints \u00e2\u20ac\u201d [[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",
    "doc": "Runtime model: **keyword hints** (ratified 2026-06-12 \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d each hint fires **once per session** (a `/clear` mints a new session, nat"
  },
  "REQ-MANIFEST-5": {
    "title": "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 \u00e2\u20ac\u201d 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].",
    "doc": "Runtime model: **adapter strings** (ratified 2026-06-11, Gateway grill): <!-- --> A `[strings]` manifest section \u00e2\u20ac\u201d an adapter-authored JSON/TOML KV tree, dot-path-readable by anything on the node via `spt adapter get-string <adapter-option> <key.path>` (e.g. a harness hook fetching per-profile `additionalContext` \u00e2\u20ac\u201d one hook script serves every profile, only the data differs). Resolution rides the **same leaf-replace profile overlay** as the rest of the manifest: a shipped or local profile may override base strings; `get-string` returns the merged view for the named adapter option. **Strings ar"
  },
  "REQ-MESH-1": {
    "title": "Membership proof (seed-proof): symmetric current-epoch seed-knowledge replaces is_trusted at EVERY inbound gate (registry apply, WAN receive, sync, notif, connection accept). MK = HKDF(seed, domain \u00e2\u20ac\u2013 subnet_id \u00e2\u20ac\u2013 seed_epoch); mutual channel-bound challenge-response at connect (transcript binds both handshake-proven node pubkeys, both nonces, subnet_id, seed_epoch, role); verified once per connection, cached on the broker ConnEntry, kept warm via QUIC keep-alive so re-proof is restart/partition/rotation-only. Exact-epoch match (re-seed is the sole N-1 exception). SECURITY INVARIANTS: channel-bound (no cross-connection replay), mutual, accepts a member it never paired (the mesh property).",
    "doc": ""
  },
  "REQ-MESH-3": {
    "title": "Mesh row fan-out: registry rows stay OWN-AUTHORED; the only change is the push target widens from directly-paired peers to ALL roster members (a wider DIRECT fan-out, never a third-party relay). Every row/message still arrives from its author over a handshake \u00e2\u2020\u2019 KNOWN-HAZARDS 7.5 (origin = handshake node) and 4.10 (eviction lease: any future update comes from that node itself, alive) PRESERVED VERBATIM. Closes the staggered A\u00e2\u2020\u2019B\u00e2\u2020\u2019C repro: C (roster-seeded with A at pairing) initiates to A, seed-proof admits C unpaired, A learns C, both push directly.",
    "doc": ""
  },
  "REQ-MESH-4": {
    "title": "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 \u00e2\u20ac\u201d never in roster/registry gossip \u00e2\u20ac\u201d 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; \u00e2\u2030\u00a52 stale \u00e2\u2020\u2019 re-pair.",
    "doc": ""
  },
  "REQ-MSG-3": {
    "title": "Ready-agent lifecycle: register perch (info.json + listener + registry address) on ready, drain spooled backlog on startup, clean teardown",
    "doc": ""
  },
  "REQ-MSG-4": {
    "title": "Listener stream stdout emits EVENT envelope lines (sister-format, ADR-0001): parse the __REPLY_TO__ frame, pass pre-formed typed envelopes through verbatim (no double-wrap), compose <EVENT type=\"msg\" from=\u00e2\u20ac\u00a6> otherwise, chunk oversized lines into EVENT-PART",
    "doc": ""
  },
  "REQ-MSG-6": {
    "title": "cross-node Gateway user-msg honored via advertised endpoint_type: a user-msg from a Gateway-typed origin survives the receive_wan funnel as user-msg (vs the fail-closed re-stamp), keyed on the QUIC-handshake-proven origin node (never wire `from`). Trust boundary = subnet membership (operator-ratified 2026-06-13); no defense against an in-subnet member forging the type. Instance.endpoint_type is an additive serde-default field extending REQ-INST-7's data model. Absent/unknown type \u00e2\u2020\u2019 re-stamp (N-1 rollout grace)",
    "doc": "Endpoint types: <!-- --> _Implemented posture_: the **local** user-backed origins are honored end-to-end \u00e2\u20ac\u201d a locally-hosted Gateway endpoint (info.json `state=\"gateway\"`) and the local user's CLI (M9-T4/T5). The **cross-node WAN** path is being completed (trust posture **ratified 2026-06-13**): the **subnet membership boundary is the trust boundary**. A subnet is a collection of machines the user already trusts, so a `user-msg` arriving over the subnet from a **Gateway-typed** origin is honored as the user's authority; the daemon does **not** defend against a subnet member *forging* the Gatewa"
  },
  "REQ-MSG-CLI-ORIGIN": {
    "title": "A bare non-perch CLI `spt send` (no owning perch to name as origin) stamps from = `cli@<node-label>` at compose time (bare `cli` when no node label is known \u00e2\u20ac\u201d never a dangling `cli@`), and WAN ingress renders an EMPTY from as the origin node DISPLAY (`node_label_display(origin_node, None)` = the QUIC-proven origin node's key-prefix; never blank) \u00e2\u20ac\u201d a delivered message NEVER shows a blank sender. Scoped to `spt send`: a from-less send is LEGAL (stamped, never refused), while `spt ring` keeps its NO_SELF refusal (a ring needs a routable self for the reply leg; `cli@<node>` is a display origin, not a perch address). (F-024C item 3, doyle ruled)",
    "doc": ""
  },
  "REQ-MSG-DELIVERY-AXES": {
    "title": "Activity-gated inbound delivery + per-message send control as THREE ORTHOGONAL AXES plus opaque metadata (ADR-0028; grilled w/ operator 2026-06-23). SUBSTRATE (the legacy-SPT parity gap, scaffolded-but-unwired today: `delivery::is_idle` + `resolve_inject_methods` exist but the result is discarded `let _methods`, and `broker::dispatch_endpoint_input` injects unconditionally \u00e2\u20ac\u201d its comment calls activity-gating 'a deferred follow wave'): an inbound message has an ACTIVE window (endpoint active \u00e2\u2020\u2019 spool for the receiver's hook-poll, non-disruptive) and an IDLE window (idle/idle-transition \u00e2\u2020\u2019 deliver immediately: translation binary spt-hosted \u00e2\u2020\u2019 relay-poll either topology \u00e2\u2020\u2019 spool, in fallback order). AXES (each composes; each defaults to its unrestricted value): (1) DELIVERY WINDOW \u00e2\u20ac\u201d default (both, first-to-fire) | `--idle-only` (idle window; immediate if already idle) | `--active-only` (active window only, never wakes; the RENAMED `--deferred` \u00e2\u20ac\u201d `deferred=1` spool column + `api poll --include-deferred` keep their names). (2) CHANNEL RESTRICTION \u00e2\u20ac\u201d unrestricted | `--prefer-native` (translation binary if running else fall back) | `--force-native` (binary ONLY, no fallback/no spool-to-other-m",
    "doc": "Activity-gated message delivery + send-modifier axes: <!-- -->"
  },
  "REQ-MSG-ENVELOPE": {
    "title": "The <EVENT type=\"msg\" from=\u00e2\u20ac\u00a6>body</EVENT> envelope (spt-proto::event, the ADR-0001 grammar) is the SOLE canonical arriving-message format at EVERY harness arriving-message surface on an AGENT perch \u00e2\u20ac\u201d api listen AND api poll/worker-poll, byte-identical (reverses REQ-MSG-4's 'hook drains keep the raw frame by contract'). SCOPE CARVE-OUT: the shell-command relay (api poll <shell-id> --link, cmd_poll_shell) is a distinct internal transport carrying RAW MAC'd stamped frames the shell child consumes verbatim \u00e2\u20ac\u201d NOT an arriving-message surface, deliberately EXEMPT from <EVENT> composition (notify_shell_e2e guards this boundary). __REPLY_TO__ \u00e2\u20ac\u201d mis-elevated during the clean-room port to a fake ADR-0001 'stable wire format' (spt-msg/wire.rs, lib.rs) \u00e2\u20ac\u201d is REMOVED entirely (spool format_row, the spt-msg TCP frame, emit parse_frame); (from, body) carried structurally, <EVENT> composed once at the delivery boundary. No legacy sister-interop (spt-core never required it). Reply-correlation rebinds onto the structural from / <EVENT from=\u00e2\u20ac\u00a6> attribute (ADR-0009 access-gate + ADR-0012 Psyche/spt-live reply-target). Self-delimiting by construction \u00e2\u2020\u2019 finding F-002 (non-self-delimiting multi-message poll",
    "doc": "Decision: <!-- -->"
  },
  "REQ-MSG-IDLE-TRANSLATION-BINARY": {
    "title": "spt-hosted idle message delivery via an adapter TRANSLATION BINARY (ADR-0022). New opt-in manifest section `[message-idle-translation-binary]` = a TABLE carrying a `path` scalar (doyle OPT-B ruling: modeled as a table, not a bare top-level scalar, so a preceding section cannot silently absorb it + N+1 extensible; spt-core does NOT deny_unknown_fields, so a future key degrades gracefully); spt-core LIFECYCLE-manages it (spawn when the endpoint comes up, terminate when it goes down). The binary is a PURE stdin\u00e2\u2020\u2019stdout filter; spt-core owns EVERY PTY write. stdin (JSON-lines): `{type:\"init\",endpoint_id,node}` first \u00c2\u00b7 `{type:\"event\",envelope:\"<EVENT\u00e2\u20ac\u00a6>\"}` per inbound message (ADR-0020 envelope) \u00c2\u00b7 `{type:\"input\"}` content-free ping on each operator keystroke (binary tracks user-idle for its own idle-gated buffering; PTY input content NOT duplicated). stdout (JSON-lines): keystroke-commands `{key:\u00e2\u20ac\u00a6}`/`{delay_ms:\u00e2\u20ac\u00a6}`/`{text:\u00e2\u20ac\u00a6}` (extensible). spt-core applies the emitted sequence to the broker PTY ATOMICALLY (the W1 coordination \u00e2\u20ac\u201d REQ-HAZARD-INJECT-CONTROL-COEXIST). The daemon poll feed is the ONE idle substrate for both topologies (Q1=A): harness-hosted consumer = the Monitor child, spt-host",
    "doc": "`[message-idle-translation-binary]` \u00e2\u20ac\u201d spt-hosted idle delivery: <!-- --> // `[inject]` \u00e2\u20ac\u201d inject-input methods: <!-- --> ### `[message-idle-translation-binary]` \u00e2\u20ac\u201d spt-hosted idle delivery (ADR-0022) Opt-in. The adapter's **idle-delivery translation binary**: a pure stdin\u00e2\u2020\u2019stdout JSON-lines filter spt-core lifecycle-manages (spawned when the spt-hosted endpoint comes up, terminated when it goes down). spt-core feeds it the inbound `<EVENT>` feed and reads back keystroke-commands, which spt-core applies to the broker-held PTY **atomically** \u00e2\u20ac\u201d controller input is buffered during the emitted sequenc"
  },
  "REQ-MSG-SELF-DETECT-ANCESTRY": {
    "title": "#9 (F026, operator field bug): a perch-owned `spt send` from an endpoint's OWN session must self-identify, not mis-stamp `cli@<node>` (whose replies bounce NO_PERCH). ROOT: roster::detect_self_id (roster.rs) was ENV-ONLY \u00e2\u20ac\u201d OWL_SESSION_ID matched to info.session_id, else SPT_AGENT_ID \u00e2\u20ac\u201d but an agent-session Bash child often carries NEITHER (the env export is spawn-path-dependent), so a perch-owned sender was classified bare-CLI and REQ-MSG-CLI-ORIGIN stamped it cli@<node> (the stamp works as designed on a wrong premise; that REQ's evidence stays intact). FIX: detect_self_id gains leg (c) PID-ANCESTRY fallback AFTER the env legs \u00e2\u20ac\u201d walk THIS process's ancestry, match a live non-corrupt roster perch's recorded harness pid (info.json.pid Numeric, alive-gated via is_process_alive, corrupt/BUSY skipped), first match = self. from-LABEL / routing default ONLY, NOT authentication \u00e2\u20ac\u201d authenticate() is untouched (pid-ancestry-for-AUTH stays parked per F-024 with its Windows pid-spoof caveats; a display/routing stamp has no such bar). Best-effort: a broken ancestry walk degrades to None \u00e2\u2020\u2019 cli-stamp, never errors the send.",
    "doc": ""
  },
  "REQ-NET-3": {
    "title": "Cross-node Psyche sync over P2P replaces gh-repo-sync",
    "doc": ""
  },
  "REQ-NOTIF-1": {
    "title": "Notification primitive: per-subnet replicated spool, seen/dismissed, resurface-at-boundary, subsumes update+consent prompts",
    "doc": ""
  },
  "REQ-NOTIF-SCOPE": {
    "title": "Notif scope is a per-row producer-chosen attribute node|subnet: node-scoped rows (update/consent/rollback kinds) live and die on their node and NEVER enter a replication feed; subnet-scoped rows keep the full ADR-0007 machinery (replication, cross-node dismiss, semilattice join); scope determines the first-fire candidate set (node-scoped targets the most-recently-active endpoint ON that node). New row fields additive serde-defaulted (host_binaries pattern) \u00e2\u20ac\u201d old peers parse clean, schema regen drift-gated",
    "doc": "1. Scope is a per-row, producer-chosen attribute: `node` | `subnet`: <!-- -->"
  },
  "REQ-NOTIF-SEAM-DISMISS": {
    "title": "Staleness is dismissed at the seam that knows, never evaluated at surface: a successful update apply dismisses spt-core:update-staged; the update worker's next check, seeing running >= staged, dismisses it too (covers out-of-band installs); a later successful update dismisses a rollback row; the primitive stores rows and latches \u00e2\u20ac\u201d NO relevance predicates, nothing evaluated at surface time",
    "doc": "3. Staleness is dismissed at the seam that knows, never evaluated at surface: <!-- -->"
  },
  "REQ-NOTIF-UPDATE-ROW-VERSION-RETIRE": {
    "title": "An update-available notif row minted by a node running PRE-0.40.0 spt is retired on the version the running node has ALREADY reached \u00e2\u20ac\u201d because the keyed catch-up dismissal is structurally blind to it. (DAEMON-LIFECYCLE W2 RIDER, operator-ordered; doyle root-caused end to end 2026-07-22 on the live box.) FIELD CHAIN, verified: GRAVITY-NVDA-PC (f15d837b, BIGNET) runs pre-0.40.0 spt, whose legacy producer mints the update notice SUBNET-scoped with NO coalesce key (the scoped+keyed producer shipped in 0.40.0). The row replicated fleet-wide. The modern catch-up dismissal (REQ-NOTIF-SEAM-DISMISS, pump/update.rs dismiss_staged_notif_if_caught_up) dismisses ONLY by coalesce key, so a keyless row can never be retired by it: a FULLY-UPDATED node holds a live 'v0.41.0 available' row forever, surfacing once per endpoint at every boundary (observed: todlando ~19:54 + Librarian/Athenaeum-Library; store showed seen=2, undismissed, the only undismissed update row in the whole history). FIX: a version-grounded retirement sweep at the EXISTING catch-up site, same per-tick per-subnet cadence, running ALONGSIDE the key path (which stays PRIMARY \u00e2\u20ac\u201d belt-and-braces, not a replacement): dismiss any UNDISM",
    "doc": ""
  },
  "REQ-ONEWAY-STREAM-TERMINAL": {
    "title": "REGISTRY-LIFECYCLE W1 (ADR-0040 decision 1, hertz defect B leg 1): a one-way fire-and-forget stream family is TERMINAL at successful FIN, sender-side \u00e2\u20ac\u201d the registry pump retires its OWN feed row after write+FIN via the existing net-stream-retire verb (best-effort on N-1 brokers per ADR-0038 A). Sender history on the long-lived pump conn stops accumulating: steady-state row population is O(active exchanges), not O(feeds since conn start). Gate: impl \u00e2\u20ac\u201d pump push_feed retire-after-FIN; unit \u00e2\u20ac\u201d successful feed retires its row, failed/unFINed feed does not, retire failure is best-effort non-fatal; int \u00e2\u20ac\u201d rides REQ-HAZARD-REGISTRY-STALL plateau seam (eligible rows plateau O(active) over N rounds); doc \u00e2\u20ac\u201d ADR-0040.",
    "doc": "Decision: <!-- --> 1. **One-way (fire-and-forget) stream families are terminal at FIN, sender-side.** The registry feed pump retires its own row after successful write+FIN via the existing `net-stream-retire` verb (best-effort on N-1 brokers, per ADR-0038 A). A one-way family's exchange is definitionally over at FIN; keeping the row eligible reproduces the O(history) defect forever. 2. **Eligibility filtering is server-side.** `stream_infos` excludes `initiated_locally` rows (alongside `retired`) before serializing. Consumers keep their client-side guards (double-filter harmless; N-1 compatibl"
  },
  "REQ-PAIR-5": {
    "title": "Multi-subnet pairing: subnet-name discovery input, create-new-names-up-front, rendezvous-token hashing",
    "doc": ""
  },
  "REQ-PAIR-8": {
    "title": "NTP TOTP offset: the pairing ceremony queries NTP at ceremony time (both sides) and applies the derived offset to the TOTP calculation in-process only; system-clock fallback when NTP is unreachable (offline LAN pairing unaffected \u00e2\u20ac\u201d NTP failure never blocks a pairing that succeeds today); never sets the OS clock; no background sync loop (M8 decision 18; field trigger: enlyzeam clock >1 min off exceeds the \u00c2\u00b11 window)",
    "doc": ""
  },
  "REQ-PEERADDR-INVARIANT": {
    "title": "MESH-RECOVERY W1 (ADR-0039, RCA wave 2): the peer-addrs cache INVARIANT \u00e2\u20ac\u201d outer peer key == address.id \u00e2\u20ac\u201d is ENFORCED on load and on write: invalid rows are repaired from the current roster when possible, rejected (dropped loudly) otherwise; never silently kept, never used as a route. MIGRATION = rebuild invalid rows from roster on first post-upgrade load; bare-deleting peer-addrs.json is REJECTED (cold recovery depends on the id-only path staying BEHIND warm routes \u00e2\u20ac\u201d nuking every warm route trades one trap for another). gapfill_peeraddrs and PeerAddrStore::put stop accepting mismatched mappings (the live 5ff\u00e2\u20ac\u00a6-outer poison-row class on both incident nodes). Absent/corrupt-degrades-empty behavior untouched. Gate: impl \u00e2\u20ac\u201d load/write enforcement + repair + migration; unit \u00e2\u20ac\u201d mismatch rejected on put, repaired-or-dropped on load, valid rows untouched by migration, gapfill refuses a mismatched roster entry; doc \u00e2\u20ac\u201d ADR-0039. Kin REQ-PEER-ROUTE-CHAIN, REQ-MESH-2 (gapfill), REQ-CONV-1.",
    "doc": "Context: ## Decision <!-- --> <!-- --> <!-- -->"
  },
  "REQ-PICKER-2": {
    "title": "The picker's project-history loader reads the git-backed context store, not the bare working tree: data.rs project_history_for enumerates an endpoint's projects via the BranchStore branch set (the context store keeps per-project context in git branches \u00e2\u20ac\u201d contextstore::project_branch(project_id), checked out to projects/<project>/<id>/ only on-demand) instead of raw std::fs::read_dir over the empty working tree (which returned empty for ALL rows incl wall-a \u00e2\u20ac\u201d the operator bug). Ordered newest\u00e2\u2020\u2019oldest by branch commit recency; degrades to empty (informational pane), never fails. (v0.10.0)",
    "doc": ""
  },
  "REQ-PICKER-3": {
    "title": "A self-owned subnet row reconciles its status to the LIVE roster: a Subnet-category row whose endpoint_id overlaps a local (is_local) roster id is self-owned (this node hosts it), so its status square is OVERRIDDEN with the live roster status \u00e2\u20ac\u201d the WAN registry snapshot (wansend::load_snapshots) is a periodically-advertised, independently-stale projection, while the local roster (p.alive) is ground truth for an endpoint this node hosts. One status square per endpoint (CONTEXT.md:348-350 \u00e2\u20ac\u201d nothing licenses opposite squares for one endpoint across its Local vs Subnet listings). A reconcile pass in data.rs after the local_rows + subnet_rows gather; BOTH category listings are preserved (Local + Subnet are legitimately distinct views \u00e2\u20ac\u201d you are in your own subnet), only the STATUS is unified. (v0.10.0)",
    "doc": ""
  },
  "REQ-PICKER-BACK-NAV": {
    "title": "C-4 (REMOTE-TRUTH triage \u00c2\u00a7C-4 #9): Backspace is a back() ALIAS across the picker \u00e2\u20ac\u201d one keypress backs out one screen along the SAME reverse map Esc walks (model back(), complete for all screens incl. the C-3 ConfirmPurge), and from the kind layer it cancels the picker (Esc parity) \u00e2\u20ac\u201d EXCEPT the two text-edit contexts, where Backspace stays CHAR-DELETE: CreateId entry (id_backspace) and the pick-list filter mode (filter_backspace). DELIBERATE: no empty-buffer fallthrough to back() in the text contexts \u00e2\u20ac\u201d mixing delete and nav on one key invites miskeys mid-typing; Esc already backs out (the triage's optional extra, declined). Pure key routing in handle_key (picker/mod.rs) ahead of the per-screen arms; zero model change (the reverse map pre-existed). Red-first: Backspace on Confirm \u00e2\u2020\u2019 PickExisting (pre-fix: dead key); on CreateId with a buffer \u00e2\u2020\u2019 buffer shortens, screen unchanged; empty buffer \u00e2\u2020\u2019 STILL no nav.",
    "doc": ""
  },
  "REQ-PICKER-CHANGE-ADAPTER-FLOW": {
    "title": "B-2 (F029, operator; LARGEST item): `ConfirmOption::ChangeAdapter` wrongly routes into the CREATE flow (Screen::CreateAdapter \u00e2\u2020\u2019 CreateId \u00e2\u2020\u2019 CreateHome \u00e2\u2020\u2019 START, reenter_create(true)) \u00e2\u20ac\u201d it re-prompts id + home and then STARTS the endpoint. Required: prompt ONLY the harness-adapter pick, apply the change to the perch record (update info.json.adapter via the existing write seam; an `<adapter>:<profile>` pick stamps the full option, composing with A-4), then RETURN to the endpoint's Confirm menu \u00e2\u20ac\u201d NO id prompt, NO home prompt, NO start. FIX: a return-to-Confirm mode on the adapter-pick screen (flag or dedicated Screen::ChangeAdapterPick) skipping CreateId/CreateHome + the launch outcome. Unit the flow-state transitions + the record write. Shared-seam: touches picker flow state \u00e2\u20ac\u201d run the full picker cluster. See triage B-2.",
    "doc": ""
  },
  "REQ-PICKER-CONTROLLED-LOCAL": {
    "title": "#3 local half: a LOCALLY-controlled endpoint renders CONTROLLED in its own node's picker. display_status() (crates/spt/src/picker/model.rs:415) derives Controlled ONLY from driven_by.is_some(), but driven_by is REMOTE-only by design (KH 7.15) \u00e2\u20ac\u201d a locally-controlled endpoint has driven_by=None + controlled=true, and local_rows (data.rs:220) never threads controlled into EndpointRow, so a locally-RC'd endpoint shows plain ONLINE in its own picker (remote rows are fine \u00e2\u20ac\u201d gossip stamps controller_node=self, REQ-GOSSIP-CONTROLLED-ANY; the asymmetry is the bug). Fix: EndpointRow gains controlled:bool (local: rec.controlled; remote: controller_node.is_some()); display_status -> Controlled when driven_by.is_some()||controlled; desc pane says 'controlled locally' when the driver is unnamed. See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #3.",
    "doc": ""
  },
  "REQ-PICKER-CURRENT-DIR-LABEL": {
    "title": "A-2/A-3 (F029, operator, semantic pair): the Choose-project rows must self-identify the CURRENT DIR. build_project_choices (picker/model.rs:322-352). A-2: when the run cwd IS already a history dir the `Here:` row is (correctly) suppressed by the dedup (REQ-PICKER-CHOOSE-DEDUP-ALL), but the matching history row rendered bare `r.display` with no cwd affordance \u00e2\u20ac\u201d mark it `<display> (CURRENT DIR)`. A-3: the not-in-history current-dir row changes from `Here: <run_cwd>` to `CURRENT DIR --> <project>`, deriving the display the SAME way the history refs do (folder tail; honest fallback to the raw path when underivable). `cwd` payload unchanged. Grep-tests rule: 3 `starts_with(\"Here: \")` asserts (model.rs) + a `Here: /here` render assert (view.rs) are behavior assertions on the OLD label. See triage A-2/A-3.",
    "doc": ""
  },
  "REQ-PICKER-HISTORY-FRESH": {
    "title": "The `spt endpoint run` picker shows project history for FRESH endpoints (operator-raised v0.12.0 real-harness finding). Symptom: a fresh endpoint shows no project history in the picker. ROOT TBD \u00e2\u20ac\u201d investigate the project-history loader (v0.10.0 PICKER-2, picker/data.rs) before fixing: distinguish a real loader bug from 'fresh = no history yet' semantics. (v0.12.1)",
    "doc": ""
  },
  "REQ-PICKER-ONLINE-ACTION": {
    "title": "The `spt endpoint run` picker shows the correct action for an ALREADY-ONLINE endpoint \u00e2\u20ac\u201d Attach, NOT 'Start now' (operator-raised v0.12.0 real-harness finding). Symptom: the picker offers 'Start now' for endpoints that are already online. ROOT TBD \u00e2\u20ac\u201d investigate the status\u00e2\u2020\u2019action mapping (v0.10.0 PICKER-1 four-state status, picker/model.rs): is it reading live/online state correctly, or rendering stale/wedged broker state (i.e. partly a symptom of the broker wedge / status=online latch)? Fix so online \u00e2\u2020\u2019 Attach. (v0.12.1)",
    "doc": ""
  },
  "REQ-PICKER-PURGE-STRUCTURED": {
    "title": "RC-RENDER-TRUTH W3 (ADR-0043 decision 3, hertz stale-glyphs RCA leg 3 P0): core verbs invoked from inside an active TUI return STRUCTURED outcomes and write NOTHING to the terminal \u00e2\u20ac\u201d the picker purge path calls a structured-outcome purge core (no stdout/stderr under the live alternate screen) and remains the sole renderer via model.flash (today cmd_endpoint_purge writes diagnostics/PURGED to stderr while ratatui owns the alt screen, mutating the physical screen behind the previous-Buffer diff baseline => later draws skip 'already blank' cells and stderr glyph fragments persist \u00e2\u20ac\u201d the x-purge symptom). Baseline-desync regression REQUIRES a stateful/recording backend (pure TestBackend view snapshots cannot catch it). Gate: impl \u00e2\u20ac\u201d structured purge outcome + silent-under-TUI routing; unit \u00e2\u20ac\u201d purge core emits no terminal bytes in structured mode, picker converts outcomes to flash; int \u00e2\u20ac\u201d recording backend: draw ConfirmPurge, inject an external display mutation, transition back => next frame reconstructs the COMPLETE target screen; doc \u00e2\u20ac\u201d ADR-0043.",
    "doc": "Decisions: <!-- --> 1. **One FIFO sequencer per attach sink.** The PTY drain/output writer is the sole sequencer for terminal Output and Exit: Exit is enqueued behind all prior output for each sink (drain EOF/completion first, then Exit). A mutex alone is insufficient \u00e2\u20ac\u201d producer order is the contract. Output-before-Exit is a production-path invariant, regression-proven end-to-end (broker \u00e2\u2020\u2019 attach \u00e2\u2020\u2019 rc). <!-- --> 2. **rc display teardown is unconditional, idempotent, and separate from input teardown.** A display RAII guard (distinct from the OS input/raw-mode guard) runs on every exit path incl"
  },
  "REQ-PICKER-WINDOW-TITLE": {
    "title": "B-5 (F029, operator): `spt endpoint run`'s interactive picker window/tab is untitled \u00e2\u20ac\u201d hard to find among many terminals. Set the window/tab title to `SPT Endpoint Picker`. Anchor picker/mod.rs:88 setup_terminal (crossterm SetTitle in the execute! chain). Set-only is acceptable (crossterm can't cheaply read the prior title to restore). Applies ONLY to the interactive picker path \u00e2\u20ac\u201d non-interactive/headless `endpoint run` (REQ-HOST-RUN-1) must NOT retitle the operator's terminal. See triage B-5.",
    "doc": ""
  },
  "REQ-PROJECT-INDEX-INVALIDATION": {
    "title": "PROJECT-INDEX W2 (ADR-0037): event-driven index invalidation, debounced + coalesced: new session/cwd -> refresh that endpoint + the shared cwd cache; context-store mutation -> ONE debounced global membership refresh keyed on BRANCH-TIP FINGERPRINTS (no authoritative context-commit path exists \u00e2\u20ac\u201d a writer-maintained generation counter is REJECTED because nothing maintains it); bind/start, rename, fork, purge -> affected endpoint rows; low-frequency periodic reconcile as the backstop; cwd identity refresh ONLY on path/repo-identity/.git-config change (ordinary commits do NOT alter project identity); multiple invalidations coalesce into one refresh. Gate: impl \u00e2\u20ac\u201d the event set + debounce/coalesce; unit \u00e2\u20ac\u201d each event class maps to its refresh scope + coalescing proof + ordinary-commit no-op; int \u00e2\u20ac\u201d session/context/rename/fork/purge invalidation against a live daemon; doc \u00e2\u20ac\u201d rides the ADR + triage doc. Kin REQ-PROJECT-INDEX-WRITER.",
    "doc": "Self-update: **index writer duty (daemon)** \u00e2\u20ac\u201d the brain hosts ONE writer thread (`projwriter`, spawned beside the live host; single-flight by construction). Batched complexity is contract, `O(P+B+F+C)`: ONE branch enumeration per cycle (`for-each-ref` carries recency + tips), \u00e2\u2030\u00a41 tree scan per **changed** `p-*` branch (`ls-tree` at tip, membership cached by tip), ONE derivation per distinct normalized cwd (cache stamped on the repo-identity marker \u00e2\u20ac\u201d `.git/config` / the `.git` gitfile \u00e2\u20ac\u201d so **ordinary commits are a no-op by construction**); backgrounding the legacy 100+ process loop is REJECTED."
  },
  "REQ-PSYCHE-LEGACY-RESIDENT-SWEEP": {
    "title": "W5 (F030; doyle+perri 2026-07-04): a dirty daemon upgrade from <=v0.24.0 strands a RESIDENT psyche wrapper the OLD daemon spawned \u00e2\u20ac\u201d and F-030 W4's nested-`ready` resolution fix CONVERTED that wrapper's accidental self-reap into a permanent HANG. The pre-W3 wrapper's only spt IPC is `spt ready <parent>-psyche --once` (BLOCKING, no internal timeout); pre-W4 that hit READY_FAIL on a multi-subnet home \u00e2\u2020\u2019 the wrapper exit-4'd (accidental reap). Post-W4 the nested id resolves cleanly \u00e2\u2020\u2019 the wrapper REGISTERS then BLOCKS FOREVER on its first post-upgrade poll: no exit, no psyche_host_error, no CPU (KH 2.6 invisible-loop class, one level up). Post-W3 core has no residency machinery to reap it. FIX: a ONE-SHOT legacy-resident sweep at BRAIN START (never per-reconcile/periodic \u00e2\u20ac\u201d burying residency-era machinery, not resurrecting it). GUARD = adapter-AGNOSTIC (glue-model): resurrect the retired reap_orphan_psyches LOGIC \u00e2\u20ac\u201d for each self-perch live-agent id derive `<id>-psyche` and kill iff (a) exe basename == the adapter's MANIFEST-declared psyche program (normalize_basename, never a hardcoded adapter name) AND (b) cmdline contains the id marker `<id>-psyche` AND (c) pid alive; any unreadable sig",
    "doc": ""
  },
  "REQ-PSYCHE-ROLE-OPTIONAL-SKIP": {
    "title": "W1 (LIFECYCLE-TRUTH): a manifest with NO [session.echo_commune] role SKIPS commune-sync (debug-level note, no strike) instead of hard-failing the turn. ROOT (perri filing, recovered): missing role -> commune-sync hard-fails -> 3-strike stamps the host ('manifest declares no [session.echo_commune] role') while the published contract presents the role as an optional template. FIX: missing OPTIONAL role = skip, not a turn failure.",
    "doc": ""
  },
  "REQ-PSYCHE-STAMP-CLEAR-ANY-SUCCESS": {
    "title": "W1 (LIFECYCLE-TRUTH): EVERY successful psyche operation clears psyche_host_error \u00e2\u20ac\u201d not just the pulse-loop leg. ROOT (three field confirmations, perri): the stamp clears only via note_turn_outcome's Ok leg (lifecycle.rs:1101); a SUCCESSFUL psyche op via checkpoint/wake bypasses it -> stale FAILED stamp sits over a healthy psyche. FIX: event turn, checkpoint/wake synthesis, and signoff echo all clear the stamp on success.",
    "doc": ""
  },
  "REQ-PUMP-PEER-ISOLATION": {
    "title": "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",
    "doc": ""
  },
  "REQ-PUMP-STAGE-TRUTH": {
    "title": "MESH-RECOVERY W1 (ADR-0039, RCA wave 3 \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d address-resolution, QUIC connect, ALPN, seed-proof send, seed-proof receive/verify, roster exchange \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d no green without real peer progress. New fields ADDITIVE (N-1 readers unaffected). Gate: impl \u00e2\u20ac\u201d stage split + status surfaces; unit \u00e2\u20ac\u201d stage classification + health state machine (degraded on all-peer failure, healthy only on real progress, not on heartbeat/time); int \u00e2\u20ac\u201d health flips degraded/healthy across a real peer outage/restore; doc \u00e2\u20ac\u201d reference regen (CLI surface change \u00e2\u2020\u2019 xtask gen, no internal codes in clap help). Kin REQ-PEER-ROUTE-CHAIN, REQ-DAEMON-5 (heartbeat \u00e2\u20ac\u201d answers liveness, not reachability), REQ-CLI-2/R",
    "doc": "Context: ## Decision <!-- --> <!-- --> <!-- -->"
  },
  "REQ-RC-1": {
    "title": "`spt rc <id>` \u00e2\u20ac\u201d user CLI attaching a local terminal to a broker-held PTY, reusing the cross-node attach machinery (attach.rs request_attach \u00e2\u2020\u2019 send_attach_input pump, spt-net AttachRecord codec); local attach is the degenerate single-node case of the cross-node path (rides REQ-TERM-3 byte-stream streaming). Read-only `--view` (watch, no stdin forwarded). Clean detach that does NOT terminate the broker-held session (KNOWN-HAZARDS: PTY ownership stays with the broker; no termination on detach). Explicit detach keybind that cannot collide with harness passthrough input (legacy capsule used a ctrl-b prefix); documented. ConPTY DSR auto-answer in the attach reader (hazard 5.5).",
    "doc": ""
  },
  "REQ-RC-HARNESS-ONLY-REFUSAL": {
    "title": "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') \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d pre-stream preflight + truthful copy; unit \u00e2\u20ac\u201d harness-only row refuses pre-dial with the new copy, non-harness rows unaffected; int \u00e2\u20ac\u201d live remote Instance{harness_only:true} => direct rc refuses pre-stream, names harness-hosted, never says stale; doc \u00e2\u20ac\u201d ADR-0042.",
    "doc": "Decisions: <!-- --> 1. **`spt rc` consults the honest-session authority before the offline fast-fail.** Normal `spt rc <id>` runs the same bounded `SessionProbe::has_live_session_honest` gate `endpoint run` uses (ADR-0041 single liveness authority). An honest session exists \u00e2\u2020\u2019 attach via the session-confirmed path regardless of persisted status. No honest session \u00e2\u2020\u2019 the existing offline refusal stands. A claimed session with a dead client tree \u00e2\u2020\u2019 refusal/reap, never attach. Reuse `SessionProbe`; no new liveness heuristic. <!-- --> 2. **Resume stamps UNBOUND.** A resume launch transitions an exist"
  },
  "REQ-RC-HONEST-SESSION-AUTHORITY": {
    "title": "RC-RENDER-TRUTH W1 (ADR-0042 decision 1, hertz perri-contradiction RCA): normal `spt rc` consults the ADR-0041 single honest-session authority BEFORE the persisted-offline fast-fail \u00e2\u20ac\u201d run the bounded SessionProbe::has_live_session_honest gate; honest session exists means attach via run_attach_session_confirmed regardless of persisted status; no honest session means the existing offline refusal stands; claimed session with dead client tree means refusal/reap, NEVER attach. Reuse SessionProbe \u00e2\u20ac\u201d no new liveness heuristic. Kills the authority split where rc refused ('offline \u00e2\u20ac\u201d nothing to attach to') while endpoint run --resume reattached to the same live session. Gate: impl \u00e2\u20ac\u201d the pre-fast-fail probe + session-confirmed routing; unit \u00e2\u20ac\u201d probe-true routes to session-confirmed attach, probe-false keeps the refusal, dead-tree claim refuses; int \u00e2\u20ac\u201d the 3-row regression matrix: offline persisted row + honest live broker session => rc attaches; offline + no session => existing refusal; zombie/dead client tree => refusal, never attach; doc \u00e2\u20ac\u201d ADR-0042.",
    "doc": "Decisions: <!-- --> 1. **`spt rc` consults the honest-session authority before the offline fast-fail.** Normal `spt rc <id>` runs the same bounded `SessionProbe::has_live_session_honest` gate `endpoint run` uses (ADR-0041 single liveness authority). An honest session exists \u00e2\u2020\u2019 attach via the session-confirmed path regardless of persisted status. No honest session \u00e2\u2020\u2019 the existing offline refusal stands. A claimed session with a dead client tree \u00e2\u2020\u2019 refusal/reap, never attach. Reuse `SessionProbe`; no new liveness heuristic. <!-- --> 2. **Resume stamps UNBOUND.** A resume launch transitions an exist"
  },
  "REQ-RC-RECONNECT": {
    "title": "B-3 (REMOTE-TRUTH triage \u00c2\u00a7B-3, the operator-asked UX): the rc attach viewport RECONNECTS on a severed transport instead of print-and-exit. Pre-fix rc was one-shot (resolve\u00e2\u2020\u2019dial\u00e2\u2020\u2019attach\u00e2\u2020\u2019pump\u00e2\u2020\u2019parting line); FAULT-MATRIX row 9 over-promised. FIX: the establish sequence (daemon ensure \u00e2\u2020\u2019 broker conn \u00e2\u2020\u2019 session resolve local-first/cross-node \u00e2\u2020\u2019 dial \u00e2\u2020\u2019 attach-open w/ A-4b tracing retry \u00e2\u2020\u2019 subscribe) is factored into establish_attach and run_attach_inner wraps establish+pump in a loop. RECONNECTABLE class = severed transport ONLY: PumpEnd::BrokerGone (broker-conn EOF class, broker bounce) + the NEW PumpEnd::Severed (serve-side stream EOF AFTER rendered output = remote conn drop \u00e2\u20ac\u201d pre-fix MISLABELED as 'detached \u00e2\u20ac\u201d still running'; a nothing-rendered EOF stays the honest NoLiveSession refuse). FINAL ends (Exited/Detached/Displaced/Stalled/NoLiveSession) never re-drive \u00e2\u20ac\u201d re-attaching a deliberately-ended session is wrong. On sever: full-screen centered 'Reconnecting to {target}\u00e2\u20ac\u00a6' banner (pure byte-emit like StatusRow; target = owning-node label or 'local daemon'; Q4 UX rule \u00e2\u20ac\u201d operator language, internal sever detail never paints), then re-drive establish_attach every RECONNECT_PAUSE (1s) inside RE",
    "doc": "Proven rows (injection evidence in-tree): | # | Fault injected | Blast radius (by design) | Survives untouched | Recovery | Evidence | |---|---|---|---|---|---| | 1 | **Brain killed mid-PTY-stream** (logic crash / routine self-update) | Logic halts until restart | Broker, PTY child (pid stable), output log | New brain re-subscribes from cursor; gapless + exactly-once | `spt-daemon/tests/` B2/B9 handoff suite; `tests/idempotent.rs` (crash before-intent / before-effect / after-effect) | | 2 | **Brain killed mid-QUIC-stream, receive side** | Logic halts | Broker-owned endpoint, conn, stream, read"
  },
  "REQ-RC-RESIZE-PRESENTATION-BARRIER": {
    "title": "During a resize transition, LIVE DELIVERY is barriered along with the grid parse: no attached sink (controller or viewer) ever receives mixed-geometry raw bytes; commit substitutes ONE synthesized sync frame at the target geometry. (hertz post-fix field RCA 2026-07-21 on a fully-0.39.3 node, doyle-accepted with every cite verified at source; SUCCEEDS REQ-RC-RESIZE-GEOMETRY-EPOCH, which shipped correct but scoped to the wrong surface \u00e2\u20ac\u201d broker.rs's own ResizeTransition doc states append 'still rings + fans out every chunk exactly as before' during a transition, and OutputLog::append gates ONLY grid.advance. A live attached terminal therefore receives old+new-geometry differentials across an already-resized viewport; the cold-attach repaint model is definitionally blind to that path, which is why the shipped gate was green while the field was red. REQUIREMENT-SCOPE defect: the gate held against documented design.) CONTRACT (ADR-0031 Amendment II): (1) append still assigns seq and rings every chunk, but controller handoff and viewer fan-out are SUPPRESSED during the transition \u00e2\u20ac\u201d a skip, never a block or sleep (KH 7.12 intact), and the suppressed window does not count toward the control",
    "doc": "<!-- the presentation barrier over live delivery: during a resize transition the ring keeps recording but controller and viewer delivery is suppressed; commit lands the grid at the target geometry and pushes ONE synthesized sync frame (repaint ++ deferred non-grid bytes) to every attached sink under the same OutputLog serialization before raw fan-out resumes; the cursor-of-record advances past suppressed frames as-if-written; resume never raw-replays across a transition boundary. -->"
  },
  "REQ-REACH-1": {
    "title": "Off-node remote-drive detection + file transfer",
    "doc": ""
  },
  "REQ-READY-AGENT-RESUME": {
    "title": "An offline ReadyAgent shows in `spt endpoint run`'s picker Resume-from-history and resumes correctly \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d though it has a session_id \u00e2\u20ac\u201d produces ZERO ledger rows \u00e2\u2020\u2019 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 \u00e2\u2020\u2019 sessions::append Boot, mirroring establish_perch). FIX (2): `spt endpoint run --resume <session>` honors the adapter MANIFEST's endpoint TYPE \u00e2\u20ac\u201d 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) \u00e2\u20ac\u201d so (2) likely already holds; VERIFY at code, build only the residual. (v0.12.0)",
    "doc": "**`spt endpoint run` is the spt-hosted bringup for BOTH endpoint types** (v0.12.0): <!-- --> The bringup core is **type-agnostic** \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d 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"
  },
  "REQ-REGISTRY-REPLAY-BOUNDED": {
    "title": "REGISTRY-LIFECYCLE W1 (ADR-0040 decision 4, hertz defect B leg 4, defense-in-depth behind REQ-ONEWAY-STREAM-TERMINAL): a deadline-poisoned one-way (Registry) replay carries a per-stream strike budget; at budget the row retires TERMINAL with loud telemetry (DISPATCH_EV event). Safe by family semantics: a feed is an idempotent snapshot advertisement, the next pump round (~30s) re-advertises \u00e2\u20ac\u201d worst-case loss is one advertisement round, already the family freshness unit. Request/reply families keep ADR-0038 Amendment circuit-breaker semantics \u00e2\u20ac\u201d terminal-at-budget is ONE-WAY-FAMILY-ONLY (never a revert toward burn-the-claim abandonment for request streams). Gate: impl \u00e2\u20ac\u201d strike budget + terminal retire, one-way-scoped; unit \u00e2\u20ac\u201d poison strikes accumulate per stream, budget retires terminal + emits, request/reply family unaffected by the budget path; int \u00e2\u20ac\u201d poisoned registry replay stops redispatching after budget while fresh feeds keep serving.",
    "doc": ""
  },
  "REQ-RELAY-NO-BUSY-DELIVER": {
    "title": "MSG-IDENTITY W4 (operator self-send probe 2026-07-09, companion leg): the relay/idle-inject carrier MUST NOT fire for an ACTIVE endpoint \u00e2\u20ac\u201d the daemon already guards send-time (daemon.stderr.log: 'ENDPOINT_INJECT: endpoint ACTIVE -> spool (deferred hint), not injected') yet the field row ended taken_leg='idle-inject' for a message sent while the endpoint was ACTIVE, so the guard is bypassed somewhere on the busy-to-idle EDGE (the parked-drain idle-injects rows that arrived during busy without re-checking whether a poll is concurrently draining them \u00e2\u20ac\u201d the edge itself is when BOTH carriers are plausibly live). RCA-FIRST with REQ-CARRIER-CLAIM-EXCLUSIVE (same rig, same instrument); if the exclusive claim alone closes the double-delivery this leg may reduce to an ordering assertion \u00e2\u20ac\u201d rule at RCA lock, don't build blind. Gate: unit \u00e2\u20ac\u201d the idle-edge drain re-verifies activity (or defers to the claim) before idle-injecting; a row taken by a poll is never idle-injected. Kin REQ-IDLE-PARKED-DELIVERY (the drain this guards), REQ-MSG-IDLE-EDGE-DRAIN, F-023 anti-starvation gate (do NOT break the already-idle delivery class).",
    "doc": ""
  },
  "REQ-REST-VERB-ROUTING": {
    "title": "A-3 (REMOTE-TRUTH triage \u00c2\u00a7A + Q3 operator-law): a BARE-id rest verb (spt wake/suspend <id>) routes across the subnet like send's fallback instead of failing local-only. ROOT (certain): cmd_rest (cli.rs:3296) gates the remote arm on id.contains('@'|':'); a bare id falls to the local-only arm (cli.rs:3340) \u00e2\u2020\u2019 daemon_rest_event \u00e2\u2020\u2019 info::read_info miss (resting.rs:248) \u00e2\u2020\u2019 'WOKE_FAIL:{id}: info.json absent or unreadable \u00e2\u20ac\u201d not a hosted perch'. cmd_send (cli.rs:5142) DOES fall back on a local miss; cmd_rest's remote arm (cli.rs:3307, wan_rest) already handles every WanRestOutcome \u00e2\u20ac\u201d it is simply never reached on a bare-id local miss. Contradicts CONTEXT:286 'a wake must route'. Q3 SUBSTRATE GAP: resolve_across_visible (registry.rs:971) filters only by Status::routable() and its Ambiguity payload is node-hexes-only \u00e2\u20ac\u201d it CANNOT express the Q3 status rule; per-candidate (node,status) comes from SubnetRegistry::instances(id). FIX: a NEW pure select_rest_target helper (status-aware, isolated from resolve_across_visible which cmd_send keeps) applying GOAL-SATISFACTION semantics (ADR/triage addendum @188d269, NOT naive verb symmetry \u00e2\u20ac\u201d the mixed case breaks symmetry): wake is an \u00e2\u02c6\u0192-goal (satisfied whe",
    "doc": ""
  },
  "REQ-RESUME-ADAPTER-FOLLOWS-SESSION": {
    "title": "D-2 (REMOTE-TRUTH triage \u00c2\u00a7D-2 + operator Q5 @c248afc): a resume-from-history restores the RECORDED session adapter (REQ-SESSION-ADAPTER-RECORDED) \u00e2\u20ac\u201d the resumed harness is the one the session ran under, re-stamped onto the endpoint PRE-SPAWN, and an unregistered recorded adapter refuses LOUDLY before launching anything. ROOT: the picker's resume_outcome (model.rs:1285) bakes adapter=ep.adapter_profile from the selected ENDPOINT, ignoring the ledger row \u00e2\u20ac\u201d so a resume always uses the endpoint's CURRENT adapter even when the session ran under a different one; and the endpoint's info.adapter is never re-stamped to the row's on the resume path (cli.rs:1962 skeleton writer early-returns for an existing perch \u00e2\u20ac\u201d adapter immutable, carried by bind's stamp_creation_fields). FIX (doyle fork ruling): ResumeRow (model.rs:199) gains adapter: Option<String> threaded from SessionEntry.adapter in picker/data.rs; the row title (model.rs:228) renders [{adapter}] when Some ({head} [{adapter}] - {time} (\u00e2\u20ac\u00a6{id5})); resume_outcome bakes the ROW's adapter with an endpoint fallback (row.adapter.unwrap_or(ep.adapter_profile)) \u00e2\u20ac\u201d None \u00e2\u2020\u2019 the endpoint's current stamp (benign degrade). The pre-spawn RE-STAMP + ref",
    "doc": ""
  },
  "REQ-RESUME-HARNESS-SESSION-ID": {
    "title": "B2 (F028, hall-b diagnosis, verified 0.22.0): respawn/`--resume` feeds the SPT session id to `claude -r`. After the 02:05 daemon bounce respawn built `claude.exe -r 70b5bfa40901b7d4` \u00e2\u20ac\u201d an spt session id in claude's OWN session-id namespace -> claude hangs forever at a 'No sessions match' resume-picker while the endpoint reads online. Hits after EVERY daemon bounce + every picker Resume. The HARNESS session id (claude UUID, stamped in sessions.log/info.json by the hooks) is what {session_id} must mean in the adapter's [session.resume] command; the spt sid must not leak. FIX: substitute the HARNESS session id in the resume template (spt-core substitution-key semantics + LIKELY claude-spt manifest coordination \u00e2\u20ac\u201d FLAG perri BEFORE touching the manifest, adapter-boundary rule). Int: resume template receives the ledger UUID, not the spt sid. See triage B2.",
    "doc": ""
  },
  "REQ-RESUME-REAP-PRIOR-HARNESS": {
    "title": "B4 (F028, hall-b diagnosis, verified 0.22.0): `--resume` respawns a SECOND harness onto the SAME session without reaping the first. Observed live: resume of b4421cf9 spawned pid 34432 while gen1 (250376) kept running \u00e2\u20ac\u201d two claude.exe stacks, one session id. FIX: resume must reap/refuse when the session already has a live harness. See triage B4.",
    "doc": ""
  },
  "REQ-RESUME-UNBOUND-STAMP": {
    "title": "RC-RENDER-TRUTH W1 (ADR-0042 decision 2, operator-spotted second root seam): resume launch transitions an existing offline perch to UNBOUND before/with the broker spawn \u00e2\u20ac\u201d UNBOUND semantics are fresh/resume-invariant (broker session exists + harness not bound = UNBOUND); generation/session-safe ROLLBACK to offline on spawn failure or session death; bind owns UNBOUND->ONLINE. Supersedes the rc.rs 'resume gets no UNBOUND stamp \u00e2\u20ac\u201d accepted' boundary note (the pre-bind window can be PERMANENT: stuck native resume, SessionStart never fires \u00e2\u20ac\u201d field-proven; truthful UNBOUND is the operator-recovery surface that let `spt rc` reach the wedged TUI). Writer-truth complement to REQ-RC-HONEST-SESSION-AUTHORITY \u00e2\u20ac\u201d both land, neither substitutes. Gate: impl \u00e2\u20ac\u201d UNBOUND stamp at resume spawn + rollback + bind transition; unit \u00e2\u20ac\u201d stamp fires on existing-offline perch resume, rollback on spawn-fail restores offline, generation guard refuses a stale rollback over a newer bind; int \u00e2\u20ac\u201d resumed-but-never-bound endpoint reads UNBOUND (not offline) and `spt rc` attaches to its live session; doc \u00e2\u20ac\u201d ADR-0042.",
    "doc": "Decisions: <!-- --> 1. **`spt rc` consults the honest-session authority before the offline fast-fail.** Normal `spt rc <id>` runs the same bounded `SessionProbe::has_live_session_honest` gate `endpoint run` uses (ADR-0041 single liveness authority). An honest session exists \u00e2\u2020\u2019 attach via the session-confirmed path regardless of persisted status. No honest session \u00e2\u2020\u2019 the existing offline refusal stands. A claimed session with a dead client tree \u00e2\u2020\u2019 refusal/reap, never attach. Reuse `SessionProbe`; no new liveness heuristic. <!-- --> 2. **Resume stamps UNBOUND.** A resume launch transitions an exist"
  },
  "REQ-RUN-ID-REUSES-ADAPTER": {
    "title": "D-1 (REMOTE-TRUTH triage \u00c2\u00a7D-1): `spt endpoint run --id <id>` with NO --adapter, when <id> names an EXISTING perch, REUSES that perch's recorded info.adapter and runs NON-INTERACTIVELY \u00e2\u20ac\u201d instead of always falling to the picker as a create-new prefill (an existing endpoint retyping its own adapter, or being sent to a create-new flow, is the operator wart). ROOT (certain, no design tension): the cli `match (adapter,id)` special-cased only (Some,Some)\u00e2\u2020\u2019cmd_endpoint_run; the catch-all routed EVERY lone --id to crate::picker::run as a create-new prefill, never considering an existing endpoint (cli.rs ~1290). FIX: a PURE resolve_run_target(adapter, id, recorded) over the 4 (adapter?,id?) quadrants \u00e2\u20ac\u201d (Some,Some)\u00e2\u2020\u2019Direct{a,id}; (None,Some(id))\u00e2\u2020\u2019 recorded adapter present (info.adapter = adapter-chosen-at-creation, spt-store info.rs:167) \u00e2\u2020\u2019 Direct{recorded,id}, absent/no-perch \u00e2\u2020\u2019 Picker{None,Some(id)} (today's create-new prefill UNCHANGED); (Some,None)/(None,None)\u00e2\u2020\u2019Picker unchanged. The perch lookup (read_info(resolve_perch_path(id,Infer)).adapter) is INJECTED as a closure so the router is pure + testable without a perch on disk; resume threads into BOTH Direct paths. Red-first: (None,Some(id),reco",
    "doc": ""
  },
  "REQ-RUN-PICKER": {
    "title": "Interactive `spt endpoint run` picker (ratatui TUI): bare `spt endpoint run` (no --adapter/--id) enters an in-process picker (flags-present = the REQ-HOST-RUN-1 non-interactive path, untouched). Layer 1 picks kind (Create new | Pick existing). Create-new: choose a registered kind=\"harness\" adapter with its shipped+local profiles tree-nested (registry::registered / manifest.profiles / local_profile_names) \u00e2\u2020\u2019 enter a charset-validated id \u00e2\u2020\u2019 start. Pick-existing: category select (left/right) over [<cwd-project> | Local node | Subnet], endpoints grouped + alphabetically sorted per category, a status square per endpoint (online green \u00e2\u2013\u00a0 / offline gray \u00e2\u2013\u00a2 \u00e2\u20ac\u201d the blue \"attached\" tri-state + Kick are DEFERRED to a broker attach-presence slice, M12-W2-RULING Q1), type-to-filter (`/`, nucleo-matcher), a pinned keybind legend, and a right-half two-pane description (harness adapter:profile \u00c2\u00b7 best-effort project history newest\u00e2\u2020\u2019oldest from the contextstore p-<project> branches, empty-if-none \u00c2\u00b7 `spt endpoint description`). Confirm layer offers status-dependent options \u00e2\u20ac\u201d Attach/Start/View (rc pump / cmd_endpoint_run) \u00c2\u00b7 Instantiate-locally (remote) \u00c2\u00b7 Change-harness-adapter (offline) \u00c2\u00b7 Fork (cmd_fork) \u00c2\u00b7 ",
    "doc": "Shell sleep/wake (offline \u00e2\u2020\u201d online): **spt-hosted bringup picker (`spt endpoint run`)** (M12-W2): <!-- --> The user-facing bringup flow for spt-hosted endpoints. **Bare `spt endpoint run`** (no `--adapter`/`--id`) opens an in-process **ratatui picker**; the **flagged** form is the non-interactive bringup path (`--adapter <a[:profile]> --id <id> --create|--resume <session> --start|--attach|--view`), untouched \u00e2\u20ac\u201d a picker selection bakes exactly that path. **Layer 1** picks the kind (*Create new* | *Pick existing*). **Create-new** chooses a registered `kind=\"harness\"` adapter with its shipped+loc"
  },
  "REQ-SCREENGRID-REPAINT-MODE-REPLAY": {
    "title": "RC-RENDER-TRUTH W3 (ADR-0043 decision 4, hertz stale-glyphs RCA leg 4 P1): ScreenGrid cold repaint replays EVERY tracked render-affecting mode \u00e2\u20ac\u201d DECSTBM scroll margins at minimum \u00e2\u20ac\u201d before final cursor placement (today render_repaint omits tracked margins, so client and server grids interpret subsequent raw scrolling against different regions => stale/moved rows after reattach/resize; the trailing-blank omission after ED2 is semantically correct and NOT the bug). Stateful emulator contract: dirty screen + synthesized repaint + next raw frame == server grid. Gate: impl \u00e2\u20ac\u201d tracked-mode replay in render_repaint; unit \u00e2\u20ac\u201d repaint emits tracked DECSTBM, emulator contract holds for scroll-after-repaint; doc \u00e2\u20ac\u201d ADR-0043.",
    "doc": "Decisions: <!-- --> 1. **One FIFO sequencer per attach sink.** The PTY drain/output writer is the sole sequencer for terminal Output and Exit: Exit is enqueued behind all prior output for each sink (drain EOF/completion first, then Exit). A mutex alone is insufficient \u00e2\u20ac\u201d producer order is the contract. Output-before-Exit is a production-path invariant, regression-proven end-to-end (broker \u00e2\u2020\u2019 attach \u00e2\u2020\u2019 rc). <!-- --> 2. **rc display teardown is unconditional, idempotent, and separate from input teardown.** A display RAII guard (distinct from the OS input/raw-mode guard) runs on every exit path incl"
  },
  "REQ-SEAT-LIFETIME-BOUNDED": {
    "title": "REGISTRY-LIFECYCLE W1 (ADR-0040 decision 5, hertz defect B leg 5 CROSS-FAMILY per the final attribution correction \u00e2\u20ac\u201d 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) \u00e2\u20ac\u201d field censuses attribute, not infer. Gate: impl \u00e2\u20ac\u201d verb + worker/sweep call sites + thread naming; unit \u00e2\u20ac\u201d unsubscribe removes seat/joins writer/drops cursor + idempotent + unknown-stream tolerated; int \u00e2\u20ac\u201d rides REQ-HAZARD-REGISTRY-STALL seam (physical seat/thread counts plateau CROSS-FAMILY after completion); doc \u00e2\u20ac\u201d ADR-0040.",
    "doc": "Decision: <!-- --> 1. **One-way (fire-and-forget) stream families are terminal at FIN, sender-side.** The registry feed pump retires its own row after successful write+FIN via the existing `net-stream-retire` verb (best-effort on N-1 brokers, per ADR-0038 A). A one-way family's exchange is definitionally over at FIN; keeping the row eligible reproduces the O(history) defect forever. 2. **Eligibility filtering is server-side.** `stream_infos` excludes `initiated_locally` rows (alongside `retired`) before serializing. Consumers keep their client-side guards (double-filter harmless; N-1 compatibl"
  },
  "REQ-SELF-DETECT-PARENT-PID": {
    "title": "E-1 (REMOTE-TRUTH triage \u00c2\u00a7E-1 #7): self-detect leg (c) \u00e2\u20ac\u201d the pid-ancestry fallback \u00e2\u20ac\u201d ALSO candidates on `rec.parent_pid` (the harness pid, CONTEXT's 'stable session-binding anchor', stamped at bind), not `rec.pid` alone. ROOT: for an spt-hosted endpoint (broker PTY, headless) `rec.pid` is the ephemeral bind-CLI pid, ALREADY DEAD by send time (the F-026 #11 dead-pid class, field-sighted on hall-bf) \u00e2\u20ac\u201d never in any sender's ancestry and alive-gated out \u00e2\u20ac\u201d so an spt-hosted sender could NEVER resolve self via leg (c): its messages were from-stamped `cli@NODE` (operator #7) and replies bounced NO_PERCH. FIX: detect_self_by_ancestry pushes a second candidate (id, parent_pid) when `rec.parent_pid` is Some + alive; the pure nearest-first matcher (match_self_by_ancestry) is unchanged. LABEL-ONLY, exactly like the rest of leg (c): from-label/routing default, NEVER authentication \u00e2\u20ac\u201d authenticate() untouched, the pid-ancestry-for-auth question stays parked (KH 7.3/7.5 separation holds; a wrong label self-corrects, a wrong grant does not). Env legs (a)/(b) stay first. Red-first int (the triage-specified missing test): rec.pid = dead sibling + rec.parent_pid = genuine live ancestor \u00e2\u2020\u2019 self resolves ",
    "doc": ""
  },
  "REQ-SELF-ID-TRUST-INJECTED-ENV": {
    "title": "DEFERRED to a followup vX.X.n sprint (post-LIFECYCLE-TRUTH, operator-ruled 2026-07-07): self-identity resolution must trust the harness-injected authoritative id and detect a stomped perch instead of silently mis-attributing. ROOT (doyle /diagnose 2026-07-07, field: agent sends stamped `cli@HFENDULEAM` / mis-attributed): `resolve_from` (cli.rs:5480) stamps `cli@<node>` when `detect_self_id` (roster.rs:103) returns None; detect_self_id resolves self ONLY by reverse-lookup \u00e2\u20ac\u201d matching `$OWL_SESSION_ID` against a perch's info.json.session_id (then SPT_AGENT_ID, then parent_pid) \u00e2\u20ac\u201d and IGNORES `SPT_ENDPOINT_ID`, the authoritative self-id the adapter injects (present in-env as SPT_ENDPOINT_ID=<id>). When a perch record is STOMPED (a cross-id info.json overwrite \u00e2\u20ac\u201d the REQ-SPAWN-COLLISION-GUARD-LIVE-DUP damage class; field case: doyle's live session_id written into the deployah perch), the reverse-lookup mis-resolves (doyle session -> `deployah`) or fails (real deployah -> None -> `cli@node`), and the CLI silently believes the stomped store. FIX: detect_self_id PREFERS `SPT_ENDPOINT_ID` when set+non-empty (the harness-authoritative id, immune to a stompable perch), AND cross-checks it again",
    "doc": ""
  },
  "REQ-SEND-STAMP-AGENT-ID": {
    "title": "MSG-IDENTITY W4 / endpoint-identity (operator-flagged 2026-07-09 + live-confirmed on flynn's F-038 ask arriving 'cli@HFENDULEAM'): a live agent's own CLI `spt send <target>` MUST stamp from_id with the AGENT id (e.g. 'doyle'), not the node fallback 'cli@<node>' \u00e2\u20ac\u201d today the agent-id stamp rides ONLY the adapter/perch shortform path, so any agent shelling out `spt send` (the DOCUMENTED reach-another-agent form) presents to recipients as an anonymous node CLI: replies mis-route (recipients answer cli@node \u00e2\u20ac\u201d no perch \u00e2\u20ac\u201d instead of the sender), and the F-036 victim-effect ('sends downgraded to from:cli@node') is indistinguishable from normal CLI traffic. FIX: send-time self-resolve \u00e2\u20ac\u201d when the calling process/session maps to a bound live perch on this node (the session-pin/seed machinery already resolves this for bind), stamp that endpoint id as from_id; a genuinely perchless CLI keeps 'cli@<node>'. Gate: unit \u00e2\u20ac\u201d a send from a session bound to a live perch stamps the endpoint id; a perchless shell keeps the node stamp; int \u00e2\u20ac\u201d recipient's EVENT from= carries the agent id for a shelled-out send from a live session. Kin F-036 victim effects, EVENT envelope (ADR-0020), [[owl-send-not-legacy-spt",
    "doc": ""
  },
  "REQ-SEND-WINDOW-DRAIN-HONOR": {
    "title": "F-035 (field finding 2026-07-09): active_only = POLL-ONLY for a relay-bearing live agent -- it must NEVER be RELAY-delivered (its contract is 'active hook window only, never wakes' per spool.rs WINDOW_ACTIVE_ONLY doc + cli.rs:85; `spt send --active-only` / the hidden `--deferred` alias and `send_deferred` shell-context mint it). FIELD SYMPTOM: lia (a full live agent -- relay-for-idle, poll-for-busy) surfaced an --active-only msg on her IDLE RELAY. RCA JOURNEY: v1 RCA (docs/F-035-RCA.md) analyzed the WRONG class (spt-hosted-relay-LESS, the idle-edge inject leg) and proposed a COLLAPSE that would have broken the shipped F-023 anti-starvation gate (docs/F-035-CONFLICT.md); operator reclassified to a relay-bearing live agent; the relay-class re-RCA (docs/F-035-RELAY-RCA.md) traced EVERY active_only->relay carrier and found them ALL ALREADY GUARDED on main@2c05dc9 -- so spt-core has NO code bug. doyle FINAL RULING: the real leak is the ADAPTER's busy->idle poll->idle-representation handoff (spt-claude-code -- a legitimate `api poll` on going idle drains active_only, then the adapter renders it into the idle/relay surface), OUTSIDE spt-core; perri's lane. spt-core DELIVERABLE = a REGRESS",
    "doc": "<!-- F-035 RCA \u00e2\u20ac\u201d report-before-fix. --> # F-035 RCA \u00e2\u20ac\u201d idle-edge parked-drain ignores the delivery-window tag"
  },
  "REQ-SESSION-RESUME-TEMPLATE": {
    "title": "Resuming an endpoint session that HAS conversation history brings up a BLANK session. ROOT (doyle, code-grounded + CONTEXT \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d its NATIVE resume'), and the manifest already has the resume-variant pattern (Session has BOTH psyche_init AND psyche_resume, manifest.rs:217-219) \u00e2\u20ac\u201d 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",
    "doc": "`[session.<role>]` \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d if your harness resumes a transcript by id, use that form (Claude Code: `claude -r {session_id} \u00e2\u20ac\u00a6`), **not** the fresh creat"
  },
  "REQ-SESSIONS-LOG-ENDPOINT-ATTRIBUTION": {
    "title": "C2 (F028, infra; ROOT-CAUSED + severity-upgraded doyle RCA 2026-07-03): cross-endpoint perch contamination \u00e2\u20ac\u201d a foreign psyche's SessionStart hook REBINDS a victim perch's IDENTITY, not merely its ledger. Evidence: hall-a's info.json.session_id IS f015b-probe-psyche's session (359d7bd7) + hall-a's ledger holds the foreign psyche session; same class as hall-b's dead-pid stamp (141556). This REQ = the OBSERVABLE (foreign session_id in a perch's ledger/info.json + resume offering foreign sessions) and its belt-braces: (iii) filter owlery-cwd rows OUT of resume_rows; (iv) one-time repair for already-contaminated perches (hall-a on HFENDULEAM) or self-heal on next legitimate session-start. The ROOT (identity pinned at spawn + honest bind + nested self-resolve) is REQ-BIND-HONEST-SELF-STAMP \u00e2\u20ac\u201d C2 is UPSTREAM of B3 (presence/CONTROLLED read the very stamps this corrupts). See triage C2.",
    "doc": ""
  },
  "REQ-SHELL-3": {
    "title": "Drive channel (owner->shell, REST-only, never-spooled, latest-wins): the owner->shell mirror of sensory for continuous real-time control (scroll/crank/stick/avatar) \u00e2\u20ac\u201d a [shell.drive] manifest vocab + EVENT_TYPE_DRIVE frame, delivered to the ONLINE binary only via a single live slot (a new frame supersedes an undelivered one \u00e2\u20ac\u201d no spool, no queue, no replay on relink), dropped-with-diagnostic if the shell is offline; cross-node rides the ephemeral link (REST class), never the durable shell spool. Commands = discrete+durable; drive = continuous+ephemeral (CONTEXT:260, minted 2026-06-11 Gateway grill).",
    "doc": ""
  },
  "REQ-SHELL-5": {
    "title": "Shell ownership is owner-type-agnostic: any non-Shell endpoint type may own/spawn/drive/command/link a shell (Gateway the named first) \u00e2\u20ac\u201d control-exclusivity keys on the owner endpoint_id, NEVER on the owner's endpoint type. No ownership path (mint, launch, owner-from-link, cmd, drive, tunnel, sleep/wake, owner-shutdown) inspects the owner's type (CONTEXT:264, ratified 2026-06-11 Gateway grill).",
    "doc": "Shell model (detailed): <!-- owner-type-agnostic: control-exclusivity keys on the owner endpoint_id, never the owner's endpoint type --> **Owner-linked, exclusive.** Spawned by an **owning endpoint**; both linked; **only that owner `endpoint_id` may control it**. Agent endpoints are the common owner, but ownership is NOT agent-exclusive \u00e2\u20ac\u201d any non-Shell endpoint type may own shells (ratified 2026-06-11, the Gateway grill: e.g. a **Gateway** owns the driven surfaces it steers; a future **Resource** endpoint could too). Control-exclusivity does not mean interaction-exclusivity: a Shell stays 2-wa"
  },
  "REQ-SHELL-ADAPTER-OWNED-DETACHED-SERVICE": {
    "title": "RULING OWED (unratified \u00e2\u20ac\u201d do NOT satisfy without one): may a shell adapter own a DETACHED process that deliberately OUTLIVES its shell's link-break? Today the answer is 'no, silently, on Windows only'. `shellhost::kill_shell_pid` force-kills with `taskkill /PID <pid> /T /F` \u00e2\u20ac\u201d /T is a TREE kill, so `close_shell` takes the shell's descendants with it; the Unix arm is `kill -9 <pid>`, a SINGLE process. So the same adapter's detached child dies on Windows and survives on Linux, undocumented either way. Two ratified positions collide here and neither may be quietly discarded: (a) CONTEXT \u00c2\u00a7Shell model lifecycle states flatly 'the binary never survives a link-break', and the KNOWN-HAZARDS 'stop lies' RCA (two field cases 2026-07-19, one on doyle's own production endpoint) was paid for precisely because a surviving GRANDCHILD was orphaned where 'even a direct-child kill would have missed it' \u00e2\u20ac\u201d that hazard is the standing argument FOR /T; (b) a node-singleton service that must not drop every time a shell relinks is a legitimate shape, and flynn's alchemy ships one today (Hub Daemon, CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP, lockfile-guarded), depending on surviving link-breaks to keep a ",
    "doc": ""
  },
  "REQ-SHELL-FRAME-VOCAB": {
    "title": "The shell relay frame vocabulary is a PUBLISHED contract, not a reverse-engineered one. (flynn spt-alchemy clean-room audit 2026-07-21, doyle code-verified, seed pair item 1.) TODAY: shellchan.rs composes the exact frames a shell binary must parse \u00e2\u20ac\u201d shell_command / shell_text / shell_file (+ shell_close, sensory, drive) \u00e2\u20ac\u201d but the published export (docs-site/src/shells/) carries ZERO occurrences of those type names; the docs say only 'the shell child parses its own vocabulary', so every adapter author (notify-shell, alchemy) reverse-engineers the frame shapes compatibly from source. FIX: publish, in the shells section of the docs-site export, the frame type names + their attrs (op, xfer-id, path) + body encodings \u00e2\u20ac\u201d a shell_command body is a JSON object of named args (positionals zipped against the manifest's declared arg names), a shell_text body is the raw text, a shell_file body is the original filename with the perch-relative landed path in the path attr, a shell_close body is the manifest's pre_close instruction (NOT vocabulary-checked \u00e2\u20ac\u201d the vocabulary gates agent commands, the manifest is its own authority over its own binary). Fold in the quoted-composite-tail sharp edge: the ",
    "doc": "The frame contract: what a shell binary parses: <!-- -->"
  },
  "REQ-SOFT-END-PRESERVES-LIVE-LISTENER": {
    "title": "F-2 (REMOTE-TRUTH triage \u00c2\u00a7F-2, field-repro'd hall-bf 2026-07-04): a /clear must not sever a SURVIVING poll listener's relay address \u00e2\u20ac\u201d post-clear owl-path send hit NO_PERCH while ready was present and the inject path healthy. ROOT (source-certain): the relay registry row (id\u00e2\u2020\u2019addr + owning pid, registered by the LISTENER process itself at PollListener::bind, listener.rs:109) is DELETED by the adapter's soft `api session-end` (reporting.rs:231) fired for the DEPARTING session at /clear; but the poll listener SURVIVES /clear (a session-independent process, still bound on its port), so the deletion destroys a TRUE row. The C-2 boundary re-stamp (REQ-HAZARD-BOUNDARY-READY-STRAND) restores ready + status online but CANNOT re-register \u00e2\u20ac\u201d only the listener process knows its socket addr \u00e2\u20ac\u201d so every subsequent send lookup misses \u00e2\u2020\u2019 NO_PERCH forever (until a listener restart re-binds). FIX: the SOFT arm of cmd_session_end unregisters CONDITIONALLY through the single liveness resolver (liveness::is_registry_entry_alive \u00e2\u20ac\u201d the KH 2.5-aware resolver clean_stale_entries routes through): a row whose owner is still ALIVE is PRESERVED (the row is LISTENER-scoped truth, not session-scoped; the listener ou",
    "doc": ""
  },
  "REQ-SPOOL-TAKE-AUDIT": {
    "title": "W5 (LIFECYCLE-TRUTH, RCA cost: proving WHO took delivered=1 rows burned an hour): the spool records the taker per row \u00e2\u20ac\u201d leg enum (relay-backlog / hook-poll / idle-inject / psyche) + sid/pid + taken_at ms \u00e2\u20ac\u201d surfaced by a --json debug read. Additive column, no schema break (delivered rows already retained).",
    "doc": ""
  },
  "REQ-STREAM-INFOS-SERVER-FILTER": {
    "title": "REGISTRY-LIFECYCLE W1 (ADR-0040 decision 2, hertz defect B leg 2): broker stream_infos excludes initiated_locally rows SERVER-SIDE (retired already excluded) before cloning/serializing to the dispatcher \u00e2\u20ac\u201d the O(history) IPC enumeration cost dies at the source. Dispatcher keeps its client-side guard (double-filter harmless; N-1 compatible both directions: older dispatcher skips those rows anyway, older broker just keeps the old cost). Gate: impl \u00e2\u20ac\u201d server-side filter; unit \u00e2\u20ac\u201d local rows absent from the reply while peer-initiated rows with bytes remain, opener/re-attach enumeration semantics unchanged.",
    "doc": ""
  },
  "REQ-STREAM-LEASE-CLASSES": {
    "title": "REGISTRY-LIFECYCLE W2 (ADR-0040 decision 6 + ADR-0041, emphasys C2 P0): streams declare a lifetime class at open \u00e2\u20ac\u201d RC attach/view streams are ConnectionBound (opener conn EOF means the target sees FIN, serve_attach runs detach_session, controller slot + CONTROLLED stamps clear \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d brain-swap correctness depends on it). Late-close identity validated (stale opener A close cannot evict newer controller B \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d class at open + ConnectionBound EOF chain; unit \u00e2\u20ac\u201d class routing + absent-defaults-Durable + late-close identity refusal; int \u00e2\u20ac\u201d raw viewport close frees the controller full-chain incl. across broker restart, brain_swap/daemon_refresh/redispatch legs stay green; doc \u00e2\u20ac\u201d ADR-0040/0041.",
    "doc": "Decision: <!-- --> 1. **One-way (fire-and-forget) stream families are terminal at FIN, sender-side.** The registry feed pump retires its own row after successful write+FIN via the existing `net-stream-retire` verb (best-effort on N-1 brokers, per ADR-0038 A). A one-way family's exchange is definitionally over at FIN; keeping the row eligible reproduces the O(history) defect forever. 2. **Eligibility filtering is server-side.** `stream_infos` excludes `initiated_locally` rows (alongside `retired`) before serializing. Consumers keep their client-side guards (double-filter harmless; N-1 compatibl"
  },
  "REQ-STREAM-LIFETIME-CLASS": {
    "title": "RESCOPED at W2 activation 2026-07-22 (doyle verify-first, C3-retirement precedent): the CLEAN-CASE chain this REQ was minted for is ALREADY SHIPPED under REQ-STREAM-LEASE-CLASSES / ADR-0040 decision 6, verified against @7c0f12d \u00e2\u20ac\u201d StreamLifetime is opener-declared and on the wire, serde-default Durable so N-1 openers keep exact today-semantics (msg.rs:810-836 + the additive-wire unit @msg.rs:1408); rc attach/view is the SOLE ConnectionBound opener (attach.rs:667-677); the broker binds the class at open (broker.rs:5689) and nethost carries it per StreamEntry (nethost.rs:1059-1076); the conn-exit sweep FINs each ConnectionBound stream toward its target and terminal-retires the local row while Durable rows never enter it (broker.rs:4118-4123/4227-4235/4429-4446); the target's serve_attach EOF arm runs detach_session_gen(serve_gen) so controller/viewer stamps clear on exactly one generation (attach.rs:580-597); the late-close gen guard (broker.rs:2179-2192, unit @8416) and converge_perch_stamps close the race; int coverage rides endpoint_lifecycle.rs:241. Building that again would re-implement shipped code \u00e2\u20ac\u201d the C3 lesson. WHAT REMAINS, and what this REQ now owns: RESTART-REPLAY RE-ESTA",
    "doc": "Amendment 1 (2026-07-22, DAEMON-LIFECYCLE W2) \u00e2\u20ac\u201d teardown authority is opener-declared class PLUS transport liveness, enforced at the three places decision 6 could not see: <!-- --> <!-- -->"
  },
  "REQ-SUBNET-3": {
    "title": "Node labels: hostname-default, gossiped, addressable in @node qualifiers (refuse-on-ambiguity)",
    "doc": ""
  },
  "REQ-SUBNET-4": {
    "title": "Subnet membership mutations elevation-gated (create = seed reveal; join = trust-boundary enrollment)",
    "doc": "Product-surface amendment (2026-06-05 \u00e2\u20ac\u201d M7 D3): <!-- -->"
  },
  "REQ-SUBNET-7": {
    "title": "Per-machine re-pair trust overwrite: registry rows carry a hashed stable machine identifier (OS machine id /etc/machine-id|MachineGuid, domain-separated SHA-256 before gossip, spt-minted persisted UUID fallback; additive serde-default field \u00e2\u20ac\u201d old rows parse clean); a COMPLETED pairing ceremony presenting the same node label AND machine id as an existing trusted row evicts the superseded identity's trust + registry rows on the seed-holder and replicates the eviction; a gossiped claim alone never evicts trust (M8 decisions 13, 22)",
    "doc": ""
  },
  "REQ-SUBNET-8": {
    "title": "Status render honesty: zero-subnet text is daemon-aware ('No subnets registered \u00e2\u20ac\u201d this node is standalone.' + daemon-running-dependent blurb, never implying messaging works while the daemon is down); hint footer prints on bare spt subnet only (status drops it); a stalled pump is surfaced in subnet status, never rendered implied-healthy (M8 decisions 11-12, 23)",
    "doc": ""
  },
  "REQ-TERM-2": {
    "title": "session-surface abstraction; send-keys + send-line injection",
    "doc": ""
  },
  "REQ-TRANSLATE-BINARY-LIVENESS-DECAY": {
    "title": "SUPERSEDED by REQ-TRANSLATE-COMMIT-MISS-TOLERANCE (F029 C-1). B6 (F028, perri F-e) was ROOT-PINNED as the commit-deadline-miss fault: at a checkpoint clear boundary the clear-only inject's {commit} was never observed within INJECT_COMMIT_DEADLINE, so the inject worker FAULTED + TERMINATED a HEALTHY translate binary and (by ADR-0022) never respawned \u00e2\u2020\u2019 every subsequent force-native reported delivered=false ('no live translation binary'). NOT a dormancy/liveness-registration decay (that hypothesis is dead) \u00e2\u20ac\u201d deterministic at every checkpoint-armed boundary. The fix (miss != fault + N=3 strike budget + bounded respawn + perch-visible fault stamp) lives under REQ-TRANSLATE-COMMIT-MISS-TOLERANCE + REQ-HAZARD-TRANSLATE-FAULT-PERMANENT-DEATH. See triage addendum C-1.",
    "doc": ""
  },
  "REQ-TRANSLATE-COMMIT-MISS-TOLERANCE": {
    "title": "C-1 (F029, B6 ROOT \u00e2\u20ac\u201d rescope of REQ-TRANSLATE-BINARY-LIVENESS-DECAY, now PINNED): at a checkpoint clear boundary the clear-only inject drives `/clear`; its `{commit}` is never observed within INJECT_COMMIT_DEADLINE (5s, broker.rs:158) so the inject worker FAULTS + TERMINATES a HEALTHY translate binary (broker.rs respool_and_fault + return) and by ADR-0022 design NEVER respawns \u00e2\u2020\u2019 every subsequent force-native reports delivered=false ('no live translation binary', cli.rs:5046) = B6's exact field signature; the v0.12.0 checkpoint post-clear WAKE dies with the terminated binary + the fire-and-forget FIRE in the dead window. NOT a race \u00e2\u20ac\u201d deterministic at every checkpoint-armed boundary (perri captured-stderr proof: TRANSLATION_FAULT on the F-019 unread daemon-stderr channel). FIX (REVISED, supersedes terminate-then-respawn): miss != fault \u00e2\u20ac\u201d preserve the binary; the watchdog's job is ANTI-STALL (release the operator floor), not execution-verification. See addendum C-1 + ADR-0022 amendment.",
    "doc": "Amendment (2026-07-03, F029 C-1) \u00e2\u20ac\u201d a commit-miss is NOT a fault; real faults respawn: <!-- -->"
  },
  "REQ-UPD-7": {
    "title": "Origin-source update bootstrap (`spt update fetch`): pull the latest signed release directly from the GitHub release origin (`SaberMage/spt-releases`) \u00e2\u20ac\u201d the per-platform artifact + its `<asset>.release.json` SignedRelease metadata \u00e2\u20ac\u201d and stage it through the EXISTING verify\u00e2\u2020\u2019stage 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.",
    "doc": ""
  },
  "REQ-UPD-8": {
    "title": "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 \u00e2\u20ac\u201d 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.",
    "doc": ""
  },
  "REQ-UPD-9": {
    "title": "`gh_release` adapter [update] avenue (optional signing): an adapter declares `[update] avenue = \"gh_release\", repo = \"user/repo\"` (+ optional `asset`, default `adapter.spt`; + optional Ed25519 `signing_key`); spt-core's ripple compares the repo's LATEST GitHub release version against the installed adapter version and, when newer, auto-updates by fetching the release `.spt` archive (the REQ-INSTALL-9 `--release` fetch primitive) \u00e2\u2020\u2019 verifies the `.spt` against `signing_key` if declared, else HTTPS+GitHub first-acquisition trust \u00e2\u2020\u2019 re-extracts + re-registers the adapter root. Lets a harness adapter ship updates from its own GitHub releases with NO signing tooling or plugin coupling (removes the perri file_pull/delegated avenue blockers). Acquisition-trust mirrors `--release` + the installer first-fetch; does not alter spt-core self-update (REQ-UPD-1..8).",
    "doc": "Runtime model: **adapter update declaration** (manifest field): <!-- --> Each adapter manifest declares how spt-core should *ripple-update the adapter itself* (see Self-update). One of: **file-pull** (a plugin-directory lookup regex + a gh repo for the adapter's latest files \u00e2\u20ac\u201d spt-core fetches + swaps), **delegated command** (a binary command the adapter owns, e.g. `claude.exe plugin update` \u00e2\u20ac\u201d spt-core invokes it), or **gh_release** (the adapter ships its updates from its own GitHub releases). After initial bootstrap, the plugin no longer self-manages updates; spt-core conducts them. The **gh_"
  },
  "REQ-UPDATE-ADAPTERS-VERB": {
    "title": "THE-FORKENING W4 (operator-grilled 2026-07-14): `spt update adapters [<a>[,<b>...]]` = thin ALIAS over the existing `spt adapter update` engine (cli.rs:748 gh_release avenue; the old verb STAYS \u00e2\u20ac\u201d published surface) + comma-list accepted on BOTH forms. Semantics: no names -> all gh_release-avenue registrations; names validated FAIL-FAST against the registry BEFORE any update starts (a typo must not leave a half-updated set); per-adapter failure ISOLATION (one failure doesn't stop the rest) with a per-adapter summary line; nonzero exit if any failed; local-path/dev registrations SKIP loud (not error). Gate: unit \u00e2\u20ac\u201d name validation, list parsing, isolation + exit-code aggregation, local-path skip; doc \u00e2\u20ac\u201d reference regen (drift-gated). Kin REQ-UPDATE-DEFAULT-COMPOSITE (the caller), REQ-ADAPTER-UPDATE-MESSAGE (per-adapter apply notices ride the summary).",
    "doc": "Self-update: **update composite (`spt update`)** \u00e2\u20ac\u201d the plain verb is the primary form: `update fetch --apply` then `update adapters` (core-first order); with core already current, only adapters update. `--core-only`/`-c` skips adapters; `spt update adapters [<a>[,<b>\u00e2\u20ac\u00a6]]` is the adapters leg alone (alias over `spt adapter update`). The composite's invoker always survives, because a routine apply cycles only the **brain** \u00e2\u20ac\u201d the *restart-required* message on broker-side releases is a notice, not a restart. `spt update --restart` is the one-step **full cycle**: fetch \u00e2\u2020\u2019 adapters \u00e2\u2020\u2019 `apply --finish`"
  },
  "REQ-UPDATE-APPLY-ALREADY-APPLIED": {
    "title": "`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) \u00e2\u20ac\u201d already-applied \u00e2\u2020\u2019 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)",
    "doc": ""
  },
  "REQ-UPDATE-APPLY-RESTART-NOTICE": {
    "title": "`spt update apply` prints a LOUD restart-required notice whenever the surviving broker will keep running the pre-apply image (which, until broker-restart choreography exists, is ALWAYS on a successful apply). Public wording, no internal CODE:RESULT markers (composes with REQ-ADAPTER-UPDATE-MESSAGE / the update-apply-confident-message rule) \u00e2\u20ac\u201d name the user-visible CONSEQUENCE ('daemon-coordinated features run the previous version until the daemon restarts'), not the broker/brain internals. Composes with REQ-UPDATE-RUNNING-IMAGE-SURFACE (the notice tells the user what the version-surface will then show, and how to clear it). (F-025)",
    "doc": ""
  },
  "REQ-UPDATE-DEFAULT-COMPOSITE": {
    "title": "THE-FORKENING W4 (operator-grilled 2026-07-14): plain `spt update` = `update fetch --apply` THEN `update adapters` (core-first doctrine order); when core is already current the core leg no-ops and ONLY adapters update; `--core-only`/`-c` skips the adapters leg. GROUNDING (operator-corrected, code-confirmed): fetch --apply cycles the BRAIN only \u00e2\u20ac\u201d broker + PTYs survive (apply_staged applyhost.rs:303; the restart-required text is a NOTICE, cli.rs:4615, not behavior) \u00e2\u20ac\u201d so the composite's invoking process survives by construction and NO re-run machinery is needed; on a broker-side release the existing F-025 notice remains the composite's closing output. Gate: unit \u00e2\u20ac\u201d composite sequencing incl. already-current -> adapters-only and --core-only skip; int \u00e2\u20ac\u201d composite on a staged release applies core then updates a registered adapter in one invocation; doc \u00e2\u20ac\u201d reference + self-update docs present plain `spt update` as the primary form. Kin REQ-UPDATE-ADAPTERS-VERB, REQ-UPDATE-RESTART-SAFE-SWAP, REQ-UPDATE-APPLY-RESTART-NOTICE.",
    "doc": "Self-update: **update composite (`spt update`)** \u00e2\u20ac\u201d the plain verb is the primary form: `update fetch --apply` then `update adapters` (core-first order); with core already current, only adapters update. `--core-only`/`-c` skips adapters; `spt update adapters [<a>[,<b>\u00e2\u20ac\u00a6]]` is the adapters leg alone (alias over `spt adapter update`). The composite's invoker always survives, because a routine apply cycles only the **brain** \u00e2\u20ac\u201d the *restart-required* message on broker-side releases is a notice, not a restart. `spt update --restart` is the one-step **full cycle**: fetch \u00e2\u2020\u2019 adapters \u00e2\u2020\u2019 `apply --finish`"
  },
  "REQ-UPDATE-FETCH-CURRENT-UX": {
    "title": "`spt update fetch` reports an already-staged / already-applied latest as an ACTIONABLE human outcome (exit 0), not a Debug-formatted error. ROOT: cmd_update_fetch (cli.rs) sets the rollback floor = staged_version, so when the published candidate == the already-staged version, verify_update_set_metadata returns Err(RejectReason::Rollback{current,candidate}) \u00e2\u20ac\u201d printed as {reason:?} (Debug) at exit 1, reading as a FAILURE when the update is merely already downloaded and just needs `spt update apply` (this bit the operator: fetch kept 'failing', apply was the missing step). FIX: a PURE classifier (reason, applied, staged) -> {AlreadyStaged (latest downloaded, not yet installed) / AlreadyApplied (up to date) / GenuineError}; already-staged + already-applied print a friendly message and exit 0; genuine rejects use RejectReason's Display (release.rs, not Debug) + exit 1 \u00e2\u20ac\u201d applied at ALL THREE fetch reject sites (metadata + artifact-verify + plan-verify). (v0.18.0)",
    "doc": ""
  },
  "REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL": {
    "title": "W3 (LIFECYCLE-TRUTH): daemon restart no longer massacres hosted endpoints \u00e2\u20ac\u201d daemon start RE-RUNS previously-online spt-hosted endpoints. ROOT rig-proven: daemon stop+start (the apply notice's OWN instruction) kills every hosted endpoint; they stay OFFLINE after start (no resurrection) though records exist (info.json status + adapter + cwd). SCOPE RULING (doyle): re-run-on-start, marked start-reason=daemon-restart; agents' minds ride psyche re-host as today. Int: endpoint online -> daemon stop -> start -> endpoint back ONLINE, same id, harness respawned.",
    "doc": ""
  },
  "REQ-UPDATE-GH-TRANSPORT": {
    "title": "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 \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d bytes verified after download, carrier-independent (update-set/counter/anchor continuity per ADR-0036 \u00c2\u00a72). release_verify_e2e reworked to the gh carrier. Gate: unit \u00e2\u20ac\u201d url/invocation construction + both failure classes render OS-correct hints; int \u00e2\u20ac\u201d fetch against a real gh-authed channel resolves latest + downloads and verifies an asset; do",
    "doc": "Self-update: **release channel (private, gh-carried)** \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d 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"
  },
  "REQ-UPDATE-ONE-SHOT-FINISH": {
    "title": "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 ///.",
    "doc": ""
  },
  "REQ-UPDATE-RESTART-SAFE-SWAP": {
    "title": "THE-FORKENING W4 (operator-grilled 2026-07-14; RETIRES findings-backlog seed #12 REQ-UPDATE-ONE-STEP-SAFE-SWAP): `spt update --restart` = the one-step ergonomic path to the SAFE full-cycle swap \u00e2\u20ac\u201d fetch -> `update adapters` -> `apply --finish` LAST (lethal-leg-last, ruled: apply --finish restarts the whole daemon incl. broker/PTYs, so it must be the final act \u00e2\u20ac\u201d everything completes from ANY invoking context including an spt-hosted session whose PTY dies at that step; accepted cost: a finish FAILURE leaves updated adapters on old-activated core briefly \u00e2\u20ac\u201d loud + operator-attended by nature of the flag). Composes with `-c/--core-only` (skip adapters leg). The 0.28.0 wedge lesson closes: the ergonomic one-step no longer picks the riskier path by default for operators who want the full cycle. Gate: unit \u00e2\u20ac\u201d flag sequencing incl. lethal-leg-last ordering + -c compose; doc \u00e2\u20ac\u201d self-update docs present --restart as the full-cycle form and name the finish-restart consequence. Kin REQ-UPDATE-DEFAULT-COMPOSITE, REQ-UPDATE-FINISH-COMMUNE-FLUSH (deferred commune-flush rides the same finish path when built), seed #12 (retired by this).",
    "doc": "Self-update: **update composite (`spt update`)** \u00e2\u20ac\u201d the plain verb is the primary form: `update fetch --apply` then `update adapters` (core-first order); with core already current, only adapters update. `--core-only`/`-c` skips adapters; `spt update adapters [<a>[,<b>\u00e2\u20ac\u00a6]]` is the adapters leg alone (alias over `spt adapter update`). The composite's invoker always survives, because a routine apply cycles only the **brain** \u00e2\u20ac\u201d the *restart-required* message on broker-side releases is a notice, not a restart. `spt update --restart` is the one-step **full cycle**: fetch \u00e2\u2020\u2019 adapters \u00e2\u2020\u2019 `apply --finish`"
  },
  "REQ-UPDATE-RUNNING-IMAGE-SURFACE": {
    "title": "`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) \u00e2\u20ac\u201d 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) \u00e2\u20ac\u201d 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)",
    "doc": ""
  },
  "REQ-UPDATE-TRIAL-DRAIN-DRIVE": {
    "title": "UPDATE-WEDGE (counter-54, doyle-ruled 2026-07-09 \u00e2\u20ac\u201d regression of the v0.29.0 seamless brain-swap): a brain generation DRIVES the broker's controller-liveness reap (a KIND_SESSIONS poll) each heartbeat throughout its boot/trial loop, so a hard-KILLED prior generation's black-holed LOCAL controller conn (by:None) is stall-evicted within the trial window and can never permanently strand the promotion DRAINED gate. ROOT (2026-07-09 field freeze, `spt update fetch --apply` v0.30.0->v0.30.2 froze all 7 live PTYs ~30s then rolled back): the promote gate (run_trial, brainproc.rs:657-661) needs BOTH `ready_generation==gen` AND `old_gen_drained()`; `old_gen_drained()` = `!any_local_controller_wedged()` (brainproc.rs:534) is a PURE READ of `write_blocked_since` (broker.rs:2703) \u00e2\u20ac\u201d it never DRIVES the evict. The evict (`stall_evict_controller`, broker.rs:1039, same 15s `brain_write_deadline` the wedge-read uses) only runs via `reap_dead_controller` (broker.rs:967, severed->drop ELSE stall-evict) inside the KIND_SESSIONS snapshot closure (broker.rs:2879). During the isolated brain-trial window NOTHING polls KIND_SESSIONS: the old brain was hard-killed (`child.kill()`, brainproc.rs:851) so its lo",
    "doc": "Self-update: **brain-trial promotion (readiness + drained)** \u00e2\u20ac\u201d the broker supervises the swapped-in brain through a bounded readiness **trial** and *promotes* the new binary only when it both signals ready for its own generation **and** the OUTGOING generation's control plane has **drained** \u00e2\u20ac\u201d the old brain's local (brain-owned) controller connection is closed or stall-evicted, never still holding blocked writes. A hard-killed prior generation leaves that connection **black-holed** (its hosted PTYs keep producing output the broker's writer blocks on, since a killed peer's pipe blocks rather th"
  },
  "REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT": {
    "title": "A `rc --view` VIEWER that overflows its broker subscription queue and is EVICTED (OutputLog::append try_send Full \u00e2\u2020\u2019 viewers.remove, REQ-HAZARD-VIEWER-ISOLATION session-protection) must SKIP TO LIVE, not die silently. ROOT (v0.13.0, b4 JIT item 2 = p0_paste + post-b4 a_journaled-Linux, ONE root): serve_attach forwards each frame (read_event\u00e2\u2020\u2019b64decode\u00e2\u2020\u2019re-encode AttachRecord\u00e2\u2020\u2019net_stream_send) SLOWER than the drain fans out under flood \u00e2\u2020\u2019 its VIEWER_CHANNEL_DEPTH(256) channel overflows \u00e2\u2020\u2019 the drain evicts (viewers.remove drops the ViewerSink \u00e2\u2020\u2019 drops tx \u00e2\u2020\u2019 viewer_writer's rx.recv() Err \u00e2\u2020\u2019 the writer returns WRITING NOTHING) \u00e2\u2020\u2019 serve_attach's brain.read_event() just STOPS getting Output (no EOF, no error) \u00e2\u2020\u2019 serve_attach blocks forever \u00e2\u2020\u2019 the operator receives nothing (attach_received_output=FALSE). Eviction-of-a-hopelessly-behind-viewer is CORRECT session-protection (keep it); SILENT+PERMANENT eviction is the bug. VIEWER-only \u00e2\u2020\u2019 B2-SAFE (a viewer never advances delivered_through / is not authoritative / exposes no resume cursor). FIX (doyle-gated, skip-to-live = tail -f reconnect): (1) explicit broker\u00e2\u2020\u2019viewer EVICTION SIGNAL (KIND_VIEWER_EVICTED, written in the viewer_writer thread OFF the log lo",
    "doc": "Shell sleep/wake (offline \u00e2\u2020\u201d online): <!-- --> **BUILT (M12 W2.5).** The controller/viewer model is implemented end-to-end. Attach intent is **three-valued** (`AttachIntent = Viewer | Control | Take`, wire-default `Control`): `Control` to a FREE endpoint becomes controller; `Control` to a CONTROLLED endpoint is **refused with guidance** (`--view` to watch, `--take` to control) \u00e2\u20ac\u201d never auto-viewer, never silent-displace; `Take` (`spt rc --take` / picker \"Kick\") kicks the incumbent with a **loud `Displaced{by}` notice** and full detach (not demote). The broker's per-session `OutputLog` is the fan"
  },
  "REQ-WAKE-WAIT": {
    "title": "A-2 rider (REMOTE-TRUTH triage \u00c2\u00a7A-2): `spt endpoint wake --wait` blocks on the REAL bind (status reaches online) after the daemon wake-resume lift (REQ-WAKE-RESUME-LEG), instead of the DEFAULT accepted-not-bound print (thin edge writes intent, daemon lifts async \u00e2\u20ac\u201d ADR-0033). Reuses the F-027 bind-await machinery if/when it lands, else a bounded poll on status==online with a plain-language timeout (no core lingo, F-1). Default wake is UNCHANGED (accepted-not-bound truth). Separate chunk from the core leg (doyle A-2 ruling: C-2 needs the core leg, not --wait); F-027 bind-await stays design-only until this activates.",
    "doc": ""
  },
  "REQ-WAN-SPT-HOSTED-DELIVERY": {
    "title": "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 \u00e2\u20ac\u201d it has NO spt-hosted broker-inject leg, which exists ONLY in local cmd_send (REQ-SEND-SPT-HOSTED, Brain::inject_endpoint \u00e2\u2020\u2019 KIND_ENDPOINT_INPUT \u00e2\u2020\u2019 broker dispatch_endpoint_input \u00e2\u2020\u2019 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 \u00e2\u2020\u2019 wan_mark_seen_at then return the existing 'delivered' wire token (no wire change); delivered=false \u00e2\u2020\u2019 the existing spool-with-claim transaction. v0.14.3 LAW: the shared leg is translation-binary-ONLY, NO raw-PTY fallback \u00e2\u20ac\u201d a no-binary arrival SPOOLS LOUD, never writes the PTY. (F-023, BUILD-F023-WANIDLE)",
    "doc": ""
  },
  "REQ-WHOAMI-1": {
    "title": "The `endpoint list` SELF pin carries the Self endpoint's authored `endpoint description` (info::read_info(...).resources) when present, inline after the liveness state; whoami stays a top-level hot-path verb (parse unchanged, REQ-MSG-9) and renders the same description-carrying SELF pin. HISTORY: originally minted whoami as a thin ALIAS of `spt endpoint list` \u00e2\u20ac\u201d that alias premise is SUPERSEDED by REQ-WHOAMI-IDENTITY-ONLY (PROJECT-INDEX W1, 2026-07-15): the alias inherited the list's O(perches x branches) git fanout onto hook paths (the 2026-07-15 message-delivery incident), so whoami is now identity-only over the shared render_self_pin. The pin render + parse evidence here stands; the full-roster surface lives solely on `endpoint list`.",
    "doc": "**whoami** (alias for endpoint list): <!-- --> `spt whoami` is a thin **alias for `spt endpoint list`** \u00e2\u20ac\u201d it prints the full view with the session's own endpoint **SELF-pinned first**, that pin carrying the endpoint's id, liveness state, and its authored **endpoint description** (the \"who am I\" answer). There is no separate bare-id command: nothing captured `id=$(spt whoami)` (environment variables don't persist between an agent's tool calls), so there is no scripting contract to preserve. `whoami` stays a top-level hot-path verb (its parse is unchanged, REQ-MSG-9); only the SELF pin's new des"
  },
  "REQ-WORKER-LIST-VISIBILITY": {
    "title": "V-1 (WORKER-TRUTH triage, operator rider): worker perches leave the DEFAULT `spt endpoint list` view \u00e2\u20ac\u201d they are process-local machinery, not subnet citizens; leaked-or-live worker rows rendering as permanent OFFLINE endpoints is the operator-visible symptom root. A dedicated flag (--workers) reveals them (one command + flag per the --all/--detail precedent, NOT a separate list-working command \u00e2\u20ac\u201d sister divergence deliberate). Applies to the human render, --json (additive default-absent filter), and the registry/projection legs; verify-and-stop any worker gossip into the subnet registry as peer endpoints.",
    "doc": ""
  },
  "REQ-WORKER-PICKER-EXCLUDED": {
    "title": "V-2 (WORKER-TRUTH triage, operator rider): non-drivable endpoint classes never render as `spt endpoint run` picker rows \u00e2\u20ac\u201d a worker perch cannot be driven, instantiated, or controlled; offering it is a lie the picker then fails on. Filter endpoint_type worker (and the psyche class if it ever surfaces \u00e2\u20ac\u201d same non-drivable family) at every picker source leg, extend-not-multiply for future non-drivable classes.",
    "doc": ""
  },
  "REQ-WORKER-REAP": {
    "title": "W-3 (WORKER-TRUTH triage): worker records must not persist indefinitely past their useful life \u00e2\u20ac\u201d 6 dead-pid workers leaked OFFLINE on flynn (kill-paths where SubagentStop never fires: parent killed, abort, timeout). The stored rec.pid is the ephemeral worker-start hook process (dead by design \u00e2\u20ac\u201d the REQ-HAZARD-DEAD-REC-PID class; NEVER an alive-gate signal). Honest reap signals: (a) parent-session lifecycle \u00e2\u20ac\u201d reap the parent's soft-stopped + orphaned workers at parent session-end/boundary and on parent-death detection (a worker cannot outlive its parent's live session); (b) a generous TTL floor since `created` as belt-and-braces. Soft-stop preservation semantics (REQ-HAZARD-SOFT-CLEANUP: results drain before reap) stay honored \u00e2\u20ac\u201d reap after drain-or-expiry, never mid-flight hard-delete (cascade-wipe guard rationale stands). Sister shape: claude_skill_owl doctor D-21 orphan-worker GC.",
    "doc": ""
  },
  "REQ-WORKER-SID-SYMMETRIC-AUTH": {
    "title": "W-2 (WORKER-TRUTH triage, operator-ruled 2026-07-06): worker verbs go sid-symmetric with every sibling id-scoped verb \u00e2\u20ac\u201d 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=\"\" \u00e2\u20ac\u201d worker.rs:44 \u00e2\u20ac\u201d 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 \u00e2\u20ac\u201d 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).",
    "doc": "`api worker-start <parent> [--agent-id <id>] [--agent-type <type>]`: <!-- --> ### `api worker-stop <id> --session-id <sid>` \u00c2\u00b7 `api worker-poll <id> --session-id <sid>`"
  }
}