[
  {
    "pair_num": 1,
    "idA": "REQ-ACL-ACCESS-REFRESH-ER-ONLY",
    "titleA": "spt api access-refresh stops refusing and becomes real, gated to the engine room, updating ONLY the node's captured subnet-level fallbacks (ADR-0052 decision 6; the W2 refusal REQ-ACL-ACCESS-REFRESH-VERB was minted precisely so this wave changes one behavior rather than adding a surface and its gate). The refresh is the CONSENTED half of advisory gossip: a subnet-mode change reaches a member as a notification, the engine room is briefed with the exact new posture, and a human decides whether this node adopts it \u2014 which is why the verb writes the captured fallbacks and never the node's own rules, and why no remote actor can invoke it. Its authentication is the same shared engine-room function empower uses. The W2 refusal test is repinned to the new contract in this same change, not left asserting a behavior the wave removed. Gate: doc \u2014 ADR-0052 decision 6 and the CONTEXT.md capture-refresh sentence; impl \u2014 the real refresh behind the shared engine-room gate, writing captured subnet fallbacks only; unit \u2014 an engine-room caller refreshes and the chain's bottom tier changes, a non-engine-room caller is refused, the node's own rules are untouched, and the repinned W2 test asserts the new contract.",
    "docA": "A node's **effective** subnet modes are captured at its join and are immutable from outside. Mode changes replicate as advisory metadata: they surface as a notif, and the engine-room is briefed at session start with the exact new posture, encouraged to offer the user a sync. The capture-refresh is an `spt api` verb **only the engine-room can invoke**, and it updates only the captured subnet-level fallbacks \u2014 never the node's own rules. No remote actor can change a node's effective posture.",
    "idB": "REQ-HAZARD-WAN-ORIGIN-AUTH",
    "titleB": "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)",
    "docB": "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"
  },
  {
    "pair_num": 2,
    "idA": "REQ-ACL-MODE-ADVISORY-GOSSIP",
    "titleA": "A subnet-mode change gossips ADVISORILY \u2014 it produces a notification and nothing else. An existing member's EFFECTIVE posture never changes remotely: the captured mode (REQ-ACL-SUBNET-MODE-CAPTURE) is immutable except through the node's own refresh (REQ-ACL-ACCESS-REFRESH-VERB), so no remote party can reach into a member node and re-posture its gate. This is the difference between a subnet owner ADVISING members of a policy change and COMMANDING their enforcement \u2014 on a shared subnet whose members are different humans, only the former is defensible, and a producer that quietly applied would be a remote write to security material. Gate: doc \u2014 the CONTEXT.md control-surface-modes advisory-gossip sentence; impl \u2014 the notif producer on subnet-mode change, with NO apply path; unit \u2014 a mode-change gossip raises the notification and leaves the receiving node's captured mode and its decisions byte-identical.",
    "docA": "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",
    "idB": "REQ-MSG-6",
    "titleB": "cross-node Gateway user-msg honored via advertised endpoint_type: a user-msg from a Gateway-typed origin survives the receive_wan funnel as user-msg (vs the fail-closed re-stamp), keyed on the QUIC-handshake-proven origin node (never wire `from`). Trust boundary = subnet membership (operator-ratified 2026-06-13); no defense against an in-subnet member forging the type. Instance.endpoint_type is an additive serde-default field extending REQ-INST-7's data model. Absent/unknown type \u2192 re-stamp (N-1 rollout grace)",
    "docB": "_Implemented posture_: the **local** user-backed origins are honored end-to-end \u2014 a locally-hosted Gateway endpoint (info.json `state=\"gateway\"`) and the local user's CLI (M9-T4/T5). The **cross-node WAN** path is being completed (trust posture **ratified 2026-06-13**): the **subnet membership boundary is the trust boundary**. A subnet is a collection of machines the user already trusts, so a `user-msg` arriving over the subnet from a **Gateway-typed** origin is honored as the user's authority; the daemon does **not** defend against a subnet member *forging* the Gateway type \u2014 an in-subnet com"
  },
  {
    "pair_num": 3,
    "idA": "REQ-ACL-SUBNET-MODE-CAPTURE",
    "titleA": "A subnet's control-surface mode is recorded on the subnet and CAPTURED AT JOIN TIME into the joining node's own store \u2014 the W1 captured-subnet-mode table stops being a stub and lights up tier 8, the chain's bottom fallback (CONTEXT.md 'control-surface modes' resolution order). Capture, not lookup, is the point: a node's effective posture must be a local, immutable fact it consented to when it joined, so a subnet owner cannot retroactively tighten or loosen what an already-joined member enforces (that is what makes the advisory-gossip posture in REQ-ACL-MODE-ADVISORY-GOSSIP coherent rather than a race). Migration posture is explicit and must be tested: an EXISTING subnet has no captured mode, so tier 8 stays absent, the chain bottoms out open, and fleet behavior is preserved exactly \u2014 an upgrade must not silently close a running mesh. Gate: doc \u2014 the CONTEXT.md control-surface-modes entry (three levels and the resolution chain); impl \u2014 the subnet-mode record, the join-time capture into the endpoint access store, and tier 8 reading it; unit \u2014 a joined-with-closed node refuses where an unlisted subject falls through to tier 8, a joined-with-open node admits, and a pre-existing subnet with no capture bottoms open.",
    "docA": "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",
    "idB": "REQ-MSG-SENDER-STAMP",
    "titleB": "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.",
    "docB": "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"
  },
  {
    "pair_num": 4,
    "idA": "REQ-HAZARD-WAN-ORIGIN-AUTH",
    "titleA": "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)",
    "docA": "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",
    "idB": "REQ-REACH-2",
    "titleB": "Remote command execution (deferred, consent-gated)",
    "docB": ""
  },
  {
    "pair_num": 5,
    "idA": "REQ-ADAPTER-ADD-SURFACE-ERRORS",
    "titleA": "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.",
    "docA": "",
    "idB": "REQ-WORKER-MINTED-NAME",
    "titleB": "N-1 (WORKER-TRUTH triage, operator rider): worker perch identity is CORE-MINTED and parent-derived \u2014 `{parent}-w{N}` with a per-parent counter at registration (sister shape: claude_skill_owl hook_subagent_start.rs) \u2014 never the adapter-presented agent id (CC Task ids render as random-named rows). worker-start mints + echoes the id (WORKER_STARTED:{parent}-w{N}); the adapter's agent_id/agent_type ride the record as correlation METADATA, not identity. Verb-shape contract change \u2014 freeze with W-2 in ONE coordination with perri.",
    "docB": "`api worker-start <parent> [--agent-id <id>] [--agent-type <type>]`"
  },
  {
    "pair_num": 6,
    "idA": "REQ-ADAPTER-PROOF-DIR-OVERRIDE",
    "titleA": "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)",
    "docA": "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",
    "idB": "REQ-ADAPTER-TEMPLATE-KEY-VALIDATION",
    "titleB": "P-1 core half (WORKER-TRUTH triage): `adapter add`/`adapter update` validate every declared role template \u2014 command, cwd, and [env] inject values \u2014 against the substitution-key catalog (spt-runtime BASE_KEYS + role-specific overrides) and REFUSE registration naming the offending key + role (fail-fast family of the [strings] pointer validation). Field driver: flynn's psyche died on the RETIRED {psyche_dir} key (adapter psyche_resume carried the old psyche_init cwd shape) \u2014 a permanent template config fault must die loudly at registration, not at the Nth per-event psyche turn via the 3-strike budget. The catalog stays the single source (runtime.rs FILL_KEYS \u2014 'a catalog key must have a real fill'); validation reads it, never a second list.",
    "docB": ""
  },
  {
    "pair_num": 7,
    "idA": "REQ-ADAPTER-UNRESOLVED-HINT-FORM",
    "titleA": "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.",
    "docA": "",
    "idB": "REQ-DIGEST-SEAL-ON-IDLE",
    "titleB": "A digest turn's records gain their stable `seq` at the endpoint's IDLE TRANSITION \u2014 the adapter-reported, already-authoritative turn-finished signal \u2014 not at next-user-input arrival (which is demoted to a harmless no-op fallback seal). Kills the mint-then-idle deadlock (liam/spt-progress-tool W16: a finished-but-idle turn stayed partial/seq-less, so a seq-keyed scanner could never see the owner's latest turn until the owner was prompted again; interim drive-one-more-command workaround retires at ship). Sealing is IDEMPOTENT and SEQ-STABLE: it assigns seq to what the log holds at idle; a late-flushing straggler record folds into the sealed turn WITHOUT changing the assigned seq. (ADR-0048 decision 4; rejected: extractor-observed completion = banned heuristic inference class; provisional seqs = scanner bug factory.) Gate: int \u2014 the liam field shape RED-first: finish a turn, report idle, do NOT prompt again \u2014 the turn is sealed with a stable seq and a scanner keyed on seq sees it; fallback leg: next-input on an already-sealed turn re-seals nothing; straggler leg: a post-idle log record folds in, seq unchanged.",
    "docB": "A turn closes when the endpoint goes idle \u2014 not when the next input arrives.** The moment the endpoint reports it stopped working, the trailing turn is finished: `partial` drops away and its entries take their seqs, with no further prompting needed. This matters if you scan for something in the latest** turn: you no longer have to wait for (or manufacture) another user input before that turn has a stable cursor. A later input still closes the previous turn as it always did \u2014 that path simply is not the only one any more. / 4. The idle transition seals the digest turn"
  },
  {
    "pair_num": 8,
    "idA": "REQ-API-1",
    "titleA": "api prefix and adapter_name on every machinery invocation",
    "docA": "",
    "idB": "REQ-HAZARD-SESSION-PIN-WEDGE",
    "titleB": "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)",
    "docB": "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"
  },
  {
    "pair_num": 9,
    "idA": "REQ-API-ENDPOINT-INFO",
    "titleA": "#7: spt api endpoint-info [<id>] (JSON) lets an endpoint learn its ATTACHED (controlling) node \u2014 claude-spt surfaces local + attached node names on UserPromptSubmit so the agent knows whether getting a file to the user needs extra steps (user RC'd in from another machine). spt api * is the harness-contract agent-facing surface (JSON-first, rides perch identity/auth so the bare no-<id> form self-resolves like whoami). Payload (committed DTO, additive-forever): { id, endpoint_type, adapter, local_node:{label,key}, attached_node:{label,key}|null, controlled:bool, project:<current project id>, cwd, subnets:[...] } \u2014 attached_node from controller stamps (driven_by remote / self-node when controlled with no remote driver), null when uncontrolled. HARD dependency on #2 + #3 (stamps must be honest first). Adapter-side consumable -> perri release-ping on publish. Naming: chose 'spt api endpoint-info' over alt 'spt endpoint get-info' \u2014 api is the agent surface (doc rationale). See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #7.",
    "docA": "",
    "idB": "REQ-RUN-ID-REUSES-ADAPTER",
    "titleB": "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.",
    "docB": ""
  },
  {
    "pair_num": 10,
    "idA": "REQ-DOC-ECHO-COMMUNE-CONTRACT",
    "titleA": "W6 (LIFECYCLE-TRUTH, docs \u2014 this gap cost a full outage night, priority slot): publish the [session.echo_commune] I/O contract on the docs-site: key catalog core fills; core does NOT stdin-feed [history] (field-proven); self-locate guidance incl. CLAUDE_CONFIG_DIR / read_env; drop-file protocol (single-writer, ingest-deletes, resolver semantics from W1); stdout ingestion expectations. Public docs use VERSION numbers, never wave codes; docs-publish drift gate applies.",
    "docA": "the full echo-commune I/O contract: the role + fields, the key catalog spt-core fills, the no-history-on-stdin rule, read-env self-locate, the single-writer/per-endpoint-resolver/ingest-deletes drop-file protocol, and stdout ingestion",
    "idB": "REQ-HAZARD-COMMUNE-INGEST-BLACKHOLE",
    "titleB": "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).",
    "docB": "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"
  },
  {
    "pair_num": 11,
    "idA": "REQ-ECHO-DROP-DIR-RESOLVE",
    "titleA": "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).",
    "docA": "",
    "idB": "REQ-UPDATE-ADAPTERS-VERB",
    "titleB": "THE-FORKENING W4 (operator-grilled 2026-07-14): `spt update adapters [<a>[,<b>...]]` = thin ALIAS over the existing `spt adapter update` engine (cli.rs:748 gh_release avenue; the old verb STAYS \u2014 published surface) + comma-list accepted on BOTH forms. Semantics: no names -> all gh_release-avenue registrations; names validated FAIL-FAST against the registry BEFORE any update starts (a typo must not leave a half-updated set); per-adapter failure ISOLATION (one failure doesn't stop the rest) with a per-adapter summary line; nonzero exit if any failed; local-path/dev registrations SKIP loud (not error). Gate: unit \u2014 name validation, list parsing, isolation + exit-code aggregation, local-path skip; doc \u2014 reference regen (drift-gated). Kin REQ-UPDATE-DEFAULT-COMPOSITE (the caller), REQ-ADAPTER-UPDATE-MESSAGE (per-adapter apply notices ride the summary).",
    "docB": "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"
  },
  {
    "pair_num": 12,
    "idA": "REQ-HAZARD-ADAPTER-PROFILE-STAMP-CLOBBER",
    "titleA": "A-4 (F029, operator regression): the picker/confirm views drop an endpoint's adapter `:profile` (showed `claude-spt` where `claude-spt:ccs` was created). ROOT: stamp_creation_fields (spt-store/home.rs) gave the incoming BIND-TIME adapter value UNCONDITIONAL precedence (`rec.adapter = adapter.map(...).or_else(prior)`), but a hook bind resolves the adapter ADAPTER-AGNOSTICALLY (ADR-0021: a binary basename \u2192 the BARE parent, profile unknowable), so the first hook bind rewrote the richer `claude-spt:ccs` \u2192 `claude-spt`. (F-028's establish_perch self-heal widened how often this re-stamps; the precedence is the root.) FIX: profile-preserving precedence \u2014 when the incoming adapter is exactly the PARENT of the prior's `parent:profile` composite, KEEP the prior; replace only on a genuinely different adapter (or a different explicit profile). Paid-for field bug \u2192 hazard. See triage A-4.",
    "docA": "",
    "idB": "REQ-START-4",
    "titleB": "Adapter-injected env aliases (SPT/OWL/LIVE)",
    "docB": ""
  },
  {
    "pair_num": 13,
    "idA": "REQ-HAZARD-DAEMON-SCHED-NONBLOCKING",
    "titleA": "Per-agent pulse/psyche/echo-commune scheduling must not serialize across agents: each agent's bounded LLM call (echo-commune summarizer, Psyche turn) runs off the shared scheduler so one slow/hung call cannot stall another agent's tick (7.4)",
    "docA": "",
    "idB": "REQ-WHOAMI-EXPLICIT-SID-REFUSAL",
    "titleB": "RULED DESIGN, delivery unowned (doyle 2026-07-26): when a caller hands identity resolution an EXPLICIT non-empty $OWL_SESSION_ID that resolves to NO perch, core must REFUSE identity (unresolved, exit 1, loud distinct diagnostic) rather than fall through to an ambient/inherited one \u2014 today `detect_self_id` (roster.rs, legs a\u2192b\u2192b2\u2192c) treats sid-UNMATCHED identically to sid-ABSENT, so the fallback chain re-adopts precisely the identity a sharper claim just failed to prove. MEASURED (perri, this node, 2026-07-26, three read-only whoami calls from a genuine descendant of the perri host process): (1) all SPT_*/OWL_* scrubbed \u2192 id null, exit 1 \u2014 ancestry resolved nothing (caveat honored from the probe: the perch's recorded pid was not in the caller's chain, so this run refutes lineage-as-the-mechanism for probe v1 without disproving a lineage path in general); (2) inherited SPT_ENDPOINT_ID=perri + explicit OWL_SESSION_ID matching no perch \u2192 perri, exit 0 \u2014 the mismatch datum was IN HAND (core had already scanned and failed to match the explicit sid) and the ambient id won anyway; (3) real OWL_SESSION_ID with endpoint id scrubbed \u2192 correct self \u2014 the healthy path any fix must leave untouched. SCOPE OF THE REFUSAL, ruled: only sid-PRESENT-AND-UNMATCHED poisons the fallback, and it poisons ALL weaker legs (b SPT_AGENT_ID, b2 SPT_ENDPOINT_ID, c pid-ancestry) \u2014 an explicit failed claim outranks every ambient claim below it; sid-ABSENT/empty keeps today's full chain unchanged, because the leg-b2 field root (live-repro'd 2026-07-10: the adapter surfaces OWL_SESSION_ID to the session shell as an UNEXPORTED var, so the child process legitimately carries endpoint id without sid) is exactly the flow the guard must not break \u2014 that flow is sid-absent, never sid-mismatched. WHY CORE AND NOT ONLY THE ADAPTER: the measured entry path is closed adapter-side (perri's REQ-HAZARD-INHERITED-IDENTITY-ADOPTION @ their a7558aa + claude-spt KNOWN-HAZARDS 7.4, shipped: whoami child calls scrub SPT_ENDPOINT_ID/SPT_AGENT_ID; rig discipline now detached AND env-scrubbed \u2014 the scrub is the operative half), but the inconsistency being fixed is CORE'S: the adapter fastpath's verified_env_id REFUSES an inherited SPT_ENDPOINT_ID on carrier-proof mismatch (carrier sid != payload sid) and core's fallback then RE-GRANTS what that layer just refused \u2014 one layer's refusal must not be another layer's grant, and every other harness/adapter gets the defense only if core holds it. SEVERITY UPGRADE recorded at mint: unlike the KH 7.1\u20137.3 shapes (lost reads), this adoption was WRITE-CAPABLE in the field \u2014 the adopting descendant re-pointed the ANCESTOR's session pin, so the ancestor went dark while the descendant looked healthy (perri's own pin, probe v1). KIN, same review same milestone: leg (b) SPT_AGENT_ID returns UNCONDITIONALLY today \u2014 not even perch-checked, weaker than leg b2's bound-perch gate; align it when this lands. MEASURED on the live node, not just code-read (perri, same probe run, reported 2026-07-26): SPT_AGENT_ID=nobody-xyz with a bogus OWL_SESSION_ID returned {'id':'nobody-xyz','ready':false,'alive':true,'unbound':false}, exit 0 \u2014 a phantom identity for an endpoint that does not exist, beating both the sid leg and ancestry; the returned shape has no state key and ready:false but a populated id, and the adapter parser takes .id first, so a whoami-trusting adapter writes state under the phantom \u2014 the same write-capable class as the inherited-adoption case, sourced from a made-up name instead of a real ancestor. perri's adapter scrub covers SPT_AGENT_ID as well as SPT_ENDPOINT_ID for exactly this reason. POSTURE UNCHANGED: whoami legs remain from-label/routing only, never authentication (KH 7.3/7.5, F-024 stays parked) \u2014 refusal tightens label discipline, it promotes nothing to auth. Gate at activation: unit \u2014 sid-unmatched + ambient endpoint id present \u2192 refusal with the distinct diagnostic (probe shape 2 goes loud); sid-absent + ambient endpoint id on a bound perch \u2192 still resolves (leg-b2 field root preserved); sid-matched \u2192 unchanged (probe shape 3).",
    "docB": ""
  },
  {
    "pair_num": 14,
    "idA": "REQ-INST-1",
    "titleA": "endpoint ID vs instance split (adapter-agnostic ID)",
    "docA": "",
    "idB": "REQ-RUN-ID-REUSES-ADAPTER",
    "titleB": "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.",
    "docB": ""
  },
  {
    "pair_num": 15,
    "idA": "REQ-MANIFEST-8",
    "titleA": "[adapter] host_binaries declares the harness executable basenames a kind=\"harness\" adapter hosts agents inside (e.g. host_binaries = [\"claude\"]); bind-time pid\u2192exe-basename match (case-insensitive, .exe-stripped) over the seed's parent_pid selects the candidate adapter set; zero matches \u2192 a friendly error naming the binary + the --adapter escape hatch. Additive + N-1-safe: optional Vec<String>, #[serde(default, skip_serializing_if = \"Vec::is_empty\")] (omitted-serialized like shortcut_basename, old manifests parse clean); manifest.schema.json regenerated from the derive (ADR-0001, CI drift-gated). The match-key for ADR-0021 adapter-agnostic bind-time resolution. (v0.9.0)",
    "docA": "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",
    "idB": "REQ-TERM-5",
    "titleB": "Adapter-declared digest extractor seam: a `[digest]` manifest section declaring an imperative extractor (native harness log -> the {role,text,tool,ts} contract; defaults to the [history] source files with an own-source escape hatch), `api digest-entry` push fallback, register-time validation of the section, adapter-declared presentation defaults (window depth, arg-truncation, sprint-collapse) that any consumer may override, and a `spt adapter digest-proof` author tool plus runtime skip-diagnostics (no silent drop). Reverses M9's no-manifest-seam stance; no declarative DSL.",
    "docB": "The session digest is a PROJECTION of the endpoint's session logs, never a PTY-byte parse (the superseded source mechanism). ADR-0019 gives it its OWN manifest seam \u2014 the `[digest]` extractor above \u2014 distinct from `[history]` (which stays opaque + single-session, feeding the echo-commune verbatim). The M9 \"no manifest seam / rides `[history]`\" stance is REVERSED: one `[history]` normalizer cannot serve both the opaque echo consumer and the contract-typed digest. What is published here is the digest-record CONTRACT: the small, fixed, spt-core-owned shape the `[digest]` extractor emits (or that "
  },
  {
    "pair_num": 16,
    "idA": "REQ-READY-AGENT-RESUME",
    "titleA": "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)",
    "docA": "`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",
    "idB": "REQ-WORKER-MINTED-NAME",
    "titleB": "N-1 (WORKER-TRUTH triage, operator rider): worker perch identity is CORE-MINTED and parent-derived \u2014 `{parent}-w{N}` with a per-parent counter at registration (sister shape: claude_skill_owl hook_subagent_start.rs) \u2014 never the adapter-presented agent id (CC Task ids render as random-named rows). worker-start mints + echoes the id (WORKER_STARTED:{parent}-w{N}); the adapter's agent_id/agent_type ride the record as correlation METADATA, not identity. Verb-shape contract change \u2014 freeze with W-2 in ONE coordination with perri.",
    "docB": "`api worker-start <parent> [--agent-id <id>] [--agent-type <type>]`"
  },
  {
    "pair_num": 17,
    "idA": "REQ-START-5",
    "titleA": "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)",
    "docA": "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",
    "idB": "REQ-UPDATE-FINISH-COMMUNE-FLUSH",
    "titleB": "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).",
    "docB": ""
  },
  {
    "pair_num": 18,
    "idA": "REQ-HAZARD-BRAIN-RESTART-LIFECYCLE-REHYDRATE",
    "titleA": "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)",
    "docA": "",
    "idB": "REQ-WAKE-WAIT",
    "titleB": "A-2 rider (REMOTE-TRUTH triage \u00a7A-2): `spt endpoint wake --wait` blocks on the REAL bind (status reaches online) after the daemon wake-resume lift (REQ-WAKE-RESUME-LEG), instead of the DEFAULT accepted-not-bound print (thin edge writes intent, daemon lifts async \u2014 ADR-0033). Reuses the F-027 bind-await machinery if/when it lands, else a bounded poll on status==online with a plain-language timeout (no core lingo, F-1). Default wake is UNCHANGED (accepted-not-bound truth). Separate chunk from the core leg (doyle A-2 ruling: C-2 needs the core leg, not --wait); F-027 bind-await stays design-only until this activates.",
    "docB": ""
  },
  {
    "pair_num": 19,
    "idA": "REQ-CLI-1",
    "titleA": "spt endpoint noun namespace: absorbs fork/suspend/wake/shutdown/rename/stop/digest + access (ported 1:1: allow|revoke|open|list, decision 21) + description (ex-resources blurb; bare=show, set=author); merged endpoint list [--local|--subnet <name>] grouped by subnet with SELF pinned, --detail adding the ex-resources yellow-pages blurb projection; bare spt endpoint = the list (M8 decisions 1-2, 25). SUPERSEDED (F-025 item 3): the LISTING SHAPE now lives in REQ-ENDPOINT-LIST-NODE-GROUPED (node-grouped over unique instances, not grouped-by-subnet) + REQ-ENDPOINT-LIST-REST-FILTER (suspended-hidden + --show-all); the `--local` flag was dropped by REQ-ENDPOINT-LIST-MERGE-LOCAL (the list ALWAYS merges local). This REQ owns only the endpoint noun NAMESPACE + parse surface \u2014 not the render shape.",
    "docA": "",
    "idB": "REQ-DOCS-5",
    "titleB": "Anti-drift: rustdoc/schema/exports/CLI-help generated + CI-checked",
    "docB": ""
  },
  {
    "pair_num": 20,
    "idA": "REQ-CLI-3",
    "titleA": "Agent hot path stays flat across the M8 reorg: send/ring/ready/whoami/how-to unchanged; notify moves to subnet notify while notif stays top-level; breaking renames land clean with no deprecation shims (zero external CLI consumers pre-spt-claude-code) (M8 decisions 3-4, 9)",
    "docA": "",
    "idB": "REQ-RESIDENT-SERVICE",
    "titleB": "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.",
    "docB": "`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"
  },
  {
    "pair_num": 21,
    "idA": "REQ-CLI-HELP-MARKDOWN",
    "titleA": "`spt --help` (and every subcommand --help) renders the inline Markdown authored in the clap doc-comments as terminal styling, never as literal markers: `**bold**` \u2192 ANSI bold, `` `code` `` \u2192 ANSI cyan, `[text](url)` \u2192 `text`. The markers are STRIPPED either way \u2014 a raw `**` or backtick must NEVER reach the user (the operator-reported v0.12.0 defect: help text reads `**ctrl-b**` and stray backticks verbatim). Color/bold escapes are emitted ONLY when the help is going to a real terminal AND color is not suppressed (NO_COLOR unset \u00b7 CLICOLOR != 0 \u00b7 CLICOLOR_FORCE forces on); a pipe / redirect / CI / NO_COLOR falls back to strip-only (clean plaintext, zero escapes) so machine-readable help is byte-identical regardless of marker syntax. Pure transform over the clap-rendered help string at the single run()/bare_invocation chokepoint; preserves pre-existing ANSI (CSI sequences passed through untouched), never spans markers across a newline, leaves unmatched/empty markers literal, and does not alter the help layout. (v0.12.1)",
    "docA": "",
    "idB": "REQ-ER-RULESET-TABLE",
    "titleB": "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.",
    "docB": "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."
  },
  {
    "pair_num": 22,
    "idA": "REQ-CLI-WIN-VT-ENABLE",
    "titleA": "A7 (F028, operator, Win10 conhost): ANSI emitted without VT enable \u2192 garbled console. Evidence (raw PowerShell 7, Win10 conhost): literal `\u2190[36m` in `endpoint list` + `--help`. ROOT: ENABLE_VIRTUAL_TERMINAL_PROCESSING is enabled ONLY on the rc attach path (rc.rs:746, REQ-RC-WIN-VT-OUTPUT) \u2014 plain CLI stdout never enables it, and the color decision doesn't fall back when the console can't render VT. FIX: lift the rc.rs VT-enable into a SHARED startup helper for every colored-output path; if SetConsoleMode fails (or stdout isn't a console), STRIP colors (the ansi_wrap/helpfmt color=false path already exists \u2014 plumb the decision, not new rendering). Windows Terminal masks this (VT always on) \u2014 TEST on raw conhost. See triage A7.",
    "docA": "",
    "idB": "REQ-ENDPOINT-LIST-RENDER-POLISH",
    "titleB": "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.",
    "docB": ""
  },
  {
    "pair_num": 23,
    "idA": "REQ-DIGEST-JSON-SELF-CONTAINED",
    "titleA": "TEARDOWN-AUTHORITY W4 (perri adapter-surface finding 2026-07-19, doyle-grounded and RE-SCOPED; title AMENDED at gate 2026-07-19 \u2014 the original welded version to --after polling, todlando falsified it from digesthub.rs/cli.rs and doyle ruled the amendment rides the wave: fifth instance of the claim-keyed-on-the-wrong-thing class, this one in the REQ registry itself): `spt endpoint digest <id> --json` must be self-contained on stdout. Today the digest snapshot version is NOT a field of the --json object at all \u2014 it exists ONLY in the DIGEST:<id> version=N trailer that cmd_digest eprintln!s at cli.rs:1619 \u2014 so a JSON consumer that wants it is FORCED to parse stderr. NUMBER-SPACE TRUTH (the amendment): version is digesthub's monotonic PROJECTION counter \u2014 it bumps when the projected digest CHANGES, serves as the --follow from_version floor and a change-detection cursor, and is NOT valid --after input; --after filters on entry seq ((ledger_ordinal<<32)|line_idx), a different number space, so a version passed as --after predates the window every time. NOTE the corrected history (perri's original RCA framed this as a fleet outage and doyle falsified it; perri confirmed): the trailer has been on STDERR since it was added 2026-06-03 (16f4c8e) and no stdout trailer ever existed, so stdout-only consumers never choked \u2014 this is a CONTRACT-COMPLETENESS defect, low priority, NOT an outage. FIX: (a) emit version as a top-level integer field inside the --json object (field name pinned by the consumer \u2014 perri's adapter parses 'version' \u2014 so no second round is needed), alongside the existing after_predates_window signal; (b) gate the stderr trailer on the non-json path so --json leaves stderr clean while the human path keeps its status line. Result: --json stdout = pure self-contained JSON including the version, safe whether the consumer reads stdout-only OR merges 2>&1. Additive to the JSON shape (REQ-CLI-JSON evolution rule). The published doc must state the number-space split with the EXPLICIT NEGATIVE (version is NOT valid --after input) \u2014 the original title proves the misuse is the natural reading. Gate: doc \u2014 the json-shapes digest section carries version, the stderr-clean --json contract, the complete entry-kind enum with per-kind produced-vs-injected provenance, and the seq/version asymmetry incl the explicit negative; impl \u2014 version field on the --json snapshot path + non-json-gated trailer; unit \u2014 the --json object carries version and stderr carries no DIGEST: trailer, the non-json path still prints it (predates ordering included), and the behavior-change sweep confirms no existing test asserts the old --json shape or the trailer presence under --json.",
    "docA": "the self-contained --json contract: top-level integer version cursor, stderr-clean under --json, the complete entry-kind enum with per-kind agent-produced vs spt-injected provenance, and the seq/cursor asymmetry",
    "idB": "REQ-HAZARD-DETACHED-PIPE-INHERIT",
    "titleB": "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)",
    "docB": "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"
  },
  {
    "pair_num": 24,
    "idA": "REQ-ENDPOINT-LIST-NODE-GROUPED",
    "titleA": "spt endpoint list is node-grouped over unique INSTANCES, not subnet-grouped over duplicated rows: one row per (id,node) instance \u2014 subnet duplication collapsed (ADR-0006 \u00a71: subnet is never an identity axis), freshest-epoch wins a cross-subnet status disagreement (the resolve_among twin rule) \u2014 grouped under 'This node: <label>' (cyan; local roster is the status truth, advertised-status vocabulary, corrupt = suspended + corrupt annotation) then remote nodes alphabetical (node name orange 38;5;208, the legacy $LIVE orange), each node carrying a light-gray 'Shared subnets:' line (subnets among our memberships where that node gossips any visible row; per-instance subnet detail stays in --json/--detail), instance rows [id, endpoint_type, glyph, status] (endpoint_type threaded from Instance.endpoint_type through ResourceRow \u2014 additive, pre-field rows render '-'), per-node 'Total:' lines with NO grand total (the stderr ENDPOINTS:<n> line is REMOVED \u2014 it counted subnet rows, so the same instance in N subnets counted N times), SELF pin kept first with a '(self @ <node>)' marker (REQ-WHOAMI-1 alias; self also appears as a This-node row so the node total stays honest), remote nodes with zero visible instances skipped, --subnet narrows the union to that subnet's view, --json DTO structure UNCHANGED (committed surface; gains only an additive endpoint_type field). Grill-with-docs ruling 2026-07-02 (operator + doyle); sibling of REQ-PICKER-NODE-GROUPING (the picker half of the same dedup law).",
    "docA": "",
    "idB": "REQ-SHELL-ADAPTER-OWNED-DETACHED-SERVICE",
    "titleB": "RULING OWED (unratified \u2014 do NOT satisfy without one): may a shell adapter own a DETACHED process that deliberately OUTLIVES its shell's link-break? Today the answer is 'no, silently, on Windows only'. `shellhost::kill_shell_pid` force-kills with `taskkill /PID <pid> /T /F` \u2014 /T is a TREE kill, so `close_shell` takes the shell's descendants with it; the Unix arm is `kill -9 <pid>`, a SINGLE process. So the same adapter's detached child dies on Windows and survives on Linux, undocumented either way. Two ratified positions collide here and neither may be quietly discarded: (a) CONTEXT \u00a7Shell model lifecycle states flatly 'the binary never survives a link-break', and the KNOWN-HAZARDS 'stop lies' RCA (two field cases 2026-07-19, one on doyle's own production endpoint) was paid for precisely because a surviving GRANDCHILD was orphaned where 'even a direct-child kill would have missed it' \u2014 that hazard is the standing argument FOR /T; (b) a node-singleton service that must not drop every time a shell relinks is a legitimate shape, and flynn's alchemy ships one today (Hub Daemon, CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP, lockfile-guarded), depending on surviving link-breaks to keep a Discord gateway up. Note CREATE_NEW_PROCESS_GROUP does NOT sever ParentProcessId, so it is not an escape from /T \u2014 the survival flynn depends on is unverified, not designed-for. MEASURED, not theorized (flynn, this node, 2026-07-25, current spt-core, no branch): the Hub Daemon's ParentProcessId WAS the shell's pid \u2014 a direct child, so `/T` reaches it by either reading \u2014 and `spt shell teardown alchemy-0` took the daemon to zero processes within 0.1s, still zero across a 15s poll. Nothing restarted it (no bind happens in that window and the adapter's ensure runs only at bind, so 'died and restarted fast' is excluded by construction); recovery needed an operator spawn + re-bind. BLAST RADIUS, the reason this outranks its filing: that daemon also runs alchemy's Watch engine, so tearing down whichever shell happened to parent it silently stops State-change notification delivery NODE-WIDE, for every registered Watch and every owner \u2014 cross-Project blocking waits simply never fire, with no error anywhere. It satisfies the LETTER of alchemy's own REQ-HAZARD-WATCH-SILENT-LOSS (the registry entry survives) while its INTENT fails (the delivery engine is dead) \u2014 filed separately adapter-side. Option space for the ruling: (1) adapter-owned detached services are UNSUPPORTED \u2014 such a service is an AlwaysOnEndpoint (REQ-EP-8 / ADR-0023: resident, daemon-supervised, addressable, independent of agent liveness), NOT a shell's descendant. NOTE this is not a new design: ADR-0023 was ratified 2026-06-21 and EXPLICITLY rejected 'make it a Shell' ('shells are single-owner, driven, control-exclusive; a multi-party bridge inverts every one of those invariants'), and named a Discord bridge as its first consumer \u2014 i.e. flynn's exact shape. The gap is DELIVERY (REQ-EP-8 is inactive pending its milestone), not design. (2) allow it behind an explicit manifest declaration that exempts named descendants from the tree kill AND obliges the adapter to reap them \u2014 viable only as an EXPLICIT INTERIM with (1) as the stated target, never a permanent answer, because permanence would re-adopt the shape ADR-0023 already rejected. (3) status quo, but then DOCUMENT the platform asymmetry and stop claiming 'never survives' unqualified. CONSUMER LEAN IS (1), argued against his own convenience (flynn 2026-07-25): the service is a shell's descendant only because a shell was the only thing he had that could start something; (2) buys that accident a manifest field plus a reaping obligation he must then honor forever, and leaves 'the next shell that happens to bind' responsible for the service being up \u2014 which IS the defect he measured. Available to a consumer TODAY without any core change and without an exemption: start the service out-of-band so no shell parents it (unsupervised, but never tree-killed) \u2014 the trade is 'unsupervised but safe' against 'supervised by accident and killed by design'. Whichever wins, the Windows/Unix divergence is a defect in its own right and closes with it \u2014 `/T` on one platform and single-pid `kill -9` on the other means adapters cannot reason about descendant lifetime at all. Filed by flynn 2026-07-25 as 'flagging it now rather than after'; code-read by doyle, then field-confirmed by flynn at the cost of one teardown. FIELD CORRECTION (flynn, 2026-07-25, alchemy v0.3.1): the measured kill was NOT /T over-reach \u2014 the 'detached' daemon was still the shell's DIRECT CHILD in the process table (CREATE_NO_WINDOW / CREATE_NEW_PROCESS_GROUP / null stdio detach the console and the signal group, never parentage), so any tree walk found it by construction; isolated A/B on this node confirmed a grandchild whose intermediate has exited survives /T. alchemy v0.3.1 ships the fix as one relay hop (the binary re-invokes itself, the intermediate exits, the daemon's ParentProcessId is dead and nothing can walk it) \u2014 production-verified (daemon pid 35472, ParentProcessId 11108 dead) \u2014 and WITHDREW the Scheduled Task autostart entirely (alchemy ADR-0012 now Rejected; ADR-0013 records the relay hop). alchemy no longer pushes on this seed. What remains core-side is narrowed, not changed in kind: /T's breadth (it reaches detached descendants of any adapter that has NOT done the relay hop) is a hardening item on its own merits, and the Windows/Unix kill asymmetry stands. Job-object caveat filed separately \u2014 the relay hop does NOT escape a Job Object: see REQ-SHELL-CLI-SPAWN-JOB-EXPOSURE.",
    "docB": ""
  },
  {
    "pair_num": 25,
    "idA": "REQ-EP-2",
    "titleA": "Agent endpoints vs Shells distinction in the type model",
    "docA": "",
    "idB": "REQ-SHELL-PERCH-DIR",
    "titleB": "A shell binary can mechanically resolve where its files land. (flynn spt-alchemy clean-room audit 2026-07-21, doyle code-verified, seed pair item 2, P1 \u2014 HARD-GATES flynn's alchemy W4.) TODAY: `spt shell send --file` lands the blob at <shell-perch>/files/<xfer-id>-<name> and the shell_file frame's path attr is PERCH-RELATIVE (files/...) \u2014 but the spawn template substitution keys are ONLY {id}/{adapter_name}/{link_token} (shellhost.rs fill_spawn_command) and the spawned child inherits the BROKER's cwd, so no mechanical perch resolution exists: the binary cannot turn the frame's path into a real file without guessing SPT_HOME layout. DOYLE RULING, both halves binding: the frame KEEPS the perch-relative path (an absolute path in a spooled frame LIES across perch moves and node boundaries \u2014 frames outlive layouts); the fix is an ADDITIVE {perch_dir} spawn-template substitution key (opt-in \u2014 templates that do not use it are byte-identical, N-1-safe by construction) filled with the shell perch dir so the binary receives its root at spawn and joins the frame's relative path against it. REFUSED, recorded so it is not re-proposed: blessing SPT_HOME layout guessing as an interim contract. Published landing contract rides REQ-SHELL-FRAME-VOCAB's page. Sequence-not-version given to flynn (no version floor before it ships). Gate: doc \u2014 the {perch_dir} key on the manifest spawn-template docs + the files/<xfer-id>-<name> landing shape on the frame-vocab page; impl \u2014 fill_spawn_command passes perch_dir alongside id/adapter_name/link_token; unit \u2014 the filled template carries the perch dir as ONE argv element (the multi-word/space path shape the argv-fill hazard exists for) and a template WITHOUT the key is byte-identical pre/post (the N-1 guarantee); int \u2014 a real spawned shell binary given {perch_dir} resolves a shell_file frame's perch-relative path to the landed blob and proves the read.",
    "docB": "`shell_file` \u2014 a landed file"
  },
  {
    "pair_num": 26,
    "idA": "REQ-HAZARD-TEMPLATE-ARGV-FILL",
    "titleA": "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.",
    "docA": "",
    "idB": "REQ-MSG-2",
    "titleB": "spt binary CLI surface: send/ring/ready(+--once)/list/stop/whoami, stable arg shapes + exit codes",
    "docB": ""
  },
  {
    "pair_num": 27,
    "idA": "REQ-RESIDENT-SERVICE",
    "titleA": "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.",
    "docA": "`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",
    "idB": "REQ-SHELL-ADAPTER-OWNED-DETACHED-SERVICE",
    "titleB": "RULING OWED (unratified \u2014 do NOT satisfy without one): may a shell adapter own a DETACHED process that deliberately OUTLIVES its shell's link-break? Today the answer is 'no, silently, on Windows only'. `shellhost::kill_shell_pid` force-kills with `taskkill /PID <pid> /T /F` \u2014 /T is a TREE kill, so `close_shell` takes the shell's descendants with it; the Unix arm is `kill -9 <pid>`, a SINGLE process. So the same adapter's detached child dies on Windows and survives on Linux, undocumented either way. Two ratified positions collide here and neither may be quietly discarded: (a) CONTEXT \u00a7Shell model lifecycle states flatly 'the binary never survives a link-break', and the KNOWN-HAZARDS 'stop lies' RCA (two field cases 2026-07-19, one on doyle's own production endpoint) was paid for precisely because a surviving GRANDCHILD was orphaned where 'even a direct-child kill would have missed it' \u2014 that hazard is the standing argument FOR /T; (b) a node-singleton service that must not drop every time a shell relinks is a legitimate shape, and flynn's alchemy ships one today (Hub Daemon, CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP, lockfile-guarded), depending on surviving link-breaks to keep a Discord gateway up. Note CREATE_NEW_PROCESS_GROUP does NOT sever ParentProcessId, so it is not an escape from /T \u2014 the survival flynn depends on is unverified, not designed-for. MEASURED, not theorized (flynn, this node, 2026-07-25, current spt-core, no branch): the Hub Daemon's ParentProcessId WAS the shell's pid \u2014 a direct child, so `/T` reaches it by either reading \u2014 and `spt shell teardown alchemy-0` took the daemon to zero processes within 0.1s, still zero across a 15s poll. Nothing restarted it (no bind happens in that window and the adapter's ensure runs only at bind, so 'died and restarted fast' is excluded by construction); recovery needed an operator spawn + re-bind. BLAST RADIUS, the reason this outranks its filing: that daemon also runs alchemy's Watch engine, so tearing down whichever shell happened to parent it silently stops State-change notification delivery NODE-WIDE, for every registered Watch and every owner \u2014 cross-Project blocking waits simply never fire, with no error anywhere. It satisfies the LETTER of alchemy's own REQ-HAZARD-WATCH-SILENT-LOSS (the registry entry survives) while its INTENT fails (the delivery engine is dead) \u2014 filed separately adapter-side. Option space for the ruling: (1) adapter-owned detached services are UNSUPPORTED \u2014 such a service is an AlwaysOnEndpoint (REQ-EP-8 / ADR-0023: resident, daemon-supervised, addressable, independent of agent liveness), NOT a shell's descendant. NOTE this is not a new design: ADR-0023 was ratified 2026-06-21 and EXPLICITLY rejected 'make it a Shell' ('shells are single-owner, driven, control-exclusive; a multi-party bridge inverts every one of those invariants'), and named a Discord bridge as its first consumer \u2014 i.e. flynn's exact shape. The gap is DELIVERY (REQ-EP-8 is inactive pending its milestone), not design. (2) allow it behind an explicit manifest declaration that exempts named descendants from the tree kill AND obliges the adapter to reap them \u2014 viable only as an EXPLICIT INTERIM with (1) as the stated target, never a permanent answer, because permanence would re-adopt the shape ADR-0023 already rejected. (3) status quo, but then DOCUMENT the platform asymmetry and stop claiming 'never survives' unqualified. CONSUMER LEAN IS (1), argued against his own convenience (flynn 2026-07-25): the service is a shell's descendant only because a shell was the only thing he had that could start something; (2) buys that accident a manifest field plus a reaping obligation he must then honor forever, and leaves 'the next shell that happens to bind' responsible for the service being up \u2014 which IS the defect he measured. Available to a consumer TODAY without any core change and without an exemption: start the service out-of-band so no shell parents it (unsupervised, but never tree-killed) \u2014 the trade is 'unsupervised but safe' against 'supervised by accident and killed by design'. Whichever wins, the Windows/Unix divergence is a defect in its own right and closes with it \u2014 `/T` on one platform and single-pid `kill -9` on the other means adapters cannot reason about descendant lifetime at all. Filed by flynn 2026-07-25 as 'flagging it now rather than after'; code-read by doyle, then field-confirmed by flynn at the cost of one teardown. FIELD CORRECTION (flynn, 2026-07-25, alchemy v0.3.1): the measured kill was NOT /T over-reach \u2014 the 'detached' daemon was still the shell's DIRECT CHILD in the process table (CREATE_NO_WINDOW / CREATE_NEW_PROCESS_GROUP / null stdio detach the console and the signal group, never parentage), so any tree walk found it by construction; isolated A/B on this node confirmed a grandchild whose intermediate has exited survives /T. alchemy v0.3.1 ships the fix as one relay hop (the binary re-invokes itself, the intermediate exits, the daemon's ParentProcessId is dead and nothing can walk it) \u2014 production-verified (daemon pid 35472, ParentProcessId 11108 dead) \u2014 and WITHDREW the Scheduled Task autostart entirely (alchemy ADR-0012 now Rejected; ADR-0013 records the relay hop). alchemy no longer pushes on this seed. What remains core-side is narrowed, not changed in kind: /T's breadth (it reaches detached descendants of any adapter that has NOT done the relay hop) is a hardening item on its own merits, and the Windows/Unix kill asymmetry stands. Job-object caveat filed separately \u2014 the relay hop does NOT escape a Job Object: see REQ-SHELL-CLI-SPAWN-JOB-EXPOSURE.",
    "docB": ""
  },
  {
    "pair_num": 28,
    "idA": "REQ-CONTROLLER-LEASE-IDENTITY",
    "titleA": "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.",
    "docA": "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",
    "idB": "REQ-HAZARD-CONTROLLER-LEASE",
    "titleB": "RC-RENDER-TRUTH W2 (KNOWN-HAZARDS 7.48 \u2014 umbrella conformance seam for ADR-0044): at most one input-capable controller lease per PTY session; takeover revokes atomically and loudly; input is fenced to the active lease; node identity is never a lease. The full hertz 8-step deterministic two-loopback-client broker regression rides verbatim: A subscribes Control from node N and controls; B subscribes Take from the SAME node with a different lease; A receives Displaced{by:N} then terminal stream completion (rc exits via existing PumpEnd::Displaced); output post-takeover reaches B not A; A's Input+Resize post-takeover mutate nothing; B's both apply; controlled/driven_by metadata identifies B with exactly one controller slot; a separate equal-lease/equal-generation replay test proves genuine dispatcher recovery remains silent and never self-displaces. Gate: int \u2014 the matrix; doc \u2014 KNOWN-HAZARDS 7.48.",
    "docB": "7.48 At most one input-capable controller lease per PTY session \u2014 takeover revokes atomically and loudly, input is fenced to the active lease, node identity is never a lease `[REQ-HAZARD-CONTROLLER-LEASE]` Failure (paid-for, hertz same-machine `--take` RCA, field repro 2026-07-16):** terminal A controlled an endpoint; terminal B on the SAME machine ran `spt rc --take`. Local loopback attaches carry only NODE identity, so `resolve_subscribe` computed `same_identity=true` and took the silent successor path for a distinct `--take` \u2014 intent never consulted; sink replaced with no `Displaced`, no st"
  },
  {
    "pair_num": 29,
    "idA": "REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE",
    "titleA": "The effect journal serializes EVERY PTY effect under one mutex held ACROSS two fsyncs AND the blocking PTY write \u2014 so interactive input stutters and ultimately wedges the daemon hard. ROOT (doyle /diagnose, code-grounded + MEASURED on the operator's real Windows box, 2026-06-19): EffectJournal::apply_once (effect.rs:168-188) takes `inner.lock()` and holds it across `write_line(PENDING)` \u2192 `effect()` \u2192 `write_line(DONE)`, where write_line (effect.rs:235-239) does flush()+sync_all() (a full FlushFileBuffers) \u2014 so each effect pays TWO fsyncs under a GLOBAL lock, and the closure `effect()` (the actual PTY write, broker.rs:1257 EffectKind::PtyWrite via attach.rs:197 send_effect) runs while the lock is held. Two operator-visible facets, ONE root: (A) STUTTER/LAG \u2014 every keystroke is a PtyWrite effect = 2\u00d7 sync_all serialized; measured fsync on %LOCALAPPDATA%\\spt-core = median 6.5ms, spikes to 198ms (C: was recently at 100%), so ~13ms+ per keystroke best case, hundreds under contention \u2192 'many but not all keypresses take 100s of ms, choppy, worsens with volume'. (B) HARD PERMANENT WEDGE \u2014 when a PtyWrite `effect()` blocks (ConPTY input buffer full / harness not draining stdin), the journal lock is held INDEFINITELY \u2192 the single-threaded inbound-stream dispatch (dispatch.rs serve_attach, which both applies input effects AND opens attaches) can never progress \u2192 EVERY subsequent attach (`spt rc --view`/`--take`) fails with 'attach request: brain IPC read deadline elapsed' (confirmed: two retries deadline identically; broker control-plane KIND queries still answer \u2014 different thread). This REFUTES the W2-deferred ruling that park-(b)/(c) is 'Windows-benign because ConPTY absorbs 4MiB' \u2014 on the real box the input path wedges regardless. DISTINCT from W1 (REQ-HAZARD-INJECT-CONTROL-COEXIST = the OUTPUT drain, correctly fixed @8b5583e; output uses broker.rs:1106 append, NOT the fsync journal). This is the INPUT/effect-journal path W1 never touched, and it is THE wedge the operator hits with --take/--view. FIX DIRECTION (candidates, repro-first \u2014 extend inject_control_wedge.rs to a REAL backed-up-PTY-consumer + a real rc-client attach assertion, the gap W1's gate missed): (1) do NOT hold the journal lock across effect() \u2014 reserve the key + fsync PENDING under lock, RELEASE, run effect(), re-acquire to fsync DONE + mark applied (preserve crash-idempotency via the per-key reservation, not a global hold); (2) bound/fail-fast the PtyWrite itself (the W2-deferred park bound \u2014 write_input must never block indefinitely, DSR-answer must not hold the writer mutex across a blocking write); (3) drop per-keystroke fsync on the interactive path \u2014 PtyWrite effects are EPHEMERAL (a keystroke lost on a broker crash is retyped; PTY state is not reconstructed from keystroke replay), so in-memory applied-set dedup suffices (the broker survives the brain \u2014 that IS the dedup anchor), with async/batched fsync or no-fsync for EffectKind::PtyWrite while durable kinds (NetSend/NetDial/Registry/Spool) keep their fsync. Combine (1)+(3) at minimum. Add a KNOWN-HAZARDS.md entry on landing. (v0.13.0)",
    "docA": "",
    "idB": "REQ-HAZARD-REDISPATCH-CONTROL-STEAL",
    "titleB": "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.",
    "docB": "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"
  },
  {
    "pair_num": 30,
    "idA": "REQ-DAEMON-4",
    "titleA": "Honor every KNOWN-HAZARDS invariant",
    "docA": "",
    "idB": "REQ-DAEMON-STDERR-PERSIST",
    "titleB": "W3 (LIFECYCLE-TRUTH, observability): broker + brain stderr tee to a rotating size-capped file under SPT_HOME (e.g. 2x5MB), stamped per generation. ROOT: detached daemon nulls stdio -> the 2026-07-06/07 incident window left ZERO logs (both RCAs ran blind; rigs had to recreate everything). KNOWN-HAZARDS note: never inherit handles (REQ-HAZARD-DETACHED-DAEMON-STDIO) \u2014 open the file in-process, don't pipe.",
    "docB": ""
  },
  {
    "pair_num": 31,
    "idA": "REQ-ENDPOINT-TEARDOWN-AUTHORITY",
    "titleA": "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.",
    "docA": "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",
    "idB": "REQ-ENSURE-DAEMON-STOP-INHIBIT",
    "titleB": "An operator stop outranks every implicit daemon ensure. (ADR-0047 decision 2, AMENDING REQ-DAEMON-3's anchor; hertz v0.39.4 field bug 2, RCA accepted 2026-07-22.) TODAY: every `spt api` invocation runs unconditional ensure_daemon() (api/mod.rs run()), so on a box with live adapter sessions a `daemon stop --force` loses the race to hook-driven api calls \u2014 respawn convoy (5-10 ephemeral spawner windows), several stops to stay down; the rc-side twins were fixed earlier, the api anchor stayed armed. FIX: `daemon stop` records a durable machine-scoped STOP INHIBIT before teardown begins; ensure_daemon()/ensure_running() consult it and DECLINE with one honest line naming the remedy ('daemon stopped by operator \u2014 spt daemon start to resume'); cleared by intent verbs ONLY (explicit `daemon start`; update paths that restart by design) \u2014 NO TTL (rejected: a timeout is the surprise respawn again, later); implicit autostart additionally takes a machine-wide lock around probe-and-spawn so N concurrent callers never launch N daemons. Gate: doc \u2014 the stop/start contract on the daemon CLI docs (stop now sticks; the refusal line + remedy named); impl \u2014 inhibit mint in cmd_stop + consult in both implicit anchors + clear in daemon start/update-finish + the spawn serialization lock; unit \u2014 inhibit present -> ensure declines with the message, absent -> spawns, intent verbs clear, non-intent paths never clear; int \u2014 the convoy rig: stop under a concurrent api-call storm -> daemon stays down + zero respawns + refusal printed, then explicit start clears and exactly ONE daemon comes up under the same storm (RED-first against today's anchor).",
    "docB": "\u2026with one exception, because you are allowed to mean it: **`spt daemon stop` sticks.** Auto-start is a convenience, and a convenience never overrules an explicit instruction. Once you stop the daemon, the implicit auto-start that every `spt` invocation performs *declines* to bring it back, printing one line that names the way out: / 2. An operator stop outranks every implicit ensure"
  },
  {
    "pair_num": 32,
    "idA": "REQ-DISPATCH-CLAIM-RETRY",
    "titleA": "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.",
    "docA": "",
    "idB": "REQ-SERVE-OWNERSHIP-GENERATION",
    "titleB": "REDISPATCH-STALL W1 (ADR-0038 Amendment, fix 6): terminal-exclusion enforced PRE-SERVE + ownership/generation validation on attach/detach \u2014 a stale worker can never detach or displace a REPLACEMENT controller (today detach_if compares Arc ptr identity only; the serve path re-checks nothing at completion). Covers the UNFINISHED-stale-row control-steal shape (raw-close no-FIN viewports, emphasys C2 leak class feeding it) that finished-row retirement (D1/D1b) definitionally cannot see \u2014 the discriminating field observable on the next live steal catch = the stolen row's finished+retired flags. Gate: impl \u2014 pre-serve terminal exclusion + generation/ownership tokens on attach/detach; unit \u2014 stale-generation detach refused while the same-generation detach lands; int \u2014 T6 (UNFINISHED-stale attach row + live current controller + dispatcher restart: neither takes nor clears the replacement, D1/D1b green alongside); doc \u2014 ADR-0038 Amendment. Kin REQ-HAZARD-REDISPATCH-CONTROL-STEAL (the finished sibling), REQ-REDISPATCH-FINISHED-RETIRE.",
    "docB": "Amendment \u2014 REDISPATCH-STALL (2026-07-16)"
  },
  {
    "pair_num": 33,
    "idA": "REQ-DOC-ECHO-COMMUNE-CONTRACT",
    "titleA": "W6 (LIFECYCLE-TRUTH, docs \u2014 this gap cost a full outage night, priority slot): publish the [session.echo_commune] I/O contract on the docs-site: key catalog core fills; core does NOT stdin-feed [history] (field-proven); self-locate guidance incl. CLAUDE_CONFIG_DIR / read_env; drop-file protocol (single-writer, ingest-deletes, resolver semantics from W1); stdout ingestion expectations. Public docs use VERSION numbers, never wave codes; docs-publish drift gate applies.",
    "docA": "the full echo-commune I/O contract: the role + fields, the key catalog spt-core fills, the no-history-on-stdin rule, read-env self-locate, the single-writer/per-endpoint-resolver/ingest-deletes drop-file protocol, and stdout ingestion",
    "idB": "REQ-HEAVY-UNIT-CLASSIFICATION",
    "titleB": "A unit test that stands up a REAL broker inside a lib/bin `#[cfg(test)]` block must sit in the `heavy-broker-pty` nextest group, and the classification must be ENFORCED rather than remembered. FLAKE-LEDGER #14 diagnosed this class in the `spt` binary, wrote the CLASS in prose, then shipped an ENUMERATION of four `rc::tests::` names \u2014 so the identical shape in `spt-daemon`'s lib (`applyhost`: a real `Broker::bind` + `serve()` in 10 of its 13 units) stayed in the full-parallel Phase-A pool and TIMED OUT at 240s twice, at v0.32.0 and again under the v0.39.0 W5 gate, the v0.32.0 remedy never having landed. The defect is provable from `.config/nextest.toml` ALONE (two overrides, neither matching `kind(lib)`); timing evidence only ever estimated the rate. Gate: impl \u2014 heavy-group overrides for `applyhost`/`livehost`/`pump` (spt-daemon lib) and `wansend` (spt bin, found BY the check rather than by a person), plus `xtask check`'s `check_heavy_unit_classification` keyed on the SHAPE (a `Broker::bind` after the `mod tests` marker) instead of a name list; unit \u2014 the two pure seams, including the regression for this check's OWN first draft, which substring-matched `<module>::tests` and so missed every module written inside an alternation group. Kin FLAKE-LEDGER #14/#15, REQ-CI-DOCS-ONLY-THIN.",
    "docB": ""
  },
  {
    "pair_num": 34,
    "idA": "REQ-DOCS-5",
    "titleA": "Anti-drift: rustdoc/schema/exports/CLI-help generated + CI-checked",
    "docA": "",
    "idB": "REQ-DOCS-NO-INTERNAL-CODES",
    "titleB": "Public CLI --help (the clap `///` doc-comments) and the generated `docs-site/src/cli/reference.md` MUST NOT contain internal tracker/decision codes \u2014 `REQ-*`, `F-###`, `M#-W#`, `ADR-####`. They are meaningless to an end user reading --help and ship to GH-Pages. A CI-gated scan (the `xtask check` docs gate) fails on any such token in the GENERATED reference.md (which by construction contains only clap help, so rustdoc `///` on fns/structs is OUT of scope and keeps its REQ/ADR cross-refs). Substance is kept; codes are rewritten to plain language. (v0.13.2 W6)",
    "docB": "Public help carries no internal codes.** The public CLI `--help` (the clap `///` doc-comments) and the generated `docs-site/src/cli/reference.md` MUST NOT contain internal tracker/decision codes \u2014 `REQ-*`, `F-###`, `M#-W#`, `ADR-####`. They are meaningless to an end user reading `--help` and ship to GitHub Pages. The `xtask check` docs gate scans the **generated** reference for these tokens and fails on any hit (regeneration alone keeps drift at zero but would faithfully republish a leak \u2014 the scan is what gives the gate teeth). The scan is scoped to the generated reference by construction, so"
  },
  {
    "pair_num": 35,
    "idA": "REQ-HAZARD-DEFERRED-DRAIN",
    "titleA": "Deferred spool rows excluded from the event-stream drain (1.4)",
    "docA": "",
    "idB": "REQ-XTASK-SPT-BIN-TARGET-DIR",
    "titleB": "#13 (F026 micro, tooling): xtask `spt_bin()` (crates/xtask/src/main.rs) BUILDS `spt` via cargo (which honors CARGO_TARGET_DIR) but returns a HARDCODED `<root>/target/debug/spt` path \u2014 so under a redirected target dir (CI / isolated-gate rigs that set CARGO_TARGET_DIR to a throwaway) the binary lands in `$CARGO_TARGET_DIR/debug` while xtask looks in `<root>/target/debug` -> NotFound -> `xtask check` (docs-drift gate) spuriously fails. Workaround was running `xtask check` with CARGO_TARGET_DIR unset. FIX: a pure `target_debug_dir(root, CARGO_TARGET_DIR)` seam mirroring cargo's resolution \u2014 absolute override as-is, relative resolved against `root` (the dir cargo is invoked in), default `<root>/target` \u2014 join `debug`; `spt_bin` returns from it. See docs/NEXT-MILESTONE-PICKER-TRIAGE.md.",
    "docB": ""
  },
  {
    "pair_num": 36,
    "idA": "REQ-CONN-POISON-ATTRIBUTION",
    "titleA": "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).",
    "docA": "",
    "idB": "REQ-ENDPOINT-CYCLE-HONEST",
    "titleB": "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.",
    "docB": "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"
  },
  {
    "pair_num": 37,
    "idA": "REQ-DAEMON-5",
    "titleA": "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)",
    "docA": "",
    "idB": "REQ-PUMP-PEER-ISOLATION",
    "titleB": "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).",
    "docB": ""
  },
  {
    "pair_num": 38,
    "idA": "REQ-DAEMON-BITS-AMBIGUITY",
    "titleA": "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.",
    "docA": "",
    "idB": "REQ-HAZARD-ENDPOINT-LIFECYCLE",
    "titleB": "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.",
    "docB": "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="
  },
  {
    "pair_num": 39,
    "idA": "REQ-EFFECTIVE-INSTANCE-STATE",
    "titleA": "A-1 (REMOTE-TRUTH triage \u00a7A + ADR-0033 \u00a7Decision): the effective instance state of a perch is DERIVED through ONE shared function \u2014 liveness discriminates warm/cold, stored rest intent refines within warm, absent intent NEVER defaults active. ROOT (certain): resting::apply_event derived its `from` off the stored rest_state field ALONE (resting.rs:225, `unwrap_or(RestState::Active)`) \u2014 a cold perch (offline) with no intent answered `from=Active`, so a Wake event found it 'already in target state' and returned Ok(None) = the field NO_EDGE-on-a-definitely-suspended-endpoint bug (the banked F-028 rest_state-void seed). advertised_status (registryhost.rs:821) ALREADY derived correctly (is_perch_alive\u2192intent-refined / is_perch_unbound\u2192Dormant / cold\u2192Suspended) \u2014 the two readers disagreed. FIX (Q1 shared derivation, hazard-class): a pure `effective_rest_state(alive, unbound, intent) -> RestState` mirroring advertised_status, consumed by BOTH advertised_status (mapped RestState\u2192Status, behavior identical) AND apply_event's `from` (real is_perch_alive/is_perch_unbound reads); void + cold \u21d2 Suspended. Bonus: kills the spurious active\u2192suspend echo a cold+void perch used to fire (on_rest_edge on a dead driver). Red-first: perch status=offline + no rest_state \u2192 daemon_rest_event(Wake) yields from=Suspended\u2192to=Active EdgeReport, not Ok(None).",
    "docA": "7.32 The effective resting state MUST be derived through ONE shared liveness-aware function \u2014 a stored-intent-alone read lies about cold perches `[REQ-EFFECTIVE-INSTANCE-STATE]` Failure (paid-for, field evidence):** two rest-state readers derived the effective instance state independently. `registryhost::advertised_status` read it liveness-aware (cold \u21d2 Suspended); `resting::apply_event` derived its `from` off the stored `rest_state` intent field ALONE (`unwrap_or(RestState::Active)`). A cold (offline) perch with no resting intent therefore looked **Active** to `apply_event` \u2014 so a `Wake` even",
    "idB": "REQ-WHOAMI-1",
    "titleB": "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`.",
    "docB": "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"
  },
  {
    "pair_num": 40,
    "idA": "REQ-ENDPOINT-CYCLE-HONEST",
    "titleA": "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.",
    "docA": "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",
    "idB": "REQ-RELAY-DEATH-CONVERGENCE",
    "titleB": "A dead harness relay MUST converge the endpoint's projection \u2014 an endpoint whose relay process is provably gone cannot keep rendering `ONLINE - HARNESS ONLY` with ready=true alive=true and a registered address merely because the owner parent is still alive. RE-PROVEN PRESENT on 0.44.0 (hertz W0 item 2, 2026-07-27, fresh isolated build sha-prefix 00e15f): relay pid 38764 killed 06:21:01Z, owner pid 12156 live; 148s later info.json still pid=38764 status=online, list JSON ready=true alive=true address=127.0.0.1:57709, TCP to that address unreachable, human list `ONLINE - HARNESS ONLY`. Sibling probes: live-relay sibling's socket reachable and `send` -> SENT with the EVENT received (the rig CAN see delivery); clean `endpoint stop` sibling rendered status=offline ready=false alive=false (the rig CAN see convergence). FIX SHAPE: relay-death convergence \u2014 a dead/unreachable relay clears the ready/address/liveness projection for its endpoint within a bounded window, in ONE authority: the verdict MUST route through the shipped custody/process-identity predicate (KH 2.5 \u2014 liveness authority lives in one resolver, never re-derived beside it; Unproven never kills). Kin: the Athenaeum stale-ALIVE generator (HOSTING_AUTHORITY_DEMOTED demote-to-no-claim arm, REQ-LISTEN-PRESERVES-HOSTING-TOPOLOGY \u2014 the demote-vs-offline fork needs the same identity predicate), SHELL-STALE-ONLINE (same lying-signal class, shell flavor), REQ-MSG-INJECT-LEG-DROP-VISIBLE (the observability face of the same wedge). TITLE ADDENDUM 2026-07-27 (doyle ruling, todlando build): THE VERDICT FIRES ONLY WHERE THE RECORDED PID WAS A **HOLDER** (pid_role=relay, REQ-PID-ROLE-EVIDENCE). The shipped branch asks the oracle about whatever pid the record carries, and `info.pid` means two different things by write path \u2014 a holding `api listen` relay, or an `api bind` announcer that exits within seconds. Measured on HFENDULEAM 2026-07-27: todlando 22588, doyle 45160, deployah 29176 all DEAD-and-online (binder pids) against hertz 11216 / mobile-gw 46152 alive (relay pids), so an unrole-gated verdict would have offlined three live agents. A row with no role stamp (legacy) is NO KNOWLEDGE and never converges; it heals at its next re-bind.",
    "docB": ""
  },
  {
    "pair_num": 41,
    "idA": "REQ-ENDPOINT-LIST-RENDER-POLISH",
    "titleA": "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.",
    "docA": "",
    "idB": "REQ-LIST-JSON-LIVENESS-PARITY",
    "titleB": "GATEWAY-LIVENESS (flynn field bug 2026-07-09, RCA reader-divergence root): `spt endpoint list` (human) and `endpoint list --json` MUST report an IDENTICAL status for a locally-hosted endpoint \u2014 especially a pid-alive, status-ABSENT gateway (no psyche_init). ROOT (todlando RCA STEP-1, doyle-verified): the --json builder (crates/spt/src/cli.rs cmd_endpoint_list) emits each subnet row's status straight from resource_projection (spt-net registry.rs:566, passes instance.status through verbatim :592 \u2014 the persisted WAN snapshot, a lagged gossip that can carry a stale/crash-time Suspended) and NEVER applies the self-owned reconcile the human/picker path applies (reconcile_self_owned, crates/spt/src/picker/data.rs:160 via gather_endpoints :112). So a pid-alive self-owned gateway reads Suspended on --json but ONLINE on human (roster::enumerate spt/src/roster.rs:38 -> is_perch_alive pid-fallback spt-store/liveness.rs:136); the adapter suspend-poll (parse_endpoint_status over endpoint list --json --show-all) reads the divergent --json status -> self-suspends a pid-alive gateway. Candidates REFUTED: resource_projection does NOT re-derive liveness (copies instance.status, only skips !routable :582); render is a faithful {:?}. advertised_status (registryhost.rs:822) DOES compute Active via pid-fallback (live advertise fine) \u2014 the bug is the READER showing the un-reconciled snapshot. FIX (doyle-ruled): apply the self-owned reconcile in the cli --json builder (SAME spt/picker consumer layer as gather_endpoints, NOT lifted into resource_projection which inverts the dep spt-net->perch), reading LOCAL perch truth (is_perch_alive/unbound -> Active/Offline, mirroring the picker's local_rows). Do NOT force status=online (DEFECT B latent \u2014 seed-#5 orphan-listener false-ONLINE risk). Gate: a pid-alive locally-hosted gateway (status-absent, no psyche_init) reads the SAME non-Suspended status on human AND --json. Field-verify flynn (mobile-gw). Kin REQ-PICKER-3 + REQ-PRESENCE-LIVENESS-TRUTH + seed-#5 orphan-listener false-ONLINE.",
    "docB": ""
  },
  {
    "pair_num": 42,
    "idA": "REQ-ENDPOINT-MESSAGE-ONLY-DISPLAY",
    "titleA": "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.",
    "docA": "",
    "idB": "REQ-INST-3",
    "titleB": "Dormant (warm) / suspended (cold) resting states",
    "docB": "Dormancy resource budget (D9-3 \u2014 ADR-0003 red-team #9)"
  },
  {
    "pair_num": 43,
    "idA": "REQ-ENDPOINT-STOP-OFFLINE",
    "titleA": "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)",
    "docA": "",
    "idB": "REQ-HAZARD-HOSTED-LIVENESS-RECONCILE",
    "titleB": "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)",
    "docB": ""
  },
  {
    "pair_num": 44,
    "idA": "REQ-EP-4",
    "titleA": "PresenceChannel broker endpoint (seam day-one)",
    "docA": "",
    "idB": "REQ-HAZARD-ENDPOINT-LIFECYCLE",
    "titleB": "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.",
    "docB": "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="
  },
  {
    "pair_num": 45,
    "idA": "REQ-HAZARD-ATTACH-WEDGE",
    "titleA": "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)",
    "docA": "",
    "idB": "REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK",
    "titleB": "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.",
    "docB": "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"
  },
  {
    "pair_num": 46,
    "idA": "REQ-HAZARD-BOUNDARY-READY-STRAND",
    "titleA": "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.",
    "docA": "",
    "idB": "REQ-LISTEN-PRESERVES-HOSTING-TOPOLOGY",
    "titleB": "`api listen` must not ASSERT hosting topology it does not know. (hertz v0.39.0 field RCA 2026-07-21, doyle re-grounded at source.) OBSERVED: the published adapter sequence `api bind` then identity-preserving `api listen --session-id` produces a self-contradictory live record \u2014 controlled=true AND controllable=false on a broker-hosted PTY endpoint \u2014 which controlled-precedence masks blue while attached and which a DETACH then unmasks as amber HARNESS ONLY. Detach is not the root; it only reveals the bad stamp. SOURCE: api/startup.rs passes controllable=Some(false) UNCONDITIONALLY on the relay/listen path (~191-195, reasoning 'the harness owns the process, so there is no broker PTY'), and establish_perch resolves controllable = controllable.or_else(|| prior\u2026) (~379) \u2014 explicit wins, so that Some(false) OVERWRITES the Some(true) an earlier `api bind` EARNED. The carry-forward discipline that protects cwd/adapter/rest_state does not protect this field precisely BECAUSE the listen path is not silent about it. The defect is an ASSUMPTION about hosting authority made by a path that does not know the answer. FIX (preferred): represent LISTENER CUSTODY separately from PTY HOSTING AUTHORITY, so establishing a listener says nothing about who owns the session surface. Merely preserving controllable=true when a broker-hosted session with that session_id exists is weaker \u2014 it leaves listen guessing rather than removing the guess. Gate: impl \u2014 listen no longer asserts hosting topology for a session it does not host; unit \u2014 the stamp resolution table over (prior controllable, listen path, broker-hosted session present); int \u2014 bind -> listen -> control -> detach ends at alive=true, controlled=false, controllable=true, display ONLINE. TITLE AMENDMENT 2026-07-27 (doyle ruling, todlando build; rides the build PR per registry-mints-ride-build-PRs): THE REQ PRESERVES A **LIVING** HOSTING ARRANGEMENT ACROSS LISTENER RE-BINDS; IT DOES NOT RESURRECT A DEAD ONE'S CAPABILITY STAMP. FIELD CASE: emphasys rendered ONLINE for 25+ minutes with BOTH recorded pids dead, because its listener-only wake re-bind INHERITED a controllable=Some(true) earned in an earlier broker-PTY life, and the reconcile sweep exempts Some(true) rows from relay-death convergence (livehost.rs) \u2014 so an expired capability stamp ROUTED a liveness proof and the row was exempt from EVERY liveness model. The carry-forward is now scoped: Some(true) survives a listener re-bind unless the prior record's RELAY pid is provably Gone. Liveness of the arrangement is judged via the relay-role pid (REQ-PID-ROLE-EVIDENCE), the first record-internal key that actually measures it \u2014 NOT via 'earning pid alive', which was falsified pre-build: for a BrokerPty row the record holds no pid of the hosting life at all, only the announcing CLI's.",
    "docB": ""
  },
  {
    "pair_num": 47,
    "idA": "REQ-HAZARD-CORRUPT-PERCH-COHERENCE",
    "titleA": "Corrupt (present-but-unparseable) info.json is NOT absent: liveness/status readers agree a destroyed record is neither alive nor Active (counter-39 #2)",
    "docA": "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",
    "idB": "REQ-RELAY-DEATH-CONVERGENCE",
    "titleB": "A dead harness relay MUST converge the endpoint's projection \u2014 an endpoint whose relay process is provably gone cannot keep rendering `ONLINE - HARNESS ONLY` with ready=true alive=true and a registered address merely because the owner parent is still alive. RE-PROVEN PRESENT on 0.44.0 (hertz W0 item 2, 2026-07-27, fresh isolated build sha-prefix 00e15f): relay pid 38764 killed 06:21:01Z, owner pid 12156 live; 148s later info.json still pid=38764 status=online, list JSON ready=true alive=true address=127.0.0.1:57709, TCP to that address unreachable, human list `ONLINE - HARNESS ONLY`. Sibling probes: live-relay sibling's socket reachable and `send` -> SENT with the EVENT received (the rig CAN see delivery); clean `endpoint stop` sibling rendered status=offline ready=false alive=false (the rig CAN see convergence). FIX SHAPE: relay-death convergence \u2014 a dead/unreachable relay clears the ready/address/liveness projection for its endpoint within a bounded window, in ONE authority: the verdict MUST route through the shipped custody/process-identity predicate (KH 2.5 \u2014 liveness authority lives in one resolver, never re-derived beside it; Unproven never kills). Kin: the Athenaeum stale-ALIVE generator (HOSTING_AUTHORITY_DEMOTED demote-to-no-claim arm, REQ-LISTEN-PRESERVES-HOSTING-TOPOLOGY \u2014 the demote-vs-offline fork needs the same identity predicate), SHELL-STALE-ONLINE (same lying-signal class, shell flavor), REQ-MSG-INJECT-LEG-DROP-VISIBLE (the observability face of the same wedge). TITLE ADDENDUM 2026-07-27 (doyle ruling, todlando build): THE VERDICT FIRES ONLY WHERE THE RECORDED PID WAS A **HOLDER** (pid_role=relay, REQ-PID-ROLE-EVIDENCE). The shipped branch asks the oracle about whatever pid the record carries, and `info.pid` means two different things by write path \u2014 a holding `api listen` relay, or an `api bind` announcer that exits within seconds. Measured on HFENDULEAM 2026-07-27: todlando 22588, doyle 45160, deployah 29176 all DEAD-and-online (binder pids) against hertz 11216 / mobile-gw 46152 alive (relay pids), so an unrole-gated verdict would have offlined three live agents. A row with no role stamp (legacy) is NO KNOWLEDGE and never converges; it heals at its next re-bind.",
    "docB": ""
  },
  {
    "pair_num": 48,
    "idA": "REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT",
    "titleA": "An spt-hosted endpoint driven by a REMOTE controller whose remote is gone but whose broker connection stays OPEN (a wedged/lost pump that never delivers the detach) AND whose session is IDLE (no output) stays latched ONLINE+CONTROLLED forever: the W1 drain-evict only fires on OUTPUT (CONTROLLER_WRITE_DEADLINE on a backed-up write), a clean disconnect self-heals via detach_if\u2192clear_controller, but an idle session with a half-open/wedged controller connection produces neither signal. PROVED repro-first on a real broker (v0.13.0 W5, inject_control_wedge.rs w5_a2): controller_by STAYS Some(origin) and driven_by STAYS Some after the remote is abandoned without a clean EOF on an idle session \u2014 so the brain reconcile CANNOT detect it from KIND_SESSIONS controller_by (the broker still reports it controlled). FIX DIRECTION (doyle ruling 2026-06-19, broker-side single-writer \u2014 the broker owns driven_by/clear_controller): wire the EXISTING D4c NetPresence connection-disconnect event \u2192 clear_controller for any session whose controller identity == the dead origin (become_controller already stores Some(origin); presence events already exist \u2014 modest wiring, NOT a new probe). The liveness ORACLE is QUIC's own keepalive/idle-timeout: a presence-disconnect IS a real QUIC conn close, already tolerant of transient blips within the keepalive window, so NO heavy partition ADR is needed UNLESS the QUIC timeout proves too slow for the UX (then mint an ADR for a faster controller-heartbeat + its false-evict bound). Composes with W1 (output path) + W5 Gap B (no-session) \u2014 this is the third, idle-remote, leg. (v0.13.0 follow-up)",
    "docA": "",
    "idB": "REQ-UPDATE-TRIAL-DRAIN-DRIVE",
    "titleB": "UPDATE-WEDGE (counter-54, doyle-ruled 2026-07-09 \u2014 regression of the v0.29.0 seamless brain-swap): a brain generation DRIVES the broker's controller-liveness reap (a KIND_SESSIONS poll) each heartbeat throughout its boot/trial loop, so a hard-KILLED prior generation's black-holed LOCAL controller conn (by:None) is stall-evicted within the trial window and can never permanently strand the promotion DRAINED gate. ROOT (2026-07-09 field freeze, `spt update fetch --apply` v0.30.0->v0.30.2 froze all 7 live PTYs ~30s then rolled back): the promote gate (run_trial, brainproc.rs:657-661) needs BOTH `ready_generation==gen` AND `old_gen_drained()`; `old_gen_drained()` = `!any_local_controller_wedged()` (brainproc.rs:534) is a PURE READ of `write_blocked_since` (broker.rs:2703) \u2014 it never DRIVES the evict. The evict (`stall_evict_controller`, broker.rs:1039, same 15s `brain_write_deadline` the wedge-read uses) only runs via `reap_dead_controller` (broker.rs:967, severed->drop ELSE stall-evict) inside the KIND_SESSIONS snapshot closure (broker.rs:2879). During the isolated brain-trial window NOTHING polls KIND_SESSIONS: the old brain was hard-killed (`child.kill()`, brainproc.rs:851) so its local controller conn black-holes (live PTYs keep writing to a dead Windows named pipe -> writer BLOCKS, never EOFs) -> `write_blocked_since=Some` -> wedged=true for the full 30s -> gate false -> `WindowElapsedAlive` -> kill+rollback. The candidate DID reach ready (write_ready, brainproc.rs:211, runs before the loop; the v53 log's NET_FAMILY_GATE/PAIR_MEET_UP prove the loop was entered) \u2014 so `BRAIN_TRIAL_TIMEOUT: candidate alive but never ready` is the MISLEADING ready-stamped-but-never-DRAINED case, NOT a resume_sessions hang. SECOND LEG (recovery): post-rollback the gen-2 brain's KIND_SESSIONS poll finally reaps -> `BRAIN_SUBSCRIBER_STALL_EVICT:1` fires >15s late -> a session stayed black-holed through recovery -> continued freeze. ONE root, BOTH legs. FIX (BRAIN-SIDE, self-applying \u2014 doyle ruled brain-side to AVOID a broker-side coordinated-restart flag): the boot/trial heartbeat loop (brainproc.rs run_brain, currently only `net_status` at :244) also issues `Brain::sessions()` (KIND_SESSIONS, brain.rs:1397 \u2014 already exists) every heartbeat, driving the LIVE older broker's ALREADY-SHIPPED reap (>=v0.29.0 LIFECYCLE-TRUTH; the field-stuck broker is v0.30.0 so it HAS it). The old-gen wedged conn is stall-evicted ~15s < the 30s window -> `old_gen_drained()` flips true -> PROMOTE; the same poll on the rollback/recovery brain reaps promptly -> no >15s black-hole -> kills the STALL_EVICT recovery leg. Drive it on the FIRST heartbeat (no one-tick wait) and on BOTH the trial candidate AND the recovery brain (one loop covers both). SELF-APPLIES because it drives the current broker's existing reap verb \u2014 v54's brain fixes the v0.30.0->v54 update with NO coordinated broker restart. Forward-compat: brokers <v0.29.0 have neither the DRAINED gate nor the reap, so older-broker updates never hit this path \u2014 no regression. Composes with REQ-UPDATE-PROMOTE-DRAINED (the gate this un-strands) + REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE (the stall-evict it drives) + REQ-CONTROLLER-LIVENESS-REAP (the reap verb). Int = the brain-swap-under-live-sessions rig: (1) with a hard-killed prior gen holding a wedged by:None controller past 15s, the new brain PROMOTES within the window AND sessions stay served across the cycle (RED-first: without the drive, WindowElapsedAlive->rollback); (2) a deliberately-failing trial auto-rolls-back AND the restored brain re-drives EVERY session with NO BRAIN_SUBSCRIBER_STALL_EVICT.",
    "docB": "brain-trial promotion (readiness + drained)** \u2014 the broker supervises the swapped-in brain through a bounded readiness **trial** and *promotes* the new binary only when it both signals ready for its own generation **and** the OUTGOING generation's control plane has **drained** \u2014 the old brain's local (brain-owned) controller connection is closed or stall-evicted, never still holding blocked writes. A hard-killed prior generation leaves that connection **black-holed** (its hosted PTYs keep producing output the broker's writer blocks on, since a killed peer's pipe blocks rather than EOFs), and t"
  },
  {
    "pair_num": 49,
    "idA": "REQ-HAZARD-HOSTED-LIVENESS-RECONCILE",
    "titleA": "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)",
    "docA": "",
    "idB": "REQ-PRESENCE-LIVENESS-TRUTH",
    "titleB": "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)",
    "docB": ""
  },
  {
    "pair_num": 50,
    "idA": "REQ-HAZARD-SHELL-STALE-ONLINE",
    "titleA": "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.",
    "docA": "| # | 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",
    "idB": "REQ-LIST-JSON-LIVENESS-PARITY",
    "titleB": "GATEWAY-LIVENESS (flynn field bug 2026-07-09, RCA reader-divergence root): `spt endpoint list` (human) and `endpoint list --json` MUST report an IDENTICAL status for a locally-hosted endpoint \u2014 especially a pid-alive, status-ABSENT gateway (no psyche_init). ROOT (todlando RCA STEP-1, doyle-verified): the --json builder (crates/spt/src/cli.rs cmd_endpoint_list) emits each subnet row's status straight from resource_projection (spt-net registry.rs:566, passes instance.status through verbatim :592 \u2014 the persisted WAN snapshot, a lagged gossip that can carry a stale/crash-time Suspended) and NEVER applies the self-owned reconcile the human/picker path applies (reconcile_self_owned, crates/spt/src/picker/data.rs:160 via gather_endpoints :112). So a pid-alive self-owned gateway reads Suspended on --json but ONLINE on human (roster::enumerate spt/src/roster.rs:38 -> is_perch_alive pid-fallback spt-store/liveness.rs:136); the adapter suspend-poll (parse_endpoint_status over endpoint list --json --show-all) reads the divergent --json status -> self-suspends a pid-alive gateway. Candidates REFUTED: resource_projection does NOT re-derive liveness (copies instance.status, only skips !routable :582); render is a faithful {:?}. advertised_status (registryhost.rs:822) DOES compute Active via pid-fallback (live advertise fine) \u2014 the bug is the READER showing the un-reconciled snapshot. FIX (doyle-ruled): apply the self-owned reconcile in the cli --json builder (SAME spt/picker consumer layer as gather_endpoints, NOT lifted into resource_projection which inverts the dep spt-net->perch), reading LOCAL perch truth (is_perch_alive/unbound -> Active/Offline, mirroring the picker's local_rows). Do NOT force status=online (DEFECT B latent \u2014 seed-#5 orphan-listener false-ONLINE risk). Gate: a pid-alive locally-hosted gateway (status-absent, no psyche_init) reads the SAME non-Suspended status on human AND --json. Field-verify flynn (mobile-gw). Kin REQ-PICKER-3 + REQ-PRESENCE-LIVENESS-TRUTH + seed-#5 orphan-listener false-ONLINE.",
    "docB": ""
  },
  {
    "pair_num": 51,
    "idA": "REQ-INST-4",
    "titleA": "active to dormant/suspended fires a transition echo commune",
    "docA": "",
    "idB": "REQ-LIST-JSON-LIVENESS-PARITY",
    "titleB": "GATEWAY-LIVENESS (flynn field bug 2026-07-09, RCA reader-divergence root): `spt endpoint list` (human) and `endpoint list --json` MUST report an IDENTICAL status for a locally-hosted endpoint \u2014 especially a pid-alive, status-ABSENT gateway (no psyche_init). ROOT (todlando RCA STEP-1, doyle-verified): the --json builder (crates/spt/src/cli.rs cmd_endpoint_list) emits each subnet row's status straight from resource_projection (spt-net registry.rs:566, passes instance.status through verbatim :592 \u2014 the persisted WAN snapshot, a lagged gossip that can carry a stale/crash-time Suspended) and NEVER applies the self-owned reconcile the human/picker path applies (reconcile_self_owned, crates/spt/src/picker/data.rs:160 via gather_endpoints :112). So a pid-alive self-owned gateway reads Suspended on --json but ONLINE on human (roster::enumerate spt/src/roster.rs:38 -> is_perch_alive pid-fallback spt-store/liveness.rs:136); the adapter suspend-poll (parse_endpoint_status over endpoint list --json --show-all) reads the divergent --json status -> self-suspends a pid-alive gateway. Candidates REFUTED: resource_projection does NOT re-derive liveness (copies instance.status, only skips !routable :582); render is a faithful {:?}. advertised_status (registryhost.rs:822) DOES compute Active via pid-fallback (live advertise fine) \u2014 the bug is the READER showing the un-reconciled snapshot. FIX (doyle-ruled): apply the self-owned reconcile in the cli --json builder (SAME spt/picker consumer layer as gather_endpoints, NOT lifted into resource_projection which inverts the dep spt-net->perch), reading LOCAL perch truth (is_perch_alive/unbound -> Active/Offline, mirroring the picker's local_rows). Do NOT force status=online (DEFECT B latent \u2014 seed-#5 orphan-listener false-ONLINE risk). Gate: a pid-alive locally-hosted gateway (status-absent, no psyche_init) reads the SAME non-Suspended status on human AND --json. Field-verify flynn (mobile-gw). Kin REQ-PICKER-3 + REQ-PRESENCE-LIVENESS-TRUTH + seed-#5 orphan-listener false-ONLINE.",
    "docB": ""
  },
  {
    "pair_num": 52,
    "idA": "REQ-LISTEN-PRESERVES-HOSTING-TOPOLOGY",
    "titleA": "`api listen` must not ASSERT hosting topology it does not know. (hertz v0.39.0 field RCA 2026-07-21, doyle re-grounded at source.) OBSERVED: the published adapter sequence `api bind` then identity-preserving `api listen --session-id` produces a self-contradictory live record \u2014 controlled=true AND controllable=false on a broker-hosted PTY endpoint \u2014 which controlled-precedence masks blue while attached and which a DETACH then unmasks as amber HARNESS ONLY. Detach is not the root; it only reveals the bad stamp. SOURCE: api/startup.rs passes controllable=Some(false) UNCONDITIONALLY on the relay/listen path (~191-195, reasoning 'the harness owns the process, so there is no broker PTY'), and establish_perch resolves controllable = controllable.or_else(|| prior\u2026) (~379) \u2014 explicit wins, so that Some(false) OVERWRITES the Some(true) an earlier `api bind` EARNED. The carry-forward discipline that protects cwd/adapter/rest_state does not protect this field precisely BECAUSE the listen path is not silent about it. The defect is an ASSUMPTION about hosting authority made by a path that does not know the answer. FIX (preferred): represent LISTENER CUSTODY separately from PTY HOSTING AUTHORITY, so establishing a listener says nothing about who owns the session surface. Merely preserving controllable=true when a broker-hosted session with that session_id exists is weaker \u2014 it leaves listen guessing rather than removing the guess. Gate: impl \u2014 listen no longer asserts hosting topology for a session it does not host; unit \u2014 the stamp resolution table over (prior controllable, listen path, broker-hosted session present); int \u2014 bind -> listen -> control -> detach ends at alive=true, controlled=false, controllable=true, display ONLINE. TITLE AMENDMENT 2026-07-27 (doyle ruling, todlando build; rides the build PR per registry-mints-ride-build-PRs): THE REQ PRESERVES A **LIVING** HOSTING ARRANGEMENT ACROSS LISTENER RE-BINDS; IT DOES NOT RESURRECT A DEAD ONE'S CAPABILITY STAMP. FIELD CASE: emphasys rendered ONLINE for 25+ minutes with BOTH recorded pids dead, because its listener-only wake re-bind INHERITED a controllable=Some(true) earned in an earlier broker-PTY life, and the reconcile sweep exempts Some(true) rows from relay-death convergence (livehost.rs) \u2014 so an expired capability stamp ROUTED a liveness proof and the row was exempt from EVERY liveness model. The carry-forward is now scoped: Some(true) survives a listener re-bind unless the prior record's RELAY pid is provably Gone. Liveness of the arrangement is judged via the relay-role pid (REQ-PID-ROLE-EVIDENCE), the first record-internal key that actually measures it \u2014 NOT via 'earning pid alive', which was falsified pre-build: for a BrokerPty row the record holds no pid of the hosting life at all, only the announcing CLI's.",
    "docA": "",
    "idB": "REQ-PRESENCE-LIVENESS-TRUTH",
    "titleB": "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)",
    "docB": ""
  },
  {
    "pair_num": 53,
    "idA": "REQ-PICKER-3",
    "titleA": "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)",
    "docA": "",
    "idB": "REQ-WHOAMI-1",
    "titleB": "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`.",
    "docB": "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"
  },
  {
    "pair_num": 54,
    "idA": "REQ-PRESENCE-LIVENESS-TRUTH",
    "titleA": "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)",
    "docA": "",
    "idB": "REQ-RUN-PICKER",
    "titleB": "Interactive `spt endpoint run` picker (ratatui TUI): bare `spt endpoint run` (no --adapter/--id) enters an in-process picker (flags-present = the REQ-HOST-RUN-1 non-interactive path, untouched). Layer 1 picks kind (Create new | Pick existing). Create-new: choose a registered kind=\"harness\" adapter with its shipped+local profiles tree-nested (registry::registered / manifest.profiles / local_profile_names) \u2192 enter a charset-validated id \u2192 start. Pick-existing: category select (left/right) over [<cwd-project> | Local node | Subnet], endpoints grouped + alphabetically sorted per category, a status square per endpoint (online green \u25a0 / offline gray \u25a2 \u2014 the blue \"attached\" tri-state + Kick are DEFERRED to a broker attach-presence slice, M12-W2-RULING Q1), type-to-filter (`/`, nucleo-matcher), a pinned keybind legend, and a right-half two-pane description (harness adapter:profile \u00b7 best-effort project history newest\u2192oldest from the contextstore p-<project> branches, empty-if-none \u00b7 `spt endpoint description`). Confirm layer offers status-dependent options \u2014 Attach/Start/View (rc pump / cmd_endpoint_run) \u00b7 Instantiate-locally (remote) \u00b7 Change-harness-adapter (offline) \u00b7 Fork (cmd_fork) \u00b7 Resume-from-history (offline+LOCAL only; enumerate spt_store::sessions::last_k, titles `<project> @ <ts> (\u2026id5)`, feed session_id \u2192 cmd_endpoint_run --resume). A single action enum is the source of truth so a future tap-mode (phone PTY) layers on without re-coupling to keybinds. EVERY terminal action routes through cmd_endpoint_run / existing CLI fns \u2014 no second bringup path.",
    "docB": "spt-hosted bringup picker (`spt endpoint run`)** (M12-W2): The user-facing bringup flow for spt-hosted endpoints. **Bare `spt endpoint run`** (no `--adapter`/`--id`) opens an in-process **ratatui picker**; the **flagged** form is the non-interactive bringup path (`--adapter <a[:profile]> --id <id> --create|--resume <session start|--attach|--view`), untouched \u2014 a picker selection bakes exactly that path. **Layer 1 picks the kind (*Create new* | *Pick existing*). **Create-new** chooses a registered `kind=\"harness\"` adapter with its shipped+local **profiles tree-nested**, then a charset-validated"
  },
  {
    "pair_num": 55,
    "idA": "REQ-REST-TERMINAL-NORMALIZE",
    "titleA": "REGISTRY-LIFECYCLE W2 (ADR-0041 decision 3, rest-normalize P0): definitive hosted-session loss normalizes TERMINALLY and ATOMICALLY \u2014 one store-level mutation writes status=offline + rest_state=suspended + clears dormant_since_ms in the SAME info.json write, invoked at authoritative broker-session-loss/liveness-reap + cmd_stop. NOT daemon_rest_event(Suspend) (no edge when effective already Suspended \u2014 raw Active intent survives); NOT reader-side blanket offline-implies-suspended (destroys explicit-Wake semantics: wake writes intent first, reconcile consumes). Graceful shutdown keeps echo-before-teardown. Kills the zombie WAKE_RESUME loop (perri field: repeated cross-generation resumes of a dead session). Gate: impl \u2014 atomic terminal-normalize mutation + call sites; unit \u2014 store-level atomic pair never mixed + endpoint_stop covers already-offline/raw-Active input; int \u2014 session vanish means offline+suspended and the NEXT reconcile emits NO WAKE_RESUME, explicit suspended/offline->Wake->Active launches EXACTLY once, RefuseLivePid-then-valid-bind custody race leaves the revived seat unsuspended; doc \u2014 ADR-0041.",
    "docA": "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",
    "idB": "REQ-UPDATE-TRIAL-DRAIN-DRIVE",
    "titleB": "UPDATE-WEDGE (counter-54, doyle-ruled 2026-07-09 \u2014 regression of the v0.29.0 seamless brain-swap): a brain generation DRIVES the broker's controller-liveness reap (a KIND_SESSIONS poll) each heartbeat throughout its boot/trial loop, so a hard-KILLED prior generation's black-holed LOCAL controller conn (by:None) is stall-evicted within the trial window and can never permanently strand the promotion DRAINED gate. ROOT (2026-07-09 field freeze, `spt update fetch --apply` v0.30.0->v0.30.2 froze all 7 live PTYs ~30s then rolled back): the promote gate (run_trial, brainproc.rs:657-661) needs BOTH `ready_generation==gen` AND `old_gen_drained()`; `old_gen_drained()` = `!any_local_controller_wedged()` (brainproc.rs:534) is a PURE READ of `write_blocked_since` (broker.rs:2703) \u2014 it never DRIVES the evict. The evict (`stall_evict_controller`, broker.rs:1039, same 15s `brain_write_deadline` the wedge-read uses) only runs via `reap_dead_controller` (broker.rs:967, severed->drop ELSE stall-evict) inside the KIND_SESSIONS snapshot closure (broker.rs:2879). During the isolated brain-trial window NOTHING polls KIND_SESSIONS: the old brain was hard-killed (`child.kill()`, brainproc.rs:851) so its local controller conn black-holes (live PTYs keep writing to a dead Windows named pipe -> writer BLOCKS, never EOFs) -> `write_blocked_since=Some` -> wedged=true for the full 30s -> gate false -> `WindowElapsedAlive` -> kill+rollback. The candidate DID reach ready (write_ready, brainproc.rs:211, runs before the loop; the v53 log's NET_FAMILY_GATE/PAIR_MEET_UP prove the loop was entered) \u2014 so `BRAIN_TRIAL_TIMEOUT: candidate alive but never ready` is the MISLEADING ready-stamped-but-never-DRAINED case, NOT a resume_sessions hang. SECOND LEG (recovery): post-rollback the gen-2 brain's KIND_SESSIONS poll finally reaps -> `BRAIN_SUBSCRIBER_STALL_EVICT:1` fires >15s late -> a session stayed black-holed through recovery -> continued freeze. ONE root, BOTH legs. FIX (BRAIN-SIDE, self-applying \u2014 doyle ruled brain-side to AVOID a broker-side coordinated-restart flag): the boot/trial heartbeat loop (brainproc.rs run_brain, currently only `net_status` at :244) also issues `Brain::sessions()` (KIND_SESSIONS, brain.rs:1397 \u2014 already exists) every heartbeat, driving the LIVE older broker's ALREADY-SHIPPED reap (>=v0.29.0 LIFECYCLE-TRUTH; the field-stuck broker is v0.30.0 so it HAS it). The old-gen wedged conn is stall-evicted ~15s < the 30s window -> `old_gen_drained()` flips true -> PROMOTE; the same poll on the rollback/recovery brain reaps promptly -> no >15s black-hole -> kills the STALL_EVICT recovery leg. Drive it on the FIRST heartbeat (no one-tick wait) and on BOTH the trial candidate AND the recovery brain (one loop covers both). SELF-APPLIES because it drives the current broker's existing reap verb \u2014 v54's brain fixes the v0.30.0->v54 update with NO coordinated broker restart. Forward-compat: brokers <v0.29.0 have neither the DRAINED gate nor the reap, so older-broker updates never hit this path \u2014 no regression. Composes with REQ-UPDATE-PROMOTE-DRAINED (the gate this un-strands) + REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE (the stall-evict it drives) + REQ-CONTROLLER-LIVENESS-REAP (the reap verb). Int = the brain-swap-under-live-sessions rig: (1) with a hard-killed prior gen holding a wedged by:None controller past 15s, the new brain PROMOTES within the window AND sessions stay served across the cycle (RED-first: without the drive, WindowElapsedAlive->rollback); (2) a deliberately-failing trial auto-rolls-back AND the restored brain re-drives EVERY session with NO BRAIN_SUBSCRIBER_STALL_EVICT.",
    "docB": "brain-trial promotion (readiness + drained)** \u2014 the broker supervises the swapped-in brain through a bounded readiness **trial** and *promotes* the new binary only when it both signals ready for its own generation **and** the OUTGOING generation's control plane has **drained** \u2014 the old brain's local (brain-owned) controller connection is closed or stall-evicted, never still holding blocked writes. A hard-killed prior generation leaves that connection **black-holed** (its hosted PTYs keep producing output the broker's writer blocks on, since a killed peer's pipe blocks rather than EOFs), and t"
  },
  {
    "pair_num": 56,
    "idA": "REQ-TRANSLATE-COMMIT-MISS-TOLERANCE",
    "titleA": "C-1 (F029, B6 ROOT \u2014 rescope of REQ-TRANSLATE-BINARY-LIVENESS-DECAY, now PINNED): at a checkpoint clear boundary the clear-only inject drives `/clear`; its `{commit}` is never observed within INJECT_COMMIT_DEADLINE (5s, broker.rs:158) so the inject worker FAULTS + TERMINATES a HEALTHY translate binary (broker.rs respool_and_fault + return) and by ADR-0022 design NEVER respawns \u2192 every subsequent force-native reports delivered=false ('no live translation binary', cli.rs:5046) = B6's exact field signature; the v0.12.0 checkpoint post-clear WAKE dies with the terminated binary + the fire-and-forget FIRE in the dead window. NOT a race \u2014 deterministic at every checkpoint-armed boundary (perri captured-stderr proof: TRANSLATION_FAULT on the F-019 unread daemon-stderr channel). FIX (REVISED, supersedes terminate-then-respawn): miss != fault \u2014 preserve the binary; the watchdog's job is ANTI-STALL (release the operator floor), not execution-verification. See addendum C-1 + ADR-0022 amendment.",
    "docA": "Amendment (2026-07-03, F029 C-1) \u2014 a commit-miss is NOT a fault; real faults respawn",
    "idB": "REQ-WAKE-RESUME-LEG",
    "titleB": "A-2 (REMOTE-TRUTH triage \u00a7A-2 + ADR-0033): the daemon reconcile gains a WAKE-RESUME LEG \u2014 an endpoint whose rest INTENT is Active but whose harness session is COLD (status != online) is resumed by the daemon via the adapter's [session.resume] template using the LAST LEDGER session id, so a bare `spt wake <id>` on a suspended live agent actually brings it back (today: reconcile_once start-arm hosts ONLY status==online (livehost.rs:199), so a woken-but-unbound endpoint is skipped forever \u2014 neither status reaches online nor does reconcile re-host). This is the ADR-0033 LIFT: the thin `spt wake` edge writes rest intent, the DAEMON does the work. Mirrors shellwake::resolve_wake (read rest state, live-pid double-launch guard, launch, NEVER flip status \u2014 the harness self-binds \u2192 online). The leg reads the recorded adapter (D-2, REQ-SESSION-ADAPTER-RECORDED); an UNREGISTERED recorded adapter is the Q5 daemon-variant refuse: do NOT spawn, record a LOUD host_error report (F-1 naming the adapter + `spt adapter add`), never silent, never fallback-spawn on a different adapter. BINDS: (1) status=online is set ONLY by a real bind \u2014 the resume leg NEVER stamps it (CONTEXT liveness truth; the A-1 effective-state derivation depends on this staying honest). (2) host_error is a REPORT of the most recent host-level failure, NEVER a liveness input \u2014 neither liveness nor advertised_status reads it (host_error + online still derives Active); cleared on a successful host/bind; the existing silent `continue` on a deregistered online adapter (livehost.rs:205) folds into the same field. (3) the resume-pid guard marker is CUSTODY-ONLY (F-030 nested-record discipline) \u2014 never a liveness input. cold-with-no-ledger-row degrades benign (loud-logged skip, no crash, today's behavior). Single-node; C-2 picker Wake-now unblocks after. --wait is a SEPARATE rider (REQ-WAKE-WAIT).",
    "docB": ""
  },
  {
    "pair_num": 57,
    "idA": "REQ-EP-5",
    "titleA": "Concrete shell instantiation model: spawn-mints-instance (vs relink/online), registered-on-node permission + broadcast-is-discovery, per-shell require_approval gate, max_instances_per_owner + over_cap, instance aliasing, discovery scope",
    "docA": "",
    "idB": "REQ-SEAM-SPAWN",
    "titleB": "spawn-session seam",
    "docB": ""
  },
  {
    "pair_num": 58,
    "idA": "REQ-HOST-RUN-1",
    "titleA": "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.",
    "docA": "",
    "idB": "REQ-TERM-6",
    "titleB": "Thread-spanning digest across session boundaries: a per-endpoint session ledger (`<perch>/sessions.log`) appended at first bind and by `api boundary` on `/clear`|`/compact` session rotation, the digest enumerating the last K sessions so its rolling window bridges a boundary, and a distinctive in-timeline boundary marker (DigestEntry::Boundary). The digest follows the live-agent thread, not a single session.",
    "docB": ""
  },
  {
    "pair_num": 59,
    "idA": "REQ-ENDPOINT-STOP-OFFLINE",
    "titleA": "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)",
    "docA": "",
    "idB": "REQ-HAZARD-SOFT-CLEANUP",
    "titleB": "Soft-cleanup preserves state, removes only the ready marker (6.2)",
    "docB": ""
  },
  {
    "pair_num": 60,
    "idA": "REQ-HAZARD-RING-PERCH-ADOPTION",
    "titleA": "A ring NEVER adopts \u2014 and therefore never deletes \u2014 a perch directory it did not create in THIS invocation (3.4). The twin of REQ-HAZARD-EPHEMERAL-CLEANUP, which guarded the LEAK direction only: cleanup runs on every exit path, so the moment `ring` mistakes somebody's REAL perch for its own ephemeral one, that same guaranteed cleanup becomes a guaranteed DELETION \u2014 ready marker, info.json, spool.db and the dir itself, by silent CLI-side fs ops with no daemon log (field 2026-07-27: emphasys lost a live endpoint and its spooled mail this way). DELETION IS ONLY THE DURABILITY HALF: while the ring holds the adopted perch, its reply-wait `drain_one_at` CONSUMES whatever is in the victim's spool and RENDERS it to the ringer as the reply. When ringer and victim are the SAME agent (the field case) that is at worst loss \u2014 anything drained goes to its intended recipient. When a THIRD party rings a live agent it is CROSS-AGENT MAIL DISCLOSURE: the victim's inbox is drained into the ringer's output, with no trace on either side once the dir is removed, and on a subnet spanning machines that is a CONFIDENTIALITY failure, not merely a durability one. Proven by the red-first artifact \u2014 with the guard disabled the survival unit returns `Replied{from:\"someone\", body:\"mail that must survive\"}` instead of a refusal. (Field-bounded by emphasys 2026-07-27: his own 66-second ring window returned TIMEOUT with no `Replied`, so nothing was drained in THAT incident \u2014 the disclosure shape was available and did not fire.) SELF-CAMOUFLAGING, which is why it went unattributed: the damage \u2014 perch gone, roster row ghosted \u2014 MIMICS the stale-liveness condition people reach for `ring` to diagnose, so the field caller was probing a ghost-roster symptom and the probe manufactured a fresh one; debuggers of stale liveness are disproportionately its victims and read the wreckage as more of the symptom they were chasing. The misjudgement source is READY-MARKER-FIRST liveness: an existing perch whose ready marker is momentarily down (busy turn, soft session-end, stale re-bind \u2014 and on hosted OMP endpoints marker-down is the NORMAL steady state during work, since the extension owns the listener and publishes busy/idle through `api state`) reads 'caller is perchless', so marker-first misjudges HEALTHY endpoints routinely, not rarely. INVARIANT: before creating an ephemeral reply perch, probe the perch DIRECTORY, not the marker \u2014 a dir carrying a record or a spool is OCCUPIED and the ring refuses to adopt it; an UNREADABLE record (corrupt/truncated info.json) or an unreadable dir counts as OCCUPIED, never as residue, because deletion is irreversible and refusal is recoverable, so every ambiguity resolves toward refusal; a pre-existing EMPTY dir is refused too (it is not provably ring's own residue \u2014 `endpoint run` mid-create owns an empty perch dir for a window, and deleting it is the same race wearing a different hat). Refusal is never silent and never blocks: the message is DELIVERED, the call declines to block-wait (the reply lands on the caller's own listener), and the caller gets a distinct loud report \u2014 RING_PERCH_EXISTS (record/spool) or RING_STALE_DIR (empty, naming the path and the manual remedy), so the permanent-refusal failure mode is solved by LOUDNESS rather than by a self-heal that would re-introduce the deletion race. Structural, not advisory: the leaf dir is created with `create_dir` (fails AlreadyExists), so even a perch that appears between the probe and the create cannot be adopted, and the occupied-refusal path never calls cleanup.",
    "docA": "3.4 A `ring` never adopts \u2014 so never deletes \u2014 a perch it did not create Failure:** 3.1 guards the LEAK direction (cleanup on every exit path). It says nothing about *whose* perch is being cleaned, and the two compose into a data-loss bug: `ring` decides \"the caller has no perch\" **ready-marker-first**, so an existing perch whose marker is momentarily down \u2014 busy turn, soft session-end, stale re-bind \u2014 reads *perchless*, `setup_ephemeral` clobbers the caller's real `info.json`, and 3.1's guaranteed cleanup then runs on the way out and **deletes** the ready marker, `info.json`, `spool.db` and t",
    "idB": "REQ-HAZARD-SOFT-CLEANUP",
    "titleB": "Soft-cleanup preserves state, removes only the ready marker (6.2)",
    "docB": ""
  },
  {
    "pair_num": 61,
    "idA": "REQ-ACL-ACCESS-REFRESH-VERB",
    "titleA": "`spt api access-refresh` is MINTED THIS WAVE BUT REFUSES \u2014 the verb exists, parses and is documented, and its refusal names why: the capture-refresh is engine-room-only, and engine-room enforcement (ADR-0052) does not land until W3. Minting the refusing verb now is deliberate: it fixes the contract adapters and the engine-room brief will be built against, and it makes the wave that implements enforcement a change to ONE behavior rather than a new surface plus its gate. A refusal that merely says 'unknown command' would invite an adapter to route around it. When it does light up it updates ONLY the node's captured subnet-level fallbacks \u2014 never the node's own rules, which are the operator's, not the subnet's. Gate: doc \u2014 the CONTEXT.md capture-refresh sentence naming the verb and its engine-room-only gate; impl \u2014 the verb, parsing, and a clear refusal naming the W3 dependency; unit \u2014 invoking it refuses with the engine-room diagnostic, changes no stored state, and is not reachable as an unknown-command fallthrough.",
    "docA": "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",
    "idB": "REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE",
    "titleB": "W2 (LIFECYCLE-TRUTH, KNOWN-HAZARDS, flagship \u2014 the update wedge): PTY viewer fan-out and control mutations must not depend synchronously on a live draining brain. ROOT rig-CONFIRMED (NtSuspendProcess on the brain, no update involved): brain-subscriber session-output writes ride UNDER the per-session log lock (broker.rs:19-20); failed writes are handled (cursor freeze + detach :3486) but BLOCKED writes are not. Suspended brain => within seconds attached rc output freezes; detach does NOT release the control stamp (release routes through the brain); reattach REFUSED (controlled-by); rc --take hangs; daemon status stays healthy. Field: every brain cycle (incl. every update apply) has a freeze window; a stalled/slow-draining new brain = permanent wedge until bounce; brain.ready != subscribers drained. FIX SHAPE (todlando proposes, doyle RULES BEFORE IMPL): subscriber writes move OFF the log lock (bounded/nonblocking, stall => detach-subscriber like viewer eviction \u2014 broker already buffers + replays on re-attach, so a detached-stalled brain self-heals by rewind); control stamp release/take completes against the BROKER without brain round-trip (or bounded with loud timeout). doc = KNOWN-HAZARDS entry. Int (tonight's rig, encoded): suspend brain child mid-session -> attached viewer ticks CONTINUE + rc --take completes; resume -> no output lost (cursor replay).",
    "docB": "7.36 The broker control plane and PTY fan-out must NEVER block on a single subscriber connection \u2014 a suspended brain conn must not wedge control `[REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]` Failure (paid-for, rig-CONFIRMED 2026-07-06/07 \u2014 `NtSuspendProcess` on the brain, no update involved):** a controller's writer thread does a BLOCKING socket write to its brain subscriber conn. When that brain is suspended (or black-holed) the write never returns. The output-driven eviction path (`append` \u2192 `mark_controller_gone`, bounded by 7.12's `CONTROLLER_WRITE_DEADLINE`) only fires on NEW output, and `r"
  },
  {
    "pair_num": 62,
    "idA": "REQ-ER-CEREMONY-VERB",
    "titleA": "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.",
    "docA": "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.",
    "idB": "REQ-PICKER-PURGE-SHORTCUT",
    "titleB": "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).",
    "docB": ""
  },
  {
    "pair_num": 63,
    "idA": "REQ-ACTIVITY-LINK-PUSH",
    "titleA": "An owned Shell observes its owner's busy/idle transitions as an activity frame on the EXISTING shell-link event stream (ADR-0048 decisions 1-3; perri contract-lock 2026-07-24). Link-scoped (owner implied by the link token \u2014 no new verb, no addressing surface). Drive-class semantics: ephemeral, latest-wins, current-state-carrying; redundant same-state resend is a harmless no-op (consumer derives edges); NEVER spooled or replayed (stale transitions are actively wrong). Both directions (busy->idle, idle->busy). Current state emitted on EVERY link establishment and re-link \u2014 restart resync is free. Latency promise = BOUNDED OBSERVATION: a frame per transition, sub-second class, never hard-real-time; each frame carries the TRANSITION TIMESTAMP (when the sentinel flipped, not when the frame was emitted) so edge-anchored consumers self-correct emission latency. Frame name/attrs publish in the shell frame vocabulary doc (REQ-SHELL-FRAME-VOCAB lineage) \u2014 doc stage rides there. Gate: int \u2014 a live shell link sees busy->idle and idle->busy frames with truthful transition timestamps, a re-link re-emits current state, and a transition during link-down is represented by the re-link emit (current state), never a replayed backlog.",
    "docA": "`activity` \u2014 the owner's busy/idle state, pushed",
    "idB": "REQ-ADAPTER-TRANSLATE-PROOF",
    "titleB": "`spt adapter translate-proof <adapter> --event <envelope> [--session <id>]` \u2014 the author-time EMIT-half proof tool for `[message-idle-translation-binary]` (ADR-0022), symmetric to `spt adapter digest-proof` (REQ-TERM-5). It spawns and feeds the adapter's declared translation binary EXACTLY as the daemon does at idle-delivery \u2014 running the REAL `spt_daemon::translation` driver VERBATIM (no protocol reimplementation): `TranslationChild::spawn` the binary, send the `{type:\"init\",endpoint_id,node}` line then the `{type:\"event\",envelope}` line, and read back the emitted `{key}`/`{text}`/`{delay_ms}`/`{commit}` keystroke-command stream \u2014 then prints it author-readable (each Key with its `key_to_bytes` rendering, Text quoted, Delay in ms, Commit marker) with counts. It fills the SAME `{id}`\u2192option and `{session_id}`\u2192(--session, else a placeholder) keys into the `--event` envelope the daemon fills at runtime, so an envelope that proofs here feeds faithfully live. EMIT-half ONLY: it proves the binary's spawn+feed+emit contract; it does NOT exercise the daemon's atomic PTY apply / controller-buffering (that stays covered by the W2 inject_control_wedge int gate) \u2014 `--help` says so. Exit codes mirror digest-proof: 0 ok, 1 on spawn-fail / zero commands / no-commit-or-output / unparseable, 2 when the adapter declares no `[message-idle-translation-binary]` section. The `TranslationChild` Drop does the bounded no-zombie reap. (v0.13.x)",
    "docB": "Author-time proof: `spt adapter translate-proof`"
  },
  {
    "pair_num": 64,
    "idA": "REQ-PSYCHE-CONTEXT-FILE-INDIRECTION",
    "titleA": "W4 (F030; doyle Q2 ruling + perri file-always freeze, 2026-07-04): the composed psyche mind ({psyche_context}) rides the SHIM argv today \u2014 a real ~20KB doyle psyche-download exceeds the win32 CreateProcess lpCommandLine ~32k cap \u2192 the shim spawn BRICKS. FIX (file-always, replaces {psyche_context} outright \u2014 no size-branch, no argv cliff, one path): core writes the mind to a file in the psyche's NESTED perch dir BEFORE each turn spawn and fills a single {psyche_context_file} = that PATH (argv-cap-immune). The soft fresh/continue discriminator moves from KEY-presence to FILE-CONTENT: FreshWithPreload writes the composed mind NON-EMPTY (the <fresh-psyche/> never-empty guarantee carries to the file content); ContinueExisting writes it TRULY 0-BYTE (perri BINDING PIN 1 \u2014 NO sentinel/placeholder EVER, else her non-empty=fresh discriminator misfires a spurious --session-id adopt). Core owns the file lifecycle: write-before-spawn each turn, overwrite in place, persists between turns in the nested perch (debuggability); never deleted per turn. perri shim delta: --psyche-context-file <path> arg, read-file prefix, TRIM-based emptiness (her tolerance, NOT core's license \u2014 core writes exactly 0 bytes on continue, PIN 2), read-failure = generic fail NEVER 95, never writes/deletes the file. Red-first: a ~40KB mind \u2192 the old {psyche_context}-on-argv path BRICKS the win32 shim spawn; the file path succeeds (shim reads the full mind from file).",
    "docA": "Psyche-download \u2014 `{psyche_context_file}` (file-always, replaces `{psyche_context}`).** The composed Psyche mind rides a **file**, never the command argv: before each turn spt-core writes the mind into the nested psyche perch dir and fills a single **`{psyche_context_file}` = that path** (argv-cap-immune \u2014 a real ~20 KB mind exceeds the win32 command-line cap and would brick the spawn). The soft **fresh-vs-continue** discriminator is the file's **content**, not key presence: a **fresh** (first / reseeded) turn writes the composed mind **non-empty** (a never-empty `<fresh-psyche/>` marker when ",
    "idB": "REQ-TRANSLATE-COMMAND",
    "titleB": "`[message-idle-translation-binary]` accepts a `command` (opaque; args + ADAPTER-STATIC {adapter_dir}/{adapter_name} substitution ONLY \u2014 ratified v0.16.0 W1, NOT session {key}: the translation binary is a persistent process serving all sessions on the endpoint (session/event ctx arrives per-message via the stdin Init/Event protocol, never the spawn argv) and the live-update respawn site has no session ctx (a {id}-bearing command would MissingKey\u2192spool); program token resolved against install_dir like [digest].extractor/[session.psyche_init]) in addition to the bare `path`. `path` is DEPRECATED \u2014 keeps parsing (manifest forward/back-compat) but emits a registration warning steering to command. Exactly one of {path, command} (both-set refused at registration; neither = no translation binary). The spawn lifecycle + stdin/stdout JSON-lines protocol (Init/Event/Input \u2192 key/text/delay_ms/commit) are UNCHANGED \u2014 command alters only how the executable+args are located/launched (read_translation_path \u2192 read_translation_command). Unblocks folding `claude-spt translate` into the one consolidated binary (downstream ADR-0006). (v0.16.0)",
    "docB": "`command` (preferred, since v0.16.0)** \u2014 an **opaque** command string (a program token plus args), exactly like the other command seams. Its program token resolves against the adapter **install dir** (REQ-INSTALL-11), like `[digest].extractor` / `[session.psyche_resume]`: a bare/relative program (e.g. `claude-spt`) resolves to `<install_dir>/<program>(.exe)` before PATH. Args support **adapter-static `{adapter_dir}` / `{adapter_name}` substitution only** \u2014 **not** session keys. The translation binary is a *persistent* process serving **every** session on the endpoint: session/event context arr"
  },
  {
    "pair_num": 65,
    "idA": "REQ-ADAPTER-FLOOR-ENFORCE",
    "titleA": "F-5 (REMOTE-TRUTH triage \u00a7F-5 + doyle rulings 2026-07-05): BOTH adapter acquisition verbs (spt adapter add + spt adapter update) REFUSE when the installed spt-core is BELOW the adapter's declared [adapter].min_spt_core_version floor \u2014 with an F-1 operator refusal naming the installed core, the floor, and the next action (update spt-core first). ROOT: the floor was PARSED + required (manifest.rs) but never compared to the running core \u2014 dead enforcement; and the [update].version_check knob that gated it was DOC'D-BUT-DEAD (never read by any production path \u2014 a contract lie). RULINGS: RETIRE version_check (drop the manifest field + schema + docs + the cfg(test) literals; a pre-existing manifest still setting it deserializes fine \u2014 serde ignores the unknown key, no deny_unknown_fields, so retiring is back-compatible); SEMVER-compare NOT string-compare (the 0.9.0 < 0.25.0 lexical trap); enforce on BOTH verbs; nothing installs / registry untouched on refuse (binds both verbs, no residuals). FIX: (1) a pure spt-runtime version_meets_floor(core, floor) -> bool (numeric per-component: split '.', u64, missing\u21920, non-numeric\u21920, first-diff decides, equal-when-zero-padded \u21d2 satisfied) \u2014 mirrors the CLI version_is_newer parse (same numeric model, different question: freshness=strictly-newer vs floor=at-least). (2) ADD: the gate lives INSIDE registry::register (the choke point) via a register_with_core(core_version) seam register() delegates to with env!(CARGO_PKG_VERSION) \u2014 the floor check runs right after the manifest parse, BEFORE any registry write, returning the typed RegistryError::CoreFloor{adapter,core,floor} (Display = the ONE F-1 refusal both verbs surface); nothing recorded on refuse. (3) UPDATE: a PRE-SWAP peek (staged_floor_ok) extracts the staged .spt to a THROWAWAY temp, parses its manifest floor, and refuses BEFORE apply_release_crc_swap mutates the live pointer-mode home \u2014 so a refusal (or an unverifiable floor: FAIL-CLOSED) leaves the live install BYTE-UNTOUCHED; register@8932 stays as the defense-in-depth backstop for every other entry path. doyle bind: the register-only gate would let the crc-swap replace the live files with a floor-violating version while the record refuses (record and reality disagree \u2014 the exact contract-lie shape this milestone kills), so the pre-swap peek is the only correct answer. Red-first: perri negative repro on ADD (fresh home + synthetic low core + high-floor manifest \u2192 CoreFloor refuse, registry untouched) + the UPDATE pre-swap refuse (live home byte-untouched) + a floor-met positive control (0.25.0-on-0.25.0 installs); + version_meets_floor table incl. the 0.9<0.25 trap.",
    "docA": "`min_spt_core_version` is the **enforced** compatibility floor. Both acquisition verbs \u2014 `spt adapter add` and `spt adapter update` \u2014 REFUSE when the installed spt-core is below this version, naming the installed core, the floor, and the next action (update spt-core first). The check is a numeric per-component compare (so `0.9.0 < 0.25.0`), and it fires **before** anything is written: a refused add leaves the registry untouched, and a refused update leaves the live install byte-untouched. The enforcement is unconditional \u2014 there is **no** opt-in flag (the former `[update].version_check` knob w",
    "idB": "REQ-INSTALL-BOOTSTRAP-VERB",
    "titleB": "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.",
    "docB": "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"
  },
  {
    "pair_num": 66,
    "idA": "REQ-ADAPTER-MULTIPLATFORM-SPT",
    "titleA": "A `.spt` adapter archive may pack multiple platforms in one signed asset: shared `manifest.toml` + `strings/` at the root, role binaries under per-Rust-target-triple subdirectories (ADR-0016 triple vocabulary, e.g. `x86_64-pc-windows-msvc/`); install/update extracts the shared root plus ONLY `current_platform()`'s triple subdir, flattened into `install_dir` so flat `<install_dir>/<program>` resolution (REQ-INSTALL-11) is unchanged. Name stays `adapter.spt` (plain-tar or gzip, `--asset` optional default); one whole-archive Ed25519 signature over the fat archive (REQ-UPD-9 single-artifact verify). A legacy flat archive (no triple subdirs) extracts as today (free back-compat); a multi-platform archive sets `min_spt_core_version >= 0.13.2` (forward-compat gate, readable before extract); a multi-platform archive missing the recipient's triple -> typed `NoArtifactForPlatform`, never a silent no-op. Large adapters may still split per-platform (single-triple archives via `--asset`, or ADR-0016 update-set machinery). (ADR-0024, v0.13.2)",
    "docA": "Multi-platform adapter `.spt` packaging",
    "idB": "REQ-INSTALL-3",
    "titleB": "Idempotent + interactive-optional first run",
    "docB": ""
  },
  {
    "pair_num": 67,
    "idA": "REQ-ADAPTER-UPDATE-MESSAGE",
    "titleA": "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)",
    "docA": "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. ",
    "idB": "REQ-INSTALL-12",
    "titleB": "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)",
    "docB": "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"
  },
  {
    "pair_num": 68,
    "idA": "REQ-CI-POSTJOB-DAEMON-REAP",
    "titleA": "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).",
    "docA": "",
    "idB": "REQ-INSTALL-13",
    "titleB": "Adapter add is non-destructive & idempotent-safe (F-018): `spt adapter add --github|--release` REFUSES when the target `_github/<safe>` home already backs an ACTIVE registered record \u2014 emitting an actionable code (ADAPTER_ADD_ALREADY_REGISTERED) that routes to `spt adapter update <name>` (refresh in place) or `spt adapter remove <name>` then re-add (replace) \u2014 instead of clobbering the live install (the perri footgun: `add --github` over a `--release` pointer git-cloned a source tree over the extracted built binaries \u2192 registered pointer dangled \u2192 cryptic `os error 2`). And when it DOES (re)populate the home it STAGES-THEN-SWAPS (clone/extract to a sibling staging dir, swap into place only on success) so a failed fetch/clone never strands the previously-extracted manifest+binaries as a dangling pointer (the os-2 / DeferredManifest class). Mirrors the safe stage-then-swap `adapter update` already uses (REQ-UPD-9, apply_release_crc_swap). (v0.14.1)",
    "docB": "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"
  },
  {
    "pair_num": 69,
    "idA": "REQ-DAEMON-9",
    "titleA": "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.",
    "docA": "",
    "idB": "REQ-INSTALL-7",
    "titleB": "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)",
    "docB": ""
  },
  {
    "pair_num": 70,
    "idA": "REQ-EP-9",
    "titleA": "`#` 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).",
    "docA": "Status: accepted (2026-06-21)",
    "idB": "REQ-RELEASE-MUSL-ARTIFACT",
    "titleB": "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.",
    "docB": ""
  },
  {
    "pair_num": 71,
    "idA": "REQ-HAZARD-WMI-DAEMON-WINDOW",
    "titleA": "`spt daemon start` launches the daemon with NO visible console window. REGRESSION (v0.12.1 L1.5): the WMI job-neutral launch (spawn_daemon_via_wmi) set CREATE_NO_WINDOW on the launching powershell but NOT on the Win32_Process.Create call \u2014 Win32_Process.Create does not inherit it, so the spawned cmd.exe env-forwarding wrapper popped a console window on every cold-start (violating REQ-INSTALL-10's v0.7.4 no-persistent-window invariant; the old detached_no_inherit path set DETACHED_PROCESS|CREATE_NO_WINDOW). FIX: pass a Win32_ProcessStartup with CreateFlags=DETACHED_PROCESS (0x8 \u2014 no console so no window; CREATE_NO_WINDOW 0x08000000 is NOT a valid Win32_ProcessStartup flag \u2192 ReturnValue 21 invalid-param, which is why the naive port fails) + ShowWindow=SW_HIDE(0) belt, via the ProcessStartupInformation argument. (v0.12.2)",
    "docA": "",
    "idB": "REQ-INST-15",
    "titleB": "Immutable home subnet (assigned at creation: auto-if-one/ask-if-many) + spt fork (cross-subnet clone to a new identity, copy-then-diverge, not re-home); adapter chosen at creation from registered hostable adapters, changed only via launch/resume-under-new (ADR-0010)",
    "docB": "Delivered (M4-D9-5, 2026-06-04):** home assignment at creation (`spt_store::home` \u2014 auto-if-one / ask-if-many / local-only-until-first-join, carried forward across re-binds, no setter) + `sync_subnets = [home]` creation seeding + `spt fork <src> <new_id> --subnet <target [--delete-source]` (one-time copy of both context tiers as fresh seed commits \u2014 copied-then-independent; join-time collision check against the target; the source untouched unless deleted). Same-node only in v1 \u2014 the remote arm composes with M5 instantiate-anywhere's consent gate."
  },
  {
    "pair_num": 72,
    "idA": "REQ-INST-10",
    "titleA": "Qualified addressing [subnet:]id[@node] + ambiguity forces qualification",
    "docA": "",
    "idB": "REQ-INSTALL-8",
    "titleB": "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)",
    "docB": ""
  },
  {
    "pair_num": 73,
    "idA": "REQ-INST-13",
    "titleA": "Subnet-exclusive sync + per-endpoint subnet-membership list",
    "docA": "",
    "idB": "REQ-INSTALL-11",
    "titleB": "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)",
    "docB": "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"
  },
  {
    "pair_num": 74,
    "idA": "REQ-INST-2",
    "titleA": "Per-node files, synced Psyche mind",
    "docA": "",
    "idB": "REQ-INST-9",
    "titleB": "Multi-subnet membership (same-user N subnets; cross-user seam)",
    "docB": ""
  },
  {
    "pair_num": 75,
    "idA": "REQ-INST-5",
    "titleA": "Two-tier context sync (live to all, project to same-project)",
    "docA": "",
    "idB": "REQ-INSTALL-8",
    "titleB": "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)",
    "docB": ""
  },
  {
    "pair_num": 76,
    "idA": "REQ-INST-9",
    "titleA": "Multi-subnet membership (same-user N subnets; cross-user seam)",
    "docA": "",
    "idB": "REQ-INSTALL-10",
    "titleB": "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)",
    "docB": ""
  },
  {
    "pair_num": 77,
    "idA": "REQ-INSTALL-12",
    "titleA": "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)",
    "docA": "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",
    "idB": "REQ-INSTALL-5",
    "titleB": "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).",
    "docB": ""
  },
  {
    "pair_num": 78,
    "idA": "REQ-INSTALL-6",
    "titleA": "Linux elevation install leg: install.sh symlinks the binary into a sudo-reachable path (/usr/local/bin; graceful print-the-one-liner when unelevated) so sudo spt resolves; first sudo spt detects elevation and prompts ONCE for the default user account \u2014 thereafter any elevated daemon launch runs daemon + state under that account, never root (KH 5.7 interplay verified) (M8 decision 8)",
    "docA": "",
    "idB": "REQ-MANIFEST-SUBST",
    "titleB": "Manifest substitution primitives for resolve-not-execute (ADR-0029, supersedes a rejected `spt api run-hook`): (1) two adapter-static substitution keys `{adapter_dir}` (the registry record's precise source_dir \u2014 install dir, survives updates, the dir bare-program resolution uses) and `{adapter_name}`, available wherever command/string substitution runs; (2) lazy substitution INSIDE `[strings]` values at `get-string` read time, scoped to those adapter-static keys ONLY (session-scoped {id}/{session_id}/\u2026 are NOT available \u2014 get-string carries no session; a get-string --session-id is a deferred larger change). Invariant preserved: spt-core never executes a string \u2014 it substitutes and returns; the adapter's own wrapper executes the result (e.g. a CC hook dispatcher get-strings its packed binary once per session into an env var, then runs it per-hook, so hook logic rides `spt adapter update`). (v0.16.0)",
    "docB": "Adapter-static keys \u2014 `{adapter_dir}` and `{adapter_name}` (since v0.16.0).** Two of the catalog keys are *adapter-static* \u2014 they depend only on the resolved adapter, never on a session or event, so they are available **wherever** command/string substitution runs (every `[session.*]` template, the `[digest]` extractor, the `[message-idle-translation-binary].command`, and \u2014 uniquely \u2014 inside `[strings]` values at `get-string` read time): / Lazy substitution inside `[strings]` values (since v0.16.0).** A `[strings]` value (inline literal or the read-back contents of a pointer file) may contain t"
  },
  {
    "pair_num": 79,
    "idA": "REQ-PLATFORM-MUSL",
    "titleA": "MUSL-TIER W2 (register the musl platform, self-IDENTIFY): add x86_64-unknown-linux-musl to the W1 registry \u2014 a current_platform() cfg arm (target_arch=x86_64, target_os=linux, target_env=musl -> the musl triple, NO more 'unknown' fallback), asset name spt-x86_64-linux-musl, triple-map entry. PROVEN (doyle /diagnose 2026-07-08, backlog #14): the current tree builds+runs static musl with ZERO source changes (rustls not openssl; aws-lc-sys+bundled-sqlite clean under musl-gcc; openpty; DNS/HTTPS works statically) \u2014 so W2 is registry DATA + cfg + asset map, NO dependency changes. Gate: a musl-built spt self-reports x86_64-unknown-linux-musl (not 'unknown'); registry-membership unit (the cfg arm is cross-target, unit the registry not the arm); build the musl target in-gate (kitsubito toolchain) and assert current_platform.",
    "docA": "",
    "idB": "REQ-PLATFORM-REGISTRY",
    "titleB": "MUSL-TIER W1 (target-triple centralization, behaviour-NEUTRAL refactor): ONE authoritative platform registry from which current_platform(), KNOWN_TARGET_TRIPLES, the applyhost cross-platform 'other' logic, and the asset-name<->triple map all derive. ROOT: the target triple x86_64-unknown-linux-gnu + the implicit 'exactly 2 platforms' assumption are hardcoded across ~6 sites (release.rs current_platform cfg + KNOWN_TARGET_TRIPLES, applyhost.rs:740-743 win/linux binary if/else, xtask asset map, release.yml), so adding any platform (musl, future arm64) is a scattered edit. FIX: a data-driven registry (candidate: SUPPORTED_PLATFORMS const table of {triple, asset_name}) + generalize applyhost 'other' to 'every registered platform except current_platform()'. gnu+windows behaviour BYTE-IDENTICAL \u2014 the existing release/update/apply/propagate suites stay green (that is the gate). DESIGN FORK (doyle rules pre-dispatch): enum vs const-table; applyhost N-platform generalization; current_platform stays cfg->triple but output must be a registry member, loud 'unknown' fallback kept.",
    "docB": ""
  },
  {
    "pair_num": 80,
    "idA": "REQ-ADAPTER-UPDATE-POST",
    "titleA": "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)",
    "docA": "`[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.",
    "idB": "REQ-TERM-5",
    "titleB": "Adapter-declared digest extractor seam: a `[digest]` manifest section declaring an imperative extractor (native harness log -> the {role,text,tool,ts} contract; defaults to the [history] source files with an own-source escape hatch), `api digest-entry` push fallback, register-time validation of the section, adapter-declared presentation defaults (window depth, arg-truncation, sprint-collapse) that any consumer may override, and a `spt adapter digest-proof` author tool plus runtime skip-diagnostics (no silent drop). Reverses M9's no-manifest-seam stance; no declarative DSL.",
    "docB": "The session digest is a PROJECTION of the endpoint's session logs, never a PTY-byte parse (the superseded source mechanism). ADR-0019 gives it its OWN manifest seam \u2014 the `[digest]` extractor above \u2014 distinct from `[history]` (which stays opaque + single-session, feeding the echo-commune verbatim). The M9 \"no manifest seam / rides `[history]`\" stance is REVERSED: one `[history]` normalizer cannot serve both the opaque echo consumer and the contract-typed digest. What is published here is the digest-record CONTRACT: the small, fixed, spt-core-owned shape the `[digest]` extractor emits (or that "
  },
  {
    "pair_num": 81,
    "idA": "REQ-HAZARD-DIRECT-WRITE-PRECEDENCE",
    "titleA": "Direct-write precedence marker (with node id) guards stale overwrite (6.5)",
    "docA": "",
    "idB": "REQ-HAZARD-STALE-INDEX-LOCK",
    "titleB": "Sweep stale lockfiles on daemon boot (1.3)",
    "docB": ""
  },
  {
    "pair_num": 82,
    "idA": "REQ-HAZARD-REGISTRY-STALE-CLEAN",
    "titleA": "Stale registry entries degrade to fallback, never hard-fail (4.3)",
    "docA": "",
    "idB": "REQ-SEAM-HISTORY",
    "titleB": "History subsystem (fetcher / locate-normalize / native store)",
    "docB": ""
  },
  {
    "pair_num": 83,
    "idA": "REQ-INSTALL-4",
    "titleA": "Adapter registration lifecycle: spt adapter add (--github, manifest-first, install-is-first-update) + soft-deregister remove + optional manifest uninstall template; node-local registered-adapter set self-update ripples over",
    "docA": "",
    "idB": "REQ-MANIFEST-1",
    "titleB": "Per-adapter manifest with adapter_name and min_spt_core_version",
    "docB": "The sections below are the authoritative schema; the typed form in `crates/spt-runtime/src/manifest.rs` is kept in lockstep."
  },
  {
    "pair_num": 84,
    "idA": "REQ-MANIFEST-5",
    "titleA": "File-backed adapter [strings] (M12-W3-T3.1): a [strings] dot-path value MAY be an inline-table FILE POINTER `key = { file = \"rel/path\" }` resolved to the file's contents at get-string time, keeping large bodies (skill-instructions, hint text) out of the manifest. A value-position table with a `file` key IS the pointer form (reserved \u2014 cannot double as data). Per-adapter aux storage `adapters/<adapter>/strings/`; pointers resolve relative to it with CONTAINMENT (reject `..`/absolute escaping the dir). UPDATE-SAFETY: a LOCAL profile's file-pointers resolve relative to the user-owned local-profile dir (NOT adapter-shipped strings/, which adapter updates overwrite), or the local profile inlines. Validate-at-register (fail-fast on a bad/escaping/missing pointer) + LAZY read at get-string (live file edits reflect, no re-register) + skip-diagnostics on missing-at-read (no hard-crash, mirrors [digest]). Rides the same leaf-replace profile overlay as the rest of [strings].",
    "docA": "adapter strings** (ratified 2026-06-11, Gateway grill): A `[strings]` manifest section \u2014 an adapter-authored JSON/TOML KV tree, dot-path-readable by anything on the node via `spt adapter get-string <adapter-option> <key.path>` (e.g. a harness hook fetching per-profile `additionalContext` \u2014 one hook script serves every profile, only the data differs). Resolution rides the **same leaf-replace profile overlay** as the rest of the manifest: a shipped or local profile may override base strings; `get-string` returns the merged view for the named adapter option. **Strings are data only** \u2014 nothing in",
    "idB": "REQ-PROJECT-INDEX-READER-CUTOVER",
    "titleB": "PROJECT-INDEX W3 (ADR-0037): endpoint list, the picker, and endpoint-info consume the materialized index \u2014 NO git work in any user-facing read path (the O(PxB+C) fanout at cli.rs ~2954 / picker/data.rs ~456-518 dies). BEHAVIORAL PARITY is binding: precedence session-cwd -> origin-cwd -> context-recency and rendered project IDs/display names unchanged (parity suite vs the old derivation on a fixture); bare/partial run shares the indexed projection; fully-qualified --adapter+--id direct run stays picker-free; the direct-run 25s broker-session gate stays separately tested/observable. Degradation legs (git unavailable, branch malformed/locked, cwd deleted) keep fast reads. Manual latency acceptance on the 13-perch/7-branch fixture (~30s -> sub-second) + hertz field-verify on HFENDULEAM \u2014 NOT a CI wall-clock gate. Gate: impl \u2014 reader cutover; unit \u2014 parity + degradation; int \u2014 list/picker against a daemon-maintained index incl. counters proving zero reader git spawns; doc \u2014 reference regen + CONTEXT avoid-list. Kin REQ-WHOAMI-IDENTITY-ONLY, REQ-PROJECT-INDEX-STORE/WRITER/INVALIDATION.",
    "docB": "project index** \u2014 a node's endpoint\u2192project attribution is DERIVED state, held as a **persistent materialized index** (ADR-0037): `spt-store` owns the versioned format + read path (daemon-offline reads = last persisted snapshot); the **daemon is the sole single-flight writer** (load-at-start, ready-without-warm, background batched reconcile, atomic replace, coalesced event-driven invalidation keyed on branch-tip fingerprints, last-known-good on failure). Readers \u2014 list, picker, endpoint-info, hooks \u2014 join index \u00d7 perch roster and **never run git**; stale renders last-known or `-`, never a stal"
  },
  {
    "pair_num": 85,
    "idA": "REQ-CARRIER-CLAIM-EXCLUSIVE",
    "titleA": "MSG-IDENTITY W4 / F-033 + operator self-send probe 2026-07-09 (dup-delivery cluster, RCA-FIRST): a spooled message row is delivered by EXACTLY ONE carrier \u2014 the first carrier to take a row (hook-poll drain, relay idle-inject, relay-backlog, psyche) atomically CLAIMS it so no other carrier can re-deliver the same row. FIELD EVIDENCE (authoritative, operator-observed): a default-window doyle-to-doyle send while doyle was BUSY delivered on BOTH the busy POLL path AND the idle RELAY path; spool row 156: window='default', delivered=1, taken_leg='idle-inject' \u2014 the relay claimed a row a poll also surfaced (REQ-SPOOL-TAKE-AUDIT instrument, already shipped, is the RCA tool: taken_leg/taken_sid/taken_at per row). PRIOR: F-033 (perri 2026-07-08) \u2014 the psyche-download filing arrived as TWO copies, dup-delivery live-confirmed. RCA-FIRST (report-before-fix): pin whether the poll drains before/after the relay's delivered=1 mark; whether the busy-to-idle edge re-offers a row a poll already took; whether take-marking is atomic per carrier or check-then-mark racy. DISTINCT from F-035 (that = active_only window honor, spt-core exonerated; THIS = a default msg on both carriers \u2014 F-035's lock never asserted a default msg can't ride both). Gate: int \u2014 a default send to a BUSY live agent that polls mid-turn AND transitions idle delivers EXACTLY ONCE (spool-audit shows one taken_leg, recipient sees one copy); unit \u2014 concurrent take attempts on one row yield one winner. Kin REQ-SPOOL-TAKE-AUDIT, REQ-RELAY-NO-BUSY-DELIVER, REQ-IDLE-PARKED-DELIVERY, [[spt-core-findings-backlog]].",
    "docA": "",
    "idB": "REQ-HAZARD-ENV-SUBST",
    "titleB": "`spt endpoint run` HONORS manifest [env.<VAR>] direction=inject values (with {key} substitution) on the spt-hosted spawn. Today only the [session.self] command ARGV is {id}-substituted; the [env] inject value is NEITHER substituted NOR applied \u2014 manifest.schema.json promises EnvVar.value = 'Value to inject (with substitution)' but prepare_harness_spawn fills only argv and SpawnReq carries no env, so a [env.SPT_ENDPOINT_ID].value='{id}' arrives EMPTY. A FLAGLESS harness (bare `claude`, no argv slot for {id}) then routes the id via [env] \u2192 empty \u2192 SessionStart sees empty $SPT_ENDPOINT_ID \u2192 seeds-by-PPID instead of binding \u2192 ZERO perch \u2192 NO_PERCH (the actual wall-b bind blocker; perri hard-repro'd). SILENT failure (empty inject, no error). FIX (doyle ruled a): fill every [env] inject value from the SAME {key} catalog as argv/role (mirror F-009 TEMPLATE fill, whole-string fill_template for an env value), thread it through SpawnReq.env \u2192 the broker sets it on the spawned PTY child. Correctness fix \u2014 schema already promises it, NO manifest change, NO new binary. PAIRS with REQ-SEND-SPT-HOSTED to make endpoint run fully work. doyle F-013. (post-v0.10.0)",
    "docB": ""
  },
  {
    "pair_num": 86,
    "idA": "REQ-HAZARD-IDLE-SILENT-NONDELIVERY",
    "titleA": "An idle delivery to a session whose translation binary is in a FAILED STATE \u2014 absent (none declared), spawn-failed, FAULTED, or its inject-worker channel gone \u2014 must SPOOL (delivered=false), never raw-inject a pseudo-delivery reported as delivered. The GUARANTEE is the STEADY STATE (the failed-binary state), not every in-flight message (see the fault-transient carve-out below). ROOT (F-019 post-mortem, ADR-0022 amendment): the v0.11.0 path raw-injected `payload+\\r` into the PTY whenever no working translation binary handled an inbound message (none declared, spawn-failed, FAULTED, or its inject-worker channel gone) AND acked `delivered=true` \u2014 but a bare `payload+\\r` does NOT submit on a modern TUI (Claude Code), so the message was TYPED but never sent: a silent pseudo-delivery reported as success. That silent degrade-to-raw-inject is exactly what MASKED F-019 through a multi-hour black-box hunt. FIX (operator-ruled, doyle-scoped): idle delivery is translation-binary-ONLY \u2014 `dispatch_endpoint_input` with no working binary replies `endpoint_injected_envelope(ep, delivered=false)` (the caller `try_broker_inject`\u2192`cmd_send` then falls through to `deliver::send` = SPOOL, poll-fed, never lost) and writes NOTHING to the PTY, LOUDLY (eprintln names the absent/faulted/worker-gone cause). A failed binary becomes a VISIBLE no-delivery (spooled + honest QUEUED report), never a confident-but-false 'Sent'. The raw-inject fallback (`input.enqueue`) is REMOVED from the no-binary, worker-dropped, AND post-fault paths. OUT OF SCOPE (doyle ruling, follow-up note only): broker-side auto-redrive of already-spooled inbound the instant a live-update binary spawns (ordering/exactly-once hazards; the poll substrate + subsequent sends cover re-delivery). NOT COVERED \u2014 the FAULT-TRANSIENT (the STATE-vs-transient precision): a delivery landing in the worker's commit window \u2014 BEFORE `event_rx` is dropped / `faulted` is set \u2014 can be optimistically enqueue-acked (`delivered=true` the instant `event_tx.send` succeeds) then DROPPED when the worker faults+returns. That is a SEPARATE, PRE-EXISTING hazard: raw-inject removal did not touch it (the old code dropped that queued event too) \u2014 v0.14.3 is a strict improvement that makes nothing worse. It is tracked for v0.15.0 under REQ-MSG-DELIVERY-AXES (the spool-centric delivery redesign: ack-on-SPOOL replaces ack-on-enqueue, which closes the optimistic-ack drop naturally). v0.14.3 guarantees only the steady FAILED state \u2192 spool (faulted is MONOTONIC \u2014 set once, never respawns \u2014 so it converges deterministically; the g2 gate asserts the steady state via bounded-retry-until-spool, not a single-shot ack). EPHEMERAL CARVE-OUT (v0.15.0 W3, ADR-0028): `--ephemeral` is the SOLE sender-opted-in exception \u2014 an ephemeral message MAY drop silently if it cannot deliver in its accepted window (at window-open with no live carrier, or at TTL). Every NON-ephemeral path still spools + reports `delivered=false` (the guarantee is unchanged for the default durable path). v0.15.0 realizes the ephemeral drop for the spt-hosted-binary no-carrier-at-window leg + TTL; the harness-relay no-live-listener leg is a documented partial (CONTEXT.md \u00a7persistence). KNOWN-HAZARDS class (rule 4). (v0.14.3; ephemeral carve-out v0.15.0)",
    "docA": "7.22 An idle delivery with no working translation binary must SPOOL, never raw-inject a pseudo-delivery reported as delivered `[REQ-HAZARD-IDLE-SILENT-NONDELIVERY]` Failure (F-019 post-mortem):** the v0.11.0 path raw-injected `payload+\\r` into the spt-hosted PTY whenever no working translation binary handled an inbound message \u2014 none declared, spawn-failed, FAULTED (commit-deadline miss / binary death), or its inject-worker channel dropped \u2014 AND acked `delivered=true`. But a bare `payload+\\r` does NOT submit on a modern TUI (Claude Code): the message was TYPED into the harness but never sent, ",
    "idB": "REQ-SEND-SPT-HOSTED",
    "titleB": "An inbound `spt send` is DELIVERED to an spt-hosted endpoint (brought up via `spt endpoint run` \u2192 `api bind`, broker holds its PTY, NO `api listen` relay). Today cmd_bind\u2192establish_perch (api/startup.rs ~441) writes info.json + ready marker + controllable=Some(true) but registers NO message-listener / NO address, so deliver.rs resolve_address\u2192None\u2192spool (deliver.rs:132-140) and the message NEVER reaches the live PTY \u2014 the endpoint reads 'online' (ready marker) yet `spt send` silently SPOOLS ('online but not deliverable' lie). Per CONTEXT:187-188 the daemon owns the PTY and delivers, manifest-configurable per activity-state (direct PTY injection / relay / HTTP). FIX: route an inbound send for an spt-hosted target through the daemon \u2192 broker InputReq \u2192 session.write_input PTY-inject (broker.rs dispatch_input/write_input ~988-1022), the same path the brain uses; the live-delivery handshake must report Sent (not Queued) and stop the spool-only fallback for a broker-hosted, PTY-resident endpoint. Detection is local: controllable==Some(true) + spt-hosted state + resolve_address==None. = the spt-core HALF of the wall-b finding (perri owns the adapter half: bind-hook fired-zero-perch + the missing endpoint-run int test). (post-v0.10.0)",
    "docB": ""
  },
  {
    "pair_num": 87,
    "idA": "REQ-SEAM-INJECT",
    "titleA": "inject-input methods configurable per activity-state",
    "docA": "",
    "idB": "REQ-SEND-WINDOW-DRAIN-HONOR",
    "titleB": "F-035 (field finding 2026-07-09): active_only = POLL-ONLY for a relay-bearing live agent -- it must NEVER be RELAY-delivered (its contract is 'active hook window only, never wakes' per spool.rs WINDOW_ACTIVE_ONLY doc + cli.rs:85; `spt send --active-only` / the hidden `--deferred` alias and `send_deferred` shell-context mint it). FIELD SYMPTOM: lia (a full live agent -- relay-for-idle, poll-for-busy) surfaced an --active-only msg on her IDLE RELAY. RCA JOURNEY: v1 RCA (docs/F-035-RCA.md) analyzed the WRONG class (spt-hosted-relay-LESS, the idle-edge inject leg) and proposed a COLLAPSE that would have broken the shipped F-023 anti-starvation gate (docs/F-035-CONFLICT.md); operator reclassified to a relay-bearing live agent; the relay-class re-RCA (docs/F-035-RELAY-RCA.md) traced EVERY active_only->relay carrier and found them ALL ALREADY GUARDED on main@2c05dc9 -- so spt-core has NO code bug. doyle FINAL RULING: the real leak is the ADAPTER's busy->idle poll->idle-representation handoff (spt-claude-code -- a legitimate `api poll` on going idle drains active_only, then the adapter renders it into the idle/relay surface), OUTSIDE spt-core; perri's lane. spt-core DELIVERABLE = a REGRESSION GUARD (tests only, NO behavior change) locking the 3 load-bearing guards that keep active_only off a relay: (1) send_windowed:217 -- an active_only send SKIPS deliver_tcp (never rides a live relay's TCP channel), spools poll-only; (2) cli.rs:5762 -- a cross-node active_only send stays LOCAL-ONLY (the WanMessage wire record has no window field, so shipping it would strip the class and relay-deliver at the far node); (3) relay.rs drain_backlog -> drain_non_deferred (deferred=0) -- the relay backlog NEVER forwards an active_only (deferred=1) row. Guard suite: unit (send_windowed active_only-skips-tcp-to-live-relay + relay_backlog-never-drains-active_only) + int (cross-node active_only stays local-only, never WAN, while a default send to the same remote target DOES take the WAN leg). AMENDED 2026-07-26 (FIELD-TRUTH W1 roll-in, operator ruling): guard (4) added \u2014 the idle-edge and parked-re-offer inject claims NEVER take a deferred (active_only) row, on ANY endpoint class. The collapse proposed in docs/F-035-RCA.md v1 and rejected in docs/F-035-CONFLICT.md is now ADOPTED on new field evidence (doyle spool-audit: spt-shells active_only rows taken_leg=idle-inject on a live perch, each starting a turn \u2014 the exact 'never wakes' violation this REQ exists to forbid); the F-023 deferred-rescue it collided with is revoked by the REQ-MSG-IDLE-EDGE-DRAIN amendment. Kin REQ-MSG-DELIVERY-AXES + REQ-MSG-IDLE-EDGE-DRAIN + REQ-INST-6.",
    "docB": "F-035 RCA \u2014 report-before-fix. F-035 RCA \u2014 idle-edge parked-drain ignores the delivery-window tag"
  },
  {
    "pair_num": 88,
    "idA": "REQ-EP-3",
    "titleA": "Messaging payloads carry typed operation commands + file blobs",
    "docA": "",
    "idB": "REQ-SEND-STAMP-AGENT-ID",
    "titleB": "MSG-IDENTITY W4 / endpoint-identity (operator-flagged 2026-07-09 + live-confirmed on flynn's F-038 ask arriving 'cli@HFENDULEAM'): a live agent's own CLI `spt send <target>` MUST stamp from_id with the AGENT id (e.g. 'doyle'), not the node fallback 'cli@<node>' \u2014 today the agent-id stamp rides ONLY the adapter/perch shortform path, so any agent shelling out `spt send` (the DOCUMENTED reach-another-agent form) presents to recipients as an anonymous node CLI: replies mis-route (recipients answer cli@node \u2014 no perch \u2014 instead of the sender), and the F-036 victim-effect ('sends downgraded to from:cli@node') is indistinguishable from normal CLI traffic. FIX: send-time self-resolve \u2014 when the calling process/session maps to a bound live perch on this node (the session-pin/seed machinery already resolves this for bind), stamp that endpoint id as from_id; a genuinely perchless CLI keeps 'cli@<node>'. Gate: unit \u2014 a send from a session bound to a live perch stamps the endpoint id; a perchless shell keeps the node stamp; int \u2014 recipient's EVENT from= carries the agent id for a shelled-out send from a live session. Kin F-036 victim effects, EVENT envelope (ADR-0020), [[owl-send-not-legacy-spt-send]] (adapter-path stamp works today \u2014 this closes the CLI-path gap).",
    "docB": ""
  },
  {
    "pair_num": 89,
    "idA": "REQ-MSG-ENVELOPE",
    "titleA": "The <EVENT type=\"msg\" from=\u2026>body</EVENT> envelope (spt-proto::event, the ADR-0001 grammar) is the SOLE canonical arriving-message format at EVERY harness arriving-message surface on an AGENT perch \u2014 api listen AND api poll/worker-poll, byte-identical (reverses REQ-MSG-4's 'hook drains keep the raw frame by contract'). SCOPE CARVE-OUT: the shell-command relay (api poll <shell-id> --link, cmd_poll_shell) is a distinct internal transport carrying RAW MAC'd stamped frames the shell child consumes verbatim \u2014 NOT an arriving-message surface, deliberately EXEMPT from <EVENT> composition (notify_shell_e2e guards this boundary). __REPLY_TO__ \u2014 mis-elevated during the clean-room port to a fake ADR-0001 'stable wire format' (spt-msg/wire.rs, lib.rs) \u2014 is REMOVED entirely (spool format_row, the spt-msg TCP frame, emit parse_frame); (from, body) carried structurally, <EVENT> composed once at the delivery boundary. No legacy sister-interop (spt-core never required it). Reply-correlation rebinds onto the structural from / <EVENT from=\u2026> attribute (ADR-0009 access-gate + ADR-0012 Psyche/spt-live reply-target). Self-delimiting by construction \u2192 finding F-002 (non-self-delimiting multi-message poll) dissolves. ADR-0020.",
    "docA": "Decision",
    "idB": "REQ-SHELL-4",
    "titleB": "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).",
    "docB": "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"
  },
  {
    "pair_num": 90,
    "idA": "REQ-ER-BRINGUP-TOTP-GATE",
    "titleA": "Bringing the engine room online \u2014 and attaching a controller to it \u2014 requires a same-node CLI call PLUS a member-or-admin TOTP for its home subnet, and never OS elevation (ADR-0052 decision 2). The gate proves 'a human holding this subnet's material is at the controls', which is the question that matters for a surface that sets access posture; elevation proves only 'a process on this machine ran elevated', which every agent-spawned installer path can arrange and which says nothing about subnet authority. Either seed passes because an admin key IS a membership key (ADR-0051), and the two-acceptable-secrets budget is answered by REQ-ER-BRINGUP-ATTEMPT-BOUND rather than by refusing the admin key. Bring-up FAILS CLOSED when the bound harness adapter is missing \u2014 an engine room that cannot host its own mind must not come online half-formed (ADR-0053 spirit). Gate: doc \u2014 ADR-0052 decision 2 and the CONTEXT.md engine-room bring-up sentence; impl \u2014 the same-node CLI bring-up path, local verification of the member and admin TOTP against the replicated seeds, and the missing-adapter refusal; unit \u2014 a member code brings it up, an admin code brings it up, a wrong code refuses, elevation alone never substitutes, and a missing bound adapter refuses.",
    "docA": "Bring-online + controller-attach requires a **same-node CLI call plus a member-or-admin TOTP** for the engine-room's home subnet. The gate proves a human holding subnet material is at the controls; agents cannot pass it.",
    "idB": "REQ-HAZARD-CEREMONY-CLOCK-STEP",
    "titleB": "W1/D3 (JOIN-TRUTH, KNOWN-HAZARDS): the cached ceremony offset goes stale-WRONG when the OS clock STEPS under a live daemon \u2014 an offset measured against the OLD clock keeps applying for up to the 15-min TTL. Field-proven timeline (enlyzeam): refresh cadence 15:08/15:23/15:38/15:53; operator `w32tm /resync` stepped the clock \u2212210s at 15:45:46; every `subnet join` returned NO_SEED_HOLDER until a daemon bounce forced a fresh query. Fix: the cache snapshot stores an (Instant, SystemTime) PAIR; on read, if |wall-elapsed \u2212 mono-elapsed| > ~2s the clock stepped \u21d2 force an immediate refresh (offset recomputed against the new clock). PLUS: `meet_seed_holder` (pairhost.rs), on search-deadline exhaustion, forces ONE fresh NTP refresh + one final sweep before returning NO_SEED_HOLDER \u2014 so a stepped-clock join self-heals without a bounce. Clock reads must be seam-injectable for the hazard unit (inject the (mono,wall) pair \u2014 do NOT sleep 15 min).",
    "docB": ""
  },
  {
    "pair_num": 91,
    "idA": "REQ-JOIN-DEFERRED-ELEVATION",
    "titleA": "W2 (JOIN-TRUTH, operator UX ruling verbatim): 'if --code was not supplied, don't spawn the elevated subnet-join window until a target machine is discovered \u2192 just in time for the code prompt.' When --code is ABSENT and the process is UNELEVATED, run the name prompt + ALREADY_MEMBER check + ensure_daemon + the MEET phase (brain.pair_meet) UNELEVATED; only on MetMember spawn the elevated window via the EXISTING try_auto_elevate machinery (the elevated re-run re-executes the join flow \u2014 its second meet is cheap, the member is proven present). A FAILED search must NEVER show a UAC/sudo/pkexec prompt. The --code path is UNCHANGED (gate-first, one-shot). The unelevated phase performs ZERO trust mutation \u2014 meet is pre-trust per REQ-JOIN-TWO-PHASE/ADR-0030. RULED OUT (doyle): cross-elevation hold-session adoption (passing the daemon-held pair session_id into the elevated process) \u2014 a new security seam we don't need; the elevated re-run re-meets instead. The elevation gate MOVES from command-entry to the enrollment boundary; discovery is read-only pre-trust.",
    "docA": "Deferred-elevation amendment (2026-07-06 \u2014 JOIN-TRUTH W2)",
    "idB": "REQ-PAIR-8",
    "titleB": "NTP TOTP offset: the pairing ceremony queries NTP at ceremony time (both sides) and applies the derived offset to the TOTP calculation in-process only; system-clock fallback when NTP is unreachable (offline LAN pairing unaffected \u2014 NTP failure never blocks a pairing that succeeds today); never sets the OS clock; no background sync loop (M8 decision 18; field trigger: enlyzeam clock >1 min off exceeds the \u00b11 window)",
    "docB": ""
  },
  {
    "pair_num": 92,
    "idA": "REQ-PAIR-1",
    "titleA": "TOTP-seeded SPAKE2 pairing",
    "docA": "",
    "idB": "REQ-SUBNET-2",
    "titleB": "Guided join e2e: spt subnet join CLI initiator + always-on daemon pairing responder",
    "docB": "Product-surface amendment (2026-06-05 \u2014 M7 D3)"
  },
  {
    "pair_num": 93,
    "idA": "REQ-ENDPOINT-LIST-PALETTE",
    "titleA": "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.",
    "docA": "",
    "idB": "REQ-PICKER-FORK-LABEL-CWD",
    "titleB": "B-3 (F029, operator): the confirm-panel `Fork endpoint` option label is static and says nothing about WHERE the fork lands. A fork runs in the picker's launch cwd (run_cwd); the label must state that dir honestly: `Fork endpoint here --> <current dir>`. Anchor picker/view.rs confirm_option_label (was `fn(opt)->&'static str`). Make the label model-aware for the dir-relative options. See triage B-3.",
    "docB": ""
  },
  {
    "pair_num": 94,
    "idA": "REQ-HAZARD-BIND-CWD-UNSET",
    "titleA": "A bound endpoint's `info.cwd` is SET at bind so a freshly-created perch appears under its own project tab. ROOT (found, v0.13.0): `info.cwd` is NEVER set on bind \u2014 `cmd_bind` (spt-hosted) and `bind_from_seed` (harness-hosted) never thread cwd into `establish_perch`/`rec.cwd`. FIX: `cmd_bind` reads its own `current_dir` (the broker spawned it in `project_cwd`); `bind_from_seed` passes `seed.cwd` (already captured at seed time, currently DISCARDED). DISTINCT from REQ-PICKER-HISTORY-FRESH (v0.12.1) \u2014 that unioned cwd-origin into picker MEMBERSHIP but tested merge_origin_project with a PROVIDED origin; it never asserted `info.cwd` is actually set on bind, so a real `endpoint run` perch still had an empty cwd and the union had nothing to union. This is the v0.12.1 P1 'appears under its own project right away' claim that was REFUTED in the changelog \u2014 delivered for real here. (v0.13.0)",
    "docA": "",
    "idB": "REQ-PICKER-PROJECT-HISTORY-TRUTH",
    "titleB": "#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.",
    "docB": ""
  },
  {
    "pair_num": 95,
    "idA": "REQ-PICKER-4",
    "titleA": "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)",
    "docA": "",
    "idB": "REQ-PICKER-CHOOSE-DEDUP-ALL",
    "titleB": "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.",
    "docB": ""
  },
  {
    "pair_num": 96,
    "idA": "REQ-PICKER-CHANGE-ADAPTER-FLOW",
    "titleA": "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.",
    "docA": "",
    "idB": "REQ-PICKER-OFFLINE-NO-VIEW",
    "titleB": "A3 (F028, operator #3): 'View now (read-only)' is offered for OFFLINE endpoints. model.rs:1030 offline branch of confirm_options is vec![Start, View] \u2014 View is meaningless with no live PTY. FIX: offline set = [Start] (+ the existing Resume/ChangeAdapter/Instantiate/Fork/Shortcut tail). Update the view.rs options tests. See triage A3.",
    "docB": ""
  },
  {
    "pair_num": 97,
    "idA": "REQ-PICKER-KEY-GATE-LAUNCH-CAPABLE",
    "titleA": "B-1 (F029, operator): the `h` (headless start) / `s` (shortcut) keybinds fire from broad picker contexts (mod.rs handle_confirm_key / ChooseProject / Resume) regardless of whether the highlighted row would LAUNCH the endpoint. Restrict both to launch-capable highlights: (a) `Start now` in the immediate-start case (should_offer_project_choice == false), (b) a Choose-project row, (c) a Resume-from-history row. The footer hint line must render `h`/`s` ONLY when actually live (hint truth = availability truth). FIX: gate the key handlers on (screen, highlighted-option), unit the gate as a pure matrix. See triage B-1.",
    "docA": "",
    "idB": "REQ-PICKER-REMOTE-WAKE",
    "titleB": "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).",
    "docB": ""
  },
  {
    "pair_num": 98,
    "idA": "REQ-PICKER-REMOTE-WAKE",
    "titleA": "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).",
    "docA": "",
    "idB": "REQ-PICKER-UX-V013",
    "titleB": "`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)",
    "docB": ""
  },
  {
    "pair_num": 99,
    "idA": "REQ-CI-POSTJOB-DAEMON-REAP",
    "titleA": "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).",
    "docA": "",
    "idB": "REQ-HAZARD-DEAD-REC-PID",
    "titleB": "E-1 rider (F-026 #11 dead-pid class, doyle-ruled SCOPED 2026-07-05 \u2014 KNOWN-HAZARDS 7.34): a dead `rec.pid` on an spt-hosted perch is EXPECTED, not staleness \u2014 the recorded pid is the ephemeral bind-CLI pid, which dies immediately after bind (the broker holds the PTY; no resident harness process at that pid). NO reader may alive-gate on `rec.pid` alone: spt-hosted LIVENESS comes from the daemon-managed status field (KH 2.5 \u2014 status present \u21d2 authoritative, never a per-pid probe); IDENTITY comes from session/ancestry resolution where `rec.parent_pid` (the harness pid, the stable session-binding anchor) is the ancestry candidate (REQ-SELF-DETECT-PARENT-PID). Re-stamping rec.pid with the harness pid (shape (a)) is OVERRULED: ADR-0021 demoted pid to a bind-time seed hint (re-anchoring truth there reverses the design); every pre-existing record keeps the old CLI pid so readers need the scoped discipline anyway (migration hole); blast radius (every rec.pid consumer + KH 2.5 external-perch probe semantics) buys nothing the reader-side fix doesn't. CLASS rule: any newly sighted rec.pid-alive-gating reader gets the same scoped fix and EXTENDS this requirement's evidence \u2014 no new REQ per reader.",
    "docB": "7.34 A dead `rec.pid` on an spt-hosted perch is EXPECTED \u2014 no reader may alive-gate on `rec.pid` alone `[REQ-HAZARD-DEAD-REC-PID]` Failure (paid-for, field evidence \u2014 the F-026 #11 dead-pid class):** an spt-hosted endpoint's `rec.pid` records the ephemeral bind-CLI pid, which dies IMMEDIATELY after bind (the broker holds the PTY; there is no resident harness process at that pid). Readers that alive-gated on `rec.pid` alone treated the perch as stale: self-detect leg (c) could NEVER resolve an spt-hosted sender (its from-stamp degraded to `cli@NODE`, operator #7, and replies bounced `NO_PERCH` "
  },
  {
    "pair_num": 100,
    "idA": "REQ-LISTEN-SESSION-ID-FALLBACK",
    "titleA": "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.",
    "docA": "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.",
    "idB": "REQ-RESUME-CUSTODY-IDENTITY",
    "titleB": "Resume custody is an identity pair (pid + process creation time), never a bare PID. (ADR-0047 decision 1; hertz v0.39.4 field bug 1, RCA accepted 2026-07-22.) TODAY: livehost's restart gate and liveness-reconcile DEFER both consume `read_resume_pid(..).is_some_and(is_process_alive)` \u2014 zero identity binding, so a dead wake-resume spawn's pid recycled onto an unrelated process (field: resume.pid=29456 -> cmd.exe) reads as a live resume forever: reconcile defers every tick, the row stays online-authoritative, FALSE-ONLINE with no self-repair. FIX: the custody record stores (pid, creation_time) written atomically at spawn-mint; every consumer tests the PAIR; mismatch = NOT OURS -> the discovering reader DELETES the record and proceeds (self-heal, not error); successful bind and spawn-reap clear custody atomically with their own outcome. Creation time from the process SNAPSHOT, never a retained handle (KH 7.50); platform without a snapshot -> unproven -> defer one tick, never a manufactured verdict. Gate: impl \u2014 the paired custody record + both livehost consumers on the pair test; unit \u2014 pair mismatch reads NOT-OURS + record deleted, pair match reads OURS, absent-snapshot defers; int \u2014 the recycled-pid rig: mint custody, kill the spawn, occupy the pid with an impostor, prove reconcile deletes custody and the row goes honest (the bug-1 field shape, RED-first against the bare-PID gate).",
    "docB": "1. Process custody is an identity, never a bare PID"
  },
  {
    "pair_num": 101,
    "idA": "REQ-BIND-HONEST-SELF-STAMP",
    "titleA": "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).",
    "docA": "",
    "idB": "REQ-PUBLIC-ERROR-SURFACES",
    "titleB": "F-1 (REMOTE-TRUTH triage \u00a7F-1, Q4 UX rule, operator-ruled): CLI stderr a non-developer can hit names the OBSERVABLE SITUATION + the NEXT ACTION \u2014 never journal/op/brain/store lingo. The sweep's named offenders: (1) `RC_FAIL:{id}: \u2026 brain IPC read deadline elapsed` \u2014 the brain transport error surfaced RAW through rc's residual Err arm (rc.rs run_attach_inner); operators read 'brain IPC' where the situation is 'the daemon didn't answer in time'. (2) `WOKE_FAIL:{id}: info.json absent or unreadable \u2014 not a hosted perch` (resting.rs apply_event miss) \u2014 store-file lingo in the one rest-verb line a stale remote row still surfaces cross-node (the qualified-arm D6 case; the A-3 bare-id local path already routes instead). The miss stays SINGLE-SOURCED from NOT_A_HOSTED_PERCH_MARKER (in-process discriminant, resting.rs \u2014 reword is compat-safe per its own doc; the drift-pin unit keeps builder+matcher fused). (3) translation_fault never human-rendered (F-030 post-release seed): a broker-stamped input-translation fault (e.g. 'inject worker panicked') was invisible in `endpoint list`/`whoami` while keystrokes silently degraded \u2014 rendered now as a SELF-pin annotation exactly like the psyche_host_error pattern (REQ-HAZARD-LIVEHOST-BOOT-RACE), human line + additive skip-if-none JSON field. Kin to banked patterns: public --help no internal codes; 'Updated' not 'trial'. The A-4b retry terminal + B-3 give-up line + A-3 routing strings shipped F-1-clean already \u2014 this REQ sweeps the stragglers and is the home for future sightings (extend, don't multiply).",
    "docB": ""
  },
  {
    "pair_num": 102,
    "idA": "REQ-CI-WINDOWS-PHASE-A-BOUND",
    "titleA": "Phase-A nextest parallelism is BOUNDED on the Windows self-hosted leg, so the full-parallel 1881-test battery stops saturating a box that also hosts the live agent fleet. (Load-flake family leg 2 = deployah option (c), doyle-ratified 2026-07-22.) Windows leg ONLY: kitsubito does not host the fleet, and its parallelism is deliberately untouched. MECHANISM AND WHY THIS ONE: the bound lives in a [profile.ci-windows] nextest profile selected by a STEP-LEVEL NEXTEST_PROFILE expression on the Phase-A step, NOT by a per-OS pair of Phase-A steps \u2014 duplicating that step would put a FOURTH copy of the <HEAVY> filter string in the tree, and .config/nextest.toml's own header names keeping THREE in sync as the standing hazard. THE NON-INHERITANCE GATE-CHECK (doyle demanded it discharged, and it did not come back clean): a custom nextest profile inherits scalar settings from default but NOT profile.default.overrides \u2014 the argument cuts both ways. Of the five overrides on default, exactly ONE is kind(test): the by-binary <HEAVY> integration filter, which is Phase B's alone and needs no mirror. The other FOUR are the FLAKE-LEDGER #14 class \u2014 real-broker units that are kind(bin)/kind(lib), escape the by-binary filter, and therefore RUN IN PHASE A depending on the heavy-broker-pty group to stay serialized (rc::tests four names, (applyhost|livehost|pump)::tests, wansend::tests, api::startup::tests). Shipping the profile without them would have SILENTLY UN-SERIALIZED ON WINDOWS the exact class whose un-serialization produced two separate 240s timeouts. They are mirrored verbatim, and the parity is ENFORCED rather than remembered: xtask check's check_phase_a_profile_parity fails the build when a default override that is not kind(test) is missing from ci-windows, keyed on that SHAPE and not on a name list \u2014 the same posture as FLAKE-LEDGER #14's second half, whose whole lesson is that a class written in prose and shipped as an enumeration decays. A DRIFTED mirror reads as missing, not as present: the invariant is that it is the SAME filter in both profiles, not that something is in both. THE BOUND ITSELF IS CHOSEN, NOT DERIVED, and is recorded that way at the value: hfenduleam is 16 logical cores and the bound is 8 (half), for fleet-co-tenant headroom. No measurement separates 8 from 6 or 12 and none was run \u2014 the ratified fix is stop saturating the box, not find the saturation knee. Retuning is one integer. Gate: impl \u2014 the ci-windows profile with its mirrored overrides plus the ci.yml step-level profile selection plus the xtask parity check; unit \u2014 the parity predicate's exemption rule (a kind(test) override needs no mirror), its detection of an unmirrored Phase-A override, its rejection of a DRIFTED mirror, and a case over the REAL checked-in config so the check cannot ship green against a tree that already violates it. Kin REQ-CI-POSTJOB-DAEMON-REAP (the other cause-side leg), REQ-HEAVY-UNIT-CLASSIFICATION (the check one layer in), FLAKE-LEDGER #14/#15.",
    "docA": "",
    "idB": "REQ-PSYCHE-SPAWN-ENV-PARITY",
    "titleB": "P-2 (WORKER-TRUTH triage addendum, perri-filed field finding 2026-07-06): the per-event psyche_resume spawn threads the perch record's CAPTURED read_env stamps into the spawn ENVIRONMENT \u2014 the F-027 Half-B env-parity contract (BINDING, design-frozen: read_env captured at creation + stamped on the record + threaded IDENTICALLY to every session spawn; the spawn never reads its own process env for a stamped var) extended to the psyche role the design predates. Field driver: flynn (claude-spt:ccs) \u2014 the ccs wrapper relocates the account root via CLAUDE_CONFIG_DIR at PARENT launch and the perch record correctly captured it, but the daemon spawns psyche_resume with bare env \u2192 default ~/.claude root \u2192 headless 'Not logged in' exit-1 \u2192 strike loop; psyche + parent land in DIFFERENT account roots (auth AND root-scoped continuity both break). Core stays harness-agnostic (threads whatever [env] direction=read captured \u2014 knows nothing of CLAUDE_CONFIG_DIR). Scope note: this is the URGENT psyche leg of F-027 Half B; the full pre_spawn seam + endpoint-session env threading stays design-parked (F-027-ENDPOINT-SPAWN-FAIL-DESIGN.md) unless operator pulls it forward.",
    "docB": ""
  },
  {
    "pair_num": 103,
    "idA": "REQ-DIGEST-GENERATION-SUPERSEDE",
    "titleA": "W3 (LIFECYCLE-TRUTH, digest projection truth \u2014 flynn filing spt-mobile d0aa3f4): a one-shot `endpoint digest --json` snapshot must return each logical activity row ONCE across a checkpoint/resume, not once per seq-generation. ROOT (spt-core-side, not a consumer bug): the K-session span (digest.rs activity_spanned, SPAN_SESSIONS=5) runs the [digest] extractor per session file and tags each row seq=(ledger_ordinal<<32)|localseq (REQ-DIGEST-CURSOR). A checkpoint/resume (self-/clear + Psyche rebuild) makes the harness REPLAY the prior generation's transcript into the NEW session file, so the ancestor's rows appear in BOTH the ancestor file AND the resume file at the SAME localseq \u2014 the span UNIONS them, one logical row surfacing under two full seqs (gen23,local208) + (gen25,local208), identical text/ts/localseq. Consumers dedup by exact seq (the documented authoritative key) so nothing collapses -> duplicate rows in every snapshot / `--after` view (`--follow from:0` is CLEAN \u2014 it reads current-generation only; the SPAN is the sole culprit). The trigger cannot disambiguate: `api boundary clear` records SessionTrigger::Clear for BOTH a fresh /clear (disjoint) and a carry-forward checkpoint (reporting.rs:94) \u2014 so a structural skip-ancestor needs new boundary metadata + adapter cooperation, deferred. FIX (doyle ruling \u2014 flynn Option 1 Supersede, projection-local, source-independent): within the span, collapse cross-generation replay dupes \u2014 an Activity record from an OLDER ordinal is dropped when an identical logical record (role, ts, text, tool) exists under a NEWER ordinal; keep the NEWEST-ordinal occurrence so the surviving seq is the live generation (snapshot + follow agree on seq). Cross-generation ONLY (never dedup within one ordinal \u2014 a session cannot replay itself; identical within-gen rows are real). Supersede runs on the raw span items BEFORE the window fold (project_timeline) so window_turns counts real turns, not phantoms; and a boundary divider adjacent to a now-fully-superseded ancestor is not left orphaned. Context entries (REQ-TERM-7, single digest.log) are not per-session-spanned -> untouched. Int = a two-session span rig where session B's extracted lines are a superset replay of A (same ts/text at same localseq) + B's own new tail: RED-first (pre-fix shows every A row twice); post-fix each logical row appears ONCE under B's generation, B's tail intact, the /clear boundary marker preserved when A retains rows.",
    "docA": "",
    "idB": "REQ-PSYCHE-TURN-STREAM-EVIDENCE",
    "titleB": "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).",
    "docB": ""
  },
  {
    "pair_num": 104,
    "idA": "REQ-HAZARD-BRAIN-RESTART-LIFECYCLE-REHYDRATE",
    "titleA": "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)",
    "docA": "",
    "idB": "REQ-TERM-7",
    "titleB": "Two-origin digest merge: spt-owned context-injection entries (psyche_download | echo_mirror | owl_message) appended by spt to the endpoint `digest.log`, timestamp-interleaved with the adapter's extracted activity records into one ordered timeline, via a distinct context-injection record category. Data model only this milestone; GUI collapse/expand and the echo-reads-digest delta loop are deferred to the surfaces that consume them.",
    "docB": ""
  },
  {
    "pair_num": 105,
    "idA": "REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT",
    "titleA": "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)",
    "docA": "",
    "idB": "REQ-HAZARD-UNHOST-PSYCHE-REAP",
    "titleB": "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)",
    "docB": ""
  },
  {
    "pair_num": 106,
    "idA": "REQ-HAZARD-LIVEHOST-BOOT-RACE",
    "titleA": "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.",
    "docA": "",
    "idB": "REQ-PSYCHE-EPHEMERAL-DRIVER",
    "titleB": "W1 (F030, design \u00a73): each psyche-relevant event runs exactly ONE bounded per-event turn through the existing driver stack (psyche_turn_and_relay for outbound-intent events / resume_psyche for session-custody transitions / run_psyche_turn for pure merges) \u2014 no resident psyche process exists between events. host_one (livehost.rs:518) STOPS spawning spawn_psyche_owned; the pulse loop stays as the daemon-side scheduler (thread + stop-flag + drop-dir watch correct) but a fire now invokes one bounded turn, daemon-driving every substitution key from daemon-known context (child never self-resolves home/subnet/perch \u2014 direction-(a) multi-subnet churn impossible by construction). Turn failures consume a bounded failure budget (C3(b) shape): N consecutive failures \u2192 psyche_host_error stamp + cooldown, reset on success; no respawn storm (nothing resident to respawn). Red-first: fire an event on a hosted live endpoint \u2192 assert one turn ran (SIDE-EFFECT PROOF FILE \u2014 transcript-jsonl asserts are structurally blind, 2026-07-04 rig lesson) and no {id}-psyche process survives the turn.",
    "docB": "Psyche**: The Psyche companion's own perch, distinct from its paired LiveAgent's perch. First-class endpoint type so messages addressed to a LiveAgent's Psyche route directly without ambiguity. **A Psyche is a bounded per-event turn, not a resident process (since v0.25.0).** Each psyche-relevant event (a pulse fire, a commune/signoff drop, a session-custody transition) runs **exactly one** bounded turn through the psyche role template, spawned by the daemon, which exits at turn end \u2014 there is no long-lived psyche loop or psyche pid between events. <!-- --> **Liveness = turns succeed** \u2014 never "
  },
  {
    "pair_num": 107,
    "idA": "REQ-HAZARD-PSYCHE-RESIDENCY-EXPECTATION",
    "titleA": "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).",
    "docA": "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",
    "idB": "REQ-HAZARD-TEMPLATE-ARGV-FILL",
    "titleB": "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.",
    "docB": ""
  },
  {
    "pair_num": 108,
    "idA": "REQ-HAZARD-THRASH-GUARD-BLIND",
    "titleA": "W3 (F030 hazard; paid-for: evidence #6, hall-bf ~12/min re-host NEVER tripped the C3(b) thrash guard \u2014 boot records were not ledger boundaries to the guard): the failure budget must count REAL attempts (ledger-derived: boot/turn records via the psyche perch ledger), not whatever it counted that let 12/min churn run invisibly. Red-first synthetic loop: a 12/min synthetic failure loop MUST trip the budget.",
    "docA": "7.31 The Psyche failure budget must count REAL per-event attempts \u2014 a resident rate-guard is blind to per-event churn `[REQ-HAZARD-THRASH-GUARD-BLIND]` Failure (paid-for, field evidence 2026-07-04):** the pre-F-030 resident-hosting thrash guard keyed on ledger-rate boundaries that were NOT the real re-host attempts. On hall-bf a **~12/min** re-host churn ran completely **invisibly** \u2014 the guard never tripped, never stamped, never cooled down, because the boot records it looked at were not ledger boundaries the guard counted. A runaway failure loop looked healthy. Invariant:** the failure budge",
    "idB": "REQ-PSYCHE-TURN-STREAM-EVIDENCE",
    "titleB": "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).",
    "docB": ""
  },
  {
    "pair_num": 109,
    "idA": "REQ-HEAVY-UNIT-CLASSIFICATION",
    "titleA": "A unit test that stands up a REAL broker inside a lib/bin `#[cfg(test)]` block must sit in the `heavy-broker-pty` nextest group, and the classification must be ENFORCED rather than remembered. FLAKE-LEDGER #14 diagnosed this class in the `spt` binary, wrote the CLASS in prose, then shipped an ENUMERATION of four `rc::tests::` names \u2014 so the identical shape in `spt-daemon`'s lib (`applyhost`: a real `Broker::bind` + `serve()` in 10 of its 13 units) stayed in the full-parallel Phase-A pool and TIMED OUT at 240s twice, at v0.32.0 and again under the v0.39.0 W5 gate, the v0.32.0 remedy never having landed. The defect is provable from `.config/nextest.toml` ALONE (two overrides, neither matching `kind(lib)`); timing evidence only ever estimated the rate. Gate: impl \u2014 heavy-group overrides for `applyhost`/`livehost`/`pump` (spt-daemon lib) and `wansend` (spt bin, found BY the check rather than by a person), plus `xtask check`'s `check_heavy_unit_classification` keyed on the SHAPE (a `Broker::bind` after the `mod tests` marker) instead of a name list; unit \u2014 the two pure seams, including the regression for this check's OWN first draft, which substring-matched `<module>::tests` and so missed every module written inside an alternation group. Kin FLAKE-LEDGER #14/#15, REQ-CI-DOCS-ONLY-THIN.",
    "docA": "",
    "idB": "REQ-PSYCHE-SID-CUSTODY",
    "titleB": "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).",
    "docB": "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}`."
  },
  {
    "pair_num": 110,
    "idA": "REQ-HOSTING-AUTHORITY-CONTROLLABLE",
    "titleA": "RC-RENDER-TRUTH v0.38.1 fast-follow leg 3 (hertz todlando immortal-hybrid trace, doyle fork ruling 2026-07-18 = controllable-authority): ONE hosting authority \u2014 persisted `state` stays the durable endpoint TYPE (REQ-EP-6 open type system; establish_perch's prior-type preserve at startup.rs:346-350 is INTENTIONAL and stays), `controllable==Some(true)` is the source-definitive broker-PTY authority. FIELD ROOT (C1 coverage gap, not new family): an spt-hosted bind over a prior ready_agent perch preserves state=ready_agent while stamping controllable=true + online -> livehost restart_resume_gate (508-534) Skip's state!=live_agent so the orphan never resumes, and reconcile's C1 dead-pid hybrid heal predicate was scoped controllable=false so controllable=true escapes -> immortal dead-PID ready_agent hybrid, latch-driven Active projection (todlando field state; rc's no-session refusal was TRUTHFUL). FIX (hertz refinement, ratified): remove the state rejection from restart_resume_gate; reconcile routes only non-live_agent && controllable!=Some(true) through the PID-model hybrid heal, while controllable==Some(true) rows fall through BROKER-SESSION truth (orphan with ledger/adapter material => Resume; no session + dead pid => terminal offline via the W2 atomic normalize). Psyche hosting stays separately state-gated (live_agent only). endpoint_survival tables BOTH live_agent and ready_agent broker-owned rows -- both resume their PTY at daemon start, ready stays no-Psyche (hertz definitive-trace addendum). restart_resume_gate keys on online+controllable+session/relay/custody belts, no state arg. cmd_bind's online gate reads/verifies the PERSISTED state it just wrote, never only the requested arg (parity with cmd_listen's W2 creator gate). Gate: impl -- gate/reconcile routing + bind online-gate persisted-read; unit -- routing table (non-live+controllable!=true -> PID-model; ready+true -> session-truth; live_agent unchanged); int -- hertz matrix verbatim: (i) ready_agent+controllable=true+online ORPHAN (dead harness, ledger+material) => restart RESUMES; (ii) ready+true+online with dead pid/NO session => reconcile terminally offlines + Active projection removed; (iii) legitimate ready listener (controllable!=true, live pid) => stays messaging-online on the PID model, never treated as PTY-attachable; (iv) spt-hosted bind over prior ready_agent => controllable=true+online+restart-resume works with preserved type; doc -- ADR-0041 amendment note (authority split: type vs hosting).",
    "docA": "Amendment (RC-RENDER-TRUTH v0.38.1 leg 3, doyle ruling 2026-07-19): one hosting authority \u2014 the online-earn authority splits by hosting topology",
    "idB": "REQ-SESSIONS-LOG-ENDPOINT-ATTRIBUTION",
    "titleB": "C2 (F028, infra; ROOT-CAUSED + severity-upgraded doyle RCA 2026-07-03): cross-endpoint perch contamination \u2014 a foreign psyche's SessionStart hook REBINDS a victim perch's IDENTITY, not merely its ledger. Evidence: hall-a's info.json.session_id IS f015b-probe-psyche's session (359d7bd7) + hall-a's ledger holds the foreign psyche session; same class as hall-b's dead-pid stamp (141556). This REQ = the OBSERVABLE (foreign session_id in a perch's ledger/info.json + resume offering foreign sessions) and its belt-braces: (iii) filter owlery-cwd rows OUT of resume_rows; (iv) one-time repair for already-contaminated perches (hall-a on HFENDULEAM) or self-heal on next legitimate session-start. The ROOT (identity pinned at spawn + honest bind + nested self-resolve) is REQ-BIND-HONEST-SELF-STAMP \u2014 C2 is UPSTREAM of B3 (presence/CONTROLLED read the very stamps this corrupts). See triage C2.",
    "docB": ""
  },
  {
    "pair_num": 111,
    "idA": "REQ-NET-3",
    "titleA": "Cross-node Psyche sync over P2P replaces gh-repo-sync",
    "docA": "",
    "idB": "REQ-PSYCHE-NESTED-RESOLUTION",
    "titleB": "W4 (F030, design \u00a73; F-017 sibling, general not psyche-only): nested {parent}/{nested} ids resolve under the PARENT's home \u2014 subnet-less resolution for nested perches (the home-ASSIGNMENT seam, not perch-path which is already subnet-less) \u2014 so child-side verbs acting on nested perches need no --subnet the child cannot know. ROOT: home::assign_home returns Ambiguous on a multi-subnet node w/o --subnet; a nested id must instead derive home from its parent perch. Additionally expose a SINGLE {subnet} base_keys fill WHEN KNOWN (own_subnet = home-subnet label, 'local' when unhomed; absent \u2192 LOUD missing-key fail, the {node} precedent). NO {home} key (doyle W4 Q1: the endpoint home subnet is ONE concept per CONTEXT.md:640, and 'home:' is already the subnet-name qualifier position CONTEXT.md:652 \u2014 a {home} template key invites meaning-drift). Red-first = evidence #3's exact repro: 2-subnet home, nested-id verb, must succeed (was: `spt ready <id> --once` \u2192 exit 1 READY_FAIL \u2026 pass --subnet).",
    "docB": "`{subnet}` \u2014 the endpoint's home-subnet label.** A single `{subnet}` key fills to this endpoint's home-subnet label (`local` when unhomed), supplied whenever it is known so a psyche turn need never resolve a `--subnet` it cannot know; when no subnet is known the key is left unfilled so a referencing template fails **loud** (the `{node}` precedent). There is deliberately **no `{home}` key** \u2014 the endpoint home subnet is one concept, and `home:` is already the subnet-name qualifier position, so a `{home}` template key would only invite meaning-drift."
  },
  {
    "pair_num": 112,
    "idA": "REQ-PSYCHE-ACCOUNT-REFUSAL-EXIT",
    "titleA": "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.",
    "docA": "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.",
    "idB": "REQ-SESSION-ADAPTER-RECORDED",
    "titleB": "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).",
    "docB": ""
  },
  {
    "pair_num": 113,
    "idA": "REQ-PSYCHE-NESTED-RESOLUTION",
    "titleA": "W4 (F030, design \u00a73; F-017 sibling, general not psyche-only): nested {parent}/{nested} ids resolve under the PARENT's home \u2014 subnet-less resolution for nested perches (the home-ASSIGNMENT seam, not perch-path which is already subnet-less) \u2014 so child-side verbs acting on nested perches need no --subnet the child cannot know. ROOT: home::assign_home returns Ambiguous on a multi-subnet node w/o --subnet; a nested id must instead derive home from its parent perch. Additionally expose a SINGLE {subnet} base_keys fill WHEN KNOWN (own_subnet = home-subnet label, 'local' when unhomed; absent \u2192 LOUD missing-key fail, the {node} precedent). NO {home} key (doyle W4 Q1: the endpoint home subnet is ONE concept per CONTEXT.md:640, and 'home:' is already the subnet-name qualifier position CONTEXT.md:652 \u2014 a {home} template key invites meaning-drift). Red-first = evidence #3's exact repro: 2-subnet home, nested-id verb, must succeed (was: `spt ready <id> --once` \u2192 exit 1 READY_FAIL \u2026 pass --subnet).",
    "docA": "`{subnet}` \u2014 the endpoint's home-subnet label.** A single `{subnet}` key fills to this endpoint's home-subnet label (`local` when unhomed), supplied whenever it is known so a psyche turn need never resolve a `--subnet` it cannot know; when no subnet is known the key is left unfilled so a referencing template fails **loud** (the `{node}` precedent). There is deliberately **no `{home}` key** \u2014 the endpoint home subnet is one concept, and `home:` is already the subnet-name qualifier position, so a `{home}` template key would only invite meaning-drift.",
    "idB": "REQ-WORKER-PICKER-EXCLUDED",
    "titleB": "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.",
    "docB": ""
  },
  {
    "pair_num": 114,
    "idA": "REQ-SESSION-ADAPTER-RECORDED",
    "titleA": "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).",
    "docA": "",
    "idB": "REQ-TERM-7",
    "titleB": "Two-origin digest merge: spt-owned context-injection entries (psyche_download | echo_mirror | owl_message) appended by spt to the endpoint `digest.log`, timestamp-interleaved with the adapter's extracted activity records into one ordered timeline, via a distinct context-injection record category. Data model only this milestone; GUI collapse/expand and the echo-reads-digest delta loop are deferred to the surfaces that consume them.",
    "docB": ""
  },
  {
    "pair_num": 115,
    "idA": "REQ-RC-CROSS-NODE-ATTACH",
    "titleA": "Bug #4: spt rc to a remote endpoint fails with 'no live session' though endpoint list shows it Active \u2014 rc.rs:1063 resolves only the LOCAL broker session table and always dials loopback, never consulting the registry or dialing the owning node (the cross-node attach transport exists in the broker; only the client leg is missing). Fix: on a local resolve miss, resolve the owning node from the registry (reuse resolve_across_visible), net_dial that node, and run a remote session-resolve + serve_attach round-trip (mirror the wansend resolve-dial-round-trip pattern). Shares the resolve-owning-node primitive with REQ-WAN-SEND-DELIVERY. See docs/NEXT-MILESTONE-BUG-TRIAGE.md #4.",
    "docA": "Remote-control vs local operation (two distinct modes \u2014 not the same as instances): Operate locally:** drive the native instance on *your* machine (its local files, its synced mind). The normal case. Remote-control (Shell-like):** attach a control/view surface to an instance *running on another node* \u2014 compute + files stay remote; you are a viewport (the byte-stream terminal attach, daemon-to-daemon over Iroh). Used when you specifically want *that machine's* environment. This is effectively a Shell (a driven surface, user\u2192agent direction), separate from the instance concept itself. Owning-nod",
    "idB": "REQ-RC-HARNESS-ONLY-REFUSAL",
    "titleB": "RC-RENDER-TRUTH W1 (ADR-0042 decision 3, hertz elevated-endpoints RCA core leg 1): direct `spt rc <id>` preflights the already-known harness_only/controllable=Some(false) fact (local + gossiped rows both carry it) and refuses BEFORE any attach/stream with truthful copy naming the actual state ('online but harness-hosted; spt does not own its terminal') \u2014 never the stale-row guess ('it may have ended, or the registry row is stale'). Reproduced deterministically 2x live on an ONLINE - HARNESS ONLY endpoint. RC support for harness-hosted endpoints is NOT claimed until the elevated PTY-host satellite exists (standing ruling). Gate: impl \u2014 pre-stream preflight + truthful copy; unit \u2014 harness-only row refuses pre-dial with the new copy, non-harness rows unaffected; int \u2014 live remote Instance{harness_only:true} => direct rc refuses pre-stream, names harness-hosted, never says stale; doc \u2014 ADR-0042.",
    "docB": "1. **`spt rc` consults the honest-session authority before the offline fast-fail. Normal `spt rc <id>` runs the same bounded `SessionProbe::has_live_session_honest` gate `endpoint run` uses (ADR-0041 single liveness authority). An honest session exists \u2192 attach via the session-confirmed path regardless of persisted status. No honest session \u2192 the existing offline refusal stands. A claimed session with a dead client tree \u2192 refusal/reap, never attach. Reuse `SessionProbe`; no new liveness heuristic. 2. **Resume stamps UNBOUND.** A resume launch transitions an existing `offline` perch to `UNBOUND"
  },
  {
    "pair_num": 116,
    "idA": "REQ-ADAPTER-MULTIPLATFORM-SPT",
    "titleA": "A `.spt` adapter archive may pack multiple platforms in one signed asset: shared `manifest.toml` + `strings/` at the root, role binaries under per-Rust-target-triple subdirectories (ADR-0016 triple vocabulary, e.g. `x86_64-pc-windows-msvc/`); install/update extracts the shared root plus ONLY `current_platform()`'s triple subdir, flattened into `install_dir` so flat `<install_dir>/<program>` resolution (REQ-INSTALL-11) is unchanged. Name stays `adapter.spt` (plain-tar or gzip, `--asset` optional default); one whole-archive Ed25519 signature over the fat archive (REQ-UPD-9 single-artifact verify). A legacy flat archive (no triple subdirs) extracts as today (free back-compat); a multi-platform archive sets `min_spt_core_version >= 0.13.2` (forward-compat gate, readable before extract); a multi-platform archive missing the recipient's triple -> typed `NoArtifactForPlatform`, never a silent no-op. Large adapters may still split per-platform (single-triple archives via `--asset`, or ADR-0016 update-set machinery). (ADR-0024, v0.13.2)",
    "docA": "Multi-platform adapter `.spt` packaging",
    "idB": "REQ-UPDATE-RESTART-SAFE-SWAP",
    "titleB": "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).",
    "docB": "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"
  },
  {
    "pair_num": 117,
    "idA": "REQ-ADAPTER-UPDATE-POST",
    "titleA": "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)",
    "docA": "`[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.",
    "idB": "REQ-START-1",
    "titleB": "Adapters never resolve SPT_HOME; binary on PATH; api bridging only",
    "docB": ""
  },
  {
    "pair_num": 118,
    "idA": "REQ-BROKER-ATTACH-JOURNAL-RESILIENT",
    "titleA": "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.",
    "docA": "",
    "idB": "REQ-DAEMON-2",
    "titleB": "Broker/brain split for seamless self-update",
    "docB": "Restoration field-run evidence (D7-4) \u2014 the seamless-update acceptance"
  },
  {
    "pair_num": 119,
    "idA": "REQ-DAEMON-2",
    "titleA": "Broker/brain split for seamless self-update",
    "docA": "Restoration field-run evidence (D7-4) \u2014 the seamless-update acceptance",
    "idB": "REQ-UPD-8",
    "titleB": "Platform-safe `spt update fetch` + apply platform-guard (v0.3.1 cross-OS brick fix): `spt update fetch` stages the signed multi-platform `SignedUpdateSet` (`update-set.json` + every platform artifact it names), never a platform-blind single `SignedRelease`, so local apply selects `current_platform()` and P2P re-serve lets each peer select ITS own platform. Defense-in-depth: `apply_staged` REFUSES a staged single-release artifact unless it is platform-stamped for THIS node (an unstamped pre-v0.3.2 single, or a single stamped for another OS, fail-safe refuses \u2014 the guard that alone prevents the v0.3.1 brick where a Linux ELF was applied as `spt.exe`). UX: a friendly post-apply message (`Updated spt-core to vX.Y.Z.` + changelog URL) driven by an additive `product_version` metadata field, with a release-counter fallback when absent.",
    "docB": ""
  },
  {
    "pair_num": 120,
    "idA": "REQ-HAZARD-BROKER-PROCESS-ISOLATION",
    "titleA": "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).",
    "docA": "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 ",
    "idB": "REQ-UPD-5",
    "titleB": "spt-core ripple-updates registered adapters",
    "docB": ""
  },
  {
    "pair_num": 121,
    "idA": "REQ-HAZARD-INPUT-ACK-BACKPRESSURE",
    "titleA": "A FLOOD of operator input on one brain\u2194broker connection deadlocks the broker PERMANENTLY (entire broker \u2014 no new/existing attach; the controller stays latched because the per-conn handler can't process the detach). ROOT (doyle /diagnose, code-grounded + HITL capture, the v0.13.0 P1 ctrl+V re-open): `serve_attach` processes a whole `NetStreamData` batch of N operator `Input` records in its inner `for rec in decoder.push()` loop, calling `brain.send_effect(op_id, &bytes)` N times WITHOUT returning to `read_event()` \u2014 so the brain writes N `KIND_INPUT` frames back-to-back and drains nothing. The broker's single-threaded per-conn handler answers EACH with `send_frame(applied_envelope)` on the SAME conn (B5 exactly-once ack, KNOWN-HAZARDS 7.2). With the brain not reading, the broker\u2192brain return direction fills (~10 frames = the IPC pipe buffer) \u2192 `send_frame` BLOCKS \u2192 the handler stops reading \u2192 the brain's writes block too \u2192 mutual full-duplex DEADLOCK. Capture pinned it: 11 input frames, write_input 11/11 (P0 holds \u2014 the PTY write is fine), ack send START=11 / END=10 (frame #11's applied-ack never returns). Same class as the v0.12.1 L0 two-conn split. Windows Terminal's ctrl+V paste accelerator was the trigger (injects the clipboard as a char-by-char key flood) but the deadlock is generic to ANY input flood, NOT ctrl+V-specific and NOT a P0 (PTY-write) or W1 (output-drain) regression. The applied-ack is load-bearing ONLY for `shellchan` (one-at-a-time spool delivery WAITS on `BrokerEvent::Applied`); `serve_attach` DISCARDS it (the operator/rc path is fire-and-forward, op_id for dedup only, never gates on the ack). FIX (doyle-approved): CONDITIONAL ACK \u2014 `InputReq` gains `ack: bool` (serde default = true, N-1-safe: an older brain's input still acks = today's behavior). `serve_attach`'s operator path calls `send_effect_no_ack` (ack=false) \u2192 `dispatch_input` writes NO applied frame \u2192 the per-conn handler never writes back while servicing the flood \u2192 it always drains \u2192 no deadlock (cures ANY input flood). `shellchan` keeps `send_effect` (ack=true) and its `Applied`-wait. Exactly-once PRESERVED: the broker still dedups by (session, op_id) at the applied-set regardless of the ack. N-1 caveat: an OLD resident broker (self-update window) ignores `ack=false` \u2192 still acks \u2192 the deadlock persists until a broker restart (inherent KNOWN-HAZARDS 7.9 broker-resident-wire-change class). (v0.13.0)",
    "docA": "7.19 An operator input FLOOD must not deadlock the broker via the applied-ack on the same conn `[REQ-HAZARD-INPUT-ACK-BACKPRESSURE]` Failure (operator HITL, the ctrl+V re-open):** a flood of operator input on one brain\u2194broker conn wedged the WHOLE broker PERMANENTLY (no new/existing attach; the controller stayed latched \u2014 the per-conn handler couldn't process the detach). `serve_attach` processes a whole `NetStreamData` batch of N `Input` records in its inner loop, calling `send_effect` N times WITHOUT returning to `read_event()`; the broker answers each with `send_frame(applied_envelope)` on ",
    "idB": "REQ-UPDATE-GH-TRANSPORT",
    "titleB": "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.",
    "docB": "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"
  },
  {
    "pair_num": 122,
    "idA": "REQ-INSTALL-13",
    "titleA": "Adapter add is non-destructive & idempotent-safe (F-018): `spt adapter add --github|--release` REFUSES when the target `_github/<safe>` home already backs an ACTIVE registered record \u2014 emitting an actionable code (ADAPTER_ADD_ALREADY_REGISTERED) that routes to `spt adapter update <name>` (refresh in place) or `spt adapter remove <name>` then re-add (replace) \u2014 instead of clobbering the live install (the perri footgun: `add --github` over a `--release` pointer git-cloned a source tree over the extracted built binaries \u2192 registered pointer dangled \u2192 cryptic `os error 2`). And when it DOES (re)populate the home it STAGES-THEN-SWAPS (clone/extract to a sibling staging dir, swap into place only on success) so a failed fetch/clone never strands the previously-extracted manifest+binaries as a dangling pointer (the os-2 / DeferredManifest class). Mirrors the safe stage-then-swap `adapter update` already uses (REQ-UPD-9, apply_release_crc_swap). (v0.14.1)",
    "docA": "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",
    "idB": "REQ-UPD-8",
    "titleB": "Platform-safe `spt update fetch` + apply platform-guard (v0.3.1 cross-OS brick fix): `spt update fetch` stages the signed multi-platform `SignedUpdateSet` (`update-set.json` + every platform artifact it names), never a platform-blind single `SignedRelease`, so local apply selects `current_platform()` and P2P re-serve lets each peer select ITS own platform. Defense-in-depth: `apply_staged` REFUSES a staged single-release artifact unless it is platform-stamped for THIS node (an unstamped pre-v0.3.2 single, or a single stamped for another OS, fail-safe refuses \u2014 the guard that alone prevents the v0.3.1 brick where a Linux ELF was applied as `spt.exe`). UX: a friendly post-apply message (`Updated spt-core to vX.Y.Z.` + changelog URL) driven by an additive `product_version` metadata field, with a release-counter fallback when absent.",
    "docB": ""
  },
  {
    "pair_num": 123,
    "idA": "REQ-RELEASE-CHANNEL-PRIVATE",
    "titleA": "THE-FORKENING W2 (ADR-0036 \u00a72): the publish pipeline targets `BigscreenVR/spt-bs-releases` \u2014 release.yml assemble/draft/flip retargeted (draft lands on the bs releases repo, untagged, per the existing spt-releases pattern); docs-publish.yml RETIRED (no public docs, ADR-0014 superseded) with the mdbook build folded into ci.yml as the drift gate (CLAUDE.md mandate: doc generation stays CI-gated); counter + signing key + update-set format CONTINUE unchanged (trust anchor is the continuity, carrier is not). Gate: impl \u2014 workflow retarget + drift-gate fold; int \u2014 a full release dry-run assembles binaries + docs asset + signed update-set against the private channel. Kin REQ-DOCS-RELEASE-ASSET, REQ-UPDATE-GH-TRANSPORT, ADR-0036.",
    "docA": "",
    "idB": "REQ-UPDATE-GH-TRANSPORT",
    "titleB": "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.",
    "docB": "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"
  },
  {
    "pair_num": 124,
    "idA": "REQ-UPD-3",
    "titleA": "No endpoint process terminates/suspends during self-update",
    "docA": "",
    "idB": "REQ-UPDATE-APPLY-ALREADY-APPLIED",
    "titleB": "`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)",
    "docB": ""
  },
  {
    "pair_num": 125,
    "idA": "REQ-UPDATE-DEFAULT-COMPOSITE",
    "titleA": "THE-FORKENING W4 (operator-grilled 2026-07-14): plain `spt update` = `update fetch --apply` THEN `update adapters` (core-first doctrine order); when core is already current the core leg no-ops and ONLY adapters update; `--core-only`/`-c` skips the adapters leg. GROUNDING (operator-corrected, code-confirmed): fetch --apply cycles the BRAIN only \u2014 broker + PTYs survive (apply_staged applyhost.rs:303; the restart-required text is a NOTICE, cli.rs:4615, not behavior) \u2014 so the composite's invoking process survives by construction and NO re-run machinery is needed; on a broker-side release the existing F-025 notice remains the composite's closing output. Gate: unit \u2014 composite sequencing incl. already-current -> adapters-only and --core-only skip; int \u2014 composite on a staged release applies core then updates a registered adapter in one invocation; doc \u2014 reference + self-update docs present plain `spt update` as the primary form. Kin REQ-UPDATE-ADAPTERS-VERB, REQ-UPDATE-RESTART-SAFE-SWAP, REQ-UPDATE-APPLY-RESTART-NOTICE.",
    "docA": "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",
    "idB": "REQ-UPDATE-ONE-SHOT-FINISH",
    "titleB": "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 ///.",
    "docB": ""
  },
  {
    "pair_num": 126,
    "idA": "REQ-HAZARD-SESSION-PIN-WEDGE",
    "titleA": "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)",
    "docA": "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",
    "idB": "REQ-UPDATE-PROMOTE-DRAINED",
    "titleB": "W3 (LIFECYCLE-TRUTH, mechanic-d MOVED FROM W2 per doyle gate verdict @e5ae7a9 \u2014 binding): the update-apply brain-generation promotion completes only when the OLD generation's broker subscriber connection is CLOSED or stall-EVICTED \u2014 never while blocked writes still pend on it. ROOT: `brain.ready` != subscribers drained; W2's stall-evict (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE) only BOUNDS the false-promote window to BRAIN_WRITE_DEADLINE (15s), it does NOT close it \u2014 a new brain can signal ready inside that window while the old gen's conn is still wedged, so the apply 'promotes' onto a still-frozen control plane (the 22:47 incident-night false-promote). FIX: the promotion gate (ADR-0018 brain-trial, brainproc.rs) adds an explicit DRAINED precondition \u2014 promote only on ready AND old-gen-subscriber-drained (conn closed OR stall-evicted); the drained signal reads broker truth (the W2 stall-evict tally / the old conn's liveness), no brain round-trip. The residual W2 left open, now closed. Int = a FALSE-PROMOTE rig that exercises the promotion path itself: an old-gen subscriber conn held wedged past ready must NOT promote until it drains (RED-first: ready-alone promotes).",
    "docB": ""
  },
  {
    "pair_num": 127,
    "idA": "REQ-HAZARD-SESSION-PIN-WEDGE",
    "titleA": "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)",
    "docA": "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",
    "idB": "REQ-SESSIONS-LOG-ENDPOINT-ATTRIBUTION",
    "titleB": "C2 (F028, infra; ROOT-CAUSED + severity-upgraded doyle RCA 2026-07-03): cross-endpoint perch contamination \u2014 a foreign psyche's SessionStart hook REBINDS a victim perch's IDENTITY, not merely its ledger. Evidence: hall-a's info.json.session_id IS f015b-probe-psyche's session (359d7bd7) + hall-a's ledger holds the foreign psyche session; same class as hall-b's dead-pid stamp (141556). This REQ = the OBSERVABLE (foreign session_id in a perch's ledger/info.json + resume offering foreign sessions) and its belt-braces: (iii) filter owlery-cwd rows OUT of resume_rows; (iv) one-time repair for already-contaminated perches (hall-a on HFENDULEAM) or self-heal on next legitimate session-start. The ROOT (identity pinned at spawn + honest bind + nested self-resolve) is REQ-BIND-HONEST-SELF-STAMP \u2014 C2 is UPSTREAM of B3 (presence/CONTROLLED read the very stamps this corrupts). See triage C2.",
    "docB": ""
  },
  {
    "pair_num": 128,
    "idA": "REQ-ENDPOINT-ONLINE-TRUTH",
    "titleA": "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.",
    "docA": "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",
    "idB": "REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT",
    "titleB": "A `rc --view` VIEWER that overflows its broker subscription queue and is EVICTED (OutputLog::append try_send Full \u2192 viewers.remove, REQ-HAZARD-VIEWER-ISOLATION session-protection) must SKIP TO LIVE, not die silently. ROOT (v0.13.0, b4 JIT item 2 = p0_paste + post-b4 a_journaled-Linux, ONE root): serve_attach forwards each frame (read_event\u2192b64decode\u2192re-encode AttachRecord\u2192net_stream_send) SLOWER than the drain fans out under flood \u2192 its VIEWER_CHANNEL_DEPTH(256) channel overflows \u2192 the drain evicts (viewers.remove drops the ViewerSink \u2192 drops tx \u2192 viewer_writer's rx.recv() Err \u2192 the writer returns WRITING NOTHING) \u2192 serve_attach's brain.read_event() just STOPS getting Output (no EOF, no error) \u2192 serve_attach blocks forever \u2192 the operator receives nothing (attach_received_output=FALSE). Eviction-of-a-hopelessly-behind-viewer is CORRECT session-protection (keep it); SILENT+PERMANENT eviction is the bug. VIEWER-only \u2192 B2-SAFE (a viewer never advances delivered_through / is not authoritative / exposes no resume cursor). FIX (doyle-gated, skip-to-live = tail -f reconnect): (1) explicit broker\u2192viewer EVICTION SIGNAL (KIND_VIEWER_EVICTED, written in the viewer_writer thread OFF the log lock, DISTINCT from session-exit EOF so serve must NOT tear down on it); (2) serve_attach re-subscribes from the CURRENT ring floor (skip-to-live, replays nothing, sees the next live burst) \u2014 resetting the cold serve-brain's next_seq so the post-eviction forward-jump replay is accepted (the legacy reject-gap path, brain.rs:618-626, would otherwise FATAL the forward jump); (3) HARD constraint NO evict\u2192resubscribe busy-loop: serve_attach rate-limits re-subscribes (RESUBSCRIBE_INTERVAL) so under max-flood the operator sees intermittent LIVE bursts, never a CPU spin. (v0.13.0)",
    "docB": "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"
  },
  {
    "pair_num": 129,
    "idA": "REQ-MSG-2",
    "titleA": "spt binary CLI surface: send/ring/ready(+--once)/list/stop/whoami, stable arg shapes + exit codes",
    "docA": "",
    "idB": "REQ-WAN-SPT-HOSTED-DELIVERY",
    "titleB": "A WAN-ARRIVED `spt send` is DELIVERED to an spt-hosted endpoint (broker holds its PTY, NO api-listen relay), not spooled-forever. Today receive_wan (spt-daemon/wan.rs:271-276) tries deliver_tcp (the harness-hosted relay leg) then falls to spool \u2014 it has NO spt-hosted broker-inject leg, which exists ONLY in local cmd_send (REQ-SEND-SPT-HOSTED, Brain::inject_endpoint \u2192 KIND_ENDPOINT_INPUT \u2192 broker dispatch_endpoint_input \u2192 translation-binary idle-inject). So a WAN arrival to an idle spt-hosted perch with a live translation binary ALWAYS sleeps in spool until an adapter hook polls (F-023: perch verifiably idle 7min, binary healthy, zero injection). FIX: factor cmd_send's spt-hosted delivery leg into a SHARED fn; receive_wan calls it after the replay-check (wan_seen_at) + restamp (restamp_wan_user_msg), BEFORE the spool fallback. Claim discipline UNCHANGED: inject delivered=true \u2192 wan_mark_seen_at then return the existing 'delivered' wire token (no wire change); delivered=false \u2192 the existing spool-with-claim transaction. v0.14.3 LAW: the shared leg is translation-binary-ONLY, NO raw-PTY fallback \u2014 a no-binary arrival SPOOLS LOUD, never writes the PTY. (F-023, BUILD-F023-WANIDLE)",
    "docB": ""
  },
  {
    "pair_num": 130,
    "idA": "REQ-DRIVEN-BY-OWN-NODE-NORMALIZE",
    "titleA": "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.",
    "docA": "v0.38.1 consequence note \u2014 `driven_by` own-node truth (ruling v3)",
    "idB": "REQ-HAZARD-DRIVEN-BY-SELFHEAL",
    "titleB": "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)",
    "docB": ""
  },
  {
    "pair_num": 131,
    "idA": "REQ-BRAIN-RESUME-NO-CONN-DEADLOCK",
    "titleA": "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).",
    "docA": "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 [`",
    "idB": "REQ-HAZARD-INJECT-WORKER-POISON",
    "titleB": "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.",
    "docB": ""
  },
  {
    "pair_num": 132,
    "idA": "REQ-HAZARD-VIEWER-ISOLATION",
    "titleA": "A slow / dead / hostile VIEWER must NEVER stall the controller, the PTY child, or the session drain thread. The broker drain fans output to the controller on the authoritative blocking bounded path (advances delivered_through) but to each viewer via a bounded per-viewer channel with a dedicated writer thread; the drain `try_send`s under the log lock and a viewer whose bounded queue OVERFLOWS (can't keep up) is EVICTED (queue dropped, writer thread ends, removed from the viewers map) \u2014 the drain thread NEVER touches a viewer socket, so no viewer write can backpressure or block it. A soft viewer cap bounds the thread count. Viewer eviction never perturbs the controller stream, the delivered_through cursor, or the child.",
    "docA": "7.7 A slow/dead/hostile remote VIEWER must never stall the controller, child, or drain `[REQ-HAZARD-VIEWER-ISOLATION]` Failure:** the W2.5 controller/viewer model lets ANY number of read-only `--view` attachers ride one session's broker `OutputLog`. The single drain thread fans each output chunk to every attacher. If a viewer's socket is fanned out with a **blocking** write under the log lock (the controller's authoritative path), one wedged viewer (a slow terminal, a black-holed WAN peer, a hostile non-reader) stalls the drain \u2014 freezing the controller's stream and backing up the PTY child. A",
    "idB": "REQ-PUMP-PEER-ISOLATION",
    "titleB": "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).",
    "docB": ""
  },
  {
    "pair_num": 133,
    "idA": "REQ-ENDPOINT-CYCLE-HONEST",
    "titleA": "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.",
    "docA": "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",
    "idB": "REQ-HAZARD-TEARDOWN-DEADEND",
    "titleB": "TEARDOWN-AUTHORITY W1 HAZARD (ADR-0045 decision 8; hertz RCA 2 \u2014 hit doyle's OWN live production endpoint, recoverable only by out-of-band scoped kill): `endpoint stop` followed by `endpoint run --id <same>` MUST succeed (spawn or resume) and must NEVER answer ENDPOINT_CREATE_CONFLICT about a session that stop claimed to end. TRAP SHAPE (two individually-CORRECT behaviors composing into a lifecycle DEAD END with no in-band exit): (1) stop leaves the broker session alive (REQ-ENDPOINT-TEARDOWN-AUTHORITY), and (2) `endpoint run` correctly REFUSES ENDPOINT_CREATE_CONFLICT rather than silently reattaching (v0.37.0 no-silent-reattach rule, deliberately chosen). The survivor is therefore simultaneously what stop claims to have killed AND what run refuses to work around; with a wedged harness every in-band verb is exhausted (stop lies, run refuses, rc replays a dead PTY, rc --take controls a process that never answers). Neither behavior is individually wrong \u2014 the COMPOSITION is the hazard, so the regression must assert the composition, not either verb alone. This single assertion is the whole user-visible point of W1. Gate: impl \u2014 covered by the shared primitive; int \u2014 start a real broker host, hard-stop it, assert subtree + broker row gone, THEN `endpoint run --id <same>` SUCCEEDS (never CREATE_CONFLICT) and `endpoint run --resume <recorded sid>` creates a NEW PTY rather than rc-ing the old one; PLUS the durability leg \u2014 info stays offline + terminally-normalized across a bounded settle window (proves no re-bind by a survivor, the field rest_state=active finding made into an assertion).",
    "docB": "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"
  },
  {
    "pair_num": 134,
    "idA": "REQ-HAZARD-BRAIN-RESTART-LIFECYCLE-REHYDRATE",
    "titleA": "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)",
    "docA": "",
    "idB": "REQ-HAZARD-DAEMON-HOSTED-LIVENESS",
    "titleB": "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)",
    "docB": ""
  },
  {
    "pair_num": 135,
    "idA": "REQ-LISTEN-PRESERVES-HOSTING-TOPOLOGY",
    "titleA": "`api listen` must not ASSERT hosting topology it does not know. (hertz v0.39.0 field RCA 2026-07-21, doyle re-grounded at source.) OBSERVED: the published adapter sequence `api bind` then identity-preserving `api listen --session-id` produces a self-contradictory live record \u2014 controlled=true AND controllable=false on a broker-hosted PTY endpoint \u2014 which controlled-precedence masks blue while attached and which a DETACH then unmasks as amber HARNESS ONLY. Detach is not the root; it only reveals the bad stamp. SOURCE: api/startup.rs passes controllable=Some(false) UNCONDITIONALLY on the relay/listen path (~191-195, reasoning 'the harness owns the process, so there is no broker PTY'), and establish_perch resolves controllable = controllable.or_else(|| prior\u2026) (~379) \u2014 explicit wins, so that Some(false) OVERWRITES the Some(true) an earlier `api bind` EARNED. The carry-forward discipline that protects cwd/adapter/rest_state does not protect this field precisely BECAUSE the listen path is not silent about it. The defect is an ASSUMPTION about hosting authority made by a path that does not know the answer. FIX (preferred): represent LISTENER CUSTODY separately from PTY HOSTING AUTHORITY, so establishing a listener says nothing about who owns the session surface. Merely preserving controllable=true when a broker-hosted session with that session_id exists is weaker \u2014 it leaves listen guessing rather than removing the guess. Gate: impl \u2014 listen no longer asserts hosting topology for a session it does not host; unit \u2014 the stamp resolution table over (prior controllable, listen path, broker-hosted session present); int \u2014 bind -> listen -> control -> detach ends at alive=true, controlled=false, controllable=true, display ONLINE. TITLE AMENDMENT 2026-07-27 (doyle ruling, todlando build; rides the build PR per registry-mints-ride-build-PRs): THE REQ PRESERVES A **LIVING** HOSTING ARRANGEMENT ACROSS LISTENER RE-BINDS; IT DOES NOT RESURRECT A DEAD ONE'S CAPABILITY STAMP. FIELD CASE: emphasys rendered ONLINE for 25+ minutes with BOTH recorded pids dead, because its listener-only wake re-bind INHERITED a controllable=Some(true) earned in an earlier broker-PTY life, and the reconcile sweep exempts Some(true) rows from relay-death convergence (livehost.rs) \u2014 so an expired capability stamp ROUTED a liveness proof and the row was exempt from EVERY liveness model. The carry-forward is now scoped: Some(true) survives a listener re-bind unless the prior record's RELAY pid is provably Gone. Liveness of the arrangement is judged via the relay-role pid (REQ-PID-ROLE-EVIDENCE), the first record-internal key that actually measures it \u2014 NOT via 'earning pid alive', which was falsified pre-build: for a BrokerPty row the record holds no pid of the hosting life at all, only the announcing CLI's.",
    "docA": "",
    "idB": "REQ-SESSIONS-LOG-ENDPOINT-ATTRIBUTION",
    "titleB": "C2 (F028, infra; ROOT-CAUSED + severity-upgraded doyle RCA 2026-07-03): cross-endpoint perch contamination \u2014 a foreign psyche's SessionStart hook REBINDS a victim perch's IDENTITY, not merely its ledger. Evidence: hall-a's info.json.session_id IS f015b-probe-psyche's session (359d7bd7) + hall-a's ledger holds the foreign psyche session; same class as hall-b's dead-pid stamp (141556). This REQ = the OBSERVABLE (foreign session_id in a perch's ledger/info.json + resume offering foreign sessions) and its belt-braces: (iii) filter owlery-cwd rows OUT of resume_rows; (iv) one-time repair for already-contaminated perches (hall-a on HFENDULEAM) or self-heal on next legitimate session-start. The ROOT (identity pinned at spawn + honest bind + nested self-resolve) is REQ-BIND-HONEST-SELF-STAMP \u2014 C2 is UPSTREAM of B3 (presence/CONTROLLED read the very stamps this corrupts). See triage C2.",
    "docB": ""
  },
  {
    "pair_num": 136,
    "idA": "REQ-GOSSIP-CONTROLLED-CROSS-NODE",
    "titleA": "B7 (F028, operator, cross-node): a remote endpoint's CONTROLLED state is not rendered. Evidence: ball-b ONLINE + CONTROLLED on ENLYZEAM (local view), but HFENDULEAM renders remote ball-b as plain ONLINE (both 0.22.0). The F-026 #4 gossiped any-controller datum (REQ-GOSSIP-ADAPTER-PROJECTS controlled bool) either isn't SENT for the locally-controlled case, isn't APPLIED on the receiving row, or DECAYS. Local leg confirmed fine (sibling hall-b renders blue-glyph correctly); the gap is the REMOTE leg. perri's validation had this ENV-BLOCKED \u2014 two live nodes now available to RCA. FIX: RCA sender-side (is controlled gossiped when locally-controlled?) / receiver-render (does from_resource_row surface it?) / decay, then lock with a cross-node int. See triage B7.",
    "docA": "",
    "idB": "REQ-PICKER-CONTROLLED-LOCAL",
    "titleB": "#3 local half: a LOCALLY-controlled endpoint renders CONTROLLED in its own node's picker. display_status() (crates/spt/src/picker/model.rs:415) derives Controlled ONLY from driven_by.is_some(), but driven_by is REMOTE-only by design (KH 7.15) \u2014 a locally-controlled endpoint has driven_by=None + controlled=true, and local_rows (data.rs:220) never threads controlled into EndpointRow, so a locally-RC'd endpoint shows plain ONLINE in its own picker (remote rows are fine \u2014 gossip stamps controller_node=self, REQ-GOSSIP-CONTROLLED-ANY; the asymmetry is the bug). Fix: EndpointRow gains controlled:bool (local: rec.controlled; remote: controller_node.is_some()); display_status -> Controlled when driven_by.is_some()||controlled; desc pane says 'controlled locally' when the driver is unnamed. See docs/NEXT-MILESTONE-PICKER-TRIAGE.md #3.",
    "docB": ""
  },
  {
    "pair_num": 137,
    "idA": "REQ-LIVE-AGENT-NO-INJECT-DELIVERY",
    "titleA": "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].",
    "docA": "",
    "idB": "REQ-MSG-DELIVERY-AXES",
    "titleB": "Activity-gated inbound delivery + per-message send control as THREE ORTHOGONAL AXES plus opaque metadata (ADR-0028; grilled w/ operator 2026-06-23). SUBSTRATE (the legacy-SPT parity gap, scaffolded-but-unwired today: `delivery::is_idle` + `resolve_inject_methods` exist but the result is discarded `let _methods`, and `broker::dispatch_endpoint_input` injects unconditionally \u2014 its comment calls activity-gating 'a deferred follow wave'): an inbound message has an ACTIVE window (endpoint active \u2192 spool for the receiver's hook-poll, non-disruptive) and an IDLE window (idle/idle-transition \u2192 deliver immediately: translation binary spt-hosted \u2192 relay-poll either topology \u2192 spool, in fallback order). AXES (each composes; each defaults to its unrestricted value): (1) DELIVERY WINDOW \u2014 default (both, first-to-fire) | `--idle-only` (idle window; immediate if already idle) | `--active-only` (active window only, never wakes; the RENAMED `--deferred` \u2014 `deferred=1` spool column + `api poll --include-deferred` keep their names). (2) CHANNEL RESTRICTION \u2014 unrestricted | `--prefer-native` (translation binary if running else fall back) | `--force-native` (binary ONLY, no fallback/no spool-to-other-method). Native flags do NOT respect the binary's idle-gating: the WINDOW says when, the native flag says through-what (so `--force-native --active-only` = binary injects during the active window, mid-turn-safe via the existing InjectFloor). (3) PERSISTENCE \u2014 durable (default; spool until delivered or TTL) | `--ephemeral` (drop if undeliverable in the accepted window \u2014 at window-open with no live carrier, or at TTL, whichever first). METADATA (orthogonal): `--json-payload '<json>'` \u2192 a single attr-escaped `json=\"\u2026\"` envelope attr ALONGSIDE (not replacing) the body, pure verbatim passthrough across spool/TCP/WAN/EVENT-PART, parsed only by the receiving adapter; collision-proof by construction (structured data lives INSIDE the one `json` value, can never forge `from`/`type`); available to ANY sender (confers no spt-core authority). HAZARD: `--ephemeral` is the ONLY path permitted to drop silently \u2014 the sender-opted-in carve-out to REQ-HAZARD-IDLE-SILENT-NONDELIVERY (that hazard gains a '\u2026unless --ephemeral' clause in v0.15.0). (v0.15.0)",
    "docB": "Activity-gated message delivery + send-modifier axes"
  },
  {
    "pair_num": 138,
    "idA": "REQ-HAZARD-DELIVERY-STARVATION",
    "titleA": "A NON-DEFERRED message that has REACHED a node's spool (WAN-arrived or locally spooled-while-active) is NEVER dependent on an adapter HOOK-POLL cadence for its eventual delivery to an spt-hosted (relay-less) endpoint \u2014 the daemon itself drives delivery on the events it owns (WAN ingress + the ACTIVE\u2192IDLE edge). Hazard class: delivery starvation. Without this, cross-node and post-active messages to an spt-hosted perch strand indefinitely whenever the adapter's hooks are quiet (idle session, no user turns), presenting as 'sent but never lands' with a healthy binary and an idle perch (F-023). SCOPE NARROWED 2026-07-26 (FIELD-TRUTH W1 roll-in, operator ruling): the deferred (active_only) class is EXPLICITLY OUTSIDE this guarantee \u2014 per ADR-0028 it is hook-carried background context that never wakes an idle agent; with no hook cadence it waits by design. The no-starvation guarantee binds the default and idle_only classes only. Guarded by REQ-WAN-SPT-HOSTED-DELIVERY (WAN ingress leg) + REQ-MSG-IDLE-EDGE-DRAIN (idle-edge drain, as amended). (F-023)",
    "docA": "\u26a0 REVERSED 2026-07-27 \u2014 the collapse this document argued AGAINST is now SHIPPED Read this banner before treating anything below as current.** The 2026-07-09 ruling recorded here \u2014 that the idle edge SHOULD carry `active_only` for the relay-less spt-hosted class, and that the active/idle asymmetry was *intentional* \u2014 was **reversed by operator ruling in the FIELD-TRUTH W1 roll-in.** The idle-edge and parked-re-offer claims now exclude `deferred = 1` **unconditionally**, on every endpoint class. That is precisely the \"collapse\" this document was written to block. What changed was the evidence, ",
    "idB": "REQ-WAN-SEND-DELIVERY",
    "titleB": "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.",
    "docB": ""
  },
  {
    "pair_num": 139,
    "idA": "REQ-HAZARD-RENDER-LIFECYCLE",
    "titleA": "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.",
    "docA": "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",
    "idB": "REQ-RC-RECONNECT-TRUTH",
    "titleB": "W3 (LIFECYCLE-TRUTH): rc reconnect never auto-starts a daemon and never hangs forever. ROOTS rig-proven (the operator's long-standing 'stop 2-4 times' bug): (a) an rc client's reconnect loop AUTO-LAUNCHES a daemon via WMI (rig: DAEMON_LAUNCH_VIA_WMI from the rc) \u2014 resurrection fights the operator's stops; (b) rc freezes at 'Reconnecting to local daemon\u2026' forever when its session died with the broker. FIX: rc NEVER auto-starts a daemon (reconnect only to an already-up broker; loud 'session lost \u2014 daemon down' exit otherwise), bounded reconnect with visible countdown.",
    "docB": ""
  },
  {
    "pair_num": 140,
    "idA": "REQ-CONV-2",
    "titleA": "Event-driven advertisement: endpoint online/offline transitions (ready-listener start/stop, rest-state transition, perch death) trigger an immediate advertise_local + peer push as a WAKE of the existing pump loop (no second advertisement path \u2014 epoch lease + visibility gates ride unchanged); the cadence stays the steady-state floor (M8 decision 15)",
    "docA": "",
    "idB": "REQ-TEST-TMPDIR-HYGIENE",
    "titleB": "Windows test fixtures MUST NOT leak their temp dirs \u2014 find and fix the leak that accumulated 14,319 `.tmp*` dirs (163 GB) in %TEMP% on hfenduleam over 2026-07-19\u219226 and ate the box to 0.00 GB free during the v0.44.0 cut (find credit: deployah). The class presents as resource exhaustion wearing a timing-flake mask: the release-blocking red was spt-daemon::sync `two_tier_sync_lands_and_gate_refuses_server_side` dying 'No space left on device' INSIDE its own leaked-class tmpdir, and the PRIOR red (digest_cross_node brain-IPC deadline elapse) was the SAME root through a timing probe \u2014 burns release windows and invites false environmental discharges (deployah self-corrected his own). ROOT IS UNPROVEN AT MINT (investigate, don't assume): tempfile-crate dirs self-clean on Drop, so something defeats Drop \u2014 candidates from the incident record: process killed mid-drop (fixture children force-killed while owning the dir), detached/leaked children pinning the dir cwd so removal fails, panic paths that never unwind. Deliverable: name the leaking fixture path(s) with evidence, fix the cleanup (guard/finalizer that survives the kill path, or a fixture-scoped reaper), and prove it with a before/after dir-count on the suite. Second recorded hfenduleam disk-full-CI instance.",
    "docB": ""
  },
  {
    "pair_num": 141,
    "idA": "REQ-PEER-PUMP-CHURN-STALL",
    "titleA": "B5 (F028, perri F-a; DEFECT daemon, OBSERVED-ONCE, HIGH): the peer pump STALLS under rapid rc attach/EOF-detach/--take churn. Fresh 0.22.0 daemon ~10min after restart, during rapid rc cycling: `peer pump: STALLED (last tick 122s)`; while stalled `spt rc --view` -> `RC_FAIL: attach request: brain IPC read deadline elapsed` (repeatable) and controlled-clear stopped propagating. Daemon restart recovered + endpoints auto-revived. Prior class: REQ-HAZARD-PUMP-IPC-DEADLINE (reader-thread+channel carrier), REQ-broker-QUIC-deadline (bounded_block_on) \u2014 something in the rc-churn path can still wedge the pump tick. perri holds exact timestamps + a repro candidate (rapid attach/detach/take against one endpoint) \u2014 REQUEST before RCA. See triage B5.",
    "docA": "",
    "idB": "REQ-SEAT-LIFETIME-BOUNDED",
    "titleB": "REGISTRY-LIFECYCLE W1 (ADR-0040 decision 5, hertz defect B leg 5 CROSS-FAMILY per the final attribution correction \u2014 my_stream_subs releases only at conn-loop exit, NO unsubscribe verb existed, ~546/589 broker threads were unnamed parked SubscriberSeat writers on completed Registry/sync/update seats): new KIND_NET_STREAM_UNSUBSCRIBE broker verb removes the SubscriberSeat, stops AND JOINS its writer thread, drops its cursor; every dispatch worker calls it on serve completion (ALL families, success or failure); the retire sweep calls it for retired rows. N-1: unknown-kind on an older broker = tolerated send_error path, caller best-effort. Rider: spawned daemon threads NAMED (SubscriberSeat writer sub-writer-s<id> minimum, conn accept handlers) \u2014 field censuses attribute, not infer. Gate: impl \u2014 verb + worker/sweep call sites + thread naming; unit \u2014 unsubscribe removes seat/joins writer/drops cursor + idempotent + unknown-stream tolerated; int \u2014 rides REQ-HAZARD-REGISTRY-STALL seam (physical seat/thread counts plateau CROSS-FAMILY after completion); doc \u2014 ADR-0040.",
    "docB": "1. **One-way (fire-and-forget) stream families are terminal at FIN, sender-side.** The registry feed pump retires its own row after successful write+FIN via the existing `net-stream-retire` verb (best-effort on N-1 brokers, per ADR-0038 A). A one-way family's exchange is definitionally over at FIN; keeping the row eligible reproduces the O(history) defect forever. 2. **Eligibility filtering is server-side.** `stream_infos` excludes `initiated_locally` rows (alongside `retired`) before serializing. Consumers keep their client-side guards (double-filter harmless; N-1 compatible both directions)."
  },
  {
    "pair_num": 142,
    "idA": "REQ-ACL-SUBNET-MODE-CAPTURE",
    "titleA": "A subnet's control-surface mode is recorded on the subnet and CAPTURED AT JOIN TIME into the joining node's own store \u2014 the W1 captured-subnet-mode table stops being a stub and lights up tier 8, the chain's bottom fallback (CONTEXT.md 'control-surface modes' resolution order). Capture, not lookup, is the point: a node's effective posture must be a local, immutable fact it consented to when it joined, so a subnet owner cannot retroactively tighten or loosen what an already-joined member enforces (that is what makes the advisory-gossip posture in REQ-ACL-MODE-ADVISORY-GOSSIP coherent rather than a race). Migration posture is explicit and must be tested: an EXISTING subnet has no captured mode, so tier 8 stays absent, the chain bottoms out open, and fleet behavior is preserved exactly \u2014 an upgrade must not silently close a running mesh. Gate: doc \u2014 the CONTEXT.md control-surface-modes entry (three levels and the resolution chain); impl \u2014 the subnet-mode record, the join-time capture into the endpoint access store, and tier 8 reading it; unit \u2014 a joined-with-closed node refuses where an unlisted subject falls through to tier 8, a joined-with-open node admits, and a pre-existing subnet with no capture bottoms open.",
    "docA": "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",
    "idB": "REQ-SUBNET-STATUS-MODES",
    "titleB": "`spt subnet status <name>` states the subnet's three mode facts (surface ruled 2026-07-30 \u2014 the per-subnet view is `subnet status`, no near-synonym `show` verb minted): the mode the subnet DECLARES as this node knows it, the mode this node CAPTURED (the enforced fallback \u2014 join-time immutable, changed only through the engine room's access-refresh), and any declared change SEEN but not adopted (`declared_seen`), named PENDING with when it was seen and the explicit statement that this node's posture is unchanged until the engine room adopts it. Absences are stated in words \u2014 a pre-mode subnet and an uncaptured fallback each say so \u2014 never rendered as blank, and the `--json` rows carry the same facts as optional fields. Gate: doc \u2014 the CONTEXT.md access-entity entry's mode-facts sentence; impl \u2014 the mode-facts resolver over the two stores that each own one half of the truth and the status-view wiring; unit \u2014 the three-way wording including both absence lines and the no-pending case.",
    "docB": "access entity** (ratified 2026-07-30, fast-follow grill): Anything that can be granted (or denied) control via access rules \u2014 a **subnet**, **node**, or **endpoint**. A **ruled access entity**, relative to a given target, is an access entity that at least one of the target's rules names. Access views are **roster-first**: a target lists its ruled access entities grouped by type (subnets, then nodes, then endpoints), each with its rule count (and, for a subnet or the home node, its mode); the **granular rule list is viewable only per named ruled entity**, and external entities with no explicit "
  },
  {
    "pair_num": 143,
    "idA": "REQ-PSYCHE-NESTED-RESOLUTION",
    "titleA": "W4 (F030, design \u00a73; F-017 sibling, general not psyche-only): nested {parent}/{nested} ids resolve under the PARENT's home \u2014 subnet-less resolution for nested perches (the home-ASSIGNMENT seam, not perch-path which is already subnet-less) \u2014 so child-side verbs acting on nested perches need no --subnet the child cannot know. ROOT: home::assign_home returns Ambiguous on a multi-subnet node w/o --subnet; a nested id must instead derive home from its parent perch. Additionally expose a SINGLE {subnet} base_keys fill WHEN KNOWN (own_subnet = home-subnet label, 'local' when unhomed; absent \u2192 LOUD missing-key fail, the {node} precedent). NO {home} key (doyle W4 Q1: the endpoint home subnet is ONE concept per CONTEXT.md:640, and 'home:' is already the subnet-name qualifier position CONTEXT.md:652 \u2014 a {home} template key invites meaning-drift). Red-first = evidence #3's exact repro: 2-subnet home, nested-id verb, must succeed (was: `spt ready <id> --once` \u2192 exit 1 READY_FAIL \u2026 pass --subnet).",
    "docA": "`{subnet}` \u2014 the endpoint's home-subnet label.** A single `{subnet}` key fills to this endpoint's home-subnet label (`local` when unhomed), supplied whenever it is known so a psyche turn need never resolve a `--subnet` it cannot know; when no subnet is known the key is left unfilled so a referencing template fails **loud** (the `{node}` precedent). There is deliberately **no `{home}` key** \u2014 the endpoint home subnet is one concept, and `home:` is already the subnet-name qualifier position, so a `{home}` template key would only invite meaning-drift.",
    "idB": "REQ-SUBNET-DUAL-SEED-MINT",
    "titleB": "`subnet create` mints TWO TOTP seeds in one ceremony \u2014 the member key (today's subnet seed, unchanged) and a second admin key \u2014 and provisions both into the creator's authenticator at that single moment (ADR-0051 decision 1, CONTEXT.md 'member key / admin key'). An admin key IS a membership key; a member key is NOT an admin credential. The same ceremony captures the subnet's universal control-surface mode: prompted with NO PRESELECTION (an operator must state open or closed rather than accept a default that a hurried Enter would pick for them), with `--open`/`--closed` bypassing the prompt for scripted creation. This is the moment the whole two-key model depends on: the admin seed has no reveal verb ever (REQ-SUBNET-ADMIN-SEED-REPLICATION), so a creation path that mints it without displaying it, or displays it without persisting it, permanently destroys the subnet's admin authority with no recovery but re-minting the subnet. Gate: doc \u2014 the CONTEXT.md two-key entry and ADR-0051; impl \u2014 dual mint at create, both authenticator provisionings, the no-preselection mode prompt and its flag bypass; unit \u2014 creation yields two DISTINCT seeds, the mode is recorded from prompt or flag, and no code path yields a subnet holding one seed.",
    "docB": "member key / admin key (two-key subnet)** (ratified 2026-07-28, access-control grill): Subnet creation mints **two** TOTP seeds. The **member key** is today's subnet seed (join ceremony, show-code under elevation). The **admin key** is a second seed: it also joins its origin subnet (an admin key IS a membership key; a member key is NOT an admin key), and it additionally gates subnet-scope access administration (see *empower*). **Held everywhere, revealable nowhere:** every member node holds both seeds (replicated at join \u2014 needed to verify `empower` and to serve admin-code joins), but the admi"
  },
  {
    "pair_num": 144,
    "idA": "REQ-ATTACH-RESIZE-REPAINT",
    "titleA": "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).",
    "docA": "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",
    "idB": "REQ-RC-RESIZE-PRESENTATION-BARRIER",
    "titleB": "During a resize transition, LIVE DELIVERY is barriered along with the grid parse: no attached sink (controller or viewer) ever receives mixed-geometry raw bytes; commit substitutes ONE synthesized sync frame at the target geometry. (hertz post-fix field RCA 2026-07-21 on a fully-0.39.3 node, doyle-accepted with every cite verified at source; SUCCEEDS REQ-RC-RESIZE-GEOMETRY-EPOCH, which shipped correct but scoped to the wrong surface \u2014 broker.rs's own ResizeTransition doc states append 'still rings + fans out every chunk exactly as before' during a transition, and OutputLog::append gates ONLY grid.advance. A live attached terminal therefore receives old+new-geometry differentials across an already-resized viewport; the cold-attach repaint model is definitionally blind to that path, which is why the shipped gate was green while the field was red. REQUIREMENT-SCOPE defect: the gate held against documented design.) CONTRACT (ADR-0031 Amendment II): (1) append still assigns seq and rings every chunk, but controller handoff and viewer fan-out are SUPPRESSED during the transition \u2014 a skip, never a block or sleep (KH 7.12 intact), and the suppressed window does not count toward the controller Full-eviction deadline; (2) commit replays held segments at their emitting geometry (the geometry-epoch machinery, unchanged), lands the grid at target geometry, then UNDER THE SAME OutputLog LOCK ACQUISITION pushes viewers the size frame FIRST and then one synthesized sync frame (render_repaint at target geometry ++ deferred non-grid bytes) to EVERY attached sink, controller included, before raw fan-out resumes \u2014 no raw frame may interleave; (3) the controller sync frame carries watermark seq = highest suppressed seq, so the cursor-of-record advances past the whole suppressed range as-if-written (repaint supersedes; the repaint_initial watermark shape); WIRE-FLAG AMENDMENT (doyle ruling 2026-07-21, from the build's real-wire int finding \u2014 the watermark jump would otherwise fatal every STRICT consumer as an output gap and ladder into ControllerIrrecoverablyBehind = marked truncation on every resize under a live controller): OutputEvent gains additive `sync: bool` (serde default false, never serialized when false \u2014 the resume_seq D4-1 additive shape), set by the broker on the commit AND abort sync frames AND on every repaint_initial batch; a consumer accepts a FLAGGED FORWARD jump and baselines on it on BOTH dedup paths (the supersession is explicit and broker-authored \u2014 B2-sound, nothing silently skipped); FORWARD-ONLY, binding: a flagged frame at/below the cursor dedup-drops exactly as today, never a backward baseline; an UNFLAGGED jump keeps strict reject-gap byte-for-byte; baseline_next_output STAYS as the old-broker x new-client cold-attach compat path (retirement is an N-2 seed at most); N-1 matrix published: new broker x old client = a resize in the mixed-version window ends the attach with a marked truncation (honest termination, strictly better than the pre-fix silent corruption; release notes carry 'update CLI and broker together'), old broker x new client = no flag arrives, strict paths byte-identical (unit-pinned as the N-1 leg); REJECTED shapes recorded in ADR-0031 Amendment II so none is re-proposed: unconditional baseline-on-jump, empty-frame delivery through the window, seq rebase, cold-reattach-on-second-gap; (4) abort takes the same uniform path minus the size frame, at the old geometry; (5) the log records the presentation floor (seq after last commit/abort) and a resume-from-floor asking BELOW it is served the cold-attach shape (sync repaint + skip to live), never a raw replay across the boundary \u2014 at-least-once preserved in effect because the repaint supersedes the skipped range. NON-GRID BYTE DISPOSITION (enumerated at triage, every class ruled): tracked-by-grid state (title OSC 0/2, alt screen 47/1047/1049, DECTCEM 25, DECSTBM, pen, cursor) = SUPERSEDED by the repaint by construction, unit-pinned per class; untracked STATEFUL sequences (bracketed paste 2004, mouse 9/1000/1002/1003 + encodings 1005/1006/1015, focus 1004, DECCKM 1, DECAWM 7, keypad ESC=/ESC>, DECSCUSR, OSC color 4/10/11/12/104/110/111/112, charset designation), ONE-SHOT events (BEL, OSC 52 clipboard, OSC 9/777 notifications) and child->client QUERIES (DA1/DA2, DECRQM, XTWINOPS, OSC 10/11 '?') = DEFERRED verbatim in emission order, flushed inside the sync frame (a dropped toggle is indefinite divergence, a dropped query can hang a waiting child; a deferred one is bounded-window latency); cell-scoped decorations the grid does not carry (OSC 8 hyperlinks, DCS graphics) = DROPPED as a balanced class (content superseded by the repaint; an unbalanced deferred open would decorate unrelated post-repaint text; matches the repaint's existing fidelity boundary). DSR is in NO class: the drain strips + answers it pre-append (REQ-DSR-SINGLE-CPR); its mid-transition CPR reports the pre-transition cursor \u2014 accepted bounded residual, recorded not built-around. MECHANISM CONSTRAINT, binding: classification runs in the ONE parser authority \u2014 ScreenGrid's vte::Perform in a capture mode during held-byte replay, re-encoding unconsumed sequences from callback parameters; NEVER a second scanner over raw bytes (two parsers = two truths about sequence boundaries). SIDE EFFECT, must be recorded when built: the commit-time sync frame IS the seeded 'push a fresh repaint to transition-era attachers' follow-up \u2014 the cold-attach-during-transition residual's 'not bounded in VISIBILITY' caveat dies (the stale window now ends at commit); update the KNOWN ACCEPTED RESIDUAL text at repaint_initial and in REQ-RC-RESIZE-GEOMETRY-EPOCH accordingly. RESIZE_HOLD_CAP overflow now also means suppressed-never-delivered bytes: the sync repaint after a partial parse leaves client and grid sharing the same self-healing partial view \u2014 strictly more consistent than shipped; keep the overflow loud. BINDING CONSTRAINTS CARRIED FORWARD from the predecessor: NO whitespace special-casing, NO extra clears (one geometry authority for placement AND erasure \u2014 a fix that passes by clearing harder fails gate); fixture rules verbatim (readable prose, repeated words, every cell incl. expected-blank interiors, full-row untrimmed equality, no reflow-crossing at the resize instant). FENCED SEPARATE, do not build in this wave: the heuristic epoch-split strengthening (mark_resize_issued precedes session.resize; the 20ms-quiet/250ms-cap split is a heuristic, RESIZE_SETTLE_CAP admits old-geometry bytes may land in the new epoch) \u2014 only if the field stays red after the fanout repair. Gate: doc \u2014 ADR-0031 Amendment II states the presentation contract incl. the byte-class disposition table; impl \u2014 suppression + commit-time sync frame + watermark cursor advance + presentation floor in OutputLog/append/commit_resize/abort_resize; unit \u2014 per-class disposition pins (a title change, cursor-visibility toggle, DECSTBM change, alt-screen switch, AND a bracketed-paste/mouse toggle inside the suppressed window each surface in or after the sync frame; a hyperlink pair does not), the watermark advance (a resumed controller never re-receives a suppressed seq), the eviction-deadline exemption, the abort path, AND the wire-flag matrix (flagged-forward accepted + baselined on both dedup paths; flagged-backward dedup-dropped unchanged; unflagged-forward strict reject-gap byte-for-byte; old-broker wire without the key defaults false and a cold attach still baselines via baseline_next_output); int \u2014 the REAL live controller writer driven across a transition: an attached controller whose socket feed is parsed by an independent terminal authority at the CLIENT TARGET geometry, full untrimmed row equality every row after the sync frame, every cell incl. blanks; a viewer leg asserting size-frame-before-sync-frame and zero raw frames between commit and sync; a resume-across-resize leg (controller detaches pre-resize, resumes post-commit, receives zero mixed-geometry raw bytes and lands exact at target geometry). The cold-attach oracle is insufficient by construction and does not satisfy the int stage.",
    "docB": "the presentation barrier over live delivery: during a resize transition the ring keeps recording but controller and viewer delivery is suppressed; commit lands the grid at the target geometry and pushes ONE synthesized sync frame (repaint ++ deferred non-grid bytes) to every attached sink under the same OutputLog serialization before raw fan-out resumes; the cursor-of-record advances past suppressed frames as-if-written; resume never raw-replays across a transition boundary."
  },
  {
    "pair_num": 145,
    "idA": "REQ-PICKER-PURGE-STRUCTURED",
    "titleA": "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.",
    "docA": "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",
    "idB": "REQ-RC-RECONNECT",
    "titleB": "B-3 (REMOTE-TRUTH triage \u00a7B-3, the operator-asked UX): the rc attach viewport RECONNECTS on a severed transport instead of print-and-exit. Pre-fix rc was one-shot (resolve\u2192dial\u2192attach\u2192pump\u2192parting line); FAULT-MATRIX row 9 over-promised. FIX: the establish sequence (daemon ensure \u2192 broker conn \u2192 session resolve local-first/cross-node \u2192 dial \u2192 attach-open w/ A-4b tracing retry \u2192 subscribe) is factored into establish_attach and run_attach_inner wraps establish+pump in a loop. RECONNECTABLE class = severed transport ONLY: PumpEnd::BrokerGone (broker-conn EOF class, broker bounce) + the NEW PumpEnd::Severed (serve-side stream EOF AFTER rendered output = remote conn drop \u2014 pre-fix MISLABELED as 'detached \u2014 still running'; a nothing-rendered EOF stays the honest NoLiveSession refuse). FINAL ends (Exited/Detached/Displaced/Stalled/NoLiveSession) never re-drive \u2014 re-attaching a deliberately-ended session is wrong. On sever: full-screen centered 'Reconnecting to {target}\u2026' banner (pure byte-emit like StatusRow; target = owning-node label or 'local daemon'; Q4 UX rule \u2014 operator language, internal sever detail never paints), then re-drive establish_attach every RECONNECT_PAUSE (1s) inside RECONNECT_WINDOW (30s, generous for a daemon bounce); a Detach keypress mid-window aborts honestly to [detached]; window expiry \u2192 PumpEnd::ReconnectGaveUp with a plain-language give-up line naming the cause, the window, and the retry action (never op/read-err lingo). Per re-establish: fresh OpMinter (ADR-0034 rc tracing per viewport), fresh initial resize (PTY matches the CURRENT terminal), pump-local render cursor resets so the re-serve ring replay REPAINTS the screen the banner cleared. FAULT-MATRIX row 9 made TRUE (F-3), not edited down. Red-first: serve-EOF-after-render \u2192 Severed (vs the pre-fix false Detached); only BrokerGone/Severed classify Reconnect.",
    "docB": "| # | Fault injected | Blast radius (by design) | Survives untouched | Recovery | Evidence | |---|---|---|---|---|---| | 1 | **Brain killed mid-PTY-stream** (logic crash / routine self-update) | Logic halts until restart | Broker, PTY child (pid stable), output log | New brain re-subscribes from cursor; gapless + exactly-once | `spt-daemon/tests/` B2/B9 handoff suite; `tests/idempotent.rs` (crash before-intent / before-effect / after-effect) | | 2 | **Brain killed mid-QUIC-stream, receive side** | Logic halts | Broker-owned endpoint, conn, stream, read ring | Resubscribe from durable cursor; r"
  },
  {
    "pair_num": 146,
    "idA": "REQ-RC-RESIZE-GEOMETRY-EPOCH",
    "titleA": "A resize transition is ATOMIC from ScreenGrid's perspective: every byte is parsed at the geometry it was EMITTED under. (hertz v0.39.0 field RCA 2026-07-21, doyle re-grounded at source the same hour; P0 \u2014 this falsifies a SHIPPED v0.39.0 release claim, so it is a regression, not new scope.) SYMPTOM: right-margin fragments and left-shifted rows on a node where CLI and broker are BOTH 0.39.0 \u2014 i.e. exactly the defect the v0.39.0 notes claim fixed. SOURCE (statically provable, no repro box required): broker.rs dispatch_resize calls session.resize(SurfaceSize::new(rows, cols)) (~4557-4559) and only THEN recover_log(&log).set_size_and_notify(rows, cols) (~4560), which reaches self.grid.resize(rows, cols) (~1492) \u2014 TWO SEPARATE recover_log acquisitions, so the reader thread's append/parse interleaves between them BY CONSTRUCTION. On ConPTY the resize ITSELF emits a full repaint asynchronously; the reader parses that repaint into the grid at the OLD geometry; the subsequent top-left-preserving grid.resize then faithfully preserves an ALREADY-MISWRAPPED model; the cold attach repaint emits the shift. WHY THE EXISTING TESTS PASS: the spt-term width oracle (REQ-SCREENGRID-WIDTH's int stage) is green 5/5 while the field is red \u2014 it never crosses the ConPTY-emission -> grid-geometry -> attach-repaint seam, and the surface mock structurally cannot. The W3 width model is therefore NOT falsified; it is correct work running under a broken geometry transition. FIX \u2014 and the record must carry WHY the obvious fix is wrong: merely REORDERING (set grid geometry before invoking the ConPTY resize) is INSUFFICIENT. It is safe on a GROW and unsafe on a SHRINK, because bytes already in flight at the old WIDER geometry are then parsed at the new narrow width and wrap early. Neither pure ordering is correct because the defect is not the ORDER \u2014 it is that the grid has no notion of 'these bytes were emitted under the old geometry'. Required shape: quiesce/gate the drain across the ordered surface+grid transition AND carry an explicit geometry EPOCH so parse always reckons at the emitting geometry, with rollback/refetch of the grid geometry if the surface resize fails. Gate: doc \u2014 the epoch/barrier contract stated where the resize path is documented, including the shrink counter-example so the insufficient reorder is not re-proposed; impl \u2014 barrier + geometry epoch + failure rollback in the dispatch_resize/set_size_and_notify path; unit \u2014 a resize whose repaint bytes arrive mid-transition is parsed at the emitting geometry (both grow AND shrink), and a failed surface resize leaves the grid geometry unchanged; int \u2014 Windows REAL ConPTY (not the surface mock): resize emits a dense frame, then the synthesized attach repaint is compared against an INDEPENDENT terminal authority, never ScreenGrid against itself. FIXTURE AMENDMENT (hertz field supplement 2026-07-21 second capture, doyle-accepted, carried by todlando at build): the dense frame is ordinary readable PROSE with REPEATED WORDS and punctuation, not sparse synthetic markers \u2014 the field captures are semantic corruption of rendered content (`ReQ2`, `ROADMAPnsayh'credertigls`, `Recommend:gdirect REST`, `thii reqw st`, `resdluuion`: clobber, merge, substitution and displacement of width-1 cells MID-ROW), and repeated words are what make a displaced fragment land somewhere that still looks plausible \u2014 the shape that let this read as cosmetic. The leg must fail on DELETION, INSERTION, SUBSTITUTION, MERGE or DISPLACEMENT of any width-1 cell, with NO normalization and NO presence-only assertions (full-row untrimmed string equality plus equal row counts satisfies this by construction). CONSTRAINT, binding: the fixture stays inside the region where the authority emulator and ConPTY AGREE \u2014 the screen at the resize instant carries no line reaching either margin and loses no row, so reflow-vs-truncate is a no-op on it, and the wrap-crossing prose is emitted AFTER the transition opens, at the new geometry. A fixture crossing the margin at the resize instant would red on emulator POLICY DIVERGENCE rather than on this defect; pinning the wrap policy between authority and ConPTY is separate work and must not be smuggled in here. The comparison is EVERY CELL, including cells expected to be BLANK \u2014 not row starts, right margins, or text presence. The dense frame must include erased interior spans and repeated spaces, and those cells must still be blank after the synthesized repaint. (hertz field supplement 2026-07-21, doyle-accepted: the operator confirmed a THIRD corruption shape on the same fully-0.39.0 node \u2014 stale characters surviving INSIDE nominal whitespace between words, e.g. `isnsettled`, `fulllyacaptured`. A mis-widthed repaint places glyphs into wrong interior cells; later differential output and EL operations address the CORRECT geometry and so do not necessarily clear them; the synthesized cold repaint then faithfully re-emits the contaminated interior. Accepted as further evidence for THIS requirement, not a new root, on a DISCRIMINATION rather than the prediction: the captured debris is pure ASCII, every glyph width-1, so no width-table or wide-half defect can produce it \u2014 and the erase paths are already width-normalized on both halves at screen.rs clear_cell_keep_pen ~416/~420, the span helper ~440-443, and delete/insert_chars ~617-620, with erase_across_a_wide_half_leaves_no_orphan ~1391 passing. Word-scale `isn`+`settled` collapsing across a space is the same shifted-by-N placement as the whole-row left shift, observed at word scale. OPEN DISCRIMINATOR: interior debris in a session where NO resize occurred at any point would mean a SECOND root that this fix will not clear.) BINDING CONSTRAINT on satisfying the blank-cell assertions: NO whitespace special-casing and NO extra clears \u2014 the barrier must restore ONE geometry authority for glyph placement AND erasure. A fix that passes by clearing harder satisfies the test and leaves the defect. KNOWN ACCEPTED RESIDUAL (doyle ruling 2026-07-21): a cold attach landing DURING a transition renders the PRE-TRANSITION screen \u2014 the held bytes are not parsed yet. Accepted because it is a strict improvement over the shipped behavior on the same input (pre-fix: miswrapped/corrupt; post-fix: stale but coherent). The window is bounded in TIME (settle + one surface call) AND \u2014 since REQ-RC-RESIZE-PRESENTATION-BARRIER (built 2026-07-21) \u2014 in VISIBILITY: the commit/abort-time sync frame delivers every attached sink, transition-era attachers included, the fresh repaint the moment the barrier tears down, quiet child or not (the successor REQ's commit-time sync frame IS the follow-up seed this residual carried, absorbed by construction). The attach itself still renders the pre-transition screen for the width of the window. REFUSED, so nobody builds it later without a fresh argument: making the attach repaint WAIT on an in-flight transition \u2014 that puts a user-facing attach behind the settle cap / serialize wait.",
    "docA": "the resize barrier + geometry epoch on the dispatch_resize / set_size_and_notify path: hold the grid parse across the ordered surface+grid transition, tag held output with the geometry it was emitted under, replay it at that geometry, and roll back on a failed surface resize. Includes the SHRINK counter-example that rejects the reorder.",
    "idB": "REQ-SUBNET-8",
    "titleB": "Status render honesty: zero-subnet text is daemon-aware ('No subnets registered \u2014 this node is standalone.' + daemon-running-dependent blurb, never implying messaging works while the daemon is down); hint footer prints on bare spt subnet only (status drops it); a stalled pump is surfaced in subnet status, never rendered implied-healthy (M8 decisions 11-12, 23)",
    "docB": ""
  },
  {
    "pair_num": 147,
    "idA": "REQ-ACL-DISCOVER-GATE",
    "titleA": "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.",
    "docA": "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 ",
    "idB": "REQ-PUMP-STAGE-TRUTH",
    "titleB": "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).",
    "docB": "Decision"
  },
  {
    "pair_num": 148,
    "idA": "REQ-EP-6",
    "titleA": "Gateway type acceptance: a Gateway-typed perch binds (api bind --type, open type system \u2014 un-hardcode the live_agent default), advertises/addressable like any endpoint, owns shells (owner validation not agent-family-gated), subscribes to digests, and is the user-msg identity gate's user-backed origin (REQ-MSG-5); in-tree mock-gateway fixture (R-DOCS-2 pattern, no downstream adapter code). Cross-node WAN Gateway-origin (registry endpoint_type trust) tracked by REQ-MSG-6",
    "docA": "Gateway** (concept ratified 2026-06-11; registered via the open type system, first instance downstream): A **human-backed endpoint** \u2014 a user's specialized window into the subnet from a device or surface with no conventional-harness compatibility. Nothing LLM-shaped runs there; the intelligence at the endpoint is the **user**. Addressable like any endpoint (receives digests/messages, sends via the normal verbs) and may **own Shells** (it is an owning endpoint \u2014 see \u00a7Shell model). Distinct from a Shell: a Shell is *driven from elsewhere*; a Gateway *originates* interaction. No `tracked/` mind, ",
    "idB": "REQ-SUBNET-3",
    "titleB": "Node labels: hostname-default, gossiped, addressable in @node qualifiers (refuse-on-ambiguity)",
    "docB": ""
  },
  {
    "pair_num": 149,
    "idA": "REQ-HAZARD-REGISTRY-STALL",
    "titleA": "REGISTRY-LIFECYCLE W1 (KNOWN-HAZARDS 7.44, hertz post-close v0.36 RCA \u2014 the umbrella conformance seam): streams and seats on a long-lived connection have BOUNDED lifetime. The hertz regression seam verbatim: real long-lived pump + dispatcher integration, N registry-only rounds over ONE persistent connection, asserting (a) dispatcher-eligible Registry rows plateau O(active) not O(N); (b) physical stream/subscriber/seat counts plateau after completion CROSS-FAMILY (sync/update seats included, not just Registry); (c) snapshot writes O(feeds) not O(chunks x record-kinds); (d) brain refresh produces ZERO historical Registry replay subscriptions; (e) zero CONN_WRITE_POISONED / replay-write-failed events; (f) broker thread count returns to a bounded baseline. HEAVY nextest group at birth (spawns a real daemon tree). Binding: redispatch D1/D1b + REDISPATCH-STALL T1-T7 + mesh-recovery legs green every leg (retire machinery + registry gate = substrate). Gate: int \u2014 the seam above; doc \u2014 KNOWN-HAZARDS 7.44.",
    "docA": "7.44 Streams and seats on a long-lived connection must have bounded lifetime \u2014 one-way rows terminal at FIN, seats released at serve completion, no per-chunk full-state rewrites in a drain loop `[REQ-HAZARD-REGISTRY-STALL]` Failure (paid-for, hertz post-close v0.36 field RCA 2026-07-17 \u2014 live prod box):** FOUR compounding consequences of \"physical teardown waits for conn close\" on connections that never close. The registry pump opens one fresh stream per feed (~30s) on the persistent pump conn; nothing retires the rows (the dispatcher \u2014 sole `retire_stream` caller \u2014 skips `initiated_locally`) ",
    "idB": "REQ-RC-1",
    "titleB": "`spt rc <id>` \u2014 user CLI attaching a local terminal to a broker-held PTY, reusing the cross-node attach machinery (attach.rs request_attach \u2192 send_attach_input pump, spt-net AttachRecord codec); local attach is the degenerate single-node case of the cross-node path (rides REQ-TERM-3 byte-stream streaming). Read-only `--view` (watch, no stdin forwarded). Clean detach that does NOT terminate the broker-held session (KNOWN-HAZARDS: PTY ownership stays with the broker; no termination on detach). Explicit detach keybind that cannot collide with harness passthrough input (legacy capsule used a ctrl-b prefix); documented. ConPTY DSR auto-answer in the attach reader (hazard 5.5).",
    "docB": ""
  },
  {
    "pair_num": 150,
    "idA": "REQ-NOTIF-SCOPE",
    "titleA": "Notif scope is a per-row producer-chosen attribute node|subnet: node-scoped rows (update/consent/rollback kinds) live and die on their node and NEVER enter a replication feed; subnet-scoped rows keep the full ADR-0007 machinery (replication, cross-node dismiss, semilattice join); scope determines the first-fire candidate set (node-scoped targets the most-recently-active endpoint ON that node). New row fields additive serde-defaulted (host_binaries pattern) \u2014 old peers parse clean, schema regen drift-gated",
    "docA": "1. Scope is a per-row, producer-chosen attribute: `node` | `subnet`",
    "idB": "REQ-SUBNET-5",
    "titleB": "Per-subnet serve-state: spt subnet detach <NAME> [--save] / attach <NAME> [--save] \u2014 daemon keeps running, stops/starts advertising + connecting for that subnet (peer pump + responder selective); --save persists the startup default in daemon config; the all-attached banner gains per-subnet states (M8 decision 6, --save renamed from --auto per decision 25 session)",
    "docB": ""
  }
]