{"map": {"REQ-ACTIVITY-INFO-PULL": {"title": "The endpoint's current activity state (busy|idle) is readable via `spt api endpoint-info` \u2014 a point-in-time read of the perch idle sentinel, for consumers that need a check rather than a stream (ADR-0048 decision 1, pull avenue; operator-ruled 2026-07-24). Additive key, N-1-safe per the additive-evolution posture.", "doc": "1. Activity observation is a first-class surface with two avenues \u2014 and the digest is not one of them: <!-- --> <!-- -->"}, "REQ-ACTIVITY-LINK-PUSH": {"title": "An owned Shell observes its owner's busy/idle transitions as an activity frame on the EXISTING shell-link event stream (ADR-0048 decisions 1-3; perri contract-lock 2026-07-24). Link-scoped (owner implied by the link token \u2014 no new verb, no addressing surface). Drive-class semantics: ephemeral, latest-wins, current-state-carrying; redundant same-state resend is a harmless no-op (consumer derives edges); NEVER spooled or replayed (stale transitions are actively wrong). Both directions (busy->idle, idle->busy). Current state emitted on EVERY link establishment and re-link \u2014 restart resync is free. Latency promise = BOUNDED OBSERVATION: a frame per transition, sub-second class, never hard-real-time; each frame carries the TRANSITION TIMESTAMP (when the sentinel flipped, not when the frame was emitted) so edge-anchored consumers self-correct emission latency. Frame name/attrs publish in the shell frame vocabulary doc (REQ-SHELL-FRAME-VOCAB lineage) \u2014 doc stage rides there. Gate: int \u2014 a live shell link sees busy->idle and idle->busy frames with truthful transition timestamps, a re-link re-emits current state, and a transition during link-down is represented by the re-link emit (current ", "doc": "`drive` \u2014 owner\u2192shell, ephemeral: <!-- --> ## `activity` \u2014 the owner's busy/idle state, pushed"}, "REQ-ADAPTER-FLOOR-ENFORCE": {"title": "F-5 (REMOTE-TRUTH triage \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 \u2014 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 \u2014 dead enforcement; and the [update].version_check knob that gated it was DOC'D-BUT-DEAD (never read by any production path \u2014 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 \u2014 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\u21920, non-numeric\u21920, first-diff decides, equal-when-zero-padded \u21d2 satisfied) \u2014 mirror", "doc": "`[adapter]` \u2014 header: <!-- --> `min_spt_core_version` is the **enforced** compatibility floor. Both acquisition verbs \u2014 `spt adapter add` and `spt adapter update` \u2014 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 \u2014 there is **no** opt-in flag (the former"}, "REQ-ADAPTER-GH-TRANSPORT": {"title": "The `gh_release` avenue (and `spt adapter add --release`) gain a fetch `transport`: `https` (current reqwest direct, public), `gh` (shell the pre-authorized `gh` CLI \u2014 the private-repo path; `gh` honors OAuth and `GH_TOKEN`, so spt custodies no token), or `auto` (default: prefer `gh` when installed+authed, else HTTPS). `--gh`/`--https` force it on `add`. Additive over the existing fetch path; verify->extract->register downstream is unchanged. (v0.13.2)", "doc": "Runtime model: **adapter packaging & live update** (v0.13.2; ADR-0024, ADR-0025): <!-- --> A `.spt` may be **multi-platform**: shared `manifest.toml` + `strings/` at the root, role binaries under per-target-triple subdirectories (`x86_64-pc-windows-msvc/`, \u2026); install/update extracts the shared root plus only the current node's triple, flattened into `install_dir`, so flat `<install_dir>/<program>` resolution is unchanged. It stays one signed asset (`adapter.spt`, plain-tar or gzip); a multi-platform archive missing the recipient's triple is a typed `NoArtifactForPlatform`. Large adapters may"}, "REQ-ADAPTER-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-ADAPTER-MULTIPLATFORM-SPT": {"title": "A `.spt` adapter archive may pack multiple platforms in one signed asset: shared `manifest.toml` + `strings/` at the root, role binaries under per-Rust-target-triple subdirectories (ADR-0016 triple vocabulary, e.g. `x86_64-pc-windows-msvc/`); install/update extracts the shared root plus ONLY `current_platform()`'s triple subdir, flattened into `install_dir` so flat `<install_dir>/<program>` resolution (REQ-INSTALL-11) is unchanged. Name stays `adapter.spt` (plain-tar or gzip, `--asset` optional default); one whole-archive Ed25519 signature over the fat archive (REQ-UPD-9 single-artifact verify). A legacy flat archive (no triple subdirs) extracts as today (free back-compat); a multi-platform archive sets `min_spt_core_version >= 0.13.2` (forward-compat gate, readable before extract); a multi-platform archive missing the recipient's triple -> typed `NoArtifactForPlatform`, never a silent no-op. Large adapters may still split per-platform (single-triple archives via `--asset`, or ADR-0016 update-set machinery). (ADR-0024, v0.13.2)", "doc": "Multi-platform adapter `.spt` packaging: <!-- -->"}, "REQ-ADAPTER-TEMPLATE-KEY-VALIDATION": {"title": "P-1 core half (WORKER-TRUTH triage): `adapter add`/`adapter update` validate every declared role template \u2014 command, cwd, and [env] inject values \u2014 against the substitution-key catalog (spt-runtime BASE_KEYS + role-specific overrides) and REFUSE registration naming the offending key + role (fail-fast family of the [strings] pointer validation). Field driver: flynn's psyche died on the RETIRED {psyche_dir} key (adapter psyche_resume carried the old psyche_init cwd shape) \u2014 a permanent template config fault must die loudly at registration, not at the Nth per-event psyche turn via the 3-strike budget. The catalog stays the single source (runtime.rs FILL_KEYS \u2014 'a catalog key must have a real fill'); validation reads it, never a second list.", "doc": ""}, "REQ-API-3": {"title": "commune/signoff are file-drops, not commands", "doc": ""}, "REQ-ARCH-1": {"title": "Many small acyclically-layered crates", "doc": ""}, "REQ-ARCH-2": {"title": "Public SDK surface is spt-proto, spt-runtime, spt-msg", "doc": ""}, "REQ-ARCH-3": {"title": "Wire-protocol version independent of crate semver, N-1 compat window", "doc": ""}, "REQ-ATTACH-IDEMPOTENT-REPLAY": {"title": "The equal-generation lease rung is idempotent for the same connection. (ADR-0047 decision 3, AMENDING ADR-0044's ladder inside the equal-gen rung; hertz v0.39.4 field bug 4, PINNED via OBS breadcrumbs on authorized same-seam `daemon refresh` 2026-07-22 \u2014 gen+1 premise FALSIFIED.) TODAY: a Control/Take subscribe with same identity + same nonzero gen classifies 'same lease, silent re-take' (broker.rs equal-gen branch \u2014 correct, no revoke) but re-take = become_controller, which unconditionally takes+drops the prior seat (writer exits channel-closed) with NO same-conn check \u2014 designed for the dead-seat dispatcher-restart successor, it also fires against the SAME LIVE conn re-served 15ms apart by post-cycle dispatcher replay: the lease kills its own writer, the rc viewer freezes until detach+re-attach (the field 'update freezes PTYs'). FIX: keyed (endpoint/session, by, conn, gen) \u2014 same-conn equal-gen = IDEMPOTENT REPLAY: seat + writer PRESERVED, no controller-replaced, no second initial batch; breadcrumb answers decision=idempotent (additive vocab). Equal-gen DIFFERENT-conn keeps today's silent swap (the ADR-0038 fix-6 successor \u2014 must not regress); strictly-newer keeps loud supersessi", "doc": "3. The equal-generation rung is idempotent for the same connection: <!-- -->"}, "REQ-ATTACH-SEED-REQUEST-DOUBLE-SERVE": {"title": "A dispatcher-SEEDED attach Request is honored ONCE \u2014 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 \u2014 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 \u2014 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) \u2014 exactly hertz's v0.39.4 bug-4 capture (conn1011, 15ms apart, post-refresh replay). The two arms are the same function on the same seed record; the seed is pinned by the dispatcher on the post-cycle replay, which is the field trigger. SIBLING", "doc": ""}, "REQ-BIND-HONEST-SELF-STAMP": {"title": "C2-ROOT (F028, doyle RCA 2026-07-03): the identity-attribution ROOT behind REQ-SESSIONS-LOG-ENDPOINT-ATTRIBUTION \u2014 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 \u2014 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 \u2014 a session-start stamp may only write a perch whose resolved id AFFIRMATIVELY matches; never a fallback pick; refuse + loud-skip when unresolved (kin REQ-MSG-CLI-ORIGIN honest-default + #9 ancestry). ADAPTER half = PERRI touchpoint (psyche", "doc": ""}, "REQ-BOUNDARY-ROTATION-CREDENTIAL": {"title": "api boundary's rotation credential is designed, documented, and eventually anchor-proven (ADR-0032): the proof is the DEPARTED session's (prior sid / token) \u2014 --to-session-id is payload, never proof; the published surface documents the adapter prior-sid persistence pattern + loud-refusal requirement; the design-true end-state additionally accepts an OS-verified parent_pid-anchor ancestry proof making adapter sid-state optional", "doc": "`api boundary <clear|compact> <id> --to-session-id <new-sid> --session-id <prior-sid>`: <!-- --> // Boundary rotation credential: the departed-session catch-22 and the parent_pid anchor: <!-- -->"}, "REQ-BRAIN-RESUME-NO-CONTROL-STEAL": {"title": "UPDATE-WEDGE round 2 (v0.30.4, doyle-ruled 2026-07-09 \u2014 field incident on the counter-54 fetch--apply): a brain-respawn must NEVER steal, then stall-evict, the controller of a broker PTY session the daemon brain does not DRIVE. ROOT (field-pinned + SME, docs/UPDATE-WEDGE-2-RCA.md + docs/UPDATE-WEDGE-2-SME-todlando.md): `resume_sessions` (brain.rs:985) re-attaches EVERY session `KIND_SESSIONS` returns via `subscribe` = `subscribe_with(AttachIntent::Control, by:None)` (brain.rs:1450-1455). The docstring's 'a None identity never displaces (falls back to viewer)' is a MYTH for FREE / SAME-LOCAL-IDENTITY slots: `resolve_subscribe` (broker.rs:1134-1153) stall-evicts FIRST, then `become_controller` if the slot is now free OR the incumbent is also local (None==None) \u2014 viewer-fallback fires ONLY when a DIFFERENT REMOTE controls. So on a box with N spt-hosted broker PTYs, a brain-respawn STEALS the by:None controller of every free/local-controlled session (incl. the operator's LOCAL `spt rc`), which the daemon brain never drains (it hosts no PTY sessions \u2014 brainproc.rs:184) \u2192 15s later `stall_evict_controller` (broker.rs:1039) releases driven_by \u2192 the session is UNCONTROLLABLE (Failure A). I", "doc": "Self-update: **resume re-attach is view-only for non-driven sessions** \u2014 on respawn the new brain queries the broker for every hosted session and re-attaches to rebuild output-continuity cursors, but it re-attaches as a **viewer**, never a controller, for any session it does not itself drive (which is *all* of them today \u2014 the supervised daemon brain hosts no PTY sessions; spt-hosted PTYs are driven by the operator's attach or the endpoint's own loop). Re-attaching as a controller would seize the controller slot of every free/local-controlled session \u2014 including the operator's local `spt rc` \u2014"}, "REQ-BRAIN-UPDATE-RESTART-CLEAN-CLOSE": {"title": "SEED (DEFERRED, doyle 2026-07-09 \u2014 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) \u2192 the broker reads them WEDGED (broker.rs:2695-2700) \u2192 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 \u2014 but at a ~15s wedge-maturity hitch (frozen PTYs during the swap). A CLEAN close makes the conn 'simply absent \u2192 drained=false AT ONCE \u2192 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-CLI-WIN-VT-ENABLE": {"title": "A7 (F028, operator, Win10 conhost): ANSI emitted without VT enable \u2192 garbled console. Evidence (raw PowerShell 7, Win10 conhost): literal `\u2190[36m` in `endpoint list` + `--help`. ROOT: ENABLE_VIRTUAL_TERMINAL_PROCESSING is enabled ONLY on the rc attach path (rc.rs:746, REQ-RC-WIN-VT-OUTPUT) \u2014 plain CLI stdout never enables it, and the color decision doesn't fall back when the console can't render VT. FIX: lift the rc.rs VT-enable into a SHARED startup helper for every colored-output path; if SetConsoleMode fails (or stdout isn't a console), STRIP colors (the ansi_wrap/helpfmt color=false path already exists \u2014 plumb the decision, not new rendering). Windows Terminal masks this (VT always on) \u2014 TEST on raw conhost. See triage A7.", "doc": ""}, "REQ-CONN-BLACKHOLE-LIFECYCLE-HARNESS": {"title": "MSG-IDENTITY W6 / F-039 leg e (doyle W6 LOCK 2026-07-10, minted per amendment 3 \u2014 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 \u2014 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 \u2014 its brain_decouple int stays the Windows-mandatory gate leg), REQ-CONN-POISON-DIAL-SCOPE.", "doc": ""}, "REQ-CONN-POISON-DIAL-SCOPE": {"title": "MSG-IDENTITY W6 / F-039 (deployah field-acceptance follow-up 2026-07-10, RCA-FIRST \u2014 mint per the v0.30.6 PASS handoff): ambient CONN_WRITE_POISONED log-churn correlates 1:1 with PUMP_PEER_FAIL submit-dials to OFFLINE peers (enlyzeam/kitsubito/gravity) with NO wedge and NO freeze \u2014 pre-existed the blackhole rig = log-noise/mislabel, not a defect in the r4 fix. CODE CONTEXT: conn.rs poison_and_cancel emits the loud CONN_WRITE_POISONED line for a write that 'exceeded its bound (OR FAILED)' (conn.rs:181) \u2014 the fast-FAIL branch (broken pipe / conn refused on an already-dead counterpart) shares the log tag with the TIMEOUT branch that is the field-acceptance wedge observable, so routine conn teardown under offline-peer dial churn reads like poison events. RCA-FIRST: pin the exact write site that fails per PUMP_PEER_FAIL cycle (BrokerConn is broker-side \u2014 which broker conn write rides each pump dial failure? status/event fan-out to a departed subscriber? brain-side notification?) BEFORE changing anything \u2014 the correlation mechanism is unpinned. FIX SHAPE (post-RCA, doyle rules at lock): reserve the loud CONN_WRITE_POISONED token for the DEADLINE-EXCEEDED class (the wedge observable black", "doc": ""}, "REQ-CONTROL-STAMP-CONVERGENCE": {"title": "REGISTRY-LIFECYCLE W2 (ADR-0041 decision 4, emphasys C3 P1): control-stamp writes are session/generation-VALIDATED (or per-endpoint stamp transitions serialized) so a pre-reap KIND_SESSIONS snapshot can never relatch controlled=true after the exit-waiter reap; no fs I/O under global locks (KH 7.12); every no-session path clears all three control fields (controlled/driven_by/viewer_count). Gate: impl \u2014 validated/serialized stamp transitions; unit \u2014 stale-snapshot write refused after a newer no-session truth, no-session paths clear all three; int \u2014 poll-vs-reap interleave converges to cleared stamps (the relatch regression); doc \u2014 ADR-0041.", "doc": "Decision: <!-- --> 1. **Online is earned, not declared.** A creator may stamp `status=online` only from actual persisted state + hosting authority \u2014 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 \u2014 regardless of state or controllability \u2014 while offline classification ke"}, "REQ-CONV-1": {"title": "Peer address seeding, both cold starts: durable peer-addrs.json (identity dir) maps peer pubkey \u2192 last-known dialable address; the pump's resolver consults it FIRST with id-only discovery fallback on miss or dial failure (a stale addr never strands a peer); written by the pairing ceremony (both sides, from the live connection) and by the pump on successful connect; post-join first sync and post-restart resync converge in seconds, not ~1 min (M8 decisions 14, 20)", "doc": ""}, "REQ-DAEMON-3": {"title": "Any api invocation auto-starts the daemon if absent", "doc": ""}, "REQ-DAEMON-BITS-AMBIGUITY": {"title": "SEED (inactive, RCA-first \u2014 do NOT close on agreement): nothing on a node surfaces WHICH BITS ARE SERVING, and the same silent wrong-state shape bit twice in one day (2026-07-25). Case 1 (adapter-side echo): a post-reboot ensure race left a dev-build alchemy Hub Daemon serving release shells \u2014 version skew visible only by manually comparing process image paths. Case 2 (core, field-measured by flynn): TWO spt daemons resident with live brains on one node \u2014 the installed main daemon (owning ALL sockets: the 5474 listeners and every established connection, single home_tag pipe family) and an orphaned scratchpad-built daemon (auto-started into the node by ensure_running from a stray dev-binary invocation at 16:18, holding zero sockets, resident for hours) \u2014 while `spt --version` on any binary file answers nothing about which process is answering. Measured sharp edges to carry into the RCA: (a) exe path and resolved HOME are independent \u2014 the orphan ran scratchpad bits against the DEFAULT home, so 'where the binary lives' predicts nothing about 'whose state it mutates'; (b) the brain.ready breadcrumb is ONE FILE PER HOME, LAST-WRITER-WINS, keyed by generation \u2014 with two brains in one ho", "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 \u2014 transcript record index across the session ledger / digest.log index \u2014 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 \u2014 the published digest-record contract (ADR-0019): <!-- --> **Turn boundaries \u2014 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) \u2014 not as `agent`/`tool` output. If messaging-delivered turns are not opened as `input`, a messaging-driven session collapses into a few giant turns and `--last"}, "REQ-DIGEST-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 \u2014 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]` \u2014 env-var table: **`direction = \"read\"` \u2014 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 \u2014 the on-demand digest runs later in the daemon context where th"}, "REQ-DISPATCH-CLAIM-RETRY": {"title": "REDISPATCH-TRUTH W1 (ADR-0038, hertz fix C): dispatcher claims are RETRYABLE and outcomes are CLASSIFIED \u2014 today claimed.insert(stream_id) happens PRE-spawn (dispatch.rs:206/214) and is never cleared or retried on Unknown/Failed, so one transient worker-setup failure permanently abandons the stream. Distinguish active/retryable vs served vs finished: a transient worker-setup failure releases/requeues the claim (bounded/backoff-shaped); terminal classification outcomes stay terminal and do NOT hot-loop (show the distinction in test, not just code). Gate: impl \u2014 claim lifecycle; unit \u2014 transient failure requeues + terminal outcome does not (no hot-loop under a persistently-failing stream); int \u2014 production-path regression D3: inject ONE transient worker-start failure, prove claim retry recovers the stream with NO duplicate controller/output. HEAVY nextest group at birth. Kin REQ-REDISPATCH-FINISHED-RETIRE, REQ-STREAM-OPENER-DURABLE.", "doc": ""}, "REQ-DISPATCH-HYGIENE-TELEMETRY": {"title": "REDISPATCH-STALL W1 (ADR-0038 Amendment, fixes 7+8+9): dispatcher hygiene + keyed observability \u2014 (a) bounded redispatch worker pool with batched cold enumeration, NO claim locks held during I/O (today thread::spawn per stream, unbounded on a cold table); (b) sessions-lock discipline: clone-then-drop before detach_if/info.json I/O (KH 7.12 kin \u2014 no fs/conn I/O under global locks); (c) keyed stage telemetry: gen/stream/family/endpoint/attempt/conn on every dispatch record + replay/poison/cancel/worker-outcome events + gauges + PTY high-water/RC cursor \u2014 the field-discriminator surface (poison-window census, stream-sub-attach per generation dropping to O(active streams)). Gate: impl \u2014 pool + enumeration batching + lock discipline + telemetry keys; unit \u2014 pool bound honored under a cold flood + no-lock-across-I/O seam + telemetry key completeness; doc \u2014 ADR-0038 Amendment. Kin REQ-HAZARD-REDISPATCH-STALL, KH 7.12 (locks), REQ-CONN-POISON-ATTRIBUTION (extends its attribution).", "doc": "Consequences: ## Amendment \u2014 REDISPATCH-STALL (2026-07-16) <!-- --> <!-- --> <!-- --> <!-- -->"}, "REQ-DOC-DELIVERY-VOCAB": {"title": "W6 (LIFECYCLE-TRUTH, docs \u2014 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 \u2014 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-DOC-ENDPOINT-DROP-RESOLUTION": {"title": "D1 (F028, perri F-c; docs/truth): SI-1's resolution rule \u2014 a RELATIVE watched drop dir resolves against the ENDPOINT's cwd, never the daemon's (KH 7.28, shipped v0.22.0) \u2014 is documented NOWHERE public. Add it to harness-contract/manifest.md + the manifest schema field descriptions so an adapter author knows a relative commune_dir/signoff_dir is endpoint-resolved. docs-drift gate applies. See triage D1.", "doc": ""}, "REQ-DRIVEN-BY-OWN-NODE-NORMALIZE": {"title": "RC-RENDER-TRUTH v0.38.1 fast-follow leg 1 \u2014 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 \u2014 never silent-displace; --take is the opt-in kick), and `driven_by` = the CONTROLLING NODE including the own node (CONTEXT:386 single-writer datum \u2014 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 \u2014 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 \u2014 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 \u2014 `driven_by` own-node truth (ruling v3)"}, "REQ-EFFECTIVE-INSTANCE-STATE": {"title": "A-1 (REMOTE-TRUTH triage \u00a7A + ADR-0033 \u00a7Decision): the effective instance state of a perch is DERIVED through ONE shared function \u2014 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)`) \u2014 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\u2192intent-refined / is_perch_unbound\u2192Dormant / cold\u2192Suspended) \u2014 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\u2192Status, behavior identical) AND apply_event's `from` (real is_perch_alive/is_perch_unbound reads); void + cold \u21d2 Suspended. Bonus: kills the spurious active\u2192suspend echo a cold+void perch used to fire (on_rest_edge ", "doc": "7.31 The Psyche failure budget must count REAL per-event attempts \u2014 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 \u2014 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 \u21d2 Suspended); `resting::apply_event` derived its `from` off the stored `rest"}, "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 \u2014 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 \u2014 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 \u2026 --save` persists the run \u2014 endpoint id, adapter option, and working directory \u2014 as a **startup default** in `daemon.json`; the daemon **replays** every saved default when it starts, as a fresh session with the adapter re-resolved at replay time. One entry per endpoint id (a re-save replaces the prior one); remove the entry from `daemon.json`'s `startup_endpoints` to stop auto-starting"}, "REQ-ENDPOINT-CYCLE-HONEST": {"title": "REGISTRY-LIFECYCLE W3 (ADR-0041 decision 6, operator deployah stop/run wedge): cycle verbs share ONE liveness authority \u2014 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 \u2014 probing dup-guard + unified authority; unit \u2014 dead-tree claim probes and reaps, live claim still refuses; int \u2014 controlled zombie (killed client tree, surviving hosted record) leads to endpoint run succeeding honestly end-to-end; doc \u2014 ADR-0041.", "doc": "Decision: <!-- --> 1. **Online is earned, not declared.** A creator may stamp `status=online` only from actual persisted state + hosting authority \u2014 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 \u2014 regardless of state or controllability \u2014 while offline classification ke"}, "REQ-ENDPOINT-LIST-MERGE-LOCAL": {"title": "`spt endpoint list` always merges this node's LOCAL (unadvertised) perches into the view; the `--local` flag is REMOVED (operator decision 2026-06-17). Rationale: `spt whoami` is a thin alias of `endpoint list` \u2014 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 \u2014 so without the merge a freshly-online endpoint (or the caller's own, under `whoami`) could be **absent** from its own listing, which reads as lost. The earlier `--local` flag (a separate this-node-only view) is **removed**: the local view is no longer a mode, it is uncond"}, "REQ-ENDPOINT-LIST-NODE-IDENT": {"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 \u2014 factor a node_ident_display helper); compute in the impure print_local_section, pass into the pure renderer. Update the two test assertions (cli.rs:10711/10716). See docs/NEXT-MILESTONE-BUG-TRIAGE.md #5.", "doc": ""}, "REQ-ENDPOINT-LIST-RENDER-POLISH": {"title": "A6 (F028, operator, 4 asks): `spt endpoint list` render polish. (a) the 'Shared subnets' line is NOT dim \u2014 LIGHT_GRAY = \"37\" (cli.rs:3019) is standard-palette WHITE, indistinguishable from row text; use SGR 90 (bright-black/gray) for the dim intent. (b) the `Total:` line takes the same dim color. (c) move the status glyph ADJACENT to the endpoint name (operator: 'right behind the endpoint name'), mirroring the picker's glyph-beside-name presentation (today the glyph sits at the end next to the status word). (d) color the status WORD like the picker TUI (green ONLINE / gray OFFLINE / blue when driven, matching picker glyph semantics). All in render_node_grouped/render_instance_row (cli.rs ~3000s); pure render with an injected color decision \u2014 unit-testable off a tty. See triage A6.", "doc": ""}, "REQ-ENDPOINT-LIST-REST-FILTER": {"title": "spt endpoint list hides SUSPENDED instances by default; a new --show-all flag reveals them. Status-first row ordering with fixed precedence ONLINE > CONTROLLED > UNBOUND > SUSPENDED (when shown) > corrupt last, alphabetical by id within each band. Two invariants: (1) CORRUPT rows ALWAYS render regardless of filters \u2014 corrupt is a record condition demanding operator action (purge/re-mint), not resting clutter; hiding it would re-create counter-39 bug #3 (cross-ref REQ-HAZARD-CORRUPT-PERCH-COHERENCE, CONTEXT.md instance-state _Also avoid_); (2) the per-node Total line DISCLOSES the filter \u2014 'Total: N (+M suspended hidden)' \u2014 so nothing silently vanishes. Registry-Offline rows stay excluded by projection law (resource_projection skips unroutable; unchanged). Grill-with-docs ruling 2026-07-02 (operator + doyle).", "doc": ""}, "REQ-ENDPOINT-ONLINE-TRUTH": {"title": "REGISTRY-LIFECYCLE W2 (ADR-0041 decisions 1+2, emphasys C1 P0): ONLINE is earned, not declared \u2014 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 \u2014 creator gate + reconcile split + self-heal + terminal path; unit \u2014 creator refuses capability-only online, cleanup clears stamps on state-quirk rows, broker-failure never mass-offlines; int \u2014 dead-PID hybrid row does NOT survive a reconcile cycle (the immortal-row regression); doc \u2014 ADR-0041.", "doc": "Decision: <!-- --> 1. **Online is earned, not declared.** A creator may stamp `status=online` only from actual persisted state + hosting authority \u2014 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 \u2014 regardless of state or controllability \u2014 while offline classification ke"}, "REQ-ENDPOINT-TEARDOWN-AUTHORITY": {"title": "TEARDOWN-AUTHORITY W1 (ADR-0045; two hertz field RCAs 2026-07-19, doyle code-verified + ruled): ONE shared topology-aware broker-teardown primitive behind BOTH `endpoint shutdown` and `endpoint stop`. Today both verbs stamp state they never cause: cmd_shutdown (cli.rs:3979) = remove ready marker + cmd_rest(Suspend) \u2014 the Suspend edge (resting.rs daemon_rest_event_with_liveness -> apply_event -> cascade_shells_on_edge -> advertise) fires echo + shell cascade + advertise and NEVER touches a session, even though the verb already probed broker-session truth (cli.rs:3797-3804 has_live_session_honest) to force from=alive; cmd_stop (cli.rs:7071) = marker + unregister_address + terminal_normalize + advertise, whose own comment calls it a DEFINITIVE death observation it fabricates. Field: broker retains the whole subtree (adapter -> node -> harness -> nested resumed harness + MCP children; the surviving `spt api listen` is a DESCENDANT, so a direct-child kill misses it). PRIMITIVE (ordered, ADR-0045 decisions 1/6/7/9): resolve broker SessionInfo -> dedicated sid/endpoint-keyed broker kill claiming NO controller (NOT Brain::attach()+kill_session(), brain.rs:622 require_session() = controller", "doc": "7.48 At most one input-capable controller lease per PTY session \u2014 takeover revokes atomically and loudly, input is fenced to the active lease, node identity is never a lease `[REQ-HAZARD-CONTROLLER-LEASE]`: ### 7.49 A teardown verb never stamps a terminal or resting state it has not caused \u2014 and two individually-correct verbs must not compose into a lifecycle dead end `[REQ-HAZARD-TEARDOWN-DEADEND]` <!-- --> <!-- --> - **Failure (paid-for, two hertz field RCAs 2026-07-19, both doyle code-verified the same day; the second hit doyle's OWN live production endpoint):** `endpoint shutdown` reported"}, "REQ-ENDPOINT-UNBOUND-ATTACH": {"title": "An spt-hosted endpoint is ATTACHABLE between spawn and bind: gate the attach on the broker SESSION being attachable (session+PTY+OutputLog exist at spawn, before bind), not on perch STATUS_ONLINE (bind). cmd_endpoint_run + `spt rc <id>` attach to a live broker session regardless of perch status (headless bringups too; lets an operator clear a bind-gating prompt) -- replaces await_endpoint_online; preserves REQ-HAZARD-RC-ATTACH-ONLINE-RACE's 'no attach before a session' intent at the earlier session-exists point; source = the broker sessions map (ADR-0025 W3a); local-only. New on-disk status STATUS_UNBOUND (spawn->unbound, bind->online, death->offline); lifecycle reuses the existing exit-waiter/reconcile (session death->offline); unbound is attachable but NOT message-addressable (messaging stays online/bound-gated). EpDisplay gains Unbound = HOLLOW (+ hollow-controlled variant) -- amber=HarnessOnly is taken + means not-controllable (the opposite of attachable). (ADR-0027)", "doc": "Pieces the Instances model requires: <!-- --> **Unbound endpoint**: The lifecycle point between *spawn* and *bind*: an spt-hosted endpoint whose broker **session + PTY are live** but whose harness has **not yet bound** its perch (the *post-spawn seam* hasn't fired \u2014 e.g. the harness is waiting on a startup prompt). On-disk status `unbound` (spawn \u2192 `unbound`; bind \u2192 `online`; session death \u2192 `offline`). An Unbound endpoint is **attachable** (a live PTY \u2014 `spt rc` and the `endpoint run` attach reach it, so an operator can see and drive the harness, including clearing a bind-gating prompt) but *"}, "REQ-EP-1": {"title": "Day-one endpoint types; open type system", "doc": ""}, "REQ-EP-2": {"title": "Agent endpoints vs Shells distinction in the type model", "doc": ""}, "REQ-EP-4": {"title": "PresenceChannel broker endpoint (seam day-one)", "doc": ""}, "REQ-GOSSIP-CONTROLLED-CROSS-NODE": {"title": "B7 (F028, operator, cross-node): a remote endpoint's CONTROLLED state is not rendered. Evidence: ball-b ONLINE + CONTROLLED on ENLYZEAM (local view), but HFENDULEAM renders remote ball-b as plain ONLINE (both 0.22.0). The F-026 #4 gossiped any-controller datum (REQ-GOSSIP-ADAPTER-PROJECTS controlled bool) either isn't SENT for the locally-controlled case, isn't APPLIED on the receiving row, or DECAYS. Local leg confirmed fine (sibling hall-b renders blue-glyph correctly); the gap is the REMOTE leg. perri's validation had this ENV-BLOCKED \u2014 two live nodes now available to RCA. FIX: RCA sender-side (is controlled gossiped when locally-controlled?) / receiver-render (does from_resource_row surface it?) / decay, then lock with a cross-node int. See triage B7.", "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` \u2014 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 \u2014 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 \u2014 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 \u2014 `LIVE` then `DONE` printed, but the v"}, "REQ-HAZARD-BIND-REST-STATE-CARRY": {"title": "GATEWAY-LIVENESS DEFECT A (flynn field bug 2026-07-09, confirmed independent): a re-bind MUST preserve the daemon-owned resting intent (rest_state, D9-2/REQ-INST-3) \u2014 the same carry-forward discipline establish_perch already applies to cwd/controllable/adapter/read_env. ROOT: establish_perch's record build (crates/spt/src/api/startup.rs, the build closure) constructs a fresh InfoJson via InfoJson::new (defaults rest_state None) and carries cwd/controllable/read_env forward from prior but NOT rest_state -> a re-bind WIPES the wake intent (flynn tick11 rest_state:active vanish). FIX: carry prior.rest_state (and its paired dormant_since_ms anchor, present iff dormant) forward on re-bind, like the sibling fields. Gate: a re-bind over a prior record with rest_state set preserves it (unit \u2014 the build closure carries rest_state + dormant_since_ms). KNOWN-HAZARDS entry on landing. Kin REQ-HAZARD-BIND-CWD-UNSET / REQ-PICKER-1 + REQ-INST-3.", "doc": ""}, "REQ-HAZARD-BOUNDARY-READY-STRAND": {"title": "C-2 (F029, SEAM-2 pinned \u2014 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] \u2192 `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 \u2192 is_online false \u2192 try_spt_hosted_inject Nones on the CLI gate BEFORE any broker RPC \u2192 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 \u2014 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-BRAIN-RESTART-PSYCHE-DUP": {"title": "A bare brain restart leaves EXACTLY ONE `{id}-psyche` process per endpoint \u2014 no duplicate. On an abrupt brain death stop_host never runs (the LiveSet + owned child handles die with the brain) and Breap's job/group only reaps at DAEMON stop, so the PRIOR brain's Psyche stays ALIVE; the respawned brain's reconcile re-hosts a SECOND Psyche and overwrites the `{id}-psyche` perch pid, leaving the old one untracked + alive = a duplicate that lingers until daemon-stop (the operator's 'brain kill+restart wedged everything'). FIX: at brain start, BEFORE the first reconcile re-hosts, reap any pre-existing `{id}-psyche` orphan \u2014 ID-SPECIFICALLY (recycle-safe on the shared box, where sibling agents share the `claude` basename): scoped-kill the recorded pid ONLY IF it is alive AND its exe basename == the adapter's psyche program (normalize_basename) AND its COMMAND LINE contains the full psyche id `<id>-psyche` (baked via {id}); a sibling never carries THIS id, and any unreadable signal FAILS SAFE (decline to reap \u2014 a missed dup is bounded by Breap, a wrong-kill is catastrophic). CAVEAT: the cmdline carries `<id>-psyche` only when the adapter's psyche_init.command uses {id} (the norm); a non-{i", "doc": ""}, "REQ-HAZARD-BROKER-QUIC-DEADLINE": {"title": "The broker bounds every brain-waiting QUIC op (dial / open_stream / send_stream) so a black-holed or dead peer fails PROMPTLY with an ORDINARY error the broker REPLIES, never an unbounded await. The bound (< the brain's 30s PUMP_PEER_IO_TIMEOUT so the BROKER fires first) surfaces to the pump as a normal broker error reply \u2192 peer_outcome's non-TimedOut arm \u2192 drop conn + redial next tick, the round CONTINUES and the heartbeat keeps advancing \u2014 it must NEVER manifest as the brain's own read-deadline (the A-half poison \u2192 supervised-restart path REQ-HAZARD-PUMP-IPC-DEADLINE guards). Exactly-once is preserved: a timed-out journaled op fails INSIDE its apply_once closure so no phantom conn_id/stream_id is recorded and a fresh tick re-dials cleanly. The happy path is unchanged (a live peer completes with zero added latency; the bound only bites a non-responsive peer). This is the ROOT-cause cure for the 2.2h hfenduleam pump wedge \u2014 a dead roster peer whose QUIC path the broker awaited unbounded \u2014 recurring on hfenduleam 2026-06-16.", "doc": "7.7 A slow/dead/hostile remote VIEWER must never stall the controller, child, or drain `[REQ-HAZARD-VIEWER-ISOLATION]`: ### 7.8 The broker must never make a brain wait UNBOUNDED on a QUIC op (the pump-IPC-deadline B-half) `[REQ-HAZARD-BROKER-QUIC-DEADLINE]` <!-- --> - **Failure:** the broker's brain-facing QUIC handlers (`dispatch_net_dial` / `dispatch_net_stream_open` / `dispatch_net_stream_send`) call into `NetHost::dial` / `open_stream` / `send_stream`, whose iroh awaits (`endpoint.connect` + `prove_membership`; `open_bi`; `write_all`/`finish`) had NO bound of their own. A dead/black-holed"}, "REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE": {"title": "W2 (LIFECYCLE-TRUTH, KNOWN-HAZARDS, flagship \u2014 the update wedge): PTY viewer fan-out and control mutations must not depend synchronously on a live draining brain. ROOT rig-CONFIRMED (NtSuspendProcess on the brain, no update involved): brain-subscriber session-output writes ride UNDER the per-session log lock (broker.rs:19-20); failed writes are handled (cursor freeze + detach :3486) but BLOCKED writes are not. Suspended brain => within seconds attached rc output freezes; detach does NOT release the control stamp (release routes through the brain); reattach REFUSED (controlled-by); rc --take hangs; daemon status stays healthy. Field: every brain cycle (incl. every update apply) has a freeze window; a stalled/slow-draining new brain = permanent wedge until bounce; brain.ready != subscribers drained. FIX SHAPE (todlando proposes, doyle RULES BEFORE IMPL): subscriber writes move OFF the log lock (bounded/nonblocking, stall => detach-subscriber like viewer eviction \u2014 broker already buffers + replays on re-attach, so a detached-stalled brain self-heals by rewind); control stamp release/take completes against the BROKER without brain round-trip (or bounded with loud timeout). doc = KNOWN-", "doc": "7.35 The cached ceremony-clock NTP offset must NOT survive an OS clock STEP \u2014 an offset measured against the pre-step clock strands every pairing for the TTL `[REQ-HAZARD-CEREMONY-CLOCK-STEP]`: ### 7.36 The broker control plane and PTY fan-out must NEVER block on a single subscriber connection \u2014 a suspended brain conn must not wedge control `[REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]` <!-- --> - **Failure (paid-for, rig-CONFIRMED 2026-07-06/07 \u2014 `NtSuspendProcess` on the brain, no update involved):** a controller's writer thread does a BLOCKING socket write to its brain subscriber conn. When th"}, "REQ-HAZARD-CASCADE-WIPE-GUARD": {"title": "No hard-delete of a parent hosting non-empty children (6.3)", "doc": ""}, "REQ-HAZARD-CONPTY-DSR": {"title": "ConPTY reader must auto-answer DSR (ESC[6n) or all child output stalls (5.5)", "doc": ""}, "REQ-HAZARD-CONTROL-STAMP-LIFETIME": {"title": "#2: a control/viewer stamp never outlives its session \u2014 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 \u2014 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) \u2014 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 \u2014 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 \u2014 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` \u2014 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 \u2014 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 \u2014 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-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 \u2014 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) \u2014 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-HAZARD-DAEMON-STOP-BARRIER": {"title": "B3: `spt daemon stop` then an immediate `spt daemon start` does NOT race \u2014 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 \u2192 STOPPED then start \u2192 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-DAEMON-STOP-REAP": {"title": "Breap: `spt daemon stop` REAPS the spt-hosted children it spawned \u2014 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 \u2014 '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 \u2014 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-HAZARD-DEAD-REC-PID": {"title": "E-1 rider (F-026 #11 dead-pid class, doyle-ruled SCOPED 2026-07-05 \u2014 KNOWN-HAZARDS 7.34): a dead `rec.pid` on an spt-hosted perch is EXPECTED, not staleness \u2014 the recorded pid is the ephemeral bind-CLI pid, which dies immediately after bind (the broker holds the PTY; no resident harness process at that pid). NO reader may alive-gate on `rec.pid` alone: spt-hosted LIVENESS comes from the daemon-managed status field (KH 2.5 \u2014 status present \u21d2 authoritative, never a per-pid probe); IDENTITY comes from session/ancestry resolution where `rec.parent_pid` (the harness pid, the stable session-binding anchor) is the ancestry candidate (REQ-SELF-DETECT-PARENT-PID). Re-stamping rec.pid with the harness pid (shape (a)) is OVERRULED: ADR-0021 demoted pid to a bind-time seed hint (re-anchoring truth there reverses the design); every pre-existing record keeps the old CLI pid so readers need the scoped discipline anyway (migration hole); blast radius (every rec.pid consumer + KH 2.5 external-perch probe semantics) buys nothing the reader-side fix doesn't. CLASS rule: any newly sighted rec.pid-alive-gating reader gets the same scoped fix and EXTENDS this requirement's evidence \u2014 no new REQ per read", "doc": "7.33 NO bare `.lock().unwrap()` on a broker-resident lock reachable from serve/dispatch \u2014 a poison permanently wedges every attach `[REQ-HAZARD-BROKER-FLOOR-LOCK-POISON]`: ### 7.34 A dead `rec.pid` on an spt-hosted perch is EXPECTED \u2014 no reader may alive-gate on `rec.pid` alone `[REQ-HAZARD-DEAD-REC-PID]` <!-- --> - **Failure (paid-for, field evidence \u2014 the F-026 #11 dead-pid class):** an spt-hosted endpoint's `rec.pid` records the ephemeral bind-CLI pid, which dies IMMEDIATELY after bind (the broker holds the PTY; there is no resident harness process at that pid). Readers that alive-gated on"}, "REQ-HAZARD-DEFERRED-DRAIN": {"title": "Deferred spool rows excluded from the event-stream drain (1.4)", "doc": ""}, "REQ-HAZARD-DEFERRED-SURVIVE-DRAIN": {"title": "Deferred rows survive poll drain (4.4)", "doc": ""}, "REQ-HAZARD-DIRECT-WRITE-PRECEDENCE": {"title": "Direct-write precedence marker (with node id) guards stale overwrite (6.5)", "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 \u2014 do NOT rely on the detach signal (same lesson as REQ-HAZARD-HOSTED-LIVENESS-RECONCILE B2): the reconcile loop clears `driven_by` when the endpoint has no live controller/session. Today a wedged or lost pump never delivers the detach, so the endpoint stays latched CONTROLLED forever. Composes with W1 (the wedge no longer blocks the detach) and rides the same pull-primary reconcile substrate as B2. (v0.13.0)", "doc": ""}, "REQ-HAZARD-DROP-FILE-SINGLE-WRITER": {"title": "Drop files are daemon-owned single-writer (6.4)", "doc": ""}, "REQ-HAZARD-EBUSY-RENAME": {"title": "tmp-write + atomic-rename + retry on Windows EBUSY (5.2)", "doc": ""}, "REQ-HAZARD-ECHO-BEFORE-SIGNOFF": {"title": "Echo-commune fires before INIT_SIGNOFF on orphan teardown (3.3)", "doc": ""}, "REQ-HAZARD-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 \u2014 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) \u2014 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 \u2014 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 \u2192 the same dispatch_spawn (broker.rs:706/835) which s", "doc": ""}, "REQ-HAZARD-ENVELOPE-PARSER-SAFE": {"title": "Two-slice envelope parser is panic-free and tolerant (4.2)", "doc": ""}, "REQ-HAZARD-EPHEMERAL-CLEANUP": {"title": "Ephemeral perch cleanup on every ring exit path (3.1)", "doc": ""}, "REQ-HAZARD-GRACE-BEFORE-SIGNOFF": {"title": "Grace-period wait completes before composing INIT_SIGNOFF (1.1)", "doc": ""}, "REQ-HAZARD-HANDOFF-ARGV-COMPAT": {"title": "Broker/brain IPC + handoff argv version-tolerant (2.3)", "doc": ""}, "REQ-HAZARD-HOSTED-LIVENESS-RECONCILE": {"title": "B2 KEYSTONE: a daemon-hosted (spt-hosted) endpoint's info.json status is RECONCILED to real liveness, not left latched online. The broker exit-waiter (broker.rs:889-910) reaps its in-mem session table + emits ExitEvent but NEVER touches info.json; lifecycle::mark_offline only fires on Psyche teardown \u2014 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 \u2014 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 \u2192 status=offline \u2192 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\u2192mark_offline) is an OPTIONAL fast-path only if the daemon brain is rel", "doc": ""}, "REQ-HAZARD-INBOX-NO-DOUBLE": {"title": "No double-delivery via legacy inbox (4.5)", "doc": ""}, "REQ-HAZARD-INFO-RMW-LOST-UPDATE": {"title": "Concurrent info.json writers must serialize under the per-perch lock (5.16): an unlocked whole-record write racing a locked RMW is a silent lost update", "doc": "5.15 Fixed atomic-write tmp name \u2192 concurrent writers collide (loser renames a consumed file) `[REQ-HAZARD-ATOMIC-TMP-COLLISION]`: <!-- --> ### 5.16 Unlocked whole-record info.json write races a locked RMW \u2192 silent lost update `[REQ-HAZARD-INFO-RMW-LOST-UPDATE]` - **Failure:** `mutate_info` serializes its read\u2192mutate\u2192write under the per-perch `.info.lock` sentinel, but `establish_perch` (`spt::api::startup`) did read\u2192conflict-check\u2192`write_info` with **no lock**. At bind the two writers race (~700\u00b5s apart): the daemon RMW reads the PRE-BIND record, bind's `write_info` renames the full record in"}, "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\u2192host_one\u2192spawn_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 \u2014 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 \u2014 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\u2192brain-child, real api seed+listen, real install-dir psyche binary). spt-core SURFACES the failure; the adapter owns fixing its pack", "doc": ""}, "REQ-HAZARD-LIVEHOST-NONRESIDENT": {"title": "A daemon-hosted Psyche that spawns then EXITS IMMEDIATELY is a host failure, surfaced like a spawn failure (closes the v0.8.1 residual masking): the REQ-HAZARD-LIVEHOST-BOOT-RACE signal stamps `psyche_host_error` only when `spawn_psyche` returns Err, NOT when the detached spawn() returns Ok but the child dies within moments (e.g. a bad-argv child exiting 2 \u2014 the F-009 case). That leaves the residual 'online + no Psyche + no cause' gap: the nested `{id}-psyche` info.json is written status=online with a real-but-DEAD pid and the PARENT perch carries NO psyche_host_error (perri's F-010: tasklist showed 0 host procs across the window while info.json read online). The host MUST confirm RESIDENCY \u2014 a hosted child not alive (or whose `{id}-psyche` perch never re-registers / has a dead pid) within N seconds of spawn is treated as a host failure: stamp the parent perch `psyche_host_error{reason:\"host not resident within <n>s (psyche perch missing/dead pid)\"}` (and do not leave a phantom online nested perch). Closes the last masking gap the v0.8.1 fix left open. perri's F-010 (v0.8.1 dogfood). Sibling of REQ-HAZARD-LIVEHOST-BOOT-RACE.", "doc": ""}, "REQ-HAZARD-LOCAL-API-AUTH": {"title": "Every local `api` mutation authenticated to an endpoint/session (codex #13)", "doc": ""}, "REQ-HAZARD-MESH-BOOTSTRAP-TRAP": {"title": "MESH-RECOVERY W1 (KNOWN-HAZARDS 7.42, hertz field RCA 2026-07-10 \u2014 HFENDULEAM+ENLYZEAM symmetric green-status sequester): a node holding a valid RosterEntry.address for a peer is NEVER route-less \u2014 a failed dial must not delete the only bootstrap route, and recovery must never require an already-successful connection or operator state surgery. Today: resolve_submit_addr = exact cache else id-only (never roster), PRESENCE_DIAL_FAILED unconditionally drop_seed's the cache row, and the cache refills only after a successful seed-proof exchange \u2014 one transient + stalled discovery = self-sustaining isolation, invisible (net_up true, heartbeat fresh, durable counts normal). Gate: int \u2014 production-path regression at the REAL pump resolver/failure-lifecycle seam: valid roster + matching cache, ONE transient dial failure, id-only discovery DISABLED, prove the next attempt still holds the roster-derived route AND all-peer-fail-then-restore converges with zero state surgery; doc \u2014 KNOWN-HAZARDS 7.42. HEAVY nextest group at birth if it spawns a daemon tree. Kin REQ-PEER-ROUTE-CHAIN (the mechanism), REQ-CONV-1 (the falsified drop-on-fail predecessor), ADR-0039.", "doc": "7.41 A fresh dispatcher must NEVER re-serve a terminal stream \u2014 historical replay must not steal or clear a live controller `[REQ-HAZARD-REDISPATCH-CONTROL-STEAL]`: ### 7.42 A node holding a valid roster address for a peer is NEVER route-less \u2014 a failed dial must not delete the only bootstrap route `[REQ-HAZARD-MESH-BOOTSTRAP-TRAP]` <!-- --> - **Failure (paid-for, hertz field RCA 2026-07-10 \u2014 HFENDULEAM + ENLYZEAM fully sequestered from every subnet member, symmetric, green-status):** the pump resolved dial addresses from the exact `peer-addrs.json` entry else id-only discovery \u2014 never the val"}, "REQ-HAZARD-PAIR-RATE-LIMIT": {"title": "Subnet-global pairing rate limit: one active ceremony per subnet, shared attempt counter, exponential backoff \u2014 a public pre-trust relay + multiple seed-holders otherwise enables distributed SPAKE2 guessing (and \u00b11 TOTP window triples the valid-password space) (ADR-0005 #11)", "doc": ""}, "REQ-HAZARD-PSYCHE-OUTBOUND-PROXY": {"title": "Psyche outbound captured + sanitized: the live-Psyche turn driver captures stdout (never Stdio::null), and the daemon strips/re-stamps Psyche-supplied from=/target and constrains routing (reply\u2192__REPLY_TO__ sender, notify\u2192own user/subnet) (7.3)", "doc": ""}, "REQ-HAZARD-PUMP-IPC-DEADLINE": {"title": "The single-threaded peer pump's brain-IPC reads are deadline-bounded (PUMP_PEER_IO_TIMEOUT, total-wait per call); a TimedOut read POISONS the client and escalates to a SUPERVISED RESTART, never a per-peer retry \u2014 a black-holed peer must never wedge the whole pump", "doc": "7.5 WAN-inbound origin is transport truth, never payload `[REQ-HAZARD-WAN-ORIGIN-AUTH]`: ### 7.6 Pump brain-IPC reads must be deadline-bounded (a blocked read wedges the whole pump) `[REQ-HAZARD-PUMP-IPC-DEADLINE]` <!-- --> - **Failure:** the peer pump is a SINGLE thread driving every leg (registry/notif/sync/update) against every peer over ONE brain-IPC client. Its reply reads (`net_open_stream`, `net_stream_send`, `net_dial`, and the sync/update pull `read_event` loops) were `loop { read_event() }` with no deadline. When a peer's QUIC path black-holes, the broker's stream-open/send awaits th"}, "REQ-HAZARD-RC-ATTACH-FAILFAST": {"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 \u2014 the broker still resolved a session for it). FIX: (a) once B2 lands, gate attach on is_online/status \u2014 an offline endpoint yields a clean 'endpoint offline, start it' not an attach; (b) fail-fast \u2014 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 \u2014 the wall-f Windows tab-close: child alive-silent vs dead-not-reaped). (v0.12.0)", "doc": ""}, "REQ-HAZARD-RC-ATTACH-TRUTH": {"title": "RC-RENDER-TRUTH W1 (KNOWN-HAZARDS 7.46 \u2014 umbrella conformance seam for ADR-0042): an rc surface answers from live session authority, never a stale persisted projection; a resuming perch is UNBOUND, not offline. Regression matrix from the hertz RCAs + operator field recovery: offline-row-over-honest-session attaches; offline-no-session refuses; zombie refuses/reaps never attaches; resume-never-bound reads UNBOUND and is attachable; harness-only refuses truthfully pre-stream; qualified targets attach with bare wire id. HEAVY nextest group at birth for any leg spawning a daemon tree (standing CI lint). Gate: int \u2014 the matrix; doc \u2014 KNOWN-HAZARDS 7.46.", "doc": "7.45 Endpoint lifecycle state converges to truth from every death path \u2014 no optimistic online without authority, no surviving control stamps, no immortal wake intent, no untruthful create `[REQ-HAZARD-ENDPOINT-LIFECYCLE]`: ### 7.46 An rc surface answers from live session authority, never a stale persisted projection \u2014 and a resuming perch is UNBOUND, not offline `[REQ-HAZARD-RC-ATTACH-TRUTH]` <!-- --> - **Failure (paid-for, hertz perri contradiction RCA 2026-07-17/18 + operator field recovery):** the broker hosted an honest live session (client tree alive, `SessionProbe::has_live_session_hones"}, "REQ-HAZARD-RC-EOF": {"title": "A severed broker stream during a live rc session surfaces GRACEFULLY, never as a raw io error that crashes the PTY. The rc read-loop (rc.rs:352-362) continues only on WouldBlock/TimedOut; ANY other read_event_until error \u2014 including UnexpectedEof 'failed to fill whole buffer' \u2014 returns Err \u2192 RC_FAIL \u2192 the PTY 'crashes' from the user's view. Confirmed trigger: a deliberate `spt daemon stop` (broker bounce) severs an active rc (perri stopped the daemon to release owlery watch handles). Same severed-broker-stream EOF class as the v0.9.1 seed fix (seed_fail_message) and the listener-death case \u2014 spt-core must classify a broker-gone EOF and (a) surface a CLEAR actionable message ('daemon stopped/restarted \u2014 re-run / reconnect'), never the raw buffer error, and ideally (b) AUTO-REATTACH to the same session on the fresh broker (the broker is the daemon-lifetime anchor; it returns on the next `spt api` call). FOLD two side-observations: (1) `spt daemon stop` SILENTLY drops active rc/live sessions \u2014 warn ('N active session(s) will drop') or graceful-detach on stop; (2) the daemon holds owlery WATCH HANDLES on perch dirs so a torn-down perch dir stays 'Device busy' until a full daemon stop r", "doc": ""}, "REQ-HAZARD-REDISPATCH-STALL": {"title": "REDISPATCH-STALL W1 (KNOWN-HAZARDS 7.43, hertz v0.34 field RCA 2026-07-16 \u2014 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 \u2014 serial 15s bounded-write poison windows (33 observed, all 15,000-15,154ms) composing into the field stalls. Gate: int \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 live v0.34 boxes, recurrent 20\u201330s PTY/RC freezes, DISPATCH tails 17\u201362s):** a COMPOSITION, not one new timer. The dispatcher's ret"}, "REQ-HAZARD-REGISTRY-EPOCH-LEASE": {"title": "Registry merge ordered by per-node monotonic epoch, never wall-clock \u2014 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 \u2192 [instances]` eventually-consistently across nodes. Under a partition or clock skew, a lagging node re-announces a stale `Active` for an endpoint that has actually gone `Offline`. If the merge ordered updates by wall-clock (or \"last write wins\"), the stale `Active` overwrites the newer `Offline` and resolution routes a message to a dead/wrong instance. - **Invariant:** the merge pr"}, "REQ-HAZARD-REGISTRY-GHOST-ROWS": {"title": "Registry rows must decay (no immortal rows) via TWO triggers: (a) NODE-SILENCE \u2014 evict rows whose author node has not been heard (admitted inbound feed) within the eviction window, so a vanished node's rows stop poisoning bare-id resolution with phantom AcrossNodes ambiguity; AND (b) per-row OFFLINE-TTL \u2014 evict rows that have been non-routable (Offline) beyond the per-row grace even while the author node is alive, because purge/erase leaves an immortal Offline row otherwise (ghost-heal re-advertises Offline ONCE with a fresh epoch, and whole-node eviction never fires for a still-alive author) so Offline ghost rows accumulate unbounded on a remote viewer under purge/erase churn (#2-secondary). Both keyed on RECEIVER-observed state (heard-map recency / a sticky receiver-observed offline_since, NOT the gossip epoch \u2014 an epoch-keyed clock would be reset by ghost-heal's fresh-epoch re-advertise); own rows never decay; a revived/re-flapped row re-inserts (or clears its offline_since) from its durable epoch within one pump cadence (4.10)", "doc": "4.9 SQLite stores must create their parent dir \u2014 SQLite won't: ### 4.10 Dead node identities leave immortal registry rows `[REQ-HAZARD-REGISTRY-GHOST-ROWS]` <!-- --> - **Failure:** the registry's only superseding mechanism is the per-`(endpoint_id, node)` epoch lease (4.8) \u2014 a row is replaced only by a newer row *from the same node*. When a node identity dies permanently (machine retired, or `node.key` regenerated so the \"node\" never speaks again), its rows are never superseded and never expire: they sit in the in-memory registries and the `identity/registry/<subnet>.json` snapshots forever. A"}, "REQ-HAZARD-REGISTRY-STALE-CLEAN": {"title": "Stale registry entries degrade to fallback, never hard-fail (4.3)", "doc": ""}, "REQ-HAZARD-RENDER-LIFECYCLE": {"title": "RC-RENDER-TRUTH W3 (KNOWN-HAZARDS 7.47 \u2014 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 \u2014 the matrix; doc \u2014 KNOWN-HAZARDS 7.47.", "doc": "7.46 An rc surface answers from live session authority, never a stale persisted projection \u2014 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 \u2014 output before exit, owned baselines, unconditional teardown `[REQ-HAZARD-RENDER-LIFECYCLE]` <!-- --> - **Failure (paid-for, hertz stale-glyphs RCA 2026-07-18, all legs doyle seam-verified):** four render-lifecycle defects presenting as \"missing whitespace\"/stale glyphs. (a) The broker exit waiter direct-writes `KIND_EXIT`"}, "REQ-HAZARD-RESTART-IDEMPOTENT": {"title": "Idempotent/exactly-once delivery across brain restart at every broker boundary (codex #14)", "doc": ""}, "REQ-HAZARD-RESUME-CUSTODY-ABA": {"title": "KNOWN-HAZARDS 7.51: process custody is an identity, never a bare PID \u2014 a recycled pid must read NOT OURS. The hazard-conformance twin of REQ-RESUME-CUSTODY-IDENTITY: its int-stage recycled-pid rig IS this hazard's required test (custody pair mismatching a live impostor pid -> record deleted, reconcile proceeds, row goes honest). Registered separately so the hazard list stays a conformance checklist (CLAUDE.md rule 4) \u2014 evidence may tag the same rig.", "doc": "7.51 Process custody is an identity, never a bare PID \u2014 a recycled pid must read NOT OURS `[REQ-HAZARD-RESUME-CUSTODY-ABA]`: <!-- --> - **Failure (paid-for, hertz v0.39.4 field RCA 2026-07-22, doyle code-verified same day):** `resume.pid` custody is a bare PID consumed as `read_resume_pid(..).is_some_and(is_process_alive)` at BOTH the livehost restart gate and the liveness-reconcile DEFER. A dead wake-resume spawn's pid, recycled by the OS onto an unrelated process (field proof: `resume.pid=29456` resolved to a random `cmd.exe`), reads as \"a resume is in flight\" indefinitely: reconcile defers"}, "REQ-HAZARD-SEEDMAP-CONNECT-UNBOUNDED": {"title": "SEED (doyle-filed, 2026-07-05, from the REQ-HAZARD-DAEMON-STOP-BARRIER B2 fix): the PRODUCTION seedmap connect callers (put / take / is_running) inherit the SAME interprocess WaitNamedPipeW-forever hazard the stop path just bounded \u2014 a Windows named-pipe connect to a name that EXISTS but has NO accepting instance parks in NMPWAIT_WAIT_FOREVER, so a slow / half-dead seed daemon could wedge a live `api seed` / `api listen` / `daemon start`. UPDATE (2026-07-05, doyle reversed the stop-path scope-guard): request_stop's OWN initial connect became load-bearing (a stop-guard re-dialing an already-dying name parked forever, resurrecting the convoy) \u2192 it is now bounded via connect_bounded under REQ-HAZARD-DAEMON-STOP-BARRIER (every dial on the STOP path is bounded). REMAINING deferred here = the put / take / is_running production clients. FIX (deferred, needs its own ruling): a shared bounded seed-control connect for those \u2014 but a 2s-style cap on a legitimately slow daemon-start connect is a real behavior change (a slow-but-fine start could become a spurious failure), so the timeout + degrade semantics need design first. NOT built \u2014 activate when scoped.", "doc": ""}, "REQ-HAZARD-SESSION-PIN-WEDGE": {"title": "A perch PINNED to a DEAD session-id self-heals instead of wedging forever. authenticate() (spt/src/api/auth.rs:78) gates api poll/state/boundary on proof-sid == info.json.session_id; if ONE boundary rotation is lost (transient env corruption kills the /clear-era hook), the perch stays pinned to the dead sid and EVERY id-scoped hook call refuses \u2014 INCLUDING boundary itself (it presents the new sid), a permanent strand (ready:false, stale .idle, drain no-ops, WAN spool sleeps forever; AUTH_REFUSED is stderr-only = invisible inside a hook). FIX: authenticate() gains a DEAD-OWNER fallback \u2014 when the sid MISMATCHES AND the perch's recorded pid is dead (proc::is_process_alive==false), ACCEPT the caller's sid and RE-PIN (rotate session_id + log SESSION_REPIN loud). Same trust model as establish_perch's conflict gate (api/startup.rs:207-210), which already allows rebind exactly when owner_alive==false (an orphaned perch accepts a new LOCAL owner). A LIVE-owner mismatch STILL refuses (squat protection UNCHANGED). ADDITIVE to token auth \u2014 the existing token-auth recovery path is UNTOUCHED; the new branch fires only on (no token) AND (sid mismatch) AND (owner dead). COVERAGE SPLIT (explicit, ", "doc": "F-019 diagnosis lesson \u2014 confirm an adapter binary actually SPAWNED before behavioral diagnosis `[REQ-INSTALL-11]`: ### 7.25 A perch PINNED to a DEAD session self-heals (dead-owner re-pin) instead of wedging forever; a LIVE-owner rotation still refuses `[REQ-HAZARD-SESSION-PIN-WEDGE]` - **Failure (F-024C/F-024D, ENLYZEAM field + clean-room repro 2026-07-02):** `authenticate()` (auth.rs) gates `api poll`/`state`/`boundary` on `proof.session_id == info.json.session_id`. If ONE boundary rotation is LOST \u2014 the departing session dies (crash / tab-close) or its `/clear`-era `boundary` call never lan"}, "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 \u2014 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 \u2192 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 hertz RCA, UPDATE-WEDGE round-4):** `controller_writer` held the `SharedSend = Arc<Mutex<SendHalf>>` guard ACRO"}, "REQ-HAZARD-SHELL-STALE-ONLINE": {"title": "A shell instance's ONLINE-ness is DERIVED (recorded status AND its recorded `shell.pid` not provably dead), never the recorded `status` field alone \u2014 an abruptly-dead binary (force-kill, crash, OOM: no link-break, so `close_shell`'s offline flip never runs) must not read online forever. The shell-side twin of REQ-HAZARD-DAEMON-HOSTED-LIVENESS, which gave AGENT perches exactly this resolver and which shells never got. PROVABLY DEAD is the narrow discriminant: `shell.pid` present AND parses non-zero AND `!is_process_alive` \u2014 pid absent, unparseable, or 0 (a broker-hosted spawn whose backend exposed no pid records 0) reads ALIVE, the same interim-parity/fail-toward-alive stance `liveness.rs` already holds, so a pid-less backend is NEVER falsely declared dead. Recycled-pid caveat, accepted at mint: a reused pid reads alive, so the heal is missed, never mis-fired \u2014 the failure direction is 'stays stale', never 'kills a live instance'. SITE CLASSIFICATION IS PART OF THE REQUIREMENT (authoritative cfg(test)-excluded census at mint = 8 status reads, 3 classes \u2014 do NOT blanket-swap the predicate): (a) DERIVED \u2014 relink's already-online refusal (the gate that made recovery impossible), the `s", "doc": "Conformance checklist (condensed): | # | Invariant | spt-core surface | |---|---|---| | 1.1 | Grace wait precedes INIT_SIGNOFF | daemon teardown | | 1.4/4.4 | Deferred rows excluded from event-stream drain | daemon spool drain | | 2.1/5.1 | Stable PID/broker-handle over ephemeral PID | liveness detection | | 2.3 | Handoff argv/IPC version-tolerant (newer brain \u2194 older broker) | broker\u2194brain IPC, self-update | | 2.4 | gen_start = now() on cold-start + handoff | per-instance generation | | 2.6 | A shell's ONLINE-ness is DERIVED (recorded status AND a not-provably-dead `shell.pid`) \u2014 an abruptly-"}, "REQ-HAZARD-SINGLE-PATH-SOURCE": {"title": "Single path/registry source of truth; no layout ambiguity (6.1)", "doc": ""}, "REQ-HAZARD-SOFT-CLEANUP": {"title": "Soft-cleanup preserves state, removes only the ready marker (6.2)", "doc": ""}, "REQ-HAZARD-STALE-INDEX-LOCK": {"title": "Sweep stale lockfiles on daemon boot (1.3)", "doc": ""}, "REQ-HAZARD-STDIN-SESSION-ID": {"title": "Stdin session_id precedence over env (2.2)", "doc": ""}, "REQ-HAZARD-TEARDOWN-DEADEND": {"title": "TEARDOWN-AUTHORITY W1 HAZARD (ADR-0045 decision 8; hertz RCA 2 \u2014 hit doyle's OWN live production endpoint, recoverable only by out-of-band scoped kill): `endpoint stop` followed by `endpoint run --id <same>` MUST succeed (spawn or resume) and must NEVER answer ENDPOINT_CREATE_CONFLICT about a session that stop claimed to end. TRAP SHAPE (two individually-CORRECT behaviors composing into a lifecycle DEAD END with no in-band exit): (1) stop leaves the broker session alive (REQ-ENDPOINT-TEARDOWN-AUTHORITY), and (2) `endpoint run` correctly REFUSES ENDPOINT_CREATE_CONFLICT rather than silently reattaching (v0.37.0 no-silent-reattach rule, deliberately chosen). The survivor is therefore simultaneously what stop claims to have killed AND what run refuses to work around; with a wedged harness every in-band verb is exhausted (stop lies, run refuses, rc replays a dead PTY, rc --take controls a process that never answers). Neither behavior is individually wrong \u2014 the COMPOSITION is the hazard, so the regression must assert the composition, not either verb alone. This single assertion is the whole user-visible point of W1. Gate: impl \u2014 covered by the shared primitive; int \u2014 start a real broke", "doc": "7.48 At most one input-capable controller lease per PTY session \u2014 takeover revokes atomically and loudly, input is fenced to the active lease, node identity is never a lease `[REQ-HAZARD-CONTROLLER-LEASE]`: ### 7.49 A teardown verb never stamps a terminal or resting state it has not caused \u2014 and two individually-correct verbs must not compose into a lifecycle dead end `[REQ-HAZARD-TEARDOWN-DEADEND]` <!-- --> <!-- --> - **Failure (paid-for, two hertz field RCAs 2026-07-19, both doyle code-verified the same day; the second hit doyle's OWN live production endpoint):** `endpoint shutdown` reported"}, "REQ-HAZARD-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:\u2026 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 \u2192 phantom hosted perch. Applies to EVERY [session.<role>] template (psyche_init, extractor, notif, \u2026", "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 \u2192 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 \u2192 the viewer serve-brain uses the LEGACY reject-gap \u2192 a PRE-eviction ring-roll forward-gap FATALS read_event \u2192 serve_attach returns \u2192 forwarding stops \u2192 attach_received_pty_output=FALSE (a_journaled / p0_paste / attach.rs:1071 wedged_viewer, Linux forkpty; Windows ConPTY floods slower \u2192 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 \u2192 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 \u2194 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) \u2014 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-HOST-RUN-1": {"title": "spt-hosted harness bringup: `spt endpoint run` spawns an adapter's `[session.self]` command template into a broker-held PTY (the spawn-session seam, brain.rs spawn_session_pid \u2014 same broker path shellhost.rs launch_shell_brokered_in uses for shells, now for kind=\"harness\" self-role), registers the perch under the given endpoint id, returns the id. Reverses today's harness-hosted-only launch (external launcher \u2192 `api bind`). Non-interactive flag set (--adapter <a[:profile]> --id <id> --create --resume <session> --attach|--start|--view) covers every terminal action of the W2 interactive picker so shortcuts (cc-<id>) bake fully non-interactive launches; composite adapter:profile resolves via registry::resolve_option leaf-replace overlay.", "doc": ""}, "REQ-IDLE-PARKED-DELIVERY": {"title": "W5 (LIFECYCLE-TRUTH): a message QUEUED to an ALREADY-idle spt-hosted endpoint is delivered without an operator poke. ROOT (live during the milestone dispatch 2026-07-07): the idle-edge drain (F-023 leg 2) fires only on the ACTIVE->IDLE transition; no new edge ever comes for a parked session, and the send-time inject didn't carry it \u2014 both doyle->todlando dispatches sat delivered=0 in the spool while the endpoint showed ONLINE. FIX: send-time inject fires for an already-idle spt-hosted target (activity sense says idle => inject now, not spool), and/or a bounded spool sweep re-offers pending rows to idle endpoints (piggyback the pulse tick, no new loop). Int: send to a session idle for N minutes -> delivered without any operator poke.", "doc": ""}, "REQ-INST-15": {"title": "Immutable home subnet (assigned at creation: auto-if-one/ask-if-many) + spt fork (cross-subnet clone to a new identity, copy-then-diverge, not re-home); adapter chosen at creation from registered hostable adapters, changed only via launch/resume-under-new (ADR-0010)", "doc": "Immutable home subnet; fork (copy-then-diverge) is the cross-subnet move, not re-home: <!-- --> > **Delivered (M4-D9-5, 2026-06-04):** home assignment at creation > (`spt_store::home` \u2014 auto-if-one / ask-if-many / local-only-until-first-join, > carried forward across re-binds, no setter) + `sync_subnets = [home]` > creation seeding + `spt fork <src> <new_id> --subnet <target> > [--delete-source]` (one-time copy of both context tiers as fresh seed > commits \u2014 copied-then-independent; join-time collision check against the > target; the source untouched unless deleted). Same-node only in v1 \u2014 the"}, "REQ-INST-3": {"title": "Dormant (warm) / suspended (cold) resting states", "doc": "Dormancy resource budget (D9-3 \u2014 ADR-0003 red-team #9): <!-- -->"}, "REQ-INST-4": {"title": "active to dormant/suspended fires a transition echo commune", "doc": ""}, "REQ-INST-7": {"title": "Subnet registry + bare-id resolution policy", "doc": ""}, "REQ-INST-8": {"title": "Remote-control mode distinct from local operation", "doc": ""}, "REQ-INSTALL-1": {"title": "Two install paths (harness-bootstrapped calls into standalone); OS-service registration deferred. HISTORY: originally 'signed one-line script' \u2014 the hosted one-liner retired as the PUBLIC install surface at THE-FORKENING W1/W2 (ADR-0036; the canonical bootstrap is gh + the spt install verb, REQ-INSTALL-BOOTSTRAP-VERB); installer/ scripts remain in-repo as the hermetic oneliner_e2e fixture + air-gap/mirror fallback, which is what this REQ's evidence now attests (doyle-ratified 2026-07-14).", "doc": "Installation: <!-- the two-paths model + the one-line script half (v0.1 phasing below; OS-service leg = docs/DEFERRED.md) --> <!-- the marketplace-repackaging stance: relocatable binary + minimal, non-OS-entangled install logic --> spt-core is per-machine and harness-independent, so it installs *before* and *independent of* any adapter."}, "REQ-INSTALL-12": {"title": "Durable active-profile pointer for bind-time profile selection (ADR-0021): adapters/active-profiles.toml at the registry ROOT (sibling to the per-adapter <name>/ dirs, so adapter add/update/remove \u2014 which only rewrite a <name>/ subdir \u2014 can never clobber it), a flat host_binary \u2192 \"adapter[:profile]\" map. Read at bind as the PRIMARY profile selector; unset \u2192 the registered_at_ms fallback (REQ-START-5). Written ONLY by `spt adapter use <adapter>[:profile]` (resolves the adapter's host_binaries \u2192 sets each binary\u2192adapter[:profile]); `spt adapter use --clear <adapter|binary>` drops. NEVER auto-written by install/update/adapter add (that is precisely what would let an update silently flip the active profile). A stale pointer (uninstalled adapter / deleted profile) self-heals: ignored, fall back, warn once. Pruned on adapter remove. Atomic write (spt_store atomic). (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 \u2014 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` \u2192 that process's **executable basename** (case-insensitive, `.exe`-stripped); 2. **candidate adapters** = registered `kind=\"harness\"` adapters whose **`host_binaries`*"}, "REQ-INSTALL-8": {"title": "OS-service registration (REQ-INSTALL-1's deferred third leg): Linux systemd USER service + loginctl enable-linger (linger rides the elevated install leg; daemon starts at boot pre-login, user universe per KH 5.7, systemctl --user managed); Windows scheduled task at-logon (interactive session, no stored credentials); a node is reachable after reboot without any manual spt invocation (M8 decision 17)", "doc": ""}, "REQ-INSTALL-BOOTSTRAP-VERB": {"title": "THE-FORKENING W1 (ADR-0036 \u00a73, bootstrap shape b operator-ruled): virgin-box install = `gh release download` the platform binary + ONE self-install verb in the binary itself \u2014 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 \u2014 no installer-detection trigger words in exe names. README (bs-core) documents: install gh -> gh auth login -> gh release download -> the verb. Gate: unit \u2014 verb places/registers idempotently, refuses cross-platform binaries (platform-stamp check exists, v0.3.2); int \u2014 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 \u2014 README install section rewritten. Kin REQ-UPDATE-GH-TRANSPORT, REQ-I", "doc": "Installation: **Installer form (gh bootstrap, ADR-0036):** install gh \u2192 `gh auth login` (org membership) \u2192 `gh release download` the platform binary from the private channel \u2192 one **self-install verb** in the binary places it at the canonical install path and registers the *user* PATH (so adapters call `spt api \u2026` cross-OS); first-run identity gen + daemon start stay the existing idempotent unattended first-run. Hosted one-liner scripts are retired with the public channel; first-fetch trust = gh's authenticated TLS + org membership (full ed25519 verification is `spt update`'s job thereafter)."}, "REQ-JOIN-DIAGNOSTICS": {"title": "`spt subnet join` never fails SILENTLY (ADR-0030; the field incident showed no output at all). (a) LIVE progress during the meet (replace the one-shot \"Searching\u2026\" cli.rs:6268 with periodic elapsed/deadline) so silence \u2260 hang; (b) DETAILED failure on meet-exhaustion \u2014 rendezvous candidates + families attempted (IPv4/IPv6) + relay-vs-direct + the last concrete error \u2014 surfaced BEFORE any code prompt (a dead subnet must not make the user fetch a code); connect_seed_holder (pairhost.rs:437) and dial_via_rendezvous (meet.rs:281) currently swallow per-attempt errors \u2014 thread the last error up with attempt context; (c) PROPAGATE the terminal event \u2014 brain.rs:1024 `_ => continue` must deliver a daemon NoSeedHolder/PairFail to the CLI as a printed error (this is WHY the user saw nothing); (d) `--verbose`/`SPT_LOG` discovery TRACE (per-probe derived id, discovery path mDNS/n0-DNS/relay, per-family timeouts), opt-in \u2014 no such knob exists today. (next milestone)", "doc": "Robust WAN subnet join: meet-before-code + per-family bind gate: <!-- -->"}, "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 \u2014 controlled=true AND controllable=false on a broker-hosted PTY endpoint \u2014 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\u2026) (~379) \u2014 explicit wins, so that Some(false) OVERWRITES the Some(true) an earlier `api bind` EARNED. The carry-forward discipline that protects cwd/adapter/rest_state does not protect this field precisely BECAUSE the listen path is not silent about it. The defect is an ASSUMPTION about hosting authority made by a path that does not know the answer. FIX (preferred): represent LISTENER CUSTODY separately from PTY HOSTING AUTHORITY,", "doc": ""}, "REQ-LISTEN-SEED-CONSUME-AFTER-BIND": {"title": "F-034 leg b (perri/hertz field finding 2026-07-09, hertz's HEADLINE): `api listen` must NOT consume the consume-once ephemeral seed on a PRE-BIND refusal \u2014 validate (adapter resolvable, home/subnet) and BIND first, THEN consume the seed. ROOT: today `api listen` burns the consume-once seed BEFORE it validates home/subnet, so on a multi-subnet node HOME_REFUSED (needs --subnet) fires AFTER the seed is already gone \u2192 the corrected retry (adding --subnet) on the SAME pid hits NO_SEED, a dead end (plausibly ADAPTER_UNRESOLVED burns it the same way). A refusal that never bound must leave the seed intact for the corrected retry. Same EFFECT-BEFORE-IRREVERSIBLE-CONSUME ordering class as F-032 (commune commit-before-delete) \u2014 the irreversible consume must follow the successful effect, never precede a refusal. Gate: a pre-bind refusal (HOME_REFUSED on a multi-subnet node without --subnet; ADAPTER_UNRESOLVED) leaves the seed CONSUMABLE \u2014 the corrected retry on the same pid binds (no NO_SEED); a SUCCESSFUL bind still consumes the seed exactly once (no double-bind). Files: the api-listen bind path (seed consume ordering). Kin F-032 [[spt-core-findings-backlog]].", "doc": "`api listen <id> [--once] [--parent-pid <pid>] [--subnet <name>] [--session-id <sid>]`: <!-- --> **Recoverable refusals do not consume the seed.** The seed is consumed by a **successful bind** \u2014 or by a refusal that proves the seed itself dead (see spend-vs-restore below). A recoverable refusal that never bound \u2014 `HOME_REFUSED` on a multi-subnet node without `--subnet`, `ADAPTER_UNRESOLVED`, a live-perch conflict \u2014 leaves the seed consumable, so the corrected retry on the same pid binds instead of dead-ending on `NO_SEED`. (Effect before irreversible consume: the destructive step follows the s"}, "REQ-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 \u2014 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 \u2014 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 \u2014 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** \u2014 no file \u2014 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 \u2014 that is what `listen --session-id` (below) is for."}, "REQ-LIVE-AGENT-NO-INJECT-DELIVERY": {"title": "F-033 RE-SCOPED (doyle re-ruling 2026-07-10 after the #82 gate falsified the original premise \u2014 the hosting-mode class split is BINDING context): 'state:live_agent has a self-delivery reader' CONFLATED two hosting modes. (A) HARNESS-hosted live agents (api listen path) DO deliver via adapter channels only \u2014 and are ALREADY structurally excluded from inject: bind_from_seed stamps controllable=Some(false), no broker PTY exists. (B) SPT-HOSTED/CONTROLLED live agents' inject leg IS their delivery reader (broker PTY + translation binary \u2014 doyle's own endpoint is field proof: ENDPOINT_INJECT + IDLE_PARKED_DRAIN alongside hook-poll); the original blanket state:live_agent exclusion broke REQ-MSG-IDLE-EDGE-DRAIN + the v0.14.3 LAW on both CI platforms and was REVERTED (predicate stays controllable-gated). perri's adapter-channels-only model (F-dupmsg-adapter-confirm.md) holds for class (A) only \u2014 do not re-seed the conflation. The F-033 DUPLICATE mechanism (hook-poll + idle-inject both delivering one row, operator spool row 156) is closed structurally by REQ-CARRIER-CLAIM-EXCLUSIVE's atomic cross-carrier take. REMAINING LEGS OF THIS REQ: (a) unit \u2014 a harness-hosted live agent (controllable S", "doc": ""}, "REQ-LIVEHOST-RECONCILE-TRIAL-SILENT": {"title": "SEED (DEFERRED investigation, doyle 2026-07-09 \u2014 UPDATE-WEDGE follow-up): determine WHY the trial/rollback brain's livehost reconcile loop did NOT drive the broker controller-reap (nor re-host the live agents) during the ~30s field update-trial window, when livehost polls `query_live_session_endpoints()` \u2192 `brain.sessions()` (KIND_SESSIONS) UNCONDITIONALLY every `LIVE_RECONCILE_INTERVAL_MS`=5000ms (livehost.rs:1026). CONTEXT (surfaced building the counter-54 rig): livehost's 5s KIND_SESSIONS poll drives the SAME broker `reap_dead_controller` sweep the fix drives \u2014 so it would otherwise reap the 15s-matured wedge by ~T20 < the 30s trial and SELF-HEAL. It didn't (field froze 30s \u2192 rollback), so the field trial-brain livehost was silent/delayed (PIN Q2: no `DAEMON_RESTART_RESUME` under gen-1/gen-2; the 30s kill landed before/around livehost's first reconcile tick). The counter-54 fix (REQ-UPDATE-TRIAL-DRAIN-DRIVE) puts a RELIABLE 500ms reap-driver in run_brain's CORE heartbeat loop, making the wedge-reap INDEPENDENT of livehost \u2014 so this does NOT block counter-54. But the livehost silence is a latent anomaly with a SECOND consequence: live-agent HARNESS re-hosting was also delayed ~30", "doc": ""}, "REQ-LIVENESS-ORACLE-SOUND": {"title": "TEARDOWN-AUTHORITY W2 (todlando W1 gate-round-0 finding, doyle-scoped from the LANDED W1 code 2026-07-19): 'does this pid still exist' has ONE answer in spt-core and it is derived from the OS process table. TODAY spt-daemon/src/broker.rs session_is_zombie computes wrapper_alive from spt_store::proc::is_process_alive, which probes OpenProcess on Windows \u2014 and OpenProcess keeps SUCCEEDING for a TERMINATED process while any parent holds an open handle, which the broker ALWAYS does (Arc<PtySession>) for every PTY child it spawned. A correctly-reaped harness therefore reads ALIVE, flipping zombie_verdict off its PRIMARY class (Some(false) = dead root + surviving record = always a zombie) onto the conditional arm, which additionally demands adapter_labeled && past_grace && !has_live_descendants. CONSEQUENCE, live today: a dead-root session that is NOT adapter-labeled is claimed LIVE indefinitely \u2014 `endpoint run`'s dup-guard refuses ENDPOINT_ALREADY_LIVE over an already-dead tree and cmd_rest's Suspend alive_hint forces from=alive on the same false claim (both via has_live_session_honest, cli.rs:2014 and :3805). REQ-ENDPOINT-CYCLE-HONEST exists to give the cycle verbs ONE liveness authori", "doc": "7.50 The liveness oracle answers from the process table, never from a handle a caller still holds `[REQ-LIVENESS-ORACLE-SOUND]`: <!-- --> - **Failure (paid-for, found by todlando during TEARDOWN-AUTHORITY W1 gate round 0, 2026-07-19; latent in `session_is_zombie` since the cycle verbs were built):** `spt_store::proc::is_process_alive` probes `OpenProcess` on Windows, which keeps SUCCEEDING for a TERMINATED process while any parent still holds an open handle to it \u2014 and the broker holds `Arc<PtySession>`, hence such a handle, for every PTY child it spawned. So a correctly-reaped harness reads A"}, "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-MESH-2": {"title": "Member roster: node-level union-merge grow-set (per member: pubkey, label, machine_id, last-known address, last-seen \u2014 NOT the seed), the discovery directory the mesh dials by. Seeded IN FULL at pairing (seed-holder hands joiner the whole current roster, incl. offline members \u2014 folds in deferred pairing-time hostname capture + post-join address seeding); each node authors its own entry stamped with its lease_epoch, merged strictly-greater-wins (the node_label lease); exchanged only over seed-proof'd member connections; forgery-inert (a fake entry names a pubkey that still can't seed-proof). Removal needs a TOMBSTONE \u2014 a per-pubkey revoked marker that propagates, dominates the entry, gates admission (seed-proof \u2227 \u00actombstoned), and prevents reinsert; cleared by a completed re-pair of that pubkey. Persists through silence (offline member keeps its entry).", "doc": ""}, "REQ-MESH-6": {"title": "Concurrent liveness probes: `spt subnet status --nodes` fans out its offline/serve-probes (REQ-SUBNET-5) CONCURRENTLY \u2014 total wall-time bounded by the single-probe ceiling (~3s), never k\u00d7ceiling. The mesh makes a node see ALL members (many possibly offline), so a serial probe loop would be offline_count\u00d73s. (Planning verifies the current REQ-SUBNET-5 probe loop's behavior and fixes it if serial.)", "doc": ""}, "REQ-MSG-2": {"title": "spt binary CLI surface: send/ring/ready(+--once)/list/stop/whoami, stable arg shapes + exit codes", "doc": ""}, "REQ-MSG-3": {"title": "Ready-agent lifecycle: register perch (info.json + listener + registry address) on ready, drain spooled backlog on startup, clean teardown", "doc": ""}, "REQ-MSG-4": {"title": "Listener stream stdout emits EVENT envelope lines (sister-format, ADR-0001): parse the __REPLY_TO__ frame, pass pre-formed typed envelopes through verbatim (no double-wrap), compose <EVENT type=\"msg\" from=\u2026> otherwise, chunk oversized lines into EVENT-PART", "doc": ""}, "REQ-NET-2": {"title": "n0 relay default + self-host knob + plain-language disclosure", "doc": ""}, "REQ-NET-3": {"title": "Cross-node Psyche sync over P2P replaces gh-repo-sync", "doc": ""}, "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 \u2014 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 \u2014 every spt connection benefits. (next milestone)", "doc": "Robust WAN subnet join: meet-before-code + per-family bind gate: <!-- -->"}, "REQ-NODE-IDENTITY": {"title": "Ed25519 identity primitive: keypair, detached sign/verify, stable pubkey<->hex", "doc": ""}, "REQ-PAIR-2": {"title": "Local trust store with TOFU + warn-on-change", "doc": ""}, "REQ-PAIR-NTP-LOUD-FAIL": {"title": "W1/D2 (JOIN-TRUTH): total NTP failure (no server on any family answered) is LOUD, not silent \u2014 a node running the ceremony on its raw skewed system clock must be visible. ROOT: current_offset_secs (ntp.rs) does `query_offset_secs().unwrap_or(0)` and eprintln's ONLY on a nonzero success, so an all-servers-unreachable refresh is indistinguishable from 'clock agrees'. Fix: log the TRANSITION into all-servers-failed once per refresh (suggested `NTP_TOTP_UNCORRECTED: all NTP servers unreachable \u2014 ceremony clock = raw system clock`) and the recovery transition back to corrected; the OFFSET_TTL already bounds refresh cadence so no per-call spam. Fallback behavior (offset 0 \u2192 system clock) is UNCHANGED \u2014 this adds observability only.", "doc": ""}, "REQ-PEER-PUMP-CHURN-STALL": {"title": "B5 (F028, perri F-a; DEFECT daemon, OBSERVED-ONCE, HIGH): the peer pump STALLS under rapid rc attach/EOF-detach/--take churn. Fresh 0.22.0 daemon ~10min after restart, during rapid rc cycling: `peer pump: STALLED (last tick 122s)`; while stalled `spt rc --view` -> `RC_FAIL: attach request: brain IPC read deadline elapsed` (repeatable) and controlled-clear stopped propagating. Daemon restart recovered + endpoints auto-revived. Prior class: REQ-HAZARD-PUMP-IPC-DEADLINE (reader-thread+channel carrier), REQ-broker-QUIC-deadline (bounded_block_on) \u2014 something in the rc-churn path can still wedge the pump tick. perri holds exact timestamps + a repro candidate (rapid attach/detach/take against one endpoint) \u2014 REQUEST before RCA. See triage B5.", "doc": ""}, "REQ-PICKER-2": {"title": "The picker's project-history loader reads the git-backed context store, not the bare working tree: data.rs project_history_for enumerates an endpoint's projects via the BranchStore branch set (the context store keeps per-project context in git branches \u2014 contextstore::project_branch(project_id), checked out to projects/<project>/<id>/ only on-demand) instead of raw std::fs::read_dir over the empty working tree (which returned empty for ALL rows incl wall-a \u2014 the operator bug). Ordered newest\u2192oldest by branch commit recency; degrades to empty (informational pane), never fails. (v0.10.0)", "doc": ""}, "REQ-PICKER-3": {"title": "A self-owned subnet row reconciles its status to the LIVE roster: a Subnet-category row whose endpoint_id overlaps a local (is_local) roster id is self-owned (this node hosts it), so its status square is OVERRIDDEN with the live roster status \u2014 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 \u2014 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 \u2014 you are in your own subnet), only the STATUS is unified. (v0.10.0)", "doc": ""}, "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-PICKER-CONTROLLED-LOCAL": {"title": "#3 local half: a LOCALLY-controlled endpoint renders CONTROLLED in its own node's picker. display_status() (crates/spt/src/picker/model.rs:415) derives Controlled ONLY from driven_by.is_some(), but driven_by is REMOTE-only by design (KH 7.15) \u2014 a locally-controlled endpoint has driven_by=None + controlled=true, and local_rows (data.rs:220) never threads controlled into EndpointRow, so a locally-RC'd endpoint shows plain ONLINE in its own picker (remote rows are fine \u2014 gossip stamps controller_node=self, REQ-GOSSIP-CONTROLLED-ANY; the asymmetry is the bug). Fix: EndpointRow gains controlled:bool (local: rec.controlled; remote: controller_node.is_some()); display_status -> Controlled when driven_by.is_some()||controlled; desc pane says 'controlled locally' when the driver is unnamed. See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #3.", "doc": ""}, "REQ-PICKER-CURRENT-DIR-LABEL": {"title": "A-2/A-3 (F029, operator, semantic pair): the Choose-project rows must self-identify the CURRENT DIR. build_project_choices (picker/model.rs:322-352). A-2: when the run cwd IS already a history dir the `Here:` row is (correctly) suppressed by the dedup (REQ-PICKER-CHOOSE-DEDUP-ALL), but the matching history row rendered bare `r.display` with no cwd affordance \u2014 mark it `<display> (CURRENT DIR)`. A-3: the not-in-history current-dir row changes from `Here: <run_cwd>` to `CURRENT DIR --> <project>`, deriving the display the SAME way the history refs do (folder tail; honest fallback to the raw path when underivable). `cwd` payload unchanged. Grep-tests rule: 3 `starts_with(\"Here: \")` asserts (model.rs) + a `Here: /here` render assert (view.rs) are behavior assertions on the OLD label. See triage A-2/A-3.", "doc": ""}, "REQ-PICKER-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-NODE-GROUPING": {"title": "Bug #13: the endpoint run Subnet tab shows a machine once PER shared subnet (subnet_rows data.rs:253 iterates per-subnet, groups by subnet:node, no cross-subnet dedup). Fix: dedup by (node, endpoint_id) across the subnet loop, collect the set of shared subnet names per endpoint, emit one group per MACHINE (group = node_display) with its shared subnets listed beneath the machine name; reconcile per-endpoint status across subnets (most-alive). Couples REQ-ENDPOINT-LIST-PALETTE (both edit subnet_rows). See docs/NEXT-MILESTONE-BUG-TRIAGE.md #13.", "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] \u2014 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-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' \u2014 the user stays contextually informed about what they're picking (today the resume view replaces the whole screen). crates/spt/src/picker/view.rs (resume screen) + model screen state. See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #6.", "doc": ""}, "REQ-PRES-1": {"title": "Presence resolution: the presence datum (last_active_node, last_active_endpoint, ts) gossiped subnet-wide via the agent-interaction heartbeat (rides registry distribution, visibility-gated) + one first-class most-recently-active resolution API consumed by notif first-fire, update-consent delivery, consent escalation, and shell wake resolution (M5 scope decision 1: resolution only \u2014 the PresenceChannel endpoint stays deferred)", "doc": "Deferred Features: | Feature | Cut from | Why deferred | Trigger to revisit | |---|---|---|---| | Scrollback on-disk spillover | terminal wrapper v1 | In-memory ring covers the common case; spillover adds a persistence/rotation story | First long-running session that overflows the ring usefully, or any \"scroll back further than the buffer\" user need | | Sidecar adapter process (long-running, wire-protocol) | harness contract v1 | Manifest + `spt.exe` subcommand surface covers v1 harnesses; sidecar only earns its keep for streaming / in-memory cross-event state | A harness outgrows manifest+hoo"}, "REQ-PROJECT-INDEX-STORE": {"title": "PROJECT-INDEX W1 (ADR-0037, RCA .claude/reports/2026-07-10-hertz-session/03): spt-store owns the VERSIONED materialized project-index format + read path. Reader contract: read one compact versioned index, join with the local perch roster, return immediately; stale/missing renders last-known-good or '-'; NEVER fall back to synchronous git enrichment; daemon-offline readers consume the last persisted snapshot; truncated/schema-mismatched index degrades to fast reads + last-known-good, never an error stall. Gate: impl \u2014 format + store read path; unit \u2014 version/schema-mismatch/truncation degradation legs + join semantics; doc \u2014 CONTEXT.md project-index entry + STORAGE.md section. Kin REQ-PROJECT-INDEX-WRITER (the producer), ADR-0037.", "doc": "Self-update: **project index** \u2014 a node's endpoint\u2192project attribution is DERIVED state, held as a **persistent materialized index** (ADR-0037): `spt-store` owns the versioned format + read path (daemon-offline reads = last persisted snapshot); the **daemon is the sole single-flight writer** (load-at-start, ready-without-warm, background batched reconcile, atomic replace, coalesced event-driven invalidation keyed on branch-tip fingerprints, last-known-good on failure). Readers \u2014 list, picker, endpoint-info, hooks \u2014 join index \u00d7 perch roster and **never run git**; stale renders last-known or `-"}, "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 \u2014 a real ~20KB doyle psyche-download exceeds the win32 CreateProcess lpCommandLine ~32k cap \u2192 the shim spawn BRICKS. FIX (file-always, replaces {psyche_context} outright \u2014 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 \u2014 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 \u2014 core writes exactly 0", "doc": "then it exits \u2014 no resident process, no detach.: <!-- --> **Psyche-download \u2014 `{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 \u2014 a real ~20 KB mind exceeds the win32 command-line cap and would brick the spawn). The soft **fresh-vs-continue** discriminator is the file's **content**, not key presence: a **fresh** (first / reseeded) turn writes the composed mind"}, "REQ-PSYCHE-CRASHLOOP-BACKOFF-SHUTDOWN": {"title": "C3 (F028, perri F-h): psyche wrapper crash-loop has no backoff, and `endpoint shutdown` misses a wedged wrapper. A probe psyche crash-looped ~3 boots/sec for ~30min (CC died instantly on the untrusted owlery cwd; ledger hit ordinal 5358) \u2014 SILENTLY; and `spt endpoint shutdown` did NOT tear the looping wrapper down (docs say shutdown tears the Psyche with the perch; manual kill was required). FIX: (i) bounded backoff + loud give-up on a psyche boot loop (the psyche_host_error surface already exists), (ii) shutdown must cover a wedged/looping wrapper. See triage C3.", "doc": ""}, "REQ-PSYCHE-EPHEMERAL-DRIVER": {"title": "W1 (F030, design \u00a73): each psyche-relevant event runs exactly ONE bounded per-event turn through the existing driver stack (psyche_turn_and_relay for outbound-intent events / resume_psyche for session-custody transitions / run_psyche_turn for pure merges) \u2014 no resident psyche process exists between events. host_one (livehost.rs:518) STOPS spawning spawn_psyche_owned; the pulse loop stays as the daemon-side scheduler (thread + stop-flag + drop-dir watch correct) but a fire now invokes one bounded turn, daemon-driving every substitution key from daemon-known context (child never self-resolves home/subnet/perch \u2014 direction-(a) multi-subnet churn impossible by construction). Turn failures consume a bounded failure budget (C3(b) shape): N consecutive failures \u2192 psyche_host_error stamp + cooldown, reset on success; no respawn storm (nothing resident to respawn). Red-first: fire an event on a hosted live endpoint \u2192 assert one turn ran (SIDE-EFFECT PROOF FILE \u2014 transcript-jsonl asserts are structurally blind, 2026-07-04 rig lesson) and no {id}-psyche process survives the turn.", "doc": "Endpoint types: **Psyche**: The Psyche companion's own perch, distinct from its paired LiveAgent's perch. First-class endpoint type so messages addressed to a LiveAgent's Psyche route directly without ambiguity. **A Psyche is a bounded per-event turn, not a resident process (since v0.25.0).** Each psyche-relevant event (a pulse fire, a commune/signoff drop, a session-custody transition) runs **exactly one** bounded turn through the psyche role template, spawned by the daemon, which exits at turn end \u2014 there is no long-lived psyche loop or psyche pid between events. <!-- --> **Liveness = turns"}, "REQ-PSYCHE-NESTED-RESOLUTION": {"title": "W4 (F030, design \u00a73; F-017 sibling, general not psyche-only): nested {parent}/{nested} ids resolve under the PARENT's home \u2014 subnet-less resolution for nested perches (the home-ASSIGNMENT seam, not perch-path which is already subnet-less) \u2014 so child-side verbs acting on nested perches need no --subnet the child cannot know. ROOT: home::assign_home returns Ambiguous on a multi-subnet node w/o --subnet; a nested id must instead derive home from its parent perch. Additionally expose a SINGLE {subnet} base_keys fill WHEN KNOWN (own_subnet = home-subnet label, 'local' when unhomed; absent \u2192 LOUD missing-key fail, the {node} precedent). NO {home} key (doyle W4 Q1: the endpoint home subnet is ONE concept per CONTEXT.md:640, and 'home:' is already the subnet-name qualifier position CONTEXT.md:652 \u2014 a {home} template key invites meaning-drift). Red-first = evidence #3's exact repro: 2-subnet home, nested-id verb, must succeed (was: `spt ready <id> --once` \u2192 exit 1 READY_FAIL \u2026 pass --subnet).", "doc": "then it exits \u2014 no resident process, no detach.: <!-- --> **`{subnet}` \u2014 the endpoint's home-subnet label.** A single `{subnet}` key fills to this endpoint's home-subnet label (`local` when unhomed), supplied whenever it is known so a psyche turn need never resolve a `--subnet` it cannot know; when no subnet is known the key is left unfilled so a referencing template fails **loud** (the `{node}` precedent). There is deliberately **no `{home}` key** \u2014 the endpoint home subnet is one concept, and `home:` is already the subnet-name qualifier position, so a `{home}` template key would only invite"}, "REQ-PSYCHE-TURN-STREAM-EVIDENCE": {"title": "A failed psyche turn preserves BOTH captured streams as evidence: TurnError::Failed carries the child's stdout alongside stderr, and the failure display appends a bounded single-line stdout TAIL (last ~500 bytes, UTF-8-boundary-safe cut, newlines collapsed, the literal <EMPTY> when the stream said nothing \u2014 absence stated, never implied by a missing field). WHY (2026-07-26 spend-limit RCA): a failed turn's stdout is not psyche output, so BOTH core (turn.rs kept {status_code, stderr} only) and the adapter (guarding its outbound channel) independently discarded it \u2014 the same blind spot implemented twice \u2014 and an account-level outage surfaced as a bare 'claude exited exit code: 1' with the decisive refusal text thrown away at two layers; three agents then chain-hypothesized on an error string the real failing path never emitted. The tail is cause-agnostic instrumentation: it does not care what the failure is, which is why it survives being wrong about it. Adapter twin: claude-spt v0.25.14 dual-stream tail (shipped 2026-07-26).", "doc": ""}, "REQ-RC-1": {"title": "`spt rc <id>` \u2014 user CLI attaching a local terminal to a broker-held PTY, reusing the cross-node attach machinery (attach.rs request_attach \u2192 send_attach_input pump, spt-net AttachRecord codec); local attach is the degenerate single-node case of the cross-node path (rides REQ-TERM-3 byte-stream streaming). Read-only `--view` (watch, no stdin forwarded). Clean detach that does NOT terminate the broker-held session (KNOWN-HAZARDS: PTY ownership stays with the broker; no termination on detach). Explicit detach keybind that cannot collide with harness passthrough input (legacy capsule used a ctrl-b prefix); documented. ConPTY DSR auto-answer in the attach reader (hazard 5.5).", "doc": ""}, "REQ-RC-CROSS-NODE-ATTACH": {"title": "Bug #4: spt rc to a remote endpoint fails with 'no live session' though endpoint list shows it Active \u2014 rc.rs:1063 resolves only the LOCAL broker session table and always dials loopback, never consulting the registry or dialing the owning node (the cross-node attach transport exists in the broker; only the client leg is missing). Fix: on a local resolve miss, resolve the owning node from the registry (reuse resolve_across_visible), net_dial that node, and run a remote session-resolve + serve_attach round-trip (mirror the wansend resolve-dial-round-trip pattern). Shares the resolve-owning-node primitive with REQ-WAN-SEND-DELIVERY. See docs/NEXT-MILESTONE-BUG-TRIAGE.md #4.", "doc": "Instances: <!-- --> **Remote-control vs local operation (two distinct modes \u2014 not the same as instances):** - **Operate locally:** drive the native instance on *your* machine (its local files, its synced mind). The normal case. - **Remote-control (Shell-like):** attach a control/view surface to an instance *running on another node* \u2014 compute + files stay remote; you are a viewport (the byte-stream terminal attach, daemon-to-daemon over Iroh). Used when you specifically want *that machine's* environment. This is effectively a Shell (a driven surface, user\u2192agent direction), separate from the ins"}, "REQ-RC-HARNESS-ONLY-REFUSAL": {"title": "RC-RENDER-TRUTH W1 (ADR-0042 decision 3, hertz elevated-endpoints RCA core leg 1): direct `spt rc <id>` preflights the already-known harness_only/controllable=Some(false) fact (local + gossiped rows both carry it) and refuses BEFORE any attach/stream with truthful copy naming the actual state ('online but harness-hosted; spt does not own its terminal') \u2014 never the stale-row guess ('it may have ended, or the registry row is stale'). Reproduced deterministically 2x live on an ONLINE - HARNESS ONLY endpoint. RC support for harness-hosted endpoints is NOT claimed until the elevated PTY-host satellite exists (standing ruling). Gate: impl \u2014 pre-stream preflight + truthful copy; unit \u2014 harness-only row refuses pre-dial with the new copy, non-harness rows unaffected; int \u2014 live remote Instance{harness_only:true} => direct rc refuses pre-stream, names harness-hosted, never says stale; doc \u2014 ADR-0042.", "doc": "Decisions: <!-- --> 1. **`spt rc` consults the honest-session authority before the offline fast-fail.** Normal `spt rc <id>` runs the same bounded `SessionProbe::has_live_session_honest` gate `endpoint run` uses (ADR-0041 single liveness authority). An honest session exists \u2192 attach via the session-confirmed path regardless of persisted status. No honest session \u2192 the existing offline refusal stands. A claimed session with a dead client tree \u2192 refusal/reap, never attach. Reuse `SessionProbe`; no new liveness heuristic. <!-- --> 2. **Resume stamps UNBOUND.** A resume launch transitions an exist"}, "REQ-RC-KEY-VT-TRANSLATE": {"title": "On Windows, `spt rc` translates CONSOLE KEY EVENTS to standard xterm VT so ALL keys reach the hosted harness \u2014 arrows/Home/End/PgUp/PgDn/Insert/Delete/F-keys, every modifier combo, Backspace/Ctrl+Backspace \u2014 not just the byte-emitting ones. ROOT (operator HITL, doyle /diagnose): `spt rc` reads raw STDIN BYTES (spawn_stdin_reader, std::io::stdin().read); on the Windows LEGACY console (no ENABLE_VIRTUAL_TERMINAL_INPUT) the special keys produce console KEY_EVENTs, NOT stdin bytes, so the byte-pump sees nothing \u2192 those keys are DEAD. Enabling ENABLE_VIRTUAL_TERMINAL_INPUT was rejected (W7 dc07c39): on Windows Terminal it yields harness-specific win32-input-mode + broke ctrl-b detach. FIX (agnostic, full fidelity): on Windows, replace the stdin byte-read with a crossterm EVENT source (crossterm 0.28 already a dep; the picker already reads events) and translate each KeyEvent \u2192 STANDARD xterm VT bytes via a PURE translate_key_event(KeyEvent)->Vec<u8> (copy a known-correct xterm table verbatim, ADR-0001 spirit), forwarded through the SAME rc pump \u2014 the harness receives ordinary xterm VT (harness-AGNOSTIC, no win32-input-mode). Press-only (drop Repeat/Release). Detach stays the ctrl-b+'d' P", "doc": "Shell sleep/wake (offline \u2194 online): <!-- --> **rc keyboard input (Windows VT translation, v0.13.0 bug 2).** On **Windows** an interactive `spt rc` console reads crossterm **key events** and translates each to **standard xterm VT** (`translate_key_event`) \u2014 arrows / Home / End / PgUp/Dn / Insert / Delete / F-keys + modifiers all reach the harness as the universal terminal contract (**agnostic**, NOT win32-input-mode; the legacy console delivers those keys as events, not bytes, so the old byte-pump left them DEAD). **Unix passes through** (its raw-mode stream is already VT; cfg-split, zero Unix"}, "REQ-RC-QUALIFIED-TARGET-CANONICAL": {"title": "RC-RENDER-TRUTH W1 (ADR-0042 decision 4, hertz elevated-endpoints RCA core leg 2, doyle seam-confirmed rc.rs establish_attach): the resolver's canonical BARE endpoint id is carried separately from the user-facing qualified target \u2014 AttachRequest.endpoint_id is always the bare id (today rc passes the ORIGINAL qualified string; the target's resolve_local_session compares verbatim vs the bare HostedSession.endpoint, so `spt rc id@node`/`subnet:id` dials the RIGHT node then gets a false no-live-session refusal). N-1-additive: bare-form callers are unchanged. Gate: impl \u2014 canonical-id carry through establish_attach; unit \u2014 Address::parse qualified forms yield bare wire id, user-facing copy keeps the qualified spelling; int \u2014 bare + id@node + subnet:id ALL attach against a remote broker-hosted target, wire always carries the canonical bare id; doc \u2014 ADR-0042.", "doc": "Decisions: <!-- --> 1. **`spt rc` consults the honest-session authority before the offline fast-fail.** Normal `spt rc <id>` runs the same bounded `SessionProbe::has_live_session_honest` gate `endpoint run` uses (ADR-0041 single liveness authority). An honest session exists \u2192 attach via the session-confirmed path regardless of persisted status. No honest session \u2192 the existing offline refusal stands. A claimed session with a dead client tree \u2192 refusal/reap, never attach. Reuse `SessionProbe`; no new liveness heuristic. <!-- --> 2. **Resume stamps UNBOUND.** A resume launch transitions an exist"}, "REQ-RC-RESIZE-GEOMETRY-EPOCH": {"title": "A resize transition is ATOMIC from ScreenGrid's perspective: every byte is parsed at the geometry it was EMITTED under. (hertz v0.39.0 field RCA 2026-07-21, doyle re-grounded at source the same hour; P0 \u2014 this falsifies a SHIPPED v0.39.0 release claim, so it is a regression, not new scope.) SYMPTOM: right-margin fragments and left-shifted rows on a node where CLI and broker are BOTH 0.39.0 \u2014 i.e. exactly the defect the v0.39.0 notes claim fixed. SOURCE (statically provable, no repro box required): broker.rs dispatch_resize calls session.resize(SurfaceSize::new(rows, cols)) (~4557-4559) and only THEN recover_log(&log).set_size_and_notify(rows, cols) (~4560), which reaches self.grid.resize(rows, cols) (~1492) \u2014 TWO SEPARATE recover_log acquisitions, so the reader thread's append/parse interleaves between them BY CONSTRUCTION. On ConPTY the resize ITSELF emits a full repaint asynchronously; the reader parses that repaint into the grid at the OLD geometry; the subsequent top-left-preserving grid.resize then faithfully preserves an ALREADY-MISWRAPPED model; the cold attach repaint emits the shift. WHY THE EXISTING TESTS PASS: the spt-term width oracle (REQ-SCREENGRID-WIDTH's int stage) ", "doc": "Decision: <!-- the resize barrier + geometry epoch on the dispatch_resize / set_size_and_notify path: hold the grid parse across the ordered surface+grid transition, tag held output with the geometry it was emitted under, replay it at that geometry, and roll back on a failed surface resize. Includes the SHRINK counter-example that rejects the reorder. -->"}, "REQ-REACH-1": {"title": "Off-node remote-drive detection + file transfer", "doc": ""}, "REQ-REDISPATCH-FINISHED-RETIRE": {"title": "REDISPATCH-TRUTH W1 (ADR-0038, hertz fix A): finished/terminal stream rows are RETIRED from redispatch eligibility \u2014 NetShared.streams today has NO removal path (single insert nethost.rs ~649; StreamLog::finish only marks) so every dispatcher generation re-enumerates every historical stream forever. Retire terminal rows from the enumeration the dispatcher claims from (remove, or lifecycle-exclude), preserving only the post-EOF state genuinely needed by other readers (presence/log reads); bounded growth replaces forever-discoverable rows. Clearing the whole table on brain restart is REJECTED (destroys live streams' reconstruction facts). Gate: impl \u2014 the retirement path; unit \u2014 a finished stream is invisible to the dispatch enumeration while an active one stays claimable + post-EOF reader state survives retirement; doc \u2014 rides ADR-0038 + the triage doc. Kin REQ-HAZARD-REDISPATCH-CONTROL-STEAL (the invariant it satisfies), REQ-STREAM-OPENER-DURABLE.", "doc": "Context: ## Decision <!-- -->"}, "REQ-REGISTRY-APPLY-TRANSACTIONAL": {"title": "REGISTRY-LIFECYCLE W1 (ADR-0040 decision 3, hertz defect B leg 3): registry feed application is TRANSACTIONAL per feed \u2014 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 \u2014 the per-chunk rewrites are what stalled IPC drain and manufactured the 15s seat-writer poisons). Gate: impl \u2014 accumulate-then-apply; unit \u2014 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 \u2014 rides REQ-HAZARD-REGISTRY-STALL seam (snapshot writes O(feeds), zero poisons); doc \u2014 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-REGISTRY-REPLAY-BOUNDED": {"title": "REGISTRY-LIFECYCLE W1 (ADR-0040 decision 4, hertz defect B leg 4, defense-in-depth behind REQ-ONEWAY-STREAM-TERMINAL): a deadline-poisoned one-way (Registry) replay carries a per-stream strike budget; at budget the row retires TERMINAL with loud telemetry (DISPATCH_EV event). Safe by family semantics: a feed is an idempotent snapshot advertisement, the next pump round (~30s) re-advertises \u2014 worst-case loss is one advertisement round, already the family freshness unit. Request/reply families keep ADR-0038 Amendment circuit-breaker semantics \u2014 terminal-at-budget is ONE-WAY-FAMILY-ONLY (never a revert toward burn-the-claim abandonment for request streams). Gate: impl \u2014 strike budget + terminal retire, one-way-scoped; unit \u2014 poison strikes accumulate per stream, budget retires terminal + emits, request/reply family unaffected by the budget path; int \u2014 poisoned registry replay stops redispatching after budget while fresh feeds keep serving.", "doc": ""}, "REQ-REL-3": {"title": "Two-key release-signing trust anchor: primary + offline never-used recovery, both pubkeys embedded in the binary's trusted set, manual local signing (ADR-0015)", "doc": ""}, "REQ-RESIDENT-SERVICE": {"title": "ResidentService substrate (ADR-0049, design ratified 2026-07-26): a daemon-supervised binary an adapter declares via a `[service]` manifest section \u2014 core-owned from birth, NO perch/identity/address. SPAWN: the daemon launches it job-neutrally (detached_no_inherit + the cold-start ladder posture), so it is never a shell's child (`/T` tree-kill cannot reach it; the shell-descendant hazard class of REQ-SHELL-ADAPTER-OWNED-DETACHED-SERVICE never arises) and never inside a launching terminal's Job Object (the REQ-SHELL-CLI-SPAWN-JOB-EXPOSURE service half closes by construction). START TRIGGER declared in the manifest: start = 'boot' or start = 'bind'; supervised identically once running, with the wake-watcher scaffolding (backoff, give-up latch, one-per-instance lock, orphan-kill, brain-side reconcile) minus the offline-only flip. 'boot' is DESIRED-STATE-RUNNING, not an event: the supervisor reconciles a boot service toward running at EVERY opportunity \u2014 daemon boot, ADAPTER REGISTRATION while the daemon is live (operator addition 2026-07-26: installing or registering an adapter whose manifest declares a boot service starts it THEN \u2014 spt itself is never restarted to bring a new adapter", "doc": "`[service]` \u2014 a daemon-supervised resident service (ADR-0049): <!-- --> - **`command`** \u2014 an **opaque** command string (program token plus args), like every other command seam. Its program token resolves against the adapter **install dir** before PATH (REQ-INSTALL-11), and args support adapter-static `{adapter_dir}` / `{adapter_name}` substitution only. Must be non-empty: a declared service means spt-core owns and supervises a process. - **`start`** \u2014 **required**, no default. `\"boot\"` is **desired-state-running, not an event**: the supervisor reconciles the service toward running at daemon bo"}, "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 \u2014 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 \u2014 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 \u2014 input bytes must never come back as output `[REQ-RESIZE-INPUT-MODE-INTEGRITY]`: <!-- --> - **Failure (paid-for, second ENLYZEAM production capture 2026-07-22 \u2014 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 \u2014 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-RESUME-CONTEXT-PULL": {"title": "Adapter-callable resume-context pull verb + not-yet-synthesized commune/signoff drop append (legacy-SPT parity, operator-directed 2026-06-24). GAP: spt-core exposes NO verb for a harness adapter's SessionStart hook to pull an agent's resume context \u2014 `resume::download_psyche_context` (spt-live/src/resume.rs:88, composes <live-role>+<live-context>+<project-context> from the durable two-tier store) is INTERNAL with ZERO spt callers and no ApiCmd verb (api/mod.rs ApiCmd enum has none); resume.rs:9 documents the intended 'adapter pulls it in its SessionStart hook' path but it was NEVER wired. Result: a harness adapter cannot inject the agent's durable mind on resume at all (claude-spt today runs only `api boundary` session-rotation + an identity brief \u2014 the agent resumes WITHOUT its mind). TIER-1 SCOPE (operator-approved; Tier-2 = drift-stamp/<current>/drift-directive + <memformat> + Pulse-Log DEFERRED to a separate parity item, NOT v0.15.0 \u2014 the legacy download_payload [claude_skill_owl context.rs:344] is richer but memformat is roadmap-deferred + drift-stamp is an orthogonal cross-machine-drift feature). TWO PARTS: (1) EXPOSE `spt api psyche-download <id> [--session-id <sid>]` -> std", "doc": "Manifest seams (outbound contract, detailed): **resume-session seam** \u2014 two distinct forms: - **fresh-with-preload:** resume with *cleared* context (a fresh session) + psyche-download. Accepts a `$psyche-context` key to launch the fresh session with the psyche-download preloaded \u2014 or the adapter instead pulls it via an spt-core command in its SessionStart hook. <!-- --> That command is **`spt api psyche-download <id> [--session-id <sid>]`**: it emits the durable resume brief (role \u2192 live-context \u2192 project-context, project resolved from the perch's bound cwd) to stdout for the adapter's Session"}, "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` \u2014 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 \u2014 FLAG perri BEFORE touching the manifest, adapter-boundary rule). Int: resume template receives the ledger UUID, not the spt sid. See triage B2.", "doc": ""}, "REQ-RESUME-ROW-PER-PROJECT": {"title": "A5 (F028, operator #6): resume-from-history labels EVERY session with the endpoint's newest project. data.rs:480-496 resume_rows_for clones project_history.first() onto every ResumeRow (line 481/488), so all sessions read as the head project (the ghost). The per-row e.cwd is already carried for launch-into-dir. FIX: derive per-row project_id_for_dir(e.cwd) (owlery-excluded -> fall back to trigger token), rendered through A1's display-name path. See triage A5.", "doc": ""}, "REQ-RUN-MULTISUBNET-HOME": {"title": "`spt endpoint run` resolves the home subnet at the skeleton-create step and pre-creates the skeleton perch carrying it, so the harness `bind` inherits home via establish_perch's immutable prior-branch (no hook change, no env injection). Resolution: sole-subnet auto; multi-subnet + no --subnet + NON-interactive terminal -> refuse early with MRU-ordered --subnet guidance (never the silent 25s online-timeout); multi-subnet + no --subnet + INTERACTIVE -> print proposed config (id/project/adapter[:profile]/home=MRU-default) + 'Ok to proceed? Y/n', n -> --subnet guidance; --subnet overrides + validates membership. MRU = ordered move-to-front LISTs at two levels (per-project + always-updated node-global fallback). Home stays IMMUTABLE (ADR-0010). Fixes the LATENT multi-subnet bringup gap (perri, not a regression \u2014 HOME_REFUSED established >=0.11.0; exposed by the node crossing 1->2 subnets). (ADR-0026)", "doc": "Multi-subnet home resolution at `endpoint run` creation: <!-- -->"}, "REQ-RUN-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) \u2192 enter a charset-validated id \u2192 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 \u25a0 / offline gray \u25a2 \u2014 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 \u00b7 best-effort project history newest\u2192oldest from the contextstore p-<project> branches, empty-if-none \u00b7 `spt endpoint description`). Confirm layer offers status-dependent options \u2014 Attach/Start/View (rc pump / cmd_endpoint_run) \u00b7 Instantiate-locally (remote) \u00b7 Change-harness-adapter (offline) \u00b7 Fork (cmd_fork) \u00b7 ", "doc": "Shell sleep/wake (offline \u2194 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 \u2014 a picker selection bakes exactly that path. **Layer 1** picks the kind (*Create new* | *Pick existing*). **Create-new** chooses a registered `kind=\"harness\"` adapter with its shipped+loc"}, "REQ-SEAM-HISTORY": {"title": "History subsystem (fetcher / locate-normalize / native store)", "doc": ""}, "REQ-SEAM-INJECT": {"title": "inject-input methods configurable per activity-state", "doc": ""}, "REQ-SEAM-POSTSPAWN": {"title": "post-spawn / api bind seam with boot nonce", "doc": ""}, "REQ-SEAM-PSYCHE": {"title": "spawn-psyche seam (fresh + resume templates)", "doc": ""}, "REQ-SEAM-RESUME": {"title": "resume-session seam (fresh-with-preload / continue-existing)", "doc": ""}, "REQ-SEAT-LIFETIME-BOUNDED": {"title": "REGISTRY-LIFECYCLE W1 (ADR-0040 decision 5, hertz defect B leg 5 CROSS-FAMILY per the final attribution correction \u2014 my_stream_subs releases only at conn-loop exit, NO unsubscribe verb existed, ~546/589 broker threads were unnamed parked SubscriberSeat writers on completed Registry/sync/update seats): new KIND_NET_STREAM_UNSUBSCRIBE broker verb removes the SubscriberSeat, stops AND JOINS its writer thread, drops its cursor; every dispatch worker calls it on serve completion (ALL families, success or failure); the retire sweep calls it for retired rows. N-1: unknown-kind on an older broker = tolerated send_error path, caller best-effort. Rider: spawned daemon threads NAMED (SubscriberSeat writer sub-writer-s<id> minimum, conn accept handlers) \u2014 field censuses attribute, not infer. Gate: impl \u2014 verb + worker/sweep call sites + thread naming; unit \u2014 unsubscribe removes seat/joins writer/drops cursor + idempotent + unknown-stream tolerated; int \u2014 rides REQ-HAZARD-REGISTRY-STALL seam (physical seat/thread counts plateau CROSS-FAMILY after completion); doc \u2014 ADR-0040.", "doc": "Decision: <!-- --> 1. **One-way (fire-and-forget) stream families are terminal at FIN, sender-side.** The registry feed pump retires its own row after successful write+FIN via the existing `net-stream-retire` verb (best-effort on N-1 brokers, per ADR-0038 A). A one-way family's exchange is definitionally over at FIN; keeping the row eligible reproduces the O(history) defect forever. 2. **Eligibility filtering is server-side.** `stream_infos` excludes `initiated_locally` rows (alongside `retired`) before serializing. Consumers keep their client-side guards (double-filter harmless; N-1 compatibl"}, "REQ-SELF-DETECT-PARENT-PID": {"title": "E-1 (REMOTE-TRUTH triage \u00a7E-1 #7): self-detect leg (c) \u2014 the pid-ancestry fallback \u2014 ALSO candidates on `rec.parent_pid` (the harness pid, CONTEXT's 'stable session-binding anchor', stamped at bind), not `rec.pid` alone. ROOT: for an spt-hosted endpoint (broker PTY, headless) `rec.pid` is the ephemeral bind-CLI pid, ALREADY DEAD by send time (the F-026 #11 dead-pid class, field-sighted on hall-bf) \u2014 never in any sender's ancestry and alive-gated out \u2014 so an spt-hosted sender could NEVER resolve self via leg (c): its messages were from-stamped `cli@NODE` (operator #7) and replies bounced NO_PERCH. FIX: detect_self_by_ancestry pushes a second candidate (id, parent_pid) when `rec.parent_pid` is Some + alive; the pure nearest-first matcher (match_self_by_ancestry) is unchanged. LABEL-ONLY, exactly like the rest of leg (c): from-label/routing default, NEVER authentication \u2014 authenticate() untouched, the pid-ancestry-for-auth question stays parked (KH 7.3/7.5 separation holds; a wrong label self-corrects, a wrong grant does not). Env legs (a)/(b) stay first. Red-first int (the triage-specified missing test): rec.pid = dead sibling + rec.parent_pid = genuine live ancestor \u2192 self resolves ", "doc": ""}, "REQ-SELF-ID-TRUST-INJECTED-ENV": {"title": "DEFERRED to a followup vX.X.n sprint (post-LIFECYCLE-TRUTH, operator-ruled 2026-07-07): self-identity resolution must trust the harness-injected authoritative id and detect a stomped perch instead of silently mis-attributing. ROOT (doyle /diagnose 2026-07-07, field: agent sends stamped `cli@HFENDULEAM` / mis-attributed): `resolve_from` (cli.rs:5480) stamps `cli@<node>` when `detect_self_id` (roster.rs:103) returns None; detect_self_id resolves self ONLY by reverse-lookup \u2014 matching `$OWL_SESSION_ID` against a perch's info.json.session_id (then SPT_AGENT_ID, then parent_pid) \u2014 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 \u2014 the REQ-SPAWN-COLLISION-GUARD-LIVE-DUP damage class; field case: doyle's live session_id written into the deployah perch), the reverse-lookup mis-resolves (doyle session -> `deployah`) or fails (real deployah -> None -> `cli@node`), and the CLI silently believes the stomped store. FIX: detect_self_id PREFERS `SPT_ENDPOINT_ID` when set+non-empty (the harness-authoritative id, immune to a stompable perch), AND cross-checks it again", "doc": ""}, "REQ-SEND-REPLYTO-REMOVE": {"title": "Remove `--reply-to` from `spt send` \u2014 a target-fallback + REPLIED-label nicety that confuses agents, with no wire effect (ADR-0020 already made messages structural (from,body), no __REPLY_TO__). Hard-remove (no deprecation shim): the clap flag, the is_reply/REPLIED label branch (always SENT/QUEUED), the `send` how-to --reply-to example, and the reply-to mention in REQ-DOCS-6's send topic. Reply-correlation stays on the structural `from` attribute. (v0.16.0)", "doc": ""}, "REQ-SESSION-ADAPTER-RECORDED": {"title": "D-2 (REMOTE-TRUTH triage \u00a7D-2 + operator Q5 @c248afc): the session ledger records the adapter[:profile] a session ran under, so a later resume can restore the harness the session actually used (not merely the endpoint's CURRENT stamp). ROOT: SessionEntry (spt-store/sessions.rs:58) carries ts/session_id/trigger/cwd/ordinal but NOT the adapter \u2014 a resume-from-history row cannot know which harness authored the transcript, so a resume under a since-changed endpoint adapter (B-2 ChangeAdapter, or a fork) launches the wrong harness. FIX: an ADDITIVE `adapter: Option<String>` on SessionEntry, exact cwd/ordinal serde pattern (#[serde(default, skip_serializing_if=\"Option::is_none\")]) \u2014 a pre-migration row missing the key deserializes None; None omits the key on serialize (byte-identical to old rows); an unknown key on an old reader is ignored (serde default) \u2014 back-compat BOTH directions. Stamped at every PRODUCTION session-boundary append. CENSUS (doyle-confirmed @94f0205, corrects the triage-era 5-site drift to the real 3): startup.rs:317 (live bind boot row, rec.adapter in scope), reporting.rs:94 (boundary rotation row UNDER the mutate_info lock, capture adapter_for_ledger=rec.adapter be", "doc": ""}, "REQ-SHELL-3": {"title": "Drive channel (owner->shell, REST-only, never-spooled, latest-wins): the owner->shell mirror of sensory for continuous real-time control (scroll/crank/stick/avatar) \u2014 a [shell.drive] manifest vocab + EVENT_TYPE_DRIVE frame, delivered to the ONLINE binary only via a single live slot (a new frame supersedes an undelivered one \u2014 no spool, no queue, no replay on relink), dropped-with-diagnostic if the shell is offline; cross-node rides the ephemeral link (REST class), never the durable shell spool. Commands = discrete+durable; drive = continuous+ephemeral (CONTEXT:260, minted 2026-06-11 Gateway grill).", "doc": ""}, "REQ-SHELL-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 \u2014 shell_command / shell_text / shell_file (+ shell_close, sensory, drive) \u2014 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 \u2014 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 \u2014 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-SHELL-LIST-DERIVED-PROVENANCE": {"title": "SEED (inactive \u2014 observability, perri-backed 2026-07-26): a status a reader can act on should be distinguishable from a status a record actually holds. Since REQ-HAZARD-SHELL-STALE-ONLINE, `shell list` renders DERIVED online-ness (recorded status AND pid-liveness), deliberately healing poisoned records at the read gate \u2014 but nothing marks WHICH rows were healed, so an agent doing forensics reads the healed view and infers a clean record (field, twice on 2026-07-26: liam inferred close_shell had run when the raw info.json still said online over a corpse pid and proposed spending flynn's live instance to manufacture a specimen they already owned; perri independently named the same misread class from their own work). Shape at activation: an additive marker on the --json row when derived != recorded (e.g. recorded_status alongside status, or derived=true) \u2014 additive-evolution posture, text view unchanged or minimally annotated; NEVER a behavior change to the derivation itself. Kin: REQ-DAEMON-BITS-AMBIGUITY (silent wrong-state a human catches only by staring at the right field) and the NEVER-SEALING-OBSERVABILITY candidate \u2014 same class, view-vs-truth.", "doc": ""}, "REQ-SHELL-PERCH-DIR": {"title": "A shell binary can mechanically resolve where its files land. (flynn spt-alchemy clean-room audit 2026-07-21, doyle code-verified, seed pair item 2, P1 \u2014 HARD-GATES flynn's alchemy W4.) TODAY: `spt shell send --file` lands the blob at <shell-perch>/files/<xfer-id>-<name> and the shell_file frame's path attr is PERCH-RELATIVE (files/...) \u2014 but the spawn template substitution keys are ONLY {id}/{adapter_name}/{link_token} (shellhost.rs fill_spawn_command) and the spawned child inherits the BROKER's cwd, so no mechanical perch resolution exists: the binary cannot turn the frame's path into a real file without guessing SPT_HOME layout. DOYLE RULING, both halves binding: the frame KEEPS the perch-relative path (an absolute path in a spooled frame LIES across perch moves and node boundaries \u2014 frames outlive layouts); the fix is an ADDITIVE {perch_dir} spawn-template substitution key (opt-in \u2014 templates that do not use it are byte-identical, N-1-safe by construction) filled with the shell perch dir so the binary receives its root at spawn and joins the frame's relative path against it. REFUSED, recorded so it is not re-proposed: blessing SPT_HOME layout guessing as an interim contract. Pu", "doc": "`shell_text` \u2014 free text: <!-- --> ## `shell_file` \u2014 a landed file"}, "REQ-SPAWN-COLLISION-GUARD-LIVE-DUP": {"title": "W4 (LIFECYCLE-TRUTH): single-flight wake per endpoint \u2014 the WAKE/RESUME respawn seam must not launch twice for one wake. ROOT (perri parentage + recovered filing): one wake processed TWICE within 1s \u2014 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-STAMP-CONVERGENCE-ORDER": {"title": "RETIRED at W2 verify-first (doyle 2026-07-22): the C3 poll-vs-reap ordering race (hertz emphasys RCA 2026-07-16, P1) was ALREADY CLOSED by REQ-CONTROL-STAMP-CONVERGENCE (ADR-0041 decision 4, REGISTRY-LIFECYCLE W2 build todlando 2026-07-17 \u2014 one day after the RCA). Code-verified chain: the exit-waiter reap (broker.rs stamp_reaped) bumps a per-endpoint StampSlot generation UNDER the stamp-write serial then clears (the comment names the emphasys C3 window); the KIND_SESSIONS handler snapshots stamp_gen under the log lock; converge_perch_stamps validates snap_gen under the same write serial and REFUSES stale (STAMP_STALE_SKIP) \u2014 a pre-reap snapshot can never relatch controlled=true. Full stage coverage rides that REQ: unit stale_snapshot_stamp_write_refused_after_reap_generation_bump + int endpoint_lifecycle.rs:488. Kept as a stable pointer; no build owed.", "doc": ""}, "REQ-SUBNET-4": {"title": "Subnet membership mutations elevation-gated (create = seed reveal; join = trust-boundary enrollment)", "doc": "Product-surface amendment (2026-06-05 \u2014 M7 D3): <!-- -->"}, "REQ-SUBNET-DISPLAY-PARITY": {"title": "The `spt endpoint run` picker renders endpoint state IDENTICALLY for local and remote (subnet) rows \u2014 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 \u2014 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) \u2014 replaces green-hollow Unbound + absorbs the dropped UnboundControlled; AMBER-HOLLOW=online+harness-only (no broker seat \u2192 can't rc) \u2014 was amber-FILLED; GRAY-FILLED=Suspended (cold, node up \u2014 wakeable) NEW; gray-hollow=Offline (node ", "doc": ""}, "REQ-TERM-1": {"title": "Process-supervisor terminal wrapper hosting broker PTYs", "doc": ""}, "REQ-TERM-6": {"title": "Thread-spanning digest across session boundaries: a per-endpoint session ledger (`<perch>/sessions.log`) appended at first bind and by `api boundary` on `/clear`|`/compact` session rotation, the digest enumerating the last K sessions so its rolling window bridges a boundary, and a distinctive in-timeline boundary marker (DigestEntry::Boundary). The digest follows the live-agent thread, not a single session.", "doc": ""}, "REQ-TRANSLATE-COMMIT-MISS-TOLERANCE": {"title": "C-1 (F029, B6 ROOT \u2014 rescope of REQ-TRANSLATE-BINARY-LIVENESS-DECAY, now PINNED): at a checkpoint clear boundary the clear-only inject drives `/clear`; its `{commit}` is never observed within INJECT_COMMIT_DEADLINE (5s, broker.rs:158) so the inject worker FAULTS + TERMINATES a HEALTHY translate binary (broker.rs respool_and_fault + return) and by ADR-0022 design NEVER respawns \u2192 every subsequent force-native reports delivered=false ('no live translation binary', cli.rs:5046) = B6's exact field signature; the v0.12.0 checkpoint post-clear WAKE dies with the terminated binary + the fire-and-forget FIRE in the dead window. NOT a race \u2014 deterministic at every checkpoint-armed boundary (perri captured-stderr proof: TRANSLATION_FAULT on the F-019 unread daemon-stderr channel). FIX (REVISED, supersedes terminate-then-respawn): miss != fault \u2014 preserve the binary; the watchdog's job is ANTI-STALL (release the operator floor), not execution-verification. See addendum C-1 + ADR-0022 amendment.", "doc": "Amendment (2026-07-03, F029 C-1) \u2014 a commit-miss is NOT a fault; real faults respawn: <!-- -->"}, "REQ-UPD-1": {"title": "Peer-propagated update over P2P", "doc": ""}, "REQ-UPD-3": {"title": "No endpoint process terminates/suspends during self-update", "doc": ""}, "REQ-UPD-5": {"title": "spt-core ripple-updates registered adapters", "doc": ""}, "REQ-UPD-8": {"title": "Platform-safe `spt update fetch` + apply platform-guard (v0.3.1 cross-OS brick fix): `spt update fetch` stages the signed multi-platform `SignedUpdateSet` (`update-set.json` + every platform artifact it names), never a platform-blind single `SignedRelease`, so local apply selects `current_platform()` and P2P re-serve lets each peer select ITS own platform. Defense-in-depth: `apply_staged` REFUSES a staged single-release artifact unless it is platform-stamped for THIS node (an unstamped pre-v0.3.2 single, or a single stamped for another OS, fail-safe refuses \u2014 the guard that alone prevents the v0.3.1 brick where a Linux ELF was applied as `spt.exe`). UX: a friendly post-apply message (`Updated spt-core to vX.Y.Z.` + changelog URL) driven by an additive `product_version` metadata field, with a release-counter fallback when absent.", "doc": ""}, "REQ-UPDATE-ADAPTERS-VERB": {"title": "THE-FORKENING W4 (operator-grilled 2026-07-14): `spt update adapters [<a>[,<b>...]]` = thin ALIAS over the existing `spt adapter update` engine (cli.rs:748 gh_release avenue; the old verb STAYS \u2014 published surface) + comma-list accepted on BOTH forms. Semantics: no names -> all gh_release-avenue registrations; names validated FAIL-FAST against the registry BEFORE any update starts (a typo must not leave a half-updated set); per-adapter failure ISOLATION (one failure doesn't stop the rest) with a per-adapter summary line; nonzero exit if any failed; local-path/dev registrations SKIP loud (not error). Gate: unit \u2014 name validation, list parsing, isolation + exit-code aggregation, local-path skip; doc \u2014 reference regen (drift-gated). Kin REQ-UPDATE-DEFAULT-COMPOSITE (the caller), REQ-ADAPTER-UPDATE-MESSAGE (per-adapter apply notices ride the summary).", "doc": "Self-update: **update composite (`spt update`)** \u2014 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>\u2026]]` is the adapters leg alone (alias over `spt adapter update`). The composite's invoker always survives, because a routine apply cycles only the **brain** \u2014 the *restart-required* message on broker-side releases is a notice, not a restart. `spt update --restart` is the one-step **full cycle**: fetch \u2192 adapters \u2192 `apply --finish`"}, "REQ-UPDATE-FINISH-COMMUNE-FLUSH": {"title": "DEFERRED (post-LIFECYCLE-TRUTH, operator-ruled 2026-07-07 \u2014 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 \u2014 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 \u2014 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-FINISH-ENDPOINT-SURVIVAL": {"title": "W3 (LIFECYCLE-TRUTH): daemon restart no longer massacres hosted endpoints \u2014 daemon start RE-RUNS previously-online spt-hosted endpoints. ROOT rig-proven: daemon stop+start (the apply notice's OWN instruction) kills every hosted endpoint; they stay OFFLINE after start (no resurrection) though records exist (info.json status + adapter + cwd). SCOPE RULING (doyle): re-run-on-start, marked start-reason=daemon-restart; agents' minds ride psyche re-host as today. Int: endpoint online -> daemon stop -> start -> endpoint back ONLINE, same id, harness respawned.", "doc": ""}, "REQ-UPDATE-GH-TRANSPORT": {"title": "THE-FORKENING W1 (ADR-0036, operator-ruled 2026-07-14): the release channel is PRIVATE (`BigscreenVR/spt-bs-releases`) and the gh CLI is the mandated carrier \u2014 release discovery (`releases/latest`, cli.rs:9717) and asset download (cli.rs:4861 public browser URLs) move to deadline-wrapped `gh` subprocess calls (`gh api`, `gh release download`; run_git pattern). WHY gh not token+HTTP: private-repo `browser_download_url` 404s even with a valid token \u2014 the API asset-id dance is gh's job. Default repo flips via the existing SPT_INSTALL_REPO seam (cli.rs:5363) + xtask REPO const (main.rs:729) + notif.rs consent-changelog URL rider. Loud failure classes: gh missing -> UPDATE_FETCH_REJECTED:GhCliRequired with OS-SPECIFIC install hints (winget/apt/brew); gh unauthed -> distinct GhAuthRequired pointing at `gh auth login`. Signature verification unchanged \u2014 bytes verified after download, carrier-independent (update-set/counter/anchor continuity per ADR-0036 \u00a72). release_verify_e2e reworked to the gh carrier. Gate: unit \u2014 url/invocation construction + both failure classes render OS-correct hints; int \u2014 fetch against a real gh-authed channel resolves latest + downloads and verifies an asset; do", "doc": "Self-update: **release channel (private, gh-carried)** \u2014 the release channel is a **private** GitHub repo (`BigscreenVR/spt-bs-releases`, ADR-0036); the **gh CLI is the mandated carrier** for release discovery and asset download (each node authenticates via org membership). A node without an authed `gh` cannot fetch \u2014 refused loud with OS-specific install hints, never a silent hang. Signature verification is carrier-independent: bytes are verified after download exactly as before; counter, signing key, and update-set format are unchanged from the public-channel era. <!-- --> // How updates mov"}, "REQ-UPDATE-ONE-SHOT-FINISH": {"title": "W3 (LIFECYCLE-TRUTH): update apply works daemonless and one command finishes the cycle. ROOT (operator wart): update fetch/apply run ensure_daemon_announced (cli.rs:4386) -> on a stopped box they BOOT THE OLD broker pre-swap, guaranteeing the mixed old-broker/new-brain pair + a manual bounce. FIX: apply works daemonless (swap + record, next start runs new bytes); `update apply --finish` (name subject to docs-token gate) completes the cycle: swap -> brain cycle -> broker restart onto new bytes (rides REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL so the restart is not a massacre). CLI change -> xtask docs gen, no internal codes in clap ///.", "doc": ""}, "REQ-UPDATE-TRIAL-DRAIN-DRIVE": {"title": "UPDATE-WEDGE (counter-54, doyle-ruled 2026-07-09 \u2014 regression of the v0.29.0 seamless brain-swap): a brain generation DRIVES the broker's controller-liveness reap (a KIND_SESSIONS poll) each heartbeat throughout its boot/trial loop, so a hard-KILLED prior generation's black-holed LOCAL controller conn (by:None) is stall-evicted within the trial window and can never permanently strand the promotion DRAINED gate. ROOT (2026-07-09 field freeze, `spt update fetch --apply` v0.30.0->v0.30.2 froze all 7 live PTYs ~30s then rolled back): the promote gate (run_trial, brainproc.rs:657-661) needs BOTH `ready_generation==gen` AND `old_gen_drained()`; `old_gen_drained()` = `!any_local_controller_wedged()` (brainproc.rs:534) is a PURE READ of `write_blocked_since` (broker.rs:2703) \u2014 it never DRIVES the evict. The evict (`stall_evict_controller`, broker.rs:1039, same 15s `brain_write_deadline` the wedge-read uses) only runs via `reap_dead_controller` (broker.rs:967, severed->drop ELSE stall-evict) inside the KIND_SESSIONS snapshot closure (broker.rs:2879). During the isolated brain-trial window NOTHING polls KIND_SESSIONS: the old brain was hard-killed (`child.kill()`, brainproc.rs:851) so its lo", "doc": "Self-update: **brain-trial promotion (readiness + drained)** \u2014 the broker supervises the swapped-in brain through a bounded readiness **trial** and *promotes* the new binary only when it both signals ready for its own generation **and** the OUTGOING generation's control plane has **drained** \u2014 the old brain's local (brain-owned) controller connection is closed or stall-evicted, never still holding blocked writes. A hard-killed prior generation leaves that connection **black-holed** (its hosted PTYs keep producing output the broker's writer blocks on, since a killed peer's pipe blocks rather th"}, "REQ-WAKE-RESUME-LEG": {"title": "A-2 (REMOTE-TRUTH triage \u00a7A-2 + ADR-0033): the daemon reconcile gains a WAKE-RESUME LEG \u2014 an endpoint whose rest INTENT is Active but whose harness session is COLD (status != online) is resumed by the daemon via the adapter's [session.resume] template using the LAST LEDGER session id, so a bare `spt wake <id>` on a suspended live agent actually brings it back (today: reconcile_once start-arm hosts ONLY status==online (livehost.rs:199), so a woken-but-unbound endpoint is skipped forever \u2014 neither status reaches online nor does reconcile re-host). This is the ADR-0033 LIFT: the thin `spt wake` edge writes rest intent, the DAEMON does the work. Mirrors shellwake::resolve_wake (read rest state, live-pid double-launch guard, launch, NEVER flip status \u2014 the harness self-binds \u2192 online). The leg reads the recorded adapter (D-2, REQ-SESSION-ADAPTER-RECORDED); an UNREGISTERED recorded adapter is the Q5 daemon-variant refuse: do NOT spawn, record a LOUD host_error report (F-1 naming the adapter + `spt adapter add`), never silent, never fallback-spawn on a different adapter. BINDS: (1) status=online is set ONLY by a real bind \u2014 the resume leg NEVER stamps it (CONTEXT liveness truth; the A-1 e", "doc": ""}, "REQ-WHOAMI-1": {"title": "The `endpoint list` SELF pin carries the Self endpoint's authored `endpoint description` (info::read_info(...).resources) when present, inline after the liveness state; whoami stays a top-level hot-path verb (parse unchanged, REQ-MSG-9) and renders the same description-carrying SELF pin. HISTORY: originally minted whoami as a thin ALIAS of `spt endpoint list` \u2014 that alias premise is SUPERSEDED by REQ-WHOAMI-IDENTITY-ONLY (PROJECT-INDEX W1, 2026-07-15): the alias inherited the list's O(perches x branches) git fanout onto hook paths (the 2026-07-15 message-delivery incident), so whoami is now identity-only over the shared render_self_pin. The pin render + parse evidence here stands; the full-roster surface lives solely on `endpoint list`.", "doc": "**whoami** (alias for endpoint list): <!-- --> `spt whoami` is a thin **alias for `spt endpoint list`** \u2014 it prints the full view with the session's own endpoint **SELF-pinned first**, that pin carrying the endpoint's id, liveness state, and its authored **endpoint description** (the \"who am I\" answer). There is no separate bare-id command: nothing captured `id=$(spt whoami)` (environment variables don't persist between an agent's tool calls), so there is no scripting contract to preserve. `whoami` stays a top-level hot-path verb (its parse is unchanged, REQ-MSG-9); only the SELF pin's new des"}, "REQ-WHOAMI-EXPLICIT-SID-REFUSAL": {"title": "RULED DESIGN, delivery unowned (doyle 2026-07-26): when a caller hands identity resolution an EXPLICIT non-empty $OWL_SESSION_ID that resolves to NO perch, core must REFUSE identity (unresolved, exit 1, loud distinct diagnostic) rather than fall through to an ambient/inherited one \u2014 today `detect_self_id` (roster.rs, legs a\u2192b\u2192b2\u2192c) treats sid-UNMATCHED identically to sid-ABSENT, so the fallback chain re-adopts precisely the identity a sharper claim just failed to prove. MEASURED (perri, this node, 2026-07-26, three read-only whoami calls from a genuine descendant of the perri host process): (1) all SPT_*/OWL_* scrubbed \u2192 id null, exit 1 \u2014 ancestry resolved nothing (caveat honored from the probe: the perch's recorded pid was not in the caller's chain, so this run refutes lineage-as-the-mechanism for probe v1 without disproving a lineage path in general); (2) inherited SPT_ENDPOINT_ID=perri + explicit OWL_SESSION_ID matching no perch \u2192 perri, exit 0 \u2014 the mismatch datum was IN HAND (core had already scanned and failed to match the explicit sid) and the ambient id won anyway; (3) real OWL_SESSION_ID with endpoint id scrubbed \u2192 correct self \u2014 the healthy path any fix must leave untouch", "doc": ""}, "REQ-WHOAMI-IDENTITY-ONLY": {"title": "PROJECT-INDEX W1 (F-040, perri filing claude-spt docs/SPT-CORE-FINDINGS.md @d775b38; correctness-critical opener \u2014 the 2026-07-15 message-bodies incident root): a core IDENTITY-ONLY resolution \u2014 session -> endpoint|null \u2014 that touches NO list/registry/project/git/network path, and `spt whoami` DE-ALIASED from cmd_endpoint_list (cli.rs ~6609 aliases the full list = 100+ git children under hook deadlines). endpoint-info is DISQUALIFIED as the carrier (runs latest_project_ref). Adapters/hooks get a bounded-time identity verb; the harness-hosted adapter fallback stays deadline-vulnerable until this ships. Gate: impl \u2014 the resolver + whoami de-alias; unit \u2014 resolver returns endpoint|null with zero project derivation (assert no git spawn seam); int \u2014 whoami on a multi-perch home answers fast-path without touching context branches; doc \u2014 harness-contract api.md names the identity verb + its no-derivation bound. Kin REQ-PROJECT-INDEX-READER-CUTOVER (list-shaped verbs), REQ-WHOAMI-1, docs/PROJECT-INDEX-TRIAGE.md.", "doc": "`spt whoami` \u2014 the identity verb *(identity-only since v0.33.0)*: <!-- --> The bounded-time \"which endpoint am I?\" answer for hooks and adapter glue: resolves the calling session to its endpoint (`$OWL_SESSION_ID` / `$SPT_AGENT_ID` / process ancestry) and prints that ONE endpoint's SELF line \u2014 id, liveness, description. **The no-derivation bound is the contract**: whoami never enumerates the roster, never derives projects, never runs git, never touches the network \u2014 safe to call from deadline-bounded hook paths (the class that previously timed out and black-holed message delivery). Unresolved"}, "REQ-WORKER-SID-SYMMETRIC-AUTH": {"title": "W-2 (WORKER-TRUTH triage, operator-ruled 2026-07-06): worker verbs go sid-symmetric with every sibling id-scoped verb \u2014 worker-start mints NO token and worker-stop takes NONE (token custody is undue adapter burden, ruling via perri). Registration STORES the sid it authenticated (the parent's sid at start; today cmd_worker_start hardcodes session_id=\"\" \u2014 worker.rs:44 \u2014 so a sid-authed stop compares against empty and refuses 100%). Stop accepts the parent's CURRENT sid OR the stored registration sid (a /clear between start and stop rotates the parent's sid; either rotation endpoint is honest custody \u2014 the REQ-PSYCHE-SID-CUSTODY rotation reasoning). Under the ruling the field adapter's existing emission (worker-stop <id> --session-id <parent sid>) becomes contract-correct as-is. Publish the frozen verb shape to the docs-site with the landing wave (perri blind-builds from published docs).", "doc": "`api worker-start <parent> [--agent-id <id>] [--agent-type <type>]`: <!-- --> ### `api worker-stop <id> --session-id <sid>` \u00b7 `api worker-poll <id> --session-id <sid>`"}}, "pairs": [["REQ-DOC-ENDPOINT-DROP-RESOLUTION", "REQ-HAZARD-LOCAL-API-AUTH"], ["REQ-UPD-1", "REQ-UPD-8"], ["REQ-HAZARD-PSYCHE-OUTBOUND-PROXY", "REQ-PSYCHE-TURN-STREAM-EVIDENCE"], ["REQ-HAZARD-SOFT-CLEANUP", "REQ-HAZARD-EPHEMERAL-CLEANUP"], ["REQ-ARCH-3", "REQ-HAZARD-HANDOFF-ARGV-COMPAT"], ["REQ-HAZARD-RC-ATTACH-TRUTH", "REQ-RC-QUALIFIED-TARGET-CANONICAL"], ["REQ-HAZARD-MESH-BOOTSTRAP-TRAP", "REQ-UPDATE-GH-TRANSPORT"], ["REQ-PICKER-RESUME-CONTEXT-PANEL", "REQ-RESUME-HARNESS-SESSION-ID"], ["REQ-HAZARD-BOUNDARY-READY-STRAND", "REQ-LIVEHOST-RECONCILE-TRIAL-SILENT"], ["REQ-HAZARD-REGISTRY-GHOST-ROWS", "REQ-INSTALL-12"], ["REQ-HAZARD-RC-EOF", "REQ-INST-8"], ["REQ-INSTALL-1", "REQ-INSTALL-8"], ["REQ-HAZARD-STALE-INDEX-LOCK", "REQ-HAZARD-REGISTRY-STALE-CLEAN"], ["REQ-HAZARD-GRACE-BEFORE-SIGNOFF", "REQ-HAZARD-ECHO-BEFORE-SIGNOFF"], ["REQ-ARCH-3", "REQ-SHELL-FRAME-VOCAB"], ["REQ-DRIVEN-BY-OWN-NODE-NORMALIZE", "REQ-HAZARD-DRIVEN-BY-SELFHEAL"], ["REQ-HAZARD-RESTART-IDEMPOTENT", "REQ-LIVE-AGENT-NO-INJECT-DELIVERY"], ["REQ-API-3", "REQ-HAZARD-ENVELOPE-PARSER-SAFE"], ["REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE", "REQ-LISTEN-PRESERVES-HOSTING-TOPOLOGY"], ["REQ-RESUME-ROW-PER-PROJECT", "REQ-SUBNET-4"], ["REQ-HAZARD-CONPTY-DSR", "REQ-HAZARD-EBUSY-RENAME"], ["REQ-INSTALL-BOOTSTRAP-VERB", "REQ-SEAM-POSTSPAWN"], ["REQ-HAZARD-DAEMON-STOP-REAP", "REQ-HAZARD-SEEDMAP-CONNECT-UNBOUNDED"], ["REQ-SHELL-PERCH-DIR", "REQ-TERM-1"], ["REQ-PROJECT-INDEX-STORE", "REQ-SESSION-ADAPTER-RECORDED"], ["REQ-ADAPTER-LIVE-UPDATE", "REQ-ADAPTER-TEMPLATE-KEY-VALIDATION"], ["REQ-HAZARD-DEFERRED-DRAIN", "REQ-HAZARD-DEFERRED-SURVIVE-DRAIN"], ["REQ-ADAPTER-LIVE-UPDATE", "REQ-UPD-5"], ["REQ-CONV-1", "REQ-HAZARD-PAIR-RATE-LIMIT"], ["REQ-HAZARD-ADAPTER-APPLY-SILENT-NOOP", "REQ-RESUME-CONTEXT-PULL"], ["REQ-INST-15", "REQ-UPDATE-ADAPTERS-VERB"], ["REQ-PAIR-2", "REQ-REL-3"], ["REQ-DIGEST-CURSOR", "REQ-DIGEST-PROFILE-ENV"], ["REQ-INSTALL-8", "REQ-RC-CROSS-NODE-ATTACH"], ["REQ-REDISPATCH-FINISHED-RETIRE", "REQ-REGISTRY-REPLAY-BOUNDED"], ["REQ-MSG-3", "REQ-NODE-IDENTITY"], ["REQ-PSYCHE-NESTED-RESOLUTION", "REQ-RC-QUALIFIED-TARGET-CANONICAL"], ["REQ-RC-RESIZE-GEOMETRY-EPOCH", "REQ-RESIZE-INPUT-MODE-INTEGRITY"], ["REQ-PICKER-FORK-LABEL-CWD", "REQ-PICKER-OFFLINE-NO-VIEW"], ["REQ-RC-HARNESS-ONLY-REFUSAL", "REQ-RC-KEY-VT-TRANSLATE"], ["REQ-EP-1", "REQ-RESIDENT-SERVICE"], ["REQ-STAMP-CONVERGENCE-ORDER", "REQ-UPDATE-TRIAL-DRAIN-DRIVE"], ["REQ-ENDPOINT-CYCLE-HONEST", "REQ-HAZARD-RC-ATTACH-FAILFAST"], ["REQ-ENDPOINT-TEARDOWN-AUTHORITY", "REQ-HAZARD-TEARDOWN-DEADEND"], ["REQ-DISPATCH-HYGIENE-TELEMETRY", "REQ-HAZARD-REDISPATCH-STALL"], ["REQ-ENDPOINT-AUTOSTART", "REQ-ENDPOINT-UNBOUND-ATTACH"], ["REQ-RUN-MULTISUBNET-HOME", "REQ-SUBNET-DISPLAY-PARITY"], ["REQ-HAZARD-INBOX-NO-DOUBLE", "REQ-HAZARD-RESTART-IDEMPOTENT"], ["REQ-ENDPOINT-LIST-MERGE-LOCAL", "REQ-SHELL-LIST-DERIVED-PROVENANCE"], ["REQ-HAZARD-RESUME-CUSTODY-ABA", "REQ-SELF-DETECT-PARENT-PID"], ["REQ-HAZARD-BRAIN-RESTART-PSYCHE-DUP", "REQ-REL-3"], ["REQ-API-3", "REQ-HAZARD-DROP-FILE-SINGLE-WRITER"], ["REQ-SEAM-POSTSPAWN", "REQ-WHOAMI-EXPLICIT-SID-REFUSAL"], ["REQ-ADAPTER-MULTIPLATFORM-SPT", "REQ-HAZARD-STDIN-SESSION-ID"], ["REQ-HAZARD-DROP-FILE-SINGLE-WRITER", "REQ-HAZARD-DIRECT-WRITE-PRECEDENCE"], ["REQ-HAZARD-BROKER-QUIC-DEADLINE", "REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK"], ["REQ-ATTACH-IDEMPOTENT-REPLAY", "REQ-BRAIN-RESUME-NO-CONTROL-STEAL"], ["REQ-CONTROL-STAMP-CONVERGENCE", "REQ-HAZARD-CONTROLLER-IRRECOVERABLE-BEHIND"], ["REQ-HAZARD-LIVEHOST-BOOT-RACE", "REQ-HOST-RUN-1"], ["REQ-EP-4", "REQ-HAZARD-CONTROL-STAMP-LIFETIME"], ["REQ-RC-CROSS-NODE-ATTACH", "REQ-REACH-1"], ["REQ-CONN-BLACKHOLE-LIFECYCLE-HARNESS", "REQ-SEAT-LIFETIME-BOUNDED"], ["REQ-JOIN-DIAGNOSTICS", "REQ-LISTEN-SEED-CONSUME-AFTER-BIND"], ["REQ-NET-3", "REQ-WORKER-SID-SYMMETRIC-AUTH"], ["REQ-PSYCHE-CRASHLOOP-BACKOFF-SHUTDOWN", "REQ-RESUME-CONTEXT-PULL"], ["REQ-INST-3", "REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL"], ["REQ-PICKER-3", "REQ-UPDATE-FINISH-COMMUNE-FLUSH"], ["REQ-HAZARD-CONTROL-STAMP-LIFETIME", "REQ-HAZARD-REGISTRY-GHOST-ROWS"], ["REQ-HAZARD-INBOX-NO-DOUBLE", "REQ-REGISTRY-APPLY-TRANSACTIONAL"], ["REQ-HAZARD-SHELL-STALE-ONLINE", "REQ-SHELL-LIST-DERIVED-PROVENANCE"], ["REQ-DOC-DELIVERY-VOCAB", "REQ-MSG-2"], ["REQ-INST-4", "REQ-WAKE-RESUME-LEG"], ["REQ-ATTACH-SEED-REQUEST-DOUBLE-SERVE", "REQ-SPAWN-COLLISION-GUARD-LIVE-DUP"], ["REQ-CONTROL-STAMP-CONVERGENCE", "REQ-HAZARD-CONTROL-STAMP-LIFETIME"], ["REQ-ACTIVITY-LINK-PUSH", "REQ-MSG-4"], ["REQ-DRIVEN-BY-OWN-NODE-NORMALIZE", "REQ-SUBNET-DISPLAY-PARITY"], ["REQ-HAZARD-PUMP-IPC-DEADLINE", "REQ-HAZARD-RC-ATTACH-FAILFAST"], ["REQ-MESH-2", "REQ-SUBNET-4"], ["REQ-NET-2", "REQ-NET-FAMILY-GATE"], ["REQ-MANIFEST-1", "REQ-SEND-REPLYTO-REMOVE"], ["REQ-BIND-HONEST-SELF-STAMP", "REQ-WHOAMI-1"], ["REQ-PICKER-CURRENT-DIR-LABEL", "REQ-RUN-PICKER"], ["REQ-RESUME-CONTEXT-PULL", "REQ-WHOAMI-1"], ["REQ-SELF-ID-TRUST-INJECTED-ENV", "REQ-WORKER-SID-SYMMETRIC-AUTH"], ["REQ-EP-1", "REQ-EP-2"], ["REQ-ADAPTER-FLOOR-ENFORCE", "REQ-BRAIN-UPDATE-RESTART-CLEAN-CLOSE"], ["REQ-INST-3", "REQ-PAIR-NTP-LOUD-FAIL"], ["REQ-CONN-POISON-DIAL-SCOPE", "REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK"], ["REQ-ATTACH-IDEMPOTENT-REPLAY", "REQ-HAZARD-CONTROLLER-RETAKE-FLOOR"], ["REQ-ENDPOINT-LIST-NODE-IDENT", "REQ-ENDPOINT-LIST-RENDER-POLISH"], ["REQ-ENDPOINT-TEARDOWN-AUTHORITY", "REQ-HAZARD-BIND-REST-STATE-CARRY"], ["REQ-HAZARD-CONTROL-STAMP-LIFETIME", "REQ-HAZARD-DRIVEN-BY-SELFHEAL"], ["REQ-RESUME-ROW-PER-PROJECT", "REQ-TRANSLATE-COMMIT-MISS-TOLERANCE"], ["REQ-DISPATCH-CLAIM-RETRY", "REQ-HAZARD-DEFERRED-DRAIN"], ["REQ-HAZARD-HOSTED-LIVENESS-RECONCILE", "REQ-IDLE-PARKED-DELIVERY"], ["REQ-HOST-RUN-1", "REQ-PICKER-NODE-GROUPING"], ["REQ-GOSSIP-CONTROLLED-CROSS-NODE", "REQ-HAZARD-INFO-RMW-LOST-UPDATE"], ["REQ-HAZARD-SESSION-PIN-WEDGE", "REQ-HAZARD-VIEWER-RING-ROLL-SNAP"], ["REQ-HAZARD-DAEMON-STOP-BARRIER", "REQ-HAZARD-LIVEHOST-BOOT-RACE"], ["REQ-ADAPTER-GH-TRANSPORT", "REQ-HAZARD-EBUSY-RENAME"], ["REQ-UPD-3", "REQ-UPDATE-ONE-SHOT-FINISH"], ["REQ-CLI-WIN-VT-ENABLE", "REQ-LIVE-AGENT-NO-INJECT-DELIVERY"], ["REQ-CLI-WIN-VT-ENABLE", "REQ-HAZARD-RENDER-LIFECYCLE"], ["REQ-ARCH-1", "REQ-ARCH-2"], ["REQ-PSYCHE-EPHEMERAL-DRIVER", "REQ-SEAM-PSYCHE"], ["REQ-HAZARD-LIVEHOST-NONRESIDENT", "REQ-LIVENESS-ORACLE-SOUND"], ["REQ-PICKER-CONTROL-LINE-STATUS-GATE", "REQ-PICKER-CONTROLLED-LOCAL"], ["REQ-TERM-6", "REQ-WORKER-SID-SYMMETRIC-AUTH"], ["REQ-CONN-POISON-DIAL-SCOPE", "REQ-PEER-PUMP-CHURN-STALL"], ["REQ-SEAM-POSTSPAWN", "REQ-SEAM-RESUME"], ["REQ-HAZARD-DAEMON-STOP-REAP", "REQ-HAZARD-DEAD-REC-PID"], ["REQ-PSYCHE-CONTEXT-FILE-INDIRECTION", "REQ-RESIDENT-SERVICE"], ["REQ-HAZARD-TEMPLATE-ARGV-FILL", "REQ-SHELL-PERCH-DIR"], ["REQ-RC-QUALIFIED-TARGET-CANONICAL", "REQ-RESUME-HARNESS-SESSION-ID"], ["REQ-DAEMON-3", "REQ-HAZARD-DAEMON-STOP-REAP"], ["REQ-NET-2", "REQ-PICKER-CONTROL-LINE-STATUS-GATE"], ["REQ-PICKER-2", "REQ-SEAM-HISTORY"], ["REQ-ENDPOINT-ONLINE-TRUTH", "REQ-HAZARD-RC-ATTACH-FAILFAST"], ["REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT", "REQ-HOST-RUN-1"], ["REQ-EFFECTIVE-INSTANCE-STATE", "REQ-INST-4"], ["REQ-SEAM-INJECT", "REQ-SHELL-3"], ["REQ-LISTEN-SESSION-ID-FALLBACK", "REQ-WHOAMI-IDENTITY-ONLY"], ["REQ-ENDPOINT-LIST-MERGE-LOCAL", "REQ-MESH-6"], ["REQ-ACTIVITY-INFO-PULL", "REQ-DAEMON-BITS-AMBIGUITY"], ["REQ-HAZARD-CASCADE-WIPE-GUARD", "REQ-HAZARD-SINGLE-PATH-SOURCE"], ["REQ-ENDPOINT-CYCLE-HONEST", "REQ-SUBNET-DISPLAY-PARITY"], ["REQ-RESIZE-INPUT-MODE-INTEGRITY", "REQ-SEAM-INJECT"], ["REQ-BOUNDARY-ROTATION-CREDENTIAL", "REQ-RC-1"], ["REQ-PRES-1", "REQ-UPDATE-GH-TRANSPORT"], ["REQ-ACTIVITY-INFO-PULL", "REQ-ENDPOINT-LIST-REST-FILTER"], ["REQ-HAZARD-REGISTRY-EPOCH-LEASE", "REQ-INST-7"], ["REQ-RESUME-HARNESS-SESSION-ID", "REQ-WORKER-SID-SYMMETRIC-AUTH"]]}