==================================================================================================== PAIR 1: REQ-ENDPOINT-PURGE vs REQ-HAZARD-BIND-REST-STATE-CARRY ==================================================================================================== [A] REQ-ENDPOINT-PURGE 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 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 o... [B] REQ-HAZARD-BIND-REST-STATE-CARRY Title: GATEWAY-LIVENESS DEFECT A (flynn field bug 2026-07-09, confirmed independent): a re-bind MUST preserve the daemon-owned resting intent (rest_state, D9-2/REQ-INST-3) — the same carry-forward discipline establish_perch already applies to cwd/controllable/adapter/read_env. ROOT: establish_perch's record build (crates/spt/src/api/startup.rs, the build closure) constructs a fresh InfoJson via InfoJson::new (defaults rest_state None) and carries cwd/controllable/read_env forward from prior but NOT rest_state -> a re-bind WIPES the wake intent (flynn tick11 rest_state:active vanish). FIX: carry prior.rest_state (and its paired dormant_since_ms anchor, present iff dormant) forward on re-bind, like the sibling fields. Gate: a re-bind over a prior record with rest_state set preserves it (unit — the build closure carries rest_state + dormant_since_ms). KNOWN-HAZARDS entry on landing. Kin REQ-HAZARD-BIND-CWD-UNSET / REQ-PICKER-1 + REQ-INST-3. CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 2: REQ-ADAPTER-UPDATE-MESSAGE vs REQ-MANIFEST-6 ==================================================================================================== [A] REQ-ADAPTER-UPDATE-MESSAGE Title: An adapter manifest may declare `[update].message` — a plain (multi-line) human notice surfaced to stdout, markdown-rendered (the v0.13.0 helpfmt prose path), ONLY when `spt adapter update` actually APPLIES an update (version changed), not on a no-op. Read from the newly-installed manifest; avenue-agnostic (gh_release/delegated/file_pull). No `{key}` substitution. Use: an adapter telling the operator a post-update action, e.g. spt-claude-code's "run `/reload-plugins` in any ongoing sessions". (v0.13.2) Doc: Runtime model: **adapter packaging & live update** (v0.13.2; ADR-0024, ADR-0025): A `.spt` may be **multi-platform**: shared `manifest.toml` + `strings/` at the root, role binaries under per-target-triple subdirectories (`x86_64-pc-windows-msvc/`, …); install/update extracts the shared ro... [B] REQ-MANIFEST-6 Title: Cross-adapter fallback target addressing (M12-W3-T3.2): a cross-adapter fallback target is addressed as `:` (not just a bare adapter_name), resolved through the one composite-addressing resolver (registry::resolve_option) at every adapter-option read site so a fallback may select a shipped/local profile (e.g. a `ccs` profile). CONTEXT.md §cross-adapter-fallback reconciled ("ccs is a profile; cross-adapter fallback may target :"). Contract-only this milestone: the node-wide fallback SETTING + its rate-limit invocation are deferred to the consuming milestone (the runtime path does not exist yet); this REQ guarantees the ADDRESSING resolves. Doc: Manifest seams (outbound contract, detailed): - **Command templates are opaque.** spt-core never parses out a model/tool/flag — the adapter writes the whole command line; spt-core fills substitution keys and runs it. - **A command template's program token resolves against the adapter install dir b... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 3: REQ-EP-1 vs REQ-EP-2 ==================================================================================================== [A] REQ-EP-1 Title: Day-one endpoint types; open type system [B] REQ-EP-2 Title: Agent endpoints vs Shells distinction in the type model CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 4: REQ-RC-HONEST-SESSION-AUTHORITY vs REQ-SEAM-RESUME ==================================================================================================== [A] REQ-RC-HONEST-SESSION-AUTHORITY Title: RC-RENDER-TRUTH W1 (ADR-0042 decision 1, hertz perri-contradiction RCA): normal `spt rc` consults the ADR-0041 single honest-session authority BEFORE the persisted-offline fast-fail — run the bounded SessionProbe::has_live_session_honest gate; honest session exists means attach via run_attach_session_confirmed regardless of persisted status; no honest session means the existing offline refusal stands; claimed session with dead client tree means refusal/reap, NEVER attach. Reuse SessionProbe — no new liveness heuristic. Kills the authority split where rc refused ('offline — nothing to attach to') while endpoint run --resume reattached to the same live session. Gate: impl — the pre-fast-fail probe + session-confirmed routing; unit — probe-true routes to session-confirmed attach, probe-false keeps the refusal, dead-tree claim refuses; int — the 3-row regression matrix: offline persisted row + honest live broker session => rc attaches; offline + no session => existing refusal; zombie/dead client tree => refusal, never attach; doc — ADR-0042. 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 sessio... [B] REQ-SEAM-RESUME Title: resume-session seam (fresh-with-preload / continue-existing) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 5: REQ-HAZARD-VIEWER-CLOSE-DETACH vs REQ-HAZARD-VIEWER-RING-ROLL-SNAP ==================================================================================================== [A] REQ-HAZARD-VIEWER-CLOSE-DETACH Title: A VIEW is independent from the endpoint: closing the tab/window where `spt endpoint run` was invoked must detach ONLY the `spt rc` attach pump — the daemon-hosted harness keeps running and stays re-attachable via `spt rc `. ROOT (Windows, v0.12.0 real-harness defect): the daemon never breaks away from the launching terminal's Job Object. Windows Terminal / VS Code place the launched shell AND every descendant into a Job Object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; closing the tab drops the terminal's last job handle → the OS terminates every process still in that job. A child escapes only with CREATE_BREAKAWAY_FROM_JOB — used NOWHERE in the tree. Both daemon spawn paths (daemon.rs:707 detached_no_inherit = DETACHED_PROCESS|CREATE_NEW_PROCESS_GROUP|CREATE_NO_WINDOW; deelevate.rs:519 elevated = CREATE_NEW_CONSOLE|...) drop the CONSOLE but NOT job membership, so the daemon's freshly broker-spawned ConPTY harness subtree is reaped on tab-close. The ConPTY/pseudoconsole isolation itself is CORRECT (portable-pty builds the pseudoconsole in the daemon; no console signal / handle leak) — the leaking lifetime binding is the Job Object, not the console. FIX: add CREATE_BREAKAWAY_FROM_ Doc: Terminal wrapper: **A view is independent from the endpoint** (invariant): An spt-hosted endpoint runs in a **daemon-owned PTY, decoupled from whatever terminal launched it**. Closing the tab/window where `spt endpoint run` was invoked detaches only the `spt rc` attach pump — the endpoint... [B] REQ-HAZARD-VIEWER-RING-ROLL-SNAP Title: A read-only rc --view VIEWER whose serving brain falls behind the live ring under a hard flood and receives a FORWARD Output seq gap (the ring rolled frames out between reads, BEFORE any channel-overflow eviction → NO KIND_VIEWER_EVICTED marker) must SNAP TO LIVE (accept-and-advance via dedup-below + snap-above), NOT fatal with output gap (brain.rs:624/628 legacy reject-gap). ROOT (v0.13.0 forkpty, post-b4+skip-to-live): serve_attach subscribes a viewer via brain.attach_as(Viewer) leaving session_cursors EMPTY → the viewer serve-brain uses the LEGACY reject-gap → a PRE-eviction ring-roll forward-gap FATALS read_event → serve_attach returns → forwarding stops → attach_received_pty_output=FALSE (a_journaled / p0_paste / attach.rs:1071 wedged_viewer, Linux forkpty; Windows ConPTY floods slower → MASKED false-green). DISTINCT from REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT (the POST-eviction re-subscribe-from-floor): this is PRE-eviction gap-tolerance while STILL subscribed. VIEWER-only → B2-SAFE (a viewer never advances delivered_through / is not authoritative); the CONTROLLER keeps strict reject-gap (exactly-once resume). FIX: arm snap-above at initial viewer attach (attach_as_viewer_snap = at Doc: Shell sleep/wake (offline ↔ 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 CONTROLLE... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 6: REQ-MANIFEST-7 vs REQ-PICKER-SHORTCUT-LABEL-FILENAME ==================================================================================================== [A] REQ-MANIFEST-7 Title: Adapter-declared shortcut basename (M12-W2 follow-on): an optional `[adapter] shortcut_basename` manifest field names the basename the `spt endpoint run` picker bakes into the generated `-` launcher shortcut (REQ-RUN-SHORTCUT). Absent ⇒ the harness-agnostic default `spt` (→ `spt-`); an adapter sets it to brand its shortcuts (claude-spt → `cc` → `cc-`), so the Claude-Code-ness lives in the PUBLISHED adapter manifest, never hardcoded in spt-core. The picker reads it from the RESOLVED manifest of the selected adapter (registry::resolve_option), falling back to `spt` when absent/empty/unresolvable. Additive + N-1-safe (serde-default Option, omitted from serialization when absent; old manifests parse clean); manifest.schema.json regenerated from the derive (ADR-0001, CI drift-gated). Documented in docs/MANIFEST.md `[adapter]` section + the claude-spt worked example — the adapter-author contract perri builds spt-claude-code against. Doc: `[adapter]` — header: `shortcut_basename` *(optional, default `spt`)* — the basename the `spt endpoint run` picker's `s` keybind bakes into the generated `-` launcher shortcut at the project root (REQ-MANIFEST-7). Absent ⇒ the harness-agnostic `spt` (→ `spt-`); an ... [B] REQ-PICKER-SHORTCUT-LABEL-FILENAME Title: B-4 (F029, operator): the confirm-panel shortcut option label is a static `New/Update spt- shortcut (s)` placeholder — it should name the REAL file it writes: `Set shortcut here --> /` where is the EXACT on-disk filename (incl. extension). The name must be produced by the SAME function that names the file in shortcut creation (picker/shortcut.rs shortcut_filename over the manifest-resolved basename) so label and writer can NEVER drift. Anchor picker/view.rs confirm_option_label. See triage B-4. CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 7: REQ-HAZARD-DETACHED-PIPE-INHERIT vs REQ-SHELL-CLI-SPAWN-JOB-EXPOSURE ==================================================================================================== [A] REQ-HAZARD-DETACHED-PIPE-INHERIT Title: Windows detached long-lived children must not inherit a captured caller's pipe: every detach-spawn of an immortal child (daemon, shell binary) runs bInheritHandles=FALSE, or a caller capturing output anywhere up the process chain hangs forever on a pipe that never EOFs — std-handle flag stripping is NOT sufficient (grandparent strays still flow) (5.6) Doc: 5.5 ConPTY withholds output until DSR is answered `[REQ-HAZARD-CONPTY-DSR]`: ### 5.6 Windows detached children inherit a captured caller's pipe `[REQ-HAZARD-DETACHED-PIPE-INHERIT]` - **Failure:** a caller captures an `spt` invocation's output through a pipe (`Command::output()`, a harness h... [B] REQ-SHELL-CLI-SPAWN-JOB-EXPOSURE Title: HARDENING SEED (inactive — no defect claimed): a CLI-side `spt shell spawn` rises inside the launching terminal's Job Object when that job denies breakaway. Launch-path census (2026-07-25): `spt shell spawn` calls `shellhost::launch_shell` IN the CLI process, so the shell binary is spawned by a terminal-resident process and is subject to whatever job the terminal wrapped the CLI in; daemon-side (re)launches (`shellwake` relaunch-on-wake, linkhost relink) spawn from the daemon, which is job-neutral once its own cold-start ladder (WMI → schtasks → breakaway → in-job; REQ-HAZARD-VIEWER-CLOSE-DETACH) escaped. The shared spawn primitive (`daemon::detached_no_inherit`) requests CREATE_BREAKAWAY_FROM_JOB best-effort: a job without JOB_OBJECT_LIMIT_BREAKAWAY_OK denies it (ERROR_ACCESS_DENIED) and the DELIBERATE fallback spawns IN-JOB with the loud diagnostic 'DETACH_BREAKAWAY_DENIED: launching Job Object forbids breakaway; spawned IN-JOB (may be reaped if the launching terminal closes)' — chosen so a spawn that once worked never regresses to failure. spt CANNOT grant itself breakaway: BREAKAWAY_OK is a limit only the job's CREATOR (the terminal) sets on the job; a member process has no say CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 8: REQ-INST-4 vs REQ-PRES-1 ==================================================================================================== [A] REQ-INST-4 Title: active to dormant/suspended fires a transition echo commune [B] REQ-PRES-1 Title: Presence resolution: the presence datum (last_active_node, last_active_endpoint, ts) gossiped subnet-wide via the agent-interaction heartbeat (rides registry distribution, visibility-gated) + one first-class most-recently-active resolution API consumed by notif first-fire, update-consent delivery, consent escalation, and shell wake resolution (M5 scope decision 1: resolution only — the PresenceChannel endpoint stays deferred) Doc: Deferred Features: | Feature | Cut from | Why deferred | Trigger to revisit | |---|---|---|---| | Scrollback on-disk spillover | terminal wrapper v1 | In-memory ring covers the common case; spillover adds a persistence/rotation story | First long-running session that overflows the ring usefully, or ... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 9: REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT vs REQ-HAZARD-REGISTRY-DIR-CREATE ==================================================================================================== [A] REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT Title: A clean `spt rc` attach to a LIVE spt-hosted (`endpoint run`) harness must DELIVER the harness's PTY output. KEYSTONE — 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 [B] REQ-HAZARD-REGISTRY-DIR-CREATE Title: SQLite store opens create their parent dir themselves — a fresh-home registry op must not SQLITE_CANTOPEN (4.9) Doc: 4.8 Registry merge ordered by epoch, never wall-clock (red-team #8): ### 4.9 SQLite stores must create their parent dir — SQLite won't - **Failure:** `Connection::open` creates the database FILE but never its parent DIRECTORY. On a fresh home (first boot, fresh CI `_work` dir) a registry ... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 10: REQ-DOCS-2 vs REQ-FRONT-1 ==================================================================================================== [A] REQ-DOCS-2 Title: Sub-10-minute runnable killer quickstart per audience Doc: Harness integration checklist: // Quickstart: build an adapter: 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 `ad... [B] REQ-MANIFEST-5 Title: File-backed adapter [strings] (M12-W3-T3.1): a [strings] dot-path value MAY be an inline-table FILE POINTER `key = { file = "rel/path" }` resolved to the file's contents at get-string time, keeping large bodies (skill-instructions, hint text) out of the manifest. A value-position table with a `file` key IS the pointer form (reserved — cannot double as data). Per-adapter aux storage `adapters//strings/`; pointers resolve relative to it with CONTAINMENT (reject `..`/absolute escaping the dir). UPDATE-SAFETY: a LOCAL profile's file-pointers resolve relative to the user-owned local-profile dir (NOT adapter-shipped strings/, which adapter updates overwrite), or the local profile inlines. Validate-at-register (fail-fast on a bad/escaping/missing pointer) + LAZY read at get-string (live file edits reflect, no re-register) + skip-diagnostics on missing-at-read (no hard-crash, mirrors [digest]). Rides the same leaf-replace profile overlay as the rest of [strings]. Doc: Runtime model: **adapter strings** (ratified 2026-06-11, Gateway grill): A `[strings]` manifest section — 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 `ad... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 12: REQ-NOTIF-COALESCE vs REQ-NOTIF-SCOPE ==================================================================================================== [A] REQ-NOTIF-COALESCE 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) Doc: 2. Supersession: a required-namespaced coalesce key, latest-wins: [B] REQ-NOTIF-SCOPE Title: Notif scope is a per-row producer-chosen attribute node|subnet: node-scoped rows (update/consent/rollback kinds) live and die on their node and NEVER enter a replication feed; subnet-scoped rows keep the full ADR-0007 machinery (replication, cross-node dismiss, semilattice join); scope determines the first-fire candidate set (node-scoped targets the most-recently-active endpoint ON that node). New row fields additive serde-defaulted (host_binaries pattern) — old peers parse clean, schema regen drift-gated Doc: 1. Scope is a per-row, producer-chosen attribute: `node` | `subnet`: CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 13: REQ-PSYCHE-NESTED-RESOLUTION vs REQ-RC-CROSS-NODE-ATTACH ==================================================================================================== [A] REQ-PSYCHE-NESTED-RESOLUTION Title: W4 (F030, design §3; F-017 sibling, general not psyche-only): nested {parent}/{nested} ids resolve under the PARENT's home — subnet-less resolution for nested perches (the home-ASSIGNMENT seam, not perch-path which is already subnet-less) — so child-side verbs acting on nested perches need no --subnet the child cannot know. ROOT: home::assign_home returns Ambiguous on a multi-subnet node w/o --subnet; a nested id must instead derive home from its parent perch. Additionally expose a SINGLE {subnet} base_keys fill WHEN KNOWN (own_subnet = home-subnet label, 'local' when unhomed; absent → LOUD missing-key fail, the {node} precedent). NO {home} key (doyle W4 Q1: the endpoint home subnet is ONE concept per CONTEXT.md:640, and 'home:' is already the subnet-name qualifier position CONTEXT.md:652 — a {home} template key invites meaning-drift). Red-first = evidence #3's exact repro: 2-subnet home, nested-id verb, must succeed (was: `spt ready --once` → exit 1 READY_FAIL … pass --subnet). Doc: then it exits — no resident process, no detach.: **`{subnet}` — the endpoint's home-subnet label.** A single `{subnet}` key fills to this endpoint's home-subnet label (`local` when unhomed), supplied whenever it is known so a psyche turn need never resolve a `--subnet` it cannot know; w... [B] REQ-RC-CROSS-NODE-ATTACH Title: Bug #4: spt rc to a remote endpoint fails with 'no live session' though endpoint list shows it Active — rc.rs:1063 resolves only the LOCAL broker session table and always dials loopback, never consulting the registry or dialing the owning node (the cross-node attach transport exists in the broker; only the client leg is missing). Fix: on a local resolve miss, resolve the owning node from the registry (reuse resolve_across_visible), net_dial that node, and run a remote session-resolve + serve_attach round-trip (mirror the wansend resolve-dial-round-trip pattern). Shares the resolve-owning-node primitive with REQ-WAN-SEND-DELIVERY. See docs/NEXT-MILESTONE-BUG-TRIAGE.md #4. Doc: Instances: **Remote-control vs local operation (two distinct modes — not the same as instances):** - **Operate locally:** drive the native instance on *your* machine (its local files, its synced mind). The normal case. - **Remote-control (Shell-like):** attach a control/view surface to an... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 14: REQ-REL-2 vs REQ-UPDATE-APPLY-RESTART-NOTICE ==================================================================================================== [A] REQ-REL-2 Title: Release asset set consumable by the self-updater: platform binaries, SHA256SUMS, SignedRelease metadata, manifest schema, mock-adapter zip; tag-triggered cross-repo pipeline Doc: Release runbook: [B] REQ-UPDATE-APPLY-RESTART-NOTICE Title: `spt update apply` prints a LOUD restart-required notice whenever the surviving broker will keep running the pre-apply image (which, until broker-restart choreography exists, is ALWAYS on a successful apply). Public wording, no internal CODE:RESULT markers (composes with REQ-ADAPTER-UPDATE-MESSAGE / the update-apply-confident-message rule) — 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) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 15: REQ-BRAIN-UPDATE-RESTART-CLEAN-CLOSE vs REQ-CI-POSTJOB-DAEMON-REAP ==================================================================================================== [A] REQ-BRAIN-UPDATE-RESTART-CLEAN-CLOSE Title: SEED (DEFERRED, doyle 2026-07-09 — post-counter-54 root-hardening for UPDATE-WEDGE; mint now, impl a FUTURE milestone): on a PLANNED brain-restart (`BRAIN_UPDATE_RESTART`, the seamless update-apply brain-cycle), the outgoing brain's LOCAL (by:None) controller conns are GRACEFULLY CLEAN-CLOSED as the brain is cycled, instead of hard-killed and left to black-hole. ROOT (field-pinned 2026-07-09, daemon.stderr.log L24277-24303): the update-restart path hard-kills the outgoing brain (`child.kill()`, brainproc.rs:851); its live-agent controller conns then block on dead pipes (never EOF) → the broker reads them WEDGED (broker.rs:2695-2700) → the new candidate's promotion DRAINED gate (`any_local_controller_wedged`, broker.rs:2704) stays true until the W2 stall-evict matures (~15s). REQ-UPDATE-TRIAL-DRAIN-DRIVE (counter-54) makes the candidate DRIVE that reap so it promotes within the 30s window — but at a ~15s wedge-maturity hitch (frozen PTYs during the swap). A CLEAN close makes the conn 'simply absent → drained=false AT ONCE → fast promote' (broker.rs:2699-2700), ELIMINATING the hitch = truly seamless (honors the paradigm the field freeze broke). SUPERSEDES the earlier livehost-reattac [B] REQ-CI-POSTJOB-DAEMON-REAP Title: A CI job REAPS ITS OWN test-spawned daemons at battery end, in-job, and logs a process census at job start AND job end so contamination and reap effectiveness are visible in every run's log. (Load-flake family leg 1, doyle-ratified 2026-07-22 from deployah's third-run analysis.) THE SIGNATURE THIS CLOSES: a DIFFERENT single daemon-spawning test dying per run with a bare exit 1 and NO assertion output — process-level death, not a failed assert — while sibling tests in the same families pass alongside it, on BYTE-IDENTICAL code. Evidence: release PR #56 ran four times over a zero-.rs-delta tree; runs 1/2/3 killed brain_decouple (twice, on a disk-starved box), then adapter_translate, then adapter_digest at 105.9 GB free; box census during runs showed 43 live spt-family processes and 6486 handles against an 1881-test Phase-A full-parallel battery; run 4 went GREEN once disk and leaked session-0 daemons were cleared. WHY IN-JOB IS LOAD-BEARING AND NOT A CONVENIENCE: there are TWO leak populations on hfenduleam. Population A is session-1 (agent/gate-spawned) and is sweepable by path from any shell. Population B is SESSION-0, spawned by the actions.runner.* service — a session-1 shell CAN CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 16: REQ-RC-NEWLINE-PRESENTATION-TRUTH vs REQ-UPDATE-APPLY-RESTART-NOTICE ==================================================================================================== [A] REQ-RC-NEWLINE-PRESENTATION-TRUTH Title: W3 (v0.39.4 field bug 3 ROOT, pinned 2026-07-22): the rc client presents relayed bytes exactly as the emitter addressed them — KNOWN-HAZARDS 7.56. rc's with_vt_output enables ENABLE_PROCESSED_OUTPUT without DISABLE_NEWLINE_AUTO_RETURN (DNAR: zero grep hits tree-wide), so the operator's console translates relayed bare LF into CR+LF — a column reset the emitter never asked for; the /c/config field scraps are capture record t=1784714152501's bare-LF index presented through that mode. Relay byte-clean end to end (why both isolated rigs read NOT-REPRODUCED and the fresh viewer read clean — the synthesized repaint is CUP-absolute). FIX: DNAR joins the with_vt_output composition whenever processed/VT output is enabled; prior-mode capture/restore unchanged; Unix needs nothing (raw mode already clears OPOST/ONLCR). Regression: the W3 captured-bytes rig PAIR (vehicle console without DNAR = field-client model = RED pre-fix; with DNAR = GREEN post-fix) + the minimal LF column-preservation probe through an rc-mode console. Amends REQ-RC-WIN-VT-OUTPUT (#12); kin KH 7.55 + the W3 vehicle-fidelity clause (three console-mode-seam manifestations, one day). Attribution CLOSED when hertz confirms fixe Doc: 7.56 The rc client presents relayed bytes exactly as the emitter addressed them — console output modes are part of presentation truth `[REQ-RC-NEWLINE-PRESENTATION-TRUTH]`: - **Failure (paid-for — v0.39.4 field bug 3, the `/c/config` scraps, pinned 2026-07-22 via the W3 bisect):** rc's ... [B] REQ-UPDATE-APPLY-RESTART-NOTICE Title: `spt update apply` prints a LOUD restart-required notice whenever the surviving broker will keep running the pre-apply image (which, until broker-restart choreography exists, is ALWAYS on a successful apply). Public wording, no internal CODE:RESULT markers (composes with REQ-ADAPTER-UPDATE-MESSAGE / the update-apply-confident-message rule) — 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) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 17: REQ-HAZARD-ENVELOPE-CR-LINESAFE vs REQ-MSG-ENVELOPE ==================================================================================================== [A] REQ-HAZARD-ENVELOPE-CR-LINESAFE Title: Envelope CR-linesafety (4.1): the line-framed EVENT codec must neutralize raw carriage returns — `event_body_escape` folds CRLF/lone-CR to the codec's representable linebreak (`\n`→`
`) BEFORE framing, so a body carrying `\r` (Windows `echo`/CRLF text crossing nodes) cannot survive into the single-line envelope and trigger a receiver terminal CR→col0 overwrite that corrupts the frame. Robustness on unrepresentable input, NOT a wire-format change (decoder untouched, amp-last invariant held). Belt-and-suspenders: `spt send`/`ring` also trim stdin (parity with `notify`). Doc: Body and attribute encoding: **Carriage returns are unrepresentable.** The encoder normalizes `\r\n` and lone `\r` to `\n` *before* the `
` encoding, so no frame ever carries a raw CR and a decoder always receives `\n` newlines. Do not expect `\r` to round-trip — content that needs CRs... [B] REQ-MSG-ENVELOPE Title: The body envelope (spt-proto::event, the ADR-0001 grammar) is the SOLE canonical arriving-message format at EVERY harness arriving-message surface on an AGENT perch — api listen AND api poll/worker-poll, byte-identical (reverses REQ-MSG-4's 'hook drains keep the raw frame by contract'). SCOPE CARVE-OUT: the shell-command relay (api poll --link, cmd_poll_shell) is a distinct internal transport carrying RAW MAC'd stamped frames the shell child consumes verbatim — NOT an arriving-message surface, deliberately EXEMPT from composition (notify_shell_e2e guards this boundary). __REPLY_TO__ — mis-elevated during the clean-room port to a fake ADR-0001 'stable wire format' (spt-msg/wire.rs, lib.rs) — is REMOVED entirely (spool format_row, the spt-msg TCP frame, emit parse_frame); (from, body) carried structurally, composed once at the delivery boundary. No legacy sister-interop (spt-core never required it). Reply-correlation rebinds onto the structural from / attribute (ADR-0009 access-gate + ADR-0012 Psyche/spt-live reply-target). Self-delimiting by construction → finding F-002 (non-self-delimiting multi-message poll Doc: Decision: CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 18: REQ-HAZARD-EPHEMERAL-CLEANUP vs REQ-HAZARD-ROSTER-GHOST ==================================================================================================== [A] REQ-HAZARD-EPHEMERAL-CLEANUP Title: Ephemeral perch cleanup on every ring exit path (3.1) [B] REQ-HAZARD-ROSTER-GHOST Title: A LOCAL subnet roster entry whose backing perch is erased does NOT keep advertising Active (no phantom perch-less endpoint). `api session-end --erase` removes the perch (owlery dir gone) but the subnet roster (identity/registry/.json) keeps the endpoint's instance row ACTIVE with no backing perch; `endpoint stop` says 'address unregistered' yet the line persists; no CLI verb forgets a roster entry, and a hand-edit is re-added by the single-writer daemon advertiser. FIX: daemon-side self-heal — the advertiser DROPS/forgets a LOCAL roster entry whose backing perch no longer exists (stops advertising it Active), and/or a `forget`/evict verb; verify whether the epoch lease eventually evicts it (slow-self-heal) vs a real leak and scope accordingly. doyle secondary finding (perri). (post-v0.10.0) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 19: REQ-ENDPOINT-STOP-RESOLVES vs REQ-ENDPOINT-TEARDOWN-AUTHORITY ==================================================================================================== [A] REQ-ENDPOINT-STOP-RESOLVES Title: `spt endpoint stop ` REFUSES an id that nothing on the node knows, instead of stamping success on a no-op — an unconditional-success verb is a lying instrument (find: liam via flynn's discriminating repro, mechanism corrected by flynn 2026-07-26 superseding the original shell-half-action framing; shells aren't endpoints and the verb correctly never tried to resolve one — it then answered incorrectly). TODAY (cli.rs `stop_endpoint_core`, read at mint): ready-marker removal is `.is_ok()`-best-effort, `teardown_hosted_session` topology-gates on a `controllable` flag a nonexistent perch cannot have and falls through, `unregister_address` is `let _`, `terminal_normalize` silently skips a recordless perch — so EVERY string returns `Stopped{removed:false}` → `STOPPED: (no ready marker; address unregistered)` exit 0, and the 'address unregistered' clause prints whether or not any address existed to unregister. FIX SHAPE: resolve FIRST — an id with ZERO evidence on this node (no ready marker, no perch record, no registered address, no broker session row) is REFUSED with a non-zero exit and a line naming that nothing by that id exists here; ANY evidence → proceed EXACTLY as today (st [B] REQ-ENDPOINT-TEARDOWN-AUTHORITY Title: TEARDOWN-AUTHORITY W1 (ADR-0045; two hertz field RCAs 2026-07-19, doyle code-verified + ruled): ONE shared topology-aware broker-teardown primitive behind BOTH `endpoint shutdown` and `endpoint stop`. Today both verbs stamp state they never cause: cmd_shutdown (cli.rs:3979) = remove ready marker + cmd_rest(Suspend) — the Suspend edge (resting.rs daemon_rest_event_with_liveness -> apply_event -> cascade_shells_on_edge -> advertise) fires echo + shell cascade + advertise and NEVER touches a session, even though the verb already probed broker-session truth (cli.rs:3797-3804 has_live_session_honest) to force from=alive; cmd_stop (cli.rs:7071) = marker + unregister_address + terminal_normalize + advertise, whose own comment calls it a DEFINITIVE death observation it fabricates. Field: broker retains the whole subtree (adapter -> node -> harness -> nested resumed harness + MCP children; the surviving `spt api listen` is a DESCENDANT, so a direct-child kill misses it). PRIMITIVE (ordered, ADR-0045 decisions 1/6/7/9): resolve broker SessionInfo -> dedicated sid/endpoint-keyed broker kill claiming NO controller (NOT Brain::attach()+kill_session(), brain.rs:622 require_session() = controller Doc: 7.48 At most one input-capable controller lease per PTY session — 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... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 20: REQ-UPDATE-APPLY-RESTART-NOTICE vs REQ-UPDATE-GH-TRANSPORT ==================================================================================================== [A] REQ-UPDATE-APPLY-RESTART-NOTICE Title: `spt update apply` prints a LOUD restart-required notice whenever the surviving broker will keep running the pre-apply image (which, until broker-restart choreography exists, is ALWAYS on a successful apply). Public wording, no internal CODE:RESULT markers (composes with REQ-ADAPTER-UPDATE-MESSAGE / the update-apply-confident-message rule) — 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) [B] REQ-UPDATE-GH-TRANSPORT Title: THE-FORKENING W1 (ADR-0036, operator-ruled 2026-07-14): the release channel is PRIVATE (`BigscreenVR/spt-bs-releases`) and the gh CLI is the mandated carrier — 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 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 authe... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 21: REQ-HAZARD-REGISTRY-CONCURRENT vs REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE ==================================================================================================== [A] REQ-HAZARD-REGISTRY-CONCURRENT Title: Concurrent SQLite openers (registry/spool) must not fail with 'database is locked' (4.7) Doc: 4.6 Addressable-id charset reserves the address delimiters: ### 4.7 Concurrent SQLite openers must not fail with "database is locked" - **Failure:** two endpoints on one machine open the same SQLite store at once (e.g. two `ReadyAgent::start` calls registering simultaneously) and one fails ... [B] REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE 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 Doc: Consequences: ## Amendment — REDISPATCH-STALL (2026-07-16) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 22: REQ-DOC-DELIVERY-VOCAB vs REQ-HAZARD-ID-CHARSET ==================================================================================================== [A] REQ-DOC-DELIVERY-VOCAB Title: W6 (LIFECYCLE-TRUTH, docs — remaining flynn/perri gaps folded): publish the full send-outcome vocabulary (SENT / SENT(WAN) / QUEUED window semantics / DEFERRED / NO_PERCH), digest --json row schema, api poll auth + MAC-stamp prefix, remaining --json shapes checklist (seed #3). Public docs use VERSION numbers, never wave codes; docs-publish drift gate applies. Doc: Semantics: // JSON output shapes: - **Failure:** a bare endpoint id that contains `:` or `@` (or a path separator / whitespace / control char) makes the canonical qualified address `[subnet:]id[@node]` (ADR-0006 /... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 23: REQ-HAZARD-GEN-START-NOW vs REQ-PICKER-UX-V013 ==================================================================================================== [A] REQ-HAZARD-GEN-START-NOW Title: gen_start = now() on cold-start and handoff (2.4) [B] REQ-PICKER-UX-V013 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) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 24: REQ-ENDPOINT-CYCLE-HONEST vs REQ-HAZARD-SHELL-STALE-ONLINE ==================================================================================================== [A] REQ-ENDPOINT-CYCLE-HONEST 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. 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 ... [B] REQ-HAZARD-SHELL-STALE-ONLINE Title: A shell instance's ONLINE-ness is DERIVED (recorded status AND its recorded `shell.pid` not provably dead), never the recorded `status` field alone — an abruptly-dead binary (force-kill, crash, OOM: no link-break, so `close_shell`'s offline flip never runs) must not read online forever. The shell-side twin of REQ-HAZARD-DAEMON-HOSTED-LIVENESS, which gave AGENT perches exactly this resolver and which shells never got. PROVABLY DEAD is the narrow discriminant: `shell.pid` present AND parses non-zero AND `!is_process_alive` — pid absent, unparseable, or 0 (a broker-hosted spawn whose backend exposed no pid records 0) reads ALIVE, the same interim-parity/fail-toward-alive stance `liveness.rs` already holds, so a pid-less backend is NEVER falsely declared dead. Recycled-pid caveat, accepted at mint: a reused pid reads alive, so the heal is missed, never mis-fired — the failure direction is 'stays stale', never 'kills a live instance'. SITE CLASSIFICATION IS PART OF THE REQUIREMENT (authoritative cfg(test)-excluded census at mint = 8 status reads, 3 classes — do NOT blanket-swap the predicate): (a) DERIVED — relink's already-online refusal (the gate that made recovery impossible), the `s Doc: Conformance checklist (condensed): | # | Invariant | spt-core surface | |---|---|---| | 1.1 | Grace wait precedes INIT_SIGNOFF | daemon teardown | | 1.4/4.4 | Deferred rows excluded from event-stream drain | daemon spool drain | | 2.1/5.1 | Stable PID/broker-handle over ephemeral PID | liveness dete... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 25: REQ-INSTALL-1 vs REQ-INSTALL-9 ==================================================================================================== [A] REQ-INSTALL-1 Title: Two install paths (harness-bootstrapped calls into standalone); OS-service registration deferred. HISTORY: originally 'signed one-line script' — the hosted one-liner retired as the PUBLIC install surface at THE-FORKENING W1/W2 (ADR-0036; the canonical bootstrap is gh + the spt install verb, REQ-INSTALL-BOOTSTRAP-VERB); installer/ scripts remain in-repo as the hermetic oneliner_e2e fixture + air-gap/mirror fallback, which is what this REQ's evidence now attests (doyle-ratified 2026-07-14). Doc: Installation: spt-core is per-machine and harness-independent, so it installs *bef... [B] REQ-INSTALL-9 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) 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}... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 26: REQ-LIVE-AGENT-NO-INJECT-DELIVERY vs REQ-READY-AGENT-RESUME ==================================================================================================== [A] REQ-LIVE-AGENT-NO-INJECT-DELIVERY Title: F-033 RE-SCOPED (doyle re-ruling 2026-07-10 after the #82 gate falsified the original premise — the hosting-mode class split is BINDING context): 'state:live_agent has a self-delivery reader' CONFLATED two hosting modes. (A) HARNESS-hosted live agents (api listen path) DO deliver via adapter channels only — and are ALREADY structurally excluded from inject: bind_from_seed stamps controllable=Some(false), no broker PTY exists. (B) SPT-HOSTED/CONTROLLED live agents' inject leg IS their delivery reader (broker PTY + translation binary — doyle's own endpoint is field proof: ENDPOINT_INJECT + IDLE_PARKED_DRAIN alongside hook-poll); the original blanket state:live_agent exclusion broke REQ-MSG-IDLE-EDGE-DRAIN + the v0.14.3 LAW on both CI platforms and was REVERTED (predicate stays controllable-gated). perri's adapter-channels-only model (F-dupmsg-adapter-confirm.md) holds for class (A) only — do not re-seed the conflation. The F-033 DUPLICATE mechanism (hook-poll + idle-inject both delivering one row, operator spool row 156) is closed structurally by REQ-CARRIER-CLAIM-EXCLUSIVE's atomic cross-carrier take. REMAINING LEGS OF THIS REQ: (a) unit — a harness-hosted live agent (controllable S [B] REQ-READY-AGENT-RESUME Title: An offline ReadyAgent shows in `spt endpoint run`'s picker Resume-from-history and resumes correctly — closing the gap that today only LiveAgents do. ROOT: a harness-hosted ready bind (ReadyAgent::start_homed, ready.rs) writes info.json DIRECTLY and never appends the session ledger (unlike the shared establish_perch:250 live path), so a ready agent — though it has a session_id — produces ZERO ledger rows → the picker's offline+local Resume-from-history (which gates on ledger rows) never offers it. FIX (1): ledger the ready bind (ReadyAgent::start_homed → sessions::append Boot, mirroring establish_perch). FIX (2): `spt endpoint run --resume ` honors the adapter MANIFEST's endpoint TYPE — a ReadyAgent manifest (no [session.psyche_init]) resumes as a ready endpoint (poll listener, NO psyche-host); a LiveAgent (with psyche_init) as live. NO new bringup mode + NO picker changes (operator 2026-06-18): `spt endpoint run` is the spt-hosted ENDPOINT bringup for BOTH types, the type IS the adapter-manifest's concern (psyche-host already keys on psyche_init presence) — so (2) likely already holds; VERIFY at code, build only the residual. (v0.12.0) Doc: **`spt endpoint run` is the spt-hosted bringup for BOTH endpoint types** (v0.12.0): The bringup core is **type-agnostic** — the endpoint TYPE is the adapter manifest's concern, not a separate bringup mode. A manifest declaring `[session.psyche_init]` brings up a **LiveAgent** (the daemon ... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 27: REQ-HAZARD-PAIR-RATE-LIMIT vs REQ-INST-7 ==================================================================================================== [A] REQ-HAZARD-PAIR-RATE-LIMIT Title: Subnet-global pairing rate limit: one active ceremony per subnet, shared attempt counter, exponential backoff — a public pre-trust relay + multiple seed-holders otherwise enables distributed SPAKE2 guessing (and ±1 TOTP window triples the valid-password space) (ADR-0005 #11) [B] REQ-INST-7 Title: Subnet registry + bare-id resolution policy CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 28: REQ-HAZARD-DROP-FILE-SINGLE-WRITER vs REQ-HAZARD-DIRECT-WRITE-PRECEDENCE ==================================================================================================== [A] REQ-HAZARD-DROP-FILE-SINGLE-WRITER Title: Drop files are daemon-owned single-writer (6.4) [B] REQ-HAZARD-DIRECT-WRITE-PRECEDENCE Title: Direct-write precedence marker (with node id) guards stale overwrite (6.5) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 29: REQ-HAZARD-LIVEHOST-BOOT-LIVENESS-GATE vs REQ-PRESENCE-CONTROL-REAP-ON-EXIT ==================================================================================================== [A] REQ-HAZARD-LIVEHOST-BOOT-LIVENESS-GATE Title: B5: `spt daemon start` does NOT revive phantom Psyches for dead-but-online-latched perches. Today reconcile_once (livehost.rs:285) spawns a Psyche per status=online live_agent perch at boot WITHOUT verifying the harness child / {id}-psyche is actually alive — so a Cold start after an unclean stop revives N psyches for N dead-but-latched perches (3 psyches for 3 dead perches). FIX: gate the boot psyche-spawn on real child-liveness — a perch with NO live broker session (the B2 reconcile signal) is marked OFFLINE at boot instead of hosted, so a dead-harness perch is never revived. Shares the B2 reconcile loop (this is its boot-gate arm); composes with B2's honest latch. Also closes wall-a's psyche_host_error gap (residency-confirm does not run at boot tick-1, livehost.rs:395-441 / 257-263). (v0.12.0) [B] REQ-PRESENCE-CONTROL-REAP-ON-EXIT Title: B3 (F028, hall-b diagnosis + deferred #11 seed; BROADENED perri F-b CONFIRMED): dead-pid ONLINE decay window + sticky CONTROLLED stamp. Repro: /exit -> all endpoint processes dead -> `endpoint list` stays ■ 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). CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 30: REQ-ADAPTER-LIVE-UPDATE vs REQ-SEND-REPLYTO-REMOVE ==================================================================================================== [A] REQ-ADAPTER-LIVE-UPDATE Title: An adapter update is live and daemon-coordinated (the adapter analog of brain self-update, ADR-0004): for an endpoint with a running RESIDENT adapter binary (today the `[message-idle-translation-binary]`), the CLI keeps fetch+verify and hands the APPLY to the daemon over IPC, which per affected endpoint (1) STOPS the resident binary -> releases the OS file lock (fixes the Windows 'Access denied (os error 5)' overwrite failure), (2) swaps on disk ONLY files whose CRC differs from the staged archive (unchanged files + their still-running binaries untouched), (3) RE-CLONES the new on-disk manifest into the running `BrainLifecycle` (the in-memory manifest is cached at bringup and otherwise goes stale -> binaries+manifest back on the same page), (4) RESTARTS the resident binary from the new files. An endpoint NOT running -> CLI swaps directly (no lock, no cache). Only the resident class is cycled; ephemeral adapter binaries (Psyche loop, `[digest]` extractor, `[session.*]` runners, hooks) self-heal on next spawn and are excluded. The daemon keeps a per-endpoint registry of resident adapter children. (ADR-0025, v0.13.2) Doc: Live, daemon-coordinated adapter update: // Amendment (W3 build, 2026-06-22): [B] REQ-SEND-REPLYTO-REMOVE Title: Remove `--reply-to` from `spt send` — a target-fallback + REPLIED-label nicety that confuses agents, with no wire effect (ADR-0020 already made messages structural (from,body), no __REPLY_TO__). Hard-remove (no deprecation shim): the clap flag, the is_reply/REPLIED label branch (always SENT/QUEUED), the `send` how-to --reply-to example, and the reply-to mention in REQ-DOCS-6's send topic. Reply-correlation stays on the structural `from` attribute. (v0.16.0) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 31: REQ-HAZARD-BRAIN-RESPAWN-PATH vs REQ-UPD-8 ==================================================================================================== [A] REQ-HAZARD-BRAIN-RESPAWN-PATH Title: The broker respawns the brain onto the APPLIED bytes, not the renamed old binary: the candidate-binary default is the canonical exe path captured ONCE at broker start, never a per-spawn std::env::current_exe() — 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. 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-... [B] REQ-UPD-8 Title: Platform-safe `spt update fetch` + apply platform-guard (v0.3.1 cross-OS brick fix): `spt update fetch` stages the signed multi-platform `SignedUpdateSet` (`update-set.json` + every platform artifact it names), never a platform-blind single `SignedRelease`, so local apply selects `current_platform()` and P2P re-serve lets each peer select ITS own platform. Defense-in-depth: `apply_staged` REFUSES a staged single-release artifact unless it is platform-stamped for THIS node (an unstamped pre-v0.3.2 single, or a single stamped for another OS, fail-safe refuses — the guard that alone prevents the v0.3.1 brick where a Linux ELF was applied as `spt.exe`). UX: a friendly post-apply message (`Updated spt-core to vX.Y.Z.` + changelog URL) driven by an additive `product_version` metadata field, with a release-counter fallback when absent. CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 32: REQ-ENDPOINT-AUTOSTART vs REQ-ENDPOINT-ONLINE-TRUTH ==================================================================================================== [A] REQ-ENDPOINT-AUTOSTART Title: MSG-IDENTITY W5 / F-038 (flynn operator-directed ask 2026-07-10, SPT-CORE-NEEDS #7 + deployah field-confirm same day: mobile-gw alive=false after the v0.30.6 full daemon restart = this feature's absence, live): an endpoint can be marked a STARTUP DEFAULT so the daemon brings it back up at daemon start — 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 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... [B] REQ-ENDPOINT-ONLINE-TRUTH Title: REGISTRY-LIFECYCLE W2 (ADR-0041 decisions 1+2, emphasys C1 P0): ONLINE is earned, not declared — cmd_listen stamps status=online only from actual persisted state + hosting authority, never from manifest psyche_init capability alone (no more ready_agent/controllable=false hybrid rows born online-authoritative); livehost reconcile SPLITS control cleanup (clear controlled/driven_by/viewer_count for EVERY endpoint absent from session truth, regardless of state/controllable) from offline classification (live_agent+controllable gate unchanged); legacy hybrid rows self-heal after a SUCCESSFUL broker query only (broker failure is never interpreted as an empty session set); terminal signoff/owner-loss = atomic CAS-guarded offline + ready/address removal WITHOUT overloading soft api session-end (/clear preserves the live listener). Gate: impl — creator gate + reconcile split + self-heal + terminal path; unit — creator refuses capability-only online, cleanup clears stamps on state-quirk rows, broker-failure never mass-offlines; int — dead-PID hybrid row does NOT survive a reconcile cycle (the immortal-row regression); doc — ADR-0041. 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 ... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 33: REQ-BRAIN-RESUME-NO-CONTROL-STEAL vs REQ-RESUME-ADAPTER-FOLLOWS-SESSION ==================================================================================================== [A] REQ-BRAIN-RESUME-NO-CONTROL-STEAL 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 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 (whic... [B] REQ-RESUME-ADAPTER-FOLLOWS-SESSION Title: D-2 (REMOTE-TRUTH triage §D-2 + operator Q5 @c248afc): a resume-from-history restores the RECORDED session adapter (REQ-SESSION-ADAPTER-RECORDED) — the resumed harness is the one the session ran under, re-stamped onto the endpoint PRE-SPAWN, and an unregistered recorded adapter refuses LOUDLY before launching anything. ROOT: the picker's resume_outcome (model.rs:1285) bakes adapter=ep.adapter_profile from the selected ENDPOINT, ignoring the ledger row — so a resume always uses the endpoint's CURRENT adapter even when the session ran under a different one; and the endpoint's info.adapter is never re-stamped to the row's on the resume path (cli.rs:1962 skeleton writer early-returns for an existing perch — adapter immutable, carried by bind's stamp_creation_fields). FIX (doyle fork ruling): ResumeRow (model.rs:199) gains adapter: Option threaded from SessionEntry.adapter in picker/data.rs; the row title (model.rs:228) renders [{adapter}] when Some ({head} [{adapter}] - {time} (…{id5})); resume_outcome bakes the ROW's adapter with an endpoint fallback (row.adapter.unwrap_or(ep.adapter_profile)) — None → the endpoint's current stamp (benign degrade). The pre-spawn RE-STAMP + ref CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 34: REQ-GOSSIP-PROJECT-DERIVE-ONCE vs REQ-SHELL-LIST-DERIVED-PROVENANCE ==================================================================================================== [A] REQ-GOSSIP-PROJECT-DERIVE-ONCE Title: REGISTRY-LIFECYCLE W4 fast-follow (hertz v0.37.0 field-verify CPU-gate FAIL RCA 2026-07-17, doyle seam-verified same day; timing-pinned ~16s burst clusters per 30s Registry pump round): recent-project gossip derives a project id AT MOST ONCE PER DISTINCT CWD per advertisement round — recent_projects_for dedups the RAW cwd string BEFORE project_id_for_dir (today seen.insert(pid) dedups AFTER derivation at registryhost.rs:1001, so every duplicate-cwd sessions.log row pays a git remote/rev-parse spawn: ~200 derivations/round on a 12-perch box, the post-self-hash-fix CPU floor). The pid-level dedup stays as the output-uniqueness second layer (distinct cwd spellings may collapse to one project); ordering semantics unchanged (first-occurrence newest-first); bounded ledger read unchanged. Gate: impl — cwd-dedup before derivation; unit — derivation-count observable == distinct cwds not ledger rows (duplicate-cwd ledger derives once), output/order parity vs the pre-fix shape on a mixed ledger. SEEDED SEPARATELY (not this REQ): gossip leg consults the ADR-0037 materialized index (kills git entirely + cross-round rederivation; staleness-semantics design change) + epoch-mint batching (26 atomi [B] REQ-SHELL-LIST-DERIVED-PROVENANCE Title: SEED (inactive — observability, perri-backed 2026-07-26): a status a reader can act on should be distinguishable from a status a record actually holds. Since REQ-HAZARD-SHELL-STALE-ONLINE, `shell list` renders DERIVED online-ness (recorded status AND pid-liveness), deliberately healing poisoned records at the read gate — but nothing marks WHICH rows were healed, so an agent doing forensics reads the healed view and infers a clean record (field, twice on 2026-07-26: liam inferred close_shell had run when the raw info.json still said online over a corpse pid and proposed spending flynn's live instance to manufacture a specimen they already owned; perri independently named the same misread class from their own work). Shape at activation: an additive marker on the --json row when derived != recorded (e.g. recorded_status alongside status, or derived=true) — additive-evolution posture, text view unchanged or minimally annotated; NEVER a behavior change to the derivation itself. Kin: REQ-DAEMON-BITS-AMBIGUITY (silent wrong-state a human catches only by staring at the right field) and the NEVER-SEALING-OBSERVABILITY candidate — same class, view-vs-truth. CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 35: REQ-CLI-3 vs REQ-UPDATE-RUNNING-IMAGE-SURFACE ==================================================================================================== [A] REQ-CLI-3 Title: Agent hot path stays flat across the M8 reorg: send/ring/ready/whoami/how-to unchanged; notify moves to subnet notify while notif stays top-level; breaking renames land clean with no deprecation shims (zero external CLI consumers pre-spt-claude-code) (M8 decisions 3-4, 9) [B] REQ-UPDATE-RUNNING-IMAGE-SURFACE Title: `spt` surfaces the RUNNING broker image version beside the on-disk version so an updated-looking node reveals broker-side dormancy. ROOT (F-025): `spt update apply` restarts the BRAIN only (ADR-0018 D3-3) — the BROKER process survives and keeps running its pre-apply compiled image, so every broker-side surface of a freshly-applied release (the F015B live-apply matcher, dispatch inject legs, etc.) is silently dormant until a full daemon bounce, with nothing in the CLI revealing the split. FIX: the running broker SELF-REPORTS its compiled image version over IPC (a new request KIND answered by the live broker process from its own compiled build constant) — HARD CONSTRAINT: the version comes FROM the running broker process, NEVER inferred from disk bytes, install manifest, or file timestamps, since the disk is exactly the half that is already ahead; a version-surface command (`spt version` and/or `spt daemon status`) prints the running-broker version beside the on-disk/product version and flags a mismatch. Keeps read-side truth independent of write-side claims (the field lesson from F015B). (F-025) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 36: REQ-EP-6 vs REQ-EP-8 ==================================================================================================== [A] REQ-EP-6 Title: Gateway type acceptance: a Gateway-typed perch binds (api bind --type, open type system — un-hardcode the live_agent default), advertises/addressable like any endpoint, owns shells (owner validation not agent-family-gated), subscribes to digests, and is the user-msg identity gate's user-backed origin (REQ-MSG-5); in-tree mock-gateway fixture (R-DOCS-2 pattern, no downstream adapter code). Cross-node WAN Gateway-origin (registry endpoint_type trust) tracked by REQ-MSG-6 Doc: Endpoint types: **Gateway** (concept ratified 2026-06-11; registered via the open type system, first instance downstream): A **human-backed endpoint** — a user's specialized window into the subnet from a device or surface with no conventional-harness compatibility. Nothing LLM-shaped runs... [B] REQ-EP-8 Title: AlwaysOnEndpoint: a resident, addressable, mindless endpoint whose adapter binary the daemon supervises continuously — register-triggered by an adapter-option's `[always-on]` manifest section, one supervised binary per `[:profile]`, running independent of agent liveness. It self-manages its `#`-addressed channel endpoints via the existing `api bind` (one connection fronts many). The SECOND class of spt-core-boot-launched third-party binary (after the shell wake-watcher); supervision reuses the wake-watcher scaffolding (backoff / give-up latch / one-per-instance lock / orphan-kill / brain-side reconcile) MINUS the offline-only flip — always online, never resting (no dormant/suspended states). Two-way: agents message it; it may call `endpoint wake `, target-side authorized (REQ-INST-3/6 wake resolution + access whitelist + shell_wake_spawn_anywhere — no caller-ownership gate). First consumer downstream: spt-discord. Doc: Always-on endpoints: resident supervised adapter binaries, `#`-sigil addressed: CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 37: REQ-DISPATCH-HYGIENE-TELEMETRY vs REQ-MSG-DELIVERY-AXES ==================================================================================================== [A] REQ-DISPATCH-HYGIENE-TELEMETRY Title: REDISPATCH-STALL W1 (ADR-0038 Amendment, fixes 7+8+9): dispatcher hygiene + keyed observability — (a) bounded redispatch worker pool with batched cold enumeration, NO claim locks held during I/O (today thread::spawn per stream, unbounded on a cold table); (b) sessions-lock discipline: clone-then-drop before detach_if/info.json I/O (KH 7.12 kin — no fs/conn I/O under global locks); (c) keyed stage telemetry: gen/stream/family/endpoint/attempt/conn on every dispatch record + replay/poison/cancel/worker-outcome events + gauges + PTY high-water/RC cursor — the field-discriminator surface (poison-window census, stream-sub-attach per generation dropping to O(active streams)). Gate: impl — pool + enumeration batching + lock discipline + telemetry keys; unit — pool bound honored under a cold flood + no-lock-across-I/O seam + telemetry key completeness; doc — ADR-0038 Amendment. Kin REQ-HAZARD-REDISPATCH-STALL, KH 7.12 (locks), REQ-CONN-POISON-ATTRIBUTION (extends its attribution). Doc: Consequences: ## Amendment — REDISPATCH-STALL (2026-07-16) [B] REQ-MSG-DELIVERY-AXES Title: Activity-gated inbound delivery + per-message send control as THREE ORTHOGONAL AXES plus opaque metadata (ADR-0028; grilled w/ operator 2026-06-23). SUBSTRATE (the legacy-SPT parity gap, scaffolded-but-unwired today: `delivery::is_idle` + `resolve_inject_methods` exist but the result is discarded `let _methods`, and `broker::dispatch_endpoint_input` injects unconditionally — its comment calls activity-gating 'a deferred follow wave'): an inbound message has an ACTIVE window (endpoint active → spool for the receiver's hook-poll, non-disruptive) and an IDLE window (idle/idle-transition → deliver immediately: translation binary spt-hosted → relay-poll either topology → spool, in fallback order). AXES (each composes; each defaults to its unrestricted value): (1) DELIVERY WINDOW — default (both, first-to-fire) | `--idle-only` (idle window; immediate if already idle) | `--active-only` (active window only, never wakes; the RENAMED `--deferred` — `deferred=1` spool column + `api poll --include-deferred` keep their names). (2) CHANNEL RESTRICTION — unrestricted | `--prefer-native` (translation binary if running else fall back) | `--force-native` (binary ONLY, no fallback/no spool-to-other-m Doc: Activity-gated message delivery + send-modifier axes: CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 38: REQ-HAZARD-STDIN-SESSION-ID vs REQ-START-3 ==================================================================================================== [A] REQ-HAZARD-STDIN-SESSION-ID Title: Stdin session_id precedence over env (2.2) [B] REQ-START-3 Title: spt-hosted startup: spawn-session then api bind (no file) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 39: REQ-HAZARD-DEFERRED-DRAIN vs REQ-HAZARD-DEFERRED-SURVIVE-DRAIN ==================================================================================================== [A] REQ-HAZARD-DEFERRED-DRAIN Title: Deferred spool rows excluded from the event-stream drain (1.4) [B] REQ-HAZARD-DEFERRED-SURVIVE-DRAIN Title: Deferred rows survive poll drain (4.4) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 40: REQ-DAEMON-1 vs REQ-ENDPOINT-AUTOSTART ==================================================================================================== [A] REQ-DAEMON-1 Title: One per-machine spt-daemon owning all per-machine state [B] REQ-ENDPOINT-AUTOSTART Title: MSG-IDENTITY W5 / F-038 (flynn operator-directed ask 2026-07-10, SPT-CORE-NEEDS #7 + deployah field-confirm same day: mobile-gw alive=false after the v0.30.6 full daemon restart = this feature's absence, live): an endpoint can be marked a STARTUP DEFAULT so the daemon brings it back up at daemon start — 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 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... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 41: REQ-HAZARD-CONPTY-DSR vs REQ-START-4 ==================================================================================================== [A] REQ-HAZARD-CONPTY-DSR Title: ConPTY reader must auto-answer DSR (ESC[6n) or all child output stalls (5.5) [B] REQ-START-4 Title: Adapter-injected env aliases (SPT/OWL/LIVE) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 42: REQ-HAZARD-INBOX-NO-DOUBLE vs REQ-HAZARD-RESTART-IDEMPOTENT ==================================================================================================== [A] REQ-HAZARD-INBOX-NO-DOUBLE Title: No double-delivery via legacy inbox (4.5) [B] REQ-HAZARD-RESTART-IDEMPOTENT Title: Idempotent/exactly-once delivery across brain restart at every broker boundary (codex #14) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 43: REQ-ENDPOINT-PURGE vs REQ-RESUME-REAP-PRIOR-HARNESS ==================================================================================================== [A] REQ-ENDPOINT-PURGE 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 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 o... [B] REQ-RESUME-REAP-PRIOR-HARNESS Title: B4 (F028, hall-b diagnosis, verified 0.22.0): `--resume` respawns a SECOND harness onto the SAME session without reaping the first. Observed live: resume of b4421cf9 spawned pid 34432 while gen1 (250376) kept running — two claude.exe stacks, one session id. FIX: resume must reap/refuse when the session already has a live harness. See triage B4. CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 44: REQ-HAZARD-CEREMONY-CLOCK-STEP vs REQ-NET-3 ==================================================================================================== [A] REQ-HAZARD-CEREMONY-CLOCK-STEP Title: W1/D3 (JOIN-TRUTH, KNOWN-HAZARDS): the cached ceremony offset goes stale-WRONG when the OS clock STEPS under a live daemon — an offset measured against the OLD clock keeps applying for up to the 15-min TTL. Field-proven timeline (enlyzeam): refresh cadence 15:08/15:23/15:38/15:53; operator `w32tm /resync` stepped the clock −210s at 15:45:46; every `subnet join` returned NO_SEED_HOLDER until a daemon bounce forced a fresh query. Fix: the cache snapshot stores an (Instant, SystemTime) PAIR; on read, if |wall-elapsed − mono-elapsed| > ~2s the clock stepped ⇒ force an immediate refresh (offset recomputed against the new clock). PLUS: `meet_seed_holder` (pairhost.rs), on search-deadline exhaustion, forces ONE fresh NTP refresh + one final sweep before returning NO_SEED_HOLDER — so a stepped-clock join self-heals without a bounce. Clock reads must be seam-injectable for the hazard unit (inject the (mono,wall) pair — do NOT sleep 15 min). [B] REQ-NET-3 Title: Cross-node Psyche sync over P2P replaces gh-repo-sync CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 45: REQ-ACTIVITY-LIST-JSON vs REQ-STREAM-INFOS-SERVER-FILTER ==================================================================================================== [A] REQ-ACTIVITY-LIST-JSON Title: `spt endpoint list --json` carries a per-endpoint `activity` (busy|idle) key, for consumers surveying the idle/busy state of many endpoints at once (ADR-0048 decision 1, roster pull avenue; operator addition 2026-07-24). Additive key, N-1-safe; kin the flynn 2026-07-06 last-active/description/adapter list-enrichment seed (same additive posture, may ride together). Doc: `--json` catalog: | Command | Top-level shape | |---|---| | `endpoint list` | `{ self, subnets[], local[] }` — `self`: `{id, status, ready, alive, unbound, description, psyche_host_error, translation_fault?}`; `subnets[]`: `{name, endpoints[]}` where each endpoint is `{id, node, node_label, status... [B] REQ-STREAM-INFOS-SERVER-FILTER Title: REGISTRY-LIFECYCLE W1 (ADR-0040 decision 2, hertz defect B leg 2): broker stream_infos excludes initiated_locally rows SERVER-SIDE (retired already excluded) before cloning/serializing to the dispatcher — 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. CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 46: REQ-EP-3 vs REQ-MSG-5 ==================================================================================================== [A] REQ-EP-3 Title: Messaging payloads carry typed operation commands + file blobs [B] REQ-MSG-5 Title: user-msg envelope kind + daemon identity gate: a Gateway endpoint / the local user's CLI author user-msg (the user's authority); agent-family senders re-stamped to plain msg; identity-gated never payload-trusted (KH 7.3/7.5); wire-additive (N-1 receivers tolerate the new type) Doc: Endpoint types: A message sent from a Gateway carries **the user's authority** — it *is* the user speaking through a device — and is delivered typed **`user-msg`** (ratified 2026-06-12) so receiving agents weight it as user instruction, not peer-agent chatter. The type is **identity-gat... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 47: REQ-CONV-1 vs REQ-SUBNET-2 ==================================================================================================== [A] REQ-CONV-1 Title: Peer address seeding, both cold starts: durable peer-addrs.json (identity dir) maps peer pubkey → last-known dialable address; the pump's resolver consults it FIRST with id-only discovery fallback on miss or dial failure (a stale addr never strands a peer); written by the pairing ceremony (both sides, from the live connection) and by the pump on successful connect; post-join first sync and post-restart resync converge in seconds, not ~1 min (M8 decisions 14, 20) [B] REQ-SUBNET-2 Title: Guided join e2e: spt subnet join CLI initiator + always-on daemon pairing responder Doc: Product-surface amendment (2026-06-05 — M7 D3): CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 48: REQ-EP-7 vs REQ-RC-IDENTITY ==================================================================================================== [A] REQ-EP-7 Title: Durable live-role.md: a per-agent broad-purpose statement in tracked/agents// beside live-context.md (replicates with the mind on the same a- branch); renders FIRST at start-transition context injection (role -> live-context -> project-context); SOLE writer `spt endpoint role --overwrite ` — mechanical no-automated-writer guarantee (echo-commune ingest / signoff / Psyche reconcile structurally exclude it). The user-backed-origin hard gate on the writer is a deferred later tightening (rides the user-msg identity plumbing) Doc: Cross-node Psyche sync: **live role** (`live-role.md`, ratified 2026-06-12 — core milestone A): A durable statement of an agent's **broad purpose** — rarely modified, and only at deliberate user instruction. Lives in `tracked/` (the mind) beside `live-context.md`, so it replicates with ... [B] REQ-RC-IDENTITY Title: `spt rc` overlays a persistent endpoint-identity marker so the operator always groks which endpoint they control: a reserved TOP status row via a DECSTBM scroll-region (shrink the PTY's reported rows by 1, own the row), right-aligned `SUBNET : ENDPOINT_ID @ NODE`, CYAN text. Re-assert the margin + repaint on alt-screen enter / DECSTBM reset / resize (output-scanning, like the existing mouse_scanner). NO window title (the harness, e.g. CC, owns it for busyness — OSC dropped). Resolve subnet/node/id once at attach (perch/registry read) and thread into the pump; subnet = the endpoint's home/primary ("local" if none). The literal floating rounded-rectangle corner box is DEFERRED to the future web-based GUI (not a grid-model rc — that lift is better spent on the GUI). (v0.16.0) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 49: REQ-LIVE-AGENT-NO-INJECT-DELIVERY vs REQ-SEND-SPT-HOSTED ==================================================================================================== [A] REQ-LIVE-AGENT-NO-INJECT-DELIVERY Title: F-033 RE-SCOPED (doyle re-ruling 2026-07-10 after the #82 gate falsified the original premise — the hosting-mode class split is BINDING context): 'state:live_agent has a self-delivery reader' CONFLATED two hosting modes. (A) HARNESS-hosted live agents (api listen path) DO deliver via adapter channels only — and are ALREADY structurally excluded from inject: bind_from_seed stamps controllable=Some(false), no broker PTY exists. (B) SPT-HOSTED/CONTROLLED live agents' inject leg IS their delivery reader (broker PTY + translation binary — doyle's own endpoint is field proof: ENDPOINT_INJECT + IDLE_PARKED_DRAIN alongside hook-poll); the original blanket state:live_agent exclusion broke REQ-MSG-IDLE-EDGE-DRAIN + the v0.14.3 LAW on both CI platforms and was REVERTED (predicate stays controllable-gated). perri's adapter-channels-only model (F-dupmsg-adapter-confirm.md) holds for class (A) only — do not re-seed the conflation. The F-033 DUPLICATE mechanism (hook-poll + idle-inject both delivering one row, operator spool row 156) is closed structurally by REQ-CARRIER-CLAIM-EXCLUSIVE's atomic cross-carrier take. REMAINING LEGS OF THIS REQ: (a) unit — a harness-hosted live agent (controllable S [B] REQ-SEND-SPT-HOSTED Title: An inbound `spt send` is DELIVERED to an spt-hosted endpoint (brought up via `spt endpoint run` → `api bind`, broker holds its PTY, NO `api listen` relay). Today cmd_bind→establish_perch (api/startup.rs ~441) writes info.json + ready marker + controllable=Some(true) but registers NO message-listener / NO address, so deliver.rs resolve_address→None→spool (deliver.rs:132-140) and the message NEVER reaches the live PTY — the endpoint reads 'online' (ready marker) yet `spt send` silently SPOOLS ('online but not deliverable' lie). Per CONTEXT:187-188 the daemon owns the PTY and delivers, manifest-configurable per activity-state (direct PTY injection / relay / HTTP). FIX: route an inbound send for an spt-hosted target through the daemon → broker InputReq → session.write_input PTY-inject (broker.rs dispatch_input/write_input ~988-1022), the same path the brain uses; the live-delivery handshake must report Sent (not Queued) and stop the spool-only fallback for a broker-hosted, PTY-resident endpoint. Detection is local: controllable==Some(true) + spt-hosted state + resolve_address==None. = the spt-core HALF of the wall-b finding (perri owns the adapter half: bind-hook fired-zero-perch + the CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 50: REQ-HAZARD-WORKER-PATH vs REQ-WORKER-LIST-VISIBILITY ==================================================================================================== [A] REQ-HAZARD-WORKER-PATH Title: Single source of truth for Worker/Psyche perch location (1.5) [B] REQ-WORKER-LIST-VISIBILITY 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. CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 51: REQ-ADAPTER-MULTIPLATFORM-SPT vs REQ-MANIFEST-8 ==================================================================================================== [A] REQ-ADAPTER-MULTIPLATFORM-SPT Title: A `.spt` adapter archive may pack multiple platforms in one signed asset: shared `manifest.toml` + `strings/` at the root, role binaries under per-Rust-target-triple subdirectories (ADR-0016 triple vocabulary, e.g. `x86_64-pc-windows-msvc/`); install/update extracts the shared root plus ONLY `current_platform()`'s triple subdir, flattened into `install_dir` so flat `/` resolution (REQ-INSTALL-11) is unchanged. Name stays `adapter.spt` (plain-tar or gzip, `--asset` optional default); one whole-archive Ed25519 signature over the fat archive (REQ-UPD-9 single-artifact verify). A legacy flat archive (no triple subdirs) extracts as today (free back-compat); a multi-platform archive sets `min_spt_core_version >= 0.13.2` (forward-compat gate, readable before extract); a multi-platform archive missing the recipient's triple -> typed `NoArtifactForPlatform`, never a silent no-op. Large adapters may still split per-platform (single-triple archives via `--asset`, or ADR-0016 update-set machinery). (ADR-0024, v0.13.2) Doc: Multi-platform adapter `.spt` packaging: [B] REQ-MANIFEST-8 Title: [adapter] host_binaries declares the harness executable basenames a kind="harness" adapter hosts agents inside (e.g. host_binaries = ["claude"]); bind-time pid→exe-basename match (case-insensitive, .exe-stripped) over the seed's parent_pid selects the candidate adapter set; zero matches → a friendly error naming the binary + the --adapter escape hatch. Additive + N-1-safe: optional Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] (omitted-serialized like shortcut_basename, old manifests parse clean); manifest.schema.json regenerated from the derive (ADR-0001, CI drift-gated). The match-key for ADR-0021 adapter-agnostic bind-time resolution. (v0.9.0) Doc: Startup flows (the two topologies): **Bind-time adapter/profile resolution (ADR-0021).** Because the seed is adapter-agnostic, `listen`/`poll` resolve the owning adapter/profile when they bind, as a pure read — never a seed-time snapshot that could drift. `--adapter ` is an **optio... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 52: REQ-HAZARD-CONTROL-STAMP-LIFETIME vs REQ-PSYCHE-STAMP-CLEAR-ANY-SUCCESS ==================================================================================================== [A] REQ-HAZARD-CONTROL-STAMP-LIFETIME 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. 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-HA... [B] REQ-PSYCHE-STAMP-CLEAR-ANY-SUCCESS 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. CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 53: REQ-ENDPOINT-LIST-NODE-IDENT vs REQ-RC-QUALIFIED-TARGET-CANONICAL ==================================================================================================== [A] REQ-ENDPOINT-LIST-NODE-IDENT Title: Bug #5: spt endpoint list local section header is the hardcoded literal LOCAL (this node) (render_local_section cli.rs:4359). Change to 'This node: ' using the existing node-ident idiom (os_hostname + nodeid public-key prefix, cli.rs:5531 — factor a node_ident_display helper); compute in the impure print_local_section, pass into the pure renderer. Update the two test assertions (cli.rs:10711/10716). See docs/NEXT-MILESTONE-BUG-TRIAGE.md #5. [B] REQ-RC-QUALIFIED-TARGET-CANONICAL Title: RC-RENDER-TRUTH W1 (ADR-0042 decision 4, hertz elevated-endpoints RCA core leg 2, doyle seam-confirmed rc.rs establish_attach): the resolver's canonical BARE endpoint id is carried separately from the user-facing qualified target — AttachRequest.endpoint_id is always the bare id (today rc passes the ORIGINAL qualified string; the target's resolve_local_session compares verbatim vs the bare HostedSession.endpoint, so `spt rc id@node`/`subnet:id` dials the RIGHT node then gets a false no-live-session refusal). N-1-additive: bare-form callers are unchanged. Gate: impl — canonical-id carry through establish_attach; unit — Address::parse qualified forms yield bare wire id, user-facing copy keeps the qualified spelling; int — bare + id@node + subnet:id ALL attach against a remote broker-hosted target, wire always carries the canonical bare id; doc — ADR-0042. 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 sessio... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 54: REQ-DOCS-2 vs REQ-START-4 ==================================================================================================== [A] REQ-DOCS-2 Title: Sub-10-minute runnable killer quickstart per audience Doc: Harness integration checklist: // Quickstart: build an adapter: [B] REQ-SELF-ID-TRUST-INJECTED-ENV Title: DEFERRED to a followup vX.X.n sprint (post-LIFECYCLE-TRUTH, operator-ruled 2026-07-07): self-identity resolution must trust the harness-injected authoritative id and detect a stomped perch instead of silently mis-attributing. ROOT (doyle /diagnose 2026-07-07, field: agent sends stamped `cli@HFENDULEAM` / mis-attributed): `resolve_from` (cli.rs:5480) stamps `cli@` when `detect_self_id` (roster.rs:103) returns None; detect_self_id resolves self ONLY by reverse-lookup — matching `$OWL_SESSION_ID` against a perch's info.json.session_id (then SPT_AGENT_ID, then parent_pid) — and IGNORES `SPT_ENDPOINT_ID`, the authoritative self-id the adapter injects (present in-env as SPT_ENDPOINT_ID=). When a perch record is STOMPED (a cross-id info.json overwrite — the REQ-SPAWN-COLLISION-GUARD-LIVE-DUP damage class; field case: doyle's live session_id written into the deployah perch), the reverse-lookup mis-resolves (doyle session -> `deployah`) or fails (real deployah -> None -> `cli@node`), and the CLI silently believes the stomped store. FIX: detect_self_id PREFERS `SPT_ENDPOINT_ID` when set+non-empty (the harness-authoritative id, immune to a stompable perch), AND cross-checks it again CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 56: REQ-HAZARD-CASCADE-WIPE-GUARD vs REQ-HAZARD-SINGLE-PATH-SOURCE ==================================================================================================== [A] REQ-HAZARD-CASCADE-WIPE-GUARD Title: No hard-delete of a parent hosting non-empty children (6.3) [B] REQ-HAZARD-SINGLE-PATH-SOURCE Title: Single path/registry source of truth; no layout ambiguity (6.1) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 57: REQ-ENDPOINT-LIST-NODE-IDENT vs REQ-MANIFEST-NODE-KEY ==================================================================================================== [A] REQ-ENDPOINT-LIST-NODE-IDENT Title: Bug #5: spt endpoint list local section header is the hardcoded literal LOCAL (this node) (render_local_section cli.rs:4359). Change to 'This node: ' using the existing node-ident idiom (os_hostname + nodeid public-key prefix, cli.rs:5531 — factor a node_ident_display helper); compute in the impure print_local_section, pass into the pure renderer. Update the two test assertions (cli.rs:10711/10716). See docs/NEXT-MILESTONE-BUG-TRIAGE.md #5. [B] REQ-MANIFEST-NODE-KEY Title: A new session-scoped manifest fill key `{node}` resolves to THIS node's advertised label — available wherever the session-scoped keys ({id}/{session_id}/{session_name}) populate: BOTH topologies' spawn-prep catalogs (harnesshost.rs:111-118 self-spawn guaranteed-fill + lifecycle.rs:280 base lifecycle keys, at minimum [session.self] and [session.resume]) AND lazy [strings] eligibility (ADR-0029 family). VALUE (design-true per CONTEXT §node label / REQ-SUBNET-3): the node's ADVERTISED LABEL — the same value node_label_display renders — read from the label store (NodeLabel, registry.rs:118/220, OS-hostname default re-checked at daemon startup), NOT the pubkey and NOT a fresh gethostname at fill time when the store already holds the refreshed label; fall back to the OS hostname only if no label is known. perri's concrete use: templating `--remote-control {id}--{node}` in the claude-spt launch/resume commands. CAVEAT (documented in the manifest.md key-table row AND here): SINGLE-TOKEN fills only — tokenize-then-fill (REQ post-F-009) cannot produce a space-carrying argv element, so composite display names like ` @ ` remain adapter-shim territory (claude-spt v0.10.3's launch shim Doc: Claude Code: native resume into an existing transcript by id, in its project cwd.: ```toml [session.self] # {node} fills as one argv token — the node's advertised label (its hostname). command = "claude --session-id {session_id} --remote-control {id}--{node}" keys = ["session_id", "id", "... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 58: REQ-HAZARD-BRAIN-RESTART-LIFECYCLE-REHYDRATE vs REQ-START-5 ==================================================================================================== [A] REQ-HAZARD-BRAIN-RESTART-LIFECYCLE-REHYDRATE Title: B4 (deepest): a bare brain restart (broker survives) REHYDRATES the live-agent lifecycle so post-restart endpoints are hosted + attachable. Today resume_sessions (brainproc.rs:186, brain.rs:797-809) re-subscribes to the broker's PTY sessions but ALL BrainLifecycle instances (lifecycle.rs:58-130; the ephemeral brain.rs:254-275) are LOST on restart → 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) [B] REQ-START-5 Title: Adapter-agnostic harness-hosted seed + bind-time adapter/profile resolution (ADR-0021): `api seed` carries only parent_pid + session_id (+ optional cwd), no --adapter — a pure "a harness session exists at this pid" record; --adapter becomes an OPTIONAL override across the whole api group (an explicit name[:profile] for adapter dev, never required). Omitted, listen/poll resolve the owning adapter/profile AT BIND as a pure read against the live registry — never a seed-time snapshot that can drift: seed parent_pid → exe basename → host_binaries candidate set (REQ-MANIFEST-8) → active-profile pointer (REQ-INSTALL-12) primary, else greatest-registered_at_ms candidate base profile (name-asc tie) → friendly zero-match error. Covers BOTH LiveAgent (listen) and ReadyAgent (poll) bringup. Restores legacy parity: `$LIVE start ` → `$SPT listen ` with no mandatory --adapter, one generic SessionStart hook per harness binary. (v0.9.0) Doc: Startup flows (the two topologies): **Harness-hosted (e.g. spt-plugin; the harness binary is user-launched, harness is the parent).** Key constraint: the SPT *live agent* does not exist until the agent invokes start — the `live_id` isn't chosen at session boot, and `$LIVE start` is itself invoked ... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 59: REQ-DISPATCH-CLAIM-RETRY vs REQ-HAZARD-CONTROLLER-GAP-RESUME ==================================================================================================== [A] REQ-DISPATCH-CLAIM-RETRY 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. [B] REQ-HAZARD-CONTROLLER-GAP-RESUME Title: A serving CONTROLLER whose serve-brain hits a b4 drop-don't-block FORWARD output gap must RESUME-FROM-FLOOR (re-subscribe from delivered_through and re-fetch the dropped frames from the ring), NOT snap-above and NOT fatal. ROOT (v0.13.0 forkpty re-run, post-keystone): b4 made the controller a non-blocking try_send that DROPS frames when its bounded channel fills (a controller that falls behind its OWN echo under a hard flood), so the next read is a forward gap the strict reject-gap (brain.rs:624/628, B2 exactly-once) FATALS — wedged_viewer_does_not_stall_controller (attach.rs:1048) drove ctrl.read_event() raw and fataled on `output gap got 6134 want 4643`. Pre-b4 the inline sleep-poll BLOCKED the drain to the controller's rate (no drops, no gaps); this is a b4 SIDE-EFFECT, not a new class. A controller CANNOT snap (it is authoritative — advances delivered_through; skipping rolled frames = not-exactly-once = B2 violation), so REQ-HAZARD-VIEWER-RING-ROLL-SNAP does NOT apply. B2 INVARIANT (doyle, broker.rs:327-330): the ring trim is delivered_through-BLIND (`while ring.len() > cap_chunks { pop_front() }`), so re-fetch is exactly-once IFF tail - delivered_through <= cap_chunks (4096) — CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 60: REQ-ENDPOINT-LIST-PROJECT-COL vs REQ-EP-5 ==================================================================================================== [A] REQ-ENDPOINT-LIST-PROJECT-COL Title: #8: spt endpoint list gains a second column / (the endpoint's LATEST project) -> 4 columns total: id / / / type / status. Local rows: head of REQ-PICKER-PROJECT-HISTORY-TRUTH (sessions.log-derived, owlery-excluded). Remote rows: head of REQ-GOSSIP-ADAPTER-PROJECTS recent_projects. Project IDs only + #4 disambiguation; '-' when unknown (pre-field remote rows). Extends the v0.21.0 node-grouped renderer (format_instance_rows — additive column, alignment char-width-safe). --json: additive project field on the row DTO (skip-if-none, N-1 safe). Depends on #1 + #4. See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #8. [B] REQ-EP-5 Title: Concrete shell instantiation model: spawn-mints-instance (vs relink/online), registered-on-node permission + broadcast-is-discovery, per-shell require_approval gate, max_instances_per_owner + over_cap, instance aliasing, discovery scope CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 61: REQ-IDLE-PARKED-DELIVERY vs REQ-INST-6 ==================================================================================================== [A] REQ-IDLE-PARKED-DELIVERY Title: W5 (LIFECYCLE-TRUTH): a message QUEUED to an ALREADY-idle spt-hosted endpoint is delivered without an operator poke. ROOT (live during the milestone dispatch 2026-07-07): the idle-edge drain (F-023 leg 2) fires only on the ACTIVE->IDLE transition; no new edge ever comes for a parked session, and the send-time inject didn't carry it — both doyle->todlando dispatches sat delivered=0 in the spool while the endpoint showed ONLINE. FIX: send-time inject fires for an already-idle spt-hosted target (activity sense says idle => inject now, not spool), and/or a bounded spool sweep re-offers pending rows to idle endpoints (piggyback the pulse tick, no new loop). Int: send to a session idle for N minutes -> delivered without any operator poke. [B] REQ-INST-6 Title: Deferred messages not delivered to dormant/suspended instances Doc: Deferred Features: | Feature | Cut from | Why deferred | Trigger to revisit | |---|---|---|---| | Scrollback on-disk spillover | terminal wrapper v1 | In-memory ring covers the common case; spillover adds a persistence/rotation story | First long-running session that overflows the ring usefully, or ... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 62: REQ-DAEMON-REFRESH vs REQ-HAZARD-DAEMON-STOP-BARRIER ==================================================================================================== [A] REQ-DAEMON-REFRESH Title: THE-FORKENING W4 (operator add 2026-07-14): `spt daemon refresh` — restart the daemon BRAIN without a binary swap and WITHOUT touching the broker: exactly the apply_staged brain-cycle path (brain stop -> respawn -> readiness trial -> promote, incl. the trial-drain drive REQ-UPDATE-TRIAL-DRAIN-DRIVE and viewer-only resume REQ-BRAIN-RESUME-NO-CONTROL-STEAL) minus the swap. Recovery verb for wedged brain-held state (field motivator 2026-07-14: endpoint bringup broken on a live daemon + deployah down — today's only remedy is a full daemon bounce that kills every PTY). Broker + PTYs survive by construction (handoff invariant). Failure = the existing trial rollback semantics (old brain resumes; refresh reports loud). Gate: unit — verb routes the brain-cycle without staging/swap preconditions; int — refresh on a live daemon with a hosted PTY: brain generation changes, PTY survives, endpoint stays attached; doc — daemon docs name refresh next to stop/start. Kin apply_staged (the path it reuses), REQ-UPDATE-TRIAL-DRAIN-DRIVE, REQ-BRAIN-RESUME-NO-CONTROL-STEAL. Doc: Self-update: **daemon refresh (`spt daemon refresh`)** — restart the **brain** in place, no binary swap, broker and every held PTY untouched: the routine-update handoff path minus the swap. The recovery verb for wedged brain-held state (broken endpoint bringup, a downed hosted agent) that previous... [B] REQ-HAZARD-DAEMON-STOP-BARRIER 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) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 63: REQ-PAIR-2 vs REQ-SUBNET-6 ==================================================================================================== [A] REQ-PAIR-2 Title: Local trust store with TOFU + warn-on-change [B] REQ-SUBNET-6 Title: Trust lifecycle verbs, elevation-gated: spt subnet leave (membership exit) and spt subnet prune (removes a dead identity's trust + registry rows, killing its dead dials; trust mutation = security surface, REQ-PAIR-6 gate machinery) (M8 decisions 6-7) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 64: REQ-ADAPTER-FLOOR-ENFORCE vs REQ-SEAM-UPDATE ==================================================================================================== [A] REQ-ADAPTER-FLOOR-ENFORCE Title: F-5 (REMOTE-TRUTH triage §F-5 + doyle rulings 2026-07-05): BOTH adapter acquisition verbs (spt adapter add + spt adapter update) REFUSE when the installed spt-core is BELOW the adapter's declared [adapter].min_spt_core_version floor — with an F-1 operator refusal naming the installed core, the floor, and the next action (update spt-core first). ROOT: the floor was PARSED + required (manifest.rs) but never compared to the running core — dead enforcement; and the [update].version_check knob that gated it was DOC'D-BUT-DEAD (never read by any production path — a contract lie). RULINGS: RETIRE version_check (drop the manifest field + schema + docs + the cfg(test) literals; a pre-existing manifest still setting it deserializes fine — serde ignores the unknown key, no deny_unknown_fields, so retiring is back-compatible); SEMVER-compare NOT string-compare (the 0.9.0 < 0.25.0 lexical trap); enforce on BOTH verbs; nothing installs / registry untouched on refuse (binds both verbs, no residuals). FIX: (1) a pure spt-runtime version_meets_floor(core, floor) -> bool (numeric per-component: split '.', u64, missing→0, non-numeric→0, first-diff decides, equal-when-zero-padded ⇒ satisfied) — mirror Doc: `[adapter]` — header: `min_spt_core_version` is the **enforced** compatibility floor. Both acquisition verbs — `spt adapter add` and `spt adapter update` — REFUSE when the installed spt-core is below this version, naming the installed core, the floor, and the next action (update spt-c... [B] REQ-SEAM-UPDATE Title: Adapter-update avenue (file-pull / delegated command) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 65: REQ-ARCH-3 vs REQ-HAZARD-HANDOFF-ARGV-COMPAT ==================================================================================================== [A] REQ-ARCH-3 Title: Wire-protocol version independent of crate semver, N-1 compat window [B] REQ-HAZARD-HANDOFF-ARGV-COMPAT Title: Broker/brain IPC + handoff argv version-tolerant (2.3) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 66: REQ-TERM-5 vs REQ-TERM-7 ==================================================================================================== [A] REQ-TERM-5 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. Doc: Session digest — the published digest-record contract (ADR-0019): 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 ... [B] REQ-HAZARD-REGISTRY-DIR-CREATE Title: SQLite store opens create their parent dir themselves — a fresh-home registry op must not SQLITE_CANTOPEN (4.9) Doc: 4.8 Registry merge ordered by epoch, never wall-clock (red-team #8): ### 4.9 SQLite stores must create their parent dir — SQLite won't - **Failure:** `Connection::open` creates the database FILE but never its parent DIRECTORY. On a fresh home (first boot, fresh CI `_work` dir) a registry ... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 72: REQ-IDLE-PARKED-DELIVERY vs REQ-MSG-1 ==================================================================================================== [A] REQ-IDLE-PARKED-DELIVERY Title: W5 (LIFECYCLE-TRUTH): a message QUEUED to an ALREADY-idle spt-hosted endpoint is delivered without an operator poke. ROOT (live during the milestone dispatch 2026-07-07): the idle-edge drain (F-023 leg 2) fires only on the ACTIVE->IDLE transition; no new edge ever comes for a parked session, and the send-time inject didn't carry it — both doyle->todlando dispatches sat delivered=0 in the spool while the endpoint showed ONLINE. FIX: send-time inject fires for an already-idle spt-hosted target (activity sense says idle => inject now, not spool), and/or a bounded spool sweep re-offers pending rows to idle endpoints (piggyback the pulse tick, no new loop). Int: send to a session idle for N minutes -> delivered without any operator poke. [B] REQ-MSG-1 Title: Local message delivery: TCP-first to a registered address, spool fallback when offline; id->address via registry (stale-clean first); reply routing (__REPLY_TO__) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 73: REQ-PICKER-PURGE-SHORTCUT vs REQ-RUN-ID-REUSES-ADAPTER ==================================================================================================== [A] REQ-PICKER-PURGE-SHORTCUT Title: C-3 (REMOTE-TRUTH triage §C-3 #8): the pick-existing list gains an `x` purge shortcut — on an OFFLINE LOCAL highlight, `x` opens a small in-TUI confirm screen (Screen::ConfirmPurge, the B-2 ChangeAdapterPick shape) and Enter purges via the ONE existing purge core `cmd_endpoint_purge(id, yes=true, force=false)` — NEVER the core's stdin [y/N] (it fights the picker's raw mode; the confirm screen IS the confirm). The shortcut INHERITS both purge gates (offline-only + node-local, cli.rs cmd_endpoint_purge / CONTEXT:189): gated-off presses stay on the list and FLASH WHY (online → offline-only, remote → local-only). force=false is deliberate — the model gate is advisory; the core's own offline check is the authority, and a race to online between gate and purge must REFUSE, never stop-then-purge. After a successful purge the picker STAYS (inline outcome, like Shortcut/ChangeAdapter): the row leaves the in-memory list (remove_endpoint, cursor re-clamped) + flash PURGED:{id}. Hint truth (B-1/F029 discipline): the pick legend renders `x purge` ONLY when purge_key_live() — the same predicate the handler gates on. Red-first: purge outcome reachable ONLY from an offline LOCAL highlight (online/r [B] REQ-RUN-ID-REUSES-ADAPTER Title: D-1 (REMOTE-TRUTH triage §D-1): `spt endpoint run --id ` with NO --adapter, when names an EXISTING perch, REUSES that perch's recorded info.adapter and runs NON-INTERACTIVELY — instead of always falling to the picker as a create-new prefill (an existing endpoint retyping its own adapter, or being sent to a create-new flow, is the operator wart). ROOT (certain, no design tension): the cli `match (adapter,id)` special-cased only (Some,Some)→cmd_endpoint_run; the catch-all routed EVERY lone --id to crate::picker::run as a create-new prefill, never considering an existing endpoint (cli.rs ~1290). FIX: a PURE resolve_run_target(adapter, id, recorded) over the 4 (adapter?,id?) quadrants — (Some,Some)→Direct{a,id}; (None,Some(id))→ recorded adapter present (info.adapter = adapter-chosen-at-creation, spt-store info.rs:167) → Direct{recorded,id}, absent/no-perch → Picker{None,Some(id)} (today's create-new prefill UNCHANGED); (Some,None)/(None,None)→Picker unchanged. The perch lookup (read_info(resolve_perch_path(id,Infer)).adapter) is INJECTED as a closure so the router is pure + testable without a perch on disk; resume threads into BOTH Direct paths. Red-first: (None,Some(id),reco CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 74: REQ-BROKER-OUTPUT-BEFORE-EXIT vs REQ-HAZARD-CONTROLLER-WRITER-REORDER ==================================================================================================== [A] REQ-BROKER-OUTPUT-BEFORE-EXIT Title: RC-RENDER-TRUTH W3 (ADR-0043 decision 1, hertz stale-glyphs RCA leg 1 P0): the PTY drain/output writer is the SOLE FIFO sequencer for terminal Output + Exit per attach sink — 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. 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 contr... [B] REQ-HAZARD-CONTROLLER-WRITER-REORDER Title: Two `controller_writer` threads must never race ONE brain↔broker connection's socket. ROOT (doyle, instrumented RACEDIAG repro on kitsubito): on a brain-restart re-serve the handoff brain registers as controller on the SAME session TWICE over the SAME `Brain::conn` socket — (1) `Brain::handoff` eagerly `subscribe(prior.session_id, prior.next_seq=1)` → `become_controller(from_seq=1)`, initial=[1], spawns writer-A (writes seq 1); (2) `serve_attach` re-handles the replayed `Request{from_seq:0}` → `attach_as(sid,0)` → `become_controller(from_seq=0)`, initial=[0,1], spawns writer-B (writes 0 then 1). `become_controller` (broker.rs) drops the prior `ControllerSink` (its `tx`) but does NOT stop the prior writer thread — writer-A keeps flushing its owned `initial` batch, and both writers hold clones of the same `SharedSend` (`Arc>`) with NO inter-thread ordering. When writer-A's seq 1 wins the socket before writer-B's seq 0, the strict legacy consumer (brain.rs read_event reject-gap path) sees `output gap: got seq 1 want 0` → the test `attach_survives_target_brain_restart_exactly_once` panics at `.expect("re-serve")` OR HANGS in `render_until` (serve thread died on the gap → Doc: 7.20 `spt rc` must forward the scroll wheel to the harness (our mouse capture steals WT's native scroll) `[REQ-RC-MOUSE-FORWARD]`: ### 7.21 Exactly ONE `controller_writer` per brain↔broker connection — a superseded writer must write nothing further `[REQ-HAZARD-CONTROLLER-WRITER-REORDER... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 75: REQ-HAZARD-DEAD-REC-PID vs REQ-LIST-JSON-LIVENESS-PARITY ==================================================================================================== [A] REQ-HAZARD-DEAD-REC-PID Title: E-1 rider (F-026 #11 dead-pid class, doyle-ruled SCOPED 2026-07-05 — KNOWN-HAZARDS 7.34): a dead `rec.pid` on an spt-hosted perch is EXPECTED, not staleness — the recorded pid is the ephemeral bind-CLI pid, which dies immediately after bind (the broker holds the PTY; no resident harness process at that pid). NO reader may alive-gate on `rec.pid` alone: spt-hosted LIVENESS comes from the daemon-managed status field (KH 2.5 — status present ⇒ authoritative, never a per-pid probe); IDENTITY comes from session/ancestry resolution where `rec.parent_pid` (the harness pid, the stable session-binding anchor) is the ancestry candidate (REQ-SELF-DETECT-PARENT-PID). Re-stamping rec.pid with the harness pid (shape (a)) is OVERRULED: ADR-0021 demoted pid to a bind-time seed hint (re-anchoring truth there reverses the design); every pre-existing record keeps the old CLI pid so readers need the scoped discipline anyway (migration hole); blast radius (every rec.pid consumer + KH 2.5 external-perch probe semantics) buys nothing the reader-side fix doesn't. CLASS rule: any newly sighted rec.pid-alive-gating reader gets the same scoped fix and EXTENDS this requirement's evidence — no new REQ per read Doc: 7.33 NO bare `.lock().unwrap()` on a broker-resident lock reachable from serve/dispatch — a poison permanently wedges every attach `[REQ-HAZARD-BROKER-FLOOR-LOCK-POISON]`: ### 7.34 A dead `rec.pid` on an spt-hosted perch is EXPECTED — no reader may alive-gate on `rec.pid` alone `[REQ-HAZARD-DEAD... [B] REQ-LIST-JSON-LIVENESS-PARITY Title: GATEWAY-LIVENESS (flynn field bug 2026-07-09, RCA reader-divergence root): `spt endpoint list` (human) and `endpoint list --json` MUST report an IDENTICAL status for a locally-hosted endpoint — especially a pid-alive, status-ABSENT gateway (no psyche_init). ROOT (todlando RCA STEP-1, doyle-verified): the --json builder (crates/spt/src/cli.rs cmd_endpoint_list) emits each subnet row's status straight from resource_projection (spt-net registry.rs:566, passes instance.status through verbatim :592 — the persisted WAN snapshot, a lagged gossip that can carry a stale/crash-time Suspended) and NEVER applies the self-owned reconcile the human/picker path applies (reconcile_self_owned, crates/spt/src/picker/data.rs:160 via gather_endpoints :112). So a pid-alive self-owned gateway reads Suspended on --json but ONLINE on human (roster::enumerate spt/src/roster.rs:38 -> is_perch_alive pid-fallback spt-store/liveness.rs:136); the adapter suspend-poll (parse_endpoint_status over endpoint list --json --show-all) reads the divergent --json status -> self-suspends a pid-alive gateway. Candidates REFUTED: resource_projection does NOT re-derive liveness (copies instance.status, only skips !routable : CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 76: REQ-HAZARD-VIEWER-CLOSE-DETACH vs REQ-READY-AGENT-RESUME ==================================================================================================== [A] REQ-HAZARD-VIEWER-CLOSE-DETACH Title: A VIEW is independent from the endpoint: closing the tab/window where `spt endpoint run` was invoked must detach ONLY the `spt rc` attach pump — the daemon-hosted harness keeps running and stays re-attachable via `spt rc `. ROOT (Windows, v0.12.0 real-harness defect): the daemon never breaks away from the launching terminal's Job Object. Windows Terminal / VS Code place the launched shell AND every descendant into a Job Object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; closing the tab drops the terminal's last job handle → the OS terminates every process still in that job. A child escapes only with CREATE_BREAKAWAY_FROM_JOB — used NOWHERE in the tree. Both daemon spawn paths (daemon.rs:707 detached_no_inherit = DETACHED_PROCESS|CREATE_NEW_PROCESS_GROUP|CREATE_NO_WINDOW; deelevate.rs:519 elevated = CREATE_NEW_CONSOLE|...) drop the CONSOLE but NOT job membership, so the daemon's freshly broker-spawned ConPTY harness subtree is reaped on tab-close. The ConPTY/pseudoconsole isolation itself is CORRECT (portable-pty builds the pseudoconsole in the daemon; no console signal / handle leak) — the leaking lifetime binding is the Job Object, not the console. FIX: add CREATE_BREAKAWAY_FROM_ Doc: Terminal wrapper: **A view is independent from the endpoint** (invariant): An spt-hosted endpoint runs in a **daemon-owned PTY, decoupled from whatever terminal launched it**. Closing the tab/window where `spt endpoint run` was invoked detaches only the `spt rc` attach pump — the endpoint... [B] REQ-READY-AGENT-RESUME Title: An offline ReadyAgent shows in `spt endpoint run`'s picker Resume-from-history and resumes correctly — closing the gap that today only LiveAgents do. ROOT: a harness-hosted ready bind (ReadyAgent::start_homed, ready.rs) writes info.json DIRECTLY and never appends the session ledger (unlike the shared establish_perch:250 live path), so a ready agent — though it has a session_id — produces ZERO ledger rows → the picker's offline+local Resume-from-history (which gates on ledger rows) never offers it. FIX (1): ledger the ready bind (ReadyAgent::start_homed → sessions::append Boot, mirroring establish_perch). FIX (2): `spt endpoint run --resume ` honors the adapter MANIFEST's endpoint TYPE — a ReadyAgent manifest (no [session.psyche_init]) resumes as a ready endpoint (poll listener, NO psyche-host); a LiveAgent (with psyche_init) as live. NO new bringup mode + NO picker changes (operator 2026-06-18): `spt endpoint run` is the spt-hosted ENDPOINT bringup for BOTH types, the type IS the adapter-manifest's concern (psyche-host already keys on psyche_init presence) — so (2) likely already holds; VERIFY at code, build only the residual. (v0.12.0) Doc: **`spt endpoint run` is the spt-hosted bringup for BOTH endpoint types** (v0.12.0): The bringup core is **type-agnostic** — the endpoint TYPE is the adapter manifest's concern, not a separate bringup mode. A manifest declaring `[session.psyche_init]` brings up a **LiveAgent** (the daemon ... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 77: REQ-HAZARD-GRACE-BEFORE-SIGNOFF vs REQ-HAZARD-ECHO-BEFORE-SIGNOFF ==================================================================================================== [A] REQ-HAZARD-GRACE-BEFORE-SIGNOFF Title: Grace-period wait completes before composing INIT_SIGNOFF (1.1) [B] REQ-HAZARD-ECHO-BEFORE-SIGNOFF Title: Echo-commune fires before INIT_SIGNOFF on orphan teardown (3.3) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 78: REQ-HAZARD-PARENT-PID-PREFER vs REQ-HAZARD-RESUME-CUSTODY-ABA ==================================================================================================== [A] REQ-HAZARD-PARENT-PID-PREFER Title: Prefer stable parent PID / broker handle over ephemeral PID (2.1) [B] REQ-HAZARD-RESUME-CUSTODY-ABA 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. 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_s... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 79: REQ-HAZARD-BIND-CWD-UNSET vs REQ-SESSION-RESUME-TEMPLATE ==================================================================================================== [A] REQ-HAZARD-BIND-CWD-UNSET Title: A bound endpoint's `info.cwd` is SET at bind so a freshly-created perch appears under its own project tab. ROOT (found, v0.13.0): `info.cwd` is NEVER set on bind — `cmd_bind` (spt-hosted) and `bind_from_seed` (harness-hosted) never thread cwd into `establish_perch`/`rec.cwd`. FIX: `cmd_bind` reads its own `current_dir` (the broker spawned it in `project_cwd`); `bind_from_seed` passes `seed.cwd` (already captured at seed time, currently DISCARDED). DISTINCT from REQ-PICKER-HISTORY-FRESH (v0.12.1) — that unioned cwd-origin into picker MEMBERSHIP but tested merge_origin_project with a PROVIDED origin; it never asserted `info.cwd` is actually set on bind, so a real `endpoint run` perch still had an empty cwd and the union had nothing to union. This is the v0.12.1 P1 'appears under its own project right away' claim that was REFUTED in the changelog — delivered for real here. (v0.13.0) [B] REQ-SESSION-RESUME-TEMPLATE Title: Resuming an endpoint session that HAS conversation history brings up a BLANK session. ROOT (doyle, code-grounded + CONTEXT — case-3 spt-core MISSING feature, NOT a perri docs-miss): CONTEXT L127-129 already defines the resume-session seam ('continue-existing: resume an existing harness session under the adapter — its NATIVE resume'), and the manifest already has the resume-variant pattern (Session has BOTH psyche_init AND psyche_resume, manifest.rs:217-219) — but the agent's own session has ONLY self_ (`[session.self]`, no resume sibling). cmd_endpoint_run (cli.rs:1304) re-passes the session_id through `[session.self]` on resume (resume.unwrap_or_else(mint_session_id)), so the adapter's FRESH command (e.g. `claude --session-id ..`) runs again instead of the harness NATIVE resume (`claude -r ..`) -> CC starts a fresh transcript -> blank. spt-core forwards session_id + cwd faithfully; it just has no way to express the native-resume invocation. SECOND GAP: CC resolves a transcript by session_id + cwd, but the session ledger records only {ts, session_id, trigger} (no cwd), so picker Resume-from-history (cross-project rows) can't restore the right cwd. FIX (doyle design, V0.13.0-P2-SESS Doc: `[session.]` — outbound templates: **Resuming an existing harness session (since v0.13.0).** `[session.self]` is the *fresh* bringup; `[session.resume]` is the **native-resume** sibling. spt-core selects `[session.resume]` over `[session.self]` only when a bringup carries a prior se... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 80: REQ-HAZARD-ENVELOPE-CR-LINESAFE vs REQ-HAZARD-VIEWER-CLOSE-DETACH ==================================================================================================== [A] REQ-HAZARD-ENVELOPE-CR-LINESAFE Title: Envelope CR-linesafety (4.1): the line-framed EVENT codec must neutralize raw carriage returns — `event_body_escape` folds CRLF/lone-CR to the codec's representable linebreak (`\n`→`
`) BEFORE framing, so a body carrying `\r` (Windows `echo`/CRLF text crossing nodes) cannot survive into the single-line envelope and trigger a receiver terminal CR→col0 overwrite that corrupts the frame. Robustness on unrepresentable input, NOT a wire-format change (decoder untouched, amp-last invariant held). Belt-and-suspenders: `spt send`/`ring` also trim stdin (parity with `notify`). Doc: Body and attribute encoding: **Carriage returns are unrepresentable.** The encoder normalizes `\r\n` and lone `\r` to `\n` *before* the `
` encoding, so no frame ever carries a raw CR and a decoder always receives `\n` newlines. Do not expect `\r` to round-trip — content that needs CRs... [B] REQ-HAZARD-VIEWER-CLOSE-DETACH Title: A VIEW is independent from the endpoint: closing the tab/window where `spt endpoint run` was invoked must detach ONLY the `spt rc` attach pump — the daemon-hosted harness keeps running and stays re-attachable via `spt rc `. ROOT (Windows, v0.12.0 real-harness defect): the daemon never breaks away from the launching terminal's Job Object. Windows Terminal / VS Code place the launched shell AND every descendant into a Job Object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; closing the tab drops the terminal's last job handle → the OS terminates every process still in that job. A child escapes only with CREATE_BREAKAWAY_FROM_JOB — used NOWHERE in the tree. Both daemon spawn paths (daemon.rs:707 detached_no_inherit = DETACHED_PROCESS|CREATE_NEW_PROCESS_GROUP|CREATE_NO_WINDOW; deelevate.rs:519 elevated = CREATE_NEW_CONSOLE|...) drop the CONSOLE but NOT job membership, so the daemon's freshly broker-spawned ConPTY harness subtree is reaped on tab-close. The ConPTY/pseudoconsole isolation itself is CORRECT (portable-pty builds the pseudoconsole in the daemon; no console signal / handle leak) — the leaking lifetime binding is the Job Object, not the console. FIX: add CREATE_BREAKAWAY_FROM_ Doc: Terminal wrapper: **A view is independent from the endpoint** (invariant): An spt-hosted endpoint runs in a **daemon-owned PTY, decoupled from whatever terminal launched it**. Closing the tab/window where `spt endpoint run` was invoked detaches only the `spt rc` attach pump — the endpoint... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 81: REQ-INST-1 vs REQ-INST-13 ==================================================================================================== [A] REQ-INST-1 Title: endpoint ID vs instance split (adapter-agnostic ID) [B] REQ-INST-13 Title: Subnet-exclusive sync + per-endpoint subnet-membership list CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 82: REQ-CONTROLLER-LIVENESS-REAP vs REQ-ENDPOINT-CYCLE-HONEST ==================================================================================================== [A] REQ-CONTROLLER-LIVENESS-REAP Title: B-2 (REMOTE-TRUTH triage §B-2, REDUCED @bdc1242): a stale ONLINE+CONTROLLED stamp on a live-session perch self-heals — the info.json driven_by/controlled RECORD is made to match the broker's SINK-TABLE TRUTH. ROOT (persisted-stale-stamp class, doyle Q1): the livehost control reap gates on !has_session (reconcile_hosted_liveness), and a brain-only update KEEPS the session (REQ-UPD-3), so a controller stamp that went stale WHILE the session lived was never re-derived from broker truth. NOT a transport bug: (a) a persisted conn is the REQ-UPD-3 feature; (b) an idle-severed conn eventually EOFs via QUIC keepalive → handle_conn detach (path 1) — and the reason paths 1-3 previously failed to clean up was the B-1 broker floor-lock POISON WEDGE (cleanup panicked under the poisoned lock), now fixed. REDUCTION (doyle, my ground-truth): the prescription was 80% pre-built — converge_perch_stamps (broker.rs, REQ-HAZARD-CONTROL-STAMP-CONVERGENCE) ALREADY converges info.json driven_by/controlled to the broker's controller_by/has_controller on EVERY KIND_SESSIONS poll, and the livehost reconcile already TRIGGERS that poll per tick (query_live_session_endpoints). So NO new IPC query, NO new livehos [B] REQ-ENDPOINT-CYCLE-HONEST 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. 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 ... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 83: REQ-CLI-1 vs REQ-INST-5 ==================================================================================================== [A] REQ-CLI-1 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. [B] REQ-INST-5 Title: Two-tier context sync (live to all, project to same-project) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 84: REQ-HAZARD-STALE-INDEX-LOCK vs REQ-HAZARD-REGISTRY-STALE-CLEAN ==================================================================================================== [A] REQ-HAZARD-STALE-INDEX-LOCK Title: Sweep stale lockfiles on daemon boot (1.3) [B] REQ-HAZARD-REGISTRY-STALE-CLEAN Title: Stale registry entries degrade to fallback, never hard-fail (4.3) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 85: REQ-API-2 vs REQ-DOCS-6 ==================================================================================================== [A] REQ-API-2 Title: The api subcommand surface (bind/listen/poll/state/worker/boundary/...) [B] REQ-DOCS-6 Title: spt how-to : in-binary task-oriented agent instructions (anti-drift; quickstart prompts point agents at it) Doc: Quickstart: two agents exchange a message: - **Failure:** the per-subnet registry replicates `endpoint_id → [instances]` eventually-consistently across nodes. Under a partition or clock ske... [B] REQ-MESH-5 Title: Hard cutover from pairwise trust: delete peers.json + the is_trusted authorization path (no migration — expendable test fleet, re-pairs fresh under the new model, user decision 2026-06-08). Warn-on-change DEMOTED from a gate to an awareness notice anchored on machine_id (not label): 'machine M, last seen as K1, now presents K2' — fires the same event as the REQ-SUBNET-7 re-pair overwrite. The TrustStore/peers.json code and its call sites are removed, not left dead. CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 88: REQ-ATTACH-IDEMPOTENT-REPLAY vs REQ-STREAM-LIFETIME-CLASS ==================================================================================================== [A] REQ-ATTACH-IDEMPOTENT-REPLAY Title: The equal-generation lease rung is idempotent for the same connection. (ADR-0047 decision 3, AMENDING ADR-0044's ladder inside the equal-gen rung; hertz v0.39.4 field bug 4, PINNED via OBS breadcrumbs on authorized same-seam `daemon refresh` 2026-07-22 — gen+1 premise FALSIFIED.) TODAY: a Control/Take subscribe with same identity + same nonzero gen classifies 'same lease, silent re-take' (broker.rs equal-gen branch — correct, no revoke) but re-take = become_controller, which unconditionally takes+drops the prior seat (writer exits channel-closed) with NO same-conn check — designed for the dead-seat dispatcher-restart successor, it also fires against the SAME LIVE conn re-served 15ms apart by post-cycle dispatcher replay: the lease kills its own writer, the rc viewer freezes until detach+re-attach (the field 'update freezes PTYs'). FIX: keyed (endpoint/session, by, conn, gen) — same-conn equal-gen = IDEMPOTENT REPLAY: seat + writer PRESERVED, no controller-replaced, no second initial batch; breadcrumb answers decision=idempotent (additive vocab). Equal-gen DIFFERENT-conn keeps today's silent swap (the ADR-0038 fix-6 successor — must not regress); strictly-newer keeps loud supersessi Doc: 3. The equal-generation rung is idempotent for the same connection: [B] REQ-STREAM-LIFETIME-CLASS Title: RESCOPED at W2 activation 2026-07-22 (doyle verify-first, C3-retirement precedent): the CLEAN-CASE chain this REQ was minted for is ALREADY SHIPPED under REQ-STREAM-LEASE-CLASSES / ADR-0040 decision 6, verified against @7c0f12d — StreamLifetime is opener-declared and on the wire, serde-default Durable so N-1 openers keep exact today-semantics (msg.rs:810-836 + the additive-wire unit @msg.rs:1408); rc attach/view is the SOLE ConnectionBound opener (attach.rs:667-677); the broker binds the class at open (broker.rs:5689) and nethost carries it per StreamEntry (nethost.rs:1059-1076); the conn-exit sweep FINs each ConnectionBound stream toward its target and terminal-retires the local row while Durable rows never enter it (broker.rs:4118-4123/4227-4235/4429-4446); the target's serve_attach EOF arm runs detach_session_gen(serve_gen) so controller/viewer stamps clear on exactly one generation (attach.rs:580-597); the late-close gen guard (broker.rs:2179-2192, unit @8416) and converge_perch_stamps close the race; int coverage rides endpoint_lifecycle.rs:241. Building that again would re-implement shipped code — the C3 lesson. WHAT REMAINS, and what this REQ now owns: RESTART-REPLAY RE-ESTA Doc: Amendment 1 (2026-07-22, DAEMON-LIFECYCLE W2) — teardown authority is opener-declared class PLUS transport liveness, enforced at the three places decision 6 could not see: CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 89: REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK vs REQ-SEAT-LIFETIME-BOUNDED ==================================================================================================== [A] REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK Title: UPDATE-WEDGE round-4 (hertz RCA, root reproduced + source-pinned on Windows 2026-07-09): EVERY write on a physical broker connection rides ONE bounded + cancelable + poison-on-failure framed-write primitive — 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 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 ... [B] REQ-SEAT-LIFETIME-BOUNDED 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. 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 definitio... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 90: REQ-PAIR-8 vs REQ-PAIR-NTP-MULTIHOME ==================================================================================================== [A] REQ-PAIR-8 Title: NTP TOTP offset: the pairing ceremony queries NTP at ceremony time (both sides) and applies the derived offset to the TOTP calculation in-process only; system-clock fallback when NTP is unreachable (offline LAN pairing unaffected — NTP failure never blocks a pairing that succeeds today); never sets the OS clock; no background sync loop (M8 decision 18; field trigger: enlyzeam clock >1 min off exceeds the ±1 window) [B] REQ-PAIR-NTP-MULTIHOME Title: W1/D1 (JOIN-TRUTH): the ceremony NTP query reaches a server on EITHER IP family — `query_unix_secs` (ntp.rs) must iterate every address `to_socket_addrs()` resolves (not just the first) and bind a socket of the matching family per candidate (IPv4 addr → bind 0.0.0.0:0; IPv6 addr → bind [::]:0), first successful answer wins. ROOT (proven 3/3-FAIL via our exact code on enlyzeam): today `UdpSocket::bind(("0.0.0.0",0))` is v4-only and `send_to(&packet, server)` sends ONLY to the FIRST resolved addr — time.google.com resolves 4×AAAA before any A on a v6-first dual-stack box → the primary server is PERMANENTLY unreachable via our code (w32tm reaches it over v6), silently halving NTP redundancy (pool.ntp.org v4 carried everything; a DNS rotation making BOTH v6-first would zero it). Fix keeps the lazy-cache/TTL/fallback contract of REQ-PAIR-8 unchanged — only the socket/resolve leg changes. CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 91: REQ-DAEMON-5 vs REQ-RC-SINGLE-PUMP-BRAIN ==================================================================================================== [A] REQ-DAEMON-5 Title: Pump liveness: the peer pump writes a last-tick heartbeat consumed by daemon status / subnet status (decision 23 render legs in REQ-CLI-2/REQ-SUBNET-8); the daemon supervises the pump task — a panic is caught, logged loudly, and the pump restarts with capped backoff (≤5 min), so a 5.9-class death self-heals visibly instead of silently halving the daemon (M8 decision 23; field motivation: hfenduleam 2026-06-07 half-death) [B] REQ-RC-SINGLE-PUMP-BRAIN Title: RC-RENDER-TRUTH v0.38.1 fast-follow leg 2 (hertz v0.38.0 field repro 2, hertz RCA confirmed + doyle-accepted): plain `spt rc` constructs EXACTLY ONE pump Brain — the W1 truth probe (SessionProbe::connect, rc.rs ~1388/981-987, KIND_SESSIONS then drop) and establish_attach (~1464/1632) each build a real pump Brain today = two transient IPC reader threads/conns + a doubled user-visible 'PUMP_IPC_READER: spawned' banner per invocation (brain.rs:254 emits once per BrainConn::split_with_reader via cold_start_pump — the log site is NOT duplicated). FIX (hertz seam, ratified): carry the SessionProbe's Brain INTO establish_attach and re-query sessions on that same conn for freshness — do NOT suppress the log line and do NOT switch to Whole (the banner is truthful; the double construction is the defect). Qualified/session-confirmed paths (which skip the probe) and the reconnect loop (one fresh pump per attempt, correct) unchanged. Gate: impl — probe-Brain carry + same-conn freshness re-query; unit — probe-then-establish reuses the conn (construction-count observable); int — rc_attach_truth offline_row_over_live_session_attaches extended: capture stderr, assert PUMP_IPC_READER spawned count = CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 92: REQ-PAIR-3 vs REQ-SUBNET-4 ==================================================================================================== [A] REQ-PAIR-3 Title: Fetch current pairing code from any paired node [B] REQ-SUBNET-4 Title: Subnet membership mutations elevation-gated (create = seed reveal; join = trust-boundary enrollment) Doc: Product-surface amendment (2026-06-05 — M7 D3): CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 93: REQ-START-1 vs REQ-WAKE-RESUME-LEG ==================================================================================================== [A] REQ-START-1 Title: Adapters never resolve SPT_HOME; binary on PATH; api bridging only [B] REQ-WAKE-RESUME-LEG 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 CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 94: REQ-HAZARD-INJECT-CONTROL-COEXIST vs REQ-SUBNET-6 ==================================================================================================== [A] REQ-HAZARD-INJECT-CONTROL-COEXIST Title: SPINE INVARIANT (v0.13.0 keystone): the broker must accept INJECTED keystrokes into an spt-hosted PTY (the v0.11.0 raw direct-inject today; the ADR-0022 translation-binary choreography tomorrow) WHILE a live `spt rc` controller is attached to the SAME PTY, without (a) the operator losing control, (b) the endpoint latching ONLINE+CONTROLLED, or (c) the broker wedging. The injection inlet is PERMANENT — spt-claude-code requires keystroke injection — so this is root-caused + fixed at the PTY-injection layer, IN STEP with the ADR-0022 delivery redesign that formalizes the inlet. REOPENS the wedge facet of REQ-HAZARD-ATTACH-WEDGE: the v0.12.1 prove-don't-change covered only DEAD-CHILD backpressure, NOT the injection trigger (operator's signal — one injected keystroke succeeds, the next wedges → the single-threaded broker parks on a blocking PTY/loopback write after injection-induced harness output). REPRO-FIRST on the real dummy-harness fixture (NO theory): instrument to nail the exact blocking call before any fix. Fix candidates: non-blocking/fail-fast PTY write, split input/output, bounded-evicting. Mechanism shared with W2 — spt-core owns EVERY PTY write and applies an injected seque [B] REQ-SUBNET-6 Title: Trust lifecycle verbs, elevation-gated: spt subnet leave (membership exit) and spt subnet prune (removes a dead identity's trust + registry rows, killing its dead dials; trust mutation = security surface, REQ-PAIR-6 gate machinery) (M8 decisions 6-7) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 95: REQ-DAEMON-5 vs REQ-DAEMON-REFRESH ==================================================================================================== [A] REQ-DAEMON-5 Title: Pump liveness: the peer pump writes a last-tick heartbeat consumed by daemon status / subnet status (decision 23 render legs in REQ-CLI-2/REQ-SUBNET-8); the daemon supervises the pump task — a panic is caught, logged loudly, and the pump restarts with capped backoff (≤5 min), so a 5.9-class death self-heals visibly instead of silently halving the daemon (M8 decision 23; field motivation: hfenduleam 2026-06-07 half-death) [B] REQ-DAEMON-REFRESH Title: THE-FORKENING W4 (operator add 2026-07-14): `spt daemon refresh` — restart the daemon BRAIN without a binary swap and WITHOUT touching the broker: exactly the apply_staged brain-cycle path (brain stop -> respawn -> readiness trial -> promote, incl. the trial-drain drive REQ-UPDATE-TRIAL-DRAIN-DRIVE and viewer-only resume REQ-BRAIN-RESUME-NO-CONTROL-STEAL) minus the swap. Recovery verb for wedged brain-held state (field motivator 2026-07-14: endpoint bringup broken on a live daemon + deployah down — today's only remedy is a full daemon bounce that kills every PTY). Broker + PTYs survive by construction (handoff invariant). Failure = the existing trial rollback semantics (old brain resumes; refresh reports loud). Gate: unit — verb routes the brain-cycle without staging/swap preconditions; int — refresh on a live daemon with a hosted PTY: brain generation changes, PTY survives, endpoint stays attached; doc — daemon docs name refresh next to stop/start. Kin apply_staged (the path it reuses), REQ-UPDATE-TRIAL-DRAIN-DRIVE, REQ-BRAIN-RESUME-NO-CONTROL-STEAL. Doc: Self-update: **daemon refresh (`spt daemon refresh`)** — restart the **brain** in place, no binary swap, broker and every held PTY untouched: the routine-update handoff path minus the swap. The recovery verb for wedged brain-held state (broken endpoint bringup, a downed hosted agent) that previous... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 96: REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT vs REQ-RC-NEWLINE-PRESENTATION-TRUTH ==================================================================================================== [A] REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT Title: A clean `spt rc` attach to a LIVE spt-hosted (`endpoint run`) harness must DELIVER the harness's PTY output. KEYSTONE — 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 [B] REQ-RC-NEWLINE-PRESENTATION-TRUTH Title: W3 (v0.39.4 field bug 3 ROOT, pinned 2026-07-22): the rc client presents relayed bytes exactly as the emitter addressed them — KNOWN-HAZARDS 7.56. rc's with_vt_output enables ENABLE_PROCESSED_OUTPUT without DISABLE_NEWLINE_AUTO_RETURN (DNAR: zero grep hits tree-wide), so the operator's console translates relayed bare LF into CR+LF — a column reset the emitter never asked for; the /c/config field scraps are capture record t=1784714152501's bare-LF index presented through that mode. Relay byte-clean end to end (why both isolated rigs read NOT-REPRODUCED and the fresh viewer read clean — the synthesized repaint is CUP-absolute). FIX: DNAR joins the with_vt_output composition whenever processed/VT output is enabled; prior-mode capture/restore unchanged; Unix needs nothing (raw mode already clears OPOST/ONLCR). Regression: the W3 captured-bytes rig PAIR (vehicle console without DNAR = field-client model = RED pre-fix; with DNAR = GREEN post-fix) + the minimal LF column-preservation probe through an rc-mode console. Amends REQ-RC-WIN-VT-OUTPUT (#12); kin KH 7.55 + the W3 vehicle-fidelity clause (three console-mode-seam manifestations, one day). Attribution CLOSED when hertz confirms fixe Doc: 7.56 The rc client presents relayed bytes exactly as the emitter addressed them — console output modes are part of presentation truth `[REQ-RC-NEWLINE-PRESENTATION-TRUTH]`: - **Failure (paid-for — v0.39.4 field bug 3, the `/c/config` scraps, pinned 2026-07-22 via the W3 bisect):** rc's ... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 97: REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK vs REQ-STAMP-CONVERGENCE-ORDER ==================================================================================================== [A] REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK Title: UPDATE-WEDGE round-4 (hertz RCA, root reproduced + source-pinned on Windows 2026-07-09): EVERY write on a physical broker connection rides ONE bounded + cancelable + poison-on-failure framed-write primitive — 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 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 ... [B] REQ-STAMP-CONVERGENCE-ORDER Title: RETIRED at W2 verify-first (doyle 2026-07-22): the C3 poll-vs-reap ordering race (hertz emphasys RCA 2026-07-16, P1) was ALREADY CLOSED by REQ-CONTROL-STAMP-CONVERGENCE (ADR-0041 decision 4, REGISTRY-LIFECYCLE W2 build todlando 2026-07-17 — one day after the RCA). Code-verified chain: the exit-waiter reap (broker.rs stamp_reaped) bumps a per-endpoint StampSlot generation UNDER the stamp-write serial then clears (the comment names the emphasys C3 window); the KIND_SESSIONS handler snapshots stamp_gen under the log lock; converge_perch_stamps validates snap_gen under the same write serial and REFUSES stale (STAMP_STALE_SKIP) — a pre-reap snapshot can never relatch controlled=true. Full stage coverage rides that REQ: unit stale_snapshot_stamp_write_refused_after_reap_generation_bump + int endpoint_lifecycle.rs:488. Kept as a stable pointer; no build owed. CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 98: REQ-HAZARD-RC-ATTACH-TRUTH vs REQ-HAZARD-THRASH-GUARD-BLIND ==================================================================================================== [A] REQ-HAZARD-RC-ATTACH-TRUTH 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. 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 p... [B] REQ-HAZARD-THRASH-GUARD-BLIND Title: W3 (F030 hazard; paid-for: evidence #6, hall-bf ~12/min re-host NEVER tripped the C3(b) thrash guard — boot records were not ledger boundaries to the guard): the failure budget must count REAL attempts (ledger-derived: boot/turn records via the psyche perch ledger), not whatever it counted that let 12/min churn run invisibly. Red-first synthetic loop: a 12/min synthetic failure loop MUST trip the budget. Doc: 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]`: ### 7.31 The Psyche failure budget must count REAL per-event attempts — a resident rate-guard is blind to per-event churn `[REQ-HAZARD-THRASH-GUARD-BL... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 99: REQ-BROKER-OUTPUT-BEFORE-EXIT vs REQ-PICKER-CURRENT-DIR-LABEL ==================================================================================================== [A] REQ-BROKER-OUTPUT-BEFORE-EXIT Title: RC-RENDER-TRUTH W3 (ADR-0043 decision 1, hertz stale-glyphs RCA leg 1 P0): the PTY drain/output writer is the SOLE FIFO sequencer for terminal Output + Exit per attach sink — 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. 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 contr... [B] REQ-PICKER-CURRENT-DIR-LABEL Title: A-2/A-3 (F029, operator, semantic pair): the Choose-project rows must self-identify the CURRENT DIR. build_project_choices (picker/model.rs:322-352). A-2: when the run cwd IS already a history dir the `Here:` row is (correctly) suppressed by the dedup (REQ-PICKER-CHOOSE-DEDUP-ALL), but the matching history row rendered bare `r.display` with no cwd affordance — mark it ` (CURRENT DIR)`. A-3: the not-in-history current-dir row changes from `Here: ` to `CURRENT DIR --> `, deriving the display the SAME way the history refs do (folder tail; honest fallback to the raw path when underivable). `cwd` payload unchanged. Grep-tests rule: 3 `starts_with("Here: ")` asserts (model.rs) + a `Here: /here` render assert (view.rs) are behavior assertions on the OLD label. See triage A-2/A-3. CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 100: REQ-HAZARD-SINGLE-PATH-SOURCE vs REQ-MANIFEST-1 ==================================================================================================== [A] REQ-HAZARD-SINGLE-PATH-SOURCE Title: Single path/registry source of truth; no layout ambiguity (6.1) [B] REQ-MANIFEST-1 Title: Per-adapter manifest with adapter_name and min_spt_core_version Doc: What is NOT in the manifest (spt-core-owned): CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 101: REQ-HAZARD-STALE-SIGNOFF-SENTINEL vs REQ-SEAM-RESUME ==================================================================================================== [A] REQ-HAZARD-STALE-SIGNOFF-SENTINEL Title: Stale signoff sentinel does not kill a fresh start (3.2) [B] REQ-SEAM-RESUME Title: resume-session seam (fresh-with-preload / continue-existing) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 102: REQ-HAZARD-CONPTY-DSR vs REQ-HAZARD-EBUSY-RENAME ==================================================================================================== [A] REQ-HAZARD-CONPTY-DSR Title: ConPTY reader must auto-answer DSR (ESC[6n) or all child output stalls (5.5) [B] REQ-HAZARD-EBUSY-RENAME Title: tmp-write + atomic-rename + retry on Windows EBUSY (5.2) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 103: REQ-ENDPOINT-TEARDOWN-AUTHORITY vs REQ-PSYCHE-LEGACY-RESIDENT-SWEEP ==================================================================================================== [A] REQ-ENDPOINT-TEARDOWN-AUTHORITY Title: TEARDOWN-AUTHORITY W1 (ADR-0045; two hertz field RCAs 2026-07-19, doyle code-verified + ruled): ONE shared topology-aware broker-teardown primitive behind BOTH `endpoint shutdown` and `endpoint stop`. Today both verbs stamp state they never cause: cmd_shutdown (cli.rs:3979) = remove ready marker + cmd_rest(Suspend) — the Suspend edge (resting.rs daemon_rest_event_with_liveness -> apply_event -> cascade_shells_on_edge -> advertise) fires echo + shell cascade + advertise and NEVER touches a session, even though the verb already probed broker-session truth (cli.rs:3797-3804 has_live_session_honest) to force from=alive; cmd_stop (cli.rs:7071) = marker + unregister_address + terminal_normalize + advertise, whose own comment calls it a DEFINITIVE death observation it fabricates. Field: broker retains the whole subtree (adapter -> node -> harness -> nested resumed harness + MCP children; the surviving `spt api listen` is a DESCENDANT, so a direct-child kill misses it). PRIMITIVE (ordered, ADR-0045 decisions 1/6/7/9): resolve broker SessionInfo -> dedicated sid/endpoint-keyed broker kill claiming NO controller (NOT Brain::attach()+kill_session(), brain.rs:622 require_session() = controller Doc: 7.48 At most one input-capable controller lease per PTY session — 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... [B] REQ-PSYCHE-LEGACY-RESIDENT-SWEEP Title: W5 (F030; doyle+perri 2026-07-04): a dirty daemon upgrade from <=v0.24.0 strands a RESIDENT psyche wrapper the OLD daemon spawned — and F-030 W4's nested-`ready` resolution fix CONVERTED that wrapper's accidental self-reap into a permanent HANG. The pre-W3 wrapper's only spt IPC is `spt ready -psyche --once` (BLOCKING, no internal timeout); pre-W4 that hit READY_FAIL on a multi-subnet home → the wrapper exit-4'd (accidental reap). Post-W4 the nested id resolves cleanly → the wrapper REGISTERS then BLOCKS FOREVER on its first post-upgrade poll: no exit, no psyche_host_error, no CPU (KH 2.6 invisible-loop class, one level up). Post-W3 core has no residency machinery to reap it. FIX: a ONE-SHOT legacy-resident sweep at BRAIN START (never per-reconcile/periodic — burying residency-era machinery, not resurrecting it). GUARD = adapter-AGNOSTIC (glue-model): resurrect the retired reap_orphan_psyches LOGIC — for each self-perch live-agent id derive `-psyche` and kill iff (a) exe basename == the adapter's MANIFEST-declared psyche program (normalize_basename, never a hardcoded adapter name) AND (b) cmdline contains the id marker `-psyche` AND (c) pid alive; any unreadable sig CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 104: REQ-NOTIF-UPDATE-ROW-VERSION-RETIRE vs REQ-SPOOL-TAKE-AUDIT ==================================================================================================== [A] REQ-NOTIF-UPDATE-ROW-VERSION-RETIRE Title: An update-available notif row minted by a node running PRE-0.40.0 spt is retired on the version the running node has ALREADY reached — because the keyed catch-up dismissal is structurally blind to it. (DAEMON-LIFECYCLE W2 RIDER, operator-ordered; doyle root-caused end to end 2026-07-22 on the live box.) FIELD CHAIN, verified: GRAVITY-NVDA-PC (f15d837b, BIGNET) runs pre-0.40.0 spt, whose legacy producer mints the update notice SUBNET-scoped with NO coalesce key (the scoped+keyed producer shipped in 0.40.0). The row replicated fleet-wide. The modern catch-up dismissal (REQ-NOTIF-SEAM-DISMISS, pump/update.rs dismiss_staged_notif_if_caught_up) dismisses ONLY by coalesce key, so a keyless row can never be retired by it: a FULLY-UPDATED node holds a live 'v0.41.0 available' row forever, surfacing once per endpoint at every boundary (observed: todlando ~19:54 + Librarian/Athenaeum-Library; store showed seen=2, undismissed, the only undismissed update row in the whole history). FIX: a version-grounded retirement sweep at the EXISTING catch-up site, same per-tick per-subnet cadence, running ALONGSIDE the key path (which stays PRIMARY — belt-and-braces, not a replacement): dismiss any UNDISM [B] REQ-SPOOL-TAKE-AUDIT Title: W5 (LIFECYCLE-TRUTH, RCA cost: proving WHO took delivered=1 rows burned an hour): the spool records the taker per row — leg enum (relay-backlog / hook-poll / idle-inject / psyche) + sid/pid + taken_at ms — surfaced by a --json debug read. Additive column, no schema break (delivered rows already retained). CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 105: REQ-NOTIF-UPDATE-ROW-VERSION-RETIRE vs REQ-START-2 ==================================================================================================== [A] REQ-NOTIF-UPDATE-ROW-VERSION-RETIRE Title: An update-available notif row minted by a node running PRE-0.40.0 spt is retired on the version the running node has ALREADY reached — because the keyed catch-up dismissal is structurally blind to it. (DAEMON-LIFECYCLE W2 RIDER, operator-ordered; doyle root-caused end to end 2026-07-22 on the live box.) FIELD CHAIN, verified: GRAVITY-NVDA-PC (f15d837b, BIGNET) runs pre-0.40.0 spt, whose legacy producer mints the update notice SUBNET-scoped with NO coalesce key (the scoped+keyed producer shipped in 0.40.0). The row replicated fleet-wide. The modern catch-up dismissal (REQ-NOTIF-SEAM-DISMISS, pump/update.rs dismiss_staged_notif_if_caught_up) dismisses ONLY by coalesce key, so a keyless row can never be retired by it: a FULLY-UPDATED node holds a live 'v0.41.0 available' row forever, surfacing once per endpoint at every boundary (observed: todlando ~19:54 + Librarian/Athenaeum-Library; store showed seen=2, undismissed, the only undismissed update row in the whole history). FIX: a version-grounded retirement sweep at the EXISTING catch-up site, same per-tick per-subnet cadence, running ALONGSIDE the key path (which stays PRIMARY — belt-and-braces, not a replacement): dismiss any UNDISM [B] REQ-START-2 Title: Harness-hosted startup: api seed then listen CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 106: REQ-HAZARD-DAEMON-STOP-BARRIER vs REQ-HAZARD-ENDPOINT-LIFECYCLE ==================================================================================================== [A] REQ-HAZARD-DAEMON-STOP-BARRIER 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) [B] REQ-HAZARD-ENDPOINT-LIFECYCLE 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. 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 pat... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 107: REQ-SEAM-POSTSPAWN vs REQ-START-2 ==================================================================================================== [A] REQ-SEAM-POSTSPAWN Title: post-spawn / api bind seam with boot nonce [B] REQ-START-2 Title: Harness-hosted startup: api seed then listen CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 108: REQ-ENDPOINT-ONLINE-TRUTH vs REQ-HAZARD-RC-ATTACH-ONLINE-RACE ==================================================================================================== [A] REQ-ENDPOINT-ONLINE-TRUTH Title: REGISTRY-LIFECYCLE W2 (ADR-0041 decisions 1+2, emphasys C1 P0): ONLINE is earned, not declared — cmd_listen stamps status=online only from actual persisted state + hosting authority, never from manifest psyche_init capability alone (no more ready_agent/controllable=false hybrid rows born online-authoritative); livehost reconcile SPLITS control cleanup (clear controlled/driven_by/viewer_count for EVERY endpoint absent from session truth, regardless of state/controllable) from offline classification (live_agent+controllable gate unchanged); legacy hybrid rows self-heal after a SUCCESSFUL broker query only (broker failure is never interpreted as an empty session set); terminal signoff/owner-loss = atomic CAS-guarded offline + ready/address removal WITHOUT overloading soft api session-end (/clear preserves the live listener). Gate: impl — creator gate + reconcile split + self-heal + terminal path; unit — creator refuses capability-only online, cleanup clears stamps on state-quirk rows, broker-failure never mass-offlines; int — dead-PID hybrid row does NOT survive a reconcile cycle (the immortal-row regression); doc — ADR-0041. 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 ... [B] REQ-HAZARD-RC-ATTACH-ONLINE-RACE Title: `spt endpoint run` in an ATTACH/VIEW terminal action attaches BEFORE the freshly-spawned endpoint is online, so the attach races (or outright loses to) the harness bind. ROOT (doyle /diagnose, code-grounded): cmd_endpoint_run (cli.rs) does launch_harness_brokered_in -> (if start: return) -> run_attach with NO await-online between them. launch_harness_brokered_in returns once the harness PROCESS is spawned, but the broker-PTY bind (info status -> STATUS_ONLINE + the live session) lands ASYNC. Both picker attach paths route here with start=false (RunMode::Attach -> cmd_endpoint_run start=false,view=false): Start-now catches the endpoint mid-bringup -> run_attach attempts + loses the handshake race; Resume-from-history catches it still fully OFFLINE -> run_attach's status-gate (REQ-HAZARD-RC-ATTACH-FAILFAST) short-circuits 'offline - nothing to attach' and NEVER attempts. SAME root, two faces (the W4 attach-by-default surfaced both; an online endpoint is unaffected - the picker returns Outcome::Attach, not Run). FIX: in cmd_endpoint_run, when the terminal action is attach/view (NOT start), AWAIT the endpoint online between launch_harness_brokered_in success and run_attach - poll spt_s CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 109: REQ-INSTALL-10 vs REQ-START-5 ==================================================================================================== [A] REQ-INSTALL-10 Title: Windows at-logon autostart runs the daemon in the background with no persistent window: the scheduled task launches `spt daemon start` (which spawn_detaches a console-less DETACHED_PROCESS daemon and exits) rather than the foreground `spt daemon run` — 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) [B] REQ-START-5 Title: Adapter-agnostic harness-hosted seed + bind-time adapter/profile resolution (ADR-0021): `api seed` carries only parent_pid + session_id (+ optional cwd), no --adapter — a pure "a harness session exists at this pid" record; --adapter becomes an OPTIONAL override across the whole api group (an explicit name[:profile] for adapter dev, never required). Omitted, listen/poll resolve the owning adapter/profile AT BIND as a pure read against the live registry — never a seed-time snapshot that can drift: seed parent_pid → exe basename → host_binaries candidate set (REQ-MANIFEST-8) → active-profile pointer (REQ-INSTALL-12) primary, else greatest-registered_at_ms candidate base profile (name-asc tie) → friendly zero-match error. Covers BOTH LiveAgent (listen) and ReadyAgent (poll) bringup. Restores legacy parity: `$LIVE start ` → `$SPT listen ` with no mandatory --adapter, one generic SessionStart hook per harness binary. (v0.9.0) Doc: Startup flows (the two topologies): **Harness-hosted (e.g. spt-plugin; the harness binary is user-launched, harness is the parent).** Key constraint: the SPT *live agent* does not exist until the agent invokes start — the `live_id` isn't chosen at session boot, and `$LIVE start` is itself invoked ... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 110: REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT vs REQ-HAZARD-PTY-INPUT-WRITER-WEDGE ==================================================================================================== [A] REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT Title: A clean `spt rc` attach to a LIVE spt-hosted (`endpoint run`) harness must DELIVER the harness's PTY output. KEYSTONE — 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 [B] REQ-HAZARD-PTY-INPUT-WRITER-WEDGE Title: Pasting into an `spt rc` session WEDGES the broker — after a paste the operator can no longer type AND can no longer attach to NEW or EXISTING sessions (`brain IPC read deadline`). ROOT (doyle /diagnose, code-grounded): the operator-keystroke path rc -> net-stream Input -> serve_attach (attach.rs:197 brain.send_effect) -> KIND_INPUT -> broker dispatch loop (broker.rs:1091) -> dispatch_input (broker.rs:1459) -> session.write_input(&bytes) runs SYNCHRONOUSLY on the broker request-handling thread. W1b (REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE) released the journal lock across the effect (fix 1) + made PtyWrite ephemeral/no-fsync (fix 3) but EXPLICITLY DEFERRED fix (2) — bound/fail-fast the PtyWrite itself. A single keystroke never fills the ConPTY input buffer; a PASTE BURST does -> write_input blocks -> the dispatch thread cannot service the next frame (a re-attach subscribe, a become_controller restore-write, an inject-floor flush) -> wedge. Not a bug-2 regression (the byte path funnels to the same write_input; paste just reliably fills the buffer). FIX (doyle design, V0.13.0-P0-PTY-INPUT-WRITER-DESIGN.md, CONTEXT L33 broker-owns-PTY/minimal + L435 SessionSurface + single-writer pattern) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 111: REQ-PICKER-3 vs REQ-PICKER-ONLINE-ACTION ==================================================================================================== [A] REQ-PICKER-3 Title: A self-owned subnet row reconciles its status to the LIVE roster: a Subnet-category row whose endpoint_id overlaps a local (is_local) roster id is self-owned (this node hosts it), so its status square is OVERRIDDEN with the live roster status — the WAN registry snapshot (wansend::load_snapshots) is a periodically-advertised, independently-stale projection, while the local roster (p.alive) is ground truth for an endpoint this node hosts. One status square per endpoint (CONTEXT.md:348-350 — nothing licenses opposite squares for one endpoint across its Local vs Subnet listings). A reconcile pass in data.rs after the local_rows + subnet_rows gather; BOTH category listings are preserved (Local + Subnet are legitimately distinct views — you are in your own subnet), only the STATUS is unified. (v0.10.0) [B] REQ-PICKER-ONLINE-ACTION Title: The `spt endpoint run` picker shows the correct action for an ALREADY-ONLINE endpoint — Attach, NOT 'Start now' (operator-raised v0.12.0 real-harness finding). Symptom: the picker offers 'Start now' for endpoints that are already online. ROOT TBD — investigate the status→action mapping (v0.10.0 PICKER-1 four-state status, picker/model.rs): is it reading live/online state correctly, or rendering stale/wedged broker state (i.e. partly a symptom of the broker wedge / status=online latch)? Fix so online → Attach. (v0.12.1) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 112: REQ-HAZARD-REGISTRY-STALL vs REQ-RC-RECONNECT ==================================================================================================== [A] REQ-HAZARD-REGISTRY-STALL Title: REGISTRY-LIFECYCLE W1 (KNOWN-HAZARDS 7.44, hertz post-close v0.36 RCA — the umbrella conformance seam): streams and seats on a long-lived connection have BOUNDED lifetime. The hertz regression seam verbatim: real long-lived pump + dispatcher integration, N registry-only rounds over ONE persistent connection, asserting (a) dispatcher-eligible Registry rows plateau O(active) not O(N); (b) physical stream/subscriber/seat counts plateau after completion CROSS-FAMILY (sync/update seats included, not just Registry); (c) snapshot writes O(feeds) not O(chunks x record-kinds); (d) brain refresh produces ZERO historical Registry replay subscriptions; (e) zero CONN_WRITE_POISONED / replay-write-failed events; (f) broker thread count returns to a bounded baseline. HEAVY nextest group at birth (spawns a real daemon tree). Binding: redispatch D1/D1b + REDISPATCH-STALL T1-T7 + mesh-recovery legs green every leg (retire machinery + registry gate = substrate). Gate: int — the seam above; doc — KNOWN-HAZARDS 7.44. Doc: 7.43 One wedged stream subscriber must NEVER stall stream serving — replay halts at the first failed write, a poisoned subscriber is removed, and recovery machinery must not manufacture new replay victims `[REQ-HAZARD-REDISPATCH-STALL]`: ### 7.44 Streams and seats on a long-lived connection must h... [B] REQ-RC-RECONNECT 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 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), out... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 113: REQ-HAZARD-LIVEHOST-BOOT-LIVENESS-GATE vs REQ-RESUME-REAP-PRIOR-HARNESS ==================================================================================================== [A] REQ-HAZARD-LIVEHOST-BOOT-LIVENESS-GATE Title: B5: `spt daemon start` does NOT revive phantom Psyches for dead-but-online-latched perches. Today reconcile_once (livehost.rs:285) spawns a Psyche per status=online live_agent perch at boot WITHOUT verifying the harness child / {id}-psyche is actually alive — so a Cold start after an unclean stop revives N psyches for N dead-but-latched perches (3 psyches for 3 dead perches). FIX: gate the boot psyche-spawn on real child-liveness — a perch with NO live broker session (the B2 reconcile signal) is marked OFFLINE at boot instead of hosted, so a dead-harness perch is never revived. Shares the B2 reconcile loop (this is its boot-gate arm); composes with B2's honest latch. Also closes wall-a's psyche_host_error gap (residency-confirm does not run at boot tick-1, livehost.rs:395-441 / 257-263). (v0.12.0) [B] REQ-RESUME-REAP-PRIOR-HARNESS Title: B4 (F028, hall-b diagnosis, verified 0.22.0): `--resume` respawns a SECOND harness onto the SAME session without reaping the first. Observed live: resume of b4421cf9 spawned pid 34432 while gen1 (250376) kept running — two claude.exe stacks, one session id. FIX: resume must reap/refuse when the session already has a live harness. See triage B4. CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 114: REQ-HAZARD-LIVEHOST-BOOT-RACE vs REQ-SHELL-1 ==================================================================================================== [A] REQ-HAZARD-LIVEHOST-BOOT-RACE Title: The brain's daemon-hosted Psyche lifecycle surfaces a host-FAILURE on the live perch (harness-diagnosable) and runs net-INDEPENDENTLY. When reconcile_once→host_one→spawn_psyche fails for a state=live_agent+status=online endpoint (e.g. the adapter's psyche binary absent from its install dir, REQ-INSTALL-11), the failure MUST be written to the perch info.json as a CURRENT-STATE field (reason + ts + attempt count; overwritten each 5s retry, CLEARED on successful host) and surfaced by `spt endpoint list`/status — never left as an eprintln on the brain's invisible stderr where a harness reading only perch state is blind. status=online stays authoritative (agent reachable; only the Psyche is missing — brain-restart rehydrate legitimately has online-without-Psyche windows), so this is a SEPARATE psyche-host-health field, never a status de-stamp. Net-independence is a locked-in invariant: spawn_live_host (brainproc.rs:230) reaches the reconcile and hosts the Psyche on a net-less/unpaired/peer-pump-STALLED node, proven by a REAL detached-daemon E2E (real broker→brain-child, real api seed+listen, real install-dir psyche binary). spt-core SURFACES the failure; the adapter owns fixing its pack [B] REQ-SHELL-1 Title: Shell hosting machinery: shell perch under the owner (type/owner/adapter_name/status/alias), broker-launched binary + api bind local-link handshake, the three channels (command durable, text+file durable + progress-queryable, sensory REST-only never spooled + dropped-unless-owner-live), owner exclusivity (CONTEXT Shell model) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 115: REQ-SEAM-UPDATE vs REQ-UPDATE-ADAPTERS-VERB ==================================================================================================== [A] REQ-SEAM-UPDATE Title: Adapter-update avenue (file-pull / delegated command) [B] REQ-UPDATE-ADAPTERS-VERB 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). 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 alon... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 116: REQ-HOSTING-AUTHORITY-CONTROLLABLE vs REQ-START-3 ==================================================================================================== [A] REQ-HOSTING-AUTHORITY-CONTROLLABLE Title: RC-RENDER-TRUTH v0.38.1 fast-follow leg 3 (hertz todlando immortal-hybrid trace, doyle fork ruling 2026-07-18 = controllable-authority): ONE hosting authority — persisted `state` stays the durable endpoint TYPE (REQ-EP-6 open type system; establish_perch's prior-type preserve at startup.rs:346-350 is INTENTIONAL and stays), `controllable==Some(true)` is the source-definitive broker-PTY authority. FIELD ROOT (C1 coverage gap, not new family): an spt-hosted bind over a prior ready_agent perch preserves state=ready_agent while stamping controllable=true + online -> livehost restart_resume_gate (508-534) Skip's state!=live_agent so the orphan never resumes, and reconcile's C1 dead-pid hybrid heal predicate was scoped controllable=false so controllable=true escapes -> immortal dead-PID ready_agent hybrid, latch-driven Active projection (todlando field state; rc's no-session refusal was TRUTHFUL). FIX (hertz refinement, ratified): remove the state rejection from restart_resume_gate; reconcile routes only non-live_agent && controllable!=Some(true) through the PID-model hybrid heal, while controllable==Some(true) rows fall through BROKER-SESSION truth (orphan with ledger/adapter material = Doc: Requirements: ## Amendment (RC-RENDER-TRUTH v0.38.1 leg 3, doyle ruling 2026-07-19): one hosting authority — the online-earn authority splits by hosting topology [B] REQ-START-3 Title: spt-hosted startup: spawn-session then api bind (no file) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 117: REQ-ATTACH-RESIZE-REPAINT vs REQ-HAZARD-RC-ATTACH-ONLINE-RACE ==================================================================================================== [A] REQ-ATTACH-RESIZE-REPAINT Title: RESCOPED 2026-07-22 by ADR-0047 Amendment 2 (doyle-ratified): this REQ no longer asserts a defect. Its original claim — that the attach-stream resize never arms the presentation barrier, and that this is v0.39.4 field bug 3's root — is RETRACTED; the W3 bisect falsified it three ways (static trace send_attach_resize -> serve_attach Resize arm -> brain.resize -> KIND_RESIZE -> dispatch_resize -> begin_resize_serialized; the synthetic sibling's pass; the broker's dynamic trace attach -> RESIZE_DISPATCH -> SYNC_PUSH{controller=true} with no RESIZE_SYNC_DROP) and then pinned the real root in the rc CLIENT's console mode (REQ-RC-NEWLINE-PRESENTATION-TRUTH / KNOWN-HAZARDS 7.56). The ADR-0047 Amendment 1 DECISION (geometry change = one transaction, one exit shape, any entry) stands on its own merits and is not disturbed. WHAT THIS REQ NOW OWNS — the captured-shape regression apparatus that the bisect proved worth keeping: hertz's ENLYZEAM production capture + its integrity pins (*.log -text, the fixture README sha256 table, per-record length refusal in both the player and the reader); the capture-player replay vehicle; and the probe ladder that makes any verdict from it mean something — v Doc: 7.54 A geometry change invalidates the client's painted cells by itself — every resize entry emits the authoritative repaint, and an attach's old-geometry repaint is never the final word `[REQ-ATTACH-RESIZE-REPAINT]`: - **Failure (paid-for THREE TIMES — v0.39.3 geometry-epoch, v0.39.4 p... [B] REQ-HAZARD-RC-ATTACH-ONLINE-RACE Title: `spt endpoint run` in an ATTACH/VIEW terminal action attaches BEFORE the freshly-spawned endpoint is online, so the attach races (or outright loses to) the harness bind. ROOT (doyle /diagnose, code-grounded): cmd_endpoint_run (cli.rs) does launch_harness_brokered_in -> (if start: return) -> run_attach with NO await-online between them. launch_harness_brokered_in returns once the harness PROCESS is spawned, but the broker-PTY bind (info status -> STATUS_ONLINE + the live session) lands ASYNC. Both picker attach paths route here with start=false (RunMode::Attach -> cmd_endpoint_run start=false,view=false): Start-now catches the endpoint mid-bringup -> run_attach attempts + loses the handshake race; Resume-from-history catches it still fully OFFLINE -> run_attach's status-gate (REQ-HAZARD-RC-ATTACH-FAILFAST) short-circuits 'offline - nothing to attach' and NEVER attempts. SAME root, two faces (the W4 attach-by-default surfaced both; an online endpoint is unaffected - the picker returns Outcome::Attach, not Run). FIX: in cmd_endpoint_run, when the terminal action is attach/view (NOT start), AWAIT the endpoint online between launch_harness_brokered_in success and run_attach - poll spt_s CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 118: REQ-CONTROLLER-LIVENESS-REAP vs REQ-GOSSIP-CONTROLLED-CROSS-NODE ==================================================================================================== [A] REQ-CONTROLLER-LIVENESS-REAP Title: B-2 (REMOTE-TRUTH triage §B-2, REDUCED @bdc1242): a stale ONLINE+CONTROLLED stamp on a live-session perch self-heals — the info.json driven_by/controlled RECORD is made to match the broker's SINK-TABLE TRUTH. ROOT (persisted-stale-stamp class, doyle Q1): the livehost control reap gates on !has_session (reconcile_hosted_liveness), and a brain-only update KEEPS the session (REQ-UPD-3), so a controller stamp that went stale WHILE the session lived was never re-derived from broker truth. NOT a transport bug: (a) a persisted conn is the REQ-UPD-3 feature; (b) an idle-severed conn eventually EOFs via QUIC keepalive → handle_conn detach (path 1) — and the reason paths 1-3 previously failed to clean up was the B-1 broker floor-lock POISON WEDGE (cleanup panicked under the poisoned lock), now fixed. REDUCTION (doyle, my ground-truth): the prescription was 80% pre-built — converge_perch_stamps (broker.rs, REQ-HAZARD-CONTROL-STAMP-CONVERGENCE) ALREADY converges info.json driven_by/controlled to the broker's controller_by/has_controller on EVERY KIND_SESSIONS poll, and the livehost reconcile already TRIGGERS that poll per tick (query_live_session_endpoints). So NO new IPC query, NO new livehos [B] REQ-GOSSIP-CONTROLLED-CROSS-NODE Title: B7 (F028, operator, cross-node): a remote endpoint's CONTROLLED state is not rendered. Evidence: ball-b ONLINE + CONTROLLED on ENLYZEAM (local view), but HFENDULEAM renders remote ball-b as plain ONLINE (both 0.22.0). The F-026 #4 gossiped any-controller datum (REQ-GOSSIP-ADAPTER-PROJECTS controlled bool) either isn't SENT for the locally-controlled case, isn't APPLIED on the receiving row, or DECAYS. Local leg confirmed fine (sibling hall-b renders blue-glyph correctly); the gap is the REMOTE leg. perri's validation had this ENV-BLOCKED — two live nodes now available to RCA. FIX: RCA sender-side (is controlled gossiped when locally-controlled?) / receiver-render (does from_resource_row surface it?) / decay, then lock with a cross-node int. See triage B7. CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 119: REQ-HAZARD-DAEMON-STOP-REAP vs REQ-PSYCHE-CRASHLOOP-BACKOFF-SHUTDOWN ==================================================================================================== [A] REQ-HAZARD-DAEMON-STOP-REAP Title: Breap: `spt daemon stop` REAPS the spt-hosted children it spawned — no orphaned psyche/harness processes. Today a stop leaves ~8 orphaned claude-spt-psyche.exe + spt.exe: Psyches are spawned DETACHED (runtime.rs:342-356, the Child is dropped — 'Detached' ~349) and the livehost stop flag Arc is NEVER raised (brainproc.rs:227-230 holds it 'for symmetry'). FIX: on stop, raise the livehost stop flag AND kill the spawned psyche/spt-hosted children — via a Windows job object / Unix process-group so the children die with the daemon (not detached-immortal). Folds with B3 (both the stop path). (v0.12.0) [B] REQ-PSYCHE-CRASHLOOP-BACKOFF-SHUTDOWN Title: C3 (F028, perri F-h): psyche wrapper crash-loop has no backoff, and `endpoint shutdown` misses a wedged wrapper. A probe psyche crash-looped ~3 boots/sec for ~30min (CC died instantly on the untrusted owlery cwd; ledger hit ordinal 5358) — 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. CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 120: REQ-REL-2 vs REQ-UPD-6 ==================================================================================================== [A] REQ-REL-2 Title: Release asset set consumable by the self-updater: platform binaries, SHA256SUMS, SignedRelease metadata, manifest schema, mock-adapter zip; tag-triggered cross-repo pipeline Doc: Release runbook: [B] REQ-UPD-6 Title: Platform-targeted update sets and debug rollout: signed multi-platform update metadata, recipient platform selection, channel-scoped monotonic counters, debug-channel opt-in via release-key overlay, local staging plus pull-based peer propagation, and maintainer-only convergence tooling (ADR-0016) Doc: Build plan — `xtask debug-converge` (deferred follow-up): // Debug rollout runbook: CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 121: REQ-HAZARD-CONTROLLER-LEASE vs REQ-TERM-2 ==================================================================================================== [A] REQ-HAZARD-CONTROLLER-LEASE Title: RC-RENDER-TRUTH W2 (KNOWN-HAZARDS 7.48 — umbrella conformance seam for ADR-0044): at most one input-capable controller lease per PTY session; takeover revokes atomically and loudly; input is fenced to the active lease; node identity is never a lease. The full hertz 8-step deterministic two-loopback-client broker regression rides verbatim: A subscribes Control from node N and controls; B subscribes Take from the SAME node with a different lease; A receives Displaced{by:N} then terminal stream completion (rc exits via existing PumpEnd::Displaced); output post-takeover reaches B not A; A's Input+Resize post-takeover mutate nothing; B's both apply; controlled/driven_by metadata identifies B with exactly one controller slot; a separate equal-lease/equal-generation replay test proves genuine dispatcher recovery remains silent and never self-displaces. Gate: int — the matrix; doc — KNOWN-HAZARDS 7.48. Doc: 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]`: ### 7.48 At most one input-capable controller lease per PTY session — takeover revokes atomically an... [B] REQ-TERM-2 Title: session-surface abstraction; send-keys + send-line injection CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 122: REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL vs REQ-WAKE-RESUME-LEG ==================================================================================================== [A] REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL 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] REQ-WAKE-RESUME-LEG 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 CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 123: REQ-ADAPTER-ADD-SURFACE-ERRORS vs REQ-MANIFEST-8 ==================================================================================================== [A] REQ-ADAPTER-ADD-SURFACE-ERRORS Title: Bug #1: adapter add runs the install-as-first-update via conduct (cli.rs:6963) which on a non-zero exit prints only the exit code and DISCARDS the subprocess stdout/stderr, so the real error is invisible (the failure itself does propagate). Fix: include out.stderr/stdout in the ADAPTER_INSTALL_FAIL message (mirror run_update_post_step). Operator ruling: ALSO run the [update.post] composite step at install-time (today it runs only on explicit adapter update), so an install both surfaces detail and completes the delegated post-step. See docs/NEXT-MILESTONE-BUG-TRIAGE.md #1. [B] REQ-MANIFEST-8 Title: [adapter] host_binaries declares the harness executable basenames a kind="harness" adapter hosts agents inside (e.g. host_binaries = ["claude"]); bind-time pid→exe-basename match (case-insensitive, .exe-stripped) over the seed's parent_pid selects the candidate adapter set; zero matches → a friendly error naming the binary + the --adapter escape hatch. Additive + N-1-safe: optional Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] (omitted-serialized like shortcut_basename, old manifests parse clean); manifest.schema.json regenerated from the derive (ADR-0001, CI drift-gated). The match-key for ADR-0021 adapter-agnostic bind-time resolution. (v0.9.0) Doc: Startup flows (the two topologies): **Bind-time adapter/profile resolution (ADR-0021).** Because the seed is adapter-agnostic, `listen`/`poll` resolve the owning adapter/profile when they bind, as a pure read — never a seed-time snapshot that could drift. `--adapter ` is an **optio... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 124: REQ-ACTIVITY-LIST-JSON vs REQ-RUN-ID-REUSES-ADAPTER ==================================================================================================== [A] REQ-ACTIVITY-LIST-JSON Title: `spt endpoint list --json` carries a per-endpoint `activity` (busy|idle) key, for consumers surveying the idle/busy state of many endpoints at once (ADR-0048 decision 1, roster pull avenue; operator addition 2026-07-24). Additive key, N-1-safe; kin the flynn 2026-07-06 last-active/description/adapter list-enrichment seed (same additive posture, may ride together). Doc: `--json` catalog: | Command | Top-level shape | |---|---| | `endpoint list` | `{ self, subnets[], local[] }` — `self`: `{id, status, ready, alive, unbound, description, psyche_host_error, translation_fault?}`; `subnets[]`: `{name, endpoints[]}` where each endpoint is `{id, node, node_label, status... [B] REQ-RUN-ID-REUSES-ADAPTER Title: D-1 (REMOTE-TRUTH triage §D-1): `spt endpoint run --id ` with NO --adapter, when names an EXISTING perch, REUSES that perch's recorded info.adapter and runs NON-INTERACTIVELY — instead of always falling to the picker as a create-new prefill (an existing endpoint retyping its own adapter, or being sent to a create-new flow, is the operator wart). ROOT (certain, no design tension): the cli `match (adapter,id)` special-cased only (Some,Some)→cmd_endpoint_run; the catch-all routed EVERY lone --id to crate::picker::run as a create-new prefill, never considering an existing endpoint (cli.rs ~1290). FIX: a PURE resolve_run_target(adapter, id, recorded) over the 4 (adapter?,id?) quadrants — (Some,Some)→Direct{a,id}; (None,Some(id))→ recorded adapter present (info.adapter = adapter-chosen-at-creation, spt-store info.rs:167) → Direct{recorded,id}, absent/no-perch → Picker{None,Some(id)} (today's create-new prefill UNCHANGED); (Some,None)/(None,None)→Picker unchanged. The perch lookup (read_info(resolve_perch_path(id,Infer)).adapter) is INJECTED as a closure so the router is pure + testable without a perch on disk; resume threads into BOTH Direct paths. Red-first: (None,Some(id),reco CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 125: REQ-RESIZE-INPUT-MODE-INTEGRITY vs REQ-TERM-ECHO-CLAMP-WINDOW ==================================================================================================== [A] REQ-RESIZE-INPUT-MODE-INTEGRITY Title: RESCOPED 2026-07-22 by ADR-0047 Amendment 2 + KNOWN-HAZARDS 7.55 (doyle-ratified), on the measurement rather than on the supposition. FIELD EVIDENCE IS REAL: hertz's second ENLYZEAM capture shows the operator's keystrokes as isolated one-byte OUTPUT records in the child->broker RAW DRAIN — server-side, upstream of any client console, so independent of the 7.56 presentation root that explains the other half of the /c/config line. LOCAL SEAM MEASURES CLEAN: a probe child under a real ConPTY, clamped raw at startup and resized 24x80->60x131 live, reports an IDENTICAL input mode word at four sample points (boot / before-resize / after-resize / before-write, all in=0x000001f0 echo=0 line=0), and typed bytes do not come back as output; a seeded capability probe moves BOTH observables, so the absence is earned, not vacuous. Amendment 1's addendum supposed the resize path re-enables console echo — on this box and portable_pty/ConPTY version it does NOT. TRIGGER UNPINNED. OPEN CANDIDATE (hertz field question, deliberately NOT built against): the pseudoconsole BOOTS with ENABLE_ECHO_INPUT/ENABLE_LINE_INPUT ON (in=0x000001f7 measured) and the CHILD clamps them off, so any UNCLAMPED WINDOW (st Doc: 7.55 A surface resize never alters the hosted terminal's input discipline — input bytes must never come back as output `[REQ-RESIZE-INPUT-MODE-INTEGRITY]`: - **Failure (paid-for, second ENLYZEAM production capture 2026-07-22 — operator resized a LIVE established rc viewport; hertz froze... [B] REQ-TERM-ECHO-CLAMP-WINDOW Title: SEED (DAEMON-LIFECYCLE W3 rideout, field-grounded 2026-07-22): the hosted-TUI echo CLAMP WINDOW — a Windows pseudoconsole boots with echo/line input ON (in=0x1f7, measured by the 7.55 instrument @0f74bba) and it is the hosted CHILD that clamps them, so any window in which the TUI has not yet (re-)clamped echoes typed bytes into PTY output SERVER-SIDE with no seam re-enabling anything. FIELD GROUNDING (hertz capture-2 byte timeline, output-only taps): echo onset is NOT resize-instant — first echoed key lands 6003ms after the first resize-associated repaint burst, IMMEDIATELY after a 3225-byte TUI-reinitialization-shaped absolute repaint (no alt-screen/mode CSI anywhere — cursor hide/show + HOME/absolute repaints only); echo CEASES mid-input (isolated c/o/n/f/i, NO g) directly after a 535-byte TUI diff — consistent with a late clamp landing (a WinAPI mode call is invisible to a byte tap). The resize seam itself is measured MODE-PRESERVING (REQ-RESIZE-INPUT-MODE-INTEGRITY rig, four sample points). OPEN AT TRIAGE: (1) whether the clamp is the TUI's own SetConsoleMode or portable-pty/ConPTY-internal; (2) whether spt-core CAN mitigate at all — the daemon structurally cannot read or set t CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 126: REQ-HAZARD-DEFERRED-SURVIVE-DRAIN vs REQ-SCREENGRID-WIDTH ==================================================================================================== [A] REQ-HAZARD-DEFERRED-SURVIVE-DRAIN Title: Deferred rows survive poll drain (4.4) [B] REQ-SCREENGRID-WIDTH Title: TEARDOWN-AUTHORITY W3 (hertz field RCA 2026-07-19, doyle-confirmed at source): ScreenGrid models every character as ONE display column, so wide characters (CJK, emoji, and other 2-column glyphs) shift subsequent text left and leave stale scraps at the right margin — field repro on the Claude settings UI (left-shifted rows + To/Wh/Es left-margin scraps). SOURCE: spt-term/src/screen.rs Cell { ch: char, pen } (~127) carries NO width datum, and put_char (~327-343) unconditionally advances col += 1, with pending_wrap likewise advancing a single display cell. FIX: give the grid a real display-width model — a wide glyph occupies its leading cell plus a continuation cell that renders nothing and is never independently addressable; cursor motion, wrap, erase, and scroll all reckon in DISPLAY columns. WIDTH POLICY (hertz-proposed, doyle-ratified — pinned so the renderer and the emulator can never disagree): share ratatui's pinned unicode-width 0.2.0 as a WORKSPACE dependency, ambiguous-width = 1, no CJK-context override. A second width authority in the tree is the defect this policy exists to prevent. Gate: doc — the width policy stated where the grid is documented; impl — width-aware Cell/p Doc: Server-side **screen grid** — the clean-room render model behind the clean repaint on attach (field bug #6 / `REQ-BROKER-SCREEN-GRID`, ADR-0031). A cold attach used to stream the entire retained raw-byte ring as its initial batch. For an alt-screen TUI that ring is a *rendering protocol mid-stream... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 127: REQ-PAIR-3 vs REQ-PAIR-4 ==================================================================================================== [A] REQ-PAIR-3 Title: Fetch current pairing code from any paired node [B] REQ-PAIR-4 Title: Subnet naming on first pairing CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 128: REQ-INST-10 vs REQ-MANIFEST-NODE-KEY ==================================================================================================== [A] REQ-INST-10 Title: Qualified addressing [subnet:]id[@node] + ambiguity forces qualification [B] REQ-MANIFEST-NODE-KEY Title: A new session-scoped manifest fill key `{node}` resolves to THIS node's advertised label — available wherever the session-scoped keys ({id}/{session_id}/{session_name}) populate: BOTH topologies' spawn-prep catalogs (harnesshost.rs:111-118 self-spawn guaranteed-fill + lifecycle.rs:280 base lifecycle keys, at minimum [session.self] and [session.resume]) AND lazy [strings] eligibility (ADR-0029 family). VALUE (design-true per CONTEXT §node label / REQ-SUBNET-3): the node's ADVERTISED LABEL — the same value node_label_display renders — read from the label store (NodeLabel, registry.rs:118/220, OS-hostname default re-checked at daemon startup), NOT the pubkey and NOT a fresh gethostname at fill time when the store already holds the refreshed label; fall back to the OS hostname only if no label is known. perri's concrete use: templating `--remote-control {id}--{node}` in the claude-spt launch/resume commands. CAVEAT (documented in the manifest.md key-table row AND here): SINGLE-TOKEN fills only — tokenize-then-fill (REQ post-F-009) cannot produce a space-carrying argv element, so composite display names like ` @ ` remain adapter-shim territory (claude-spt v0.10.3's launch shim Doc: Claude Code: native resume into an existing transcript by id, in its project cwd.: ```toml [session.self] # {node} fills as one argv token — the node's advertised label (its hostname). command = "claude --session-id {session_id} --remote-control {id}--{node}" keys = ["session_id", "id", "... CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 129: REQ-LISTEN-PRESERVES-HOSTING-TOPOLOGY vs REQ-PAIR-7 ==================================================================================================== [A] REQ-LISTEN-PRESERVES-HOSTING-TOPOLOGY Title: `api listen` must not ASSERT hosting topology it does not know. (hertz v0.39.0 field RCA 2026-07-21, doyle re-grounded at source.) OBSERVED: the published adapter sequence `api bind` then identity-preserving `api listen --session-id` produces a self-contradictory live record — 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, [B] REQ-PAIR-7 Title: Subnet icon (inline image metadata, GUI-only consumer) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 130: REQ-HAZARD-SOFT-CLEANUP vs REQ-HAZARD-EPHEMERAL-CLEANUP ==================================================================================================== [A] REQ-HAZARD-SOFT-CLEANUP Title: Soft-cleanup preserves state, removes only the ready marker (6.2) [B] REQ-HAZARD-EPHEMERAL-CLEANUP Title: Ephemeral perch cleanup on every ring exit path (3.1) CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 131: REQ-HAZARD-EVENTPART-REASSEMBLY vs REQ-HAZARD-RC-EOF ==================================================================================================== [A] REQ-HAZARD-EVENTPART-REASSEMBLY Title: EVENT-PART split/reassembly is byte-exact; orphan parts dropped silently Doc: The `` wire contract: ### EVENT-PART reassembly (listener stream) [B] REQ-HAZARD-RC-EOF Title: A severed broker stream during a live rc session surfaces GRACEFULLY, never as a raw io error that crashes the PTY. The rc read-loop (rc.rs:352-362) continues only on WouldBlock/TimedOut; ANY other read_event_until error — including UnexpectedEof 'failed to fill whole buffer' — returns Err → RC_FAIL → the PTY 'crashes' from the user's view. Confirmed trigger: a deliberate `spt daemon stop` (broker bounce) severs an active rc (perri stopped the daemon to release owlery watch handles). Same severed-broker-stream EOF class as the v0.9.1 seed fix (seed_fail_message) and the listener-death case — spt-core must classify a broker-gone EOF and (a) surface a CLEAR actionable message ('daemon stopped/restarted — re-run / reconnect'), never the raw buffer error, and ideally (b) AUTO-REATTACH to the same session on the fresh broker (the broker is the daemon-lifetime anchor; it returns on the next `spt api` call). FOLD two side-observations: (1) `spt daemon stop` SILENTLY drops active rc/live sessions — warn ('N active session(s) will drop') or graceful-detach on stop; (2) the daemon holds owlery WATCH HANDLES on perch dirs so a torn-down perch dir stays 'Device busy' until a full daemon stop r CONFUSABILITY ANALYSIS: (To be filled in) ==================================================================================================== PAIR 132: REQ-PRES-1 vs REQ-SUBNET-COUNT-ROUTABLE ==================================================================================================== [A] REQ-PRES-1 Title: Presence resolution: the presence datum (last_active_node, last_active_endpoint, ts) gossiped subnet-wide via the agent-interaction heartbeat (rides registry distribution, visibility-gated) + one first-class most-recently-active resolution API consumed by notif first-fire, update-consent delivery, consent escalation, and shell wake resolution (M5 scope decision 1: resolution only — the PresenceChannel endpoint stays deferred) Doc: Deferred Features: | Feature | Cut from | Why deferred | Trigger to revisit | |---|---|---|---| | Scrollback on-disk spillover | terminal wrapper v1 | In-memory ring covers the common case; spillover adds a persistence/rotation story | First long-running session that overflows the ring usefully, or ... [B] REQ-SUBNET-COUNT-ROUTABLE Title: Bug #2: a remote node endpoint count drifts (0/2, 1/3) because node_status_rows (cli.rs:5314) increments the per-node total unconditionally, counting non-routable Offline ghost rows; purge is not a registry eviction (it gossips a one-shot Offline row that is immortal on remote viewers — 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. CONFUSABILITY ANALYSIS: (To be filled in)