### region 1 (read, ~2185 tok)

[deelevate.rs#30C0]
570:    ///
571:    /// `Ok(None)` = no de-elevation target (not elevated, no desktop shell —
572:    /// headless/service session — or the shell itself runs elevated): the
573:    /// caller falls back to its normal spawn. `Ok(Some(pid))` = the
574:    /// de-elevated child is running. `Err` = a target existed but the spawn
575:    /// failed.
576:    ///
577:    /// KH 5.6 holds by construction: `CreateProcessWithTokenW` never
578:    /// inherits handles (the API has no inherit flag and runs the child in
579:    /// a fresh handle table), so no captured caller's pipe can wedge on the
580:    /// immortal child.
581:    // [impl->REQ-HAZARD-ELEVATED-DAEMON-SPAWN]
582:    pub fn spawn_deelevated(program: &str, args: &[String]) -> io::Result<Option<u32>> {
583:        if !is_elevated() {
584:            return Ok(None);
585:        }
586:        let Some(shell) = shell_primary_token()? else {
587:            return Ok(None);
588:        };
589:        // A shell running ELEVATED (admin desktop session, UAC off) is no
590:        // de-elevation target — spawning under it would reproduce the bug.
591:        if token_is_elevated(shell) {
592:            unsafe { CloseHandle(shell) };
593:            return Ok(None);
594:        }
595:        let result = create_with_token(shell, program, args).map(Some);
596:        unsafe { CloseHandle(shell) };
597:        result
598:    }
599:
600:    /// Whether an elevated `daemon run` here WOULD respawn de-elevated and
601:    /// detach (vanish into the background) — i.e. a real de-elevation target
602:    /// exists: elevated AND a desktop shell token exists AND that shell is
603:    /// itself unelevated. `false` when not elevated, when there is no desktop
604:    /// shell (a headless / service session — e.g. an elevated CI runner), or
605:    /// when the shell is itself elevated (a uniformly-elevated universe): in
606:    /// those cases `run` serves elevated in the foreground consistently rather
607:    /// than vanishing. Mirrors [`spawn_deelevated`]'s `Ok(Some)` condition
608:    /// WITHOUT spawning, so `daemon run` can refuse only the vanishing case
609:    /// (REQ-DAEMON-7).
610:    // [impl->REQ-DAEMON-7]
611:    pub fn has_deelevation_target() -> bool {
612:        if !is_elevated() {
613:            return false;
614:        }
615:        match shell_primary_token() {
616:            Ok(Some(shell)) => {
617:                let elevated = token_is_elevated(shell);
618:                unsafe { CloseHandle(shell) };
619:                !elevated
620:            }
621:            _ => false,
622:        }
623:    }
624:
625:    /// The desktop shell process's token as a PRIMARY token, or `Ok(None)`
626:    /// when no shell window exists (headless / service session).
627:    fn shell_primary_token() -> io::Result<Option<isize>> {
628:        unsafe {
629:            let hwnd = GetShellWindow();
630:            if hwnd == 0 {
631:                return Ok(None);
632:            }
633:            let mut pid: u32 = 0;
634:            GetWindowThreadProcessId(hwnd, &mut pid);
635:            if pid == 0 {
636:                return Ok(None);
637:            }
638:            let process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid);
639:            if process == 0 {
640:                return Err(io::Error::last_os_error());
641:            }
642:            let mut token: isize = 0;
643:            let ok = OpenProcessToken(process, TOKEN_DUPLICATE, &mut token);
644:            CloseHandle(process);
645:            if ok == 0 {
646:                return Err(io::Error::last_os_error());
647:            }
648:            let mut primary: isize = 0;
649:            let ok = DuplicateTokenEx(
650:                token,
651:                TOKEN_QUERY
652:                    | TOKEN_DUPLICATE
653:                    | TOKEN_ASSIGN_PRIMARY
654:                    | TOKEN_ADJUST_DEFAULT
655:                    | TOKEN_ADJUST_SESSIONID,
656:                std::ptr::null_mut(),
657:                SECURITY_IMPERSONATION,
658:                TOKEN_PRIMARY,
659:                &mut primary,
660:            );
661:            CloseHandle(token);
662:            if ok == 0 {
663:                return Err(io::Error::last_os_error());
664:            }
665:            Ok(Some(primary))
666:        }
667:    }
668:
669:    /// Whether an arbitrary token is elevated (the shell-token sanity gate).
670:    /// A failed probe reads `true` — fail toward "no de-elevation target"
671:    /// rather than spawning under a token we could not vet.
672:    fn token_is_elevated(token: isize) -> bool {
673:        unsafe {
674:            let mut elevated: u32 = 0;
675:            let mut ret_len: u32 = 0;
676:            let ok = GetTokenInformation(
677:                token,
678:                TOKEN_ELEVATION,
679:                &mut elevated as *mut _ as *mut core::ffi::c_void,
680:                std::mem::size_of::<u32>() as u32,
681:                &mut ret_len,
682:            );
683:            ok == 0 || elevated != 0
684:        }
685:    }
686:
687:    /// `CreateProcessWithTokenW` under the same flags as
688:    /// `detached_no_inherit`, command line quoted by the shared
689:    /// [`quote_windows_arg`] rules. The environment is BUILT FOR THE TOKEN
690:    /// (`CreateEnvironmentBlock`) — inheriting the elevated caller's
691:    /// variables would point the child at the wrong `%LOCALAPPDATA%`
692:    /// universe when UAC elevated through a different admin account. A
693:    /// failed block build falls back to inheriting ours (same-account
694:    /// elevation resolves identically there).
695:    /// The invoker's explicit universe overrides that must survive the
696:    /// de-elevation respawn. `SPT_HOME` is the relocation knob
697:    /// (`spt_store::perch::spt_home`): when set, it selects the daemon's home
698:    /// (and therefore its seed-control socket) over `%LOCALAPPDATA%`, so it
699:    /// must ride through the rebuilt desktop-user environment block. Empty in
700:    /// the common case (no override) — the block then passes through untouched.
701:    fn deelevation_env_overrides() -> Vec<(String, String)> {
702:        match std::env::var("SPT_HOME") {
703:            Ok(v) if !v.is_empty() => vec![("SPT_HOME".to_string(), v)],
704:            _ => Vec::new(),
705:        }
706:    }
707:
708:    /// Copy a `CreateEnvironmentBlock` result (a double-null-terminated UTF-16
709:    /// run) into an owned `Vec<u16>` including its terminator, so it can be fed
710:    /// to the pure [`super::apply_env_overrides`].
…
837:    fn parse(block: &[u16]) -> Vec<String> {
…
840:        for &u in block {
841:            if u == 0 {
842:                if cur.is_empty() {
843:                    break;
844:                }
845:                out.push(String::from_utf16_lossy(&cur));
846:                cur.clear();
847:            } else {
848:                cur.push(u);
849:            }
850:        }
851:        out
852:    }
853:
854:    // [unit->REQ-HAZARD-ELEVATED-DAEMON-SPAWN] the de-elevation env overlay:
855:    // an explicit SPT_HOME REPLACES the rebuilt desktop-user value (so the
856:    // respawned daemon binds the invoker's home's seed-control socket, not the
857:    // default home's — the DAEMON_NOT_RUNNING acceptance bug), matches the env
858:    // name case-insensitively, appends when absent, and leaves the block intact
859:    // when there are no overrides. The result is always double-null-terminated.
860:    #[test]
861:    fn env_overlay_keeps_explicit_spt_home_alive() {
862:        // Replace: desktop block already has SPT_HOME (the default home).
863:        let b = block(&["PATH=C:\\win", "spt_home=C:\\default"]);
864:        let out = apply_env_overrides(
865:            &b,
866:            &[("SPT_HOME".to_string(), "C:\\accept".to_string())],
867:        );
868:        let got = parse(&out);
869:        assert!(got.contains(&"PATH=C:\\win".to_string()));
870:        assert!(got.contains(&"SPT_HOME=C:\\accept".to_string()));
871:        // exactly one SPT_HOME entry (the old, case-variant one is gone).
872:        assert_eq!(
873:            got.iter()
874:                .filter(|e| e.to_ascii_uppercase().starts_with("SPT_HOME="))
875:                .count(),
876:            1
877:        );
878:        // double-null terminated.
879:        assert_eq!(&out[out.len() - 2..], &[0u16, 0u16]);
880:
881:        // Append: block has no SPT_HOME.
882:        let b = block(&["PATH=C:\\win"]);
883:        let out = apply_env_overrides(
884:            &b,
885:            &[("SPT_HOME".to_string(), "C:\\accept".to_string())],
886:        );
…
892:    }

### region 2 (job, ~20 tok)

## Still Running (1)

- `ShortformDifferential` [task] — ShortformDifferential

### region 3 (ask, ~28 tok)

User answers:
gaki_shortform_receipt: Never received
tag_position: End of final reply
spt_home_shape: Not set

### region 4 (grep, ~5942 tok)

# docs/
## KNOWN-HAZARDS.md#8FD5
 45:### 2.1 Parent PID over ephemeral poll PID
*46:- **Failure:** orphan check polls an ephemeral listener PID; it dies and is recycled (esp. Windows); a foreign process with the recycled PID reads as alive → false-positive teardown (or false-negative).
 47:- **Invariant:** prefer the stable harness-session PID (`parent_pid`) over any ephemeral process PID for liveness; minimal `info.json` for supervisor-owned perches to avoid stale leaks.
 48:- **spt-core mapping:** session binding (parent-process-tree anchor) still applies for harness-hosted topology. For spt-hosted sessions the broker holds the child directly → liveness is the broker's held-handle state, more reliable than PID polling.
 49:- **Sister cite:** `src/live/wrapper/orphan.rs:141-161`; CHANGELOG v1.11.20.
...
 86:### 3.2 Stale signoff sentinel must not kill a fresh start
*87:- **Failure:** a leftover `.claude/<id>-signoff.md` from a prior session is read by a fresh listener as a live signoff → immediate teardown.
 88:- **Invariant:** on every listener/daemon spawn, sweep stale signoff sentinels; signoff files are write-once per generation.
 89:- **spt-core mapping:** same sweep on daemon (re)start per hosted instance.
 90:- **Sister cite:** CHANGELOG v1.11.20; `src/owl/cleanup.rs:97`.
...
 281:- **Invariant:** every `info.json` writer serializes under the one per-perch `.info.lock`. A whole-record write (`write_info`) takes the lock exactly as the RMW (`mutate_info`) does — a unique tmp only makes concurrent writes *last-writer-wins*, which is safe ONLY if the writers are serialized (cross-ref 5.15). A multi-step read→check→write (bind's establish) must hold ONE lock acquisition across all three (a true compare-and-set) — check-then-write with the lock dropped in between still interlea...
*282:- **spt-core mapping:** `spt_store::info` — `write_info` now acquires the sentinel then calls a private `write_info_unlocked` (and `mutate_info` — the public RMW primitive — calls the unlocked writer while holding its own lock, so no double-lock deadlock); `info::establish_locked(perch, build)` runs the bind's read→check→build→write as one locked CAS, called by `spt::api::startup::establish_perch`. The other read-modify-write callers were audited and the two that mutate a load-bearing field w...
 283:- **Source:** RCA 2026-07-01 (doyle gate rig) — differential (baseline PASS / 77beeac+baseline-atomic PASS / fe385f5 2/3 FAIL) isolated the atomic rework as the delta; `reconcile_hosted_liveness` emitting ZERO `LIVENESS_RECONCILE_OFFLINE` lines for the dead victim over 20s pinned the silent-skip. The unique-tmp fix worked (bringup + serve fine) but exposed this deeper pairing.
 284:
 285:---
...
 658:- **Failure (paid-for, perri field finding 2026-07-08 — 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` — 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 → the project-context content permanently lost. perri's two-sliced echo-commune INGESTED (file deleted) yet never surf...
*659:- **Invariant:** delete-after-commit, per slice. Distinguish a write **SUPPRESSED by precedence** (incoming older than durable → already superseded → safe to delete) from a slice **un-committable now** (empty `project_id`, or any write error → the `?` retry path). An un-committable non-empty project slice is preserved as a **project-only pending slice under the COMMUNE suffix, whatever the source drop's kind** — a signoff-suffix pending would be reaped by `sweep_stale_signoff` on the next listene...
 660:- **spt-core mapping:** `spt-live/src/ingest.rs` `ingest_drops` (deferred-project preserve + `Ingested.preserved`); `route_slices` project gate unchanged (`REQ-STORE-CONTEXT-BRANCH-FILL`); kin `REQ-HAZARD-DROP-FILE-SINGLE-WRITER` (core stays the single writer — the preserve rewrite is core's own write).
 661:- **Source:** MSG-IDENTITY W3 (doyle triage, code-grounded ingest.rs:156 gate vs :200 delete; repro fixture F-032-commune-2026-07-08T222721Z.md).
 662:
...
 719:| 7.14 | `EffectJournal::apply_once` releases its lock ACROSS `effect()` (reserve→release→run→finalize), never holding it across the blocking PTY write; `EffectKind::PtyWrite` is ephemeral (no per-keystroke fsync, in-memory dedup), durable kinds keep fsync — fixes interactive stutter + the hard input wedge (`brain IPC read deadline`) | `effect.rs` `apply_once`/`is_durable`, int `inject_control_wedge.rs` |
*720:| 7.15 | `reconcile_hosted_liveness` clears stale `driven_by` when it offlines a sessionless controllable perch (no live broker session) — an OFFLINE endpoint never renders phantom `ONLINE+CONTROLLED`; race-free (no session ⇒ no concurrent broker re-stamp). `controller_by==None` is ambiguous (local controller reads None) so it is NOT a clear trigger; the idle wedged-remote leg is deferred (`REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT`) | `livehost.rs` `reconcile_hosted_liveness`, int `driven_by_selfheal.r...
 721:| 7.16 | `spt rc` on Windows reads crossterm KEY EVENTS and translates to standard xterm VT (arrows/Home/End/PgUp/Dn/Ins/Del/F-keys + modifiers reach the harness; agnostic, NOT win32-input-mode); `ctrl-b d` detach preserved event-sourced; non-tty + Unix keep the byte path; supersedes the 7.13 byte-swap | `rc.rs` `translate_key_event`/`key_event_step`/`spawn_stdin_reader_events`, unit-only (live = HITL) |
 722:| 7.17 | PTY **input** is single-writer: each session spawns ONE dedicated input-writer thread = the SOLE caller of the blocking `write_input`, fed by a bounded FIFO (`sync_channel`). Every caller (`dispatch_input`, `dispatch_endpoint_input`, the inject worker/floor flush) ENQUEUES (`try_send`) + returns at once — a paste burst that fills the harness input buffer parks only that thread, never the broker dispatch thread. Full queue ⇒ DROP excess + stamp the perch `input_backpressure` (heal-on-resume: ...
 723:| 7.18 | `spt rc` paste is **client-originated** on Windows: CC runs daemon-side with no access to the operator's LOCAL clipboard. On a RIGHT-CLICK rc reads the local clipboard itself and injects a BRACKETED paste (`ESC[200~` + content + `ESC[201~`) — CC has bracketed-paste mode on (`ESC[?2004h`), so a multi-line paste lands intact with NO `\r` submit-storm, harness-agnostic, content VERBATIM. RawGuard captures the mouse (disables console QuickEdit so right-click reaches the app) on an interactive cons...
## BROKER-BRAIN-SPLIT-RESTORATION.md#78BB
 72:
*73:- The brain cannot restart onto a new binary without killing the in-process broker thread — which would close every PTY, orphan every harness child, and drop every listening socket. So **the no-endpoint-drop self-update pillar is silently unrealized.**
 74:- `apply` therefore performs an in-process `Brain::handoff` that re-attaches a subscriber within the *same old process* — achieving nothing for a binary swap. `update.rs:234`'s statement that "the live daemon execs the verified new binary" is **aspirational; it was never wired** because the process split it depends on does not exist in production.
 75:- Updates do not run until an unrelated restart/logon (observed live, Section 1).
 76:
## GATEWAY-LIVENESS-DISPATCH.md#97CA
 24:- Fix at the pinned site (most likely: apply the local-roster / self-owned reconcile to the `--json` local-endpoint rows, OR make `resource_projection`'s liveness for a locally-owned row honor the pid-fallback instead of a status-only check).
*25:- Do NOT reach for DEFECT B (forcing status=online for gateways) as the fix — it risks the seed-#5 orphan-listener falsely-ONLINE amplification; the reader-parity is the correct, minimal fix. (If STEP-1 shows the cleanest fix genuinely is establishing status for a relay-holding listener, flag it back to me before taking it — that's a design call, not a silent choice.)
 26:- **DEFECT A:** preserve `prior.rest_state` on re-bind in `establish_perch`'s build (same carry-forward discipline as cwd/controllable).
 27:
 28:## REQs (mint at build-start, activate-don't-pre-fail)
## GATEWAY-RCA-STEP1.md#2F1B
 55:`--json`. Minimal + does NOT touch the advertise or force status=online (avoids the
*56:seed-#5 orphan-listener false-ONLINE amplification you flagged).
 57:
 58:`REQ-HAZARD-BIND-REST-STATE-CARRY` (DEFECT A, confirmed, independent): preserve
 59:`prior.rest_state` on re-bind in `establish_perch`'s build (spt/src/api/startup.rs
## NEXT-MILESTONE-BUG-TRIAGE.md#1CA3
 37:### B-#4 — cross-node online endpoints not detected by `spt rc`  · STATUS: TRIAGE
*38:`spt endpoint list` shows `ball-b ENLYZEAM Active`, but `spt rc ball-b` → `RC_FAIL:ball-b: no live session for endpoint 'ball-b'`. rc resolves only LOCAL live sessions; a cross-node Active endpoint isn't reachable via rc (no remote-attach path wired, or the resolver doesn't route to the owning node).
 39:- Root cause: _TBD_
 40:- Fix approach: _TBD_
 41:
...
 137:
*138:**B-#4 cross-node rc can't find remote live session** · conf H · effort M · MISSING-FEATURE. ROOT: rc.rs:1063-1072 `run_attach_inner` cold-starts the LOCAL broker then `resolve_session` (rc.rs:758-765) queries only `brain.sessions()` (local table) → errors "no live session" (1070-1072) → RC_FAIL (cli.rs:1294-1299). rc always rides loopback (`net_dial_loopback` rc.rs:1083-1085), never consults registry/resolves owning node. `endpoint list` shows it because it reads gossiped registry snapshots. Tran...
 139:
 140:**B-#9/#10 cross-node send SENT(WAN) but no delivery** — TWO issues:
 141:- **(b) PRIMARY DEFECT — SENT(WAN) is optimistic local-buffer ack, not delivery confirmation** · conf H · effort M. ROOT: wansend.rs:138-149 send is fire-and-forget — net_stream_send → SendHalf::write_all (buffers) + finish() (nethost.rs:370-374 quinn finish() = non-blocking, does NOT await peer stopped() ack) → Ok()=bytes-buffered+locally-finished only → prints SENT(WAN) (cli.rs:4187). Receiver `receive_wan` (wan.rs:133-185) COMPUTES Refused/NoPerch/Duplicate/DeliveredTcp/Spooled but **write...
## NEXT-MILESTONE-LIFECYCLETRUTH-TRIAGE.md#DD81
 118:Root (mobile-gw RCA): `api listen --parent-pid N` is auth-anchor ONLY — no liveness watch;
*119:host death orphans the listener forever → perch held alive (false ONLINE), EVENTs stream to a
*120:dead stdout, dead-owner rebind BLOCKED (recorded pid = the live orphan). Fix: listener watches
 121:`--parent-pid` liveness (Windows: job object or poll; Unix: PDEATHSIG or poll) and exits loud
 122:on parent death. flynn's job-object guard (spt-mobile side) stays regardless; filed
 123:SPT-CORE-NEEDS §5.
## REDISPATCH-TRUTH-TRIAGE.md#65D7
 135:- Daemon-lifecycle cluster (endpoint-cycle-honest + comeback + massacre +
*136:  orphan-listener).
 137:- Any change to attach-intent semantics, controller/viewer model, or the
 138:  brain session-cursor resume path (0.30.5/6 fix stands).
## W4-DISPATCH-RULING.md#2289
 2:
*3:**GO todlando.** W4 = spawn/wake + listener lifecycle. Spec = `docs/NEXT-MILESTONE-LIFECYCLETRUTH-TRIAGE.md` §W4 (roots fully pinned) + backlog seeds #5 (listen-orphan) / #7 (dup-resume). Two REQs, both this wave:
 4:
 5:- **REQ-SPAWN-COLLISION-GUARD-LIVE-DUP** — required_stages `[impl, unit, int]`
 6:- **REQ-HAZARD-LISTEN-ORPHAN** — required_stages `[impl, unit]`
...
 21:
*22:The REQ offers "job object / PDEATHSIG or poll." **Ruling: bounded POLL of `--parent-pid` liveness as the portable baseline** (matches the unit "parent-death → listener exits within one poll window"). Reasoning: one code path both platforms, no OS-handle plumbing, and the latency floor (one poll window, seconds) is fine for a headless-gateway orphan — the failure is "held ONLINE for minutes," so seconds-to-exit is a full fix. OS-native tightening (Win job object / Linux PDEATHSIG, immediate exit) is ...
 23:
 24:Note: perri's spt-mobile-side job-object guard (SPT-CORE-NEEDS §5) stays regardless — **independent defense-in-depth**, her side; the core watchdog is the authoritative fix. No conflict, both proceed.
 25:
## W4-GATE-VERDICT.md#5562
 17:- **REQ-SPAWN-COLLISION-GUARD-LIVE-DUP** (`broker.rs`): single-flight wake, **broker-side claim** (ruling 1 — not a perch-record write). `wake_gate_decision` pure truth table (AlreadyLive / Racing / Claim); `dispatch_spawn` reads the live-session check + the `wake_inflight` claim **together under one atomic critical section** (sessions then wake_inflight — wake_inflight is a leaf lock, taken alone only in the RAII drop, no inversion), **no I/O under lock** (cached `process_id`). Serialized so no doub...
*18:- **REQ-HAZARD-LISTEN-ORPHAN** (`startup.rs`): `--parent-pid` **poll watchdog** (ruling 2 — POLL baseline, OS-native optional). 2s poll of `parent_is_gone`, loud `exit(3)` (`EXIT_PARENT_GONE`) so the perch pid dies → liveness flips OFFLINE → dead-owner rebind unblocks. **Persistent-relay branch ONLY** (spawned after the `--once` early return) — verified against every listen e2e (all `--once`) so no test listener is killed. Win reaped-`Child`-holds-handle test gotcha correctly handled (drop before...
 19:
 20:## Verdict
 21:**W4 GATED PASS.** LIFECYCLE-TRUTH W1–W4 all gated. Remaining milestone content: **W5** (delivery integrity — INJECT-MULTILINE-INTEGRITY + IDLE-PARKED-DELIVERY + SPOOL-TAKE-AUDIT) then **W6** (docs). Release counter 49 + W1 field-accept swap window still operator/deployah-gated.
## W4-WAVE-GATE-REPORT.md#2A42
 40:**Root (mobile-gw RCA):** `api listen --parent-pid N` was auth-anchor ONLY — no liveness watch;
*41:host death orphans the listener forever (perch held false-ONLINE, EVENTs stream to a dead
 42:stdout, dead-owner rebind BLOCKED because the recorded pid is the live orphan).
 43:
 44:**Fix (ruling 2 — POLL baseline, OS-native optional):** `spawn_parent_watchdog` (startup.rs)

## adr/
### 0004-single-daemon-broker-brain-split-and-self-update.md#B832
 10:
*11:1. **Consolidation.** The sister project runs poll listeners as ephemeral per-session background tasks and Psyche wrappers as detached per-live-agent supervisor processes. But poll listeners already interact directly with the agent session (capsule/idle), and Psyche wrappers already invoke harness binaries directly. Once the daemon owns every PTY, keeping these as separate processes is unjustified.
 12:
 13:2. **Seamless self-update with a hard no-terminate constraint.** Self-update is a day-one pillar. The constraint: *no endpoint process may terminate or suspend during an spt-core update* — we cannot assume every endpoint can safely suspend. The naive "drain + restart the daemon" approach violates this for spt-hosted sessions (the daemon owns their PTY; killing the daemon SIGHUPs the child).
 14:
...
 32:
*33:- The daemon is the single brain for a machine; crash-recovery and update logic must cover PTYs, networking, registry, spools, listeners, and psyche loops together.
 34:- A small internal broker process exists beneath the daemon — a deliberate, bounded walk-back of "literally one process," preserving B1's *intent* (one network identity, one supervisor, one firewall prompt) while guaranteeing endpoint survival across updates.
 35:- Peer-propagated updates make release signing mandatory, not optional.
 36:- spt-core becomes the update conductor for adapters too; adapter manifests must declare an update avenue.
### 0018-broker-brain-process-isolation-restoration.md#8F34
 13:Consequences of the drift:
*14:- The brain cannot restart onto a new binary without killing the in-process broker thread, which would close every PTY, orphan every harness child, and drop every listening socket. **The no-endpoint-drop self-update pillar is therefore silently unrealized.**
 15:- `spt update apply` performs an in-process `Brain::handoff` that re-attaches a subscriber within the *same old process* — a no-op for a binary swap. `update.rs:233-234`'s "the live daemon execs the verified new binary" is aspirational and was never wired.
 16:- New code does not run until an unrelated restart/logon. Observed live: `enlyzeam` ran 0.3.0 with the valid 0.3.2 binary on disk for ~a day, continuing to reproduce the `\r`-corruption bug the update was meant to fix.
 17:- **REQ-DAEMON-2 and REQ-UPD-3 carry `int` evidence that proves only the in-process handoff shape** (`tests/update.rs`, `brain_swap.rs`, the M3b-B9 daemon E2E) — i.e. the regression is masked in the requirement registry: the tests pass while proving the wrong thing.
### 0027-unbound-endpoint-state-and-attach-on-session.md#87CF
 10:
*11:`spt endpoint run`, for an attach/view bringup, waits for the perch to reach `STATUS_ONLINE` — i.e. for the harness to **bind** — before attaching (`await_endpoint_online`, REQ-HAZARD-RC-ATTACH-ONLINE-RACE). That gate was added because attaching too early lost the handshake three ways (empty ring → EOF, the offline status-gate, no live session yet).
 12:
 13:But the broker creates the **session + PTY + OutputLog at spawn — before bind**. Gating the attach on *bind* (perch online) deadlocks a real case: a harness that shows a startup prompt **before** it binds (e.g. Claude Code waiting for the user to clear an initial prompt) never binds until the operator interacts — but the operator can't see or interact, because nothing is attached until bind. Result: the operator stares at a 25s timeout for a prompt they were never shown. The attachable thing (the bro...
 14:

## design/
### subnet-presence-display.md#CB23
 22:  these — reuse the picker's `is_perch_unbound` signal) → `Active`/`Dormant` (still warm).
*23:- **else** (cold, no live session — but the node is UP, since *this very daemon* is gossiping) →
 24:  **`Suspended`**. **Never `Dormant`** (not warm) and **never `Offline`** (the node is up; a live node
 25:  never self-gossips Offline — `Offline` is what a remote viewer infers when a node stops gossiping).
 26:

# crates/

## spt-daemon/src/
### attach.rs#ED4F
 197:/// session id — names the endpoint and we map it here, never trusting a wire
*198:/// `session_id` it could not have. `None` ⇒ no live session under that endpoint
 199:/// (a stale registry row routed us an attach for something gone → the caller
 200:/// refuses cleanly, D6).
 201:// [impl->REQ-RC-CROSS-NODE-ATTACH]
### consent.rs#0FD2
 45:    /// Default-gated — hold the update until the user confirms. Carries the
*46:    /// resolved session to prompt, or `None` when no live session could be
 47:    /// resolved (the orchestrator then defers / falls back to a node-level surface
 48:    /// rather than applying unprompted — the gate never silently proceeds).
 49:    NeedsConsent(Option<ConsentTarget>),
...
 141:
*142:    // [unit->REQ-UPD-4] no live session ⇒ no target (the gate then defers; it
 143:    // never proceeds unprompted).
 144:    #[test]
 145:    fn no_live_session_resolves_to_none() {
### grants.rs#58BE
 60:    /// most-recently-active session (allow-once / allow-always / deny, D1b).
*61:    /// `None` target = no live session resolved; the caller defers — the gate
 62:    /// never silently proceeds (same stance as
 63:    /// [`ConsentDecision::NeedsConsent`](crate::consent::ConsentDecision)).
 64:    NeedsEscalation(Option<ConsentTarget>),
...
 379:
*380:    // [unit->REQ-CONSENT-1] no live session ⇒ escalation carries None — the
 381:    // caller defers; the gate never silently proceeds.
 382:    #[test]
 383:    fn no_session_escalates_with_none_target() {
### livehost.rs#427B
 692:        // below. `attached_node` derives from these two honest stamps, so clearing
*693:        // both clears it. RACE-FREE: with no live session there is no controller to
 694:        // re-stamp concurrently (the broker single-writer invariant is uncontended —
 695:        // the same argument as the Gap-B driven_by self-heal). A harness-hosted relay
 696:        // (controllable Some(false)) never carries these stamps (no broker PTY to
...
 1255:
*1256:            // No live sessions (the restarted daemon's broker hosts nothing for it).
 1257:            let offlined = reconcile_hosted_liveness(&perch::owlery_dir(), &BTreeSet::new());
 1258:            assert!(
 1259:                offlined.is_empty(),
...
 1275:    // reconcile runs reconcile_hosted_liveness BEFORE reconcile_once, so a
*1276:    // sessionless spt-hosted (controllable) online perch is OFFLINED first and
 1277:    // reconcile_once then never REVIVES its Psyche (the cold-start-after-unclean-stop
 1278:    // phantom). A session-backed perch is left online and IS hosted — proving the
 1279:    // gate is the live broker session, not a blanket boot block. This is the exact
...
 1411:    // [unit->REQ-ENDPOINT-UNBOUND-ATTACH] B2 reconcile stamps an `unbound` skeleton
*1412:    // offline when the broker reports no live session (session death before bind).
 1413:    // The controllable gate is skipped for unbound — bind has not run yet so
 1414:    // `controllable` is absent, but an unbound endpoint is always spt-hosted.
 1415:    // A second unbound perch WITH a session stays unbound (still pre-bind).
...
 2097:    // belts, as a pure matrix. Only a previously-ONLINE, spt-hosted (controllable),
*2098:    // orphaned (no live session), non-relay, DEAD-custody endpoint re-runs; every other
 2099:    // class is excluded, and a LIVE custody pid refuses (the dup guard, not a silent skip).
 2100:    #[test]
 2101:    fn restart_resume_gate_only_reruns_the_orphaned_spt_hosted_dead_set() {
### registryhost.rs#D951
 806:///   refines the *display* of unbound).
*807:/// - **Cold** (no live session, not unbound): this node is nonetheless UP — this
 808:///   very daemon is gossiping the row — so `Suspended` (cold-but-node-up,
 809:///   resumable-on-wake). NEVER `Dormant` (that requires warm and falsely reads
 810:///   ONLINE on a remote picker — the bug this fixes) and NEVER `Offline` (a live
...
 1735:    // removes the false-ONLINE: a live-but-UNBOUND perch is WARM → Dormant
*1736:    // (a routing fallback), but a COLD perch (no live session) reads
 1737:    // Suspended — NOT the old blanket Dormant (which a remote picker mapped to
 1738:    // green ONLINE for a dead endpoint). Offline is never self-gossiped here.
 1739:    #[test]
...
 1778:
*1779:            // Cold via explicit offline status (no live session) ⇒ Suspended,
 1780:            // never the old blanket Dormant (the false-ONLINE source).
 1781:            let cold = mk("cold");
 1782:            info::write_info(

## spt/src/api/
### reporting.rs#56F1
 255:        // F-2 (REQ-SOFT-END-PRESERVES-LIVE-LISTENER): the relay address row is
*256:        // LISTENER-scoped truth, not session-scoped — the poll listener SURVIVES
 257:        // a /clear (whose adapter hook fires this soft end for the DEPARTING
*258:        // session), and only the listener process knows its socket addr, so
 259:        // deleting a live owner's row severs delivery unrecoverably (post-clear
 260:        // sends hit NO_PERCH while the C-2 boundary re-stamp reads healthy).
 261:        // Unregister ONLY a row whose owner is dead/offline per the ONE liveness
...
 1285:    // [unit->REQ-SOFT-END-PRESERVES-LIVE-LISTENER] erase stays unconditional: the
*1286:    // hard wipe orphans any listener by intent, so its row dies with the endpoint.
 1287:    #[test]
 1288:    fn erase_unregisters_regardless_of_liveness() {
 1289:        let _h = isolated_home();

Showing files 1-20 of 21. Use skip=20 for the next page, or narrow paths/pattern.

[Some lines truncated to 512 chars]

### region 5 (ask, ~20 tok)

User answers:
slammie_adapter_version: 0.23.x
core_version_target: 0.30–0.32

### region 6 (write, ~43 tok)

[C:/Users/decid/.omp/state/spt-endpoint/hertz-gaki-correction.txt#8A5E]
Successfully wrote 6991 bytes to C:/Users/decid/.omp/state/spt-endpoint/hertz-gaki-correction.txt
