<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
  <Obj RefId="0">
    <TN RefId="0">
      <T>System.Collections.Hashtable</T>
      <T>System.Object</T>
    </TN>
    <DCT>
      <En>
        <S N="Key">REQ-PAIR-5</S>
        <Obj N="Value" RefId="1">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Multi-subnet pairing: subnet-name discovery input, create-new-names-up-front, rendezvous-token hashing</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-LISTEN-PRESERVES-HOSTING-TOPOLOGY</S>
        <Obj N="Value" RefId="2">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">`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,</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-MANIFEST-2</S>
        <Obj N="Value" RefId="3">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Adapter profiles — sparse leaf-replace overlays (shipped + local), composite &lt;adapter&gt;:&lt;profile&gt; addressing, shadow-refusal, tighten-only consent floors</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Runtime model: &lt;!-- --&gt; **adapter profile** (ratified 2026-06-11, Gateway grill; future spt-core milestone — first beneficiaries `spt-claude-code` and the usbip shell): A named **sparse overlay** on its parent adapter manifest. Merge semantics are **leaf-replace**: a profile key replaces the whole value at that path (arrays included — never spliced or appended). The merged result is a complete manifest, and the profile behaves as a distinct adapter option everywhere: canonical addressing is the composite **`&lt;adapter&gt;:&lt;profile&gt;`** (`claude-spt:work`, `spt-usbip-driver:hid-only`) in every place</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-LOCAL-API-AUTH</S>
        <Obj N="Value" RefId="4">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Every local `api` mutation authenticated to an endpoint/session (codex #13)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-EP-2</S>
        <Obj N="Value" RefId="5">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Agent endpoints vs Shells distinction in the type model</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-RC-RECONNECT-TRUTH</S>
        <Obj N="Value" RefId="6">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">W3 (LIFECYCLE-TRUTH): rc reconnect never auto-starts a daemon and never hangs forever. ROOTS rig-proven (the operator's long-standing 'stop 2-4 times' bug): (a) an rc client's reconnect loop AUTO-LAUNCHES a daemon via WMI (rig: DAEMON_LAUNCH_VIA_WMI from the rc) — resurrection fights the operator's stops; (b) rc freezes at 'Reconnecting to local daemon…' forever when its session died with the broker. FIX: rc NEVER auto-starts a daemon (reconnect only to an already-up broker; loud 'session lost — daemon down' exit otherwise), bounded reconnect with visible countdown.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-INST-11</S>
        <Obj N="Value" RefId="7">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">spt rename &lt;id&gt; rippled to all instances (collision-checked, 6.5-reconciled)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-CONSENT-1</S>
        <Obj N="Value" RefId="8">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Consent grant store: capability x subject-agent x target-node rows, enforced at the target node, subnet-settable (replicates as security material near the trust store), revocable; gated-capability ids (remote-exec, instantiate-anywhere) reserved-but-refusing; v1 consumers are the shell spawn gates (CONTEXT Consent &amp; security gates)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-ARCH-2</S>
        <Obj N="Value" RefId="9">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Public SDK surface is spt-proto, spt-runtime, spt-msg</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-REGISTRY-STALL</S>
        <Obj N="Value" RefId="10">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">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 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]` &lt;!-- --&gt; - **Failure (paid-for, hertz post-close v0.36 field RCA 2026-07-17 — live prod box):** FOUR compounding consequences of "phys</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-PICKER-HISTORY-FRESH</S>
        <Obj N="Value" RefId="11">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">The `spt endpoint run` picker shows project history for FRESH endpoints (operator-raised v0.12.0 real-harness finding). Symptom: a fresh endpoint shows no project history in the picker. ROOT TBD — investigate the project-history loader (v0.10.0 PICKER-2, picker/data.rs) before fixing: distinguish a real loader bug from 'fresh = no history yet' semantics. (v0.12.1)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-PICKER-ADAPTER-DESCRIPTION</S>
        <Obj N="Value" RefId="12">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">The Create-new adapter-CHOICE screen of `spt endpoint run`'s picker shows a right-hand Description panel (like the Pick-existing endpoint picker's two-pane) surfacing per-adapter detail: install date, last-updated, adapter TYPE / the endpoint types it hosts, and the adapter description — so the user can see WHAT each adapter is before choosing it (today the selector lists bare names). DEFERRED fast-follow to v0.12.0 (operator 2026-06-18). (post-v0.12.0)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-PICKER-PURGE-SHORTCUT</S>
        <Obj N="Value" RefId="13">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-ENDPOINT-LIFECYCLE</S>
        <Obj N="Value" RefId="14">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">7.44 Streams and seats on a long-lived connection must have bounded lifetime — one-way rows terminal at FIN, seats released at serve completion, no per-chunk full-state rewrites in a drain loop `[REQ-HAZARD-REGISTRY-STALL]`: ### 7.45 Endpoint lifecycle state converges to truth from every death path — no optimistic online without authority, no surviving control stamps, no immortal wake intent, no untruthful create `[REQ-HAZARD-ENDPOINT-LIFECYCLE]` &lt;!-- --&gt; - **Failure (paid-for, three hertz reports + operator field 2026-07-16):** four families, one root shape — lifecycle state written by multip</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-SOFT-CLEANUP</S>
        <Obj N="Value" RefId="15">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Soft-cleanup preserves state, removes only the ready marker (6.2)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-WAN-SEND-DELIVERY</S>
        <Obj N="Value" RefId="16">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Bug #9/#10: cross-node spt send reports SENT(WAN) but does not deliver, even on stable-IP pairs. Real root: spt send resolves the dial with id-only addr_for_node_hex (endpoint.rs:538) which forces a fresh iroh discovery round-trip every send, while the gossip pump uses cached direct addresses (dial_seeded/PeerAddrStore) so gossip stays green but send rides a marginal discovery path that cannot carry the fire-and-forget payload; the handshake completes so SENT(WAN) prints falsely. Fix: (1) route the WAN dial through the pump seeded-direct-address resolution (PeerAddrStore first, id-only fallback); (2) receiver writes its WanOutcome back so the sender confirms delivery under the QUIC deadline and only reports SENT on confirmed delivery, honest failure otherwise. Access-gate/perch/spool all verified correct (ruled out). See docs/NEXT-MILESTONE-BUG-TRIAGE.md #9-10.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-PSYCHE-CONTEXT-FILE-INDIRECTION</S>
        <Obj N="Value" RefId="17">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">W4 (F030; doyle Q2 ruling + perri file-always freeze, 2026-07-04): the composed psyche mind ({psyche_context}) rides the SHIM argv today — a real ~20KB doyle psyche-download exceeds the win32 CreateProcess lpCommandLine ~32k cap → the shim spawn BRICKS. FIX (file-always, replaces {psyche_context} outright — no size-branch, no argv cliff, one path): core writes the mind to a file in the psyche's NESTED perch dir BEFORE each turn spawn and fills a single {psyche_context_file} = that PATH (argv-cap-immune). The soft fresh/continue discriminator moves from KEY-presence to FILE-CONTENT: FreshWithPreload writes the composed mind NON-EMPTY (the &lt;fresh-psyche/&gt; never-empty guarantee carries to the file content); ContinueExisting writes it TRULY 0-BYTE (perri BINDING PIN 1 — NO sentinel/placeholder EVER, else her non-empty=fresh discriminator misfires a spurious --session-id adopt). Core owns the file lifecycle: write-before-spawn each turn, overwrite in place, persists between turns in the nested perch (debuggability); never deleted per turn. perri shim delta: --psyche-context-file &lt;path&gt; arg, read-file prefix, TRIM-based emptiness (her tolerance, NOT core's license — core writes exactly 0</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">then it exits — no resident process, no detach.: &lt;!-- --&gt; **Psyche-download — `{psyche_context_file}` (file-always, replaces `{psyche_context}`).** The composed Psyche mind rides a **file**, never the command argv: before each turn spt-core writes the mind into the nested psyche perch dir and fills a single **`{psyche_context_file}` = that path** (argv-cap-immune — a real ~20 KB mind exceeds the win32 command-line cap and would brick the spawn). The soft **fresh-vs-continue** discriminator is the file's **content**, not key presence: a **fresh** (first / reseeded) turn writes the composed mind</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-ENDPOINT-UNBOUND-ATTACH</S>
        <Obj N="Value" RefId="18">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">An spt-hosted endpoint is ATTACHABLE between spawn and bind: gate the attach on the broker SESSION being attachable (session+PTY+OutputLog exist at spawn, before bind), not on perch STATUS_ONLINE (bind). cmd_endpoint_run + `spt rc &lt;id&gt;` attach to a live broker session regardless of perch status (headless bringups too; lets an operator clear a bind-gating prompt) -- replaces await_endpoint_online; preserves REQ-HAZARD-RC-ATTACH-ONLINE-RACE's 'no attach before a session' intent at the earlier session-exists point; source = the broker sessions map (ADR-0025 W3a); local-only. New on-disk status STATUS_UNBOUND (spawn-&gt;unbound, bind-&gt;online, death-&gt;offline); lifecycle reuses the existing exit-waiter/reconcile (session death-&gt;offline); unbound is attachable but NOT message-addressable (messaging stays online/bound-gated). EpDisplay gains Unbound = HOLLOW (+ hollow-controlled variant) -- amber=HarnessOnly is taken + means not-controllable (the opposite of attachable). (ADR-0027)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Pieces the Instances model requires: &lt;!-- --&gt; **Unbound endpoint**: The lifecycle point between *spawn* and *bind*: an spt-hosted endpoint whose broker **session + PTY are live** but whose harness has **not yet bound** its perch (the *post-spawn seam* hasn't fired — e.g. the harness is waiting on a startup prompt). On-disk status `unbound` (spawn → `unbound`; bind → `online`; session death → `offline`). An Unbound endpoint is **attachable** (a live PTY — `spt rc` and the `endpoint run` attach reach it, so an operator can see and drive the harness, including clearing a bind-gating prompt) but *</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-KICK-1</S>
        <Obj N="Value" RefId="19">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Explicit, loud controller displacement: `spt rc kick &lt;target&gt;` / `--take` (Take intent) kicks the incumbent controller and becomes controller; the displaced controller receives a LOUD `Displaced{by}` notice and is FULLY DETACHED (not demoted to a viewer). A default attach to a controlled endpoint is NEVER a silent displace (it is the Control busy-refusal). An old (N-1) rc omits intent → Control, so it can drive a free endpoint but CANNOT `--take` — it can never silently steal, and gets a clean busy-refusal instead. Taking control rides the same access_check(endpoint, origin, Unsolicited) as a normal control attach (if you may drive, you may take — no elevated kick policy). The picker surfaces 'Kick &lt;node&gt; and attach' (Take) only on a controlled (blue ■) endpoint, via the existing attach dispatch (single-bringup-path: intent is a parameter).</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Shell sleep/wake (offline ↔ online): &lt;!-- --&gt; **BUILT (M12 W2.5).** The controller/viewer model is implemented end-to-end. Attach intent is **three-valued** (`AttachIntent = Viewer | Control | Take`, wire-default `Control`): `Control` to a FREE endpoint becomes controller; `Control` to a CONTROLLED endpoint is **refused with guidance** (`--view` to watch, `--take` to control) — never auto-viewer, never silent-displace; `Take` (`spt rc --take` / picker "Kick") kicks the incumbent with a **loud `Displaced{by}` notice** and full detach (not demote). The broker's per-session `OutputLog` is the fan</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-SEAM-ACTIVITY</S>
        <Obj N="Value" RefId="20">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Activity/idle reported via api sentinels, not PTY quiescence</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-CONTROLLER-LEASE</S>
        <Obj N="Value" RefId="21">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">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 and loudly, input is fenced to the active lease, node identity is never a lease `[REQ-HAZARD-CONTROLLER-LEASE]` &lt;!-- --&gt; - **Failure (paid-for, hertz same-machine `--take` RCA, field repro 2026-07-16):** terminal A controlled an endpoint; terminal B on the SAME machine ran `spt rc --take`. Local loopback</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-UPD-6</S>
        <Obj N="Value" RefId="22">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Build plan — `xtask debug-converge` (deferred follow-up): &lt;!-- --&gt; // Debug rollout runbook: &lt;!-- --&gt;</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-MESH-BOOTSTRAP-TRAP</S>
        <Obj N="Value" RefId="23">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">MESH-RECOVERY W1 (KNOWN-HAZARDS 7.42, hertz field RCA 2026-07-10 — HFENDULEAM+ENLYZEAM symmetric green-status sequester): a node holding a valid RosterEntry.address for a peer is NEVER route-less — a failed dial must not delete the only bootstrap route, and recovery must never require an already-successful connection or operator state surgery. Today: resolve_submit_addr = exact cache else id-only (never roster), PRESENCE_DIAL_FAILED unconditionally drop_seed's the cache row, and the cache refills only after a successful seed-proof exchange — one transient + stalled discovery = self-sustaining isolation, invisible (net_up true, heartbeat fresh, durable counts normal). Gate: int — production-path regression at the REAL pump resolver/failure-lifecycle seam: valid roster + matching cache, ONE transient dial failure, id-only discovery DISABLED, prove the next attempt still holds the roster-derived route AND all-peer-fail-then-restore converges with zero state surgery; doc — KNOWN-HAZARDS 7.42. HEAVY nextest group at birth if it spawns a daemon tree. Kin REQ-PEER-ROUTE-CHAIN (the mechanism), REQ-CONV-1 (the falsified drop-on-fail predecessor), ADR-0039.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">7.41 A fresh dispatcher must NEVER re-serve a terminal stream — historical replay must not steal or clear a live controller `[REQ-HAZARD-REDISPATCH-CONTROL-STEAL]`: ### 7.42 A node holding a valid roster address for a peer is NEVER route-less — a failed dial must not delete the only bootstrap route `[REQ-HAZARD-MESH-BOOTSTRAP-TRAP]` &lt;!-- --&gt; - **Failure (paid-for, hertz field RCA 2026-07-10 — HFENDULEAM + ENLYZEAM fully sequestered from every subnet member, symmetric, green-status):** the pump resolved dial addresses from the exact `peer-addrs.json` entry else id-only discovery — never the val</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-CLI-3</S>
        <Obj N="Value" RefId="24">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-THRASH-GUARD-BLIND</S>
        <Obj N="Value" RefId="25">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">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-BLIND]` &lt;!-- --&gt; - **Failure (paid-for, field evidence 2026-07-04):** the pre-F-030 resident-hosting thrash guard keyed on ledger-rate boundaries that were NOT the real re-host attempts. On hall-bf a **~12/min** re-host churn ran completely **invisibly** — the guard never tripped, never stamped, never c</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-INJECT-WORKER-POISON</S>
        <Obj N="Value" RefId="26">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">The per-session inject-worker floor Mutex is SHARED by the inject worker (open/flush) and the controller-input path (dispatch_input Layer C buffer_if_held); a panic under the lock on EITHER poisons it, and a bare .lock().unwrap() at the next site then panics too — a panic in the inject WORKER kills its thread WITHOUT reaping the translation child, orphaning a live binary while event_tx.send fails, so force-native reports 'worker-gone' delivered=false FOREVER (the F-e generic-miss shape). HARDENING, NOT the F-e incident root (perri's matrix exonerated poison under thrash/dormancy/churn): (i) poison-tolerant floor lock (unwrap_or_else into_inner) at all 3 sites so one panic can't cascade the session's delivery dead; (ii) a panic-resilient inject worker (catch_unwind -&gt; fault+terminate the child on a worker panic) so a dead worker never orphans a live binary + strands delivery. Poison-tolerance class of REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE / bug #16.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-ENDPOINT-STOP-OFFLINE</S>
        <Obj N="Value" RefId="27">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">H3: `spt endpoint stop &lt;id&gt;` marks the endpoint OFFLINE (alive=false), not merely de-readied. cmd_stop (cli.rs:2994-3010) removes the ready marker + unregisters the address but does NOT set status offline, so a stopped daemon-hosted endpoint still reports alive=true (status=online latch). FIX: add set_status(perch, STATUS_OFFLINE) to cmd_stop — folds with B2 (same setter). Unit: stop → is_perch_alive=false / alive=false. (v0.12.0)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-SHELL-PERCH-DIR</S>
        <Obj N="Value" RefId="28">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">A shell binary can mechanically resolve where its files land. (flynn spt-alchemy clean-room audit 2026-07-21, doyle code-verified, seed pair item 2, P1 — HARD-GATES flynn's alchemy W4.) TODAY: `spt shell send --file` lands the blob at &lt;shell-perch&gt;/files/&lt;xfer-id&gt;-&lt;name&gt; and the shell_file frame's path attr is PERCH-RELATIVE (files/...) — but the spawn template substitution keys are ONLY {id}/{adapter_name}/{link_token} (shellhost.rs fill_spawn_command) and the spawned child inherits the BROKER's cwd, so no mechanical perch resolution exists: the binary cannot turn the frame's path into a real file without guessing SPT_HOME layout. DOYLE RULING, both halves binding: the frame KEEPS the perch-relative path (an absolute path in a spooled frame LIES across perch moves and node boundaries — frames outlive layouts); the fix is an ADDITIVE {perch_dir} spawn-template substitution key (opt-in — templates that do not use it are byte-identical, N-1-safe by construction) filled with the shell perch dir so the binary receives its root at spawn and joins the frame's relative path against it. REFUSED, recorded so it is not re-proposed: blessing SPT_HOME layout guessing as an interim contract. Pu</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">`shell_text` — free text: &lt;!-- --&gt; ## `shell_file` — a landed file</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-LISTEN-SESSION-ID-FALLBACK</S>
        <Obj N="Value" RefId="29">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">F-034 leg c (perri/hertz field finding 2026-07-09): a session that goes live LATE (hours after SessionStart, or after a daemon restart) must still be able to bind — the ephemeral SessionStart seed ('consumed within seconds') is GONE by then and nothing re-fires it until the NEXT SessionStart, so even `api listen --parent-pid &lt;correct claude pid&gt;` hits NO_SEED. Design assumption 're-fired on the next SessionStart if needed' does not hold for long-lived sessions. FIX (perri-recommended, cleanest): `api listen --session-id &lt;sid&gt;` fallback that binds from the session-id when the pid has no live seed — removes the ephemeral-seed dependency entirely (the adapter already knows the sid; the skill passes it, and can then DROP its manual re-seed step). Alternative (option 1, less clean): re-fire the seed on daemon restart. Gate: a session with NO live seed (expired / post-daemon-restart) binds via `listen --session-id &lt;sid&gt;` (no NO_SEED); the sid-bind carries the same identity/auth the seed-bind would (session_id custody — kin REQ-PSYCHE-SID-CUSTODY / the sid-symmetric-auth pattern). Files: api-listen bind path (sid-fallback seam), clap --session-id flag (plain doc-comment). hertz/perri live</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">`api seed --pid &lt;pid&gt; --session-id &lt;id&gt;`: &lt;!-- --&gt; **Seed lifetime.** The seed lives **in the daemon's memory only** — no file — and survives until exactly one of: a successful `listen` bind consumes it, a newer `seed` for the same pid overwrites it, or the daemon process restarts (which drops the whole map). Nothing re-fires it until the harness's **next** SessionStart. So an adapter must not rely on the seed for a session that goes live late (hours after SessionStart) or after a daemon restart — that is what `listen --session-id` (below) is for.</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-REGISTRY-GHOST-ROWS</S>
        <Obj N="Value" RefId="30">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Registry rows must decay (no immortal rows) via TWO triggers: (a) NODE-SILENCE — evict rows whose author node has not been heard (admitted inbound feed) within the eviction window, so a vanished node's rows stop poisoning bare-id resolution with phantom AcrossNodes ambiguity; AND (b) per-row OFFLINE-TTL — evict rows that have been non-routable (Offline) beyond the per-row grace even while the author node is alive, because purge/erase leaves an immortal Offline row otherwise (ghost-heal re-advertises Offline ONCE with a fresh epoch, and whole-node eviction never fires for a still-alive author) so Offline ghost rows accumulate unbounded on a remote viewer under purge/erase churn (#2-secondary). Both keyed on RECEIVER-observed state (heard-map recency / a sticky receiver-observed offline_since, NOT the gossip epoch — an epoch-keyed clock would be reset by ghost-heal's fresh-epoch re-advertise); own rows never decay; a revived/re-flapped row re-inserts (or clears its offline_since) from its durable epoch within one pump cadence (4.10)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">4.9 SQLite stores must create their parent dir — SQLite won't: ### 4.10 Dead node identities leave immortal registry rows `[REQ-HAZARD-REGISTRY-GHOST-ROWS]` &lt;!-- --&gt; - **Failure:** the registry's only superseding mechanism is the per-`(endpoint_id, node)` epoch lease (4.8) — a row is replaced only by a newer row *from the same node*. When a node identity dies permanently (machine retired, or `node.key` regenerated so the "node" never speaks again), its rows are never superseded and never expire: they sit in the in-memory registries and the `identity/registry/&lt;subnet&gt;.json` snapshots forever. A</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-UPDATE-APPLY-ALREADY-APPLIED</S>
        <Obj N="Value" RefId="31">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">`spt update apply` classifies an already-staged / already-applied state as a friendly exit-0 no-op instead of dying at the binary-aside rename with 'Access is denied (os error 5)'. ROOT (F-025): a second apply on an already-applied staged version reaches the two-phase binary-aside rename and fails os-error-5, reading as a hard failure when the machine is simply up to date. FIX: apply gains the same pure classifier `fetch` got in v0.18.0 (REQ-UPDATE-FETCH-CURRENT-UX) — already-applied → clear message + exit 0, and the flow MUST short-circuit BEFORE the binary-aside rename in that state; mirror the classifier at ALL apply reject/entry sites the way the fetch fix covered its three. Genuine errors (bad signature, wrong platform, true downgrade, network) still propagate nonzero. (F-025)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-SELF-DETECT-PARENT-PID</S>
        <Obj N="Value" RefId="32">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">E-1 (REMOTE-TRUTH triage §E-1 #7): self-detect leg (c) — the pid-ancestry fallback — ALSO candidates on `rec.parent_pid` (the harness pid, CONTEXT's 'stable session-binding anchor', stamped at bind), not `rec.pid` alone. ROOT: for an spt-hosted endpoint (broker PTY, headless) `rec.pid` is the ephemeral bind-CLI pid, ALREADY DEAD by send time (the F-026 #11 dead-pid class, field-sighted on hall-bf) — never in any sender's ancestry and alive-gated out — so an spt-hosted sender could NEVER resolve self via leg (c): its messages were from-stamped `cli@NODE` (operator #7) and replies bounced NO_PERCH. FIX: detect_self_by_ancestry pushes a second candidate (id, parent_pid) when `rec.parent_pid` is Some + alive; the pure nearest-first matcher (match_self_by_ancestry) is unchanged. LABEL-ONLY, exactly like the rest of leg (c): from-label/routing default, NEVER authentication — authenticate() untouched, the pid-ancestry-for-auth question stays parked (KH 7.3/7.5 separation holds; a wrong label self-corrects, a wrong grant does not). Env legs (a)/(b) stay first. Red-first int (the triage-specified missing test): rec.pid = dead sibling + rec.parent_pid = genuine live ancestor → self resolves </S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-INJECT-CONTROL-COEXIST</S>
        <Obj N="Value" RefId="33">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-EP-7</S>
        <Obj N="Value" RefId="34">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Durable live-role.md: a per-agent broad-purpose statement in tracked/agents/&lt;id&gt;/ beside live-context.md (replicates with the mind on the same a-&lt;id&gt; branch); renders FIRST at start-transition context injection (role -&gt; live-context -&gt; project-context); SOLE writer `spt endpoint role --overwrite &lt;file&gt;` — 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)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Cross-node Psyche sync: &lt;!-- --&gt; **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 the mind and follows the agent across nodes. At start-transition context injection it renders **first** (role, then live context, then project context). The guarantee is **mechanical**: no automated writer exists — Psyche reconcile, echo-communes, and signoff structurally never touch it; the sole writer</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-ENVELOPE-DECODE-ORDER</S>
        <Obj N="Value" RefId="35">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Envelope decode order, ampersand decoded last (4.1)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Body and attribute encoding: &lt;!-- --&gt; **Decode order is binding.** Decode a *body* as: `&lt;br&gt;` → `\n` **first**, then `&amp;lt;`/`&amp;gt;`/`&amp;quot;`, then `&amp;amp;` → `&amp;` **last**. Decode an *attribute value* the same way minus the `&lt;br&gt;` step. Amp-last is the invariant that prevents double-decoding: a body carrying the literal text `&amp;lt;` arrives as `&amp;amp;lt;`, and decoding the ampersand first would turn it into `&lt;` instead of `&amp;lt;`. And decode **only the extracted body or attribute substring** — never run the unescape over the full envelope line, or the framing tokens themselves get rewritten.</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-STREAM-LIFETIME-CLASS</S>
        <Obj N="Value" RefId="36">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">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: &lt;!-- --&gt; &lt;!-- --&gt;</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-WORKER-MINTED-NAME</S>
        <Obj N="Value" RefId="37">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">N-1 (WORKER-TRUTH triage, operator rider): worker perch identity is CORE-MINTED and parent-derived — `{parent}-w{N}` with a per-parent counter at registration (sister shape: claude_skill_owl hook_subagent_start.rs) — never the adapter-presented agent id (CC Task ids render as random-named rows). worker-start mints + echoes the id (WORKER_STARTED:{parent}-w{N}); the adapter's agent_id/agent_type ride the record as correlation METADATA, not identity. Verb-shape contract change — freeze with W-2 in ONE coordination with perri.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Workers: &lt;!-- --&gt; ### `api worker-start &lt;parent&gt; [--agent-id &lt;id&gt;] [--agent-type &lt;type&gt;]`</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-SEAM-POSTSPAWN</S>
        <Obj N="Value" RefId="38">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">post-spawn / api bind seam with boot nonce</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-START-3</S>
        <Obj N="Value" RefId="39">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">spt-hosted startup: spawn-session then api bind (no file)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-BIND-REST-STATE-CARRY</S>
        <Obj N="Value" RefId="40">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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 -&gt; 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.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-ADAPTER-UNRESOLVED-HINT-FORM</S>
        <Obj N="Value" RefId="41">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">F-034 leg a (perri/hertz field finding 2026-07-09): the ADAPTER_UNRESOLVED refusal hint must print a WORKING command form. It currently says 'pass --adapter &lt;name[:profile]&gt;', but --adapter is a `spt api` GROUP flag, NOT a `listen` flag — following the hint literally (`spt api listen &lt;id&gt; --adapter &lt;name&gt;`) produces clap `error: unexpected argument '--adapter'` (exit 2). Fix: the hint prints the group-level form, e.g. `spt api --adapter &lt;name&gt; &lt;cmd&gt; …` (a hint the operator can copy-paste and have work). Gate: the ADAPTER_UNRESOLVED message text carries a clap-VALID invocation (group-level --adapter placement) — a unit asserting the hint string parses under the api clap grammar, or at minimum places --adapter before the subcommand. Pure UX/hint-correctness fix, no behavior change.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-DAEMON-REFRESH</S>
        <Obj N="Value" RefId="42">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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 -&gt; respawn -&gt; readiness trial -&gt; 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.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">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 previously required a full daemon bounce. &lt;!-- --&gt;</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-ADAPTER-PROFILE-STAMP-CLOBBER</S>
        <Obj N="Value" RefId="43">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">A-4 (F029, operator regression): the picker/confirm views drop an endpoint's adapter `:profile` (showed `claude-spt` where `claude-spt:ccs` was created). ROOT: stamp_creation_fields (spt-store/home.rs) gave the incoming BIND-TIME adapter value UNCONDITIONAL precedence (`rec.adapter = adapter.map(...).or_else(prior)`), but a hook bind resolves the adapter ADAPTER-AGNOSTICALLY (ADR-0021: a binary basename → the BARE parent, profile unknowable), so the first hook bind rewrote the richer `claude-spt:ccs` → `claude-spt`. (F-028's establish_perch self-heal widened how often this re-stamps; the precedence is the root.) FIX: profile-preserving precedence — when the incoming adapter is exactly the PARENT of the prior's `parent:profile` composite, KEEP the prior; replace only on a genuinely different adapter (or a different explicit profile). Paid-for field bug → hazard. See triage A-4.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-PRESENCE-CONTROL-REAP-ON-EXIT</S>
        <Obj N="Value" RefId="44">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">B3 (F028, hall-b diagnosis + deferred #11 seed; BROADENED perri F-b CONFIRMED): dead-pid ONLINE decay window + sticky CONTROLLED stamp. Repro: /exit -&gt; all endpoint processes dead -&gt; `endpoint list` stays ■ ONLINE for a decay window before OFFLINE. Sticky CONTROLLED: perri confirmed controlled=true + attached_node SET while alive=false/OFFLINE, persisting &gt;20min AND ACROSS A DAEMON RESTART (hall-b) — worse than the SIGKILL&gt;=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).</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-WORKER-SID-SYMMETRIC-AUTH</S>
        <Obj N="Value" RefId="45">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">W-2 (WORKER-TRUTH triage, operator-ruled 2026-07-06): worker verbs go sid-symmetric with every sibling id-scoped verb — worker-start mints NO token and worker-stop takes NONE (token custody is undue adapter burden, ruling via perri). Registration STORES the sid it authenticated (the parent's sid at start; today cmd_worker_start hardcodes session_id="" — worker.rs:44 — so a sid-authed stop compares against empty and refuses 100%). Stop accepts the parent's CURRENT sid OR the stored registration sid (a /clear between start and stop rotates the parent's sid; either rotation endpoint is honest custody — the REQ-PSYCHE-SID-CUSTODY rotation reasoning). Under the ruling the field adapter's existing emission (worker-stop &lt;id&gt; --session-id &lt;parent sid&gt;) becomes contract-correct as-is. Publish the frozen verb shape to the docs-site with the landing wave (perri blind-builds from published docs).</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">`api worker-start &lt;parent&gt; [--agent-id &lt;id&gt;] [--agent-type &lt;type&gt;]`: &lt;!-- --&gt; ### `api worker-stop &lt;id&gt; --session-id &lt;sid&gt;` · `api worker-poll &lt;id&gt; --session-id &lt;sid&gt;`</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-RESTART-IDEMPOTENT</S>
        <Obj N="Value" RefId="46">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Idempotent/exactly-once delivery across brain restart at every broker boundary (codex #14)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-DAEMON-STDERR-PERSIST</S>
        <Obj N="Value" RefId="47">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">W3 (LIFECYCLE-TRUTH, observability): broker + brain stderr tee to a rotating size-capped file under SPT_HOME (e.g. 2x5MB), stamped per generation. ROOT: detached daemon nulls stdio -&gt; the 2026-07-06/07 incident window left ZERO logs (both RCAs ran blind; rigs had to recreate everything). KNOWN-HAZARDS note: never inherit handles (REQ-HAZARD-DETACHED-DAEMON-STDIO) — open the file in-process, don't pipe.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-ENDPOINT-STOP-RESOLVES</S>
        <Obj N="Value" RefId="48">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">`spt endpoint stop &lt;id&gt;` 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:&lt;id&gt; (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</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-UPDATE-GH-TRANSPORT</S>
        <Obj N="Value" RefId="49">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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 -&gt; UPDATE_FETCH_REJECTED:GhCliRequired with OS-SPECIFIC install hints (winget/apt/brew); gh unauthed -&gt; 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</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Self-update: **release channel (private, gh-carried)** — the release channel is a **private** GitHub repo (`BigscreenVR/spt-bs-releases`, ADR-0036); the **gh CLI is the mandated carrier** for release discovery and asset download (each node authenticates via org membership). A node without an authed `gh` cannot fetch — refused loud with OS-specific install hints, never a silent hang. Signature verification is carrier-independent: bytes are verified after download exactly as before; counter, signing key, and update-set format are unchanged from the public-channel era. &lt;!-- --&gt; // How updates mov</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HOST-RUN-2</S>
        <Obj N="Value" RefId="50">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Project-scoped working directory for spt-hosted bringup: `spt endpoint run` lands the broker-spawned harness PTY in the user's PROJECT cwd, not the daemon's, via an additive `SpawnReq.cwd` field carried through the broker PTY spawn (portable-pty CommandBuilder cwd). N-1-safe wire change (additive, defaulted). Required because the consumer (Claude Code) is project-scoped: broker-inherited cwd = the daemon's cwd = the wrong `.claude`, wrong session history, wrong digest source; `cc &lt;id&gt;` at a project root MUST land the harness in that project. W1 ships broker-inherited cwd as a bringup-proof shortcut only; this REQ must land before the M12 gate (doyle, 2026-06-14).</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-PAIR-4</S>
        <Obj N="Value" RefId="51">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Subnet naming on first pairing</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-HANDOFF-ARGV-COMPAT</S>
        <Obj N="Value" RefId="52">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Broker/brain IPC + handoff argv version-tolerant (2.3)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-DETACHED-DAEMON-STDIO</S>
        <Obj N="Value" RefId="53">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">A daemon DETACHED-IN-FACT (no interactive console, or an inherited stderr PIPE nobody drains) that never nulled its std handles will BLOCK on stdio writes when the pipe fills, and/or pop a visible conhost window (REQ-HAZARD-WMI-DAEMON-WINDOW is a covered surface of this hazard). detach_console nulls the 3 handles only under the --detached flag; a rung that omits it (the bare line-82 elevated-&gt;deelevated respawn; a STALE installer at-logon task registered as bare `daemon run`, confirmed live field-drift on ENLYZEAM) is exposed. FIX: (load-bearing) inside `daemon run`, null the 3 std handles when stderr GetFileType==FILE_TYPE_PIPE — a pipe is the ONLY std sink that BLOCKS the daemon when it fills; catches every rung whose inherited stderr is an undrained pipe, independent of whether each caller passed --detached, while a FILE (2&gt;run.log AND every int-test Stdio::from(file) brain-log capture), a CONSOLE (scrolls), and a NULL/absent handle (DETACHED_PROCESS rungs, already discard) all SURVIVE. DELIBERATELY NOT gated on GetConsoleWindow==NULL: a CREATE_NO_WINDOW daemon has no console window yet a drained FILE stderr — nulling it would blank the capture for ZERO safety gain (a file never</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-PSYCHE-TURN-STREAM-EVIDENCE</S>
        <Obj N="Value" RefId="54">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">A failed psyche turn preserves BOTH captured streams as evidence: TurnError::Failed carries the child's stdout alongside stderr, and the failure display appends a bounded single-line stdout TAIL (last ~500 bytes, UTF-8-boundary-safe cut, newlines collapsed, the literal &lt;EMPTY&gt; when the stream said nothing — absence stated, never implied by a missing field). WHY (2026-07-26 spend-limit RCA): a failed turn's stdout is not psyche output, so BOTH core (turn.rs kept {status_code, stderr} only) and the adapter (guarding its outbound channel) independently discarded it — the same blind spot implemented twice — and an account-level outage surfaced as a bare 'claude exited exit code: 1' with the decisive refusal text thrown away at two layers; three agents then chain-hypothesized on an error string the real failing path never emitted. The tail is cause-agnostic instrumentation: it does not care what the failure is, which is why it survives being wrong about it. Adapter twin: claude-spt v0.25.14 dual-stream tail (shipped 2026-07-26).</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-DOCS-NO-INTERNAL-CODES</S>
        <Obj N="Value" RefId="55">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Public CLI --help (the clap `///` doc-comments) and the generated `docs-site/src/cli/reference.md` MUST NOT contain internal tracker/decision codes — `REQ-*`, `F-###`, `M#-W#`, `ADR-####`. They are meaningless to an end user reading --help and ship to GH-Pages. A CI-gated scan (the `xtask check` docs gate) fails on any such token in the GENERATED reference.md (which by construction contains only clap help, so rustdoc `///` on fns/structs is OUT of scope and keeps its REQ/ADR cross-refs). Substance is kept; codes are rewritten to plain language. (v0.13.2 W6)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">CI commitments: &lt;!-- --&gt; **Public help carries no internal codes.** The public CLI `--help` (the clap `///` doc-comments) and the generated `docs-site/src/cli/reference.md` MUST NOT contain internal tracker/decision codes — `REQ-*`, `F-###`, `M#-W#`, `ADR-####`. They are meaningless to an end user reading `--help` and ship to GitHub Pages. The `xtask check` docs gate scans the **generated** reference for these tokens and fails on any hit (regeneration alone keeps drift at zero but would faithfully republish a leak — the scan is what gives the gate teeth). The scan is scoped to the generated re</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-DROP-FILE-SINGLE-WRITER</S>
        <Obj N="Value" RefId="56">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Drop files are daemon-owned single-writer (6.4)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-TERM-6</S>
        <Obj N="Value" RefId="57">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Thread-spanning digest across session boundaries: a per-endpoint session ledger (`&lt;perch&gt;/sessions.log`) appended at first bind and by `api boundary` on `/clear`|`/compact` session rotation, the digest enumerating the last K sessions so its rolling window bridges a boundary, and a distinctive in-timeline boundary marker (DigestEntry::Boundary). The digest follows the live-agent thread, not a single session.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-WORKER-PICKER-EXCLUDED</S>
        <Obj N="Value" RefId="58">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">V-2 (WORKER-TRUTH triage, operator rider): non-drivable endpoint classes never render as `spt endpoint run` picker rows — a worker perch cannot be driven, instantiated, or controlled; offering it is a lie the picker then fails on. Filter endpoint_type worker (and the psyche class if it ever surfaces — same non-drivable family) at every picker source leg, extend-not-multiply for future non-drivable classes.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-SUBNET-DISPLAY-PARITY</S>
        <Obj N="Value" RefId="59">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">The `spt endpoint run` picker renders endpoint state IDENTICALLY for local and remote (subnet) rows — bound/unbound, controlled (+driver node), and harness-only are all visible across the subnet, not local-only. Today data.rs:293-294 reduces a remote row to plain green-filled/gray-hollow because those facts aren't propagated. (1) GOSSIP additive per-Instance fields: bound/unbound, controlled + driver-node, harness_only (Offline is never gossiped — node-down is remote-inferred). (2) Derive the full EpDisplay for subnet rows from the gossiped fields, same path as local (remove the remote-reduction). (3) PALETTE rework (fill = ACTIONABLE: filled=can act now (rc-control if online, WAKE if suspended-on-live-node) / hollow=cannot (no control seat / node gone)): green-filled=online+bound+free; blue-filled=online+controlled (desc shows `controlled by &lt;node&gt;`); RED-filled=online+UNBOUND (controlled-or-not; controlled-ness shown via available options not glyph) — replaces green-hollow Unbound + absorbs the dropped UnboundControlled; AMBER-HOLLOW=online+harness-only (no broker seat → can't rc) — was amber-FILLED; GRAY-FILLED=Suspended (cold, node up — wakeable) NEW; gray-hollow=Offline (node </S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-VIEWER-ISOLATION</S>
        <Obj N="Value" RefId="60">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">A slow / dead / hostile VIEWER must NEVER stall the controller, the PTY child, or the session drain thread. The broker drain fans output to the controller on the authoritative blocking bounded path (advances delivered_through) but to each viewer via a bounded per-viewer channel with a dedicated writer thread; the drain `try_send`s under the log lock and a viewer whose bounded queue OVERFLOWS (can't keep up) is EVICTED (queue dropped, writer thread ends, removed from the viewers map) — the drain thread NEVER touches a viewer socket, so no viewer write can backpressure or block it. A soft viewer cap bounds the thread count. Viewer eviction never perturbs the controller stream, the delivered_through cursor, or the child.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">7.6 Pump brain-IPC reads must be deadline-bounded (a blocked read wedges the whole pump) `[REQ-HAZARD-PUMP-IPC-DEADLINE]`: ### 7.7 A slow/dead/hostile remote VIEWER must never stall the controller, child, or drain `[REQ-HAZARD-VIEWER-ISOLATION]` &lt;!-- --&gt; - **Failure:** the W2.5 controller/viewer model lets ANY number of read-only `--view` attachers ride one session's broker `OutputLog`. The single drain thread fans each output chunk to every attacher. If a viewer's socket is fanned out with a **blocking** write under the log lock (the controller's authoritative path), one wedged viewer (a slow</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-INST-9</S>
        <Obj N="Value" RefId="61">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Multi-subnet membership (same-user N subnets; cross-user seam)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-RC-ATTACH-ONLINE-RACE</S>
        <Obj N="Value" RefId="62">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">`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 -&gt; (if start: return) -&gt; 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 -&gt; STATUS_ONLINE + the live session) lands ASYNC. Both picker attach paths route here with start=false (RunMode::Attach -&gt; cmd_endpoint_run start=false,view=false): Start-now catches the endpoint mid-bringup -&gt; run_attach attempts + loses the handshake race; Resume-from-history catches it still fully OFFLINE -&gt; 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</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-UPDATE-TRIAL-DRAIN-DRIVE</S>
        <Obj N="Value" RefId="63">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">UPDATE-WEDGE (counter-54, doyle-ruled 2026-07-09 — regression of the v0.29.0 seamless brain-swap): a brain generation DRIVES the broker's controller-liveness reap (a KIND_SESSIONS poll) each heartbeat throughout its boot/trial loop, so a hard-KILLED prior generation's black-holed LOCAL controller conn (by:None) is stall-evicted within the trial window and can never permanently strand the promotion DRAINED gate. ROOT (2026-07-09 field freeze, `spt update fetch --apply` v0.30.0-&gt;v0.30.2 froze all 7 live PTYs ~30s then rolled back): the promote gate (run_trial, brainproc.rs:657-661) needs BOTH `ready_generation==gen` AND `old_gen_drained()`; `old_gen_drained()` = `!any_local_controller_wedged()` (brainproc.rs:534) is a PURE READ of `write_blocked_since` (broker.rs:2703) — it never DRIVES the evict. The evict (`stall_evict_controller`, broker.rs:1039, same 15s `brain_write_deadline` the wedge-read uses) only runs via `reap_dead_controller` (broker.rs:967, severed-&gt;drop ELSE stall-evict) inside the KIND_SESSIONS snapshot closure (broker.rs:2879). During the isolated brain-trial window NOTHING polls KIND_SESSIONS: the old brain was hard-killed (`child.kill()`, brainproc.rs:851) so its lo</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Self-update: **brain-trial promotion (readiness + drained)** — the broker supervises the swapped-in brain through a bounded readiness **trial** and *promotes* the new binary only when it both signals ready for its own generation **and** the OUTGOING generation's control plane has **drained** — the old brain's local (brain-owned) controller connection is closed or stall-evicted, never still holding blocked writes. A hard-killed prior generation leaves that connection **black-holed** (its hosted PTYs keep producing output the broker's writer blocks on, since a killed peer's pipe blocks rather th</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-SEEDMAP-CONNECT-UNBOUNDED</S>
        <Obj N="Value" RefId="64">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">SEED (doyle-filed, 2026-07-05, from the REQ-HAZARD-DAEMON-STOP-BARRIER B2 fix): the PRODUCTION seedmap connect callers (put / take / is_running) inherit the SAME interprocess WaitNamedPipeW-forever hazard the stop path just bounded — a Windows named-pipe connect to a name that EXISTS but has NO accepting instance parks in NMPWAIT_WAIT_FOREVER, so a slow / half-dead seed daemon could wedge a live `api seed` / `api listen` / `daemon start`. UPDATE (2026-07-05, doyle reversed the stop-path scope-guard): request_stop's OWN initial connect became load-bearing (a stop-guard re-dialing an already-dying name parked forever, resurrecting the convoy) → it is now bounded via connect_bounded under REQ-HAZARD-DAEMON-STOP-BARRIER (every dial on the STOP path is bounded). REMAINING deferred here = the put / take / is_running production clients. FIX (deferred, needs its own ruling): a shared bounded seed-control connect for those — but a 2s-style cap on a legitimately slow daemon-start connect is a real behavior change (a slow-but-fine start could become a spurious failure), so the timeout + degrade semantics need design first. NOT built — activate when scoped.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-BRAIN-RESUME-NO-CONTROL-STEAL</S>
        <Obj N="Value" RefId="65">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Self-update: **resume re-attach is view-only for non-driven sessions** — on respawn the new brain queries the broker for every hosted session and re-attaches to rebuild output-continuity cursors, but it re-attaches as a **viewer**, never a controller, for any session it does not itself drive (which is *all* of them today — the supervised daemon brain hosts no PTY sessions; spt-hosted PTYs are driven by the operator's attach or the endpoint's own loop). Re-attaching as a controller would seize the controller slot of every free/local-controlled session — including the operator's local `spt rc` —</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-ECHO-BEFORE-SIGNOFF</S>
        <Obj N="Value" RefId="66">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Echo-commune fires before INIT_SIGNOFF on orphan teardown (3.3)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HOST-RUN-1</S>
        <Obj N="Value" RefId="67">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">spt-hosted harness bringup: `spt endpoint run` spawns an adapter's `[session.self]` command template into a broker-held PTY (the spawn-session seam, brain.rs spawn_session_pid — same broker path shellhost.rs launch_shell_brokered_in uses for shells, now for kind="harness" self-role), registers the perch under the given endpoint id, returns the id. Reverses today's harness-hosted-only launch (external launcher → `api bind`). Non-interactive flag set (--adapter &lt;a[:profile]&gt; --id &lt;id&gt; --create --resume &lt;session&gt; --attach|--start|--view) covers every terminal action of the W2 interactive picker so shortcuts (cc-&lt;id&gt;) bake fully non-interactive launches; composite adapter:profile resolves via registry::resolve_option leaf-replace overlay.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-CONN-POISON-DIAL-SCOPE</S>
        <Obj N="Value" RefId="68">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">MSG-IDENTITY W6 / F-039 (deployah field-acceptance follow-up 2026-07-10, RCA-FIRST — mint per the v0.30.6 PASS handoff): ambient CONN_WRITE_POISONED log-churn correlates 1:1 with PUMP_PEER_FAIL submit-dials to OFFLINE peers (enlyzeam/kitsubito/gravity) with NO wedge and NO freeze — pre-existed the blackhole rig = log-noise/mislabel, not a defect in the r4 fix. CODE CONTEXT: conn.rs poison_and_cancel emits the loud CONN_WRITE_POISONED line for a write that 'exceeded its bound (OR FAILED)' (conn.rs:181) — the fast-FAIL branch (broken pipe / conn refused on an already-dead counterpart) shares the log tag with the TIMEOUT branch that is the field-acceptance wedge observable, so routine conn teardown under offline-peer dial churn reads like poison events. RCA-FIRST: pin the exact write site that fails per PUMP_PEER_FAIL cycle (BrokerConn is broker-side — which broker conn write rides each pump dial failure? status/event fan-out to a departed subscriber? brain-side notification?) BEFORE changing anything — the correlation mechanism is unpinned. FIX SHAPE (post-RCA, doyle rules at lock): reserve the loud CONN_WRITE_POISONED token for the DEADLINE-EXCEEDED class (the wedge observable black</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-MSG-ENVELOPE</S>
        <Obj N="Value" RefId="69">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">The &lt;EVENT type="msg" from=…&gt;body&lt;/EVENT&gt; 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 &lt;shell-id&gt; --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 &lt;EVENT&gt; 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, &lt;EVENT&gt; composed once at the delivery boundary. No legacy sister-interop (spt-core never required it). Reply-correlation rebinds onto the structural from / &lt;EVENT from=…&gt; 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</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Decision: &lt;!-- --&gt;</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-TERM-1</S>
        <Obj N="Value" RefId="70">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Process-supervisor terminal wrapper hosting broker PTYs</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-DAEMON-STOP-REAP</S>
        <Obj N="Value" RefId="71">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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&lt;AtomicBool&gt; 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)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-PICKER-1</S>
        <Obj N="Value" RefId="72">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">The picker renders a FOUR-state endpoint status (extending the W2 online/offline duality): the list-item square AND a color-coded STATUS line at the top of the pick-existing right-side details both show — gray OFFLINE; green ONLINE (online + PTY-controllable spt-hosted, not controlled); amber 'ONLINE - HARNESS ONLY' (online but NOT broker-PTY-controllable = harness-hosted, no broker PTY seat — today mis-shows green); blue 'ONLINE + CONTROLLED' (online + driven_by.is_some()). Derived on EndpointRow from {offline | controllable | driven_by} with precedence offline→gray, else driven_by→blue, else !controllable→amber, else green (driven_by outranks harness-only; mutually exclusive in practice — a harness-only endpoint has no broker PTY to control). The controllable discriminator is a NEW InfoJson.controllable: Option&lt;bool&gt; (serde-default, N-1-safe), stamped at the establish seam — cmd_listen (harness-hosted relay, no broker PTY) → Some(false); cmd_bind live_agent (spt-hosted broker PTY) → Some(true); absent → not-controllable (amber) default (harness-hosted is the common mis-reported case; one bind self-corrects). Store-projection-only (no live daemon query — doyle ruling). (v0.10.0)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-SEND-STAMP-AGENT-ID</S>
        <Obj N="Value" RefId="73">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">MSG-IDENTITY W4 / endpoint-identity (operator-flagged 2026-07-09 + live-confirmed on flynn's F-038 ask arriving 'cli@HFENDULEAM'): a live agent's own CLI `spt send &lt;target&gt;` MUST stamp from_id with the AGENT id (e.g. 'doyle'), not the node fallback 'cli@&lt;node&gt;' — today the agent-id stamp rides ONLY the adapter/perch shortform path, so any agent shelling out `spt send` (the DOCUMENTED reach-another-agent form) presents to recipients as an anonymous node CLI: replies mis-route (recipients answer cli@node — no perch — instead of the sender), and the F-036 victim-effect ('sends downgraded to from:cli@node') is indistinguishable from normal CLI traffic. FIX: send-time self-resolve — when the calling process/session maps to a bound live perch on this node (the session-pin/seed machinery already resolves this for bind), stamp that endpoint id as from_id; a genuinely perchless CLI keeps 'cli@&lt;node&gt;'. Gate: unit — a send from a session bound to a live perch stamps the endpoint id; a perchless shell keeps the node stamp; int — recipient's EVENT from= carries the agent id for a shelled-out send from a live session. Kin F-036 victim effects, EVENT envelope (ADR-0020), [[owl-send-not-legacy-spt</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-UNHOST-PSYCHE-REAP</S>
        <Obj N="Value" RefId="74">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">On un-host, the detached `{id}-psyche` HARNESS PROCESS is reaped — not just its in-brain pulse-driver thread. Today stop_host (livehost.rs:203) trips the HostedLife stop flag + JOINS the driver thread, but the Psyche is a detached harness process (spawn_psyche → ManifestRuntime detached spawn, runtime.rs:341-356; its pid is untracked in HostedLife though stamped on the `{id}-psyche` perch, where residency-confirm already reads it). So endpoint-stop / mid-life agent-death / a B2/B5 offline-then-unhost leaves the psyche process ORPHANED, alive until the next daemon-stop (where Breap's job/group reaps the whole brain subtree). The Psyche STAYS a harness process by design (CONTEXT.md 97/203/251 — headless harness session, its own perch) — the fix does NOT move it in-brain; it SCOPED-kills the `{id}-psyche` pid on un-host (never machine-wide — shared box). Track the pid in HostedLife at host_one (cleanest) or read the `{id}-psyche` perch pid at stop_host. Composes with H3 (endpoint stop → offline → reconcile un-host → reap) and B2/B5 (the offline arms that trigger un-host). (v0.12.0)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-ADAPTER-UPDATE-POST</S>
        <Obj N="Value" RefId="75">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Composite adapter update — an avenue-agnostic `[update.post]` sub-table `{ command, self_verifies }` run AFTER the primary avenue (gh_release/file_pull/delegated) resolves, in the same `spt adapter update` (ADR-0029). Runs UNCONDITIONALLY (even on an adapter version no-op — the post-step's own idempotent check decides). PUBLISHED stdin JSON seam: one line `{adapter_applied, adapter_name, profile_name, version, previous_version, adapter_dir}` (additive keys; post-step ignores unknown). stdout decides the notice: custom text SUPERSEDES [update].message; a reserved sentinel fires the static [update].message; empty = no notice. exit code orthogonal (0 ok / nonzero failed). Precedence: dynamic-stdout &gt; sentinel/manifest-message &gt; nothing. NO [update.post] declared ⇒ today's adapter_applied→[update].message unchanged; post-step FAILS ⇒ loud warning + fall back to adapter_applied→message. FAILURE-ISOLATED: a committed gh_release pull is never rolled back if the post-step fails (independent channels). (v0.16.0)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">`[update.post]` — the composite post-step (since v0.16.0): &lt;!-- --&gt; // file_pull: repo + path_regex: &lt;!-- --&gt; **Composite update — `[update.post]` (since v0.16.0).** An optional **avenue-agnostic** sub-table that runs a delegated **post-step** *after* the primary update avenue resolves, in the same `spt adapter update`. It lets an adapter pull its `.spt` from `gh_release` **and** run a second, adapter-owned step (e.g. an in-harness plugin sync) under one lever.</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-ADAPTER-LIVE-UPDATE</S>
        <Obj N="Value" RefId="76">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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 -&gt; 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 -&gt; binaries+manifest back on the same page), (4) RESTARTS the resident binary from the new files. An endpoint NOT running -&gt; 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)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Live, daemon-coordinated adapter update: &lt;!-- --&gt; // Amendment (W3 build, 2026-06-22): &lt;!-- --&gt; &lt;!-- --&gt;</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-RUN-NO-DUP-SESSION</S>
        <Obj N="Value" RefId="77">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">B1 (F028, hall-b diagnosis, verified 0.22.0): `endpoint run --id X --create` on an endpoint with a LIVE session mints a silent DUPLICATE session — and attach output can CROSS sessions (second create for diag-hallc minted a new session while the old ran; the new run's attach viewport rendered the OLD session's screen — claude resume-picker UI of pid 84512 while new claude 356020 had no -r). ROOT CLASS of the 0.21.0 attach-stall (zero events in FIRST_EVENT_GRACE rc.rs:1402 = attach bound to dead/wrong same-id slot); also the triplicate `launch --id ball-b` on ENLYZEAM. FIX: (i) run-on-live-session must REFUSE or REATTACH, never silently duplicate; (ii) RCA the attach/output routing that let frames cross same-id sessions (broker session-slot keying, dispatch_adapter vs serve_attach resolution). Int: two sessions one endpoint id -&gt; each attach sees only its own frames. See triage B1.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-ENDPOINT-LIST-NODE-IDENT</S>
        <Obj N="Value" RefId="78">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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: &lt;node-id&gt;' 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.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-UPDATE-RUNNING-IMAGE-SURFACE</S>
        <Obj N="Value" RefId="79">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">`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)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-BOUNDARY-READY-STRAND</S>
        <Obj N="Value" RefId="80">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">C-2 (F029, SEAM-2 pinned — B6's SECOND HALF, the live-wake blocker; perri wakep9 vs wakep4 gate-state dump + doyle code trace): at a /clear, CC fires SessionEnd(reason=clear) for the DEPARTING session BEFORE SessionStart; the departing sid STILL matches the perch pin at that instant, so the adapter's [hooks.SessionEnd] → `api session-end` AUTHENTICATES and the soft handler REMOVES the ready marker (+ unregister_address, reporting.rs cmd_session_end:206-207). The subsequent `api boundary` rotates the sid but NOTHING re-writes ready → is_online false → try_spt_hosted_inject Nones on the CLI gate BEFORE any broker RPC → every post-clear force-native (incl. the checkpoint FIRE) reports the generic UNDELIVERED, persistent by construction (no path re-stamps ready outside a real bind). The single differing gate field at every UNDELIVERED instant is ready-absent (info online/controllable/rotated-sid all healthy, translate alive). Paid-for hazard. FIX: cmd_boundary re-stamps the ready marker (+ status online, idempotent) ATOMICALLY with the sid rotation — a boundary PROVES a live successor session on the same harness process; a REAL end has no subsequent boundary so genuine teardown is unto</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-SUBNET-6</S>
        <Obj N="Value" RefId="81">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Trust lifecycle verbs, elevation-gated: spt subnet leave &lt;NAME&gt; (membership exit) and spt subnet prune &lt;node&gt; (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)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-INST-7</S>
        <Obj N="Value" RefId="82">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Subnet registry + bare-id resolution policy</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-DAEMON-STATUS-JSON-TRUTH</S>
        <Obj N="Value" RefId="83">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">D2 (F028, perri F-d): `daemon status --json` truth drift. managed_by/managed_active read null in JSON while the HUMAN view says 'managed-by: manual — at-logon task registered' (the two surfaces disagree); and pump staleness (the STALLED diagnosis, B5) is NOT computable from JSON — only a raw pump_heartbeat_ms is emitted, no derived staleness/stalled field. FIX: JSON managed_by/active match the human render, and add a derived pump-staleness/stalled field so B5's condition is machine-observable. See triage D2.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-API-1</S>
        <Obj N="Value" RefId="84">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">api prefix and adapter_name on every machinery invocation</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-CONV-1</S>
        <Obj N="Value" RefId="85">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-ENDPOINT-ONLINE-TRUTH</S>
        <Obj N="Value" RefId="86">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Decision: &lt;!-- --&gt; 1. **Online is earned, not declared.** A creator may stamp `status=online` only from actual persisted state + hosting authority — never from manifest capability alone. Legacy hybrid rows self-heal at reconcile, but only after a SUCCESSFUL broker query: a broker failure is never interpreted as an empty session set (no mass-offline on a hiccup). 2. **Control cleanup splits from offline classification.** Reconcile clears `controlled`/`driven_by`/`viewer_count` for EVERY endpoint absent from session truth — regardless of state or controllability — while offline classification ke</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-SUBNET-1</S>
        <Obj N="Value" RefId="87">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">spt subnet noun namespace: status view (bare + status [NAME] [--nodes]), create (QR/otpauth), show-code; spt pair deleted</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-ENDPOINT-PURGE</S>
        <Obj N="Value" RefId="88">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">`spt endpoint purge &lt;id&gt;` 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/&lt;id&gt;/ 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-&lt;id&gt; branch+worktree + the &lt;id&gt;/ rows from every p-&lt;project&gt; branch (the same fn `for</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Inbound `api` surface (detailed): **`spt endpoint purge &lt;id&gt;`** (CLI, not `api`) — the standalone, formal **full teardown**: wipe an endpoint and *every* record keyed on it. It is the dev/CI sibling of `api session-end --erase` (which is adapter-triggered at session end); `purge` is the explicit operator/test command for clean setup-and-reset. **Deliberately NOT consent-gated** — a local dev/test op, never a peer-visible action. **Offline-only**: it refuses a live / daemon-hosted endpoint (deleting records out from under a running host would let the daemon re-create or re-host mid-purge); **`-</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-PUMP-PEER-ISOLATION</S>
        <Obj N="Value" RefId="89">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">PUMP-TRUTH W2 (architectural, operator ruling 2026-07-08): one peer must NOT block or poison all others -- peer discovery is async / per-peer-independent. Two coupled defects in run_peer_pump: (1) SEQUENTIAL fan-out (for peer in fan_targets dials one-at-a-time, each up to the bound -&gt; peer N+1 waits behind peer N); (2) WHOLE-ROUND POISON (peer_outcome(...)? -- one TimedOut aborts the ENTIRE round via ? -&gt; supervise_pump doubling-backoff restart, resetting ALL conns). FIX: per-peer concurrency + fault isolation -- the pump issues non-blocking dial requests; the broker (already async tokio+iroh) returns connection/presence results as async events (the D4c presence seam), no serial per-peer block; a peer TimedOut drops + reschedules ONLY that peer, NEVER aborts the round or restarts the pump. Supervised-restart is RESERVED for a dead BROKER conn, not a dead peer (the single-thread+bounded-read A-half REQ-HAZARD-PUMP-IPC-DEADLINE was defensive -- it stopped the infinite wedge but coupled every peer's fate; this decouples). Gate: a mixed roster (1 live + N offline peers) -- the live peer connects AND this node advertises presence in the SAME round the offline peers fail; heartbeat advan</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-RELAY-NO-BUSY-DELIVER</S>
        <Obj N="Value" RefId="90">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">MSG-IDENTITY W4 (operator self-send probe 2026-07-09, companion leg): the relay/idle-inject carrier MUST NOT fire for an ACTIVE endpoint — the daemon already guards send-time (daemon.stderr.log: 'ENDPOINT_INJECT: endpoint ACTIVE -&gt; spool (deferred hint), not injected') yet the field row ended taken_leg='idle-inject' for a message sent while the endpoint was ACTIVE, so the guard is bypassed somewhere on the busy-to-idle EDGE (the parked-drain idle-injects rows that arrived during busy without re-checking whether a poll is concurrently draining them — the edge itself is when BOTH carriers are plausibly live). RCA-FIRST with REQ-CARRIER-CLAIM-EXCLUSIVE (same rig, same instrument); if the exclusive claim alone closes the double-delivery this leg may reduce to an ordering assertion — rule at RCA lock, don't build blind. Gate: unit — the idle-edge drain re-verifies activity (or defers to the claim) before idle-injecting; a row taken by a poll is never idle-injected. Kin REQ-IDLE-PARKED-DELIVERY (the drain this guards), REQ-MSG-IDLE-EDGE-DRAIN, F-023 anti-starvation gate (do NOT break the already-idle delivery class).</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-SCREENGRID-REPAINT-MODE-REPLAY</S>
        <Obj N="Value" RefId="91">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">RC-RENDER-TRUTH W3 (ADR-0043 decision 4, hertz stale-glyphs RCA leg 4 P1): ScreenGrid cold repaint replays EVERY tracked render-affecting mode — DECSTBM scroll margins at minimum — before final cursor placement (today render_repaint omits tracked margins, so client and server grids interpret subsequent raw scrolling against different regions =&gt; stale/moved rows after reattach/resize; the trailing-blank omission after ED2 is semantically correct and NOT the bug). Stateful emulator contract: dirty screen + synthesized repaint + next raw frame == server grid. Gate: impl — tracked-mode replay in render_repaint; unit — repaint emits tracked DECSTBM, emulator contract holds for scroll-after-repaint; doc — ADR-0043.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Decisions: &lt;!-- --&gt; 1. **One FIFO sequencer per attach sink.** The PTY drain/output writer is the sole sequencer for terminal Output and Exit: Exit is enqueued behind all prior output for each sink (drain EOF/completion first, then Exit). A mutex alone is insufficient — producer order is the contract. Output-before-Exit is a production-path invariant, regression-proven end-to-end (broker → attach → rc). &lt;!-- --&gt; 2. **rc display teardown is unconditional, idempotent, and separate from input teardown.** A display RAII guard (distinct from the OS input/raw-mode guard) runs on every exit path incl</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-WHOAMI-EXPLICIT-SID-REFUSAL</S>
        <Obj N="Value" RefId="92">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">RULED DESIGN, delivery unowned (doyle 2026-07-26): when a caller hands identity resolution an EXPLICIT non-empty $OWL_SESSION_ID that resolves to NO perch, core must REFUSE identity (unresolved, exit 1, loud distinct diagnostic) rather than fall through to an ambient/inherited one — today `detect_self_id` (roster.rs, legs a→b→b2→c) treats sid-UNMATCHED identically to sid-ABSENT, so the fallback chain re-adopts precisely the identity a sharper claim just failed to prove. MEASURED (perri, this node, 2026-07-26, three read-only whoami calls from a genuine descendant of the perri host process): (1) all SPT_*/OWL_* scrubbed → id null, exit 1 — ancestry resolved nothing (caveat honored from the probe: the perch's recorded pid was not in the caller's chain, so this run refutes lineage-as-the-mechanism for probe v1 without disproving a lineage path in general); (2) inherited SPT_ENDPOINT_ID=perri + explicit OWL_SESSION_ID matching no perch → perri, exit 0 — the mismatch datum was IN HAND (core had already scanned and failed to match the explicit sid) and the ambient id won anyway; (3) real OWL_SESSION_ID with endpoint id scrubbed → correct self — the healthy path any fix must leave untouch</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-BROKER-FLOOR-LOCK-POISON</S>
        <Obj N="Value" RefId="93">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">B-1 (REMOTE-TRUTH triage §B-1, PIVOTED @e5eb99a): NO bare `.lock().unwrap()` on a broker-resident lock reachable from serve/dispatch — a brain-only self-update keeps the broker + all its Mutexes ALIVE (REQ-UPD-3), so a single panic under one poisons it PERMANENTLY: the next `.lock().unwrap()` panics, kills its per-conn reply thread, and EVERY subsequent attach silently deadlines ('brain IPC read deadline elapsed') while non-locked ops keep working. TRIAGE-DRIFT (sweep-dispatch-site-counts discipline): the triage named 3 sites (broker.rs:1163 flush_inject_floor / :1297 inject-worker-open / :2142 buffer_if_held) as the surviving class, but ALL 3 are the INJECT FLOOR and were ALREADY poison-proofed by REQ-HAZARD-INJECT-WORKER-POISON (lock_floor, shipped post-triage — the FLOOR HALF is SUBSUMED, this seed redirects). The SURVIVING class (matching the triage's own symptom description) is the ATTACH-PATH lock set: self.sessions Mutex&lt;HashMap&gt; ×18 + its sessions_exit alias ×1, the per-session OutputLog RING ×11 (log/h.log/log_drain/log_exit), pair_holds ×4 — 34 production bare .lock().unwrap() (cfg(test) excluded). FIX (doyle B-1 ruling): recover ALL THREE via ONE shared `recover&lt;T&gt;(&amp;Mute</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">7.32 The effective resting state MUST be derived through ONE shared liveness-aware function — a stored-intent-alone read lies about cold perches `[REQ-EFFECTIVE-INSTANCE-STATE]`: ### 7.33 NO bare `.lock().unwrap()` on a broker-resident lock reachable from serve/dispatch — a poison permanently wedges every attach `[REQ-HAZARD-BROKER-FLOOR-LOCK-POISON]` &lt;!-- --&gt; - **Failure (paid-for class):** a brain-only self-update restarts the BRAIN but keeps the BROKER process — and every one of its `Mutex`es — ALIVE by design (REQ-UPD-3). So a single panic while another thread held a broker-resident lock P</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-LIVEHOST-BOOT-RACE</S>
        <Obj N="Value" RefId="94">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-EBUSY-RENAME</S>
        <Obj N="Value" RefId="95">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">tmp-write + atomic-rename + retry on Windows EBUSY (5.2)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-BRAIN-RESUME-NO-CONN-DEADLOCK</S>
        <Obj N="Value" RefId="96">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">UPDATE-WEDGE round 3 (v0.30.5, doyle-ruled Option A 2026-07-09 — the v0.30.4 field-verify re-wedge, root code-PROVEN + dead-peer-INDEPENDENT): the daemon brain must NOT subscribe broker PTY sessions onto its own request/reply IPC conn — it has no consumer for that output and the subscription DEADLOCKS the conn. ROOT (todlando code-read, docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md; the net-runtime AND the counter-54 reap-drive were both FALSIFIED first — docs/UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md): a conn's send half is a single `SharedSend = Arc&lt;Mutex&lt;SendHalf&gt;&gt;` (broker.rs:78). Subscriber writer threads (`viewer_writer` broker.rs:1333/1342, `controller_writer` :1451) hold `send.lock()` ACROSS a BLOCKING `write_frame`; the dispatch reply path (`send_frame` :4221 → KIND_SESSIONS_REPLY / KIND_NET_STATUS_REPLY) needs the SAME lock. `resume_sessions` (brain.rs:1031→1054) subscribes every session as a Viewer onto the brain's MAIN conn — which is ALSO the brain's request/reply channel. The daemon brain hosts no PTY sessions (brainproc.rs:184) so run_brain never drains that output; it reads the conn only during the 500ms-heartbeat net_status()/sessions() calls (drain-and-DISCARD, `_ =&gt; continue</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Cold-start multi-session resume (restoration D4-2, ADR-0018 Q6): query the broker for **every** hosted session and re-attach **each** in resume mode from the broker's per-session delivered cursor (`resume_seq`). This is the production replacement for the retired single-session `BrainState` handoff frame — a brain the supervisor respawns (crash *or* update) reconstructs all session continuity by querying the persistent side, never a brain→brain message. Returns the ids re-attached (empty when the broker hosts none — the supervised daemon brain's no-op-today case). Each session is seeded into [`</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-UPD-9</S>
        <Obj N="Value" RefId="97">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">`gh_release` adapter [update] avenue (optional signing): an adapter declares `[update] avenue = "gh_release", repo = "user/repo"` (+ optional `asset`, default `adapter.spt`; + optional Ed25519 `signing_key`); spt-core's ripple compares the repo's LATEST GitHub release version against the installed adapter version and, when newer, auto-updates by fetching the release `.spt` archive (the REQ-INSTALL-9 `--release` fetch primitive) → verifies the `.spt` against `signing_key` if declared, else HTTPS+GitHub first-acquisition trust → re-extracts + re-registers the adapter root. Lets a harness adapter ship updates from its own GitHub releases with NO signing tooling or plugin coupling (removes the perri file_pull/delegated avenue blockers). Acquisition-trust mirrors `--release` + the installer first-fetch; does not alter spt-core self-update (REQ-UPD-1..8).</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Runtime model: **adapter update declaration** (manifest field): &lt;!-- --&gt; Each adapter manifest declares how spt-core should *ripple-update the adapter itself* (see Self-update). One of: **file-pull** (a plugin-directory lookup regex + a gh repo for the adapter's latest files — spt-core fetches + swaps), **delegated command** (a binary command the adapter owns, e.g. `claude.exe plugin update` — spt-core invokes it), or **gh_release** (the adapter ships its updates from its own GitHub releases). After initial bootstrap, the plugin no longer self-manages updates; spt-core conducts them. The **gh_</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-SUBNET-4</S>
        <Obj N="Value" RefId="98">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Subnet membership mutations elevation-gated (create = seed reveal; join = trust-boundary enrollment)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Product-surface amendment (2026-06-05 — M7 D3): &lt;!-- --&gt;</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-DIGEST-FETCHER-STRATEGY</S>
        <Obj N="Value" RefId="99">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Bug #17 (W6b, closes eel-a end-to-end): [digest] gains a `fetcher` strategy mirroring [history]'s locate/normalize split (CONTEXT §history: [digest] mirrors history's two strategies — locate ownership). ROOT: the pre-W6b [digest] had only the locate_normalize analog (spt-core resolves ONE `source` template + pre-reads the file), which CANNOT express a PARTITIONED transcript layout — CC's projects/&lt;munge(cwd)&gt;/&lt;session_id&gt;.jsonl or a date-globbed rollout tree — the exact case CONTEXT already assigns to the adapter. spt-core (correctly) provides NO {project}/slug key (harness-specific cwd munging = the charter violation FIX-A was rejected for). Fix: strategy = fetcher makes the ADAPTER's extractor locate + read + emit normalized records; spt-core runs it bounded (no locate, no pre-read, no stdin) and consumes stdout, feeding only the harness-NEUTRAL inputs it owns — {session_id}, the perch-bound {cwd} (info.json.cwd), and the captured [env] direction=read vars (W6/REQ-DIGEST-PROFILE-ENV) — so the extractor globs the unique {session_id} under {read-var-root}/projects/ with no slug. Keeps locate_normalize (default, back-compat) for a trivial single-file harness. Distinct capability fro</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">`[digest]` — session-digest extractor: &lt;!-- --&gt; `[digest]` supports the same two locate strategies as `[history]` — pick with `strategy` // native: adapter pushes via `api history-log`; spt-core stores (also used for Shells): ### `[digest]` — session-digest extractor (ADR-0019) The session digest's own seam — **distinct from `[history]`** (which stays opaque and single-session, feeding the echo-commune verbatim). Declares an **imperative extractor** that maps the harness's native log → the digest-record contract. ```toml [digest] extractor = "claude-spt-digest --session {session_id} --in {sour</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-STREAM-INFOS-SERVER-FILTER</S>
        <Obj N="Value" RefId="100">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-SEAM-UPDATE</S>
        <Obj N="Value" RefId="101">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Adapter-update avenue (file-pull / delegated command)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-MESH-3</S>
        <Obj N="Value" RefId="102">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Mesh row fan-out: registry rows stay OWN-AUTHORED; the only change is the push target widens from directly-paired peers to ALL roster members (a wider DIRECT fan-out, never a third-party relay). Every row/message still arrives from its author over a handshake → KNOWN-HAZARDS 7.5 (origin = handshake node) and 4.10 (eviction lease: any future update comes from that node itself, alive) PRESERVED VERBATIM. Closes the staggered A→B→C repro: C (roster-seeded with A at pairing) initiates to A, seed-proof admits C unpaired, A learns C, both push directly.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-PEER-ROUTE-CHAIN</S>
        <Obj N="Value" RefId="103">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">MESH-RECOVERY W1 (ADR-0039, RCA wave 1): dial-address resolution is the ROUTE CHAIN — exact peer-cache entry, then VALIDATED RosterEntry.address (address.id must match the peer key; a poison row never becomes a route), then id-only discovery — always fully consulted in order (no failure-count heuristics gating legs; rotation machinery REJECTED). Retention is NONDESTRUCTIVE: PRESENCE_DIAL_FAILED demotes the cached route to suspect (skipped in favor of the roster leg while suspect, superseded by any validated fresher address from connect write-back or reconcile), never deletes a sole route; removal only via validated-fresher replacement or roster tombstone. Validated roster addresses RECONCILE into the cache at daemon startup and on roster merge (beyond gapfill's fill-only: validated-fresher replaces failed/suspect rows) — recovery is connection-independent. Amends REQ-CONV-1's drop-on-fail mechanism; peeraddrs.rs/pump doc-comment truth rides the same change. Gate: impl — chain + demote + reconcile; unit — chain order incl. id-mismatch roster row resolves nothing + suspect row survives N failures with no replacement + reconcile replaces suspect with validated-fresher; int — rides REQ</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Context: ## Decision &lt;!-- --&gt; &lt;!-- --&gt; &lt;!-- --&gt;</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-SHELL-1</S>
        <Obj N="Value" RefId="104">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-WHOAMI-IDENTITY-ONLY</S>
        <Obj N="Value" RefId="105">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">PROJECT-INDEX W1 (F-040, perri filing claude-spt docs/SPT-CORE-FINDINGS.md @d775b38; correctness-critical opener — the 2026-07-15 message-bodies incident root): a core IDENTITY-ONLY resolution — session -&gt; endpoint|null — that touches NO list/registry/project/git/network path, and `spt whoami` DE-ALIASED from cmd_endpoint_list (cli.rs ~6609 aliases the full list = 100+ git children under hook deadlines). endpoint-info is DISQUALIFIED as the carrier (runs latest_project_ref). Adapters/hooks get a bounded-time identity verb; the harness-hosted adapter fallback stays deadline-vulnerable until this ships. Gate: impl — the resolver + whoami de-alias; unit — resolver returns endpoint|null with zero project derivation (assert no git spawn seam); int — whoami on a multi-perch home answers fast-path without touching context branches; doc — harness-contract api.md names the identity verb + its no-derivation bound. Kin REQ-PROJECT-INDEX-READER-CUTOVER (list-shaped verbs), REQ-WHOAMI-1, docs/PROJECT-INDEX-TRIAGE.md.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">`spt whoami` — the identity verb *(identity-only since v0.33.0)*: &lt;!-- --&gt; The bounded-time "which endpoint am I?" answer for hooks and adapter glue: resolves the calling session to its endpoint (`$OWL_SESSION_ID` / `$SPT_AGENT_ID` / process ancestry) and prints that ONE endpoint's SELF line — id, liveness, description. **The no-derivation bound is the contract**: whoami never enumerates the roster, never derives projects, never runs git, never touches the network — safe to call from deadline-bounded hook paths (the class that previously timed out and black-holed message delivery). Unresolved</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-UPDATE-ROLLBACK</S>
        <Obj N="Value" RefId="106">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Self-update rejects version rollback; metadata expiry + adapter content signing (codex #5)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-LIVEHOST-RECONCILE-TRIAL-SILENT</S>
        <Obj N="Value" RefId="107">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">SEED (DEFERRED investigation, doyle 2026-07-09 — UPDATE-WEDGE follow-up): determine WHY the trial/rollback brain's livehost reconcile loop did NOT drive the broker controller-reap (nor re-host the live agents) during the ~30s field update-trial window, when livehost polls `query_live_session_endpoints()` → `brain.sessions()` (KIND_SESSIONS) UNCONDITIONALLY every `LIVE_RECONCILE_INTERVAL_MS`=5000ms (livehost.rs:1026). CONTEXT (surfaced building the counter-54 rig): livehost's 5s KIND_SESSIONS poll drives the SAME broker `reap_dead_controller` sweep the fix drives — so it would otherwise reap the 15s-matured wedge by ~T20 &lt; the 30s trial and SELF-HEAL. It didn't (field froze 30s → rollback), so the field trial-brain livehost was silent/delayed (PIN Q2: no `DAEMON_RESTART_RESUME` under gen-1/gen-2; the 30s kill landed before/around livehost's first reconcile tick). The counter-54 fix (REQ-UPDATE-TRIAL-DRAIN-DRIVE) puts a RELIABLE 500ms reap-driver in run_brain's CORE heartbeat loop, making the wedge-reap INDEPENDENT of livehost — so this does NOT block counter-54. But the livehost silence is a latent anomaly with a SECOND consequence: live-agent HARNESS re-hosting was also delayed ~30</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-PSYCHE-LEGACY-RESIDENT-SWEEP</S>
        <Obj N="Value" RefId="108">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">W5 (F030; doyle+perri 2026-07-04): a dirty daemon upgrade from &lt;=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 &lt;parent&gt;-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 `&lt;id&gt;-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 `&lt;id&gt;-psyche` AND (c) pid alive; any unreadable sig</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-MESH-1</S>
        <Obj N="Value" RefId="109">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Membership proof (seed-proof): symmetric current-epoch seed-knowledge replaces is_trusted at EVERY inbound gate (registry apply, WAN receive, sync, notif, connection accept). MK = HKDF(seed, domain ‖ subnet_id ‖ seed_epoch); mutual channel-bound challenge-response at connect (transcript binds both handshake-proven node pubkeys, both nonces, subnet_id, seed_epoch, role); verified once per connection, cached on the broker ConnEntry, kept warm via QUIC keep-alive so re-proof is restart/partition/rotation-only. Exact-epoch match (re-seed is the sole N-1 exception). SECURITY INVARIANTS: channel-bound (no cross-connection replay), mutual, accepts a member it never paired (the mesh property).</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-RC-IDENTITY</S>
        <Obj N="Value" RefId="110">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">`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)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-CASCADE-WIPE-GUARD</S>
        <Obj N="Value" RefId="111">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">No hard-delete of a parent hosting non-empty children (6.3)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-REST-VERB-ROUTING</S>
        <Obj N="Value" RefId="112">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">A-3 (REMOTE-TRUTH triage §A + Q3 operator-law): a BARE-id rest verb (spt wake/suspend &lt;id&gt;) routes across the subnet like send's fallback instead of failing local-only. ROOT (certain): cmd_rest (cli.rs:3296) gates the remote arm on id.contains('@'|':'); a bare id falls to the local-only arm (cli.rs:3340) → daemon_rest_event → info::read_info miss (resting.rs:248) → 'WOKE_FAIL:{id}: info.json absent or unreadable — not a hosted perch'. cmd_send (cli.rs:5142) DOES fall back on a local miss; cmd_rest's remote arm (cli.rs:3307, wan_rest) already handles every WanRestOutcome — it is simply never reached on a bare-id local miss. Contradicts CONTEXT:286 'a wake must route'. Q3 SUBSTRATE GAP: resolve_across_visible (registry.rs:971) filters only by Status::routable() and its Ambiguity payload is node-hexes-only — it CANNOT express the Q3 status rule; per-candidate (node,status) comes from SubnetRegistry::instances(id). FIX: a NEW pure select_rest_target helper (status-aware, isolated from resolve_across_visible which cmd_send keeps) applying GOAL-SATISFACTION semantics (ADR/triage addendum @188d269, NOT naive verb symmetry — the mixed case breaks symmetry): wake is an ∃-goal (satisfied whe</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-RC-CROSS-NODE-ATTACH</S>
        <Obj N="Value" RefId="113">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Instances: &lt;!-- --&gt; **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 instance *running on another node* — compute + files stay remote; you are a viewport (the byte-stream terminal attach, daemon-to-daemon over Iroh). Used when you specifically want *that machine's* environment. This is effectively a Shell (a driven surface, user→agent direction), separate from the ins</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-INBOX-NO-DOUBLE</S>
        <Obj N="Value" RefId="114">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">No double-delivery via legacy inbox (4.5)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-CONTROL-STAMP-LIFETIME</S>
        <Obj N="Value" RefId="115">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">#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(&amp;id) but does NOT clear the perch's controller/viewer stamps; clear_controller()-&gt;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.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">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]`: &lt;!-- --&gt; ### 7.27 A control/viewer stamp must NEVER outlive its session — every teardown path clears what attach stamped `[REQ-HAZARD-CONTROL-STAMP-LIFETIME]` - **Failure (F-026 #2, live evidence HFENDULEAM):** an spt-hosted endpoint stayed `ONLINE+CONTROLLED` after the operator's RC `/exit` — hours later hall-a's `info.json` still read `controlled:true` (status offline, dormant) and hfenduleam still GOSSIPED `controller_node=sel</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-COMMUNE-INGEST-BLACKHOLE</S>
        <Obj N="Value" RefId="116">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">F-032 (perri field finding 2026-07-08, LEGACY-SPT-PARITY-GAP, data-loss): commune/signoff ingest MUST NOT delete a drop until its content is DURABLY COMMITTED to every APPLICABLE tier — a slice that cannot be committed this ingest must leave the drop in place for a later ingest (retry when the precondition resolves) OR durably preserve the un-committed slice, NEVER delete-then-lose. ROOT (doyle triage, code-grounded): ingest_drops (spt-live/src/ingest.rs:200) unconditionally `remove_file(&amp;drop_path)?` AFTER route_slices (ingest.rs:121), but route_slices GATES the project tier on `!project_id.is_empty()` (ingest.rs:156) — when the endpoint's cwd is unresolved/owlery-internal at ingest time the project_id is empty, so the `&lt;project-context&gt;` slice is PARSED but never write_context'd/commit_project'd, yet the source drop is still deleted → the project-context content is permanently lost (black-hole). perri's repro: a two-sliced echo-commune (&lt;live-context&gt; role+release recipe + &lt;project-context&gt; v0.17.4 status + Items 3-5 map) INGESTED (file deleted) yet never surfaced at her next SessionStart resume-pull; adapter exonerated (file-write + slicing tags correct); fixture at (system temp</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">7.39 Per-session identity env (`SPT_ENDPOINT_ID`/`OWL_SESSION_ID`/`SPT_AGENT_ID`) is NEVER inherited — the daemon scrubs it at startup AND on every role spawn, regardless of any role's declared `env_remove` `[REQ-HAZARD-DAEMON-IDENTITY-ENV-SANITIZE]`: ### 7.40 A commune/signoff drop is deleted ONLY after every applicable tier is durably committed — an un-committable slice preserves the drop, never delete-then-lose `[REQ-HAZARD-COMMUNE-INGEST-BLACKHOLE]` &lt;!-- --&gt; - **Failure (paid-for, perri field finding 2026-07-08 — F-032, data-loss):** `ingest_drops` unconditionally deleted the drop after `r</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-REGISTRY-APPLY-TRANSACTIONAL</S>
        <Obj N="Value" RefId="117">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">REGISTRY-LIFECYCLE W1 (ADR-0040 decision 3, hertz defect B leg 3): registry feed application is TRANSACTIONAL per feed — serve_registry_feed merges decoded labels + instance updates in memory across transport chunks and applies ONCE at EOF (or one bounded batch commit for oversized feeds); write_snapshots runs O(feeds), never O(chunks x record-kinds); attention-shift side effects fire once post-merge. No synchronous full-registry rewrite inside a per-chunk drain iteration (KH 7.12/7.43 discipline on the brain side — the per-chunk rewrites are what stalled IPC drain and manufactured the 15s seat-writer poisons). Gate: impl — accumulate-then-apply; unit — snapshot-write counter across a multi-chunk feed == 1 (bounded batches: == ceil(records/batch)), merge result equals per-chunk semantics, gate policy still applied per record; int — rides REQ-HAZARD-REGISTRY-STALL seam (snapshot writes O(feeds), zero poisons); doc — ADR-0040.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Decision: &lt;!-- --&gt; 1. **One-way (fire-and-forget) stream families are terminal at FIN, sender-side.** The registry feed pump retires its own row after successful write+FIN via the existing `net-stream-retire` verb (best-effort on N-1 brokers, per ADR-0038 A). A one-way family's exchange is definitionally over at FIN; keeping the row eligible reproduces the O(history) defect forever. 2. **Eligibility filtering is server-side.** `stream_infos` excludes `initiated_locally` rows (alongside `retired`) before serializing. Consumers keep their client-side guards (double-filter harmless; N-1 compatibl</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-CONSENT-3</S>
        <Obj N="Value" RefId="118">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Per-capability approval gates (class-keyed): the require_approval enum may ride INDIVIDUAL [shell.capabilities] entries — gating the dangerous ACT, not just the spawn — with an optional class_key scoping the grant qualifier finer than the capability id ((owner endpoint x device class x node); a remembered HID-class attach grant never authorizes a storage-class attach). Reuses the grant store + interactive escalation + tighten-only floor (REQ-CONSENT-1/2 plumbing). Spawn gates govern EXISTENCE; capability gates govern ACTS — an explicitly distinct invariant (CONTEXT:283, ratified 2026-06-11 Gateway grill).</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Shell model (detailed): &lt;!-- --&gt; **per-capability approval gates** (ratified 2026-06-11, Gateway grill): the same `require_approval` enum may ride **individual capability entries** in a shell manifest — gating the dangerous *operation*, not just the spawn. Same grant store, same interactive escalation, same floor semantics. A capability may declare a **class key** so grants are scoped finer than the capability itself: the first consumer is the usbip shell's `attach`, granted per **(owner endpoint × device class × node)** — a remembered HID-attach grant never authorizes a storage-class attach.</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-PSYCHE-SID-CUSTODY</S>
        <Obj N="Value" RefId="119">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">W2 (F030, design §3): the psyche mints and keeps its OWN session id, stored in the nested {id}-psyche perch record — {session_id} in psyche role templates becomes the psyche's sid, never the parent's (today's fill at livehost.rs:518 is the PARENT's — the custody bug). Parent boundary (/clear, /compact) does NOT rotate the psyche sid (the psyche's conversational thread survives parent resets — its job). resume_psyche validates the custody key before spawn (resume.rs:183). Reseed path: psyche session lost/invalid → ResumeMode::FreshWithPreload (download_psyche_context composes role/live/project into {psyche_context}, resume.rs:100) + LOUD PSYCHE_RESEED:{id} marker (custody-loss loop visible; W1 budget bounds it). If the parent sid is still needed by a template it gets its OWN explicit key {parent_session_id} — never aliased. Red-first: parent `api boundary clear` → nested perch sid UNCHANGED (today it is the parent's — guard-revert reproduces).</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">The role spt-core actually drives — one bounded turn per Psyche event.: &lt;!-- --&gt; &lt;!-- --&gt; // then it exits — no resident process, no detach.: &lt;!-- --&gt; **Custody sid — `{session_id}` is the Psyche's OWN id.** In a psyche role template `{session_id}` is the **Psyche's own minted session id**, kept in its nested `&lt;parent&gt;-psyche` perch record — **not** the parent's. A parent boundary (`/clear`, `/compact`) rotates the *parent's* sid but does **NOT** rotate the psyche sid: the Psyche's conversational thread survives parent resets (that is its job). When a template still needs the parent's sid it t</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-MANIFEST-3</S>
        <Obj N="Value" RefId="120">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Adapter strings — [strings] KV tree, dot-path get-string resolving through the profile leaf-replace overlay, set-string editing a local profile's [strings] only; data-only (nothing executes a string)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Runtime model: **adapter strings** (ratified 2026-06-11, Gateway grill): &lt;!-- --&gt; A `[strings]` manifest section — an adapter-authored JSON/TOML KV tree, dot-path-readable by anything on the node via `spt adapter get-string &lt;adapter-option&gt; &lt;key.path&gt;` (e.g. a harness hook fetching per-profile `additionalContext` — one hook script serves every profile, only the data differs). Resolution rides the **same leaf-replace profile overlay** as the rest of the manifest: a shipped or local profile may override base strings; `get-string` returns the merged view for the named adapter option. **Strings ar</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-RESUME-UNBOUND-STAMP</S>
        <Obj N="Value" RefId="121">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">RC-RENDER-TRUTH W1 (ADR-0042 decision 2, operator-spotted second root seam): resume launch transitions an existing offline perch to UNBOUND before/with the broker spawn — UNBOUND semantics are fresh/resume-invariant (broker session exists + harness not bound = UNBOUND); generation/session-safe ROLLBACK to offline on spawn failure or session death; bind owns UNBOUND-&gt;ONLINE. Supersedes the rc.rs 'resume gets no UNBOUND stamp — accepted' boundary note (the pre-bind window can be PERMANENT: stuck native resume, SessionStart never fires — field-proven; truthful UNBOUND is the operator-recovery surface that let `spt rc` reach the wedged TUI). Writer-truth complement to REQ-RC-HONEST-SESSION-AUTHORITY — both land, neither substitutes. Gate: impl — UNBOUND stamp at resume spawn + rollback + bind transition; unit — stamp fires on existing-offline perch resume, rollback on spawn-fail restores offline, generation guard refuses a stale rollback over a newer bind; int — resumed-but-never-bound endpoint reads UNBOUND (not offline) and `spt rc` attaches to its live session; doc — ADR-0042.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Decisions: &lt;!-- --&gt; 1. **`spt rc` consults the honest-session authority before the offline fast-fail.** Normal `spt rc &lt;id&gt;` runs the same bounded `SessionProbe::has_live_session_honest` gate `endpoint run` uses (ADR-0041 single liveness authority). An honest session exists → attach via the session-confirmed path regardless of persisted status. No honest session → the existing offline refusal stands. A claimed session with a dead client tree → refusal/reap, never attach. Reuse `SessionProbe`; no new liveness heuristic. &lt;!-- --&gt; 2. **Resume stamps UNBOUND.** A resume launch transitions an exist</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-DOCS-2</S>
        <Obj N="Value" RefId="122">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Sub-10-minute runnable killer quickstart per audience</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Harness integration checklist: &lt;!-- the harness-author checklist: every contract surface a harness touches, grouped by necessity, mapped to the interaction lifecycle, with the modern Claude Code adapter (spt-claude-code) as the worked example --&gt; // Quickstart: build an adapter: &lt;!-- the dev-agent killer quickstart: minimal adapter satisfying the manifest + api contract, walked via the shipped mock adapter --&gt; The "build a harness for spt-core" hello-world: take the reference **mock adapter** apart, register it, drive the contract with real commands, then swap in your own harness. No spt-core</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-MESH-5</S>
        <Obj N="Value" RefId="123">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-DAEMON-BITS-AMBIGUITY</S>
        <Obj N="Value" RefId="124">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">SEED (inactive, RCA-first — do NOT close on agreement): nothing on a node surfaces WHICH BITS ARE SERVING, and the same silent wrong-state shape bit twice in one day (2026-07-25). Case 1 (adapter-side echo): a post-reboot ensure race left a dev-build alchemy Hub Daemon serving release shells — version skew visible only by manually comparing process image paths. Case 2 (core, field-measured by flynn): TWO spt daemons resident with live brains on one node — the installed main daemon (owning ALL sockets: the 5474 listeners and every established connection, single home_tag pipe family) and an orphaned scratchpad-built daemon (auto-started into the node by ensure_running from a stray dev-binary invocation at 16:18, holding zero sockets, resident for hours) — while `spt --version` on any binary file answers nothing about which process is answering. Measured sharp edges to carry into the RCA: (a) exe path and resolved HOME are independent — the orphan ran scratchpad bits against the DEFAULT home, so 'where the binary lives' predicts nothing about 'whose state it mutates'; (b) the brain.ready breadcrumb is ONE FILE PER HOME, LAST-WRITER-WINS, keyed by generation — with two brains in one ho</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-SEND-WINDOW-DRAIN-HONOR</S>
        <Obj N="Value" RefId="125">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">F-035 (field finding 2026-07-09): active_only = POLL-ONLY for a relay-bearing live agent -- it must NEVER be RELAY-delivered (its contract is 'active hook window only, never wakes' per spool.rs WINDOW_ACTIVE_ONLY doc + cli.rs:85; `spt send --active-only` / the hidden `--deferred` alias and `send_deferred` shell-context mint it). FIELD SYMPTOM: lia (a full live agent -- relay-for-idle, poll-for-busy) surfaced an --active-only msg on her IDLE RELAY. RCA JOURNEY: v1 RCA (docs/F-035-RCA.md) analyzed the WRONG class (spt-hosted-relay-LESS, the idle-edge inject leg) and proposed a COLLAPSE that would have broken the shipped F-023 anti-starvation gate (docs/F-035-CONFLICT.md); operator reclassified to a relay-bearing live agent; the relay-class re-RCA (docs/F-035-RELAY-RCA.md) traced EVERY active_only-&gt;relay carrier and found them ALL ALREADY GUARDED on main@2c05dc9 -- so spt-core has NO code bug. doyle FINAL RULING: the real leak is the ADAPTER's busy-&gt;idle poll-&gt;idle-representation handoff (spt-claude-code -- a legitimate `api poll` on going idle drains active_only, then the adapter renders it into the idle/relay surface), OUTSIDE spt-core; perri's lane. spt-core DELIVERABLE = a REGRESS</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">&lt;!-- F-035 RCA — report-before-fix. --&gt; # F-035 RCA — idle-edge parked-drain ignores the delivery-window tag</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-API-3</S>
        <Obj N="Value" RefId="126">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">commune/signoff are file-drops, not commands</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-EVENTPART-REASSEMBLY</S>
        <Obj N="Value" RefId="127">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">EVENT-PART split/reassembly is byte-exact; orphan parts dropped silently</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">The `&lt;EVENT&gt;` wire contract: &lt;!-- --&gt; ### EVENT-PART reassembly (listener stream)</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-PICKER-CURRENT-DIR-LABEL</S>
        <Obj N="Value" RefId="128">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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 `&lt;display&gt; (CURRENT DIR)`. A-3: the not-in-history current-dir row changes from `Here: &lt;run_cwd&gt;` to `CURRENT DIR --&gt; &lt;project&gt;`, 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.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-SERVE-OWNERSHIP-GENERATION</S>
        <Obj N="Value" RefId="129">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">REDISPATCH-STALL W1 (ADR-0038 Amendment, fix 6): terminal-exclusion enforced PRE-SERVE + ownership/generation validation on attach/detach — a stale worker can never detach or displace a REPLACEMENT controller (today detach_if compares Arc ptr identity only; the serve path re-checks nothing at completion). Covers the UNFINISHED-stale-row control-steal shape (raw-close no-FIN viewports, emphasys C2 leak class feeding it) that finished-row retirement (D1/D1b) definitionally cannot see — the discriminating field observable on the next live steal catch = the stolen row's finished+retired flags. Gate: impl — pre-serve terminal exclusion + generation/ownership tokens on attach/detach; unit — stale-generation detach refused while the same-generation detach lands; int — T6 (UNFINISHED-stale attach row + live current controller + dispatcher restart: neither takes nor clears the replacement, D1/D1b green alongside); doc — ADR-0038 Amendment. Kin REQ-HAZARD-REDISPATCH-CONTROL-STEAL (the finished sibling), REQ-REDISPATCH-FINISHED-RETIRE.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Consequences: ## Amendment — REDISPATCH-STALL (2026-07-16) &lt;!-- --&gt; &lt;!-- --&gt; &lt;!-- --&gt; &lt;!-- --&gt;</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-DIGEST-GENERATION-SUPERSEDE</S>
        <Obj N="Value" RefId="130">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">W3 (LIFECYCLE-TRUTH, digest projection truth — flynn filing spt-mobile d0aa3f4): a one-shot `endpoint digest --json` snapshot must return each logical activity row ONCE across a checkpoint/resume, not once per seq-generation. ROOT (spt-core-side, not a consumer bug): the K-session span (digest.rs activity_spanned, SPAN_SESSIONS=5) runs the [digest] extractor per session file and tags each row seq=(ledger_ordinal&lt;&lt;32)|localseq (REQ-DIGEST-CURSOR). A checkpoint/resume (self-/clear + Psyche rebuild) makes the harness REPLAY the prior generation's transcript into the NEW session file, so the ancestor's rows appear in BOTH the ancestor file AND the resume file at the SAME localseq — the span UNIONS them, one logical row surfacing under two full seqs (gen23,local208) + (gen25,local208), identical text/ts/localseq. Consumers dedup by exact seq (the documented authoritative key) so nothing collapses -&gt; duplicate rows in every snapshot / `--after` view (`--follow from:0` is CLEAN — it reads current-generation only; the SPAN is the sole culprit). The trigger cannot disambiguate: `api boundary clear` records SessionTrigger::Clear for BOTH a fresh /clear (disjoint) and a carry-forward checkpoi</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-STAMP-CONVERGENCE-ORDER</S>
        <Obj N="Value" RefId="131">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-LIVENESS-ORACLE-SOUND</S>
        <Obj N="Value" RefId="132">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">TEARDOWN-AUTHORITY W2 (todlando W1 gate-round-0 finding, doyle-scoped from the LANDED W1 code 2026-07-19): 'does this pid still exist' has ONE answer in spt-core and it is derived from the OS process table. TODAY spt-daemon/src/broker.rs session_is_zombie computes wrapper_alive from spt_store::proc::is_process_alive, which probes OpenProcess on Windows — and OpenProcess keeps SUCCEEDING for a TERMINATED process while any parent holds an open handle, which the broker ALWAYS does (Arc&lt;PtySession&gt;) for every PTY child it spawned. A correctly-reaped harness therefore reads ALIVE, flipping zombie_verdict off its PRIMARY class (Some(false) = dead root + surviving record = always a zombie) onto the conditional arm, which additionally demands adapter_labeled &amp;&amp; past_grace &amp;&amp; !has_live_descendants. CONSEQUENCE, live today: a dead-root session that is NOT adapter-labeled is claimed LIVE indefinitely — `endpoint run`'s dup-guard refuses ENDPOINT_ALREADY_LIVE over an already-dead tree and cmd_rest's Suspend alive_hint forces from=alive on the same false claim (both via has_live_session_honest, cli.rs:2014 and :3805). REQ-ENDPOINT-CYCLE-HONEST exists to give the cycle verbs ONE liveness authori</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">7.50 The liveness oracle answers from the process table, never from a handle a caller still holds `[REQ-LIVENESS-ORACLE-SOUND]`: &lt;!-- --&gt; - **Failure (paid-for, found by todlando during TEARDOWN-AUTHORITY W1 gate round 0, 2026-07-19; latent in `session_is_zombie` since the cycle verbs were built):** `spt_store::proc::is_process_alive` probes `OpenProcess` on Windows, which keeps SUCCEEDING for a TERMINATED process while any parent still holds an open handle to it — and the broker holds `Arc&lt;PtySession&gt;`, hence such a handle, for every PTY child it spawned. So a correctly-reaped harness reads A</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-PAIR-7</S>
        <Obj N="Value" RefId="133">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Subnet icon (inline image metadata, GUI-only consumer)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-STORE-1</S>
        <Obj N="Value" RefId="134">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">spt-store::BranchStore (git branch as versioned KV; commit=checkpoint/tip=resume, atomic multi-key, merge-native sync) is the substrate for coarse/durable/audited state (context, registry snapshot+distribution, daemon checkpoint); hot paths (B5 fsync journal) + indexed queries (SQLite spool) excluded (ADR-0011)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-REDISPATCH-STALL</S>
        <Obj N="Value" RefId="135">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">REDISPATCH-STALL W1 (KNOWN-HAZARDS 7.43, hertz v0.34 field RCA 2026-07-16 — recurrent 20-30s PTY/RC freezes, 17-62s DISPATCH tails): one wedged stream subscriber must NEVER stall stream serving, and recovery machinery must not manufacture new replay victims. Today: claim retries x the broad Err(_) opener fallback (dispatch.rs:414) install throwaway peek subscribers whose StreamLog::attach replays the entire retained ring UNDER the per-stream mutex with discarded write errors and the poisoned subscriber left installed — serial 15s bounded-write poison windows (33 observed, all 15,000-15,154ms) composing into the field stalls. Gate: int — production-path regression at the REAL run_dispatch_loop + StreamLog + serve_attach seams: wedge one subscriber conn, prove producer appends and unrelated streams stay flat while the poisoned subscriber is removed and the stream recovers (no abandonment); doc — KNOWN-HAZARDS 7.43. Binding: redispatch D1/D1b stay green every leg. HEAVY nextest group at birth. Kin REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE + REQ-DISPATCH-FALLBACK-CIRCUIT (the mechanisms), REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK (the deadline that fires), ADR-0038 Amendment.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">7.42 A node holding a valid roster address for a peer is NEVER route-less — a failed dial must not delete the only bootstrap route `[REQ-HAZARD-MESH-BOOTSTRAP-TRAP]`: ### 7.43 One wedged stream subscriber must NEVER stall stream serving — 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]` &lt;!-- --&gt; - **Failure (paid-for, hertz field RCA 2026-07-16 — live v0.34 boxes, recurrent 20–30s PTY/RC freezes, DISPATCH tails 17–62s):** a COMPOSITION, not one new timer. The dispatcher's ret</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-RESUME-ADAPTER-FOLLOWS-SESSION</S>
        <Obj N="Value" RefId="136">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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&lt;String&gt; 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</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-NET-2</S>
        <Obj N="Value" RefId="137">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">n0 relay default + self-host knob + plain-language disclosure</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-SEAM-PSYCHE</S>
        <Obj N="Value" RefId="138">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">spawn-psyche seam (fresh + resume templates)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-ATTACH-WEDGE</S>
        <Obj N="Value" RefId="139">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">A legitimately dead PTY child (real crash/kill) + an undrained operator pump must NOT wedge the broker for all other clients. ROOT (v0.12.0 real-harness defect): loopback attach output is a blocking write_all into a bounded 64KB tokio duplex (nethost.rs:1040,1090); when the operator's rc pump stops draining (tab closed) the buffer fills and write_all blocks forever (the 'loopback never hangs' assumption at nethost.rs:1103 is false), parking a worker in the 2-worker net runtime (nethost.rs:640); a couple of these saturate BOTH workers → every new attach / `endpoint run` stalls right after 'PUMP_IPC_READER: spawned' → 30s FIRST_EVENT_GRACE → 'no output / dead or wedged'; `daemon stop` cannot join the stuck workers. DISTINCT from the removed B1 path-(c) mutex deadlock. DISPOSITION = PROVE-DON'T-CHANGE (doyle GATE-PASS @e883f45, 2026-06-18): this ROOT is the SUPERSEDED v0.12.0 hypothesis — the post-L0 code ALREADY prevents the wedge, so NO fail-fast / worker-count code was added. serve_attach forwards fire-and-forget (net_stream_send op_id=None) and the broker-side send_stream is already BROKER-QUIC-DEADLINE-bounded (bounded_block_on, 10s); the loopback duplex is drained broker-INTERNA</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-PSYCHE-OUTBOUND-PROXY</S>
        <Obj N="Value" RefId="140">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Psyche outbound captured + sanitized: the live-Psyche turn driver captures stdout (never Stdio::null), and the daemon strips/re-stamps Psyche-supplied from=/target and constrains routing (reply→__REPLY_TO__ sender, notify→own user/subnet) (7.3)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-VIEWER-CLOSE-DETACH</S>
        <Obj N="Value" RefId="141">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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 &lt;id&gt;`. 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_</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Terminal wrapper: **A view is independent from the endpoint** (invariant): &lt;!-- --&gt; 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 keeps running under the daemon and stays re-attachable via `spt rc &lt;id&gt;`. A view is a transient frontend over a daemon-owned session, never the session's lifeline. *Implementation:* the daemon must never live inside the launching terminal's process grouping — on Windows the cold-started daemon is lau</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-RC-ATTACH-FAILFAST</S>
        <Obj N="Value" RefId="142">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">B1: `spt rc &lt;id&gt;` to a DEAD or non-streaming session fails fast with a clear message, never an INFINITE blank screen. Today rc.rs run_attach (209-231) + pump spawns PUMP_IPC_READER and blocks: the poll times out each slice but the stream never produces output, so the operator sees a permanent blank (operator: fresh wall-f attached, closed tab, then `spt rc wall-f` HUNG — the broker still resolved a session for it). FIX: (a) once B2 lands, gate attach on is_online/status — an offline endpoint yields a clean 'endpoint offline, start it' not an attach; (b) fail-fast — if the attach-open ack / first output does not arrive within a bound, surface a clear message, never an infinite blank; (c) the broker EOFs the attach stream when the session's child is dead, so rc's existing PumpEnd::BrokerGone graceful path (REQ-HAZARD-RC-EOF) catches it. PIN the exact sub-mechanism with a repro test FIRST (dead-session-lingers-in-broker vs reaped-but-rc-waits vs alive-resting-no-wake — the wall-f Windows tab-close: child alive-silent vs dead-not-reaped). (v0.12.0)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-ARCH-1</S>
        <Obj N="Value" RefId="143">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Many small acyclically-layered crates</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-DOC-ENDPOINT-DROP-RESOLUTION</S>
        <Obj N="Value" RefId="144">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">D1 (F028, perri F-c; docs/truth): SI-1's resolution rule — a RELATIVE watched drop dir resolves against the ENDPOINT's cwd, never the daemon's (KH 7.28, shipped v0.22.0) — is documented NOWHERE public. Add it to harness-contract/manifest.md + the manifest schema field descriptions so an adapter author knows a relative commune_dir/signoff_dir is endpoint-resolved. docs-drift gate applies. See triage D1.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-DAEMON-SERVICE-INSTALL</S>
        <Obj N="Value" RefId="145">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">F-038 RIDER (flynn nice-to-have, QUEUED not activated): a documented OS-service registration recipe or `spt daemon install-service` verb so the daemon itself survives box reboot (flynn's box runs managed_by:null = daemon-at-boot unprovisioned; kitsubito's hand-rolled systemd --user unit = prior art). NOT in MSG-IDENTITY scope — REQ-ENDPOINT-AUTOSTART covers the daemon-start-to-endpoint leg; the boot-to-daemon leg stays interim (logon scheduled task / systemd unit). Activate at an infra-provisioning milestone; shape (recipe doc vs verb) ruled then. Kin [[daemon-service-detection-gotcha]] (global-OS-state detection blind on dev box — a verb must not regress that), [[kitsubito-linux-rig]].</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-LIVEHOST-BOOT-LIVENESS-GATE</S>
        <Obj N="Value" RefId="146">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-CONV-2</S>
        <Obj N="Value" RefId="147">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Event-driven advertisement: endpoint online/offline transitions (ready-listener start/stop, rest-state transition, perch death) trigger an immediate advertise_local + peer push as a WAKE of the existing pump loop (no second advertisement path — epoch lease + visibility gates ride unchanged); the cadence stays the steady-state floor (M8 decision 15)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-EFFECTIVE-INSTANCE-STATE</S>
        <Obj N="Value" RefId="148">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">A-1 (REMOTE-TRUTH triage §A + ADR-0033 §Decision): the effective instance state of a perch is DERIVED through ONE shared function — liveness discriminates warm/cold, stored rest intent refines within warm, absent intent NEVER defaults active. ROOT (certain): resting::apply_event derived its `from` off the stored rest_state field ALONE (resting.rs:225, `unwrap_or(RestState::Active)`) — a cold perch (offline) with no intent answered `from=Active`, so a Wake event found it 'already in target state' and returned Ok(None) = the field NO_EDGE-on-a-definitely-suspended-endpoint bug (the banked F-028 rest_state-void seed). advertised_status (registryhost.rs:821) ALREADY derived correctly (is_perch_alive→intent-refined / is_perch_unbound→Dormant / cold→Suspended) — the two readers disagreed. FIX (Q1 shared derivation, hazard-class): a pure `effective_rest_state(alive, unbound, intent) -&gt; RestState` mirroring advertised_status, consumed by BOTH advertised_status (mapped RestState→Status, behavior identical) AND apply_event's `from` (real is_perch_alive/is_perch_unbound reads); void + cold ⇒ Suspended. Bonus: kills the spurious active→suspend echo a cold+void perch used to fire (on_rest_edge </S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">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-BLIND]`: ### 7.32 The effective resting state MUST be derived through ONE shared liveness-aware function — a stored-intent-alone read lies about cold perches `[REQ-EFFECTIVE-INSTANCE-STATE]` &lt;!-- --&gt; - **Failure (paid-for, field evidence):** two rest-state readers derived the effective instance state independently. `registryhost::advertised_status` read it liveness-aware (cold ⇒ Suspended); `resting::apply_event` derived its `from` off the stored `rest</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-ENDPOINT-LIST-RENDER-POLISH</S>
        <Obj N="Value" RefId="149">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">A6 (F028, operator, 4 asks): `spt endpoint list` render polish. (a) the 'Shared subnets' line is NOT dim — LIGHT_GRAY = "37" (cli.rs:3019) is standard-palette WHITE, indistinguishable from row text; use SGR 90 (bright-black/gray) for the dim intent. (b) the `Total:` line takes the same dim color. (c) move the status glyph ADJACENT to the endpoint name (operator: 'right behind the endpoint name'), mirroring the picker's glyph-beside-name presentation (today the glyph sits at the end next to the status word). (d) color the status WORD like the picker TUI (green ONLINE / gray OFFLINE / blue when driven, matching picker glyph semantics). All in render_node_grouped/render_instance_row (cli.rs ~3000s); pure render with an injected color decision — unit-testable off a tty. See triage A6.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-RUN-ID-REUSES-ADAPTER</S>
        <Obj N="Value" RefId="150">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">D-1 (REMOTE-TRUTH triage §D-1): `spt endpoint run --id &lt;id&gt;` with NO --adapter, when &lt;id&gt; 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</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-REL-3</S>
        <Obj N="Value" RefId="151">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Two-key release-signing trust anchor: primary + offline never-used recovery, both pubkeys embedded in the binary's trusted set, manual local signing (ADR-0015)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-EP-1</S>
        <Obj N="Value" RefId="152">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Day-one endpoint types; open type system</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-DISPATCH-HYGIENE-TELEMETRY</S>
        <Obj N="Value" RefId="153">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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).</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Consequences: ## Amendment — REDISPATCH-STALL (2026-07-16) &lt;!-- --&gt; &lt;!-- --&gt; &lt;!-- --&gt; &lt;!-- --&gt;</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-RELEASE-CHANNEL-PRIVATE</S>
        <Obj N="Value" RefId="154">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">THE-FORKENING W2 (ADR-0036 §2): the publish pipeline targets `BigscreenVR/spt-bs-releases` — release.yml assemble/draft/flip retargeted (draft lands on the bs releases repo, untagged, per the existing spt-releases pattern); docs-publish.yml RETIRED (no public docs, ADR-0014 superseded) with the mdbook build folded into ci.yml as the drift gate (CLAUDE.md mandate: doc generation stays CI-gated); counter + signing key + update-set format CONTINUE unchanged (trust anchor is the continuity, carrier is not). Gate: impl — workflow retarget + drift-gate fold; int — a full release dry-run assembles binaries + docs asset + signed update-set against the private channel. Kin REQ-DOCS-RELEASE-ASSET, REQ-UPDATE-GH-TRANSPORT, ADR-0036.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-START-2</S>
        <Obj N="Value" RefId="155">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Harness-hosted startup: api seed then listen</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-INST-6</S>
        <Obj N="Value" RefId="156">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Deferred messages not delivered to dormant/suspended instances</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Deferred Features: | Feature | Cut from | Why deferred | Trigger to revisit | |---|---|---|---| | Scrollback on-disk spillover | terminal wrapper v1 | In-memory ring covers the common case; spillover adds a persistence/rotation story | First long-running session that overflows the ring usefully, or any "scroll back further than the buffer" user need | | Sidecar adapter process (long-running, wire-protocol) | harness contract v1 | Manifest + `spt.exe` subcommand surface covers v1 harnesses; sidecar only earns its keep for streaming / in-memory cross-event state | A harness outgrows manifest+hoo</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-DAEMON-9</S>
        <Obj N="Value" RefId="157">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Net-bind boot-race resilience: a daemon that comes up net-less (NetHost::start failed — e.g. the systemd unit autostarted before the network/DNS stack was ready, `Failed to create an address lookup service`) must SELF-HEAL — retry the net bring-up in the background with capped backoff and, on success, attach net to the broker + spawn the dispatcher/peer-pump (which today are gated on `net_up` at boot and so never start, leaving the node silently unreachable until a manual restart — kitsubito 2026-06-08). Status surfaces the net-less state honestly (a net-less broker renders as 'no connection', not only a pump-STALLED line with a bogus pre-boot heartbeat age). The installer's autostart unit waits for the network (`Wants=/After=network-online.target`) as belt-and-suspenders.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-PSYCHE-CRASHLOOP-BACKOFF-SHUTDOWN</S>
        <Obj N="Value" RefId="158">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-INSTALL-10</S>
        <Obj N="Value" RefId="159">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-DRIVEN-BY-SELFHEAL</S>
        <Obj N="Value" RefId="160">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">An spt-hosted endpoint's ONLINE+CONTROLLED state (`driven_by`) must CLEAR even when the detach IPC is lost — do NOT rely on the detach signal (same lesson as REQ-HAZARD-HOSTED-LIVENESS-RECONCILE B2): the reconcile loop clears `driven_by` when the endpoint has no live controller/session. Today a wedged or lost pump never delivers the detach, so the endpoint stays latched CONTROLLED forever. Composes with W1 (the wedge no longer blocks the detach) and rides the same pull-primary reconcile substrate as B2. (v0.13.0)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-SEAT-LIFETIME-BOUNDED</S>
        <Obj N="Value" RefId="161">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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&lt;id&gt; 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.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Decision: &lt;!-- --&gt; 1. **One-way (fire-and-forget) stream families are terminal at FIN, sender-side.** The registry feed pump retires its own row after successful write+FIN via the existing `net-stream-retire` verb (best-effort on N-1 brokers, per ADR-0038 A). A one-way family's exchange is definitionally over at FIN; keeping the row eligible reproduces the O(history) defect forever. 2. **Eligibility filtering is server-side.** `stream_infos` excludes `initiated_locally` rows (alongside `retired`) before serializing. Consumers keep their client-side guards (double-filter harmless; N-1 compatibl</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-PSYCHE-ACCOUNT-REFUSAL-EXIT</S>
        <Obj N="Value" RefId="162">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">RESERVED EXIT 96 — account/credential refusal from a psyche_resume turn: the inner tool refused for account-level reasons (spend/usage cap, expired/revoked credential, org quota) — session healthy, code healthy, retry correct-but-pointless until a HUMAN acts. Core discriminates on the EXIT CODE ALONE (text-blind, the exit-95 layering exactly: adapters own text matching because their inner tool's wording is theirs to track; core's contract survives any rewording). SEMANTICS ruled 2026-07-26: (1) OWN PACING, fully separate from the C3(b) strike budget — an account refusal fails FAST (refused before a billed turn), so ten near-instant cycles could exhaust the defect budget in seconds and kill the psyche host as a thrashing component while nothing thrashes; the strike budget is a DEFECT budget and an outage must not be able to spend it (fold-with-higher-threshold REFUSED at ruling: it keeps the bug in a quieter form). Slow capped exponential ~60s doubling to ~15m cap, held INDEFINITELY (no give-up: a cap clears on human action or a calendar boundary — unpredictable but CERTAIN — and a permanently-given-up psyche is invisible), reset on first success, no state to unwind. (2) DISTINCT SU</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">The role spt-core actually drives — one bounded turn per Psyche event.: &lt;!-- --&gt; &lt;!-- --&gt;</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-BRAIN-UPDATE-RESTART-CLEAN-CLOSE</S>
        <Obj N="Value" RefId="163">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-PSYCHE-ROLE-OPTIONAL-SKIP</S>
        <Obj N="Value" RefId="164">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">W1 (LIFECYCLE-TRUTH): a manifest with NO [session.echo_commune] role SKIPS commune-sync (debug-level note, no strike) instead of hard-failing the turn. ROOT (perri filing, recovered): missing role -&gt; commune-sync hard-fails -&gt; 3-strike stamps the host ('manifest declares no [session.echo_commune] role') while the published contract presents the role as an optional template. FIX: missing OPTIONAL role = skip, not a turn failure.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-PICKER-PROJECT-HISTORY-TRUTH</S>
        <Obj N="Value" RefId="165">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">#1: picker project history is derived from sessions.log cwds (newest-&gt;oldest, deduped by project_id_for_dir) UNION context-store branches, EXCLUDING owlery-internal paths (any cwd under spt_home()/owlery) everywhere a project is displayed or inferred. Fixes three stacked defects (crates/spt/src/picker/data.rs): (1a) project_history_for (data.rs:372) reads ONLY context-store p-* branches, which are empty on this box -&gt; history []; (1b) the fallback origin project (data.rs:207) is derived from info.json.cwd = latest-boot-cwd (rewritten every rebind), not origin; (1c) psyche-host sessions bind owlery-internal cwds that pollute history. Full DIRS stay available in the model (feature #5 needs them). PROJECT REPRESENTATION RULING (operator 2026-07-03): project IDs ONLY, EVERYWHERE incl local display; on ID collision disambiguate minimally via a PURE disambiguate_project_ids(entries)-&gt;display-names fn (append one-level-up parent folder and/or root drive letter, e.g. 'spt-core (projects)' vs 'spt-core (D:)'). See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #1.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-STALE-INDEX-LOCK</S>
        <Obj N="Value" RefId="166">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Sweep stale lockfiles on daemon boot (1.3)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-NOTIF-UPDATE-ROW-VERSION-RETIRE</S>
        <Obj N="Value" RefId="167">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-INST-10</S>
        <Obj N="Value" RefId="168">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Qualified addressing [subnet:]id[@node] + ambiguity forces qualification</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-WMI-DAEMON-WINDOW</S>
        <Obj N="Value" RefId="169">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">`spt daemon start` launches the daemon with NO visible console window. REGRESSION (v0.12.1 L1.5): the WMI job-neutral launch (spawn_daemon_via_wmi) set CREATE_NO_WINDOW on the launching powershell but NOT on the Win32_Process.Create call — Win32_Process.Create does not inherit it, so the spawned cmd.exe env-forwarding wrapper popped a console window on every cold-start (violating REQ-INSTALL-10's v0.7.4 no-persistent-window invariant; the old detached_no_inherit path set DETACHED_PROCESS|CREATE_NO_WINDOW). FIX: pass a Win32_ProcessStartup with CreateFlags=DETACHED_PROCESS (0x8 — no console so no window; CREATE_NO_WINDOW 0x08000000 is NOT a valid Win32_ProcessStartup flag → ReturnValue 21 invalid-param, which is why the naive port fails) + ShowWindow=SW_HIDE(0) belt, via the ProcessStartupInformation argument. (v0.12.2)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-MSG-IDLE-TRANSLATION-BINARY</S>
        <Obj N="Value" RefId="170">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">spt-hosted idle message delivery via an adapter TRANSLATION BINARY (ADR-0022). New opt-in manifest section `[message-idle-translation-binary]` = a TABLE carrying a `path` scalar (doyle OPT-B ruling: modeled as a table, not a bare top-level scalar, so a preceding section cannot silently absorb it + N+1 extensible; spt-core does NOT deny_unknown_fields, so a future key degrades gracefully); spt-core LIFECYCLE-manages it (spawn when the endpoint comes up, terminate when it goes down). The binary is a PURE stdin→stdout filter; spt-core owns EVERY PTY write. stdin (JSON-lines): `{type:"init",endpoint_id,node}` first · `{type:"event",envelope:"&lt;EVENT…&gt;"}` per inbound message (ADR-0020 envelope) · `{type:"input"}` content-free ping on each operator keystroke (binary tracks user-idle for its own idle-gated buffering; PTY input content NOT duplicated). stdout (JSON-lines): keystroke-commands `{key:…}`/`{delay_ms:…}`/`{text:…}` (extensible). spt-core applies the emitted sequence to the broker PTY ATOMICALLY (the W1 coordination — REQ-HAZARD-INJECT-CONTROL-COEXIST). The daemon poll feed is the ONE idle substrate for both topologies (Q1=A): harness-hosted consumer = the Monitor child, spt-host</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">`[message-idle-translation-binary]` — spt-hosted idle delivery: &lt;!-- --&gt; // `[inject]` — inject-input methods: &lt;!-- --&gt; ### `[message-idle-translation-binary]` — spt-hosted idle delivery (ADR-0022) Opt-in. The adapter's **idle-delivery translation binary**: a pure stdin→stdout JSON-lines filter spt-core lifecycle-manages (spawned when the spt-hosted endpoint comes up, terminated when it goes down). spt-core feeds it the inbound `&lt;EVENT&gt;` feed and reads back keystroke-commands, which spt-core applies to the broker-held PTY **atomically** — controller input is buffered during the emitted sequenc</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-CONTROLLER-GAP-RESUME</S>
        <Obj N="Value" RefId="171">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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() &gt; cap_chunks { pop_front() }`), so re-fetch is exactly-once IFF tail - delivered_through &lt;= cap_chunks (4096) —</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE</S>
        <Obj N="Value" RefId="172">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">The effect journal serializes EVERY PTY effect under one mutex held ACROSS two fsyncs AND the blocking PTY write — so interactive input stutters and ultimately wedges the daemon hard. ROOT (doyle /diagnose, code-grounded + MEASURED on the operator's real Windows box, 2026-06-19): EffectJournal::apply_once (effect.rs:168-188) takes `inner.lock()` and holds it across `write_line(PENDING)` → `effect()` → `write_line(DONE)`, where write_line (effect.rs:235-239) does flush()+sync_all() (a full FlushFileBuffers) — so each effect pays TWO fsyncs under a GLOBAL lock, and the closure `effect()` (the actual PTY write, broker.rs:1257 EffectKind::PtyWrite via attach.rs:197 send_effect) runs while the lock is held. Two operator-visible facets, ONE root: (A) STUTTER/LAG — every keystroke is a PtyWrite effect = 2× sync_all serialized; measured fsync on %LOCALAPPDATA%\spt-core = median 6.5ms, spikes to 198ms (C: was recently at 100%), so ~13ms+ per keystroke best case, hundreds under contention → 'many but not all keypresses take 100s of ms, choppy, worsens with volume'. (B) HARD PERMANENT WEDGE — when a PtyWrite `effect()` blocks (ConPTY input buffer full / harness not draining stdin), the journa</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-PAIR-SEED-ROTATION</S>
        <Obj N="Value" RefId="173">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Removing a node rotates the subnet seed (epoch bump) so an old node/old seed cannot rejoin; trust-store delete alone is NOT revocation because the seed is replicated to every trusted node (ADR-0005 #10)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-REGISTRY-DIR-CREATE</S>
        <Obj N="Value" RefId="174">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">SQLite store opens create their parent dir themselves — a fresh-home registry op must not SQLITE_CANTOPEN (4.9)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">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 &lt;!-- --&gt; - **Failure:** `Connection::open` creates the database FILE but never its parent DIRECTORY. On a fresh home (first boot, fresh CI `_work` dir) a registry op that runs before any perch-creating op (`create_dir_all` side effects) fails `SQLITE_CANTOPEN` — "unable to open database file …owlery\.registry". Timing-dependent: whichever code path touches the home first decides the outcome, so it surfaces as a parallel-test flake (bind-first tests losing the d</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-RC-ATTACH-TRUTH</S>
        <Obj N="Value" RefId="175">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">7.45 Endpoint lifecycle state converges to truth from every death path — no optimistic online without authority, no surviving control stamps, no immortal wake intent, no untruthful create `[REQ-HAZARD-ENDPOINT-LIFECYCLE]`: ### 7.46 An rc surface answers from live session authority, never a stale persisted projection — and a resuming perch is UNBOUND, not offline `[REQ-HAZARD-RC-ATTACH-TRUTH]` &lt;!-- --&gt; - **Failure (paid-for, hertz perri contradiction RCA 2026-07-17/18 + operator field recovery):** the broker hosted an honest live session (client tree alive, `SessionProbe::has_live_session_hones</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-UPD-7</S>
        <Obj N="Value" RefId="176">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Origin-source update bootstrap (`spt update fetch`): pull the latest signed release directly from the GitHub release origin (`SaberMage/spt-releases`) — the per-platform artifact + its `&lt;asset&gt;.release.json` SignedRelease metadata — and stage it through the EXISTING verify→stage pipeline (the same `plan_verified` gate: two-key signature + channel + monotonic rollback floor + SHA-256), after which the normal consent-notif / `spt update apply` flow is unchanged. Closes the peer-only-discovery gap (REQ-UPD-1): a first-in-fleet / isolated node can update with no peer to pull from. The signed-release anchor keeps the GitHub transport untrusted-but-verified.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-LIST-JSON-LIVENESS-PARITY</S>
        <Obj N="Value" RefId="177">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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 -&gt; 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 -&gt; self-suspends a pid-alive gateway. Candidates REFUTED: resource_projection does NOT re-derive liveness (copies instance.status, only skips !routable :</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-INSTALL-9</S>
        <Obj N="Value" RefId="178">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Adapter add from a GitHub release archive: `spt adapter add --release &lt;user/repo&gt; [--tag &lt;tag&gt;] [--asset &lt;name&gt;]` 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)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Installation: **adapter registration (`spt adapter add`)**: How a node comes to *know* an adapter — harness or shell. An explicit **`spt adapter add &lt;path&gt;`** (or **`--github &lt;user/repo&gt;`**) validates the manifest against the published JSON Schema and writes a registration record under `{SPT_HOME}/…/adapters/` — a **copy** of the files for `file_pull`-update adapters (spt-core owns what it later swaps) or a **pointer** for `delegated`-update adapters (the plugin owns + updates its own files). One command + one dir for both `kind="harness"` and `kind="shell"`; the `kind` field differentiates. T</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-MSG-SELF-DETECT-ANCESTRY</S>
        <Obj N="Value" RefId="179">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">#9 (F026, operator field bug): a perch-owned `spt send` from an endpoint's OWN session must self-identify, not mis-stamp `cli@&lt;node&gt;` (whose replies bounce NO_PERCH). ROOT: roster::detect_self_id (roster.rs) was ENV-ONLY — OWL_SESSION_ID matched to info.session_id, else SPT_AGENT_ID — but an agent-session Bash child often carries NEITHER (the env export is spawn-path-dependent), so a perch-owned sender was classified bare-CLI and REQ-MSG-CLI-ORIGIN stamped it cli@&lt;node&gt; (the stamp works as designed on a wrong premise; that REQ's evidence stays intact). FIX: detect_self_id gains leg (c) PID-ANCESTRY fallback AFTER the env legs — walk THIS process's ancestry, match a live non-corrupt roster perch's recorded harness pid (info.json.pid Numeric, alive-gated via is_process_alive, corrupt/BUSY skipped), first match = self. from-LABEL / routing default ONLY, NOT authentication — authenticate() is untouched (pid-ancestry-for-AUTH stays parked per F-024 with its Windows pid-spoof caveats; a display/routing stamp has no such bar). Best-effort: a broken ancestry walk degrades to None → cli-stamp, never errors the send.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-NOTIF-QUIET-DELIVERY</S>
        <Obj N="Value" RefId="180">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">The notify kind rides the active_only window UNCONDITIONALLY, rollback included: spool-only, never live TCP, never a wake, for EVERY producer; loudness = resurface-until-dismissed persistence, never PTY interruption; boundary resurface (clear/compact/new-session/wake) + the adapter safe-point drain are the surfacing paths; a future interrupting alert needs a new kind + its own ADR, not an exception here</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">4. Delivery: the notify kind rides `active_only`, unconditionally: &lt;!-- --&gt;</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-RESUME-CUSTODY-ABA</S>
        <Obj N="Value" RefId="181">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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 -&gt; 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.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">7.51 Process custody is an identity, never a bare PID — a recycled pid must read NOT OURS `[REQ-HAZARD-RESUME-CUSTODY-ABA]`: &lt;!-- --&gt; - **Failure (paid-for, hertz v0.39.4 field RCA 2026-07-22, doyle code-verified same day):** `resume.pid` custody is a bare PID consumed as `read_resume_pid(..).is_some_and(is_process_alive)` at BOTH the livehost restart gate and the liveness-reconcile DEFER. A dead wake-resume spawn's pid, recycled by the OS onto an unrelated process (field proof: `resume.pid=29456` resolved to a random `cmd.exe`), reads as "a resume is in flight" indefinitely: reconcile defers</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-REGISTRY-STALE-CLEAN</S>
        <Obj N="Value" RefId="182">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Stale registry entries degrade to fallback, never hard-fail (4.3)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-CI-POSTJOB-DAEMON-REAP</S>
        <Obj N="Value" RefId="183">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-MSG-6</S>
        <Obj N="Value" RefId="184">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">cross-node Gateway user-msg honored via advertised endpoint_type: a user-msg from a Gateway-typed origin survives the receive_wan funnel as user-msg (vs the fail-closed re-stamp), keyed on the QUIC-handshake-proven origin node (never wire `from`). Trust boundary = subnet membership (operator-ratified 2026-06-13); no defense against an in-subnet member forging the type. Instance.endpoint_type is an additive serde-default field extending REQ-INST-7's data model. Absent/unknown type → re-stamp (N-1 rollout grace)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Endpoint types: &lt;!-- --&gt; _Implemented posture_: the **local** user-backed origins are honored end-to-end — a locally-hosted Gateway endpoint (info.json `state="gateway"`) and the local user's CLI (M9-T4/T5). The **cross-node WAN** path is being completed (trust posture **ratified 2026-06-13**): the **subnet membership boundary is the trust boundary**. A subnet is a collection of machines the user already trusts, so a `user-msg` arriving over the subnet from a **Gateway-typed** origin is honored as the user's authority; the daemon does **not** defend against a subnet member *forging* the Gatewa</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-PUMP-STAGE-TRUTH</S>
        <Obj N="Value" RefId="185">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">MESH-RECOVERY W1 (ADR-0039, RCA wave 3 — the acceptance surface, same contract): peer-failure telemetry is STAGE-SPLIT and health is USER-MEANINGFUL. The single 10s PUMP_PEER_FAIL token splits into attributed stages — address-resolution, QUIC connect, ALPN, seed-proof send, seed-proof receive/verify, roster exchange — each failure stamped (wall+mono) and peer-attributed (subsumes the 2026-07-14 PUMP_PEER_FAIL-unstamped seed). daemon status / subnet status report: live peer count, last successful peer dial, last admitted registry update, duration of any all-peer failure; the incident fingerprint (all dials failing + heartbeat fresh + net_up true) MUST render degraded — no green without real peer progress. New fields ADDITIVE (N-1 readers unaffected). Gate: impl — stage split + status surfaces; unit — stage classification + health state machine (degraded on all-peer failure, healthy only on real progress, not on heartbeat/time); int — health flips degraded/healthy across a real peer outage/restore; doc — reference regen (CLI surface change → xtask gen, no internal codes in clap help). Kin REQ-PEER-ROUTE-CHAIN, REQ-DAEMON-5 (heartbeat — answers liveness, not reachability), REQ-CLI-2/R</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Context: ## Decision &lt;!-- --&gt; &lt;!-- --&gt; &lt;!-- --&gt;</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-RC-EOF</S>
        <Obj N="Value" RefId="186">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-DIRECT-WRITE-PRECEDENCE</S>
        <Obj N="Value" RefId="187">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Direct-write precedence marker (with node id) guards stale overwrite (6.5)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-DSR-SINGLE-CPR</S>
        <Obj N="Value" RefId="188">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">TEARDOWN-AUTHORITY W3 rider (hertz RCA 2026-07-19): a Device Status Report query yields EXACTLY ONE Cursor Position Report. Rides W3 because it is the same emulator-conformance surface as the width work and the same field capture surfaced it; kept a separate REQ so its evidence is not buried inside the width tags. Gate: impl — one CPR per DSR on the emulator reply path; unit — a DSR in the input stream produces a single well-formed CPR carrying the DISPLAY-column cursor position (i.e. consistent with REQ-SCREENGRID-WIDTH reckoning, not the raw char count).</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-RC-SINGLE-PUMP-BRAIN</S>
        <Obj N="Value" RefId="189">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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 =</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-SUBNET-8</S>
        <Obj N="Value" RefId="190">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Status render honesty: zero-subnet text is daemon-aware ('No subnets registered — this node is standalone.' + daemon-running-dependent blurb, never implying messaging works while the daemon is down); hint footer prints on bare spt subnet only (status drops it); a stalled pump is surfaced in subnet status, never rendered implied-healthy (M8 decisions 11-12, 23)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-SEAM-SPAWN</S>
        <Obj N="Value" RefId="191">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">spawn-session seam</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-WAKE-RESUME-LEG</S>
        <Obj N="Value" RefId="192">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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 &lt;id&gt;` 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</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-DEFERRED-DRAIN</S>
        <Obj N="Value" RefId="193">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Deferred spool rows excluded from the event-stream drain (1.4)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-EP-6</S>
        <Obj N="Value" RefId="194">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Endpoint types: &lt;!-- --&gt; **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 there; the intelligence at the endpoint is the **user**. Addressable like any endpoint (receives digests/messages, sends via the normal verbs) and may **own Shells** (it is an owning endpoint — see §Shell model). Distinct from a Shell: a Shell is *driven from elsewhere*; a Gateway *originates* intera</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-PICKER-CONTROL-LINE-STATUS-GATE</S>
        <Obj N="Value" RefId="195">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">A2 (F028, operator #2): the picker confirm-panel 'controlled locally' line renders for OFFLINE endpoints. view.rs:425-436 builds control_line from ep.controlled with NO status gate; an offline endpoint with a stale controlled stamp shows 'controlled locally' (operator screenshot: hall-a offline + controlled locally). RENDER HALF (this REQ): control_line MUST be empty when status != Online. The upstream STICKY-stamp half (stamp survives client SIGKILL &gt;=5min) is B3/REQ-PRESENCE-CONTROL-REAP-ON-EXIT. FIX: gate the render. See triage A2(a).</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-ACTIVITY-LIST-JSON</S>
        <Obj N="Value" RefId="196">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">`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).</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">`--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, resources, endpoint_type?, project?}`; `local[]`: `{id, state, address, ready, alive, unbound, project?, activity?}`. *(Since v0.33.0 the local `project` field reads the daemon-maintained project index — answers are immediate and may lag a just-changed project by moments; absent while the index has</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-ENVELOPE-PARSER-SAFE</S>
        <Obj N="Value" RefId="197">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Two-slice envelope parser is panic-free and tolerant (4.2)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-SELF-ID-TRUST-INJECTED-ENV</S>
        <Obj N="Value" RefId="198">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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@&lt;node&gt;` 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=&lt;id&gt;). 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 -&gt; `deployah`) or fails (real deployah -&gt; None -&gt; `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</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-REACH-1</S>
        <Obj N="Value" RefId="199">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Off-node remote-drive detection + file transfer</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-TERM-5</S>
        <Obj N="Value" RefId="200">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Adapter-declared digest extractor seam: a `[digest]` manifest section declaring an imperative extractor (native harness log -&gt; 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.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Session digest — the published digest-record contract (ADR-0019): &lt;!-- The session digest is a PROJECTION of the endpoint's session logs, never a PTY-byte parse (the superseded source mechanism). ADR-0019 gives it its OWN manifest seam — the `[digest]` extractor above — distinct from `[history]` (which stays opaque + single-session, feeding the echo-commune verbatim). The M9 "no manifest seam / rides `[history]`" stance is REVERSED: one `[history]` normalizer cannot serve both the opaque echo consumer and the contract-typed digest. What is published here is the digest-record CONTRACT: the smal</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-DAEMON-3</S>
        <Obj N="Value" RefId="201">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Any api invocation auto-starts the daemon if absent</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-DEFERRED-SURVIVE-DRAIN</S>
        <Obj N="Value" RefId="202">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Deferred rows survive poll drain (4.4)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-ADAPTER-UPDATE-MESSAGE</S>
        <Obj N="Value" RefId="203">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Runtime model: **adapter packaging &amp; live update** (v0.13.2; ADR-0024, ADR-0025): &lt;!-- --&gt; A `.spt` may be **multi-platform**: shared `manifest.toml` + `strings/` at the root, role binaries under per-target-triple subdirectories (`x86_64-pc-windows-msvc/`, …); install/update extracts the shared root plus only the current node's triple, flattened into `install_dir`, so flat `&lt;install_dir&gt;/&lt;program&gt;` resolution is unchanged. It stays one signed asset (`adapter.spt`, plain-tar or gzip); a multi-platform archive missing the recipient's triple is a typed `NoArtifactForPlatform`. Large adapters may</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-CONPTY-DSR</S>
        <Obj N="Value" RefId="204">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">ConPTY reader must auto-answer DSR (ESC[6n) or all child output stalls (5.5)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-SINGLE-PATH-SOURCE</S>
        <Obj N="Value" RefId="205">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Single path/registry source of truth; no layout ambiguity (6.1)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-REDISPATCH-FINISHED-RETIRE</S>
        <Obj N="Value" RefId="206">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">REDISPATCH-TRUTH W1 (ADR-0038, hertz fix A): finished/terminal stream rows are RETIRED from redispatch eligibility — NetShared.streams today has NO removal path (single insert nethost.rs ~649; StreamLog::finish only marks) so every dispatcher generation re-enumerates every historical stream forever. Retire terminal rows from the enumeration the dispatcher claims from (remove, or lifecycle-exclude), preserving only the post-EOF state genuinely needed by other readers (presence/log reads); bounded growth replaces forever-discoverable rows. Clearing the whole table on brain restart is REJECTED (destroys live streams' reconstruction facts). Gate: impl — the retirement path; unit — a finished stream is invisible to the dispatch enumeration while an active one stays claimable + post-EOF reader state survives retirement; doc — rides ADR-0038 + the triage doc. Kin REQ-HAZARD-REDISPATCH-CONTROL-STEAL (the invariant it satisfies), REQ-STREAM-OPENER-DURABLE.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Context: ## Decision &lt;!-- --&gt;</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-EP-4</S>
        <Obj N="Value" RefId="207">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">PresenceChannel broker endpoint (seam day-one)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-GRACE-BEFORE-SIGNOFF</S>
        <Obj N="Value" RefId="208">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Grace-period wait completes before composing INIT_SIGNOFF (1.1)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-CONTROLLER-RETAKE-FLOOR</S>
        <Obj N="Value" RefId="209">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">`become_controller` should STRUCTURALLY refuse a controller re-take whose `from_seq` falls below the connection's already-delivered contiguous floor — making the P1c reorder invariant un-reintroducible by a future caller, not just removed at the one caller. ROOT/SCOPE (doyle proposed, P1c gate dialogue): P1c fixes REQ-HAZARD-CONTROLLER-WRITER-REORDER three ways (handoff single-take + epoch-gate-under-lock + session_cursors seed), removing the one decreasing-floor double-take and bounding any other to already-committed-only. A self-enforcing broker guard would refuse the bad SHAPE outright. BLOCKER: the obvious predicate (`from_seq &gt;= delivered_through`) is UNSAFE because `delivered_through` is SESSION-WIDE (the `Arc&lt;AtomicU64&gt;` on `OutputLog`, shared by all controllers/viewers, advanced monotonic-MAX; `resume_seq` reads it) — a normal fresh-operator `from_seq=0` attach to a producing session legitimately sits below it (full ring replay + consumer dedup-below/snap-above), and monotonic-MAX can't distinguish the hazard (a `seq1`-without-`seq0` write reads as `2`). The structurally-correct guard needs a NEW per-connection contiguous-sent cursor (the true highest-contiguous seq this so</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-PERCH-RECORD-POWER-LOSS</S>
        <Obj N="Value" RefId="210">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Authoritative/identity records fsync data before the rename (5.13): a hard reset must not resurrect a full-length NUL-filled record — SCOPED, not a blanket fsync</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">5.12 Native-PTY spawn of a bare program runs the wrong (non-PE) file on Windows `[REQ-HAZARD-WIN-PTY-PROGRAM-RESOLVE]`: &lt;!-- --&gt; ### 5.13 Atomic write leaves data un-synced before the rename → NUL zero-fill on power loss `[REQ-HAZARD-PERCH-RECORD-POWER-LOSS]` - **Failure:** `atomic_write_bytes` was `fs::write(tmp)` + `rename(tmp, path)` with no `fsync`. The rename's directory **metadata** is journaled durable, but the tmp file's **data blocks** are still in the page cache. A hard reset (power loss, forced reboot) between the two flushes lands the rename but loses the data → the file reappears</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-ARCH-3</S>
        <Obj N="Value" RefId="211">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Wire-protocol version independent of crate semver, N-1 compat window</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-EPHEMERAL-CLEANUP</S>
        <Obj N="Value" RefId="212">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Ephemeral perch cleanup on every ring exit path (3.1)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-PAIR-NTP-MULTIHOME</S>
        <Obj N="Value" RefId="213">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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(&amp;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.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-STREAM-LEASE-CLASSES</S>
        <Obj N="Value" RefId="214">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">REGISTRY-LIFECYCLE W2 (ADR-0040 decision 6 + ADR-0041, emphasys C2 P0): streams declare a lifetime class at open — RC attach/view streams are ConnectionBound (opener conn EOF means the target sees FIN, serve_attach runs detach_session, controller slot + CONTROLLED stamps clear — a dead viewer can never pin a controller across its own connection death; today the raw-closed viewport attach stream is restart-durable forever); inter-brain streams stay Durable (NEVER globally retire on Brain disconnect — brain-swap correctness depends on it). Late-close identity validated (stale opener A close cannot evict newer controller B — rides ADR-0038 Amendment fix-6 generation tokens + W1 seat teardown machinery; same neighborhood, built once per the standing C2 coordination ruling). Lifetime class = additive open field, absent = Durable (N-1 openers keep exact current semantics). Gate: impl — class at open + ConnectionBound EOF chain; unit — class routing + absent-defaults-Durable + late-close identity refusal; int — raw viewport close frees the controller full-chain incl. across broker restart, brain_swap/daemon_refresh/redispatch legs stay green; doc — ADR-0040/0041.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">Decision: &lt;!-- --&gt; 1. **One-way (fire-and-forget) stream families are terminal at FIN, sender-side.** The registry feed pump retires its own row after successful write+FIN via the existing `net-stream-retire` verb (best-effort on N-1 brokers, per ADR-0038 A). A one-way family's exchange is definitionally over at FIN; keeping the row eligible reproduces the O(history) defect forever. 2. **Eligibility filtering is server-side.** `stream_infos` excludes `initiated_locally` rows (alongside `retired`) before serializing. Consumers keep their client-side guards (double-filter harmless; N-1 compatibl</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-DELIVERY-STARVATION</S>
        <Obj N="Value" RefId="215">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">A message that has REACHED a node's spool (WAN-arrived or locally spooled-while-active) is NEVER dependent on an adapter HOOK-POLL cadence for its eventual delivery to an spt-hosted (relay-less) endpoint — the daemon itself drives delivery on the events it owns (WAN ingress + the ACTIVE→IDLE edge). Hazard class: delivery starvation. Without this, cross-node and post-active messages to an spt-hosted perch strand indefinitely whenever the adapter's hooks are quiet (idle session, no user turns), presenting as 'sent but never lands' with a healthy binary and an idle perch (F-023). Guarded by REQ-WAN-SPT-HOSTED-DELIVERY (WAN ingress leg) + REQ-MSG-IDLE-EDGE-DRAIN (idle-edge drain). (F-023)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">7.22 An idle delivery with no working translation binary must SPOOL, never raw-inject a pseudo-delivery reported as delivered `[REQ-HAZARD-IDLE-SILENT-NONDELIVERY]`: ### 7.23 A message that has REACHED a node's spool must NEVER depend on an adapter hook-poll cadence to reach an spt-hosted (relay-less) endpoint — the daemon drives delivery on the events it owns `[REQ-HAZARD-DELIVERY-STARVATION]` - **Failure (F-023, RCA @ `f023-f024-wan-idle-starvation`):** a message that has already landed in a node's spool — WAN-arrived, or locally spooled-while-active — stranded INDEFINITELY on an spt-hosted,</S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-DAEMON-8</S>
        <Obj N="Value" RefId="216">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Internal auto-start prefers the service: `ensure_running` (any spt command's implicit daemon start, REQ-DAEMON-3) routes through the service-aware start path — when a manager has a registered service it starts THAT, never a competing manual `spawn_detached` daemon that would fight the service for the socket.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-ROSTER-GHOST</S>
        <Obj N="Value" RefId="217">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">A LOCAL subnet roster entry whose backing perch is erased does NOT keep advertising Active (no phantom perch-less endpoint). `api session-end &lt;id&gt; --erase` removes the perch (owlery dir gone) but the subnet roster (identity/registry/&lt;subnet&gt;.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)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-UPDATE-APPLY-RESTART-NOTICE</S>
        <Obj N="Value" RefId="218">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">`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)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-PICKER-RESUME-CONTEXT-PANEL</S>
        <Obj N="Value" RefId="219">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">#6: the 'Resume from history' view keeps the endpoint's 'Confirm selection' top panel and swaps ONLY the bottom panel to 'Resume from a prior session' — the user stays contextually informed about what they're picking (today the resume view replaces the whole screen). crates/spt/src/picker/view.rs (resume screen) + model screen state. See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #6.</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT</S>
        <Obj N="Value" RefId="220">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">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</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value"></S>
            </En>
          </DCT>
        </Obj>
      </En>
      <En>
        <S N="Key">REQ-HAZARD-CHILD-CONSOLE-FLASH</S>
        <Obj N="Value" RefId="221">
          <TNRef RefId="0" />
          <DCT>
            <En>
              <S N="Key">title</S>
              <S N="Value">Console-subsystem children of the console-less daemon spawn with CREATE_NO_WINDOW, or each spawn flashes a visible blank window on the user's desktop (5.8)</S>
            </En>
            <En>
              <S N="Key">doc</S>
              <S N="Value">5.7 Elevated commands spawn the daemon with the wrong token `[REQ-HAZARD-ELEVATED-DAEMON-SPAWN]`: &lt;!-- --&gt; ### 5.8 Console children of the console-less daemon flash visible windows `[REQ-HAZARD-CHILD-CONSOLE-FLASH]` - **Failure:** the daemon runs DETACHED (no console, 5.6/`detached_no_inherit`). Any console-subsystem child it spawns (`git`, `taskkill`, manifest hook commands) gets a **fresh conhost with a visible window** — piped/null stdio does NOT prevent it. Field shape: the 60s sync pump's two git calls (`for-each-ref` + `rev-parse`) flashed two blank windows per minute on the user's deskt</S>
            </En>
          </DCT>
        </Obj>
      </En>
    </DCT>
  </Obj>
</Objs>