{
  "REQ-ACTIVITY-LINK-PUSH": {
    "id": "REQ-ACTIVITY-LINK-PUSH",
    "title": "An owned Shell observes its owner's busy/idle transitions as an activity frame on the EXISTING shell-link event stream (ADR-0048 decisions 1-3; perri contract-lock 2026-07-24). Link-scoped (owner implied by the link token \u00e2\u20ac\u201d no new verb, no addressing surface). Drive-class semantics: ephemeral, latest-wins, current-state-carrying; redundant same-state resend is a harmless no-op (consumer derives edges); NEVER spooled or replayed (stale transitions are actively wrong). Both directions (busy->idle, idle->busy). Current state emitted on EVERY link establishment and re-link \u00e2\u20ac\u201d restart resync is free. Latency promise = BOUNDED OBSERVATION: a frame per transition, sub-second class, never hard-real-time; each frame carries the TRANSITION TIMESTAMP (when the sentinel flipped, not when the frame was emitted) so edge-anchored consumers self-correct emission latency. Frame name/attrs publish in the shell frame vocabulary doc (REQ-SHELL-FRAME-VOCAB lineage) \u00e2\u20ac\u201d doc stage rides there. Gate: int \u00e2\u20ac\u201d a live shell link sees busy->idle and idle->busy frames with truthful transition timestamps, a re-link re-emits current state, and a transition during link-down is represented by the re-link emit (current ",
    "doc": "`drive` \u00e2\u20ac\u201d owner\u00e2\u2020\u2019shell, ephemeral: <!-- --> ## `activity` \u00e2\u20ac\u201d the owner's busy/idle state, pushed"
  },
  "REQ-ACTIVITY-LIST-JSON": {
    "id": "REQ-ACTIVITY-LIST-JSON",
    "title": "`spt endpoint list --json` carries a per-endpoint `activity` (busy|idle) key, for consumers surveying the idle/busy state of many endpoints at once (ADR-0048 decision 1, roster pull avenue; operator addition 2026-07-24). Additive key, N-1-safe; kin the flynn 2026-07-06 last-active/description/adapter list-enrichment seed (same additive posture, may ride together).",
    "doc": "`--json` catalog: | Command | Top-level shape | |---|---| | `endpoint list` | `{ self, subnets[], local[] }` \u00e2\u20ac\u201d `self`: `{id, status, ready, alive, unbound, description, psyche_host_error, translation_fault?}`; `subnets[]`: `{name, endpoints[]}` where each endpoint is `{id, node, node_label, status, resources, endpoint_type?, project?}`; `local[]`: `{id, state, address, ready, alive, unbound, project?, activity?}`. *(Since v0.33.0 the local `project` field reads the daemon-maintained project index \u00e2\u20ac\u201d answers are immediate and may lag a just-changed project by moments; absent while the index has"
  },
  "REQ-ADAPTER-UPDATE-MESSAGE": {
    "id": "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-ADAPTER-UPDATE-POST": {
    "id": "REQ-ADAPTER-UPDATE-POST",
    "title": "Composite adapter update \u00e2\u20ac\u201d an avenue-agnostic `[update.post]` sub-table `{ command, self_verifies }` run AFTER the primary avenue (gh_release/file_pull/delegated) resolves, in the same `spt adapter update` (ADR-0029). Runs UNCONDITIONALLY (even on an adapter version no-op \u00e2\u20ac\u201d the post-step's own idempotent check decides). PUBLISHED stdin JSON seam: one line `{adapter_applied, adapter_name, profile_name, version, previous_version, adapter_dir}` (additive keys; post-step ignores unknown). stdout decides the notice: custom text SUPERSEDES [update].message; a reserved sentinel fires the static [update].message; empty = no notice. exit code orthogonal (0 ok / nonzero failed). Precedence: dynamic-stdout > sentinel/manifest-message > nothing. NO [update.post] declared \u00e2\u2021\u2019 today's adapter_applied\u00e2\u2020\u2019[update].message unchanged; post-step FAILS \u00e2\u2021\u2019 loud warning + fall back to adapter_applied\u00e2\u2020\u2019message. FAILURE-ISOLATED: a committed gh_release pull is never rolled back if the post-step fails (independent channels). (v0.16.0)",
    "doc": "`[update.post]` \u00e2\u20ac\u201d the composite post-step (since v0.16.0): <!-- --> // file_pull: repo + path_regex: <!-- --> **Composite update \u00e2\u20ac\u201d `[update.post]` (since v0.16.0).** An optional **avenue-agnostic** sub-table that runs a delegated **post-step** *after* the primary update avenue resolves, in the same `spt adapter update`. It lets an adapter pull its `.spt` from `gh_release` **and** run a second, adapter-owned step (e.g. an in-harness plugin sync) under one lever."
  },
  "REQ-API-3": {
    "id": "REQ-API-3",
    "title": "commune/signoff are file-drops, not commands",
    "doc": ""
  },
  "REQ-API-ENDPOINT-INFO": {
    "id": "REQ-API-ENDPOINT-INFO",
    "title": "#7: spt api endpoint-info [<id>] (JSON) lets an endpoint learn its ATTACHED (controlling) node \u00e2\u20ac\u201d claude-spt surfaces local + attached node names on UserPromptSubmit so the agent knows whether getting a file to the user needs extra steps (user RC'd in from another machine). spt api * is the harness-contract agent-facing surface (JSON-first, rides perch identity/auth so the bare no-<id> form self-resolves like whoami). Payload (committed DTO, additive-forever): { id, endpoint_type, adapter, local_node:{label,key}, attached_node:{label,key}|null, controlled:bool, project:<current project id>, cwd, subnets:[...] } \u00e2\u20ac\u201d attached_node from controller stamps (driven_by remote / self-node when controlled with no remote driver), null when uncontrolled. HARD dependency on #2 + #3 (stamps must be honest first). Adapter-side consumable -> perri release-ping on publish. Naming: chose 'spt api endpoint-info' over alt 'spt endpoint get-info' \u00e2\u20ac\u201d api is the agent surface (doc rationale). See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #7.",
    "doc": ""
  },
  "REQ-ARCH-1": {
    "id": "REQ-ARCH-1",
    "title": "Many small acyclically-layered crates",
    "doc": ""
  },
  "REQ-ARCH-2": {
    "id": "REQ-ARCH-2",
    "title": "Public SDK surface is spt-proto, spt-runtime, spt-msg",
    "doc": ""
  },
  "REQ-ARCH-3": {
    "id": "REQ-ARCH-3",
    "title": "Wire-protocol version independent of crate semver, N-1 compat window",
    "doc": ""
  },
  "REQ-ATTACH-SEED-REQUEST-DOUBLE-SERVE": {
    "id": "REQ-ATTACH-SEED-REQUEST-DOUBLE-SERVE",
    "title": "A dispatcher-SEEDED attach Request is honored ONCE \u00e2\u20ac\u201d the wire replay's copy of that same Request must not re-establish it. (Seed: DAEMON-LIFECYCLE W1 build, answering ADR-0047 decision 3's banked open question; origin CODE-PROVEN, not inferred.) THE ORIGIN: serve_attach (spt-daemon/src/attach.rs) processes the dispatcher-pinned `seed_request` through handle_attach_request BEFORE its event loop (ADR-0038 B, the durable-opener seed \u00e2\u20ac\u201d it exists because a ROLLED ring can no longer replay the Request). It then subscribes the stream from the floor and enters the loop, whose `AttachRecord::Request` arm calls handle_attach_request UNCONDITIONALLY \u00e2\u20ac\u201d there is NO `!attached` guard, and NO record that the seed already established this exact Request. When the ring has NOT rolled, the wire replay still carries that Request, so the SAME record is handled TWICE on the SAME conn, milliseconds apart, with identical (session_id, from_seq, intent, gen, identity) \u00e2\u20ac\u201d exactly hertz's v0.39.4 bug-4 capture (conn1011, 15ms apart, post-refresh replay). The two arms are the same function on the same seed record; the seed is pinned by the dispatcher on the post-cycle replay, which is the field trigger. SIBLING",
    "doc": ""
  },
  "REQ-BIND-HONEST-SELF-STAMP": {
    "id": "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-BRAIN-RESUME-NO-CONTROL-STEAL": {
    "id": "REQ-BRAIN-RESUME-NO-CONTROL-STEAL",
    "title": "UPDATE-WEDGE round 2 (v0.30.4, doyle-ruled 2026-07-09 \u00e2\u20ac\u201d field incident on the counter-54 fetch--apply): a brain-respawn must NEVER steal, then stall-evict, the controller of a broker PTY session the daemon brain does not DRIVE. ROOT (field-pinned + SME, docs/UPDATE-WEDGE-2-RCA.md + docs/UPDATE-WEDGE-2-SME-todlando.md): `resume_sessions` (brain.rs:985) re-attaches EVERY session `KIND_SESSIONS` returns via `subscribe` = `subscribe_with(AttachIntent::Control, by:None)` (brain.rs:1450-1455). The docstring's 'a None identity never displaces (falls back to viewer)' is a MYTH for FREE / SAME-LOCAL-IDENTITY slots: `resolve_subscribe` (broker.rs:1134-1153) stall-evicts FIRST, then `become_controller` if the slot is now free OR the incumbent is also local (None==None) \u00e2\u20ac\u201d viewer-fallback fires ONLY when a DIFFERENT REMOTE controls. So on a box with N spt-hosted broker PTYs, a brain-respawn STEALS the by:None controller of every free/local-controlled session (incl. the operator's LOCAL `spt rc`), which the daemon brain never drains (it hosts no PTY sessions \u00e2\u20ac\u201d brainproc.rs:184) \u00e2\u2020\u2019 15s later `stall_evict_controller` (broker.rs:1039) releases driven_by \u00e2\u2020\u2019 the session is UNCONTROLLABLE (Failure A). I",
    "doc": "Self-update: **resume re-attach is view-only for non-driven sessions** \u00e2\u20ac\u201d on respawn the new brain queries the broker for every hosted session and re-attaches to rebuild output-continuity cursors, but it re-attaches as a **viewer**, never a controller, for any session it does not itself drive (which is *all* of them today \u00e2\u20ac\u201d the supervised daemon brain hosts no PTY sessions; spt-hosted PTYs are driven by the operator's attach or the endpoint's own loop). Re-attaching as a controller would seize the controller slot of every free/local-controlled session \u00e2\u20ac\u201d including the operator's local `spt rc` \u00e2\u20ac\u201d"
  },
  "REQ-BRAIN-UPDATE-RESTART-CLEAN-CLOSE": {
    "id": "REQ-BRAIN-UPDATE-RESTART-CLEAN-CLOSE",
    "title": "SEED (DEFERRED, doyle 2026-07-09 \u00e2\u20ac\u201d post-counter-54 root-hardening for UPDATE-WEDGE; mint now, impl a FUTURE milestone): on a PLANNED brain-restart (`BRAIN_UPDATE_RESTART`, the seamless update-apply brain-cycle), the outgoing brain's LOCAL (by:None) controller conns are GRACEFULLY CLEAN-CLOSED as the brain is cycled, instead of hard-killed and left to black-hole. ROOT (field-pinned 2026-07-09, daemon.stderr.log L24277-24303): the update-restart path hard-kills the outgoing brain (`child.kill()`, brainproc.rs:851); its live-agent controller conns then block on dead pipes (never EOF) \u00e2\u2020\u2019 the broker reads them WEDGED (broker.rs:2695-2700) \u00e2\u2020\u2019 the new candidate's promotion DRAINED gate (`any_local_controller_wedged`, broker.rs:2704) stays true until the W2 stall-evict matures (~15s). REQ-UPDATE-TRIAL-DRAIN-DRIVE (counter-54) makes the candidate DRIVE that reap so it promotes within the 30s window \u00e2\u20ac\u201d but at a ~15s wedge-maturity hitch (frozen PTYs during the swap). A CLEAN close makes the conn 'simply absent \u00e2\u2020\u2019 drained=false AT ONCE \u00e2\u2020\u2019 fast promote' (broker.rs:2699-2700), ELIMINATING the hitch = truly seamless (honors the paradigm the field freeze broke). SUPERSEDES the earlier livehost-reattac",
    "doc": ""
  },
  "REQ-BROKER-OUTPUT-BEFORE-EXIT": {
    "id": "REQ-BROKER-OUTPUT-BEFORE-EXIT",
    "title": "RC-RENDER-TRUTH W3 (ADR-0043 decision 1, hertz stale-glyphs RCA leg 1 P0): the PTY drain/output writer is the SOLE FIFO sequencer for terminal Output + Exit per attach sink \u00e2\u20ac\u201d Exit is enqueued BEHIND all prior output (drain EOF/completion first, then Exit); the exit waiter never direct-writes KIND_EXIT around the queued output path (a mutex serializes bytes, not producer order). Kills the stranded-final-frame race (final EL/SGR-reset/cursor-show/?1049l lost when Exit overtakes Output \u00e2\u20ac\u201d already admitted and compensated in the broker test suite, never fixed in production rc). Gate: impl \u00e2\u20ac\u201d single sequencer, exit-behind-output enqueue; unit \u00e2\u20ac\u201d ordering invariant on the writer queue (exit never precedes queued output for a sink); int \u00e2\u20ac\u201d short-lived child emits 'XXXX ESC[2K ESC[?25h ESC[?1049l' then exits => that exact Output precedes Exit through the PRODUCTION broker->attach->rc path; doc \u00e2\u20ac\u201d ADR-0043.",
    "doc": "Decisions: <!-- --> 1. **One FIFO sequencer per attach sink.** The PTY drain/output writer is the sole sequencer for terminal Output and Exit: Exit is enqueued behind all prior output for each sink (drain EOF/completion first, then Exit). A mutex alone is insufficient \u00e2\u20ac\u201d producer order is the contract. Output-before-Exit is a production-path invariant, regression-proven end-to-end (broker \u00e2\u2020\u2019 attach \u00e2\u2020\u2019 rc). <!-- --> 2. **rc display teardown is unconditional, idempotent, and separate from input teardown.** A display RAII guard (distinct from the OS input/raw-mode guard) runs on every exit path incl"
  },
  "REQ-CLI-3": {
    "id": "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-CLI-OUTPUT-MARKDOWN": {
    "id": "REQ-CLI-OUTPUT-MARKDOWN",
    "title": "Human-prose COMMAND OUTPUT (not just `--help`) renders the inline Markdown authored in its source strings as terminal styling, never literal markers: `` `code` `` \u00e2\u2020\u2019 ANSI cyan, `**bold**` \u00e2\u2020\u2019 ANSI bold, `[text](url)` \u00e2\u2020\u2019 `text`, markers STRIPPED either way. REQ-CLI-HELP-MARKDOWN only hooked the clap `--help` chokepoint, so command output still printed raw Markdown (audit: `spt how-to` topic text showed `# headers`/backticks, `spt subnet`/`subnet status` hint footers showed stray backticks, the daemon-status `not running` line, the `ENDPOINT_RUN_STARTED` attach hint, and the daemon's `SUBNET_DETACHED` startup line \u00e2\u20ac\u201d 13 prose surfaces). The same line-bounded pure `helpfmt::render` is applied at each emit site, color-gated by the OUTPUT STREAM's own tty (`stdout_color` for print/println, the new `stderr_color` for eprintln). HARNESS-SAFETY (binding): color is tty-gated, so an adapter (piped / non-tty / NO_COLOR) gets STRIP mode = zero ANSI + markers removed; every dual-contract MACHINE token on a rendered line (`ENDPOINT_RUN_STARTED:`, `NO_SUCH_TOPIC:`, `SUBNET_DETACHED:`) carries NO Markdown markers, so it survives strip byte-intact \u00e2\u20ac\u201d the adapter parse is never perturbed. Pure-machine out",
    "doc": ""
  },
  "REQ-CONN-BLACKHOLE-LIFECYCLE-HARNESS": {
    "id": "REQ-CONN-BLACKHOLE-LIFECYCLE-HARNESS",
    "title": "MSG-IDENTITY W6 / F-039 leg e (doyle W6 LOCK 2026-07-10, minted per amendment 3 \u00e2\u20ac\u201d hertz's five invariants VERBATIM from his RCA fix-shape item 5): 'Build a deterministic black-holed-controller harness against current v0.30.6 semantics and assert: unrelated sessions continue; the bad physical connection is canceled/closed within the bound; its writer exits; a fresh viewer can attach; no lock or task remains owned by the retired connection.' The harness is the standing conformance rig for the r4 SHAREDSEND fix-class \u00e2\u20ac\u201d hertz's RCA discipline: only after a timestamped incident maps to a FAILING lifecycle invariant does an ownership/cancellation defect get fixed (the likely shape being complete physical-connection cancellation and writer-task join/retirement, never a broader timeout increase). Consumes REQ-CONN-POISON-ATTRIBUTION's records (conn id + lifecycle events are what make the five assertions checkable deterministically). Kin REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK (the invariant class under test \u00e2\u20ac\u201d its brain_decouple int stays the Windows-mandatory gate leg), REQ-CONN-POISON-DIAL-SCOPE.",
    "doc": ""
  },
  "REQ-CONTROL-STAMP-CONVERGENCE": {
    "id": "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": {
    "id": "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-CONV-2": {
    "id": "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-3": {
    "id": "REQ-DAEMON-3",
    "title": "Any api invocation auto-starts the daemon if absent",
    "doc": ""
  },
  "REQ-DAEMON-6": {
    "id": "REQ-DAEMON-6",
    "title": "Service-aware `daemon start`/`stop`: when an OS service manager has a registered spt-daemon for this user, `spt daemon start` and `spt daemon stop` drive THAT service (so stop doesn't IPC-kill a unit that auto-restart-fights for the broker socket \u00e2\u20ac\u201d the kitsubito 2026-06-08 loop). `start` graduates from a `run` alias to a first-class background verb (ensure-up, idempotent, non-blocking); stop routes managed\u00e2\u2020\u2019manager, manual\u00e2\u2020\u2019IPC. Linux=systemd user unit (`systemctl --user start|stop|is-active spt-daemon`, detected by unit-file presence); Windows=no controllable manager (the logon task is boot-only), so start=detached spawn / stop=IPC.",
    "doc": ""
  },
  "REQ-DAEMON-RESTART-SELF-INTERRUPTION": {
    "id": "REQ-DAEMON-RESTART-SELF-INTERRUPTION",
    "title": "SEED (inactive \u00e2\u20ac\u201d operator-workflow trap, field-paid 2026-07-25): an spt-hosted context CANNOT restart the daemon, because the restart's STOP phase tears down the very context running the restart command \u00e2\u20ac\u201d the START half never executes and the daemon stays down until an operator starts it by hand. Field case (this node, 17:47\u00e2\u20ac\u201c17:50): an agent-kicked restart's stop-begin swept the initiating context and everything sharing its Job Object (flynn's shell + the relay-hopped alchemy Hub, cross-context job members \u00e2\u20ac\u201d see REQ-SHELL-CLI-SPAWN-JOB-EXPOSURE's field-firing record); the broker drained ~2m10s to final exit (stderr ends abruptly at 17:50:05.8 amid routine traffic, no shutdown markers); the START was already dead; the operator manually started whatever binary was installed (v0.42.0), with no channel conveying which bits the restarting agent had intended \u00e2\u20ac\u201d binary choice silently transferred to whoever brings the daemon up (kin: REQ-DAEMON-BITS-AMBIGUITY, the same which-bits-serve blindness). CONSEQUENCES: (a) every RESTART-REQUIRED apply (broker update, seed-skew restart per hazard 7.9) is operator-only today, and nothing documents that; (b) stop-begin kills bound shells with `close_",
    "doc": ""
  },
  "REQ-DAEMON-STATUS-JSON-TRUTH": {
    "id": "REQ-DAEMON-STATUS-JSON-TRUTH",
    "title": "D2 (F028, perri F-d): `daemon status --json` truth drift. managed_by/managed_active read null in JSON while the HUMAN view says 'managed-by: manual \u00e2\u20ac\u201d at-logon task registered' (the two surfaces disagree); and pump staleness (the STALLED diagnosis, B5) is NOT computable from JSON \u00e2\u20ac\u201d only a raw pump_heartbeat_ms is emitted, no derived staleness/stalled field. FIX: JSON managed_by/active match the human render, and add a derived pump-staleness/stalled field so B5's condition is machine-observable. See triage D2.",
    "doc": ""
  },
  "REQ-DIGEST-CURSOR": {
    "id": "REQ-DIGEST-CURSOR",
    "title": "`spt endpoint digest` gains incremental turn-end consumption (extends REQ-TERM-4/5): `--last <N>` = the last N TURNS (the digest's natural unit; --last 1 = the latest turn = turn-end output); a per-entry STABLE SOURCE-DERIVED `seq` in the --json output (deterministic from the entry's append position in the source \u00e2\u20ac\u201d transcript record index across the session ledger / digest.log index \u00e2\u20ac\u201d so a live re-projection yields the same seq for the same committed entry; NOT a window-position index that renumbers on slide); `--after <seq>` = entries newer than seq still in the window (full window + signal if seq predates it, mirroring the version-slide full-refresh). An in-flight (still-growing) entry is flagged `partial: true` with NO stable seq until finalized (consumer reprocesses partial, skips <= seq). Also emit per-entry `ts` where present (seq is the authoritative dedup+cursor key). The digest's agent text is sufficient fidelity (no raw-source mode). BINDING doc-guidance: an adapter's [digest] extractor / api digest-entry MUST classify delivered user-facing messages as turn-opening `input` (equiv to direct PTY user-input), else messaging-driven sessions collapse into a few giant turns and",
    "doc": "Session digest \u00e2\u20ac\u201d the published digest-record contract (ADR-0019): <!-- --> **Turn boundaries \u00e2\u20ac\u201d classify delivered messages as `input` (binding).** The projection treats a `role: \"input\"` record as the **turn boundary** (the unit `--last`/`seq` count). An adapter's `[digest]` extractor / `api digest-entry` therefore **MUST classify a delivered user-facing message as a turn-opening `input`** record (equivalent to a direct PTY user-input) \u00e2\u20ac\u201d not as `agent`/`tool` output. If messaging-delivered turns are not opened as `input`, a messaging-driven session collapses into a few giant turns and `--last"
  },
  "REQ-DIGEST-JSON-SELF-CONTAINED": {
    "id": "REQ-DIGEST-JSON-SELF-CONTAINED",
    "title": "TEARDOWN-AUTHORITY W4 (perri adapter-surface finding 2026-07-19, doyle-grounded and RE-SCOPED; title AMENDED at gate 2026-07-19 \u00e2\u20ac\u201d the original welded version to --after polling, todlando falsified it from digesthub.rs/cli.rs and doyle ruled the amendment rides the wave: fifth instance of the claim-keyed-on-the-wrong-thing class, this one in the REQ registry itself): `spt endpoint digest <id> --json` must be self-contained on stdout. Today the digest snapshot version is NOT a field of the --json object at all \u00e2\u20ac\u201d it exists ONLY in the DIGEST:<id> version=N trailer that cmd_digest eprintln!s at cli.rs:1619 \u00e2\u20ac\u201d so a JSON consumer that wants it is FORCED to parse stderr. NUMBER-SPACE TRUTH (the amendment): version is digesthub's monotonic PROJECTION counter \u00e2\u20ac\u201d it bumps when the projected digest CHANGES, serves as the --follow from_version floor and a change-detection cursor, and is NOT valid --after input; --after filters on entry seq ((ledger_ordinal<<32)|line_idx), a different number space, so a version passed as --after predates the window every time. NOTE the corrected history (perri's original RCA framed this as a fleet outage and doyle falsified it; perri confirmed): the trailer has b",
    "doc": "Session digest \u00e2\u20ac\u201d `endpoint digest --json`: <!-- the self-contained --json contract: top-level integer version cursor, stderr-clean under --json, the complete entry-kind enum with per-kind agent-produced vs spt-injected provenance, and the seq/cursor asymmetry -->"
  },
  "REQ-DOCS-3": {
    "id": "REQ-DOCS-3",
    "title": "Di\u00c3\u00a1taxis structure; one canonical way to do X",
    "doc": "Windows: --pattern 'spt-x86_64-windows.exe', then .\\spt-x86_64-windows.exe install: <!-- Di\u00c3\u00a1taxis four-mode separation + one-canonical-way, stated as the corpus's contract --> ## How these docs are organized"
  },
  "REQ-DRIVEN-BY-OWN-NODE-NORMALIZE": {
    "id": "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-EFFECTIVE-INSTANCE-STATE": {
    "id": "REQ-EFFECTIVE-INSTANCE-STATE",
    "title": "A-1 (REMOTE-TRUTH triage \u00c2\u00a7A + ADR-0033 \u00c2\u00a7Decision): the effective instance state of a perch is DERIVED through ONE shared function \u00e2\u20ac\u201d liveness discriminates warm/cold, stored rest intent refines within warm, absent intent NEVER defaults active. ROOT (certain): resting::apply_event derived its `from` off the stored rest_state field ALONE (resting.rs:225, `unwrap_or(RestState::Active)`) \u00e2\u20ac\u201d a cold perch (offline) with no intent answered `from=Active`, so a Wake event found it 'already in target state' and returned Ok(None) = the field NO_EDGE-on-a-definitely-suspended-endpoint bug (the banked F-028 rest_state-void seed). advertised_status (registryhost.rs:821) ALREADY derived correctly (is_perch_alive\u00e2\u2020\u2019intent-refined / is_perch_unbound\u00e2\u2020\u2019Dormant / cold\u00e2\u2020\u2019Suspended) \u00e2\u20ac\u201d the two readers disagreed. FIX (Q1 shared derivation, hazard-class): a pure `effective_rest_state(alive, unbound, intent) -> RestState` mirroring advertised_status, consumed by BOTH advertised_status (mapped RestState\u00e2\u2020\u2019Status, behavior identical) AND apply_event's `from` (real is_perch_alive/is_perch_unbound reads); void + cold \u00e2\u2021\u2019 Suspended. Bonus: kills the spurious active\u00e2\u2020\u2019suspend echo a cold+void perch used to fire (on_rest_edge ",
    "doc": "7.31 The Psyche failure budget must count REAL per-event attempts \u00e2\u20ac\u201d a resident rate-guard is blind to per-event churn `[REQ-HAZARD-THRASH-GUARD-BLIND]`: ### 7.32 The effective resting state MUST be derived through ONE shared liveness-aware function \u00e2\u20ac\u201d a stored-intent-alone read lies about cold perches `[REQ-EFFECTIVE-INSTANCE-STATE]` <!-- --> - **Failure (paid-for, field evidence):** two rest-state readers derived the effective instance state independently. `registryhost::advertised_status` read it liveness-aware (cold \u00e2\u2021\u2019 Suspended); `resting::apply_event` derived its `from` off the stored `rest"
  },
  "REQ-ENDPOINT-LIST-MERGE-LOCAL": {
    "id": "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-NODE-IDENT": {
    "id": "REQ-ENDPOINT-LIST-NODE-IDENT",
    "title": "Bug #5: spt endpoint list local section header is the hardcoded literal LOCAL (this node) (render_local_section cli.rs:4359). Change to 'This node: <node-id>' using the existing node-ident idiom (os_hostname + nodeid public-key prefix, cli.rs:5531 \u00e2\u20ac\u201d factor a node_ident_display helper); compute in the impure print_local_section, pass into the pure renderer. Update the two test assertions (cli.rs:10711/10716). See docs/NEXT-MILESTONE-BUG-TRIAGE.md #5.",
    "doc": ""
  },
  "REQ-ENDPOINT-LIST-PALETTE": {
    "id": "REQ-ENDPOINT-LIST-PALETTE",
    "title": "Bugs #11 + #15 (display): spt endpoint list renders status as plain text while the picker turns the same ResourceRow into the W5 colored EpDisplay palette. Fix: extract one shared ResourceRow-to-EpDisplay builder + make the picker display enums/helpers public, and have endpoint list render the same colored status squares (via helpfmt stdout_color, not ratatui Span). This also fixes #15 \u00e2\u20ac\u201d a lone warm detached instance renders as its online flavor (Dormant maps to online) instead of leaking the bare word Dormant through the text-only list (no resting.rs/CONTEXT model change; operator ruling display-only). Couples REQ-PICKER-NODE-GROUPING (both edit subnet_rows \u00e2\u20ac\u201d sequence the shared-builder extraction first). See docs/NEXT-MILESTONE-BUG-TRIAGE.md #11/#15.",
    "doc": ""
  },
  "REQ-ENDPOINT-LIST-RENDER-POLISH": {
    "id": "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-LIST-REST-FILTER": {
    "id": "REQ-ENDPOINT-LIST-REST-FILTER",
    "title": "spt endpoint list hides SUSPENDED instances by default; a new --show-all flag reveals them. Status-first row ordering with fixed precedence ONLINE > CONTROLLED > UNBOUND > SUSPENDED (when shown) > corrupt last, alphabetical by id within each band. Two invariants: (1) CORRUPT rows ALWAYS render regardless of filters \u00e2\u20ac\u201d corrupt is a record condition demanding operator action (purge/re-mint), not resting clutter; hiding it would re-create counter-39 bug #3 (cross-ref REQ-HAZARD-CORRUPT-PERCH-COHERENCE, CONTEXT.md instance-state _Also avoid_); (2) the per-node Total line DISCLOSES the filter \u00e2\u20ac\u201d 'Total: N (+M suspended hidden)' \u00e2\u20ac\u201d so nothing silently vanishes. Registry-Offline rows stay excluded by projection law (resource_projection skips unroutable; unchanged). Grill-with-docs ruling 2026-07-02 (operator + doyle).",
    "doc": ""
  },
  "REQ-ENDPOINT-ONLINE-TRUTH": {
    "id": "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-TEARDOWN-AUTHORITY": {
    "id": "REQ-ENDPOINT-TEARDOWN-AUTHORITY",
    "title": "TEARDOWN-AUTHORITY W1 (ADR-0045; two hertz field RCAs 2026-07-19, doyle code-verified + ruled): ONE shared topology-aware broker-teardown primitive behind BOTH `endpoint shutdown` and `endpoint stop`. Today both verbs stamp state they never cause: cmd_shutdown (cli.rs:3979) = remove ready marker + cmd_rest(Suspend) \u00e2\u20ac\u201d the Suspend edge (resting.rs daemon_rest_event_with_liveness -> apply_event -> cascade_shells_on_edge -> advertise) fires echo + shell cascade + advertise and NEVER touches a session, even though the verb already probed broker-session truth (cli.rs:3797-3804 has_live_session_honest) to force from=alive; cmd_stop (cli.rs:7071) = marker + unregister_address + terminal_normalize + advertise, whose own comment calls it a DEFINITIVE death observation it fabricates. Field: broker retains the whole subtree (adapter -> node -> harness -> nested resumed harness + MCP children; the surviving `spt api listen` is a DESCENDANT, so a direct-child kill misses it). PRIMITIVE (ordered, ADR-0045 decisions 1/6/7/9): resolve broker SessionInfo -> dedicated sid/endpoint-keyed broker kill claiming NO controller (NOT Brain::attach()+kill_session(), brain.rs:622 require_session() = controller",
    "doc": "7.48 At most one input-capable controller lease per PTY session \u00e2\u20ac\u201d takeover revokes atomically and loudly, input is fenced to the active lease, node identity is never a lease `[REQ-HAZARD-CONTROLLER-LEASE]`: ### 7.49 A teardown verb never stamps a terminal or resting state it has not caused \u00e2\u20ac\u201d and two individually-correct verbs must not compose into a lifecycle dead end `[REQ-HAZARD-TEARDOWN-DEADEND]` <!-- --> <!-- --> - **Failure (paid-for, two hertz field RCAs 2026-07-19, both doyle code-verified the same day; the second hit doyle's OWN live production endpoint):** `endpoint shutdown` reported"
  },
  "REQ-ENSURE-DAEMON-STOP-INHIBIT": {
    "id": "REQ-ENSURE-DAEMON-STOP-INHIBIT",
    "title": "An operator stop outranks every implicit daemon ensure. (ADR-0047 decision 2, AMENDING REQ-DAEMON-3's anchor; hertz v0.39.4 field bug 2, RCA accepted 2026-07-22.) TODAY: every `spt api` invocation runs unconditional ensure_daemon() (api/mod.rs run()), so on a box with live adapter sessions a `daemon stop --force` loses the race to hook-driven api calls \u00e2\u20ac\u201d respawn convoy (5-10 ephemeral spawner windows), several stops to stay down; the rc-side twins were fixed earlier, the api anchor stayed armed. FIX: `daemon stop` records a durable machine-scoped STOP INHIBIT before teardown begins; ensure_daemon()/ensure_running() consult it and DECLINE with one honest line naming the remedy ('daemon stopped by operator \u00e2\u20ac\u201d spt daemon start to resume'); cleared by intent verbs ONLY (explicit `daemon start`; update paths that restart by design) \u00e2\u20ac\u201d NO TTL (rejected: a timeout is the surprise respawn again, later); implicit autostart additionally takes a machine-wide lock around probe-and-spawn so N concurrent callers never launch N daemons. Gate: doc \u00e2\u20ac\u201d the stop/start contract on the daemon CLI docs (stop now sticks; the refusal line + remedy named); impl \u00e2\u20ac\u201d inhibit mint in cmd_stop + consult in both imp",
    "doc": "The daemon: broker and brain: <!-- --> \u00e2\u20ac\u00a6with one exception, because you are allowed to mean it: **`spt daemon stop` sticks.** Auto-start is a convenience, and a convenience never overrules an explicit instruction. Once you stop the daemon, the implicit auto-start that every `spt` invocation performs *declines* to bring it back, printing one line that names the way out // 2. An operator stop outranks every implicit ensure: <!-- -->"
  },
  "REQ-EP-1": {
    "id": "REQ-EP-1",
    "title": "Day-one endpoint types; open type system",
    "doc": ""
  },
  "REQ-EP-2": {
    "id": "REQ-EP-2",
    "title": "Agent endpoints vs Shells distinction in the type model",
    "doc": ""
  },
  "REQ-EP-4": {
    "id": "REQ-EP-4",
    "title": "PresenceChannel broker endpoint (seam day-one)",
    "doc": ""
  },
  "REQ-EP-6": {
    "id": "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-FRONT-1": {
    "id": "REQ-FRONT-1",
    "title": "Day-one launcher/manager frontend (list/launch/attach/init)",
    "doc": ""
  },
  "REQ-GOSSIP-ADAPTER-PROJECTS": {
    "id": "REQ-GOSSIP-ADAPTER-PROJECTS",
    "title": "#4: remote endpoint details (harness + project history) are gossiped, not faked. Today from_resource_row (crates/spt/src/picker/model.rs:340) hardcodes project_history=Vec::new() for every remote row and passes adapter_profile=row.resources (the blurb masquerading as the harness), and Instance/ResourceRow (crates/spt-net/src/net/registry.rs:457) carry no adapter field and no project list. Fix: additive gossip fields N-1-safe exactly like endpoint_type \u00e2\u20ac\u201d Instance.adapter (composite <adapter>[:profile]) + Instance.recent_projects (bounded, newest-first, project IDs only) -> thread to ResourceRow -> from_resource_row stops faking. Pre-field remote rows render '-'. Project IDs only + REQ-PICKER-PROJECT-HISTORY-TRUTH's disambiguation. See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #4.",
    "doc": ""
  },
  "REQ-HAZARD-BIND-CWD-UNSET": {
    "id": "REQ-HAZARD-BIND-CWD-UNSET",
    "title": "A bound endpoint's `info.cwd` is SET at bind so a freshly-created perch appears under its own project tab. ROOT (found, v0.13.0): `info.cwd` is NEVER set on bind \u00e2\u20ac\u201d `cmd_bind` (spt-hosted) and `bind_from_seed` (harness-hosted) never thread cwd into `establish_perch`/`rec.cwd`. FIX: `cmd_bind` reads its own `current_dir` (the broker spawned it in `project_cwd`); `bind_from_seed` passes `seed.cwd` (already captured at seed time, currently DISCARDED). DISTINCT from REQ-PICKER-HISTORY-FRESH (v0.12.1) \u00e2\u20ac\u201d that unioned cwd-origin into picker MEMBERSHIP but tested merge_origin_project with a PROVIDED origin; it never asserted `info.cwd` is actually set on bind, so a real `endpoint run` perch still had an empty cwd and the union had nothing to union. This is the v0.12.1 P1 'appears under its own project right away' claim that was REFUTED in the changelog \u00e2\u20ac\u201d delivered for real here. (v0.13.0)",
    "doc": ""
  },
  "REQ-HAZARD-BOUNDARY-READY-STRAND": {
    "id": "REQ-HAZARD-BOUNDARY-READY-STRAND",
    "title": "C-2 (F029, SEAM-2 pinned \u00e2\u20ac\u201d B6's SECOND HALF, the live-wake blocker; perri wakep9 vs wakep4 gate-state dump + doyle code trace): at a /clear, CC fires SessionEnd(reason=clear) for the DEPARTING session BEFORE SessionStart; the departing sid STILL matches the perch pin at that instant, so the adapter's [hooks.SessionEnd] \u00e2\u2020\u2019 `api session-end` AUTHENTICATES and the soft handler REMOVES the ready marker (+ unregister_address, reporting.rs cmd_session_end:206-207). The subsequent `api boundary` rotates the sid but NOTHING re-writes ready \u00e2\u2020\u2019 is_online false \u00e2\u2020\u2019 try_spt_hosted_inject Nones on the CLI gate BEFORE any broker RPC \u00e2\u2020\u2019 every post-clear force-native (incl. the checkpoint FIRE) reports the generic UNDELIVERED, persistent by construction (no path re-stamps ready outside a real bind). The single differing gate field at every UNDELIVERED instant is ready-absent (info online/controllable/rotated-sid all healthy, translate alive). Paid-for hazard. FIX: cmd_boundary re-stamps the ready marker (+ status online, idempotent) ATOMICALLY with the sid rotation \u00e2\u20ac\u201d a boundary PROVES a live successor session on the same harness process; a REAL end has no subsequent boundary so genuine teardown is unto",
    "doc": ""
  },
  "REQ-HAZARD-CASCADE-WIPE-GUARD": {
    "id": "REQ-HAZARD-CASCADE-WIPE-GUARD",
    "title": "No hard-delete of a parent hosting non-empty children (6.3)",
    "doc": ""
  },
  "REQ-HAZARD-CEREMONY-CLOCK-STEP": {
    "id": "REQ-HAZARD-CEREMONY-CLOCK-STEP",
    "title": "W1/D3 (JOIN-TRUTH, KNOWN-HAZARDS): the cached ceremony offset goes stale-WRONG when the OS clock STEPS under a live daemon \u00e2\u20ac\u201d an offset measured against the OLD clock keeps applying for up to the 15-min TTL. Field-proven timeline (enlyzeam): refresh cadence 15:08/15:23/15:38/15:53; operator `w32tm /resync` stepped the clock \u00e2\u02c6\u2019210s at 15:45:46; every `subnet join` returned NO_SEED_HOLDER until a daemon bounce forced a fresh query. Fix: the cache snapshot stores an (Instant, SystemTime) PAIR; on read, if |wall-elapsed \u00e2\u02c6\u2019 mono-elapsed| > ~2s the clock stepped \u00e2\u2021\u2019 force an immediate refresh (offset recomputed against the new clock). PLUS: `meet_seed_holder` (pairhost.rs), on search-deadline exhaustion, forces ONE fresh NTP refresh + one final sweep before returning NO_SEED_HOLDER \u00e2\u20ac\u201d so a stepped-clock join self-heals without a bounce. Clock reads must be seam-injectable for the hazard unit (inject the (mono,wall) pair \u00e2\u20ac\u201d do NOT sleep 15 min).",
    "doc": ""
  },
  "REQ-HAZARD-CONPTY-DSR": {
    "id": "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-LIFETIME": {
    "id": "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": {
    "id": "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": {
    "id": "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-CONTROLLER-WRITER-REORDER": {
    "id": "REQ-HAZARD-CONTROLLER-WRITER-REORDER",
    "title": "Two `controller_writer` threads must never race ONE brain\u00e2\u2020\u201dbroker connection's socket. ROOT (doyle, instrumented RACEDIAG repro on kitsubito): on a brain-restart re-serve the handoff brain registers as controller on the SAME session TWICE over the SAME `Brain::conn` socket \u00e2\u20ac\u201d (1) `Brain::handoff` eagerly `subscribe(prior.session_id, prior.next_seq=1)` \u00e2\u2020\u2019 `become_controller(from_seq=1)`, initial=[1], spawns writer-A (writes seq 1); (2) `serve_attach` re-handles the replayed `Request{from_seq:0}` \u00e2\u2020\u2019 `attach_as(sid,0)` \u00e2\u2020\u2019 `become_controller(from_seq=0)`, initial=[0,1], spawns writer-B (writes 0 then 1). `become_controller` (broker.rs) drops the prior `ControllerSink` (its `tx`) but does NOT stop the prior writer thread \u00e2\u20ac\u201d writer-A keeps flushing its owned `initial` batch, and both writers hold clones of the same `SharedSend` (`Arc<Mutex<socket>>`) with NO inter-thread ordering. When writer-A's seq 1 wins the socket before writer-B's seq 0, the strict legacy consumer (brain.rs read_event reject-gap path) sees `output gap: got seq 1 want 0` \u00e2\u2020\u2019 the test `attach_survives_target_brain_restart_exactly_once` panics at `.expect(\"re-serve\")` OR HANGS in `render_until` (serve thread died on the gap \u00e2\u2020\u2019 ",
    "doc": "7.20 `spt rc` must forward the scroll wheel to the harness (our mouse capture steals WT's native scroll) `[REQ-RC-MOUSE-FORWARD]`: <!-- --> ### 7.21 Exactly ONE `controller_writer` per brain\u00e2\u2020\u201dbroker connection \u00e2\u20ac\u201d a superseded writer must write nothing further `[REQ-HAZARD-CONTROLLER-WRITER-REORDER]` - **Failure (doyle instrumented RACEDIAG repro, kitsubito):** on a brain-restart re-serve the handoff brain registered as controller on the SAME session TWICE over the SAME socket \u00e2\u20ac\u201d `Brain::handoff` eagerly `subscribe(prior.next_seq=1)` \u00e2\u2020\u2019 `become_controller(from_seq=1)`, spawning writer-A (writes seq"
  },
  "REQ-HAZARD-CORRUPT-PERCH-COHERENCE": {
    "id": "REQ-HAZARD-CORRUPT-PERCH-COHERENCE",
    "title": "Corrupt (present-but-unparseable) info.json is NOT absent: liveness/status readers agree a destroyed record is neither alive nor Active (counter-39 #2)",
    "doc": "5.13 Atomic write leaves data un-synced before the rename \u00e2\u2020\u2019 NUL zero-fill on power loss `[REQ-HAZARD-PERCH-RECORD-POWER-LOSS]`: <!-- --> ### 5.14 Corrupt info.json read as ABSENT \u00e2\u2020\u2019 fail-open readers gossip a wiped perch ONLINE `[REQ-HAZARD-CORRUPT-PERCH-COHERENCE]` - **Failure:** three readers each collapsed a **corrupt** (present-but-unparseable) `info.json` into their fail-open ABSENT default, so a NUL-wiped perch (5.13) read as permanently live: `is_perch_alive` returned `true` (unreadable \u00e2\u2021\u2019 interim-alive), `advertised_status` then saw alive + no resting record \u00e2\u2021\u2019 `Active`, and the daemon se"
  },
  "REQ-HAZARD-DEFERRED-DRAIN": {
    "id": "REQ-HAZARD-DEFERRED-DRAIN",
    "title": "Deferred spool rows excluded from the event-stream drain (1.4)",
    "doc": ""
  },
  "REQ-HAZARD-DEFERRED-SURVIVE-DRAIN": {
    "id": "REQ-HAZARD-DEFERRED-SURVIVE-DRAIN",
    "title": "Deferred rows survive poll drain (4.4)",
    "doc": ""
  },
  "REQ-HAZARD-DIRECT-WRITE-PRECEDENCE": {
    "id": "REQ-HAZARD-DIRECT-WRITE-PRECEDENCE",
    "title": "Direct-write precedence marker (with node id) guards stale overwrite (6.5)",
    "doc": ""
  },
  "REQ-HAZARD-DRIVEN-BY-SELFHEAL": {
    "id": "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": {
    "id": "REQ-HAZARD-DROP-FILE-SINGLE-WRITER",
    "title": "Drop files are daemon-owned single-writer (6.4)",
    "doc": ""
  },
  "REQ-HAZARD-EBUSY-RENAME": {
    "id": "REQ-HAZARD-EBUSY-RENAME",
    "title": "tmp-write + atomic-rename + retry on Windows EBUSY (5.2)",
    "doc": ""
  },
  "REQ-HAZARD-ECHO-BEFORE-SIGNOFF": {
    "id": "REQ-HAZARD-ECHO-BEFORE-SIGNOFF",
    "title": "Echo-commune fires before INIT_SIGNOFF on orphan teardown (3.3)",
    "doc": ""
  },
  "REQ-HAZARD-ENDPOINT-LIFECYCLE": {
    "id": "REQ-HAZARD-ENDPOINT-LIFECYCLE",
    "title": "REGISTRY-LIFECYCLE W2 (KNOWN-HAZARDS 7.45 \u00e2\u20ac\u201d the umbrella conformance seam for ADR-0041): endpoint lifecycle state converges to truth from EVERY death path. Regression matrix from the three hertz reports + operator field: dead-PID hybrid row does not survive reconcile; raw viewport close frees the controller (full chain, broker restart included \u00e2\u20ac\u201d shared with REQ-STREAM-LEASE-CLASSES int); definitive death means offline+suspended atomically and the next reconcile emits no WAKE_RESUME; explicit Wake still launches exactly once; poll-vs-reap interleave converges to cleared stamps. HEAVY nextest group at birth for any leg spawning a daemon tree. Gate: int \u00e2\u20ac\u201d the matrix; doc \u00e2\u20ac\u201d KNOWN-HAZARDS 7.45.",
    "doc": "7.44 Streams and seats on a long-lived connection must have bounded lifetime \u00e2\u20ac\u201d one-way rows terminal at FIN, seats released at serve completion, no per-chunk full-state rewrites in a drain loop `[REQ-HAZARD-REGISTRY-STALL]`: ### 7.45 Endpoint lifecycle state converges to truth from every death path \u00e2\u20ac\u201d no optimistic online without authority, no surviving control stamps, no immortal wake intent, no untruthful create `[REQ-HAZARD-ENDPOINT-LIFECYCLE]` <!-- --> - **Failure (paid-for, three hertz reports + operator field 2026-07-16):** four families, one root shape \u00e2\u20ac\u201d lifecycle state written by multip"
  },
  "REQ-HAZARD-ENVELOPE-PARSER-SAFE": {
    "id": "REQ-HAZARD-ENVELOPE-PARSER-SAFE",
    "title": "Two-slice envelope parser is panic-free and tolerant (4.2)",
    "doc": ""
  },
  "REQ-HAZARD-EPHEMERAL-CLEANUP": {
    "id": "REQ-HAZARD-EPHEMERAL-CLEANUP",
    "title": "Ephemeral perch cleanup on every ring exit path (3.1)",
    "doc": ""
  },
  "REQ-HAZARD-GRACE-BEFORE-SIGNOFF": {
    "id": "REQ-HAZARD-GRACE-BEFORE-SIGNOFF",
    "title": "Grace-period wait completes before composing INIT_SIGNOFF (1.1)",
    "doc": ""
  },
  "REQ-HAZARD-HANDOFF-ARGV-COMPAT": {
    "id": "REQ-HAZARD-HANDOFF-ARGV-COMPAT",
    "title": "Broker/brain IPC + handoff argv version-tolerant (2.3)",
    "doc": ""
  },
  "REQ-HAZARD-HOSTED-LIVENESS-RECONCILE": {
    "id": "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": {
    "id": "REQ-HAZARD-INBOX-NO-DOUBLE",
    "title": "No double-delivery via legacy inbox (4.5)",
    "doc": ""
  },
  "REQ-HAZARD-INFO-JSON-TORN-READ": {
    "id": "REQ-HAZARD-INFO-JSON-TORN-READ",
    "title": "State-file reads tolerate concurrent writes (1.2)",
    "doc": ""
  },
  "REQ-HAZARD-INPUT-ACK-BACKPRESSURE": {
    "id": "REQ-HAZARD-INPUT-ACK-BACKPRESSURE",
    "title": "A FLOOD of operator input on one brain\u00e2\u2020\u201dbroker connection deadlocks the broker PERMANENTLY (entire broker \u00e2\u20ac\u201d no new/existing attach; the controller stays latched because the per-conn handler can't process the detach). ROOT (doyle /diagnose, code-grounded + HITL capture, the v0.13.0 P1 ctrl+V re-open): `serve_attach` processes a whole `NetStreamData` batch of N operator `Input` records in its inner `for rec in decoder.push()` loop, calling `brain.send_effect(op_id, &bytes)` N times WITHOUT returning to `read_event()` \u00e2\u20ac\u201d so the brain writes N `KIND_INPUT` frames back-to-back and drains nothing. The broker's single-threaded per-conn handler answers EACH with `send_frame(applied_envelope)` on the SAME conn (B5 exactly-once ack, KNOWN-HAZARDS 7.2). With the brain not reading, the broker\u00e2\u2020\u2019brain return direction fills (~10 frames = the IPC pipe buffer) \u00e2\u2020\u2019 `send_frame` BLOCKS \u00e2\u2020\u2019 the handler stops reading \u00e2\u2020\u2019 the brain's writes block too \u00e2\u2020\u2019 mutual full-duplex DEADLOCK. Capture pinned it: 11 input frames, write_input 11/11 (P0 holds \u00e2\u20ac\u201d the PTY write is fine), ack send START=11 / END=10 (frame #11's applied-ack never returns). Same class as the v0.12.1 L0 two-conn split. Windows Terminal's ctrl+V paste",
    "doc": "7.18 `spt rc` paste is client-originated on Windows \u00e2\u20ac\u201d read the LOCAL clipboard, inject a BRACKETED paste `[REQ-RC-WIN-PASTE]`: <!-- --> ### 7.19 An operator input FLOOD must not deadlock the broker via the applied-ack on the same conn `[REQ-HAZARD-INPUT-ACK-BACKPRESSURE]` - **Failure (operator HITL, the ctrl+V re-open):** a flood of operator input on one brain\u00e2\u2020\u201dbroker conn wedged the WHOLE broker PERMANENTLY (no new/existing attach; the controller stayed latched \u00e2\u20ac\u201d the per-conn handler couldn't process the detach). `serve_attach` processes a whole `NetStreamData` batch of N `Input` records in it"
  },
  "REQ-HAZARD-LISTEN-ORPHAN": {
    "id": "REQ-HAZARD-LISTEN-ORPHAN",
    "title": "W4 (LIFECYCLE-TRUTH, KNOWN-HAZARDS): `api listen --parent-pid N` watches parent liveness and exits loud on parent death. ROOT (mobile-gw RCA): --parent-pid is auth-anchor ONLY \u00e2\u20ac\u201d no liveness watch; host death orphans the listener forever -> perch held alive (false ONLINE), EVENTs stream to a dead stdout, dead-owner rebind BLOCKED (recorded pid = the live orphan). FIX: listener watches --parent-pid liveness (Windows: job object or poll; Unix: PDEATHSIG or poll) and exits loud on parent death. flynn's job-object guard (spt-mobile side) stays regardless; filed SPT-CORE-NEEDS \u00c2\u00a75. Unit: parent-death -> listener exits within one poll window.",
    "doc": ""
  },
  "REQ-HAZARD-LIVEHOST-BOOT-LIVENESS-GATE": {
    "id": "REQ-HAZARD-LIVEHOST-BOOT-LIVENESS-GATE",
    "title": "B5: `spt daemon start` does NOT revive phantom Psyches for dead-but-online-latched perches. Today reconcile_once (livehost.rs:285) spawns a Psyche per status=online live_agent perch at boot WITHOUT verifying the harness child / {id}-psyche is actually alive \u00e2\u20ac\u201d so a Cold start after an unclean stop revives N psyches for N dead-but-latched perches (3 psyches for 3 dead perches). FIX: gate the boot psyche-spawn on real child-liveness \u00e2\u20ac\u201d a perch with NO live broker session (the B2 reconcile signal) is marked OFFLINE at boot instead of hosted, so a dead-harness perch is never revived. Shares the B2 reconcile loop (this is its boot-gate arm); composes with B2's honest latch. Also closes wall-a's psyche_host_error gap (residency-confirm does not run at boot tick-1, livehost.rs:395-441 / 257-263). (v0.12.0)",
    "doc": ""
  },
  "REQ-HAZARD-LIVEHOST-BOOT-RACE": {
    "id": "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-LIVEHOST-NONRESIDENT": {
    "id": "REQ-HAZARD-LIVEHOST-NONRESIDENT",
    "title": "A daemon-hosted Psyche that spawns then EXITS IMMEDIATELY is a host failure, surfaced like a spawn failure (closes the v0.8.1 residual masking): the REQ-HAZARD-LIVEHOST-BOOT-RACE signal stamps `psyche_host_error` only when `spawn_psyche` returns Err, NOT when the detached spawn() returns Ok but the child dies within moments (e.g. a bad-argv child exiting 2 \u00e2\u20ac\u201d the F-009 case). That leaves the residual 'online + no Psyche + no cause' gap: the nested `{id}-psyche` info.json is written status=online with a real-but-DEAD pid and the PARENT perch carries NO psyche_host_error (perri's F-010: tasklist showed 0 host procs across the window while info.json read online). The host MUST confirm RESIDENCY \u00e2\u20ac\u201d a hosted child not alive (or whose `{id}-psyche` perch never re-registers / has a dead pid) within N seconds of spawn is treated as a host failure: stamp the parent perch `psyche_host_error{reason:\"host not resident within <n>s (psyche perch missing/dead pid)\"}` (and do not leave a phantom online nested perch). Closes the last masking gap the v0.8.1 fix left open. perri's F-010 (v0.8.1 dogfood). Sibling of REQ-HAZARD-LIVEHOST-BOOT-RACE.",
    "doc": ""
  },
  "REQ-HAZARD-MESH-BOOTSTRAP-TRAP": {
    "id": "REQ-HAZARD-MESH-BOOTSTRAP-TRAP",
    "title": "MESH-RECOVERY W1 (KNOWN-HAZARDS 7.42, hertz field RCA 2026-07-10 \u00e2\u20ac\u201d HFENDULEAM+ENLYZEAM symmetric green-status sequester): a node holding a valid RosterEntry.address for a peer is NEVER route-less \u00e2\u20ac\u201d a failed dial must not delete the only bootstrap route, and recovery must never require an already-successful connection or operator state surgery. Today: resolve_submit_addr = exact cache else id-only (never roster), PRESENCE_DIAL_FAILED unconditionally drop_seed's the cache row, and the cache refills only after a successful seed-proof exchange \u00e2\u20ac\u201d one transient + stalled discovery = self-sustaining isolation, invisible (net_up true, heartbeat fresh, durable counts normal). Gate: int \u00e2\u20ac\u201d production-path regression at the REAL pump resolver/failure-lifecycle seam: valid roster + matching cache, ONE transient dial failure, id-only discovery DISABLED, prove the next attempt still holds the roster-derived route AND all-peer-fail-then-restore converges with zero state surgery; doc \u00e2\u20ac\u201d KNOWN-HAZARDS 7.42. HEAVY nextest group at birth if it spawns a daemon tree. Kin REQ-PEER-ROUTE-CHAIN (the mechanism), REQ-CONV-1 (the falsified drop-on-fail predecessor), ADR-0039.",
    "doc": "7.41 A fresh dispatcher must NEVER re-serve a terminal stream \u00e2\u20ac\u201d historical replay must not steal or clear a live controller `[REQ-HAZARD-REDISPATCH-CONTROL-STEAL]`: ### 7.42 A node holding a valid roster address for a peer is NEVER route-less \u00e2\u20ac\u201d a failed dial must not delete the only bootstrap route `[REQ-HAZARD-MESH-BOOTSTRAP-TRAP]` <!-- --> - **Failure (paid-for, hertz field RCA 2026-07-10 \u00e2\u20ac\u201d HFENDULEAM + ENLYZEAM fully sequestered from every subnet member, symmetric, green-status):** the pump resolved dial addresses from the exact `peer-addrs.json` entry else id-only discovery \u00e2\u20ac\u201d never the val"
  },
  "REQ-HAZARD-PAIR-SEED-ROTATION": {
    "id": "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-RESIDENCY-EXPECTATION": {
    "id": "REQ-HAZARD-PSYCHE-RESIDENCY-EXPECTATION",
    "title": "W3 (F030 hazard; paid-for: hall-bf churn ordinal 6491+ + adapter v0.13.2 bad-ship brick 2026-07-04): a psyche failure of ANY shape must NOT remove or alter the parent endpoint's ready/hosted state, and hosting must NOT churn-respawn. The v0.13.2 shim-exit tripped the residency machinery (confirm_residency_or_unhost) which tore down the endpoint's hosted state \u00e2\u20ac\u201d ready marker removed, never re-stamped, every force-native gated leg=cli-gate-not-hosted PERMANENTLY (field brick). FIX: residency machinery retires with the resident child; the teardown that touches parent hosted state is DELETED \u00e2\u20ac\u201d psyche trouble stamps psyche fields only. REQ-HAZARD-LIVEHOST-NONRESIDENT's spirit transfers to the W1 failure budget (its entry gets a SUPERSEDED pointer here, LIVENESS-DECAY\u00e2\u2020\u2019SUPERSEDED pattern from C-1). Conformance int = the hall-bf shape: multi-subnet home, live endpoint, failing psyche \u00e2\u2020\u2019 parent stays deliverable, no rehost churn, error stamped (the wave's heart).",
    "doc": "7.29 Control/viewer stamps CONVERGE to broker session-table truth, not merely edge-trigger `[REQ-HAZARD-CONTROL-STAMP-CONVERGENCE]`: ### 7.30 A Psyche failure of ANY shape must NEVER remove or alter the parent endpoint's ready/hosted state `[REQ-HAZARD-PSYCHE-RESIDENCY-EXPECTATION]` <!-- --> - **Failure (paid-for, field brick 2026-07-04, adapter v0.13.2):** the pre-F-030 model kept a **resident** Psyche process the daemon supervised, with residency machinery (`confirm_residency_or_unhost`) that **un-hosted the parent endpoint** when the resident child went missing. A bad adapter ship (v0.13.2)"
  },
  "REQ-HAZARD-PUMP-IPC-DEADLINE": {
    "id": "REQ-HAZARD-PUMP-IPC-DEADLINE",
    "title": "The single-threaded peer pump's brain-IPC reads are deadline-bounded (PUMP_PEER_IO_TIMEOUT, total-wait per call); a TimedOut read POISONS the client and escalates to a SUPERVISED RESTART, never a per-peer retry \u00e2\u20ac\u201d a black-holed peer must never wedge the whole pump",
    "doc": "7.5 WAN-inbound origin is transport truth, never payload `[REQ-HAZARD-WAN-ORIGIN-AUTH]`: ### 7.6 Pump brain-IPC reads must be deadline-bounded (a blocked read wedges the whole pump) `[REQ-HAZARD-PUMP-IPC-DEADLINE]` <!-- --> - **Failure:** the peer pump is a SINGLE thread driving every leg (registry/notif/sync/update) against every peer over ONE brain-IPC client. Its reply reads (`net_open_stream`, `net_stream_send`, `net_dial`, and the sync/update pull `read_event` loops) were `loop { read_event() }` with no deadline. When a peer's QUIC path black-holes, the broker's stream-open/send awaits th"
  },
  "REQ-HAZARD-RC-ATTACH-FAILFAST": {
    "id": "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-EOF": {
    "id": "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-REDISPATCH-CONTROL-STEAL": {
    "id": "REQ-HAZARD-REDISPATCH-CONTROL-STEAL",
    "title": "REDISPATCH-TRUTH W1 (KNOWN-HAZARDS 7.41, hertz field RCA 2026-07-16 \u00e2\u20ac\u201d 4/5 endpoints frozen per brain cycle): a fresh dispatcher must NEVER re-serve a terminal stream \u00e2\u20ac\u201d a replayed historical Attach must not steal (same-identity silent become_controller, no Displaced) or clear (replayed-EOF detach_session) a LIVE controller. The legitimate same-by successor re-take after a brain restart still silently re-takes: the discriminator is stream LIFECYCLE, never origin identity. Gate: int \u00e2\u20ac\u201d production-path regression D1: finished historical Attach + current active Attach, same endpoint/origin; restart target brain only (real run_dispatch_loop rediscovery, NO manual re-serve \u00e2\u20ac\u201d the pre-fix e2e bypass is the lesson); prove the historical stream neither takes nor clears the current controller and current input/output stays exactly-once without detach; doc \u00e2\u20ac\u201d KNOWN-HAZARDS 7.41. HEAVY nextest group at birth (FLAKE-LEDGER #15). Kin REQ-REDISPATCH-FINISHED-RETIRE (the mechanism), REQ-BRAIN-RESUME-NO-CONTROL-STEAL (the CLOSED session-cursor sibling \u00e2\u20ac\u201d different leg), ADR-0038.",
    "doc": "7.40 A commune/signoff drop is deleted ONLY after every applicable tier is durably committed \u00e2\u20ac\u201d an un-committable slice preserves the drop, never delete-then-lose `[REQ-HAZARD-COMMUNE-INGEST-BLACKHOLE]`: ### 7.41 A fresh dispatcher must NEVER re-serve a terminal stream \u00e2\u20ac\u201d historical replay must not steal or clear a live controller `[REQ-HAZARD-REDISPATCH-CONTROL-STEAL]` <!-- --> - **Failure (paid-for, hertz field RCA 2026-07-16 \u00e2\u20ac\u201d 4/5 endpoints frozen, twice in one day):** every brain cycle (`spt daemon refresh`, `spt update` apply \u00e2\u20ac\u201d same `applyhost.rs` path) launches a fresh `dispatch::run_dispa"
  },
  "REQ-HAZARD-REGISTRY-DIR-CREATE": {
    "id": "REQ-HAZARD-REGISTRY-DIR-CREATE",
    "title": "SQLite store opens create their parent dir themselves \u00e2\u20ac\u201d a fresh-home registry op must not SQLITE_CANTOPEN (4.9)",
    "doc": "4.8 Registry merge ordered by epoch, never wall-clock (red-team #8): ### 4.9 SQLite stores must create their parent dir \u00e2\u20ac\u201d SQLite won't <!-- --> - **Failure:** `Connection::open` creates the database FILE but never its parent DIRECTORY. On a fresh home (first boot, fresh CI `_work` dir) a registry op that runs before any perch-creating op (`create_dir_all` side effects) fails `SQLITE_CANTOPEN` \u00e2\u20ac\u201d \"unable to open database file \u00e2\u20ac\u00a6owlery\\.registry\". Timing-dependent: whichever code path touches the home first decides the outcome, so it surfaces as a parallel-test flake (bind-first tests losing the d"
  },
  "REQ-HAZARD-REGISTRY-EPOCH-LEASE": {
    "id": "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-GHOST-ROWS": {
    "id": "REQ-HAZARD-REGISTRY-GHOST-ROWS",
    "title": "Registry rows must decay (no immortal rows) via TWO triggers: (a) NODE-SILENCE \u00e2\u20ac\u201d evict rows whose author node has not been heard (admitted inbound feed) within the eviction window, so a vanished node's rows stop poisoning bare-id resolution with phantom AcrossNodes ambiguity; AND (b) per-row OFFLINE-TTL \u00e2\u20ac\u201d evict rows that have been non-routable (Offline) beyond the per-row grace even while the author node is alive, because purge/erase leaves an immortal Offline row otherwise (ghost-heal re-advertises Offline ONCE with a fresh epoch, and whole-node eviction never fires for a still-alive author) so Offline ghost rows accumulate unbounded on a remote viewer under purge/erase churn (#2-secondary). Both keyed on RECEIVER-observed state (heard-map recency / a sticky receiver-observed offline_since, NOT the gossip epoch \u00e2\u20ac\u201d an epoch-keyed clock would be reset by ghost-heal's fresh-epoch re-advertise); own rows never decay; a revived/re-flapped row re-inserts (or clears its offline_since) from its durable epoch within one pump cadence (4.10)",
    "doc": "4.9 SQLite stores must create their parent dir \u00e2\u20ac\u201d SQLite won't: ### 4.10 Dead node identities leave immortal registry rows `[REQ-HAZARD-REGISTRY-GHOST-ROWS]` <!-- --> - **Failure:** the registry's only superseding mechanism is the per-`(endpoint_id, node)` epoch lease (4.8) \u00e2\u20ac\u201d a row is replaced only by a newer row *from the same node*. When a node identity dies permanently (machine retired, or `node.key` regenerated so the \"node\" never speaks again), its rows are never superseded and never expire: they sit in the in-memory registries and the `identity/registry/<subnet>.json` snapshots forever. A"
  },
  "REQ-HAZARD-REGISTRY-STALE-CLEAN": {
    "id": "REQ-HAZARD-REGISTRY-STALE-CLEAN",
    "title": "Stale registry entries degrade to fallback, never hard-fail (4.3)",
    "doc": ""
  },
  "REQ-HAZARD-RENDER-LIFECYCLE": {
    "id": "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": {
    "id": "REQ-HAZARD-RESTART-IDEMPOTENT",
    "title": "Idempotent/exactly-once delivery across brain restart at every broker boundary (codex #14)",
    "doc": ""
  },
  "REQ-HAZARD-RESUME-CUSTODY-ABA": {
    "id": "REQ-HAZARD-RESUME-CUSTODY-ABA",
    "title": "KNOWN-HAZARDS 7.51: process custody is an identity, never a bare PID \u00e2\u20ac\u201d a recycled pid must read NOT OURS. The hazard-conformance twin of REQ-RESUME-CUSTODY-IDENTITY: its int-stage recycled-pid rig IS this hazard's required test (custody pair mismatching a live impostor pid -> record deleted, reconcile proceeds, row goes honest). Registered separately so the hazard list stays a conformance checklist (CLAUDE.md rule 4) \u00e2\u20ac\u201d evidence may tag the same rig.",
    "doc": "7.51 Process custody is an identity, never a bare PID \u00e2\u20ac\u201d a recycled pid must read NOT OURS `[REQ-HAZARD-RESUME-CUSTODY-ABA]`: <!-- --> - **Failure (paid-for, hertz v0.39.4 field RCA 2026-07-22, doyle code-verified same day):** `resume.pid` custody is a bare PID consumed as `read_resume_pid(..).is_some_and(is_process_alive)` at BOTH the livehost restart gate and the liveness-reconcile DEFER. A dead wake-resume spawn's pid, recycled by the OS onto an unrelated process (field proof: `resume.pid=29456` resolved to a random `cmd.exe`), reads as \"a resume is in flight\" indefinitely: reconcile defers"
  },
  "REQ-HAZARD-ROLLBACK-STATE-COMPAT": {
    "id": "REQ-HAZARD-ROLLBACK-STATE-COMPAT",
    "title": "A brain must not irreversibly migrate durable state before update ready-promotion: the readiness-gated auto-rollback (ADR-0018 Q7) spawns the N-1 binary against durable state the new brain may have written, so every pre-ready write must stay N-1-readable (schema migrations gated behind ready-promotion, or written N-1-tolerant/additive). Else the first in-place schema migration silently bricks rollback (KNOWN-HAZARDS 6.8). Free now \u00e2\u20ac\u201d a 2026-06-09 audit confirmed zero state-migration code exists; unmintable retroactively once a migration ships.",
    "doc": "6.7 Broker and brain MUST be separate processes (in-process collapse silently breaks no-endpoint-drop update) `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]`: ### 6.8 No irreversible durable-state migration before update ready-promotion `[REQ-HAZARD-ROLLBACK-STATE-COMPAT]` - **Failure:** the readiness-gated auto-rollback (ADR-0018 Q7) spawns the *previous* binary against durable state the *new* brain already wrote. The first release that migrates a durable-state schema in place would make the old binary unable to read it \u00e2\u20ac\u201d silently bricking rollback exactly when it is needed (a logic-bricking update t"
  },
  "REQ-HAZARD-SESSION-PIN-WEDGE": {
    "id": "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-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK": {
    "id": "REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK",
    "title": "UPDATE-WEDGE round-4 (hertz RCA, root reproduced + source-pinned on Windows 2026-07-09): EVERY write on a physical broker connection rides ONE bounded + cancelable + poison-on-failure framed-write primitive \u00e2\u20ac\u201d no writer may hold the connection's serialized send gate across an UNBOUNDED OS write. ROOT: controller_writer held the SharedSend = Arc<Mutex<SendHalf>> guard ACROSS a blocking write_frame; on Windows interprocess 2.4.2 routes the send to WriteFileEx + SleepEx(INFINITE, alertable) with NO supported write timeout (set_timeout \u00e2\u2020\u2019 Unsupported), so an `rc --take` controller consumer that stops reading blocks the write INDEFINITELY (~127.95 s in the field capture, released only when a brain restart tore the conns down). Logical stall_evict_controller (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE) removes the controller ROLE but neither cancels the in-flight pipe write, closes the physical connection, nor invokes CancelIoEx \u00e2\u20ac\u201d the detached writer keeps its SharedSend clone + live stack-owned mutex guard. Load-gated: needs a real seq>0 frame + a non-draining consumer (seq-0 boot conns + a quiescent update are clean \u00e2\u20ac\u201d why v0.30.5's controlled apply passed); CTRL_WRITE_LOCKED wait_us=0 on al",
    "doc": "7.37 The Layer-1 settle-gate must RE-ARM per delivery on an observable PTY \u00e2\u20ac\u201d a mid-session reader reattach (`/clear`) re-creates the head-swallow race `[REQ-HAZARD-INJECT-SETTLE-REARM]`: ### 7.38 EVERY write on a physical broker connection is bounded + cancelable + poison-on-failure \u00e2\u20ac\u201d no writer holds the send gate across an UNBOUNDED OS write `[REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]` <!-- --> - **Failure (paid-for, field capture + deterministic Windows repro 2026-07-09 \u00e2\u20ac\u201d hertz RCA, UPDATE-WEDGE round-4):** `controller_writer` held the `SharedSend = Arc<Mutex<SendHalf>>` guard ACRO"
  },
  "REQ-HAZARD-SHELL-STALE-ONLINE": {
    "id": "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": {
    "id": "REQ-HAZARD-SINGLE-PATH-SOURCE",
    "title": "Single path/registry source of truth; no layout ambiguity (6.1)",
    "doc": ""
  },
  "REQ-HAZARD-SOFT-CLEANUP": {
    "id": "REQ-HAZARD-SOFT-CLEANUP",
    "title": "Soft-cleanup preserves state, removes only the ready marker (6.2)",
    "doc": ""
  },
  "REQ-HAZARD-STALE-INDEX-LOCK": {
    "id": "REQ-HAZARD-STALE-INDEX-LOCK",
    "title": "Sweep stale lockfiles on daemon boot (1.3)",
    "doc": ""
  },
  "REQ-HAZARD-STALE-SIGNOFF-SENTINEL": {
    "id": "REQ-HAZARD-STALE-SIGNOFF-SENTINEL",
    "title": "Stale signoff sentinel does not kill a fresh start (3.2)",
    "doc": ""
  },
  "REQ-HAZARD-STOP-RESPAWN-CONVOY": {
    "id": "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": {
    "id": "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-UNHOST-PSYCHE-REAP": {
    "id": "REQ-HAZARD-UNHOST-PSYCHE-REAP",
    "title": "On un-host, the detached `{id}-psyche` HARNESS PROCESS is reaped \u00e2\u20ac\u201d not just its in-brain pulse-driver thread. Today stop_host (livehost.rs:203) trips the HostedLife stop flag + JOINS the driver thread, but the Psyche is a detached harness process (spawn_psyche \u00e2\u2020\u2019 ManifestRuntime detached spawn, runtime.rs:341-356; its pid is untracked in HostedLife though stamped on the `{id}-psyche` perch, where residency-confirm already reads it). So endpoint-stop / mid-life agent-death / a B2/B5 offline-then-unhost leaves the psyche process ORPHANED, alive until the next daemon-stop (where Breap's job/group reaps the whole brain subtree). The Psyche STAYS a harness process by design (CONTEXT.md 97/203/251 \u00e2\u20ac\u201d headless harness session, its own perch) \u00e2\u20ac\u201d the fix does NOT move it in-brain; it SCOPED-kills the `{id}-psyche` pid on un-host (never machine-wide \u00e2\u20ac\u201d shared box). Track the pid in HostedLife at host_one (cleanest) or read the `{id}-psyche` perch pid at stop_host. Composes with H3 (endpoint stop \u00e2\u2020\u2019 offline \u00e2\u2020\u2019 reconcile un-host \u00e2\u2020\u2019 reap) and B2/B5 (the offline arms that trigger un-host). (v0.12.0)",
    "doc": ""
  },
  "REQ-HAZARD-VIEWER-CLOSE-DETACH": {
    "id": "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": {
    "id": "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-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE": {
    "id": "REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE",
    "title": "A SLOW controller must not starve a concurrent `rc --view` VIEWER. W1 (REQ-HAZARD-INJECT-CONTROL-COEXIST) moved the controller SOCKET WRITE off the drain thread onto controller_writer, but left the bounded HANDOFF (ControllerJob::deliver) as an INLINE try_send SLEEP-POLL on the drain (broker.rs:1450-1457 \u00e2\u2020\u2019 deliver:669-685, up to CONTROLLER_WRITE_DEADLINE=5s). So when a controller drains slower than the PTY floods, its CONTROLLER_CHANNEL_DEPTH(4096) channel fills, deliver() polls inline, and the DRAIN THREAD is throttled to the controller's read rate \u00e2\u2020\u2019 OutputLog::append's viewer fan-out (try_send) stops running \u00e2\u2020\u2019 a concurrent VIEWER receives only the initial replay then nothing (root 'b4', warm forkpty: a_journaled c1=0/EVICT=0/got_output=FALSE; steady-state-near-full = no recovery; forkpty-only, floods harder than Windows ConPTY). The viewer-not-starved-by-a-busy-session property is legitimate (rc --view of a noisy session must show LIVE output). FIX: the controller becomes a SINGLE NON-BLOCKING try_send (like a viewer), done IN append() under the log lock; deliver()'s sleep-poll DELETED; the drain NEVER sleeps. ControllerSink gains a stateful last_ok deadline \u00e2\u2020\u2019 a TRULY-stalled con",
    "doc": ""
  },
  "REQ-HAZARD-WINDOWS-PID-RECYCLE": {
    "id": "REQ-HAZARD-WINDOWS-PID-RECYCLE",
    "title": "Windows PID-recycling false positives guarded (5.1)",
    "doc": ""
  },
  "REQ-HAZARD-WMI-DAEMON-WINDOW": {
    "id": "REQ-HAZARD-WMI-DAEMON-WINDOW",
    "title": "`spt daemon start` launches the daemon with NO visible console window. REGRESSION (v0.12.1 L1.5): the WMI job-neutral launch (spawn_daemon_via_wmi) set CREATE_NO_WINDOW on the launching powershell but NOT on the Win32_Process.Create call \u00e2\u20ac\u201d Win32_Process.Create does not inherit it, so the spawned cmd.exe env-forwarding wrapper popped a console window on every cold-start (violating REQ-INSTALL-10's v0.7.4 no-persistent-window invariant; the old detached_no_inherit path set DETACHED_PROCESS|CREATE_NO_WINDOW). FIX: pass a Win32_ProcessStartup with CreateFlags=DETACHED_PROCESS (0x8 \u00e2\u20ac\u201d no console so no window; CREATE_NO_WINDOW 0x08000000 is NOT a valid Win32_ProcessStartup flag \u00e2\u2020\u2019 ReturnValue 21 invalid-param, which is why the naive port fails) + ShowWindow=SW_HIDE(0) belt, via the ProcessStartupInformation argument. (v0.12.2)",
    "doc": ""
  },
  "REQ-HEAVY-UNIT-CLASSIFICATION": {
    "id": "REQ-HEAVY-UNIT-CLASSIFICATION",
    "title": "A unit test that stands up a REAL broker inside a lib/bin `#[cfg(test)]` block must sit in the `heavy-broker-pty` nextest group, and the classification must be ENFORCED rather than remembered. FLAKE-LEDGER #14 diagnosed this class in the `spt` binary, wrote the CLASS in prose, then shipped an ENUMERATION of four `rc::tests::` names \u00e2\u20ac\u201d so the identical shape in `spt-daemon`'s lib (`applyhost`: a real `Broker::bind` + `serve()` in 10 of its 13 units) stayed in the full-parallel Phase-A pool and TIMED OUT at 240s twice, at v0.32.0 and again under the v0.39.0 W5 gate, the v0.32.0 remedy never having landed. The defect is provable from `.config/nextest.toml` ALONE (two overrides, neither matching `kind(lib)`); timing evidence only ever estimated the rate. Gate: impl \u00e2\u20ac\u201d heavy-group overrides for `applyhost`/`livehost`/`pump` (spt-daemon lib) and `wansend` (spt bin, found BY the check rather than by a person), plus `xtask check`'s `check_heavy_unit_classification` keyed on the SHAPE (a `Broker::bind` after the `mod tests` marker) instead of a name list; unit \u00e2\u20ac\u201d the two pure seams, including the regression for this check's OWN first draft, which substring-matched `<module>::tests` and so miss",
    "doc": ""
  },
  "REQ-HOST-RUN-1": {
    "id": "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-HOST-RUN-2": {
    "id": "REQ-HOST-RUN-2",
    "title": "Project-scoped working directory for spt-hosted bringup: `spt endpoint run` lands the broker-spawned harness PTY in the user's PROJECT cwd, not the daemon's, via an additive `SpawnReq.cwd` field carried through the broker PTY spawn (portable-pty CommandBuilder cwd). N-1-safe wire change (additive, defaulted). Required because the consumer (Claude Code) is project-scoped: broker-inherited cwd = the daemon's cwd = the wrong `.claude`, wrong session history, wrong digest source; `cc <id>` at a project root MUST land the harness in that project. W1 ships broker-inherited cwd as a bringup-proof shortcut only; this REQ must land before the M12 gate (doyle, 2026-06-14).",
    "doc": ""
  },
  "REQ-HOSTING-AUTHORITY-CONTROLLABLE": {
    "id": "REQ-HOSTING-AUTHORITY-CONTROLLABLE",
    "title": "RC-RENDER-TRUTH v0.38.1 fast-follow leg 3 (hertz todlando immortal-hybrid trace, doyle fork ruling 2026-07-18 = controllable-authority): ONE hosting authority \u00e2\u20ac\u201d persisted `state` stays the durable endpoint TYPE (REQ-EP-6 open type system; establish_perch's prior-type preserve at startup.rs:346-350 is INTENTIONAL and stays), `controllable==Some(true)` is the source-definitive broker-PTY authority. FIELD ROOT (C1 coverage gap, not new family): an spt-hosted bind over a prior ready_agent perch preserves state=ready_agent while stamping controllable=true + online -> livehost restart_resume_gate (508-534) Skip's state!=live_agent so the orphan never resumes, and reconcile's C1 dead-pid hybrid heal predicate was scoped controllable=false so controllable=true escapes -> immortal dead-PID ready_agent hybrid, latch-driven Active projection (todlando field state; rc's no-session refusal was TRUTHFUL). FIX (hertz refinement, ratified): remove the state rejection from restart_resume_gate; reconcile routes only non-live_agent && controllable!=Some(true) through the PID-model hybrid heal, while controllable==Some(true) rows fall through BROKER-SESSION truth (orphan with ledger/adapter material =",
    "doc": "Requirements: <!-- --> ## Amendment (RC-RENDER-TRUTH v0.38.1 leg 3, doyle ruling 2026-07-19): one hosting authority \u00e2\u20ac\u201d the online-earn authority splits by hosting topology"
  },
  "REQ-INST-10": {
    "id": "REQ-INST-10",
    "title": "Qualified addressing [subnet:]id[@node] + ambiguity forces qualification",
    "doc": ""
  },
  "REQ-INST-12": {
    "id": "REQ-INST-12",
    "title": "Endpoint visibility per-(endpoint,subnet): excluded semantics, OR-of-defaults + override, gates sync",
    "doc": ""
  },
  "REQ-INST-13": {
    "id": "REQ-INST-13",
    "title": "Subnet-exclusive sync + per-endpoint subnet-membership list",
    "doc": ""
  },
  "REQ-INST-7": {
    "id": "REQ-INST-7",
    "title": "Subnet registry + bare-id resolution policy",
    "doc": ""
  },
  "REQ-INST-9": {
    "id": "REQ-INST-9",
    "title": "Multi-subnet membership (same-user N subnets; cross-user seam)",
    "doc": ""
  },
  "REQ-INSTALL-10": {
    "id": "REQ-INSTALL-10",
    "title": "Windows at-logon autostart runs the daemon in the background with no persistent window: the scheduled task launches `spt daemon start` (which spawn_detaches a console-less DETACHED_PROCESS daemon and exits) rather than the foreground `spt daemon run` \u00e2\u20ac\u201d Task Scheduler's interactive ONLOGON launch of a long-lived console process otherwise leaves a visible console window for the daemon's whole lifetime (v0.7.4)",
    "doc": ""
  },
  "REQ-INSTALL-13": {
    "id": "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-2": {
    "id": "REQ-INSTALL-2",
    "title": "Marketplace-repackaging-friendly install",
    "doc": "Installation: <!-- the two-paths model + the one-line script half (v0.1 phasing below; OS-service leg = docs/DEFERRED.md) --> <!-- the marketplace-repackaging stance: relocatable binary + minimal, non-OS-entangled install logic --> spt-core is per-machine and harness-independent, so it installs *before* and *independent of* any adapter."
  },
  "REQ-INSTALL-4": {
    "id": "REQ-INSTALL-4",
    "title": "Adapter registration lifecycle: spt adapter add (--github, manifest-first, install-is-first-update) + soft-deregister remove + optional manifest uninstall template; node-local registered-adapter set self-update ripples over",
    "doc": ""
  },
  "REQ-INSTALL-5": {
    "id": "REQ-INSTALL-5",
    "title": "Non-interactive install path: the install path doubles as every adapter's pack-in on-demand install (no second mechanism); sha256-verified fetch; user-PATH registration. HISTORY: 'the canonical one-liner' \u00e2\u20ac\u201d since THE-FORKENING (ADR-0036) the canonical path is gh + the spt install verb (itself non-interactive, REQ-INSTALL-BOOTSTRAP-VERB); the scripts this REQ's evidence attests remain as the hermetic CI fixture + air-gap fallback, still non-interactive by construction (doyle-ratified 2026-07-14).",
    "doc": ""
  },
  "REQ-LISTEN-PRESERVES-HOSTING-TOPOLOGY": {
    "id": "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-LISTEN-SEED-CONSUME-AFTER-BIND": {
    "id": "REQ-LISTEN-SEED-CONSUME-AFTER-BIND",
    "title": "F-034 leg b (perri/hertz field finding 2026-07-09, hertz's HEADLINE): `api listen` must NOT consume the consume-once ephemeral seed on a PRE-BIND refusal \u00e2\u20ac\u201d validate (adapter resolvable, home/subnet) and BIND first, THEN consume the seed. ROOT: today `api listen` burns the consume-once seed BEFORE it validates home/subnet, so on a multi-subnet node HOME_REFUSED (needs --subnet) fires AFTER the seed is already gone \u00e2\u2020\u2019 the corrected retry (adding --subnet) on the SAME pid hits NO_SEED, a dead end (plausibly ADAPTER_UNRESOLVED burns it the same way). A refusal that never bound must leave the seed intact for the corrected retry. Same EFFECT-BEFORE-IRREVERSIBLE-CONSUME ordering class as F-032 (commune commit-before-delete) \u00e2\u20ac\u201d the irreversible consume must follow the successful effect, never precede a refusal. Gate: a pre-bind refusal (HOME_REFUSED on a multi-subnet node without --subnet; ADAPTER_UNRESOLVED) leaves the seed CONSUMABLE \u00e2\u20ac\u201d the corrected retry on the same pid binds (no NO_SEED); a SUCCESSFUL bind still consumes the seed exactly once (no double-bind). Files: the api-listen bind path (seed consume ordering). Kin F-032 [[spt-core-findings-backlog]].",
    "doc": "`api listen <id> [--once] [--parent-pid <pid>] [--subnet <name>] [--session-id <sid>]`: <!-- --> **Recoverable refusals do not consume the seed.** The seed is consumed by a **successful bind** \u00e2\u20ac\u201d or by a refusal that proves the seed itself dead (see spend-vs-restore below). A recoverable refusal that never bound \u00e2\u20ac\u201d `HOME_REFUSED` on a multi-subnet node without `--subnet`, `ADAPTER_UNRESOLVED`, a live-perch conflict \u00e2\u20ac\u201d leaves the seed consumable, so the corrected retry on the same pid binds instead of dead-ending on `NO_SEED`. (Effect before irreversible consume: the destructive step follows the s"
  },
  "REQ-LIVE-AGENT-NO-INJECT-DELIVERY": {
    "id": "REQ-LIVE-AGENT-NO-INJECT-DELIVERY",
    "title": "F-033 RE-SCOPED (doyle re-ruling 2026-07-10 after the #82 gate falsified the original premise \u00e2\u20ac\u201d the hosting-mode class split is BINDING context): 'state:live_agent has a self-delivery reader' CONFLATED two hosting modes. (A) HARNESS-hosted live agents (api listen path) DO deliver via adapter channels only \u00e2\u20ac\u201d and are ALREADY structurally excluded from inject: bind_from_seed stamps controllable=Some(false), no broker PTY exists. (B) SPT-HOSTED/CONTROLLED live agents' inject leg IS their delivery reader (broker PTY + translation binary \u00e2\u20ac\u201d doyle's own endpoint is field proof: ENDPOINT_INJECT + IDLE_PARKED_DRAIN alongside hook-poll); the original blanket state:live_agent exclusion broke REQ-MSG-IDLE-EDGE-DRAIN + the v0.14.3 LAW on both CI platforms and was REVERTED (predicate stays controllable-gated). perri's adapter-channels-only model (F-dupmsg-adapter-confirm.md) holds for class (A) only \u00e2\u20ac\u201d do not re-seed the conflation. The F-033 DUPLICATE mechanism (hook-poll + idle-inject both delivering one row, operator spool row 156) is closed structurally by REQ-CARRIER-CLAIM-EXCLUSIVE's atomic cross-carrier take. REMAINING LEGS OF THIS REQ: (a) unit \u00e2\u20ac\u201d a harness-hosted live agent (controllable S",
    "doc": ""
  },
  "REQ-LIVEHOST-RECONCILE-TRIAL-SILENT": {
    "id": "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-2": {
    "id": "REQ-MANIFEST-2",
    "title": "Adapter profiles \u00e2\u20ac\u201d sparse leaf-replace overlays (shipped + local), composite <adapter>:<profile> addressing, shadow-refusal, tighten-only consent floors",
    "doc": "Runtime model: <!-- --> **adapter profile** (ratified 2026-06-11, Gateway grill; future spt-core milestone \u00e2\u20ac\u201d first beneficiaries `spt-claude-code` and the usbip shell): A named **sparse overlay** on its parent adapter manifest. Merge semantics are **leaf-replace**: a profile key replaces the whole value at that path (arrays included \u00e2\u20ac\u201d never spliced or appended). The merged result is a complete manifest, and the profile behaves as a distinct adapter option everywhere: canonical addressing is the composite **`<adapter>:<profile>`** (`claude-spt:work`, `spt-usbip-driver:hid-only`) in every place"
  },
  "REQ-MANIFEST-5": {
    "id": "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": {
    "id": "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-2": {
    "id": "REQ-MESH-2",
    "title": "Member roster: node-level union-merge grow-set (per member: pubkey, label, machine_id, last-known address, last-seen \u00e2\u20ac\u201d NOT the seed), the discovery directory the mesh dials by. Seeded IN FULL at pairing (seed-holder hands joiner the whole current roster, incl. offline members \u00e2\u20ac\u201d folds in deferred pairing-time hostname capture + post-join address seeding); each node authors its own entry stamped with its lease_epoch, merged strictly-greater-wins (the node_label lease); exchanged only over seed-proof'd member connections; forgery-inert (a fake entry names a pubkey that still can't seed-proof). Removal needs a TOMBSTONE \u00e2\u20ac\u201d a per-pubkey revoked marker that propagates, dominates the entry, gates admission (seed-proof \u00e2\u02c6\u00a7 \u00c2\u00actombstoned), and prevents reinsert; cleared by a completed re-pair of that pubkey. Persists through silence (offline member keeps its entry).",
    "doc": ""
  },
  "REQ-MESH-6": {
    "id": "REQ-MESH-6",
    "title": "Concurrent liveness probes: `spt subnet status --nodes` fans out its offline/serve-probes (REQ-SUBNET-5) CONCURRENTLY \u00e2\u20ac\u201d total wall-time bounded by the single-probe ceiling (~3s), never k\u00c3\u2014ceiling. The mesh makes a node see ALL members (many possibly offline), so a serial probe loop would be offline_count\u00c3\u20143s. (Planning verifies the current REQ-SUBNET-5 probe loop's behavior and fixes it if serial.)",
    "doc": ""
  },
  "REQ-MIGRATE-1": {
    "id": "REQ-MIGRATE-1",
    "title": "Auto-detect and migrate a legacy claude_skill_owl install",
    "doc": ""
  },
  "REQ-MSG-3": {
    "id": "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-IDLE-EDGE-DRAIN": {
    "id": "REQ-MSG-IDLE-EDGE-DRAIN",
    "title": "On an endpoint's ACTIVE\u00e2\u2020\u2019IDLE transition the daemon DRAINS its pending spool (deferred AND non-deferred) through the same shared spt-hosted inject leg \u00e2\u20ac\u201d closing the SECOND F-023 gap: no idle-edge drain exists anywhere, so an spt-hosted endpoint (which has no api-listen relay to wake it) strands BOTH message classes ('ACTIVE \u00e2\u2020\u2019 spool deferred for hook-poll' and 'IDLE+no-binary \u00e2\u2020\u2019 non-deferred for a relay that does not exist'). FIX: on the state ACTIVE\u00e2\u2020\u2019IDLE edge, offer the pending spool through the shared inject leg; REUSE the hook-poll drain's take/ack machinery so a concurrent `api poll` cannot double-deliver \u00e2\u20ac\u201d ONE drain path, TWO triggers (hook-poll + idle-edge). v0.14.3 LAW holds on BOTH triggers: translation-binary-ONLY, a no-binary idle drain SPOOLS LOUD, never writes the PTY. (F-023, BUILD-F023-WANIDLE)",
    "doc": ""
  },
  "REQ-MSG-INJECT-LEG-DROP-VISIBLE": {
    "id": "REQ-MSG-INJECT-LEG-DROP-VISIBLE",
    "title": "SEED (inactive \u00e2\u20ac\u201d observability): a silently-dropped delivery leg must be DISTINGUISHABLE from an honestly-offline endpoint on a status surface. The spt-hosted inject leg (spt-daemon inject.rs `try_spt_hosted_inject` \u00e2\u20ac\u201d the ONE shared implementation behind local cmd_send, the WAN ingress, the idle-edge drain, and the parked-idle/pulse re-offer belts) gates on `is_spt_hosted_no_relay` \u00e2\u2020\u2019 `deliver::is_online` \u00e2\u2020\u2019 `liveness::is_perch_alive`: a perch PINNED TO A DEAD SESSION (the KH 7.25 wedge class \u00e2\u20ac\u201d dead owner, record not yet healed by the next auth touch) makes EVERY belt on that leg silently return None \u00e2\u2020\u2019 messages spool as if the endpoint were ordinarily offline, while the operator-facing view can keep reading the recorded state. Nothing anywhere surfaces 'the inject leg stopped firing for this endpoint' \u00e2\u20ac\u201d the idle-window injections just stop, which from the outside is indistinguishable from 'no messages arrived' (field shape: IDLE-EDGE W1 field-verify, perri's killed-resident rig \u00e2\u20ac\u201d their 'so it did not repeat' conclusion was exactly this invisibility, self-corrected only by re-rigging with a live resident). Shape at activation: a status/list surface DERIVES and reports the inject-leg v",
    "doc": ""
  },
  "REQ-NODE-IDENTITY": {
    "id": "REQ-NODE-IDENTITY",
    "title": "Ed25519 identity primitive: keypair, detached sign/verify, stable pubkey<->hex",
    "doc": ""
  },
  "REQ-NOTIF-DRAIN-ROW-VALIDITY": {
    "id": "REQ-NOTIF-DRAIN-ROW-VALIDITY",
    "title": "A spooled notify envelope is validated against its notif row at DELIVERY time \u00e2\u20ac\u201d a copy outliving its row must not deliver. (ADR-0046 Amendment 1 + KNOWN-HAZARDS 7.53; operator field regression from perri's node 2026-07-22, doyle root-caused same day \u00e2\u20ac\u201d DAEMON-LIFECYCLE W1 RIDER.) TODAY: quiet delivery (REQ-NOTIF-QUIET-DELIVERY) makes every surface an active_only SPOOL write per endpoint, and every row-lifecycle mechanism (apply-seam dismissal REQ-NOTIF-SEAM-DISMISS, coalesce supersession, TTL, the one-shot migration) touches ROWS only \u00e2\u20ac\u201d so a copy spooled while the endpoint was busy/offline is a detached snapshot no dismissal can recall, delivering 'update available' on an already-updated node at the next drain, once per qualifying surface event (perri: twice; doyle's own session: four stale 0.39.x drains post-upgrade). FIX: at the safe-point drain choke point (api poll deferred presentation, spt/src/api/delivery.rs cmd_poll), a notify-kind envelope delivers ONLY if its notif_id resolves to a live UNDISMISSED row in the local notif store; dismissed/superseded/TTL-expired/unknown -> dropped silently; N copies of one notif_id in a drain dedupe to ONE delivery. Non-notify spool content ",
    "doc": "7.53 A durable copy of revocable content is validated against its source of truth at DELIVERY time \u00e2\u20ac\u201d a spooled notice outliving its row must not deliver `[REQ-NOTIF-DRAIN-ROW-VALIDITY]`: <!-- --> - **Failure (paid-for, operator field report from perri's node 2026-07-22 \u00e2\u20ac\u201d the day after the v0.40.0 notif redesign shipped; doyle root-caused same day, own session corroborating with stale v0.39.x drains):** a node already ON v0.40.0 received the v0.40.0 \"update available\" notice twice. ADR-0046's quiet delivery makes every notif surface an `active_only` SPOOL write per endpoint; every lifecycle mec"
  },
  "REQ-NOTIF-QUIET-DELIVERY": {
    "id": "REQ-NOTIF-QUIET-DELIVERY",
    "title": "The notify kind rides the active_only window UNCONDITIONALLY, rollback included: spool-only, never live TCP, never a wake, for EVERY producer; loudness = resurface-until-dismissed persistence, never PTY interruption; boundary resurface (clear/compact/new-session/wake) + the adapter safe-point drain are the surfacing paths; a future interrupting alert needs a new kind + its own ADR, not an exception here",
    "doc": "4. Delivery: the notify kind rides `active_only`, unconditionally: <!-- -->"
  },
  "REQ-NOTIF-SEAM-DISMISS": {
    "id": "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": {
    "id": "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-PAIR-1": {
    "id": "REQ-PAIR-1",
    "title": "TOTP-seeded SPAKE2 pairing",
    "doc": ""
  },
  "REQ-PAIR-4": {
    "id": "REQ-PAIR-4",
    "title": "Subnet naming on first pairing",
    "doc": ""
  },
  "REQ-PAIR-7": {
    "id": "REQ-PAIR-7",
    "title": "Subnet icon (inline image metadata, GUI-only consumer)",
    "doc": ""
  },
  "REQ-PAIR-NTP-MULTIHOME": {
    "id": "REQ-PAIR-NTP-MULTIHOME",
    "title": "W1/D1 (JOIN-TRUTH): the ceremony NTP query reaches a server on EITHER IP family \u00e2\u20ac\u201d `query_unix_secs` (ntp.rs) must iterate every address `to_socket_addrs()` resolves (not just the first) and bind a socket of the matching family per candidate (IPv4 addr \u00e2\u2020\u2019 bind 0.0.0.0:0; IPv6 addr \u00e2\u2020\u2019 bind [::]:0), first successful answer wins. ROOT (proven 3/3-FAIL via our exact code on enlyzeam): today `UdpSocket::bind((\"0.0.0.0\",0))` is v4-only and `send_to(&packet, server)` sends ONLY to the FIRST resolved addr \u00e2\u20ac\u201d time.google.com resolves 4\u00c3\u2014AAAA before any A on a v6-first dual-stack box \u00e2\u2020\u2019 the primary server is PERMANENTLY unreachable via our code (w32tm reaches it over v6), silently halving NTP redundancy (pool.ntp.org v4 carried everything; a DNS rotation making BOTH v6-first would zero it). Fix keeps the lazy-cache/TTL/fallback contract of REQ-PAIR-8 unchanged \u00e2\u20ac\u201d only the socket/resolve leg changes.",
    "doc": ""
  },
  "REQ-PICKER-3": {
    "id": "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-5": {
    "id": "REQ-PICKER-5",
    "title": "`spt endpoint list` (bare/subnet view) renders an ALIGNED table with canonical node labels: cmd_endpoint_list prints subnet rows with `\\t` TAB separators (cli.rs:~1651-1662) so variable-width endpoint_ids snap fields to different tab-stops \u00e2\u2020\u2019 a RAGGED status column (operator screenshot: X/help statuses misaligned vs rt-*/sptc-*/wall-a); and it calls the node renderer with no label \u00e2\u2020\u2019 bare key-hex for every row (SAME ResourceRow-drops-node_label root as REQ-PICKER-4). FIX: max-width per-column padding (mirror render_node_rows' pad, pad by char count not byte len \u00e2\u20ac\u201d '\u00e2\u20ac\u00a6' is multibyte) replacing the tabs, and render the node via the shared node_label_display now that ResourceRow carries node_label (REQ-PICKER-4). Extract a pure row-formatter seam so the alignment+label is unit-testable. ALSO: the bare list is the SUBNET view (a just-run LOCAL perch is invisible cross-subnet until the next advertise tick), so emit a `--local` hint line so a freshly-run endpoint isn't perceived as lost. (v0.10.0; operator-flagged + doyle dispatch 2026-06-17)",
    "doc": ""
  },
  "REQ-PICKER-ADAPTER-DESCRIPTION": {
    "id": "REQ-PICKER-ADAPTER-DESCRIPTION",
    "title": "The Create-new adapter-CHOICE screen of `spt endpoint run`'s picker shows a right-hand Description panel (like the Pick-existing endpoint picker's two-pane) surfacing per-adapter detail: install date, last-updated, adapter TYPE / the endpoint types it hosts, and the adapter description \u00e2\u20ac\u201d so the user can see WHAT each adapter is before choosing it (today the selector lists bare names). DEFERRED fast-follow to v0.12.0 (operator 2026-06-18). (post-v0.12.0)",
    "doc": ""
  },
  "REQ-PICKER-CHANGE-ADAPTER-FLOW": {
    "id": "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": {
    "id": "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-KEY-GATE-LAUNCH-CAPABLE": {
    "id": "REQ-PICKER-KEY-GATE-LAUNCH-CAPABLE",
    "title": "B-1 (F029, operator): the `h` (headless start) / `s` (shortcut) keybinds fire from broad picker contexts (mod.rs handle_confirm_key / ChooseProject / Resume) regardless of whether the highlighted row would LAUNCH the endpoint. Restrict both to launch-capable highlights: (a) `Start now` in the immediate-start case (should_offer_project_choice == false), (b) a Choose-project row, (c) a Resume-from-history row. The footer hint line must render `h`/`s` ONLY when actually live (hint truth = availability truth). FIX: gate the key handlers on (screen, highlighted-option), unit the gate as a pure matrix. See triage B-1.",
    "doc": ""
  },
  "REQ-PICKER-ONLINE-ACTION": {
    "id": "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-SHORTCUT": {
    "id": "REQ-PICKER-PURGE-SHORTCUT",
    "title": "C-3 (REMOTE-TRUTH triage \u00c2\u00a7C-3 #8): the pick-existing list gains an `x` purge shortcut \u00e2\u20ac\u201d on an OFFLINE LOCAL highlight, `x` opens a small in-TUI confirm screen (Screen::ConfirmPurge, the B-2 ChangeAdapterPick shape) and Enter purges via the ONE existing purge core `cmd_endpoint_purge(id, yes=true, force=false)` \u00e2\u20ac\u201d NEVER the core's stdin [y/N] (it fights the picker's raw mode; the confirm screen IS the confirm). The shortcut INHERITS both purge gates (offline-only + node-local, cli.rs cmd_endpoint_purge / CONTEXT:189): gated-off presses stay on the list and FLASH WHY (online \u00e2\u2020\u2019 offline-only, remote \u00e2\u2020\u2019 local-only). force=false is deliberate \u00e2\u20ac\u201d the model gate is advisory; the core's own offline check is the authority, and a race to online between gate and purge must REFUSE, never stop-then-purge. After a successful purge the picker STAYS (inline outcome, like Shortcut/ChangeAdapter): the row leaves the in-memory list (remove_endpoint, cursor re-clamped) + flash PURGED:{id}. Hint truth (B-1/F029 discipline): the pick legend renders `x purge` ONLY when purge_key_live() \u00e2\u20ac\u201d the same predicate the handler gates on. Red-first: purge outcome reachable ONLY from an offline LOCAL highlight (online/r",
    "doc": ""
  },
  "REQ-PICKER-RESUME-CONTEXT-PANEL": {
    "id": "REQ-PICKER-RESUME-CONTEXT-PANEL",
    "title": "#6: the 'Resume from history' view keeps the endpoint's 'Confirm selection' top panel and swaps ONLY the bottom panel to 'Resume from a prior session' \u00e2\u20ac\u201d the user stays contextually informed about what they're picking (today the resume view replaces the whole screen). crates/spt/src/picker/view.rs (resume screen) + model screen state. See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #6.",
    "doc": ""
  },
  "REQ-PRES-1": {
    "id": "REQ-PRES-1",
    "title": "Presence resolution: the presence datum (last_active_node, last_active_endpoint, ts) gossiped subnet-wide via the agent-interaction heartbeat (rides registry distribution, visibility-gated) + one first-class most-recently-active resolution API consumed by notif first-fire, update-consent delivery, consent escalation, and shell wake resolution (M5 scope decision 1: resolution only \u00e2\u20ac\u201d the PresenceChannel endpoint stays deferred)",
    "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-PRESENCE-LIVENESS-TRUTH": {
    "id": "REQ-PRESENCE-LIVENESS-TRUTH",
    "title": "A node's gossiped per-endpoint registry Status reflects real liveness, so a remote viewer never paints a DEAD endpoint as ONLINE. ROOT (confirmed + gated vs CONTEXT.md): registryhost.rs:397-405 advertises a NOT-alive perch as Status::Dormant (the `else` of is_perch_alive), re-stamped every gossip round (never ages to Offline). Design intent GATED vs resting.rs + CONTEXT.md: active/dormant is the MULTI-INSTANCE routing differentiator (active = the bare-id routing target; dormant = a WARM non-target sibling \u00e2\u20ac\u201d 'driving ling@laptop makes ling@desktop dormant', resting.rs:97; transitions active\u00e2\u2020\u2019dormant on AttentionShift/Detach). suspended = COLD (session closed, resumable-on-wake) while its NODE is UP. offline = NODE DOWN \u00e2\u20ac\u201d NEVER self-gossiped (RestState has no Offline; a live node only ever gossips active/dormant/suspended), remote-inferred via epoch-lease eviction. So labeling a NOT-running perch Dormant is the DEFECT (dormant requires warm/running). PRIMARY FIX (registryhost `else`, not-bound-alive): live-but-UNBOUND (has a live broker session; is_perch_alive is bound-gated) \u00e2\u2020\u2019 Active/Dormant (still warm); else (cold, no live session, but its node is up because this very daemon is gos",
    "doc": ""
  },
  "REQ-PROJECT-INDEX-INVALIDATION": {
    "id": "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-CONTEXT-FILE-INDIRECTION": {
    "id": "REQ-PSYCHE-CONTEXT-FILE-INDIRECTION",
    "title": "W4 (F030; doyle Q2 ruling + perri file-always freeze, 2026-07-04): the composed psyche mind ({psyche_context}) rides the SHIM argv today \u00e2\u20ac\u201d a real ~20KB doyle psyche-download exceeds the win32 CreateProcess lpCommandLine ~32k cap \u00e2\u2020\u2019 the shim spawn BRICKS. FIX (file-always, replaces {psyche_context} outright \u00e2\u20ac\u201d no size-branch, no argv cliff, one path): core writes the mind to a file in the psyche's NESTED perch dir BEFORE each turn spawn and fills a single {psyche_context_file} = that PATH (argv-cap-immune). The soft fresh/continue discriminator moves from KEY-presence to FILE-CONTENT: FreshWithPreload writes the composed mind NON-EMPTY (the <fresh-psyche/> never-empty guarantee carries to the file content); ContinueExisting writes it TRULY 0-BYTE (perri BINDING PIN 1 \u00e2\u20ac\u201d NO sentinel/placeholder EVER, else her non-empty=fresh discriminator misfires a spurious --session-id adopt). Core owns the file lifecycle: write-before-spawn each turn, overwrite in place, persists between turns in the nested perch (debuggability); never deleted per turn. perri shim delta: --psyche-context-file <path> arg, read-file prefix, TRIM-based emptiness (her tolerance, NOT core's license \u00e2\u20ac\u201d core writes exactly 0",
    "doc": "then it exits \u00e2\u20ac\u201d no resident process, no detach.: <!-- --> **Psyche-download \u00e2\u20ac\u201d `{psyche_context_file}` (file-always, replaces `{psyche_context}`).** The composed Psyche mind rides a **file**, never the command argv: before each turn spt-core writes the mind into the nested psyche perch dir and fills a single **`{psyche_context_file}` = that path** (argv-cap-immune \u00e2\u20ac\u201d a real ~20 KB mind exceeds the win32 command-line cap and would brick the spawn). The soft **fresh-vs-continue** discriminator is the file's **content**, not key presence: a **fresh** (first / reseeded) turn writes the composed mind"
  },
  "REQ-PSYCHE-LEGACY-RESIDENT-SWEEP": {
    "id": "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-PUBLIC-ERROR-SURFACES": {
    "id": "REQ-PUBLIC-ERROR-SURFACES",
    "title": "F-1 (REMOTE-TRUTH triage \u00c2\u00a7F-1, Q4 UX rule, operator-ruled): CLI stderr a non-developer can hit names the OBSERVABLE SITUATION + the NEXT ACTION \u00e2\u20ac\u201d never journal/op/brain/store lingo. The sweep's named offenders: (1) `RC_FAIL:{id}: \u00e2\u20ac\u00a6 brain IPC read deadline elapsed` \u00e2\u20ac\u201d the brain transport error surfaced RAW through rc's residual Err arm (rc.rs run_attach_inner); operators read 'brain IPC' where the situation is 'the daemon didn't answer in time'. (2) `WOKE_FAIL:{id}: info.json absent or unreadable \u00e2\u20ac\u201d not a hosted perch` (resting.rs apply_event miss) \u00e2\u20ac\u201d store-file lingo in the one rest-verb line a stale remote row still surfaces cross-node (the qualified-arm D6 case; the A-3 bare-id local path already routes instead). The miss stays SINGLE-SOURCED from NOT_A_HOSTED_PERCH_MARKER (in-process discriminant, resting.rs \u00e2\u20ac\u201d reword is compat-safe per its own doc; the drift-pin unit keeps builder+matcher fused). (3) translation_fault never human-rendered (F-030 post-release seed): a broker-stamped input-translation fault (e.g. 'inject worker panicked') was invisible in `endpoint list`/`whoami` while keystrokes silently degraded \u00e2\u20ac\u201d rendered now as a SELF-pin annotation exactly like the psyche_host_e",
    "doc": ""
  },
  "REQ-PUMP-DIAL-FASTFAIL": {
    "id": "REQ-PUMP-DIAL-FASTFAIL",
    "title": "PUMP-TRUTH W1 (RE-SCOPED post round-2 empirical lock \u00e2\u20ac\u201d the DIAL is EXONERATED, healthy ~100ms): a pump worker-leg PEER-REPLY read to a connect-then-silent / half-alive peer must drop THAT peer as an ORDINARY per-peer failure (peer_outcome's non-TimedOut arm -> PUMP_PEER_FAIL -> drop conn + redial, round CONTINUES, heartbeat advances), NEVER burn the brain's 30s PUMP_PEER_IO_TIMEOUT carrier deadline into a whole-round TimedOut POISON -> supervise_pump doubling-backoff restart. ROOT (deployah leg-instrumented capture, enlyzeam, 3 identical rounds): DIAL_EXIT 96ms ok, LEG i=3 update ms=30025 err[TimedOut] = the wedge. request_update (propagate.rs:373-375) opens the update stream + sends UpdRecord::Query (all bounded, all land), then BLOCKS read_event_until(deadline=call_deadline()=30s) on the peer's Offer/UpToDate reply; a peer that accepts the stream but never answers burns the full 30s -> TimedOut -> peer_outcome (pump/mod.rs:601) POISON -> whole-round abort + restart (= the field PEER_PUMP_FAIL: brain IPC read deadline, always-zero PUMP_PEER_FAIL). request_sync (sync.rs:374-376) is the LATENT TWIN (SKIPS the reply-read only when the want-set is empty; bites the moment it is non-emp",
    "doc": ""
  },
  "REQ-PUMP-PEER-ISOLATION": {
    "id": "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": {
    "id": "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": {
    "id": "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-DISPLAY-SOLE-WRITER": {
    "id": "REQ-RC-DISPLAY-SOLE-WRITER",
    "title": "TEARDOWN-AUTHORITY W5 (hertz RCA 3 bug 1, 2026-07-19 \u00e2\u20ac\u201d REPRODUCED on 0.38.1 CLI + 0.38.1 broker; doyle ruled, and it CORRECTS an incomplete v0.38.1 call of doyle's own): while `spt rc` owns the terminal (raw / alternate screen), rc is the SOLE writer to that display \u00e2\u20ac\u201d no background thread may write to the inherited stderr. TODAY: BrainConn::split_with_reader (spt-daemon/src/brain.rs:253-270) unconditionally eprintln!s `PUMP_IPC_READER: spawned` / `exited` from its reader thread. Every reconnect attempt calls establish_attach -> Brain::cold_start_pump (rc.rs:1700-1712), and reconnect_banner_bytes (rc.rs:2040-2054) deliberately clears/homes and leaves the cursor immediately after the countdown with NO trailing newline \u00e2\u20ac\u201d so the marker lands exactly at that cursor and the operator sees `Reconnecting to local daemon... 9sPUMP_IPC_READER: spawned`. Field screenshot confirms it also reaches the harness alt screen on a real `endpoint run` + `rc --take`. WHY THIS IS A CORRECTION, ON THE RECORD: v0.38.1's REQ-RC-SINGLE-PUMP-BRAIN removed the DOUBLE construction and doyle ruled the surviving single banner 'truthful, keep it' and classified the change Internal (diagnostic hygiene, not user-fac",
    "doc": ""
  },
  "REQ-RC-WIN-PASTE": {
    "id": "REQ-RC-WIN-PASTE",
    "title": "In an `spt rc` session neither ctrl+V nor right-click pastes (CC explicitly supports ctrl+V). ROOT (doyle /diagnose): RawGuard does only enable_raw_mode (no bracketed paste / no mouse capture / no clipboard interception); the Windows console delivers a paste as synthetic per-char KEY EVENTs (no crossterm Event::Paste), and ctrl+V translates to bare ^V forwarded to CC \u00e2\u20ac\u201d but CC runs DAEMON-SIDE with NO access to the operator's LOCAL clipboard, so remote paste is fundamentally CLIENT-ORIGINATED. A multi-line paste-as-keys also becomes a \\r submit-storm. FIX (doyle design, V0.13.0-P1-RC-PASTE-DESIGN.md, cfg(windows), folds into the bug-2 event path): on a paste gesture rc reads the LOCAL clipboard + forwards a BRACKETED PASTE (ESC[200~ + content + ESC[201~); CC has bracketed-paste mode on (its TUI sets ESC[?2004h) so it treats it as a paste \u00e2\u20ac\u201d content intact, no submit-storm, harness-AGNOSTIC. ctrl+V: intercept Char('v')+CONTROL in the event loop -> read_clipboard -> bracketed paste. Right-click: RawGuard also EnableMouseCapture (disables console QuickEdit + enables ENABLE_MOUSE_INPUT so right-click surfaces as Event::Mouse on legacy cmd/powershell) -> right-button -> read_clipboard -> ",
    "doc": "7.16 `spt rc` translates Windows console KEY EVENTS to standard xterm VT (arrows/Home/End/F-keys reach the harness); supersedes the W7 byte-swap `[REQ-RC-KEY-VT-TRANSLATE]`: <!-- --> ### 7.18 `spt rc` paste is client-originated on Windows \u00e2\u20ac\u201d read the LOCAL clipboard, inject a BRACKETED paste `[REQ-RC-WIN-PASTE]` - **Failure (operator dogfooding):** in an `spt rc` session neither ctrl+V nor right-click pasted (CC explicitly supports ctrl+V). `RawGuard` did only `enable_raw_mode` (no bracketed paste, no mouse capture, no clipboard interception); the Windows console delivers a paste as synthetic p"
  },
  "REQ-READY-AGENT-RESUME": {
    "id": "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-REL-2": {
    "id": "REQ-REL-2",
    "title": "Release asset set consumable by the self-updater: platform binaries, SHA256SUMS, SignedRelease metadata, manifest schema, mock-adapter zip; tag-triggered cross-repo pipeline",
    "doc": "Release runbook: <!-- -->"
  },
  "REQ-RELEASE-CHANNEL-PRIVATE": {
    "id": "REQ-RELEASE-CHANNEL-PRIVATE",
    "title": "THE-FORKENING W2 (ADR-0036 \u00c2\u00a72): the publish pipeline targets `BigscreenVR/spt-bs-releases` \u00e2\u20ac\u201d release.yml assemble/draft/flip retargeted (draft lands on the bs releases repo, untagged, per the existing spt-releases pattern); docs-publish.yml RETIRED (no public docs, ADR-0014 superseded) with the mdbook build folded into ci.yml as the drift gate (CLAUDE.md mandate: doc generation stays CI-gated); counter + signing key + update-set format CONTINUE unchanged (trust anchor is the continuity, carrier is not). Gate: impl \u00e2\u20ac\u201d workflow retarget + drift-gate fold; int \u00e2\u20ac\u201d a full release dry-run assembles binaries + docs asset + signed update-set against the private channel. Kin REQ-DOCS-RELEASE-ASSET, REQ-UPDATE-GH-TRANSPORT, ADR-0036.",
    "doc": ""
  },
  "REQ-RESIZE-INPUT-MODE-INTEGRITY": {
    "id": "REQ-RESIZE-INPUT-MODE-INTEGRITY",
    "title": "RESCOPED 2026-07-22 by ADR-0047 Amendment 2 + KNOWN-HAZARDS 7.55 (doyle-ratified), on the measurement rather than on the supposition. FIELD EVIDENCE IS REAL: hertz's second ENLYZEAM capture shows the operator's keystrokes as isolated one-byte OUTPUT records in the child->broker RAW DRAIN \u00e2\u20ac\u201d server-side, upstream of any client console, so independent of the 7.56 presentation root that explains the other half of the /c/config line. LOCAL SEAM MEASURES CLEAN: a probe child under a real ConPTY, clamped raw at startup and resized 24x80->60x131 live, reports an IDENTICAL input mode word at four sample points (boot / before-resize / after-resize / before-write, all in=0x000001f0 echo=0 line=0), and typed bytes do not come back as output; a seeded capability probe moves BOTH observables, so the absence is earned, not vacuous. Amendment 1's addendum supposed the resize path re-enables console echo \u00e2\u20ac\u201d on this box and portable_pty/ConPTY version it does NOT. TRIGGER UNPINNED. OPEN CANDIDATE (hertz field question, deliberately NOT built against): the pseudoconsole BOOTS with ENABLE_ECHO_INPUT/ENABLE_LINE_INPUT ON (in=0x000001f7 measured) and the CHILD clamps them off, so any UNCLAMPED WINDOW (st",
    "doc": "7.55 A surface resize never alters the hosted terminal's input discipline \u00e2\u20ac\u201d input bytes must never come back as output `[REQ-RESIZE-INPUT-MODE-INTEGRITY]`: <!-- --> - **Failure (paid-for, second ENLYZEAM production capture 2026-07-22 \u00e2\u20ac\u201d operator resized a LIVE established rc viewport; hertz froze the taps):** after the resize, the raw ConPTY drain emits the operator's keystrokes as isolated OUTPUT records \u00e2\u20ac\u201d literal one-byte `c`/`o`/`n`/`f`/`i` frames interleaved with the TUI's cursor-addressed menu diffs, raw and broker taps byte-identical. The hosted Claude TUI runs raw/no-echo, so the hosted"
  },
  "REQ-REST-TERMINAL-NORMALIZE": {
    "id": "REQ-REST-TERMINAL-NORMALIZE",
    "title": "REGISTRY-LIFECYCLE W2 (ADR-0041 decision 3, rest-normalize P0): definitive hosted-session loss normalizes TERMINALLY and ATOMICALLY \u00e2\u20ac\u201d one store-level mutation writes status=offline + rest_state=suspended + clears dormant_since_ms in the SAME info.json write, invoked at authoritative broker-session-loss/liveness-reap + cmd_stop. NOT daemon_rest_event(Suspend) (no edge when effective already Suspended \u00e2\u20ac\u201d raw Active intent survives); NOT reader-side blanket offline-implies-suspended (destroys explicit-Wake semantics: wake writes intent first, reconcile consumes). Graceful shutdown keeps echo-before-teardown. Kills the zombie WAKE_RESUME loop (perri field: repeated cross-generation resumes of a dead session). Gate: impl \u00e2\u20ac\u201d atomic terminal-normalize mutation + call sites; unit \u00e2\u20ac\u201d store-level atomic pair never mixed + endpoint_stop covers already-offline/raw-Active input; int \u00e2\u20ac\u201d session vanish means offline+suspended and the NEXT reconcile emits NO WAKE_RESUME, explicit suspended/offline->Wake->Active launches EXACTLY once, RefuseLivePid-then-valid-bind custody race leaves the revived seat unsuspended; doc \u00e2\u20ac\u201d ADR-0041.",
    "doc": "Decision: <!-- --> 1. **Online is earned, not declared.** A creator may stamp `status=online` only from actual persisted state + hosting authority \u00e2\u20ac\u201d never from manifest capability alone. Legacy hybrid rows self-heal at reconcile, but only after a SUCCESSFUL broker query: a broker failure is never interpreted as an empty session set (no mass-offline on a hiccup). 2. **Control cleanup splits from offline classification.** Reconcile clears `controlled`/`driven_by`/`viewer_count` for EVERY endpoint absent from session truth \u00e2\u20ac\u201d regardless of state or controllability \u00e2\u20ac\u201d while offline classification ke"
  },
  "REQ-RESUME-REAP-PRIOR-HARNESS": {
    "id": "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": {
    "id": "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": {
    "id": "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-MULTISUBNET-HOME": {
    "id": "REQ-RUN-MULTISUBNET-HOME",
    "title": "`spt endpoint run` resolves the home subnet at the skeleton-create step and pre-creates the skeleton perch carrying it, so the harness `bind` inherits home via establish_perch's immutable prior-branch (no hook change, no env injection). Resolution: sole-subnet auto; multi-subnet + no --subnet + NON-interactive terminal -> refuse early with MRU-ordered --subnet guidance (never the silent 25s online-timeout); multi-subnet + no --subnet + INTERACTIVE -> print proposed config (id/project/adapter[:profile]/home=MRU-default) + 'Ok to proceed? Y/n', n -> --subnet guidance; --subnet overrides + validates membership. MRU = ordered move-to-front LISTs at two levels (per-project + always-updated node-global fallback). Home stays IMMUTABLE (ADR-0010). Fixes the LATENT multi-subnet bringup gap (perri, not a regression \u00e2\u20ac\u201d HOME_REFUSED established >=0.11.0; exposed by the node crossing 1->2 subnets). (ADR-0026)",
    "doc": "Multi-subnet home resolution at `endpoint run` creation: <!-- -->"
  },
  "REQ-RUN-NO-DUP-SESSION": {
    "id": "REQ-RUN-NO-DUP-SESSION",
    "title": "B1 (F028, hall-b diagnosis, verified 0.22.0): `endpoint run --id X --create` on an endpoint with a LIVE session mints a silent DUPLICATE session \u00e2\u20ac\u201d and attach output can CROSS sessions (second create for diag-hallc minted a new session while the old ran; the new run's attach viewport rendered the OLD session's screen \u00e2\u20ac\u201d claude resume-picker UI of pid 84512 while new claude 356020 had no -r). ROOT CLASS of the 0.21.0 attach-stall (zero events in FIRST_EVENT_GRACE rc.rs:1402 = attach bound to dead/wrong same-id slot); also the triplicate `launch --id ball-b` on ENLYZEAM. FIX: (i) run-on-live-session must REFUSE or REATTACH, never silently duplicate; (ii) RCA the attach/output routing that let frames cross same-id sessions (broker session-slot keying, dispatch_adapter vs serve_attach resolution). Int: two sessions one endpoint id -> each attach sees only its own frames. See triage B1.",
    "doc": ""
  },
  "REQ-RUN-PICKER-HOME": {
    "id": "REQ-RUN-PICKER-HOME",
    "title": "Home-subnet selection LAYER in the `spt endpoint run` ratatui Create-new picker (v0.14.1; the deferred half of REQ-RUN-MULTISUBNET-HOME's interactive path \u00e2\u20ac\u201d ADR-0026 \u00c2\u00a73 'the interactive picker lists subnets MRU-ordered'). On a MULTI-SUBNET node the Create-new flow gains a `CreateHome` screen (CreateAdapter \u00e2\u2020\u2019 CreateId \u00e2\u2020\u2019 CreateHome \u00e2\u2020\u2019 Confirm) that lists the node's MEMBER subnets MRU-ordered (reusing recent_home::mru_preference + order_by_mru), default cursor = MRU head; the chosen subnet rides Outcome::Run{subnet} into cmd_endpoint_run's --subnet, so decide_run_home resolves Home directly and the post-TUI `Ok to proceed? Y/n` confirm NEVER fires for the picker path. Single-subnet / local-only nodes SKIP the layer (assign_home auto-homes; CreateId \u00e2\u2020\u2019 Confirm unchanged). The CLI / flagged `endpoint run` path KEEPS the decide_run_home Y/n confirm + the non-interactive MULTI_SUBNET_HOME refuse (operator: the confirm stays useful for CLI-only bringup, just not in the TUI). Esc backs CreateHome \u00e2\u2020\u2019 CreateId; Enter selects \u00e2\u2020\u2019 Confirm. Pure front-end invariant preserved: the layer only collects --subnet, routes through the one bringup core.",
    "doc": "Shell sleep/wake (offline \u00e2\u2020\u201d online): **spt-hosted bringup picker (`spt endpoint run`)** (M12-W2): <!-- --> The user-facing bringup flow for spt-hosted endpoints. **Bare `spt endpoint run`** (no `--adapter`/`--id`) opens an in-process **ratatui picker**; the **flagged** form is the non-interactive bringup path (`--adapter <a[:profile]> --id <id> --create|--resume <session> --start|--attach|--view`), untouched \u00e2\u20ac\u201d a picker selection bakes exactly that path. **Layer 1** picks the kind (*Create new* | *Pick existing*). **Create-new** chooses a registered `kind=\"harness\"` adapter with its shipped+loc"
  },
  "REQ-SEAM-ACTIVITY": {
    "id": "REQ-SEAM-ACTIVITY",
    "title": "Activity/idle reported via api sentinels, not PTY quiescence",
    "doc": ""
  },
  "REQ-SEAM-CAPABILITY": {
    "id": "REQ-SEAM-CAPABILITY",
    "title": "Hostable endpoint-types capability declaration",
    "doc": ""
  },
  "REQ-SEAM-POSTSPAWN": {
    "id": "REQ-SEAM-POSTSPAWN",
    "title": "post-spawn / api bind seam with boot nonce",
    "doc": ""
  },
  "REQ-SEAM-RESUME": {
    "id": "REQ-SEAM-RESUME",
    "title": "resume-session seam (fresh-with-preload / continue-existing)",
    "doc": ""
  },
  "REQ-SEAM-SPAWN": {
    "id": "REQ-SEAM-SPAWN",
    "title": "spawn-session seam",
    "doc": ""
  },
  "REQ-SEAM-UPDATE": {
    "id": "REQ-SEAM-UPDATE",
    "title": "Adapter-update avenue (file-pull / delegated command)",
    "doc": ""
  },
  "REQ-SELF-ID-TRUST-INJECTED-ENV": {
    "id": "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-WINDOW-DRAIN-HONOR": {
    "id": "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-SERVE-OWNERSHIP-GENERATION": {
    "id": "REQ-SERVE-OWNERSHIP-GENERATION",
    "title": "REDISPATCH-STALL W1 (ADR-0038 Amendment, fix 6): terminal-exclusion enforced PRE-SERVE + ownership/generation validation on attach/detach \u00e2\u20ac\u201d a stale worker can never detach or displace a REPLACEMENT controller (today detach_if compares Arc ptr identity only; the serve path re-checks nothing at completion). Covers the UNFINISHED-stale-row control-steal shape (raw-close no-FIN viewports, emphasys C2 leak class feeding it) that finished-row retirement (D1/D1b) definitionally cannot see \u00e2\u20ac\u201d the discriminating field observable on the next live steal catch = the stolen row's finished+retired flags. Gate: impl \u00e2\u20ac\u201d pre-serve terminal exclusion + generation/ownership tokens on attach/detach; unit \u00e2\u20ac\u201d stale-generation detach refused while the same-generation detach lands; int \u00e2\u20ac\u201d T6 (UNFINISHED-stale attach row + live current controller + dispatcher restart: neither takes nor clears the replacement, D1/D1b green alongside); doc \u00e2\u20ac\u201d ADR-0038 Amendment. Kin REQ-HAZARD-REDISPATCH-CONTROL-STEAL (the finished sibling), REQ-REDISPATCH-FINISHED-RETIRE.",
    "doc": "Consequences: ## Amendment \u00e2\u20ac\u201d REDISPATCH-STALL (2026-07-16) <!-- --> <!-- --> <!-- --> <!-- -->"
  },
  "REQ-SESSION-ADAPTER-RECORDED": {
    "id": "REQ-SESSION-ADAPTER-RECORDED",
    "title": "D-2 (REMOTE-TRUTH triage \u00c2\u00a7D-2 + operator Q5 @c248afc): the session ledger records the adapter[:profile] a session ran under, so a later resume can restore the harness the session actually used (not merely the endpoint's CURRENT stamp). ROOT: SessionEntry (spt-store/sessions.rs:58) carries ts/session_id/trigger/cwd/ordinal but NOT the adapter \u00e2\u20ac\u201d a resume-from-history row cannot know which harness authored the transcript, so a resume under a since-changed endpoint adapter (B-2 ChangeAdapter, or a fork) launches the wrong harness. FIX: an ADDITIVE `adapter: Option<String>` on SessionEntry, exact cwd/ordinal serde pattern (#[serde(default, skip_serializing_if=\"Option::is_none\")]) \u00e2\u20ac\u201d a pre-migration row missing the key deserializes None; None omits the key on serialize (byte-identical to old rows); an unknown key on an old reader is ignored (serde default) \u00e2\u20ac\u201d back-compat BOTH directions. Stamped at every PRODUCTION session-boundary append. CENSUS (doyle-confirmed @94f0205, corrects the triage-era 5-site drift to the real 3): startup.rs:317 (live bind boot row, rec.adapter in scope), reporting.rs:94 (boundary rotation row UNDER the mutate_info lock, capture adapter_for_ledger=rec.adapter be",
    "doc": ""
  },
  "REQ-SESSIONS-LOG-ENDPOINT-ATTRIBUTION": {
    "id": "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-ADAPTER-OWNED-DETACHED-SERVICE": {
    "id": "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-SOFT-END-PRESERVES-LIVE-LISTENER": {
    "id": "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-SPAWN-COLLISION-GUARD-LIVE-DUP": {
    "id": "REQ-SPAWN-COLLISION-GUARD-LIVE-DUP",
    "title": "W4 (LIFECYCLE-TRUTH): single-flight wake per endpoint \u00e2\u20ac\u201d the WAKE/RESUME respawn seam must not launch twice for one wake. ROOT (perri parentage + recovered filing): one wake processed TWICE within 1s \u00e2\u20ac\u201d broker (306368) spawned two identical `launch --cli ccs --id flynn --resume <sid>` 1s apart, both survived; check-then-spawn TOCTOU in the spawn-side guard. DAMAGE: duplicate-perch writers STOMP info.json (the duplicate's compact re-stamped an OLD sid over a fresh /clear rotation -> injects routed to the contended record and lost). FIX: single-flight wake per endpoint (claim on the perch record or broker-side in-flight set keyed by id; second wake within the window = no-op ack), and the spawn path re-checks liveness UNDER the claim. Int: two concurrent wake requests -> exactly one launch tree.",
    "doc": ""
  },
  "REQ-START-1": {
    "id": "REQ-START-1",
    "title": "Adapters never resolve SPT_HOME; binary on PATH; api bridging only",
    "doc": ""
  },
  "REQ-START-2": {
    "id": "REQ-START-2",
    "title": "Harness-hosted startup: api seed then listen",
    "doc": ""
  },
  "REQ-START-3": {
    "id": "REQ-START-3",
    "title": "spt-hosted startup: spawn-session then api bind (no file)",
    "doc": ""
  },
  "REQ-STREAM-INFOS-SERVER-FILTER": {
    "id": "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-LIFETIME-CLASS": {
    "id": "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-1": {
    "id": "REQ-SUBNET-1",
    "title": "spt subnet noun namespace: status view (bare + status [NAME] [--nodes]), create (QR/otpauth), show-code; spt pair deleted",
    "doc": ""
  },
  "REQ-SUBNET-7": {
    "id": "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": {
    "id": "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-SUBNET-COUNT-ROUTABLE": {
    "id": "REQ-SUBNET-COUNT-ROUTABLE",
    "title": "Bug #2: a remote node endpoint count drifts (0/2, 1/3) because node_status_rows (cli.rs:5314) increments the per-node total unconditionally, counting non-routable Offline ghost rows; purge is not a registry eviction (it gossips a one-shot Offline row that is immortal on remote viewers \u00e2\u20ac\u201d eviction is per whole-node only). Fix: routable-only denominator (total += status.routable()) keeping a separate raw count for the all-Offline liveness branch; plus per-row Offline-TTL eviction so purged endpoints stop accumulating on remote snapshots. See docs/NEXT-MILESTONE-BUG-TRIAGE.md #2.",
    "doc": ""
  },
  "REQ-SUBNET-DISPLAY-PARITY": {
    "id": "REQ-SUBNET-DISPLAY-PARITY",
    "title": "The `spt endpoint run` picker renders endpoint state IDENTICALLY for local and remote (subnet) rows \u00e2\u20ac\u201d bound/unbound, controlled (+driver node), and harness-only are all visible across the subnet, not local-only. Today data.rs:293-294 reduces a remote row to plain green-filled/gray-hollow because those facts aren't propagated. (1) GOSSIP additive per-Instance fields: bound/unbound, controlled + driver-node, harness_only (Offline is never gossiped \u00e2\u20ac\u201d node-down is remote-inferred). (2) Derive the full EpDisplay for subnet rows from the gossiped fields, same path as local (remove the remote-reduction). (3) PALETTE rework (fill = ACTIONABLE: filled=can act now (rc-control if online, WAKE if suspended-on-live-node) / hollow=cannot (no control seat / node gone)): green-filled=online+bound+free; blue-filled=online+controlled (desc shows `controlled by <node>`); RED-filled=online+UNBOUND (controlled-or-not; controlled-ness shown via available options not glyph) \u00e2\u20ac\u201d replaces green-hollow Unbound + absorbs the dropped UnboundControlled; AMBER-HOLLOW=online+harness-only (no broker seat \u00e2\u2020\u2019 can't rc) \u00e2\u20ac\u201d was amber-FILLED; GRAY-FILLED=Suspended (cold, node up \u00e2\u20ac\u201d wakeable) NEW; gray-hollow=Offline (node ",
    "doc": ""
  },
  "REQ-TERM-7": {
    "id": "REQ-TERM-7",
    "title": "Two-origin digest merge: spt-owned context-injection entries (psyche_download | echo_mirror | owl_message) appended by spt to the endpoint `digest.log`, timestamp-interleaved with the adapter's extracted activity records into one ordered timeline, via a distinct context-injection record category. Data model only this milestone; GUI collapse/expand and the echo-reads-digest delta loop are deferred to the surfaces that consume them.",
    "doc": ""
  },
  "REQ-UPD-2": {
    "id": "REQ-UPD-2",
    "title": "All binaries signature-verified before handoff",
    "doc": ""
  },
  "REQ-UPD-5": {
    "id": "REQ-UPD-5",
    "title": "spt-core ripple-updates registered adapters",
    "doc": ""
  },
  "REQ-UPD-7": {
    "id": "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": {
    "id": "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": {
    "id": "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-APPLY-RESTART-NOTICE": {
    "id": "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": {
    "id": "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-APPLY-FLAG": {
    "id": "REQ-UPDATE-FETCH-APPLY-FLAG",
    "title": "`spt update fetch --apply` is the one-shot get-to-latest: fetch, then INSTALL the staged update REGARDLESS of whether the fetch itself staged anything new \u00e2\u20ac\u201d so the brittle `fetch && apply` chain (which broke when fetch no-oped / exited nonzero on an already-staged latest, skipping the chained apply) is unnecessary. Composes with REQ-UPDATE-FETCH-CURRENT-UX: the end state is 'installed latest', reached idempotently from new-staged -> apply / already-staged (applied<candidate) -> STILL apply / already-applied -> noop+exit0 / genuine error (bad signature, no artifact for platform, true downgrade, network) -> do NOT apply, propagate the error + nonzero. Reuses the existing cmd_update_apply core (its own verify + two-phase + auto-rollback own correctness; no duplicated swap/respawn). Additive clap flag (plain doc-comment, no internal codes); reference.md regenerated. (v0.18.0)",
    "doc": ""
  },
  "REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL": {
    "id": "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-ONE-SHOT-FINISH": {
    "id": "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-VIEWER-SKIP-TO-LIVE-ON-EVICT": {
    "id": "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-RESUME-LEG": {
    "id": "REQ-WAKE-RESUME-LEG",
    "title": "A-2 (REMOTE-TRUTH triage \u00c2\u00a7A-2 + ADR-0033): the daemon reconcile gains a WAKE-RESUME LEG \u00e2\u20ac\u201d an endpoint whose rest INTENT is Active but whose harness session is COLD (status != online) is resumed by the daemon via the adapter's [session.resume] template using the LAST LEDGER session id, so a bare `spt wake <id>` on a suspended live agent actually brings it back (today: reconcile_once start-arm hosts ONLY status==online (livehost.rs:199), so a woken-but-unbound endpoint is skipped forever \u00e2\u20ac\u201d neither status reaches online nor does reconcile re-host). This is the ADR-0033 LIFT: the thin `spt wake` edge writes rest intent, the DAEMON does the work. Mirrors shellwake::resolve_wake (read rest state, live-pid double-launch guard, launch, NEVER flip status \u00e2\u20ac\u201d the harness self-binds \u00e2\u2020\u2019 online). The leg reads the recorded adapter (D-2, REQ-SESSION-ADAPTER-RECORDED); an UNREGISTERED recorded adapter is the Q5 daemon-variant refuse: do NOT spawn, record a LOUD host_error report (F-1 naming the adapter + `spt adapter add`), never silent, never fallback-spawn on a different adapter. BINDS: (1) status=online is set ONLY by a real bind \u00e2\u20ac\u201d the resume leg NEVER stamps it (CONTEXT liveness truth; the A-1 e",
    "doc": ""
  },
  "REQ-WORKER-MINTED-NAME": {
    "id": "REQ-WORKER-MINTED-NAME",
    "title": "N-1 (WORKER-TRUTH triage, operator rider): worker perch identity is CORE-MINTED and parent-derived \u00e2\u20ac\u201d `{parent}-w{N}` with a per-parent counter at registration (sister shape: claude_skill_owl hook_subagent_start.rs) \u00e2\u20ac\u201d never the adapter-presented agent id (CC Task ids render as random-named rows). worker-start mints + echoes the id (WORKER_STARTED:{parent}-w{N}); the adapter's agent_id/agent_type ride the record as correlation METADATA, not identity. Verb-shape contract change \u00e2\u20ac\u201d freeze with W-2 in ONE coordination with perri.",
    "doc": "Workers: <!-- --> ### `api worker-start <parent> [--agent-id <id>] [--agent-type <type>]`"
  },
  "REQ-WORKER-REAP": {
    "id": "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": ""
  }
}