=== PAIR 1 === A ID: REQ-HAZARD-DAEMON-STOP-BARRIER B ID: REQ-SEAT-LIFETIME-BOUNDED A TITLE: B3: `spt daemon stop` then an immediate `spt daemon start` does NOT race — stop fully completes before it returns. Today request_stop (seedmap.rs:240-255) returns on the KIND_STOPPING ack (sent seedmap.rs:174-176) BEFORE the seed socket unbinds, so a following is_running ping (daemon.rs:375) wins the exit window and start reports ALREADY_RUNNING (operator: daemon stop → STOPPED then start → ALREADY_RUNNING). FIX: unbind/stop-gate the seed socket BEFORE acking KIND_STOPPING, OR request_stop waits for a ping-to-fail before returning. Unit: stop then immediate is_running()==false. (v0.12.0) A DOC: (empty) B TITLE: REGISTRY-LIFECYCLE W1 (ADR-0040 decision 5, hertz defect B leg 5 CROSS-FAMILY per the final attribution correction — my_stream_subs releases only at conn-loop exit, NO unsubscribe verb existed, ~546/589 broker threads were unnamed parked SubscriberSeat writers on completed Registry/sync/update seats): new KIND_NET_STREAM_UNSUBSCRIBE broker verb removes the SubscriberSeat, stops AND JOINS its writer thread, drops its cursor; every dispatch worker calls it on serve completion (ALL families, success or failure); the retire sweep calls it for retired rows. N-1: unknown-kind on an older broker = tolerated send_error path, caller best-effort. Rider: spawned daemon threads NAMED (SubscriberSeat writer sub-writer-s minimum, conn accept handlers) — field censuses attribute, not infer. Gate: impl — verb + worker/sweep call sites + thread naming; unit — unsubscribe removes seat/joins writer/drops cursor + idempotent + unknown-stream tolerated; int — rides REQ-HAZARD-REGISTRY-STALL seam (physical seat/thread counts plateau CROSS-FAMILY after completion); doc — ADR-0040. B 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` === PAIR 2 === A ID: REQ-CLI-4 B ID: REQ-MSG-INJECT-LEG-DROP-VISIBLE A TITLE: User-facing CLI output is human-readable: DIRECT-USER commands (e.g. adapter update/list/use) render friendly prose instead of raw CODE:RESULT markers — "claude-spt is up to date (0.2.0)." not "ADAPTER_UPDATE_UPTODATE:claude-spt: installed 0.2.0, latest 0.2.0". Strictly bounded to the direct-user surface: the adapter-PARSED bringup tokens (SEEDED/BOUND/READY/NO_SEED on seed/listen, which adapters grep) stay machine-parseable — humanization is additive (a human line beside the marker, or a --porcelain/--quiet split), never a silent rename of a dual-contract marker. The user-facing bringup composition belongs to the adapter (perri); this REQ owns only the direct-user CLI surface. (v0.9.0) A DOC: (empty) B TITLE: SEED (inactive — observability): a silently-dropped delivery leg must be DISTINGUISHABLE from an honestly-offline endpoint on a status surface. The spt-hosted inject leg (spt-daemon inject.rs `try_spt_hosted_inject` — the ONE shared implementation behind local cmd_send, the WAN ingress, the idle-edge drain, and the parked-idle/pulse re-offer belts) gates on `is_spt_hosted_no_relay` → `deliver::is_online` → `liveness::is_perch_alive`: a perch PINNED TO A DEAD SESSION (the KH 7.25 wedge class — dead owner, record not yet healed by the next auth touch) makes EVERY belt on that leg silently return None → messages spool as if the endpoint were ordinarily offline, while the operator-facing view can keep reading the recorded state. Nothing anywhere surfaces 'the inject leg stopped firing for this endpoint' — the idle-window injections just stop, which from the outside is indistinguishable from 'no messages arrived' (field shape: IDLE-EDGE W1 field-verify, perri's killed-resident rig — their 'so it did not repeat' conclusion was exactly this invisibility, self-corrected only by re-rigging with a live resident). Shape at activation: a status/list surface DERIVES and reports the inject-leg v B DOC: (empty) === PAIR 3 === A ID: REQ-PRESENCE-CONTROL-REAP-ON-EXIT B ID: REQ-TEARDOWN-UNCOOPERATIVE-HOST A TITLE: B3 (F028, hall-b diagnosis + deferred #11 seed; BROADENED perri F-b CONFIRMED): dead-pid ONLINE decay window + sticky CONTROLLED stamp. Repro: /exit -> all endpoint processes dead -> `endpoint list` stays ■ ONLINE for a decay window before OFFLINE. Sticky CONTROLLED: perri confirmed controlled=true + attached_node SET while alive=false/OFFLINE, persisting >20min AND ACROSS A DAEMON RESTART (hall-b) — worse than the SIGKILL>=5min original (CAVEAT still: may reflect claude's --remote-control channel not the PTY attach — DISAMBIGUATE first). This is the deferred #11; RCA belongs to this wave. FIX: reap must clear presence AND control stamps promptly across FOUR paths — (i) clean exit, (ii) serve conn-drop, (iii) session-died-without-exit (crash/bounce), (iv) a BOOT-TIME sweep so a restarted daemon does NOT resurrect control stamps for endpoints it can see are dead. Int tests per edge. Closes A2(b). See triage B3 (broadened). A DOC: (empty) B TITLE: TEARDOWN-AUTHORITY W1 (ADR-0045 decision 4): the teardown kill NEVER depends on harness cooperation — no graceful-input path, no waiting on PTY EOF, no ask-it-to-exit-first step. A WEDGED host is the design case, not the edge case: it is the situation that produced the ADR (doyle's own endpoint — read-only rc replayed the retained PTY and `rc --take` acquired control, but a direct prompt produced no output for 30+s because the retained harness subtree itself was nonresponsive while the broker correctly preserved and replayed its last PTY state). Any cooperation-dependent step re-imports the exact hang the verb exists to break. Gate: impl — the kill path proves no dependency on session responsiveness; int — a deliberately nonresponsive/wedged hosted harness is still reaped within the bound (broker row gone + subtree gone), asserted against a real broker-hosted session. B DOC: Decisions: **4. The kill never depends on harness cooperation** — no graceful-input path, no waiting on PTY EOF, no "ask it to exit first". A wedged host is the design case, not the edge case: it is the situation that produced this ADR. === PAIR 4 === A ID: REQ-INSTALL-5 B ID: REQ-XTASK-SPT-BIN-TARGET-DIR A TITLE: Non-interactive install path: the install path doubles as every adapter's pack-in on-demand install (no second mechanism); sha256-verified fetch; user-PATH registration. HISTORY: 'the canonical one-liner' — since THE-FORKENING (ADR-0036) the canonical path is gh + the spt install verb (itself non-interactive, REQ-INSTALL-BOOTSTRAP-VERB); the scripts this REQ's evidence attests remain as the hermetic CI fixture + air-gap fallback, still non-interactive by construction (doyle-ratified 2026-07-14). A DOC: (empty) B TITLE: #13 (F026 micro, tooling): xtask `spt_bin()` (crates/xtask/src/main.rs) BUILDS `spt` via cargo (which honors CARGO_TARGET_DIR) but returns a HARDCODED `/target/debug/spt` path — so under a redirected target dir (CI / isolated-gate rigs that set CARGO_TARGET_DIR to a throwaway) the binary lands in `$CARGO_TARGET_DIR/debug` while xtask looks in `/target/debug` -> NotFound -> `xtask check` (docs-drift gate) spuriously fails. Workaround was running `xtask check` with CARGO_TARGET_DIR unset. FIX: a pure `target_debug_dir(root, CARGO_TARGET_DIR)` seam mirroring cargo's resolution — absolute override as-is, relative resolved against `root` (the dir cargo is invoked in), default `/target` — join `debug`; `spt_bin` returns from it. See docs/NEXT-MILESTONE-PICKER-TRIAGE.md. B DOC: (empty) === PAIR 5 === A ID: REQ-CRC-SWAP-OLD-DISPLACE B ID: REQ-INSTALL-9 A TITLE: C1 (F028, infra; memory crc-swap-old-litter-brick, RCA'd ENLYZEAM + confirmed systemic): crc_swap `.old`-litter bricks every subsequent adapter update. apply_crc_swap Phase-3 `let _ = remove_file(.old)` (spt-daemon/src/crc_swap.rs:129-133) silently fails whenever ANY pre-update process still image-maps the old binary (NORMAL on a live box — endpoint launch children survive updates). The NEXT update's first commit-op rename(exe->exe.old) = MoveFileExW(REPLACE_EXISTING) must delete the mapped .old -> win32 err 5 -> whole apply fails + rolls back FOREVER, context-free. FIX: (i) DISPLACE not replace — when .old exists, rename it aside to a unique suffix (rename succeeds on mapped files; spt's own updater already does spt.exe.old-); GC stale .old.* opportunistically. (ii) Wrap swap io errors with op + path (`rename claude-spt.exe -> claude-spt.exe.old: …`). Unit seam exists (crc_swap tests). See triage C1. A DOC: (empty) B TITLE: Adapter add from a GitHub release archive: `spt adapter add --release [--tag ] [--asset ]` fetches a `.spt` tar asset over HTTPS+GitHub trust, extracts it to the durable adapters/_github home, and registers the root — ships built binaries source-free and versioned (the distribution path for an adapter whose dev repo is a monorepo subdir, where --github root-only clone does not fit) B DOC: Installation: **adapter registration (`spt adapter add`)**: How a node comes to *know* an adapter — harness or shell. An explicit **`spt adapter add `** (or **`--github `**) validates the manifest against the published JSON Schema and writes a registration record under `{SPT_HOME}/…/adapters/` — a **copy** of the files for `file_pull`-update adapters (spt-core owns what it later swaps) or a **pointer** for `delegated`-update adapters (the plugin owns + updates its own files). On === PAIR 6 === A ID: REQ-HAZARD-DROP-FILE-SINGLE-WRITER B ID: REQ-HAZARD-DIRECT-WRITE-PRECEDENCE A TITLE: Drop files are daemon-owned single-writer (6.4) A DOC: (empty) B TITLE: Direct-write precedence marker (with node id) guards stale overwrite (6.5) B DOC: (empty) === PAIR 7 === A ID: REQ-ENDPOINT-CYCLE-HONEST B ID: REQ-HAZARD-CONTROL-STAMP-LIFETIME A TITLE: REGISTRY-LIFECYCLE W3 (ADR-0041 decision 6, operator deployah stop/run wedge): cycle verbs share ONE liveness authority — 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 — probing dup-guard + unified authority; unit — dead-tree claim probes and reaps, live claim still refuses; int — controlled zombie (killed client tree, surviving hosted record) leads to endpoint run succeeding honestly end-to-end; doc — ADR-0041. A DOC: Decision: 1. **Online is earned, not declared.** A creator may stamp `status=online` only from actual persisted state + hosting authority — never from manifest capability alone. Legacy hybrid rows self-heal at reconcile, but only after a SUCCESSFUL broker query: a broker failure is never interpreted as an empty session set (no mass-offline on a hiccup). 2. **Control cleanup splits from offline classification.** Reconcile clears `controlled`/`driven_by`/`viewer_count` for EVERY endpoint B TITLE: #2: a control/viewer stamp never outlives its session — 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 — 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) — broker stays the single writer. KNOWN-HAZARDS invariant. See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #2. B DOC: 7.26 Concurrent first-touch of ONE fresh BranchStore must ALL succeed — 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 — 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` — hours later hall-a's `info.json` still === PAIR 8 === A ID: REQ-DISPATCH-CLAIM-RETRY B ID: REQ-HAZARD-REDISPATCH-CONTROL-STEAL A TITLE: REDISPATCH-TRUTH W1 (ADR-0038, hertz fix C): dispatcher claims are RETRYABLE and outcomes are CLASSIFIED — 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 — claim lifecycle; unit — transient failure requeues + terminal outcome does not (no hot-loop under a persistently-failing stream); int — 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. A DOC: (empty) B TITLE: REDISPATCH-TRUTH W1 (KNOWN-HAZARDS 7.41, hertz field RCA 2026-07-16 — 4/5 endpoints frozen per brain cycle): a fresh dispatcher must NEVER re-serve a terminal stream — a replayed historical Attach must not steal (same-identity silent become_controller, no Displaced) or clear (replayed-EOF detach_session) a LIVE controller. The legitimate same-by successor re-take after a brain restart still silently re-takes: the discriminator is stream LIFECYCLE, never origin identity. Gate: int — production-path regression D1: finished historical Attach + current active Attach, same endpoint/origin; restart target brain only (real run_dispatch_loop rediscovery, NO manual re-serve — the pre-fix e2e bypass is the lesson); prove the historical stream neither takes nor clears the current controller and current input/output stays exactly-once without detach; doc — KNOWN-HAZARDS 7.41. HEAVY nextest group at birth (FLAKE-LEDGER #15). Kin REQ-REDISPATCH-FINISHED-RETIRE (the mechanism), REQ-BRAIN-RESUME-NO-CONTROL-STEAL (the CLOSED session-cursor sibling — different leg), ADR-0038. B DOC: 7.40 A commune/signoff drop is deleted ONLY after every applicable tier is durably committed — an un-committable slice preserves the drop, never delete-then-lose `[REQ-HAZARD-COMMUNE-INGEST-BLACKHOLE]`: ### 7.41 A fresh dispatcher must NEVER re-serve a terminal stream — historical replay must not steal or clear a live controller `[REQ-HAZARD-REDISPATCH-CONTROL-STEAL]` - **Failure (paid-for, hertz field RCA 2026-07-16 — 4/5 endpoints frozen, twice in one day):** every brain cycle (`spt d === PAIR 9 === A ID: REQ-PICKER-5 B ID: REQ-PICKER-PROJECT-HISTORY-TRUTH A TITLE: `spt endpoint list` (bare/subnet view) renders an ALIGNED table with canonical node labels: cmd_endpoint_list prints subnet rows with `\t` TAB separators (cli.rs:~1651-1662) so variable-width endpoint_ids snap fields to different tab-stops → a RAGGED status column (operator screenshot: X/help statuses misaligned vs rt-*/sptc-*/wall-a); and it calls the node renderer with no label → bare key-hex for every row (SAME ResourceRow-drops-node_label root as REQ-PICKER-4). FIX: max-width per-column padding (mirror render_node_rows' pad, pad by char count not byte len — '…' is multibyte) replacing the tabs, and render the node via the shared node_label_display now that ResourceRow carries node_label (REQ-PICKER-4). Extract a pure row-formatter seam so the alignment+label is unit-testable. ALSO: the bare list is the SUBNET view (a just-run LOCAL perch is invisible cross-subnet until the next advertise tick), so emit a `--local` hint line so a freshly-run endpoint isn't perceived as lost. (v0.10.0; operator-flagged + doyle dispatch 2026-06-17) A DOC: (empty) B TITLE: #1: picker project history is derived from sessions.log cwds (newest->oldest, deduped by project_id_for_dir) UNION context-store branches, EXCLUDING owlery-internal paths (any cwd under spt_home()/owlery) everywhere a project is displayed or inferred. Fixes three stacked defects (crates/spt/src/picker/data.rs): (1a) project_history_for (data.rs:372) reads ONLY context-store p-* branches, which are empty on this box -> history []; (1b) the fallback origin project (data.rs:207) is derived from info.json.cwd = latest-boot-cwd (rewritten every rebind), not origin; (1c) psyche-host sessions bind owlery-internal cwds that pollute history. Full DIRS stay available in the model (feature #5 needs them). PROJECT REPRESENTATION RULING (operator 2026-07-03): project IDs ONLY, EVERYWHERE incl local display; on ID collision disambiguate minimally via a PURE disambiguate_project_ids(entries)->display-names fn (append one-level-up parent folder and/or root drive letter, e.g. 'spt-core (projects)' vs 'spt-core (D:)'). See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #1. B DOC: (empty) === PAIR 10 === A ID: REQ-HAZARD-STOP-RESPAWN-CONVOY B ID: REQ-RC-RECONNECT-TRUTH A TITLE: KNOWN-HAZARDS 7.52: an operator stop outranks every implicit ensure — no convenience path resurrects what the operator just killed. The hazard-conformance twin of REQ-ENSURE-DAEMON-STOP-INHIBIT: its int-stage convoy rig IS this hazard's required test (stop under api-call storm -> down + zero respawns + honest refusal; start clears; one-daemon race). Registered separately per CLAUDE.md rule 4 — evidence may tag the same rig. A DOC: 7.52 An operator stop outranks every implicit ensure — no convenience path resurrects what the operator just killed `[REQ-HAZARD-STOP-RESPAWN-CONVOY]`: - **Failure (paid-for, hertz v0.39.4 field RCA 2026-07-22, doyle code-verified same day):** `spt daemon stop --force` is non-terminal on a box hosting live adapter sessions: every `spt api` invocation runs an unconditional `ensure_daemon()` (REQ-DAEMON-3's anchor), hook-driven api calls arrive continuously, so the stop is a race the oper B TITLE: W3 (LIFECYCLE-TRUTH): rc reconnect never auto-starts a daemon and never hangs forever. ROOTS rig-proven (the operator's long-standing 'stop 2-4 times' bug): (a) an rc client's reconnect loop AUTO-LAUNCHES a daemon via WMI (rig: DAEMON_LAUNCH_VIA_WMI from the rc) — resurrection fights the operator's stops; (b) rc freezes at 'Reconnecting to local daemon…' forever when its session died with the broker. FIX: rc NEVER auto-starts a daemon (reconnect only to an already-up broker; loud 'session lost — daemon down' exit otherwise), bounded reconnect with visible countdown. B DOC: (empty) === PAIR 11 === A ID: REQ-PICKER-PURGE-STRUCTURED B ID: REQ-RC-VT-TEARDOWN A TITLE: RC-RENDER-TRUTH W3 (ADR-0043 decision 3, hertz stale-glyphs RCA leg 3 P0): core verbs invoked from inside an active TUI return STRUCTURED outcomes and write NOTHING to the terminal — the picker purge path calls a structured-outcome purge core (no stdout/stderr under the live alternate screen) and remains the sole renderer via model.flash (today cmd_endpoint_purge writes diagnostics/PURGED to stderr while ratatui owns the alt screen, mutating the physical screen behind the previous-Buffer diff baseline => later draws skip 'already blank' cells and stderr glyph fragments persist — the x-purge symptom). Baseline-desync regression REQUIRES a stateful/recording backend (pure TestBackend view snapshots cannot catch it). Gate: impl — structured purge outcome + silent-under-TUI routing; unit — purge core emits no terminal bytes in structured mode, picker converts outcomes to flash; int — recording backend: draw ConfirmPurge, inject an external display mutation, transition back => next frame reconstructs the COMPLETE target screen; doc — ADR-0043. A DOC: Decisions: 1. **One FIFO sequencer per attach sink.** The PTY drain/output writer is the sole sequencer for terminal Output and Exit: Exit is enqueued behind all prior output for each sink (drain EOF/completion first, then Exit). A mutex alone is insufficient — producer order is the contract. Output-before-Exit is a production-path invariant, regression-proven end-to-end (broker → attach → rc). 2. **rc display teardown is unconditional, idempotent, and separate from input teard B TITLE: RC-RENDER-TRUTH W3 (ADR-0043 decision 2, hertz stale-glyphs RCA leg 2 P0): rc display teardown is a display RAII guard SEPARATE from the OS input/raw-mode guard, unconditional and idempotent on EVERY exit path including errors and unwind — best-effort SGR reset + full scroll-region reset + cursor show + leave alternate screen + clear/home, emitted while VT output processing is still enabled, THEN restore the prior console output mode, THEN parting prose (today RawGuard::drop restores raw/mouse/console-mode only; detach, child exit, displacement, first-event stall, fatal error, and the 30s reconnect give-up all can leave the operator terminal dirty; the reconnect banner clears+homes then give-up prints at the centered cursor). Gate: impl — split display guard + every-path coverage; unit — guard emits the cleanup postlude exactly once, idempotent on double-drop; int — dirty sink (?1049h ?25l SGR31) x every PumpEnd/error class => cleanup postlude precedes the final prose; doc — ADR-0043. B DOC: Decisions: 1. **One FIFO sequencer per attach sink.** The PTY drain/output writer is the sole sequencer for terminal Output and Exit: Exit is enqueued behind all prior output for each sink (drain EOF/completion first, then Exit). A mutex alone is insufficient — producer order is the contract. Output-before-Exit is a production-path invariant, regression-proven end-to-end (broker → attach → rc). 2. **rc display teardown is unconditional, idempotent, and separate from input teard === PAIR 12 === A ID: REQ-BROKER-SCREEN-GRID B ID: REQ-RC-WIN-VT-OUTPUT A TITLE: Bugs #6 + #12 + #7/#8-artifacts: the broker is a raw-byte pump with no screen model — OutputLog replays the raw ring from seq 0 into a fresh terminal on attach, so an alt-screen TUI (Claude Code) corrupts scrollback (#6) and rc-to-a-pre-running-endpoint garbles (#12 — rc and endpoint run --attach are the SAME client fn, so it is replay content not a client-VT bug). Fix: a server-side VT/grid/screen model (tmux/mosh-style) that maintains authoritative screen + alt/main + cursor and synthesizes a CLEAN current-screen repaint on attach instead of replaying mid-stream ring bytes. Also eliminates residual-cell artifacts on animate/scroll/resize (#7/#8). Operator NON-NEGOTIABLE: accurate PTY representation with zero artifacts. (win32 vterm in the report means this server-side emulator, not ConPTY which is already the backend.) See docs/NEXT-MILESTONE-BUG-TRIAGE.md #6/#12. A DOC: Decision: B TITLE: Bug #12: `spt rc` to an endpoint renders ANSI escapes LITERALLY (raw ←[K / color codes) on a Win10 conhost console, garbling the viewport — while `endpoint run --attach` in the SAME env renders fine and Win11 Windows Terminal is unaffected. Root (code-grounded, doyle): rc.rs RawGuard::enable calls only crossterm enable_raw_mode (INPUT raw mode) and NEVER enables ENABLE_VIRTUAL_TERMINAL_PROCESSING on the OUTPUT handle; Win10 conhost defaults VT-output OFF so escapes print literally, whereas the picker/alt-screen setup on the endpoint-run path enters crossterm's VT-enabling console setup first (leaving VT-output on) — so it is the rc-attach CLIENT PATH specifically, and --attach-works-same-env confirms (not refutes) the VT-out theory. Fix: in the rc attach path (RawGuard), on cfg(windows) + interactive console (mirror the windows_mouse_wanted guard so piped stdin/stdout keeps clean bytes for the e2e byte tests), SetConsoleMode STD_OUTPUT_HANDLE |= ENABLE_VIRTUAL_TERMINAL_PROCESSING|ENABLE_PROCESSED_OUTPUT, capture the prior mode, restore on Drop. cfg(windows)-only, client-side, independent of #4/#6. See docs/NEXT-MILESTONE-BUG-TRIAGE.md #12. B DOC: (empty) === PAIR 13 === A ID: REQ-NOTIF-SCOPE B ID: REQ-NOTIF-TTL A TITLE: Notif scope is a per-row producer-chosen attribute node|subnet: node-scoped rows (update/consent/rollback kinds) live and die on their node and NEVER enter a replication feed; subnet-scoped rows keep the full ADR-0007 machinery (replication, cross-node dismiss, semilattice join); scope determines the first-fire candidate set (node-scoped targets the most-recently-active endpoint ON that node). New row fields additive serde-defaulted (host_binaries pattern) — old peers parse clean, schema regen drift-gated A DOC: 1. Scope is a per-row, producer-chosen attribute: `node` | `subnet`: B TITLE: Producer-optional TTL for informational kinds with no dismissal seam (node-paired, agent-issued): expired row auto-dismissed instead of surfaced — a timestamp compare, not a predicate; expiry IGNORES seen-state (TTL means stale-after-this; a producer that cannot accept silent expiry must not set one); no global default B DOC: 5. TTL: producer-optional, expiry ignores seen-state: === PAIR 14 === A ID: REQ-ENDPOINT-UNBOUND-ATTACH B ID: REQ-HAZARD-RC-ATTACH-TRUTH A 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 ` 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) A 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 — e.g. the harness is waiting on a startup prompt). On-disk status `unbound` (spawn → `unbound`; bind → `online`; session death → `offline`). An Unbound endpoint is **attachable** (a live PTY — `spt rc` and the `endpoint run` attach re B TITLE: RC-RENDER-TRUTH W1 (KNOWN-HAZARDS 7.46 — umbrella conformance seam for ADR-0042): an rc surface answers from live session authority, never a stale persisted projection; a resuming perch is UNBOUND, not offline. Regression matrix from the hertz RCAs + operator field recovery: offline-row-over-honest-session attaches; offline-no-session refuses; zombie refuses/reaps never attaches; resume-never-bound reads UNBOUND and is attachable; harness-only refuses truthfully pre-stream; qualified targets attach with bare wire id. HEAVY nextest group at birth for any leg spawning a daemon tree (standing CI lint). Gate: int — the matrix; doc — KNOWN-HAZARDS 7.46. B DOC: 7.45 Endpoint lifecycle state converges to truth from every death path — no optimistic online without authority, no surviving control stamps, no immortal wake intent, no untruthful create `[REQ-HAZARD-ENDPOINT-LIFECYCLE]`: ### 7.46 An rc surface answers from live session authority, never a stale persisted projection — and a resuming perch is UNBOUND, not offline `[REQ-HAZARD-RC-ATTACH-TRUTH]` - **Failure (paid-for, hertz perri contradiction RCA 2026-07-17/18 + operator field recovery):* === PAIR 15 === A ID: REQ-INST-3 B ID: REQ-REST-TERMINAL-NORMALIZE A TITLE: Dormant (warm) / suspended (cold) resting states A DOC: Dormancy resource budget (D9-3 — ADR-0003 red-team #9): B TITLE: REGISTRY-LIFECYCLE W2 (ADR-0041 decision 3, rest-normalize P0): definitive hosted-session loss normalizes TERMINALLY and ATOMICALLY — one store-level mutation writes status=offline + rest_state=suspended + clears dormant_since_ms in the SAME info.json write, invoked at authoritative broker-session-loss/liveness-reap + cmd_stop. NOT daemon_rest_event(Suspend) (no edge when effective already Suspended — raw Active intent survives); NOT reader-side blanket offline-implies-suspended (destroys explicit-Wake semantics: wake writes intent first, reconcile consumes). Graceful shutdown keeps echo-before-teardown. Kills the zombie WAKE_RESUME loop (perri field: repeated cross-generation resumes of a dead session). Gate: impl — atomic terminal-normalize mutation + call sites; unit — store-level atomic pair never mixed + endpoint_stop covers already-offline/raw-Active input; int — session vanish means offline+suspended and the NEXT reconcile emits NO WAKE_RESUME, explicit suspended/offline->Wake->Active launches EXACTLY once, RefuseLivePid-then-valid-bind custody race leaves the revived seat unsuspended; doc — ADR-0041. B DOC: Decision: 1. **Online is earned, not declared.** A creator may stamp `status=online` only from actual persisted state + hosting authority — never from manifest capability alone. Legacy hybrid rows self-heal at reconcile, but only after a SUCCESSFUL broker query: a broker failure is never interpreted as an empty session set (no mass-offline on a hiccup). 2. **Control cleanup splits from offline classification.** Reconcile clears `controlled`/`driven_by`/`viewer_count` for EVERY endpoint === PAIR 16 === A ID: REQ-SUBNET-5 B ID: REQ-UPDATE-PROMOTE-DRAINED A TITLE: Per-subnet serve-state: spt subnet detach [--save] / attach [--save] — daemon keeps running, stops/starts advertising + connecting for that subnet (peer pump + responder selective); --save persists the startup default in daemon config; the all-attached banner gains per-subnet states (M8 decision 6, --save renamed from --auto per decision 25 session) A DOC: (empty) B TITLE: W3 (LIFECYCLE-TRUTH, mechanic-d MOVED FROM W2 per doyle gate verdict @e5ae7a9 — binding): the update-apply brain-generation promotion completes only when the OLD generation's broker subscriber connection is CLOSED or stall-EVICTED — never while blocked writes still pend on it. ROOT: `brain.ready` != subscribers drained; W2's stall-evict (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE) only BOUNDS the false-promote window to BRAIN_WRITE_DEADLINE (15s), it does NOT close it — a new brain can signal ready inside that window while the old gen's conn is still wedged, so the apply 'promotes' onto a still-frozen control plane (the 22:47 incident-night false-promote). FIX: the promotion gate (ADR-0018 brain-trial, brainproc.rs) adds an explicit DRAINED precondition — promote only on ready AND old-gen-subscriber-drained (conn closed OR stall-evicted); the drained signal reads broker truth (the W2 stall-evict tally / the old conn's liveness), no brain round-trip. The residual W2 left open, now closed. Int = a FALSE-PROMOTE rig that exercises the promotion path itself: an old-gen subscriber conn held wedged past ready must NOT promote until it drains (RED-first: ready-alone promotes). B DOC: (empty) === PAIR 17 === A ID: REQ-BROKER-OUTPUT-BEFORE-EXIT B ID: REQ-PRESENCE-CONTROL-REAP-ON-EXIT A TITLE: RC-RENDER-TRUTH W3 (ADR-0043 decision 1, hertz stale-glyphs RCA leg 1 P0): the PTY drain/output writer is the SOLE FIFO sequencer for terminal Output + Exit per attach sink — Exit is enqueued BEHIND all prior output (drain EOF/completion first, then Exit); the exit waiter never direct-writes KIND_EXIT around the queued output path (a mutex serializes bytes, not producer order). Kills the stranded-final-frame race (final EL/SGR-reset/cursor-show/?1049l lost when Exit overtakes Output — already admitted and compensated in the broker test suite, never fixed in production rc). Gate: impl — single sequencer, exit-behind-output enqueue; unit — ordering invariant on the writer queue (exit never precedes queued output for a sink); int — short-lived child emits 'XXXX ESC[2K ESC[?25h ESC[?1049l' then exits => that exact Output precedes Exit through the PRODUCTION broker->attach->rc path; doc — ADR-0043. A DOC: Decisions: 1. **One FIFO sequencer per attach sink.** The PTY drain/output writer is the sole sequencer for terminal Output and Exit: Exit is enqueued behind all prior output for each sink (drain EOF/completion first, then Exit). A mutex alone is insufficient — producer order is the contract. Output-before-Exit is a production-path invariant, regression-proven end-to-end (broker → attach → rc). 2. **rc display teardown is unconditional, idempotent, and separate from input teard B TITLE: B3 (F028, hall-b diagnosis + deferred #11 seed; BROADENED perri F-b CONFIRMED): dead-pid ONLINE decay window + sticky CONTROLLED stamp. Repro: /exit -> all endpoint processes dead -> `endpoint list` stays ■ ONLINE for a decay window before OFFLINE. Sticky CONTROLLED: perri confirmed controlled=true + attached_node SET while alive=false/OFFLINE, persisting >20min AND ACROSS A DAEMON RESTART (hall-b) — worse than the SIGKILL>=5min original (CAVEAT still: may reflect claude's --remote-control channel not the PTY attach — DISAMBIGUATE first). This is the deferred #11; RCA belongs to this wave. FIX: reap must clear presence AND control stamps promptly across FOUR paths — (i) clean exit, (ii) serve conn-drop, (iii) session-died-without-exit (crash/bounce), (iv) a BOOT-TIME sweep so a restarted daemon does NOT resurrect control stamps for endpoints it can see are dead. Int tests per edge. Closes A2(b). See triage B3 (broadened). B DOC: (empty) === PAIR 18 === A ID: REQ-PSYCHE-CRASHLOOP-BACKOFF-SHUTDOWN B ID: REQ-PSYCHE-STAMP-CLEAR-ANY-SUCCESS A 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) — 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. A DOC: (empty) B TITLE: W1 (LIFECYCLE-TRUTH): EVERY successful psyche operation clears psyche_host_error — not just the pulse-loop leg. ROOT (three field confirmations, perri): the stamp clears only via note_turn_outcome's Ok leg (lifecycle.rs:1101); a SUCCESSFUL psyche op via checkpoint/wake bypasses it -> stale FAILED stamp sits over a healthy psyche. FIX: event turn, checkpoint/wake synthesis, and signoff echo all clear the stamp on success. B DOC: (empty) === PAIR 19 === A ID: REQ-HAZARD-RESUME-CUSTODY-ABA B ID: REQ-WAKE-RESUME-LEG A TITLE: KNOWN-HAZARDS 7.51: process custody is an identity, never a bare PID — 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) — evidence may tag the same rig. A DOC: 7.51 Process custody is an identity, never a bare PID — 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=2945 B TITLE: A-2 (REMOTE-TRUTH triage §A-2 + ADR-0033): the daemon reconcile gains a WAKE-RESUME LEG — an endpoint whose rest INTENT is Active but whose harness session is COLD (status != online) is resumed by the daemon via the adapter's [session.resume] template using the LAST LEDGER session id, so a bare `spt wake ` on a suspended live agent actually brings it back (today: reconcile_once start-arm hosts ONLY status==online (livehost.rs:199), so a woken-but-unbound endpoint is skipped forever — neither status reaches online nor does reconcile re-host). This is the ADR-0033 LIFT: the thin `spt wake` edge writes rest intent, the DAEMON does the work. Mirrors shellwake::resolve_wake (read rest state, live-pid double-launch guard, launch, NEVER flip status — the harness self-binds → online). The leg reads the recorded adapter (D-2, REQ-SESSION-ADAPTER-RECORDED); an UNREGISTERED recorded adapter is the Q5 daemon-variant refuse: do NOT spawn, record a LOUD host_error report (F-1 naming the adapter + `spt adapter add`), never silent, never fallback-spawn on a different adapter. BINDS: (1) status=online is set ONLY by a real bind — the resume leg NEVER stamps it (CONTEXT liveness truth; the A-1 e B DOC: (empty) === PAIR 20 === A ID: REQ-HAZARD-DEFERRED-MANIFEST B ID: REQ-PAIR-5 A TITLE: A pointer-mode (delegated / GhReleaseManaged) adapter whose binary/manifest is not yet extracted is reported with a CLEAR diagnostic, never silently dropped. Today such an adapter reads its manifest LIVE from source_dir (registry.rs manifest_dir ~146/149); a deferred / un-extracted install makes load_manifest fail → registered() (~410, filter_map(.ok())) SILENTLY DROPS the row → downstream ADAPTER_UNRESOLVED + a cryptic os-error-2 on `spt adapter use`. FIX: surface a clear diagnostic at the resolver + at `adapter use` (name the adapter + the deferred/missing-manifest cause + the fix), not a silent filter-drop and not a bare os-error-2; consider an eager manifest copy at register time so host_binaries survive before the binary download completes. doyle Finding A. (post-v0.10.0) A DOC: (empty) B TITLE: Multi-subnet pairing: subnet-name discovery input, create-new-names-up-front, rendezvous-token hashing B DOC: (empty) === PAIR 21 === A ID: REQ-REGISTRY-APPLY-TRANSACTIONAL B ID: REQ-STREAM-INFOS-SERVER-FILTER A TITLE: REGISTRY-LIFECYCLE W1 (ADR-0040 decision 3, hertz defect B leg 3): registry feed application is TRANSACTIONAL per feed — 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 — the per-chunk rewrites are what stalled IPC drain and manufactured the 15s seat-writer poisons). Gate: impl — accumulate-then-apply; unit — 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 — rides REQ-HAZARD-REGISTRY-STALL seam (snapshot writes O(feeds), zero poisons); doc — ADR-0040. A 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` B TITLE: REGISTRY-LIFECYCLE W1 (ADR-0040 decision 2, hertz defect B leg 2): broker stream_infos excludes initiated_locally rows SERVER-SIDE (retired already excluded) before cloning/serializing to the dispatcher — the O(history) IPC enumeration cost dies at the source. Dispatcher keeps its client-side guard (double-filter harmless; N-1 compatible both directions: older dispatcher skips those rows anyway, older broker just keeps the old cost). Gate: impl — server-side filter; unit — local rows absent from the reply while peer-initiated rows with bytes remain, opener/re-attach enumeration semantics unchanged. B DOC: (empty) === PAIR 22 === A ID: REQ-HAZARD-VIEWER-ISOLATION B ID: REQ-RCVIEW-1 A TITLE: A slow / dead / hostile VIEWER must NEVER stall the controller, the PTY child, or the session drain thread. The broker drain fans output to the controller on the authoritative blocking bounded path (advances delivered_through) but to each viewer via a bounded per-viewer channel with a dedicated writer thread; the drain `try_send`s under the log lock and a viewer whose bounded queue OVERFLOWS (can't keep up) is EVICTED (queue dropped, writer thread ends, removed from the viewers map) — the drain thread NEVER touches a viewer socket, so no viewer write can backpressure or block it. A soft viewer cap bounds the thread count. Viewer eviction never perturbs the controller stream, the delivered_through cursor, or the child. A DOC: 7.6 Pump brain-IPC reads must be deadline-bounded (a blocked read wedges the whole pump) `[REQ-HAZARD-PUMP-IPC-DEADLINE]`: ### 7.7 A slow/dead/hostile remote VIEWER must never stall the controller, child, or drain `[REQ-HAZARD-VIEWER-ISOLATION]` - **Failure:** the W2.5 controller/viewer model lets ANY number of read-only `--view` attachers ride one session's broker `OutputLog`. The single drain thread fans each output chunk to every attacher. If a viewer's socket is fanned out with a ** B TITLE: Remote-attach controller/viewer model (CONTEXT.md:317): a session's broker OutputLog serves ONE interactive controller (input + EXCLUSIVE PTY resize; its viewport sets the size, sent on attach + every window change via crossterm Event::Resize) plus ANY NUMBER of read-only `--view` attachers (output-only, no input, no resize; client-side letterbox — center+pad when larger, clip+1-line indicator when smaller; only the local ctrl-b d detach chord). Attach intent is three-valued (`Viewer | Control | Take`, wire-default Control): Control to a FREE endpoint becomes controller, Control to a CONTROLLED endpoint is REFUSED with guidance (`--view`/`--take`) — never auto-viewer, never silent-displace. Wire adds (additive, N-1 skip-unknown): `Request.intent`, `Resize{rows,cols}` (controller-only), `Size{rows,cols}` (→viewer), `Displaced{by}` (→displaced controller). The brain-resume cursor (delivered_through, ADR-0018) tracks the CONTROLLER ONLY; viewers replay from their own from_seq and never move it. Dormancy keys on the controller ONLY: controller attach wakes / controller detach goes dormant (even with viewers present); viewer attach/detach is wake-neutral and may watch a dormant endpoint B DOC: Shell sleep/wake (offline ↔ online): **BUILT (M12 W2.5).** The controller/viewer model is implemented end-to-end. Attach intent is **three-valued** (`AttachIntent = Viewer | Control | Take`, wire-default `Control`): `Control` to a FREE endpoint becomes controller; `Control` to a CONTROLLED endpoint is **refused with guidance** (`--view` to watch, `--take` to control) — never auto-viewer, never silent-displace; `Take` (`spt rc --take` / picker "Kick") kicks the incumbent with a **loud `D === PAIR 23 === A ID: REQ-HAZARD-EPHEMERAL-CLEANUP B ID: REQ-SHELL-ADAPTER-OWNED-DETACHED-SERVICE A TITLE: Ephemeral perch cleanup on every ring exit path (3.1) A DOC: (empty) B TITLE: RULING OWED (unratified — do NOT satisfy without one): may a shell adapter own a DETACHED process that deliberately OUTLIVES its shell's link-break? Today the answer is 'no, silently, on Windows only'. `shellhost::kill_shell_pid` force-kills with `taskkill /PID /T /F` — /T is a TREE kill, so `close_shell` takes the shell's descendants with it; the Unix arm is `kill -9 `, a SINGLE process. So the same adapter's detached child dies on Windows and survives on Linux, undocumented either way. Two ratified positions collide here and neither may be quietly discarded: (a) CONTEXT §Shell model lifecycle states flatly 'the binary never survives a link-break', and the KNOWN-HAZARDS 'stop lies' RCA (two field cases 2026-07-19, one on doyle's own production endpoint) was paid for precisely because a surviving GRANDCHILD was orphaned where 'even a direct-child kill would have missed it' — that hazard is the standing argument FOR /T; (b) a node-singleton service that must not drop every time a shell relinks is a legitimate shape, and flynn's alchemy ships one today (Hub Daemon, CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP, lockfile-guarded), depending on surviving link-breaks to keep a B DOC: (empty) === PAIR 24 === A ID: REQ-BROKER-OUTPUT-BEFORE-EXIT B ID: REQ-MSG-IDLE-EDGE-DRAIN A TITLE: RC-RENDER-TRUTH W3 (ADR-0043 decision 1, hertz stale-glyphs RCA leg 1 P0): the PTY drain/output writer is the SOLE FIFO sequencer for terminal Output + Exit per attach sink — Exit is enqueued BEHIND all prior output (drain EOF/completion first, then Exit); the exit waiter never direct-writes KIND_EXIT around the queued output path (a mutex serializes bytes, not producer order). Kills the stranded-final-frame race (final EL/SGR-reset/cursor-show/?1049l lost when Exit overtakes Output — already admitted and compensated in the broker test suite, never fixed in production rc). Gate: impl — single sequencer, exit-behind-output enqueue; unit — ordering invariant on the writer queue (exit never precedes queued output for a sink); int — short-lived child emits 'XXXX ESC[2K ESC[?25h ESC[?1049l' then exits => that exact Output precedes Exit through the PRODUCTION broker->attach->rc path; doc — ADR-0043. A DOC: Decisions: 1. **One FIFO sequencer per attach sink.** The PTY drain/output writer is the sole sequencer for terminal Output and Exit: Exit is enqueued behind all prior output for each sink (drain EOF/completion first, then Exit). A mutex alone is insufficient — producer order is the contract. Output-before-Exit is a production-path invariant, regression-proven end-to-end (broker → attach → rc). 2. **rc display teardown is unconditional, idempotent, and separate from input teard B TITLE: On an endpoint's ACTIVE→IDLE transition the daemon DRAINS its pending spool (deferred AND non-deferred) through the same shared spt-hosted inject leg — closing the SECOND F-023 gap: no idle-edge drain exists anywhere, so an spt-hosted endpoint (which has no api-listen relay to wake it) strands BOTH message classes ('ACTIVE → spool deferred for hook-poll' and 'IDLE+no-binary → non-deferred for a relay that does not exist'). FIX: on the state ACTIVE→IDLE edge, offer the pending spool through the shared inject leg; REUSE the hook-poll drain's take/ack machinery so a concurrent `api poll` cannot double-deliver — ONE drain path, TWO triggers (hook-poll + idle-edge). v0.14.3 LAW holds on BOTH triggers: translation-binary-ONLY, a no-binary idle drain SPOOLS LOUD, never writes the PTY. (F-023, BUILD-F023-WANIDLE) B DOC: (empty) === PAIR 25 === A ID: REQ-HAZARD-DEFERRED-DRAIN B ID: REQ-HAZARD-DEFERRED-SURVIVE-DRAIN A TITLE: Deferred spool rows excluded from the event-stream drain (1.4) A DOC: (empty) B TITLE: Deferred rows survive poll drain (4.4) B DOC: (empty) === PAIR 26 === A ID: REQ-ADAPTER-GH-TRANSPORT B ID: REQ-UPD-7 A 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 — 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) A 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/`, …); install/update extracts the shared root plus only the current node's triple, flattened into `install_dir`, so flat `/` resolution is unchanged. It stays one signed asset (`adapter.spt`, plain-tar or gzip); a multi-pla B TITLE: Origin-source update bootstrap (`spt update fetch`): pull the latest signed release directly from the GitHub release origin (`SaberMage/spt-releases`) — the per-platform artifact + its `.release.json` SignedRelease metadata — and stage it through the EXISTING verify→stage pipeline (the same `plan_verified` gate: two-key signature + channel + monotonic rollback floor + SHA-256), after which the normal consent-notif / `spt update apply` flow is unchanged. Closes the peer-only-discovery gap (REQ-UPD-1): a first-in-fleet / isolated node can update with no peer to pull from. The signed-release anchor keeps the GitHub transport untrusted-but-verified. B DOC: (empty) === PAIR 27 === A ID: REQ-CLI-1 B ID: REQ-SUBNET-COUNT-ROUTABLE A TITLE: spt endpoint noun namespace: absorbs fork/suspend/wake/shutdown/rename/stop/digest + access (ported 1:1: allow|revoke|open|list, decision 21) + description (ex-resources blurb; bare=show, set=author); merged endpoint list [--local|--subnet ] grouped by subnet with SELF pinned, --detail adding the ex-resources yellow-pages blurb projection; bare spt endpoint = the list (M8 decisions 1-2, 25). SUPERSEDED (F-025 item 3): the LISTING SHAPE now lives in REQ-ENDPOINT-LIST-NODE-GROUPED (node-grouped over unique instances, not grouped-by-subnet) + REQ-ENDPOINT-LIST-REST-FILTER (suspended-hidden + --show-all); the `--local` flag was dropped by REQ-ENDPOINT-LIST-MERGE-LOCAL (the list ALWAYS merges local). This REQ owns only the endpoint noun NAMESPACE + parse surface — not the render shape. A DOC: (empty) B TITLE: Bug #2: a remote node endpoint count drifts (0/2, 1/3) because node_status_rows (cli.rs:5314) increments the per-node total unconditionally, counting non-routable Offline ghost rows; purge is not a registry eviction (it gossips a one-shot Offline row that is immortal on remote viewers — eviction is per whole-node only). Fix: routable-only denominator (total += status.routable()) keeping a separate raw count for the all-Offline liveness branch; plus per-row Offline-TTL eviction so purged endpoints stop accumulating on remote snapshots. See docs/NEXT-MILESTONE-BUG-TRIAGE.md #2. B DOC: (empty) === PAIR 28 === A ID: REQ-HAZARD-BRAIN-RESTART-LIFECYCLE-REHYDRATE B ID: REQ-NET-3 A TITLE: B4 (deepest): a bare brain restart (broker survives) REHYDRATES the live-agent lifecycle so post-restart endpoints are hosted + attachable. Today resume_sessions (brainproc.rs:186, brain.rs:797-809) re-subscribes to the broker's PTY sessions but ALL BrainLifecycle instances (lifecycle.rs:58-130; the ephemeral brain.rs:254-275) are LOST on restart → a post-restart live endpoint gets no livehost → its Psyche is never (re)hosted and new spawns die / can't attach until a FULL daemon reset (operator: perri's brain kill+restart wedged everything until a full daemon kill). FIX: on brain startup, rebuild a BrainLifecycle per resumed live-capable session — load the manifest from the adapter registry → instantiate → start the pulse — the rehydrate the resume no-op cannot do. Composes with B2 (the reconcile re-hosts from the honest on-disk status after rehydrate). (v0.12.0) A DOC: (empty) B TITLE: Cross-node Psyche sync over P2P replaces gh-repo-sync B DOC: (empty) === PAIR 29 === A ID: REQ-HAZARD-RC-ATTACH-TRUTH B ID: REQ-PICKER-PURGE-STRUCTURED A TITLE: RC-RENDER-TRUTH W1 (KNOWN-HAZARDS 7.46 — umbrella conformance seam for ADR-0042): an rc surface answers from live session authority, never a stale persisted projection; a resuming perch is UNBOUND, not offline. Regression matrix from the hertz RCAs + operator field recovery: offline-row-over-honest-session attaches; offline-no-session refuses; zombie refuses/reaps never attaches; resume-never-bound reads UNBOUND and is attachable; harness-only refuses truthfully pre-stream; qualified targets attach with bare wire id. HEAVY nextest group at birth for any leg spawning a daemon tree (standing CI lint). Gate: int — the matrix; doc — KNOWN-HAZARDS 7.46. A DOC: 7.45 Endpoint lifecycle state converges to truth from every death path — no optimistic online without authority, no surviving control stamps, no immortal wake intent, no untruthful create `[REQ-HAZARD-ENDPOINT-LIFECYCLE]`: ### 7.46 An rc surface answers from live session authority, never a stale persisted projection — and a resuming perch is UNBOUND, not offline `[REQ-HAZARD-RC-ATTACH-TRUTH]` - **Failure (paid-for, hertz perri contradiction RCA 2026-07-17/18 + operator field recovery):* B TITLE: RC-RENDER-TRUTH W3 (ADR-0043 decision 3, hertz stale-glyphs RCA leg 3 P0): core verbs invoked from inside an active TUI return STRUCTURED outcomes and write NOTHING to the terminal — the picker purge path calls a structured-outcome purge core (no stdout/stderr under the live alternate screen) and remains the sole renderer via model.flash (today cmd_endpoint_purge writes diagnostics/PURGED to stderr while ratatui owns the alt screen, mutating the physical screen behind the previous-Buffer diff baseline => later draws skip 'already blank' cells and stderr glyph fragments persist — the x-purge symptom). Baseline-desync regression REQUIRES a stateful/recording backend (pure TestBackend view snapshots cannot catch it). Gate: impl — structured purge outcome + silent-under-TUI routing; unit — purge core emits no terminal bytes in structured mode, picker converts outcomes to flash; int — recording backend: draw ConfirmPurge, inject an external display mutation, transition back => next frame reconstructs the COMPLETE target screen; doc — ADR-0043. B DOC: Decisions: 1. **One FIFO sequencer per attach sink.** The PTY drain/output writer is the sole sequencer for terminal Output and Exit: Exit is enqueued behind all prior output for each sink (drain EOF/completion first, then Exit). A mutex alone is insufficient — producer order is the contract. Output-before-Exit is a production-path invariant, regression-proven end-to-end (broker → attach → rc). 2. **rc display teardown is unconditional, idempotent, and separate from input teard === PAIR 30 === A ID: REQ-HAZARD-RESTART-IDEMPOTENT B ID: REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL A TITLE: Idempotent/exactly-once delivery across brain restart at every broker boundary (codex #14) A DOC: (empty) B TITLE: W3 (LIFECYCLE-TRUTH): daemon restart no longer massacres hosted endpoints — daemon start RE-RUNS previously-online spt-hosted endpoints. ROOT rig-proven: daemon stop+start (the apply notice's OWN instruction) kills every hosted endpoint; they stay OFFLINE after start (no resurrection) though records exist (info.json status + adapter + cwd). SCOPE RULING (doyle): re-run-on-start, marked start-reason=daemon-restart; agents' minds ride psyche re-host as today. Int: endpoint online -> daemon stop -> start -> endpoint back ONLINE, same id, harness respawned. B DOC: (empty) === PAIR 31 === A ID: REQ-ADAPTER-VERSION-CMD B ID: REQ-PICKER-ADAPTER-DESCRIPTION A TITLE: `spt adapter version ` prints a registered adapter's declared version — the EXISTING mandatory `[adapter].version` manifest field (manifest.rs already requires it; NOT a `[strings].version`, NOT `get-string`, no second version source). A new `AdapterCmd::Version{option}` resolves the option's merged view via `registry::resolve_option` like the sibling adapter subcommands and prints `manifest.adapter.version`; an unresolvable option errors (exit 1) the same way. (v0.13.2 W6) A DOC: `[adapter]` — header: `version` is **mandatory**. It is the single declared adapter version — read before any update (the `gh_release` compare point), surfaced by `spt adapter version ` (which prints this `[adapter].version` of a registered adapter; exit 1 if unregistered), and the value the post-update `message` gate keys on. There is no second version source — no `[strings].version`, no `get-string` convention. B TITLE: The Create-new adapter-CHOICE screen of `spt endpoint run`'s picker shows a right-hand Description panel (like the Pick-existing endpoint picker's two-pane) surfacing per-adapter detail: install date, last-updated, adapter TYPE / the endpoint types it hosts, and the adapter description — so the user can see WHAT each adapter is before choosing it (today the selector lists bare names). DEFERRED fast-follow to v0.12.0 (operator 2026-06-18). (post-v0.12.0) B DOC: (empty) === PAIR 32 === A ID: REQ-DAEMON-3 B ID: REQ-ENDPOINT-AUTOSTART A TITLE: Any api invocation auto-starts the daemon if absent A DOC: (empty) B 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 — Gateway-class endpoints are infra (the phone treats mobile-gw as always-there; box reboot / daemon cold start currently leaves it down until hands-on). SHAPE RULED (doyle, dispatch): (a) `spt endpoint run --save` persists the run (id + adapter/profile + args) as a startup default REPLAYED at daemon start, symmetric with the shipped `subnet attach/detach --save` precedent — smallest orthogonal cut, explicit operator intent, no interaction with effective_rest_state/F-035 reader-parity semantics (shape (c) restore-what-was-up REJECTED for now: principled but couples to the rest_state neighborhood that just churned; revisit if --save proves insufficient in the field). A saved endpoint that fails to come up logs loud + does not block daemon start or other replays. flynn docs sweep confirmed missing-feature not docs-gap (rest/wake manual-only; no endpoint analog of subnet --save; no manif B 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 … --save` persists the run — endpoint id, adapter option, and working directory — as a **startup default** in `daemon.json`; the daemon **replays** every saved default when it starts, as a fresh session with the adapter re-resolved at replay time. One entry per endpoint id (a re-save repla === PAIR 33 === A ID: REQ-BRAIN-RESUME-NO-CONTROL-STEAL B ID: REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK A TITLE: UPDATE-WEDGE round 2 (v0.30.4, doyle-ruled 2026-07-09 — field incident on the counter-54 fetch--apply): a brain-respawn must NEVER steal, then stall-evict, the controller of a broker PTY session the daemon brain does not DRIVE. ROOT (field-pinned + SME, docs/UPDATE-WEDGE-2-RCA.md + docs/UPDATE-WEDGE-2-SME-todlando.md): `resume_sessions` (brain.rs:985) re-attaches EVERY session `KIND_SESSIONS` returns via `subscribe` = `subscribe_with(AttachIntent::Control, by:None)` (brain.rs:1450-1455). The docstring's 'a None identity never displaces (falls back to viewer)' is a MYTH for FREE / SAME-LOCAL-IDENTITY slots: `resolve_subscribe` (broker.rs:1134-1153) stall-evicts FIRST, then `become_controller` if the slot is now free OR the incumbent is also local (None==None) — viewer-fallback fires ONLY when a DIFFERENT REMOTE controls. So on a box with N spt-hosted broker PTYs, a brain-respawn STEALS the by:None controller of every free/local-controlled session (incl. the operator's LOCAL `spt rc`), which the daemon brain never drains (it hosts no PTY sessions — brainproc.rs:184) → 15s later `stall_evict_controller` (broker.rs:1039) releases driven_by → the session is UNCONTROLLABLE (Failure A). I A DOC: Self-update: **resume re-attach is view-only for non-driven sessions** — on respawn the new brain queries the broker for every hosted session and re-attaches to rebuild output-continuity cursors, but it re-attaches as a **viewer**, never a controller, for any session it does not itself drive (which is *all* of them today — the supervised daemon brain hosts no PTY sessions; spt-hosted PTYs are driven by the operator's attach or the endpoint's own loop). Re-attaching as a controller would seize th B 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 — no writer may hold the connection's serialized send gate across an UNBOUNDED OS write. ROOT: controller_writer held the SharedSend = Arc> 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 → 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 — 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 — why v0.30.5's controlled apply passed); CTRL_WRITE_LOCKED wait_us=0 on al B DOC: 7.37 The Layer-1 settle-gate must RE-ARM per delivery on an observable PTY — 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 — 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 — hertz RCA, === PAIR 34 === A ID: REQ-EP-1 B ID: REQ-EP-2 A TITLE: Day-one endpoint types; open type system A DOC: (empty) B TITLE: Agent endpoints vs Shells distinction in the type model B DOC: (empty) === PAIR 35 === A ID: REQ-BROKER-ATTACH-JOURNAL-RESILIENT B ID: REQ-PICKER-WINDOW-TITLE A TITLE: A poisoned EffectJournal mutex or a sick NetHost runtime must NOT permanently brick all future attaches. Bug #16 (URGENT): a live spt-hosted endpoint (eel-a) attach fails with 'brain IPC read deadline elapsed' after a self-update brain-respawn — the broker survives the respawn and one journaled op (dispatch_net_stream_open journal.apply_once + loopback open_stream runtime.block_on nethost.rs:1060) enters a bad state, so every journaled attach silently kills its per-conn reply thread while non-journaled ops keep working. Fix: recover PoisonError via into_inner (effect.rs apply_once, replace the .expect panics) so one panic cannot brick all attaches; bound the loopback open_stream block_on (nethost.rs:1060) like the QUIC bounded_block_on so a sick runtime fails fast with an error frame not an opaque 10s deadline. Reinforces REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE. See docs/NEXT-MILESTONE-BUG-TRIAGE.md #16. A DOC: (empty) B TITLE: B-5 (F029, operator): `spt endpoint run`'s interactive picker window/tab is untitled — hard to find among many terminals. Set the window/tab title to `SPT Endpoint Picker`. Anchor picker/mod.rs:88 setup_terminal (crossterm SetTitle in the execute! chain). Set-only is acceptable (crossterm can't cheaply read the prior title to restore). Applies ONLY to the interactive picker path — non-interactive/headless `endpoint run` (REQ-HOST-RUN-1) must NOT retitle the operator's terminal. See triage B-5. B DOC: (empty) === PAIR 36 === A ID: REQ-HAZARD-BRAIN-RESPAWN-PATH B ID: REQ-UPDATE-RESTART-SAFE-SWAP A TITLE: The broker respawns the brain onto the APPLIED bytes, not the renamed old binary: the candidate-binary default is the canonical exe path captured ONCE at broker start, never a per-spawn std::env::current_exe() — on Linux current_exe (readlink /proc/self/exe) is inode-tracking and follows the `apply` rename (spt -> spt.old-N), so a resident broker would respawn the brain onto OLD bytes while recording `applied` (Windows GetModuleFileName is path-at-start, so Windows was green; ADR-0018 Q3 silently assumed path-string semantics). Backstop: promotion gates on bytes — a trial promotes only if brain.ready exe_hash == the staged artifact hash for this platform, else auto-rollback + loud notif (readiness != new-bytes was the false-success that recorded applied:8 over a v0.4.0 brain on kitsubito, 2026-06-11). KNOWN-HAZARDS 6.11. A DOC: 6.10 Phase-significant loop timing must be a durable absolute-deadline grid, not phase-relative sleep `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]`: ### 6.11 Brain respawn must exec the APPLIED bytes, not the renamed old binary (Linux `current_exe` follows the apply-rename; readiness ≠ new-bytes) `[REQ-HAZARD-BRAIN-RESPAWN-PATH]` - **Failure:** the broker respawns the brain candidate from `std::env::current_exe()` resolved **per spawn** (`brainproc.rs:817`). `spt update apply` swaps the binary by rena B TITLE: THE-FORKENING W4 (operator-grilled 2026-07-14; RETIRES findings-backlog seed #12 REQ-UPDATE-ONE-STEP-SAFE-SWAP): `spt update --restart` = the one-step ergonomic path to the SAFE full-cycle swap — fetch -> `update adapters` -> `apply --finish` LAST (lethal-leg-last, ruled: apply --finish restarts the whole daemon incl. broker/PTYs, so it must be the final act — everything completes from ANY invoking context including an spt-hosted session whose PTY dies at that step; accepted cost: a finish FAILURE leaves updated adapters on old-activated core briefly — loud + operator-attended by nature of the flag). Composes with `-c/--core-only` (skip adapters leg). The 0.28.0 wedge lesson closes: the ergonomic one-step no longer picks the riskier path by default for operators who want the full cycle. Gate: unit — flag sequencing incl. lethal-leg-last ordering + -c compose; doc — self-update docs present --restart as the full-cycle form and name the finish-restart consequence. Kin REQ-UPDATE-DEFAULT-COMPOSITE, REQ-UPDATE-FINISH-COMMUNE-FLUSH (deferred commune-flush rides the same finish path when built), seed #12 (retired by this). B DOC: Self-update: **update composite (`spt update`)** — 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 [[,…]]` is the adapters leg alone (alias over `spt adapter update`). The composite's invoker always survives, because a routine apply cycles only the **brain** — the *restart-required* message on broker-side releases is a notice, not a === PAIR 37 === A ID: REQ-DISPATCH-CLAIM-RETRY B ID: REQ-ONEWAY-STREAM-TERMINAL A TITLE: REDISPATCH-TRUTH W1 (ADR-0038, hertz fix C): dispatcher claims are RETRYABLE and outcomes are CLASSIFIED — 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 — claim lifecycle; unit — transient failure requeues + terminal outcome does not (no hot-loop under a persistently-failing stream); int — 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. A DOC: (empty) B TITLE: REGISTRY-LIFECYCLE W1 (ADR-0040 decision 1, hertz defect B leg 1): a one-way fire-and-forget stream family is TERMINAL at successful FIN, sender-side — the registry pump retires its OWN feed row after write+FIN via the existing net-stream-retire verb (best-effort on N-1 brokers per ADR-0038 A). Sender history on the long-lived pump conn stops accumulating: steady-state row population is O(active exchanges), not O(feeds since conn start). Gate: impl — pump push_feed retire-after-FIN; unit — successful feed retires its row, failed/unFINed feed does not, retire failure is best-effort non-fatal; int — rides REQ-HAZARD-REGISTRY-STALL plateau seam (eligible rows plateau O(active) over N rounds); doc — ADR-0040. B 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` === PAIR 38 === A ID: REQ-HAZARD-RC-INPUT-KEY-ENCODING B ID: REQ-PUBLIC-ERROR-SURFACES A TITLE: An `spt rc` session forwards the Backspace key as the VT DEL byte (0x7f), so the hosted TUI (Claude Code) deletes ONE character — never a whole word. SYMPTOM (operator dogfooding): Backspace in an rc session always behaves like ctrl+Backspace — deletes the entire last word. ROOT (doyle /diagnose, code-grounded, byte PENDING HITL confirm): rc is a RAW VERBATIM byte pump — spawn_stdin_reader (rc.rs:152) reads std::io::stdin() bytes under crossterm raw mode and forwards them unchanged (parse_stdin_chunk only intercepts the ctrl-b detach prefix); there is NO key-event encoding and NO 0x08↔0x7f normalization ANYWHERE in the tree (grep: zero SetConsoleMode / ENABLE_VIRTUAL_TERMINAL_INPUT). On Windows, crossterm enable_raw_mode does NOT set ENABLE_VIRTUAL_TERMINAL_INPUT, so the LEGACY console delivers ^H (0x08, ctrl+h) for Backspace instead of VT DEL (0x7f); Claude Code maps ^H → backward-kill-word → the observed whole-word delete. CONFIRM-FIRST (build the loop): an env-gated hexdump in spawn_stdin_reader (SPT_RC_DEBUG_KEYS) prints the forwarded byte; operator presses Backspace + ctrl+Backspace in a real rc session. FIX CANDIDATES: (a) enable ENABLE_VIRTUAL_TERMINAL_INPUT on the rc stdin A DOC: (empty) B TITLE: F-1 (REMOTE-TRUTH triage §F-1, Q4 UX rule, operator-ruled): CLI stderr a non-developer can hit names the OBSERVABLE SITUATION + the NEXT ACTION — never journal/op/brain/store lingo. The sweep's named offenders: (1) `RC_FAIL:{id}: … brain IPC read deadline elapsed` — the brain transport error surfaced RAW through rc's residual Err arm (rc.rs run_attach_inner); operators read 'brain IPC' where the situation is 'the daemon didn't answer in time'. (2) `WOKE_FAIL:{id}: info.json absent or unreadable — not a hosted perch` (resting.rs apply_event miss) — store-file lingo in the one rest-verb line a stale remote row still surfaces cross-node (the qualified-arm D6 case; the A-3 bare-id local path already routes instead). The miss stays SINGLE-SOURCED from NOT_A_HOSTED_PERCH_MARKER (in-process discriminant, resting.rs — reword is compat-safe per its own doc; the drift-pin unit keeps builder+matcher fused). (3) translation_fault never human-rendered (F-030 post-release seed): a broker-stamped input-translation fault (e.g. 'inject worker panicked') was invisible in `endpoint list`/`whoami` while keystrokes silently degraded — rendered now as a SELF-pin annotation exactly like the psyche_host_e B DOC: (empty) === PAIR 39 === A ID: REQ-HAZARD-PSYCHE-RESIDENCY-EXPECTATION B ID: REQ-HAZARD-RENDER-LIFECYCLE A TITLE: W3 (F030 hazard; paid-for: hall-bf churn ordinal 6491+ + adapter v0.13.2 bad-ship brick 2026-07-04): a psyche failure of ANY shape must NOT remove or alter the parent endpoint's ready/hosted state, and hosting must NOT churn-respawn. The v0.13.2 shim-exit tripped the residency machinery (confirm_residency_or_unhost) which tore down the endpoint's hosted state — ready marker removed, never re-stamped, every force-native gated leg=cli-gate-not-hosted PERMANENTLY (field brick). FIX: residency machinery retires with the resident child; the teardown that touches parent hosted state is DELETED — psyche trouble stamps psyche fields only. REQ-HAZARD-LIVEHOST-NONRESIDENT's spirit transfers to the W1 failure budget (its entry gets a SUPERSEDED pointer here, LIVENESS-DECAY→SUPERSEDED pattern from C-1). Conformance int = the hall-bf shape: multi-subnet home, live endpoint, failing psyche → parent stays deliverable, no rehost churn, error stamped (the wave's heart). A DOC: 7.29 Control/viewer stamps CONVERGE to broker session-table truth, not merely edge-trigger `[REQ-HAZARD-CONTROL-STAMP-CONVERGENCE]`: ### 7.30 A Psyche failure of ANY shape must NEVER remove or alter the parent endpoint's ready/hosted state `[REQ-HAZARD-PSYCHE-RESIDENCY-EXPECTATION]` - **Failure (paid-for, field brick 2026-07-04, adapter v0.13.2):** the pre-F-030 model kept a **resident** Psyche process the daemon supervised, with residency machinery (`confirm_residency_or_unhost`) that B TITLE: RC-RENDER-TRUTH W3 (KNOWN-HAZARDS 7.47 — 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 — the matrix; doc — KNOWN-HAZARDS 7.47. B DOC: 7.46 An rc surface answers from live session authority, never a stale persisted projection — and a resuming perch is UNBOUND, not offline `[REQ-HAZARD-RC-ATTACH-TRUTH]`: ### 7.47 The physical terminal is never mutated or terminated outside its renderer's ordered state model — output before exit, owned baselines, unconditional teardown `[REQ-HAZARD-RENDER-LIFECYCLE]` - **Failure (paid-for, hertz stale-glyphs RCA 2026-07-18, all legs doyle seam-verified):** four render-lifecycle defects p === PAIR 40 === A ID: REQ-DAEMON-1 B ID: REQ-HAZARD-CONTROL-STAMP-LIFETIME A TITLE: One per-machine spt-daemon owning all per-machine state A DOC: (empty) B TITLE: #2: a control/viewer stamp never outlives its session — 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 — 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) — broker stays the single writer. KNOWN-HAZARDS invariant. See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #2. B DOC: 7.26 Concurrent first-touch of ONE fresh BranchStore must ALL succeed — 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 — 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` — hours later hall-a's `info.json` still === PAIR 41 === A ID: REQ-HAZARD-STALE-INDEX-LOCK B ID: REQ-HAZARD-REGISTRY-STALE-CLEAN A TITLE: Sweep stale lockfiles on daemon boot (1.3) A DOC: (empty) B TITLE: Stale registry entries degrade to fallback, never hard-fail (4.3) B DOC: (empty) === PAIR 42 === A ID: REQ-HAZARD-SPOOL-SENTINEL-CREATE-FAIL B ID: REQ-UPDATE-GH-TRANSPORT A TITLE: A persistent failure to (re)create the spool has-messages sentinel is SURFACED, never silently swallowed. touch_has_messages (spt-store/src/spool.rs:147) does `let _ = File::create(...)` — a live field defect on ENLYZEAM left a stale .has-messages (2026-06-29) beside a fresh spool.db insert (06:59:17Z) in ONE directory, i.e. the create silently failed while rows accumulated (suspected read-only-attrib / share-lock). FIX: on File::create failure emit a LOUD-ONCE-per-perch stderr diagnostic naming the concrete io::Error (self-identifying regardless of kind); do NOT make it fatal (spool writes still proceed). (F-024C item 2, doyle) A DOC: (empty) B 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 — release discovery (`releases/latest`, cli.rs:9717) and asset download (cli.rs:4861 public browser URLs) move to deadline-wrapped `gh` subprocess calls (`gh api`, `gh release download`; run_git pattern). WHY gh not token+HTTP: private-repo `browser_download_url` 404s even with a valid token — the API asset-id dance is gh's job. Default repo flips via the existing SPT_INSTALL_REPO seam (cli.rs:5363) + xtask REPO const (main.rs:729) + notif.rs consent-changelog URL rider. Loud failure classes: gh missing -> UPDATE_FETCH_REJECTED:GhCliRequired with OS-SPECIFIC install hints (winget/apt/brew); gh unauthed -> distinct GhAuthRequired pointing at `gh auth login`. Signature verification unchanged — bytes verified after download, carrier-independent (update-set/counter/anchor continuity per ADR-0036 §2). release_verify_e2e reworked to the gh carrier. Gate: unit — url/invocation construction + both failure classes render OS-correct hints; int — fetch against a real gh-authed channel resolves latest + downloads and verifies an asset; do B DOC: Self-update: **release channel (private, gh-carried)** — the release channel is a **private** GitHub repo (`BigscreenVR/spt-bs-releases`, ADR-0036); the **gh CLI is the mandated carrier** for release discovery and asset download (each node authenticates via org membership). A node without an authed `gh` cannot fetch — refused loud with OS-specific install hints, never a silent hang. Signature verification is carrier-independent: bytes are verified after download exactly as before; counter, signi === PAIR 43 === A ID: REQ-INST-11 B ID: REQ-UPD-5 A TITLE: spt rename rippled to all instances (collision-checked, 6.5-reconciled) A DOC: (empty) B TITLE: spt-core ripple-updates registered adapters B DOC: (empty) === PAIR 44 === A ID: REQ-HAZARD-CONPTY-DSR B ID: REQ-HAZARD-EBUSY-RENAME A TITLE: ConPTY reader must auto-answer DSR (ESC[6n) or all child output stalls (5.5) A DOC: (empty) B TITLE: tmp-write + atomic-rename + retry on Windows EBUSY (5.2) B DOC: (empty) === PAIR 45 === A ID: REQ-RC-HARNESS-ONLY-REFUSAL B ID: REQ-RESUME-UNBOUND-STAMP A TITLE: RC-RENDER-TRUTH W1 (ADR-0042 decision 3, hertz elevated-endpoints RCA core leg 1): direct `spt rc ` preflights the already-known harness_only/controllable=Some(false) fact (local + gossiped rows both carry it) and refuses BEFORE any attach/stream with truthful copy naming the actual state ('online but harness-hosted; spt does not own its terminal') — never the stale-row guess ('it may have ended, or the registry row is stale'). Reproduced deterministically 2x live on an ONLINE - HARNESS ONLY endpoint. RC support for harness-hosted endpoints is NOT claimed until the elevated PTY-host satellite exists (standing ruling). Gate: impl — pre-stream preflight + truthful copy; unit — harness-only row refuses pre-dial with the new copy, non-harness rows unaffected; int — live remote Instance{harness_only:true} => direct rc refuses pre-stream, names harness-hosted, never says stale; doc — ADR-0042. A DOC: Decisions: 1. **`spt rc` consults the honest-session authority before the offline fast-fail.** Normal `spt rc ` runs the same bounded `SessionProbe::has_live_session_honest` gate `endpoint run` uses (ADR-0041 single liveness authority). An honest session exists → attach via the session-confirmed path regardless of persisted status. No honest session → the existing offline refusal stands. A claimed session with a dead client tree → refusal/reap, never attach. Reuse `SessionProbe`; no B TITLE: RC-RENDER-TRUTH W1 (ADR-0042 decision 2, operator-spotted second root seam): resume launch transitions an existing offline perch to UNBOUND before/with the broker spawn — UNBOUND semantics are fresh/resume-invariant (broker session exists + harness not bound = UNBOUND); generation/session-safe ROLLBACK to offline on spawn failure or session death; bind owns UNBOUND->ONLINE. Supersedes the rc.rs 'resume gets no UNBOUND stamp — accepted' boundary note (the pre-bind window can be PERMANENT: stuck native resume, SessionStart never fires — field-proven; truthful UNBOUND is the operator-recovery surface that let `spt rc` reach the wedged TUI). Writer-truth complement to REQ-RC-HONEST-SESSION-AUTHORITY — both land, neither substitutes. Gate: impl — UNBOUND stamp at resume spawn + rollback + bind transition; unit — stamp fires on existing-offline perch resume, rollback on spawn-fail restores offline, generation guard refuses a stale rollback over a newer bind; int — resumed-but-never-bound endpoint reads UNBOUND (not offline) and `spt rc` attaches to its live session; doc — ADR-0042. B DOC: Decisions: 1. **`spt rc` consults the honest-session authority before the offline fast-fail.** Normal `spt rc ` runs the same bounded `SessionProbe::has_live_session_honest` gate `endpoint run` uses (ADR-0041 single liveness authority). An honest session exists → attach via the session-confirmed path regardless of persisted status. No honest session → the existing offline refusal stands. A claimed session with a dead client tree → refusal/reap, never attach. Reuse `SessionProbe`; no === PAIR 46 === A ID: REQ-HAZARD-RESTART-IDEMPOTENT B ID: REQ-RC-RECONNECT A TITLE: Idempotent/exactly-once delivery across brain restart at every broker boundary (codex #14) A DOC: (empty) B TITLE: B-3 (REMOTE-TRUTH triage §B-3, the operator-asked UX): the rc attach viewport RECONNECTS on a severed transport instead of print-and-exit. Pre-fix rc was one-shot (resolve→dial→attach→pump→parting line); FAULT-MATRIX row 9 over-promised. FIX: the establish sequence (daemon ensure → broker conn → session resolve local-first/cross-node → dial → attach-open w/ A-4b tracing retry → subscribe) is factored into establish_attach and run_attach_inner wraps establish+pump in a loop. RECONNECTABLE class = severed transport ONLY: PumpEnd::BrokerGone (broker-conn EOF class, broker bounce) + the NEW PumpEnd::Severed (serve-side stream EOF AFTER rendered output = remote conn drop — pre-fix MISLABELED as 'detached — still running'; a nothing-rendered EOF stays the honest NoLiveSession refuse). FINAL ends (Exited/Detached/Displaced/Stalled/NoLiveSession) never re-drive — re-attaching a deliberately-ended session is wrong. On sever: full-screen centered 'Reconnecting to {target}…' banner (pure byte-emit like StatusRow; target = owning-node label or 'local daemon'; Q4 UX rule — operator language, internal sever detail never paints), then re-drive establish_attach every RECONNECT_PAUSE (1s) inside RE B DOC: Proven rows (injection evidence in-tree): | # | Fault injected | Blast radius (by design) | Survives untouched | Recovery | Evidence | |---|---|---|---|---|---| | 1 | **Brain killed mid-PTY-stream** (logic crash / routine self-update) | Logic halts until restart | Broker, PTY child (pid stable), output log | New brain re-subscribes from cursor; gapless + exactly-once | `spt-daemon/tests/` B2/B9 handoff suite; `tests/idempotent.rs` (crash before-intent / before-effect / after-effect) | | 2 | **Br === PAIR 47 === A ID: REQ-PICKER-ADAPTER-DESCRIPTION B ID: REQ-PICKER-UX-V013 A TITLE: The Create-new adapter-CHOICE screen of `spt endpoint run`'s picker shows a right-hand Description panel (like the Pick-existing endpoint picker's two-pane) surfacing per-adapter detail: install date, last-updated, adapter TYPE / the endpoint types it hosts, and the adapter description — so the user can see WHAT each adapter is before choosing it (today the selector lists bare names). DEFERRED fast-follow to v0.12.0 (operator 2026-06-18). (post-v0.12.0) A DOC: (empty) B TITLE: `spt endpoint run` picker UX (v0.13.0 operator dogfooding): (1) SKIP the first screen — open directly on 'Pick existing'; `n` jumps to 'Create new'. (2) AUTO-ATTACH after both Start-new AND Resume-from-history (both currently don't attach and show no stdout); add an `h` shortcut to run headless (no attach). (3) 'controlled by' shows the node NAME (node_label_display), not the raw hex. (4) Clean up Start-new output — drop the Rust `pid=Some(142748)` leak and the 'harness binds its perch on startup' internals; user-friendly, not a process log. (v0.13.0) B DOC: (empty) === PAIR 48 === A ID: REQ-CONN-BLACKHOLE-LIFECYCLE-HARNESS B ID: REQ-HAZARD-ENDPOINT-LIFECYCLE A TITLE: MSG-IDENTITY W6 / F-039 leg e (doyle W6 LOCK 2026-07-10, minted per amendment 3 — 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 — 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 — its brain_decouple int stays the Windows-mandatory gate leg), REQ-CONN-POISON-DIAL-SCOPE. A DOC: (empty) B TITLE: REGISTRY-LIFECYCLE W2 (KNOWN-HAZARDS 7.45 — the umbrella conformance seam for ADR-0041): endpoint lifecycle state converges to truth from EVERY death path. Regression matrix from the three hertz reports + operator field: dead-PID hybrid row does not survive reconcile; raw viewport close frees the controller (full chain, broker restart included — shared with REQ-STREAM-LEASE-CLASSES int); definitive death means offline+suspended atomically and the next reconcile emits no WAKE_RESUME; explicit Wake still launches exactly once; poll-vs-reap interleave converges to cleared stamps. HEAVY nextest group at birth for any leg spawning a daemon tree. Gate: int — the matrix; doc — KNOWN-HAZARDS 7.45. B DOC: 7.44 Streams and seats on a long-lived connection must have bounded lifetime — one-way rows terminal at FIN, seats released at serve completion, no per-chunk full-state rewrites in a drain loop `[REQ-HAZARD-REGISTRY-STALL]`: ### 7.45 Endpoint lifecycle state converges to truth from every death path — no optimistic online without authority, no surviving control stamps, no immortal wake intent, no untruthful create `[REQ-HAZARD-ENDPOINT-LIFECYCLE]` - **Failure (paid-for, three hertz repor === PAIR 49 === A ID: REQ-ENDPOINT-PURGE B ID: REQ-MANIFEST-3 A TITLE: `spt endpoint purge ` fully removes an endpoint AND every record keyed on it — the formal teardown devs/CI need for clean test setup/reset. NOT consent-gated (a local dev/test op — no peer consent). OFFLINE-ONLY: refuses while the endpoint is online / daemon-hosted (deleting records out from under a live host risks the daemon re-creating or re-hosting mid-purge); `--force` STOPS it first (endpoint stop → wait for the daemon reconcile to un-host + reap the Psyche) THEN purges. Confirms interactively unless `--yes` (the CI path). Refuses purging the CALLER's OWN running id. All LOCAL — purge reaches only THIS node's records; a remote endpoint's records can't be touched, and its subnet-registry rows decay via the epoch-lease eviction (REQ-HAZARD-REGISTRY-DECAY). Removes: (1) the perch dir TREE recursively — owlery// incl every nested {id}-psyche / {id}-w* / shells child (info.json, ready marker, sessions.log ledger, spool.db, inbox, .idle/.more-done sentinels, auth token); (2) the registry address (registry::unregister_address); (3) the context store — ContextStore::remove_endpoint(id): the a- branch+worktree + the / rows from every p- branch (the same fn `for A DOC: Inbound `api` surface (detailed): **`spt endpoint purge `** (CLI, not `api`) — the standalone, formal **full teardown**: wipe an endpoint and *every* record keyed on it. It is the dev/CI sibling of `api session-end --erase` (which is adapter-triggered at session end); `purge` is the explicit operator/test command for clean setup-and-reset. **Deliberately NOT consent-gated** — a local dev/test op, never a peer-visible action. **Offline-only**: it refuses a live / daemon-hosted endpoint (delet B TITLE: Adapter strings — [strings] KV tree, dot-path get-string resolving through the profile leaf-replace overlay, set-string editing a local profile's [strings] only; data-only (nothing executes a string) B DOC: Runtime model: **adapter strings** (ratified 2026-06-11, Gateway grill): A `[strings]` manifest section — an adapter-authored JSON/TOML KV tree, dot-path-readable by anything on the node via `spt adapter get-string ` (e.g. a harness hook fetching per-profile `additionalContext` — one hook script serves every profile, only the data differs). Resolution rides the **same leaf-replace profile overlay** as the rest of the manifest: a shipped or local profile may ov === PAIR 50 === A ID: REQ-DOCS-6 B ID: REQ-HAZARD-RC-INPUT-KEY-ENCODING A TITLE: spt how-to : in-binary task-oriented agent instructions (anti-drift; quickstart prompts point agents at it) A DOC: Quickstart: two agents exchange a message: End to end in under 10 minutes. The roles matter here: **you** install (and optionally pair machines); **your agents** exchange the messages. You hand each agent a short prompt; the binary its B TITLE: An `spt rc` session forwards the Backspace key as the VT DEL byte (0x7f), so the hosted TUI (Claude Code) deletes ONE character — never a whole word. SYMPTOM (operator dogfooding): Backspace in an rc session always behaves like ctrl+Backspace — deletes the entire last word. ROOT (doyle /diagnose, code-grounded, byte PENDING HITL confirm): rc is a RAW VERBATIM byte pump — spawn_stdin_reader (rc.rs:152) reads std::io::stdin() bytes under crossterm raw mode and forwards them unchanged (parse_stdin_chunk only intercepts the ctrl-b detach prefix); there is NO key-event encoding and NO 0x08↔0x7f normalization ANYWHERE in the tree (grep: zero SetConsoleMode / ENABLE_VIRTUAL_TERMINAL_INPUT). On Windows, crossterm enable_raw_mode does NOT set ENABLE_VIRTUAL_TERMINAL_INPUT, so the LEGACY console delivers ^H (0x08, ctrl+h) for Backspace instead of VT DEL (0x7f); Claude Code maps ^H → backward-kill-word → the observed whole-word delete. CONFIRM-FIRST (build the loop): an env-gated hexdump in spawn_stdin_reader (SPT_RC_DEBUG_KEYS) prints the forwarded byte; operator presses Backspace + ctrl+Backspace in a real rc session. FIX CANDIDATES: (a) enable ENABLE_VIRTUAL_TERMINAL_INPUT on the rc stdin B DOC: (empty) === PAIR 51 === A ID: REQ-DAEMON-2 B ID: REQ-UPDATE-FINISH-COMMUNE-FLUSH A TITLE: Broker/brain split for seamless self-update A DOC: Restoration field-run evidence (D7-4) — the seamless-update acceptance: B TITLE: DEFERRED (post-LIFECYCLE-TRUTH, operator-ruled 2026-07-07 — 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 — 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 — 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 B DOC: (empty) === PAIR 52 === A ID: REQ-LISTEN-PRESERVES-HOSTING-TOPOLOGY B ID: REQ-START-3 A 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 — controlled=true AND controllable=false on a broker-hosted PTY endpoint — which controlled-precedence masks blue while attached and which a DETACH then unmasks as amber HARNESS ONLY. Detach is not the root; it only reveals the bad stamp. SOURCE: api/startup.rs passes controllable=Some(false) UNCONDITIONALLY on the relay/listen path (~191-195, reasoning 'the harness owns the process, so there is no broker PTY'), and establish_perch resolves controllable = controllable.or_else(|| prior…) (~379) — explicit wins, so that Some(false) OVERWRITES the Some(true) an earlier `api bind` EARNED. The carry-forward discipline that protects cwd/adapter/rest_state does not protect this field precisely BECAUSE the listen path is not silent about it. The defect is an ASSUMPTION about hosting authority made by a path that does not know the answer. FIX (preferred): represent LISTENER CUSTODY separately from PTY HOSTING AUTHORITY, A DOC: (empty) B TITLE: spt-hosted startup: spawn-session then api bind (no file) B DOC: (empty) === PAIR 53 === A ID: REQ-ENDPOINT-TEARDOWN-AUTHORITY B ID: REQ-RC-VT-TEARDOWN A 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) — 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 A DOC: 7.48 At most one input-capable controller lease per PTY session — takeover revokes atomically and loudly, input is fenced to the active lease, node identity is never a lease `[REQ-HAZARD-CONTROLLER-LEASE]`: ### 7.49 A teardown verb never stamps a terminal or resting state it has not caused — and two individually-correct verbs must not compose into a lifecycle dead end `[REQ-HAZARD-TEARDOWN-DEADEND]` - **Failure (paid-for, two hertz field RCAs 2026-07-19, both doyle code-verifie B TITLE: RC-RENDER-TRUTH W3 (ADR-0043 decision 2, hertz stale-glyphs RCA leg 2 P0): rc display teardown is a display RAII guard SEPARATE from the OS input/raw-mode guard, unconditional and idempotent on EVERY exit path including errors and unwind — best-effort SGR reset + full scroll-region reset + cursor show + leave alternate screen + clear/home, emitted while VT output processing is still enabled, THEN restore the prior console output mode, THEN parting prose (today RawGuard::drop restores raw/mouse/console-mode only; detach, child exit, displacement, first-event stall, fatal error, and the 30s reconnect give-up all can leave the operator terminal dirty; the reconnect banner clears+homes then give-up prints at the centered cursor). Gate: impl — split display guard + every-path coverage; unit — guard emits the cleanup postlude exactly once, idempotent on double-drop; int — dirty sink (?1049h ?25l SGR31) x every PumpEnd/error class => cleanup postlude precedes the final prose; doc — ADR-0043. B DOC: Decisions: 1. **One FIFO sequencer per attach sink.** The PTY drain/output writer is the sole sequencer for terminal Output and Exit: Exit is enqueued behind all prior output for each sink (drain EOF/completion first, then Exit). A mutex alone is insufficient — producer order is the contract. Output-before-Exit is a production-path invariant, regression-proven end-to-end (broker → attach → rc). 2. **rc display teardown is unconditional, idempotent, and separate from input teard === PAIR 54 === A ID: REQ-API-3 B ID: REQ-HAZARD-ENVELOPE-PARSER-SAFE A TITLE: commune/signoff are file-drops, not commands A DOC: (empty) B TITLE: Two-slice envelope parser is panic-free and tolerant (4.2) B DOC: (empty) === PAIR 55 === A ID: REQ-ENDPOINT-PURGE B ID: REQ-NOTIF-DRAIN-ROW-VALIDITY A TITLE: `spt endpoint purge ` fully removes an endpoint AND every record keyed on it — the formal teardown devs/CI need for clean test setup/reset. NOT consent-gated (a local dev/test op — no peer consent). OFFLINE-ONLY: refuses while the endpoint is online / daemon-hosted (deleting records out from under a live host risks the daemon re-creating or re-hosting mid-purge); `--force` STOPS it first (endpoint stop → wait for the daemon reconcile to un-host + reap the Psyche) THEN purges. Confirms interactively unless `--yes` (the CI path). Refuses purging the CALLER's OWN running id. All LOCAL — purge reaches only THIS node's records; a remote endpoint's records can't be touched, and its subnet-registry rows decay via the epoch-lease eviction (REQ-HAZARD-REGISTRY-DECAY). Removes: (1) the perch dir TREE recursively — owlery// incl every nested {id}-psyche / {id}-w* / shells child (info.json, ready marker, sessions.log ledger, spool.db, inbox, .idle/.more-done sentinels, auth token); (2) the registry address (registry::unregister_address); (3) the context store — ContextStore::remove_endpoint(id): the a- branch+worktree + the / rows from every p- branch (the same fn `for A DOC: Inbound `api` surface (detailed): **`spt endpoint purge `** (CLI, not `api`) — the standalone, formal **full teardown**: wipe an endpoint and *every* record keyed on it. It is the dev/CI sibling of `api session-end --erase` (which is adapter-triggered at session end); `purge` is the explicit operator/test command for clean setup-and-reset. **Deliberately NOT consent-gated** — a local dev/test op, never a peer-visible action. **Offline-only**: it refuses a live / daemon-hosted endpoint (delet B TITLE: A spooled notify envelope is validated against its notif row at DELIVERY time — a copy outliving its row must not deliver. (ADR-0046 Amendment 1 + KNOWN-HAZARDS 7.53; operator field regression from perri's node 2026-07-22, doyle root-caused same day — DAEMON-LIFECYCLE W1 RIDER.) TODAY: quiet delivery (REQ-NOTIF-QUIET-DELIVERY) makes every surface an active_only SPOOL write per endpoint, and every row-lifecycle mechanism (apply-seam dismissal REQ-NOTIF-SEAM-DISMISS, coalesce supersession, TTL, the one-shot migration) touches ROWS only — so a copy spooled while the endpoint was busy/offline is a detached snapshot no dismissal can recall, delivering 'update available' on an already-updated node at the next drain, once per qualifying surface event (perri: twice; doyle's own session: four stale 0.39.x drains post-upgrade). FIX: at the safe-point drain choke point (api poll deferred presentation, spt/src/api/delivery.rs cmd_poll), a notify-kind envelope delivers ONLY if its notif_id resolves to a live UNDISMISSED row in the local notif store; dismissed/superseded/TTL-expired/unknown -> dropped silently; N copies of one notif_id in a drain dedupe to ONE delivery. Non-notify spool content B DOC: 7.53 A durable copy of revocable content is validated against its source of truth at DELIVERY time — a spooled notice outliving its row must not deliver `[REQ-NOTIF-DRAIN-ROW-VALIDITY]`: - **Failure (paid-for, operator field report from perri's node 2026-07-22 — the day after the v0.40.0 notif redesign shipped; doyle root-caused same day, own session corroborating with stale v0.39.x drains):** a node already ON v0.40.0 received the v0.40.0 "update available" notice twice. ADR-0046's qui === PAIR 56 === A ID: REQ-INSTALL-10 B ID: REQ-START-2 A TITLE: Windows at-logon autostart runs the daemon in the background with no persistent window: the scheduled task launches `spt daemon start` (which spawn_detaches a console-less DETACHED_PROCESS daemon and exits) rather than the foreground `spt daemon run` — Task Scheduler's interactive ONLOGON launch of a long-lived console process otherwise leaves a visible console window for the daemon's whole lifetime (v0.7.4) A DOC: (empty) B TITLE: Harness-hosted startup: api seed then listen B DOC: (empty) === PAIR 57 === A ID: REQ-HAZARD-CONTROLLER-RETAKE-FLOOR B ID: REQ-HAZARD-INSTANT-UNDERFLOW A TITLE: `become_controller` should STRUCTURALLY refuse a controller re-take whose `from_seq` falls below the connection's already-delivered contiguous floor — making the P1c reorder invariant un-reintroducible by a future caller, not just removed at the one caller. ROOT/SCOPE (doyle proposed, P1c gate dialogue): P1c fixes REQ-HAZARD-CONTROLLER-WRITER-REORDER three ways (handoff single-take + epoch-gate-under-lock + session_cursors seed), removing the one decreasing-floor double-take and bounding any other to already-committed-only. A self-enforcing broker guard would refuse the bad SHAPE outright. BLOCKER: the obvious predicate (`from_seq >= delivered_through`) is UNSAFE because `delivered_through` is SESSION-WIDE (the `Arc` on `OutputLog`, shared by all controllers/viewers, advanced monotonic-MAX; `resume_seq` reads it) — a normal fresh-operator `from_seq=0` attach to a producing session legitimately sits below it (full ring replay + consumer dedup-below/snap-above), and monotonic-MAX can't distinguish the hazard (a `seq1`-without-`seq0` write reads as `2`). The structurally-correct guard needs a NEW per-connection contiguous-sent cursor (the true highest-contiguous seq this so A DOC: (empty) B TITLE: Scheduling never subtracts a Duration from Instant::now() (underflow-panics on a host booted more recently than the offset); 'due now / never run' is Option=None gated on forward duration_since only (5.9) B DOC: (empty) === PAIR 58 === A ID: REQ-REL-3 B ID: REQ-UPD-2 A 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) A DOC: (empty) B TITLE: All binaries signature-verified before handoff B DOC: (empty) === PAIR 59 === A ID: REQ-PRESENCE-LIVENESS-TRUTH B ID: REQ-SUBNET-DISPLAY-PARITY A TITLE: A node's gossiped per-endpoint registry Status reflects real liveness, so a remote viewer never paints a DEAD endpoint as ONLINE. ROOT (confirmed + gated vs CONTEXT.md): registryhost.rs:397-405 advertises a NOT-alive perch as Status::Dormant (the `else` of is_perch_alive), re-stamped every gossip round (never ages to Offline). Design intent GATED vs resting.rs + CONTEXT.md: active/dormant is the MULTI-INSTANCE routing differentiator (active = the bare-id routing target; dormant = a WARM non-target sibling — 'driving ling@laptop makes ling@desktop dormant', resting.rs:97; transitions active→dormant on AttentionShift/Detach). suspended = COLD (session closed, resumable-on-wake) while its NODE is UP. offline = NODE DOWN — NEVER self-gossiped (RestState has no Offline; a live node only ever gossips active/dormant/suspended), remote-inferred via epoch-lease eviction. So labeling a NOT-running perch Dormant is the DEFECT (dormant requires warm/running). PRIMARY FIX (registryhost `else`, not-bound-alive): live-but-UNBOUND (has a live broker session; is_perch_alive is bound-gated) → Active/Dormant (still warm); else (cold, no live session, but its node is up because this very daemon is gos A DOC: (empty) B TITLE: The `spt endpoint run` picker renders endpoint state IDENTICALLY for local and remote (subnet) rows — 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 — 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 `); RED-filled=online+UNBOUND (controlled-or-not; controlled-ness shown via available options not glyph) — replaces green-hollow Unbound + absorbs the dropped UnboundControlled; AMBER-HOLLOW=online+harness-only (no broker seat → can't rc) — was amber-FILLED; GRAY-FILLED=Suspended (cold, node up — wakeable) NEW; gray-hollow=Offline (node B DOC: (empty) === PAIR 60 === A ID: REQ-ARCH-2 B ID: REQ-INST-3 A TITLE: Public SDK surface is spt-proto, spt-runtime, spt-msg A DOC: (empty) B TITLE: Dormant (warm) / suspended (cold) resting states B DOC: Dormancy resource budget (D9-3 — ADR-0003 red-team #9): === PAIR 61 === A ID: REQ-CLI-1 B ID: REQ-WORKER-LIST-VISIBILITY A TITLE: spt endpoint noun namespace: absorbs fork/suspend/wake/shutdown/rename/stop/digest + access (ported 1:1: allow|revoke|open|list, decision 21) + description (ex-resources blurb; bare=show, set=author); merged endpoint list [--local|--subnet ] grouped by subnet with SELF pinned, --detail adding the ex-resources yellow-pages blurb projection; bare spt endpoint = the list (M8 decisions 1-2, 25). SUPERSEDED (F-025 item 3): the LISTING SHAPE now lives in REQ-ENDPOINT-LIST-NODE-GROUPED (node-grouped over unique instances, not grouped-by-subnet) + REQ-ENDPOINT-LIST-REST-FILTER (suspended-hidden + --show-all); the `--local` flag was dropped by REQ-ENDPOINT-LIST-MERGE-LOCAL (the list ALWAYS merges local). This REQ owns only the endpoint noun NAMESPACE + parse surface — not the render shape. A DOC: (empty) B TITLE: V-1 (WORKER-TRUTH triage, operator rider): worker perches leave the DEFAULT `spt endpoint list` view — they are process-local machinery, not subnet citizens; leaked-or-live worker rows rendering as permanent OFFLINE endpoints is the operator-visible symptom root. A dedicated flag (--workers) reveals them (one command + flag per the --all/--detail precedent, NOT a separate list-working command — sister divergence deliberate). Applies to the human render, --json (additive default-absent filter), and the registry/projection legs; verify-and-stop any worker gossip into the subnet registry as peer endpoints. B DOC: (empty) === PAIR 62 === A ID: REQ-HAZARD-RC-ATTACH-FAILFAST B ID: REQ-RESUME-CUSTODY-IDENTITY A TITLE: B1: `spt rc ` to a DEAD or non-streaming session fails fast with a clear message, never an INFINITE blank screen. Today rc.rs run_attach (209-231) + pump spawns PUMP_IPC_READER and blocks: the poll times out each slice but the stream never produces output, so the operator sees a permanent blank (operator: fresh wall-f attached, closed tab, then `spt rc wall-f` HUNG — the broker still resolved a session for it). FIX: (a) once B2 lands, gate attach on is_online/status — an offline endpoint yields a clean 'endpoint offline, start it' not an attach; (b) fail-fast — if the attach-open ack / first output does not arrive within a bound, surface a clear message, never an infinite blank; (c) the broker EOFs the attach stream when the session's child is dead, so rc's existing PumpEnd::BrokerGone graceful path (REQ-HAZARD-RC-EOF) catches it. PIN the exact sub-mechanism with a repro test FIRST (dead-session-lingers-in-broker vs reaped-but-rc-waits vs alive-resting-no-wake — the wall-f Windows tab-close: child alive-silent vs dead-not-reaped). (v0.12.0) A DOC: (empty) B TITLE: Resume custody is an identity pair (pid + process creation time), never a bare PID. (ADR-0047 decision 1; hertz v0.39.4 field bug 1, RCA accepted 2026-07-22.) TODAY: livehost's restart gate and liveness-reconcile DEFER both consume `read_resume_pid(..).is_some_and(is_process_alive)` — zero identity binding, so a dead wake-resume spawn's pid recycled onto an unrelated process (field: resume.pid=29456 -> cmd.exe) reads as a live resume forever: reconcile defers every tick, the row stays online-authoritative, FALSE-ONLINE with no self-repair. FIX: the custody record stores (pid, creation_time) written atomically at spawn-mint; every consumer tests the PAIR; mismatch = NOT OURS -> the discovering reader DELETES the record and proceeds (self-heal, not error); successful bind and spawn-reap clear custody atomically with their own outcome. Creation time from the process SNAPSHOT, never a retained handle (KH 7.50); platform without a snapshot -> unproven -> defer one tick, never a manufactured verdict. Gate: impl — the paired custody record + both livehost consumers on the pair test; unit — pair mismatch reads NOT-OURS + record deleted, pair match reads OURS, absent-snapshot defers; int — B DOC: 1. Process custody is an identity, never a bare PID: === PAIR 63 === A ID: REQ-MIGRATE-1 B ID: REQ-NET-1 A TITLE: Auto-detect and migrate a legacy claude_skill_owl install A DOC: (empty) B TITLE: WAN messaging first-class, behind default-on net feature flag B DOC: (empty) === PAIR 64 === A ID: REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT B ID: REQ-HAZARD-TEARDOWN-DEADEND A TITLE: A clean `spt rc` attach to a LIVE spt-hosted (`endpoint run`) harness must DELIVER the harness's PTY output. KEYSTONE — 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) — 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 — 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 → the same dispatch_spawn (broker.rs:706/835) which s A DOC: (empty) B TITLE: TEARDOWN-AUTHORITY W1 HAZARD (ADR-0045 decision 8; hertz RCA 2 — hit doyle's OWN live production endpoint, recoverable only by out-of-band scoped kill): `endpoint stop` followed by `endpoint run --id ` MUST succeed (spawn or resume) and must NEVER answer ENDPOINT_CREATE_CONFLICT about a session that stop claimed to end. TRAP SHAPE (two individually-CORRECT behaviors composing into a lifecycle DEAD END with no in-band exit): (1) stop leaves the broker session alive (REQ-ENDPOINT-TEARDOWN-AUTHORITY), and (2) `endpoint run` correctly REFUSES ENDPOINT_CREATE_CONFLICT rather than silently reattaching (v0.37.0 no-silent-reattach rule, deliberately chosen). The survivor is therefore simultaneously what stop claims to have killed AND what run refuses to work around; with a wedged harness every in-band verb is exhausted (stop lies, run refuses, rc replays a dead PTY, rc --take controls a process that never answers). Neither behavior is individually wrong — the COMPOSITION is the hazard, so the regression must assert the composition, not either verb alone. This single assertion is the whole user-visible point of W1. Gate: impl — covered by the shared primitive; int — start a real broke B DOC: 7.48 At most one input-capable controller lease per PTY session — takeover revokes atomically and loudly, input is fenced to the active lease, node identity is never a lease `[REQ-HAZARD-CONTROLLER-LEASE]`: ### 7.49 A teardown verb never stamps a terminal or resting state it has not caused — and two individually-correct verbs must not compose into a lifecycle dead end `[REQ-HAZARD-TEARDOWN-DEADEND]` - **Failure (paid-for, two hertz field RCAs 2026-07-19, both doyle code-verifie === PAIR 65 === A ID: REQ-HAZARD-STOP-PATH-PSYCHE-ORPHAN-REAP B ID: REQ-SHELL-5 A TITLE: Endpoint-stop and brain-death reconcile MUST reap a brain-less perch's orphan detached Psyche via the cmdline-scoped guard (`psyche_orphan_should_reap`) — the handle-reap (`LiveSet::stop_host`, REQ-HAZARD-UNHOST-PSYCHE-REAP) CANNOT, because the owning brain is gone (its `psyche_child` handle died with it), and the brain-start scoped-reap (REQ-HAZARD-BRAIN-RESTART-PSYCHE-DUP) never fires for a perch being STOPPED rather than re-hosted. So the live-host calls the scoped reap after `stop_host` at the reconcile stop-side AND in `confirm_residency_or_unhost`. Preserves fail-safe-decline (pid-alive AND exe-basename==psyche-program AND cmdline contains `-psyche`; any unreadable signal DECLINES — a missed dup is bounded, a wrong-kill is catastrophic). This is the orphan-leak half of the perri F-010xF-015 field bug (the unsupervised install-dir Psyche that locked an update); the other half is the psyche own-copy (ADR-0025 amendment). (v0.13.2 W3 (a)) A DOC: Conformance checklist (condensed): | # | Invariant | spt-core surface | |---|---|---| | 1.1 | Grace wait precedes INIT_SIGNOFF | daemon teardown | | 1.4/4.4 | Deferred rows excluded from event-stream drain | daemon spool drain | | 2.1/5.1 | Stable PID/broker-handle over ephemeral PID | liveness detection | | 2.3 | Handoff argv/IPC version-tolerant (newer brain ↔ older broker) | broker↔brain IPC, self-update | | 2.4 | gen_start = now() on cold-start + handoff | per-instance generation | | 2.6 | A B TITLE: Shell ownership is owner-type-agnostic: any non-Shell endpoint type may own/spawn/drive/command/link a shell (Gateway the named first) — control-exclusivity keys on the owner endpoint_id, NEVER on the owner's endpoint type. No ownership path (mint, launch, owner-from-link, cmd, drive, tunnel, sleep/wake, owner-shutdown) inspects the owner's type (CONTEXT:264, ratified 2026-06-11 Gateway grill). B DOC: Shell model (detailed): **Owner-linked, exclusive.** Spawned by an **owning endpoint**; both linked; **only that owner `endpoint_id` may control it**. Agent endpoints are the common owner, but ownership is NOT agent-exclusive — any non-Shell endpoint type may own shells (ratified 2026-06-11, the Gateway grill: e.g. a **Gateway** owns the driven surfaces it steers; a future **Resource* === PAIR 66 === A ID: REQ-HAZARD-SOFT-CLEANUP B ID: REQ-HAZARD-EPHEMERAL-CLEANUP A TITLE: Soft-cleanup preserves state, removes only the ready marker (6.2) A DOC: (empty) B TITLE: Ephemeral perch cleanup on every ring exit path (3.1) B DOC: (empty) === PAIR 67 === A ID: REQ-MESH-1 B ID: REQ-TERM-6 A TITLE: Membership proof (seed-proof): symmetric current-epoch seed-knowledge replaces is_trusted at EVERY inbound gate (registry apply, WAN receive, sync, notif, connection accept). MK = HKDF(seed, domain ‖ subnet_id ‖ seed_epoch); mutual channel-bound challenge-response at connect (transcript binds both handshake-proven node pubkeys, both nonces, subnet_id, seed_epoch, role); verified once per connection, cached on the broker ConnEntry, kept warm via QUIC keep-alive so re-proof is restart/partition/rotation-only. Exact-epoch match (re-seed is the sole N-1 exception). SECURITY INVARIANTS: channel-bound (no cross-connection replay), mutual, accepts a member it never paired (the mesh property). A DOC: (empty) B TITLE: Thread-spanning digest across session boundaries: a per-endpoint session ledger (`/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. B DOC: (empty) === PAIR 68 === A ID: REQ-ARCH-3 B ID: REQ-HAZARD-HANDOFF-ARGV-COMPAT A TITLE: Wire-protocol version independent of crate semver, N-1 compat window A DOC: (empty) B TITLE: Broker/brain IPC + handoff argv version-tolerant (2.3) B DOC: (empty) === PAIR 69 === A ID: REQ-TERM-1 B ID: REQ-TERM-5 A TITLE: Process-supervisor terminal wrapper hosting broker PTYs A DOC: (empty) B TITLE: Adapter-declared digest extractor seam: a `[digest]` manifest section declaring an imperative extractor (native harness log -> the {role,text,tool,ts} contract; defaults to the [history] source files with an own-source escape hatch), `api digest-entry` push fallback, register-time validation of the section, adapter-declared presentation defaults (window depth, arg-truncation, sprint-collapse) that any consumer may override, and a `spt adapter digest-proof` author tool plus runtime skip-diagnostics (no silent drop). Reverses M9's no-manifest-seam stance; no declarative DSL. B DOC: Session digest — the published digest-record contract (ADR-0019): 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 --id --create|--resume --start|--attach|--view`), untouched — a picker selection bakes exactly that path. **Layer 1** picks the kind (*Create new* | === PAIR 72 === A ID: REQ-DOC-ECHO-COMMUNE-CONTRACT B ID: REQ-SHELL-3 A TITLE: W6 (LIFECYCLE-TRUTH, docs — this gap cost a full outage night, priority slot): publish the [session.echo_commune] I/O contract on the docs-site: key catalog core fills; core does NOT stdin-feed [history] (field-proven); self-locate guidance incl. CLAUDE_CONFIG_DIR / read_env; drop-file protocol (single-writer, ingest-deletes, resolver semantics from W1); stdout ingestion expectations. Public docs use VERSION numbers, never wave codes; docs-publish drift gate applies. A DOC: Echo-commune — the I/O contract: B 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) — 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 — 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). B DOC: (empty) === PAIR 73 === A ID: REQ-HAZARD-DRIVEN-BY-SELFHEAL B ID: REQ-RESUME-UNBOUND-STAMP A TITLE: An spt-hosted endpoint's ONLINE+CONTROLLED state (`driven_by`) must CLEAR even when the detach IPC is lost — do NOT rely on the detach signal (same lesson as REQ-HAZARD-HOSTED-LIVENESS-RECONCILE B2): the reconcile loop clears `driven_by` when the endpoint has no live controller/session. Today a wedged or lost pump never delivers the detach, so the endpoint stays latched CONTROLLED forever. Composes with W1 (the wedge no longer blocks the detach) and rides the same pull-primary reconcile substrate as B2. (v0.13.0) A DOC: (empty) B TITLE: RC-RENDER-TRUTH W1 (ADR-0042 decision 2, operator-spotted second root seam): resume launch transitions an existing offline perch to UNBOUND before/with the broker spawn — UNBOUND semantics are fresh/resume-invariant (broker session exists + harness not bound = UNBOUND); generation/session-safe ROLLBACK to offline on spawn failure or session death; bind owns UNBOUND->ONLINE. Supersedes the rc.rs 'resume gets no UNBOUND stamp — accepted' boundary note (the pre-bind window can be PERMANENT: stuck native resume, SessionStart never fires — field-proven; truthful UNBOUND is the operator-recovery surface that let `spt rc` reach the wedged TUI). Writer-truth complement to REQ-RC-HONEST-SESSION-AUTHORITY — both land, neither substitutes. Gate: impl — UNBOUND stamp at resume spawn + rollback + bind transition; unit — stamp fires on existing-offline perch resume, rollback on spawn-fail restores offline, generation guard refuses a stale rollback over a newer bind; int — resumed-but-never-bound endpoint reads UNBOUND (not offline) and `spt rc` attaches to its live session; doc — ADR-0042. B DOC: Decisions: 1. **`spt rc` consults the honest-session authority before the offline fast-fail.** Normal `spt rc ` runs the same bounded `SessionProbe::has_live_session_honest` gate `endpoint run` uses (ADR-0041 single liveness authority). An honest session exists → attach via the session-confirmed path regardless of persisted status. No honest session → the existing offline refusal stands. A claimed session with a dead client tree → refusal/reap, never attach. Reuse `SessionProbe`; no === PAIR 74 === A ID: REQ-UPDATE-APPLY-RESTART-NOTICE B ID: REQ-UPDATE-RESTART-SAFE-SWAP A TITLE: `spt update apply` prints a LOUD restart-required notice whenever the surviving broker will keep running the pre-apply image (which, until broker-restart choreography exists, is ALWAYS on a successful apply). Public wording, no internal CODE:RESULT markers (composes with REQ-ADAPTER-UPDATE-MESSAGE / the update-apply-confident-message rule) — name the user-visible CONSEQUENCE ('daemon-coordinated features run the previous version until the daemon restarts'), not the broker/brain internals. Composes with REQ-UPDATE-RUNNING-IMAGE-SURFACE (the notice tells the user what the version-surface will then show, and how to clear it). (F-025) A DOC: (empty) B TITLE: THE-FORKENING W4 (operator-grilled 2026-07-14; RETIRES findings-backlog seed #12 REQ-UPDATE-ONE-STEP-SAFE-SWAP): `spt update --restart` = the one-step ergonomic path to the SAFE full-cycle swap — fetch -> `update adapters` -> `apply --finish` LAST (lethal-leg-last, ruled: apply --finish restarts the whole daemon incl. broker/PTYs, so it must be the final act — everything completes from ANY invoking context including an spt-hosted session whose PTY dies at that step; accepted cost: a finish FAILURE leaves updated adapters on old-activated core briefly — loud + operator-attended by nature of the flag). Composes with `-c/--core-only` (skip adapters leg). The 0.28.0 wedge lesson closes: the ergonomic one-step no longer picks the riskier path by default for operators who want the full cycle. Gate: unit — flag sequencing incl. lethal-leg-last ordering + -c compose; doc — self-update docs present --restart as the full-cycle form and name the finish-restart consequence. Kin REQ-UPDATE-DEFAULT-COMPOSITE, REQ-UPDATE-FINISH-COMMUNE-FLUSH (deferred commune-flush rides the same finish path when built), seed #12 (retired by this). B DOC: Self-update: **update composite (`spt update`)** — 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 [[,…]]` is the adapters leg alone (alias over `spt adapter update`). The composite's invoker always survives, because a routine apply cycles only the **brain** — the *restart-required* message on broker-side releases is a notice, not a === PAIR 75 === A ID: REQ-HAZARD-UPDATE-ROLLBACK B ID: REQ-NOTIF-COALESCE A TITLE: Self-update rejects version rollback; metadata expiry + adapter content signing (codex #5) A DOC: (empty) B TITLE: Coalesce-key supersession: producer-stamped key REQUIRED in namespaced form : (e.g. spt-core:update-staged) — the produce front door rejects un-namespaced keys; producing a new row with the same (scope-target, kind, key) auto-dismisses the prior rows, latest-wins; supersession expresses through the existing dismissed one-way latch (semilattice unchanged) B DOC: 2. Supersession: a required-namespaced coalesce key, latest-wins: === PAIR 76 === A ID: REQ-RUN-PICKER B ID: REQ-WORKER-PICKER-EXCLUDED A 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) → enter a charset-validated id → start. Pick-existing: category select (left/right) over [ | Local node | Subnet], endpoints grouped + alphabetically sorted per category, a status square per endpoint (online green ■ / offline gray ▢ — 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 · best-effort project history newest→oldest from the contextstore p- branches, empty-if-none · `spt endpoint description`). Confirm layer offers status-dependent options — Attach/Start/View (rc pump / cmd_endpoint_run) · Instantiate-locally (remote) · Change-harness-adapter (offline) · Fork (cmd_fork) · A DOC: Shell sleep/wake (offline ↔ 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 --id --create|--resume --start|--attach|--view`), untouched — a picker selection bakes exactly that path. **Layer 1** picks the kind (*Create new* | B TITLE: V-2 (WORKER-TRUTH triage, operator rider): non-drivable endpoint classes never render as `spt endpoint run` picker rows — a worker perch cannot be driven, instantiated, or controlled; offering it is a lie the picker then fails on. Filter endpoint_type worker (and the psyche class if it ever surfaces — same non-drivable family) at every picker source leg, extend-not-multiply for future non-drivable classes. B DOC: (empty) === PAIR 77 === A ID: REQ-HAZARD-VIEWER-ISOLATION B ID: REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE A TITLE: A slow / dead / hostile VIEWER must NEVER stall the controller, the PTY child, or the session drain thread. The broker drain fans output to the controller on the authoritative blocking bounded path (advances delivered_through) but to each viewer via a bounded per-viewer channel with a dedicated writer thread; the drain `try_send`s under the log lock and a viewer whose bounded queue OVERFLOWS (can't keep up) is EVICTED (queue dropped, writer thread ends, removed from the viewers map) — the drain thread NEVER touches a viewer socket, so no viewer write can backpressure or block it. A soft viewer cap bounds the thread count. Viewer eviction never perturbs the controller stream, the delivered_through cursor, or the child. A DOC: 7.6 Pump brain-IPC reads must be deadline-bounded (a blocked read wedges the whole pump) `[REQ-HAZARD-PUMP-IPC-DEADLINE]`: ### 7.7 A slow/dead/hostile remote VIEWER must never stall the controller, child, or drain `[REQ-HAZARD-VIEWER-ISOLATION]` - **Failure:** the W2.5 controller/viewer model lets ANY number of read-only `--view` attachers ride one session's broker `OutputLog`. The single drain thread fans each output chunk to every attacher. If a viewer's socket is fanned out with a ** B TITLE: REDISPATCH-STALL W1 (ADR-0038 Amendment, fixes 2+3+4): StreamLog subscriber write discipline — (a) replay and live fan-out HALT at the first failed subscriber write and the failed subscriber is REMOVED, at ALL sites (attach replay, append, finish; PresenceLog mirrors ride the same change — today let _ = sub.write() discards errors and iteration continues, nethost.rs:322-346); (b) subscriber I/O moves OFF the StreamLog mutex: bounded per-subscriber writer queue, enqueue-under-lock / I/O-outside, overflow = detach + resume-from-cursor (the existing gapless-resume contract), never a producer stall; (c) attach worker completion + forwarding BOUNDED and cancelable — a poisoned brain subscriber cancels its paired serve/wire worker, no orphan forwarding legs (COORDINATE with DAEMON-LIFECYCLE C2 leases at build — same neighborhood, build once). Gate: impl — halt+remove all-sites + writer queue + cancelable pairing; unit — halt-at-first-failure removes the subscriber at each site + queue overflow detaches with cursor intact + producer append never blocks past enqueue; int — T1 (poisoned-replay halt) + T3 (producer latency flat under a wedged subscriber) + T4 (paired worker canceled on poiso B DOC: Consequences: ## Amendment — REDISPATCH-STALL (2026-07-16) === PAIR 78 === A ID: REQ-DAEMON-1 B ID: REQ-EP-1 A TITLE: One per-machine spt-daemon owning all per-machine state A DOC: (empty) B TITLE: Day-one endpoint types; open type system B DOC: (empty) === PAIR 79 === A ID: REQ-TRANSLATE-COMMIT-MISS-TOLERANCE B ID: REQ-UPDATE-ADAPTERS-VERB A TITLE: C-1 (F029, B6 ROOT — 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 → 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 — 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 — preserve the binary; the watchdog's job is ANTI-STALL (release the operator floor), not execution-verification. See addendum C-1 + ADR-0022 amendment. A DOC: Amendment (2026-07-03, F029 C-1) — a commit-miss is NOT a fault; real faults respawn: B TITLE: THE-FORKENING W4 (operator-grilled 2026-07-14): `spt update adapters [[,...]]` = thin ALIAS over the existing `spt adapter update` engine (cli.rs:748 gh_release avenue; the old verb STAYS — 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 — name validation, list parsing, isolation + exit-code aggregation, local-path skip; doc — reference regen (drift-gated). Kin REQ-UPDATE-DEFAULT-COMPOSITE (the caller), REQ-ADAPTER-UPDATE-MESSAGE (per-adapter apply notices ride the summary). B DOC: Self-update: **update composite (`spt update`)** — 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 [[,…]]` is the adapters leg alone (alias over `spt adapter update`). The composite's invoker always survives, because a routine apply cycles only the **brain** — the *restart-required* message on broker-side releases is a notice, not a === PAIR 80 === A ID: REQ-ENDPOINT-LIST-NODE-GROUPED B ID: REQ-SUBNET-COUNT-ROUTABLE A TITLE: spt endpoint list is node-grouped over unique INSTANCES, not subnet-grouped over duplicated rows: one row per (id,node) instance — subnet duplication collapsed (ADR-0006 §1: subnet is never an identity axis), freshest-epoch wins a cross-subnet status disagreement (the resolve_among twin rule) — grouped under 'This node: