[
  {
    "index": 1,
    "req_a": "REQ-ACL-DISCOVER-GATE",
    "req_b": "REQ-ACL-SURFACE-VOCAB",
    "title_a": "DISCOVER is a real gated surface: registry resolve/advertise and the resources blurb are filtered per viewer, so discovery leaks nothing a viewer could not reach. ADR-0009 NAMED this leg \u2014 'discovery (resource advertisement) is gated by the first two gates' \u2014 and CONTEXT.md's resource-advertisement entry promises that an endpoint whose access rules exclude the viewer's node never appears in that view, but only the VISIBILITY half was ever built; the access half has been an unbuilt promise since M4. Un-gated discovery on a shared subnet is an inventory of every agent on every member node, including the ones an operator deliberately walled off \u2014 and a resources blurb is a yellow-pages description written to attract exactly the collaboration the access rules refuse. Gate: doc \u2014 the CONTEXT.md resource-advertisement entry (the promise this closes) and the control-surface entry minting the id; impl \u2014 the DISCOVER check on the registry resolve/advertise path plus per-viewer filtering of the resources blurb; unit \u2014 a viewer refused DISCOVER sees neither the endpoint row nor its blurb, while a viewer allowed DISCOVER sees both, and the filter is per-viewer rather than global.",
    "title_b": "Access control is granular at the CONTROL SURFACE, not at the endpoint. A rule is (target endpoint x surface x subject) -> allow/deny, where a surface is a named remote-reachable operation class with an OPEN CONSTANT_CASE string vocabulary \u2014 new surfaces mint ids without a schema change, and an unrecognized surface string in a rule is legal (it governs nothing until that surface is built). The v1 set is the nine ratified ids: MSG, RC_VIEW, RC_ATTACH, DIGEST, WAKE, SUSPEND, XFER, SHELL_LINK, DISCOVER. WHAT THIS ENDS: the ADR-0009 whitelist was all-or-nothing per endpoint \u2014 admitting a node for messages also admitted it to drive the terminal, pull digests, and transfer files, because one access_check covered every wire-inbound family at once. On a SHARED SUBNET (member nodes belonging to different human operators, the gated adversary being agents) that coupling is the whole problem: there is no way to publish a view without handing over the keyboard. Each of the daemon's gate call-site families now tags itself with its surface, and the two families carrying a request-shaped distinction split: attach on AttachIntent (Viewer -> RC_VIEW vs Control/Take -> RC_ATTACH, see REQ-ACL-RC-VIEW-SPLIT), rest on the rest event (Wake -> WAKE vs Suspend -> SUSPEND). Gate: doc \u2014 the CONTEXT.md control-surface glossary entry; impl \u2014 the spt_store::access::surface vocabulary module, access_check's surface parameter, and the six call-site families tagging themselves; unit \u2014 the vocabulary shape (nine ids, CONSTANT_CASE, open to unminted strings) plus a per-surface decision table proving a grant on one surface does not admit the same node on another.",
    "doc_a": "resource advertisement (subnet resource registry)**: A per-endpoint **free-text blurb** describing the services/functions the endpoint can serve \u2014 an agent **yellow-pages** for service discovery, distinct from *capability declaration* (machine-readable, which endpoint *types* a node hosts) and from *endpoint visibility* (whether it's addressable at all). **Both-authored + mutable:** config seeds a default; the agent refines its own at runtime (`spt endpoint description set \u2026`). It is **not a separate registry** \u2014 it is a field on the endpoint record and a **projection** of the subnet registry ",
    "doc_b": "control surface** (ratified 2026-07-28, access-control grill): The unit of access-control granularity: a named remote-reachable operation class on an endpoint. **Open string vocabulary, CONSTANT_CASE ids** (like capability ids \u2014 new surfaces mint ids without schema change). v1 set = the existing gate families: `MSG`, `RC_VIEW`, `RC_ATTACH`, `DIGEST`, `WAKE`, `SUSPEND`, `XFER`, `SHELL_LINK`, `DISCOVER`. Later waves (remote endpoint-info, adapter package serving, webservice facets) mint their ids when the capability itself is built. An access rule is (target endpoint \u00d7 surface \u00d7 subject-chain) \u2192",
    "score": null,
    "reasoning": null
  },
  {
    "index": 2,
    "req_a": "REQ-ACL-NODE-MODE-SET",
    "req_b": "REQ-ER-CEREMONY-VERB",
    "title_a": "The node's control-surface modes are settable through the engine room and nowhere else (ADR-0052 decisions 1 and 3, CONTEXT.md 'control-surface modes' \u2014 the node level of the three). Modes are exactly what a confused or adversarial agent would loosen, so the mutation surface must be the one place an agent cannot reach without passing a human-held TOTP; every other candidate \u2014 a plain CLI verb, an elevation-gated verb, a config file the daemon reads \u2014 is reachable by something running as the user. Subnet-scope mode authority is separate and rides empower (REQ-SUBNET-EMPOWER-VERB); this requirement is the node's own posture, which needs no empowerment because the bring-up gate already proved subnet membership. Gate: doc \u2014 the CONTEXT.md control-surface-modes entry naming the engine room as the node-level setter; impl \u2014 node-scope mode writes reachable only through an engine-room-authenticated path; unit \u2014 an engine-room caller sets a node mode, every other caller is refused, and the resolution chain reads the written mode at its node tier.",
    "title_b": "The engine-room ceremony is invocable: `spt endpoint engine-room <subnet> --adapter <id>` runs the one create/reset code path (`provision_at`), reporting Created against an absent record and Reset against a present one (ADR-0052 \u00a74a, fast-follow grill 2026-07-30). Before this verb the ceremony had ZERO production call sites \u2014 a user could not set an engine room up at all. Its gates, in refusal order: BOTH arms refuse invocation by an SPT agent, detected on env markers AND on process ancestry against the broker-hosted sessions' recorded pids (defense-in-depth in front of both arms, never a substitute for the reset elevation gate; ancestry powers only this REFUSAL, never a grant \u2014 the direction that keeps the F-024 ancestry-for-auth parking intact); an unjoined subnet refuses (no seeds to verify a bring-up code against later); an unregistered adapter refuses (bring-up fails closed on it anyway \u2014 fail at bind time instead); then creation (absent record) proceeds UNELEVATED \u2014 bootstrap friction ruled to outweigh pre-emption, the window closes permanently at the first run, docs say run the ceremony early \u2014 while reset (present record) requires confirmed OS elevation, with Unknown refusing like the purge gate. Gate: doc \u2014 ADR-0052 \u00a74a; impl \u2014 the verb, the pure ceremony decision, the two-layer agent detection, and the provision_at call; unit \u2014 the decision table pinning agent-deny-beats-elevation on both arms, bind-time refusals before the arm split, create-unelevated across every elevation, and reset refusing on NotElevated and Unknown alike.",
    "doc_a": "A locked-down **agent endpoint** (harness-adapter-backed, spt-hosted, has a mind \u2014 it can be briefed on and reason about the node's access posture). It is the designated way to set the **node's** control-surface modes.",
    "doc_b": "Engine room: first-time setup / Ruled at the fast-follow grill 2026-07-30 (operator + doyle). The ceremony ships as **`spt endpoint engine-room <subnet> --adapter <id>`** \u2014 one verb, one code path (`provision_at`), reporting `Created` against an absent record and `Reset` against a present one.",
    "score": null,
    "reasoning": null
  },
  {
    "index": 3,
    "req_a": "REQ-ACL-SUBJECT-CHAIN",
    "req_b": "REQ-HAZARD-WAN-ORIGIN-AUTH",
    "title_a": "One FIRST-MATCH-WINS subject chain decides every access question, with an implicit-open bottom that preserves current fleet behavior. Order: per-endpoint sender-endpoint rule -> per-endpoint node rule -> per-endpoint subnet-wildcard rule -> node-scope node rule -> node-scope subnet-wildcard rule -> endpoint mode for the surface -> node mode for the surface -> join-time-captured subnet mode for the surface -> (nothing matched) OPEN. The stateful-firewall reply exemption and the same-node loopback Allow PRECEDE the chain and are not tiers \u2014 a degraded or locked-down node keeps its own conversations alive. Modes are the per-surface default posture for unlisted subjects (open/closed), settable at endpoint and node scope; the captured-subnet-mode table ships as a SCHEMA STUB in W1 (W2's join capture populates it \u2014 absent means the chain bottoms out open). Store schema v2 carries subject-kind entries at both scopes, migrates every v1 AccessEntry.nodes row into an equivalent (endpoint mode closed + one all-surface node Allow per listed node, so a listed node keeps access and an unlisted one loses it exactly as before), and DROPS the inert users field with the no-user-identity ruling (a shared subnet's trust unit is the node as human-proxy; 'user' is deliberately not a security identity). TIER 1 IS SCHEMA-REAL BUT INERT IN W1 (doyle ruling 2026-07-29, on todlando's pre-build finding): the design-of-record premise 'sender identity is daemon-stamped and nodes are trusted' is FALSE of its first clause today. The only wire record carrying a sender endpoint is WanMessage.from, which KNOWN-HAZARDS 7.5 (REQ-HAZARD-WAN-ORIGIN-AUTH) binds verbatim as reply-routing metadata 'never an authorization subject' \u2014 and it is caller-supplied (spt send --from; cli.rs resolve_from lets the explicit arg win over session detection), so keying a rule on it would admit a FORGED subject, the exact failure 7.5 exists to prevent. Precedent already ruled in-tree: REQ-MSG-5's classify_local_origin faces the same question and answers it with the session-proven perch id, noting '--from alone never confers an agent identity'. Independently, five of the six gate families (attach, xfer, rest, shell-link, digest) carry no sender endpoint on the wire in any form. So the kind persists, matches, and round-trips, but every W1 call site supplies None. Lighting it up needs an authenticated daemon-stamped sender field \u2014 a NEW additive field, never a repurposing of from \u2014 which is its own wave and its own REQ. Gate: doc \u2014 the CONTEXT.md endpoint-access-whitelist entry, whose false daemon-stamped premise this change corrects in the same diff, plus the control-surface-modes entry carrying the chain; impl \u2014 spt_store::access schema v2 (Subject/AccessRule/Modes/EndpointAcl/NodeAcl/CapturedSubnetModes), AccessStore::decide, the v1 migration, and the daemon gate threading origin subnet membership in; unit \u2014 the chain proven tier by tier (each tier decides only when every tier above abstains, bottom is open), the v1 verbs' polarity (allow restricts, revoke narrows without widening, open is the only widening), v1->v2 migration semantics, v2 round-trip of every construct, and TWO inertness guards \u2014 a store-level one proving the kind matches when fed and abstains when not, and a gate-level one proving no call site feeds it (that guard goes red on purpose if a later change threads a sender identity into the gate).",
    "title_b": "WAN-inbound origin is transport truth, never payload: the access gate's subject (ADR-0009 origin-node whitelist) is the QUIC handshake-proven remote node id from the broker's conn/stream table \u2014 a forged origin/node field inside record bytes is inert (7.5)",
    "doc_a": "control-surface modes (`open` / `closed`)** (ratified 2026-07-29, access-control grill): Per-surface default posture for unlisted subjects \u2014 `open` = allowed (no forced whitelisting), `closed` = blocked. Defined at three levels: **subnet** (a universal all-surfaces mode chosen at `subnet create` \u2014 prompted with **no preselection**, flags `--open`/`--closed`; per-surface customization later only via an *empower*ed engine-room), **node** (set via the node's engine-room, member-or-admin TOTP), and optionally **per-endpoint** (exists only if deliberately set). **Resolution \u2014 first match wins, mode",
    "doc_b": "7.5 WAN-inbound origin is transport truth, never payload `[REQ-HAZARD-WAN-ORIGIN-AUTH]` Failure:** the ADR-0009 access whitelist gates **unsolicited wire inbound by origin node**. If the gate's subject is read from record bytes (an `origin_node`/`from`/`node` field a sender wrote), any sender forges any origin and the whitelist is decoration \u2014 same spoof class as 7.3's Psyche-supplied `from=`, now on the cross-node surface. Invariant:** the origin the gate (and detection/UX \u2014 \"node X is driving\") consumes is the **QUIC handshake-proven remote node id** (iroh `EndpointId` == Ed25519 node pubkey",
    "score": null,
    "reasoning": null
  },
  {
    "index": 4,
    "req_a": "REQ-ADAPTER-ADD-SURFACE-ERRORS",
    "req_b": "REQ-EP-9",
    "title_a": "Bug #1: adapter add runs the install-as-first-update via conduct (cli.rs:6963) which on a non-zero exit prints only the exit code and DISCARDS the subprocess stdout/stderr, so the real error is invisible (the failure itself does propagate). Fix: include out.stderr/stdout in the ADAPTER_INSTALL_FAIL message (mirror run_update_post_step). Operator ruling: ALSO run the [update.post] composite step at install-time (today it runs only on explicit adapter update), so an install both surfaces detail and completes the delegated post-step. See docs/NEXT-MILESTONE-BUG-TRIAGE.md #1.",
    "title_b": "`#` always-on address sigil: a reserved LEADING sigil marking an AlwaysOnEndpoint, extending the REQ-INST-10 grammar to `[subnet:]#id[@node]`. Mandatory + bijective \u2014 `#name` \u27fa always-on endpoint, bare `name` \u27fa agent endpoint \u2014 so the router resolves endpoint class from the address alone, before any registry lookup. Sits ABOVE REQ-HAZARD-ID-CHARSET: the address parser strips the single leading `#` before id validation, so the bare/stored id stays charset-clean and a mid-id `#` remains rejected (the charset contract is unchanged).",
    "doc_a": "",
    "doc_b": "Status: accepted (2026-06-21)",
    "score": null,
    "reasoning": null
  },
  {
    "index": 5,
    "req_a": "REQ-ADAPTER-GH-TRANSPORT",
    "req_b": "REQ-HAZARD-ADAPTER-APPLY-SILENT-NOOP",
    "title_a": "The `gh_release` avenue (and `spt adapter add --release`) gain a fetch `transport`: `https` (current reqwest direct, public), `gh` (shell the pre-authorized `gh` CLI \u2014 the private-repo path; `gh` honors OAuth and `GH_TOKEN`, so spt custodies no token), or `auto` (default: prefer `gh` when installed+authed, else HTTPS). `--gh`/`--https` force it on `add`. Additive over the existing fetch path; verify->extract->register downstream is unchanged. (v0.13.2)",
    "title_b": "A DELEGATED live adapter apply MUST NEVER report success without performing the swap, and the live-update seam MUST use ONE parent-aware adapter matcher across all its comparators. TWO defects made the field repro (BUILD-F015B-APPLYMATCH: `--adapter cc:ccs` live update silently no-ops): (D1, matcher skew) the broker's dispatch_adapter_apply filtered sessions by EXACT `s.adapter == req.adapter`, but a `--adapter <adapter>:<profile>` endpoint stores the COMPOSITE `cc:ccs` while the apply carries the PARENT record name `cc` \u2014 so every :profile endpoint fell out to affected=[]; select_endpoints_running_adapter had the same `adp == adapter` skew, while the CLI live-gate (adapter_has_live_endpoint) already parent-matched \u2014 divergent rules on ONE seam. (D2, silent success) the affected.is_empty() branch replied KIND_APPLIED and RETURNED WITHOUT SWAPPING; once the CLI delegates the apply there is no CLI-side fallback swap, so success-without-swap = the update never lands (re-register re-reads the OLD manifest, version-of-truth honestly says old). FIX: (1) ONE shared spt_runtime::profile::adapter_parent_matches(session_adapter, parent) used by the live-gate + broker apply-filter + select_endpoints_running_adapter (no exact `==` against a record name at any live-update seam); (2) the daemon owns the whole apply once delegated \u2014 the CRC swap runs UNCONDITIONALLY (terminate/restart loops no-op when nothing is resident), KIND_APPLIED reported ONLY after a real swap. (F015B, ADR-0025 amendment)",
    "doc_a": "adapter packaging & live update** (v0.13.2; ADR-0024, ADR-0025): A `.spt` may be **multi-platform**: shared `manifest.toml` + `strings/` at the root, role binaries under per-target-triple subdirectories (`x86_64-pc-windows-msvc/`, \u2026); install/update extracts the shared root plus only the current node's triple, flattened into `install_dir`, so flat `<install_dir>/<program>` resolution is unchanged. It stays one signed asset (`adapter.spt`, plain-tar or gzip); a multi-platform archive missing the recipient's triple is a typed `NoArtifactForPlatform`. Large adapters may still split per-platform. ",
    "doc_b": "7.24 A delegated live adapter apply must NEVER report success without swapping, and the live-update seam must use ONE parent-aware adapter matcher `[REQ-HAZARD-ADAPTER-APPLY-SILENT-NOOP]` Failure (F015B / BUILD-F015B-APPLYMATCH):** a live adapter update to a PROFILE-COMPOSITE endpoint (`--adapter cc:ccs`) silently no-oped \u2014 `LIVE` then `DONE` printed, but the version-of-truth stayed OLD. TWO defects on one seam. **(D1, matcher skew):** the broker's `dispatch_adapter_apply` filtered sessions by EXACT `s.adapter == req.adapter`, but a `--adapter <adapter>:<profile>` endpoint stores the COMPOSITE",
    "score": null,
    "reasoning": null
  },
  {
    "index": 6,
    "req_a": "REQ-ADAPTER-LIVE-UPDATE",
    "req_b": "REQ-RUN-SHORTCUT",
    "title_a": "An adapter update is live and daemon-coordinated (the adapter analog of brain self-update, ADR-0004): for an endpoint with a running RESIDENT adapter binary (today the `[message-idle-translation-binary]`), the CLI keeps fetch+verify and hands the APPLY to the daemon over IPC, which per affected endpoint (1) STOPS the resident binary -> releases the OS file lock (fixes the Windows 'Access denied (os error 5)' overwrite failure), (2) swaps on disk ONLY files whose CRC differs from the staged archive (unchanged files + their still-running binaries untouched), (3) RE-CLONES the new on-disk manifest into the running `BrainLifecycle` (the in-memory manifest is cached at bringup and otherwise goes stale -> binaries+manifest back on the same page), (4) RESTARTS the resident binary from the new files. An endpoint NOT running -> CLI swaps directly (no lock, no cache). Only the resident class is cycled; ephemeral adapter binaries (Psyche loop, `[digest]` extractor, `[session.*]` runners, hooks) self-heal on next spawn and are excluded. The daemon keeps a per-endpoint registry of resident adapter children. (ADR-0025, v0.13.2)",
    "title_b": "`<basename>-<id>` launcher shortcut generation (picker `s` keybind, M12-W2-T2.4): from any pre-start options set the picker writes/updates a `<basename>-<id>` launcher at the project root baking the current selection's non-interactive `spt endpoint run` flags (terminal actions only: adapter[:profile] + id + (create|resume) + (start|attach|view); Kick/Instantiate/Change-adapter/Fork are interactive-only, not bakeable). BASENAME IS A PARAMETER (operator rev. 2026-06-14): harness-agnostic spt-core defaults to `spt` (\u2192 `spt-<id>`); an adapter/flow OVERRIDES it (spt-claude-code \u2192 `cc`), so spt-core NEVER bakes `cc` (a harness name) into itself. The basename must be a DISTINCT token, never bare `spt` (a `spt.cmd` would shadow the real `spt.exe` only under cmd.exe cwd-first search, silently no-op in PowerShell/Unix, and self-recurse). The script is the CURRENT OS's native form \u2014 `.cmd` on Windows (NOT `.ps1`: default PATHEXT excludes `.ps1` so a bare/ext-less name never resolves one; `.cmd` is PATHEXT-resolvable), POSIX `sh` (+chmod +x) on Unix (a single portable form can't be both). The generated header documents the invocation reality (cmd.exe bare `<name>` in the project dir / PowerShell `.\\<name>` / Unix `./<name>`; a truly-bare basename on PATH = a PATH-installed launcher, `/spt:setup`'s job). Overwrite is SENTINEL-guarded: the generator writes + checks a generated-by header marker \u2014 it overwrites its own prior output freely, but REFUSES + warns if a same-named file lacks the sentinel (never clobber a user file). Requires the additive `--create` flag on `Run{}` (the default-fresh made explicit; N-1-safe).",
    "doc_a": "Live, daemon-coordinated adapter update / Amendment (W3 build, 2026-06-22)",
    "doc_b": "`spt-<id>` shortcut** (picker `s` keybind, M12-W2): From any pre-start options set, `s` writes (or updates) a **`<basename>-<id>` launcher** at the project root that bakes the current selection's **non-interactive** flags (terminal actions only: adapter[:profile] + id + create|resume + start|attach|view; the interactive-only branches \u2014 Kick/Instantiate/Change-adapter/Fork \u2014 are not bakeable). The **basename is a parameter**: harness-agnostic spt-core defaults to **`spt`** (\u2192 `spt-<id>`, e.g. `spt-doyle`); an adapter/flow **overrides** it (spt-claude-code \u2192 `cc`, giving `cc-<id>`) \u2014 the Claude-",
    "score": null,
    "reasoning": null
  },
  {
    "index": 7,
    "req_a": "REQ-ADAPTER-PROOF-DIR-OVERRIDE",
    "req_b": "REQ-MANIFEST-7",
    "title_a": "The author-time proof commands (`spt adapter digest-proof`, `spt adapter translate-proof`) gain a `--dir <path>` / `--manifest <file>` override so an author proofs a DEV binary against an on-disk manifest+install dir WITHOUT staging a full extracted GhReleaseManaged install (mirrors digest-proof's `--sample` pointing straight at a file). Fixes perri F-011: a bare-file-added gh_release adapter currently can't be resolved by the *-proof commands ('manifest is not present yet at <dir>'); un-stales the bare-file digest-proof int. (perri F-011, v0.13.x DX)",
    "title_b": "Adapter-declared shortcut basename (M12-W2 follow-on): an optional `[adapter] shortcut_basename` manifest field names the basename the `spt endpoint run` picker bakes into the generated `<basename>-<id>` launcher shortcut (REQ-RUN-SHORTCUT). Absent \u21d2 the harness-agnostic default `spt` (\u2192 `spt-<id>`); an adapter sets it to brand its shortcuts (claude-spt \u2192 `cc` \u2192 `cc-<id>`), so the Claude-Code-ness lives in the PUBLISHED adapter manifest, never hardcoded in spt-core. The picker reads it from the RESOLVED manifest of the selected adapter (registry::resolve_option), falling back to `spt` when absent/empty/unresolvable. Additive + N-1-safe (serde-default Option, omitted from serialization when absent; old manifests parse clean); manifest.schema.json regenerated from the derive (ADR-0001, CI drift-gated). Documented in docs/MANIFEST.md `[adapter]` section + the claude-spt worked example \u2014 the adapter-author contract perri builds spt-claude-code against.",
    "doc_a": "Proof a DEV build off disk \u2014 `--dir` / `--manifest`.** Both `digest-proof` and `translate-proof` accept `--dir <install-dir>` (binaries resolve there, just like a registered install) or `--manifest <file>` (pins the manifest its parent is the install dir) to proof an adapter that is **not registered \u2014 e.g. a freshly built binary beside a hand-written `manifest.toml`, or a bare-file `gh_release` adapter that was never staged into a full extracted install. `--dir` defaults the manifest to `<dir>/manifest.toml`; with neither flag the command resolves the registered adapter as before. Mirrors `dig",
    "doc_b": "`shortcut_basename` *(optional, default `spt`)* \u2014 the basename the `spt endpoint run` picker's `s` keybind bakes into the generated `<basename>-<id>` launcher shortcut at the project root (REQ-MANIFEST-7). Absent \u21d2 the harness-agnostic `spt` (\u2192 `spt-<id>`); an adapter sets it to brand its shortcuts \u2014 `claude-spt` uses `cc`, giving `cc-doyle`. spt-core never hardcodes a harness name; the picker reads this from the **resolved** manifest of the selected adapter. The launcher is the current OS's native form (`.cmd` on Windows \u2014 `.ps1` is excluded by the default `PATHEXT`; a POSIX `sh` `+chmod +x` ",
    "score": null,
    "reasoning": null
  },
  {
    "index": 8,
    "req_a": "REQ-ADAPTER-UNRESOLVED-HINT-FORM",
    "req_b": "REQ-RUN-ID-REUSES-ADAPTER",
    "title_a": "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 <name[:profile]>', but --adapter is a `spt api` GROUP flag, NOT a `listen` flag \u2014 following the hint literally (`spt api listen <id> --adapter <name>`) produces clap `error: unexpected argument '--adapter'` (exit 2). Fix: the hint prints the group-level form, e.g. `spt api --adapter <name> <cmd> \u2026` (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) \u2014 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.",
    "title_b": "D-1 (REMOTE-TRUTH triage \u00a7D-1): `spt endpoint run --id <id>` with NO --adapter, when <id> names an EXISTING perch, REUSES that perch's recorded info.adapter and runs NON-INTERACTIVELY \u2014 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)\u2192cmd_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 \u2014 (Some,Some)\u2192Direct{a,id}; (None,Some(id))\u2192 recorded adapter present (info.adapter = adapter-chosen-at-creation, spt-store info.rs:167) \u2192 Direct{recorded,id}, absent/no-perch \u2192 Picker{None,Some(id)} (today's create-new prefill UNCHANGED); (Some,None)/(None,None)\u2192Picker 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),recorded=Some) \u2192 Direct (pre-fix this routed to Picker create-new). int (live run --id on an existing perch reuses its recorded adapter non-interactively) deferred to the rig.",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 9,
    "req_a": "REQ-ADAPTER-UPDATE-INPLACE",
    "req_b": "REQ-SEAM-UPDATE",
    "title_a": "Bug #18: spt adapter update fails at re-register with os error 2 because it derives the install dir from the update repo NAME (_github/<safe>) instead of updating in place at the adapter record source_dir; when the adapter repo is intentionally renamed across releases (spt-claude-code to claude-spt, supported), the derived dir is fresh/empty and re-register reads a missing manifest. Fix: adapter update installs and re-registers in place at the registered source_dir and tolerates a changed update repo/URL across a rename. See docs/NEXT-MILESTONE-BUG-TRIAGE.md #18.",
    "title_b": "Adapter-update avenue (file-pull / delegated command)",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 10,
    "req_a": "REQ-ADAPTER-UPDATE-MESSAGE",
    "req_b": "REQ-INSTALL-9",
    "title_a": "An adapter manifest may declare `[update].message` \u2014 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)",
    "title_b": "Adapter add from a GitHub release archive: `spt adapter add --release <user/repo> [--tag <tag>] [--asset <name>]` fetches a `.spt` tar asset over HTTPS+GitHub trust, extracts it to the durable adapters/_github home, and registers the root \u2014 ships built binaries source-free and versioned (the distribution path for an adapter whose dev repo is a monorepo subdir, where --github root-only clone does not fit)",
    "doc_a": "adapter packaging & live update** (v0.13.2; ADR-0024, ADR-0025): A `.spt` may be **multi-platform**: shared `manifest.toml` + `strings/` at the root, role binaries under per-target-triple subdirectories (`x86_64-pc-windows-msvc/`, \u2026); install/update extracts the shared root plus only the current node's triple, flattened into `install_dir`, so flat `<install_dir>/<program>` resolution is unchanged. It stays one signed asset (`adapter.spt`, plain-tar or gzip); a multi-platform archive missing the recipient's triple is a typed `NoArtifactForPlatform`. Large adapters may still split per-platform. ",
    "doc_b": "adapter registration (`spt adapter add`)**: How a node comes to *know* an adapter \u2014 harness or shell. An explicit **`spt adapter add <path>`** (or **`--github <user/repo>`**) validates the manifest against the published JSON Schema and writes a registration record under `{SPT_HOME}/\u2026/adapters/` \u2014 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. The `--github` fo",
    "score": null,
    "reasoning": null
  },
  {
    "index": 11,
    "req_a": "REQ-ADAPTER-UPDATE-POST",
    "req_b": "REQ-UPDATE-RESTART-SAFE-SWAP",
    "title_a": "Composite adapter update \u2014 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 \u2014 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 > sentinel/manifest-message > nothing. NO [update.post] declared \u21d2 today's adapter_applied\u2192[update].message unchanged; post-step FAILS \u21d2 loud warning + fall back to adapter_applied\u2192message. FAILURE-ISOLATED: a committed gh_release pull is never rolled back if the post-step fails (independent channels). (v0.16.0)",
    "title_b": "THE-FORKENING W4 (operator-grilled 2026-07-14; RETIRES findings-backlog seed #12 REQ-UPDATE-ONE-STEP-SAFE-SWAP): `spt update --restart` = the one-step ergonomic path to the SAFE full-cycle swap \u2014 fetch -> `update adapters` -> `apply --finish` LAST (lethal-leg-last, ruled: apply --finish restarts the whole daemon incl. broker/PTYs, so it must be the final act \u2014 everything completes from ANY invoking context including an spt-hosted session whose PTY dies at that step; accepted cost: a finish FAILURE leaves updated adapters on old-activated core briefly \u2014 loud + operator-attended by nature of the flag). Composes with `-c/--core-only` (skip adapters leg). The 0.28.0 wedge lesson closes: the ergonomic one-step no longer picks the riskier path by default for operators who want the full cycle. Gate: unit \u2014 flag sequencing incl. lethal-leg-last ordering + -c compose; doc \u2014 self-update docs present --restart as the full-cycle form and name the finish-restart consequence. Kin REQ-UPDATE-DEFAULT-COMPOSITE, REQ-UPDATE-FINISH-COMMUNE-FLUSH (deferred commune-flush rides the same finish path when built), seed #12 (retired by this).",
    "doc_a": "`[update.post]` \u2014 the composite post-step (since v0.16.0) / Composite update \u2014 `[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.",
    "doc_b": "update composite (`spt update`)** \u2014 the plain verb is the primary form: `update fetch --apply` then `update adapters` (core-first order); with core already current, only adapters update. `--core-only`/`-c` skips adapters; `spt update adapters [<a>[,<b>\u2026]]` is the adapters leg alone (alias over `spt adapter update`). The composite's invoker always survives, because a routine apply cycles only the **brain** \u2014 the *restart-required* message on broker-side releases is a notice, not a restart. `spt update --restart` is the one-step **full cycle**: fetch \u2192 adapters \u2192 `apply --finish` last (the finis",
    "score": null,
    "reasoning": null
  },
  {
    "index": 12,
    "req_a": "REQ-API-4",
    "req_b": "REQ-INSTALL-11",
    "title_a": "api resolves the adapter manifest (+ profile + install dir) from `--adapter name:profile` via the registry when `--manifest` is omitted; `--manifest` becomes an optional OVERRIDE (unregistered / local-dev manifests). Removes the require-both-flags redundancy \u2014 a registered adapter's live bringup / digest / capability needs only `--adapter` \u2014 and yields the precise install dir (the record's source_dir) rather than the --manifest parent, closing the copy-mode psyche-binary edge (v0.8.0)",
    "title_b": "Adapter command templates resolve their program against the adapter's install dir BEFORE PATH: a `.spt`-shipped binary (dropped to adapters/_github/<safe>/ by --release/--github acquisition, or kept in the source_dir under copy-mode where only manifest+strings/ are copied to adapters/<name>) runs without any PATH placement \u2014 a bare-name template token (e.g. `claude-spt-digest ...`) is rewritten to <install_dir>/<program>(.exe on Windows) when that file exists, else left bare for the PATH fallback. Makes a `.spt` self-contained (closes the --release bundled-binary gap perri confirmed) (v0.7.4)",
    "doc_a": "Manifest resolution from `--adapter` (since v0.8.0).** `spt api <cmd> --adapter <name[:profile]>` resolves the registered adapter's manifest, `:profile` overlay, and install dir from the registry when `--manifest` is omitted \u2014 a registered adapter's `api` calls need only `--adapter`. `--manifest <path>` becomes an optional **override** (an unregistered or local-dev manifest): when present, the manifest loads from that file and the install dir is its parent directory; when absent, both come from the registry record (the install dir is the record's precise `source_dir`). An unregistered adapter ",
    "doc_b": "Command templates are opaque.** spt-core never parses out a model/tool/flag \u2014 the adapter writes the whole command line; spt-core fills substitution keys and runs it. A command template's program token resolves against the adapter install dir before PATH (since v0.8.0).** A `.spt` adapter ships its built binaries to the adapter's install dir (`adapters/_github/<safe>/` via `--release`/`--github`, or the record's `source_dir` under copy-mode), so a bare program name (e.g. `claude-spt-digest \u2026`) binds to the shipped binary first and falls back to PATH when absent \u2014 a `.spt` that ships its binari",
    "score": null,
    "reasoning": null
  },
  {
    "index": 13,
    "req_a": "REQ-ATTACH-RESIZE-REPAINT",
    "req_b": "REQ-RC-IDMARKER-DISABLE",
    "title_a": "RESCOPED 2026-07-22 by ADR-0047 Amendment 2 (doyle-ratified): this REQ no longer asserts a defect. Its original claim \u2014 that the attach-stream resize never arms the presentation barrier, and that this is v0.39.4 field bug 3's root \u2014 is RETRACTED; the W3 bisect falsified it three ways (static trace send_attach_resize -> serve_attach Resize arm -> brain.resize -> KIND_RESIZE -> dispatch_resize -> begin_resize_serialized; the synthetic sibling's pass; the broker's dynamic trace attach -> RESIZE_DISPATCH -> SYNC_PUSH{controller=true} with no RESIZE_SYNC_DROP) and then pinned the real root in the rc CLIENT's console mode (REQ-RC-NEWLINE-PRESENTATION-TRUTH / KNOWN-HAZARDS 7.56). The ADR-0047 Amendment 1 DECISION (geometry change = one transaction, one exit shape, any entry) stands on its own merits and is not disturbed. WHAT THIS REQ NOW OWNS \u2014 the captured-shape regression apparatus that the bisect proved worth keeping: hertz's ENLYZEAM production capture + its integrity pins (*.log -text, the fixture README sha256 table, per-record length refusal in both the player and the reader); the capture-player replay vehicle; and the probe ladder that makes any verdict from it mean something \u2014 vehicle transparency per CELL (ConPTY re-renders, so byte equality is unsatisfiable by construction), the pre-state soundness self-check (narrow-captured bytes into a wide authority is sound only if absolutely addressed), the capability probe re-run at every shape revision with its own precondition asserted, and the no-vehicle control (grid+resize+repaint over the same bytes = 0 divergent rows, the standing proof the seam is clean). Oracle clauses are binding for this rig class and are written out in Amendment 2. Gate: unit \u2014 the vehicle-transparency predicate + its non-vacuity sibling + the record-integrity refusals; int \u2014 the client-model rig pair (rc-fixed vehicle = clean, rc vehicle = reproduces the field scrap) plus the no-vehicle control. NO impl stage: there is no broker-side implementation owed, and pretending otherwise would point an activated stage at evidence that does not exist. Kin: REQ-RC-NEWLINE-PRESENTATION-TRUTH (the actual fix), REQ-RESIZE-INPUT-MODE-INTEGRITY (7.55, untouched \u2014 its capture has no replay vehicle in its path).",
    "title_b": "Bugs #14 + #7/#8 (marker half): feature-flag the top-right StatusRow endpoint-id marker OFF (rc.rs:198-307). It is a one-shot absolutely-positioned paint that scrolls off-screen and is not re-stickied (#14), and its DECSC/clear/SGR injection splices into the harness in-flight drawing causing residual artifacts (#7/#8). Ship disabled next release (operator: save the concept for a future web SPT GUI); revisit as a proper per-frame sticky overlay only once REQ-BROKER-SCREEN-GRID provides the screen model. See docs/NEXT-MILESTONE-BUG-TRIAGE.md #14.",
    "doc_a": "Failure (paid-for THREE TIMES \u2014 v0.39.3 geometry-epoch, v0.39.4 presentation barrier, and still red in the field; pinned by hertz's ENLYZEAM production byte capture 2026-07-22, doyle seam-verified same day):** the codebase has TWO resize entries. The IPC `ResizeReq` path is sound post-0.39.4 (arm barrier \u2192 settle \u2192 issue \u2192 commit pushes the authoritative repaint at the landed geometry). But rc's INITIAL viewport resize rides the attach-stream verb (`send_attach_resize`, once per establish) which never arms the barrier \u2014 the no-transition branch of `commit_resize` resizes the grid and pushes NO",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 14,
    "req_a": "REQ-BIND-HONEST-SELF-STAMP",
    "req_b": "REQ-NODE-IDENTITY",
    "title_a": "C2-ROOT (F028, doyle RCA 2026-07-03): the identity-attribution ROOT behind REQ-SESSIONS-LOG-ENDPOINT-ATTRIBUTION \u2014 three composing defects let a psyche-hosted SessionStart hook stamp a FOREIGN perch: (1) roster::detect_self_id leg (a) scans the owlery ONE level (roster.rs:107 read_dir(owlery)) so a NESTED psyche perch owlery/<parent>/nested/<id>-psyche can never self-resolve; (2) [session.psyche_init] (claude-spt manifest ~L347) spawns the psyche with NO env_remove + no pinned child identity, so whatever OWL_SESSION_ID/SPT_AGENT_ID reaches the child resolves to a foreign top-level perch; (3) the hook then writes info.json (session_id/pid rebind) + sessions.log on the mis-resolved victim. FIX (spt-core half): (i) identity PINNED at spawn \u2014 ManifestRuntime role spawns inject the child's OWN SPT_AGENT_ID=<child perch id> + OWL_SESSION_ID=<child session>, AND detect_self_id enumerates NESTED perches (fix the one-level owlery scan); (ii) BIND HONESTY \u2014 a session-start stamp may only write a perch whose resolved id AFFIRMATIVELY matches; never a fallback pick; refuse + loud-skip when unresolved (kin REQ-MSG-CLI-ORIGIN honest-default + #9 ancestry). ADAPTER half = PERRI touchpoint (psyche_init env scrubbing / relies on runtime pinning; hook loses silent fallback-perch behavior) \u2014 FLAG doyle BEFORE any manifest move (adapter glue-model rule). See triage C2 fix (i)+(ii).",
    "title_b": "Ed25519 identity primitive: keypair, detached sign/verify, stable pubkey<->hex",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 15,
    "req_a": "REQ-BIND-PSYCHE-CUSTODY-SQUAT-GUARD",
    "req_b": "REQ-PSYCHE-ACCOUNT-REFUSAL-EXIT",
    "title_a": "MSG-IDENTITY W1 / F-036 leg c (perri field RCA, doyle ACCEPTED defense-in-depth): a bind whose --set-session-id equals a NESTED psyche perch's own custody sid is definitionally wrong and MUST be refused \u2014 core owns psyche-custody.json and can see the collision at bind time. ROOT CONTEXT: with the F-036 env leak, each stolen bind carried a psyche sid as the new pin; the identity-env sanitize (leg a) removes the known vector, this guard makes the CLASS unreachable (any future vector that tries to rotate a real endpoint's perch onto a psyche's custody sid is refused loud). Gate: a bind attempt whose target sid appears in psyche-custody.json as a psyche's OWN sid is REFUSED with a distinct loud token (unit: custody-sid collision refuses; a normal non-custody sid bind is unaffected). Kin REQ-PSYCHE-SID-CUSTODY, session-pin cluster, [[spt-core-findings-backlog]] F-036.",
    "title_b": "RESERVED EXIT 96 \u2014 account/credential refusal from a psyche_resume turn: the inner tool refused for account-level reasons (spend/usage cap, expired/revoked credential, org quota) \u2014 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 \u2014 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 \u2014 unpredictable but CERTAIN \u2014 and a permanently-given-up psyche is invisible), reset on first success, no state to unwind. (2) DISTINCT SURFACE: never the defect-shaped PSYCHE_TURN_FAIL prefix \u2014 renders as its own class (adapter-side PSYCHE_TURN_REFUSED; core-side an additive class discriminator beside psyche_host_error, never a fold into it), NAMES the actor and action (a human, the inner tool's account \u2014 not the endpoint/adapter/session), carries the inner tool's own words via the dual-stream tail (REQ-PSYCHE-TURN-STREAM-EVIDENCE), and states HOW LONG it has been refused. (3) NEVER reseeds \u2014 custody is fine; a reseed would destroy a healthy transcript for nothing. (4) Never counted as a crashloop defect. Adapter contract half: claude-spt docs/design/RESERVED-EXIT-ACCOUNT-REFUSAL.md (appendix of observed CC strings is informative-not-contract, populated ONLY from observed bytes \u2014 the invented-string chain of 2026-07-26 is the anti-pattern it refuses). Gate at activation: unit \u2014 exit 96 classifies refused (no strike increment, no reseed, own backoff schedule engaged); 95/generic/None unchanged; surface renders the distinct label + duration.",
    "doc_a": "",
    "doc_b": "Reserved exit codes \u2014 how a `psyche_resume` turn tells spt-core *why* it failed.** spt-core classifies a failed turn on the process **exit code alone \u2014 never on output text** (your harness may reword, restructure, or JSON-wrap its errors freely; only the code is contract). Any other nonzero exit is a generic failure: spt-core keeps the Psyche's session custody, counts a strike, and retries.",
    "score": null,
    "reasoning": null
  },
  {
    "index": 16,
    "req_a": "REQ-BRAIN-RESUME-NO-CONN-DEADLOCK",
    "req_b": "REQ-RESUME-ADAPTER-FOLLOWS-SESSION",
    "title_a": "UPDATE-WEDGE round 3 (v0.30.5, doyle-ruled Option A 2026-07-09 \u2014 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 \u2014 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 \u2014 docs/UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md): a conn's send half is a single `SharedSend = Arc<Mutex<SendHalf>>` (broker.rs:78). Subscriber writer threads (`viewer_writer` broker.rs:1333/1342, `controller_writer` :1451) hold `send.lock()` ACROSS a BLOCKING `write_frame`; the dispatch reply path (`send_frame` :4221 \u2192 KIND_SESSIONS_REPLY / KIND_NET_STATUS_REPLY) needs the SAME lock. `resume_sessions` (brain.rs:1031\u21921054) subscribes every session as a Viewer onto the brain's MAIN conn \u2014 which is ALSO the brain's request/reply channel. The daemon brain hosts no PTY sessions (brainproc.rs:184) so run_brain never drains that output; it reads the conn only during the 500ms-heartbeat net_status()/sessions() calls (drain-and-DISCARD, `_ => continue`). When an actively-streaming session backs the conn up, a subscriber writer BLOCKS in write_frame holding send.lock() \u2192 the dispatch thread can't send the heartbeat reply \u2192 net_status()/sessions() never return \u2192 the heartbeat loop stalls \u2192 the brain never drains \u2192 the writer stays blocked = SELF-DEADLOCK on the brain conn's send mutex \u2192 every subscriber writer on it wedges \u2192 BRAIN_SUBSCRIBER_STALL_EVICT (controller writer blocked >15s). BOTH severities, one mechanism: RESPAWN (resume subscribes N + the replay burst floods the conn before the loop drains) AND STEADY-STATE (an active streamer's output between heartbeats fills the socket buffer). Counter-54 (REQ-UPDATE-TRIAL-DRAIN-DRIVE) added a 2nd per-heartbeat reply round-trip (sessions()) through the contended mutex \u2014 WIDENED the window (regression-window-exact), did not create it. The round-2 Viewer fix (REQ-BRAIN-RESUME-NO-CONTROL-STEAL) removed the STEAL but kept the brain a SUBSCRIBER \u2014 viewer_writer has the same send.lock()-across-write pattern AND viewers have no stall-evict valve \u2014 so v0.30.4 field-verify wedged again. FIX (Option A, brain-side, SEAMLESS): resume_sessions does NOT subscribe (drop the subscribe_with call; \u00a73 verification guard confirmed NO brain consumer \u2014 digest/relay/net-consumer/shellwake/presence \u2014 reads the resumed subs). The brain conn then carries only request/reply \u2192 no subscriber backpressure \u2192 no deadlock. Keep the session_cursors seed only if harmless. Rides the brain-swap \u21d2 seamless (no daemon.rs:368 broker restart). Option B (a dedicated Split-reader drain of the brain conn, brain.rs:230 \u2014 the pump's carrier) is the FORWARD path for when genuinely daemon-DRIVEN sessions land (the live-agent adapter) \u2014 deferred, noted, not built. Option C (the broker-side durable CLASS fix) = REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK, deferred. Int = the confirmer rig (brain+broker+PTY, brain_decouple template): N sessions actively producing output, brain resume-subscribed onto its req/reply conn; RED-first = heartbeat stalls + an active-streaming controller stall-evicted >15s WITHOUT any dead peer present (proves dead-peer-independence); assert BOTH severities (respawn interleave + steady-state output backup); Option A turns both green. Composes with REQ-BRAIN-RESUME-NO-CONTROL-STEAL (the round-2 Viewer fix this supersedes as the wedge cure), REQ-UPDATE-TRIAL-DRAIN-DRIVE (the counter-54 reap-drive that widened the window), REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE (the stall-evict it stops triggering).",
    "title_b": "D-2 (REMOTE-TRUTH triage \u00a7D-2 + operator Q5 @c248afc): a resume-from-history restores the RECORDED session adapter (REQ-SESSION-ADAPTER-RECORDED) \u2014 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 \u2014 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 \u2014 adapter immutable, carried by bind's stamp_creation_fields). FIX (doyle fork ruling): ResumeRow (model.rs:199) gains adapter: Option<String> threaded from SessionEntry.adapter in picker/data.rs; the row title (model.rs:228) renders [{adapter}] when Some ({head} [{adapter}] - {time} (\u2026{id5})); resume_outcome bakes the ROW's adapter with an endpoint fallback (row.adapter.unwrap_or(ep.adapter_profile)) \u2014 None \u2192 the endpoint's current stamp (benign degrade). The pre-spawn RE-STAMP + refusal ride the picker resume dispatch (mod.rs:360 Run arm, resume.is_some()) reusing the hazard-guarded mutate_info seam (write_adapter_change/mod.rs:336), NEVER the bind path: order = read current info.adapter \u2192 if the baked adapter DIFFERS (a real replace; a None-row bakes the endpoint's own \u2192 equals current \u2192 NO write) \u2192 registered-check via resolve_option (Err(NotRegistered) \u2192 loud F-1 refusal naming the adapter + `spt adapter add`, NO stamp, NO spawn) \u2192 write_adapter_change re-stamp \u2192 spawn. ONE adapter write path (the mutate_info seam); REQ-HAZARD-ADAPTER-PROFILE-STAMP-CLOBBER's bind/hook path (stamp_creation_fields, home.rs) UNTOUCHED \u2014 both its guard tests stay green as the gate condition. Red-first: a resume row adapter=\"claude-spt\" over an endpoint stamped \"claude-spt:ccs\" \u2192 the baked Outcome.adapter == \"claude-spt\" (the deliberate replace) and the pre-spawn stamp writes it.",
    "doc_a": "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 \u2014 a brain the supervisor respawns (crash *or* update) reconstructs all session continuity by querying the persistent side, never a brain\u2192brain message. Returns the ids re-attached (empty when the broker hosts none \u2014 the supervised daemon brain's no-op-today case). Each session is seeded into [`",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 17,
    "req_a": "REQ-BRAIN-RESUME-NO-CONTROL-STEAL",
    "req_b": "REQ-RESUME-ADAPTER-FOLLOWS-SESSION",
    "title_a": "UPDATE-WEDGE round 2 (v0.30.4, doyle-ruled 2026-07-09 \u2014 field incident on the counter-54 fetch--apply): a brain-respawn must NEVER steal, then stall-evict, the controller of a broker PTY session the daemon brain does not DRIVE. ROOT (field-pinned + SME, docs/UPDATE-WEDGE-2-RCA.md + docs/UPDATE-WEDGE-2-SME-todlando.md): `resume_sessions` (brain.rs:985) re-attaches EVERY session `KIND_SESSIONS` returns via `subscribe` = `subscribe_with(AttachIntent::Control, by:None)` (brain.rs:1450-1455). The docstring's 'a None identity never displaces (falls back to viewer)' is a MYTH for FREE / SAME-LOCAL-IDENTITY slots: `resolve_subscribe` (broker.rs:1134-1153) stall-evicts FIRST, then `become_controller` if the slot is now free OR the incumbent is also local (None==None) \u2014 viewer-fallback fires ONLY when a DIFFERENT REMOTE controls. So on a box with N spt-hosted broker PTYs, a brain-respawn STEALS the by:None controller of every free/local-controlled session (incl. the operator's LOCAL `spt rc`), which the daemon brain never drains (it hosts no PTY sessions \u2014 brainproc.rs:184) \u2192 15s later `stall_evict_controller` (broker.rs:1039) releases driven_by \u2192 the session is UNCONTROLLABLE (Failure A). It also head-of-line-blocks the shared brain\u2194broker conn on the N-session controller-replay burst \u2192 every journaled `spt rc` retake deadlines ('brain IPC read deadline', the REQ-BROKER-ATTACH-JOURNAL-RESILIENT / #16 shared-conn symptom) \u2192 global rc failure on ALL N (Failure B). Field 2026-07-09 (operator-confirmed): fetch--apply 0.30.2\u21920.30.3 PROMOTED CLEANLY (the counter-54 fix worked) but under 7 spt-hosted PTYs (ALL with LOCAL by:None controllers) the resume SILENTLY STOLE all 7 (become_controller same-local re-take, NO Displaced notice \u2192 orphaned, output froze immediately, no rc-detach splash) + blocked every rc retake. The 5-vs-2 stall-evict split is ACTIVE-vs-IDLE, not remote-vs-local: 5 producing output \u2192 stolen writer blocked >15s \u2192 stall-evict; 2 idle \u2192 writer parked \u2192 no evict, but still silently stolen+frozen. The counter-54 promotion fix did NOT cause this \u2014 pre-existing resume-steal latent bug, hidden until N broker PTYs were present at a respawn; the single-black-holed-session A'-rig never exercised N-live-controllers-under-replay. FIX (brain-side): `resume_sessions` re-attaches as **Viewer** (`AttachIntent::Viewer`), NOT Control \u2014 a viewer never touches driven_by and is never stall-evicted (broker.rs:1063+ bounded try_send + private eviction), so steal-then-drop vanishes and the operator keeps/regains control; and it relieves shared-conn pressure (a slow viewer is DROPPED, never a 15s controller block) so rc retake gets through. Control ONLY for sessions the daemon brain genuinely DRIVES (empty set today \u2192 all become Viewer; forward-correct for the live-agent-adapter future). SECONDARY (escalation, ONLY if the gate shows residual B): stagger the resume re-attach + bound the viewer replay so the respawn burst can't saturate the conn. Int = the multi-broker-PTY-session RESPAWN rig (the coverage the A'-rig lacked): N real broker-spawned sessions with controllers producing output \u2192 real brain respawn/promote \u2192 assert (1) EVERY session keeps its controller across the swap (no stall-evict of a session the brain doesn't drive), (2) `spt rc` attaches/retakes IMMEDIATELY post-promote (no shared-conn saturation), (3) promotion still succeeds. RED-first: the current Control re-attach steals+evicts + deadlines rc. Composes with REQ-UPDATE-TRIAL-DRAIN-DRIVE (the orthogonal counter-54 promote fix), REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE (the stall-evict it stops mis-firing on non-driven sessions), REQ-BROKER-ATTACH-JOURNAL-RESILIENT (the #16 shared-conn resilience). Distinct from REQ-BRAIN-UPDATE-RESTART-CLEAN-CLOSE (that = the OLD brain's outgoing black-hole; this = the NEW brain's resume-steal).",
    "title_b": "D-2 (REMOTE-TRUTH triage \u00a7D-2 + operator Q5 @c248afc): a resume-from-history restores the RECORDED session adapter (REQ-SESSION-ADAPTER-RECORDED) \u2014 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 \u2014 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 \u2014 adapter immutable, carried by bind's stamp_creation_fields). FIX (doyle fork ruling): ResumeRow (model.rs:199) gains adapter: Option<String> threaded from SessionEntry.adapter in picker/data.rs; the row title (model.rs:228) renders [{adapter}] when Some ({head} [{adapter}] - {time} (\u2026{id5})); resume_outcome bakes the ROW's adapter with an endpoint fallback (row.adapter.unwrap_or(ep.adapter_profile)) \u2014 None \u2192 the endpoint's current stamp (benign degrade). The pre-spawn RE-STAMP + refusal ride the picker resume dispatch (mod.rs:360 Run arm, resume.is_some()) reusing the hazard-guarded mutate_info seam (write_adapter_change/mod.rs:336), NEVER the bind path: order = read current info.adapter \u2192 if the baked adapter DIFFERS (a real replace; a None-row bakes the endpoint's own \u2192 equals current \u2192 NO write) \u2192 registered-check via resolve_option (Err(NotRegistered) \u2192 loud F-1 refusal naming the adapter + `spt adapter add`, NO stamp, NO spawn) \u2192 write_adapter_change re-stamp \u2192 spawn. ONE adapter write path (the mutate_info seam); REQ-HAZARD-ADAPTER-PROFILE-STAMP-CLOBBER's bind/hook path (stamp_creation_fields, home.rs) UNTOUCHED \u2014 both its guard tests stay green as the gate condition. Red-first: a resume row adapter=\"claude-spt\" over an endpoint stamped \"claude-spt:ccs\" \u2192 the baked Outcome.adapter == \"claude-spt\" (the deliberate replace) and the pre-spawn stamp writes it.",
    "doc_a": "resume re-attach is view-only for non-driven sessions** \u2014 on respawn the new brain queries the broker for every hosted session and re-attaches to rebuild output-continuity cursors, but it re-attaches as a **viewer**, never a controller, for any session it does not itself drive (which is *all* of them today \u2014 the supervised daemon brain hosts no PTY sessions; spt-hosted PTYs are driven by the operator's attach or the endpoint's own loop). Re-attaching as a controller would seize the controller slot of every free/local-controlled session \u2014 including the operator's local `spt rc` \u2014 and then, beca",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 18,
    "req_a": "REQ-BROKER-ATTACH-JOURNAL-RESILIENT",
    "req_b": "REQ-CRC-SWAP-OLD-DISPLACE",
    "title_a": "A poisoned EffectJournal mutex or a sick NetHost runtime must NOT permanently brick all future attaches. Bug #16 (URGENT): a live spt-hosted endpoint (eel-a) attach fails with 'brain IPC read deadline elapsed' after a self-update brain-respawn \u2014 the broker survives the respawn and one journaled op (dispatch_net_stream_open journal.apply_once + loopback open_stream runtime.block_on nethost.rs:1060) enters a bad state, so every journaled attach silently kills its per-conn reply thread while non-journaled ops keep working. Fix: recover PoisonError via into_inner (effect.rs apply_once, replace the .expect panics) so one panic cannot brick all attaches; bound the loopback open_stream block_on (nethost.rs:1060) like the QUIC bounded_block_on so a sick runtime fails fast with an error frame not an opaque 10s deadline. Reinforces REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE. See docs/NEXT-MILESTONE-BUG-TRIAGE.md #16.",
    "title_b": "C1 (F028, infra; memory crc-swap-old-litter-brick, RCA'd ENLYZEAM + confirmed systemic): crc_swap `.old`-litter bricks every subsequent adapter update. apply_crc_swap Phase-3 `let _ = remove_file(.old)` (spt-daemon/src/crc_swap.rs:129-133) silently fails whenever ANY pre-update process still image-maps the old binary (NORMAL on a live box \u2014 endpoint launch children survive updates). The NEXT update's first commit-op rename(exe->exe.old) = MoveFileExW(REPLACE_EXISTING) must delete the mapped .old -> win32 err 5 -> whole apply fails + rolls back FOREVER, context-free. FIX: (i) DISPLACE not replace \u2014 when <target>.old exists, rename it aside to a unique suffix (rename succeeds on mapped files; spt's own updater already does spt.exe.old-<counter>); GC stale .old.* opportunistically. (ii) Wrap swap io errors with op + path (`rename claude-spt.exe -> claude-spt.exe.old: \u2026`). Unit seam exists (crc_swap tests). See triage C1.",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 19,
    "req_a": "REQ-BROKER-SCREEN-GRID",
    "req_b": "REQ-HAZARD-RENDER-LIFECYCLE",
    "title_a": "Bugs #6 + #12 + #7/#8-artifacts: the broker is a raw-byte pump with no screen model \u2014 OutputLog replays the raw ring from seq 0 into a fresh terminal on attach, so an alt-screen TUI (Claude Code) corrupts scrollback (#6) and rc-to-a-pre-running-endpoint garbles (#12 \u2014 rc and endpoint run --attach are the SAME client fn, so it is replay content not a client-VT bug). Fix: a server-side VT/grid/screen model (tmux/mosh-style) that maintains authoritative screen + alt/main + cursor and synthesizes a CLEAN current-screen repaint on attach instead of replaying mid-stream ring bytes. Also eliminates residual-cell artifacts on animate/scroll/resize (#7/#8). Operator NON-NEGOTIABLE: accurate PTY representation with zero artifacts. (win32 vterm in the report means this server-side emulator, not ConPTY which is already the backend.) See docs/NEXT-MILESTONE-BUG-TRIAGE.md #6/#12.",
    "title_b": "RC-RENDER-TRUTH W3 (KNOWN-HAZARDS 7.47 \u2014 umbrella conformance seam for ADR-0043): the physical terminal is never mutated or terminated outside its renderer's ordered state model. Regression matrix from the hertz RCA: output-before-exit through the production path; unconditional display teardown across every rc exit class; TUI baseline reconstructs whole after out-of-band mutation (recording backend); repaint replays tracked modes. Deferred P2 seeds recorded, NOT this milestone: Exit{after_seq} watermark defense; per-client semantic baseline (only if spt ever transforms live frames). Gate: int \u2014 the matrix; doc \u2014 KNOWN-HAZARDS 7.47.",
    "doc_a": "the server-side render grid + clean repaint on attach: a clean-room `ScreenGrid` (vte::Perform) interprets the byte stream into an authoritative current screen; `become_controller`/`add_viewer` emit one synthesized repaint instead of the raw ring. Companion design: V0.19.0-P6-SCREEN-GRID-DESIGN.md.",
    "doc_b": "7.47 The physical terminal is never mutated or terminated outside its renderer's ordered state model \u2014 output before exit, owned baselines, unconditional teardown `[REQ-HAZARD-RENDER-LIFECYCLE]` Failure (paid-for, hertz stale-glyphs RCA 2026-07-18, all legs doyle seam-verified):** four render-lifecycle defects presenting as \"missing whitespace\"/stale glyphs. (a) The broker exit waiter direct-writes `KIND_EXIT` via `all_sinks()` while PTY output queues through `controller_writer` \u2014 the per-conn gate serializes bytes but not producer order, so Exit overtakes the child's final erase/SGR-reset/`?1",
    "score": null,
    "reasoning": null
  },
  {
    "index": 20,
    "req_a": "REQ-CI-POSTJOB-DAEMON-REAP",
    "req_b": "REQ-UPDATE-GH-TRANSPORT",
    "title_a": "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 \u2014 process-level death, not a failed assert \u2014 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 \u2014 a session-1 shell CANNOT kill those (Access denied; ExecutablePath unreadable) even though they are healthy. Every CI run therefore leaves session-0 daemons behind that contend with the NEXT run while also leaking its own mid-run, and an external sweep can never reach them. The runner's own job context owns its session-0 children, so only a post-job step inside the job can reap them. KILL SCOPE IS NARROW AND PATH-VERIFIED PER-PID AT KILL TIME (never machine-wide, and never trusting the census snapshot, whose pids can be recycled): eligible only under the run's own build roots \u2014 CARGO_TARGET_DIR, the workspace target, the notify-adapter checkout's target, RUNNER_TEMP, and the pinned n1 old-broker build cache. TWO HARD EXCLUSIONS are checked AFTER the root test rather than instead of it, so that live infra survives a bug in the root computation: anything under an spt-core/bin/ install prefix, and any owl binary. A process whose image path cannot be READ is reported but NEVER killed \u2014 unreadable means unverifiable and the safe direction is to leave it standing. A BOUNDED SETTLE precedes the kill pass so a cleanly-exiting daemon is not counted as a leak; after it, a survivor is a leak by definition, which is what makes the strict-mode trigger a mechanism rather than a judgement call. Rides BOTH the test and n1-gate jobs (doyle scope ruling): n1_pairing spawns real daemon trees from the workspace and from the out-of-tree pin cache, so reaping one job leaves half the cause standing. Gate: impl \u2014 the two census/reap scripts under .github/ci/ plus their job-start and always() job-end wiring on both jobs, both runners. Kin REQ-CI-WINDOWS-PHASE-A-BOUND (the other cause-side leg), REQ-CI-DOCS-ONLY-THIN (recipe-layer precedent: impl-only, no product code).",
    "title_b": "THE-FORKENING W1 (ADR-0036, operator-ruled 2026-07-14): the release channel is PRIVATE (`BigscreenVR/spt-bs-releases`) and the gh CLI is the mandated carrier \u2014 release discovery (`releases/latest`, cli.rs:9717) and asset download (cli.rs:4861 public browser URLs) move to deadline-wrapped `gh` subprocess calls (`gh api`, `gh release download`; run_git pattern). WHY gh not token+HTTP: private-repo `browser_download_url` 404s even with a valid token \u2014 the API asset-id dance is gh's job. Default repo flips via the existing SPT_INSTALL_REPO seam (cli.rs:5363) + xtask REPO const (main.rs:729) + notif.rs consent-changelog URL rider. Loud failure classes: gh missing -> UPDATE_FETCH_REJECTED:GhCliRequired with OS-SPECIFIC install hints (winget/apt/brew); gh unauthed -> distinct GhAuthRequired pointing at `gh auth login`. Signature verification unchanged \u2014 bytes verified after download, carrier-independent (update-set/counter/anchor continuity per ADR-0036 \u00a72). release_verify_e2e reworked to the gh carrier. Gate: unit \u2014 url/invocation construction + both failure classes render OS-correct hints; int \u2014 fetch against a real gh-authed channel resolves latest + downloads and verifies an asset; doc \u2014 self-update docs name the gh prerequisite. Kin REQ-INSTALL-BOOTSTRAP-VERB (same carrier at first install), ADR-0036.",
    "doc_a": "",
    "doc_b": "release channel (private, gh-carried)** \u2014 the release channel is a **private** GitHub repo (`BigscreenVR/spt-bs-releases`, ADR-0036); the **gh CLI is the mandated carrier** for release discovery and asset download (each node authenticates via org membership). A node without an authed `gh` cannot fetch \u2014 refused loud with OS-specific install hints, never a silent hang. Signature verification is carrier-independent: bytes are verified after download exactly as before; counter, signing key, and update-set format are unchanged from the public-channel era. / Prerequisite: the GitHub CLI.** The rele",
    "score": null,
    "reasoning": null
  },
  {
    "index": 21,
    "req_a": "REQ-CLI-2",
    "req_b": "REQ-ENDPOINT-LIST-REST-FILTER",
    "title_a": "spt daemon noun: run|stop|status (hidden daemon verb becomes daemon run; agent-endpoint shutdown keeps its name under endpoint); daemon status renders the pump heartbeat (last-tick recency) so a half-dead daemon is never rendered implied-healthy (M8 decisions 5, 23)",
    "title_b": "spt endpoint list hides SUSPENDED instances by default; a new --show-all flag reveals them. Status-first row ordering with fixed precedence ONLINE > CONTROLLED > UNBOUND > SUSPENDED (when shown) > corrupt last, alphabetical by id within each band. Two invariants: (1) CORRUPT rows ALWAYS render regardless of filters \u2014 corrupt is a record condition demanding operator action (purge/re-mint), not resting clutter; hiding it would re-create counter-39 bug #3 (cross-ref REQ-HAZARD-CORRUPT-PERCH-COHERENCE, CONTEXT.md instance-state _Also avoid_); (2) the per-node Total line DISCLOSES the filter \u2014 'Total: N (+M suspended hidden)' \u2014 so nothing silently vanishes. Registry-Offline rows stay excluded by projection law (resource_projection skips unroutable; unchanged). Grill-with-docs ruling 2026-07-02 (operator + doyle).",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 22,
    "req_a": "REQ-CLI-4",
    "req_b": "REQ-INSTALL-10",
    "title_a": "User-facing CLI output is human-readable: DIRECT-USER commands (e.g. adapter update/list/use) render friendly prose instead of raw CODE:RESULT markers \u2014 \"claude-spt is up to date (0.2.0).\" not \"ADAPTER_UPDATE_UPTODATE:claude-spt: installed 0.2.0, latest 0.2.0\". Strictly bounded to the direct-user surface: the adapter-PARSED bringup tokens (SEEDED/BOUND/READY/NO_SEED on seed/listen, which adapters grep) stay machine-parseable \u2014 humanization is additive (a human line beside the marker, or a --porcelain/--quiet split), never a silent rename of a dual-contract marker. The user-facing bringup composition belongs to the adapter (perri); this REQ owns only the direct-user CLI surface. (v0.9.0)",
    "title_b": "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` \u2014 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)",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 23,
    "req_a": "REQ-CLI-JSON",
    "req_b": "REQ-HAZARD-ADAPTER-APPLY-SILENT-NOOP",
    "title_a": "A global `--json` flag (clap global=true) honored by the READ/STATUS command set: endpoint list/whoami, daemon status, subnet status/show-code, endpoint description/role, adapter list/version, notif list, grant list, access list, shell list, how-to (endpoint digest already has it). Action commands do not honor it. A shared print_json() helper + a coverage TEST asserting every command in the set emits valid JSON (guards against the missing-shared-formatter drift). Output uses explicit per-command output DTOs with committed field names \u2014 internal structs are NOT serialized verbatim (their fields would become a public contract; JSON is a consumed wire-parity surface). (v0.16.0)",
    "title_b": "A DELEGATED live adapter apply MUST NEVER report success without performing the swap, and the live-update seam MUST use ONE parent-aware adapter matcher across all its comparators. TWO defects made the field repro (BUILD-F015B-APPLYMATCH: `--adapter cc:ccs` live update silently no-ops): (D1, matcher skew) the broker's dispatch_adapter_apply filtered sessions by EXACT `s.adapter == req.adapter`, but a `--adapter <adapter>:<profile>` endpoint stores the COMPOSITE `cc:ccs` while the apply carries the PARENT record name `cc` \u2014 so every :profile endpoint fell out to affected=[]; select_endpoints_running_adapter had the same `adp == adapter` skew, while the CLI live-gate (adapter_has_live_endpoint) already parent-matched \u2014 divergent rules on ONE seam. (D2, silent success) the affected.is_empty() branch replied KIND_APPLIED and RETURNED WITHOUT SWAPPING; once the CLI delegates the apply there is no CLI-side fallback swap, so success-without-swap = the update never lands (re-register re-reads the OLD manifest, version-of-truth honestly says old). FIX: (1) ONE shared spt_runtime::profile::adapter_parent_matches(session_adapter, parent) used by the live-gate + broker apply-filter + select_endpoints_running_adapter (no exact `==` against a record name at any live-update seam); (2) the daemon owns the whole apply once delegated \u2014 the CRC swap runs UNCONDITIONALLY (terminate/restart loops no-op when nothing is resident), KIND_APPLIED reported ONLY after a real swap. (F015B, ADR-0025 amendment)",
    "doc_a": "",
    "doc_b": "7.24 A delegated live adapter apply must NEVER report success without swapping, and the live-update seam must use ONE parent-aware adapter matcher `[REQ-HAZARD-ADAPTER-APPLY-SILENT-NOOP]` Failure (F015B / BUILD-F015B-APPLYMATCH):** a live adapter update to a PROFILE-COMPOSITE endpoint (`--adapter cc:ccs`) silently no-oped \u2014 `LIVE` then `DONE` printed, but the version-of-truth stayed OLD. TWO defects on one seam. **(D1, matcher skew):** the broker's `dispatch_adapter_apply` filtered sessions by EXACT `s.adapter == req.adapter`, but a `--adapter <adapter>:<profile>` endpoint stores the COMPOSITE",
    "score": null,
    "reasoning": null
  },
  {
    "index": 24,
    "req_a": "REQ-CONN-POISON-ATTRIBUTION",
    "req_b": "REQ-DAEMON-SERVICE-INSTALL",
    "title_a": "MSG-IDENTITY W6 / F-039 legs b-d (doyle W6 LOCK 2026-07-10, minted per amendment 3): every broker-conn lifecycle record is ATTRIBUTABLE \u2014 the W6 RCA's terminal undecidability (per-line 1:1 CONN_WRITE_POISONED churn = fresh-carrier churn OR stderr interleave artifact) exists because records carry no stable conn identity, no role/endpoint/session context, and no timestamps, and the once-per-conn poison latch hides multiplicity. THREE LEGS. (b) IDENTITY: mint a stable per-physical-conn id (monotonic u64 at conn construction \u2014 Arc::ptr_eq is the only identity today and it does not survive a log line) plus subscriber role and endpoint/session where known, stamped on CONN_WRITE_POISONED, CONN_WRITE_RETIRED, logical stall-evict, attach/resume/detach, and write-retirement records (RCA attach sites: presence nethost.rs:379, stream nethost.rs:258, controller broker.rs:891, viewer broker.rs:1073). (c) TIME: daemon stderr correlation records carry wall-clock AND monotonic timestamps (stderrlog has neither; broker+brain share one file \u2014 interleave is unresolvable without them). (d) LIFECYCLE (doyle-confirmed UNCONDITIONAL, not debug-gated): one BOUNDED set of per-conn lifecycle events \u2014 write start/timeout-cancel/transport close/writer exit/replacement-reattach (hertz RCA fix-shape items 1-3). Constraint (doyle LOCK): the split/attribution must not REDUCE total information, only correct its attribution; NO timeout-value changes; NO suppression-as-fix. Gate: unit \u2014 lifecycle records carry conn id + role + timestamps; the id is unique per physical conn and stable across that conn's records. Kin REQ-CONN-POISON-DIAL-SCOPE (leg a, the token split these fields ride on), REQ-CONN-BLACKHOLE-LIFECYCLE-HARNESS (leg e, consumes these records), REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK (behavior invariant preserved).",
    "title_b": "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 \u2014 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 \u2014 a verb must not regress that), [[kitsubito-linux-rig]].",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 25,
    "req_a": "REQ-CONSENT-1",
    "req_b": "REQ-UPD-7",
    "title_a": "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 & security gates)",
    "title_b": "Origin-source update bootstrap (`spt update fetch`): pull the latest signed release directly from the GitHub release origin (`SaberMage/spt-releases`) \u2014 the per-platform artifact + its `<asset>.release.json` SignedRelease metadata \u2014 and stage it through the EXISTING verify\u2192stage pipeline (the same `plan_verified` gate: two-key signature + channel + monotonic rollback floor + SHA-256), after which the normal consent-notif / `spt update apply` flow is unchanged. Closes the peer-only-discovery gap (REQ-UPD-1): a first-in-fleet / isolated node can update with no peer to pull from. The signed-release anchor keeps the GitHub transport untrusted-but-verified.",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 26,
    "req_a": "REQ-CONTROLLER-LEASE-IDENTITY",
    "req_b": "REQ-HAZARD-REDISPATCH-CONTROL-STEAL",
    "title_a": "RC-RENDER-TRUTH W2 (ADR-0044 decisions 1+2, hertz same-machine --take split-brain RCA P0-A/B, doyle seam-verified broker.rs resolve_subscribe 1296-1318 + 1334-1347): each rc invocation/attach stream mints a UNIQUE controller lease id carried through SubscribeReq, the controller slot, and Input/Resize; node identity stays separate as attribution/access policy only. The ONLY silent successor/replay case is same-lease + equal-or-newer generation (ADR-0038 fix-6 dispatcher-recovery contract preserved exactly); same node + different lease = DISTINCT controller. Explicit Take on a distinct incumbent lease ALWAYS revokes loudly AND authoritatively \u2014 atomically revoke/fence the old lease and FORCE its attach stream closed, then install the taker, regardless of whether by-node strings match; the Displaced notice is best-effort (today old.tx.try_send at broker.rs:1342-1345 can DROP the notice on a Full queue while become_controller still replaces \u2014 the revoke/close must land even when notice enqueue fails; the closed stream is itself the terminal signal rc's PumpEnd::Displaced/EOF path handles). Today same_identity keys on controller_by()==by alone, intent never consulted; two same-machine windows collapse to one identity and --take silently swaps the sink leaving the incumbent input-capable but blind. Acceptable identity seam: (by, nonzero attach generation) as the logical lease. GENERATION LADDER (doyle ruling 2026-07-18, resolves fix-6/Busy conflict at T6): within the SAME by \u2014 equal gen = silent successor (dispatcher recovery, unchanged); STRICTLY NEWER gen (Control or Take) = loud+fenced supersession (full W2 revoke \u2014 without it a live replacement viewport bounces Busy off a stale-replay worker serving a zombie stream forever = post-restart lockout, fix-6 order-independence regression); OLDER gen = refused Busy (unchanged). Across DIFFERENT by: Control = Busy, Take = loud revoke. Deliberate UX: a second same-node window's plain rc loudly displaces the first (pre-W2 did the same replacement SILENTLY with the loser blind-but-interactive; loud+fenced strictly better; --view = coexistence). redispatch_stall T6 must stay green UNMODIFIED. N-1: lease field additive; absent lease (older client) degrades to legacy node-identity semantics for that subscriber. Gate: impl \u2014 lease mint + carry + loud distinct-lease Take; unit \u2014 same-lease equal/newer gen stays silent, same-node different-lease Take displaces loudly, older-gen replay still refused busy; int \u2014 hertz regression steps 1-4 + 7-8 (A gets Displaced{by} + terminal stream completion, output reaches B only, metadata identifies B, one controller slot; separate equal-lease replay test proves dispatcher recovery stays silent); doc \u2014 ADR-0044.",
    "title_b": "REDISPATCH-TRUTH W1 (KNOWN-HAZARDS 7.41, hertz field RCA 2026-07-16 \u2014 4/5 endpoints frozen per brain cycle): a fresh dispatcher must NEVER re-serve a terminal stream \u2014 a replayed historical Attach must not steal (same-identity silent become_controller, no Displaced) or clear (replayed-EOF detach_session) a LIVE controller. The legitimate same-by successor re-take after a brain restart still silently re-takes: the discriminator is stream LIFECYCLE, never origin identity. Gate: int \u2014 production-path regression D1: finished historical Attach + current active Attach, same endpoint/origin; restart target brain only (real run_dispatch_loop rediscovery, NO manual re-serve \u2014 the pre-fix e2e bypass is the lesson); prove the historical stream neither takes nor clears the current controller and current input/output stays exactly-once without detach; doc \u2014 KNOWN-HAZARDS 7.41. HEAVY nextest group at birth (FLAKE-LEDGER #15). Kin REQ-REDISPATCH-FINISHED-RETIRE (the mechanism), REQ-BRAIN-RESUME-NO-CONTROL-STEAL (the CLOSED session-cursor sibling \u2014 different leg), ADR-0038.",
    "doc_a": "1. **Distinct viewport/lease identity.** Each rc invocation/attach stream mints a unique controller lease id, carried through SubscribeReq, the controller slot, and Input/Resize. Node identity is kept separately for display/access policy. The ONLY silent successor/replay case is same-lease + equal-or-newer generation (the ADR-0038 fix-6 dispatcher-recovery contract, preserved exactly). Same node but different lease is a DISTINCT controller. 2. **Explicit Take always revokes a distinct incumbent loudly \u2014 and revocation is authoritative, notification is not.** If `intent == Take` and the incumbe",
    "doc_b": "7.41 A fresh dispatcher must NEVER re-serve a terminal stream \u2014 historical replay must not steal or clear a live controller `[REQ-HAZARD-REDISPATCH-CONTROL-STEAL]` Failure (paid-for, hertz field RCA 2026-07-16 \u2014 4/5 endpoints frozen, twice in one day):** every brain cycle (`spt daemon refresh`, `spt update` apply \u2014 same `applyhost.rs` path) launches a fresh `dispatch::run_dispatch_loop` whose EMPTY per-process `claimed` set enumerates every broker-held peer stream. `NetShared.streams` has NO removal path (`StreamLog::finish` only marks) and the claim condition has no finished filter \u2192 the fres",
    "score": null,
    "reasoning": null
  },
  {
    "index": 27,
    "req_a": "REQ-CREATE-BIND-REST-ACTIVE",
    "req_b": "REQ-RUN-NO-DUP-SESSION",
    "title_a": "A successful fresh `endpoint run --create` bind MUST normalize rest intent to active IN THE SAME lifecycle transition \u2014 it must not leave `rest_state=suspended` from the prior life, and must never require a separate explicit `endpoint wake` to make a freshly-bound-online endpoint's rest vocabulary truthful. RE-PROVEN PRESENT on 0.44.0 (hertz W0 item 3, 2026-07-27, isolated build): `endpoint stop` -> raw status=offline rest_state=suspended session_id=5c93...; successful `run --create --start` minted session 714260b31d440df3 status=online \u2014 rest_state STILL suspended; independent repeat matched (new session 595f..., same sticky suspended). Sibling probes: fresh create with no prior perch carries NO rest_state field (the field itself is inherited residue, not a fresh stamp); explicit `endpoint wake` on the reproduced row flips suspended->active (the transition exists and works \u2014 it is simply never taken by the create/bind path). Downstream symptom from the original F-034 filing: `endpoint suspend` NO_EDGEs on a visibly ONLINE endpoint because the recorded rest state already claims suspended. FIX SHAPE: the create/bind success path stamps rest intent active atomically with the online transition (terminal-normalize family \u2014 same transition, never a follow-up repair step); deliberate suspend-while-online semantics (ADR'd rest vocabulary) must be preserved \u2014 the normalization applies to FRESH binds inheriting a dead life's rest state, not to a live session an operator suspended. Kin: KH 7.49 (a verb never stamps a state it did not cause \u2014 this is the inverse: a transition failing to stamp the state it DID cause), terminal-normalize work 0.41-0.43 (which closed the adjacent shapes but never reached this seam).",
    "title_b": "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 \u2014 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 \u2014 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 -> each attach sees only its own frames. See triage B1.",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 28,
    "req_a": "REQ-DAEMON-5",
    "req_b": "REQ-HAZARD-ADAPTER-APPLY-SILENT-NOOP",
    "title_a": "Pump liveness: the peer pump writes a last-tick heartbeat consumed by daemon status / subnet status (decision 23 render legs in REQ-CLI-2/REQ-SUBNET-8); the daemon supervises the pump task \u2014 a panic is caught, logged loudly, and the pump restarts with capped backoff (\u22645 min), so a 5.9-class death self-heals visibly instead of silently halving the daemon (M8 decision 23; field motivation: hfenduleam 2026-06-07 half-death)",
    "title_b": "A DELEGATED live adapter apply MUST NEVER report success without performing the swap, and the live-update seam MUST use ONE parent-aware adapter matcher across all its comparators. TWO defects made the field repro (BUILD-F015B-APPLYMATCH: `--adapter cc:ccs` live update silently no-ops): (D1, matcher skew) the broker's dispatch_adapter_apply filtered sessions by EXACT `s.adapter == req.adapter`, but a `--adapter <adapter>:<profile>` endpoint stores the COMPOSITE `cc:ccs` while the apply carries the PARENT record name `cc` \u2014 so every :profile endpoint fell out to affected=[]; select_endpoints_running_adapter had the same `adp == adapter` skew, while the CLI live-gate (adapter_has_live_endpoint) already parent-matched \u2014 divergent rules on ONE seam. (D2, silent success) the affected.is_empty() branch replied KIND_APPLIED and RETURNED WITHOUT SWAPPING; once the CLI delegates the apply there is no CLI-side fallback swap, so success-without-swap = the update never lands (re-register re-reads the OLD manifest, version-of-truth honestly says old). FIX: (1) ONE shared spt_runtime::profile::adapter_parent_matches(session_adapter, parent) used by the live-gate + broker apply-filter + select_endpoints_running_adapter (no exact `==` against a record name at any live-update seam); (2) the daemon owns the whole apply once delegated \u2014 the CRC swap runs UNCONDITIONALLY (terminate/restart loops no-op when nothing is resident), KIND_APPLIED reported ONLY after a real swap. (F015B, ADR-0025 amendment)",
    "doc_a": "",
    "doc_b": "7.24 A delegated live adapter apply must NEVER report success without swapping, and the live-update seam must use ONE parent-aware adapter matcher `[REQ-HAZARD-ADAPTER-APPLY-SILENT-NOOP]` Failure (F015B / BUILD-F015B-APPLYMATCH):** a live adapter update to a PROFILE-COMPOSITE endpoint (`--adapter cc:ccs`) silently no-oped \u2014 `LIVE` then `DONE` printed, but the version-of-truth stayed OLD. TWO defects on one seam. **(D1, matcher skew):** the broker's `dispatch_adapter_apply` filtered sessions by EXACT `s.adapter == req.adapter`, but a `--adapter <adapter>:<profile>` endpoint stores the COMPOSITE",
    "score": null,
    "reasoning": null
  },
  {
    "index": 29,
    "req_a": "REQ-DAEMON-6",
    "req_b": "REQ-HAZARD-CONTROLLER-GAP-RESUME",
    "title_a": "Service-aware `daemon start`/`stop`: when an OS service manager has a registered spt-daemon for this user, `spt daemon start` and `spt daemon stop` drive THAT service (so stop doesn't IPC-kill a unit that auto-restart-fights for the broker socket \u2014 the kitsubito 2026-06-08 loop). `start` graduates from a `run` alias to a first-class background verb (ensure-up, idempotent, non-blocking); stop routes managed\u2192manager, manual\u2192IPC. Linux=systemd user unit (`systemctl --user start|stop|is-active spt-daemon`, detected by unit-file presence); Windows=no controllable manager (the logon task is boot-only), so start=detached spawn / stop=IPC.",
    "title_b": "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 \u2014 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 \u2014 advances delivered_through; skipping rolled frames = not-exactly-once = B2 violation), so REQ-HAZARD-VIEWER-RING-ROLL-SNAP does NOT apply. B2 INVARIANT (doyle, broker.rs:327-330): the ring trim is delivered_through-BLIND (`while ring.len() > cap_chunks { pop_front() }`), so re-fetch is exactly-once IFF tail - delivered_through <= cap_chunks (4096) \u2014 NOT guaranteed in general, but the common case (burst < ring; wedged_viewer ~1492 < 4096) holds. FIX: serve_attach catches the output-gap on the controller path (does not ?-propagate) and re-subscribes from Brain::controller_resume_floor (= delivered_through = the gap's `want`; NO mid-stream KIND_SESSIONS round-trip \u2014 sessions() loops on read_event and would re-fatal on the same gap + discard Output); the broker replays the dropped frames. The IRRECOVERABLE edge (floor unchanged across two resumes = ring rolled past delivered_through = frames gone) surfaces a MARKED truncation to the operator (never silent-skip = B2 lie, never spin) and ends cleanly \u2014 full graceful handling deferred to REQ-HAZARD-CONTROLLER-IRRECOVERABLE-BEHIND. Do NOT make the ring trim delivered_through-aware (that risks an unbounded ring under a stuck controller; the 5s eviction + 4096 ring is the practical bound). (v0.13.0)",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 30,
    "req_a": "REQ-DAEMON-9",
    "req_b": "REQ-HAZARD-DAEMON-STOP-REAP",
    "title_a": "Net-bind boot-race resilience: a daemon that comes up net-less (NetHost::start failed \u2014 e.g. the systemd unit autostarted before the network/DNS stack was ready, `Failed to create an address lookup service`) must SELF-HEAL \u2014 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 \u2014 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.",
    "title_b": "Breap: `spt daemon stop` REAPS the spt-hosted children it spawned \u2014 no orphaned psyche/harness processes. Today a stop leaves ~8 orphaned claude-spt-psyche.exe + spt.exe: Psyches are spawned DETACHED (runtime.rs:342-356, the Child is dropped \u2014 'Detached' ~349) and the livehost stop flag Arc<AtomicBool> is NEVER raised (brainproc.rs:227-230 holds it 'for symmetry'). FIX: on stop, raise the livehost stop flag AND kill the spawned psyche/spt-hosted children \u2014 via a Windows job object / Unix process-group so the children die with the daemon (not detached-immortal). Folds with B3 (both the stop path). (v0.12.0)",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 31,
    "req_a": "REQ-DAEMON-BITS-AMBIGUITY",
    "req_b": "REQ-HAZARD-TRANSLATE-FAULT-PERMANENT-DEATH",
    "title_a": "SEED (inactive, RCA-first \u2014 do NOT close on agreement): nothing on a node surfaces WHICH BITS ARE SERVING, and the same silent wrong-state shape bit twice in one day (2026-07-25). Case 1 (adapter-side echo): a post-reboot ensure race left a dev-build alchemy Hub Daemon serving release shells \u2014 version skew visible only by manually comparing process image paths. Case 2 (core, field-measured by flynn): TWO spt daemons resident with live brains on one node \u2014 the installed main daemon (owning ALL sockets: the 5474 listeners and every established connection, single home_tag pipe family) and an orphaned scratchpad-built daemon (auto-started into the node by ensure_running from a stray dev-binary invocation at 16:18, holding zero sockets, resident for hours) \u2014 while `spt --version` on any binary file answers nothing about which process is answering. Measured sharp edges to carry into the RCA: (a) exe path and resolved HOME are independent \u2014 the orphan ran scratchpad bits against the DEFAULT home, so 'where the binary lives' predicts nothing about 'whose state it mutates'; (b) the brain.ready breadcrumb is ONE FILE PER HOME, LAST-WRITER-WINS, keyed by generation \u2014 with two brains in one home the stamp can be written by the daemon you are NOT gating on, so any readiness/identity gate that trusts it must first establish single-writer; (c) the breadcrumb's exe_hash (SHA-256 of resident bytes captured at process start) is the RIGHT discriminator \u2014 image path answers what is on disk, not which bits are answering \u2014 but only under (b)'s single-writer precondition; (d) reap order matters: killing the breadcrumb's last writer leaves the file describing a dead brain's bits until the survivor's next ready write, so any bits-gate readback must be re-established AFTER a reap, never carried across one. Open RCA questions before any fix is designed: why did the second daemon's cold-start not refuse against the live singleton (socket-bind loss is survivable-and-resident today \u2014 is that the right posture?); what should ensure_running check BEYOND socket liveness (bits identity?); where does 'which bits are serving' surface to an operator (endpoint list? daemon status verb?). Kin: the NEVER-SEALING-OBSERVABILITY candidate (same shape \u2014 silent wrong-state only a human staring at the right field catches). Proposed by todlando (his lane), relayed by flynn with the socket-ownership + exe_hash measurements; seeded by doyle. The orphan pair was reaped by path 2026-07-25 (verified by exact ExecutablePath, supervisor before brain); the reap resolved the instance, not the class. RCA POINTS FROM THE PROPOSING LANE (todlando, extended into THIS record 2026-07-26 rather than minted as a second seed): (1) ORDERING \u2014 'do both pids resolve the same spt_home?' is the FIRST question, not a co-equal fact, because every other discriminator is conditioned on its answer: brain.ready is `<spt_home>/brain.ready`, ONE path, single-writer BY DESIGN, so a shared home makes the breadcrumb a contended file and `generation` \u2014 the readiness gate's key, which exists precisely to prevent false promotion \u2014 becomes satisfiable by the stamp of the daemon you are NOT gating on. Prior art on this node: default-home `home_tag` sockets already cross-talk the live daemon's hubs, so shared-home cross-talk is an established class here, not a hypothetical. (2) GATE ON RESIDENT BYTES, NEVER ON IMAGE PATH \u2014 AND TREAT ABSENCE AS UNPROVEN. Path answers 'what is on disk where I asked'; with two daemons on different bits the only question that matters is 'which bits answered me'. Path is the exact field that has already lied in the field: KH 6.11 \u2014 the broker resolves `current_exe()` PER SPAWN (`crates/spt-daemon/src/brainproc.rs`), which on Linux is inode-tracking, so an `update apply` rename made the respawn land on the OLD bytes while readiness passed and the trial recorded `applied:N` (kitsubito v0.4.1); `exe_hash` (lowercase-hex SHA-256 of resident bytes, captured ONCE at process start \u2014 `current_exe_hash`, `crates/spt-daemon/src/brainproc.rs:402`) exists BECAUSE the path-derived belief was provably wrong, and the enlyzeam 0.3.0-under-0.3.2-on-disk case is the same record/reality divergence one layer up. Constraint any observability gate must inherit and must NOT weaken: `exe_hash` is ADDITIVE/BEST-EFFORT \u2014 omitted when the self-read fails, `None` on any pre-D7 stamp \u2014 and today's `bytes_gate` deliberately degrades an absent hash to readiness-only promotion with a loud `PROMOTE_BYTES_UNVERIFIED` (N-1 compat, `brainproc.rs:1042`). An operator-facing 'which bits are serving' answer must degrade the OTHER WAY: absent hash reads UNPROVEN \u2014 never PASS, and never a silent fall back to image path. A compat degrade that is correct for ACCEPTING an update is wrong for ASSERTING an identity. (3) SERVICE OWNERSHIP IS DECIDED BY SOCKET/HUB BINDING, NOT BY START TIME. Case 2 measured the instance (main holds all 5474 listeners + every established connection; the scratchpad daemon holds zero sockets and was the LATER start) but the INFERENCE RULE is what must survive the instance: a later start is not thereby the loser and an earlier start is not thereby the server \u2014 'who is serving' is answered by who owns the binding, so any gate, reap, or diagnostic that ranks candidates by pid or start time is guessing at the one fact it is supposed to establish. (4) REAP-ORDER SYMMETRY \u2014 (d)'s re-establish-the-gate-AFTER-the-reap rule holds identically on the REVERT path; a bits-gate readback may never be carried across a reap or a rollback in either direction. (5) SINGLE RECORD \u2014 this seed is the one home for the class (premature-closure guard: convergent reads are not a root cause, and a green re-read after a reap assigns owner without closing); the socket-bind-loss posture question stays OPEN inside it, and the orphan-pair instance stays closed.",
    "title_b": "C-1 hazard (F029; paid-for: B6 + three-version checkpoint-wake breakage): a REAL translation fault (binary death \u2014 stdin write fail / stdout disconnect \u2014 or strike-budget exhaustion) must get a BOUNDED eager respawn (C3(b) give-up budget) instead of permanent death, and must stamp a PERCH-VISIBLE fault surface (mutate_info field, cleared on healthy respawn/commit) \u2014 TRANSLATION_FAULT is daemon-stderr-only today (the F-019 unread-channel trap; same honesty rule as F-027 ENDPOINT_SPAWN_FAIL). A real fault legitimately loses in-memory state (the wake is NOT carried across a real fault, unlike a mere commit-miss). See addendum C-1 (3)-(4).",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 32,
    "req_a": "REQ-DAEMON-RESTART-SELF-INTERRUPTION",
    "req_b": "REQ-HAZARD-DAEMON-STOP-REAP",
    "title_a": "SEED (inactive \u2014 operator-workflow trap, field-paid 2026-07-25): an spt-hosted context CANNOT restart the daemon, because the restart's STOP phase tears down the very context running the restart command \u2014 the START half never executes and the daemon stays down until an operator starts it by hand. Field case (this node, 17:47\u201317:50): an agent-kicked restart's stop-begin swept the initiating context and everything sharing its Job Object (flynn's shell + the relay-hopped alchemy Hub, cross-context job members \u2014 see REQ-SHELL-CLI-SPAWN-JOB-EXPOSURE's field-firing record); the broker drained ~2m10s to final exit (stderr ends abruptly at 17:50:05.8 amid routine traffic, no shutdown markers); the START was already dead; the operator manually started whatever binary was installed (v0.42.0), with no channel conveying which bits the restarting agent had intended \u2014 binary choice silently transferred to whoever brings the daemon up (kin: REQ-DAEMON-BITS-AMBIGUITY, the same which-bits-serve blindness). CONSEQUENCES: (a) every RESTART-REQUIRED apply (broker update, seed-skew restart per hazard 7.9) is operator-only today, and nothing documents that; (b) stop-begin kills bound shells with `close_shell` unrun, so even the GRACEFUL stop path manufactures stale-online shell records on every restart \u2014 healed at the read gates by REQ-HAZARD-SHELL-STALE-ONLINE's derivation, but manufactured nonetheless, and on pre-derivation binaries the records lie until the next bind; (c) a restart initiated from inside the system it restarts is a self-interruption with no error surface \u2014 the initiator dies believing the restart is in flight. Option space at activation: (1) daemon-owned restart verb \u2014 the OUTGOING broker spawns its successor job-neutrally (the 7.10 WMI\u2192schtasks\u2192breakaway ladder already exists) before exiting, so the caller only requests and its death is irrelevant; (2) a detached job-neutral restart helper (same detached_no_inherit + ladder) that survives the caller; (3) status quo + the public contract documents restart as operator-only. Whichever wins must also answer where the INTENDED-BITS declaration lives so a manual or automated starter picks the right binary (the (c)/BITS-AMBIGUITY joint).",
    "title_b": "Breap: `spt daemon stop` REAPS the spt-hosted children it spawned \u2014 no orphaned psyche/harness processes. Today a stop leaves ~8 orphaned claude-spt-psyche.exe + spt.exe: Psyches are spawned DETACHED (runtime.rs:342-356, the Child is dropped \u2014 'Detached' ~349) and the livehost stop flag Arc<AtomicBool> is NEVER raised (brainproc.rs:227-230 holds it 'for symmetry'). FIX: on stop, raise the livehost stop flag AND kill the spawned psyche/spt-hosted children \u2014 via a Windows job object / Unix process-group so the children die with the daemon (not detached-immortal). Folds with B3 (both the stop path). (v0.12.0)",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 33,
    "req_a": "REQ-DAEMON-STATUS-JSON-TRUTH",
    "req_b": "REQ-RC-VT-TEARDOWN",
    "title_a": "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 \u2014 at-logon task registered' (the two surfaces disagree); and pump staleness (the STALLED diagnosis, B5) is NOT computable from JSON \u2014 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.",
    "title_b": "RC-RENDER-TRUTH W3 (ADR-0043 decision 2, hertz stale-glyphs RCA leg 2 P0): rc display teardown is a display RAII guard SEPARATE from the OS input/raw-mode guard, unconditional and idempotent on EVERY exit path including errors and unwind \u2014 best-effort SGR reset + full scroll-region reset + cursor show + leave alternate screen + clear/home, emitted while VT output processing is still enabled, THEN restore the prior console output mode, THEN parting prose (today RawGuard::drop restores raw/mouse/console-mode only; detach, child exit, displacement, first-event stall, fatal error, and the 30s reconnect give-up all can leave the operator terminal dirty; the reconnect banner clears+homes then give-up prints at the centered cursor). Gate: impl \u2014 split display guard + every-path coverage; unit \u2014 guard emits the cleanup postlude exactly once, idempotent on double-drop; int \u2014 dirty sink (?1049h ?25l SGR31) x every PumpEnd/error class => cleanup postlude precedes the final prose; doc \u2014 ADR-0043.",
    "doc_a": "",
    "doc_b": "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 \u2014 producer order is the contract. Output-before-Exit is a production-path invariant, regression-proven end-to-end (broker \u2192 attach \u2192 rc). 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 including errors and unwind: best-e",
    "score": null,
    "reasoning": null
  },
  {
    "index": 34,
    "req_a": "REQ-DIGEST-FETCHER-STRATEGY",
    "req_b": "REQ-PSYCHE-ROLE-OPTIONAL-SKIP",
    "title_a": "Bug #17 (W6b, closes eel-a end-to-end): [digest] gains a `fetcher` strategy mirroring [history]'s locate/normalize split (CONTEXT \u00a7history: [digest] mirrors history's two strategies \u2014 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 \u2014 CC's projects/<munge(cwd)>/<session_id>.jsonl or a date-globbed rollout tree \u2014 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 \u2014 {session_id}, the perch-bound {cwd} (info.json.cwd), and the captured [env] direction=read vars (W6/REQ-DIGEST-PROFILE-ENV) \u2014 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 from REQ-DIGEST-PROFILE-ENV (which supplies the root env). See docs/NEXT-MILESTONE-BUG-TRIAGE.md #17.",
    "title_b": "W1 (LIFECYCLE-TRUTH): a manifest with NO [session.echo_commune] role SKIPS commune-sync (debug-level note, no strike) instead of hard-failing the turn. ROOT (perri filing, recovered): missing role -> commune-sync hard-fails -> 3-strike stamps the host ('manifest declares no [session.echo_commune] role') while the published contract presents the role as an optional template. FIX: missing OPTIONAL role = skip, not a turn failure.",
    "doc_a": "`[digest]` supports the same two locate strategies as `[history]` \u2014 pick with `strategy`: / `[digest]` \u2014 session-digest extractor (ADR-0019) The session digest's own seam \u2014 **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 \u2192 the digest-record contract. ```toml [digest] extractor = \"claude-spt-digest --session {session_id} --in {source}\" # native log \u2192 contract JSONL source = \"{CLAUDE_CONFIG_DIR}/projects/{session_id}.jsonl\" # optional; defaults to [history].locate_tem",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 35,
    "req_a": "REQ-DISPATCH-CLAIM-RETRY",
    "req_b": "REQ-STREAM-OPENER-DURABLE",
    "title_a": "REDISPATCH-TRUTH W1 (ADR-0038, hertz fix C): dispatcher claims are RETRYABLE and outcomes are CLASSIFIED \u2014 today claimed.insert(stream_id) happens PRE-spawn (dispatch.rs:206/214) and is never cleared or retried on Unknown/Failed, so one transient worker-setup failure permanently abandons the stream. Distinguish active/retryable vs served vs finished: a transient worker-setup failure releases/requeues the claim (bounded/backoff-shaped); terminal classification outcomes stay terminal and do NOT hot-loop (show the distinction in test, not just code). Gate: impl \u2014 claim lifecycle; unit \u2014 transient failure requeues + terminal outcome does not (no hot-loop under a persistently-failing stream); int \u2014 production-path regression D3: inject ONE transient worker-start failure, prove claim retry recovers the stream with NO duplicate controller/output. HEAVY nextest group at birth. Kin REQ-REDISPATCH-FINISHED-RETIRE, REQ-STREAM-OPENER-DURABLE.",
    "title_b": "REDISPATCH-TRUTH W1 (ADR-0038, hertz fix B): stream classification identity is RESTART-DURABLE and independent of the evictable data ring \u2014 today reconstruction classifies via peek_first_line from ring seq 0, but StreamLog is a bounded 4096-transport-chunk ring (DEFAULT_STREAM_RING_CHUNKS, nethost.rs:111), so a high-traffic ACTIVE stream's opener/Request record evicts and replay classifies Unknown/Failed = active stream permanently abandoned. Pin an immutable bounded opener/classification fact (the complete first NDJSON record, or its derived family+cursor facts) OUTSIDE the data ring per broker-held inbound stream, held until stream close; recovery classifies from that metadata, never ring seq 0. Ring semantics and size untouched (enlarging the ring for a correctness fact is REJECTED). Gate: impl \u2014 the pinned opener fact + classification cutover; unit \u2014 classification survives full ring roll-over; int \u2014 production-path regression D2: push >4096 transport chunks on an active Attach (opener evicted), restart dispatcher, prove durable classification resumes the SAME operator stream. HEAVY nextest group at birth. Kin REQ-DISPATCH-CLAIM-RETRY, REQ-HAZARD-REDISPATCH-CONTROL-STEAL.",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 36,
    "req_a": "REQ-DOCS-3",
    "req_b": "REQ-HAZARD-DEFERRED-MANIFEST",
    "title_a": "Di\u00e1taxis structure; one canonical way to do X",
    "title_b": "A pointer-mode (delegated / GhReleaseManaged) adapter whose binary/manifest is not yet extracted is reported with a CLEAR diagnostic, never silently dropped. Today such an adapter reads its manifest LIVE from source_dir (registry.rs manifest_dir ~146/149); a deferred / un-extracted install makes load_manifest fail \u2192 registered() (~410, filter_map(.ok())) SILENTLY DROPS the row \u2192 downstream ADAPTER_UNRESOLVED + a cryptic os-error-2 on `spt adapter use`. FIX: surface a clear diagnostic at the resolver + at `adapter use` (name the adapter + the deferred/missing-manifest cause + the fix), not a silent filter-drop and not a bare os-error-2; consider an eager manifest copy at register time so host_binaries survive before the binary download completes. doyle Finding A. (post-v0.10.0)",
    "doc_a": "Di\u00e1taxis four-mode separation + one-canonical-way, stated as the corpus's contract How these docs are organized",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 37,
    "req_a": "REQ-DRIVEN-BY-OWN-NODE-NORMALIZE",
    "req_b": "REQ-HAZARD-CONTROL-STAMP-CONVERGENCE",
    "title_a": "RC-RENDER-TRUTH v0.38.1 fast-follow leg 1 \u2014 RESHAPED by operator/hertz correction + CONTEXT.md:382-389 grounding (doyle ruling v3, 2026-07-19; the original own-node NORMALIZATION is WITHDRAWN): plain `spt rc` on a CONTROLLED endpoint is refused-with-guidance BY DESIGN (CONTEXT:386 \u2014 never silent-displace; --take is the opt-in kick), and `driven_by` = the CONTROLLING NODE including the own node (CONTEXT:386 single-writer datum \u2014 the stamp comment's remote-only claim was a legacy-path artifact, NOT the model). v0.38.0 shipped behavior is CORRECT: the own-hex latch + client guidance blocking a second same-node plain rc IS the documented refusal, and the W2 generation ladder stays reachable exactly where it belongs \u2014 the recovery seams (rc reconnect re-drive + dispatcher re-serve bypass the client gate; the hertz field FAIL leg is reclassified NOT-A-DEFECT). Remaining work = truth/cosmetics across ALL own-node display surfaces (doyle gate finding 2026-07-19 \u2014 the own-hex latch is truthful but only rc.rs first learned to HUMANIZE it; the picker pin + endpoint-info attached_node still read the own-hex stamp as a foreign remote driver and print raw hex): (a) rc.rs guidance copy names the actual state \u2014 controlled from another window on this machine \u2014 via a pure driver_phrase keyed on the SHARED own-node predicate (roster::is_own_node_hex); (b) picker control_line (view.rs) reads an own-node driver as controlled locally (driven_by_is_self computed at data-build where the raw hex is available, driven_by STAYS populated so View+Kick is still offered); (c) endpoint-info attached_node (reporting.rs derive_attached_node) self-attributes an own-node driver to THIS node, never a foreign null-label hex; (d) stamp_driven_by + set_driven_by/set_controlled comments rewritten to the CONTEXT:386 meaning (controller node, own included) + KH 7.15/secondary remote-only model-claim comments reconciled (selfheal/unlatch semantics unchanged); (e) plain-Control-on-controlled refusal + --view/--take bypass pinned for BOTH remote and own-node drivers. NO broker behavior change; ladder untouched; pure display (no int, same as the ruling-v3 dropped int). Gate: impl \u2014 rc/picker/endpoint-info humanization + shared predicate + comment/docs; unit \u2014 own-node latch stamps truthfully (broker), guidance fires for own+remote with the right copy (rc), driver_phrase names own-node plainly (rc), control_line reads own-node as controlled locally (view), attached_node self-attributes own-node (reporting), --view/--take bypass; doc \u2014 KH 7.15 + ADR-0044 consequence-note alignment.",
    "title_b": "Control/viewer stamps must CONVERGE to broker session-table truth for every session-backed endpoint, not merely edge-trigger \u2014 the UPWARD companion to the DOWNWARD edge-clear REQ-HAZARD-CONTROL-STAMP-LIFETIME (7.27); same 'stamps == broker truth' family. ROOT (F-026 stamp-gap, hall-b/ball-b): a picker-created endpoint's broker spawn become_controller->stamp_driven_by->set_controlled(true) fires BEFORE the adapter binds its perch (a fresh endpoint has no perch until claude boots + binds), so mutate_info returns NotFound and the edge stamp is SWALLOWED (let _); the adapter's bind then writes InfoJson::new with controlled:false DEFAULT and no later edge re-stamps -> the endpoint reads uncontrolled FOREVER while driven (the #3 display fix is correct but datum-starved on this creation path). FIX: the broker (SINGLE WRITER) re-asserts each live session's control/viewer stamps to session-table truth, DIVERGENCE-GATED (read info; compare driven_by/controlled/viewer_count; write ONLY on diff \u2014 no per-poll fsync storm), on the KIND_SESSIONS handler (piggyback: the daemon reconcile + picker poll it, so a fresh perch converges within one reconcile-poll window after bind = the BOUNDED window, no new timer). Event-on-input rejected (an idle controlled session like hall-b never converges). Writes run OFF the log lock (snapshot truth under the lock, converge off it) per the lock-across-effect discipline (KH 7.12/5.16).",
    "doc_a": "v0.38.1 consequence note \u2014 `driven_by` own-node truth (ruling v3)",
    "doc_b": "7.29 Control/viewer stamps CONVERGE to broker session-table truth, not merely edge-trigger `[REQ-HAZARD-CONTROL-STAMP-CONVERGENCE]` Failure (F-026 stamp-gap, hall-b + the original ball-b):** a picker-created endpoint (`endpoint run` \u2192 new) read plain `ONLINE` in the list + picker while genuinely driven \u2014 `info.json` `controlled:false` throughout. ROOT (the UPWARD companion to 7.27's downward edge-clear): the broker spawn path's `become_controller` \u2192 `stamp_driven_by` \u2192 `set_controlled(true)` fires at SPAWN time, but a FRESH endpoint has NO PERCH yet (the adapter binds it after claude boots), s",
    "score": null,
    "reasoning": null
  },
  {
    "index": 38,
    "req_a": "REQ-ECHO-DROP-DIR-RESOLVE",
    "req_b": "REQ-SESSION-ADAPTER-RECORDED",
    "title_a": "W1 (LIFECYCLE-TRUTH): fire_echo resolves the manifest commune_dir through the SAME resolver its siblings use before any write. ROOT (pinned): fire_echo (spt-daemon lifecycle.rs:790) passes the RAW manifest commune_dir into run_echo_commune -> echo.rs:115-117 create_dir_all+join; a relative `.claude` under the WMI-launched daemon's System32 cwd = os error 5 deterministic (two live psyches stamped FAILED on it). Siblings already resolve correctly (ingest ~:583, psyche_drop_file :1072 via resolve_endpoint_drop_dir(raw, cwd)). FIX: fire_echo routes through resolve_endpoint_drop_dir; relative-with-no-cwd = SKIP LOUD (stderr), never a raw relative write \u2014 kills the latent-worse variant where a writable daemon cwd writes the drop to a WRONG dir silently (echo communes lost, no error). Hardening riders (same touch, no separate REQ): bounded EACCES retry on the drop write; echo claude spawn gets explicit cwd = endpoint cwd (perri ask).",
    "title_b": "D-2 (REMOTE-TRUTH triage \u00a7D-2 + operator Q5 @c248afc): the session ledger records the adapter[:profile] a session ran under, so a later resume can restore the harness the session actually used (not merely the endpoint's CURRENT stamp). ROOT: SessionEntry (spt-store/sessions.rs:58) carries ts/session_id/trigger/cwd/ordinal but NOT the adapter \u2014 a resume-from-history row cannot know which harness authored the transcript, so a resume under a since-changed endpoint adapter (B-2 ChangeAdapter, or a fork) launches the wrong harness. FIX: an ADDITIVE `adapter: Option<String>` on SessionEntry, exact cwd/ordinal serde pattern (#[serde(default, skip_serializing_if=\"Option::is_none\")]) \u2014 a pre-migration row missing the key deserializes None; None omits the key on serialize (byte-identical to old rows); an unknown key on an old reader is ignored (serde default) \u2014 back-compat BOTH directions. Stamped at every PRODUCTION session-boundary append. CENSUS (doyle-confirmed @94f0205, corrects the triage-era 5-site drift to the real 3): startup.rs:317 (live bind boot row, rec.adapter in scope), reporting.rs:94 (boundary rotation row UNDER the mutate_info lock, capture adapter_for_ledger=rec.adapter beside cwd_for_ledger), ready.rs:119 in crates/spt-msg (ready-agent boot row, rec.adapter in scope). NOT digest.rs:601 (cfg(test) fixture) and NOT a livehost psyche-ledger append (none exists \u2014 the live /clear|/compact boundary shells `api boundary` \u2192 reporting.rs:94, the SAME append). None-stamp is a benign degrade (resume falls back to the endpoint's current adapter).",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 39,
    "req_a": "REQ-ENDPOINT-AUTOSTART",
    "req_b": "REQ-HAZARD-SHELL-STALE-ONLINE",
    "title_a": "MSG-IDENTITY W5 / F-038 (flynn operator-directed ask 2026-07-10, SPT-CORE-NEEDS #7 + deployah field-confirm same day: mobile-gw alive=false after the v0.30.6 full daemon restart = this feature's absence, live): an endpoint can be marked a STARTUP DEFAULT so the daemon brings it back up at daemon start \u2014 Gateway-class endpoints are infra (the phone treats mobile-gw as always-there; box reboot / daemon cold start currently leaves it down until hands-on). SHAPE RULED (doyle, dispatch): (a) `spt endpoint run --save` persists the run (id + adapter/profile + args) as a startup default REPLAYED at daemon start, symmetric with the shipped `subnet attach/detach --save` precedent \u2014 smallest orthogonal cut, explicit operator intent, no interaction with effective_rest_state/F-035 reader-parity semantics (shape (c) restore-what-was-up REJECTED for now: principled but couples to the rest_state neighborhood that just churned; revisit if --save proves insufficient in the field). A saved endpoint that fails to come up logs loud + does not block daemon start or other replays. flynn docs sweep confirmed missing-feature not docs-gap (rest/wake manual-only; no endpoint analog of subnet --save; no manifest field; no api surface). Gate: int \u2014 daemon restart brings a --save'd endpoint back up (fresh daemon, saved default, endpoint reaches its steady state without hands-on); doc \u2014 public docs page for the verb (VERSION-scoped); unit \u2014 persistence round-trip + replay skip-on-missing-adapter loud. Kin subnet --save (the symmetry precedent), REQ-LIST-JSON-LIVENESS-PARITY + REQ-HAZARD-BIND-REST-STATE-CARRY (the F-035 neighborhood shape (c) would have coupled to), [[spt-core-findings-backlog]] F-038. Interim on flynn's box (logon scheduled task) dissolves when this lands.",
    "title_b": "A shell instance's ONLINE-ness is DERIVED (recorded status AND its recorded `shell.pid` not provably dead), never the recorded `status` field alone \u2014 an abruptly-dead binary (force-kill, crash, OOM: no link-break, so `close_shell`'s offline flip never runs) must not read online forever. The shell-side twin of REQ-HAZARD-DAEMON-HOSTED-LIVENESS, which gave AGENT perches exactly this resolver and which shells never got. PROVABLY DEAD is the narrow discriminant: `shell.pid` present AND parses non-zero AND `!is_process_alive` \u2014 pid absent, unparseable, or 0 (a broker-hosted spawn whose backend exposed no pid records 0) reads ALIVE, the same interim-parity/fail-toward-alive stance `liveness.rs` already holds, so a pid-less backend is NEVER falsely declared dead. Recycled-pid caveat, accepted at mint: a reused pid reads alive, so the heal is missed, never mis-fired \u2014 the failure direction is 'stays stale', never 'kills a live instance'. SITE CLASSIFICATION IS PART OF THE REQUIREMENT (authoritative cfg(test)-excluded census at mint = 8 status reads, 3 classes \u2014 do NOT blanket-swap the predicate): (a) DERIVED \u2014 relink's already-online refusal (the gate that made recovery impossible), the `shell cmd` wake-if-offline arm (which silently spooled to a corpse), the drive drop-if-offline branch, `shelldisc::discover` (the single source of BOTH `shell list` renders, text + --json), and the activity fan-out's online filter; (b) RAW status, deliberately \u2014 `bind_shell_by_token`/`close_shell` (the WRITERS) and `cascade_owner_edge`'s suspend-close arm, where routing through the resolver would SKIP the close that is itself the cleanup, removing a heal path; (c) RAW status, pending an operator ruling \u2014 the `shellwake::reconcile_once` watcher-eligibility read: making it liveness-aware would let a dead persistent instance's watcher relaunch the binary spontaneously (~1 tick), which is the correct crash self-heal but the WRONG mid-deploy behavior on Windows, where an operator kills the process precisely to free the exe for overwrite and spt-core would re-lock it under them (flynn's forcing case: shared install dir \u21d2 'kill the process' is a routine deploy step). This req therefore delivers NO spontaneous relaunch: recovery is demand-driven (an unblocked `relink`, or a `shell cmd` that wakes) and `shell list` tells the truth. LOCALITY SYMMETRY (field-caught by flynn's leg-2 run, 2026-07-25): 'a shell cmd that wakes' holds for BOTH the local CLI cmd and the cross-node serve \u2014 at mint the wake arm lived in the SHELL_LINK_CMD serve handler only (module docs scoped wake-if-offline to remote cmd), so a LOCAL cmd against a corpse spooled silently and nothing woke, the exact accepted-happily/drained-by-nobody shape this req exists to kill; the arm is now the shared `linkhost::wake_if_offline_persistent`, called by both, and the derived status READ inside it is the same single census site as before (the census of 8 reads/3 classes is unchanged \u2014 the read moved, it did not multiply). ROTATION CARRIES THE DURABLE CHANNEL (flynn's frame-loss field catch, 2026-07-25, both trials): a spooled command frame is MAC-stamped under the link token current at SPOOL time, and the drain is a raw destructive passthrough \u2014 the shell verifies against its CURRENT key \u2014 so a relink's token mint orphaned every pending frame: drained to the woken binary, failed verify, correctly discarded BY THE ADAPTER, lost permanently; the wake-triggering command itself was the frame the wake lost (#23 armed the wake and never answered; #24/#19 spooled after rotation and drained in order). The fix is three-layered (todlando's A1/A2 race+crash hazards addressed at mint): (1) the token mint re-stamps all pending rows old-key\u2192new-key (idempotent, crash-partial converges \u2014 an old-stamped remainder is converted by the next rotation or drain); (2) the rotated-out token is STASHED (`link.token.retired`, never a live credential \u2014 bind resolves only the parked file) so (3) the drain paths give any race straggler a second-chance re-stamp at delivery (`restamp_for_drain`), while frames verifying under neither key pass through untouched for the adapter to refuse exactly as before. Rows are selected by MAC verification against the threaded new key, never by token-snapshot equality (the D-2 class). MOCK CONFORMANCE IS A PROPERTY OF THE RIG, not a patch (todlando A4): mock-shell MUST verify inbound frame MACs exactly as the public contract demands of a real adapter and drop failures loudly \u2014 a mock that accepts what the field rejects is a broken rig, and that exact divergence (credulous mock) is how the frame loss passed the e2e while failing in the field. The adapter-side discard behavior is CORRECT and must never be softened to paper over the spool side. Relink additionally probes LOCALLY rather than trusting a daemon sweep, so recovery holds with the daemon down. RECOVERY PRESERVES CONSUMER STATE (the property consumers actually depend on, flynn 2026-07-25): 'same canonical id, same perch' exists so that state a consumer PERSISTED IN THE PERCH survives the recovery \u2014 an adapter's repo binding, a scanner's cursor. The teardown+spawn workaround destroyed exactly that, and its worst cost was SILENT, not the rename: alchemy's tag cursor re-baselines at the digest tip, so tags written between the kill and the re-bind are never scanned \u2014 not failed-and-retried, just never seen. A loud failure gets retried; a silent one does not. Gate: int \u2014 force-kill a bound persistent instance's process, then prove (1) `shell list` reads offline, (2) `relink` succeeds instead of SHELL_ALREADY_ONLINE and the SAME canonical id + perch survive, carrying perch-persisted consumer state with them (no teardown+spawn, no id churn, no re-baselined cursor), (3) no spontaneous relaunch occurs while the instance sits dead and undriven. FIELD VERDICT \u2014 PASS END-TO-END (flynn, alchemy-0, delivered 2026-07-26; v0.43.0, counter 77): every gate leg held in the field, on a record the pre-fix code had already poisoned. (1) `shell list` read OFFLINE while the on-disk info.json still said status=online \u2014 the daemon DERIVED offline from the corpse pid (29036 absent from the process table); the record was never corrected and did not need to be. (2) relink ADMITTED, no SHELL_ALREADY_ONLINE \u2014 verbatim SHELL_RELINKED:alchemy-0 owner=flynn pid=38644 status=offline, list online thereafter. (3) identity/state integrity: same canonical id, same owner, same perch; repo binding byte-identical (token_provenance=gh-cli); armed=true preserved; no spontaneous relaunch across the ~6.5h dead window. CURSOR SCOPING, ruled at closure (doyle 2026-07-26) \u2014 the one non-byte-identical field: the tag cursor moved 206158430541\u2192210453397553 (gen:seq 48:333\u219249:49) because the recovery crossed a DAEMON restart that slid the retained digest window past the armed cursor. NOT a gap and NOT this req's property failing: spt-core raised after_predates_window and the consumer took its specced armed-cursor branch (alchemy REQ-TAG-SCANNER missed-rows leg \u2014 warn the owner, never silently re-baseline an armed cursor; alchemy src/tags.rs, two unit tests) \u2014 the FIRST FIELD FIRING of a path previously exercised only by unit tests. The 'no re-baselined cursor' gate property is hereby scoped: byte-identity holds across SHELL death within one daemon generation (the prior LEG 5 measurement, one daemon instance, uncontradicted \u2014 different scenario, generation never moved); across a daemon restart that slides the window the contract is DETECT-AND-ANNOUNCE, not preserve \u2014 corroborated independently by a second record's cursor decomposing under the same gen:seq scheme (4294967486 = 1:190). Practical exposure in this event: nil (every agent stood down through the gap; no tag traffic to miss). SECOND SPECIMEN HELD, not spent (doyle ruling 2026-07-26): liam's record \u2014 precondition verified by flynn (pid 35464 a CLEAN corpse, not recycled, so the accepted-at-mint recycled-pid caveat does not apply; same poisoned status=online shape; binding intact; cursor armed at 4294967486) \u2014 is deliberately NOT relinked while its owner is absent: the admit gate is already field-proven, a relink measurably re-baselines the cursor across a daemon-restart boundary, and liam's is the only untouched specimen with the longer (~8.5h) gap \u2014 spending it would destroy the cursor datum liam's own return produces, to buy a redundant second sample of a gate already proven to admit. It waits for liam. SPECIMEN HARVESTED (liam's own return, 2026-07-26 \u2014 operator-authorized endpoint start executed by doyle; pre-state snapshotted by doyle AND independently re-verified on disk by liam before touching anything): (1) relink ADMITTED at the ~8.5h gap \u2014 verbatim SHELL_RELINKED:alchemy-0 owner=liam pid=47608 status=offline (online at bind); the poisoned record did not refuse; info.json online is now TRUE. (2) integrity FULL PASS: id unchanged (no new mint slot), perch onlined at bind, repo binding byte-identical (token_provenance gh-cli), and the ROTATION-CARRIES-THE-DURABLE-CHANNEL machinery observed live in the field for the first time \u2014 link.token.retired present beside the fresh mint, and a post-relink round trip proven end-to-end (shell cmd spooled -> engine executed -> reply home on the owner's perch), not merely an outbound spool. (3) cursor, the sharpest datum of the harvest: BYTE-IDENTICAL through the relink itself (4294967486 armed at pid-change instant, doyle's file watch) \u2014 the relink preserves; the SCANNER moves. First scan re-baselined 1:190 -> 2:0 silently, and liam PROVED no-fire-by-precondition rather than assuming: the daemon restart bumped the generation but EVICTED NOTHING (window floor 4294967464 < cursor 4294967486, every gen-1 row past the cursor still retained), so after_predates_window is correctly false at every link of the chain (filter_after floor test -> emit-only-when-true -> unwrap_or(false)) and the missed-rows announce had no precondition to fire on. Flynn's earlier gen-bump reading is refined by this: a restart alone does not slide the window; flynn's announce fired because HIS window had genuinely slid. Scope note carried: a valid negative \u2014 the fix's announce chain verified link-by-link to contract on a specimen where it correctly stayed quiet. RESIDUE from the harvest (liam's find, alchemy lane, no core REQ): permanently-unsealable turns from a dark session are jumped by scanner cursor advance with no possible missed-rows notice (a generation bump is not a slide); ruled scanner-side announce for input-bearing null-seq jumps, pseudo-turns jump silently by design; core's per-turn input/input_seq/partial already suffice to discriminate.",
    "doc_a": "Infrastructure endpoints (a gateway the phone treats as always-there) should not need hands-on bringup after a box reboot or daemon restart. `spt endpoint run \u2026 --save` persists the run \u2014 endpoint id, adapter option, and working directory \u2014 as a **startup default** in `daemon.json`; the daemon **replays every saved default when it starts, as a fresh session with the adapter re-resolved at replay time. One entry per endpoint id (a re-save replaces the prior one); remove the entry from `daemon.json`'s `startup_endpoints` to stop auto-starting it.",
    "doc_b": "| # | Invariant | spt-core surface | |---|---|---| | 1.1 | Grace wait precedes INIT_SIGNOFF | daemon teardown | | 1.4/4.4 | Deferred rows excluded from event-stream drain | daemon spool drain | | 2.1/5.1 | Stable PID/broker-handle over ephemeral PID | liveness detection | | 2.3 | Handoff argv/IPC version-tolerant (newer brain \u2194 older broker) | broker\u2194brain IPC, self-update | | 2.4 | gen_start = now() on cold-start + handoff | per-instance generation | | 2.6 | A shell's ONLINE-ness is DERIVED (recorded status AND a not-provably-dead `shell.pid`) \u2014 an abruptly-killed binary breaks no link, so `c",
    "score": null,
    "reasoning": null
  },
  {
    "index": 40,
    "req_a": "REQ-ENDPOINT-CYCLE-HONEST",
    "req_b": "REQ-SPAWN-COLLISION-GUARD-LIVE-DUP",
    "title_a": "REGISTRY-LIFECYCLE W3 (ADR-0041 decision 6, operator deployah stop/run wedge): cycle verbs share ONE liveness authority \u2014 the ALREADY_LIVE dup-guard liveness-probes the claimed session client tree before refusing (dead tree means reap + respawn honestly, never a refusal citing a zombie); the shutdown state machine consults the same source so is-it-live has one answer (no ALREADY_LIVE / list-OFFLINE / shutdown-NO_EDGE three-way contradiction on the same endpoint). Gate: impl \u2014 probing dup-guard + unified authority; unit \u2014 dead-tree claim probes and reaps, live claim still refuses; int \u2014 controlled zombie (killed client tree, surviving hosted record) leads to endpoint run succeeding honestly end-to-end; doc \u2014 ADR-0041.",
    "title_b": "W4 (LIFECYCLE-TRUTH): single-flight wake per endpoint \u2014 the WAKE/RESUME respawn seam must not launch twice for one wake. ROOT (perri parentage + recovered filing): one wake processed TWICE within 1s \u2014 broker (306368) spawned two identical `launch --cli ccs --id flynn --resume <sid>` 1s apart, both survived; check-then-spawn TOCTOU in the spawn-side guard. DAMAGE: duplicate-perch writers STOMP info.json (the duplicate's compact re-stamped an OLD sid over a fresh /clear rotation -> injects routed to the contended record and lost). FIX: single-flight wake per endpoint (claim on the perch record or broker-side in-flight set keyed by id; second wake within the window = no-op ack), and the spawn path re-checks liveness UNDER the claim. Int: two concurrent wake requests -> exactly one launch tree.",
    "doc_a": "1. **Online is earned, not declared.** A creator may stamp `status=online` only from actual persisted state + hosting authority \u2014 never from manifest capability alone. Legacy hybrid rows self-heal at reconcile, but only after a SUCCESSFUL broker query: a broker failure is never interpreted as an empty session set (no mass-offline on a hiccup). 2. **Control cleanup splits from offline classification.** Reconcile clears `controlled`/`driven_by`/`viewer_count` for EVERY endpoint absent from session truth \u2014 regardless of state or controllability \u2014 while offline classification keeps its narrow gate",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 41,
    "req_a": "REQ-ENDPOINT-LIST-PALETTE",
    "req_b": "REQ-ENDPOINT-LIST-PROJECT-COL",
    "title_a": "Bugs #11 + #15 (display): spt endpoint list renders status as plain text while the picker turns the same ResourceRow into the W5 colored EpDisplay palette. Fix: extract one shared ResourceRow-to-EpDisplay builder + make the picker display enums/helpers public, and have endpoint list render the same colored status squares (via helpfmt stdout_color, not ratatui Span). This also fixes #15 \u2014 a lone warm detached instance renders as its online flavor (Dormant maps to online) instead of leaking the bare word Dormant through the text-only list (no resting.rs/CONTEXT model change; operator ruling display-only). Couples REQ-PICKER-NODE-GROUPING (both edit subnet_rows \u2014 sequence the shared-builder extraction first). See docs/NEXT-MILESTONE-BUG-TRIAGE.md #11/#15.",
    "title_b": "#8: spt endpoint list gains a second column <project>/ (the endpoint's LATEST project) -> 4 columns total: id / <project>/ / type / status. Local rows: head of REQ-PICKER-PROJECT-HISTORY-TRUTH (sessions.log-derived, owlery-excluded). Remote rows: head of REQ-GOSSIP-ADAPTER-PROJECTS recent_projects. Project IDs only + #4 disambiguation; '-' when unknown (pre-field remote rows). Extends the v0.21.0 node-grouped renderer (format_instance_rows \u2014 additive column, alignment char-width-safe). --json: additive project field on the row DTO (skip-if-none, N-1 safe). Depends on #1 + #4. See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #8.",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 42,
    "req_a": "REQ-ENDPOINT-LIST-PALETTE",
    "req_b": "REQ-TRANSLATE-BINARY-LIVENESS-DECAY",
    "title_a": "Bugs #11 + #15 (display): spt endpoint list renders status as plain text while the picker turns the same ResourceRow into the W5 colored EpDisplay palette. Fix: extract one shared ResourceRow-to-EpDisplay builder + make the picker display enums/helpers public, and have endpoint list render the same colored status squares (via helpfmt stdout_color, not ratatui Span). This also fixes #15 \u2014 a lone warm detached instance renders as its online flavor (Dormant maps to online) instead of leaking the bare word Dormant through the text-only list (no resting.rs/CONTEXT model change; operator ruling display-only). Couples REQ-PICKER-NODE-GROUPING (both edit subnet_rows \u2014 sequence the shared-builder extraction first). See docs/NEXT-MILESTONE-BUG-TRIAGE.md #11/#15.",
    "title_b": "SUPERSEDED by REQ-TRANSLATE-COMMIT-MISS-TOLERANCE (F029 C-1). B6 (F028, perri F-e) was ROOT-PINNED as the commit-deadline-miss fault: at a checkpoint clear boundary the clear-only inject's {commit} was never observed within INJECT_COMMIT_DEADLINE, so the inject worker FAULTED + TERMINATED a HEALTHY translate binary and (by ADR-0022) never respawned \u2192 every subsequent force-native reported delivered=false ('no live translation binary'). NOT a dormancy/liveness-registration decay (that hypothesis is dead) \u2014 deterministic at every checkpoint-armed boundary. The fix (miss != fault + N=3 strike budget + bounded respawn + perch-visible fault stamp) lives under REQ-TRANSLATE-COMMIT-MISS-TOLERANCE + REQ-HAZARD-TRANSLATE-FAULT-PERMANENT-DEATH. See triage addendum C-1.",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 43,
    "req_a": "REQ-ENDPOINT-LIST-RENDER-POLISH",
    "req_b": "REQ-PICKER-3",
    "title_a": "A6 (F028, operator, 4 asks): `spt endpoint list` render polish. (a) the 'Shared subnets' line is NOT dim \u2014 LIGHT_GRAY = \"37\" (cli.rs:3019) is standard-palette WHITE, indistinguishable from row text; use SGR 90 (bright-black/gray) for the dim intent. (b) the `Total:` line takes the same dim color. (c) move the status glyph ADJACENT to the endpoint name (operator: 'right behind the endpoint name'), mirroring the picker's glyph-beside-name presentation (today the glyph sits at the end next to the status word). (d) color the status WORD like the picker TUI (green ONLINE / gray OFFLINE / blue when driven, matching picker glyph semantics). All in render_node_grouped/render_instance_row (cli.rs ~3000s); pure render with an injected color decision \u2014 unit-testable off a tty. See triage A6.",
    "title_b": "A self-owned subnet row reconciles its status to the LIVE roster: a Subnet-category row whose endpoint_id overlaps a local (is_local) roster id is self-owned (this node hosts it), so its status square is OVERRIDDEN with the live roster status \u2014 the WAN registry snapshot (wansend::load_snapshots) is a periodically-advertised, independently-stale projection, while the local roster (p.alive) is ground truth for an endpoint this node hosts. One status square per endpoint (CONTEXT.md:348-350 \u2014 nothing licenses opposite squares for one endpoint across its Local vs Subnet listings). A reconcile pass in data.rs after the local_rows + subnet_rows gather; BOTH category listings are preserved (Local + Subnet are legitimately distinct views \u2014 you are in your own subnet), only the STATUS is unified. (v0.10.0)",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 44,
    "req_a": "REQ-ENDPOINT-MESSAGE-ONLY-DISPLAY",
    "req_b": "REQ-INST-11",
    "title_a": "An online agent-family endpoint with NO session surface reads as message-reachable, not as a harness-hosted live agent and not as a plain ONLINE. (hertz v0.39.0 field report 2026-07-21; doyle PRODUCT RULING \u2014 deliberately NEITHER of the two options offered.) OBSERVED: an adapterless `spt ready` perch (type=ready_agent, adapter=null, ready/alive true) displays plain ONLINE with '(unknown adapter)'. SOURCE: picker/model.rs display_status returns Online for every non-live_agent type (~680-681) BEFORE consulting controllable, and amber HarnessOnly is live-agent-only (~683-687). RULING: that type gate is CORRECT and STAYS. 'ONLINE - HARNESS ONLY' means one specific thing \u2014 a LIVE AGENT whose session surface is owned by a harness rather than a broker PTY. Broadening it to 'online non-controllable agent-family' would make one label mean two different things, which is how a status label starts lying. An adapterless ready receiver is a THIRD truth: message-reachable, no session surface at all, nothing to attach to ever. So: a DISTINCT display state (working name 'ONLINE - MESSAGE ONLY') keyed on the endpoint TYPE (ready_agent), never on absence-of-adapter, and never an invented adapter name. SECOND RULING (same surface, separate lie): '(unknown adapter)' is itself a small diagnostic untruth \u2014 adapter=null is ABSENT, deliberately so, not unknown; the copy must say absent. Gate: impl \u2014 the distinct display state + the absent-adapter copy; unit \u2014 the display table gains the ready_agent row and the existing live_agent/gateway rows are UNCHANGED (this must not perturb the HarnessOnly gate), plus a label assertion for the new state.",
    "title_b": "spt rename <id> rippled to all instances (collision-checked, 6.5-reconciled)",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 45,
    "req_a": "REQ-ENDPOINT-ONLINE-TRUTH",
    "req_b": "REQ-PICKER-CONTROL-LINE-STATUS-GATE",
    "title_a": "REGISTRY-LIFECYCLE W2 (ADR-0041 decisions 1+2, emphasys C1 P0): ONLINE is earned, not declared \u2014 cmd_listen stamps status=online only from actual persisted state + hosting authority, never from manifest psyche_init capability alone (no more ready_agent/controllable=false hybrid rows born online-authoritative); livehost reconcile SPLITS control cleanup (clear controlled/driven_by/viewer_count for EVERY endpoint absent from session truth, regardless of state/controllable) from offline classification (live_agent+controllable gate unchanged); legacy hybrid rows self-heal after a SUCCESSFUL broker query only (broker failure is never interpreted as an empty session set); terminal signoff/owner-loss = atomic CAS-guarded offline + ready/address removal WITHOUT overloading soft api session-end (/clear preserves the live listener). Gate: impl \u2014 creator gate + reconcile split + self-heal + terminal path; unit \u2014 creator refuses capability-only online, cleanup clears stamps on state-quirk rows, broker-failure never mass-offlines; int \u2014 dead-PID hybrid row does NOT survive a reconcile cycle (the immortal-row regression); doc \u2014 ADR-0041.",
    "title_b": "A2 (F028, operator #2): the picker confirm-panel 'controlled locally' line renders for OFFLINE endpoints. view.rs:425-436 builds control_line from ep.controlled with NO status gate; an offline endpoint with a stale controlled stamp shows 'controlled locally' (operator screenshot: hall-a offline + controlled locally). RENDER HALF (this REQ): control_line MUST be empty when status != Online. The upstream STICKY-stamp half (stamp survives client SIGKILL >=5min) is B3/REQ-PRESENCE-CONTROL-REAP-ON-EXIT. FIX: gate the render. See triage A2(a).",
    "doc_a": "1. **Online is earned, not declared.** A creator may stamp `status=online` only from actual persisted state + hosting authority \u2014 never from manifest capability alone. Legacy hybrid rows self-heal at reconcile, but only after a SUCCESSFUL broker query: a broker failure is never interpreted as an empty session set (no mass-offline on a hiccup). 2. **Control cleanup splits from offline classification.** Reconcile clears `controlled`/`driven_by`/`viewer_count` for EVERY endpoint absent from session truth \u2014 regardless of state or controllability \u2014 while offline classification keeps its narrow gate",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 46,
    "req_a": "REQ-ENDPOINT-STOP-OFFLINE",
    "req_b": "REQ-PUMP-PEER-ISOLATION",
    "title_a": "H3: `spt endpoint stop <id>` 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 \u2014 folds with B2 (same setter). Unit: stop \u2192 is_perch_alive=false / alive=false. (v0.12.0)",
    "title_b": "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 -> peer N+1 waits behind peer N); (2) WHOLE-ROUND POISON (peer_outcome(...)? -- one TimedOut aborts the ENTIRE round via ? -> 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 advances every round; no PEER_PUMP_RESTART from a dead peer. Depends on W1 (a fast-failing dial is the precondition for clean per-peer scheduling).",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 47,
    "req_a": "REQ-ENDPOINT-TEARDOWN-AUTHORITY",
    "req_b": "REQ-HAZARD-ENDPOINT-LIFECYCLE",
    "title_a": "TEARDOWN-AUTHORITY W1 (ADR-0045; two hertz field RCAs 2026-07-19, doyle code-verified + ruled): ONE shared topology-aware broker-teardown primitive behind BOTH `endpoint shutdown` and `endpoint stop`. Today both verbs stamp state they never cause: cmd_shutdown (cli.rs:3979) = remove ready marker + cmd_rest(Suspend) \u2014 the Suspend edge (resting.rs daemon_rest_event_with_liveness -> apply_event -> cascade_shells_on_edge -> advertise) fires echo + shell cascade + advertise and NEVER touches a session, even though the verb already probed broker-session truth (cli.rs:3797-3804 has_live_session_honest) to force from=alive; cmd_stop (cli.rs:7071) = marker + unregister_address + terminal_normalize + advertise, whose own comment calls it a DEFINITIVE death observation it fabricates. Field: broker retains the whole subtree (adapter -> node -> harness -> nested resumed harness + MCP children; the surviving `spt api listen` is a DESCENDANT, so a direct-child kill misses it). PRIMITIVE (ordered, ADR-0045 decisions 1/6/7/9): resolve broker SessionInfo -> dedicated sid/endpoint-keyed broker kill claiming NO controller (NOT Brain::attach()+kill_session(), brain.rs:622 require_session() = controller theft under the ADR-0044 lease ladder; idempotent \u2014 unknown session is benign success) -> reap the whole DESCENDANT subtree (reuse spt_store::proc.rs kill-root+process_descendants, do not mint a second tree-walk) -> await broker-row removal under a bound -> ONLY THEN stamp + unregister + advertise. Stamp-after-reap is load-bearing: field-observed post-stop info.json read status=offline BUT rest_state=active + controlled=true because the SURVIVING host RE-BOUND after the CAS-less terminal_normalize(path, None) \u2014 no hardening of the write fixes this, only the reap makes the stamp true (shutdown passes Some(sid) for the concurrent-newer-bind CAS; stop's unconditional posture stays, it is the operator's own definitive command). TOPOLOGY SPLIT (decision 2): gated controllable==Some(true) (ADR-0041 hosting authority \u2014 the SAME predicate cmd_bind's online-earn and the livehost reconcile use); harness-hosted/external keeps marker+address+status-only behavior (CONTEXT.md:39 \u2014 core spawned nothing, holds nothing) and the misleading cmd_shutdown doc comment claiming marker-removal stops the listener is corrected to name its topology. VERB DIFFERENCES ARE ONLY ceremony + resulting intent (decision 3): shutdown = echo commune + shell cascade FIRST then teardown, result rest_state=suspended (wrap the EXISTING rest edge \u2014 apply_event already echoes before the flip per KH 3.3; do NOT reimplement the ordering); stop = NO ceremony, result terminal_normalize. TIMEOUT (decision 7): do NOT stamp cold over a survivor \u2014 exit non-zero, and because stop is the last rung with no in-band escalation behind it its failure line names the surviving ROOT PID + the scoped-kill remedy. reconcile_hosted_liveness stays the partial-failure catch-up net, not duplicated. Precedent: REQ-HAZARD-DAEMON-STOP-REAP already ruled this reap principle at DAEMON scope; this is the ENDPOINT scope it was never extended to. Gate: doc \u2014 ADR-0045 + CONTEXT.md amendments (190 record-vs-process axis, 640 verb semantics, 647 stop echo exception) + KNOWN-HAZARDS entry; impl \u2014 the shared primitive + both verb call sites + topology gate + corrected comments; unit \u2014 topology routing table (controllable=true -> teardown, harness-hosted -> marker/address/status only), verb-difference table (ceremony + resulting intent), timeout posture (no cold stamp on survivor, failure line carries root pid), REWRITE shutdown_soft_stops_and_suspends (it asserts marker+intent only = it asserts the bug); int \u2014 real broker-hosted endpoint: after each verb, no broker session row + process subtree gone INCLUDING the descendant `spt api listen` + perch ready=false/alive=false/address unregistered/unbound=false + effective and advertised state correct + (shutdown) echo/cascade ran BEFORE the kill + wake/resume works from the recorded sid under the recorded adapter.",
    "title_b": "REGISTRY-LIFECYCLE W2 (KNOWN-HAZARDS 7.45 \u2014 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 \u2014 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 \u2014 the matrix; doc \u2014 KNOWN-HAZARDS 7.45.",
    "doc_a": "7.49 A teardown verb never stamps a terminal or resting state it has not caused \u2014 and two individually-correct verbs must not compose into a lifecycle dead end `[REQ-HAZARD-TEARDOWN-DEADEND]` Failure (paid-for, two hertz field RCAs 2026-07-19, both doyle code-verified the same day; the second hit doyle's OWN live production endpoint):** `endpoint shutdown` reported `Active -> Suspended` and `endpoint stop` reported `STOPPED` while the broker session, its harness child, and a descendant `spt api listen` all kept running \u2014 the verbs only removed the ready marker, ran the Suspend rest edge / unre",
    "doc_b": "7.45 Endpoint lifecycle state converges to truth from every death path \u2014 no optimistic online without authority, no surviving control stamps, no immortal wake intent, no untruthful create `[REQ-HAZARD-ENDPOINT-LIFECYCLE]` Failure (paid-for, three hertz reports + operator field 2026-07-16):** four families, one root shape \u2014 lifecycle state written by multiple non-converging paths, optimistic stamps never verified. (a) `cmd_listen` stamped `status=online` from manifest capability alone \u2192 dead-PID hybrid rows survived EVERY restart (reconcile skipped them by state, cleanup gated on `controllable=",
    "score": null,
    "reasoning": null
  },
  {
    "index": 48,
    "req_a": "REQ-EP-1",
    "req_b": "REQ-ER-RESERVED-ENDPOINT",
    "title_a": "Day-one endpoint types; open type system",
    "title_b": "A node has exactly ONE engine-room endpoint, and 'exactly one' is STRUCTURAL rather than policed: the engine room lives at a reserved per-node endpoint id, so a second one cannot be created any more than a directory can hold two entries of the same name (ADR-0052 decision 1). It is an ordinary agent endpoint in substrate \u2014 harness-adapter-backed, spt-hosted, minded, so it can be briefed on and reason about the node's access posture \u2014 and an extraordinary one in lifecycle: its home subnet and its bound harness adapter are settable ONLY through the create/reset ceremony (REQ-ER-PURGE-RESETS), never by an ordinary endpoint edit. Creation IS that ceremony run against an empty record \u2014 one code path, so a creation that skipped a lock a reset applies cannot exist. Gate: doc \u2014 ADR-0052 decision 1 and the CONTEXT.md engine-room entry; impl \u2014 the reserved id, the engine-room record with its home subnet and bound adapter, and the single create/reset code path; unit \u2014 the reserved id resolves to at most one record, creation and reset run the same path, and an ordinary endpoint mutation cannot change the bound adapter or home subnet.",
    "doc_a": "",
    "doc_b": "A locked-down **agent endpoint** (harness-adapter-backed, spt-hosted, has a mind \u2014 it can be briefed on and reason about the node's access posture). It is the designated way to set the **node's** control-surface modes.",
    "score": null,
    "reasoning": null
  },
  {
    "index": 49,
    "req_a": "REQ-EP-7",
    "req_b": "REQ-RESUME-CONTEXT-PULL",
    "title_a": "Durable live-role.md: a per-agent broad-purpose statement in tracked/agents/<id>/ beside live-context.md (replicates with the mind on the same a-<id> branch); renders FIRST at start-transition context injection (role -> live-context -> project-context); SOLE writer `spt endpoint role --overwrite <file>` \u2014 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)",
    "title_b": "Adapter-callable resume-context pull verb + not-yet-synthesized commune/signoff drop append (legacy-SPT parity, operator-directed 2026-06-24). GAP: spt-core exposes NO verb for a harness adapter's SessionStart hook to pull an agent's resume context \u2014 `resume::download_psyche_context` (spt-live/src/resume.rs:88, composes <live-role>+<live-context>+<project-context> from the durable two-tier store) is INTERNAL with ZERO spt callers and no ApiCmd verb (api/mod.rs ApiCmd enum has none); resume.rs:9 documents the intended 'adapter pulls it in its SessionStart hook' path but it was NEVER wired. Result: a harness adapter cannot inject the agent's durable mind on resume at all (claude-spt today runs only `api boundary` session-rotation + an identity brief \u2014 the agent resumes WITHOUT its mind). TIER-1 SCOPE (operator-approved; Tier-2 = drift-stamp/<current>/drift-directive + <memformat> + Pulse-Log DEFERRED to a separate parity item, NOT v0.15.0 \u2014 the legacy download_payload [claude_skill_owl context.rs:344] is richer but memformat is roadmap-deferred + drift-stamp is an orthogonal cross-machine-drift feature). TWO PARTS: (1) EXPOSE `spt api psyche-download <id> [--session-id <sid>]` -> stdout = the composed brief, project_id resolved from the endpoint's bound cwd (info::read_info -> cwd -> project derive; NO --project arg), auth-gated like sibling id-scoped verbs (the `gated(&id,&auth,\u2026)` pattern); empty store -> NO-CONTEXT on stderr (mirror legacy). The adapter SessionStart hook runs it + injects stdout as additionalContext. (2) APPEND any commune/signoff drop NOT YET SYNTHESIZED into the durable tiers as a distinct <pending-commune>/<pending-signoff> slice AFTER the durable slices. GATING (operator ruling): append while NOT-YET-SYNTHESIZED, NOT merely 'while the raw file is on disk' \u2014 in today's synchronous ingest (ingest_drops route_two_slice writes durable THEN deletes the file, lifecycle.rs:466 @ DEFAULT_PULSE_PERIOD 5s) the two coincide (a watched-dir drop IS pre-synthesis), so the v1 realization reads the manifest-declared session.commune_dir/signoff_dir (manifest.rs:208/210) for a present <id>-commune.md/<id>-signoff.md (COMMUNE_SUFFIX/SIGNOFF_SUFFIX, ingest.rs); the CONTRACT keys on synthesis-state so it stays correct when async Psyche synthesis lands (a consumed-but-not-yet-committed drop stays appended via a pending-synthesis staging set \u2014 forward hook). The agent-checkpoint trigger sentinel CHECKPOINT_SENTINEL=`!!checkpoint!!` (a FIXED spt-core constant \u2014 operator-specified, CONTEXT.md \u00a7fixed-constants, NOT adapter-configurable) is stripped at BOTH drop-body points via one shared `strip_checkpoint_markers` (remove every token, keep inter-marker text, collapse trivial whitespace): the PRE-synthesis pending-append (resume::append_pending) AND the POST-synthesis durable ingest (ingest::route_slices \u2014 the single choke covering route_two_slice + signoff.write_resume_commune; strip-then-empty-filter so a marker-only slice routes nowhere) \u2014 else the marker would persist PERMANENTLY in live-context.md once a checkpoint drop synthesizes + re-trigger once the adapter's checkpoint detection is live. PRESENTATION-ONLY: the append NEVER writes the durable store (spt-core remains sole store-writer, REQ-HAZARD-DROP-FILE-SINGLE-WRITER; mirror legacy's read-only/process_file_drop-sole-deleter discipline). SELF-CLEARING: once synthesis commits the <pending-*> slice vanishes \u2014 no duplication. CORE-OWNED (not adapter): an adapter-side raw-file read RACES spt-core's ingest-delete (TOCTOU, ingest.rs:161 removes the drop on pulse-consume); the fold MUST live in the single composer all resume pulls flow through. New public CLI verb -> docs-drift gate (xtask gen + reference.md no-internal-codes, cli-command-docs-drift). (v0.15.0 parity wave W5)",
    "doc_a": "live role** (`live-role.md`, ratified 2026-06-12 \u2014 core milestone A): A durable statement of an agent's **broad purpose** \u2014 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 \u2014 Psyche reconcile, echo-communes, and signoff structurally never touch it; the sole writer is `spt endpoint role [--overwrite",
    "doc_b": "resume-session seam** \u2014 two distinct forms: fresh-with-preload:** resume with *cleared* context (a fresh session) + psyche-download. Accepts a `$psyche-context` key to launch the fresh session with the psyche-download preloaded \u2014 or the adapter instead pulls it via an spt-core command in its SessionStart hook. <!-- --> That command is **`spt api psyche-download <id> [--session-id <sid>]`**: it emits the durable resume brief (role \u2192 live-context \u2192 project-context, project resolved from the perch's bound cwd) to stdout for the adapter's SessionStart hook to inject as additional context, and APPE",
    "score": null,
    "reasoning": null
  },
  {
    "index": 50,
    "req_a": "REQ-ER-BRINGUP-PROMPT",
    "req_b": "REQ-ER-RULESET-TABLE",
    "title_a": "Bare `spt rc engine-room` prompts interactively for the bring-up TOTP instead of attaching straight into the gate's refusal (ADR-0052 \u00a72 entry UX, fast-follow grill 2026-07-30). The prompt is the preferred entry because argv is readable by other processes on the box while a code is still valid; `--code <CODE>` stays for scripted/non-interactive use with that caveat documented in its help. Prompt rules: Esc-cancel (the ceremony-overlay pattern) and ctrl-c both cancel with NO attach attempt even when the buffer holds digits; Enter submits a non-empty buffer and is inert on an empty one; only ASCII digits accumulate, length-bounded, so pasted garbage never rides into the gate and spends ledger budget; every other endpoint never sees a prompt; and a non-terminal stdin refuses with guidance naming --code rather than blocking on a pipe. Gate: doc \u2014 the ADR-0052 \u00a72 entry-UX note; impl \u2014 the prompt behind the bare invocation, the pure key-fold kernel, and the non-terminal refusal; unit \u2014 the key-fold rules (digits-only accumulation, bound, backspace, inert-empty-Enter, submit, and both cancel paths beating a full buffer).",
    "title_b": "The engine room presents access rulesets as TABLES (ADR-0052 decision 3). The rendering is a requirement rather than a nicety because the operator decision this surface exists to support \u2014 is this node's posture what I think it is \u2014 is a comparison across subjects, surfaces and tiers, and prose forces a human to hold that grid in their head while an agent narrates it to them. A table also makes an omission visible: a row that should be there and is not is legible in a grid and invisible in a paragraph. It rides the same briefing message the session opens with (REQ-ER-SESSION-BRIEFING) and the same renderer serves an on-demand ruleset request. Gate: doc \u2014 ADR-0052 decision 3's table clause; impl \u2014 the ruleset table renderer used by the briefing and by an on-demand request; unit \u2014 the renderer emits one row per rule with subject, surface, tier and decision, renders an empty ruleset as an explicit empty table rather than silence, and is stable enough to diff across two postures.",
    "doc_a": "Entry UX (ruled 2026-07-30, fast-follow grill):** bare `spt rc engine-room` opens an interactive TOTP prompt (Esc-cancel overlay, the releases#19 pattern) `--code <totp>` stays for direct passing, with the documented caveat that argv is readable by other processes on the box while the code is still valid.",
    "doc_b": "Refuses all inbound except replies to its own outbound (knocks and knock-codes ARE accepted); online **only while a controller is attached** \u2014 detach drops it offline and every empowerment dies with it; `rc --view` denied even locally; remote attach denied; **local `rc --take` allowed** precisely because it forces a harness restart and revokes all empowerments; not registry-advertised by default (only to endpoints it has whitelisted); every session start delivers a briefing message stating its capabilities and responsibilities; it presents access rulesets as tables.",
    "score": null,
    "reasoning": null
  },
  {
    "index": 51,
    "req_a": "REQ-ER-CONTROLLER-BOUND-POSTURE",
    "req_b": "REQ-HAZARD-CONTROLLER-RETAKE-FLOOR",
    "title_a": "The engine room is empowered ONLY while a controller is attached, and losing that controller drops its POSTURE \u2014 not its process (ADR-0052 decision 3, as amended 2026-07-29 on todlando's finding). Read literally, 'detach drops it offline' would kill a harness on detach and re-break the attach-lifecycle invariant (KNOWN-HAZARDS: an rc detach ends only the viewport, the harness keeps running and stays re-attachable \u2014 an invariant the broker's PTY ownership satisfies by construction). The buildable and equally strong reading is a posture: on detach the engine room refuses all inbound, drops every empowerment (REQ-SUBNET-EMPOWER-VERB), and stops being advertised, while the process lives and can be re-attached through the same TOTP gate. Empowerment is therefore session state with TWO named drop edges \u2014 detach and local take \u2014 each of which needs its own test; 'revoked by a restart' is not an available argument, because no restart happens (REQ-ER-RC-INTENT-LOCKS). Gate: doc \u2014 the ADR-0052 section 3a amendment; impl \u2014 the posture transition on detach and on take, covering inbound refusal, empowerment drop and de-advertisement, with no process teardown; unit \u2014 both drop edges revoke empowerments, a detached engine room refuses inbound, and neither edge terminates the hosted session.",
    "title_b": "`become_controller` should STRUCTURALLY refuse a controller re-take whose `from_seq` falls below the connection's already-delivered contiguous floor \u2014 making the P1c reorder invariant un-reintroducible by a future caller, not just removed at the one caller. ROOT/SCOPE (doyle proposed, P1c gate dialogue): P1c fixes REQ-HAZARD-CONTROLLER-WRITER-REORDER three ways (handoff single-take + epoch-gate-under-lock + session_cursors seed), removing the one decreasing-floor double-take and bounding any other to already-committed-only. A self-enforcing broker guard would refuse the bad SHAPE outright. BLOCKER: the obvious predicate (`from_seq >= delivered_through`) is UNSAFE because `delivered_through` is SESSION-WIDE (the `Arc<AtomicU64>` on `OutputLog`, shared by all controllers/viewers, advanced monotonic-MAX; `resume_seq` reads it) \u2014 a normal fresh-operator `from_seq=0` attach to a producing session legitimately sits below it (full ring replay + consumer dedup-below/snap-above), and monotonic-MAX can't distinguish the hazard (a `seq1`-without-`seq0` write reads as `2`). The structurally-correct guard needs a NEW per-connection contiguous-sent cursor (the true highest-contiguous seq this socket has received) that does not exist today; the guard then refuses a re-take below THAT. Bigger than P1c; no live gap (P1c fully fixes the actual bug). Mint/refine stages when the per-connection cursor is built. (v0.13.0 follow-up, post-ship)",
    "doc_a": "Decision 3 was written in the language of the ceremony, and two of its clauses do not survive contact with the daemon's lifecycle invariants. Both are amended here rather than built literally.",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 52,
    "req_a": "REQ-ER-RC-INTENT-LOCKS",
    "req_b": "REQ-HAZARD-INJECT-WORKER-POISON",
    "title_a": "The engine room's attach surface is locked by INTENT: rc --view is denied even same-node, remote attach of any intent is denied, and local --take is allowed (ADR-0052 decision 3). Viewing is denied because a read-only spectator on the surface that renders access rulesets is an information leak with no operator benefit \u2014 and because the same-node loopback allow precedes the resolution chain, this must be an explicit engine-room carve-out rather than a rule row that the loopback shortcut would skip past. Local take is allowed on two grounds, neither of them the original 'it forces a harness restart' (it does not \u2014 a take is a broker lease displacement and restarts nothing): the displacing controller must pass the same bring-up gate the incumbent passed, so a take is a gate attempt and is bounded by the same ledger (REQ-ER-BRINGUP-ATTEMPT-BOUND); and the displacement is LOUD, so an incumbent human cannot be silently unseated. Gate: doc \u2014 ADR-0052 decision 3 and the section 3a amendment; impl \u2014 the intent-keyed engine-room carve-out ahead of the chain, the remote-attach refusal, and the take path's gate pass plus empowerment revocation; unit \u2014 same-node view denied, remote attach denied for every intent, local take admitted only after a gate pass, and a take revoking empowerments.",
    "title_b": "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 \u2014 a panic in the inject WORKER kills its thread WITHOUT reaping the translation child, orphaning a live binary while event_tx.send fails, so force-native reports 'worker-gone' delivered=false FOREVER (the F-e generic-miss shape). HARDENING, NOT the F-e incident root (perri's matrix exonerated poison under thrash/dormancy/churn): (i) poison-tolerant floor lock (unwrap_or_else into_inner) at all 3 sites so one panic can't cascade the session's delivery dead; (ii) a panic-resilient inject worker (catch_unwind -> fault+terminate the child on a worker panic) so a dead worker never orphans a live binary + strands delivery. Poison-tolerance class of REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE / bug #16.",
    "doc_a": "Decision 3 was written in the language of the ceremony, and two of its clauses do not survive contact with the daemon's lifecycle invariants. Both are amended here rather than built literally.",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 53,
    "req_a": "REQ-GOSSIP-CONTROLLED-ANY",
    "req_b": "REQ-PRESENCE-CONTROL-REAP-ON-EXIT",
    "title_a": "Bug #3: a locally-controlled endpoint gossips controller_node = None so remote viewers show it free to control. Root: driven_by is stamped Some(node) only for a REMOTE WAN attach (attach.rs:337); a local controller is by=None by design (broker.rs:1750, KH 7.15 \u2014 a local-only controller must not latch driven_by). Fix: broker stamps a SEPARATE any-controller datum (true/Some(host) for a local OR remote controller) alongside stamp_driven_by, and advertise_local gossips Instance controller_node from it, leaving the remote-only driven_by untouched (do not trip REQ-HAZARD-DRIVEN-BY-SELFHEAL). node-refresh is NOT the fix (data is absent at source). See docs/NEXT-MILESTONE-BUG-TRIAGE.md #3.",
    "title_b": "B3 (F028, hall-b diagnosis + deferred #11 seed; BROADENED perri F-b CONFIRMED): dead-pid ONLINE decay window + sticky CONTROLLED stamp. Repro: /exit -> all endpoint processes dead -> `endpoint list` stays \u25a0 ONLINE for a decay window before OFFLINE. Sticky CONTROLLED: perri confirmed controlled=true + attached_node SET while alive=false/OFFLINE, persisting >20min AND ACROSS A DAEMON RESTART (hall-b) \u2014 worse than the SIGKILL>=5min original (CAVEAT still: may reflect claude's --remote-control channel not the PTY attach \u2014 DISAMBIGUATE first). This is the deferred #11; RCA belongs to this wave. FIX: reap must clear presence AND control stamps promptly across FOUR paths \u2014 (i) clean exit, (ii) serve conn-drop, (iii) session-died-without-exit (crash/bounce), (iv) a BOOT-TIME sweep so a restarted daemon does NOT resurrect control stamps for endpoints it can see are dead. Int tests per edge. Closes A2(b). See triage B3 (broadened).",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 54,
    "req_a": "REQ-HAZARD-ADAPTER-APPLY-SILENT-NOOP",
    "req_b": "REQ-RESUME-ADAPTER-FOLLOWS-SESSION",
    "title_a": "A DELEGATED live adapter apply MUST NEVER report success without performing the swap, and the live-update seam MUST use ONE parent-aware adapter matcher across all its comparators. TWO defects made the field repro (BUILD-F015B-APPLYMATCH: `--adapter cc:ccs` live update silently no-ops): (D1, matcher skew) the broker's dispatch_adapter_apply filtered sessions by EXACT `s.adapter == req.adapter`, but a `--adapter <adapter>:<profile>` endpoint stores the COMPOSITE `cc:ccs` while the apply carries the PARENT record name `cc` \u2014 so every :profile endpoint fell out to affected=[]; select_endpoints_running_adapter had the same `adp == adapter` skew, while the CLI live-gate (adapter_has_live_endpoint) already parent-matched \u2014 divergent rules on ONE seam. (D2, silent success) the affected.is_empty() branch replied KIND_APPLIED and RETURNED WITHOUT SWAPPING; once the CLI delegates the apply there is no CLI-side fallback swap, so success-without-swap = the update never lands (re-register re-reads the OLD manifest, version-of-truth honestly says old). FIX: (1) ONE shared spt_runtime::profile::adapter_parent_matches(session_adapter, parent) used by the live-gate + broker apply-filter + select_endpoints_running_adapter (no exact `==` against a record name at any live-update seam); (2) the daemon owns the whole apply once delegated \u2014 the CRC swap runs UNCONDITIONALLY (terminate/restart loops no-op when nothing is resident), KIND_APPLIED reported ONLY after a real swap. (F015B, ADR-0025 amendment)",
    "title_b": "D-2 (REMOTE-TRUTH triage \u00a7D-2 + operator Q5 @c248afc): a resume-from-history restores the RECORDED session adapter (REQ-SESSION-ADAPTER-RECORDED) \u2014 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 \u2014 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 \u2014 adapter immutable, carried by bind's stamp_creation_fields). FIX (doyle fork ruling): ResumeRow (model.rs:199) gains adapter: Option<String> threaded from SessionEntry.adapter in picker/data.rs; the row title (model.rs:228) renders [{adapter}] when Some ({head} [{adapter}] - {time} (\u2026{id5})); resume_outcome bakes the ROW's adapter with an endpoint fallback (row.adapter.unwrap_or(ep.adapter_profile)) \u2014 None \u2192 the endpoint's current stamp (benign degrade). The pre-spawn RE-STAMP + refusal ride the picker resume dispatch (mod.rs:360 Run arm, resume.is_some()) reusing the hazard-guarded mutate_info seam (write_adapter_change/mod.rs:336), NEVER the bind path: order = read current info.adapter \u2192 if the baked adapter DIFFERS (a real replace; a None-row bakes the endpoint's own \u2192 equals current \u2192 NO write) \u2192 registered-check via resolve_option (Err(NotRegistered) \u2192 loud F-1 refusal naming the adapter + `spt adapter add`, NO stamp, NO spawn) \u2192 write_adapter_change re-stamp \u2192 spawn. ONE adapter write path (the mutate_info seam); REQ-HAZARD-ADAPTER-PROFILE-STAMP-CLOBBER's bind/hook path (stamp_creation_fields, home.rs) UNTOUCHED \u2014 both its guard tests stay green as the gate condition. Red-first: a resume row adapter=\"claude-spt\" over an endpoint stamped \"claude-spt:ccs\" \u2192 the baked Outcome.adapter == \"claude-spt\" (the deliberate replace) and the pre-spawn stamp writes it.",
    "doc_a": "7.24 A delegated live adapter apply must NEVER report success without swapping, and the live-update seam must use ONE parent-aware adapter matcher `[REQ-HAZARD-ADAPTER-APPLY-SILENT-NOOP]` Failure (F015B / BUILD-F015B-APPLYMATCH):** a live adapter update to a PROFILE-COMPOSITE endpoint (`--adapter cc:ccs`) silently no-oped \u2014 `LIVE` then `DONE` printed, but the version-of-truth stayed OLD. TWO defects on one seam. **(D1, matcher skew):** the broker's `dispatch_adapter_apply` filtered sessions by EXACT `s.adapter == req.adapter`, but a `--adapter <adapter>:<profile>` endpoint stores the COMPOSITE",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 55,
    "req_a": "REQ-HAZARD-ATTACH-WEDGE",
    "req_b": "REQ-HAZARD-CONFLICT-BOTH-PRESERVED",
    "title_a": "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 \u2192 every new attach / `endpoint run` stalls right after 'PUMP_IPC_READER: spawned' \u2192 30s FIRST_EVENT_GRACE \u2192 '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 \u2014 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-INTERNALLY by the operator row's own read pump (RecvHalf::Loopback, retentive_cap==0 \u2192 evict-not-park) so a dead rc (a dropped IPC subscriber) never backs peer_w up; bounded_block_on parks the BROKER DISPATCH thread, not a net worker \u2192 no worker-pool exhaustion (full mechanism in the required_stages comment). Folds the status=online sub-check: a dead spt-hosted endpoint is marked OFFLINE within one reconcile tick on abrupt child death (broker exit-waiter reaps the session \u2192 B2 sees it absent) \u2014 PROVEN, no change. (v0.12.1)",
    "title_b": "A surfaced concurrent context pair is durably preserved (both versions, tracked artifacts) until a strictly dominating write clears it; no reconcile failure path discards an unmerged version (6.6, ADR-0013)",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 56,
    "req_a": "REQ-HAZARD-ATTACH-WEDGE",
    "req_b": "REQ-SPAWN-COLLISION-GUARD-LIVE-DUP",
    "title_a": "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 \u2192 every new attach / `endpoint run` stalls right after 'PUMP_IPC_READER: spawned' \u2192 30s FIRST_EVENT_GRACE \u2192 '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 \u2014 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-INTERNALLY by the operator row's own read pump (RecvHalf::Loopback, retentive_cap==0 \u2192 evict-not-park) so a dead rc (a dropped IPC subscriber) never backs peer_w up; bounded_block_on parks the BROKER DISPATCH thread, not a net worker \u2192 no worker-pool exhaustion (full mechanism in the required_stages comment). Folds the status=online sub-check: a dead spt-hosted endpoint is marked OFFLINE within one reconcile tick on abrupt child death (broker exit-waiter reaps the session \u2192 B2 sees it absent) \u2014 PROVEN, no change. (v0.12.1)",
    "title_b": "W4 (LIFECYCLE-TRUTH): single-flight wake per endpoint \u2014 the WAKE/RESUME respawn seam must not launch twice for one wake. ROOT (perri parentage + recovered filing): one wake processed TWICE within 1s \u2014 broker (306368) spawned two identical `launch --cli ccs --id flynn --resume <sid>` 1s apart, both survived; check-then-spawn TOCTOU in the spawn-side guard. DAMAGE: duplicate-perch writers STOMP info.json (the duplicate's compact re-stamped an OLD sid over a fresh /clear rotation -> injects routed to the contended record and lost). FIX: single-flight wake per endpoint (claim on the perch record or broker-side in-flight set keyed by id; second wake within the window = no-op ack), and the spawn path re-checks liveness UNDER the claim. Int: two concurrent wake requests -> exactly one launch tree.",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 57,
    "req_a": "REQ-HAZARD-BIND-REST-STATE-CARRY",
    "req_b": "REQ-PRESENCE-CONTROL-REAP-ON-EXIT",
    "title_a": "GATEWAY-LIVENESS DEFECT A (flynn field bug 2026-07-09, confirmed independent): a re-bind MUST preserve the daemon-owned resting intent (rest_state, D9-2/REQ-INST-3) \u2014 the same carry-forward discipline establish_perch already applies to cwd/controllable/adapter/read_env. ROOT: establish_perch's record build (crates/spt/src/api/startup.rs, the build closure) constructs a fresh InfoJson via InfoJson::new (defaults rest_state None) and carries cwd/controllable/read_env forward from prior but NOT rest_state -> a re-bind WIPES the wake intent (flynn tick11 rest_state:active vanish). FIX: carry prior.rest_state (and its paired dormant_since_ms anchor, present iff dormant) forward on re-bind, like the sibling fields. Gate: a re-bind over a prior record with rest_state set preserves it (unit \u2014 the build closure carries rest_state + dormant_since_ms). KNOWN-HAZARDS entry on landing. Kin REQ-HAZARD-BIND-CWD-UNSET / REQ-PICKER-1 + REQ-INST-3.",
    "title_b": "B3 (F028, hall-b diagnosis + deferred #11 seed; BROADENED perri F-b CONFIRMED): dead-pid ONLINE decay window + sticky CONTROLLED stamp. Repro: /exit -> all endpoint processes dead -> `endpoint list` stays \u25a0 ONLINE for a decay window before OFFLINE. Sticky CONTROLLED: perri confirmed controlled=true + attached_node SET while alive=false/OFFLINE, persisting >20min AND ACROSS A DAEMON RESTART (hall-b) \u2014 worse than the SIGKILL>=5min original (CAVEAT still: may reflect claude's --remote-control channel not the PTY attach \u2014 DISAMBIGUATE first). This is the deferred #11; RCA belongs to this wave. FIX: reap must clear presence AND control stamps promptly across FOUR paths \u2014 (i) clean exit, (ii) serve conn-drop, (iii) session-died-without-exit (crash/bounce), (iv) a BOOT-TIME sweep so a restarted daemon does NOT resurrect control stamps for endpoints it can see are dead. Int tests per edge. Closes A2(b). See triage B3 (broadened).",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 58,
    "req_a": "REQ-HAZARD-BOUNDARY-READY-STRAND",
    "req_b": "REQ-SPAWN-COLLISION-GUARD-LIVE-DUP",
    "title_a": "C-2 (F029, SEAM-2 pinned \u2014 B6's SECOND HALF, the live-wake blocker; perri wakep9 vs wakep4 gate-state dump + doyle code trace): at a /clear, CC fires SessionEnd(reason=clear) for the DEPARTING session BEFORE SessionStart; the departing sid STILL matches the perch pin at that instant, so the adapter's [hooks.SessionEnd] \u2192 `api session-end` AUTHENTICATES and the soft handler REMOVES the ready marker (+ unregister_address, reporting.rs cmd_session_end:206-207). The subsequent `api boundary` rotates the sid but NOTHING re-writes ready \u2192 is_online false \u2192 try_spt_hosted_inject Nones on the CLI gate BEFORE any broker RPC \u2192 every post-clear force-native (incl. the checkpoint FIRE) reports the generic UNDELIVERED, persistent by construction (no path re-stamps ready outside a real bind). The single differing gate field at every UNDELIVERED instant is ready-absent (info online/controllable/rotated-sid all healthy, translate alive). Paid-for hazard. FIX: cmd_boundary re-stamps the ready marker (+ status online, idempotent) ATOMICALLY with the sid rotation \u2014 a boundary PROVES a live successor session on the same harness process; a REAL end has no subsequent boundary so genuine teardown is untouched. See triage addendum C-2.",
    "title_b": "W4 (LIFECYCLE-TRUTH): single-flight wake per endpoint \u2014 the WAKE/RESUME respawn seam must not launch twice for one wake. ROOT (perri parentage + recovered filing): one wake processed TWICE within 1s \u2014 broker (306368) spawned two identical `launch --cli ccs --id flynn --resume <sid>` 1s apart, both survived; check-then-spawn TOCTOU in the spawn-side guard. DAMAGE: duplicate-perch writers STOMP info.json (the duplicate's compact re-stamped an OLD sid over a fresh /clear rotation -> injects routed to the contended record and lost). FIX: single-flight wake per endpoint (claim on the perch record or broker-side in-flight set keyed by id; second wake within the window = no-op ack), and the spawn path re-checks liveness UNDER the claim. Int: two concurrent wake requests -> exactly one launch tree.",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 59,
    "req_a": "REQ-HAZARD-BRAIN-RESTART-LIFECYCLE-REHYDRATE",
    "req_b": "REQ-PSYCHE-LEGACY-RESIDENT-SWEEP",
    "title_a": "B4 (deepest): a bare brain restart (broker survives) REHYDRATES the live-agent lifecycle so post-restart endpoints are hosted + attachable. Today resume_sessions (brainproc.rs:186, brain.rs:797-809) re-subscribes to the broker's PTY sessions but ALL BrainLifecycle instances (lifecycle.rs:58-130; the ephemeral brain.rs:254-275) are LOST on restart \u2192 a post-restart live endpoint gets no livehost \u2192 its Psyche is never (re)hosted and new spawns die / can't attach until a FULL daemon reset (operator: perri's brain kill+restart wedged everything until a full daemon kill). FIX: on brain startup, rebuild a BrainLifecycle per resumed live-capable session \u2014 load the manifest from the adapter registry \u2192 instantiate \u2192 start the pulse \u2014 the rehydrate the resume no-op cannot do. Composes with B2 (the reconcile re-hosts from the honest on-disk status after rehydrate). (v0.12.0)",
    "title_b": "W5 (F030; doyle+perri 2026-07-04): a dirty daemon upgrade from <=v0.24.0 strands a RESIDENT psyche wrapper the OLD daemon spawned \u2014 and F-030 W4's nested-`ready` resolution fix CONVERTED that wrapper's accidental self-reap into a permanent HANG. The pre-W3 wrapper's only spt IPC is `spt ready <parent>-psyche --once` (BLOCKING, no internal timeout); pre-W4 that hit READY_FAIL on a multi-subnet home \u2192 the wrapper exit-4'd (accidental reap). Post-W4 the nested id resolves cleanly \u2192 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 \u2014 burying residency-era machinery, not resurrecting it). GUARD = adapter-AGNOSTIC (glue-model): resurrect the retired reap_orphan_psyches LOGIC \u2014 for each self-perch live-agent id derive `<id>-psyche` and kill iff (a) exe basename == the adapter's MANIFEST-declared psyche program (normalize_basename, never a hardcoded adapter name) AND (b) cmdline contains the id marker `<id>-psyche` AND (c) pid alive; any unreadable signal \u2192 DECLINE + loud log (fail-safe-decline, positive-match-only; infra never-kill inside the sweep). FRATRICIDE is closed by TIMING (perri-confirmed from the owning side): the ephemeral shim is daemon-spawned per-event, bounded, exits at turn end \u2014 at brain start BEFORE the first reconcile/pulse no current shim is resident, so any `<id>-psyche` psyche-program process alive then is unambiguously stranded-legacy. RESIDUE (doyle PIN 3): the hung wrapper REGISTERED a `<parent>-psyche` ready perch before blocking; killing the pid alone leaves a phantom ready-record with a dead pid (the REMOTE-TRUTH presence-lie class) \u2014 the sweep MUST also clear that stale registration or prove the existing stale-perch cleanup reaps it. No field window pre-W6 (nothing releases). (F-030 W5)",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 60,
    "req_a": "REQ-HAZARD-BRAIN-RESTART-PSYCHE-DUP",
    "req_b": "REQ-HAZARD-STOP-PATH-PSYCHE-ORPHAN-REAP",
    "title_a": "A bare brain restart leaves EXACTLY ONE `{id}-psyche` process per endpoint \u2014 no duplicate. On an abrupt brain death stop_host never runs (the LiveSet + owned child handles die with the brain) and Breap's job/group only reaps at DAEMON stop, so the PRIOR brain's Psyche stays ALIVE; the respawned brain's reconcile re-hosts a SECOND Psyche and overwrites the `{id}-psyche` perch pid, leaving the old one untracked + alive = a duplicate that lingers until daemon-stop (the operator's 'brain kill+restart wedged everything'). FIX: at brain start, BEFORE the first reconcile re-hosts, reap any pre-existing `{id}-psyche` orphan \u2014 ID-SPECIFICALLY (recycle-safe on the shared box, where sibling agents share the `claude` basename): scoped-kill the recorded pid ONLY IF it is alive AND its exe basename == the adapter's psyche program (normalize_basename) AND its COMMAND LINE contains the full psyche id `<id>-psyche` (baked via {id}); a sibling never carries THIS id, and any unreadable signal FAILS SAFE (decline to reap \u2014 a missed dup is bounded by Breap, a wrong-kill is catastrophic). CAVEAT: the cmdline carries `<id>-psyche` only when the adapter's psyche_init.command uses {id} (the norm); a non-{id} adapter safely MISSES the reap (today's behavior, Breap bounds it) \u2014 never a wrong-kill. (v0.12.0)",
    "title_b": "Endpoint-stop and brain-death reconcile MUST reap a brain-less perch's orphan detached Psyche via the cmdline-scoped guard (`psyche_orphan_should_reap`) \u2014 the handle-reap (`LiveSet::stop_host`, REQ-HAZARD-UNHOST-PSYCHE-REAP) CANNOT, because the owning brain is gone (its `psyche_child` handle died with it), and the brain-start scoped-reap (REQ-HAZARD-BRAIN-RESTART-PSYCHE-DUP) never fires for a perch being STOPPED rather than re-hosted. So the live-host calls the scoped reap after `stop_host` at the reconcile stop-side AND in `confirm_residency_or_unhost`. Preserves fail-safe-decline (pid-alive AND exe-basename==psyche-program AND cmdline contains `<id>-psyche`; any unreadable signal DECLINES \u2014 a missed dup is bounded, a wrong-kill is catastrophic). This is the orphan-leak half of the perri F-010xF-015 field bug (the unsupervised install-dir Psyche that locked an update); the other half is the psyche own-copy (ADR-0025 amendment). (v0.13.2 W3 (a))",
    "doc_a": "",
    "doc_b": "| # | Invariant | spt-core surface | |---|---|---| | 1.1 | Grace wait precedes INIT_SIGNOFF | daemon teardown | | 1.4/4.4 | Deferred rows excluded from event-stream drain | daemon spool drain | | 2.1/5.1 | Stable PID/broker-handle over ephemeral PID | liveness detection | | 2.3 | Handoff argv/IPC version-tolerant (newer brain \u2194 older broker) | broker\u2194brain IPC, self-update | | 2.4 | gen_start = now() on cold-start + handoff | per-instance generation | | 2.6 | A shell's ONLINE-ness is DERIVED (recorded status AND a not-provably-dead `shell.pid`) \u2014 an abruptly-killed binary breaks no link, so `c",
    "score": null,
    "reasoning": null
  },
  {
    "index": 61,
    "req_a": "REQ-HAZARD-BROKER-PROCESS-ISOLATION",
    "req_b": "REQ-HAZARD-RESTART-IDEMPOTENT",
    "title_a": "Broker and brain are separate processes: the broker runs as its own long-lived per-machine process that survives every brain restart, so a routine (brain-only) self-update restarts the brain onto the swapped binary while every hosted endpoint (PTY child, live QUIC conn, listening socket) stays untouched at the PROCESS level. The in-process-thread broker (daemon.rs:165-170) is a regression that silently unrealizes REQ-UPD-3 \u2014 apply degrades to an in-process Brain::handoff no-op and new code does not run until an unrelated restart (KNOWN-HAZARDS 6.7). Evidence must prove process-level survival (SPIKE-01/03 productionized as int: PTY child + live QUIC survive a brain-PROCESS restart onto a swapped binary), re-pointing the regression-masked in-process int tags currently on REQ-DAEMON-2 / REQ-UPD-3 (ADR-0018).",
    "title_b": "Idempotent/exactly-once delivery across brain restart at every broker boundary (codex #14)",
    "doc_a": "6.7 Broker and brain MUST be separate processes (in-process collapse silently breaks no-endpoint-drop update) `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]` Failure:** the daemon hosts the broker as a background *thread* in the single `spt daemon` process (`daemon.rs:165-170`, `Arc<Broker>` + `thread::spawn(serve)`) instead of a separate process. A brain restart onto a swapped binary then cannot happen without killing the broker thread \u2014 closing every PTY, orphaning every harness child, dropping every socket. So `spt update apply` degrades to an in-process `Brain::handoff` no-op: the binary swaps on ",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 62,
    "req_a": "REQ-HAZARD-BROKER-SEED-WIRE-SKEW",
    "req_b": "REQ-RESUME-CONTEXT-PULL",
    "title_a": "A daemon-state wire-format change (e.g. the v0.9.0 adapter-agnostic Seed) does NOT take effect until a DELIBERATE full broker restart: the broker serves the seed-control channel and is RESIDENT across a brain-only self-update (ADR-0004 no-terminate-during-update forbids auto-killing it), so a NEW-version CLI talking to a still-resident OLD broker fails the seed handshake \u2014 the old broker cannot deserialize the new Seed (its formerly-required `adapter` field is gone) and drops the conn without an ack, which surfaces to the CLI as a raw UnexpectedEof 'failed to fill whole buffer'. spt-core must (a) surface an ACTIONABLE diagnostic on that seed-ack EOF (name the stale-broker cause + the `spt daemon stop` fix \u2014 the broker restarts on the next api call), never the cryptic io error; and (b) document the operational rule (a deliberate broker restart is required on any daemon-state wire change \u2014 NOT automatic) + the FORWARD discipline (daemon-state/Seed schema changes stay additive + serde-default so a resident OLD broker tolerates a NEW CLI across a brain-only update; note this would NOT have rescued 0.9.0 itself, since the old broker's `adapter` was a required field). perri PREP-4 FINDING 1 (v0.9.0 CLI vs stale 0.8.x broker).",
    "title_b": "Adapter-callable resume-context pull verb + not-yet-synthesized commune/signoff drop append (legacy-SPT parity, operator-directed 2026-06-24). GAP: spt-core exposes NO verb for a harness adapter's SessionStart hook to pull an agent's resume context \u2014 `resume::download_psyche_context` (spt-live/src/resume.rs:88, composes <live-role>+<live-context>+<project-context> from the durable two-tier store) is INTERNAL with ZERO spt callers and no ApiCmd verb (api/mod.rs ApiCmd enum has none); resume.rs:9 documents the intended 'adapter pulls it in its SessionStart hook' path but it was NEVER wired. Result: a harness adapter cannot inject the agent's durable mind on resume at all (claude-spt today runs only `api boundary` session-rotation + an identity brief \u2014 the agent resumes WITHOUT its mind). TIER-1 SCOPE (operator-approved; Tier-2 = drift-stamp/<current>/drift-directive + <memformat> + Pulse-Log DEFERRED to a separate parity item, NOT v0.15.0 \u2014 the legacy download_payload [claude_skill_owl context.rs:344] is richer but memformat is roadmap-deferred + drift-stamp is an orthogonal cross-machine-drift feature). TWO PARTS: (1) EXPOSE `spt api psyche-download <id> [--session-id <sid>]` -> stdout = the composed brief, project_id resolved from the endpoint's bound cwd (info::read_info -> cwd -> project derive; NO --project arg), auth-gated like sibling id-scoped verbs (the `gated(&id,&auth,\u2026)` pattern); empty store -> NO-CONTEXT on stderr (mirror legacy). The adapter SessionStart hook runs it + injects stdout as additionalContext. (2) APPEND any commune/signoff drop NOT YET SYNTHESIZED into the durable tiers as a distinct <pending-commune>/<pending-signoff> slice AFTER the durable slices. GATING (operator ruling): append while NOT-YET-SYNTHESIZED, NOT merely 'while the raw file is on disk' \u2014 in today's synchronous ingest (ingest_drops route_two_slice writes durable THEN deletes the file, lifecycle.rs:466 @ DEFAULT_PULSE_PERIOD 5s) the two coincide (a watched-dir drop IS pre-synthesis), so the v1 realization reads the manifest-declared session.commune_dir/signoff_dir (manifest.rs:208/210) for a present <id>-commune.md/<id>-signoff.md (COMMUNE_SUFFIX/SIGNOFF_SUFFIX, ingest.rs); the CONTRACT keys on synthesis-state so it stays correct when async Psyche synthesis lands (a consumed-but-not-yet-committed drop stays appended via a pending-synthesis staging set \u2014 forward hook). The agent-checkpoint trigger sentinel CHECKPOINT_SENTINEL=`!!checkpoint!!` (a FIXED spt-core constant \u2014 operator-specified, CONTEXT.md \u00a7fixed-constants, NOT adapter-configurable) is stripped at BOTH drop-body points via one shared `strip_checkpoint_markers` (remove every token, keep inter-marker text, collapse trivial whitespace): the PRE-synthesis pending-append (resume::append_pending) AND the POST-synthesis durable ingest (ingest::route_slices \u2014 the single choke covering route_two_slice + signoff.write_resume_commune; strip-then-empty-filter so a marker-only slice routes nowhere) \u2014 else the marker would persist PERMANENTLY in live-context.md once a checkpoint drop synthesizes + re-trigger once the adapter's checkpoint detection is live. PRESENTATION-ONLY: the append NEVER writes the durable store (spt-core remains sole store-writer, REQ-HAZARD-DROP-FILE-SINGLE-WRITER; mirror legacy's read-only/process_file_drop-sole-deleter discipline). SELF-CLEARING: once synthesis commits the <pending-*> slice vanishes \u2014 no duplication. CORE-OWNED (not adapter): an adapter-side raw-file read RACES spt-core's ingest-delete (TOCTOU, ingest.rs:161 removes the drop on pulse-consume); the fold MUST live in the single composer all resume pulls flow through. New public CLI verb -> docs-drift gate (xtask gen + reference.md no-internal-codes, cli-command-docs-drift). (v0.15.0 parity wave W5)",
    "doc_a": "7.9 A daemon-state wire change needs a deliberate BROKER restart (the broker is resident across a brain self-update) `[REQ-HAZARD-BROKER-SEED-WIRE-SKEW]` Failure:** the broker serves the seed-control channel and is RESIDENT across a brain-only self-update (ADR-0004's no-terminate-during-update pillar forbids auto-killing it \u2014 6.7). A self-update that changes a daemon-state WIRE FORMAT \u2014 e.g. the v0.9.0 adapter-agnostic `Seed` (the `adapter` field dropped) \u2014 therefore lands a NEW-version CLI talking to the STILL-RESIDENT OLD broker. The old broker cannot deserialize the new `Seed` (its formerly",
    "doc_b": "resume-session seam** \u2014 two distinct forms: fresh-with-preload:** resume with *cleared* context (a fresh session) + psyche-download. Accepts a `$psyche-context` key to launch the fresh session with the psyche-download preloaded \u2014 or the adapter instead pulls it via an spt-core command in its SessionStart hook. <!-- --> That command is **`spt api psyche-download <id> [--session-id <sid>]`**: it emits the durable resume brief (role \u2192 live-context \u2192 project-context, project resolved from the perch's bound cwd) to stdout for the adapter's SessionStart hook to inject as additional context, and APPE",
    "score": null,
    "reasoning": null
  },
  {
    "index": 63,
    "req_a": "REQ-HAZARD-COMMUNE-INGEST-BLACKHOLE",
    "req_b": "REQ-HAZARD-INFO-RMW-LOST-UPDATE",
    "title_a": "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 \u2014 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(&drop_path)?` AFTER route_slices (ingest.rs:121), but route_slices GATES the project tier on `!project_id.is_empty()` (ingest.rs:156) \u2014 when the endpoint's cwd is unresolved/owlery-internal at ingest time the project_id is empty, so the `<project-context>` slice is PARSED but never write_context'd/commit_project'd, yet the source drop is still deleted \u2192 the project-context content is permanently lost (black-hole). perri's repro: a two-sliced echo-commune (<live-context> role+release recipe + <project-context> 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)/F-032-commune-2026-07-08T222721Z.md (5595B, sha256 9bc27e18cf385958; perri wrote it verbatim from session log 0841835d.jsonl). Legacy spt held commit-first-then-delete parity; the modern two-slice ingest broke it. FIX distinguishes: a write SUPPRESSED-by-precedence (incoming older than durable \u2192 already-superseded \u2192 safe to delete) from a slice NOT-committed-because-un-committable-now (empty project_id / write error \u2192 must NOT delete; retry or preserve). Gate: an ingest with a non-empty <project-context> slice but an EMPTY project_id must NOT delete the drop (or must durably preserve the project slice) \u2014 the content survives to the next resolvable ingest / SessionStart; the live-tier commit path stays unchanged; a genuinely superseded (precedence-suppressed) drop still deletes. KNOWN-HAZARDS entry on landing (REQ-HAZARD-* = conformance-checklist, needs a test).",
    "title_b": "Concurrent info.json writers must serialize under the per-perch lock (5.16): an unlocked whole-record write racing a locked RMW is a silent lost update",
    "doc_a": "7.40 A commune/signoff drop is deleted ONLY after every applicable tier is durably committed \u2014 an un-committable slice preserves the drop, never delete-then-lose `[REQ-HAZARD-COMMUNE-INGEST-BLACKHOLE]` Failure (paid-for, perri field finding 2026-07-08 \u2014 F-032, data-loss):** `ingest_drops` unconditionally deleted the drop after `route_slices`, but the project tier is GATED on a non-empty `project_id` \u2014 when the endpoint's anchor cwd was unresolved/owlery-internal at ingest time, the `<project-context>` slice was parsed but never committed, yet the source drop was still deleted \u2192 the project-con",
    "doc_b": "5.16 Unlocked whole-record info.json write races a locked RMW \u2192 silent lost update `[REQ-HAZARD-INFO-RMW-LOST-UPDATE]` Failure:** `mutate_info` serializes its read\u2192mutate\u2192write under the per-perch `.info.lock` sentinel, but `establish_perch` (`spt::api::startup`) did read\u2192conflict-check\u2192`write_info` with **no lock**. At bind the two writers race (~700\u00b5s apart): the daemon RMW reads the PRE-BIND record, bind's `write_info` renames the full record in (`state=live_agent`, `controllable=Some(true)`, `session_id`), then the RMW writes its STALE pre-bind snapshot back plus a `status=online` stamp. T",
    "score": null,
    "reasoning": null
  },
  {
    "index": 64,
    "req_a": "REQ-HAZARD-CONPTY-DSR",
    "req_b": "REQ-SHELL-4",
    "title_a": "ConPTY reader must auto-answer DSR (ESC[6n) or all child output stalls (5.5)",
    "title_b": "Shell tunnel (reliable-ordered opaque byte stream): an owner<->shell link may hold a long-lived, reliable-ordered, link-bound QUIC stream pair carrying opaque wire protocol traffic the channel taxonomy must NOT reinterpret (first consumer usbip URB) \u2014 manifest opt-in, not enveloped, not MAC-framed, not spooled; the link lifecycle governs it (a link-break closes the tunnel). Reliable-ordered \u21d2 congestion surfaces as lag never loss \u21d2 acceptable only on-LAN: the on-LAN posture is documented and the tunnel is NOT proven cross-WAN (CONTEXT:262, minted 2026-06-11 Gateway grill; doyle gate C2).",
    "doc_a": "",
    "doc_b": "shell tunnel: a long-lived reliable-ordered link-bound QUIC stream pair carrying opaque bytes the taxonomy never reinterprets; manifest opt-in, not enveloped/MAC-framed/spooled; link-break closes it; reliable-ordered \u21d2 on-LAN posture Channels carry typed, taxonomy-interpreted payloads. Distinct from them, an owner\u2194shell link may also hold a **shell tunnel**: a long-lived, **reliable, ordered** byte stream (a dedicated QUIC stream pair bound to the link) for protocol traffic the channel taxonomy must NOT reinterpret \u2014 opaque wire protocols spoken end-to-end (first consumer: USB/IP URB traffic t",
    "score": null,
    "reasoning": null
  },
  {
    "index": 65,
    "req_a": "REQ-HAZARD-CONTROL-STAMP-LIFETIME",
    "req_b": "REQ-HAZARD-REDISPATCH-CONTROL-STEAL",
    "title_a": "#2: a control/viewer stamp never outlives its session \u2014 every teardown path clears what attach stamped. The broker exit-waiter (broker.rs ~:1844) sends the exit frame + sessions.remove(&id) but does NOT clear the perch's controller/viewer stamps; clear_controller()->stamp_driven_by() (clears driven_by+controlled) runs ONLY on controller-detach/evict/displace. /exit kills the CHILD not the controller conn, so the OutputLog drops with controlled:true, viewer_count, (and driven_by for a remote controller) latched in info.json forever \u2014 and hfenduleam keeps gossiping controller_node=self cross-node. Fix: on session reap, clear the perch's controller/viewer stamps (set_driven_by(None)+set_controlled(false)+set_viewer_count(0) via the known endpoint id) \u2014 broker stays the single writer. KNOWN-HAZARDS invariant. See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #2.",
    "title_b": "REDISPATCH-TRUTH W1 (KNOWN-HAZARDS 7.41, hertz field RCA 2026-07-16 \u2014 4/5 endpoints frozen per brain cycle): a fresh dispatcher must NEVER re-serve a terminal stream \u2014 a replayed historical Attach must not steal (same-identity silent become_controller, no Displaced) or clear (replayed-EOF detach_session) a LIVE controller. The legitimate same-by successor re-take after a brain restart still silently re-takes: the discriminator is stream LIFECYCLE, never origin identity. Gate: int \u2014 production-path regression D1: finished historical Attach + current active Attach, same endpoint/origin; restart target brain only (real run_dispatch_loop rediscovery, NO manual re-serve \u2014 the pre-fix e2e bypass is the lesson); prove the historical stream neither takes nor clears the current controller and current input/output stays exactly-once without detach; doc \u2014 KNOWN-HAZARDS 7.41. HEAVY nextest group at birth (FLAKE-LEDGER #15). Kin REQ-REDISPATCH-FINISHED-RETIRE (the mechanism), REQ-BRAIN-RESUME-NO-CONTROL-STEAL (the CLOSED session-cursor sibling \u2014 different leg), ADR-0038.",
    "doc_a": "7.27 A control/viewer stamp must NEVER outlive its session \u2014 every teardown path clears what attach stamped `[REQ-HAZARD-CONTROL-STAMP-LIFETIME]` Failure (F-026 #2, live evidence HFENDULEAM):** an spt-hosted endpoint stayed `ONLINE+CONTROLLED` after the operator's RC `/exit` \u2014 hours later hall-a's `info.json` still read `controlled:true` (status offline, dormant) and hfenduleam still GOSSIPED `controller_node=self` cross-node (SPT_DEV.json snapshot). ROOT: `/exit` kills the CHILD, not the controller connection, so the endpoint suspends via child termination and the teardown runs the REAP path ",
    "doc_b": "7.41 A fresh dispatcher must NEVER re-serve a terminal stream \u2014 historical replay must not steal or clear a live controller `[REQ-HAZARD-REDISPATCH-CONTROL-STEAL]` Failure (paid-for, hertz field RCA 2026-07-16 \u2014 4/5 endpoints frozen, twice in one day):** every brain cycle (`spt daemon refresh`, `spt update` apply \u2014 same `applyhost.rs` path) launches a fresh `dispatch::run_dispatch_loop` whose EMPTY per-process `claimed` set enumerates every broker-held peer stream. `NetShared.streams` has NO removal path (`StreamLog::finish` only marks) and the claim condition has no finished filter \u2192 the fres",
    "score": null,
    "reasoning": null
  },
  {
    "index": 66,
    "req_a": "REQ-HAZARD-CONTROLLER-IRRECOVERABLE-BEHIND",
    "req_b": "REQ-HAZARD-VIEWER-RING-ROLL-SNAP",
    "title_a": "DEFERRED EDGE of REQ-HAZARD-CONTROLLER-GAP-RESUME: when a serving controller falls behind the live ring FURTHER than the ring holds (tail - delivered_through > cap_chunks, the ring trim being delivered_through-blind, broker.rs:327-330), the dropped frames have rolled OUT of the ring and an exactly-once re-fetch is IMPOSSIBLE. v0.13.0 DETECTS this (resume floor unchanged across two consecutive resumes) and surfaces a MARKED truncation notice to the operator then ends the attach cleanly \u2014 it never silent-skips (a B2 lie) and never spins. FULL graceful handling (a clearly-marked snap-with-data-loss that keeps the operator on the live tail, or a structured truncation record the rc renders distinctly, plus the controller-too-slow + ring-too-small backpressure/sizing policy) is DEFERRED \u2014 staging it needs a netsplit / deep-behind harness (the in-process loopback rig keeps up; wedged_viewer's gap is recoverable at ~1492 < 4096). (v0.13.0+)",
    "title_b": "A read-only rc --view VIEWER whose serving brain falls behind the live ring under a hard flood and receives a FORWARD Output seq gap (the ring rolled frames out between reads, BEFORE any channel-overflow eviction \u2192 NO KIND_VIEWER_EVICTED marker) must SNAP TO LIVE (accept-and-advance via dedup-below + snap-above), NOT fatal with output gap (brain.rs:624/628 legacy reject-gap). ROOT (v0.13.0 forkpty, post-b4+skip-to-live): serve_attach subscribes a viewer via brain.attach_as(Viewer) leaving session_cursors EMPTY \u2192 the viewer serve-brain uses the LEGACY reject-gap \u2192 a PRE-eviction ring-roll forward-gap FATALS read_event \u2192 serve_attach returns \u2192 forwarding stops \u2192 attach_received_pty_output=FALSE (a_journaled / p0_paste / attach.rs:1071 wedged_viewer, Linux forkpty; Windows ConPTY floods slower \u2192 MASKED false-green). DISTINCT from REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT (the POST-eviction re-subscribe-from-floor): this is PRE-eviction gap-tolerance while STILL subscribed. VIEWER-only \u2192 B2-SAFE (a viewer never advances delivered_through / is not authoritative); the CONTROLLER keeps strict reject-gap (exactly-once resume). FIX: arm snap-above at initial viewer attach (attach_as_viewer_snap = attach_as(Viewer) + session_cursors.insert(session_id, from_seq)); the two viewer-survival mechanisms COMPOSE \u2014 this tolerates pre-eviction ring-roll gaps, REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT recovers post-eviction. (v0.13.0)",
    "doc_a": "",
    "doc_b": "BUILT (M12 W2.5).** The controller/viewer model is implemented end-to-end. Attach intent is **three-valued** (`AttachIntent = Viewer | Control | Take`, wire-default `Control`): `Control` to a FREE endpoint becomes controller; `Control` to a CONTROLLED endpoint is **refused with guidance** (`--view` to watch, `--take` to control) \u2014 never auto-viewer, never silent-displace; `Take` (`spt rc --take` / picker \"Kick\") kicks the incumbent with a **loud `Displaced{by}` notice** and full detach (not demote). The broker's per-session `OutputLog` is the fan-out hub: ONE authoritative **controller** (adva",
    "score": null,
    "reasoning": null
  },
  {
    "index": 67,
    "req_a": "REQ-HAZARD-CORRUPT-PERCH-COHERENCE",
    "req_b": "REQ-HAZARD-DAEMON-HOSTED-LIVENESS",
    "title_a": "Corrupt (present-but-unparseable) info.json is NOT absent: liveness/status readers agree a destroyed record is neither alive nor Active (counter-39 #2)",
    "title_b": "Daemon-hosted perches (Psyche, spt-hosted Self) derive liveness from the daemon endpoint table + info.json status, never is_process_alive(info.pid) (2.5)",
    "doc_a": "5.14 Corrupt info.json read as ABSENT \u2192 fail-open readers gossip a wiped perch ONLINE `[REQ-HAZARD-CORRUPT-PERCH-COHERENCE]` Failure:** three readers each collapsed a **corrupt** (present-but-unparseable) `info.json` into their fail-open ABSENT default, so a NUL-wiped perch (5.13) read as permanently live: `is_perch_alive` returned `true` (unreadable \u21d2 interim-alive), `advertised_status` then saw alive + no resting record \u21d2 `Active`, and the daemon self-gossiped that Active row every round (epoch 173k+). Result: `hall-a`, dead since a machine restart, showed ONLINE in `spt whoami` and on every",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 68,
    "req_a": "REQ-HAZARD-DAEMON-HOSTED-LIVENESS",
    "req_b": "REQ-KICK-1",
    "title_a": "Daemon-hosted perches (Psyche, spt-hosted Self) derive liveness from the daemon endpoint table + info.json status, never is_process_alive(info.pid) (2.5)",
    "title_b": "Explicit, loud controller displacement: `spt rc kick <target>` / `--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 \u2192 Control, so it can drive a free endpoint but CANNOT `--take` \u2014 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 \u2014 no elevated kick policy). The picker surfaces 'Kick <node> and attach' (Take) only on a controlled (blue \u25a0) endpoint, via the existing attach dispatch (single-bringup-path: intent is a parameter).",
    "doc_a": "",
    "doc_b": "BUILT (M12 W2.5).** The controller/viewer model is implemented end-to-end. Attach intent is **three-valued** (`AttachIntent = Viewer | Control | Take`, wire-default `Control`): `Control` to a FREE endpoint becomes controller; `Control` to a CONTROLLED endpoint is **refused with guidance** (`--view` to watch, `--take` to control) \u2014 never auto-viewer, never silent-displace; `Take` (`spt rc --take` / picker \"Kick\") kicks the incumbent with a **loud `Displaced{by}` notice** and full detach (not demote). The broker's per-session `OutputLog` is the fan-out hub: ONE authoritative **controller** (adva",
    "score": null,
    "reasoning": null
  },
  {
    "index": 69,
    "req_a": "REQ-HAZARD-DAEMON-IDENTITY-ENV-SANITIZE",
    "req_b": "REQ-OPID-MINTER-NAMESPACE",
    "title_a": "MSG-IDENTITY W1 / F-036 leg a (perri field RCA 2026-07-09/10, psyche seat-theft \u2014 doyle ACCEPTED primary fix): the daemon MUST sanitize inherited per-session identity env (SPT_ENDPOINT_ID / OWL_SESSION_ID / SPT_AGENT_ID) at startup AND before EVERY role spawn \u2014 these are per-session identity and are NEVER correct inherited state for a daemon or its role children. ROOT: a daemon restarted from inside an agent session (routine during core dev / `spt update apply`) carries the session's SPT_ENDPOINT_ID and passes it verbatim to every [session.psyche_resume] spawn; core only strips each role's DECLARED env_remove list (runtime.rs:728), so ONE adapter env_remove miss infects the whole node \u2014 every psyche claude turn fires SessionStart, the adapter hook sees the endpoint id, takes the bind path, and ROTATES the victim's perch to the psyche's own sid with a valid prior-sid proof, every pulse (field: lia/deployah/doyle psyches ALL briefed as <sptc-active-perch id=doyle>; 37 peer msgs drained into lia's psyche transcript; victim deliveries eaten, communes dark, sends downgraded from:cli@node). Adapter half FIXED v0.18.8 (env_remove += SPT_ENDPOINT_ID + shim scrub + SPT_PSYCHE_TURN hook-bail) \u2014 this REQ is the CORE-LAYER defense so no adapter miss can ever leak identity again. FOLD (F-036 leg b docs-fix, doyle-owned): broaden the recursion_guard_env schema description (manifest.rs:314 + crates/spt-runtime/manifest.schema.json:306) \u2014 core honors it on ANY role declaring the field (runtime.rs:740, keyed on the FIELD not the role name); drop the 'summarizer children' wording (perri adopted on both psyche roles v0.18.8, proven live). Gate: a daemon started with SPT_ENDPOINT_ID/OWL_SESSION_ID/SPT_AGENT_ID in its env spawns role children WITHOUT those vars (unit: role-spawn env assembly scrubs the identity set regardless of the role's declared env_remove); KNOWN-HAZARDS entry on landing. Kin psyche-custody/session-pin cluster, [[spt-core-findings-backlog]] F-036.",
    "title_b": "A-4a (REMOTE-TRUTH triage \u00a7A + ADR-0034 Decision 1 + Amendments 1 & 2): the broker effect journal's dedup key gains a minter dimension so ops minted by independent counters can never collide. ROOT (high, ground-truthed vs HEAD): the journaled-op producers key into ONE journal namespace (NET_EFFECT_SESSION|shell_sid, op) at broker.rs (EffectKey=(u64,u64)); a CLI wake op colliding with an already-journaled daemon op reproduces the typed 'already applied \u2026 retry with a fresh op_id' with NO broker restart (field-hit: spt endpoint wake id@node WOKE_FAIL). Same latent class: nethost dial_ops/stream_ops HashMap<u64,u64> ('Shares the one net op-id namespace') would re-clobber even after the journal separates them; AND shellchan::deliver_stdin_pending journals (shell_sid, row_id) so an rc operator's ops on the same shell_sid collide with spool row ids (dropped keystroke OR dropped spool row). Amendment 2 corrected the minter set: the REAL journal minters are {cli, pump, rc, shell} + legacy \u2014 psyche/epoch are the EpochSource notif/lease counter domain, NEVER submit to apply_once, DROPPED from the journal enum (a tag with no stamp site = doc'd-but-dead knob). FIX (Decision 1 + Amdt 2): ONE canonical Minter enum {Legacy, Cli, Pump, Rc, Shell, Wake} \u2014 Legacy reserved for pre-upgrade lines + untagged wire, monotonically shrinks; enum is the single source for the TEXTUAL journal-line token (self-describing during recovery). EffectKey becomes (effect-class, minter, op); recover() DUAL-PARSES (old shorter line \u2192 minter=Legacy, new longer line \u2192 parsed tag) so old journals need no migration and old-shape keys can never equal new-shape (migration-free). A MintedOp{minter, seq} newtype REPLACES bare op_id:u64 through the brain/daemon THREADING paths so forgot-to-stamp is UNCOMPILABLE (row_id stays the shell seq \u2014 never re-minted, the durable spool exactly-once identity). Wire keeps an additive optional minter field (serde default absent \u21d2 Legacy materialized at broker decode; serde_json no deny_unknown_fields \u21d2 NO wire version bump); the newtype is NOT forced into wire structs. nethost op-maps re-key by (minter, op). Red-first: mint an rc op == a journaled shell/pump/daemon op int on the same session \u2192 pre-fix the second dedups/clobbers (WOKE_FAIL class); post-fix both are distinct keys, both Applied.",
    "doc_a": "7.39 Per-session identity env (`SPT_ENDPOINT_ID`/`OWL_SESSION_ID`/`SPT_AGENT_ID`) is NEVER inherited \u2014 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]` Failure (paid-for, perri field RCA 2026-07-09/10 \u2014 F-036 psyche seat-theft):** a daemon restarted from inside an agent session (routine during core dev / `spt update apply`) carried the session's `SPT_ENDPOINT_ID=doyle` and passed it verbatim into every `[session.psyche_resume]` spawn \u2014 core stripped only each role's DECLARED `env_remove` list (ru",
    "doc_b": "Executor recon verified the actual journaled-op surface against HEAD; the Decision-1 tag list was wrong in both directions. Corrections, binding:",
    "score": null,
    "reasoning": null
  },
  {
    "index": 70,
    "req_a": "REQ-HAZARD-DAEMON-STOP-REAP",
    "req_b": "REQ-HAZARD-RESTART-IDEMPOTENT",
    "title_a": "Breap: `spt daemon stop` REAPS the spt-hosted children it spawned \u2014 no orphaned psyche/harness processes. Today a stop leaves ~8 orphaned claude-spt-psyche.exe + spt.exe: Psyches are spawned DETACHED (runtime.rs:342-356, the Child is dropped \u2014 'Detached' ~349) and the livehost stop flag Arc<AtomicBool> is NEVER raised (brainproc.rs:227-230 holds it 'for symmetry'). FIX: on stop, raise the livehost stop flag AND kill the spawned psyche/spt-hosted children \u2014 via a Windows job object / Unix process-group so the children die with the daemon (not detached-immortal). Folds with B3 (both the stop path). (v0.12.0)",
    "title_b": "Idempotent/exactly-once delivery across brain restart at every broker boundary (codex #14)",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 71,
    "req_a": "REQ-HAZARD-DEFERRED-MANIFEST",
    "req_b": "REQ-INSTALL-9",
    "title_a": "A pointer-mode (delegated / GhReleaseManaged) adapter whose binary/manifest is not yet extracted is reported with a CLEAR diagnostic, never silently dropped. Today such an adapter reads its manifest LIVE from source_dir (registry.rs manifest_dir ~146/149); a deferred / un-extracted install makes load_manifest fail \u2192 registered() (~410, filter_map(.ok())) SILENTLY DROPS the row \u2192 downstream ADAPTER_UNRESOLVED + a cryptic os-error-2 on `spt adapter use`. FIX: surface a clear diagnostic at the resolver + at `adapter use` (name the adapter + the deferred/missing-manifest cause + the fix), not a silent filter-drop and not a bare os-error-2; consider an eager manifest copy at register time so host_binaries survive before the binary download completes. doyle Finding A. (post-v0.10.0)",
    "title_b": "Adapter add from a GitHub release archive: `spt adapter add --release <user/repo> [--tag <tag>] [--asset <name>]` fetches a `.spt` tar asset over HTTPS+GitHub trust, extracts it to the durable adapters/_github home, and registers the root \u2014 ships built binaries source-free and versioned (the distribution path for an adapter whose dev repo is a monorepo subdir, where --github root-only clone does not fit)",
    "doc_a": "",
    "doc_b": "adapter registration (`spt adapter add`)**: How a node comes to *know* an adapter \u2014 harness or shell. An explicit **`spt adapter add <path>`** (or **`--github <user/repo>`**) validates the manifest against the published JSON Schema and writes a registration record under `{SPT_HOME}/\u2026/adapters/` \u2014 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. The `--github` fo",
    "score": null,
    "reasoning": null
  },
  {
    "index": 72,
    "req_a": "REQ-HAZARD-DETACHED-PIPE-INHERIT",
    "req_b": "REQ-RESIDENT-SERVICE",
    "title_a": "Windows detached long-lived children must not inherit a captured caller's pipe: every detach-spawn of an immortal child (daemon, shell binary) runs bInheritHandles=FALSE, or a caller capturing output anywhere up the process chain hangs forever on a pipe that never EOFs \u2014 std-handle flag stripping is NOT sufficient (grandparent strays still flow) (5.6)",
    "title_b": "ResidentService substrate (ADR-0049, design ratified 2026-07-26): a daemon-supervised binary an adapter declares via a `[service]` manifest section \u2014 core-owned from birth, NO perch/identity/address. SPAWN: the daemon launches it job-neutrally (detached_no_inherit + the cold-start ladder posture), so it is never a shell's child (`/T` tree-kill cannot reach it; the shell-descendant hazard class of REQ-SHELL-ADAPTER-OWNED-DETACHED-SERVICE never arises) and never inside a launching terminal's Job Object (the REQ-SHELL-CLI-SPAWN-JOB-EXPOSURE service half closes by construction). START TRIGGER declared in the manifest: start = 'boot' or start = 'bind'; supervised identically once running, with the wake-watcher scaffolding (backoff, give-up latch, one-per-instance lock, orphan-kill, brain-side reconcile) minus the offline-only flip. 'boot' is DESIRED-STATE-RUNNING, not an event: the supervisor reconciles a boot service toward running at EVERY opportunity \u2014 daemon boot, ADAPTER REGISTRATION while the daemon is live (operator addition 2026-07-26: installing or registering an adapter whose manifest declares a boot service starts it THEN \u2014 spt itself is never restarted to bring a new adapter's service up), update-hold release, and first shell bind as the defensive ensure. 'bind' starts only at the adapter's first shell bind. CARDINALITY: one supervised instance per registered adapter-option `<adapter>[:profile]` (consumer-confirmed as COHERENT TARGET SHAPE \u2014 flynn's precision, 2026-07-26: not exercisable by the first consumer until per-option config dirs exist; alchemy today has one config dir and daemon.toml carries exactly one guild_id); the adapter may keep its own kernel file lock as a private double-start guard \u2014 core neither reads nor depends on it. PER-OPTION IDENTITY IS THREADED (flynn's gap, accepted): the supervisor passes the adapter-option name and the per-option runtime dir into the service's spawn environment, so an adapter can scope its private guard AND its config per option \u2014 the mechanism that makes two-options-two-services deliverable rather than merely permitted. Without it, two options resolving one adapter config dir produce the silent flap flynn derived: instance two exits immediately on the kernel lock, core sees only start-then-die, and crash-relaunch backoff is CORRECT behavior against that observation \u2014 two correct components disagreeing about the unit. UPDATE IS A FIRST-CLASS SUPERVISOR OPERATION WITH AN EXPLICIT HOLD: quiesce -> hold (stopped and NEVER relaunched while held) -> bits swap -> start new bits -> release; adapter update-apply performs this ordered operation; crash-relaunch with backoff applies ONLY when not held. The forcing case is structural, not advisory (flynn, argued against their own convenience): an eager relaunch during a swap re-pins the OLD exe mid-deploy (Windows exe lock), converting a diagnosable os-error-5 into an unwinnable race \u2014 if delivery must sequence, the hold ships FIRST and a dead service stays dead until told otherwise (the STALE-ONLINE no-spontaneous-relaunch ruling, same reason, one layer down). FAST-EXIT IS A CONFIGURATION FAULT, NOT A CRASH: consecutive immediate exits (exit within a startup threshold, N in a row) trip the give-up latch EARLY with a distinct loud STARTUP_FAULT diagnostic carrying the captured early stderr \u2014 a double-start lock conflict then reads as the configuration fault it is, never as a silently flapping service ground through backoff. QUIESCE IS COOPERATIVE EXIT + DEADLINE: the supervisor places a stop-request marker (a file in the service's runtime dir \u2014 polling services observe it on their next cycle; no inbox exists or is added); the service exits WHEN SAFE and the kernel-observed exit IS the ack \u2014 'not ready' is expressed by not-yet-exiting, so no busy record exists to go stale in either direction; a manifest-declared grace deadline (default ~30s) bounds the wait, then force-kill. Delay possible, veto never. An OPTIONAL advisory status line may surface in service status display \u2014 never consulted for decisions. LIVENESS IS DERIVED, NEVER RECORDED: the supervisor is the parent and holds the child handle (exit is kernel-observed); no supervisor-maintained running-record exists (the v0.43.0 stale-online lesson applied one layer down \u2014 flynn's condition, structural here). Any status/version identity surface keeps the locked-file split lesson: never require reading a file the service holds an exclusive OS lock on (Windows). CLI INVOCATION CAPABILITY (consumer-blocking, non-negotiable per flynn): the supervisor threads the environment so the service can invoke the spt CLI (`spt send` et al., identityless cli@node from-label, durable spooling per ADR-0002) \u2014 if a supervised Hub cannot shell out to spt send, node-wide Watch delivery dies silently. ADDRESSING: none \u2014 a service needing a two-way agent-facing surface has one at its adapter's endpoint/shell layer (the alchemy layering argument that re-scoped ADR-0023's faceless-service rejection); AlwaysOnEndpoint (REQ-EP-8) = this substrate + the addressable front. Gate at activation (all legs against a MOCK service adapter \u2014 the gate never depends on the first consumer being the boot specimen): int \u2014 a manifest [service start='boot'] binary rises with the daemon job-neutrally, a registration of that manifest against an ALREADY-LIVE daemon starts the service without any restart, survives a shell teardown of the same adapter (tree-kill does not reach it), a held update swaps bits with zero relaunch races (hold observed under a concurrent crash), quiesce marker -> cooperative exit within grace, deadline -> force-kill on a wedged mock, a mock that exits instantly N consecutive times surfaces STARTUP_FAULT (not a backoff flap), and the service successfully invokes spt send from its supervised environment. ACTIVATED FOR W1 2026-07-26 WITH THE FOLLOWING BUILD RULINGS FOLDED IN (doyle; constraints live in the artifact, not the dispatch thread). VERB SURFACE: the operator-facing verbs are `spt adapter service list` (all registered options + derived state) and `spt adapter service status <adapter[:profile]>`, nested under the ADAPTER group \u2014 NOT a bare `spt service`. Reason: 'service' is ALREADY public surface carrying an unrelated meaning \u2014 the platform daemon-service abstraction (REQ-DAEMON-6/-8, `crates/spt-daemon/src/service.rs`: the systemd user unit vs the Windows at-logon task) surfaces in `spt daemon` help as 'registered OS service' / 'managed service' / 'the managing service label'. Ownership-scoping separates the two meanings permanently: the OS-service is the DAEMON's and lives under `spt daemon`; the resident service is the ADAPTER's and lives under `spt adapter`. ADR-0049's 'service status display' means `spt adapter service status`; the W1 PR carries a one-line ADR errata note. GIVE-UP LATCH SCOPE: the latch suppresses relaunch grinding until something plausibly changed \u2014 it is NOT a durable verdict. Cleared by exactly three events: (1) DAEMON BOOT \u2014 desired-state-running enumerates boot as a reconcile opportunity with NO latch exception, and the re-trip is bounded (N fast exits) and LOUD (STARTUP_FAULT re-fires each boot); an in-memory per-daemon-lifetime latch is an acceptable implementation, and if the durable-marker scaffolding is reused then boot clears the marker; (2) ADAPTER RE-REGISTRATION \u2014 declared intent that manifest/config changed, reconciling immediately; (3) UPDATE-HOLD RELEASE \u2014 new bits invalidate the fault evidence. NOT cleared by the first-shell-bind ensure: a bind changes nothing about the service's config, so the bind-time reconcile SKIPS latched services \u2014 otherwise ordinary shell use converts the latch into the very flap it exists to stop. The REJECTED alternative is recorded deliberately: a durable latch with explicit-clear-only leaves an operator's already-fixed config fault sitting behind a service that stays dead and quiet forever \u2014 loud-bounded beats quiet-permanent (the heal-assigns-owner-never-closes shape one layer down). SANCTIONED BUT NOT W1-REQUIRED: `spt adapter service restart <adapter[:profile]>` as the explicit manual clear+reconcile \u2014 take it into W1 only if cheap once the verb group exists; the three automatic clears ARE the requirement. REGISTRATION-TIME START IS A WIRE OP: `registry::register` runs in the CLI PROCESS (`crates/spt/src/cli.rs`, the adapter-add and adapter-update call sites), so it cannot itself start anything in the daemon \u2014 registration-starts-the-service structurally requires ONE new adapter-scoped daemon IPC op, semantically `AdapterServiceReconcile { adapter }` (exact spelling matched to house op style where it lands), which the CLI calls after a SUCCESSFUL register on BOTH paths when the daemon is reachable. The daemon handler runs THE SAME reconcile code path as boot / hold-release / bind \u2014 ONE reconcile function taking an opportunity discriminant, never a second start authority. The response is a per-option outcome list (started / already-running / held / latched / bind-deferred / startup-fault) so the CLI prints honest per-option text. Daemon NOT reachable: registration STILL SUCCEEDS \u2014 never a refusal \u2014 and the CLI prints a REQUIRED notice that the service is declared, the daemon is not running, and it will come up at the next daemon boot. That notice is CONTRACT, not courtesy. RUNTIME-DIR ENCODING: cardinality is per adapter-option, so the option name becomes a PATH component and `:` is illegal in a Windows path. Core NEVER uses the raw option string as a path component \u2014 every construction site goes through ONE shared encoder, and that encoding MUST BE INJECTIVE (a lossless escape, never a strip/replace that can collide). Two distinct options mapping to one runtime dir means two services sharing a quiesce-marker namespace: the silent flap one layer down. The unit gate MUST include a collision-adversarial pair (e.g. if `:` maps to `_`, then `a:b` and `a_b` must remain distinct). MODULE PLACEMENT: the supervisor lands as `servicehost.rs` (the established `*host` convention \u2014 shellhost, harnesshost, linkhost, applyhost); `service.rs` is untouched and BOTH module headers cross-reference the other meaning of 'service'. INSTALL-DIR RESOLUTION RIDES W1 (operator-requested via flynn, ruled in after code verification): the `[service]` spawn resolves its binary through the EXISTING REQ-INSTALL-11 helper \u2014 the same primitive, NO parallel resolution path \u2014 and the same wiring lands at the two shell-family fill sites, which today resolve NEITHER the install-dir program token NOR `{adapter_dir}`. Without it a `--release`-installed shell adapter registers but cannot spawn (bare token \u2192 os error 2; `{adapter_dir}/x` \u2192 'no value for substitution key'), released shell adapters need a hand-maintained per-node manifest, `spt adapter update` on them is a permanent no-op, and THIS req's hold/swap/start ceremony would be exercisable by MOCK ONLY \u2014 never by the named first consumer. Site census discharged BEFORE build (authoritative grep, cfg(test) excluded, accepted by doyle): the production template-fill sites are `shellhost.rs::fill_spawn_command` and `shellwake.rs::fill_wake_command` (both targets \u2014 threading not shape, since the wake caller already holds `AdapterRecord.source_dir`, which IS the install dir), plus `harnesshost.rs` session `role.command`, which is OUT OF SCOPE and already resolves correctly through `resolve_program_in_dir`. Scope guard: shell spawn/wake + `[service]` ONLY \u2014 this does NOT expand into the `[session.self]`/`[history]` follow-on sites. FAULT COUNTERS ARE TWO, NOT ONE (ruled 2026-07-26 after the builder surfaced the reading): (1) the FAST-EXIT counter increments ONLY on exits whose uptime is UNDER the startup threshold, and RESETS the moment any run EXCEEDS that threshold. Without the reset the latch mislabels slow-crash decay as a configuration fault \u2014 the latch lying about cause, which is worse than no latch. STARTUP_FAULT is reserved for THIS path alone. (2) The ORDINARY consecutive-crash give-up (the shell wake-watcher scaffolding's `give_up_after` = 6, deliberately UPTIME-BLIND) sits BEHIND it UNCHANGED in W1 and keeps its existing diagnostic label \u2014 no silent behavior fork from the shell watcher. (3) BOTH counters reset on the latch-clear events above (daemon boot, adapter re-registration, update-hold release): a clear that left either counter primed would relatch on the first post-clear crash and thereby defeat the clear. (4) NOTED-OPEN, deliberately NOT W1 and NOT a promise: the ordinary counter's uptime-blindness means a service that crashes once a day gives up after six days and then stays down until a clear event. Whether that decay behavior is right for SERVICES (as opposed to the shell watchers it was designed for) is a future ruling; it is recorded here as open so the next builder inherits the question rather than rediscovering it in the field. ORPHAN ADOPTION IS IMAGE-VERIFIED, AND ITS PLATFORM GAP IS RECORDED-OPEN (ruled 2026-07-26 after the builder surfaced the trade): a fresh daemon kills a dead daemon's parked orphan ONLY by path-verified identity \u2014 a live pid whose image path cannot be READ classifies `Unverifiable` and BLOCKS adoption, i.e. the start refuses loudly rather than proceeding. Loud-blocked over quietly-double-started is this design's whole posture (a bare-pid kill is the recycled-pid class, and a double-start is the silent flap the cardinality rule exists to prevent). RECORDED-OPEN consequence, NOT debt owed by W1: the image oracle is `/proc` on unix, so a unix WITHOUT `/proc` (macOS/BSD) would block on every live orphan until that pid dies. This is theoretical for every platform we ship \u2014 win, linux-gnu, musl \u2014 and no macOS/BSD asset exists; a future builder adding one inherits the question here rather than rediscovering it in the field. SUPERVISOR PLACEMENT IS BROKER-SIDE (ruled 2026-07-26): the supervised set, its boot sweep and the reconcile control socket live in the BROKER process, beside the digest/drive/tunnel hubs \u2014 NOT in the restartable brain child that hosts shellwake. Two reasons, both structural. (a) A supervisor owns LIVE CHILD HANDLES and, from the update ceremony on, an in-memory HOLD: that is a daemon-lifetime continuity resource, which is the ADR-0018 Q2/Q5 broker-side test; the Q5 exception that put shellwake in the brain reads 'a pure disk-reconciler' and this is not one. (b) A brain restart is the ROUTINE UPDATE PATH (StartReason::Update exists precisely for it), so brain-hosting would bounce every resident service through the orphan-adoption path with no quiesce, no grace and no hold \u2014 the ungoverned bounce this req's ordered update operation exists to replace \u2014 and would lose the hold mid-swap. Broker-hosting is also what makes the wire op possible at all: all control sockets are broker-served because a CLI cannot reach brain memory (stated in drivehub.rs's module header and obeyed by every hub). NO PERIODIC SWEEP \u2014 CHOSEN, NOT OMITTED (ruled 2026-07-26): the supervisor host runs the boot sweep and then parks; there is deliberately no timer re-sweeping on a cadence. The four ruled opportunities are all EVENTS, each with a caller that enters the one reconcile function directly, so a timer would be a FIFTH start authority nobody ruled in \u2014 and its only distinctive work would be silently healing a failed registration nudge, converting a diagnosable defect into invisible behavior. The REQUIRED daemon-unreachable notice is the honest answer to that case; machinery that papers over its own failure class is refused (the same instrument-soundness razor as the rest of this design). A SUPERVISOR WHOSE DECLARATION DISAPPEARS RECONCILES TOWARD STOPPED (ruled into W1 2026-07-26): every sweep runs a STOP side before its start side \u2014 an option whose adapter is soft-deregistered, hard-removed, or whose manifest no longer declares a [service] is torn down through the handle that names its child. This is the symmetric half of desired-state-running, not an addition to it: without it a deregistered adapter's binary outlives its own registration until the daemon dies, which is exactly the ungoverned-lifetime shape this req abolishes. TREE TEARDOWN ON EVERY SUPERVISOR-INITIATED KILL (ruled 2026-07-26): the force-kill deadline is where the unconditional-kill promise is WRITTEN, but it is not the boundary of the problem \u2014 a supervised service's descendants are torn down on every kill the supervisor initiates, including daemon-shutdown stop_all and the stop-side sweep teardown. Descendants outliving THOSE paths are strictly worse off than ones outliving a force-kill, because the next daemon's orphan sweep is structurally blind to them: it knows one parked pid and holds no handle to anything below it. Windows reaches the tree through a SUPERVISOR-OWNED Job Object assigned at birth (CREATE_SUSPENDED -> AssignProcessToJobObject -> resume, so no descendant is ever spawned outside the job; KILL_ON_JOB_CLOSE deliberately OFF, so a dying supervisor is never an unannounced service outage); unix through the process group setsid already establishes. Job-NEUTRALITY is not contradicted: that invariant governs OTHER people's jobs reaching our processes, which CREATE_BREAKAWAY_FROM_JOB still handles at birth. A job the OS refuses is a LOUD DEGRADE, never a refusal to start: the process still dies on demand and only its descendants become unreachable, which is exactly where this path stood before the job existed \u2014 refusing the spawn would convert a bounded descendant leak into a total outage over a failure in an OS facility rather than in anything the adapter declared. THE STATUS SURFACE IS DAEMON-ANSWERED OR IT SAYS NOTHING (ruled 2026-07-26, the leg-D companion of the tree-teardown ruling): the CLI NEVER derives service state from the pid file. Either the daemon answers `spt adapter service list|status` over the control socket, or the CLI prints that the daemon is not running \u2014 verbatim, and with no fallback read. The pid file is a KILL HANDLE for the NEXT daemon, and reading it as liveness in a display surface is the v0.43.0 STALE-ONLINE class one layer down: a record answering a question the record cannot know. Three properties follow and are requirements, not implementation taste. (a) The status op is a PROJECTION over the live supervised set \u2014 the supervision threads, the hold flags and the stand-down records \u2014 plus the registry; it starts, stops, holds and kills nothing, because a diagnostic that converges the thing it measures is a start authority wearing a diagnostic's clothes. (b) A row reports its EVIDENCE: a latch surfaces with the captured startup output behind it, since a fault reported without its cause is the instrument failing at its one job. (c) An option SUPERVISED WITHOUT A DECLARATION behind it is reported as exactly that rather than hidden \u2014 with no periodic sweep, a deregistered adapter's supervisor lives until the next opportunity's stop side reaches it, and that window is precisely when an operator asks what is running. THE ADVISORY STATUS LINE IS DISPLAY-ONLY AND BOUNDED: the service may write one line into its runtime dir (named in docs/MANIFEST.md beside the stop-request marker, so it is implementable); core reads the FIRST line under a byte cap, treats an unreadable file as simply no advisory (the locked-file split rule holds), and consults it for NO decision \u2014 core deciding on it would put a least-trusted binary's self-report in the control path, and a service that stopped updating it would silently become whatever it last claimed. WIRE LENIENCY IS PLACED, NOT SPRINKLED: fields a reader BRANCHES on stay typed with a `#[serde(other)]` unknown arm (KH-2.3), while a field only ever ECHOED carries the daemon's label verbatim \u2014 re-deriving a lenient copy of the manifest's validated `start` vocabulary for the wire would weaken the one place strictness matters (registration). An op an older daemon predates HANGS UP rather than reading the request and answering nothing: a server that silently ignores an unknown kind leaves the caller blocked on a reply that never comes, so an older daemon would WEDGE a newer CLI instead of failing it.",
    "doc_a": "5.6 Windows detached children inherit a captured caller's pipe `[REQ-HAZARD-DETACHED-PIPE-INHERIT]` Failure:** a caller captures an `spt` invocation's output through a pipe (`Command::output()`, a harness hook reading the command). That `spt` process detach-spawns a **long-lived** child (the daemon via `ensure_running`; a shell binary via `spt shell spawn`). On Windows `CreateProcess` runs with `bInheritHandles = TRUE`, and the spt process's std handles \u2014 the caller's pipe write-ends \u2014 are inheritable by construction, so the immortal child inherits them even when its *own* stdio is `Stdio::nul",
    "doc_b": "`command`** \u2014 an **opaque** command string (program token plus args), like every other command seam. Its program token resolves against the adapter install dir** before PATH (REQ-INSTALL-11), and args support adapter-static `{adapter_dir}` / `{adapter_name}` substitution only. Must be non-empty: a declared service means spt-core owns and supervises a process. `start`** \u2014 **required**, no default. `\"boot\"` is **desired-state-running, not an event**: the supervisor reconciles the service toward running at daemon boot, at **adapter registration against a live daemon** (installing or registering a",
    "score": null,
    "reasoning": null
  },
  {
    "index": 73,
    "req_a": "REQ-HAZARD-DRIVEN-BY-SELFHEAL",
    "req_b": "REQ-HAZARD-LIVEHOST-NONRESIDENT",
    "title_a": "An spt-hosted endpoint's ONLINE+CONTROLLED state (`driven_by`) must CLEAR even when the detach IPC is lost \u2014 do NOT rely on the detach signal (same lesson as REQ-HAZARD-HOSTED-LIVENESS-RECONCILE B2): the reconcile loop clears `driven_by` when the endpoint has no live controller/session. Today a wedged or lost pump never delivers the detach, so the endpoint stays latched CONTROLLED forever. Composes with W1 (the wedge no longer blocks the detach) and rides the same pull-primary reconcile substrate as B2. (v0.13.0)",
    "title_b": "A daemon-hosted Psyche that spawns then EXITS IMMEDIATELY is a host failure, surfaced like a spawn failure (closes the v0.8.1 residual masking): the REQ-HAZARD-LIVEHOST-BOOT-RACE signal stamps `psyche_host_error` only when `spawn_psyche` returns Err, NOT when the detached spawn() returns Ok but the child dies within moments (e.g. a bad-argv child exiting 2 \u2014 the F-009 case). That leaves the residual 'online + no Psyche + no cause' gap: the nested `{id}-psyche` info.json is written status=online with a real-but-DEAD pid and the PARENT perch carries NO psyche_host_error (perri's F-010: tasklist showed 0 host procs across the window while info.json read online). The host MUST confirm RESIDENCY \u2014 a hosted child not alive (or whose `{id}-psyche` perch never re-registers / has a dead pid) within N seconds of spawn is treated as a host failure: stamp the parent perch `psyche_host_error{reason:\"host not resident within <n>s (psyche perch missing/dead pid)\"}` (and do not leave a phantom online nested perch). Closes the last masking gap the v0.8.1 fix left open. perri's F-010 (v0.8.1 dogfood). Sibling of REQ-HAZARD-LIVEHOST-BOOT-RACE.",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 74,
    "req_a": "REQ-HAZARD-ECHO-BEFORE-SIGNOFF",
    "req_b": "REQ-SEAM-CAPABILITY",
    "title_a": "Echo-commune fires before INIT_SIGNOFF on orphan teardown (3.3)",
    "title_b": "Hostable endpoint-types capability declaration",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 75,
    "req_a": "REQ-HAZARD-ENDPOINT-LIFECYCLE",
    "req_b": "REQ-HAZARD-STOP-PATH-PSYCHE-ORPHAN-REAP",
    "title_a": "REGISTRY-LIFECYCLE W2 (KNOWN-HAZARDS 7.45 \u2014 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 \u2014 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 \u2014 the matrix; doc \u2014 KNOWN-HAZARDS 7.45.",
    "title_b": "Endpoint-stop and brain-death reconcile MUST reap a brain-less perch's orphan detached Psyche via the cmdline-scoped guard (`psyche_orphan_should_reap`) \u2014 the handle-reap (`LiveSet::stop_host`, REQ-HAZARD-UNHOST-PSYCHE-REAP) CANNOT, because the owning brain is gone (its `psyche_child` handle died with it), and the brain-start scoped-reap (REQ-HAZARD-BRAIN-RESTART-PSYCHE-DUP) never fires for a perch being STOPPED rather than re-hosted. So the live-host calls the scoped reap after `stop_host` at the reconcile stop-side AND in `confirm_residency_or_unhost`. Preserves fail-safe-decline (pid-alive AND exe-basename==psyche-program AND cmdline contains `<id>-psyche`; any unreadable signal DECLINES \u2014 a missed dup is bounded, a wrong-kill is catastrophic). This is the orphan-leak half of the perri F-010xF-015 field bug (the unsupervised install-dir Psyche that locked an update); the other half is the psyche own-copy (ADR-0025 amendment). (v0.13.2 W3 (a))",
    "doc_a": "7.45 Endpoint lifecycle state converges to truth from every death path \u2014 no optimistic online without authority, no surviving control stamps, no immortal wake intent, no untruthful create `[REQ-HAZARD-ENDPOINT-LIFECYCLE]` Failure (paid-for, three hertz reports + operator field 2026-07-16):** four families, one root shape \u2014 lifecycle state written by multiple non-converging paths, optimistic stamps never verified. (a) `cmd_listen` stamped `status=online` from manifest capability alone \u2192 dead-PID hybrid rows survived EVERY restart (reconcile skipped them by state, cleanup gated on `controllable=",
    "doc_b": "| # | Invariant | spt-core surface | |---|---|---| | 1.1 | Grace wait precedes INIT_SIGNOFF | daemon teardown | | 1.4/4.4 | Deferred rows excluded from event-stream drain | daemon spool drain | | 2.1/5.1 | Stable PID/broker-handle over ephemeral PID | liveness detection | | 2.3 | Handoff argv/IPC version-tolerant (newer brain \u2194 older broker) | broker\u2194brain IPC, self-update | | 2.4 | gen_start = now() on cold-start + handoff | per-instance generation | | 2.6 | A shell's ONLINE-ness is DERIVED (recorded status AND a not-provably-dead `shell.pid`) \u2014 an abruptly-killed binary breaks no link, so `c",
    "score": null,
    "reasoning": null
  },
  {
    "index": 76,
    "req_a": "REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT",
    "req_b": "REQ-SESSION-ADAPTER-RECORDED",
    "title_a": "A clean `spt rc` attach to a LIVE spt-hosted (`endpoint run`) harness must DELIVER the harness's PTY output. KEYSTONE \u2014 the operator's central 'attach shows no output' symptom, reproduced on the real dummy-harness fixture (v0.12.1 Wave 1) with NO death and NO wedge: bringup succeeds (online, harness pid alive + heartbeating, psyche hosted), the attach CONNECTS (PUMP_IPC_READER spawned, no RC_FAIL, holds the full window) \u2014 but receives EXACTLY 0 bytes over 10s of the harness's flushed [session.self] stdout. DISTINCT from REQ-HAZARD-VIEWER-CLOSE-DETACH (death) and REQ-HAZARD-ATTACH-WEDGE (dead-child backpressure): here the harness is ALIVE and the attach is a clean first subscribe. This BLOCKS the 'view is independent' invariant \u2014 re-attach is meaningless if a live endpoint-run harness shows nothing. KNOWN-GOOD (rules out 'no drain'): attach.rs `local_attach_via_loopback_conn_rides_the_same_pump` + `broker_spawns_the_pty_child_in_the_requested_cwd` prove the broker DOES drain+fan a `spawn_session` PTY child to a loopback attach over the SAME transport rc uses. Both spawn_session and endpoint-run's spawn_session_pid send KIND_SPAWN \u2192 the same dispatch_spawn (broker.rs:706/835) which starts the per-session drain+OutputLog \u2014 so the gap is NARROWER than 'no drain', endpoint-run-specific. Root candidates: (a) spawn_session_pid's SpawnReq stdio/env/cwd differs so the dummy's stdout isn't the captured ConPTY; (b) the harness stdout WRITE BLOCKS because the ConPTY buffer fills (drain not reading THIS pty) \u2014 explains alive-but-0-bytes; (c) ConPTY reader-park (KH 7.6) on this path; (d) `spt rc` resolve_session/subscribe for an endpoint-run session subscribes to the wrong/empty log. (v0.12.1)",
    "title_b": "D-2 (REMOTE-TRUTH triage \u00a7D-2 + operator Q5 @c248afc): the session ledger records the adapter[:profile] a session ran under, so a later resume can restore the harness the session actually used (not merely the endpoint's CURRENT stamp). ROOT: SessionEntry (spt-store/sessions.rs:58) carries ts/session_id/trigger/cwd/ordinal but NOT the adapter \u2014 a resume-from-history row cannot know which harness authored the transcript, so a resume under a since-changed endpoint adapter (B-2 ChangeAdapter, or a fork) launches the wrong harness. FIX: an ADDITIVE `adapter: Option<String>` on SessionEntry, exact cwd/ordinal serde pattern (#[serde(default, skip_serializing_if=\"Option::is_none\")]) \u2014 a pre-migration row missing the key deserializes None; None omits the key on serialize (byte-identical to old rows); an unknown key on an old reader is ignored (serde default) \u2014 back-compat BOTH directions. Stamped at every PRODUCTION session-boundary append. CENSUS (doyle-confirmed @94f0205, corrects the triage-era 5-site drift to the real 3): startup.rs:317 (live bind boot row, rec.adapter in scope), reporting.rs:94 (boundary rotation row UNDER the mutate_info lock, capture adapter_for_ledger=rec.adapter beside cwd_for_ledger), ready.rs:119 in crates/spt-msg (ready-agent boot row, rec.adapter in scope). NOT digest.rs:601 (cfg(test) fixture) and NOT a livehost psyche-ledger append (none exists \u2014 the live /clear|/compact boundary shells `api boundary` \u2192 reporting.rs:94, the SAME append). None-stamp is a benign degrade (resume falls back to the endpoint's current adapter).",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 77,
    "req_a": "REQ-HAZARD-GEN-START-NOW",
    "req_b": "REQ-LISTEN-SESSION-ID-FALLBACK",
    "title_a": "gen_start = now() on cold-start and handoff (2.4)",
    "title_b": "F-034 leg c (perri/hertz field finding 2026-07-09): a session that goes live LATE (hours after SessionStart, or after a daemon restart) must still be able to bind \u2014 the ephemeral SessionStart seed ('consumed within seconds') is GONE by then and nothing re-fires it until the NEXT SessionStart, so even `api listen --parent-pid <correct claude pid>` hits NO_SEED. Design assumption 're-fired on the next SessionStart if needed' does not hold for long-lived sessions. FIX (perri-recommended, cleanest): `api listen --session-id <sid>` fallback that binds from the session-id when the pid has no live seed \u2014 removes the ephemeral-seed dependency entirely (the adapter already knows the sid; the skill passes it, and can then DROP its manual re-seed step). Alternative (option 1, less clean): re-fire the seed on daemon restart. Gate: a session with NO live seed (expired / post-daemon-restart) binds via `listen --session-id <sid>` (no NO_SEED); the sid-bind carries the same identity/auth the seed-bind would (session_id custody \u2014 kin REQ-PSYCHE-SID-CUSTODY / the sid-symmetric-auth pattern). Files: api-listen bind path (sid-fallback seam), clap --session-id flag (plain doc-comment). hertz/perri live-verify; if it lands the adapter skill drops the re-seed step.",
    "doc_a": "",
    "doc_b": "Seed lifetime.** The seed lives **in the daemon's memory only** \u2014 no file \u2014 and survives until exactly one of: a successful `listen` bind consumes it, a newer `seed` for the same pid overwrites it, or the daemon process restarts (which drops the whole map). Nothing re-fires it until the harness's **next SessionStart. So an adapter must not rely on the seed for a session that goes live late (hours after SessionStart) or after a daemon restart \u2014 that is what `listen --session-id` (below) is for.",
    "score": null,
    "reasoning": null
  },
  {
    "index": 78,
    "req_a": "REQ-HAZARD-HOSTED-LIVENESS-RECONCILE",
    "req_b": "REQ-SPAWN-COLLISION-GUARD-LIVE-DUP",
    "title_a": "B2 KEYSTONE: a daemon-hosted (spt-hosted) endpoint's info.json status is RECONCILED to real liveness, not left latched online. The broker exit-waiter (broker.rs:889-910) reaps its in-mem session table + emits ExitEvent but NEVER touches info.json; lifecycle::mark_offline only fires on Psyche teardown \u2014 so a dead/exited harness (operator closed the tab) stays status=online forever (is_perch_alive returns ONLINE for daemon-hosted, liveness.rs:80-93). FIX (doyle ruled PULL-PRIMARY \u2014 the live-status analog of REQ-HAZARD-ROSTER-GHOST): the livehost reconcile loop (reconcile_once livehost.rs:226-313) queries the broker's live session set (KIND_SESSIONS) each tick and, for any status=online live_agent perch PAST the boot grace whose endpoint has NO live broker session, marks it offline (lifecycle::mark_offline \u2192 status=offline \u2192 is_perch_alive=false). GATED on spt-hosted (controllable==Some(true)) so a HARNESS-HOSTED relay live agent (api listen, legitimately online with no broker session) is NEVER mis-marked. Crash-robust + self-healing on the next tick (clear-on-event is not crash-robust alone). PUSH (brain ExitEvent\u2192mark_offline) is an OPTIONAL fast-path only if the daemon brain is reliably subscribed to all hosted sessions; correctness rides the pull. Broker stays stateless (ADR-0004 \u00a7B \u2014 brain owns the info.json write). (v0.12.0)",
    "title_b": "W4 (LIFECYCLE-TRUTH): single-flight wake per endpoint \u2014 the WAKE/RESUME respawn seam must not launch twice for one wake. ROOT (perri parentage + recovered filing): one wake processed TWICE within 1s \u2014 broker (306368) spawned two identical `launch --cli ccs --id flynn --resume <sid>` 1s apart, both survived; check-then-spawn TOCTOU in the spawn-side guard. DAMAGE: duplicate-perch writers STOMP info.json (the duplicate's compact re-stamped an OLD sid over a fresh /clear rotation -> injects routed to the contended record and lost). FIX: single-flight wake per endpoint (claim on the perch record or broker-side in-flight set keyed by id; second wake within the window = no-op ack), and the spawn path re-checks liveness UNDER the claim. Int: two concurrent wake requests -> exactly one launch tree.",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 79,
    "req_a": "REQ-HAZARD-INFO-JSON-TORN-READ",
    "req_b": "REQ-MANIFEST-4",
    "title_a": "State-file reads tolerate concurrent writes (1.2)",
    "title_b": "Keyword hints \u2014 [[hints]] {keywords (literal/regex), text}; spt api hint --session emits at most one matched hint per message, once per session (seen-set), declaration-order first match; profiles overlay [[hints]] by leaf-replace",
    "doc_a": "",
    "doc_b": "keyword hints** (ratified 2026-06-12 \u2014 core milestone A): Once-per-session usage/syntax hints, a first-class adapter feature: the manifest's `[hints]` section declares entries of `{keywords (literal default, regex opt-in), text}`; the adapter's user-prompt hook pipes the **full user message** to `spt api hint --session <id>` (stdin) and receives matched hint lines (`keyword hint for SPT adapter <name>: \"<kw>\"-->{text}`) for its context-injection channel. The daemon keeps a per-session seen-set \u2014 each hint fires **once per session** (a `/clear` mints a new session, naturally re-arming) \u2014 and em",
    "score": null,
    "reasoning": null
  },
  {
    "index": 80,
    "req_a": "REQ-HAZARD-INJECT-SETTLE-REARM",
    "req_b": "REQ-HAZARD-REGISTRY-CONCURRENT",
    "title_a": "post-0.29.0 (KNOWN-HAZARDS 7.37): the Layer-1 settle-gate must RE-ARM before every delivery on an OBSERVABLE (echoing/interactive) PTY \u2014 a mid-session reader reattach re-creates the head-swallow window. ROOT (field-confirmed on 0.29.0, doyle diagnosis + perri screenshot): the shipped W5-A settle-gate (REQ-INJECT-MULTILINE-INTEGRITY) gated Layer 1 behind a worker-local ONE-SHOT (`settled_once`) on the false premise that the head-swallow race is STARTUP-only (reader not attached after spawn). A mid-session `/clear` re-enters the harness's raw-mode input reader, re-creating the pre-settle window \u2014 but the one-shot already fired at spawn, so `settle_before_inject` is SKIPPED and the head is eaten again (a checkpoint-wake payload injected right after `/clear` lost its head, mid-path `spt/Cargo.toml)`); echo-verify (Layer 2) is default-OFF for that session, so it is silent + unrecoverable. FIX (doyle ruling): re-settle before EVERY delivery on an observable PTY; latch-skip the steady-state settle ONLY where the probe is UNOBSERVABLE (non-echoing ConPTY \u2014 no reader-reattach race to guard, and each settle burns the full deadline). The settle's own bool return (observed vs timed-out) discriminates the class; a re-drive (attempt>1) ALWAYS settles. `settled_once: bool` one-shot \u2192 `probe_unobservable: bool` latch driven by the first-attempt settle outcome.",
    "title_b": "Concurrent SQLite openers (registry/spool) must not fail with 'database is locked' (4.7)",
    "doc_a": "7.37 The Layer-1 settle-gate must RE-ARM per delivery on an observable PTY \u2014 a mid-session reader reattach (`/clear`) re-creates the head-swallow race `[REQ-HAZARD-INJECT-SETTLE-REARM]` Failure (paid-for, field-CONFIRMED on 0.29.0 \u2014 doyle diagnosis + perri screenshot `WindowsTerminal_6iSjya8pMt.png`):** the shipped W5-A settle-gate (7-of-W5, `REQ-INJECT-MULTILINE-INTEGRITY`) gated Layer 1 behind a worker-local ONE-SHOT (`settled_once`) on the premise that the head-swallow race is a STARTUP condition (the input reader not yet attached after spawn). That premise is FALSE: a mid-session `/clear` ",
    "doc_b": "4.7 Concurrent SQLite openers must not fail with \"database is locked\" Failure:** two endpoints on one machine open the same SQLite store at once (e.g. two `ReadyAgent::start` calls registering simultaneously) and one fails outright with `SQLITE_BUSY` / \"database is locked\" \u2192 spurious registration/spool failure. Surfaced as a parallel-test flake in `two_agents_exchange_message_tcp_and_spool`, but the bug is real concurrency, not test-only. Invariant:** `busy_timeout` is set **before** any lock-taking statement on every connection. Switching `journal_mode=WAL` takes a brief exclusive lock; with ",
    "score": null,
    "reasoning": null
  },
  {
    "index": 81,
    "req_a": "REQ-HAZARD-LISTEN-ORPHAN",
    "req_b": "REQ-MSG-INJECT-LEG-DROP-VISIBLE",
    "title_a": "W4 (LIFECYCLE-TRUTH, KNOWN-HAZARDS): `api listen --parent-pid N` watches parent liveness and exits loud on parent death. ROOT (mobile-gw RCA): --parent-pid is auth-anchor ONLY \u2014 no liveness watch; host death orphans the listener forever -> perch held alive (false ONLINE), EVENTs stream to a dead stdout, dead-owner rebind BLOCKED (recorded pid = the live orphan). FIX: listener watches --parent-pid liveness (Windows: job object or poll; Unix: PDEATHSIG or poll) and exits loud on parent death. flynn's job-object guard (spt-mobile side) stays regardless; filed SPT-CORE-NEEDS \u00a75. Unit: parent-death -> listener exits within one poll window.",
    "title_b": "SEED (inactive \u2014 observability): a silently-dropped delivery leg must be DISTINGUISHABLE from an honestly-offline endpoint on a status surface. The spt-hosted inject leg (spt-daemon inject.rs `try_spt_hosted_inject` \u2014 the ONE shared implementation behind local cmd_send, the WAN ingress, the idle-edge drain, and the parked-idle/pulse re-offer belts) gates on `is_spt_hosted_no_relay` \u2192 `deliver::is_online` \u2192 `liveness::is_perch_alive`: a perch PINNED TO A DEAD SESSION (the KH 7.25 wedge class \u2014 dead owner, record not yet healed by the next auth touch) makes EVERY belt on that leg silently return None \u2192 messages spool as if the endpoint were ordinarily offline, while the operator-facing view can keep reading the recorded state. Nothing anywhere surfaces 'the inject leg stopped firing for this endpoint' \u2014 the idle-window injections just stop, which from the outside is indistinguishable from 'no messages arrived' (field shape: IDLE-EDGE W1 field-verify, perri's killed-resident rig \u2014 their 'so it did not repeat' conclusion was exactly this invisibility, self-corrected only by re-rigging with a live resident). Shape at activation: a status/list surface DERIVES and reports the inject-leg verdict per endpoint (inject-target / not-a-target + WHY: no perch, dead session pin, no relay, not controllable) so a dropped leg is a READABLE state, never an inferred one; additive-evolution posture on any --json surface. Kin: REQ-SHELL-LIST-DERIVED-PROVENANCE (view-vs-truth, same class), KH 7.25 (`REQ-HAZARD-SESSION-PIN-WEDGE`, the pin that drops the leg), KH 2.5 (liveness authority lives in ONE resolver \u2014 the verdict here REPORTS through it, never re-derives beside it).",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 82,
    "req_a": "REQ-HAZARD-LIVEHOST-BOOT-RACE",
    "req_b": "REQ-HAZARD-RESTART-IDEMPOTENT",
    "title_a": "The brain's daemon-hosted Psyche lifecycle surfaces a host-FAILURE on the live perch (harness-diagnosable) and runs net-INDEPENDENTLY. When reconcile_once\u2192host_one\u2192spawn_psyche fails for a state=live_agent+status=online endpoint (e.g. the adapter's psyche binary absent from its install dir, REQ-INSTALL-11), the failure MUST be written to the perch info.json as a CURRENT-STATE field (reason + ts + attempt count; overwritten each 5s retry, CLEARED on successful host) and surfaced by `spt endpoint list`/status \u2014 never left as an eprintln on the brain's invisible stderr where a harness reading only perch state is blind. status=online stays authoritative (agent reachable; only the Psyche is missing \u2014 brain-restart rehydrate legitimately has online-without-Psyche windows), so this is a SEPARATE psyche-host-health field, never a status de-stamp. Net-independence is a locked-in invariant: spawn_live_host (brainproc.rs:230) reaches the reconcile and hosts the Psyche on a net-less/unpaired/peer-pump-STALLED node, proven by a REAL detached-daemon E2E (real broker\u2192brain-child, real api seed+listen, real install-dir psyche binary). spt-core SURFACES the failure; the adapter owns fixing its packaging.",
    "title_b": "Idempotent/exactly-once delivery across brain restart at every broker boundary (codex #14)",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 83,
    "req_a": "REQ-HAZARD-LIVEHOST-NONRESIDENT",
    "req_b": "REQ-SPOOL-TAKE-AUDIT",
    "title_a": "A daemon-hosted Psyche that spawns then EXITS IMMEDIATELY is a host failure, surfaced like a spawn failure (closes the v0.8.1 residual masking): the REQ-HAZARD-LIVEHOST-BOOT-RACE signal stamps `psyche_host_error` only when `spawn_psyche` returns Err, NOT when the detached spawn() returns Ok but the child dies within moments (e.g. a bad-argv child exiting 2 \u2014 the F-009 case). That leaves the residual 'online + no Psyche + no cause' gap: the nested `{id}-psyche` info.json is written status=online with a real-but-DEAD pid and the PARENT perch carries NO psyche_host_error (perri's F-010: tasklist showed 0 host procs across the window while info.json read online). The host MUST confirm RESIDENCY \u2014 a hosted child not alive (or whose `{id}-psyche` perch never re-registers / has a dead pid) within N seconds of spawn is treated as a host failure: stamp the parent perch `psyche_host_error{reason:\"host not resident within <n>s (psyche perch missing/dead pid)\"}` (and do not leave a phantom online nested perch). Closes the last masking gap the v0.8.1 fix left open. perri's F-010 (v0.8.1 dogfood). Sibling of REQ-HAZARD-LIVEHOST-BOOT-RACE.",
    "title_b": "W5 (LIFECYCLE-TRUTH, RCA cost: proving WHO took delivered=1 rows burned an hour): the spool records the taker per row \u2014 leg enum (relay-backlog / hook-poll / idle-inject / psyche) + sid/pid + taken_at ms \u2014 surfaced by a --json debug read. Additive column, no schema break (delivered rows already retained).",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 84,
    "req_a": "REQ-HAZARD-PSYCHE-OUTBOUND-PROXY",
    "req_b": "REQ-HAZARD-WORKER-PATH",
    "title_a": "Psyche outbound captured + sanitized: the live-Psyche turn driver captures stdout (never Stdio::null), and the daemon strips/re-stamps Psyche-supplied from=/target and constrains routing (reply\u2192__REPLY_TO__ sender, notify\u2192own user/subnet) (7.3)",
    "title_b": "Single source of truth for Worker/Psyche perch location (1.5)",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 85,
    "req_a": "REQ-HAZARD-PSYCHE-RESIDENCY-EXPECTATION",
    "req_b": "REQ-WORKER-PICKER-EXCLUDED",
    "title_a": "W3 (F030 hazard; paid-for: hall-bf churn ordinal 6491+ + adapter v0.13.2 bad-ship brick 2026-07-04): a psyche failure of ANY shape must NOT remove or alter the parent endpoint's ready/hosted state, and hosting must NOT churn-respawn. The v0.13.2 shim-exit tripped the residency machinery (confirm_residency_or_unhost) which tore down the endpoint's hosted state \u2014 ready marker removed, never re-stamped, every force-native gated leg=cli-gate-not-hosted PERMANENTLY (field brick). FIX: residency machinery retires with the resident child; the teardown that touches parent hosted state is DELETED \u2014 psyche trouble stamps psyche fields only. REQ-HAZARD-LIVEHOST-NONRESIDENT's spirit transfers to the W1 failure budget (its entry gets a SUPERSEDED pointer here, LIVENESS-DECAY\u2192SUPERSEDED pattern from C-1). Conformance int = the hall-bf shape: multi-subnet home, live endpoint, failing psyche \u2192 parent stays deliverable, no rehost churn, error stamped (the wave's heart).",
    "title_b": "V-2 (WORKER-TRUTH triage, operator rider): non-drivable endpoint classes never render as `spt endpoint run` picker rows \u2014 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 \u2014 same non-drivable family) at every picker source leg, extend-not-multiply for future non-drivable classes.",
    "doc_a": "7.30 A Psyche failure of ANY shape must NEVER remove or alter the parent endpoint's ready/hosted state `[REQ-HAZARD-PSYCHE-RESIDENCY-EXPECTATION]` Failure (paid-for, field brick 2026-07-04, adapter v0.13.2):** the pre-F-030 model kept a **resident** Psyche process the daemon supervised, with residency machinery (`confirm_residency_or_unhost`) that **un-hosted the parent endpoint** when the resident child went missing. A bad adapter ship (v0.13.2) made the psyche shim exit on every turn; the residency machinery read that as a lost resident and **tore down the parent's hosted state \u2014 the ready m",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 86,
    "req_a": "REQ-HAZARD-RC-ATTACH-TRUTH",
    "req_b": "REQ-HAZARD-RC-EOF",
    "title_a": "RC-RENDER-TRUTH W1 (KNOWN-HAZARDS 7.46 \u2014 umbrella conformance seam for ADR-0042): an rc surface answers from live session authority, never a stale persisted projection; a resuming perch is UNBOUND, not offline. Regression matrix from the hertz RCAs + operator field recovery: offline-row-over-honest-session attaches; offline-no-session refuses; zombie refuses/reaps never attaches; resume-never-bound reads UNBOUND and is attachable; harness-only refuses truthfully pre-stream; qualified targets attach with bare wire id. HEAVY nextest group at birth for any leg spawning a daemon tree (standing CI lint). Gate: int \u2014 the matrix; doc \u2014 KNOWN-HAZARDS 7.46.",
    "title_b": "A severed broker stream during a live rc session surfaces GRACEFULLY, never as a raw io error that crashes the PTY. The rc read-loop (rc.rs:352-362) continues only on WouldBlock/TimedOut; ANY other read_event_until error \u2014 including UnexpectedEof 'failed to fill whole buffer' \u2014 returns Err \u2192 RC_FAIL \u2192 the PTY 'crashes' from the user's view. Confirmed trigger: a deliberate `spt daemon stop` (broker bounce) severs an active rc (perri stopped the daemon to release owlery watch handles). Same severed-broker-stream EOF class as the v0.9.1 seed fix (seed_fail_message) and the listener-death case \u2014 spt-core must classify a broker-gone EOF and (a) surface a CLEAR actionable message ('daemon stopped/restarted \u2014 re-run / reconnect'), never the raw buffer error, and ideally (b) AUTO-REATTACH to the same session on the fresh broker (the broker is the daemon-lifetime anchor; it returns on the next `spt api` call). FOLD two side-observations: (1) `spt daemon stop` SILENTLY drops active rc/live sessions \u2014 warn ('N active session(s) will drop') or graceful-detach on stop; (2) the daemon holds owlery WATCH HANDLES on perch dirs so a torn-down perch dir stays 'Device busy' until a full daemon stop releases them (perri's rt-* cleanup) \u2014 a torn-down perch's handle should release without a daemon stop. doyle Finding C, root-caused. (post-v0.10.0)",
    "doc_a": "7.46 An rc surface answers from live session authority, never a stale persisted projection \u2014 and a resuming perch is UNBOUND, not offline `[REQ-HAZARD-RC-ATTACH-TRUTH]` Failure (paid-for, hertz perri contradiction RCA 2026-07-17/18 + operator field recovery):** the broker hosted an honest live session (client tree alive, `SessionProbe::has_live_session_honest` = true) while the persisted perch row said `offline` \u2014 the resumed harness sat wedged at a failed native resume, SessionStart never fired, the perch never bound, inbound messages black-holed. `spt rc` trusted the offline row and refused ",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 87,
    "req_a": "REQ-HAZARD-REDISPATCH-STALL",
    "req_b": "REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK",
    "title_a": "REDISPATCH-STALL W1 (KNOWN-HAZARDS 7.43, hertz v0.34 field RCA 2026-07-16 \u2014 recurrent 20-30s PTY/RC freezes, 17-62s DISPATCH tails): one wedged stream subscriber must NEVER stall stream serving, and recovery machinery must not manufacture new replay victims. Today: claim retries x the broad Err(_) opener fallback (dispatch.rs:414) install throwaway peek subscribers whose StreamLog::attach replays the entire retained ring UNDER the per-stream mutex with discarded write errors and the poisoned subscriber left installed \u2014 serial 15s bounded-write poison windows (33 observed, all 15,000-15,154ms) composing into the field stalls. Gate: int \u2014 production-path regression at the REAL run_dispatch_loop + StreamLog + serve_attach seams: wedge one subscriber conn, prove producer appends and unrelated streams stay flat while the poisoned subscriber is removed and the stream recovers (no abandonment); doc \u2014 KNOWN-HAZARDS 7.43. Binding: redispatch D1/D1b stay green every leg. HEAVY nextest group at birth. Kin REQ-STREAMLOG-SUBSCRIBER-DISCIPLINE + REQ-DISPATCH-FALLBACK-CIRCUIT (the mechanisms), REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK (the deadline that fires), ADR-0038 Amendment.",
    "title_b": "UPDATE-WEDGE round-4 (hertz RCA, root reproduced + source-pinned on Windows 2026-07-09): EVERY write on a physical broker connection rides ONE bounded + cancelable + poison-on-failure framed-write primitive \u2014 no writer may hold the connection's serialized send gate across an UNBOUNDED OS write. ROOT: controller_writer held the SharedSend = Arc<Mutex<SendHalf>> guard ACROSS a blocking write_frame; on Windows interprocess 2.4.2 routes the send to WriteFileEx + SleepEx(INFINITE, alertable) with NO supported write timeout (set_timeout \u2192 Unsupported), so an `rc --take` controller consumer that stops reading blocks the write INDEFINITELY (~127.95 s in the field capture, released only when a brain restart tore the conns down). Logical stall_evict_controller (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE) removes the controller ROLE but neither cancels the in-flight pipe write, closes the physical connection, nor invokes CancelIoEx \u2014 the detached writer keeps its SharedSend clone + live stack-owned mutex guard. Load-gated: needs a real seq>0 frame + a non-draining consumer (seq-0 boot conns + a quiescent update are clean \u2014 why v0.30.5's controlled apply passed); CTRL_WRITE_LOCKED wait_us=0 on all four capture writers exonerates the mutex convoy \u2014 the block is INSIDE the OS write, after lock acquisition. Existing deadlines can't bound it: CONTROLLER_WRITE_DEADLINE is output-driven channel-full handling (a writer blocked on its first owned frame never fills the channel); BRAIN_WRITE_DEADLINE is an age PREDICATE sampled opportunistically, not an I/O timer. FIX (accepted shape, doyle GO + 3 confirms): broker-owned Arc conn object (conn.rs BrokerConn) replacing raw Arc<Mutex<SendHalf>> \u2014 serialized write gate (bounded gate-wait) + send half (never leaves the object, so a bypass cannot compile) + idempotent poisoned state + per-in-flight op identity + platform abort seam, with an INDEPENDENT per-conn watchdog firing OUT OF BAND at an ABSOLUTE deadline stamped at write entry covering BOTH gate-wait and OS write completion (brain_write_deadline() \u2014 the existing SPT_BRAIN_WRITE_DEADLINE_MS knob; the abort NEVER relies on the write returning or on opportunistic stall-evict sampling). On deadline/partial/cancel/unknown completion: (1) poison the whole physical conn, (2) abort read+write (cfg(windows) CancelIoEx then DisconnectNamedPipe; cfg(unix) UnixStream::shutdown(Both)) so handle_conn reaches existing EOF cleanup, (3) wait for the canceled op to report completion before releasing its buffer (interprocess write_exsync returns only after the completion APC \u2014 release = write return), (4) NEVER reuse the conn (a timed-out length-prefixed frame may be partially written), (5) join/finish the retired writer before reporting physical cleanup. Controller (replay + live), viewer, dispatch-reply (send_frame/send_error + inline dispatch_* replies), and nethost stream-log/presence writes ALL route through the primitive \u2014 leaving ANY raw unbounded write behind the gate preserves the failure class (grep-proven: no surviving raw send.lock()+write_frame on a physical conn path). NO new output queue (the bounded queue + isolated writer exist; the block is BELOW them); NO PIPE_NOWAIT (recorded mid-frame corruption risk). Broker-side only, no wire change, mixed-version peers wire-compatible; Unix keeps existing semantics under the same poison/retire invariant.",
    "doc_a": "7.43 One wedged stream subscriber must NEVER stall stream serving \u2014 replay halts at the first failed write, a poisoned subscriber is removed, and recovery machinery must not manufacture new replay victims `[REQ-HAZARD-REDISPATCH-STALL]` Failure (paid-for, hertz field RCA 2026-07-16 \u2014 live v0.34 boxes, recurrent 20\u201330s PTY/RC freezes, DISPATCH tails 17\u201362s):** a COMPOSITION, not one new timer. The dispatcher's retryable claims (500ms/1s \u00d73) re-drove the opener fallback whose guard arm is a broad `Err(_)` (`dispatch.rs:414` \u2014 comment intends old-broker-only; catches transport timeout/EOF/poison)",
    "doc_b": "7.38 EVERY write on a physical broker connection is bounded + cancelable + poison-on-failure \u2014 no writer holds the send gate across an UNBOUNDED OS write `[REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]` Failure (paid-for, field capture + deterministic Windows repro 2026-07-09 \u2014 hertz RCA, UPDATE-WEDGE round-4):** `controller_writer` held the `SharedSend = Arc<Mutex<SendHalf>>` guard ACROSS a blocking `write_frame`. On Windows, `interprocess 2.4.2` routes the send to `WriteFileEx` + `SleepEx(INFINITE, alertable)` \u2014 this local-socket send path exposes **no supported write timeout** (`set_t",
    "score": null,
    "reasoning": null
  },
  {
    "index": 88,
    "req_a": "REQ-HAZARD-RENDER-LIFECYCLE",
    "req_b": "REQ-SCREENGRID-REPAINT-MODE-REPLAY",
    "title_a": "RC-RENDER-TRUTH W3 (KNOWN-HAZARDS 7.47 \u2014 umbrella conformance seam for ADR-0043): the physical terminal is never mutated or terminated outside its renderer's ordered state model. Regression matrix from the hertz RCA: output-before-exit through the production path; unconditional display teardown across every rc exit class; TUI baseline reconstructs whole after out-of-band mutation (recording backend); repaint replays tracked modes. Deferred P2 seeds recorded, NOT this milestone: Exit{after_seq} watermark defense; per-client semantic baseline (only if spt ever transforms live frames). Gate: int \u2014 the matrix; doc \u2014 KNOWN-HAZARDS 7.47.",
    "title_b": "RC-RENDER-TRUTH W3 (ADR-0043 decision 4, hertz stale-glyphs RCA leg 4 P1): ScreenGrid cold repaint replays EVERY tracked render-affecting mode \u2014 DECSTBM scroll margins at minimum \u2014 before final cursor placement (today render_repaint omits tracked margins, so client and server grids interpret subsequent raw scrolling against different regions => 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 \u2014 tracked-mode replay in render_repaint; unit \u2014 repaint emits tracked DECSTBM, emulator contract holds for scroll-after-repaint; doc \u2014 ADR-0043.",
    "doc_a": "7.47 The physical terminal is never mutated or terminated outside its renderer's ordered state model \u2014 output before exit, owned baselines, unconditional teardown `[REQ-HAZARD-RENDER-LIFECYCLE]` Failure (paid-for, hertz stale-glyphs RCA 2026-07-18, all legs doyle seam-verified):** four render-lifecycle defects presenting as \"missing whitespace\"/stale glyphs. (a) The broker exit waiter direct-writes `KIND_EXIT` via `all_sinks()` while PTY output queues through `controller_writer` \u2014 the per-conn gate serializes bytes but not producer order, so Exit overtakes the child's final erase/SGR-reset/`?1",
    "doc_b": "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 \u2014 producer order is the contract. Output-before-Exit is a production-path invariant, regression-proven end-to-end (broker \u2192 attach \u2192 rc). 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 including errors and unwind: best-e",
    "score": null,
    "reasoning": null
  },
  {
    "index": 89,
    "req_a": "REQ-HAZARD-ROLLBACK-STATE-COMPAT",
    "req_b": "REQ-UPDATE-APPLY-RESTART-NOTICE",
    "title_a": "A brain must not irreversibly migrate durable state before update ready-promotion: the readiness-gated auto-rollback (ADR-0018 Q7) spawns the N-1 binary against durable state the new brain may have written, so every pre-ready write must stay N-1-readable (schema migrations gated behind ready-promotion, or written N-1-tolerant/additive). Else the first in-place schema migration silently bricks rollback (KNOWN-HAZARDS 6.8). Free now \u2014 a 2026-06-09 audit confirmed zero state-migration code exists; unmintable retroactively once a migration ships.",
    "title_b": "`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) \u2014 name the user-visible CONSEQUENCE ('daemon-coordinated features run the previous version until the daemon restarts'), not the broker/brain internals. Composes with REQ-UPDATE-RUNNING-IMAGE-SURFACE (the notice tells the user what the version-surface will then show, and how to clear it). (F-025)",
    "doc_a": "6.8 No irreversible durable-state migration before update ready-promotion `[REQ-HAZARD-ROLLBACK-STATE-COMPAT]` Failure:** the readiness-gated auto-rollback (ADR-0018 Q7) spawns the *previous* binary against durable state the *new* brain already wrote. The first release that migrates a durable-state schema in place would make the old binary unable to read it \u2014 silently bricking rollback exactly when it is needed (a logic-bricking update that can no longer fall back). Invariant:** a brain must not irreversibly migrate durable state before it is ready-promoted; equivalently, every pre-ready write",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 90,
    "req_a": "REQ-HAZARD-SESSION-PIN-WEDGE",
    "req_b": "REQ-SEAM-UPDATE",
    "title_a": "A perch PINNED to a DEAD session-id self-heals instead of wedging forever. authenticate() (spt/src/api/auth.rs:78) gates api poll/state/boundary on proof-sid == info.json.session_id; if ONE boundary rotation is lost (transient env corruption kills the /clear-era hook), the perch stays pinned to the dead sid and EVERY id-scoped hook call refuses \u2014 INCLUDING boundary itself (it presents the new sid), a permanent strand (ready:false, stale .idle, drain no-ops, WAN spool sleeps forever; AUTH_REFUSED is stderr-only = invisible inside a hook). FIX: authenticate() gains a DEAD-OWNER fallback \u2014 when the sid MISMATCHES AND the perch's recorded pid is dead (proc::is_process_alive==false), ACCEPT the caller's sid and RE-PIN (rotate session_id + log SESSION_REPIN loud). Same trust model as establish_perch's conflict gate (api/startup.rs:207-210), which already allows rebind exactly when owner_alive==false (an orphaned perch accepts a new LOCAL owner). A LIVE-owner mismatch STILL refuses (squat protection UNCHANGED). ADDITIVE to token auth \u2014 the existing token-auth recovery path is UNTOUCHED; the new branch fires only on (no token) AND (sid mismatch) AND (owner dead). COVERAGE SPLIT (explicit, perri clean-room 2026-07-02 \u2014 the wedge latches on /clear even in a CLEAN env, so the corruption domino was sufficient but NOT necessary): the dead-owner re-pin rescues CRASHED/DEAD sessions ONLY; a LIVE-pid rotation (/clear, /compact \u2014 same process, new sid) is CORRECTLY refused without the departed session's prior-sid proof and MUST NOT be widened to live owners. The live-rotation contract is adapter-side: the adapter PERSISTS the prior sid across rotation and PRESENTS it as boundary proof (perri's state-file pattern = the reference); a silent boundary skip on an unresolvable id, or a boundary call with NO auth proof, strands the perch (perri's court). Core rescues only the dead-owner orphan; live-rotation proof is the harness-contract's job (see the harness-contract boundary section, which cross-refs this hazard). PARKED (not this wave, logged): pid-ancestry self-proving rotation (core walks the caller's real ancestry vs info.json.pid) \u2014 needs an ADR + Windows parent-spoof caveats. (F-024C, F024C-AUTHWEDGE-ADDENDUM + F024D-DOCSCOPE)",
    "title_b": "Adapter-update avenue (file-pull / delegated command)",
    "doc_a": "7.25 A perch PINNED to a DEAD session self-heals (dead-owner re-pin) instead of wedging forever; a LIVE-owner rotation still refuses `[REQ-HAZARD-SESSION-PIN-WEDGE]` Failure (F-024C/F-024D, ENLYZEAM field + clean-room repro 2026-07-02):** `authenticate()` (auth.rs) gates `api poll`/`state`/`boundary` on `proof.session_id == info.json.session_id`. If ONE boundary rotation is LOST \u2014 the departing session dies (crash / tab-close) or its `/clear`-era `boundary` call never lands \u2014 the perch stays PINNED to the dead sid, and every id-scoped hook call thereafter AUTH_REFUSES *including `boundary` its",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 91,
    "req_a": "REQ-HAZARD-SHELL-STALE-ONLINE",
    "req_b": "REQ-PRESENCE-LIVENESS-TRUTH",
    "title_a": "A shell instance's ONLINE-ness is DERIVED (recorded status AND its recorded `shell.pid` not provably dead), never the recorded `status` field alone \u2014 an abruptly-dead binary (force-kill, crash, OOM: no link-break, so `close_shell`'s offline flip never runs) must not read online forever. The shell-side twin of REQ-HAZARD-DAEMON-HOSTED-LIVENESS, which gave AGENT perches exactly this resolver and which shells never got. PROVABLY DEAD is the narrow discriminant: `shell.pid` present AND parses non-zero AND `!is_process_alive` \u2014 pid absent, unparseable, or 0 (a broker-hosted spawn whose backend exposed no pid records 0) reads ALIVE, the same interim-parity/fail-toward-alive stance `liveness.rs` already holds, so a pid-less backend is NEVER falsely declared dead. Recycled-pid caveat, accepted at mint: a reused pid reads alive, so the heal is missed, never mis-fired \u2014 the failure direction is 'stays stale', never 'kills a live instance'. SITE CLASSIFICATION IS PART OF THE REQUIREMENT (authoritative cfg(test)-excluded census at mint = 8 status reads, 3 classes \u2014 do NOT blanket-swap the predicate): (a) DERIVED \u2014 relink's already-online refusal (the gate that made recovery impossible), the `shell cmd` wake-if-offline arm (which silently spooled to a corpse), the drive drop-if-offline branch, `shelldisc::discover` (the single source of BOTH `shell list` renders, text + --json), and the activity fan-out's online filter; (b) RAW status, deliberately \u2014 `bind_shell_by_token`/`close_shell` (the WRITERS) and `cascade_owner_edge`'s suspend-close arm, where routing through the resolver would SKIP the close that is itself the cleanup, removing a heal path; (c) RAW status, pending an operator ruling \u2014 the `shellwake::reconcile_once` watcher-eligibility read: making it liveness-aware would let a dead persistent instance's watcher relaunch the binary spontaneously (~1 tick), which is the correct crash self-heal but the WRONG mid-deploy behavior on Windows, where an operator kills the process precisely to free the exe for overwrite and spt-core would re-lock it under them (flynn's forcing case: shared install dir \u21d2 'kill the process' is a routine deploy step). This req therefore delivers NO spontaneous relaunch: recovery is demand-driven (an unblocked `relink`, or a `shell cmd` that wakes) and `shell list` tells the truth. LOCALITY SYMMETRY (field-caught by flynn's leg-2 run, 2026-07-25): 'a shell cmd that wakes' holds for BOTH the local CLI cmd and the cross-node serve \u2014 at mint the wake arm lived in the SHELL_LINK_CMD serve handler only (module docs scoped wake-if-offline to remote cmd), so a LOCAL cmd against a corpse spooled silently and nothing woke, the exact accepted-happily/drained-by-nobody shape this req exists to kill; the arm is now the shared `linkhost::wake_if_offline_persistent`, called by both, and the derived status READ inside it is the same single census site as before (the census of 8 reads/3 classes is unchanged \u2014 the read moved, it did not multiply). ROTATION CARRIES THE DURABLE CHANNEL (flynn's frame-loss field catch, 2026-07-25, both trials): a spooled command frame is MAC-stamped under the link token current at SPOOL time, and the drain is a raw destructive passthrough \u2014 the shell verifies against its CURRENT key \u2014 so a relink's token mint orphaned every pending frame: drained to the woken binary, failed verify, correctly discarded BY THE ADAPTER, lost permanently; the wake-triggering command itself was the frame the wake lost (#23 armed the wake and never answered; #24/#19 spooled after rotation and drained in order). The fix is three-layered (todlando's A1/A2 race+crash hazards addressed at mint): (1) the token mint re-stamps all pending rows old-key\u2192new-key (idempotent, crash-partial converges \u2014 an old-stamped remainder is converted by the next rotation or drain); (2) the rotated-out token is STASHED (`link.token.retired`, never a live credential \u2014 bind resolves only the parked file) so (3) the drain paths give any race straggler a second-chance re-stamp at delivery (`restamp_for_drain`), while frames verifying under neither key pass through untouched for the adapter to refuse exactly as before. Rows are selected by MAC verification against the threaded new key, never by token-snapshot equality (the D-2 class). MOCK CONFORMANCE IS A PROPERTY OF THE RIG, not a patch (todlando A4): mock-shell MUST verify inbound frame MACs exactly as the public contract demands of a real adapter and drop failures loudly \u2014 a mock that accepts what the field rejects is a broken rig, and that exact divergence (credulous mock) is how the frame loss passed the e2e while failing in the field. The adapter-side discard behavior is CORRECT and must never be softened to paper over the spool side. Relink additionally probes LOCALLY rather than trusting a daemon sweep, so recovery holds with the daemon down. RECOVERY PRESERVES CONSUMER STATE (the property consumers actually depend on, flynn 2026-07-25): 'same canonical id, same perch' exists so that state a consumer PERSISTED IN THE PERCH survives the recovery \u2014 an adapter's repo binding, a scanner's cursor. The teardown+spawn workaround destroyed exactly that, and its worst cost was SILENT, not the rename: alchemy's tag cursor re-baselines at the digest tip, so tags written between the kill and the re-bind are never scanned \u2014 not failed-and-retried, just never seen. A loud failure gets retried; a silent one does not. Gate: int \u2014 force-kill a bound persistent instance's process, then prove (1) `shell list` reads offline, (2) `relink` succeeds instead of SHELL_ALREADY_ONLINE and the SAME canonical id + perch survive, carrying perch-persisted consumer state with them (no teardown+spawn, no id churn, no re-baselined cursor), (3) no spontaneous relaunch occurs while the instance sits dead and undriven. FIELD VERDICT \u2014 PASS END-TO-END (flynn, alchemy-0, delivered 2026-07-26; v0.43.0, counter 77): every gate leg held in the field, on a record the pre-fix code had already poisoned. (1) `shell list` read OFFLINE while the on-disk info.json still said status=online \u2014 the daemon DERIVED offline from the corpse pid (29036 absent from the process table); the record was never corrected and did not need to be. (2) relink ADMITTED, no SHELL_ALREADY_ONLINE \u2014 verbatim SHELL_RELINKED:alchemy-0 owner=flynn pid=38644 status=offline, list online thereafter. (3) identity/state integrity: same canonical id, same owner, same perch; repo binding byte-identical (token_provenance=gh-cli); armed=true preserved; no spontaneous relaunch across the ~6.5h dead window. CURSOR SCOPING, ruled at closure (doyle 2026-07-26) \u2014 the one non-byte-identical field: the tag cursor moved 206158430541\u2192210453397553 (gen:seq 48:333\u219249:49) because the recovery crossed a DAEMON restart that slid the retained digest window past the armed cursor. NOT a gap and NOT this req's property failing: spt-core raised after_predates_window and the consumer took its specced armed-cursor branch (alchemy REQ-TAG-SCANNER missed-rows leg \u2014 warn the owner, never silently re-baseline an armed cursor; alchemy src/tags.rs, two unit tests) \u2014 the FIRST FIELD FIRING of a path previously exercised only by unit tests. The 'no re-baselined cursor' gate property is hereby scoped: byte-identity holds across SHELL death within one daemon generation (the prior LEG 5 measurement, one daemon instance, uncontradicted \u2014 different scenario, generation never moved); across a daemon restart that slides the window the contract is DETECT-AND-ANNOUNCE, not preserve \u2014 corroborated independently by a second record's cursor decomposing under the same gen:seq scheme (4294967486 = 1:190). Practical exposure in this event: nil (every agent stood down through the gap; no tag traffic to miss). SECOND SPECIMEN HELD, not spent (doyle ruling 2026-07-26): liam's record \u2014 precondition verified by flynn (pid 35464 a CLEAN corpse, not recycled, so the accepted-at-mint recycled-pid caveat does not apply; same poisoned status=online shape; binding intact; cursor armed at 4294967486) \u2014 is deliberately NOT relinked while its owner is absent: the admit gate is already field-proven, a relink measurably re-baselines the cursor across a daemon-restart boundary, and liam's is the only untouched specimen with the longer (~8.5h) gap \u2014 spending it would destroy the cursor datum liam's own return produces, to buy a redundant second sample of a gate already proven to admit. It waits for liam. SPECIMEN HARVESTED (liam's own return, 2026-07-26 \u2014 operator-authorized endpoint start executed by doyle; pre-state snapshotted by doyle AND independently re-verified on disk by liam before touching anything): (1) relink ADMITTED at the ~8.5h gap \u2014 verbatim SHELL_RELINKED:alchemy-0 owner=liam pid=47608 status=offline (online at bind); the poisoned record did not refuse; info.json online is now TRUE. (2) integrity FULL PASS: id unchanged (no new mint slot), perch onlined at bind, repo binding byte-identical (token_provenance gh-cli), and the ROTATION-CARRIES-THE-DURABLE-CHANNEL machinery observed live in the field for the first time \u2014 link.token.retired present beside the fresh mint, and a post-relink round trip proven end-to-end (shell cmd spooled -> engine executed -> reply home on the owner's perch), not merely an outbound spool. (3) cursor, the sharpest datum of the harvest: BYTE-IDENTICAL through the relink itself (4294967486 armed at pid-change instant, doyle's file watch) \u2014 the relink preserves; the SCANNER moves. First scan re-baselined 1:190 -> 2:0 silently, and liam PROVED no-fire-by-precondition rather than assuming: the daemon restart bumped the generation but EVICTED NOTHING (window floor 4294967464 < cursor 4294967486, every gen-1 row past the cursor still retained), so after_predates_window is correctly false at every link of the chain (filter_after floor test -> emit-only-when-true -> unwrap_or(false)) and the missed-rows announce had no precondition to fire on. Flynn's earlier gen-bump reading is refined by this: a restart alone does not slide the window; flynn's announce fired because HIS window had genuinely slid. Scope note carried: a valid negative \u2014 the fix's announce chain verified link-by-link to contract on a specimen where it correctly stayed quiet. RESIDUE from the harvest (liam's find, alchemy lane, no core REQ): permanently-unsealable turns from a dark session are jumped by scanner cursor advance with no possible missed-rows notice (a generation bump is not a slide); ruled scanner-side announce for input-bearing null-seq jumps, pseudo-turns jump silently by design; core's per-turn input/input_seq/partial already suffice to discriminate.",
    "title_b": "A node's gossiped per-endpoint registry Status reflects real liveness, so a remote viewer never paints a DEAD endpoint as ONLINE. ROOT (confirmed + gated vs CONTEXT.md): registryhost.rs:397-405 advertises a NOT-alive perch as Status::Dormant (the `else` of is_perch_alive), re-stamped every gossip round (never ages to Offline). Design intent GATED vs resting.rs + CONTEXT.md: active/dormant is the MULTI-INSTANCE routing differentiator (active = the bare-id routing target; dormant = a WARM non-target sibling \u2014 'driving ling@laptop makes ling@desktop dormant', resting.rs:97; transitions active\u2192dormant on AttentionShift/Detach). suspended = COLD (session closed, resumable-on-wake) while its NODE is UP. offline = NODE DOWN \u2014 NEVER self-gossiped (RestState has no Offline; a live node only ever gossips active/dormant/suspended), remote-inferred via epoch-lease eviction. So labeling a NOT-running perch Dormant is the DEFECT (dormant requires warm/running). PRIMARY FIX (registryhost `else`, not-bound-alive): live-but-UNBOUND (has a live broker session; is_perch_alive is bound-gated) \u2192 Active/Dormant (still warm); else (cold, no live session, but its node is up because this very daemon is gossiping) \u2192 SUSPENDED \u2014 NOT Dormant, NOT Offline (the node is UP; Offline is never self-gossiped). This alone removes the false-ONLINE. dormant keeps gossiping (routing/MRA needs it) but RENDERS as its online flavor (no distinct glyph; the dormant\u2192suspended auto-suspend timer disambiguates recency). The DISPLAY of these states (incl Suspended=gray-filled) is REQ-SUBNET-DISPLAY-PARITY. Design: docs/design/subnet-presence-display.md \u00a7A. (next milestone)",
    "doc_a": "| # | Invariant | spt-core surface | |---|---|---| | 1.1 | Grace wait precedes INIT_SIGNOFF | daemon teardown | | 1.4/4.4 | Deferred rows excluded from event-stream drain | daemon spool drain | | 2.1/5.1 | Stable PID/broker-handle over ephemeral PID | liveness detection | | 2.3 | Handoff argv/IPC version-tolerant (newer brain \u2194 older broker) | broker\u2194brain IPC, self-update | | 2.4 | gen_start = now() on cold-start + handoff | per-instance generation | | 2.6 | A shell's ONLINE-ness is DERIVED (recorded status AND a not-provably-dead `shell.pid`) \u2014 an abruptly-killed binary breaks no link, so `c",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 92,
    "req_a": "REQ-HAZARD-SPOOL-SENTINEL-CREATE-FAIL",
    "req_b": "REQ-LIVE-AGENT-NO-INJECT-DELIVERY",
    "title_a": "A persistent failure to (re)create the spool has-messages sentinel is SURFACED, never silently swallowed. touch_has_messages (spt-store/src/spool.rs:147) does `let _ = File::create(...)` \u2014 a live field defect on ENLYZEAM left a stale .has-messages (2026-06-29) beside a fresh spool.db insert (06:59:17Z) in ONE directory, i.e. the create silently failed while rows accumulated (suspected read-only-attrib / share-lock). FIX: on File::create failure emit a LOUD-ONCE-per-perch stderr diagnostic naming the concrete io::Error (self-identifying regardless of kind); do NOT make it fatal (spool writes still proceed). (F-024C item 2, doyle)",
    "title_b": "F-033 RE-SCOPED (doyle re-ruling 2026-07-10 after the #82 gate falsified the original premise \u2014 the hosting-mode class split is BINDING context): 'state:live_agent has a self-delivery reader' CONFLATED two hosting modes. (A) HARNESS-hosted live agents (api listen path) DO deliver via adapter channels only \u2014 and are ALREADY structurally excluded from inject: bind_from_seed stamps controllable=Some(false), no broker PTY exists. (B) SPT-HOSTED/CONTROLLED live agents' inject leg IS their delivery reader (broker PTY + translation binary \u2014 doyle's own endpoint is field proof: ENDPOINT_INJECT + IDLE_PARKED_DRAIN alongside hook-poll); the original blanket state:live_agent exclusion broke REQ-MSG-IDLE-EDGE-DRAIN + the v0.14.3 LAW on both CI platforms and was REVERTED (predicate stays controllable-gated). perri's adapter-channels-only model (F-dupmsg-adapter-confirm.md) holds for class (A) only \u2014 do not re-seed the conflation. The F-033 DUPLICATE mechanism (hook-poll + idle-inject both delivering one row, operator spool row 156) is closed structurally by REQ-CARRIER-CLAIM-EXCLUSIVE's atomic cross-carrier take. REMAINING LEGS OF THIS REQ: (a) unit \u2014 a harness-hosted live agent (controllable Some(false)/None, no broker PTY) can never route through try_spt_hosted_inject (evidence may TAG the existing is_spt_hosted_no_relay non-controllable case rather than duplicate it); (b) VERIFICATION (report-before-fix, DELIVERED to doyle 2026-07-10): the no-translation-binary raw payload+CR path is PROVABLY DEAD (broker dispatch_endpoint_input: no-binary -> loud spool, never a PTY write \u2014 v0.14.3 holds); the operator's typed-unsubmitted garbage is PINNED to the Layer-2 echo-verify RE-DRIVE (broker.rs inject worker) force-enabled host-wide by ambient SPT_INJECT_VERIFY_ECHO in the daemon's inherited dev-shell env (default-OFF declared capability turned on globally \u2014 the F-036 env-inheritance class): a false verify-miss RETYPES the whole sequence into the input field. Fix LANDED on the W4 branch (doyle-accepted echo-scrub 2026-07-10): SPT_INJECT_VERIFY_ECHO/SPT_INJECT_FORCE_ECHO_MISS folded into the W1 daemon-startup env scrub (spt_runtime::INJECT_ECHO_ENV_VARS; startup-only, role-spawn builder untouched so explicit per-spawn declaration stays the production on-switch) \u2014 evidence rides REQ-HAZARD-DAEMON-IDENTITY-ENV-SANITIZE (the F-036 class REQ). Residual-class RULED (doyle 2026-07-10): this REQ covers the harness-hosted-never-inject predicate leg (unit) + the echo-scrub itself (impl, dual-tagged) \u2014 stages [impl,unit].",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 93,
    "req_a": "REQ-HAZARD-TEMPLATE-ARGV-FILL",
    "req_b": "REQ-SESSION-ADAPTER-RECORDED",
    "title_a": "Command-template substitution fills argv ELEMENTS, not a re-tokenized string: spt-core currently `fill_template`s {key} values INTO the command STRING and THEN `tokenize`s the filled string (runtime.rs:94/122), so a multi-word {key} value whitespace-SPLITS into multiple argv tokens unless the adapter hand-quotes the placeholder, and a value containing a `\"` (or `;`) injects/breaks tokenization (shell-injection-adjacent). A filled value MUST become exactly ONE argv element regardless of spaces/quotes in the value. Fix: tokenize the TEMPLATE into argv FIRST, then `fill_template` EACH token, so a `{key}` slot resolves to a single element and the value never participates in tokenization (no whitespace-split, no quote/semicolon injection); preserve the missing-key / empty-command errors and `{{`/`}}` non-interpretation. perri's F-009 (v0.8.1 dogfood, argv-capture-confirmed): a multi-word `{psyche_prompt}` = \"PSYCHE REVIVAL time: epoch-ms:\u2026 incoming event: (none)\" arrived as argv[6..12] (7 stray tokens), the harness runner strict-parsed `--prompt` against the 2nd word, exited 2 within ~1s \u2192 phantom hosted perch. Applies to EVERY [session.<role>] template (psyche_init, extractor, notif, \u2026); digest survives today only because its fills ({session_id}/{source}) are single-token.",
    "title_b": "D-2 (REMOTE-TRUTH triage \u00a7D-2 + operator Q5 @c248afc): the session ledger records the adapter[:profile] a session ran under, so a later resume can restore the harness the session actually used (not merely the endpoint's CURRENT stamp). ROOT: SessionEntry (spt-store/sessions.rs:58) carries ts/session_id/trigger/cwd/ordinal but NOT the adapter \u2014 a resume-from-history row cannot know which harness authored the transcript, so a resume under a since-changed endpoint adapter (B-2 ChangeAdapter, or a fork) launches the wrong harness. FIX: an ADDITIVE `adapter: Option<String>` on SessionEntry, exact cwd/ordinal serde pattern (#[serde(default, skip_serializing_if=\"Option::is_none\")]) \u2014 a pre-migration row missing the key deserializes None; None omits the key on serialize (byte-identical to old rows); an unknown key on an old reader is ignored (serde default) \u2014 back-compat BOTH directions. Stamped at every PRODUCTION session-boundary append. CENSUS (doyle-confirmed @94f0205, corrects the triage-era 5-site drift to the real 3): startup.rs:317 (live bind boot row, rec.adapter in scope), reporting.rs:94 (boundary rotation row UNDER the mutate_info lock, capture adapter_for_ledger=rec.adapter beside cwd_for_ledger), ready.rs:119 in crates/spt-msg (ready-agent boot row, rec.adapter in scope). NOT digest.rs:601 (cfg(test) fixture) and NOT a livehost psyche-ledger append (none exists \u2014 the live /clear|/compact boundary shells `api boundary` \u2192 reporting.rs:94, the SAME append). None-stamp is a benign degrade (resume falls back to the endpoint's current adapter).",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 94,
    "req_a": "REQ-HAZARD-UNHOST-PSYCHE-REAP",
    "req_b": "REQ-PSYCHE-TURN-STREAM-EVIDENCE",
    "title_a": "On un-host, the detached `{id}-psyche` HARNESS PROCESS is reaped \u2014 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 \u2192 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 \u2014 headless harness session, its own perch) \u2014 the fix does NOT move it in-brain; it SCOPED-kills the `{id}-psyche` pid on un-host (never machine-wide \u2014 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 \u2192 offline \u2192 reconcile un-host \u2192 reap) and B2/B5 (the offline arms that trigger un-host). (v0.12.0)",
    "title_b": "A failed psyche turn preserves BOTH captured streams as evidence: TurnError::Failed carries the child's stdout alongside stderr, and the failure display appends a bounded single-line stdout TAIL (last ~500 bytes, UTF-8-boundary-safe cut, newlines collapsed, the literal <EMPTY> when the stream said nothing \u2014 absence stated, never implied by a missing field). WHY (2026-07-26 spend-limit RCA): a failed turn's stdout is not psyche output, so BOTH core (turn.rs kept {status_code, stderr} only) and the adapter (guarding its outbound channel) independently discarded it \u2014 the same blind spot implemented twice \u2014 and an account-level outage surfaced as a bare 'claude exited exit code: 1' with the decisive refusal text thrown away at two layers; three agents then chain-hypothesized on an error string the real failing path never emitted. The tail is cause-agnostic instrumentation: it does not care what the failure is, which is why it survives being wrong about it. Adapter twin: claude-spt v0.25.14 dual-stream tail (shipped 2026-07-26).",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 95,
    "req_a": "REQ-HAZARD-VIEWER-RING-ROLL-SNAP",
    "req_b": "REQ-KICK-1",
    "title_a": "A read-only rc --view VIEWER whose serving brain falls behind the live ring under a hard flood and receives a FORWARD Output seq gap (the ring rolled frames out between reads, BEFORE any channel-overflow eviction \u2192 NO KIND_VIEWER_EVICTED marker) must SNAP TO LIVE (accept-and-advance via dedup-below + snap-above), NOT fatal with output gap (brain.rs:624/628 legacy reject-gap). ROOT (v0.13.0 forkpty, post-b4+skip-to-live): serve_attach subscribes a viewer via brain.attach_as(Viewer) leaving session_cursors EMPTY \u2192 the viewer serve-brain uses the LEGACY reject-gap \u2192 a PRE-eviction ring-roll forward-gap FATALS read_event \u2192 serve_attach returns \u2192 forwarding stops \u2192 attach_received_pty_output=FALSE (a_journaled / p0_paste / attach.rs:1071 wedged_viewer, Linux forkpty; Windows ConPTY floods slower \u2192 MASKED false-green). DISTINCT from REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT (the POST-eviction re-subscribe-from-floor): this is PRE-eviction gap-tolerance while STILL subscribed. VIEWER-only \u2192 B2-SAFE (a viewer never advances delivered_through / is not authoritative); the CONTROLLER keeps strict reject-gap (exactly-once resume). FIX: arm snap-above at initial viewer attach (attach_as_viewer_snap = attach_as(Viewer) + session_cursors.insert(session_id, from_seq)); the two viewer-survival mechanisms COMPOSE \u2014 this tolerates pre-eviction ring-roll gaps, REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT recovers post-eviction. (v0.13.0)",
    "title_b": "Explicit, loud controller displacement: `spt rc kick <target>` / `--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 \u2192 Control, so it can drive a free endpoint but CANNOT `--take` \u2014 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 \u2014 no elevated kick policy). The picker surfaces 'Kick <node> and attach' (Take) only on a controlled (blue \u25a0) endpoint, via the existing attach dispatch (single-bringup-path: intent is a parameter).",
    "doc_a": "BUILT (M12 W2.5).** The controller/viewer model is implemented end-to-end. Attach intent is **three-valued** (`AttachIntent = Viewer | Control | Take`, wire-default `Control`): `Control` to a FREE endpoint becomes controller; `Control` to a CONTROLLED endpoint is **refused with guidance** (`--view` to watch, `--take` to control) \u2014 never auto-viewer, never silent-displace; `Take` (`spt rc --take` / picker \"Kick\") kicks the incumbent with a **loud `Displaced{by}` notice** and full detach (not demote). The broker's per-session `OutputLog` is the fan-out hub: ONE authoritative **controller** (adva",
    "doc_b": "BUILT (M12 W2.5).** The controller/viewer model is implemented end-to-end. Attach intent is **three-valued** (`AttachIntent = Viewer | Control | Take`, wire-default `Control`): `Control` to a FREE endpoint becomes controller; `Control` to a CONTROLLED endpoint is **refused with guidance** (`--view` to watch, `--take` to control) \u2014 never auto-viewer, never silent-displace; `Take` (`spt rc --take` / picker \"Kick\") kicks the incumbent with a **loud `Displaced{by}` notice** and full detach (not demote). The broker's per-session `OutputLog` is the fan-out hub: ONE authoritative **controller** (adva",
    "score": null,
    "reasoning": null
  },
  {
    "index": 96,
    "req_a": "REQ-HAZARD-WORKER-PATH",
    "req_b": "REQ-READY-AGENT-RESUME",
    "title_a": "Single source of truth for Worker/Psyche perch location (1.5)",
    "title_b": "An offline ReadyAgent shows in `spt endpoint run`'s picker Resume-from-history and resumes correctly \u2014 closing the gap that today only LiveAgents do. ROOT: a harness-hosted ready bind (ReadyAgent::start_homed, ready.rs) writes info.json DIRECTLY and never appends the session ledger (unlike the shared establish_perch:250 live path), so a ready agent \u2014 though it has a session_id \u2014 produces ZERO ledger rows \u2192 the picker's offline+local Resume-from-history (which gates on ledger rows) never offers it. FIX (1): ledger the ready bind (ReadyAgent::start_homed \u2192 sessions::append Boot, mirroring establish_perch). FIX (2): `spt endpoint run --resume <session>` honors the adapter MANIFEST's endpoint TYPE \u2014 a ReadyAgent manifest (no [session.psyche_init]) resumes as a ready endpoint (poll listener, NO psyche-host); a LiveAgent (with psyche_init) as live. NO new bringup mode + NO picker changes (operator 2026-06-18): `spt endpoint run` is the spt-hosted ENDPOINT bringup for BOTH types, the type IS the adapter-manifest's concern (psyche-host already keys on psyche_init presence) \u2014 so (2) likely already holds; VERIFY at code, build only the residual. (v0.12.0)",
    "doc_a": "",
    "doc_b": "`spt endpoint run` is the spt-hosted bringup for BOTH endpoint types** (v0.12.0): The bringup core is **type-agnostic** \u2014 the endpoint TYPE is the adapter manifest's concern, not a separate bringup mode. A manifest declaring `[session.psyche_init]` brings up a **LiveAgent** (the daemon reconcile hosts its Psyche); a manifest *without it brings up a **ReadyAgent** (a poll listener, no Psyche \u2014 see *ReadyAgent* and the harness-hosted ready bind at the *seed + bind-time resolution* note above). No `--adapter`/picker branch distinguishes them: the daemon live-host reconcile hosts only a perch whos",
    "score": null,
    "reasoning": null
  },
  {
    "index": 97,
    "req_a": "REQ-HOST-RUN-1",
    "req_b": "REQ-WORKER-SID-SYMMETRIC-AUTH",
    "title_a": "spt-hosted harness bringup: `spt endpoint run` spawns an adapter's `[session.self]` command template into a broker-held PTY (the spawn-session seam, brain.rs spawn_session_pid \u2014 same broker path shellhost.rs launch_shell_brokered_in uses for shells, now for kind=\"harness\" self-role), registers the perch under the given endpoint id, returns the id. Reverses today's harness-hosted-only launch (external launcher \u2192 `api bind`). Non-interactive flag set (--adapter <a[:profile]> --id <id> --create --resume <session> --attach|--start|--view) covers every terminal action of the W2 interactive picker so shortcuts (cc-<id>) bake fully non-interactive launches; composite adapter:profile resolves via registry::resolve_option leaf-replace overlay.",
    "title_b": "W-2 (WORKER-TRUTH triage, operator-ruled 2026-07-06): worker verbs go sid-symmetric with every sibling id-scoped verb \u2014 worker-start mints NO token and worker-stop takes NONE (token custody is undue adapter burden, ruling via perri). Registration STORES the sid it authenticated (the parent's sid at start; today cmd_worker_start hardcodes session_id=\"\" \u2014 worker.rs:44 \u2014 so a sid-authed stop compares against empty and refuses 100%). Stop accepts the parent's CURRENT sid OR the stored registration sid (a /clear between start and stop rotates the parent's sid; either rotation endpoint is honest custody \u2014 the REQ-PSYCHE-SID-CUSTODY rotation reasoning). Under the ruling the field adapter's existing emission (worker-stop <id> --session-id <parent sid>) becomes contract-correct as-is. Publish the frozen verb shape to the docs-site with the landing wave (perri blind-builds from published docs).",
    "doc_a": "",
    "doc_b": "`api worker-stop <id> --session-id <sid>` \u00b7 `api worker-poll <id> --session-id <sid>`",
    "score": null,
    "reasoning": null
  },
  {
    "index": 98,
    "req_a": "REQ-INST-1",
    "req_b": "REQ-MIGRATE-1",
    "title_a": "endpoint ID vs instance split (adapter-agnostic ID)",
    "title_b": "Auto-detect and migrate a legacy claude_skill_owl install",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 99,
    "req_a": "REQ-INST-11",
    "req_b": "REQ-PUMP-PEER-ISOLATION",
    "title_a": "spt rename <id> rippled to all instances (collision-checked, 6.5-reconciled)",
    "title_b": "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 -> peer N+1 waits behind peer N); (2) WHOLE-ROUND POISON (peer_outcome(...)? -- one TimedOut aborts the ENTIRE round via ? -> 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 advances every round; no PEER_PUMP_RESTART from a dead peer. Depends on W1 (a fast-failing dial is the precondition for clean per-peer scheduling).",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 100,
    "req_a": "REQ-INST-14",
    "req_b": "REQ-INSTALL-12",
    "title_a": "Resource advertisement (subnet resource registry): free-text blurb, both-authored, registry projection, visibility/whitelist-gated",
    "title_b": "Durable active-profile pointer for bind-time profile selection (ADR-0021): adapters/active-profiles.toml at the registry ROOT (sibling to the per-adapter <name>/ dirs, so adapter add/update/remove \u2014 which only rewrite a <name>/ subdir \u2014 can never clobber it), a flat host_binary \u2192 \"adapter[:profile]\" map. Read at bind as the PRIMARY profile selector; unset \u2192 the registered_at_ms fallback (REQ-START-5). Written ONLY by `spt adapter use <adapter>[:profile]` (resolves the adapter's host_binaries \u2192 sets each binary\u2192adapter[:profile]); `spt adapter use --clear <adapter|binary>` drops. NEVER auto-written by install/update/adapter add (that is precisely what would let an update silently flip the active profile). A stale pointer (uninstalled adapter / deleted profile) self-heals: ignored, fall back, warn once. Pruned on adapter remove. Atomic write (spt_store atomic). (v0.9.0)",
    "doc_a": "resource advertisement (subnet resource registry)**: A per-endpoint **free-text blurb** describing the services/functions the endpoint can serve \u2014 an agent **yellow-pages** for service discovery, distinct from *capability declaration* (machine-readable, which endpoint *types* a node hosts) and from *endpoint visibility* (whether it's addressable at all). **Both-authored + mutable:** config seeds a default; the agent refines its own at runtime (`spt endpoint description set \u2026`). It is **not a separate registry** \u2014 it is a field on the endpoint record and a **projection** of the subnet registry ",
    "doc_b": "Bind-time adapter/profile resolution (ADR-0021).** Because the seed is adapter-agnostic, `listen`/`poll` resolve the owning adapter/profile when they bind, as a pure read \u2014 never a seed-time snapshot that could drift. `--adapter <name[:profile]>` is an **optional override** on the `api` group (an explicit choice for adapter dev/iteration); omitted, resolution runs: 1. the seed's `parent_pid` \u2192 that process's **executable basename** (case-insensitive, `.exe`-stripped) 2. **candidate adapters** = registered `kind=\"harness\"` adapters whose **`host_binaries`** (the manifest match-key) contains tha",
    "score": null,
    "reasoning": null
  },
  {
    "index": 101,
    "req_a": "REQ-INST-2",
    "req_b": "REQ-WORKER-SID-SYMMETRIC-AUTH",
    "title_a": "Per-node files, synced Psyche mind",
    "title_b": "W-2 (WORKER-TRUTH triage, operator-ruled 2026-07-06): worker verbs go sid-symmetric with every sibling id-scoped verb \u2014 worker-start mints NO token and worker-stop takes NONE (token custody is undue adapter burden, ruling via perri). Registration STORES the sid it authenticated (the parent's sid at start; today cmd_worker_start hardcodes session_id=\"\" \u2014 worker.rs:44 \u2014 so a sid-authed stop compares against empty and refuses 100%). Stop accepts the parent's CURRENT sid OR the stored registration sid (a /clear between start and stop rotates the parent's sid; either rotation endpoint is honest custody \u2014 the REQ-PSYCHE-SID-CUSTODY rotation reasoning). Under the ruling the field adapter's existing emission (worker-stop <id> --session-id <parent sid>) becomes contract-correct as-is. Publish the frozen verb shape to the docs-site with the landing wave (perri blind-builds from published docs).",
    "doc_a": "",
    "doc_b": "`api worker-stop <id> --session-id <sid>` \u00b7 `api worker-poll <id> --session-id <sid>`",
    "score": null,
    "reasoning": null
  },
  {
    "index": 102,
    "req_a": "REQ-INST-5",
    "req_b": "REQ-INSTALL-BOOTSTRAP-VERB",
    "title_a": "Two-tier context sync (live to all, project to same-project)",
    "title_b": "THE-FORKENING W1 (ADR-0036 \u00a73, bootstrap shape b operator-ruled): virgin-box install = `gh release download` the platform binary + ONE self-install verb in the binary itself \u2014 the verb places the binary at the canonical install path (the path self-update already respawns from, v0.4.2 lesson), registers user PATH, and leaves first-run identity/daemon-start to the existing idempotent first-run; hosted one-liner install scripts (curl|sh / irm|iex at the dead Pages URL) are RETIRED. Non-interactive (CONTEXT.md Installation: the install path doubles as every adapter's pack-in on-demand install). Windows UAC-740 gotcha binding: the downloaded exe keeps the `spt-*` asset name and the verb lives INSIDE spt \u2014 no installer-detection trigger words in exe names. README (bs-core) documents: install gh -> gh auth login -> gh release download -> the verb. Gate: unit \u2014 verb places/registers idempotently, refuses cross-platform binaries (platform-stamp check exists, v0.3.2); int \u2014 from a clean SPT_HOME+PATH sim, downloaded-binary self-install yields a working `spt` on PATH whose `spt update fetch` then speaks the gh channel; doc \u2014 README install section rewritten. Kin REQ-UPDATE-GH-TRANSPORT, REQ-INSTALL-1/2 (the two-paths model this reshapes), ADR-0036.",
    "doc_a": "",
    "doc_b": "Installer form (gh bootstrap, ADR-0036):** install gh \u2192 `gh auth login` (org membership) \u2192 `gh release download` the platform binary from the private channel \u2192 one **self-install verb** in the binary places it at the canonical install path and registers the *user* PATH (so adapters call `spt api \u2026` cross-OS); first-run identity gen + daemon start stay the existing idempotent unattended first-run. Hosted one-liner scripts are retired with the public channel; first-fetch trust = gh's authenticated TLS + org membership (full ed25519 verification is `spt update`'s job thereafter). The downloaded e",
    "score": null,
    "reasoning": null
  },
  {
    "index": 103,
    "req_a": "REQ-INST-9",
    "req_b": "REQ-INSTALL-7",
    "title_a": "Multi-subnet membership (same-user N subnets; cross-user seam)",
    "title_b": "Windows inbound reachability: the elevated install leg registers the inbound-UDP firewall rule (New-NetFirewallRule); the daemon self-detects blocked inbound and renders it as the no-connection state in subnet status + the coming-online banner (covers user-scope installs that skip the elevated leg \u2014 never a silent NO_SEED_HOLDER dead-end) (M8 root cause 3)",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 104,
    "req_a": "REQ-INSTALL-12",
    "req_b": "REQ-INSTALL-5",
    "title_a": "Durable active-profile pointer for bind-time profile selection (ADR-0021): adapters/active-profiles.toml at the registry ROOT (sibling to the per-adapter <name>/ dirs, so adapter add/update/remove \u2014 which only rewrite a <name>/ subdir \u2014 can never clobber it), a flat host_binary \u2192 \"adapter[:profile]\" map. Read at bind as the PRIMARY profile selector; unset \u2192 the registered_at_ms fallback (REQ-START-5). Written ONLY by `spt adapter use <adapter>[:profile]` (resolves the adapter's host_binaries \u2192 sets each binary\u2192adapter[:profile]); `spt adapter use --clear <adapter|binary>` drops. NEVER auto-written by install/update/adapter add (that is precisely what would let an update silently flip the active profile). A stale pointer (uninstalled adapter / deleted profile) self-heals: ignored, fall back, warn once. Pruned on adapter remove. Atomic write (spt_store atomic). (v0.9.0)",
    "title_b": "Non-interactive install path: the install path doubles as every adapter's pack-in on-demand install (no second mechanism); sha256-verified fetch; user-PATH registration. HISTORY: 'the canonical one-liner' \u2014 since THE-FORKENING (ADR-0036) the canonical path is gh + the spt install verb (itself non-interactive, REQ-INSTALL-BOOTSTRAP-VERB); the scripts this REQ's evidence attests remain as the hermetic CI fixture + air-gap fallback, still non-interactive by construction (doyle-ratified 2026-07-14).",
    "doc_a": "Bind-time adapter/profile resolution (ADR-0021).** Because the seed is adapter-agnostic, `listen`/`poll` resolve the owning adapter/profile when they bind, as a pure read \u2014 never a seed-time snapshot that could drift. `--adapter <name[:profile]>` is an **optional override** on the `api` group (an explicit choice for adapter dev/iteration); omitted, resolution runs: 1. the seed's `parent_pid` \u2192 that process's **executable basename** (case-insensitive, `.exe`-stripped) 2. **candidate adapters** = registered `kind=\"harness\"` adapters whose **`host_binaries`** (the manifest match-key) contains tha",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 105,
    "req_a": "REQ-INSTALL-3",
    "req_b": "REQ-PICKER-ADAPTER-DESCRIPTION",
    "title_a": "Idempotent + interactive-optional first run",
    "title_b": "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 \u2014 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)",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 106,
    "req_a": "REQ-INSTALL-8",
    "req_b": "REQ-RELEASE-MUSL-ARTIFACT",
    "title_a": "OS-service registration (REQ-INSTALL-1's deferred third leg): Linux systemd USER service + loginctl enable-linger (linger rides the elevated install leg; daemon starts at boot pre-login, user universe per KH 5.7, systemctl --user managed); Windows scheduled task at-logon (interactive session, no stored credentials); a node is reachable after reboot without any manual spt invocation (M8 decision 17)",
    "title_b": "MUSL-TIER W3 (CI build + signed release + update-set publish + self-update E2E): release.yml gains a musl matrix entry (build on kitsubito; install musl-tools+cmake+target in-job, CC_x86_64_unknown_linux_musl=musl-gcc); the assemble job includes spt-x86_64-linux-musl in SHA256SUMS + the release upload; release-publish (xtask) signs the musl artifact; the update-set carries its artifact entry. This closes the field gap: a musl binary today fetches fine but ends UPDATE_FETCH_REJECTED:NoArtifactForPlatform('unknown'). Gate (release-pipeline touch -> real E2E): cut a draft/test release with the musl artifact; a static musl binary on a sub-2.39-glibc box runs spt update fetch -> gets the musl artifact (no NoArtifactForPlatform), verifies SHA256+signature over the musl bytes, applies, self-updates. musl is ADDITIVE \u2014 gnu stays the default Linux artifact.",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 107,
    "req_a": "REQ-JOIN-TWO-PHASE",
    "req_b": "REQ-SUBNET-ADMIN-SEED-REPLICATION",
    "title_a": "`spt subnet join` is two-phase (meet-before-code) so the entered code is FRESH at the ceremony regardless of discovery time (ADR-0030). The meet selector is the PUBLIC `(subnet-name, TOTP-epoch)` (rendezvous_token = SHA256(domain \u2016 name \u2016 totp_step)); the secret TOTP-code is the SPAKE2 password ONLY, never a discovery input \u2014 so the code is collected AFTER a member is found. Extend the brain.pair_join event stream (brain.rs:1009): CLI PairMeetReq{subnet} \u2192 daemon meets (name, current-epoch) resolving the seed-holder's REAL stable pairing address \u2192 MetMember event \u2192 CLI prompts the code (cli.rs cmd_subnet_join :6236) \u2192 PairCodeSubmit{code} \u2192 daemon dials the held real-address on SPT_PAIR_ALPN + SPAKE2 \u2192 PairJoined/PairFail. Daemon holds the real-address between phases, bounded by a 5-MINUTE wait-for-code timeout; a wrong-code retry re-runs the CEREMONY ONLY against the held address (no re-search). The non-interactive `--code` path stays one-shot (no prompt; relies on REQ-NET-FAMILY-GATE fast discovery, fails loudly per REQ-JOIN-DIAGNOSTICS on staleness). Security unchanged \u2014 the meet is pre-trust/unauthenticated (SPT_PAIR_MEET_ALPN); auth stays in SPAKE2. (next milestone)",
    "title_b": "Every member node holds BOTH subnet seeds \u2014 the admin seed replicates at join exactly as the member seed does \u2014 because empower verification and admin-code joins are LOCAL operations any member node must be able to serve without consulting a privileged peer (ADR-0051 decision 2; the mesh stays symmetric, authority rides a key rather than a node). Carries the NO-REVEAL invariant, which is the load-bearing half: there is no admin show-code, no QR re-provision, no `--json` field, no debug/diagnostic/error/log rendering of the admin seed ANYWHERE \u2014 it is displayed exactly once, at creation, and thereafter no node can leak what no node can reveal. A lost authenticator entry is irrecoverable by design (ADR-0051 decision 3): re-mint the subnet. The invariant binds tests too \u2014 a test that asserts the seed's VALUE by printing it is itself the leak (secret-probe discipline: never expand a secret into an observable sink). Gate: doc \u2014 ADR-0051 decisions 2 and 3; impl \u2014 admin-seed replication on the join path and the absence of every reveal surface; unit \u2014 a joined node holds both seeds and can verify an admin code locally, and no CLI/JSON/diagnostic output path emits the admin seed.",
    "doc_a": "Robust WAN subnet join: meet-before-code + per-family bind gate",
    "doc_b": "2. Held everywhere, revealable nowhere / 3. Irrecoverable by design",
    "score": null,
    "reasoning": null
  },
  {
    "index": 108,
    "req_a": "REQ-LISTEN-SEED-CONSUME-AFTER-BIND",
    "req_b": "REQ-PSYCHE-SID-CUSTODY",
    "title_a": "F-034 leg b (perri/hertz field finding 2026-07-09, hertz's HEADLINE): `api listen` must NOT consume the consume-once ephemeral seed on a PRE-BIND refusal \u2014 validate (adapter resolvable, home/subnet) and BIND first, THEN consume the seed. ROOT: today `api listen` burns the consume-once seed BEFORE it validates home/subnet, so on a multi-subnet node HOME_REFUSED (needs --subnet) fires AFTER the seed is already gone \u2192 the corrected retry (adding --subnet) on the SAME pid hits NO_SEED, a dead end (plausibly ADAPTER_UNRESOLVED burns it the same way). A refusal that never bound must leave the seed intact for the corrected retry. Same EFFECT-BEFORE-IRREVERSIBLE-CONSUME ordering class as F-032 (commune commit-before-delete) \u2014 the irreversible consume must follow the successful effect, never precede a refusal. Gate: a pre-bind refusal (HOME_REFUSED on a multi-subnet node without --subnet; ADAPTER_UNRESOLVED) leaves the seed CONSUMABLE \u2014 the corrected retry on the same pid binds (no NO_SEED); a SUCCESSFUL bind still consumes the seed exactly once (no double-bind). Files: the api-listen bind path (seed consume ordering). Kin F-032 [[spt-core-findings-backlog]].",
    "title_b": "W2 (F030, design \u00a73): the psyche mints and keeps its OWN session id, stored in the nested {id}-psyche perch record \u2014 {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 \u2014 the custody bug). Parent boundary (/clear, /compact) does NOT rotate the psyche sid (the psyche's conversational thread survives parent resets \u2014 its job). resume_psyche validates the custody key before spawn (resume.rs:183). Reseed path: psyche session lost/invalid \u2192 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} \u2014 never aliased. Red-first: parent `api boundary clear` \u2192 nested perch sid UNCHANGED (today it is the parent's \u2014 guard-revert reproduces).",
    "doc_a": "Recoverable refusals do not consume the seed.** The seed is consumed by a successful bind** \u2014 or by a refusal that proves the seed itself dead (see spend-vs-restore below). A recoverable refusal that never bound \u2014 `HOME_REFUSED` on a multi-subnet node without `--subnet`, `ADAPTER_UNRESOLVED`, a live-perch conflict \u2014 leaves the seed consumable, so the corrected retry on the same pid binds instead of dead-ending on `NO_SEED`. (Effect before irreversible consume: the destructive step follows the successful effect, never a recoverable refusal.)",
    "doc_b": "Custody sid \u2014 `{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 `<parent>-psyche` perch record \u2014 **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 takes the **explicit** `{parent_session_id}` key \u2014 never an alias of `{session_id}`.",
    "score": null,
    "reasoning": null
  },
  {
    "index": 109,
    "req_a": "REQ-LIVENESS-ORACLE-SOUND",
    "req_b": "REQ-PICKER-WINDOW-TITLE",
    "title_a": "TEARDOWN-AUTHORITY W2 (todlando W1 gate-round-0 finding, doyle-scoped from the LANDED W1 code 2026-07-19): 'does this pid still exist' has ONE answer in spt-core and it is derived from the OS process table. TODAY spt-daemon/src/broker.rs session_is_zombie computes wrapper_alive from spt_store::proc::is_process_alive, which probes OpenProcess on Windows \u2014 and OpenProcess keeps SUCCEEDING for a TERMINATED process while any parent holds an open handle, which the broker ALWAYS does (Arc<PtySession>) for every PTY child it spawned. A correctly-reaped harness therefore reads ALIVE, flipping zombie_verdict off its PRIMARY class (Some(false) = dead root + surviving record = always a zombie) onto the conditional arm, which additionally demands adapter_labeled && past_grace && !has_live_descendants. CONSEQUENCE, live today: a dead-root session that is NOT adapter-labeled is claimed LIVE indefinitely \u2014 `endpoint run`'s dup-guard refuses ENDPOINT_ALREADY_LIVE over an already-dead tree and cmd_rest's Suspend alive_hint forces from=alive on the same false claim (both via has_live_session_honest, cli.rs:2014 and :3805). REQ-ENDPOINT-CYCLE-HONEST exists to give the cycle verbs ONE liveness authority; after W1 there are TWO and they disagree by construction (teardown.rs::root_provably_gone asks the table and is right; session_is_zombie asks is_process_alive and is wrong). THE DISCRIMINATOR (binding, and it makes the audit checkable rather than 20 judgement calls): is_process_alive is unsound EXACTLY when the ASKER \u2014 or a live ancestor \u2014 still holds an open HANDLE to the target; dropping a Child closes it, so a spawner that DROPS is honest and one that RETAINS is not. Hence the dangerous shape is asking 'is it GONE' about a process you OWN, and the SAME CALL IS SOUND IN THE CLI AND UNSOUND IN THE DAEMON FOR THE SAME PID \u2014 soundness is a property of the asker, not the call. REJECTED ALTERNATIVE, recorded in ADR-0045 Amendment 1 so it is not re-proposed: proc::reap_if_child before the probe fails twice \u2014 it is a NO-OP on Windows (#[cfg(windows)] let _ = pid), and decisively the broker holds the handle BY CONSTRUCTION for every pid this predicate is ever asked about, so no handle-based probe can EVER be sound at this site. FIX: (a) add a sound probe as a NAMED SIBLING whose name is the question \u2014 process_exists(pid) over process_table() \u2014 and state on is_process_alive's own doc which question it answers and which it does not, pointing at the sibling (the behavior was documented VERBATIM in legacy_resident_sweep_e2e.rs since 2026 and never reached proc.rs or zombie_verdict: written where DISCOVERED, not where CONSUMED); (b) route session_is_zombie's wrapper_alive through it, leaving zombie_verdict PURE and UNCHANGED (it was fed a lie, it is not wrong); (c) an EMPTY table is NO KNOWLEDGE \u2014 it must resolve to None (zombie_verdict(None) already means 'never guess') and must NEVER manufacture Some(false), which would mass-classify every live session a zombie = the W1 blocker inverted at broker scope; (d) AUDIT, do not mass-migrate, the remaining callers using the discriminator \u2014 roster.rs/api/startup.rs/api/auth.rs are already adjudicated SOUND (the asker never spawned the target); cli.rs purge psyche-quiesce is RIGHT ANSWER FOR A FRAGILE REASON (sound only because the CLI asks and the DAEMON spawned the psyche \u2014 it goes unsound SILENTLY if that check ever moves into the daemon) and gets a comment naming the asker as what makes it safe; the daemon-side population (livehost.rs, shellhost.rs, shellwake.rs, lifecycle.rs, in-daemon is_perch_alive) is adjudicated one verdict per site (sound/unsound/unreachable), fixing only unsound AND reachable and REPORTING the verdicts even where left \u2014 if unsound-and-reachable exceeds a handful, STOP and escalate to doyle rather than widening this wave. Gate: doc \u2014 KNOWN-HAZARDS 7.50 + ADR-0045 Amendment 1; impl \u2014 process_exists + the caveat on is_process_alive + wrapper_alive routed + the adjudicated fixes; unit \u2014 the empty-table arm resolves to None (never Some(false)), a handle-held corpse classifies as a zombie, and zombie_verdict's existing pure table is extended with the previously-unreachable case (dead root + not adapter-labeled + within grace); int \u2014 manufacture the REAL handle-held-corpse condition (broker retains a handle to a killed PTY child) and assert the session classifies zombie + the run dup-guard does NOT refuse over it; Windows-only if it cannot be made on Linux, and the test NAME says so.",
    "title_b": "B-5 (F029, operator): `spt endpoint run`'s interactive picker window/tab is untitled \u2014 hard to find among many terminals. Set the window/tab title to `SPT Endpoint Picker`. Anchor picker/mod.rs:88 setup_terminal (crossterm SetTitle in the execute! chain). Set-only is acceptable (crossterm can't cheaply read the prior title to restore). Applies ONLY to the interactive picker path \u2014 non-interactive/headless `endpoint run` (REQ-HOST-RUN-1) must NOT retitle the operator's terminal. See triage B-5.",
    "doc_a": "Failure (paid-for, found by todlando during TEARDOWN-AUTHORITY W1 gate round 0, 2026-07-19; latent in `session_is_zombie` since the cycle verbs were built):** `spt_store::proc::is_process_alive` probes `OpenProcess` on Windows, which keeps SUCCEEDING for a TERMINATED process while any parent still holds an open handle to it \u2014 and the broker holds `Arc<PtySession>`, hence such a handle, for every PTY child it spawned. So a correctly-reaped harness reads ALIVE. `broker.rs::session_is_zombie` feeds exactly that call into `zombie_verdict`'s `wrapper_alive`, which flips the verdict off its PRIMARY ",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 110,
    "req_a": "REQ-MANIFEST-6",
    "req_b": "REQ-STORE-1",
    "title_a": "Cross-adapter fallback target addressing (M12-W3-T3.2): a cross-adapter fallback target is addressed as `<adapter>:<profile>` (not just a bare adapter_name), resolved through the one composite-addressing resolver (registry::resolve_option) at every adapter-option read site so a fallback may select a shipped/local profile (e.g. a `ccs` profile). CONTEXT.md \u00a7cross-adapter-fallback reconciled (\"ccs is a profile; cross-adapter fallback may target <adapter>:<profile>\"). Contract-only this milestone: the node-wide fallback SETTING + its rate-limit invocation are deferred to the consuming milestone (the runtime path does not exist yet); this REQ guarantees the ADDRESSING resolves.",
    "title_b": "spt-store::BranchStore (git branch as versioned KV; commit=checkpoint/tip=resume, atomic multi-key, merge-native sync) is the substrate for coarse/durable/audited state (context, registry snapshot+distribution, daemon checkpoint); hot paths (B5 fsync journal) + indexed queries (SQLite spool) excluded (ADR-0011)",
    "doc_a": "Command templates are opaque.** spt-core never parses out a model/tool/flag \u2014 the adapter writes the whole command line; spt-core fills substitution keys and runs it. A command template's program token resolves against the adapter install dir before PATH (since v0.8.0).** A `.spt` adapter ships its built binaries to the adapter's install dir (`adapters/_github/<safe>/` via `--release`/`--github`, or the record's `source_dir` under copy-mode), so a bare program name (e.g. `claude-spt-digest \u2026`) binds to the shipped binary first and falls back to PATH when absent \u2014 a `.spt` that ships its binari",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 111,
    "req_a": "REQ-MESH-2",
    "req_b": "REQ-SUBNET-2",
    "title_a": "Member roster: node-level union-merge grow-set (per member: pubkey, label, machine_id, last-known address, last-seen \u2014 NOT the seed), the discovery directory the mesh dials by. Seeded IN FULL at pairing (seed-holder hands joiner the whole current roster, incl. offline members \u2014 folds in deferred pairing-time hostname capture + post-join address seeding); each node authors its own entry stamped with its lease_epoch, merged strictly-greater-wins (the node_label lease); exchanged only over seed-proof'd member connections; forgery-inert (a fake entry names a pubkey that still can't seed-proof). Removal needs a TOMBSTONE \u2014 a per-pubkey revoked marker that propagates, dominates the entry, gates admission (seed-proof \u2227 \u00actombstoned), and prevents reinsert; cleared by a completed re-pair of that pubkey. Persists through silence (offline member keeps its entry).",
    "title_b": "Guided join e2e: spt subnet join CLI initiator + always-on daemon pairing responder",
    "doc_a": "",
    "doc_b": "Product-surface amendment (2026-06-05 \u2014 M7 D3)",
    "score": null,
    "reasoning": null
  },
  {
    "index": 112,
    "req_a": "REQ-MSG-5",
    "req_b": "REQ-MSG-SENDER-STAMP",
    "title_a": "user-msg envelope kind + daemon identity gate: a Gateway endpoint / the local user's CLI author user-msg (the user's authority); agent-family senders re-stamped to plain msg; identity-gated never payload-trusted (KH 7.3/7.5); wire-additive (N-1 receivers tolerate the new type)",
    "title_b": "Daemon-stamped authenticated sender: a NEW additive `WanMessage.sender_proven` field (serde-default) carrying the SESSION-PROVEN sender endpoint id, which lights up the REQ-ACL-SUBJECT-CHAIN tier-1 sender-endpoint rule that shipped schema-real but unfed in W1. The stamp is sourced from the session-proven path (`roster::detect_self_id` / the bound perch), NEVER from the caller-supplied `--from`: cli.rs `resolve_from` lets an explicit `--from` win over session detection, which is exactly why KNOWN-HAZARDS 7.5 binds `from` as reply-routing metadata and never an authorization subject. `from` is untouched and keeps its meaning; this is an addition, never a repurposing. TRUST BOUNDARY, stated so no later reader inflates \"authenticated sender\": the stamp proves the origin NODE cryptographically (QUIC handshake); the endpoint WITHIN that node is asserted by the sending daemon; strength therefore equals REQ-MSG-6's ratified boundary (trust = subnet membership, node = human-proxy). It DEFEATS agents forging `--from` on a box \u2014 the adversary milestone A's threat model actually names \u2014 and does NOT defend against a malicious member node. Same-node delivery is strictly stronger (the daemon knows the authenticated perch directly). Tier 1 ABSTAINS on absence (no stamp -> None -> the chain continues to the node tier), so N-1 senders, older daemons, and the five gate families that carry no sender endpoint keep today's behavior byte-for-byte. Adapter-invisible: a decision INPUT only, never entering the EVENT envelope, so no published adapter contract changes. Gate: doc \u2014 ADR-0009 amended (its \"not the sender endpoint's identity\" sentence becomes false the moment tier 1 fires) plus the wanmsg.rs module-doc carve-out stating that sender_proven IS decoded-and-acted-on, what bounds it, and that it never becomes the node subject (the origin-node paragraph stays verbatim \u2014 origin_node remains never-read-from-bytes, and `forged_origin_field_is_inert` stays untouched); impl \u2014 the additive field, the session-proven stamp at the send path, the receive-side threading into `AccessRequest.sender_endpoint`, and the latent-rule scan; unit \u2014 additive round-trip both directions (new field decodes, absent field defaults), a `--from` that disagrees with the stamp never becomes the subject, tier 1 fires on a proven stamp and abstains without one, and the W1 inertness guards REPLACED by their positive counterparts (the deliberate flip is the record). LATENT-RULE SCAN (doyle-ruled 2026-07-29, mechanism-not-memory): on the load where the tier goes live, count existing SenderEndpoint rules and, if any, print them loudly once \u2014 \"these rules were inert and are now live\". For the COUPLED release this finds zero by construction (no shipped version accepts a v2 store while the tier is inert; W1+W2b ship together in milestone A), and the code says so; the scan exists for the DECOUPLING scenario, where a real inert window would open in the field and a later release would silently activate latent rules.",
    "doc_a": "A message sent from a Gateway carries **the user's authority** \u2014 it *is* the user speaking through a device \u2014 and is delivered typed **`user-msg`** (ratified 2026-06-12) so receiving agents weight it as user instruction, not peer-agent chatter. The type is **identity-gated, never payload-trusted** (the KH 7.3/7.5 posture): the daemon permits `user-msg` only from user-backed origins (a Gateway endpoint, the local user's own CLI) and re-stamps an agent-family sender's `user-msg` down to plain `msg` \u2014 authority comes from who you are, not what you wrote.",
    "doc_b": "endpoint access whitelist** (distinct from the grant store \u2014 the outer reach gate): A per-endpoint allow-list controlling **who may remotely reach** an endpoint. *Subject ruling (2026-07-28, access-control grill \u2014 supersedes origin-node-only keying):* a rule's subject resolves through one precedence chain \u2014 **explicit sender-endpoint entry \u2192 node-level entry \u2192 subnet-mode default** \u2014 because the gated adversary is the **agent** (see *shared subnet*), so rules must be able to name a specific sender endpoint; a node entry is the \"I trust that whole machine\" wildcard, and the subnet mode is the d",
    "score": null,
    "reasoning": null
  },
  {
    "index": 113,
    "req_a": "REQ-MSG-INJECT-LEG-DROP-VISIBLE",
    "req_b": "REQ-SHELL-1",
    "title_a": "SEED (inactive \u2014 observability): a silently-dropped delivery leg must be DISTINGUISHABLE from an honestly-offline endpoint on a status surface. The spt-hosted inject leg (spt-daemon inject.rs `try_spt_hosted_inject` \u2014 the ONE shared implementation behind local cmd_send, the WAN ingress, the idle-edge drain, and the parked-idle/pulse re-offer belts) gates on `is_spt_hosted_no_relay` \u2192 `deliver::is_online` \u2192 `liveness::is_perch_alive`: a perch PINNED TO A DEAD SESSION (the KH 7.25 wedge class \u2014 dead owner, record not yet healed by the next auth touch) makes EVERY belt on that leg silently return None \u2192 messages spool as if the endpoint were ordinarily offline, while the operator-facing view can keep reading the recorded state. Nothing anywhere surfaces 'the inject leg stopped firing for this endpoint' \u2014 the idle-window injections just stop, which from the outside is indistinguishable from 'no messages arrived' (field shape: IDLE-EDGE W1 field-verify, perri's killed-resident rig \u2014 their 'so it did not repeat' conclusion was exactly this invisibility, self-corrected only by re-rigging with a live resident). Shape at activation: a status/list surface DERIVES and reports the inject-leg verdict per endpoint (inject-target / not-a-target + WHY: no perch, dead session pin, no relay, not controllable) so a dropped leg is a READABLE state, never an inferred one; additive-evolution posture on any --json surface. Kin: REQ-SHELL-LIST-DERIVED-PROVENANCE (view-vs-truth, same class), KH 7.25 (`REQ-HAZARD-SESSION-PIN-WEDGE`, the pin that drops the leg), KH 2.5 (liveness authority lives in ONE resolver \u2014 the verdict here REPORTS through it, never re-derives beside it).",
    "title_b": "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)",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 114,
    "req_a": "REQ-NOTIF-1",
    "req_b": "REQ-NOTIF-DRAIN-ROW-VALIDITY",
    "title_a": "Notification primitive: per-subnet replicated spool, seen/dismissed, resurface-at-boundary, subsumes update+consent prompts",
    "title_b": "A spooled notify envelope is validated against its notif row at DELIVERY time \u2014 a copy outliving its row must not deliver. (ADR-0046 Amendment 1 + KNOWN-HAZARDS 7.53; operator field regression from perri's node 2026-07-22, doyle root-caused same day \u2014 DAEMON-LIFECYCLE W1 RIDER.) TODAY: quiet delivery (REQ-NOTIF-QUIET-DELIVERY) makes every surface an active_only SPOOL write per endpoint, and every row-lifecycle mechanism (apply-seam dismissal REQ-NOTIF-SEAM-DISMISS, coalesce supersession, TTL, the one-shot migration) touches ROWS only \u2014 so a copy spooled while the endpoint was busy/offline is a detached snapshot no dismissal can recall, delivering 'update available' on an already-updated node at the next drain, once per qualifying surface event (perri: twice; doyle's own session: four stale 0.39.x drains post-upgrade). FIX: at the safe-point drain choke point (api poll deferred presentation, spt/src/api/delivery.rs cmd_poll), a notify-kind envelope delivers ONLY if its notif_id resolves to a live UNDISMISSED row in the local notif store; dismissed/superseded/TTL-expired/unknown -> dropped silently; N copies of one notif_id in a drain dedupe to ONE delivery. Non-notify spool content is untouched (ordinary messages are not revocable and MUST deliver). Legacy pre-0.40.0 plain-message copies carry no notif_id, cannot be validated, and self-clear by their one drain \u2014 no machinery. Rejected: recall-on-dismiss spool sweeps (race the drain, span every perch, miss in-flight copies); live-only notify delivery (the spool IS quiet delivery's safe-point mechanism). Gate: impl \u2014 the validity gate + dedupe at cmd_poll's deferred presentation; unit \u2014 the gate predicate cells (live row delivers / dismissed drops / unknown drops / dedupe collapses / non-notify passes untouched); int \u2014 the perri rig RED-first: surface an update notif to a non-draining endpoint (copy spools), dismiss the row via the apply seam, drain -> NOTHING delivers; control leg: undismissed row's copy delivers exactly once.",
    "doc_a": "",
    "doc_b": "Failure (paid-for, operator field report from perri's node 2026-07-22 \u2014 the day after the v0.40.0 notif redesign shipped; doyle root-caused same day, own session corroborating with stale v0.39.x drains):** a node already ON v0.40.0 received the v0.40.0 \"update available\" notice twice. ADR-0046's quiet delivery makes every notif surface an `active_only` SPOOL write per endpoint; every lifecycle mechanism (apply-seam dismissal, coalesce supersession, TTL, migration) operates on notif-store ROWS only. A copy spooled while the endpoint was busy/offline \u2014 the normal state of a working agent \u2014 is a ",
    "score": null,
    "reasoning": null
  },
  {
    "index": 115,
    "req_a": "REQ-NOTIF-MIGRATE",
    "req_b": "REQ-PSYCHE-ACCOUNT-REFUSAL-EXIT",
    "title_a": "One-shot field migration: on first run the new binary auto-dismisses existing rows with from_id = spt-update (kinds consent/rollback) \u2014 the known-stale class; idempotent; the dismissals replicate so cleanup reaches not-yet-upgraded peers; agent/psyche rows untouched; the update worker re-produces any genuinely-current update notif within one check cadence",
    "title_b": "RESERVED EXIT 96 \u2014 account/credential refusal from a psyche_resume turn: the inner tool refused for account-level reasons (spend/usage cap, expired/revoked credential, org quota) \u2014 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 \u2014 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 \u2014 unpredictable but CERTAIN \u2014 and a permanently-given-up psyche is invisible), reset on first success, no state to unwind. (2) DISTINCT SURFACE: never the defect-shaped PSYCHE_TURN_FAIL prefix \u2014 renders as its own class (adapter-side PSYCHE_TURN_REFUSED; core-side an additive class discriminator beside psyche_host_error, never a fold into it), NAMES the actor and action (a human, the inner tool's account \u2014 not the endpoint/adapter/session), carries the inner tool's own words via the dual-stream tail (REQ-PSYCHE-TURN-STREAM-EVIDENCE), and states HOW LONG it has been refused. (3) NEVER reseeds \u2014 custody is fine; a reseed would destroy a healthy transcript for nothing. (4) Never counted as a crashloop defect. Adapter contract half: claude-spt docs/design/RESERVED-EXIT-ACCOUNT-REFUSAL.md (appendix of observed CC strings is informative-not-contract, populated ONLY from observed bytes \u2014 the invented-string chain of 2026-07-26 is the anti-pattern it refuses). Gate at activation: unit \u2014 exit 96 classifies refused (no strike increment, no reseed, own backoff schedule engaged); 95/generic/None unchanged; surface renders the distinct label + duration.",
    "doc_a": "6. Field migration: one-shot targeted auto-dismiss",
    "doc_b": "Reserved exit codes \u2014 how a `psyche_resume` turn tells spt-core *why* it failed.** spt-core classifies a failed turn on the process **exit code alone \u2014 never on output text** (your harness may reword, restructure, or JSON-wrap its errors freely; only the code is contract). Any other nonzero exit is a generic failure: spt-core keeps the Psyche's session custody, counts a strike, and retries.",
    "score": null,
    "reasoning": null
  },
  {
    "index": 116,
    "req_a": "REQ-PAIR-2",
    "req_b": "REQ-SUBNET-ADMIN-SEED-REPLICATION",
    "title_a": "Local trust store with TOFU + warn-on-change",
    "title_b": "Every member node holds BOTH subnet seeds \u2014 the admin seed replicates at join exactly as the member seed does \u2014 because empower verification and admin-code joins are LOCAL operations any member node must be able to serve without consulting a privileged peer (ADR-0051 decision 2; the mesh stays symmetric, authority rides a key rather than a node). Carries the NO-REVEAL invariant, which is the load-bearing half: there is no admin show-code, no QR re-provision, no `--json` field, no debug/diagnostic/error/log rendering of the admin seed ANYWHERE \u2014 it is displayed exactly once, at creation, and thereafter no node can leak what no node can reveal. A lost authenticator entry is irrecoverable by design (ADR-0051 decision 3): re-mint the subnet. The invariant binds tests too \u2014 a test that asserts the seed's VALUE by printing it is itself the leak (secret-probe discipline: never expand a secret into an observable sink). Gate: doc \u2014 ADR-0051 decisions 2 and 3; impl \u2014 admin-seed replication on the join path and the absence of every reveal surface; unit \u2014 a joined node holds both seeds and can verify an admin code locally, and no CLI/JSON/diagnostic output path emits the admin seed.",
    "doc_a": "",
    "doc_b": "2. Held everywhere, revealable nowhere / 3. Irrecoverable by design",
    "score": null,
    "reasoning": null
  },
  {
    "index": 117,
    "req_a": "REQ-PICKER-1",
    "req_b": "REQ-PICKER-2",
    "title_a": "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 \u2014 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 \u2014 today mis-shows green); blue 'ONLINE + CONTROLLED' (online + driven_by.is_some()). Derived on EndpointRow from {offline | controllable | driven_by} with precedence offline\u2192gray, else driven_by\u2192blue, else !controllable\u2192amber, else green (driven_by outranks harness-only; mutually exclusive in practice \u2014 a harness-only endpoint has no broker PTY to control). The controllable discriminator is a NEW InfoJson.controllable: Option<bool> (serde-default, N-1-safe), stamped at the establish seam \u2014 cmd_listen (harness-hosted relay, no broker PTY) \u2192 Some(false); cmd_bind live_agent (spt-hosted broker PTY) \u2192 Some(true); absent \u2192 not-controllable (amber) default (harness-hosted is the common mis-reported case; one bind self-corrects). Store-projection-only (no live daemon query \u2014 doyle ruling). (v0.10.0)",
    "title_b": "The picker's project-history loader reads the git-backed context store, not the bare working tree: data.rs project_history_for enumerates an endpoint's projects via the BranchStore branch set (the context store keeps per-project context in git branches \u2014 contextstore::project_branch(project_id), checked out to projects/<project>/<id>/ only on-demand) instead of raw std::fs::read_dir over the empty working tree (which returned empty for ALL rows incl wall-a \u2014 the operator bug). Ordered newest\u2192oldest by branch commit recency; degrades to empty (informational pane), never fails. (v0.10.0)",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 118,
    "req_a": "REQ-PICKER-2",
    "req_b": "REQ-PICKER-UX-V013",
    "title_a": "The picker's project-history loader reads the git-backed context store, not the bare working tree: data.rs project_history_for enumerates an endpoint's projects via the BranchStore branch set (the context store keeps per-project context in git branches \u2014 contextstore::project_branch(project_id), checked out to projects/<project>/<id>/ only on-demand) instead of raw std::fs::read_dir over the empty working tree (which returned empty for ALL rows incl wall-a \u2014 the operator bug). Ordered newest\u2192oldest by branch commit recency; degrades to empty (informational pane), never fails. (v0.10.0)",
    "title_b": "`spt endpoint run` picker UX (v0.13.0 operator dogfooding): (1) SKIP the first screen \u2014 open directly on 'Pick existing'; `n` jumps to 'Create new'. (2) AUTO-ATTACH after both Start-new AND Resume-from-history (both currently don't attach and show no stdout); add an `h` shortcut to run headless (no attach). (3) 'controlled by' shows the node NAME (node_label_display), not the raw hex. (4) Clean up Start-new output \u2014 drop the Rust `pid=Some(142748)` leak and the 'harness binds its perch on startup' internals; user-friendly, not a process log. (v0.13.0)",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 119,
    "req_a": "REQ-PICKER-4",
    "req_b": "REQ-PICKER-ONLINE-ACTION",
    "title_a": "The picker's Subnet category renders the canonical node LABEL, not bare key-hex: a subnet row's node renders as 'LABEL (keyprefix\u2026)' (e.g. 'HFENDULEAM (bcead52b\u2026)') per CONTEXT.md:650 + Instance.node_label, NOT the raw node key-hex (SPT_DEV:14efb80cb\u2026 \u2014 a picker-only regression because resource_projection\u2192ResourceRow drops node_label, so data.rs subnet_rows uses the raw row.node). Thread node_label into the picker subnet path (ResourceRow gains node_label, or subnet_rows looks it up via the registry's node_labels) and REUSE the one canonical render (format!(\"{l} ({}\u2026)\", key_prefix) \u2014 cli.rs / wansend.rs), never a re-implementation. (v0.10.0)",
    "title_b": "The `spt endpoint run` picker shows the correct action for an ALREADY-ONLINE endpoint \u2014 Attach, NOT 'Start now' (operator-raised v0.12.0 real-harness finding). Symptom: the picker offers 'Start now' for endpoints that are already online. ROOT TBD \u2014 investigate the status\u2192action mapping (v0.10.0 PICKER-1 four-state status, picker/model.rs): is it reading live/online state correctly, or rendering stale/wedged broker state (i.e. partly a symptom of the broker wedge / status=online latch)? Fix so online \u2192 Attach. (v0.12.1)",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 120,
    "req_a": "REQ-PICKER-ADAPTER-DESCRIPTION",
    "req_b": "REQ-PICKER-REMOTE-WAKE",
    "title_a": "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 \u2014 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)",
    "title_b": "C-2 (REMOTE-TRUTH triage \u00a7C-2 #4 + addendum @3442ce5): a REMOTE suspended picker row offers `Wake now` \u2014 waking the endpoint THROUGH its owning node's rest edge (the A-2 daemon resume leg) \u2014 instead of a bare `Start now` that silently cold-starts a COLLIDING LOCAL instance of a remote id (node-anchored identity violation, ADR-0003/0023). ROOT (certain): confirm_options collapsed Suspended into the offline action set = [Start,\u2026]; on a REMOTE row `Start` bakes Outcome::Run with NO node \u2192 picker dispatch \u2192 cmd_endpoint_run creates a fresh LOCAL perch of the remote id (model.rs confirm_terminal / mod.rs dispatch). Remote rows are only Online/Suspended, so remote+offline == remote-suspended. FIX: confirm_options splits the offline arm on is_local \u2014 remote \u2192 vec![Wake] (a new ConfirmOption::Wake), local \u2192 vec![Start] UNCHANGED; confirm_terminal(Wake) \u2192 a new Outcome::Wake{id,node} carrying the RAW node hex; dispatch routes crate::cli::cmd_endpoint_wake_remote(id,node) \u2192 cmd_rest(id@node, RestEvent::Wake) = the EXISTING WAN rest arm (dispatch_wan_rest \u2192 wan_rest), and A-2's resume leg revives the session async (the full loop the operator wanted). `Instantiate locally` stays the separate deliberate-copy verb. ADDENDUM correction (a): EndpointRow.node is the LOSSY DISPLAY string (node_label_display = 'LABEL (prefix\u2026)'), which node_qualifier_matches (full-hex-prefix|exact-label) CANNOT match \u2014 a dead Wake; so a NEW EndpointRow.node_key: String carries the raw ResourceRow.node hex (empty for local rows \u2014 Wake is remote-only) threaded through from_resource_row + the 4 literal ctors. CO-GATE (b, addendum): ChangeAdapter (was `offline`-gated) is gated `offline && is_local` \u2014 write_adapter_change \u2192 resolve_perch_path(Infer) \u2192 mutate_info rewrites a LOCAL perch record, so offering it on a remote suspended row is the SAME colliding-local-write-for-a-remote-id class (the Start twin); a remote node's adapter is not ours to rewrite from here. Red-first: a remote suspended row \u2192 confirm_options has Wake NOT Start NOT ChangeAdapter, and confirm_terminal(Wake) \u2192 Outcome::Wake{node==raw hex} (never a local Outcome::Run); a LOCAL offline row is UNCHANGED (Start + ChangeAdapter).",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 121,
    "req_a": "REQ-PICKER-CHANGE-ADAPTER-FLOW",
    "req_b": "REQ-PICKER-NODE-GROUPING",
    "title_a": "B-2 (F029, operator; LARGEST item): `ConfirmOption::ChangeAdapter` wrongly routes into the CREATE flow (Screen::CreateAdapter \u2192 CreateId \u2192 CreateHome \u2192 START, reenter_create(true)) \u2014 it re-prompts id + home and then STARTS the endpoint. Required: prompt ONLY the harness-adapter pick, apply the change to the perch record (update info.json.adapter via the existing write seam; an `<adapter>:<profile>` pick stamps the full option, composing with A-4), then RETURN to the endpoint's Confirm menu \u2014 NO id prompt, NO home prompt, NO start. FIX: a return-to-Confirm mode on the adapter-pick screen (flag or dedicated Screen::ChangeAdapterPick) skipping CreateId/CreateHome + the launch outcome. Unit the flow-state transitions + the record write. Shared-seam: touches picker flow state \u2014 run the full picker cluster. See triage B-2.",
    "title_b": "Bug #13: the endpoint run Subnet tab shows a machine once PER shared subnet (subnet_rows data.rs:253 iterates per-subnet, groups by subnet:node, no cross-subnet dedup). Fix: dedup by (node, endpoint_id) across the subnet loop, collect the set of shared subnet names per endpoint, emit one group per MACHINE (group = node_display) with its shared subnets listed beneath the machine name; reconcile per-endpoint status across subnets (most-alive). Couples REQ-ENDPOINT-LIST-PALETTE (both edit subnet_rows). See docs/NEXT-MILESTONE-BUG-TRIAGE.md #13.",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 122,
    "req_a": "REQ-PICKER-CHOOSE-DEDUP-ALL",
    "req_b": "REQ-PICKER-PURGE-SHORTCUT",
    "title_a": "A4 (F028, operator #5): choose-project duplicate rows. model.rs:314-337 build_project_choices dedups the `Here: <run_cwd>` row only against the HEAD ref's dir (line 324) \u2014 an OLDER history ref with the SAME dir still renders, giving `Here: C:\\...\\projects` + `projects` as two rows for one project (operator screenshot). FIX: dedupe `Here` against ALL history dirs, and skip rest-rows whose dir == run_cwd when Here is present. Extend the model.rs:1847 choose-project test. See triage A4.",
    "title_b": "C-3 (REMOTE-TRUTH triage \u00a7C-3 #8): the pick-existing list gains an `x` purge shortcut \u2014 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)` \u2014 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 \u2192 offline-only, remote \u2192 local-only). force=false is deliberate \u2014 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() \u2014 the same predicate the handler gates on. Red-first: purge outcome reachable ONLY from an offline LOCAL highlight (online/remote \u2192 no screen change + why-flash).",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 123,
    "req_a": "REQ-PICKER-CURRENT-DIR-LABEL",
    "req_b": "REQ-PICKER-PROJECT-DISPLAY-NAME",
    "title_a": "A-2/A-3 (F029, operator, semantic pair): the Choose-project rows must self-identify the CURRENT DIR. build_project_choices (picker/model.rs:322-352). A-2: when the run cwd IS already a history dir the `Here:` row is (correctly) suppressed by the dedup (REQ-PICKER-CHOOSE-DEDUP-ALL), but the matching history row rendered bare `r.display` with no cwd affordance \u2014 mark it `<display> (CURRENT DIR)`. A-3: the not-in-history current-dir row changes from `Here: <run_cwd>` to `CURRENT DIR --> <project>`, deriving the display the SAME way the history refs do (folder tail; honest fallback to the raw path when underivable). `cwd` payload unchanged. Grep-tests rule: 3 `starts_with(\"Here: \")` asserts (model.rs) + a `Here: /here` render assert (view.rs) are behavior assertions on the OLD label. See triage A-2/A-3.",
    "title_b": "A1 (F028, operator #1/#4/#6-display): `github-com-*` 'ghost' project entries are NOT phantoms \u2014 project_id_for_dir (spt-store/src/project.rs:64) derives ids from the git remote slug BY DESIGN (REQ-STORE-1 cross-machine sync): `github.com/SaberMage/spt-core` -> `github-com-sabermage-spt-core`. The ref is truthful; the BUG is presentation \u2014 the raw slug renders as the DISPLAY NAME everywhere (confirm-panel history view.rs:415-419, choose-project labels model.rs:314-337, resume-row titles, endpoint-list project column via latest_project_ref data.rs:417), which no operator recognizes as 'spt-core'. FIX: keep the slug as the KEY, render a friendly display name \u2014 the repo tail (spt-core) reusing the disambiguate_project_ids (model.rs:346) suffix mechanism for collisions. One shared display-name seam across all four surfaces. See triage A1.",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 124,
    "req_a": "REQ-PICKER-NODE-GROUPING",
    "req_b": "REQ-PICKER-PROJECT-HISTORY-TRUTH",
    "title_a": "Bug #13: the endpoint run Subnet tab shows a machine once PER shared subnet (subnet_rows data.rs:253 iterates per-subnet, groups by subnet:node, no cross-subnet dedup). Fix: dedup by (node, endpoint_id) across the subnet loop, collect the set of shared subnet names per endpoint, emit one group per MACHINE (group = node_display) with its shared subnets listed beneath the machine name; reconcile per-endpoint status across subnets (most-alive). Couples REQ-ENDPOINT-LIST-PALETTE (both edit subnet_rows). See docs/NEXT-MILESTONE-BUG-TRIAGE.md #13.",
    "title_b": "#1: picker project history is derived from sessions.log cwds (newest->oldest, deduped by project_id_for_dir) UNION context-store branches, EXCLUDING owlery-internal paths (any cwd under spt_home()/owlery) everywhere a project is displayed or inferred. Fixes three stacked defects (crates/spt/src/picker/data.rs): (1a) project_history_for (data.rs:372) reads ONLY context-store p-* branches, which are empty on this box -> history []; (1b) the fallback origin project (data.rs:207) is derived from info.json.cwd = latest-boot-cwd (rewritten every rebind), not origin; (1c) psyche-host sessions bind owlery-internal cwds that pollute history. Full DIRS stay available in the model (feature #5 needs them). PROJECT REPRESENTATION RULING (operator 2026-07-03): project IDs ONLY, EVERYWHERE incl local display; on ID collision disambiguate minimally via a PURE disambiguate_project_ids(entries)->display-names fn (append one-level-up parent folder and/or root drive letter, e.g. 'spt-core (projects)' vs 'spt-core (D:)'). See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #1.",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 125,
    "req_a": "REQ-PICKER-ONLINE-ACTION",
    "req_b": "REQ-PICKER-SHORTCUT-LABEL-FILENAME",
    "title_a": "The `spt endpoint run` picker shows the correct action for an ALREADY-ONLINE endpoint \u2014 Attach, NOT 'Start now' (operator-raised v0.12.0 real-harness finding). Symptom: the picker offers 'Start now' for endpoints that are already online. ROOT TBD \u2014 investigate the status\u2192action mapping (v0.10.0 PICKER-1 four-state status, picker/model.rs): is it reading live/online state correctly, or rendering stale/wedged broker state (i.e. partly a symptom of the broker wedge / status=online latch)? Fix so online \u2192 Attach. (v0.12.1)",
    "title_b": "B-4 (F029, operator): the confirm-panel shortcut option label is a static `New/Update spt-<id> shortcut (s)` placeholder \u2014 it should name the REAL file it writes: `Set shortcut here --> <current dir>/<shortcut-name>` where <shortcut-name> is the EXACT on-disk filename (incl. extension). The name must be produced by the SAME function that names the file in shortcut creation (picker/shortcut.rs shortcut_filename over the manifest-resolved basename) so label and writer can NEVER drift. Anchor picker/view.rs confirm_option_label. See triage B-4.",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 126,
    "req_a": "REQ-PICKER-PURGE-STRUCTURED",
    "req_b": "REQ-RC-DISPLAY-SOLE-WRITER",
    "title_a": "RC-RENDER-TRUTH W3 (ADR-0043 decision 3, hertz stale-glyphs RCA leg 3 P0): core verbs invoked from inside an active TUI return STRUCTURED outcomes and write NOTHING to the terminal \u2014 the picker purge path calls a structured-outcome purge core (no stdout/stderr under the live alternate screen) and remains the sole renderer via model.flash (today cmd_endpoint_purge writes diagnostics/PURGED to stderr while ratatui owns the alt screen, mutating the physical screen behind the previous-Buffer diff baseline => later draws skip 'already blank' cells and stderr glyph fragments persist \u2014 the x-purge symptom). Baseline-desync regression REQUIRES a stateful/recording backend (pure TestBackend view snapshots cannot catch it). Gate: impl \u2014 structured purge outcome + silent-under-TUI routing; unit \u2014 purge core emits no terminal bytes in structured mode, picker converts outcomes to flash; int \u2014 recording backend: draw ConfirmPurge, inject an external display mutation, transition back => next frame reconstructs the COMPLETE target screen; doc \u2014 ADR-0043.",
    "title_b": "TEARDOWN-AUTHORITY W5 (hertz RCA 3 bug 1, 2026-07-19 \u2014 REPRODUCED on 0.38.1 CLI + 0.38.1 broker; doyle ruled, and it CORRECTS an incomplete v0.38.1 call of doyle's own): while `spt rc` owns the terminal (raw / alternate screen), rc is the SOLE writer to that display \u2014 no background thread may write to the inherited stderr. TODAY: BrainConn::split_with_reader (spt-daemon/src/brain.rs:253-270) unconditionally eprintln!s `PUMP_IPC_READER: spawned` / `exited` from its reader thread. Every reconnect attempt calls establish_attach -> Brain::cold_start_pump (rc.rs:1700-1712), and reconnect_banner_bytes (rc.rs:2040-2054) deliberately clears/homes and leaves the cursor immediately after the countdown with NO trailing newline \u2014 so the marker lands exactly at that cursor and the operator sees `Reconnecting to local daemon... 9sPUMP_IPC_READER: spawned`. Field screenshot confirms it also reaches the harness alt screen on a real `endpoint run` + `rc --take`. WHY THIS IS A CORRECTION, ON THE RECORD: v0.38.1's REQ-RC-SINGLE-PUMP-BRAIN removed the DOUBLE construction and doyle ruled the surviving single banner 'truthful, keep it' and classified the change Internal (diagnostic hygiene, not user-facing). Both halves of that ruling were wrong on facts doyle did not check: the marker is not merely a startup diagnostic, it is rendered INTO an rc-owned display, so it was user-facing all along and v0.38.1 reduced rather than eliminated the corruption. FIX: delete both unconditional eprintln diagnostics from the pump reader thread. If the observability is still wanted it goes to the daemon's persistent diagnostic sink or an explicit opt-in debug trace that NEVER inherits an interactive client's stderr \u2014 never to a stderr an attached client owns. Grounding: ADR-0043 terminal render lifecycle (one renderer owns the baseline); CONTEXT.md:33-36 (the broker's internals are not a client-visible surface). Gate: impl \u2014 diagnostics removed from split_with_reader (and any sibling unconditional client-inherited stderr write on the pump path); unit \u2014 insufficient alone and explicitly NOT the gate (the defect is cross-thread out-of-band stderr, which a banner-byte unit cannot observe); int \u2014 drive the REAL reconnect-banner path with the child's stderr captured into the SAME sink as the rendered terminal and assert neither PUMP marker appears anywhere in the captured stream, PLUS assert an ordinary initial `spt rc` is marker-free.",
    "doc_a": "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 \u2014 producer order is the contract. Output-before-Exit is a production-path invariant, regression-proven end-to-end (broker \u2192 attach \u2192 rc). 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 including errors and unwind: best-e",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 127,
    "req_a": "REQ-PICKER-START-PROJECT-CHOICE",
    "req_b": "REQ-PICKER-WINDOW-TITLE",
    "title_a": "#5: after 'Start now' in the endpoint picker, swap the bottom Options panel to a 'Choose project' list: (1) the endpoint's most recent project dir, (2) 'Here: <dir>' \u2014 the spt endpoint run cwd (only if different), (3) all other project-history dirs newest->oldest. Fire the step ONLY when (A) the run cwd mismatches a singular history entry, or (B) history has >1 entry; otherwise start immediately (today's behavior). Depends on REQ-PICKER-PROJECT-HISTORY-TRUTH (needs full DIRS from sessions.log, owlery-internal exclusion applies). Start-now is local-only (no gossip). See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #5.",
    "title_b": "B-5 (F029, operator): `spt endpoint run`'s interactive picker window/tab is untitled \u2014 hard to find among many terminals. Set the window/tab title to `SPT Endpoint Picker`. Anchor picker/mod.rs:88 setup_terminal (crossterm SetTitle in the execute! chain). Set-only is acceptable (crossterm can't cheaply read the prior title to restore). Applies ONLY to the interactive picker path \u2014 non-interactive/headless `endpoint run` (REQ-HOST-RUN-1) must NOT retitle the operator's terminal. See triage B-5.",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 128,
    "req_a": "REQ-PRESENCE-LIVENESS-TRUTH",
    "req_b": "REQ-WORKER-LIST-VISIBILITY",
    "title_a": "A node's gossiped per-endpoint registry Status reflects real liveness, so a remote viewer never paints a DEAD endpoint as ONLINE. ROOT (confirmed + gated vs CONTEXT.md): registryhost.rs:397-405 advertises a NOT-alive perch as Status::Dormant (the `else` of is_perch_alive), re-stamped every gossip round (never ages to Offline). Design intent GATED vs resting.rs + CONTEXT.md: active/dormant is the MULTI-INSTANCE routing differentiator (active = the bare-id routing target; dormant = a WARM non-target sibling \u2014 'driving ling@laptop makes ling@desktop dormant', resting.rs:97; transitions active\u2192dormant on AttentionShift/Detach). suspended = COLD (session closed, resumable-on-wake) while its NODE is UP. offline = NODE DOWN \u2014 NEVER self-gossiped (RestState has no Offline; a live node only ever gossips active/dormant/suspended), remote-inferred via epoch-lease eviction. So labeling a NOT-running perch Dormant is the DEFECT (dormant requires warm/running). PRIMARY FIX (registryhost `else`, not-bound-alive): live-but-UNBOUND (has a live broker session; is_perch_alive is bound-gated) \u2192 Active/Dormant (still warm); else (cold, no live session, but its node is up because this very daemon is gossiping) \u2192 SUSPENDED \u2014 NOT Dormant, NOT Offline (the node is UP; Offline is never self-gossiped). This alone removes the false-ONLINE. dormant keeps gossiping (routing/MRA needs it) but RENDERS as its online flavor (no distinct glyph; the dormant\u2192suspended auto-suspend timer disambiguates recency). The DISPLAY of these states (incl Suspended=gray-filled) is REQ-SUBNET-DISPLAY-PARITY. Design: docs/design/subnet-presence-display.md \u00a7A. (next milestone)",
    "title_b": "V-1 (WORKER-TRUTH triage, operator rider): worker perches leave the DEFAULT `spt endpoint list` view \u2014 they are process-local machinery, not subnet citizens; leaked-or-live worker rows rendering as permanent OFFLINE endpoints is the operator-visible symptom root. A dedicated flag (--workers) reveals them (one command + flag per the --all/--detail precedent, NOT a separate list-working command \u2014 sister divergence deliberate). Applies to the human render, --json (additive default-absent filter), and the registry/projection legs; verify-and-stop any worker gossip into the subnet registry as peer endpoints.",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 129,
    "req_a": "REQ-PSYCHE-CRASHLOOP-BACKOFF-SHUTDOWN",
    "req_b": "REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL",
    "title_a": "C3 (F028, perri F-h): psyche wrapper crash-loop has no backoff, and `endpoint shutdown` misses a wedged wrapper. A probe psyche crash-looped ~3 boots/sec for ~30min (CC died instantly on the untrusted owlery cwd; ledger hit ordinal 5358) \u2014 SILENTLY; and `spt endpoint shutdown` did NOT tear the looping wrapper down (docs say shutdown tears the Psyche with the perch; manual kill was required). FIX: (i) bounded backoff + loud give-up on a psyche boot loop (the psyche_host_error surface already exists), (ii) shutdown must cover a wedged/looping wrapper. See triage C3.",
    "title_b": "W3 (LIFECYCLE-TRUTH): daemon restart no longer massacres hosted endpoints \u2014 daemon start RE-RUNS previously-online spt-hosted endpoints. ROOT rig-proven: daemon stop+start (the apply notice's OWN instruction) kills every hosted endpoint; they stay OFFLINE after start (no resurrection) though records exist (info.json status + adapter + cwd). SCOPE RULING (doyle): re-run-on-start, marked start-reason=daemon-restart; agents' minds ride psyche re-host as today. Int: endpoint online -> daemon stop -> start -> endpoint back ONLINE, same id, harness respawned.",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 130,
    "req_a": "REQ-PSYCHE-LEGACY-RESIDENT-SWEEP",
    "req_b": "REQ-UPDATE-RUNNING-IMAGE-SURFACE",
    "title_a": "W5 (F030; doyle+perri 2026-07-04): a dirty daemon upgrade from <=v0.24.0 strands a RESIDENT psyche wrapper the OLD daemon spawned \u2014 and F-030 W4's nested-`ready` resolution fix CONVERTED that wrapper's accidental self-reap into a permanent HANG. The pre-W3 wrapper's only spt IPC is `spt ready <parent>-psyche --once` (BLOCKING, no internal timeout); pre-W4 that hit READY_FAIL on a multi-subnet home \u2192 the wrapper exit-4'd (accidental reap). Post-W4 the nested id resolves cleanly \u2192 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 \u2014 burying residency-era machinery, not resurrecting it). GUARD = adapter-AGNOSTIC (glue-model): resurrect the retired reap_orphan_psyches LOGIC \u2014 for each self-perch live-agent id derive `<id>-psyche` and kill iff (a) exe basename == the adapter's MANIFEST-declared psyche program (normalize_basename, never a hardcoded adapter name) AND (b) cmdline contains the id marker `<id>-psyche` AND (c) pid alive; any unreadable signal \u2192 DECLINE + loud log (fail-safe-decline, positive-match-only; infra never-kill inside the sweep). FRATRICIDE is closed by TIMING (perri-confirmed from the owning side): the ephemeral shim is daemon-spawned per-event, bounded, exits at turn end \u2014 at brain start BEFORE the first reconcile/pulse no current shim is resident, so any `<id>-psyche` psyche-program process alive then is unambiguously stranded-legacy. RESIDUE (doyle PIN 3): the hung wrapper REGISTERED a `<parent>-psyche` ready perch before blocking; killing the pid alone leaves a phantom ready-record with a dead pid (the REMOTE-TRUTH presence-lie class) \u2014 the sweep MUST also clear that stale registration or prove the existing stale-perch cleanup reaps it. No field window pre-W6 (nothing releases). (F-030 W5)",
    "title_b": "`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) \u2014 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) \u2014 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) AMENDED 2026-07-27 (FIELD-TRUTH W1 roll-in, operator-directed): (1) `spt daemon status` MUST also divulge the COORDINATOR (brain) image version \u2014 the process that executes daemon-coordinated features and the most relevant running version to a status reader; same HARD CONSTRAINT: sourced from the RUNNING brain process (self-report over the broker\u2194brain channel or equivalent live query), never inferred from disk. (2) The stale-daemon warning RE-KEYS to the coordinator image vs installed: it renders ONLY when the COORDINATOR is outdated \u2014 post-apply the brain restarts onto the new bytes while the broker legitimately stays old (ADR-0018 D3-3), so the broker-keyed warning fired exactly backwards on a healthy freshly-updated node. (3) The remedy line recommends solely `spt daemon refresh` (in-place coordinator cycle; hosted terminals and the net layer keep running), never `daemon stop` + `daemon start`; a broker-breaking release still forces its full bounce through the update machinery, not through status advice. (4) The broker image line stays, informational only \u2014 no warning, no remedy text. Coordinator-version query unanswered (older daemon) renders not-reported without the warning.",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 131,
    "req_a": "REQ-PSYCHE-SID-CUSTODY",
    "req_b": "REQ-WORKER-SID-SYMMETRIC-AUTH",
    "title_a": "W2 (F030, design \u00a73): the psyche mints and keeps its OWN session id, stored in the nested {id}-psyche perch record \u2014 {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 \u2014 the custody bug). Parent boundary (/clear, /compact) does NOT rotate the psyche sid (the psyche's conversational thread survives parent resets \u2014 its job). resume_psyche validates the custody key before spawn (resume.rs:183). Reseed path: psyche session lost/invalid \u2192 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} \u2014 never aliased. Red-first: parent `api boundary clear` \u2192 nested perch sid UNCHANGED (today it is the parent's \u2014 guard-revert reproduces).",
    "title_b": "W-2 (WORKER-TRUTH triage, operator-ruled 2026-07-06): worker verbs go sid-symmetric with every sibling id-scoped verb \u2014 worker-start mints NO token and worker-stop takes NONE (token custody is undue adapter burden, ruling via perri). Registration STORES the sid it authenticated (the parent's sid at start; today cmd_worker_start hardcodes session_id=\"\" \u2014 worker.rs:44 \u2014 so a sid-authed stop compares against empty and refuses 100%). Stop accepts the parent's CURRENT sid OR the stored registration sid (a /clear between start and stop rotates the parent's sid; either rotation endpoint is honest custody \u2014 the REQ-PSYCHE-SID-CUSTODY rotation reasoning). Under the ruling the field adapter's existing emission (worker-stop <id> --session-id <parent sid>) becomes contract-correct as-is. Publish the frozen verb shape to the docs-site with the landing wave (perri blind-builds from published docs).",
    "doc_a": "Custody sid \u2014 `{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 `<parent>-psyche` perch record \u2014 **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 takes the **explicit** `{parent_session_id}` key \u2014 never an alias of `{session_id}`.",
    "doc_b": "`api worker-stop <id> --session-id <sid>` \u00b7 `api worker-poll <id> --session-id <sid>`",
    "score": null,
    "reasoning": null
  },
  {
    "index": 132,
    "req_a": "REQ-PUMP-STAGE-TRUTH",
    "req_b": "REQ-WAN-SEND-DELIVERY",
    "title_a": "MESH-RECOVERY W1 (ADR-0039, RCA wave 3 \u2014 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 \u2014 address-resolution, QUIC connect, ALPN, seed-proof send, seed-proof receive/verify, roster exchange \u2014 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 \u2014 no green without real peer progress. New fields ADDITIVE (N-1 readers unaffected). Gate: impl \u2014 stage split + status surfaces; unit \u2014 stage classification + health state machine (degraded on all-peer failure, healthy only on real progress, not on heartbeat/time); int \u2014 health flips degraded/healthy across a real peer outage/restore; doc \u2014 reference regen (CLI surface change \u2192 xtask gen, no internal codes in clap help). Kin REQ-PEER-ROUTE-CHAIN, REQ-DAEMON-5 (heartbeat \u2014 answers liveness, not reachability), REQ-CLI-2/REQ-SUBNET-8 (render legs).",
    "title_b": "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.",
    "doc_a": "Decision",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 133,
    "req_a": "REQ-RC-IDENTITY",
    "req_b": "REQ-RC-IDMARKER-DISABLE",
    "title_a": "`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 \u2014 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 \u2014 that lift is better spent on the GUI). (v0.16.0)",
    "title_b": "Bugs #14 + #7/#8 (marker half): feature-flag the top-right StatusRow endpoint-id marker OFF (rc.rs:198-307). It is a one-shot absolutely-positioned paint that scrolls off-screen and is not re-stickied (#14), and its DECSC/clear/SGR injection splices into the harness in-flight drawing causing residual artifacts (#7/#8). Ship disabled next release (operator: save the concept for a future web SPT GUI); revisit as a proper per-frame sticky overlay only once REQ-BROKER-SCREEN-GRID provides the screen model. See docs/NEXT-MILESTONE-BUG-TRIAGE.md #14.",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 134,
    "req_a": "REQ-RC-SINGLE-PUMP-BRAIN",
    "req_b": "REQ-RC-VT-TEARDOWN",
    "title_a": "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 \u2014 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 \u2014 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 \u2014 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 \u2014 probe-Brain carry + same-conn freshness re-query; unit \u2014 probe-then-establish reuses the conn (construction-count observable); int \u2014 rc_attach_truth offline_row_over_live_session_attaches extended: capture stderr, assert PUMP_IPC_READER spawned count == 1 PLUS existing behavior assertions; doc \u2014 none (internal seam).",
    "title_b": "RC-RENDER-TRUTH W3 (ADR-0043 decision 2, hertz stale-glyphs RCA leg 2 P0): rc display teardown is a display RAII guard SEPARATE from the OS input/raw-mode guard, unconditional and idempotent on EVERY exit path including errors and unwind \u2014 best-effort SGR reset + full scroll-region reset + cursor show + leave alternate screen + clear/home, emitted while VT output processing is still enabled, THEN restore the prior console output mode, THEN parting prose (today RawGuard::drop restores raw/mouse/console-mode only; detach, child exit, displacement, first-event stall, fatal error, and the 30s reconnect give-up all can leave the operator terminal dirty; the reconnect banner clears+homes then give-up prints at the centered cursor). Gate: impl \u2014 split display guard + every-path coverage; unit \u2014 guard emits the cleanup postlude exactly once, idempotent on double-drop; int \u2014 dirty sink (?1049h ?25l SGR31) x every PumpEnd/error class => cleanup postlude precedes the final prose; doc \u2014 ADR-0043.",
    "doc_a": "",
    "doc_b": "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 \u2014 producer order is the contract. Output-before-Exit is a production-path invariant, regression-proven end-to-end (broker \u2192 attach \u2192 rc). 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 including errors and unwind: best-e",
    "score": null,
    "reasoning": null
  },
  {
    "index": 135,
    "req_a": "REQ-REL-2",
    "req_b": "REQ-UPD-9",
    "title_a": "Release asset set consumable by the self-updater: platform binaries, SHA256SUMS, SignedRelease metadata, manifest schema, mock-adapter zip; tag-triggered cross-repo pipeline",
    "title_b": "`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) \u2192 verifies the `.spt` against `signing_key` if declared, else HTTPS+GitHub first-acquisition trust \u2192 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).",
    "doc_a": "M6-D6/D7 (ADR-0015). How a release ships. CI builds; the maintainer signs locally \u2014 release keys never enter CI.",
    "doc_b": "adapter update declaration** (manifest field): 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 \u2014 spt-core fetches + swaps), **delegated command** (a binary command the adapter owns, e.g. `claude.exe plugin update` \u2014 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_release** avenue (since v0",
    "score": null,
    "reasoning": null
  },
  {
    "index": 136,
    "req_a": "REQ-RESIDENT-SERVICE",
    "req_b": "REQ-UPDATE-RUNNING-IMAGE-SURFACE",
    "title_a": "ResidentService substrate (ADR-0049, design ratified 2026-07-26): a daemon-supervised binary an adapter declares via a `[service]` manifest section \u2014 core-owned from birth, NO perch/identity/address. SPAWN: the daemon launches it job-neutrally (detached_no_inherit + the cold-start ladder posture), so it is never a shell's child (`/T` tree-kill cannot reach it; the shell-descendant hazard class of REQ-SHELL-ADAPTER-OWNED-DETACHED-SERVICE never arises) and never inside a launching terminal's Job Object (the REQ-SHELL-CLI-SPAWN-JOB-EXPOSURE service half closes by construction). START TRIGGER declared in the manifest: start = 'boot' or start = 'bind'; supervised identically once running, with the wake-watcher scaffolding (backoff, give-up latch, one-per-instance lock, orphan-kill, brain-side reconcile) minus the offline-only flip. 'boot' is DESIRED-STATE-RUNNING, not an event: the supervisor reconciles a boot service toward running at EVERY opportunity \u2014 daemon boot, ADAPTER REGISTRATION while the daemon is live (operator addition 2026-07-26: installing or registering an adapter whose manifest declares a boot service starts it THEN \u2014 spt itself is never restarted to bring a new adapter's service up), update-hold release, and first shell bind as the defensive ensure. 'bind' starts only at the adapter's first shell bind. CARDINALITY: one supervised instance per registered adapter-option `<adapter>[:profile]` (consumer-confirmed as COHERENT TARGET SHAPE \u2014 flynn's precision, 2026-07-26: not exercisable by the first consumer until per-option config dirs exist; alchemy today has one config dir and daemon.toml carries exactly one guild_id); the adapter may keep its own kernel file lock as a private double-start guard \u2014 core neither reads nor depends on it. PER-OPTION IDENTITY IS THREADED (flynn's gap, accepted): the supervisor passes the adapter-option name and the per-option runtime dir into the service's spawn environment, so an adapter can scope its private guard AND its config per option \u2014 the mechanism that makes two-options-two-services deliverable rather than merely permitted. Without it, two options resolving one adapter config dir produce the silent flap flynn derived: instance two exits immediately on the kernel lock, core sees only start-then-die, and crash-relaunch backoff is CORRECT behavior against that observation \u2014 two correct components disagreeing about the unit. UPDATE IS A FIRST-CLASS SUPERVISOR OPERATION WITH AN EXPLICIT HOLD: quiesce -> hold (stopped and NEVER relaunched while held) -> bits swap -> start new bits -> release; adapter update-apply performs this ordered operation; crash-relaunch with backoff applies ONLY when not held. The forcing case is structural, not advisory (flynn, argued against their own convenience): an eager relaunch during a swap re-pins the OLD exe mid-deploy (Windows exe lock), converting a diagnosable os-error-5 into an unwinnable race \u2014 if delivery must sequence, the hold ships FIRST and a dead service stays dead until told otherwise (the STALE-ONLINE no-spontaneous-relaunch ruling, same reason, one layer down). FAST-EXIT IS A CONFIGURATION FAULT, NOT A CRASH: consecutive immediate exits (exit within a startup threshold, N in a row) trip the give-up latch EARLY with a distinct loud STARTUP_FAULT diagnostic carrying the captured early stderr \u2014 a double-start lock conflict then reads as the configuration fault it is, never as a silently flapping service ground through backoff. QUIESCE IS COOPERATIVE EXIT + DEADLINE: the supervisor places a stop-request marker (a file in the service's runtime dir \u2014 polling services observe it on their next cycle; no inbox exists or is added); the service exits WHEN SAFE and the kernel-observed exit IS the ack \u2014 'not ready' is expressed by not-yet-exiting, so no busy record exists to go stale in either direction; a manifest-declared grace deadline (default ~30s) bounds the wait, then force-kill. Delay possible, veto never. An OPTIONAL advisory status line may surface in service status display \u2014 never consulted for decisions. LIVENESS IS DERIVED, NEVER RECORDED: the supervisor is the parent and holds the child handle (exit is kernel-observed); no supervisor-maintained running-record exists (the v0.43.0 stale-online lesson applied one layer down \u2014 flynn's condition, structural here). Any status/version identity surface keeps the locked-file split lesson: never require reading a file the service holds an exclusive OS lock on (Windows). CLI INVOCATION CAPABILITY (consumer-blocking, non-negotiable per flynn): the supervisor threads the environment so the service can invoke the spt CLI (`spt send` et al., identityless cli@node from-label, durable spooling per ADR-0002) \u2014 if a supervised Hub cannot shell out to spt send, node-wide Watch delivery dies silently. ADDRESSING: none \u2014 a service needing a two-way agent-facing surface has one at its adapter's endpoint/shell layer (the alchemy layering argument that re-scoped ADR-0023's faceless-service rejection); AlwaysOnEndpoint (REQ-EP-8) = this substrate + the addressable front. Gate at activation (all legs against a MOCK service adapter \u2014 the gate never depends on the first consumer being the boot specimen): int \u2014 a manifest [service start='boot'] binary rises with the daemon job-neutrally, a registration of that manifest against an ALREADY-LIVE daemon starts the service without any restart, survives a shell teardown of the same adapter (tree-kill does not reach it), a held update swaps bits with zero relaunch races (hold observed under a concurrent crash), quiesce marker -> cooperative exit within grace, deadline -> force-kill on a wedged mock, a mock that exits instantly N consecutive times surfaces STARTUP_FAULT (not a backoff flap), and the service successfully invokes spt send from its supervised environment. ACTIVATED FOR W1 2026-07-26 WITH THE FOLLOWING BUILD RULINGS FOLDED IN (doyle; constraints live in the artifact, not the dispatch thread). VERB SURFACE: the operator-facing verbs are `spt adapter service list` (all registered options + derived state) and `spt adapter service status <adapter[:profile]>`, nested under the ADAPTER group \u2014 NOT a bare `spt service`. Reason: 'service' is ALREADY public surface carrying an unrelated meaning \u2014 the platform daemon-service abstraction (REQ-DAEMON-6/-8, `crates/spt-daemon/src/service.rs`: the systemd user unit vs the Windows at-logon task) surfaces in `spt daemon` help as 'registered OS service' / 'managed service' / 'the managing service label'. Ownership-scoping separates the two meanings permanently: the OS-service is the DAEMON's and lives under `spt daemon`; the resident service is the ADAPTER's and lives under `spt adapter`. ADR-0049's 'service status display' means `spt adapter service status`; the W1 PR carries a one-line ADR errata note. GIVE-UP LATCH SCOPE: the latch suppresses relaunch grinding until something plausibly changed \u2014 it is NOT a durable verdict. Cleared by exactly three events: (1) DAEMON BOOT \u2014 desired-state-running enumerates boot as a reconcile opportunity with NO latch exception, and the re-trip is bounded (N fast exits) and LOUD (STARTUP_FAULT re-fires each boot); an in-memory per-daemon-lifetime latch is an acceptable implementation, and if the durable-marker scaffolding is reused then boot clears the marker; (2) ADAPTER RE-REGISTRATION \u2014 declared intent that manifest/config changed, reconciling immediately; (3) UPDATE-HOLD RELEASE \u2014 new bits invalidate the fault evidence. NOT cleared by the first-shell-bind ensure: a bind changes nothing about the service's config, so the bind-time reconcile SKIPS latched services \u2014 otherwise ordinary shell use converts the latch into the very flap it exists to stop. The REJECTED alternative is recorded deliberately: a durable latch with explicit-clear-only leaves an operator's already-fixed config fault sitting behind a service that stays dead and quiet forever \u2014 loud-bounded beats quiet-permanent (the heal-assigns-owner-never-closes shape one layer down). SANCTIONED BUT NOT W1-REQUIRED: `spt adapter service restart <adapter[:profile]>` as the explicit manual clear+reconcile \u2014 take it into W1 only if cheap once the verb group exists; the three automatic clears ARE the requirement. REGISTRATION-TIME START IS A WIRE OP: `registry::register` runs in the CLI PROCESS (`crates/spt/src/cli.rs`, the adapter-add and adapter-update call sites), so it cannot itself start anything in the daemon \u2014 registration-starts-the-service structurally requires ONE new adapter-scoped daemon IPC op, semantically `AdapterServiceReconcile { adapter }` (exact spelling matched to house op style where it lands), which the CLI calls after a SUCCESSFUL register on BOTH paths when the daemon is reachable. The daemon handler runs THE SAME reconcile code path as boot / hold-release / bind \u2014 ONE reconcile function taking an opportunity discriminant, never a second start authority. The response is a per-option outcome list (started / already-running / held / latched / bind-deferred / startup-fault) so the CLI prints honest per-option text. Daemon NOT reachable: registration STILL SUCCEEDS \u2014 never a refusal \u2014 and the CLI prints a REQUIRED notice that the service is declared, the daemon is not running, and it will come up at the next daemon boot. That notice is CONTRACT, not courtesy. RUNTIME-DIR ENCODING: cardinality is per adapter-option, so the option name becomes a PATH component and `:` is illegal in a Windows path. Core NEVER uses the raw option string as a path component \u2014 every construction site goes through ONE shared encoder, and that encoding MUST BE INJECTIVE (a lossless escape, never a strip/replace that can collide). Two distinct options mapping to one runtime dir means two services sharing a quiesce-marker namespace: the silent flap one layer down. The unit gate MUST include a collision-adversarial pair (e.g. if `:` maps to `_`, then `a:b` and `a_b` must remain distinct). MODULE PLACEMENT: the supervisor lands as `servicehost.rs` (the established `*host` convention \u2014 shellhost, harnesshost, linkhost, applyhost); `service.rs` is untouched and BOTH module headers cross-reference the other meaning of 'service'. INSTALL-DIR RESOLUTION RIDES W1 (operator-requested via flynn, ruled in after code verification): the `[service]` spawn resolves its binary through the EXISTING REQ-INSTALL-11 helper \u2014 the same primitive, NO parallel resolution path \u2014 and the same wiring lands at the two shell-family fill sites, which today resolve NEITHER the install-dir program token NOR `{adapter_dir}`. Without it a `--release`-installed shell adapter registers but cannot spawn (bare token \u2192 os error 2; `{adapter_dir}/x` \u2192 'no value for substitution key'), released shell adapters need a hand-maintained per-node manifest, `spt adapter update` on them is a permanent no-op, and THIS req's hold/swap/start ceremony would be exercisable by MOCK ONLY \u2014 never by the named first consumer. Site census discharged BEFORE build (authoritative grep, cfg(test) excluded, accepted by doyle): the production template-fill sites are `shellhost.rs::fill_spawn_command` and `shellwake.rs::fill_wake_command` (both targets \u2014 threading not shape, since the wake caller already holds `AdapterRecord.source_dir`, which IS the install dir), plus `harnesshost.rs` session `role.command`, which is OUT OF SCOPE and already resolves correctly through `resolve_program_in_dir`. Scope guard: shell spawn/wake + `[service]` ONLY \u2014 this does NOT expand into the `[session.self]`/`[history]` follow-on sites. FAULT COUNTERS ARE TWO, NOT ONE (ruled 2026-07-26 after the builder surfaced the reading): (1) the FAST-EXIT counter increments ONLY on exits whose uptime is UNDER the startup threshold, and RESETS the moment any run EXCEEDS that threshold. Without the reset the latch mislabels slow-crash decay as a configuration fault \u2014 the latch lying about cause, which is worse than no latch. STARTUP_FAULT is reserved for THIS path alone. (2) The ORDINARY consecutive-crash give-up (the shell wake-watcher scaffolding's `give_up_after` = 6, deliberately UPTIME-BLIND) sits BEHIND it UNCHANGED in W1 and keeps its existing diagnostic label \u2014 no silent behavior fork from the shell watcher. (3) BOTH counters reset on the latch-clear events above (daemon boot, adapter re-registration, update-hold release): a clear that left either counter primed would relatch on the first post-clear crash and thereby defeat the clear. (4) NOTED-OPEN, deliberately NOT W1 and NOT a promise: the ordinary counter's uptime-blindness means a service that crashes once a day gives up after six days and then stays down until a clear event. Whether that decay behavior is right for SERVICES (as opposed to the shell watchers it was designed for) is a future ruling; it is recorded here as open so the next builder inherits the question rather than rediscovering it in the field. ORPHAN ADOPTION IS IMAGE-VERIFIED, AND ITS PLATFORM GAP IS RECORDED-OPEN (ruled 2026-07-26 after the builder surfaced the trade): a fresh daemon kills a dead daemon's parked orphan ONLY by path-verified identity \u2014 a live pid whose image path cannot be READ classifies `Unverifiable` and BLOCKS adoption, i.e. the start refuses loudly rather than proceeding. Loud-blocked over quietly-double-started is this design's whole posture (a bare-pid kill is the recycled-pid class, and a double-start is the silent flap the cardinality rule exists to prevent). RECORDED-OPEN consequence, NOT debt owed by W1: the image oracle is `/proc` on unix, so a unix WITHOUT `/proc` (macOS/BSD) would block on every live orphan until that pid dies. This is theoretical for every platform we ship \u2014 win, linux-gnu, musl \u2014 and no macOS/BSD asset exists; a future builder adding one inherits the question here rather than rediscovering it in the field. SUPERVISOR PLACEMENT IS BROKER-SIDE (ruled 2026-07-26): the supervised set, its boot sweep and the reconcile control socket live in the BROKER process, beside the digest/drive/tunnel hubs \u2014 NOT in the restartable brain child that hosts shellwake. Two reasons, both structural. (a) A supervisor owns LIVE CHILD HANDLES and, from the update ceremony on, an in-memory HOLD: that is a daemon-lifetime continuity resource, which is the ADR-0018 Q2/Q5 broker-side test; the Q5 exception that put shellwake in the brain reads 'a pure disk-reconciler' and this is not one. (b) A brain restart is the ROUTINE UPDATE PATH (StartReason::Update exists precisely for it), so brain-hosting would bounce every resident service through the orphan-adoption path with no quiesce, no grace and no hold \u2014 the ungoverned bounce this req's ordered update operation exists to replace \u2014 and would lose the hold mid-swap. Broker-hosting is also what makes the wire op possible at all: all control sockets are broker-served because a CLI cannot reach brain memory (stated in drivehub.rs's module header and obeyed by every hub). NO PERIODIC SWEEP \u2014 CHOSEN, NOT OMITTED (ruled 2026-07-26): the supervisor host runs the boot sweep and then parks; there is deliberately no timer re-sweeping on a cadence. The four ruled opportunities are all EVENTS, each with a caller that enters the one reconcile function directly, so a timer would be a FIFTH start authority nobody ruled in \u2014 and its only distinctive work would be silently healing a failed registration nudge, converting a diagnosable defect into invisible behavior. The REQUIRED daemon-unreachable notice is the honest answer to that case; machinery that papers over its own failure class is refused (the same instrument-soundness razor as the rest of this design). A SUPERVISOR WHOSE DECLARATION DISAPPEARS RECONCILES TOWARD STOPPED (ruled into W1 2026-07-26): every sweep runs a STOP side before its start side \u2014 an option whose adapter is soft-deregistered, hard-removed, or whose manifest no longer declares a [service] is torn down through the handle that names its child. This is the symmetric half of desired-state-running, not an addition to it: without it a deregistered adapter's binary outlives its own registration until the daemon dies, which is exactly the ungoverned-lifetime shape this req abolishes. TREE TEARDOWN ON EVERY SUPERVISOR-INITIATED KILL (ruled 2026-07-26): the force-kill deadline is where the unconditional-kill promise is WRITTEN, but it is not the boundary of the problem \u2014 a supervised service's descendants are torn down on every kill the supervisor initiates, including daemon-shutdown stop_all and the stop-side sweep teardown. Descendants outliving THOSE paths are strictly worse off than ones outliving a force-kill, because the next daemon's orphan sweep is structurally blind to them: it knows one parked pid and holds no handle to anything below it. Windows reaches the tree through a SUPERVISOR-OWNED Job Object assigned at birth (CREATE_SUSPENDED -> AssignProcessToJobObject -> resume, so no descendant is ever spawned outside the job; KILL_ON_JOB_CLOSE deliberately OFF, so a dying supervisor is never an unannounced service outage); unix through the process group setsid already establishes. Job-NEUTRALITY is not contradicted: that invariant governs OTHER people's jobs reaching our processes, which CREATE_BREAKAWAY_FROM_JOB still handles at birth. A job the OS refuses is a LOUD DEGRADE, never a refusal to start: the process still dies on demand and only its descendants become unreachable, which is exactly where this path stood before the job existed \u2014 refusing the spawn would convert a bounded descendant leak into a total outage over a failure in an OS facility rather than in anything the adapter declared. THE STATUS SURFACE IS DAEMON-ANSWERED OR IT SAYS NOTHING (ruled 2026-07-26, the leg-D companion of the tree-teardown ruling): the CLI NEVER derives service state from the pid file. Either the daemon answers `spt adapter service list|status` over the control socket, or the CLI prints that the daemon is not running \u2014 verbatim, and with no fallback read. The pid file is a KILL HANDLE for the NEXT daemon, and reading it as liveness in a display surface is the v0.43.0 STALE-ONLINE class one layer down: a record answering a question the record cannot know. Three properties follow and are requirements, not implementation taste. (a) The status op is a PROJECTION over the live supervised set \u2014 the supervision threads, the hold flags and the stand-down records \u2014 plus the registry; it starts, stops, holds and kills nothing, because a diagnostic that converges the thing it measures is a start authority wearing a diagnostic's clothes. (b) A row reports its EVIDENCE: a latch surfaces with the captured startup output behind it, since a fault reported without its cause is the instrument failing at its one job. (c) An option SUPERVISED WITHOUT A DECLARATION behind it is reported as exactly that rather than hidden \u2014 with no periodic sweep, a deregistered adapter's supervisor lives until the next opportunity's stop side reaches it, and that window is precisely when an operator asks what is running. THE ADVISORY STATUS LINE IS DISPLAY-ONLY AND BOUNDED: the service may write one line into its runtime dir (named in docs/MANIFEST.md beside the stop-request marker, so it is implementable); core reads the FIRST line under a byte cap, treats an unreadable file as simply no advisory (the locked-file split rule holds), and consults it for NO decision \u2014 core deciding on it would put a least-trusted binary's self-report in the control path, and a service that stopped updating it would silently become whatever it last claimed. WIRE LENIENCY IS PLACED, NOT SPRINKLED: fields a reader BRANCHES on stay typed with a `#[serde(other)]` unknown arm (KH-2.3), while a field only ever ECHOED carries the daemon's label verbatim \u2014 re-deriving a lenient copy of the manifest's validated `start` vocabulary for the wire would weaken the one place strictness matters (registration). An op an older daemon predates HANGS UP rather than reading the request and answering nothing: a server that silently ignores an unknown kind leaves the caller blocked on a reply that never comes, so an older daemon would WEDGE a newer CLI instead of failing it.",
    "title_b": "`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) \u2014 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) \u2014 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) AMENDED 2026-07-27 (FIELD-TRUTH W1 roll-in, operator-directed): (1) `spt daemon status` MUST also divulge the COORDINATOR (brain) image version \u2014 the process that executes daemon-coordinated features and the most relevant running version to a status reader; same HARD CONSTRAINT: sourced from the RUNNING brain process (self-report over the broker\u2194brain channel or equivalent live query), never inferred from disk. (2) The stale-daemon warning RE-KEYS to the coordinator image vs installed: it renders ONLY when the COORDINATOR is outdated \u2014 post-apply the brain restarts onto the new bytes while the broker legitimately stays old (ADR-0018 D3-3), so the broker-keyed warning fired exactly backwards on a healthy freshly-updated node. (3) The remedy line recommends solely `spt daemon refresh` (in-place coordinator cycle; hosted terminals and the net layer keep running), never `daemon stop` + `daemon start`; a broker-breaking release still forces its full bounce through the update machinery, not through status advice. (4) The broker image line stays, informational only \u2014 no warning, no remedy text. Coordinator-version query unanswered (older daemon) renders not-reported without the warning.",
    "doc_a": "`command`** \u2014 an **opaque** command string (program token plus args), like every other command seam. Its program token resolves against the adapter install dir** before PATH (REQ-INSTALL-11), and args support adapter-static `{adapter_dir}` / `{adapter_name}` substitution only. Must be non-empty: a declared service means spt-core owns and supervises a process. `start`** \u2014 **required**, no default. `\"boot\"` is **desired-state-running, not an event**: the supervisor reconciles the service toward running at daemon boot, at **adapter registration against a live daemon** (installing or registering a",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 137,
    "req_a": "REQ-RESUME-CONTEXT-PULL",
    "req_b": "REQ-UPDATE-FINISH-COMMUNE-FLUSH",
    "title_a": "Adapter-callable resume-context pull verb + not-yet-synthesized commune/signoff drop append (legacy-SPT parity, operator-directed 2026-06-24). GAP: spt-core exposes NO verb for a harness adapter's SessionStart hook to pull an agent's resume context \u2014 `resume::download_psyche_context` (spt-live/src/resume.rs:88, composes <live-role>+<live-context>+<project-context> from the durable two-tier store) is INTERNAL with ZERO spt callers and no ApiCmd verb (api/mod.rs ApiCmd enum has none); resume.rs:9 documents the intended 'adapter pulls it in its SessionStart hook' path but it was NEVER wired. Result: a harness adapter cannot inject the agent's durable mind on resume at all (claude-spt today runs only `api boundary` session-rotation + an identity brief \u2014 the agent resumes WITHOUT its mind). TIER-1 SCOPE (operator-approved; Tier-2 = drift-stamp/<current>/drift-directive + <memformat> + Pulse-Log DEFERRED to a separate parity item, NOT v0.15.0 \u2014 the legacy download_payload [claude_skill_owl context.rs:344] is richer but memformat is roadmap-deferred + drift-stamp is an orthogonal cross-machine-drift feature). TWO PARTS: (1) EXPOSE `spt api psyche-download <id> [--session-id <sid>]` -> stdout = the composed brief, project_id resolved from the endpoint's bound cwd (info::read_info -> cwd -> project derive; NO --project arg), auth-gated like sibling id-scoped verbs (the `gated(&id,&auth,\u2026)` pattern); empty store -> NO-CONTEXT on stderr (mirror legacy). The adapter SessionStart hook runs it + injects stdout as additionalContext. (2) APPEND any commune/signoff drop NOT YET SYNTHESIZED into the durable tiers as a distinct <pending-commune>/<pending-signoff> slice AFTER the durable slices. GATING (operator ruling): append while NOT-YET-SYNTHESIZED, NOT merely 'while the raw file is on disk' \u2014 in today's synchronous ingest (ingest_drops route_two_slice writes durable THEN deletes the file, lifecycle.rs:466 @ DEFAULT_PULSE_PERIOD 5s) the two coincide (a watched-dir drop IS pre-synthesis), so the v1 realization reads the manifest-declared session.commune_dir/signoff_dir (manifest.rs:208/210) for a present <id>-commune.md/<id>-signoff.md (COMMUNE_SUFFIX/SIGNOFF_SUFFIX, ingest.rs); the CONTRACT keys on synthesis-state so it stays correct when async Psyche synthesis lands (a consumed-but-not-yet-committed drop stays appended via a pending-synthesis staging set \u2014 forward hook). The agent-checkpoint trigger sentinel CHECKPOINT_SENTINEL=`!!checkpoint!!` (a FIXED spt-core constant \u2014 operator-specified, CONTEXT.md \u00a7fixed-constants, NOT adapter-configurable) is stripped at BOTH drop-body points via one shared `strip_checkpoint_markers` (remove every token, keep inter-marker text, collapse trivial whitespace): the PRE-synthesis pending-append (resume::append_pending) AND the POST-synthesis durable ingest (ingest::route_slices \u2014 the single choke covering route_two_slice + signoff.write_resume_commune; strip-then-empty-filter so a marker-only slice routes nowhere) \u2014 else the marker would persist PERMANENTLY in live-context.md once a checkpoint drop synthesizes + re-trigger once the adapter's checkpoint detection is live. PRESENTATION-ONLY: the append NEVER writes the durable store (spt-core remains sole store-writer, REQ-HAZARD-DROP-FILE-SINGLE-WRITER; mirror legacy's read-only/process_file_drop-sole-deleter discipline). SELF-CLEARING: once synthesis commits the <pending-*> slice vanishes \u2014 no duplication. CORE-OWNED (not adapter): an adapter-side raw-file read RACES spt-core's ingest-delete (TOCTOU, ingest.rs:161 removes the drop on pulse-consume); the fold MUST live in the single composer all resume pulls flow through. New public CLI verb -> docs-drift gate (xtask gen + reference.md no-internal-codes, cli-command-docs-drift). (v0.15.0 parity wave W5)",
    "title_b": "DEFERRED (post-LIFECYCLE-TRUTH, operator-ruled 2026-07-07 \u2014 mint now, impl a FUTURE milestone): make the update swap LOSSLESS for live hosted endpoints by flushing a final echo-commune per endpoint BEFORE the brain-subtree reap. ROOT (operator-surfaced probing --finish): `update apply --finish` = daemonless swap -> daemon RESTART; the graceful `daemon stop` path (daemon.rs:316-325) raises brain_stop then reaper.reap() KILLS the brain subtree (brain + shellwake watchers + detached Psyches) as one unit \u2014 there is NO per-endpoint final commune before the kill. ENDPOINT-SURVIVAL (REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL) then RESPAWNS each orphaned online spt-hosted endpoint, but from its LAST commune (whatever the ongoing per-event echo-commune cadence last saved), NOT an as-of-swap checkpoint \u2014 so mid-turn / uncommuned work is lost across the bounce. Today's mitigation is operator discipline: commune-before-swap. FIX (future): the stop/finish path, before reap, drives each LIVE hosted endpoint's final echo-commune (fire_echo final context save) so the respawn resumes from a swap-fresh checkpoint. Composes with ENDPOINT-SURVIVAL (commune -> reap -> respawn) and the W1 echo pipeline (REQ-ECHO-DROP-DIR-RESOLVE / REQ-PSYCHE-STAMP-CLEAR-ANY-SUCCESS). Bounded + loud per endpoint (a commune that hangs must not wedge the stop \u2014 timeout then reap anyway, never block the swap). Int: a live hosted endpoint with uncommuned state -> --finish -> respawned endpoint's digest/psyche reflects the pre-swap state (RED-first: without the flush the respawn shows only the last-cadence commune).",
    "doc_a": "resume-session seam** \u2014 two distinct forms: fresh-with-preload:** resume with *cleared* context (a fresh session) + psyche-download. Accepts a `$psyche-context` key to launch the fresh session with the psyche-download preloaded \u2014 or the adapter instead pulls it via an spt-core command in its SessionStart hook. <!-- --> That command is **`spt api psyche-download <id> [--session-id <sid>]`**: it emits the durable resume brief (role \u2192 live-context \u2192 project-context, project resolved from the perch's bound cwd) to stdout for the adapter's SessionStart hook to inject as additional context, and APPE",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 138,
    "req_a": "REQ-RUN-EMPTY-CREATE",
    "req_b": "REQ-TERM-ECHO-CLAMP-WINDOW",
    "title_a": "`spt endpoint run` / bare `spt` routes a TOTALLY-EMPTY scope straight to the endpoint-creation flow: when gather_endpoints() is empty (nothing attachable, local OR subnet) PickerModel::new opens on Screen::CreateAdapter instead of PickExisting (today it always opens PickExisting + renders a blank list). A node WITH subnet endpoints but no local ones still has things to pick \u2192 stays on the picker. Extends REQ-RUN-PICKER. (v0.16.0)",
    "title_b": "SEED (DAEMON-LIFECYCLE W3 rideout, field-grounded 2026-07-22): the hosted-TUI echo CLAMP WINDOW \u2014 a Windows pseudoconsole boots with echo/line input ON (in=0x1f7, measured by the 7.55 instrument @0f74bba) and it is the hosted CHILD that clamps them, so any window in which the TUI has not yet (re-)clamped echoes typed bytes into PTY output SERVER-SIDE with no seam re-enabling anything. FIELD GROUNDING (hertz capture-2 byte timeline, output-only taps): echo onset is NOT resize-instant \u2014 first echoed key lands 6003ms after the first resize-associated repaint burst, IMMEDIATELY after a 3225-byte TUI-reinitialization-shaped absolute repaint (no alt-screen/mode CSI anywhere \u2014 cursor hide/show + HOME/absolute repaints only); echo CEASES mid-input (isolated c/o/n/f/i, NO g) directly after a 535-byte TUI diff \u2014 consistent with a late clamp landing (a WinAPI mode call is invisible to a byte tap). The resize seam itself is measured MODE-PRESERVING (REQ-RESIZE-INPUT-MODE-INTEGRITY rig, four sample points). OPEN AT TRIAGE: (1) whether the clamp is the TUI's own SetConsoleMode or portable-pty/ConPTY-internal; (2) whether spt-core CAN mitigate at all \u2014 the daemon structurally cannot read or set the child console's input modes from the master side (platform fact, 7.55), so candidate mitigations are indirect (e.g. hold/queue injected input until first child output after a reinit-shaped burst \u2014 evaluate honestly, may be worse than the disease) and the honest outcome may be an OUT-OF-OUR-CODE finding filed upstream (hosted-TUI reinit behavior + Windows boot default); (3) whether the window also explains historical spt-hosted-only echo reports (non-spt sessions bypass the nested ConPTY \u2014 KH 7.55 note). Kin: KH 7.55 (hazard + instrument), KH 7.56 / the DNAR class (console boot defaults and clamp ownership = one seam family), REQ-ATTACH-SEED-REQUEST-DOUBLE-SERVE (the rideout-seed precedent). Instrument + rig to reuse: the 7.55 probe child (protocol-line verdict, raw-first precondition, seeded-echo capability probe).",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 139,
    "req_a": "REQ-SCREENGRID-REPAINT-MODE-REPLAY",
    "req_b": "REQ-TERM-3",
    "title_a": "RC-RENDER-TRUTH W3 (ADR-0043 decision 4, hertz stale-glyphs RCA leg 4 P1): ScreenGrid cold repaint replays EVERY tracked render-affecting mode \u2014 DECSTBM scroll margins at minimum \u2014 before final cursor placement (today render_repaint omits tracked margins, so client and server grids interpret subsequent raw scrolling against different regions => 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 \u2014 tracked-mode replay in render_repaint; unit \u2014 repaint emits tracked DECSTBM, emulator contract holds for scroll-after-repaint; doc \u2014 ADR-0043.",
    "title_b": "Byte-stream remote terminal streaming for v1",
    "doc_a": "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 \u2014 producer order is the contract. Output-before-Exit is a production-path invariant, regression-proven end-to-end (broker \u2192 attach \u2192 rc). 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 including errors and unwind: best-e",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 140,
    "req_a": "REQ-SEAM-UPDATE",
    "req_b": "REQ-UPDATE-ONE-SHOT-FINISH",
    "title_a": "Adapter-update avenue (file-pull / delegated command)",
    "title_b": "W3 (LIFECYCLE-TRUTH): update apply works daemonless and one command finishes the cycle. ROOT (operator wart): update fetch/apply run ensure_daemon_announced (cli.rs:4386) -> on a stopped box they BOOT THE OLD broker pre-swap, guaranteeing the mixed old-broker/new-brain pair + a manual bounce. FIX: apply works daemonless (swap + record, next start runs new bytes); `update apply --finish` (name subject to docs-token gate) completes the cycle: swap -> brain cycle -> broker restart onto new bytes (rides REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL so the restart is not a massacre). CLI change -> xtask docs gen, no internal codes in clap ///.",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 141,
    "req_a": "REQ-SHELL-1",
    "req_b": "REQ-SHELL-4",
    "title_a": "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)",
    "title_b": "Shell tunnel (reliable-ordered opaque byte stream): an owner<->shell link may hold a long-lived, reliable-ordered, link-bound QUIC stream pair carrying opaque wire protocol traffic the channel taxonomy must NOT reinterpret (first consumer usbip URB) \u2014 manifest opt-in, not enveloped, not MAC-framed, not spooled; the link lifecycle governs it (a link-break closes the tunnel). Reliable-ordered \u21d2 congestion surfaces as lag never loss \u21d2 acceptable only on-LAN: the on-LAN posture is documented and the tunnel is NOT proven cross-WAN (CONTEXT:262, minted 2026-06-11 Gateway grill; doyle gate C2).",
    "doc_a": "",
    "doc_b": "shell tunnel: a long-lived reliable-ordered link-bound QUIC stream pair carrying opaque bytes the taxonomy never reinterprets; manifest opt-in, not enveloped/MAC-framed/spooled; link-break closes it; reliable-ordered \u21d2 on-LAN posture Channels carry typed, taxonomy-interpreted payloads. Distinct from them, an owner\u2194shell link may also hold a **shell tunnel**: a long-lived, **reliable, ordered** byte stream (a dedicated QUIC stream pair bound to the link) for protocol traffic the channel taxonomy must NOT reinterpret \u2014 opaque wire protocols spoken end-to-end (first consumer: USB/IP URB traffic t",
    "score": null,
    "reasoning": null
  },
  {
    "index": 142,
    "req_a": "REQ-START-5",
    "req_b": "REQ-WHOAMI-1",
    "title_a": "Adapter-agnostic harness-hosted seed + bind-time adapter/profile resolution (ADR-0021): `api seed` carries only parent_pid + session_id (+ optional cwd), no --adapter \u2014 a pure \"a harness session exists at this pid\" record; --adapter becomes an OPTIONAL override across the whole api group (an explicit name[:profile] for adapter dev, never required). Omitted, listen/poll resolve the owning adapter/profile AT BIND as a pure read against the live registry \u2014 never a seed-time snapshot that can drift: seed parent_pid \u2192 exe basename \u2192 host_binaries candidate set (REQ-MANIFEST-8) \u2192 active-profile pointer (REQ-INSTALL-12) primary, else greatest-registered_at_ms candidate base profile (name-asc tie) \u2192 friendly zero-match error. Covers BOTH LiveAgent (listen) and ReadyAgent (poll) bringup. Restores legacy parity: `$LIVE start <id>` \u2192 `$SPT listen <id>` with no mandatory --adapter, one generic SessionStart hook per harness binary. (v0.9.0)",
    "title_b": "The `endpoint list` SELF pin carries the Self endpoint's authored `endpoint description` (info::read_info(...).resources) when present, inline after the liveness state; whoami stays a top-level hot-path verb (parse unchanged, REQ-MSG-9) and renders the same description-carrying SELF pin. HISTORY: originally minted whoami as a thin ALIAS of `spt endpoint list` \u2014 that alias premise is SUPERSEDED by REQ-WHOAMI-IDENTITY-ONLY (PROJECT-INDEX W1, 2026-07-15): the alias inherited the list's O(perches x branches) git fanout onto hook paths (the 2026-07-15 message-delivery incident), so whoami is now identity-only over the shared render_self_pin. The pin render + parse evidence here stands; the full-roster surface lives solely on `endpoint list`.",
    "doc_a": "Harness-hosted (e.g. spt-plugin; the harness binary is user-launched, harness is the parent).** Key constraint: the SPT *live agent* does not exist until the agent invokes start \u2014 the `live_id` isn't chosen at session boot, and `$LIVE start` is itself invoked *behind the Monitor tool*, so it becomes the long-running relay. So binding cannot happen at SessionStart directly. A **seed record** (daemon-held, in-memory \u2014 not a file) bridges the gap: 1. The harness's SessionStart hook calls **`spt api seed --pid <parent_pid> --session-id <sid> [cwd]`**. The daemon records an ephemeral in-memory **se",
    "doc_b": "whoami** (alias for endpoint list): `spt whoami` is a thin **alias for `spt endpoint list`** \u2014 it prints the full view with the session's own endpoint **SELF-pinned first**, that pin carrying the endpoint's id, liveness state, and its authored **endpoint description** (the \"who am I\" answer). There is no separate bare-id command: nothing captured `id=$(spt whoami)` (environment variables don't persist between an agent's tool calls), so there is no scripting contract to preserve. `whoami` stays a top-level hot-path verb (its parse is unchanged, REQ-MSG-9); only the SELF pin's new description li",
    "score": null,
    "reasoning": null
  },
  {
    "index": 143,
    "req_a": "REQ-SUBNET-ADMIN-SEED-REPLICATION",
    "req_b": "REQ-SUBNET-REVOKE-ADMIN-GATE",
    "title_a": "Every member node holds BOTH subnet seeds \u2014 the admin seed replicates at join exactly as the member seed does \u2014 because empower verification and admin-code joins are LOCAL operations any member node must be able to serve without consulting a privileged peer (ADR-0051 decision 2; the mesh stays symmetric, authority rides a key rather than a node). Carries the NO-REVEAL invariant, which is the load-bearing half: there is no admin show-code, no QR re-provision, no `--json` field, no debug/diagnostic/error/log rendering of the admin seed ANYWHERE \u2014 it is displayed exactly once, at creation, and thereafter no node can leak what no node can reveal. A lost authenticator entry is irrecoverable by design (ADR-0051 decision 3): re-mint the subnet. The invariant binds tests too \u2014 a test that asserts the seed's VALUE by printing it is itself the leak (secret-probe discipline: never expand a secret into an observable sink). Gate: doc \u2014 ADR-0051 decisions 2 and 3; impl \u2014 admin-seed replication on the join path and the absence of every reveal surface; unit \u2014 a joined node holds both seeds and can verify an admin code locally, and no CLI/JSON/diagnostic output path emits the admin seed.",
    "title_b": "`spt subnet revoke` (both forms, --force-rotate-seed included) requires the subnet's CURRENT admin TOTP \u2014 a member code is insufficient, and OS elevation stays on top answering its different question (ADR-0051 \u00a72a, FF grill 2026-07-30). Eviction is a subnet-governance act, and the proof doubles as the identity gate for the re-surface: only the proven incumbent admin is shown the replacement key. The gate runs BEFORE any write, so a cancelled, thrice-wrong, or non-interactive attempt (refused up front \u2014 the gate needs a human and the ceremony after it needs one more) leaves roster, schedule, and seeds untouched. Acceptance is the same \u00b11-step window every other typed-code ceremony uses. A subnet predating the two-key model has no admin authority to prove; it proceeds loudly on elevation alone. Gate: doc \u2014 ADR-0051 \u00a72a; impl \u2014 the per-subnet gate in the revoke path, ordered before all writes; unit \u2014 the windowed code acceptance (shared with the capture proof) and the nothing-written refusal orderings exercised at the gate seam.",
    "doc_a": "2. Held everywhere, revealable nowhere / 3. Irrecoverable by design",
    "doc_b": "2a. Surfaced only to a proven admin \u2014 at mint and at rotation (amended 2026-07-30, fast-follow grill)",
    "score": null,
    "reasoning": null
  },
  {
    "index": 144,
    "req_a": "REQ-UPD-1",
    "req_b": "REQ-UPDATE-GH-TRANSPORT",
    "title_a": "Peer-propagated update over P2P",
    "title_b": "THE-FORKENING W1 (ADR-0036, operator-ruled 2026-07-14): the release channel is PRIVATE (`BigscreenVR/spt-bs-releases`) and the gh CLI is the mandated carrier \u2014 release discovery (`releases/latest`, cli.rs:9717) and asset download (cli.rs:4861 public browser URLs) move to deadline-wrapped `gh` subprocess calls (`gh api`, `gh release download`; run_git pattern). WHY gh not token+HTTP: private-repo `browser_download_url` 404s even with a valid token \u2014 the API asset-id dance is gh's job. Default repo flips via the existing SPT_INSTALL_REPO seam (cli.rs:5363) + xtask REPO const (main.rs:729) + notif.rs consent-changelog URL rider. Loud failure classes: gh missing -> UPDATE_FETCH_REJECTED:GhCliRequired with OS-SPECIFIC install hints (winget/apt/brew); gh unauthed -> distinct GhAuthRequired pointing at `gh auth login`. Signature verification unchanged \u2014 bytes verified after download, carrier-independent (update-set/counter/anchor continuity per ADR-0036 \u00a72). release_verify_e2e reworked to the gh carrier. Gate: unit \u2014 url/invocation construction + both failure classes render OS-correct hints; int \u2014 fetch against a real gh-authed channel resolves latest + downloads and verifies an asset; doc \u2014 self-update docs name the gh prerequisite. Kin REQ-INSTALL-BOOTSTRAP-VERB (same carrier at first install), ADR-0036.",
    "doc_a": "",
    "doc_b": "release channel (private, gh-carried)** \u2014 the release channel is a **private** GitHub repo (`BigscreenVR/spt-bs-releases`, ADR-0036); the **gh CLI is the mandated carrier** for release discovery and asset download (each node authenticates via org membership). A node without an authed `gh` cannot fetch \u2014 refused loud with OS-specific install hints, never a silent hang. Signature verification is carrier-independent: bytes are verified after download exactly as before; counter, signing key, and update-set format are unchanged from the public-channel era. / Prerequisite: the GitHub CLI.** The rele",
    "score": null,
    "reasoning": null
  },
  {
    "index": 145,
    "req_a": "REQ-UPD-6",
    "req_b": "REQ-UPD-9",
    "title_a": "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)",
    "title_b": "`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) \u2192 verifies the `.spt` against `signing_key` if declared, else HTTPS+GitHub first-acquisition trust \u2192 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).",
    "doc_a": "Build plan \u2014 `xtask debug-converge` (deferred follow-up) / Debug rollout runbook",
    "doc_b": "adapter update declaration** (manifest field): 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 \u2014 spt-core fetches + swaps), **delegated command** (a binary command the adapter owns, e.g. `claude.exe plugin update` \u2014 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_release** avenue (since v0",
    "score": null,
    "reasoning": null
  },
  {
    "index": 146,
    "req_a": "REQ-UPDATE-APPLY-ALREADY-APPLIED",
    "req_b": "REQ-UPDATE-FINISH-COMMUNE-FLUSH",
    "title_a": "`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) \u2014 already-applied \u2192 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)",
    "title_b": "DEFERRED (post-LIFECYCLE-TRUTH, operator-ruled 2026-07-07 \u2014 mint now, impl a FUTURE milestone): make the update swap LOSSLESS for live hosted endpoints by flushing a final echo-commune per endpoint BEFORE the brain-subtree reap. ROOT (operator-surfaced probing --finish): `update apply --finish` = daemonless swap -> daemon RESTART; the graceful `daemon stop` path (daemon.rs:316-325) raises brain_stop then reaper.reap() KILLS the brain subtree (brain + shellwake watchers + detached Psyches) as one unit \u2014 there is NO per-endpoint final commune before the kill. ENDPOINT-SURVIVAL (REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL) then RESPAWNS each orphaned online spt-hosted endpoint, but from its LAST commune (whatever the ongoing per-event echo-commune cadence last saved), NOT an as-of-swap checkpoint \u2014 so mid-turn / uncommuned work is lost across the bounce. Today's mitigation is operator discipline: commune-before-swap. FIX (future): the stop/finish path, before reap, drives each LIVE hosted endpoint's final echo-commune (fire_echo final context save) so the respawn resumes from a swap-fresh checkpoint. Composes with ENDPOINT-SURVIVAL (commune -> reap -> respawn) and the W1 echo pipeline (REQ-ECHO-DROP-DIR-RESOLVE / REQ-PSYCHE-STAMP-CLEAR-ANY-SUCCESS). Bounded + loud per endpoint (a commune that hangs must not wedge the stop \u2014 timeout then reap anyway, never block the swap). Int: a live hosted endpoint with uncommuned state -> --finish -> respawned endpoint's digest/psyche reflects the pre-swap state (RED-first: without the flush the respawn shows only the last-cadence commune).",
    "doc_a": "",
    "doc_b": "",
    "score": null,
    "reasoning": null
  },
  {
    "index": 147,
    "req_a": "REQ-UPDATE-GH-TRANSPORT",
    "req_b": "REQ-UPDATE-RUNNING-IMAGE-SURFACE",
    "title_a": "THE-FORKENING W1 (ADR-0036, operator-ruled 2026-07-14): the release channel is PRIVATE (`BigscreenVR/spt-bs-releases`) and the gh CLI is the mandated carrier \u2014 release discovery (`releases/latest`, cli.rs:9717) and asset download (cli.rs:4861 public browser URLs) move to deadline-wrapped `gh` subprocess calls (`gh api`, `gh release download`; run_git pattern). WHY gh not token+HTTP: private-repo `browser_download_url` 404s even with a valid token \u2014 the API asset-id dance is gh's job. Default repo flips via the existing SPT_INSTALL_REPO seam (cli.rs:5363) + xtask REPO const (main.rs:729) + notif.rs consent-changelog URL rider. Loud failure classes: gh missing -> UPDATE_FETCH_REJECTED:GhCliRequired with OS-SPECIFIC install hints (winget/apt/brew); gh unauthed -> distinct GhAuthRequired pointing at `gh auth login`. Signature verification unchanged \u2014 bytes verified after download, carrier-independent (update-set/counter/anchor continuity per ADR-0036 \u00a72). release_verify_e2e reworked to the gh carrier. Gate: unit \u2014 url/invocation construction + both failure classes render OS-correct hints; int \u2014 fetch against a real gh-authed channel resolves latest + downloads and verifies an asset; doc \u2014 self-update docs name the gh prerequisite. Kin REQ-INSTALL-BOOTSTRAP-VERB (same carrier at first install), ADR-0036.",
    "title_b": "`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) \u2014 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) \u2014 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) AMENDED 2026-07-27 (FIELD-TRUTH W1 roll-in, operator-directed): (1) `spt daemon status` MUST also divulge the COORDINATOR (brain) image version \u2014 the process that executes daemon-coordinated features and the most relevant running version to a status reader; same HARD CONSTRAINT: sourced from the RUNNING brain process (self-report over the broker\u2194brain channel or equivalent live query), never inferred from disk. (2) The stale-daemon warning RE-KEYS to the coordinator image vs installed: it renders ONLY when the COORDINATOR is outdated \u2014 post-apply the brain restarts onto the new bytes while the broker legitimately stays old (ADR-0018 D3-3), so the broker-keyed warning fired exactly backwards on a healthy freshly-updated node. (3) The remedy line recommends solely `spt daemon refresh` (in-place coordinator cycle; hosted terminals and the net layer keep running), never `daemon stop` + `daemon start`; a broker-breaking release still forces its full bounce through the update machinery, not through status advice. (4) The broker image line stays, informational only \u2014 no warning, no remedy text. Coordinator-version query unanswered (older daemon) renders not-reported without the warning.",
    "doc_a": "release channel (private, gh-carried)** \u2014 the release channel is a **private** GitHub repo (`BigscreenVR/spt-bs-releases`, ADR-0036); the **gh CLI is the mandated carrier** for release discovery and asset download (each node authenticates via org membership). A node without an authed `gh` cannot fetch \u2014 refused loud with OS-specific install hints, never a silent hang. Signature verification is carrier-independent: bytes are verified after download exactly as before; counter, signing key, and update-set format are unchanged from the public-channel era. / Prerequisite: the GitHub CLI.** The rele",
    "doc_b": "",
    "score": null,
    "reasoning": null
  }
]