{
  "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-RC-RESIZE-PRESENTATION-BARRIER": {
    "title": "During a resize transition, LIVE DELIVERY is barriered along with the grid parse: no attached sink (controller or viewer) ever receives mixed-geometry raw bytes; commit substitutes ONE synthesized sync frame at the target geometry. (hertz post-fix field RCA 2026-07-21 on a fully-0.39.3 node, doyle-accepted with every cite verified at source; SUCCEEDS REQ-RC-RESIZE-GEOMETRY-EPOCH, which shipped correct but scoped to the wrong surface \u00e2\u20ac\u201d broker.rs's own ResizeTransition doc states append 'still rings + fans out every chunk exactly as before' during a transition, and OutputLog::append gates ONLY grid.advance. A live attached terminal therefore receives old+new-geometry differentials across an already-resized viewport; the cold-attach repaint model is definitionally blind to that path, which is why the shipped gate was green while the field was red. REQUIREMENT-SCOPE defect: the gate held against documented design.) CONTRACT (ADR-0031 Amendment II): (1) append still assigns seq and rings every chunk, but controller handoff and viewer fan-out are SUPPRESSED during the transition \u00e2\u20ac\u201d a skip, never a block or sleep (KH 7.12 intact), and the suppressed window does not count toward the control",
    "doc": "<!-- the presentation barrier over live delivery: during a resize transition the ring keeps recording but controller and viewer delivery is suppressed; commit lands the grid at the target geometry and pushes ONE synthesized sync frame (repaint ++ deferred non-grid bytes) to every attached sink under the same OutputLog serialization before raw fan-out resumes; the cursor-of-record advances past suppressed frames as-if-written; resume never raw-replays across a transition boundary. -->"
  },
  "REQ-INST-5": {
    "title": "Two-tier context sync (live to all, project to same-project)",
    "doc": ""
  },
  "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-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-RUN-EMPTY-CREATE": {
    "title": "`spt endpoint run` / bare `spt` routes a TOTALLY-EMPTY scope straight to the endpoint-creation flow: when gather_endpoints() is empty (nothing attachable, local OR subnet) PickerModel::new opens on Screen::CreateAdapter instead of PickExisting (today it always opens PickExisting + renders a blank list). A node WITH subnet endpoints but no local ones still has things to pick \u00e2\u2020\u2019 stays on the picker. Extends REQ-RUN-PICKER. (v0.16.0)",
    "doc": ""
  },
  "REQ-UPDATE-FINISH-COMMUNE-FLUSH": {
    "title": "DEFERRED (post-LIFECYCLE-TRUTH, operator-ruled 2026-07-07 \u00e2\u20ac\u201d mint now, impl a FUTURE milestone): make the update swap LOSSLESS for live hosted endpoints by flushing a final echo-commune per endpoint BEFORE the brain-subtree reap. ROOT (operator-surfaced probing --finish): `update apply --finish` = daemonless swap -> daemon RESTART; the graceful `daemon stop` path (daemon.rs:316-325) raises brain_stop then reaper.reap() KILLS the brain subtree (brain + shellwake watchers + detached Psyches) as one unit \u00e2\u20ac\u201d there is NO per-endpoint final commune before the kill. ENDPOINT-SURVIVAL (REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL) then RESPAWNS each orphaned online spt-hosted endpoint, but from its LAST commune (whatever the ongoing per-event echo-commune cadence last saved), NOT an as-of-swap checkpoint \u00e2\u20ac\u201d so mid-turn / uncommuned work is lost across the bounce. Today's mitigation is operator discipline: commune-before-swap. FIX (future): the stop/finish path, before reap, drives each LIVE hosted endpoint's final echo-commune (fire_echo final context save) so the respawn resumes from a swap-fresh checkpoint. Composes with ENDPOINT-SURVIVAL (commune -> reap -> respawn) and the W1 echo pipeline (REQ-EC",
    "doc": ""
  },
  "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-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-PSYCHE-STAMP-CLEAR-ANY-SUCCESS": {
    "title": "W1 (LIFECYCLE-TRUTH): EVERY successful psyche operation clears psyche_host_error \u00e2\u20ac\u201d not just the pulse-loop leg. ROOT (three field confirmations, perri): the stamp clears only via note_turn_outcome's Ok leg (lifecycle.rs:1101); a SUCCESSFUL psyche op via checkpoint/wake bypasses it -> stale FAILED stamp sits over a healthy psyche. FIX: event turn, checkpoint/wake synthesis, and signoff echo all clear the stamp on success.",
    "doc": ""
  },
  "REQ-HAZARD-ENVELOPE-CR-LINESAFE": {
    "title": "Envelope CR-linesafety (4.1): the line-framed EVENT codec must neutralize raw carriage returns \u00e2\u20ac\u201d `event_body_escape` folds CRLF/lone-CR to the codec's representable linebreak (`\\n`\u00e2\u2020\u2019`<br>`) BEFORE framing, so a body carrying `\\r` (Windows `echo`/CRLF text crossing nodes) cannot survive into the single-line envelope and trigger a receiver terminal CR\u00e2\u2020\u2019col0 overwrite that corrupts the frame. Robustness on unrepresentable input, NOT a wire-format change (decoder untouched, amp-last invariant held). Belt-and-suspenders: `spt send`/`ring` also trim stdin (parity with `notify`).",
    "doc": "Body and attribute encoding: <!-- --> **Carriage returns are unrepresentable.** The encoder normalizes `\\r\\n` and lone `\\r` to `\\n` *before* the `<br>` encoding, so no frame ever carries a raw CR and a decoder always receives `\\n` newlines. Do not expect `\\r` to round-trip \u00e2\u20ac\u201d content that needs CRs preserved does not fit this codec."
  },
  "REQ-PICKER-CURRENT-DIR-LABEL": {
    "title": "A-2/A-3 (F029, operator, semantic pair): the Choose-project rows must self-identify the CURRENT DIR. build_project_choices (picker/model.rs:322-352). A-2: when the run cwd IS already a history dir the `Here:` row is (correctly) suppressed by the dedup (REQ-PICKER-CHOOSE-DEDUP-ALL), but the matching history row rendered bare `r.display` with no cwd affordance \u00e2\u20ac\u201d mark it `<display> (CURRENT DIR)`. A-3: the not-in-history current-dir row changes from `Here: <run_cwd>` to `CURRENT DIR --> <project>`, deriving the display the SAME way the history refs do (folder tail; honest fallback to the raw path when underivable). `cwd` payload unchanged. Grep-tests rule: 3 `starts_with(\"Here: \")` asserts (model.rs) + a `Here: /here` render assert (view.rs) are behavior assertions on the OLD label. See triage A-2/A-3.",
    "doc": ""
  },
  "REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT": {
    "title": "A clean `spt rc` attach to a LIVE spt-hosted (`endpoint run`) harness must DELIVER the harness's PTY output. KEYSTONE \u00e2\u20ac\u201d the operator's central 'attach shows no output' symptom, reproduced on the real dummy-harness fixture (v0.12.1 Wave 1) with NO death and NO wedge: bringup succeeds (online, harness pid alive + heartbeating, psyche hosted), the attach CONNECTS (PUMP_IPC_READER spawned, no RC_FAIL, holds the full window) \u00e2\u20ac\u201d but receives EXACTLY 0 bytes over 10s of the harness's flushed [session.self] stdout. DISTINCT from REQ-HAZARD-VIEWER-CLOSE-DETACH (death) and REQ-HAZARD-ATTACH-WEDGE (dead-child backpressure): here the harness is ALIVE and the attach is a clean first subscribe. This BLOCKS the 'view is independent' invariant \u00e2\u20ac\u201d re-attach is meaningless if a live endpoint-run harness shows nothing. KNOWN-GOOD (rules out 'no drain'): attach.rs `local_attach_via_loopback_conn_rides_the_same_pump` + `broker_spawns_the_pty_child_in_the_requested_cwd` prove the broker DOES drain+fan a `spawn_session` PTY child to a loopback attach over the SAME transport rc uses. Both spawn_session and endpoint-run's spawn_session_pid send KIND_SPAWN \u00e2\u2020\u2019 the same dispatch_spawn (broker.rs:706/835) which s",
    "doc": ""
  },
  "REQ-GOSSIP-PROJECT-DERIVE-ONCE": {
    "title": "REGISTRY-LIFECYCLE W4 fast-follow (hertz v0.37.0 field-verify CPU-gate FAIL RCA 2026-07-17, doyle seam-verified same day; timing-pinned ~16s burst clusters per 30s Registry pump round): recent-project gossip derives a project id AT MOST ONCE PER DISTINCT CWD per advertisement round \u00e2\u20ac\u201d recent_projects_for dedups the RAW cwd string BEFORE project_id_for_dir (today seen.insert(pid) dedups AFTER derivation at registryhost.rs:1001, so every duplicate-cwd sessions.log row pays a git remote/rev-parse spawn: ~200 derivations/round on a 12-perch box, the post-self-hash-fix CPU floor). The pid-level dedup stays as the output-uniqueness second layer (distinct cwd spellings may collapse to one project); ordering semantics unchanged (first-occurrence newest-first); bounded ledger read unchanged. Gate: impl \u00e2\u20ac\u201d cwd-dedup before derivation; unit \u00e2\u20ac\u201d derivation-count observable == distinct cwds not ledger rows (duplicate-cwd ledger derives once), output/order parity vs the pre-fix shape on a mixed ledger. SEEDED SEPARATELY (not this REQ): gossip leg consults the ADR-0037 materialized index (kills git entirely + cross-round rederivation; staleness-semantics design change) + epoch-mint batching (26 atomi",
    "doc": ""
  },
  "REQ-PSYCHE-ROLE-OPTIONAL-SKIP": {
    "title": "W1 (LIFECYCLE-TRUTH): a manifest with NO [session.echo_commune] role SKIPS commune-sync (debug-level note, no strike) instead of hard-failing the turn. ROOT (perri filing, recovered): missing role -> commune-sync hard-fails -> 3-strike stamps the host ('manifest declares no [session.echo_commune] role') while the published contract presents the role as an optional template. FIX: missing OPTIONAL role = skip, not a turn failure.",
    "doc": ""
  },
  "REQ-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-PSYCHE-SPAWN-ENV-PARITY": {
    "title": "P-2 (WORKER-TRUTH triage addendum, perri-filed field finding 2026-07-06): the per-event psyche_resume spawn threads the perch record's CAPTURED read_env stamps into the spawn ENVIRONMENT \u00e2\u20ac\u201d the F-027 Half-B env-parity contract (BINDING, design-frozen: read_env captured at creation + stamped on the record + threaded IDENTICALLY to every session spawn; the spawn never reads its own process env for a stamped var) extended to the psyche role the design predates. Field driver: flynn (claude-spt:ccs) \u00e2\u20ac\u201d the ccs wrapper relocates the account root via CLAUDE_CONFIG_DIR at PARENT launch and the perch record correctly captured it, but the daemon spawns psyche_resume with bare env \u00e2\u2020\u2019 default ~/.claude root \u00e2\u2020\u2019 headless 'Not logged in' exit-1 \u00e2\u2020\u2019 strike loop; psyche + parent land in DIFFERENT account roots (auth AND root-scoped continuity both break). Core stays harness-agnostic (threads whatever [env] direction=read captured \u00e2\u20ac\u201d knows nothing of CLAUDE_CONFIG_DIR). Scope note: this is the URGENT psyche leg of F-027 Half B; the full pre_spawn seam + endpoint-session env threading stays design-parked (F-027-ENDPOINT-SPAWN-FAIL-DESIGN.md) unless operator pulls it forward.",
    "doc": ""
  },
  "REQ-PICKER-PROJECT-HISTORY-TRUTH": {
    "title": "#1: picker project history is derived from sessions.log cwds (newest->oldest, deduped by project_id_for_dir) UNION context-store branches, EXCLUDING owlery-internal paths (any cwd under spt_home()/owlery) everywhere a project is displayed or inferred. Fixes three stacked defects (crates/spt/src/picker/data.rs): (1a) project_history_for (data.rs:372) reads ONLY context-store p-* branches, which are empty on this box -> history []; (1b) the fallback origin project (data.rs:207) is derived from info.json.cwd = latest-boot-cwd (rewritten every rebind), not origin; (1c) psyche-host sessions bind owlery-internal cwds that pollute history. Full DIRS stay available in the model (feature #5 needs them). PROJECT REPRESENTATION RULING (operator 2026-07-03): project IDs ONLY, EVERYWHERE incl local display; on ID collision disambiguate minimally via a PURE disambiguate_project_ids(entries)->display-names fn (append one-level-up parent folder and/or root drive letter, e.g. 'spt-core (projects)' vs 'spt-core (D:)'). See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #1.",
    "doc": ""
  },
  "REQ-STORE-CONTEXT-BRANCH-FILL": {
    "title": "#1 SI-1 (RCA, operator-promoted 2026-07-03): the context store (tracked/.seed.git) holds ZERO p-* branches on a box with months of live-agent use, while kitsubito/enlyzeam stores carry them. Context commits never land -> picker history has no store source (REQ-PICKER-PROJECT-HISTORY-TRUTH's fallback ships regardless, but the store must ALSO fill). RCA the contextstore write->branch-commit path with evidence (commune-ingest/context-commit regression vs store re-init), contrast the healthy stores, land whatever fix the RCA names. See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #1 SI-1.",
    "doc": "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]`: <!-- --> ### 7.28 A relative manifest path resolves against the ENDPOINT, never the daemon `[REQ-STORE-CONTEXT-BRANCH-FILL]` - **Class rule:** a relative manifest path naming a filesystem location the ENDPOINT reads or writes MUST resolve against the endpoint's own cwd (its `info.json.cwd`, read at USE time), never the daemon's process cwd. A single daemon hosts many endpoints with different cwds, so a relative path frozen or read against the daemon"
  },
  "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-CONTROLLER-RETAKE-FLOOR": {
    "title": "`become_controller` should STRUCTURALLY refuse a controller re-take whose `from_seq` falls below the connection's already-delivered contiguous floor \u00e2\u20ac\u201d making the P1c reorder invariant un-reintroducible by a future caller, not just removed at the one caller. ROOT/SCOPE (doyle proposed, P1c gate dialogue): P1c fixes REQ-HAZARD-CONTROLLER-WRITER-REORDER three ways (handoff single-take + epoch-gate-under-lock + session_cursors seed), removing the one decreasing-floor double-take and bounding any other to already-committed-only. A self-enforcing broker guard would refuse the bad SHAPE outright. BLOCKER: the obvious predicate (`from_seq >= delivered_through`) is UNSAFE because `delivered_through` is SESSION-WIDE (the `Arc<AtomicU64>` on `OutputLog`, shared by all controllers/viewers, advanced monotonic-MAX; `resume_seq` reads it) \u00e2\u20ac\u201d a normal fresh-operator `from_seq=0` attach to a producing session legitimately sits below it (full ring replay + consumer dedup-below/snap-above), and monotonic-MAX can't distinguish the hazard (a `seq1`-without-`seq0` write reads as `2`). The structurally-correct guard needs a NEW per-connection contiguous-sent cursor (the true highest-contiguous seq this so",
    "doc": ""
  },
  "REQ-UPD-1": {
    "title": "Peer-propagated update over P2P",
    "doc": ""
  },
  "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-NOTIF-SCOPE": {
    "title": "Notif scope is a per-row producer-chosen attribute node|subnet: node-scoped rows (update/consent/rollback kinds) live and die on their node and NEVER enter a replication feed; subnet-scoped rows keep the full ADR-0007 machinery (replication, cross-node dismiss, semilattice join); scope determines the first-fire candidate set (node-scoped targets the most-recently-active endpoint ON that node). New row fields additive serde-defaulted (host_binaries pattern) \u00e2\u20ac\u201d old peers parse clean, schema regen drift-gated",
    "doc": "1. Scope is a per-row, producer-chosen attribute: `node` | `subnet`: <!-- -->"
  },
  "REQ-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-HAZARD-DAEMON-STOP-BARRIER": {
    "title": "B3: `spt daemon stop` then an immediate `spt daemon start` does NOT race \u00e2\u20ac\u201d stop fully completes before it returns. Today request_stop (seedmap.rs:240-255) returns on the KIND_STOPPING ack (sent seedmap.rs:174-176) BEFORE the seed socket unbinds, so a following is_running ping (daemon.rs:375) wins the exit window and start reports ALREADY_RUNNING (operator: daemon stop \u00e2\u2020\u2019 STOPPED then start \u00e2\u2020\u2019 ALREADY_RUNNING). FIX: unbind/stop-gate the seed socket BEFORE acking KIND_STOPPING, OR request_stop waits for a ping-to-fail before returning. Unit: stop then immediate is_running()==false. (v0.12.0)",
    "doc": ""
  },
  "REQ-HAZARD-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-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-ENDPOINT-CYCLE-HONEST": {
    "title": "REGISTRY-LIFECYCLE W3 (ADR-0041 decision 6, operator deployah stop/run wedge): cycle verbs share ONE liveness authority \u00e2\u20ac\u201d the ALREADY_LIVE dup-guard liveness-probes the claimed session client tree before refusing (dead tree means reap + respawn honestly, never a refusal citing a zombie); the shutdown state machine consults the same source so is-it-live has one answer (no ALREADY_LIVE / list-OFFLINE / shutdown-NO_EDGE three-way contradiction on the same endpoint). Gate: impl \u00e2\u20ac\u201d probing dup-guard + unified authority; unit \u00e2\u20ac\u201d dead-tree claim probes and reaps, live claim still refuses; int \u00e2\u20ac\u201d controlled zombie (killed client tree, surviving hosted record) leads to endpoint run succeeding honestly end-to-end; doc \u00e2\u20ac\u201d ADR-0041.",
    "doc": "Decision: <!-- --> 1. **Online is earned, not declared.** A creator may stamp `status=online` only from actual persisted state + hosting authority \u00e2\u20ac\u201d never from manifest capability alone. Legacy hybrid rows self-heal at reconcile, but only after a SUCCESSFUL broker query: a broker failure is never interpreted as an empty session set (no mass-offline on a hiccup). 2. **Control cleanup splits from offline classification.** Reconcile clears `controlled`/`driven_by`/`viewer_count` for EVERY endpoint absent from session truth \u00e2\u20ac\u201d regardless of state or controllability \u00e2\u20ac\u201d while offline classification ke"
  },
  "REQ-MANIFEST-SUBST": {
    "title": "Manifest substitution primitives for resolve-not-execute (ADR-0029, supersedes a rejected `spt api run-hook`): (1) two adapter-static substitution keys `{adapter_dir}` (the registry record's precise source_dir \u00e2\u20ac\u201d install dir, survives updates, the dir bare-program resolution uses) and `{adapter_name}`, available wherever command/string substitution runs; (2) lazy substitution INSIDE `[strings]` values at `get-string` read time, scoped to those adapter-static keys ONLY (session-scoped {id}/{session_id}/\u00e2\u20ac\u00a6 are NOT available \u00e2\u20ac\u201d get-string carries no session; a get-string --session-id is a deferred larger change). Invariant preserved: spt-core never executes a string \u00e2\u20ac\u201d it substitutes and returns; the adapter's own wrapper executes the result (e.g. a CC hook dispatcher get-strings its packed binary once per session into an env var, then runs it per-hook, so hook logic rides `spt adapter update`). (v0.16.0)",
    "doc": "Claude Code: native resume into an existing transcript by id, in its project cwd.: <!-- --> **Adapter-static keys \u00e2\u20ac\u201d `{adapter_dir}` and `{adapter_name}` (since v0.16.0).** Two of the catalog keys are *adapter-static* \u00e2\u20ac\u201d they depend only on the resolved adapter, never on a session or event, so they are available **wherever** command/string substitution runs (every `[session.*]` template, the `[digest]` extractor, the `[message-idle-translation-binary].command`, and \u00e2\u20ac\u201d uniquely \u00e2\u20ac\u201d inside `[strings]` values at `get-string` read time) // `[strings]` \u00e2\u20ac\u201d adapter string values (M9 / file-backed M12-W3):"
  },
  "REQ-HAZARD-RESTART-IDEMPOTENT": {
    "title": "Idempotent/exactly-once delivery across brain restart at every broker boundary (codex #14)",
    "doc": ""
  },
  "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-HAZARD-INJECT-WORKER-POISON": {
    "title": "The per-session inject-worker floor Mutex is SHARED by the inject worker (open/flush) and the controller-input path (dispatch_input Layer C buffer_if_held); a panic under the lock on EITHER poisons it, and a bare .lock().unwrap() at the next site then panics too \u00e2\u20ac\u201d a panic in the inject WORKER kills its thread WITHOUT reaping the translation child, orphaning a live binary while event_tx.send fails, so force-native reports 'worker-gone' delivered=false FOREVER (the F-e generic-miss shape). HARDENING, NOT the F-e incident root (perri's matrix exonerated poison under thrash/dormancy/churn): (i) poison-tolerant floor lock (unwrap_or_else into_inner) at all 3 sites so one panic can't cascade the session's delivery dead; (ii) a panic-resilient inject worker (catch_unwind -> fault+terminate the child on a worker panic) so a dead worker never orphans a live binary + strands delivery. Poison-tolerance class of REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE / bug #16.",
    "doc": ""
  },
  "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-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-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-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-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-HAZARD-DROP-FILE-SINGLE-WRITER": {
    "title": "Drop files are daemon-owned single-writer (6.4)",
    "doc": ""
  },
  "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-DOC-DELIVERY-VOCAB": {
    "title": "W6 (LIFECYCLE-TRUTH, docs \u00e2\u20ac\u201d remaining flynn/perri gaps folded): publish the full send-outcome vocabulary (SENT / SENT(WAN) / QUEUED window semantics / DEFERRED / NO_PERCH), digest --json row schema, api poll auth + MAC-stamp prefix, remaining --json shapes checklist (seed #3). Public docs use VERSION numbers, never wave codes; docs-publish drift gate applies.",
    "doc": "Semantics: <!-- the send-outcome vocabulary itself \u00e2\u20ac\u201d the closed set of SENT/SENT(WAN)/QUEUED/QUEUED(idle-only)/DEFERRED/NO_PERCH + WAN failure tags with their exact conditions; the JSON-consumer view + digest/poll shapes live in reference/json-shapes.md --> // JSON output shapes: <!-- the machine-consumer reference: send-outcome vocabulary (canonical home cross-linked to Messaging), the endpoint-digest --json schema, the shell relay MAC-stamped frame prefix + api poll auth, and the full --json shapes catalog (seed #3) -->"
  },
  "REQ-ENDPOINT-LIST-NODE-GROUPED": {
    "title": "spt endpoint list is node-grouped over unique INSTANCES, not subnet-grouped over duplicated rows: one row per (id,node) instance \u00e2\u20ac\u201d subnet duplication collapsed (ADR-0006 \u00c2\u00a71: subnet is never an identity axis), freshest-epoch wins a cross-subnet status disagreement (the resolve_among twin rule) \u00e2\u20ac\u201d grouped under 'This node: <label>' (cyan; local roster is the status truth, advertised-status vocabulary, corrupt = suspended + corrupt annotation) then remote nodes alphabetical (node name orange 38;5;208, the legacy $LIVE orange), each node carrying a light-gray 'Shared subnets:' line (subnets among our memberships where that node gossips any visible row; per-instance subnet detail stays in --json/--detail), instance rows [id, endpoint_type, glyph, status] (endpoint_type threaded from Instance.endpoint_type through ResourceRow \u00e2\u20ac\u201d additive, pre-field rows render '-'), per-node 'Total:' lines with NO grand total (the stderr ENDPOINTS:<n> line is REMOVED \u00e2\u20ac\u201d it counted subnet rows, so the same instance in N subnets counted N times), SELF pin kept first with a '(self @ <node>)' marker (REQ-WHOAMI-1 alias; self also appears as a This-node row so the node total stays honest), remote nodes with zero",
    "doc": ""
  },
  "REQ-CLI-JSON": {
    "title": "A global `--json` flag (clap global=true) honored by the READ/STATUS command set: endpoint list/whoami, daemon status, subnet status/show-code, endpoint description/role, adapter list/version, notif list, grant list, access list, shell list, how-to (endpoint digest already has it). Action commands do not honor it. A shared print_json() helper + a coverage TEST asserting every command in the set emits valid JSON (guards against the missing-shared-formatter drift). Output uses explicit per-command output DTOs with committed field names \u00e2\u20ac\u201d internal structs are NOT serialized verbatim (their fields would become a public contract; JSON is a consumed wire-parity surface). (v0.16.0)",
    "doc": ""
  },
  "REQ-ADAPTER-FLOOR-ENFORCE": {
    "title": "F-5 (REMOTE-TRUTH triage \u00c2\u00a7F-5 + doyle rulings 2026-07-05): BOTH adapter acquisition verbs (spt adapter add + spt adapter update) REFUSE when the installed spt-core is BELOW the adapter's declared [adapter].min_spt_core_version floor \u00e2\u20ac\u201d with an F-1 operator refusal naming the installed core, the floor, and the next action (update spt-core first). ROOT: the floor was PARSED + required (manifest.rs) but never compared to the running core \u00e2\u20ac\u201d dead enforcement; and the [update].version_check knob that gated it was DOC'D-BUT-DEAD (never read by any production path \u00e2\u20ac\u201d a contract lie). RULINGS: RETIRE version_check (drop the manifest field + schema + docs + the cfg(test) literals; a pre-existing manifest still setting it deserializes fine \u00e2\u20ac\u201d serde ignores the unknown key, no deny_unknown_fields, so retiring is back-compatible); SEMVER-compare NOT string-compare (the 0.9.0 < 0.25.0 lexical trap); enforce on BOTH verbs; nothing installs / registry untouched on refuse (binds both verbs, no residuals). FIX: (1) a pure spt-runtime version_meets_floor(core, floor) -> bool (numeric per-component: split '.', u64, missing\u00e2\u2020\u20190, non-numeric\u00e2\u2020\u20190, first-diff decides, equal-when-zero-padded \u00e2\u2021\u2019 satisfied) \u00e2\u20ac\u201d mirror",
    "doc": "`[adapter]` \u00e2\u20ac\u201d header: <!-- --> `min_spt_core_version` is the **enforced** compatibility floor. Both acquisition verbs \u00e2\u20ac\u201d `spt adapter add` and `spt adapter update` \u00e2\u20ac\u201d REFUSE when the installed spt-core is below this version, naming the installed core, the floor, and the next action (update spt-core first). The check is a numeric per-component compare (so `0.9.0 < 0.25.0`), and it fires **before** anything is written: a refused add leaves the registry untouched, and a refused update leaves the live install byte-untouched. The enforcement is unconditional \u00e2\u20ac\u201d there is **no** opt-in flag (the former"
  },
  "REQ-TERM-ECHO-CLAMP-WINDOW": {
    "title": "SEED (DAEMON-LIFECYCLE W3 rideout, field-grounded 2026-07-22): the hosted-TUI echo CLAMP WINDOW \u00e2\u20ac\u201d a Windows pseudoconsole boots with echo/line input ON (in=0x1f7, measured by the 7.55 instrument @0f74bba) and it is the hosted CHILD that clamps them, so any window in which the TUI has not yet (re-)clamped echoes typed bytes into PTY output SERVER-SIDE with no seam re-enabling anything. FIELD GROUNDING (hertz capture-2 byte timeline, output-only taps): echo onset is NOT resize-instant \u00e2\u20ac\u201d first echoed key lands 6003ms after the first resize-associated repaint burst, IMMEDIATELY after a 3225-byte TUI-reinitialization-shaped absolute repaint (no alt-screen/mode CSI anywhere \u00e2\u20ac\u201d cursor hide/show + HOME/absolute repaints only); echo CEASES mid-input (isolated c/o/n/f/i, NO g) directly after a 535-byte TUI diff \u00e2\u20ac\u201d consistent with a late clamp landing (a WinAPI mode call is invisible to a byte tap). The resize seam itself is measured MODE-PRESERVING (REQ-RESIZE-INPUT-MODE-INTEGRITY rig, four sample points). OPEN AT TRIAGE: (1) whether the clamp is the TUI's own SetConsoleMode or portable-pty/ConPTY-internal; (2) whether spt-core CAN mitigate at all \u00e2\u20ac\u201d the daemon structurally cannot read or set t",
    "doc": ""
  },
  "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-ARCH-1": {
    "title": "Many small acyclically-layered crates",
    "doc": ""
  },
  "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-HAZARD-DEFERRED-DRAIN": {
    "title": "Deferred spool rows excluded from the event-stream drain (1.4)",
    "doc": ""
  },
  "REQ-DOCS-NO-INTERNAL-CODES": {
    "title": "Public CLI --help (the clap `///` doc-comments) and the generated `docs-site/src/cli/reference.md` MUST NOT contain internal tracker/decision codes \u00e2\u20ac\u201d `REQ-*`, `F-###`, `M#-W#`, `ADR-####`. They are meaningless to an end user reading --help and ship to GH-Pages. A CI-gated scan (the `xtask check` docs gate) fails on any such token in the GENERATED reference.md (which by construction contains only clap help, so rustdoc `///` on fns/structs is OUT of scope and keeps its REQ/ADR cross-refs). Substance is kept; codes are rewritten to plain language. (v0.13.2 W6)",
    "doc": "CI commitments: <!-- --> **Public help carries no internal codes.** The public CLI `--help` (the clap `///` doc-comments) and the generated `docs-site/src/cli/reference.md` MUST NOT contain internal tracker/decision codes \u00e2\u20ac\u201d `REQ-*`, `F-###`, `M#-W#`, `ADR-####`. They are meaningless to an end user reading `--help` and ship to GitHub Pages. The `xtask check` docs gate scans the **generated** reference for these tokens and fails on any hit (regeneration alone keeps drift at zero but would faithfully republish a leak \u00e2\u20ac\u201d the scan is what gives the gate teeth). The scan is scoped to the generated re"
  },
  "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": {
    "title": "TOTP-seeded SPAKE2 pairing",
    "doc": ""
  },
  "REQ-ARCH-2": {
    "title": "Public SDK surface is spt-proto, spt-runtime, spt-msg",
    "doc": ""
  },
  "REQ-PICKER-OFFLINE-NO-VIEW": {
    "title": "A3 (F028, operator #3): 'View now (read-only)' is offered for OFFLINE endpoints. model.rs:1030 offline branch of confirm_options is vec![Start, View] \u00e2\u20ac\u201d View is meaningless with no live PTY. FIX: offline set = [Start] (+ the existing Resume/ChangeAdapter/Instantiate/Fork/Shortcut tail). Update the view.rs options tests. See triage A3.",
    "doc": ""
  },
  "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-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-SHELL-FRAME-VOCAB": {
    "title": "The shell relay frame vocabulary is a PUBLISHED contract, not a reverse-engineered one. (flynn spt-alchemy clean-room audit 2026-07-21, doyle code-verified, seed pair item 1.) TODAY: shellchan.rs composes the exact frames a shell binary must parse \u00e2\u20ac\u201d shell_command / shell_text / shell_file (+ shell_close, sensory, drive) \u00e2\u20ac\u201d but the published export (docs-site/src/shells/) carries ZERO occurrences of those type names; the docs say only 'the shell child parses its own vocabulary', so every adapter author (notify-shell, alchemy) reverse-engineers the frame shapes compatibly from source. FIX: publish, in the shells section of the docs-site export, the frame type names + their attrs (op, xfer-id, path) + body encodings \u00e2\u20ac\u201d a shell_command body is a JSON object of named args (positionals zipped against the manifest's declared arg names), a shell_text body is the raw text, a shell_file body is the original filename with the perch-relative landed path in the path attr, a shell_close body is the manifest's pre_close instruction (NOT vocabulary-checked \u00e2\u20ac\u201d the vocabulary gates agent commands, the manifest is its own authority over its own binary). Fold in the quoted-composite-tail sharp edge: the ",
    "doc": "The frame contract: what a shell binary parses: <!-- -->"
  },
  "REQ-BROKER-ATTACH-JOURNAL-RESILIENT": {
    "title": "A poisoned EffectJournal mutex or a sick NetHost runtime must NOT permanently brick all future attaches. Bug #16 (URGENT): a live spt-hosted endpoint (eel-a) attach fails with 'brain IPC read deadline elapsed' after a self-update brain-respawn \u00e2\u20ac\u201d the broker survives the respawn and one journaled op (dispatch_net_stream_open journal.apply_once + loopback open_stream runtime.block_on nethost.rs:1060) enters a bad state, so every journaled attach silently kills its per-conn reply thread while non-journaled ops keep working. Fix: recover PoisonError via into_inner (effect.rs apply_once, replace the .expect panics) so one panic cannot brick all attaches; bound the loopback open_stream block_on (nethost.rs:1060) like the QUIC bounded_block_on so a sick runtime fails fast with an error frame not an opaque 10s deadline. Reinforces REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE. See docs/NEXT-MILESTONE-BUG-TRIAGE.md #16.",
    "doc": ""
  },
  "REQ-START-2": {
    "title": "Harness-hosted startup: api seed then listen",
    "doc": ""
  },
  "REQ-RC-IDENTITY": {
    "title": "`spt rc` overlays a persistent endpoint-identity marker so the operator always groks which endpoint they control: a reserved TOP status row via a DECSTBM scroll-region (shrink the PTY's reported rows by 1, own the row), right-aligned `SUBNET : ENDPOINT_ID @ NODE`, CYAN text. Re-assert the margin + repaint on alt-screen enter / DECSTBM reset / resize (output-scanning, like the existing mouse_scanner). NO window title (the harness, e.g. CC, owns it for busyness \u00e2\u20ac\u201d OSC dropped). Resolve subnet/node/id once at attach (perch/registry read) and thread into the pump; subnet = the endpoint's home/primary (\"local\" if none). The literal floating rounded-rectangle corner box is DEFERRED to the future web-based GUI (not a grid-model rc \u00e2\u20ac\u201d that lift is better spent on the GUI). (v0.16.0)",
    "doc": ""
  },
  "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-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-PAIR-8": {
    "title": "NTP TOTP offset: the pairing ceremony queries NTP at ceremony time (both sides) and applies the derived offset to the TOTP calculation in-process only; system-clock fallback when NTP is unreachable (offline LAN pairing unaffected \u00e2\u20ac\u201d NTP failure never blocks a pairing that succeeds today); never sets the OS clock; no background sync loop (M8 decision 18; field trigger: enlyzeam clock >1 min off exceeds the \u00c2\u00b11 window)",
    "doc": ""
  },
  "REQ-HAZARD-ATTACH-WEDGE": {
    "title": "A legitimately dead PTY child (real crash/kill) + an undrained operator pump must NOT wedge the broker for all other clients. ROOT (v0.12.0 real-harness defect): loopback attach output is a blocking write_all into a bounded 64KB tokio duplex (nethost.rs:1040,1090); when the operator's rc pump stops draining (tab closed) the buffer fills and write_all blocks forever (the 'loopback never hangs' assumption at nethost.rs:1103 is false), parking a worker in the 2-worker net runtime (nethost.rs:640); a couple of these saturate BOTH workers \u00e2\u2020\u2019 every new attach / `endpoint run` stalls right after 'PUMP_IPC_READER: spawned' \u00e2\u2020\u2019 30s FIRST_EVENT_GRACE \u00e2\u2020\u2019 'no output / dead or wedged'; `daemon stop` cannot join the stuck workers. DISTINCT from the removed B1 path-(c) mutex deadlock. DISPOSITION = PROVE-DON'T-CHANGE (doyle GATE-PASS @e883f45, 2026-06-18): this ROOT is the SUPERSEDED v0.12.0 hypothesis \u00e2\u20ac\u201d the post-L0 code ALREADY prevents the wedge, so NO fail-fast / worker-count code was added. serve_attach forwards fire-and-forget (net_stream_send op_id=None) and the broker-side send_stream is already BROKER-QUIC-DEADLINE-bounded (bounded_block_on, 10s); the loopback duplex is drained broker-INTERNA",
    "doc": ""
  },
  "REQ-UPDATE-DEFAULT-COMPOSITE": {
    "title": "THE-FORKENING W4 (operator-grilled 2026-07-14): plain `spt update` = `update fetch --apply` THEN `update adapters` (core-first doctrine order); when core is already current the core leg no-ops and ONLY adapters update; `--core-only`/`-c` skips the adapters leg. GROUNDING (operator-corrected, code-confirmed): fetch --apply cycles the BRAIN only \u00e2\u20ac\u201d broker + PTYs survive (apply_staged applyhost.rs:303; the restart-required text is a NOTICE, cli.rs:4615, not behavior) \u00e2\u20ac\u201d so the composite's invoking process survives by construction and NO re-run machinery is needed; on a broker-side release the existing F-025 notice remains the composite's closing output. Gate: unit \u00e2\u20ac\u201d composite sequencing incl. already-current -> adapters-only and --core-only skip; int \u00e2\u20ac\u201d composite on a staged release applies core then updates a registered adapter in one invocation; doc \u00e2\u20ac\u201d reference + self-update docs present plain `spt update` as the primary form. Kin REQ-UPDATE-ADAPTERS-VERB, REQ-UPDATE-RESTART-SAFE-SWAP, REQ-UPDATE-APPLY-RESTART-NOTICE.",
    "doc": "Self-update: **update composite (`spt update`)** \u00e2\u20ac\u201d the plain verb is the primary form: `update fetch --apply` then `update adapters` (core-first order); with core already current, only adapters update. `--core-only`/`-c` skips adapters; `spt update adapters [<a>[,<b>\u00e2\u20ac\u00a6]]` is the adapters leg alone (alias over `spt adapter update`). The composite's invoker always survives, because a routine apply cycles only the **brain** \u00e2\u20ac\u201d the *restart-required* message on broker-side releases is a notice, not a restart. `spt update --restart` is the one-step **full cycle**: fetch \u00e2\u2020\u2019 adapters \u00e2\u2020\u2019 `apply --finish`"
  },
  "REQ-HAZARD-DAEMON-HOSTED-LIVENESS": {
    "title": "Daemon-hosted perches (Psyche, spt-hosted Self) derive liveness from the daemon endpoint table + info.json status, never is_process_alive(info.pid) (2.5)",
    "doc": ""
  },
  "REQ-RESUME-ADAPTER-FOLLOWS-SESSION": {
    "title": "D-2 (REMOTE-TRUTH triage \u00c2\u00a7D-2 + operator Q5 @c248afc): a resume-from-history restores the RECORDED session adapter (REQ-SESSION-ADAPTER-RECORDED) \u00e2\u20ac\u201d the resumed harness is the one the session ran under, re-stamped onto the endpoint PRE-SPAWN, and an unregistered recorded adapter refuses LOUDLY before launching anything. ROOT: the picker's resume_outcome (model.rs:1285) bakes adapter=ep.adapter_profile from the selected ENDPOINT, ignoring the ledger row \u00e2\u20ac\u201d so a resume always uses the endpoint's CURRENT adapter even when the session ran under a different one; and the endpoint's info.adapter is never re-stamped to the row's on the resume path (cli.rs:1962 skeleton writer early-returns for an existing perch \u00e2\u20ac\u201d adapter immutable, carried by bind's stamp_creation_fields). FIX (doyle fork ruling): ResumeRow (model.rs:199) gains adapter: Option<String> threaded from SessionEntry.adapter in picker/data.rs; the row title (model.rs:228) renders [{adapter}] when Some ({head} [{adapter}] - {time} (\u00e2\u20ac\u00a6{id5})); resume_outcome bakes the ROW's adapter with an endpoint fallback (row.adapter.unwrap_or(ep.adapter_profile)) \u00e2\u20ac\u201d None \u00e2\u2020\u2019 the endpoint's current stamp (benign degrade). The pre-spawn RE-STAMP + ref",
    "doc": ""
  },
  "REQ-HAZARD-INJECT-CONTROL-COEXIST": {
    "title": "SPINE INVARIANT (v0.13.0 keystone): the broker must accept INJECTED keystrokes into an spt-hosted PTY (the v0.11.0 raw direct-inject today; the ADR-0022 translation-binary choreography tomorrow) WHILE a live `spt rc` controller is attached to the SAME PTY, without (a) the operator losing control, (b) the endpoint latching ONLINE+CONTROLLED, or (c) the broker wedging. The injection inlet is PERMANENT \u00e2\u20ac\u201d spt-claude-code requires keystroke injection \u00e2\u20ac\u201d so this is root-caused + fixed at the PTY-injection layer, IN STEP with the ADR-0022 delivery redesign that formalizes the inlet. REOPENS the wedge facet of REQ-HAZARD-ATTACH-WEDGE: the v0.12.1 prove-don't-change covered only DEAD-CHILD backpressure, NOT the injection trigger (operator's signal \u00e2\u20ac\u201d one injected keystroke succeeds, the next wedges \u00e2\u2020\u2019 the single-threaded broker parks on a blocking PTY/loopback write after injection-induced harness output). REPRO-FIRST on the real dummy-harness fixture (NO theory): instrument to nail the exact blocking call before any fix. Fix candidates: non-blocking/fail-fast PTY write, split input/output, bounded-evicting. Mechanism shared with W2 \u00e2\u20ac\u201d spt-core owns EVERY PTY write and applies an injected seque",
    "doc": ""
  },
  "REQ-EP-2": {
    "title": "Agent endpoints vs Shells distinction in the type model",
    "doc": ""
  },
  "REQ-START-1": {
    "title": "Adapters never resolve SPT_HOME; binary on PATH; api bridging only",
    "doc": ""
  },
  "REQ-CONV-2": {
    "title": "Event-driven advertisement: endpoint online/offline transitions (ready-listener start/stop, rest-state transition, perch death) trigger an immediate advertise_local + peer push as a WAKE of the existing pump loop (no second advertisement path \u00e2\u20ac\u201d epoch lease + visibility gates ride unchanged); the cadence stays the steady-state floor (M8 decision 15)",
    "doc": ""
  },
  "REQ-SHELL-5": {
    "title": "Shell ownership is owner-type-agnostic: any non-Shell endpoint type may own/spawn/drive/command/link a shell (Gateway the named first) \u00e2\u20ac\u201d control-exclusivity keys on the owner endpoint_id, NEVER on the owner's endpoint type. No ownership path (mint, launch, owner-from-link, cmd, drive, tunnel, sleep/wake, owner-shutdown) inspects the owner's type (CONTEXT:264, ratified 2026-06-11 Gateway grill).",
    "doc": "Shell model (detailed): <!-- owner-type-agnostic: control-exclusivity keys on the owner endpoint_id, never the owner's endpoint type --> **Owner-linked, exclusive.** Spawned by an **owning endpoint**; both linked; **only that owner `endpoint_id` may control it**. Agent endpoints are the common owner, but ownership is NOT agent-exclusive \u00e2\u20ac\u201d any non-Shell endpoint type may own shells (ratified 2026-06-11, the Gateway grill: e.g. a **Gateway** owns the driven surfaces it steers; a future **Resource** endpoint could too). Control-exclusivity does not mean interaction-exclusivity: a Shell stays 2-wa"
  },
  "REQ-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-ARCH-3": {
    "title": "Wire-protocol version independent of crate semver, N-1 compat window",
    "doc": ""
  },
  "REQ-MANIFEST-8": {
    "title": "[adapter] host_binaries declares the harness executable basenames a kind=\"harness\" adapter hosts agents inside (e.g. host_binaries = [\"claude\"]); bind-time pid\u00e2\u2020\u2019exe-basename match (case-insensitive, .exe-stripped) over the seed's parent_pid selects the candidate adapter set; zero matches \u00e2\u2020\u2019 a friendly error naming the binary + the --adapter escape hatch. Additive + N-1-safe: optional Vec<String>, #[serde(default, skip_serializing_if = \"Vec::is_empty\")] (omitted-serialized like shortcut_basename, old manifests parse clean); manifest.schema.json regenerated from the derive (ADR-0001, CI drift-gated). The match-key for ADR-0021 adapter-agnostic bind-time resolution. (v0.9.0)",
    "doc": "Startup flows (the two topologies): **Bind-time adapter/profile resolution (ADR-0021).** Because the seed is adapter-agnostic, `listen`/`poll` resolve the owning adapter/profile when they bind, as a pure read \u00e2\u20ac\u201d never a seed-time snapshot that could drift. `--adapter <name[:profile]>` is an **optional override** on the `api` group (an explicit choice for adapter dev/iteration); omitted, resolution runs: 1. the seed's `parent_pid` \u00e2\u2020\u2019 that process's **executable basename** (case-insensitive, `.exe`-stripped); 2. **candidate adapters** = registered `kind=\"harness\"` adapters whose **`host_binaries`*"
  },
  "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-NODE-IDENTITY": {
    "title": "Ed25519 identity primitive: keypair, detached sign/verify, stable pubkey<->hex",
    "doc": ""
  },
  "REQ-HAZARD-RC-ATTACH-ONLINE-RACE": {
    "title": "`spt endpoint run` in an ATTACH/VIEW terminal action attaches BEFORE the freshly-spawned endpoint is online, so the attach races (or outright loses to) the harness bind. ROOT (doyle /diagnose, code-grounded): cmd_endpoint_run (cli.rs) does launch_harness_brokered_in -> (if start: return) -> run_attach with NO await-online between them. launch_harness_brokered_in returns once the harness PROCESS is spawned, but the broker-PTY bind (info status -> STATUS_ONLINE + the live session) lands ASYNC. Both picker attach paths route here with start=false (RunMode::Attach -> cmd_endpoint_run start=false,view=false): Start-now catches the endpoint mid-bringup -> run_attach attempts + loses the handshake race; Resume-from-history catches it still fully OFFLINE -> run_attach's status-gate (REQ-HAZARD-RC-ATTACH-FAILFAST) short-circuits 'offline - nothing to attach' and NEVER attempts. SAME root, two faces (the W4 attach-by-default surfaced both; an online endpoint is unaffected - the picker returns Outcome::Attach, not Run). FIX: in cmd_endpoint_run, when the terminal action is attach/view (NOT start), AWAIT the endpoint online between launch_harness_brokered_in success and run_attach - poll spt_s",
    "doc": ""
  },
  "REQ-HAZARD-STALE-INDEX-LOCK": {
    "title": "Sweep stale lockfiles on daemon boot (1.3)",
    "doc": ""
  },
  "REQ-ENDPOINT-ONLINE-TRUTH": {
    "title": "REGISTRY-LIFECYCLE W2 (ADR-0041 decisions 1+2, emphasys C1 P0): ONLINE is earned, not declared \u00e2\u20ac\u201d cmd_listen stamps status=online only from actual persisted state + hosting authority, never from manifest psyche_init capability alone (no more ready_agent/controllable=false hybrid rows born online-authoritative); livehost reconcile SPLITS control cleanup (clear controlled/driven_by/viewer_count for EVERY endpoint absent from session truth, regardless of state/controllable) from offline classification (live_agent+controllable gate unchanged); legacy hybrid rows self-heal after a SUCCESSFUL broker query only (broker failure is never interpreted as an empty session set); terminal signoff/owner-loss = atomic CAS-guarded offline + ready/address removal WITHOUT overloading soft api session-end (/clear preserves the live listener). Gate: impl \u00e2\u20ac\u201d creator gate + reconcile split + self-heal + terminal path; unit \u00e2\u20ac\u201d creator refuses capability-only online, cleanup clears stamps on state-quirk rows, broker-failure never mass-offlines; int \u00e2\u20ac\u201d dead-PID hybrid row does NOT survive a reconcile cycle (the immortal-row regression); doc \u00e2\u20ac\u201d ADR-0041.",
    "doc": "Decision: <!-- --> 1. **Online is earned, not declared.** A creator may stamp `status=online` only from actual persisted state + hosting authority \u00e2\u20ac\u201d never from manifest capability alone. Legacy hybrid rows self-heal at reconcile, but only after a SUCCESSFUL broker query: a broker failure is never interpreted as an empty session set (no mass-offline on a hiccup). 2. **Control cleanup splits from offline classification.** Reconcile clears `controlled`/`driven_by`/`viewer_count` for EVERY endpoint absent from session truth \u00e2\u20ac\u201d regardless of state or controllability \u00e2\u20ac\u201d while offline classification ke"
  },
  "REQ-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-UPDATE-FETCH-CURRENT-UX": {
    "title": "`spt update fetch` reports an already-staged / already-applied latest as an ACTIONABLE human outcome (exit 0), not a Debug-formatted error. ROOT: cmd_update_fetch (cli.rs) sets the rollback floor = staged_version, so when the published candidate == the already-staged version, verify_update_set_metadata returns Err(RejectReason::Rollback{current,candidate}) \u00e2\u20ac\u201d printed as {reason:?} (Debug) at exit 1, reading as a FAILURE when the update is merely already downloaded and just needs `spt update apply` (this bit the operator: fetch kept 'failing', apply was the missing step). FIX: a PURE classifier (reason, applied, staged) -> {AlreadyStaged (latest downloaded, not yet installed) / AlreadyApplied (up to date) / GenuineError}; already-staged + already-applied print a friendly message and exit 0; genuine rejects use RejectReason's Display (release.rs, not Debug) + exit 1 \u00e2\u20ac\u201d applied at ALL THREE fetch reject sites (metadata + artifact-verify + plan-verify). (v0.18.0)",
    "doc": ""
  },
  "REQ-HAZARD-SPOOL-SENTINEL-CREATE-FAIL": {
    "title": "A persistent failure to (re)create the spool has-messages sentinel is SURFACED, never silently swallowed. touch_has_messages (spt-store/src/spool.rs:147) does `let _ = File::create(...)` \u00e2\u20ac\u201d a live field defect on ENLYZEAM left a stale .has-messages (2026-06-29) beside a fresh spool.db insert (06:59:17Z) in ONE directory, i.e. the create silently failed while rows accumulated (suspected read-only-attrib / share-lock). FIX: on File::create failure emit a LOUD-ONCE-per-perch stderr diagnostic naming the concrete io::Error (self-identifying regardless of kind); do NOT make it fatal (spool writes still proceed). (F-024C item 2, doyle)",
    "doc": ""
  },
  "REQ-HAZARD-ADAPTER-APPLY-SILENT-NOOP": {
    "title": "A DELEGATED live adapter apply MUST NEVER report success without performing the swap, and the live-update seam MUST use ONE parent-aware adapter matcher across all its comparators. TWO defects made the field repro (BUILD-F015B-APPLYMATCH: `--adapter cc:ccs` live update silently no-ops): (D1, matcher skew) the broker's dispatch_adapter_apply filtered sessions by EXACT `s.adapter == req.adapter`, but a `--adapter <adapter>:<profile>` endpoint stores the COMPOSITE `cc:ccs` while the apply carries the PARENT record name `cc` \u00e2\u20ac\u201d so every :profile endpoint fell out to affected=[]; select_endpoints_running_adapter had the same `adp == adapter` skew, while the CLI live-gate (adapter_has_live_endpoint) already parent-matched \u00e2\u20ac\u201d divergent rules on ONE seam. (D2, silent success) the affected.is_empty() branch replied KIND_APPLIED and RETURNED WITHOUT SWAPPING; once the CLI delegates the apply there is no CLI-side fallback swap, so success-without-swap = the update never lands (re-register re-reads the OLD manifest, version-of-truth honestly says old). FIX: (1) ONE shared spt_runtime::profile::adapter_parent_matches(session_adapter, parent) used by the live-gate + broker apply-filter + select_en",
    "doc": "7.23 A message that has REACHED a node's spool must NEVER depend on an adapter hook-poll cadence to reach an spt-hosted (relay-less) endpoint \u00e2\u20ac\u201d the daemon drives delivery on the events it owns `[REQ-HAZARD-DELIVERY-STARVATION]`: ### 7.24 A delegated live adapter apply must NEVER report success without swapping, and the live-update seam must use ONE parent-aware adapter matcher `[REQ-HAZARD-ADAPTER-APPLY-SILENT-NOOP]` - **Failure (F015B / BUILD-F015B-APPLYMATCH):** a live adapter update to a PROFILE-COMPOSITE endpoint (`--adapter cc:ccs`) silently no-oped \u00e2\u20ac\u201d `LIVE` then `DONE` printed, but the v"
  },
  "REQ-WAN-SPT-HOSTED-DELIVERY": {
    "title": "A WAN-ARRIVED `spt send` is DELIVERED to an spt-hosted endpoint (broker holds its PTY, NO api-listen relay), not spooled-forever. Today receive_wan (spt-daemon/wan.rs:271-276) tries deliver_tcp (the harness-hosted relay leg) then falls to spool \u00e2\u20ac\u201d it has NO spt-hosted broker-inject leg, which exists ONLY in local cmd_send (REQ-SEND-SPT-HOSTED, Brain::inject_endpoint \u00e2\u2020\u2019 KIND_ENDPOINT_INPUT \u00e2\u2020\u2019 broker dispatch_endpoint_input \u00e2\u2020\u2019 translation-binary idle-inject). So a WAN arrival to an idle spt-hosted perch with a live translation binary ALWAYS sleeps in spool until an adapter hook polls (F-023: perch verifiably idle 7min, binary healthy, zero injection). FIX: factor cmd_send's spt-hosted delivery leg into a SHARED fn; receive_wan calls it after the replay-check (wan_seen_at) + restamp (restamp_wan_user_msg), BEFORE the spool fallback. Claim discipline UNCHANGED: inject delivered=true \u00e2\u2020\u2019 wan_mark_seen_at then return the existing 'delivered' wire token (no wire change); delivered=false \u00e2\u2020\u2019 the existing spool-with-claim transaction. v0.14.3 LAW: the shared leg is translation-binary-ONLY, NO raw-PTY fallback \u00e2\u20ac\u201d a no-binary arrival SPOOLS LOUD, never writes the PTY. (F-023, BUILD-F023-WANIDLE)",
    "doc": ""
  },
  "REQ-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-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-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-DOCS-RELEASE-ASSET": {
    "title": "THE-FORKENING W2 (ADR-0036 \u00c2\u00a74): every release ships a platform-independent docs bundle `spt-docs.tar.gz` (BUILT mdbook output: HTML + llms.txt + llms-full.txt + raw .md + manifest.schema.json) as a release asset WITH an entry in the SIGNED update-set (sha256, same integrity chain as binaries \u00e2\u20ac\u201d docs describe the security-relevant contract surface, they do not ride unverified). Apply lands/refreshes $SPT_HOME/docs (single current copy = docs always match the installed binary). FAILURE ISOLATION binding: a docs-asset failure NEVER fails the binary update \u00e2\u20ac\u201d UPDATE_DOCS_SKIPPED loud, retried next fetch. Gate: unit \u00e2\u20ac\u201d update-set entry + sha256 verify + skip-loud isolation; int \u00e2\u20ac\u201d a fetch+apply lands version-matched docs at $SPT_HOME/docs; doc \u00e2\u20ac\u201d self-update docs name the bundle. Kin REQ-DOCS-LOCAL-SERVER (the consumer), REQ-RELEASE-CHANNEL-PRIVATE (the assemble leg), ADR-0036.",
    "doc": "Self-update: **docs bundle** \u00e2\u20ac\u201d every release ships a platform-independent archive of the **built docs** (HTML + `llms.txt` + `llms-full.txt` + raw markdown + `manifest.schema.json`) as a **signed update-set asset**; apply lands it at `$SPT_HOME/docs`, so a node's docs always match its installed version. A docs-asset failure never fails the binary update (skip loud, retry next fetch). Consumed by the *docs server* (below). <!-- -->"
  },
  "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-HAZARD-EBUSY-RENAME": {
    "title": "tmp-write + atomic-rename + retry on Windows EBUSY (5.2)",
    "doc": ""
  },
  "REQ-DIGEST-CROSS-NODE-PULL": {
    "title": "The digest SNAPSHOT PULL crosses nodes: `spt endpoint digest <[subnet:]id@node>` (including `--after <seq>` incremental polling) resolves and serves under the EXISTING address gate \u00e2\u20ac\u201d the CROSS_NODE_M4 qualified-address refusal lifts for the pull verb only. The structured-delta stream stays node-local until a real cross-node subscriber exists (ADR-0048 decision 5, defer-until-consumer; operator-ruled 2026-07-24). Access policy is NOT new \u00e2\u20ac\u201d fetch was ratified address-gated long ago; this is routing. Composition note: REQ-DIGEST-SEAL-ON-IDLE's stable seqs are what make remote --after polling trustworthy. Forcing customer: spt-mobile 'view live digest of ANY endpoint' (host proxies transparently once the refusal lifts). Gate: int \u00e2\u20ac\u201d two-node rig: a qualified digest pull from the non-hosting node returns the hosting node's buffer; --after returns only rows past the cursor; the address gate still refuses a non-resolvable caller.",
    "doc": "Session digest \u00e2\u20ac\u201d `endpoint digest --json`: <!-- --> > **Reaching an endpoint on another machine.** The snapshot pull accepts a > qualified address \u00e2\u20ac\u201d `spt endpoint digest <id@node> --json`, `--last` and > `--after` included. The endpoint's own node projects and answers, so the > content is identical to what someone standing on that machine would see, and > the same access rules apply as for every other qualified address. `--follow` > is **local-only**: a delta subscription is not available across nodes, so poll > the snapshot with `--after <seq>` instead. A qualified address that resolves > to"
  },
  "REQ-HAZARD-CONTROL-STAMP-CONVERGENCE": {
    "title": "Control/viewer stamps must CONVERGE to broker session-table truth for every session-backed endpoint, not merely edge-trigger \u00e2\u20ac\u201d the UPWARD companion to the DOWNWARD edge-clear REQ-HAZARD-CONTROL-STAMP-LIFETIME (7.27); same 'stamps == broker truth' family. ROOT (F-026 stamp-gap, hall-b/ball-b): a picker-created endpoint's broker spawn become_controller->stamp_driven_by->set_controlled(true) fires BEFORE the adapter binds its perch (a fresh endpoint has no perch until claude boots + binds), so mutate_info returns NotFound and the edge stamp is SWALLOWED (let _); the adapter's bind then writes InfoJson::new with controlled:false DEFAULT and no later edge re-stamps -> the endpoint reads uncontrolled FOREVER while driven (the #3 display fix is correct but datum-starved on this creation path). FIX: the broker (SINGLE WRITER) re-asserts each live session's control/viewer stamps to session-table truth, DIVERGENCE-GATED (read info; compare driven_by/controlled/viewer_count; write ONLY on diff \u00e2\u20ac\u201d no per-poll fsync storm), on the KIND_SESSIONS handler (piggyback: the daemon reconcile + picker poll it, so a fresh perch converges within one reconcile-poll window after bind = the BOUNDED window, n",
    "doc": "7.28 A relative manifest path resolves against the ENDPOINT, never the daemon `[REQ-STORE-CONTEXT-BRANCH-FILL]`: <!-- --> ### 7.29 Control/viewer stamps CONVERGE to broker session-table truth, not merely edge-trigger `[REQ-HAZARD-CONTROL-STAMP-CONVERGENCE]` - **Failure (F-026 stamp-gap, hall-b + the original ball-b):** a picker-created endpoint (`endpoint run` \u00e2\u2020\u2019 new) read plain `ONLINE` in the list + picker while genuinely driven \u00e2\u20ac\u201d `info.json` `controlled:false` throughout. ROOT (the UPWARD companion to 7.27's downward edge-clear): the broker spawn path's `become_controller` \u00e2\u2020\u2019 `stamp_driven_by"
  },
  "REQ-MSG-DELIVERY-AXES": {
    "title": "Activity-gated inbound delivery + per-message send control as THREE ORTHOGONAL AXES plus opaque metadata (ADR-0028; grilled w/ operator 2026-06-23). SUBSTRATE (the legacy-SPT parity gap, scaffolded-but-unwired today: `delivery::is_idle` + `resolve_inject_methods` exist but the result is discarded `let _methods`, and `broker::dispatch_endpoint_input` injects unconditionally \u00e2\u20ac\u201d its comment calls activity-gating 'a deferred follow wave'): an inbound message has an ACTIVE window (endpoint active \u00e2\u2020\u2019 spool for the receiver's hook-poll, non-disruptive) and an IDLE window (idle/idle-transition \u00e2\u2020\u2019 deliver immediately: translation binary spt-hosted \u00e2\u2020\u2019 relay-poll either topology \u00e2\u2020\u2019 spool, in fallback order). AXES (each composes; each defaults to its unrestricted value): (1) DELIVERY WINDOW \u00e2\u20ac\u201d default (both, first-to-fire) | `--idle-only` (idle window; immediate if already idle) | `--active-only` (active window only, never wakes; the RENAMED `--deferred` \u00e2\u20ac\u201d `deferred=1` spool column + `api poll --include-deferred` keep their names). (2) CHANNEL RESTRICTION \u00e2\u20ac\u201d unrestricted | `--prefer-native` (translation binary if running else fall back) | `--force-native` (binary ONLY, no fallback/no spool-to-other-m",
    "doc": "Activity-gated message delivery + send-modifier axes: <!-- -->"
  },
  "REQ-CONN-POISON-ATTRIBUTION": {
    "title": "MSG-IDENTITY W6 / F-039 legs b-d (doyle W6 LOCK 2026-07-10, minted per amendment 3): every broker-conn lifecycle record is ATTRIBUTABLE \u00e2\u20ac\u201d the W6 RCA's terminal undecidability (per-line 1:1 CONN_WRITE_POISONED churn = fresh-carrier churn OR stderr interleave artifact) exists because records carry no stable conn identity, no role/endpoint/session context, and no timestamps, and the once-per-conn poison latch hides multiplicity. THREE LEGS. (b) IDENTITY: mint a stable per-physical-conn id (monotonic u64 at conn construction \u00e2\u20ac\u201d Arc::ptr_eq is the only identity today and it does not survive a log line) plus subscriber role and endpoint/session where known, stamped on CONN_WRITE_POISONED, CONN_WRITE_RETIRED, logical stall-evict, attach/resume/detach, and write-retirement records (RCA attach sites: presence nethost.rs:379, stream nethost.rs:258, controller broker.rs:891, viewer broker.rs:1073). (c) TIME: daemon stderr correlation records carry wall-clock AND monotonic timestamps (stderrlog has neither; broker+brain share one file \u00e2\u20ac\u201d interleave is unresolvable without them). (d) LIFECYCLE (doyle-confirmed UNCONDITIONAL, not debug-gated): one BOUNDED set of per-conn lifecycle events \u00e2\u20ac\u201d write s",
    "doc": ""
  },
  "REQ-DIGEST-GENERATION-SUPERSEDE": {
    "title": "W3 (LIFECYCLE-TRUTH, digest projection truth \u00e2\u20ac\u201d flynn filing spt-mobile d0aa3f4): a one-shot `endpoint digest --json` snapshot must return each logical activity row ONCE across a checkpoint/resume, not once per seq-generation. ROOT (spt-core-side, not a consumer bug): the K-session span (digest.rs activity_spanned, SPAN_SESSIONS=5) runs the [digest] extractor per session file and tags each row seq=(ledger_ordinal<<32)|localseq (REQ-DIGEST-CURSOR). A checkpoint/resume (self-/clear + Psyche rebuild) makes the harness REPLAY the prior generation's transcript into the NEW session file, so the ancestor's rows appear in BOTH the ancestor file AND the resume file at the SAME localseq \u00e2\u20ac\u201d the span UNIONS them, one logical row surfacing under two full seqs (gen23,local208) + (gen25,local208), identical text/ts/localseq. Consumers dedup by exact seq (the documented authoritative key) so nothing collapses -> duplicate rows in every snapshot / `--after` view (`--follow from:0` is CLEAN \u00e2\u20ac\u201d it reads current-generation only; the SPAN is the sole culprit). The trigger cannot disambiguate: `api boundary clear` records SessionTrigger::Clear for BOTH a fresh /clear (disjoint) and a carry-forward checkpoi",
    "doc": ""
  },
  "REQ-HAZARD-ECHO-BEFORE-SIGNOFF": {
    "title": "Echo-commune fires before INIT_SIGNOFF on orphan teardown (3.3)",
    "doc": ""
  },
  "REQ-MSG-3": {
    "title": "Ready-agent lifecycle: register perch (info.json + listener + registry address) on ready, drain spooled backlog on startup, clean teardown",
    "doc": ""
  },
  "REQ-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-IRRECOVERABLE-BEHIND": {
    "title": "DEFERRED EDGE of REQ-HAZARD-CONTROLLER-GAP-RESUME: when a serving controller falls behind the live ring FURTHER than the ring holds (tail - delivered_through > cap_chunks, the ring trim being delivered_through-blind, broker.rs:327-330), the dropped frames have rolled OUT of the ring and an exactly-once re-fetch is IMPOSSIBLE. v0.13.0 DETECTS this (resume floor unchanged across two consecutive resumes) and surfaces a MARKED truncation notice to the operator then ends the attach cleanly \u00e2\u20ac\u201d it never silent-skips (a B2 lie) and never spins. FULL graceful handling (a clearly-marked snap-with-data-loss that keeps the operator on the live tail, or a structured truncation record the rc renders distinctly, plus the controller-too-slow + ring-too-small backpressure/sizing policy) is DEFERRED \u00e2\u20ac\u201d staging it needs a netsplit / deep-behind harness (the in-process loopback rig keeps up; wedged_viewer's gap is recoverable at ~1492 < 4096). (v0.13.0+)",
    "doc": ""
  },
  "REQ-HAZARD-RC-ATTACH-FAILFAST": {
    "title": "B1: `spt rc <id>` to a DEAD or non-streaming session fails fast with a clear message, never an INFINITE blank screen. Today rc.rs run_attach (209-231) + pump spawns PUMP_IPC_READER and blocks: the poll times out each slice but the stream never produces output, so the operator sees a permanent blank (operator: fresh wall-f attached, closed tab, then `spt rc wall-f` HUNG \u00e2\u20ac\u201d the broker still resolved a session for it). FIX: (a) once B2 lands, gate attach on is_online/status \u00e2\u20ac\u201d an offline endpoint yields a clean 'endpoint offline, start it' not an attach; (b) fail-fast \u00e2\u20ac\u201d if the attach-open ack / first output does not arrive within a bound, surface a clear message, never an infinite blank; (c) the broker EOFs the attach stream when the session's child is dead, so rc's existing PumpEnd::BrokerGone graceful path (REQ-HAZARD-RC-EOF) catches it. PIN the exact sub-mechanism with a repro test FIRST (dead-session-lingers-in-broker vs reaped-but-rc-waits vs alive-resting-no-wake \u00e2\u20ac\u201d the wall-f Windows tab-close: child alive-silent vs dead-not-reaped). (v0.12.0)",
    "doc": ""
  },
  "REQ-MSG-SELF-DETECT-ANCESTRY": {
    "title": "#9 (F026, operator field bug): a perch-owned `spt send` from an endpoint's OWN session must self-identify, not mis-stamp `cli@<node>` (whose replies bounce NO_PERCH). ROOT: roster::detect_self_id (roster.rs) was ENV-ONLY \u00e2\u20ac\u201d OWL_SESSION_ID matched to info.session_id, else SPT_AGENT_ID \u00e2\u20ac\u201d but an agent-session Bash child often carries NEITHER (the env export is spawn-path-dependent), so a perch-owned sender was classified bare-CLI and REQ-MSG-CLI-ORIGIN stamped it cli@<node> (the stamp works as designed on a wrong premise; that REQ's evidence stays intact). FIX: detect_self_id gains leg (c) PID-ANCESTRY fallback AFTER the env legs \u00e2\u20ac\u201d walk THIS process's ancestry, match a live non-corrupt roster perch's recorded harness pid (info.json.pid Numeric, alive-gated via is_process_alive, corrupt/BUSY skipped), first match = self. from-LABEL / routing default ONLY, NOT authentication \u00e2\u20ac\u201d authenticate() is untouched (pid-ancestry-for-AUTH stays parked per F-024 with its Windows pid-spoof caveats; a display/routing stamp has no such bar). Best-effort: a broken ancestry walk degrades to None \u00e2\u2020\u2019 cli-stamp, never errors the send.",
    "doc": ""
  },
  "REQ-HAZARD-SOFT-CLEANUP": {
    "title": "Soft-cleanup preserves state, removes only the ready marker (6.2)",
    "doc": ""
  },
  "REQ-INST-6": {
    "title": "Deferred messages not delivered to dormant/suspended instances",
    "doc": "Deferred Features: | Feature | Cut from | Why deferred | Trigger to revisit | |---|---|---|---| | Scrollback on-disk spillover | terminal wrapper v1 | In-memory ring covers the common case; spillover adds a persistence/rotation story | First long-running session that overflows the ring usefully, or any \"scroll back further than the buffer\" user need | | Sidecar adapter process (long-running, wire-protocol) | harness contract v1 | Manifest + `spt.exe` subcommand surface covers v1 harnesses; sidecar only earns its keep for streaming / in-memory cross-event state | A harness outgrows manifest+hoo"
  },
  "REQ-HAZARD-INBOX-NO-DOUBLE": {
    "title": "No double-delivery via legacy inbox (4.5)",
    "doc": ""
  },
  "REQ-PICKER-PROJECT-DISPLAY-NAME": {
    "title": "A1 (F028, operator #1/#4/#6-display): `github-com-*` 'ghost' project entries are NOT phantoms \u00e2\u20ac\u201d project_id_for_dir (spt-store/src/project.rs:64) derives ids from the git remote slug BY DESIGN (REQ-STORE-1 cross-machine sync): `github.com/SaberMage/spt-core` -> `github-com-sabermage-spt-core`. The ref is truthful; the BUG is presentation \u00e2\u20ac\u201d the raw slug renders as the DISPLAY NAME everywhere (confirm-panel history view.rs:415-419, choose-project labels model.rs:314-337, resume-row titles, endpoint-list project column via latest_project_ref data.rs:417), which no operator recognizes as 'spt-core'. FIX: keep the slug as the KEY, render a friendly display name \u00e2\u20ac\u201d the repo tail (spt-core) reusing the disambiguate_project_ids (model.rs:346) suffix mechanism for collisions. One shared display-name seam across all four surfaces. See triage A1.",
    "doc": ""
  },
  "REQ-DOCS-6": {
    "title": "spt how-to <topic>: in-binary task-oriented agent instructions (anti-drift; quickstart prompts point agents at it)",
    "doc": "Quickstart: two agents exchange a message: <!-- the human killer quickstart: install -> two agents message, real captured outputs, <10 min; CI-run step for step by quickstart_e2e.rs --> <!-- the agent prompt blocks point agents at the in-binary `spt how-to` topics; this page never duplicates their text --> End to end in under 10 minutes. The roles matter here: **you** install (and optionally pair machines); **your agents** exchange the messages. You hand each agent a short prompt; the binary itself teaches them the rest."
  },
  "REQ-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-ENDPOINT-PURGE": {
    "title": "`spt endpoint purge <id>` fully removes an endpoint AND every record keyed on it \u00e2\u20ac\u201d the formal teardown devs/CI need for clean test setup/reset. NOT consent-gated (a local dev/test op \u00e2\u20ac\u201d no peer consent). OFFLINE-ONLY: refuses while the endpoint is online / daemon-hosted (deleting records out from under a live host risks the daemon re-creating or re-hosting mid-purge); `--force` STOPS it first (endpoint stop \u00e2\u2020\u2019 wait for the daemon reconcile to un-host + reap the Psyche) THEN purges. Confirms interactively unless `--yes` (the CI path). Refuses purging the CALLER's OWN running id. All LOCAL \u00e2\u20ac\u201d purge reaches only THIS node's records; a remote endpoint's records can't be touched, and its subnet-registry rows decay via the epoch-lease eviction (REQ-HAZARD-REGISTRY-DECAY). Removes: (1) the perch dir TREE recursively \u00e2\u20ac\u201d owlery/<id>/ incl every nested {id}-psyche / {id}-w* / shells child (info.json, ready marker, sessions.log ledger, spool.db, inbox, .idle/.more-done sentinels, auth token); (2) the registry address (registry::unregister_address); (3) the context store \u00e2\u20ac\u201d ContextStore::remove_endpoint(id): the a-<id> branch+worktree + the <id>/ rows from every p-<project> branch (the same fn `for",
    "doc": "Inbound `api` surface (detailed): **`spt endpoint purge <id>`** (CLI, not `api`) \u00e2\u20ac\u201d the standalone, formal **full teardown**: wipe an endpoint and *every* record keyed on it. It is the dev/CI sibling of `api session-end --erase` (which is adapter-triggered at session end); `purge` is the explicit operator/test command for clean setup-and-reset. **Deliberately NOT consent-gated** \u00e2\u20ac\u201d a local dev/test op, never a peer-visible action. **Offline-only**: it refuses a live / daemon-hosted endpoint (deleting records out from under a running host would let the daemon re-create or re-host mid-purge); **`-"
  },
  "REQ-MANIFEST-1": {
    "title": "Per-adapter manifest with adapter_name and min_spt_core_version",
    "doc": "What is NOT in the manifest (spt-core-owned): <!-- The sections below are the authoritative schema; the typed form in `crates/spt-runtime/src/manifest.rs` is kept in lockstep. -->"
  },
  "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-CI-FREE-SPACE-PREFLIGHT": {
    "title": "Every CI job on a shared self-hosted runner FAILS LOUD AND FIRST on insufficient free disk, never obscurely on a downstream timing/space symptom: a preflight step asserts a free-space floor on the workspace drive and fails naming the RESOURCE (free bytes, floor, drive) so the next exhaustion costs one clear red instead of a release window of mystery flakes (proposer: deployah, during the v0.44.0 disk-exhaustion incident \u00e2\u20ac\u201d the rule-failed-its-author-needs-a-mechanism shape: 'check disk first' was already a memory rule and still cost two CI windows because it lived in judgement, not mechanism). Floor calibration honest at mint: the observed incident burned tens of GB per suite run (leaked fixture dirs + build targets); pick a floor from measured suite consumption plus headroom and RECORD the derivation in the workflow comment \u00e2\u20ac\u201d an unexplained magic number invites drift.",
    "doc": ""
  },
  "REQ-LISTEN-SESSION-ID-FALLBACK": {
    "title": "F-034 leg c (perri/hertz field finding 2026-07-09): a session that goes live LATE (hours after SessionStart, or after a daemon restart) must still be able to bind \u00e2\u20ac\u201d the ephemeral SessionStart seed ('consumed within seconds') is GONE by then and nothing re-fires it until the NEXT SessionStart, so even `api listen --parent-pid <correct claude pid>` hits NO_SEED. Design assumption 're-fired on the next SessionStart if needed' does not hold for long-lived sessions. FIX (perri-recommended, cleanest): `api listen --session-id <sid>` fallback that binds from the session-id when the pid has no live seed \u00e2\u20ac\u201d removes the ephemeral-seed dependency entirely (the adapter already knows the sid; the skill passes it, and can then DROP its manual re-seed step). Alternative (option 1, less clean): re-fire the seed on daemon restart. Gate: a session with NO live seed (expired / post-daemon-restart) binds via `listen --session-id <sid>` (no NO_SEED); the sid-bind carries the same identity/auth the seed-bind would (session_id custody \u00e2\u20ac\u201d kin REQ-PSYCHE-SID-CUSTODY / the sid-symmetric-auth pattern). Files: api-listen bind path (sid-fallback seam), clap --session-id flag (plain doc-comment). hertz/perri live",
    "doc": "`api seed --pid <pid> --session-id <id>`: <!-- --> **Seed lifetime.** The seed lives **in the daemon's memory only** \u00e2\u20ac\u201d no file \u00e2\u20ac\u201d and survives until exactly one of: a successful `listen` bind consumes it, a newer `seed` for the same pid overwrites it, or the daemon process restarts (which drops the whole map). Nothing re-fires it until the harness's **next** SessionStart. So an adapter must not rely on the seed for a session that goes live late (hours after SessionStart) or after a daemon restart \u00e2\u20ac\u201d that is what `listen --session-id` (below) is for."
  },
  "REQ-SPAWN-FRESH-TRUTHFUL": {
    "title": "REGISTRY-LIFECYCLE W3 (ADR-0041 decision 5, spawn-fresh): endpoint create is TRUTHFUL at the broker, atomically \u00e2\u20ac\u201d spawn policies split Fresh/RejectExisting vs ResumeOrReuse; new KIND_SPAWN_FRESH wire verb with TYPED conflict (distinct kind fails LOUD on an N-1 broker vs a silently-ignored serde-defaulted field), sharing the single-flight/PTY spawn body; Fresh at AlreadyLive/lost-race returns the typed conflict NEVER Spawned(existing); CLI contract exit 1 + stable ENDPOINT_CREATE_CONFLICT:<id> token + no attach + no state mutation + never kills the live session; omitted --resume keeps the documented create-default (fresh-and-conflict, same as explicit --create); preflight stays UX-only (cannot close TOCTOU). Gate: impl \u00e2\u20ac\u201d policy split + verb + CLI contract; unit \u00e2\u20ac\u201d intent-aware decision matrix; int \u00e2\u20ac\u201d create-over-live E2E (exit 1, exact token, no attach, same broker PID) + concurrent-fresh race (one Created one Conflict, never two children, never Reused-as-success) + mixed fresh/resume race + offline/raw-Active+stale-ledger create + N-1 IPC loud-fail never-Created; doc \u00e2\u20ac\u201d ADR-0041 + CLI reference regen (docs-drift gate: xtask gen, no internal codes in clap help).",
    "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-EP-1": {
    "title": "Day-one endpoint types; open type system",
    "doc": ""
  },
  "REQ-CARRIER-CLAIM-EXCLUSIVE": {
    "title": "MSG-IDENTITY W4 / F-033 + operator self-send probe 2026-07-09 (dup-delivery cluster, RCA-FIRST): a spooled message row is delivered by EXACTLY ONE carrier \u00e2\u20ac\u201d the first carrier to take a row (hook-poll drain, relay idle-inject, relay-backlog, psyche) atomically CLAIMS it so no other carrier can re-deliver the same row. FIELD EVIDENCE (authoritative, operator-observed): a default-window doyle-to-doyle send while doyle was BUSY delivered on BOTH the busy POLL path AND the idle RELAY path; spool row 156: window='default', delivered=1, taken_leg='idle-inject' \u00e2\u20ac\u201d the relay claimed a row a poll also surfaced (REQ-SPOOL-TAKE-AUDIT instrument, already shipped, is the RCA tool: taken_leg/taken_sid/taken_at per row). PRIOR: F-033 (perri 2026-07-08) \u00e2\u20ac\u201d the psyche-download filing arrived as TWO copies, dup-delivery live-confirmed. RCA-FIRST (report-before-fix): pin whether the poll drains before/after the relay's delivered=1 mark; whether the busy-to-idle edge re-offers a row a poll already took; whether take-marking is atomic per carrier or check-then-mark racy. DISTINCT from F-035 (that = active_only window honor, spt-core exonerated; THIS = a default msg on both carriers \u00e2\u20ac\u201d F-035's lock never as",
    "doc": ""
  },
  "REQ-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-CONTROLLER-LIVENESS-REAP": {
    "title": "B-2 (REMOTE-TRUTH triage \u00c2\u00a7B-2, REDUCED @bdc1242): a stale ONLINE+CONTROLLED stamp on a live-session perch self-heals \u00e2\u20ac\u201d the info.json driven_by/controlled RECORD is made to match the broker's SINK-TABLE TRUTH. ROOT (persisted-stale-stamp class, doyle Q1): the livehost control reap gates on !has_session (reconcile_hosted_liveness), and a brain-only update KEEPS the session (REQ-UPD-3), so a controller stamp that went stale WHILE the session lived was never re-derived from broker truth. NOT a transport bug: (a) a persisted conn is the REQ-UPD-3 feature; (b) an idle-severed conn eventually EOFs via QUIC keepalive \u00e2\u2020\u2019 handle_conn detach (path 1) \u00e2\u20ac\u201d and the reason paths 1-3 previously failed to clean up was the B-1 broker floor-lock POISON WEDGE (cleanup panicked under the poisoned lock), now fixed. REDUCTION (doyle, my ground-truth): the prescription was 80% pre-built \u00e2\u20ac\u201d converge_perch_stamps (broker.rs, REQ-HAZARD-CONTROL-STAMP-CONVERGENCE) ALREADY converges info.json driven_by/controlled to the broker's controller_by/has_controller on EVERY KIND_SESSIONS poll, and the livehost reconcile already TRIGGERS that poll per tick (query_live_session_endpoints). So NO new IPC query, NO new livehos",
    "doc": ""
  },
  "REQ-RUN-PICKER": {
    "title": "Interactive `spt endpoint run` picker (ratatui TUI): bare `spt endpoint run` (no --adapter/--id) enters an in-process picker (flags-present = the REQ-HOST-RUN-1 non-interactive path, untouched). Layer 1 picks kind (Create new | Pick existing). Create-new: choose a registered kind=\"harness\" adapter with its shipped+local profiles tree-nested (registry::registered / manifest.profiles / local_profile_names) \u00e2\u2020\u2019 enter a charset-validated id \u00e2\u2020\u2019 start. Pick-existing: category select (left/right) over [<cwd-project> | Local node | Subnet], endpoints grouped + alphabetically sorted per category, a status square per endpoint (online green \u00e2\u2013\u00a0 / offline gray \u00e2\u2013\u00a2 \u00e2\u20ac\u201d the blue \"attached\" tri-state + Kick are DEFERRED to a broker attach-presence slice, M12-W2-RULING Q1), type-to-filter (`/`, nucleo-matcher), a pinned keybind legend, and a right-half two-pane description (harness adapter:profile \u00c2\u00b7 best-effort project history newest\u00e2\u2020\u2019oldest from the contextstore p-<project> branches, empty-if-none \u00c2\u00b7 `spt endpoint description`). Confirm layer offers status-dependent options \u00e2\u20ac\u201d Attach/Start/View (rc pump / cmd_endpoint_run) \u00c2\u00b7 Instantiate-locally (remote) \u00c2\u00b7 Change-harness-adapter (offline) \u00c2\u00b7 Fork (cmd_fork) \u00c2\u00b7 ",
    "doc": "Shell sleep/wake (offline \u00e2\u2020\u201d online): **spt-hosted bringup picker (`spt endpoint run`)** (M12-W2): <!-- --> The user-facing bringup flow for spt-hosted endpoints. **Bare `spt endpoint run`** (no `--adapter`/`--id`) opens an in-process **ratatui picker**; the **flagged** form is the non-interactive bringup path (`--adapter <a[:profile]> --id <id> --create|--resume <session> --start|--attach|--view`), untouched \u00e2\u20ac\u201d a picker selection bakes exactly that path. **Layer 1** picks the kind (*Create new* | *Pick existing*). **Create-new** chooses a registered `kind=\"harness\"` adapter with its shipped+loc"
  },
  "REQ-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-HAZARD-INJECT-SETTLE-REARM": {
    "title": "post-0.29.0 (KNOWN-HAZARDS 7.37): the Layer-1 settle-gate must RE-ARM before every delivery on an OBSERVABLE (echoing/interactive) PTY \u00e2\u20ac\u201d a mid-session reader reattach re-creates the head-swallow window. ROOT (field-confirmed on 0.29.0, doyle diagnosis + perri screenshot): the shipped W5-A settle-gate (REQ-INJECT-MULTILINE-INTEGRITY) gated Layer 1 behind a worker-local ONE-SHOT (`settled_once`) on the false premise that the head-swallow race is STARTUP-only (reader not attached after spawn). A mid-session `/clear` re-enters the harness's raw-mode input reader, re-creating the pre-settle window \u00e2\u20ac\u201d but the one-shot already fired at spawn, so `settle_before_inject` is SKIPPED and the head is eaten again (a checkpoint-wake payload injected right after `/clear` lost its head, mid-path `spt/Cargo.toml)`); echo-verify (Layer 2) is default-OFF for that session, so it is silent + unrecoverable. FIX (doyle ruling): re-settle before EVERY delivery on an observable PTY; latch-skip the steady-state settle ONLY where the probe is UNOBSERVABLE (non-echoing ConPTY \u00e2\u20ac\u201d no reader-reattach race to guard, and each settle burns the full deadline). The settle's own bool return (observed vs timed-out) discri",
    "doc": "7.36 The broker control plane and PTY fan-out must NEVER block on a single subscriber connection \u00e2\u20ac\u201d a suspended brain conn must not wedge control `[REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]`: ### 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]` <!-- --> - **Failure (paid-for, field-CONFIRMED on 0.29.0 \u00e2\u20ac\u201d doyle diagnosis + perri screenshot `WindowsTerminal_6iSjya8pMt.png`):** the shipped W5-A settle-gate (7-of-W5, `REQ-INJECT-MULTILINE-INTEGRITY`) gated Layer 1 b"
  },
  "REQ-HAZARD-ENVELOPE-DECODE-ORDER": {
    "title": "Envelope decode order, ampersand decoded last (4.1)",
    "doc": "Body and attribute encoding: <!-- --> **Decode order is binding.** Decode a *body* as: `<br>` \u00e2\u2020\u2019 `\\n` **first**, then `&lt;`/`&gt;`/`&quot;`, then `&amp;` \u00e2\u2020\u2019 `&` **last**. Decode an *attribute value* the same way minus the `<br>` step. Amp-last is the invariant that prevents double-decoding: a body carrying the literal text `&lt;` arrives as `&amp;lt;`, and decoding the ampersand first would turn it into `<` instead of `&lt;`. And decode **only the extracted body or attribute substring** \u00e2\u20ac\u201d never run the unescape over the full envelope line, or the framing tokens themselves get rewritten."
  },
  "REQ-RESUME-HARNESS-SESSION-ID": {
    "title": "B2 (F028, hall-b diagnosis, verified 0.22.0): respawn/`--resume` feeds the SPT session id to `claude -r`. After the 02:05 daemon bounce respawn built `claude.exe -r 70b5bfa40901b7d4` \u00e2\u20ac\u201d an spt session id in claude's OWN session-id namespace -> claude hangs forever at a 'No sessions match' resume-picker while the endpoint reads online. Hits after EVERY daemon bounce + every picker Resume. The HARNESS session id (claude UUID, stamped in sessions.log/info.json by the hooks) is what {session_id} must mean in the adapter's [session.resume] command; the spt sid must not leak. FIX: substitute the HARNESS session id in the resume template (spt-core substitution-key semantics + LIKELY claude-spt manifest coordination \u00e2\u20ac\u201d FLAG perri BEFORE touching the manifest, adapter-boundary rule). Int: resume template receives the ledger UUID, not the spt sid. See triage B2.",
    "doc": ""
  },
  "REQ-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-ATTACH-RESIZE-REPAINT": {
    "title": "RESCOPED 2026-07-22 by ADR-0047 Amendment 2 (doyle-ratified): this REQ no longer asserts a defect. Its original claim \u00e2\u20ac\u201d that the attach-stream resize never arms the presentation barrier, and that this is v0.39.4 field bug 3's root \u00e2\u20ac\u201d is RETRACTED; the W3 bisect falsified it three ways (static trace send_attach_resize -> serve_attach Resize arm -> brain.resize -> KIND_RESIZE -> dispatch_resize -> begin_resize_serialized; the synthetic sibling's pass; the broker's dynamic trace attach -> RESIZE_DISPATCH -> SYNC_PUSH{controller=true} with no RESIZE_SYNC_DROP) and then pinned the real root in the rc CLIENT's console mode (REQ-RC-NEWLINE-PRESENTATION-TRUTH / KNOWN-HAZARDS 7.56). The ADR-0047 Amendment 1 DECISION (geometry change = one transaction, one exit shape, any entry) stands on its own merits and is not disturbed. WHAT THIS REQ NOW OWNS \u00e2\u20ac\u201d the captured-shape regression apparatus that the bisect proved worth keeping: hertz's ENLYZEAM production capture + its integrity pins (*.log -text, the fixture README sha256 table, per-record length refusal in both the player and the reader); the capture-player replay vehicle; and the probe ladder that makes any verdict from it mean something \u00e2\u20ac\u201d v",
    "doc": "7.54 A geometry change invalidates the client's painted cells by itself \u00e2\u20ac\u201d every resize entry emits the authoritative repaint, and an attach's old-geometry repaint is never the final word `[REQ-ATTACH-RESIZE-REPAINT]`: <!-- --> - **Failure (paid-for THREE TIMES \u00e2\u20ac\u201d v0.39.3 geometry-epoch, v0.39.4 presentation barrier, and still red in the field; pinned by hertz's ENLYZEAM production byte capture 2026-07-22, doyle seam-verified same day):** the codebase has TWO resize entries. The IPC `ResizeReq` path is sound post-0.39.4 (arm barrier \u00e2\u2020\u2019 settle \u00e2\u2020\u2019 issue \u00e2\u2020\u2019 commit pushes the authoritative repaint at t"
  },
  "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-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-MSG-ENVELOPE": {
    "title": "The <EVENT type=\"msg\" from=\u00e2\u20ac\u00a6>body</EVENT> envelope (spt-proto::event, the ADR-0001 grammar) is the SOLE canonical arriving-message format at EVERY harness arriving-message surface on an AGENT perch \u00e2\u20ac\u201d api listen AND api poll/worker-poll, byte-identical (reverses REQ-MSG-4's 'hook drains keep the raw frame by contract'). SCOPE CARVE-OUT: the shell-command relay (api poll <shell-id> --link, cmd_poll_shell) is a distinct internal transport carrying RAW MAC'd stamped frames the shell child consumes verbatim \u00e2\u20ac\u201d NOT an arriving-message surface, deliberately EXEMPT from <EVENT> composition (notify_shell_e2e guards this boundary). __REPLY_TO__ \u00e2\u20ac\u201d mis-elevated during the clean-room port to a fake ADR-0001 'stable wire format' (spt-msg/wire.rs, lib.rs) \u00e2\u20ac\u201d is REMOVED entirely (spool format_row, the spt-msg TCP frame, emit parse_frame); (from, body) carried structurally, <EVENT> composed once at the delivery boundary. No legacy sister-interop (spt-core never required it). Reply-correlation rebinds onto the structural from / <EVENT from=\u00e2\u20ac\u00a6> attribute (ADR-0009 access-gate + ADR-0012 Psyche/spt-live reply-target). Self-delimiting by construction \u00e2\u2020\u2019 finding F-002 (non-self-delimiting multi-message poll",
    "doc": "Decision: <!-- -->"
  },
  "REQ-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-START-5": {
    "title": "Adapter-agnostic harness-hosted seed + bind-time adapter/profile resolution (ADR-0021): `api seed` carries only parent_pid + session_id (+ optional cwd), no --adapter \u00e2\u20ac\u201d a pure \"a harness session exists at this pid\" record; --adapter becomes an OPTIONAL override across the whole api group (an explicit name[:profile] for adapter dev, never required). Omitted, listen/poll resolve the owning adapter/profile AT BIND as a pure read against the live registry \u00e2\u20ac\u201d never a seed-time snapshot that can drift: seed parent_pid \u00e2\u2020\u2019 exe basename \u00e2\u2020\u2019 host_binaries candidate set (REQ-MANIFEST-8) \u00e2\u2020\u2019 active-profile pointer (REQ-INSTALL-12) primary, else greatest-registered_at_ms candidate base profile (name-asc tie) \u00e2\u2020\u2019 friendly zero-match error. Covers BOTH LiveAgent (listen) and ReadyAgent (poll) bringup. Restores legacy parity: `$LIVE start <id>` \u00e2\u2020\u2019 `$SPT listen <id>` with no mandatory --adapter, one generic SessionStart hook per harness binary. (v0.9.0)",
    "doc": "Startup flows (the two topologies): **Harness-hosted (e.g. spt-plugin; the harness binary is user-launched, harness is the parent).** Key constraint: the SPT *live agent* does not exist until the agent invokes start \u00e2\u20ac\u201d the `live_id` isn't chosen at session boot, and `$LIVE start` is itself invoked *behind the Monitor tool*, so it becomes the long-running relay. So binding cannot happen at SessionStart directly. A **seed record** (daemon-held, in-memory \u00e2\u20ac\u201d not a file) bridges the gap: 1. The harness's SessionStart hook calls **`spt api seed --pid <parent_pid> --session-id <sid> [cwd]`**. The daem"
  },
  "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-CASCADE-WIPE-GUARD": {
    "title": "No hard-delete of a parent hosting non-empty children (6.3)",
    "doc": ""
  },
  "REQ-REGISTRY-APPLY-TRANSACTIONAL": {
    "title": "REGISTRY-LIFECYCLE W1 (ADR-0040 decision 3, hertz defect B leg 3): registry feed application is TRANSACTIONAL per feed \u00e2\u20ac\u201d serve_registry_feed merges decoded labels + instance updates in memory across transport chunks and applies ONCE at EOF (or one bounded batch commit for oversized feeds); write_snapshots runs O(feeds), never O(chunks x record-kinds); attention-shift side effects fire once post-merge. No synchronous full-registry rewrite inside a per-chunk drain iteration (KH 7.12/7.43 discipline on the brain side \u00e2\u20ac\u201d the per-chunk rewrites are what stalled IPC drain and manufactured the 15s seat-writer poisons). Gate: impl \u00e2\u20ac\u201d accumulate-then-apply; unit \u00e2\u20ac\u201d snapshot-write counter across a multi-chunk feed == 1 (bounded batches: == ceil(records/batch)), merge result equals per-chunk semantics, gate policy still applied per record; int \u00e2\u20ac\u201d rides REQ-HAZARD-REGISTRY-STALL seam (snapshot writes O(feeds), zero poisons); doc \u00e2\u20ac\u201d ADR-0040.",
    "doc": "Decision: <!-- --> 1. **One-way (fire-and-forget) stream families are terminal at FIN, sender-side.** The registry feed pump retires its own row after successful write+FIN via the existing `net-stream-retire` verb (best-effort on N-1 brokers, per ADR-0038 A). A one-way family's exchange is definitionally over at FIN; keeping the row eligible reproduces the O(history) defect forever. 2. **Eligibility filtering is server-side.** `stream_infos` excludes `initiated_locally` rows (alongside `retired`) before serializing. Consumers keep their client-side guards (double-filter harmless; N-1 compatibl"
  },
  "REQ-PRESENCE-CONTROL-REAP-ON-EXIT": {
    "title": "B3 (F028, hall-b diagnosis + deferred #11 seed; BROADENED perri F-b CONFIRMED): dead-pid ONLINE decay window + sticky CONTROLLED stamp. Repro: /exit -> all endpoint processes dead -> `endpoint list` stays \u00e2\u2013\u00a0 ONLINE for a decay window before OFFLINE. Sticky CONTROLLED: perri confirmed controlled=true + attached_node SET while alive=false/OFFLINE, persisting >20min AND ACROSS A DAEMON RESTART (hall-b) \u00e2\u20ac\u201d worse than the SIGKILL>=5min original (CAVEAT still: may reflect claude's --remote-control channel not the PTY attach \u00e2\u20ac\u201d DISAMBIGUATE first). This is the deferred #11; RCA belongs to this wave. FIX: reap must clear presence AND control stamps promptly across FOUR paths \u00e2\u20ac\u201d (i) clean exit, (ii) serve conn-drop, (iii) session-died-without-exit (crash/bounce), (iv) a BOOT-TIME sweep so a restarted daemon does NOT resurrect control stamps for endpoints it can see are dead. Int tests per edge. Closes A2(b). See triage B3 (broadened).",
    "doc": ""
  },
  "REQ-DIGEST-PROFILE-ENV": {
    "title": "Bug #17: spt endpoint digest returns NO_DIGEST for a ccs-profile endpoint (claude-spt:ccs) though [digest] is wired and the transcript exists \u00e2\u20ac\u201d under .ccs (CLAUDE_CONFIG_DIR relocation) not .claude. The on-demand digest runs the extractor in the daemon context WITHOUT the endpoint profile transcript-location env, so the env-aware resolver cannot find the relocated transcript. Fix: propagate/persist the endpoint profile transcript-location env (e.g. the ccs CLAUDE_CONFIG_DIR) to the on-demand digest extractor so a profile-relocated transcript resolves; confirm the exact extractor verdict via spt adapter digest-proof. Ownership spt-core (digest env/profile propagation), possibly with a claude-spt extractor-resolver assist. See docs/NEXT-MILESTONE-BUG-TRIAGE.md #17.",
    "doc": "`[env]` \u00e2\u20ac\u201d env-var table: **`direction = \"read\"` \u00e2\u20ac\u201d capture a launch-env var for template substitution.** <!-- --> A `read` directive names an environment variable spt-core **captures from the session's launch environment at bind** and then exposes as a `{VAR}` substitution key in `[digest].source` and `[history].locate_template`. This is how an adapter whose harness stores its transcript under a **relocatable root** (e.g. Claude Code's `CLAUDE_CONFIG_DIR`, which a profile like `ccs` repoints) makes that root resolve at digest time \u00e2\u20ac\u201d the on-demand digest runs later in the daemon context where th"
  },
  "REQ-SEAM-RESUME": {
    "title": "resume-session seam (fresh-with-preload / continue-existing)",
    "doc": ""
  },
  "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-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-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-HAZARD-HANDOFF-ARGV-COMPAT": {
    "title": "Broker/brain IPC + handoff argv version-tolerant (2.3)",
    "doc": ""
  },
  "REQ-STORE-1": {
    "title": "spt-store::BranchStore (git branch as versioned KV; commit=checkpoint/tip=resume, atomic multi-key, merge-native sync) is the substrate for coarse/durable/audited state (context, registry snapshot+distribution, daemon checkpoint); hot paths (B5 fsync journal) + indexed queries (SQLite spool) excluded (ADR-0011)",
    "doc": ""
  },
  "REQ-PICKER-UX-V013": {
    "title": "`spt endpoint run` picker UX (v0.13.0 operator dogfooding): (1) SKIP the first screen \u00e2\u20ac\u201d open directly on 'Pick existing'; `n` jumps to 'Create new'. (2) AUTO-ATTACH after both Start-new AND Resume-from-history (both currently don't attach and show no stdout); add an `h` shortcut to run headless (no attach). (3) 'controlled by' shows the node NAME (node_label_display), not the raw hex. (4) Clean up Start-new output \u00e2\u20ac\u201d drop the Rust `pid=Some(142748)` leak and the 'harness binds its perch on startup' internals; user-friendly, not a process log. (v0.13.0)",
    "doc": ""
  },
  "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-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-DAEMON-STOP-REAP": {
    "title": "Breap: `spt daemon stop` REAPS the spt-hosted children it spawned \u00e2\u20ac\u201d no orphaned psyche/harness processes. Today a stop leaves ~8 orphaned claude-spt-psyche.exe + spt.exe: Psyches are spawned DETACHED (runtime.rs:342-356, the Child is dropped \u00e2\u20ac\u201d 'Detached' ~349) and the livehost stop flag Arc<AtomicBool> is NEVER raised (brainproc.rs:227-230 holds it 'for symmetry'). FIX: on stop, raise the livehost stop flag AND kill the spawned psyche/spt-hosted children \u00e2\u20ac\u201d via a Windows job object / Unix process-group so the children die with the daemon (not detached-immortal). Folds with B3 (both the stop path). (v0.12.0)",
    "doc": ""
  },
  "REQ-UPD-6": {
    "title": "Platform-targeted update sets and debug rollout: signed multi-platform update metadata, recipient platform selection, channel-scoped monotonic counters, debug-channel opt-in via release-key overlay, local staging plus pull-based peer propagation, and maintainer-only convergence tooling (ADR-0016)",
    "doc": "Build plan \u00e2\u20ac\u201d `xtask debug-converge` (deferred follow-up): <!-- --> // Debug rollout runbook: <!-- -->"
  },
  "REQ-MANIFEST-NODE-KEY": {
    "title": "A new session-scoped manifest fill key `{node}` resolves to THIS node's advertised label \u00e2\u20ac\u201d available wherever the session-scoped keys ({id}/{session_id}/{session_name}) populate: BOTH topologies' spawn-prep catalogs (harnesshost.rs:111-118 self-spawn guaranteed-fill + lifecycle.rs:280 base lifecycle keys, at minimum [session.self] and [session.resume]) AND lazy [strings] eligibility (ADR-0029 family). VALUE (design-true per CONTEXT \u00c2\u00a7node label / REQ-SUBNET-3): the node's ADVERTISED LABEL \u00e2\u20ac\u201d the same value node_label_display renders \u00e2\u20ac\u201d read from the label store (NodeLabel, registry.rs:118/220, OS-hostname default re-checked at daemon startup), NOT the pubkey and NOT a fresh gethostname at fill time when the store already holds the refreshed label; fall back to the OS hostname only if no label is known. perri's concrete use: templating `--remote-control {id}--{node}` in the claude-spt launch/resume commands. CAVEAT (documented in the manifest.md key-table row AND here): SINGLE-TOKEN fills only \u00e2\u20ac\u201d tokenize-then-fill (REQ post-F-009) cannot produce a space-carrying argv element, so composite display names like `<id> @ <node>` remain adapter-shim territory (claude-spt v0.10.3's launch shim",
    "doc": "Claude Code: native resume into an existing transcript by id, in its project cwd.: <!-- --> ```toml [session.self] # {node} fills as one argv token \u00e2\u20ac\u201d the node's advertised label (its hostname). command = \"claude --session-id {session_id} --remote-control {id}--{node}\" keys = [\"session_id\", \"id\", \"node\"] ```"
  },
  "REQ-ENDPOINT-AUTOSTART": {
    "title": "MSG-IDENTITY W5 / F-038 (flynn operator-directed ask 2026-07-10, SPT-CORE-NEEDS #7 + deployah field-confirm same day: mobile-gw alive=false after the v0.30.6 full daemon restart = this feature's absence, live): an endpoint can be marked a STARTUP DEFAULT so the daemon brings it back up at daemon start \u00e2\u20ac\u201d Gateway-class endpoints are infra (the phone treats mobile-gw as always-there; box reboot / daemon cold start currently leaves it down until hands-on). SHAPE RULED (doyle, dispatch): (a) `spt endpoint run --save` persists the run (id + adapter/profile + args) as a startup default REPLAYED at daemon start, symmetric with the shipped `subnet attach/detach --save` precedent \u00e2\u20ac\u201d smallest orthogonal cut, explicit operator intent, no interaction with effective_rest_state/F-035 reader-parity semantics (shape (c) restore-what-was-up REJECTED for now: principled but couples to the rest_state neighborhood that just churned; revisit if --save proves insufficient in the field). A saved endpoint that fails to come up logs loud + does not block daemon start or other replays. flynn docs sweep confirmed missing-feature not docs-gap (rest/wake manual-only; no endpoint analog of subnet --save; no manif",
    "doc": "Startup defaults (`endpoint run --save`): <!-- --> Infrastructure endpoints (a gateway the phone treats as always-there) should not need hands-on bringup after a box reboot or daemon restart. `spt endpoint run \u00e2\u20ac\u00a6 --save` persists the run \u00e2\u20ac\u201d endpoint id, adapter option, and working directory \u00e2\u20ac\u201d as a **startup default** in `daemon.json`; the daemon **replays** every saved default when it starts, as a fresh session with the adapter re-resolved at replay time. One entry per endpoint id (a re-save replaces the prior one); remove the entry from `daemon.json`'s `startup_endpoints` to stop auto-starting"
  },
  "REQ-HAZARD-REDISPATCH-STALL": {
    "title": "REDISPATCH-STALL W1 (KNOWN-HAZARDS 7.43, hertz v0.34 field RCA 2026-07-16 \u00e2\u20ac\u201d recurrent 20-30s PTY/RC freezes, 17-62s DISPATCH tails): one wedged stream subscriber must NEVER stall stream serving, and recovery machinery must not manufacture new replay victims. Today: claim retries x the broad Err(_) opener fallback (dispatch.rs:414) install throwaway peek subscribers whose StreamLog::attach replays the entire retained ring UNDER the per-stream mutex with discarded write errors and the poisoned subscriber left installed \u00e2\u20ac\u201d serial 15s bounded-write poison windows (33 observed, all 15,000-15,154ms) composing into the field stalls. Gate: int \u00e2\u20ac\u201d production-path regression at the REAL run_dispatch_loop + StreamLog + serve_attach seams: wedge one subscriber conn, prove producer appends and unrelated streams stay flat while the poisoned subscriber is removed and the stream recovers (no abandonment); doc \u00e2\u20ac\u201d KNOWN-HAZARDS 7.43. Binding: redispatch D1/D1b stay green every leg. HEAVY nextest group at birth. Kin REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE + REQ-DISPATCH-FALLBACK-CIRCUIT (the mechanisms), REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK (the deadline that fires), ADR-0038 Amendment.",
    "doc": "7.42 A node holding a valid roster address for a peer is NEVER route-less \u00e2\u20ac\u201d a failed dial must not delete the only bootstrap route `[REQ-HAZARD-MESH-BOOTSTRAP-TRAP]`: ### 7.43 One wedged stream subscriber must NEVER stall stream serving \u00e2\u20ac\u201d replay halts at the first failed write, a poisoned subscriber is removed, and recovery machinery must not manufacture new replay victims `[REQ-HAZARD-REDISPATCH-STALL]` <!-- --> - **Failure (paid-for, hertz field RCA 2026-07-16 \u00e2\u20ac\u201d live v0.34 boxes, recurrent 20\u00e2\u20ac\u201c30s PTY/RC freezes, DISPATCH tails 17\u00e2\u20ac\u201c62s):** a COMPOSITION, not one new timer. The dispatcher's ret"
  },
  "REQ-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-HAZARD-EVENTPART-REASSEMBLY": {
    "title": "EVENT-PART split/reassembly is byte-exact; orphan parts dropped silently",
    "doc": "The `<EVENT>` wire contract: <!-- --> ### EVENT-PART reassembly (listener stream)"
  },
  "REQ-HAZARD-DEFERRED-SURVIVE-DRAIN": {
    "title": "Deferred rows survive poll drain (4.4)",
    "doc": ""
  },
  "REQ-WORKER-PICKER-EXCLUDED": {
    "title": "V-2 (WORKER-TRUTH triage, operator rider): non-drivable endpoint classes never render as `spt endpoint run` picker rows \u00e2\u20ac\u201d a worker perch cannot be driven, instantiated, or controlled; offering it is a lie the picker then fails on. Filter endpoint_type worker (and the psyche class if it ever surfaces \u00e2\u20ac\u201d same non-drivable family) at every picker source leg, extend-not-multiply for future non-drivable classes.",
    "doc": ""
  },
  "REQ-HAZARD-DIRECT-WRITE-PRECEDENCE": {
    "title": "Direct-write precedence marker (with node id) guards stale overwrite (6.5)",
    "doc": ""
  },
  "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-ENDPOINT-MESSAGE-ONLY-DISPLAY": {
    "title": "An online agent-family endpoint with NO session surface reads as message-reachable, not as a harness-hosted live agent and not as a plain ONLINE. (hertz v0.39.0 field report 2026-07-21; doyle PRODUCT RULING \u00e2\u20ac\u201d deliberately NEITHER of the two options offered.) OBSERVED: an adapterless `spt ready` perch (type=ready_agent, adapter=null, ready/alive true) displays plain ONLINE with '(unknown adapter)'. SOURCE: picker/model.rs display_status returns Online for every non-live_agent type (~680-681) BEFORE consulting controllable, and amber HarnessOnly is live-agent-only (~683-687). RULING: that type gate is CORRECT and STAYS. 'ONLINE - HARNESS ONLY' means one specific thing \u00e2\u20ac\u201d a LIVE AGENT whose session surface is owned by a harness rather than a broker PTY. Broadening it to 'online non-controllable agent-family' would make one label mean two different things, which is how a status label starts lying. An adapterless ready receiver is a THIRD truth: message-reachable, no session surface at all, nothing to attach to ever. So: a DISTINCT display state (working name 'ONLINE - MESSAGE ONLY') keyed on the endpoint TYPE (ready_agent), never on absence-of-adapter, and never an invented adapter name",
    "doc": ""
  },
  "REQ-HAZARD-DETACHED-PIPE-INHERIT": {
    "title": "Windows detached long-lived children must not inherit a captured caller's pipe: every detach-spawn of an immortal child (daemon, shell binary) runs bInheritHandles=FALSE, or a caller capturing output anywhere up the process chain hangs forever on a pipe that never EOFs \u00e2\u20ac\u201d std-handle flag stripping is NOT sufficient (grandparent strays still flow) (5.6)",
    "doc": "5.5 ConPTY withholds output until DSR is answered `[REQ-HAZARD-CONPTY-DSR]`: <!-- --> ### 5.6 Windows detached children inherit a captured caller's pipe `[REQ-HAZARD-DETACHED-PIPE-INHERIT]` - **Failure:** a caller captures an `spt` invocation's output through a pipe (`Command::output()`, a harness hook reading the command). That `spt` process detach-spawns a **long-lived** child (the daemon via `ensure_running`; a shell binary via `spt shell spawn`). On Windows `CreateProcess` runs with `bInheritHandles = TRUE`, and the spt process's std handles \u00e2\u20ac\u201d the caller's pipe write-ends \u00e2\u20ac\u201d are inheritable"
  },
  "REQ-DAEMON-STOP-LIVE-SESSION-WARN": {
    "title": "W3 (LIFECYCLE-TRUTH, promoted old follow-wave seed): `daemon stop` with live hosted sessions warns + requires --force (or names the sessions it will kill) instead of silently killing them.",
    "doc": ""
  },
  "REQ-HAZARD-REGISTRY-STALE-CLEAN": {
    "title": "Stale registry entries degrade to fallback, never hard-fail (4.3)",
    "doc": ""
  },
  "REQ-UPDATE-APPLY-RESTART-NOTICE": {
    "title": "`spt update apply` prints a LOUD restart-required notice whenever the surviving broker will keep running the pre-apply image (which, until broker-restart choreography exists, is ALWAYS on a successful apply). Public wording, no internal CODE:RESULT markers (composes with REQ-ADAPTER-UPDATE-MESSAGE / the update-apply-confident-message rule) \u00e2\u20ac\u201d name the user-visible CONSEQUENCE ('daemon-coordinated features run the previous version until the daemon restarts'), not the broker/brain internals. Composes with REQ-UPDATE-RUNNING-IMAGE-SURFACE (the notice tells the user what the version-surface will then show, and how to clear it). (F-025)",
    "doc": ""
  },
  "REQ-HAZARD-TEMPLATE-ARGV-FILL": {
    "title": "Command-template substitution fills argv ELEMENTS, not a re-tokenized string: spt-core currently `fill_template`s {key} values INTO the command STRING and THEN `tokenize`s the filled string (runtime.rs:94/122), so a multi-word {key} value whitespace-SPLITS into multiple argv tokens unless the adapter hand-quotes the placeholder, and a value containing a `\"` (or `;`) injects/breaks tokenization (shell-injection-adjacent). A filled value MUST become exactly ONE argv element regardless of spaces/quotes in the value. Fix: tokenize the TEMPLATE into argv FIRST, then `fill_template` EACH token, so a `{key}` slot resolves to a single element and the value never participates in tokenization (no whitespace-split, no quote/semicolon injection); preserve the missing-key / empty-command errors and `{{`/`}}` non-interpretation. perri's F-009 (v0.8.1 dogfood, argv-capture-confirmed): a multi-word `{psyche_prompt}` = \"PSYCHE REVIVAL time: epoch-ms:\u00e2\u20ac\u00a6 incoming event: (none)\" arrived as argv[6..12] (7 stray tokens), the harness runner strict-parsed `--prompt` against the 2nd word, exited 2 within ~1s \u00e2\u2020\u2019 phantom hosted perch. Applies to EVERY [session.<role>] template (psyche_init, extractor, notif, \u00e2\u20ac\u00a6",
    "doc": ""
  },
  "REQ-SEAM-PSYCHE": {
    "title": "spawn-psyche seam (fresh + resume templates)",
    "doc": ""
  },
  "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-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-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-ADAPTER-LIVE-UPDATE": {
    "title": "An adapter update is live and daemon-coordinated (the adapter analog of brain self-update, ADR-0004): for an endpoint with a running RESIDENT adapter binary (today the `[message-idle-translation-binary]`), the CLI keeps fetch+verify and hands the APPLY to the daemon over IPC, which per affected endpoint (1) STOPS the resident binary -> releases the OS file lock (fixes the Windows 'Access denied (os error 5)' overwrite failure), (2) swaps on disk ONLY files whose CRC differs from the staged archive (unchanged files + their still-running binaries untouched), (3) RE-CLONES the new on-disk manifest into the running `BrainLifecycle` (the in-memory manifest is cached at bringup and otherwise goes stale -> binaries+manifest back on the same page), (4) RESTARTS the resident binary from the new files. An endpoint NOT running -> CLI swaps directly (no lock, no cache). Only the resident class is cycled; ephemeral adapter binaries (Psyche loop, `[digest]` extractor, `[session.*]` runners, hooks) self-heal on next spawn and are excluded. The daemon keeps a per-endpoint registry of resident adapter children. (ADR-0025, v0.13.2)",
    "doc": "Live, daemon-coordinated adapter update: <!-- --> // Amendment (W3 build, 2026-06-22): <!-- --> <!-- -->"
  },
  "REQ-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-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-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-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-DAEMON-3": {
    "title": "Any api invocation auto-starts the daemon if absent",
    "doc": ""
  },
  "REQ-HAZARD-ENVELOPE-PARSER-SAFE": {
    "title": "Two-slice envelope parser is panic-free and tolerant (4.2)",
    "doc": ""
  },
  "REQ-TEARDOWN-UNCOOPERATIVE-HOST": {
    "title": "TEARDOWN-AUTHORITY W1 (ADR-0045 decision 4): the teardown kill NEVER depends on harness cooperation \u00e2\u20ac\u201d no graceful-input path, no waiting on PTY EOF, no ask-it-to-exit-first step. A WEDGED host is the design case, not the edge case: it is the situation that produced the ADR (doyle's own endpoint \u00e2\u20ac\u201d read-only rc replayed the retained PTY and `rc --take` acquired control, but a direct prompt produced no output for 30+s because the retained harness subtree itself was nonresponsive while the broker correctly preserved and replayed its last PTY state). Any cooperation-dependent step re-imports the exact hang the verb exists to break. Gate: impl \u00e2\u20ac\u201d the kill path proves no dependency on session responsiveness; int \u00e2\u20ac\u201d a deliberately nonresponsive/wedged hosted harness is still reaped within the bound (broker row gone + subtree gone), asserted against a real broker-hosted session.",
    "doc": "Decisions: <!-- --> **4. The kill never depends on harness cooperation** \u00e2\u20ac\u201d no graceful-input path, no waiting on PTY EOF, no \"ask it to exit first\". A wedged host is the design case, not the edge case: it is the situation that produced this ADR."
  },
  "REQ-PICKER-CONTROL-LINE-STATUS-GATE": {
    "title": "A2 (F028, operator #2): the picker confirm-panel 'controlled locally' line renders for OFFLINE endpoints. view.rs:425-436 builds control_line from ep.controlled with NO status gate; an offline endpoint with a stale controlled stamp shows 'controlled locally' (operator screenshot: hall-a offline + controlled locally). RENDER HALF (this REQ): control_line MUST be empty when status != Online. The upstream STICKY-stamp half (stamp survives client SIGKILL >=5min) is B3/REQ-PRESENCE-CONTROL-REAP-ON-EXIT. FIX: gate the render. See triage A2(a).",
    "doc": ""
  },
  "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-HAZARD-INSTANT-UNDERFLOW": {
    "title": "Scheduling never subtracts a Duration from Instant::now() (underflow-panics on a host booted more recently than the offset); 'due now / never run' is Option<Instant>=None gated on forward duration_since only (5.9)",
    "doc": ""
  },
  "REQ-MANIFEST-7": {
    "title": "Adapter-declared shortcut basename (M12-W2 follow-on): an optional `[adapter] shortcut_basename` manifest field names the basename the `spt endpoint run` picker bakes into the generated `<basename>-<id>` launcher shortcut (REQ-RUN-SHORTCUT). Absent \u00e2\u2021\u2019 the harness-agnostic default `spt` (\u00e2\u2020\u2019 `spt-<id>`); an adapter sets it to brand its shortcuts (claude-spt \u00e2\u2020\u2019 `cc` \u00e2\u2020\u2019 `cc-<id>`), so the Claude-Code-ness lives in the PUBLISHED adapter manifest, never hardcoded in spt-core. The picker reads it from the RESOLVED manifest of the selected adapter (registry::resolve_option), falling back to `spt` when absent/empty/unresolvable. Additive + N-1-safe (serde-default Option, omitted from serialization when absent; old manifests parse clean); manifest.schema.json regenerated from the derive (ADR-0001, CI drift-gated). Documented in docs/MANIFEST.md `[adapter]` section + the claude-spt worked example \u00e2\u20ac\u201d the adapter-author contract perri builds spt-claude-code against.",
    "doc": "`[adapter]` \u00e2\u20ac\u201d header: <!-- --> `shortcut_basename` *(optional, default `spt`)* \u00e2\u20ac\u201d the basename the `spt endpoint run` picker's `s` keybind bakes into the generated `<basename>-<id>` launcher shortcut at the project root (REQ-MANIFEST-7). Absent \u00e2\u2021\u2019 the harness-agnostic `spt` (\u00e2\u2020\u2019 `spt-<id>`); an adapter sets it to brand its shortcuts \u00e2\u20ac\u201d `claude-spt` uses `cc`, giving `cc-doyle`. spt-core never hardcodes a harness name; the picker reads this from the **resolved** manifest of the selected adapter. The launcher is the current OS's native form (`.cmd` on Windows \u00e2\u20ac\u201d `.ps1` is excluded by the default `PATH"
  },
  "REQ-NET-FAMILY-GATE": {
    "title": "spt-core binds only IP families that are actually REACHABLE (ADR-0030), so a half-broken family (e.g. IPv6 whose AAAA resolves but whose path is dead) never silently consumes connection/discovery time. At NetEndpoint::bind (endpoint.rs), probe each family's reachability ONCE and bind only the working ones: dual-stack when both healthy; IPv4-only when IPv6 is dead; IPv6-only when IPv4 is dead (drop the DEAD family \u00e2\u20ac\u201d NOT a fixed prefer-IPv4; IPv6-only networks must keep working). Re-evaluated on daemon restart (once-at-bind, no live re-eval in v1). Explicit overrides SPT_DISABLE_IPV6 / SPT_DISABLE_IPV4 force a family off (escape hatch + determinism + testing, mirroring SPT_NTP_SERVER); a forced-off family is never bound regardless of the probe. Reusable beyond join \u00e2\u20ac\u201d every spt connection benefits. (next milestone)",
    "doc": "Robust WAN subnet join: meet-before-code + per-family bind gate: <!-- -->"
  },
  "REQ-DAEMON-5": {
    "title": "Pump liveness: the peer pump writes a last-tick heartbeat consumed by daemon status / subnet status (decision 23 render legs in REQ-CLI-2/REQ-SUBNET-8); the daemon supervises the pump task \u00e2\u20ac\u201d a panic is caught, logged loudly, and the pump restarts with capped backoff (\u00e2\u2030\u00a45 min), so a 5.9-class death self-heals visibly instead of silently halving the daemon (M8 decision 23; field motivation: hfenduleam 2026-06-07 half-death)",
    "doc": ""
  },
  "REQ-MSG-IDLE-TRANSLATION-BINARY": {
    "title": "spt-hosted idle message delivery via an adapter TRANSLATION BINARY (ADR-0022). New opt-in manifest section `[message-idle-translation-binary]` = a TABLE carrying a `path` scalar (doyle OPT-B ruling: modeled as a table, not a bare top-level scalar, so a preceding section cannot silently absorb it + N+1 extensible; spt-core does NOT deny_unknown_fields, so a future key degrades gracefully); spt-core LIFECYCLE-manages it (spawn when the endpoint comes up, terminate when it goes down). The binary is a PURE stdin\u00e2\u2020\u2019stdout filter; spt-core owns EVERY PTY write. stdin (JSON-lines): `{type:\"init\",endpoint_id,node}` first \u00c2\u00b7 `{type:\"event\",envelope:\"<EVENT\u00e2\u20ac\u00a6>\"}` per inbound message (ADR-0020 envelope) \u00c2\u00b7 `{type:\"input\"}` content-free ping on each operator keystroke (binary tracks user-idle for its own idle-gated buffering; PTY input content NOT duplicated). stdout (JSON-lines): keystroke-commands `{key:\u00e2\u20ac\u00a6}`/`{delay_ms:\u00e2\u20ac\u00a6}`/`{text:\u00e2\u20ac\u00a6}` (extensible). spt-core applies the emitted sequence to the broker PTY ATOMICALLY (the W1 coordination \u00e2\u20ac\u201d REQ-HAZARD-INJECT-CONTROL-COEXIST). The daemon poll feed is the ONE idle substrate for both topologies (Q1=A): harness-hosted consumer = the Monitor child, spt-host",
    "doc": "`[message-idle-translation-binary]` \u00e2\u20ac\u201d spt-hosted idle delivery: <!-- --> // `[inject]` \u00e2\u20ac\u201d inject-input methods: <!-- --> ### `[message-idle-translation-binary]` \u00e2\u20ac\u201d spt-hosted idle delivery (ADR-0022) Opt-in. The adapter's **idle-delivery translation binary**: a pure stdin\u00e2\u2020\u2019stdout JSON-lines filter spt-core lifecycle-manages (spawned when the spt-hosted endpoint comes up, terminated when it goes down). spt-core feeds it the inbound `<EVENT>` feed and reads back keystroke-commands, which spt-core applies to the broker-held PTY **atomically** \u00e2\u20ac\u201d controller input is buffered during the emitted sequenc"
  },
  "REQ-INJECT-MULTILINE-INTEGRITY": {
    "title": "W5 (LIFECYCLE-TRUTH): the idle-inject TYPED delivery leg delivers multi-line bodies byte-complete. ROOT (4 field instances + spool diff): the typed leg eats HEAD bytes nondeterministically \u00e2\u20ac\u201d spool rows complete (1669B) vs ~322B received suffix; mid-turn poll envelopes always intact; a 1854B body later rode the same leg intact => timing race (terminal-readiness / enter-coalescing settle class), NOT a size cap. FIX DIRECTION (todlando proposes on the broker/translate typed-inject seam): settle-before-head, bracketed-paste where the harness supports it, or chunked write with echo-verify. STAKES: live-SENT injects leave NO spool copy \u00e2\u20ac\u201d truncation there is unrecoverable. Int: repeated large multi-line injects into a real PTY session arrive byte-complete (loop N times \u00e2\u20ac\u201d the race is timing-dependent, single-shot green is not proof).",
    "doc": ""
  },
  "REQ-ENDPOINT-LIST-PROJECT-COL": {
    "title": "#8: spt endpoint list gains a second column <project>/ (the endpoint's LATEST project) -> 4 columns total: id / <project>/ / type / status. Local rows: head of REQ-PICKER-PROJECT-HISTORY-TRUTH (sessions.log-derived, owlery-excluded). Remote rows: head of REQ-GOSSIP-ADAPTER-PROJECTS recent_projects. Project IDs only + #4 disambiguation; '-' when unknown (pre-field remote rows). Extends the v0.21.0 node-grouped renderer (format_instance_rows \u00e2\u20ac\u201d additive column, alignment char-width-safe). --json: additive project field on the row DTO (skip-if-none, N-1 safe). Depends on #1 + #4. See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #8.",
    "doc": ""
  },
  "REQ-INSTALL-BOOTSTRAP-VERB": {
    "title": "THE-FORKENING W1 (ADR-0036 \u00c2\u00a73, bootstrap shape b operator-ruled): virgin-box install = `gh release download` the platform binary + ONE self-install verb in the binary itself \u00e2\u20ac\u201d the verb places the binary at the canonical install path (the path self-update already respawns from, v0.4.2 lesson), registers user PATH, and leaves first-run identity/daemon-start to the existing idempotent first-run; hosted one-liner install scripts (curl|sh / irm|iex at the dead Pages URL) are RETIRED. Non-interactive (CONTEXT.md Installation: the install path doubles as every adapter's pack-in on-demand install). Windows UAC-740 gotcha binding: the downloaded exe keeps the `spt-*` asset name and the verb lives INSIDE spt \u00e2\u20ac\u201d no installer-detection trigger words in exe names. README (bs-core) documents: install gh -> gh auth login -> gh release download -> the verb. Gate: unit \u00e2\u20ac\u201d verb places/registers idempotently, refuses cross-platform binaries (platform-stamp check exists, v0.3.2); int \u00e2\u20ac\u201d from a clean SPT_HOME+PATH sim, downloaded-binary self-install yields a working `spt` on PATH whose `spt update fetch` then speaks the gh channel; doc \u00e2\u20ac\u201d README install section rewritten. Kin REQ-UPDATE-GH-TRANSPORT, REQ-I",
    "doc": "Installation: **Installer form (gh bootstrap, ADR-0036):** install gh \u00e2\u2020\u2019 `gh auth login` (org membership) \u00e2\u2020\u2019 `gh release download` the platform binary from the private channel \u00e2\u2020\u2019 one **self-install verb** in the binary places it at the canonical install path and registers the *user* PATH (so adapters call `spt api \u00e2\u20ac\u00a6` cross-OS); first-run identity gen + daemon start stay the existing idempotent unattended first-run. Hosted one-liner scripts are retired with the public channel; first-fetch trust = gh's authenticated TLS + org membership (full ed25519 verification is `spt update`'s job thereafter)."
  },
  "REQ-UPD-2": {
    "title": "All binaries signature-verified before handoff",
    "doc": ""
  },
  "REQ-PICKER-FORK-LABEL-CWD": {
    "title": "B-3 (F029, operator): the confirm-panel `Fork endpoint` option label is static and says nothing about WHERE the fork lands. A fork runs in the picker's launch cwd (run_cwd); the label must state that dir honestly: `Fork endpoint here --> <current dir>`. Anchor picker/view.rs confirm_option_label (was `fn(opt)->&'static str`). Make the label model-aware for the dir-relative options. See triage B-3.",
    "doc": ""
  },
  "REQ-PICKER-1": {
    "title": "The picker renders a FOUR-state endpoint status (extending the W2 online/offline duality): the list-item square AND a color-coded STATUS line at the top of the pick-existing right-side details both show \u00e2\u20ac\u201d gray OFFLINE; green ONLINE (online + PTY-controllable spt-hosted, not controlled); amber 'ONLINE - HARNESS ONLY' (online but NOT broker-PTY-controllable = harness-hosted, no broker PTY seat \u00e2\u20ac\u201d today mis-shows green); blue 'ONLINE + CONTROLLED' (online + driven_by.is_some()). Derived on EndpointRow from {offline | controllable | driven_by} with precedence offline\u00e2\u2020\u2019gray, else driven_by\u00e2\u2020\u2019blue, else !controllable\u00e2\u2020\u2019amber, else green (driven_by outranks harness-only; mutually exclusive in practice \u00e2\u20ac\u201d a harness-only endpoint has no broker PTY to control). The controllable discriminator is a NEW InfoJson.controllable: Option<bool> (serde-default, N-1-safe), stamped at the establish seam \u00e2\u20ac\u201d cmd_listen (harness-hosted relay, no broker PTY) \u00e2\u2020\u2019 Some(false); cmd_bind live_agent (spt-hosted broker PTY) \u00e2\u2020\u2019 Some(true); absent \u00e2\u2020\u2019 not-controllable (amber) default (harness-hosted is the common mis-reported case; one bind self-corrects). Store-projection-only (no live daemon query \u00e2\u20ac\u201d doyle ruling). (v0.10.0)",
    "doc": ""
  },
  "REQ-API-3": {
    "title": "commune/signoff are file-drops, not commands",
    "doc": ""
  },
  "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-HAZARD-BROKER-FLOOR-LOCK-POISON": {
    "title": "B-1 (REMOTE-TRUTH triage \u00c2\u00a7B-1, PIVOTED @e5eb99a): NO bare `.lock().unwrap()` on a broker-resident lock reachable from serve/dispatch \u00e2\u20ac\u201d a brain-only self-update keeps the broker + all its Mutexes ALIVE (REQ-UPD-3), so a single panic under one poisons it PERMANENTLY: the next `.lock().unwrap()` panics, kills its per-conn reply thread, and EVERY subsequent attach silently deadlines ('brain IPC read deadline elapsed') while non-locked ops keep working. TRIAGE-DRIFT (sweep-dispatch-site-counts discipline): the triage named 3 sites (broker.rs:1163 flush_inject_floor / :1297 inject-worker-open / :2142 buffer_if_held) as the surviving class, but ALL 3 are the INJECT FLOOR and were ALREADY poison-proofed by REQ-HAZARD-INJECT-WORKER-POISON (lock_floor, shipped post-triage \u00e2\u20ac\u201d the FLOOR HALF is SUBSUMED, this seed redirects). The SURVIVING class (matching the triage's own symptom description) is the ATTACH-PATH lock set: self.sessions Mutex<HashMap> \u00c3\u201418 + its sessions_exit alias \u00c3\u20141, the per-session OutputLog RING \u00c3\u201411 (log/h.log/log_drain/log_exit), pair_holds \u00c3\u20144 \u00e2\u20ac\u201d 34 production bare .lock().unwrap() (cfg(test) excluded). FIX (doyle B-1 ruling): recover ALL THREE via ONE shared `recover<T>(&Mute",
    "doc": "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]`: ### 7.33 NO bare `.lock().unwrap()` on a broker-resident lock reachable from serve/dispatch \u00e2\u20ac\u201d a poison permanently wedges every attach `[REQ-HAZARD-BROKER-FLOOR-LOCK-POISON]` <!-- --> - **Failure (paid-for class):** a brain-only self-update restarts the BRAIN but keeps the BROKER process \u00e2\u20ac\u201d and every one of its `Mutex`es \u00e2\u20ac\u201d ALIVE by design (REQ-UPD-3). So a single panic while another thread held a broker-resident lock P"
  },
  "REQ-NOTIF-1": {
    "title": "Notification primitive: per-subnet replicated spool, seen/dismissed, resurface-at-boundary, subsumes update+consent prompts",
    "doc": ""
  },
  "REQ-JOIN-DEFERRED-ELEVATION": {
    "title": "W2 (JOIN-TRUTH, operator UX ruling verbatim): 'if --code was not supplied, don't spawn the elevated subnet-join window until a target machine is discovered \u00e2\u2020\u2019 just in time for the code prompt.' When --code is ABSENT and the process is UNELEVATED, run the name prompt + ALREADY_MEMBER check + ensure_daemon + the MEET phase (brain.pair_meet) UNELEVATED; only on MetMember spawn the elevated window via the EXISTING try_auto_elevate machinery (the elevated re-run re-executes the join flow \u00e2\u20ac\u201d its second meet is cheap, the member is proven present). A FAILED search must NEVER show a UAC/sudo/pkexec prompt. The --code path is UNCHANGED (gate-first, one-shot). The unelevated phase performs ZERO trust mutation \u00e2\u20ac\u201d meet is pre-trust per REQ-JOIN-TWO-PHASE/ADR-0030. RULED OUT (doyle): cross-elevation hold-session adoption (passing the daemon-held pair session_id into the elevated process) \u00e2\u20ac\u201d a new security seam we don't need; the elevated re-run re-meets instead. The elevation gate MOVES from command-entry to the enrollment boundary; discovery is read-only pre-trust.",
    "doc": "Deferred-elevation amendment (2026-07-06 \u00e2\u20ac\u201d JOIN-TRUTH W2): <!-- -->"
  },
  "REQ-LIST-JSON-LIVENESS-PARITY": {
    "title": "GATEWAY-LIVENESS (flynn field bug 2026-07-09, RCA reader-divergence root): `spt endpoint list` (human) and `endpoint list --json` MUST report an IDENTICAL status for a locally-hosted endpoint \u00e2\u20ac\u201d especially a pid-alive, status-ABSENT gateway (no psyche_init). ROOT (todlando RCA STEP-1, doyle-verified): the --json builder (crates/spt/src/cli.rs cmd_endpoint_list) emits each subnet row's status straight from resource_projection (spt-net registry.rs:566, passes instance.status through verbatim :592 \u00e2\u20ac\u201d the persisted WAN snapshot, a lagged gossip that can carry a stale/crash-time Suspended) and NEVER applies the self-owned reconcile the human/picker path applies (reconcile_self_owned, crates/spt/src/picker/data.rs:160 via gather_endpoints :112). So a pid-alive self-owned gateway reads Suspended on --json but ONLINE on human (roster::enumerate spt/src/roster.rs:38 -> is_perch_alive pid-fallback spt-store/liveness.rs:136); the adapter suspend-poll (parse_endpoint_status over endpoint list --json --show-all) reads the divergent --json status -> self-suspends a pid-alive gateway. Candidates REFUTED: resource_projection does NOT re-derive liveness (copies instance.status, only skips !routable :",
    "doc": ""
  },
  "REQ-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-WORKER-LIST-VISIBILITY": {
    "title": "V-1 (WORKER-TRUTH triage, operator rider): worker perches leave the DEFAULT `spt endpoint list` view \u00e2\u20ac\u201d they are process-local machinery, not subnet citizens; leaked-or-live worker rows rendering as permanent OFFLINE endpoints is the operator-visible symptom root. A dedicated flag (--workers) reveals them (one command + flag per the --all/--detail precedent, NOT a separate list-working command \u00e2\u20ac\u201d sister divergence deliberate). Applies to the human render, --json (additive default-absent filter), and the registry/projection legs; verify-and-stop any worker gossip into the subnet registry as peer endpoints.",
    "doc": ""
  },
  "REQ-HAZARD-CONPTY-DSR": {
    "title": "ConPTY reader must auto-answer DSR (ESC[6n) or all child output stalls (5.5)",
    "doc": ""
  },
  "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-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-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-HAZARD-GRACE-BEFORE-SIGNOFF": {
    "title": "Grace-period wait completes before composing INIT_SIGNOFF (1.1)",
    "doc": ""
  },
  "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-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-HAZARD-CHILD-CONSOLE-FLASH": {
    "title": "Console-subsystem children of the console-less daemon spawn with CREATE_NO_WINDOW, or each spawn flashes a visible blank window on the user's desktop (5.8)",
    "doc": "5.7 Elevated commands spawn the daemon with the wrong token `[REQ-HAZARD-ELEVATED-DAEMON-SPAWN]`: <!-- --> ### 5.8 Console children of the console-less daemon flash visible windows `[REQ-HAZARD-CHILD-CONSOLE-FLASH]` - **Failure:** the daemon runs DETACHED (no console, 5.6/`detached_no_inherit`). Any console-subsystem child it spawns (`git`, `taskkill`, manifest hook commands) gets a **fresh conhost with a visible window** \u00e2\u20ac\u201d piped/null stdio does NOT prevent it. Field shape: the 60s sync pump's two git calls (`for-each-ref` + `rev-parse`) flashed two blank windows per minute on the user's deskt"
  },
  "REQ-BRAIN-RESUME-NO-CONN-DEADLOCK": {
    "title": "UPDATE-WEDGE round 3 (v0.30.5, doyle-ruled Option A 2026-07-09 \u00e2\u20ac\u201d the v0.30.4 field-verify re-wedge, root code-PROVEN + dead-peer-INDEPENDENT): the daemon brain must NOT subscribe broker PTY sessions onto its own request/reply IPC conn \u00e2\u20ac\u201d it has no consumer for that output and the subscription DEADLOCKS the conn. ROOT (todlando code-read, docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md; the net-runtime AND the counter-54 reap-drive were both FALSIFIED first \u00e2\u20ac\u201d docs/UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md): a conn's send half is a single `SharedSend = Arc<Mutex<SendHalf>>` (broker.rs:78). Subscriber writer threads (`viewer_writer` broker.rs:1333/1342, `controller_writer` :1451) hold `send.lock()` ACROSS a BLOCKING `write_frame`; the dispatch reply path (`send_frame` :4221 \u00e2\u2020\u2019 KIND_SESSIONS_REPLY / KIND_NET_STATUS_REPLY) needs the SAME lock. `resume_sessions` (brain.rs:1031\u00e2\u2020\u20191054) subscribes every session as a Viewer onto the brain's MAIN conn \u00e2\u20ac\u201d which is ALSO the brain's request/reply channel. The daemon brain hosts no PTY sessions (brainproc.rs:184) so run_brain never drains that output; it reads the conn only during the 500ms-heartbeat net_status()/sessions() calls (drain-and-DISCARD, `_ => continue",
    "doc": "Cold-start multi-session resume (restoration D4-2, ADR-0018 Q6): query the broker for **every** hosted session and re-attach **each** in resume mode from the broker's per-session delivered cursor (`resume_seq`). This is the production replacement for the retired single-session `BrainState` handoff frame \u00e2\u20ac\u201d a brain the supervisor respawns (crash *or* update) reconstructs all session continuity by querying the persistent side, never a brain\u00e2\u2020\u2019brain message. Returns the ids re-attached (empty when the broker hosts none \u00e2\u20ac\u201d the supervised daemon brain's no-op-today case). Each session is seeded into [`"
  },
  "REQ-HAZARD-EPHEMERAL-CLEANUP": {
    "title": "Ephemeral perch cleanup on every ring exit path (3.1)",
    "doc": ""
  },
  "REQ-RC-IDMARKER-DISABLE": {
    "title": "Bugs #14 + #7/#8 (marker half): feature-flag the top-right StatusRow endpoint-id marker OFF (rc.rs:198-307). It is a one-shot absolutely-positioned paint that scrolls off-screen and is not re-stickied (#14), and its DECSC/clear/SGR injection splices into the harness in-flight drawing causing residual artifacts (#7/#8). Ship disabled next release (operator: save the concept for a future web SPT GUI); revisit as a proper per-frame sticky overlay only once REQ-BROKER-SCREEN-GRID provides the screen model. See docs/NEXT-MILESTONE-BUG-TRIAGE.md #14.",
    "doc": ""
  },
  "REQ-HAZARD-SINGLE-PATH-SOURCE": {
    "title": "Single path/registry source of truth; no layout ambiguity (6.1)",
    "doc": ""
  },
  "REQ-HAZARD-VIEWER-CLOSE-DETACH": {
    "title": "A VIEW is independent from the endpoint: closing the tab/window where `spt endpoint run` was invoked must detach ONLY the `spt rc` attach pump \u00e2\u20ac\u201d the daemon-hosted harness keeps running and stays re-attachable via `spt rc <id>`. ROOT (Windows, v0.12.0 real-harness defect): the daemon never breaks away from the launching terminal's Job Object. Windows Terminal / VS Code place the launched shell AND every descendant into a Job Object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; closing the tab drops the terminal's last job handle \u00e2\u2020\u2019 the OS terminates every process still in that job. A child escapes only with CREATE_BREAKAWAY_FROM_JOB \u00e2\u20ac\u201d used NOWHERE in the tree. Both daemon spawn paths (daemon.rs:707 detached_no_inherit = DETACHED_PROCESS|CREATE_NEW_PROCESS_GROUP|CREATE_NO_WINDOW; deelevate.rs:519 elevated = CREATE_NEW_CONSOLE|...) drop the CONSOLE but NOT job membership, so the daemon's freshly broker-spawned ConPTY harness subtree is reaped on tab-close. The ConPTY/pseudoconsole isolation itself is CORRECT (portable-pty builds the pseudoconsole in the daemon; no console signal / handle leak) \u00e2\u20ac\u201d the leaking lifetime binding is the Job Object, not the console. FIX: add CREATE_BREAKAWAY_FROM_",
    "doc": "Terminal wrapper: **A view is independent from the endpoint** (invariant): <!-- --> An spt-hosted endpoint runs in a **daemon-owned PTY, decoupled from whatever terminal launched it**. Closing the tab/window where `spt endpoint run` was invoked detaches only the `spt rc` attach pump \u00e2\u20ac\u201d the endpoint keeps running under the daemon and stays re-attachable via `spt rc <id>`. A view is a transient frontend over a daemon-owned session, never the session's lifeline. *Implementation:* the daemon must never live inside the launching terminal's process grouping \u00e2\u20ac\u201d on Windows the cold-started daemon is lau"
  },
  "REQ-NET-2": {
    "title": "n0 relay default + self-host knob + plain-language disclosure",
    "doc": ""
  }
}