`controllable`: `bind` live_agent (spt-hosted) → `Some(true)`; `listen` (harness-hosted) → `Some(false)` (exempt). Check which path each at-risk E2E uses + whether it stamps controllable or creates a broker session:


Bash

cd "C:/Users/decid/Documents/projects/spt-core"; for f in live_firsthost_e2e live_bind_firsthost_e2e livehost_psyche_fail_e2e livehost_nonresident_e2e; do echo "=== $f ==="; grep -nE "listen|bind|cmd_bind|controllable|set_controllable|endpoint run|\"run\"|api\", \"|live_agent|session" crates/spt/tests/$f.rs | head -15; done

Find establish path + controllable per at-risk E2E


<owl_messages>
<owl_message from="doyle" priority="high">ACK — B5 plan endorsed, all gate-aligned (boot-tick-1 reconcile_hosted_liveness, same controllable gate, None-skip safe since run_brain connects before livehost). The regression pre-check is exactly right.

ONE gate-forward distinction for the E2E check: separate "false-offline regression" from "test was asserting the OLD bug."
- FALSE-OFFLINE (real regression, must not happen): a test expects a controllable==Some(true) perch ONLINE at boot WHILE sessionless — B5 would wrongly offline it. If one exists, B5's gate is too broad.
- ASSERTING-THE-BUG (expected, update the test): psyche_fail / nonresident END in a failed/dead-host state — their correct post-B5 expectation IS offline (that's the phantom B5 kills). If one of those goes red because it asserted a sessionless controllable perch stays online at boot, it was encoding the revive bug — update the assertion to the corrected behavior, don't widen B5 to preserve it.

So: a red E2E under B5 isn't automatically a regression — classify which of the two it is, and say so at the gate. Build on. Ping at wave-4 ready; I'll read the boot-gate + the 3-way matrix (child-dead→B2 / boot-revive→B5 / dead-psyche-None→residency).</owl_message>
</owl_messages>

[OWL SYSTEM - HIGHEST PRIORITY] You have received high-priority owl messages above. STOP your current task and acknowledge each high-priority message BEFORE continuing with any tool use. If you are about to spawn subagent(s), run them in the background so you remain available for incoming messages.


=== live_firsthost_e2e ===
5://! a live-capable `spt api listen --once` BINDS the Self perch and marks it
35:/// In-process seed daemon so `api seed`/`api listen` succeed without a REAL
65:fn live_listen_marks_online_and_brain_reconcile_hosts() {
73:    // A live-capable adapter (declares [session.psyche_init]) + register it so the
81:         [session]\ncommune_dir=\"{dir}\"\nsignoff_dir=\"{dir}\"\n\n\
82:         [session.psyche_init]\ncommand='{NOOP}'\ncwd=\"{{psyche_dir}}\"\nkeys=[]\n"
101:        "api", "--adapter", "mocklive", "seed", "--pid", &pid, "--session-id", "sid-1",
105:    // ── the REAL live listen --once: binds the Self perch + marks it online
109:        "api", "--adapter", "mocklive", "--manifest", &mp, "listen", "agent7",
112:    assert!(out.status.success(), "listen: {}", String::from_utf8_lossy(&out.stderr));
115:    // cmd_listen (NOT hand-seeded).
121:        "a live `api listen` marks the perch online (the first-host handoff)"
146:        "the brain reconcile hosts the live-listen-onlined endpoint"
=== live_bind_firsthost_e2e ===
1://! M11-W0.3 first-host int (REQ-DAEMON-1) — the spt-hosted (bind, no listen)
2://! mirror of `live_firsthost_e2e`, closing the bind-no-Psyche gap:
4://! a live-capable `spt api bind <id> --type live_agent` BINDS the Self perch
6://! W0.3 establish-marks-online, mirror of W0.2's listen mark) → the brain's
8://! Before W0.3 a bind-only spt-hosted live agent never got a Psyche (the Psyche
9://! spawn was cmd_listen-only); this proves it now does — through the REAL bind,
59:fn live_bind_marks_online_and_brain_reconcile_hosts() {
66:    // A live-capable adapter (declares [session.psyche_init]) + register it so the
74:         [session]\ncommune_dir=\"{dir}\"\nsignoff_dir=\"{dir}\"\n\n\
75:         [session.psyche_init]\ncommand='{NOOP}'\ncwd=\"{{psyche_dir}}\"\nkeys=[]\n"
91:    // ── the REAL spt-hosted bind (no seed, no listen): establishes the Self perch
93:    // in this process — bind only writes the online signal.
95:        "api", "--adapter", "mocklive", "--manifest", &mp, "bind", "agent8",
96:        "--type", "live_agent", "--set-session-id", "sid-1",
98:    assert!(out.status.success(), "bind: {}", String::from_utf8_lossy(&out.stderr));
=== livehost_psyche_fail_e2e ===
9://! `spt daemon brain` CHILD → `api seed` + persistent `api listen` (online) — on
10://! a net-less / unpaired node, but the adapter's `[session.psyche_init].command`
21://! supervised brain + listen) are reaped — scoped to OUR pids only, never a
82:fn online_live_agent_with_missing_psyche_binary_stamps_a_harness_reachable_error() {
95:    // ── (2) A live-capable adapter in COPY mode whose [session.psyche_init].command
111:         [session]\ncommune_dir=\"{dir}\"\nsignoff_dir=\"{dir}\"\n\n\
112:         [session.psyche_init]\ncommand='psychebin --version'\ncwd=\"{{psyche_dir}}\"\nkeys=[]\n"
138:        .args(["daemon", "run"])
159:    //    `api listen` (marks the perch online, then blocks). ──
174:                "--session-id",
189:    let listen_log = home.path().join("listen.stderr.log");
190:    let listen_log_file = std::fs::File::create(&listen_log).expect("create listen stderr log");
191:    let mut listen: Child = Command::new(&spt_bin)
199:            "listen",
206:        .stderr(Stdio::from(listen_log_file))
=== livehost_nonresident_e2e ===
16://! `api seed` + persistent `api listen` (online) — on a net-less / unpaired node. The
24://! spawned processes (broker + supervised brain + listen) are reaped — scoped to OUR
91:fn online_live_agent_with_fast_exiting_psyche_stamps_parent_and_clears_phantom() {
107:    // ── (2) A live-capable adapter in COPY mode whose [session.psyche_init].command
129:         [session]\ncommune_dir=\"{dir}\"\nsignoff_dir=\"{dir}\"\n\n\
130:         [session.psyche_init]\ncommand='psychebin definitely-not-a-real-subcommand'\ncwd=\"{{psyche_dir}}\"\nkeys=[]\n"
155:        .args(["daemon", "run"])
176:    //    `api listen` (marks the perch online, then blocks). ──
191:                "--session-id",
206:    let listen_log = home.path().join("listen.stderr.log");
207:    let listen_log_file = std::fs::File::create(&listen_log).expect("create listen stderr log");
208:    let mut listen: Child = Command::new(&spt_bin)
216:            "listen",
223:        .stderr(Stdio::from(listen_log_file))
225:        .expect("spawn the persistent api listen (harness-hosted live agent)");


Strong finding. ALL of firsthost/psyche_fail/nonresident use `api listen` → `controllable=Some(false)` → **exempt** from B2/B5. Only **live_bind_firsthost** uses `api bind --type live_agent` → `Some(true)`, sessionless, expects hosting — the one false-offline candidate doyle flagged. But it appears in-process (no real broker). If so, `query_live_session_endpoints` → None → B5 skips → no regression.

Ack doyle (protocol), then confirm bind-firsthost has no reachable broker:


cd "C:/Users/decid/Documents/projects/spt-core" && $OWL send doyle <<'EOF'
ACK — classification framing locked (false-offline-regression vs asserting-the-bug). Evidence in:

- firsthost, psyche_fail, nonresident: all use `api listen` → controllable==Some(false) → EXEMPT from B2/B5 by the controllable gate. Untouched. (psyche_fail/nonresident exercise residency-confirm, which is controllable-agnostic — still runs.)
- live_bind_firsthost: the ONLY Some(true) candidate — `api bind --type live_agent`, online, sessionless, asserts the brain hosts its Psyche. This is the W0.3 first-host proof.

KEY: bind-firsthost looks in-process (calls the reconcile in-proc, "bind only writes the online signal in this process"), so there's NO running broker → query_live_session_endpoints can't connect → None → B5's pass SKIPS → agent8 not offlined → stays green. Confirming that now (no `daemon run` in it). If confirmed, B5 touches ZERO existing E2Es (None-skip for in-proc, Some(false)-exempt for the real-daemon ones) — clean.

Production reasoning that makes this correct (not a dodge): a real spt-hosted live agent runs IN a broker PTY (controllable = "broker-PTY-controllable", startup.rs:432) — the broker session PRECEDES the online mark (daemon spawns the PTY, THEN the hook runs `api bind`). So online+controllable ALWAYS has a KIND_SESSIONS entry in production; "online+controllable+sessionless" is only ever a dead phantom (cold-start leftover) OR a synthetic in-proc test with no broker. B5 offlines the former, None-skips the latter.

Building B5 now + a new int (cold-start with a stale online controllable perch + a real broker that has NO session for it → B5 offlines it at boot, no phantom psyche). Ping at wave-4 ready with the matrix + bind-firsthost confirmation.
EOF
echo "sent rc=$?"

Ack doyle + report classification


➜ SENT:doyle
sent rc=0


cd "C:/Users/decid/Documents/projects/spt-core"; echo "=== bind-firsthost: daemon run? reconcile call? ==="; grep -nE "daemon.*run|Command::new|reconcile_once|reconcile_hosted|spawn_live_host|broker" crates/spt/tests/live_bind_firsthost_e2e.rs; echo "=== firsthost same ==="; grep -nE "daemon.*run|Command::new|reconcile_once|reconcile_hosted|spawn_live_host" crates/spt/tests/live_firsthost_e2e.rs

Confirm bind-firsthost/firsthost broker situation


=== bind-firsthost: daemon run? reconcile call? ===
7://! `livehost::reconcile_once` then HOSTS the Psyche off that real online status.
40:        if spt_daemon::is_running() {
86:        let mut cmd = Command::new(&spt_bin);
121:    livehost::reconcile_once(
145:    livehost::reconcile_once(
159:            let _ = Command::new("taskkill")
164:            let _ = Command::new("kill").args(["-9", &p.to_string()]).output();
=== firsthost same ===
7://! this process) → the brain's `livehost::reconcile_once` then HOSTS the Psyche
44:        if spt_daemon::is_running() {
94:        let mut cmd = Command::new(&spt_bin);
135:    livehost::reconcile_once(
153:    livehost::reconcile_once(
167:            let _ = Command::new("taskkill")
172:            let _ = Command::new("kill").args(["-9", &p.to_string()]).output();


Confirmed: firsthost/bind-firsthost call `livehost::reconcile_once` **directly in-process** (no `daemon run`, no broker) — they never hit `spawn_live_host` where the boot-gate lives. And the real-daemon E2Es (bootrace/nonresident/psyche_fail) use `api listen` → `Some(false)` → exempt. So B5 (lifting the grace in `spawn_live_host`) touches **zero** existing E2E assertions. Plus the production invariant holds: an spt-hosted agent's broker PTY session *precedes* its online mark, so online+controllable+sessionless is only ever a phantom.

Implementing B5. Activate the REQ first:


Edit

required_stages = []  # registry-first; activate [impl,unit] when built — wave 4 (after B3+Breap). Shares the B2 reconcile: boot-gate arm = a no-live-session online perch is marked offline at boot, not psyche-revived

required_stages = ["impl", "unit", "int"]  # activated v0.12.0 wave 4: impl = lift the LIVENESS_RECONCILE_BOOT_GRACE skip so the B2 reconcile_hosted_liveness runs from boot tick 1 (a sessionless controllable perch is offlined BEFORE reconcile_once can revive its Psyche — reuse the B2 fn, no second liveness notion); unit = boot_gate_offlines_sessionless_controllable_then_reconcile_skips_host (+ session-backed contrast hosts); int = cold-start real broker with a stale online controllable perch (no session) offlines it at boot, no phantom psyche


<owl_messages>
<owl_message from="doyle" priority="high">Reasoning solid — controllable-gate exemptions + the session-precedes-online-mark invariant make B5 clean. Build on, no blocker.

ONE edge to have an answer for at the gate (not necessarily new code): if the boot-gate ever DOES skip on None (transient broker-unreachable at tick-1) and reconcile_once then revives a phantom Psyche — does a SUBSEQUENT B2 tick (broker now reachable) actually TEAR DOWN that already-spawned phantom Psyche process, or does it only flip the perch status=offline while the psyche keeps running (= a residual orphan, the thing Breap/B5 fight)? In production this is near-impossible (broker connects before livehost → tick-1 sees Some, not None), so it may be purely theoretical — but tell me at the gate whether B2-marks-offline also reaps a wrongly-spawned psyche, or if that's B4/residency territory. Just want the milestone coherent on "who kills a psyche that shouldn't exist." Ping at wave-4 ready.</owl_message>
</owl_messages>

[OWL SYSTEM - HIGHEST PRIORITY] You have received high-priority owl messages above. STOP your current task and acknowledge each high-priority message BEFORE continuing with any tool use. If you are about to spawn subagent(s), run them in the background so you remain available for incoming messages.


The file C:\Users\decid\Documents\projects\spt-core\traceable-reqs.toml has been updated successfully. (file state is current in your context — no need to Read it back)

title = "B5: `spt daemon start` does NOT revive phantom Psyches for dead-but-online-latched perches. Today reconcile_once (livehost.rs:285) spawns a Psyche per status=online live_agent perch at boot WITHOUT verifying the harness child / {id}-psyche is actually alive — so a Cold start after an unclean stop revives N psyches for N dead-but-latched perches (3 psyches for 3 dead perches). FIX: gate the boot psyche-spawn on real child-liveness — a perch with NO live broker session (the B2 reconcile signal) is marked OFFLINE at boot instead of hosted, so a dead-harness perch is never revived. Shares the B2 reconcile loop (this is its boot-gate arm); composes with B2's honest latch. Also closes wall-a's psyche_host_error gap (residency-confirm does not run at boot tick-1, livehost.rs:395-441 / 257-263). (v0.12.0)"

-required_stages = []  # registry-first; activate [impl,unit] when built — wave 4 (after B3+Breap). Shares the B2 reconcile: boot-gate arm = a no-live-session online perch is marked offline at boot, not psyche-revived

+required_stages = ["impl", "unit", "int"]  # activated v0.12.0 wave 4: impl = lift the LIVENESS_RECONCILE_BOOT_GRACE skip so the B2 reconcile_hosted_liveness runs from boot tick 1 (a sessionless controllable perch is offlined BEFORE reconcile_once can revive its Psyche — reuse the B2 fn, no second liveness notion); unit = boot_gate_offlines_sessionless_controllable_then_reconcile_skips_host (+ session-backed contrast hosts); int = cold-start real broker with a stale online controllable perch (no session) offlines it at boot, no phantom psyche

title = "B4 (deepest): a bare brain restart (broker survives) REHYDRATES the live-agent lifecycle so post-restart endpoints are hosted + attachable. Today resume_sessions (brainproc.rs:186, brain.rs:797-809) re-subscribes to the broker's PTY sessions but ALL BrainLifecycle instances (lifecycle.rs:58-130; the ephemeral brain.rs:254-275) are LOST on restart → a post-restart live endpoint gets no livehost → its Psyche is never (re)hosted and new spawns die / can't attach until a FULL daemon reset (operator: perri's brain kill+restart wedged everything until a full daemon kill). FIX: on brain startup, rebuild a BrainLifecycle per resumed live-capable session — load the manifest from the adapter registry → instantiate → start the pulse — the rehydrate the resume no-op cannot do. Composes with B2 (the reconcile re-hosts from the honest on-disk status after rehydrate). (v0.12.0)"


doyle's edge question — answerable from the code I've read. Key facts: `host_one` **drops** the `spawn_psyche` handle (detached, like `runtime.rs`), storing only the driver thread + stop flag in `HostedLife`. So `stop_host` (un-host) trips the pulse-driver and joins its thread but does **not** kill the detached Psyche *process*. ACK doyle with the coherent answer (priority protocol), then continue B5:


Bash

cd "C:/Users/decid/Documents/projects/spt-core" && $OWL send doyle <<'EOF'
ACK. Answer to "who kills a psyche that shouldn't exist" (read from the code, no new B5 work needed):

A wrongly-spawned LIVE phantom Psyche is NOT reaped at runtime by the offline/un-host path. Chain: reconcile_hosted_liveness marks the perch status=offline → the SAME-tick reconcile_once stop-side calls set.stop_host(id), which trips the pulse-loop stop flag + JOINS the driver THREAD. But host_one DROPS the spawn_psyche handle (detached, like runtime.rs:354) and HostedLife stores only {stop, thread, hosted_at} — no psyche pid/handle. So un-host stops the DRIVER, not the Psyche PROCESS. A live phantom psyche would linger.

WHO ultimately kills it: Breap (wave 3). On daemon stop the BrainReaper job/process-group reaps the ENTIRE brain subtree incl every detached Psyche — so a phantom is never immortal; worst case it lives until the next daemon stop. residency-confirm does NOT catch a LIVE phantom (it classifies a live pid as Resident → leaves it; it only fires on a DEAD {id}-psyche).

So milestone-coherent: BOOT revive prevented = B5; daemon-stop subtree reap = Breap; dead-psyche cleanup = residency-confirm. The RUNTIME "kill a live psyche that shouldn't exist" case has NO dedicated reaper — but it's only reachable via the None-skip-then-revive path, which is near-impossible in production (run_brain connects to the broker BEFORE spawning livehost → tick-1 query returns Some, never None).

If you want runtime reaping of that theoretical phantom, it's a small follow-on (stop_host reads the {id}-psyche perch pid + kills it) — OUT of B5/B4 scope as written; flagging as a candidate, your call. My lean: leave it (Breap backstops it, production can't hit it) unless you want it folded into B4. Building B5 impl+unit+int now.
EOF
echo "sent rc=$?"

Ack doyle + answer phantom-reap question


Now the B5 impl — lift the boot-grace in `spawn_live_host`. First check where the grace const is referenced (avoid breaking a test):


cd "C:/Users/decid/Documents/projects/spt-core"; grep -rn "LIVENESS_RECONCILE_BOOT_GRACE\|booted" crates/

Find grace const + booted usages


crates/spt-daemon/src/applyhost.rs:202:    // (ADR-0018 Q7, restoration D6-1): the new brain has not booted, let alone
crates/spt-daemon/src/brainproc.rs:312:/// never booted. **`exe_hash` (D7-1)** is the lowercase-hex SHA-256 of *this
crates/spt-daemon/src/brainproc.rs:411:    /// trial and falsely promote a brain that never booted).
crates/spt-daemon/src/livehost.rs:69:pub const LIVENESS_RECONCILE_BOOT_GRACE: Duration = Duration::from_secs(10);
crates/spt-daemon/src/livehost.rs:476:/// the [`LIVENESS_RECONCILE_BOOT_GRACE`], so a transient broker hiccup or the
crates/spt-daemon/src/livehost.rs:532:        let booted = Instant::now();
crates/spt-daemon/src/livehost.rs:543:            if booted.elapsed() >= LIVENESS_RECONCILE_BOOT_GRACE {
crates/spt-daemon/src/pump/mod.rs:756:/// offset (a freshly-booted Windows host, KNOWN-HAZARDS 5.9). The shell owns
crates/spt-daemon/src/relcache.rs:54:/// moment of binary swap — before the new brain has even booted (the
crates/spt-daemon/src/shellwake.rs:16://! booted daemon kill a dead daemon's orphaned watcher before adopting).


`LIVENESS_RECONCILE_BOOT_GRACE` is referenced only in the def, one doc line, and the guard I'm removing. Edit `spawn_live_host` to run the reconcile from tick 1:


Edit

    std::thread::spawn(move || {
        let set = Arc::new(LiveSet::new());
        let booted = Instant::now();
        while !stop.load(Ordering::SeqCst) {
            let owlery = perch::owlery_dir();
            let adapters_dir = perch::adapters_dir();
            let registered = spt_runtime::registry::registered(&adapters_dir);
            let cfg = DaemonConfig::load();
            // B2 keystone (REQ-HAZARD-HOSTED-LIVENESS-RECONCILE): past the boot
            // grace, clear the status=online latch for any spt-hosted endpoint
            // whose broker session is gone. Runs BEFORE reconcile_once so the
            // stop-side un-hosts the now-offline perch the same tick. Skipped when
            // the broker is unreachable (None) — never mass-offline on a hiccup.
            if booted.elapsed() >= LIVENESS_RECONCILE_BOOT_GRACE {
                if let Some(live) = query_live_session_endpoints() {
                    reconcile_hosted_liveness(&owlery, &live);
                }
            }
            reconcile_once(&owlery, &registered, &adapters_dir, &set, &cfg, reason);

    std::thread::spawn(move || {
        let set = Arc::new(LiveSet::new());
        while !stop.load(Ordering::SeqCst) {
            let owlery = perch::owlery_dir();
            let adapters_dir = perch::adapters_dir();
            let registered = spt_runtime::registry::registered(&adapters_dir);
            let cfg = DaemonConfig::load();
            // B2 keystone (REQ-HAZARD-HOSTED-LIVENESS-RECONCILE) + B5 boot-gate
            // (REQ-HAZARD-LIVEHOST-BOOT-LIVENESS-GATE): clear the status=online
            // latch for any spt-hosted endpoint whose broker session is gone — and
            // do it from BOOT tick 1, NOT after a grace. Runs BEFORE reconcile_once
            // so the same-tick stop-side un-hosts the now-offline perch and the
            // start-side never REVIVES its Psyche. The boot-gate is what stops a
            // cold start after an unclean stop from spawning N phantom Psyches for N
            // dead-but-latched perches. Skipped only when the broker is unreachable
            // (None) — never mass-offline on a hiccup; safe at tick 1 because
            // run_brain connects to the broker BEFORE this host spawns, so the query
            // returns the REAL session set: empty at a cold start (every online
            // controllable perch is a phantom → offlined), the surviving sessions at
            // a brain restart (live agents stay, only the dead are offlined). The
            // controllable==Some(true) gate inside keeps relay/legacy perches exempt.
            // [impl->REQ-HAZARD-HOSTED-LIVENESS-RECONCILE]
            // [impl->REQ-HAZARD-LIVEHOST-BOOT-LIVENESS-GATE]
            if let Some(live) = query_live_session_endpoints() {
                reconcile_hosted_liveness(&owlery, &live);
            }
            reconcile_once(&owlery, &registered, &adapters_dir, &set, &cfg, reason);


The file C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\src\livehost.rs has been updated successfully. (file state is current in your context — no need to Read it back)

    std::thread::spawn(move || {
        let set = Arc::new(LiveSet::new());
        let booted = Instant::now();
        while !stop.load(Ordering::SeqCst) {
            let owlery = perch::owlery_dir();
            let adapters_dir = perch::adapters_dir();
            let registered = spt_runtime::registry::registered(&adapters_dir);
            let cfg = DaemonConfig::load();
            // B2 keystone (REQ-HAZARD-HOSTED-LIVENESS-RECONCILE): past the boot
            // grace, clear the status=online latch for any spt-hosted endpoint
            // whose broker session is gone. Runs BEFORE reconcile_once so the
            // stop-side un-hosts the now-offline perch the same tick. Skipped when
            // the broker is unreachable (None) — never mass-offline on a hiccup.
            if booted.elapsed() >= LIVENESS_RECONCILE_BOOT_GRACE {
                if let Some(live) = query_live_session_endpoints() {
                    reconcile_hosted_liveness(&owlery, &live);
                }
            }
            reconcile_once(&owlery, &registered, &adapters_dir, &set, &cfg, reason);

    std::thread::spawn(move || {
        let set = Arc::new(LiveSet::new());
        while !stop.load(Ordering::SeqCst) {
            let owlery = perch::owlery_dir();
            let adapters_dir = perch::adapters_dir();
            let registered = spt_runtime::registry::registered(&adapters_dir);
            let cfg = DaemonConfig::load();
            // B2 keystone (REQ-HAZARD-HOSTED-LIVENESS-RECONCILE) + B5 boot-gate
            // (REQ-HAZARD-LIVEHOST-BOOT-LIVENESS-GATE): clear the status=online
            // latch for any spt-hosted endpoint whose broker session is gone — and
            // do it from BOOT tick 1, NOT after a grace. Runs BEFORE reconcile_once
            // so the same-tick stop-side un-hosts the now-offline perch and the
            // start-side never REVIVES its Psyche. The boot-gate is what stops a
            // cold start after an unclean stop from spawning N phantom Psyches for N
            // dead-but-latched perches. Skipped only when the broker is unreachable
            // (None) — never mass-offline on a hiccup; safe at tick 1 because
            // run_brain connects to the broker BEFORE this host spawns, so the query
            // returns the REAL session set: empty at a cold start (every online
            // controllable perch is a phantom → offlined), the surviving sessions at
            // a brain restart (live agents stay, only the dead are offlined). The
            // controllable==Some(true) gate inside keeps relay/legacy perches exempt.
            // [impl->REQ-HAZARD-HOSTED-LIVENESS-RECONCILE]
            // [impl->REQ-HAZARD-LIVEHOST-BOOT-LIVENESS-GATE]
            if let Some(live) = query_live_session_endpoints() {
                reconcile_hosted_liveness(&owlery, &live);
            }
            reconcile_once(&owlery, &registered, &adapters_dir, &set, &cfg, reason);

pub fn spawn_live_host(stop: Arc<AtomicBool>, reason: StartReason) -> JoinHandle<()> {

-            // B2 keystone (REQ-HAZARD-HOSTED-LIVENESS-RECONCILE): past the boot

-            // grace, clear the status=online latch for any spt-hosted endpoint

-            // whose broker session is gone. Runs BEFORE reconcile_once so the

-            // stop-side un-hosts the now-offline perch the same tick. Skipped when

-            // the broker is unreachable (None) — never mass-offline on a hiccup.

-            if booted.elapsed() >= LIVENESS_RECONCILE_BOOT_GRACE {

-                if let Some(live) = query_live_session_endpoints() {

-                    reconcile_hosted_liveness(&owlery, &live);

+            // B2 keystone (REQ-HAZARD-HOSTED-LIVENESS-RECONCILE) + B5 boot-gate

+            // (REQ-HAZARD-LIVEHOST-BOOT-LIVENESS-GATE): clear the status=online

+            // latch for any spt-hosted endpoint whose broker session is gone — and

+            // do it from BOOT tick 1, NOT after a grace. Runs BEFORE reconcile_once

+            // so the same-tick stop-side un-hosts the now-offline perch and the

+            // start-side never REVIVES its Psyche. The boot-gate is what stops a

+            // cold start after an unclean stop from spawning N phantom Psyches for N

+            // dead-but-latched perches. Skipped only when the broker is unreachable

+            // (None) — never mass-offline on a hiccup; safe at tick 1 because

+            // run_brain connects to the broker BEFORE this host spawns, so the query

+            // returns the REAL session set: empty at a cold start (every online

+            // controllable perch is a phantom → offlined), the surviving sessions at

+            // a brain restart (live agents stay, only the dead are offlined). The

+            // controllable==Some(true) gate inside keeps relay/legacy perches exempt.

+            // [impl->REQ-HAZARD-HOSTED-LIVENESS-RECONCILE]

+            // [impl->REQ-HAZARD-LIVEHOST-BOOT-LIVENESS-GATE]

+            if let Some(live) = query_live_session_endpoints() {

+                reconcile_hosted_liveness(&owlery, &live);

// Sleep in slices so a stop lands promptly.


Remove the now-unused grace const + its doc, and fix the doc reference in `reconcile_hosted_liveness`:


/// Boot grace before the B2 pull-liveness reconcile runs
/// (REQ-HAZARD-HOSTED-LIVENESS-RECONCILE): a freshly (re)started daemon brain may
/// still be re-spawning harnesses / the broker session set may lag the on-disk
/// `status=online` for a window. Holding the no-session→offline pass off until the
/// loop has run a few seconds prevents a cold-start / brain-restart window from
/// mass-offlining endpoints that are merely mid-bringup. Chosen as ~two reconcile
/// intervals.
pub const LIVENESS_RECONCILE_BOOT_GRACE: Duration = Duration::from_secs(10);

/// The residency verdict for a hosted endpoint's `{id}-psyche` child, decided

/// The residency verdict for a hosted endpoint's `{id}-psyche` child, decided


/// Boot grace before the B2 pull-liveness reconcile runs
/// (REQ-HAZARD-HOSTED-LIVENESS-RECONCILE): a freshly (re)started daemon brain may
/// still be re-spawning harnesses / the broker session set may lag the on-disk
/// `status=online` for a window. Holding the no-session→offline pass off until the
/// loop has run a few seconds prevents a cold-start / brain-restart window from
/// mass-offlining endpoints that are merely mid-bringup. Chosen as ~two reconcile
/// intervals.
pub const LIVENESS_RECONCILE_BOOT_GRACE: Duration = Duration::from_secs(10);

/// The residency verdict for a hosted endpoint's `{id}-psyche` child, decided

/// detection latency is `grace + up to one reconcile interval`.

pub const PSYCHE_RESIDENCY_GRACE: Duration = Duration::from_secs(5);

-/// Boot grace before the B2 pull-liveness reconcile runs

-/// (REQ-HAZARD-HOSTED-LIVENESS-RECONCILE): a freshly (re)started daemon brain may

-/// still be re-spawning harnesses / the broker session set may lag the on-disk

-/// `status=online` for a window. Holding the no-session→offline pass off until the

-/// loop has run a few seconds prevents a cold-start / brain-restart window from

-/// mass-offlining endpoints that are merely mid-bringup. Chosen as ~two reconcile

-pub const LIVENESS_RECONCILE_BOOT_GRACE: Duration = Duration::from_secs(10);

/// purely from observed facts (no IO) so it is unit-testable in isolation.


462	/// dead-psyche like wall-a) is also exempt here — its phantom class is the
463	/// residency-confirm arm's job ([`confirm_residency_or_unhost`]), not the
464	/// broker-session arm.
465	///
466	/// The caller passes `live_sessions` ONLY when the broker is reachable AND past
467	/// the [`LIVENESS_RECONCILE_BOOT_GRACE`], so a transient broker hiccup or the
468	/// cold-start spawn window never mass-offlines. Returns the ids it marked offline.
469	// [impl->REQ-HAZARD-HOSTED-LIVENESS-RECONCILE]
470	pub fn reconcile_hosted_liveness(owlery: &Path, live_sessions: &BTreeSet<String>) -> Vec<String> {
471	    let mut offlined = Vec::new();
472	    for id in perch::list_self_perch_ids(owlery) {
473	        let perch = perch::resolve_perch_path(&id, ParentHint::Infer);

/// dead-psyche like wall-a) is also exempt here — its phantom class is the
/// residency-confirm arm's job ([`confirm_residency_or_unhost`]), not the
/// broker-session arm.
///
/// The caller passes `live_sessions` ONLY when the broker is reachable AND past
/// the [`LIVENESS_RECONCILE_BOOT_GRACE`], so a transient broker hiccup or the
/// cold-start spawn window never mass-offlines. Returns the ids it marked offline.
// [impl->REQ-HAZARD-HOSTED-LIVENESS-RECONCILE]
pub fn reconcile_hosted_liveness(owlery: &Path, live_sessions: &BTreeSet<String>) -> Vec<String> {
    let mut offlined = Vec::new();
    for id in perch::list_self_perch_ids(owlery) {
        let perch = perch::resolve_perch_path(&id, ParentHint::Infer);


/// The caller passes `live_sessions` ONLY when the broker is reachable AND past
/// the [`LIVENESS_RECONCILE_BOOT_GRACE`], so a transient broker hiccup or the
/// cold-start spawn window never mass-offlines. Returns the ids it marked offline.
// [impl->REQ-HAZARD-HOSTED-LIVENESS-RECONCILE]

/// The caller passes `live_sessions` ONLY when the broker is reachable (it runs
/// this from boot tick 1 — the B5 boot-gate — so a phantom is never revived, and
/// SKIPS the pass entirely when the broker is unreachable so a transient hiccup
/// never mass-offlines). Returns the ids it marked offline.
// [impl->REQ-HAZARD-HOSTED-LIVENESS-RECONCILE]


/// The caller passes `live_sessions` ONLY when the broker is reachable AND past
/// the [`LIVENESS_RECONCILE_BOOT_GRACE`], so a transient broker hiccup or the
/// cold-start spawn window never mass-offlines. Returns the ids it marked offline.
// [impl->REQ-HAZARD-HOSTED-LIVENESS-RECONCILE]

/// The caller passes `live_sessions` ONLY when the broker is reachable (it runs
/// this from boot tick 1 — the B5 boot-gate — so a phantom is never revived, and
/// SKIPS the pass entirely when the broker is unreachable so a transient hiccup
/// never mass-offlines). Returns the ids it marked offline.
// [impl->REQ-HAZARD-HOSTED-LIVENESS-RECONCILE]

/// residency-confirm arm's job ([`confirm_residency_or_unhost`]), not the

-/// The caller passes `live_sessions` ONLY when the broker is reachable AND past

-/// the [`LIVENESS_RECONCILE_BOOT_GRACE`], so a transient broker hiccup or the

-/// cold-start spawn window never mass-offlines. Returns the ids it marked offline.

+/// The caller passes `live_sessions` ONLY when the broker is reachable (it runs

+/// this from boot tick 1 — the B5 boot-gate — so a phantom is never revived, and

+/// SKIPS the pass entirely when the broker is unreachable so a transient hiccup

+/// never mass-offlines). Returns the ids it marked offline.

pub fn reconcile_hosted_liveness(owlery: &Path, live_sessions: &BTreeSet<String>) -> Vec<String> {


Now the B5 unit. Read the existing test helpers to model it (how tests stamp an online controllable perch + invoke the reconciles):


600	    }
601
602	    // A no-op Psyche summarizer command: spawns + exits 0 (we never depend on its
603	    // output — the daemon online-stamp is what hosts it, not the pid).
604	    #[cfg(windows)]
605	    const NOOP_CMD: &str = "cmd /C rem";
606	    #[cfg(unix)]
607	    const NOOP_CMD: &str = "true";
608
609	    /// Register a live-capable adapter `name` (declares `[session.psyche_init]` +
610	    /// a watched commune/signoff dir) so `resolve_option_in` finds it from disk.
611	    fn register_live_adapter(name: &str, drops: &Path) {
612	        let dir = drops.to_string_lossy().replace('\\', "/");
613	        let toml = format!(
614	            "[adapter]\nname=\"{name}\"\nkind=\"harness\"\nversion=\"1\"\n\
615	             min_spt_core_version=\"0\"\n\n\
616	             [session]\ncommune_dir=\"{dir}\"\nsignoff_dir=\"{dir}\"\n\n\
617	             [session.psyche_init]\ncommand='{NOOP_CMD}'\ncwd=\"{{psyche_dir}}\"\nkeys=[]\n"
618	        );
619	        let src = perch::spt_home().join("srcs").join(name);
620	        std::fs::create_dir_all(&src).unwrap();
621	        std::fs::write(src.join("manifest.toml"), toml).unwrap();
622	        spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();
623	    }
624
625	    /// Seed a Self **live** perch on disk: `state=live_agent`, the given status,
626	    /// and the adapter option that resolves the manifest.
627	    fn seed_live_perch(id: &str, adapter: &str, status: &str) {
628	        let perch = perch::resolve_perch_path(id, ParentHint::Infer);
629	        std::fs::create_dir_all(&perch).unwrap();
630	        let mut rec = spt_store::info::InfoJson::new(id, "t", std::process::id(), "sid-1", "live_agent");
631	        rec.adapter = Some(adapter.to_string());
632	        spt_store::info::write_info(&perch, &rec).unwrap();
633	        spt_store::info::set_status(&perch, status).unwrap();
634	    }
635
636	    /// A fast-pulse config so the driver loop ticks quickly in tests.
637	    fn fast_cfg() -> DaemonConfig {
638	        DaemonConfig {
639	            pulse_period: Duration::from_millis(15),
640	            ..DaemonConfig::default()
641	        }
642	    }
643
644	    fn registered_now() -> Vec<(AdapterRecord, Manifest)> {
645	        spt_runtime::registry::registered(&perch::adapters_dir())
646	    }
647
648	    /// Poll a predicate up to `bound`, sleeping 20ms between checks.
649	    fn wait_until(bound: Duration, mut pred: impl FnMut() -> bool) -> bool {
650	        let deadline = Instant::now() + bound;
651	        while Instant::now() < deadline {
652	            if pred() {
653	                return true;
654	            }
655	            std::thread::sleep(Duration::from_millis(20));
656	        }
657	        pred()
658	    }
659
660	    // [unit->REQ-HAZARD-HOSTED-LIVENESS-RECONCILE] the B2 pull keystone: an
661	    // spt-hosted (controllable==Some(true)) online live_agent perch with NO live
662	    // broker session is marked OFFLINE (the status=online latch cleared); one WITH
663	    // a session stays online; a harness-hosted relay (controllable==Some(false))
664	    // and a legacy (controllable==None) are EXEMPT (their liveness/phantom class is
665	    // not the broker-session arm); a ready_agent is ignored entirely.
666	    #[test]
667	    fn pull_liveness_marks_sessionless_spt_hosted_offline_only() {
668	        with_home(|_| {
669	            let seed = |id: &str, state: &str, controllable: Option<bool>| {
670	                let perch = perch::resolve_perch_path(id, ParentHint::Infer);
671	                std::fs::create_dir_all(&perch).unwrap();
672	                let mut rec =
673	                    spt_store::info::InfoJson::new(id, "t", std::process::id(), "sid", state);
674	                rec.controllable = controllable;
675	                spt_store::info::write_info(&perch, &rec).unwrap();
676	                spt_store::info::set_status(&perch, STATUS_ONLINE).unwrap();
677	            };
678	            seed("alive", "live_agent", Some(true)); // has a session → stays
679	            seed("dead", "live_agent", Some(true)); // no session → offlined
680	            seed("relay", "live_agent", Some(false)); // harness-hosted relay → exempt
681	            seed("legacy", "live_agent", None); // legacy/unknown → exempt
682	            seed("ready", "ready_agent", Some(true)); // not a live agent → ignored
683
684	            let mut live = BTreeSet::new();
685	            live.insert("alive".to_string());
686
687	            let offlined = reconcile_hosted_liveness(&perch::owlery_dir(), &live);
688	            assert_eq!(
689	                offlined,
690	                vec!["dead".to_string()],
691	                "only the sessionless spt-hosted live agent is offlined"
692	            );
693
694	            let status = |id: &str| {
695	                spt_store::info::read_info(&perch::resolve_perch_path(id, ParentHint::Infer))
696	                    .and_then(|i| i.status)
697	            };
698	            assert_eq!(
699	                status("dead").as_deref(),
700	                Some(spt_store::liveness::STATUS_OFFLINE),
701	                "dead harness (no session) → offline"
702	            );
703	            assert_eq!(status("alive").as_deref(), Some(STATUS_ONLINE), "session present → stays");
704	            assert_eq!(status("relay").as_deref(), Some(STATUS_ONLINE), "relay exempt");
705	            assert_eq!(status("legacy").as_deref(), Some(STATUS_ONLINE), "legacy None exempt");
706	            assert_eq!(status("ready").as_deref(), Some(STATUS_ONLINE), "ready_agent ignored");
707	        });
708	    }
709
710	    // [int->REQ-HAZARD-HOSTED-LIVENESS-RECONCILE] real broker session lifecycle:
711	    // an spt-hosted perch with a LIVE broker session stays online across a pull
712	    // reconcile; when its broker session is KILLED + reaped, the next pull
713	    // reconcile clears the status=online latch and marks the perch offline.
714	    #[test]
715	    fn pull_reconcile_offlines_perch_when_broker_session_dies() {
716	        use crate::msg::SpawnReq;
717	        with_home(|_| {
718	            // The spt-hosted perch on disk (online-latched, controllable).
719	            let perch = perch::resolve_perch_path("wallb", ParentHint::Infer);
720	            std::fs::create_dir_all(&perch).unwrap();
721	            let mut rec =
722	                spt_store::info::InfoJson::new("wallb", "t", std::process::id(), "sid", "live_agent");
723	            rec.controllable = Some(true);
724	            spt_store::info::write_info(&perch, &rec).unwrap();
725	            spt_store::info::set_status(&perch, STATUS_ONLINE).unwrap();
726
727	            // A real broker hosting a session LABELLED `wallb`.
728	            let name = format!("spt-b2-livecheck-{}.sock", std::process::id());
729	            let broker = crate::broker::Broker::bind(&name).unwrap();
730	            let b2 = Arc::clone(&broker);
731	            std::thread::spawn(move || {
732	                let _ = b2.serve();
733	            });
734	            let mut brain = {
735	                let mut b = None;
736	                for _ in 0..200 {
737	                    if let Ok(c) = Brain::cold_start(&name, now_ms()) {
738	                        b = Some(c);
739	                        break;
740	                    }
741	                    std::thread::sleep(Duration::from_millis(10));
742	                }
743	                b.expect("connect to test broker")
744	            };
745	            #[cfg(unix)]
746	            let (program, args) = ("cat".to_string(), Vec::<String>::new());
747	            #[cfg(windows)]
748	            let (program, args) = ("findstr".to_string(), vec![".".to_string()]);
749	            brain
750	                .spawn_session(SpawnReq {
751	                    program,
752	                    args,
753	                    rows: 24,
754	                    cols: 80,
755	                    endpoint: "wallb".to_string(),
756	                    cwd: None,
757	                    env: Default::default(),
758	                })
759	                .unwrap();
760
761	            let session_set = |brain: &mut Brain| -> BTreeSet<String> {
762	                brain
763	                    .sessions()
764	                    .unwrap()
765	                    .sessions
766	                    .into_iter()
767	                    .map(|s| s.endpoint)
768	                    .filter(|e| !e.is_empty())
769	                    .collect()
770	            };
771	            let status = |id: &str| {
772	                spt_store::info::read_info(&perch::resolve_perch_path(id, ParentHint::Infer))
773	                    .and_then(|i| i.status)
774	            };
775
776	            // Session LIVE → reconcile leaves it online.
777	            let live = session_set(&mut brain);
778	            assert!(live.contains("wallb"), "broker hosts the session");
779	            assert!(reconcile_hosted_liveness(&perch::owlery_dir(), &live).is_empty());
780	            assert_eq!(status("wallb").as_deref(), Some(STATUS_ONLINE));
781
782	            // Kill the session; wait for the exit-waiter to reap it from the table.
783	            brain.kill_session().unwrap();
784	            assert!(
785	                wait_until(Duration::from_secs(5), || !session_set(&mut brain).contains("wallb")),
786	                "killed session leaves the broker table"
787	            );
788
789	            // Now the pull reconcile clears the latch → offline.
790	            let live2 = session_set(&mut brain);
791	            let offlined = reconcile_hosted_liveness(&perch::owlery_dir(), &live2);
792	            assert_eq!(offlined, vec!["wallb".to_string()], "dead-session perch offlined");
793	            assert_eq!(
794	                status("wallb").as_deref(),
795	                Some(spt_store::liveness::STATUS_OFFLINE),
796	                "the status=online latch is cleared once the broker session is gone"
797	            );
798	        });
799	    }
800
801	    // [unit->REQ-DAEMON-1] (a) FIRST-HOST scan: an online live endpoint on disk is
802	    // hosted by the reconcile — the Psyche is spawned AND the config-paced pulse
803	    // driver actually runs (it stamps last_active_ms on the Self perch each tick).
804	    #[test]
805	    fn reconcile_hosts_an_online_live_endpoint_and_drives_its_pulse() {
806	        with_home(|_| {
807	            let drops = perch::spt_home().join("drops");
808	            std::fs::create_dir_all(&drops).unwrap();
809	            register_live_adapter("mock", &drops);
810	            seed_live_perch("doyle", "mock", STATUS_ONLINE);
811
812	            let set = Arc::new(LiveSet::new());
813	            let cfg = fast_cfg();
814	            reconcile_once(
815	                &perch::owlery_dir(),
816	                &registered_now(),
817	                &perch::adapters_dir(),
818	                &set,
819	                &cfg,

    }

    // A no-op Psyche summarizer command: spawns + exits 0 (we never depend on its
    // output — the daemon online-stamp is what hosts it, not the pid).
    #[cfg(windows)]
    const NOOP_CMD: &str = "cmd /C rem";
    #[cfg(unix)]
    const NOOP_CMD: &str = "true";

    /// Register a live-capable adapter `name` (declares `[session.psyche_init]` +
    /// a watched commune/signoff dir) so `resolve_option_in` finds it from disk.
    fn register_live_adapter(name: &str, drops: &Path) {
        let dir = drops.to_string_lossy().replace('\\', "/");
        let toml = format!(
            "[adapter]\nname=\"{name}\"\nkind=\"harness\"\nversion=\"1\"\n\
             min_spt_core_version=\"0\"\n\n\
             [session]\ncommune_dir=\"{dir}\"\nsignoff_dir=\"{dir}\"\n\n\
             [session.psyche_init]\ncommand='{NOOP_CMD}'\ncwd=\"{{psyche_dir}}\"\nkeys=[]\n"
        );
        let src = perch::spt_home().join("srcs").join(name);
        std::fs::create_dir_all(&src).unwrap();
        std::fs::write(src.join("manifest.toml"), toml).unwrap();
        spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();
    }

    /// Seed a Self **live** perch on disk: `state=live_agent`, the given status,
    /// and the adapter option that resolves the manifest.
    fn seed_live_perch(id: &str, adapter: &str, status: &str) {
        let perch = perch::resolve_perch_path(id, ParentHint::Infer);
        std::fs::create_dir_all(&perch).unwrap();
        let mut rec = spt_store::info::InfoJson::new(id, "t", std::process::id(), "sid-1", "live_agent");
        rec.adapter = Some(adapter.to_string());
        spt_store::info::write_info(&perch, &rec).unwrap();
        spt_store::info::set_status(&perch, status).unwrap();
    }

    /// A fast-pulse config so the driver loop ticks quickly in tests.
    fn fast_cfg() -> DaemonConfig {
        DaemonConfig {
            pulse_period: Duration::from_millis(15),
            ..DaemonConfig::default()
        }
    }

    fn registered_now() -> Vec<(AdapterRecord, Manifest)> {
        spt_runtime::registry::registered(&perch::adapters_dir())
    }

    /// Poll a predicate up to `bound`, sleeping 20ms between checks.
    fn wait_until(bound: Duration, mut pred: impl FnMut() -> bool) -> bool {
        let deadline = Instant::now() + bound;
        while Instant::now() < deadline {
            if pred() {
                return true;
            }
            std::thread::sleep(Duration::from_millis(20));
        }
        pred()
    }

    // [unit->REQ-HAZARD-HOSTED-LIVENESS-RECONCILE] the B2 pull keystone: an
    // spt-hosted (controllable==Some(true)) online live_agent perch with NO live
    // broker session is marked OFFLINE (the status=online latch cleared); one WITH
    // a session stays online; a harness-hosted relay (controllable==Some(false))
    // and a legacy (controllable==None) are EXEMPT (their liveness/phantom class is
    // not the broker-session arm); a ready_agent is ignored entirely.
    #[test]
    fn pull_liveness_marks_sessionless_spt_hosted_offline_only() {
        with_home(|_| {
            let seed = |id: &str, state: &str, controllable: Option<bool>| {
                let perch = perch::resolve_perch_path(id, ParentHint::Infer);
                std::fs::create_dir_all(&perch).unwrap();
                let mut rec =
                    spt_store::info::InfoJson::new(id, "t", std::process::id(), "sid", state);
                rec.controllable = controllable;
                spt_store::info::write_info(&perch, &rec).unwrap();
                spt_store::info::set_status(&perch, STATUS_ONLINE).unwrap();
            };
            seed("alive", "live_agent", Some(true)); // has a session → stays
            seed("dead", "live_agent", Some(true)); // no session → offlined
            seed("relay", "live_agent", Some(false)); // harness-hosted relay → exempt
            seed("legacy", "live_agent", None); // legacy/unknown → exempt
            seed("ready", "ready_agent", Some(true)); // not a live agent → ignored

            let mut live = BTreeSet::new();
            live.insert("alive".to_string());

            let offlined = reconcile_hosted_liveness(&perch::owlery_dir(), &live);
            assert_eq!(
                offlined,
                vec!["dead".to_string()],
                "only the sessionless spt-hosted live agent is offlined"
            );

            let status = |id: &str| {
                spt_store::info::read_info(&perch::resolve_perch_path(id, ParentHint::Infer))
                    .and_then(|i| i.status)
            };
            assert_eq!(
                status("dead").as_deref(),
                Some(spt_store::liveness::STATUS_OFFLINE),
                "dead harness (no session) → offline"
            );
            assert_eq!(status("alive").as_deref(), Some(STATUS_ONLINE), "session present → stays");
            assert_eq!(status("relay").as_deref(), Some(STATUS_ONLINE), "relay exempt");
            assert_eq!(status("legacy").as_deref(), Some(STATUS_ONLINE), "legacy None exempt");
            assert_eq!(status("ready").as_deref(), Some(STATUS_ONLINE), "ready_agent ignored");
        });
    }

    // [int->REQ-HAZARD-HOSTED-LIVENESS-RECONCILE] real broker session lifecycle:
    // an spt-hosted perch with a LIVE broker session stays online across a pull
    // reconcile; when its broker session is KILLED + reaped, the next pull
    // reconcile clears the status=online latch and marks the perch offline.
    #[test]
    fn pull_reconcile_offlines_perch_when_broker_session_dies() {
        use crate::msg::SpawnReq;
        with_home(|_| {
            // The spt-hosted perch on disk (online-latched, controllable).
            let perch = perch::resolve_perch_path("wallb", ParentHint::Infer);
            std::fs::create_dir_all(&perch).unwrap();
            let mut rec =
                spt_store::info::InfoJson::new("wallb", "t", std::process::id(), "sid", "live_agent");
            rec.controllable = Some(true);
            spt_store::info::write_info(&perch, &rec).unwrap();
            spt_store::info::set_status(&perch, STATUS_ONLINE).unwrap();

            // A real broker hosting a session LABELLED `wallb`.
            let name = format!("spt-b2-livecheck-{}.sock", std::process::id());
            let broker = crate::broker::Broker::bind(&name).unwrap();
            let b2 = Arc::clone(&broker);
            std::thread::spawn(move || {
                let _ = b2.serve();
            });
            let mut brain = {
                let mut b = None;
                for _ in 0..200 {
                    if let Ok(c) = Brain::cold_start(&name, now_ms()) {
                        b = Some(c);
                        break;
                    }
                    std::thread::sleep(Duration::from_millis(10));
                }
                b.expect("connect to test broker")
            };
            #[cfg(unix)]
            let (program, args) = ("cat".to_string(), Vec::<String>::new());
            #[cfg(windows)]
            let (program, args) = ("findstr".to_string(), vec![".".to_string()]);
            brain
                .spawn_session(SpawnReq {
                    program,
                    args,
                    rows: 24,
                    cols: 80,
                    endpoint: "wallb".to_string(),
                    cwd: None,
                    env: Default::default(),
                })
                .unwrap();

            let session_set = |brain: &mut Brain| -> BTreeSet<String> {
                brain
                    .sessions()
                    .unwrap()
                    .sessions
                    .into_iter()
                    .map(|s| s.endpoint)
                    .filter(|e| !e.is_empty())
                    .collect()
            };
            let status = |id: &str| {
                spt_store::info::read_info(&perch::resolve_perch_path(id, ParentHint::Infer))
                    .and_then(|i| i.status)
            };

            // Session LIVE → reconcile leaves it online.
            let live = session_set(&mut brain);
            assert!(live.contains("wallb"), "broker hosts the session");
            assert!(reconcile_hosted_liveness(&perch::owlery_dir(), &live).is_empty());
            assert_eq!(status("wallb").as_deref(), Some(STATUS_ONLINE));

            // Kill the session; wait for the exit-waiter to reap it from the table.
            brain.kill_session().unwrap();
            assert!(
                wait_until(Duration::from_secs(5), || !session_set(&mut brain).contains("wallb")),
                "killed session leaves the broker table"
            );

            // Now the pull reconcile clears the latch → offline.
            let live2 = session_set(&mut brain);
            let offlined = reconcile_hosted_liveness(&perch::owlery_dir(), &live2);
            assert_eq!(offlined, vec!["wallb".to_string()], "dead-session perch offlined");
            assert_eq!(
                status("wallb").as_deref(),
                Some(spt_store::liveness::STATUS_OFFLINE),
                "the status=online latch is cleared once the broker session is gone"
            );
        });
    }

    // [unit->REQ-DAEMON-1] (a) FIRST-HOST scan: an online live endpoint on disk is
    // hosted by the reconcile — the Psyche is spawned AND the config-paced pulse
    // driver actually runs (it stamps last_active_ms on the Self perch each tick).
    #[test]
    fn reconcile_hosts_an_online_live_endpoint_and_drives_its_pulse() {
        with_home(|_| {
            let drops = perch::spt_home().join("drops");
            std::fs::create_dir_all(&drops).unwrap();
            register_live_adapter("mock", &drops);
            seed_live_perch("doyle", "mock", STATUS_ONLINE);

            let set = Arc::new(LiveSet::new());
            let cfg = fast_cfg();
            reconcile_once(
                &perch::owlery_dir(),
                &registered_now(),
                &perch::adapters_dir(),
                &set,
                &cfg,


Good — I have the patterns. Let me see how the existing host test asserts hosting (set.len / wait):


818	                &set,
819	                &cfg,
820	                StartReason::Cold,
821	            );
822	            assert_eq!(set.len(), 1, "the online live endpoint is hosted");
823
824	            // The driver pulse runs: an Active perch stamps last_active_ms each tick.
825	            let self_perch = perch::resolve_perch_path("doyle", ParentHint::Infer);
826	            let ran = wait_until(Duration::from_secs(3), || {
827	                spt_store::info::read_info(&self_perch)
828	                    .and_then(|i| i.last_active_ms)
829	                    .is_some()
830	            });
831	            assert!(ran, "the pulse driver loop ticks (stamps last_active_ms)");
832
833	            // Teardown so the test process doesn't leak the driver thread.
834	            set.stop_host("doyle");
835	        });
836	    }
837
838	    // [unit->REQ-DAEMON-1] (b) ENTRY GUARD: a never-hosted OFFLINE live endpoint is
839	    // skipped — online-only. (Also covers the interim status-absent case shape.)
840	    #[test]
841	    fn reconcile_skips_an_offline_live_endpoint() {
842	        with_home(|_| {
843	            let drops = perch::spt_home().join("drops");
844	            std::fs::create_dir_all(&drops).unwrap();
845	            register_live_adapter("mock", &drops);
846	            seed_live_perch("doyle", "mock", spt_store::liveness::STATUS_OFFLINE);
847
848	            let set = Arc::new(LiveSet::new());
849	            reconcile_once(
850	                &perch::owlery_dir(),
851	                &registered_now(),
852	                &perch::adapters_dir(),
853	                &set,
854	                &fast_cfg(),
855	                StartReason::Cold,
856	            );
857	            assert!(set.is_empty(), "an offline live endpoint is never hosted");
858	        });
859	    }
860
861	    // [unit->REQ-DAEMON-1] (c) NO DOUBLE-HOST: a second reconcile over an
862	    // already-tabled endpoint leaves exactly one host (the contains-guard).
863	    #[test]
864	    fn reconcile_does_not_double_host() {
865	        with_home(|_| {
866	            let drops = perch::spt_home().join("drops");
867	            std::fs::create_dir_all(&drops).unwrap();
868	            register_live_adapter("mock", &drops);
869	            seed_live_perch("doyle", "mock", STATUS_ONLINE);
870
871	            let set = Arc::new(LiveSet::new());
872	            let cfg = fast_cfg();
873	            let run = || {
874	                reconcile_once(
875	                    &perch::owlery_dir(),
876	                    &registered_now(),
877	                    &perch::adapters_dir(),

                &set,
                &cfg,
                StartReason::Cold,
            );
            assert_eq!(set.len(), 1, "the online live endpoint is hosted");

            // The driver pulse runs: an Active perch stamps last_active_ms each tick.
            let self_perch = perch::resolve_perch_path("doyle", ParentHint::Infer);
            let ran = wait_until(Duration::from_secs(3), || {
                spt_store::info::read_info(&self_perch)
                    .and_then(|i| i.last_active_ms)
                    .is_some()
            });
            assert!(ran, "the pulse driver loop ticks (stamps last_active_ms)");

            // Teardown so the test process doesn't leak the driver thread.
            set.stop_host("doyle");
        });
    }

    // [unit->REQ-DAEMON-1] (b) ENTRY GUARD: a never-hosted OFFLINE live endpoint is
    // skipped — online-only. (Also covers the interim status-absent case shape.)
    #[test]
    fn reconcile_skips_an_offline_live_endpoint() {
        with_home(|_| {
            let drops = perch::spt_home().join("drops");
            std::fs::create_dir_all(&drops).unwrap();
            register_live_adapter("mock", &drops);
            seed_live_perch("doyle", "mock", spt_store::liveness::STATUS_OFFLINE);

            let set = Arc::new(LiveSet::new());
            reconcile_once(
                &perch::owlery_dir(),
                &registered_now(),
                &perch::adapters_dir(),
                &set,
                &fast_cfg(),
                StartReason::Cold,
            );
            assert!(set.is_empty(), "an offline live endpoint is never hosted");
        });
    }

    // [unit->REQ-DAEMON-1] (c) NO DOUBLE-HOST: a second reconcile over an
    // already-tabled endpoint leaves exactly one host (the contains-guard).
    #[test]
    fn reconcile_does_not_double_host() {
        with_home(|_| {
            let drops = perch::spt_home().join("drops");
            std::fs::create_dir_all(&drops).unwrap();
            register_live_adapter("mock", &drops);
            seed_live_perch("doyle", "mock", STATUS_ONLINE);

            let set = Arc::new(LiveSet::new());
            let cfg = fast_cfg();
            let run = || {
                reconcile_once(
                    &perch::owlery_dir(),
                    &registered_now(),
                    &perch::adapters_dir(),


Add the B5 unit (boot-gate composition) right after the B2 unit:


    // [int->REQ-HAZARD-HOSTED-LIVENESS-RECONCILE] real broker session lifecycle:
    // an spt-hosted perch with a LIVE broker session stays online across a pull
    // reconcile; when its broker session is KILLED + reaped, the next pull
    // reconcile clears the status=online latch and marks the perch offline.
    #[test]
    fn pull_reconcile_offlines_perch_when_broker_session_dies() {

    // [unit->REQ-HAZARD-LIVEHOST-BOOT-LIVENESS-GATE] the B5 boot-gate: at boot the
    // reconcile runs reconcile_hosted_liveness BEFORE reconcile_once, so a
    // sessionless spt-hosted (controllable) online perch is OFFLINED first and
    // reconcile_once then never REVIVES its Psyche (the cold-start-after-unclean-stop
    // phantom). A session-backed perch is left online and IS hosted — proving the
    // gate is the live broker session, not a blanket boot block. This is the exact
    // ordering spawn_live_host runs each tick (now from tick 1, no boot grace).
    #[test]
    fn boot_gate_offlines_sessionless_controllable_then_reconcile_skips_host() {
        with_home(|_| {
            let drops = perch::spt_home().join("drops");
            std::fs::create_dir_all(&drops).unwrap();
            register_live_adapter("mock", &drops);

            // A controllable spt-hosted online perch WITH the adapter set — so
            // reconcile_once WOULD host it absent the gate.
            let seed_ctrl = |id: &str| {
                let perch = perch::resolve_perch_path(id, ParentHint::Infer);
                std::fs::create_dir_all(&perch).unwrap();
                let mut rec =
                    spt_store::info::InfoJson::new(id, "t", std::process::id(), "sid-1", "live_agent");
                rec.adapter = Some("mock".to_string());
                rec.controllable = Some(true);
                spt_store::info::write_info(&perch, &rec).unwrap();
                spt_store::info::set_status(&perch, STATUS_ONLINE).unwrap();
            };
            seed_ctrl("phantom"); // no broker session → dead-but-latched phantom
            seed_ctrl("livee"); // broker session present → genuinely live

            // Only `livee` has a live broker session.
            let mut live = BTreeSet::new();
            live.insert("livee".to_string());

            let set = Arc::new(LiveSet::new());
            let cfg = fast_cfg();
            // Boot-tick ordering: liveness reconcile (the gate) BEFORE the host scan.
            reconcile_hosted_liveness(&perch::owlery_dir(), &live);
            reconcile_once(
                &perch::owlery_dir(),
                &registered_now(),
                &perch::adapters_dir(),
                &set,
                &cfg,
                StartReason::Cold,
            );

            let status = |id: &str| {
                spt_store::info::read_info(&perch::resolve_perch_path(id, ParentHint::Infer))
                    .and_then(|i| i.status)
            };
            // The phantom was offlined by the gate and so NEVER hosted (no revival).
            assert_eq!(
                status("phantom").as_deref(),
                Some(spt_store::liveness::STATUS_OFFLINE),
                "sessionless controllable perch is offlined at boot, not hosted"
            );
            // The live, session-backed perch stays online and IS hosted.
            assert_eq!(status("livee").as_deref(), Some(STATUS_ONLINE), "session-backed stays online");
            assert_eq!(set.len(), 1, "only the session-backed endpoint is hosted (no phantom revival)");

            set.stop_host("livee"); // teardown the driver thread
        });
    }

    // [int->REQ-HAZARD-HOSTED-LIVENESS-RECONCILE] real broker session lifecycle:
    // an spt-hosted perch with a LIVE broker session stays online across a pull
    // reconcile; when its broker session is KILLED + reaped, the next pull
    // reconcile clears the status=online latch and marks the perch offline.
    #[test]
    fn pull_reconcile_offlines_perch_when_broker_session_dies() {


    // [unit->REQ-HAZARD-LIVEHOST-BOOT-LIVENESS-GATE] the B5 boot-gate: at boot the
    // reconcile runs reconcile_hosted_liveness BEFORE reconcile_once, so a
    // sessionless spt-hosted (controllable) online perch is OFFLINED first and
    // reconcile_once then never REVIVES its Psyche (the cold-start-after-unclean-stop
    // phantom). A session-backed perch is left online and IS hosted — proving the
    // gate is the live broker session, not a blanket boot block. This is the exact
    // ordering spawn_live_host runs each tick (now from tick 1, no boot grace).
    #[test]
    fn boot_gate_offlines_sessionless_controllable_then_reconcile_skips_host() {
        with_home(|_| {
            let drops = perch::spt_home().join("drops");
            std::fs::create_dir_all(&drops).unwrap();
            register_live_adapter("mock", &drops);

            // A controllable spt-hosted online perch WITH the adapter set — so
            // reconcile_once WOULD host it absent the gate.
            let seed_ctrl = |id: &str| {
                let perch = perch::resolve_perch_path(id, ParentHint::Infer);
                std::fs::create_dir_all(&perch).unwrap();
                let mut rec =
                    spt_store::info::InfoJson::new(id, "t", std::process::id(), "sid-1", "live_agent");
                rec.adapter = Some("mock".to_string());
                rec.controllable = Some(true);
                spt_store::info::write_info(&perch, &rec).unwrap();
                spt_store::info::set_status(&perch, STATUS_ONLINE).unwrap();
            };
            seed_ctrl("phantom"); // no broker session → dead-but-latched phantom
            seed_ctrl("livee"); // broker session present → genuinely live

            // Only `livee` has a live broker session.
            let mut live = BTreeSet::new();
            live.insert("livee".to_string());

            let set = Arc::new(LiveSet::new());
            let cfg = fast_cfg();
            // Boot-tick ordering: liveness reconcile (the gate) BEFORE the host scan.
            reconcile_hosted_liveness(&perch::owlery_dir(), &live);
            reconcile_once(
                &perch::owlery_dir(),
                &registered_now(),
                &perch::adapters_dir(),
                &set,
                &cfg,
                StartReason::Cold,
            );

            let status = |id: &str| {
                spt_store::info::read_info(&perch::resolve_perch_path(id, ParentHint::Infer))
                    .and_then(|i| i.status)
            };
            // The phantom was offlined by the gate and so NEVER hosted (no revival).
            assert_eq!(
                status("phantom").as_deref(),
                Some(spt_store::liveness::STATUS_OFFLINE),
                "sessionless controllable perch is offlined at boot, not hosted"
            );
            // The live, session-backed perch stays online and IS hosted.
            assert_eq!(status("livee").as_deref(), Some(STATUS_ONLINE), "session-backed stays online");
            assert_eq!(set.len(), 1, "only the session-backed endpoint is hosted (no phantom revival)");

            set.stop_host("livee"); // teardown the driver thread
        });
    }

    // [int->REQ-HAZARD-HOSTED-LIVENESS-RECONCILE] real broker session lifecycle:
    // an spt-hosted perch with a LIVE broker session stays online across a pull
    // reconcile; when its broker session is KILLED + reaped, the next pull
    // reconcile clears the status=online latch and marks the perch offline.
    #[test]
    fn pull_reconcile_offlines_perch_when_broker_session_dies() {

+    // [unit->REQ-HAZARD-LIVEHOST-BOOT-LIVENESS-GATE] the B5 boot-gate: at boot the

+    // reconcile runs reconcile_hosted_liveness BEFORE reconcile_once, so a

+    // sessionless spt-hosted (controllable) online perch is OFFLINED first and

+    // reconcile_once then never REVIVES its Psyche (the cold-start-after-unclean-stop

+    // phantom). A session-backed perch is left online and IS hosted — proving the

+    // gate is the live broker session, not a blanket boot block. This is the exact

+    // ordering spawn_live_host runs each tick (now from tick 1, no boot grace).

+    fn boot_gate_offlines_sessionless_controllable_then_reconcile_skips_host() {

+            let drops = perch::spt_home().join("drops");

+            std::fs::create_dir_all(&drops).unwrap();

+            register_live_adapter("mock", &drops);

+            // A controllable spt-hosted online perch WITH the adapter set — so

+            // reconcile_once WOULD host it absent the gate.

+            let seed_ctrl = |id: &str| {

+                let perch = perch::resolve_perch_path(id, ParentHint::Infer);

+                std::fs::create_dir_all(&perch).unwrap();

+                    spt_store::info::InfoJson::new(id, "t", std::process::id(), "sid-1", "live_agent");

+                rec.adapter = Some("mock".to_string());

+                rec.controllable = Some(true);

+                spt_store::info::write_info(&perch, &rec).unwrap();

+                spt_store::info::set_status(&perch, STATUS_ONLINE).unwrap();

+            seed_ctrl("phantom"); // no broker session → dead-but-latched phantom

+            seed_ctrl("livee"); // broker session present → genuinely live

+            // Only `livee` has a live broker session.

+            let mut live = BTreeSet::new();

+            live.insert("livee".to_string());

+            let set = Arc::new(LiveSet::new());

+            // Boot-tick ordering: liveness reconcile (the gate) BEFORE the host scan.

+            reconcile_hosted_liveness(&perch::owlery_dir(), &live);

+                &perch::adapters_dir(),

+                spt_store::info::read_info(&perch::resolve_perch_path(id, ParentHint::Infer))

+                    .and_then(|i| i.status)

+            // The phantom was offlined by the gate and so NEVER hosted (no revival).

+                status("phantom").as_deref(),

+                Some(spt_store::liveness::STATUS_OFFLINE),

+                "sessionless controllable perch is offlined at boot, not hosted"

+            // The live, session-backed perch stays online and IS hosted.

+            assert_eq!(status("livee").as_deref(), Some(STATUS_ONLINE), "session-backed stays online");

+            assert_eq!(set.len(), 1, "only the session-backed endpoint is hosted (no phantom revival)");

+            set.stop_host("livee"); // teardown the driver thread


Now the B5 int (real cold-start daemon). Read the bootrace E2E to model the harness (daemon spawn, net-less setup, scoped reap, come-up wait):