99	    let mut manifest = Manifest::from_toml_str(
100	        &std::fs::read_to_string(manifest_path).expect("read mock manifest"),
101	    )
102	    .expect("mock manifest valid");
103	    manifest.session.self_.as_mut().unwrap().command = format!(
104	        "\"{}\" --id {{id}} --session-id {{session_id}} --spt \"{}\"",
105	        mock_bin.display(),
106	        spt_bin.display(),
107	    );
108
109	    let runtime = ManifestRuntime::new(manifest);
110	    let id = "mock-e2e";
111	    let session_id = "sid-e2e-1";
112
113	    // Outbound: spawn the session. Bounded (not detached) so the test waits for
114	    // the helper's bind + state-idle to complete before asserting.
115	    let out = runtime
116	        .run_bounded(
117	            "self",
118	            &keys(&[("id", id), ("session_id", session_id)]),
119	            Duration::from_secs(30),
120	        )
121	        .expect("spawn mock session");
122	    assert!(
123	        out.success(),
124	        "mock-session failed: code={:?}\nstdout={}\nstderr={}",
125	        out.status_code,
126	        out.stdout,
127	        out.stderr
128	    );
129
130	    // Inbound assertion 1 — `api bind` flipped the perch live (REQ-START-2 /
131	    // REQ-SEAM-POSTSPAWN): info.json exists with the reported session id.
132	    let perch_path = perch::resolve_perch_path(id, ParentHint::Infer);
133	    let rec = spt_store::info::read_info(&perch_path).expect("perch info.json after bind");
134	    assert_eq!(rec.session_id, session_id);
135	    assert_eq!(rec.state, "live_agent");
136
137	    // Inbound assertion 2 — `api state idle` wrote the activity sentinel.
138	    assert!(
139	        perch_path.join(".idle").exists(),
140	        "state idle must write the .idle sentinel"
141	    );
142
143	    // A peer spools a message to the now-live perch.
144	    spool::spool_message_at(&perch_path, "tester", "hello over the contract").unwrap();
145
146	    // Inbound assertion 3 — `api poll` drains it (the full delivery round-trip).
147	    let poll = Command::new(&spt_bin).no_window()
148	        .args([
149	            "api",
150	            "--adapter",
151	            "mock",
152	            "poll",
153	            id,
154	            "--session-id",
155	            session_id,
156	        ])
157	        .env("SPT_HOME", home.path())
158	        .output()
159	        .expect("run api poll");
160	    assert!(poll.status.success(), "api poll exit: {:?}", poll.status);
161	    let stdout = String::from_utf8_lossy(&poll.stdout);
162	    assert!(
163	        stdout.contains("hello over the contract"),
164	        "api poll must deliver the spooled message, got: {stdout:?}"
165	    );
166
167	    // Auth negative — a poll with no proof is refused (REQ-HAZARD-LOCAL-API-AUTH
168	    // exercised through the real binary).
169	    let unauth = Command::new(&spt_bin).no_window()
170	        .args(["api", "--adapter", "mock", "poll", id])
171	        .env("SPT_HOME", home.path())
172	        .env_remove("OWL_SESSION_ID")
173	        .output()
174	        .expect("run unauth poll");
175	    assert!(
176	        !unauth.status.success(),
177	        "poll without proof must be refused"
178	    );
179	}
180
181	/// Harness-hosted startup proper: `api seed` then `api listen` consumes the seed
182	/// (matched by parent pid, validated against pid-recycle), binds the perch
183	/// skeleton→live, and relays a delivered message.
184	#[test]
185	fn seed_then_listen_binds_and_relays() {
186	    // [int->REQ-START-2]
187	    // [int->REQ-API-2]
188	    let _serial = E2E_LOCK.lock().unwrap_or_else(|e| e.into_inner());
189	    let home = tempfile::tempdir().unwrap();
190	    let spt_bin = PathBuf::from(env!("CARGO_BIN_EXE_spt"));
191	    let id = "listen-e2e";
192	    let session_id = "sid-listen-1";
193	    // The anchor must be a live process; the test runner itself qualifies.
194	    let anchor = std::process::id().to_string();
195
196	    // Pre-spool a message (creates the perch spool dir) so listen drains it on bind.
197	    std::env::set_var("SPT_HOME", home.path());
198	    start_inproc_daemon();
199	    let perch_path = perch::resolve_perch_path(id, ParentHint::Infer);
200	    spool::spool_message_at(&perch_path, "tester", "delivered via listen").unwrap();
201
202	    // Seed the startup record keyed by the anchor pid.
203	    let seed = Command::new(&spt_bin).no_window()
204	        .args([
205	            "api",
206	            "--adapter",
207	            "mock",
208	            "seed",
209	            "--pid",
210	            &anchor,
211	            "--session-id",
212	            session_id,
213	        ])
214	        .env("SPT_HOME", home.path())
215	        .status()
216	        .expect("run api seed");
217	    assert!(seed.success(), "api seed exit: {seed:?}");
218
219	    // Listen consumes the seed (explicit --parent-pid for a deterministic match),
220	    // binds the perch, drains the backlog, and exits (--once).
221	    let listen = Command::new(&spt_bin).no_window()
222	        .args([
223	            "api",
224	            "--adapter",
225	            "mock",
226	            "listen",
227	            id,
228	            "--parent-pid",
229	            &anchor,
230	            "--once",
231	        ])
232	        .env("SPT_HOME", home.path())
233	        .output()
234	        .expect("run api listen");
235	    assert!(
236	        listen.status.success(),
237	        "api listen exit: {:?}",
238	        listen.status
239	    );
240	    let stdout = String::from_utf8_lossy(&listen.stdout);
241	    assert!(
242	        stdout.contains("delivered via listen"),
243	        "listen must relay the spooled message, got: {stdout:?}"
244	    );
245
246	    // The perch was bound live with the seed's session id.
247	    let rec = spt_store::info::read_info(&perch_path).expect("perch info.json after listen");
248	    assert_eq!(rec.session_id, session_id);
249	    assert_eq!(rec.state, "live_agent");
250	}
251
252	/// M2b LiveAgent lifecycle E2E (T9): prove the full live-agent lifecycle through
253	/// the **generic mock adapter**, harness-agnostic:
254	/// spawn a LiveAgent → its Psyche spawns as a nested perch → a commune drop is
255	/// ingested + deleted by spt-core → a graceful `spt shutdown` fires the
256	/// echo-commune *before* teardown.
257	#[test]
258	fn live_agent_lifecycle_e2e() {

    let mut manifest = Manifest::from_toml_str(
        &std::fs::read_to_string(manifest_path).expect("read mock manifest"),
    )
    .expect("mock manifest valid");
    manifest.session.self_.as_mut().unwrap().command = format!(
        "\"{}\" --id {{id}} --session-id {{session_id}} --spt \"{}\"",
        mock_bin.display(),
        spt_bin.display(),
    );

    let runtime = ManifestRuntime::new(manifest);
    let id = "mock-e2e";
    let session_id = "sid-e2e-1";

    // Outbound: spawn the session. Bounded (not detached) so the test waits for
    // the helper's bind + state-idle to complete before asserting.
    let out = runtime
        .run_bounded(
            "self",
            &keys(&[("id", id), ("session_id", session_id)]),
            Duration::from_secs(30),
        )
        .expect("spawn mock session");
    assert!(
        out.success(),
        "mock-session failed: code={:?}\nstdout={}\nstderr={}",
        out.status_code,
        out.stdout,
        out.stderr
    );

    // Inbound assertion 1 — `api bind` flipped the perch live (REQ-START-2 /
    // REQ-SEAM-POSTSPAWN): info.json exists with the reported session id.
    let perch_path = perch::resolve_perch_path(id, ParentHint::Infer);
    let rec = spt_store::info::read_info(&perch_path).expect("perch info.json after bind");
    assert_eq!(rec.session_id, session_id);
    assert_eq!(rec.state, "live_agent");

    // Inbound assertion 2 — `api state idle` wrote the activity sentinel.
    assert!(
        perch_path.join(".idle").exists(),
        "state idle must write the .idle sentinel"
    );

    // A peer spools a message to the now-live perch.
    spool::spool_message_at(&perch_path, "tester", "hello over the contract").unwrap();

    // Inbound assertion 3 — `api poll` drains it (the full delivery round-trip).
    let poll = Command::new(&spt_bin).no_window()
        .args([
            "api",
            "--adapter",
            "mock",
            "poll",
            id,
            "--session-id",
            session_id,
        ])
        .env("SPT_HOME", home.path())
        .output()
        .expect("run api poll");
    assert!(poll.status.success(), "api poll exit: {:?}", poll.status);
    let stdout = String::from_utf8_lossy(&poll.stdout);
    assert!(
        stdout.contains("hello over the contract"),
        "api poll must deliver the spooled message, got: {stdout:?}"
    );

    // Auth negative — a poll with no proof is refused (REQ-HAZARD-LOCAL-API-AUTH
    // exercised through the real binary).
    let unauth = Command::new(&spt_bin).no_window()
        .args(["api", "--adapter", "mock", "poll", id])
        .env("SPT_HOME", home.path())
        .env_remove("OWL_SESSION_ID")
        .output()
        .expect("run unauth poll");
    assert!(
        !unauth.status.success(),
        "poll without proof must be refused"
    );
}

/// Harness-hosted startup proper: `api seed` then `api listen` consumes the seed
/// (matched by parent pid, validated against pid-recycle), binds the perch
/// skeleton→live, and relays a delivered message.
#[test]
fn seed_then_listen_binds_and_relays() {
    // [int->REQ-START-2]
    // [int->REQ-API-2]
    let _serial = E2E_LOCK.lock().unwrap_or_else(|e| e.into_inner());
    let home = tempfile::tempdir().unwrap();
    let spt_bin = PathBuf::from(env!("CARGO_BIN_EXE_spt"));
    let id = "listen-e2e";
    let session_id = "sid-listen-1";
    // The anchor must be a live process; the test runner itself qualifies.
    let anchor = std::process::id().to_string();

    // Pre-spool a message (creates the perch spool dir) so listen drains it on bind.
    std::env::set_var("SPT_HOME", home.path());
    start_inproc_daemon();
    let perch_path = perch::resolve_perch_path(id, ParentHint::Infer);
    spool::spool_message_at(&perch_path, "tester", "delivered via listen").unwrap();

    // Seed the startup record keyed by the anchor pid.
    let seed = Command::new(&spt_bin).no_window()
        .args([
            "api",
            "--adapter",
            "mock",
            "seed",
            "--pid",
            &anchor,
            "--session-id",
            session_id,
        ])
        .env("SPT_HOME", home.path())
        .status()
        .expect("run api seed");
    assert!(seed.success(), "api seed exit: {seed:?}");

    // Listen consumes the seed (explicit --parent-pid for a deterministic match),
    // binds the perch, drains the backlog, and exits (--once).
    let listen = Command::new(&spt_bin).no_window()
        .args([
            "api",
            "--adapter",
            "mock",
            "listen",
            id,
            "--parent-pid",
            &anchor,
            "--once",
        ])
        .env("SPT_HOME", home.path())
        .output()
        .expect("run api listen");
    assert!(
        listen.status.success(),
        "api listen exit: {:?}",
        listen.status
    );
    let stdout = String::from_utf8_lossy(&listen.stdout);
    assert!(
        stdout.contains("delivered via listen"),
        "listen must relay the spooled message, got: {stdout:?}"
    );

    // The perch was bound live with the seed's session id.
    let rec = spt_store::info::read_info(&perch_path).expect("perch info.json after listen");
    assert_eq!(rec.session_id, session_id);
    assert_eq!(rec.state, "live_agent");
}

/// M2b LiveAgent lifecycle E2E (T9): prove the full live-agent lifecycle through
/// the **generic mock adapter**, harness-agnostic:
/// spawn a LiveAgent → its Psyche spawns as a nested perch → a commune drop is
/// ingested + deleted by spt-core → a graceful `spt shutdown` fires the
/// echo-commune *before* teardown.
#[test]
fn live_agent_lifecycle_e2e() {


Likely two issues: (1) `harness_pid` reads the transient `spt api bind` pid (dies immediately), not the long-lived dummy — false `alive_after`; (2) real blocker = `online=false` + no ticks. Need the actual dummy/brain stderr. Re-run full (un-grepped) to a log.


Bash

cd /c/Users/decid/Documents/projects/spt-core
CARGO_TARGET_DIR=target-seam cargo test -p spt --test dummy_harness_e2e -- --test-threads=1 --nocapture > /tmp/w1.log 2>&1
echo "EXIT=$?"
echo "=== DIAGNOSTIC + stderr tail ==="
grep -nE "DIAGNOSTIC|ENDPOINT_RUN|LIVEHOST|bind|BIND|error|fail|FAIL|panic|DUMMY" /tmp/w1.log | head -50

Re-run Wave 1 full output to log, grep diagnostics


Command running in background with ID: byxra4ido. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\1e7c582d-c03a-4db0-bd84-c7b3b7959ea0\tasks\byxra4ido.output. You will be notified when it completes. To check interim output, use Read on that file path.


Awaiting full diagnostic log.


ack


awaiting byxra4ido


Awaiting Wave 1 diagnostic re-run.


<task-notification>
<task-id>byxra4ido</task-id>
<tool-use-id>toolu_011kemQhQWEQDJJk2ug2xSqo</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\1e7c582d-c03a-4db0-bd84-c7b3b7959ea0\tasks\byxra4ido.output</output-file>
<status>completed</status>
<summary>Background command "Re-run Wave 1 full output to log, grep diagnostics" completed (exit code 0)</summary>
</task-notification>


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


1	EXIT=101
2	=== DIAGNOSTIC + stderr tail ===
3	5:test endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches ... === DIAGNOSTIC: run_ok=true online=false harness_pid=Some(200176) alive_after=false rc_saw_tick=false ===
4	7:ENDPOINT_RUN:dummy7 adapter=dummyharness pid=Some(156316) session=8b92ac4f1090dbe6 (harness binds its perch on startup)
5	8:ENDPOINT_RUN_STARTED:dummy7 (attach with `spt rc dummy7`)
6	11:thread 'endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches' (128240) panicked at crates\spt\tests\dummy_harness_e2e.rs:232:5:
7	12:the dummy harness must bind its perch ONLINE via the real broker PTY bringup.
8	14:NODE_KEY_FAIL: identity unavailable — broker runs net-less, no retry
9	18:FAILED
10	20:failures:
11	22:failures:
12	25:test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 22.64s
13	27:error: test failed, to rerun pass `-p spt --test dummy_harness_e2e`
14

EXIT=101
=== DIAGNOSTIC + stderr tail ===
5:test endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches ... === DIAGNOSTIC: run_ok=true online=false harness_pid=Some(200176) alive_after=false rc_saw_tick=false ===
7:ENDPOINT_RUN:dummy7 adapter=dummyharness pid=Some(156316) session=8b92ac4f1090dbe6 (harness binds its perch on startup)
8:ENDPOINT_RUN_STARTED:dummy7 (attach with `spt rc dummy7`)
11:thread 'endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches' (128240) panicked at crates\spt\tests\dummy_harness_e2e.rs:232:5:
12:the dummy harness must bind its perch ONLINE via the real broker PTY bringup.
14:NODE_KEY_FAIL: identity unavailable — broker runs net-less, no retry
18:FAILED
20:failures:
22:failures:
25:test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 22.64s
27:error: test failed, to rerun pass `-p spt --test dummy_harness_e2e`


endpoint run spawned the dummy (pid 156316) + `api bind` ran (stamped 200176, transient) — but perch never went online. `api bind` wrote info.json but not `status=online`. Find where the bind sets online + why it's skipped here.


cd /c/Users/decid/Documents/projects/spt-core
grep -n "STATUS_ONLINE\|set_status\|mark.*online\|fn cmd_bind\|controllable" crates/spt/src/api/startup.rs crates/spt/src/cli.rs 2>/dev/null | grep -iv test | head -30

Find where bind sets status online


crates/spt/src/api/startup.rs:143:    // so there is no broker PTY → not controllable (REQ-PICKER-1).
crates/spt/src/api/startup.rs:182:    controllable: Option<bool>,
crates/spt/src/api/startup.rs:240:    rec.controllable = controllable.or_else(|| prior.as_ref().and_then(|p| p.controllable));
crates/spt/src/api/startup.rs:351:    // declares `[session.psyche_init]`) we mark the perch `status=online` at
crates/spt/src/api/startup.rs:361:        if let Err(e) = info::set_status(&perch_path, spt_store::liveness::STATUS_ONLINE) {
crates/spt/src/api/startup.rs:408:pub fn cmd_bind(
crates/spt/src/api/startup.rs:433:    // broker-PTY-controllable (REQ-PICKER-1). A non-live bind (a gateway/shell — not
crates/spt/src/api/startup.rs:436:    let controllable = if endpoint_type == "live_agent" {
crates/spt/src/api/startup.rs:448:        controllable,
crates/spt/src/api/startup.rs:455:            // `[session.psyche_init]` is marked status=online at establish; the
crates/spt/src/api/startup.rs:468:                if let Err(e) = info::set_status(&perch_path, spt_store::liveness::STATUS_ONLINE) {
crates/spt/src/api/startup.rs:727:    fn establish_stamps_controllable_by_hosting_mode() {
crates/spt/src/api/startup.rs:738:                .controllable,
crates/spt/src/api/startup.rs:740:            "listen = harness-hosted = not controllable"
crates/spt/src/api/startup.rs:748:                .controllable,
crates/spt/src/api/startup.rs:750:            "bind live_agent = spt-hosted = controllable"
crates/spt/src/api/startup.rs:758:                .controllable,
crates/spt/src/api/startup.rs:760:            "non-live bind leaves controllable unset"
crates/spt/src/cli.rs:2027:    // any — so `start`/`stop` behavior is legible. A registered controllable
crates/spt/src/cli.rs:2694:/// `controllable==Some(true)`, and it has no resolvable TCP relay address. These
crates/spt/src/cli.rs:2698:/// `Some`) takes the normal TCP path; an offline or non-controllable endpoint is
crates/spt/src/cli.rs:2708:        .and_then(|r| r.controllable)
crates/spt/src/cli.rs:3025:    let _ = spt_store::info::set_status(&perch_path, spt_store::liveness::STATUS_OFFLINE);
crates/spt/src/cli.rs:7181:        spt_store::info::set_status(&perch, spt_store::liveness::STATUS_ONLINE).unwrap();
crates/spt/src/cli.rs:7205:        spt_store::info::set_status(&perch, spt_store::liveness::STATUS_ONLINE).unwrap();
crates/spt/src/cli.rs:7239:        spt_store::info::set_status(&perch, spt_store::liveness::STATUS_OFFLINE).unwrap();
crates/spt/src/cli.rs:7326:    // controllable endpoint with NO relay address is an inject target; a relay
crates/spt/src/cli.rs:7327:    // address (harness-hosted), an offline perch, or a non-controllable one is not.
crates/spt/src/cli.rs:7337:        rec.controllable = Some(true);
crates/spt/src/cli.rs:7341:        spt_store::info::set_status(&perch_path, spt_store::liveness::STATUS_ONLINE).unwrap();


`cmd_bind` startup.rs:455-468 — online is marked conditionally. Read it.


430	    // The session's own anchor: the process that fired this hook (the session).
431	    let parent_pid = proc::parent_pid().unwrap_or_else(std::process::id);
432	    // spt-hosted: the daemon spawned a live_agent into a broker-owned PTY → it IS
433	    // broker-PTY-controllable (REQ-PICKER-1). A non-live bind (a gateway/shell — not
434	    // a PTY-attachable agent) stamps None; the picker's type-gated derivation never
435	    // amber-paints it regardless.
436	    let controllable = if endpoint_type == "live_agent" {
437	        Some(true)
438	    } else {
439	        None
440	    };
441	    match establish_perch(
442	        id,
443	        &session_id,
444	        parent_pid,
445	        adapter,
446	        subnet,
447	        endpoint_type,
448	        controllable,
449	    ) {
450	        Ok(token) => {
451	            eprintln!("BOUND:{id} token={token}");
452	            // Live-agent FIRST-HOST handoff for the spt-hosted (bind, no listen)
453	            // topology (M11-W0.3, REQ-DAEMON-1) — the mirror of W0.2's cmd_listen
454	            // mark. A live_agent endpoint whose manifest declares
455	            // `[session.psyche_init]` is marked status=online at establish; the
456	            // brain's livehost reconcile then hosts the Psyche (the SAME single host
457	            // path — bind never spawns/hosts/calls livehost). Closes the
458	            // bind-no-Psyche gap: the Psyche spawn was previously cmd_listen-only, so
459	            // a bind-only spt-hosted live agent never got one (spt-hosted uses direct
460	            // PTY, no relay — CONTEXT:39). Gated on state==live_agent ∧ psyche_init so
461	            // the establish-online signal == the reconcile host predicate (no
462	            // online-but-unhostable perch). status is daemon-authoritative (CONTEXT:194).
463	            // [impl->REQ-DAEMON-1]
464	            let live_capable = endpoint_type == "live_agent"
465	                && manifest.is_some_and(|m| m.session.psyche_init.is_some());
466	            if live_capable {
467	                let perch_path = perch::resolve_perch_path(id, ParentHint::Infer);
468	                if let Err(e) = info::set_status(&perch_path, spt_store::liveness::STATUS_ONLINE) {
469	                    eprintln!("LIVE_ONLINE_FAIL:{id}: {e}");
470	                }
471	            }
472	            // A post-spawn bind is a new-session-start — a reported resurface
473	            // boundary (ADR-0007/D8b). Best-effort.
474	            super::reporting::resurface_notifs(id);
475	            0
476	        }
477	        Err(e) => {
478	            report_bind_error(&e);
479	            EXIT_REFUSED

    // The session's own anchor: the process that fired this hook (the session).
    let parent_pid = proc::parent_pid().unwrap_or_else(std::process::id);
    // spt-hosted: the daemon spawned a live_agent into a broker-owned PTY → it IS
    // broker-PTY-controllable (REQ-PICKER-1). A non-live bind (a gateway/shell — not
    // a PTY-attachable agent) stamps None; the picker's type-gated derivation never
    // amber-paints it regardless.
    let controllable = if endpoint_type == "live_agent" {
        Some(true)
    } else {
        None
    };
    match establish_perch(
        id,
        &session_id,
        parent_pid,
        adapter,
        subnet,
        endpoint_type,
        controllable,
    ) {
        Ok(token) => {
            eprintln!("BOUND:{id} token={token}");
            // Live-agent FIRST-HOST handoff for the spt-hosted (bind, no listen)
            // topology (M11-W0.3, REQ-DAEMON-1) — the mirror of W0.2's cmd_listen
            // mark. A live_agent endpoint whose manifest declares
            // `[session.psyche_init]` is marked status=online at establish; the
            // brain's livehost reconcile then hosts the Psyche (the SAME single host
            // path — bind never spawns/hosts/calls livehost). Closes the
            // bind-no-Psyche gap: the Psyche spawn was previously cmd_listen-only, so
            // a bind-only spt-hosted live agent never got one (spt-hosted uses direct
            // PTY, no relay — CONTEXT:39). Gated on state==live_agent ∧ psyche_init so
            // the establish-online signal == the reconcile host predicate (no
            // online-but-unhostable perch). status is daemon-authoritative (CONTEXT:194).
            // [impl->REQ-DAEMON-1]
            let live_capable = endpoint_type == "live_agent"
                && manifest.is_some_and(|m| m.session.psyche_init.is_some());
            if live_capable {
                let perch_path = perch::resolve_perch_path(id, ParentHint::Infer);
                if let Err(e) = info::set_status(&perch_path, spt_store::liveness::STATUS_ONLINE) {
                    eprintln!("LIVE_ONLINE_FAIL:{id}: {e}");
                }
            }
            // A post-spawn bind is a new-session-start — a reported resurface
            // boundary (ADR-0007/D8b). Best-effort.
            super::reporting::resurface_notifs(id);
            0
        }
        Err(e) => {
            report_bind_error(&e);
            EXIT_REFUSED


Root found: `cmd_bind` marks `status=online` ONLY when `endpoint_type=="live_agent" && manifest.psyche_init.is_some()` (startup.rs:464) — so the online signal matches the reconcile host predicate. My dummy manifest had NO `[session.psyche_init]` → never online. A faithful live-agent dummy must declare a psyche (also needed for doyle's repro-#1 psyche-reap gate). Fix: add a long-lived psyche_init (mirror brain_restart's `psychebin ready` pattern) + parse the real broker-spawned harness pid. Report to doyle + fix.


cd /c/Users/decid/Documents/projects/spt-core
$OWL send doyle <<'EOF'
WAVE 1 root (fixture WIP): the dummy harness never went ONLINE because cmd_bind (startup.rs:464) marks status=online ONLY for `live_agent && manifest.psyche_init.is_some()` — the online signal is deliberately gated to match the reconcile host predicate (no online-but-unhostable perch). My first dummy manifest had no [session.psyche_init]. Fix: the faithful dummy = a live_agent WITH a long-lived psyche_init (mirror brain_restart_psyche_dup's `psychebin ready {id}` pattern) — which also gives Wave 2 the {id}-psyche needed for your repro-#1 psyche-reap gate. Also fixing a test bug (I read the transient api-bind pid for liveness; using endpoint-run's reported broker-spawned harness pid instead). Re-running.
EOF

Report Wave 1 root to doyle


➜ SENT:doyle


Edit

    let spt_bin = PathBuf::from(env!("CARGO_BIN_EXE_spt"));
    let mock_session = sibling_bin("mock-session");
    assert!(
        mock_session.exists(),
        "the dummy-harness program must be built: {} \
         (pre-build: cargo build -p mock-adapter --bin mock-session)",
        mock_session.display()
    );

    let spt_bin = PathBuf::from(env!("CARGO_BIN_EXE_spt"));
    let exe_suffix = spt_bin
        .extension()
        .map(|e| format!(".{}", e.to_string_lossy()))
        .unwrap_or_default();
    let mock_session = sibling_bin("mock-session");
    assert!(
        mock_session.exists(),
        "the dummy-harness program must be built: {} \
         (pre-build: cargo build -p mock-adapter --bin mock-session)",
        mock_session.display()
    );


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

    let spt_bin = PathBuf::from(env!("CARGO_BIN_EXE_spt"));
    let exe_suffix = spt_bin
        .extension()
        .map(|e| format!(".{}", e.to_string_lossy()))
        .unwrap_or_default();
    let mock_session = sibling_bin("mock-session");
    assert!(
        mock_session.exists(),
        "the dummy-harness program must be built: {} \
         (pre-build: cargo build -p mock-adapter --bin mock-session)",
        mock_session.display()
    );

//! Wave 1 (v0.12.1) — the DUMMY-HARNESS fixture smoke test. The permanent
//! real-harness regression fixture the v0.12.0 mock tests never had: it drives the
//! REAL spt-hosted bringup path end-to-end —
//! `spt endpoint run` → `launch_harness_brokered_in` → broker PTY → `spt rc` attach
//! — against a REAL detached `spt daemon run`, NOT an in-proc `reconcile_once` and
//! NOT a mock that exits immediately.
//!
//! The dummy harness (`mock-session --mode dummy`) binds its perch on startup (the
//! harness contract), prints a stdout heartbeat on an interval, and stays alive
//! until killed — so "broker wedged / endpoint died" is cleanly separable from "a
//! real harness failed to launch." Wave 2's lifecycle gates (VIEWER-CLOSE-DETACH,
//! ATTACH-WEDGE, daemon-stop) build on exactly this bring-up.
//!
//! Asserts: `endpoint run --start` brings the dummy up, its perch goes ONLINE, the
//! PTY child stays alive (heartbeating), and `spt rc <id>` attaches and receives the
//! live heartbeat output. Bounded; all spawned pids reaped scoped (never
//! machine-wide — shared runner).

use std::io::Read;
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Output, Stdio};
use std::time::{Duration, Instant};

mod common;
use common::CommandNoWindowExt;

use spt_store::perch::{self, ParentHint};

fn kill_pid(pid: u32) {
    #[cfg(windows)]
    let _ = Command::new("taskkill")
        .no_window()
        .args(["/PID", &pid.to_string(), "/F", "/T"])
        .output();
    #[cfg(unix)]
    let _ = Command::new("kill").args(["-9", &pid.to_string()]).output();
}

fn output_bounded(mut cmd: Command, deadline: Duration) -> Output {
    let (tx, rx) = std::sync::mpsc::channel();
    std::thread::spawn(move || {
        let _ = tx.send(cmd.output());
    });
    rx.recv_timeout(deadline)
        .expect("captured spt call must complete within its deadline")
        .expect("run spt")
}

fn ready_pid(path: &Path) -> Option<u32> {
    let s = std::fs::read_to_string(path).ok()?;
    serde_json::from_str::<serde_json::Value>(&s)
        .ok()?
        .get("pid")?
        .as_u64()
        .map(|p| p as u32)
}

fn wait_for_ready_pid(path: &Path, budget: Duration) -> Option<u32> {
    let deadline = Instant::now() + budget;
    while Instant::now() < deadline {
        if let Some(p) = ready_pid(path) {
            return Some(p);
        }
        std::thread::sleep(Duration::from_millis(50));
    }
    None
}

fn sibling_bin(name: &str) -> PathBuf {
    PathBuf::from(env!("CARGO_BIN_EXE_spt"))
        .with_file_name(format!("{name}{}", std::env::consts::EXE_SUFFIX))
}

#[test]
fn endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches() {
    // ── (1) Isolated, NET-LESS SPT_HOME. ──
    let home = tempfile::tempdir().unwrap();
    std::env::set_var("SPT_HOME", home.path());
    let identity_dir = home.path().join("identity");
    std::fs::create_dir_all(&identity_dir).unwrap();
    std::fs::write(identity_dir.join("node.key"), "not-a-valid-seed").unwrap();

    let spt_bin = PathBuf::from(env!("CARGO_BIN_EXE_spt"));
    let mock_session = sibling_bin("mock-session");
    assert!(
        mock_session.exists(),
        "the dummy-harness program must be built: {} \
         (pre-build: cargo build -p mock-adapter --bin mock-session)",
        mock_session.display()
    );

    // ── (2) Register the DUMMY-HARNESS adapter: a kind=harness adapter whose
    //    [session.self] runs the long-lived dummy. The broker fills {id}/{session_id};
    //    the program token is the absolute mock-session path. The dummy shells the
    //    same `spt` to bind (via --spt). ──
    let spt_for_cmd = spt_bin.to_string_lossy().replace('\\', "/");
    let mock_for_cmd = mock_session.to_string_lossy().replace('\\', "/");
    let self_cmd = format!(
        "{mock_for_cmd} --mode dummy --id {{id}} --session-id {{session_id}} \
         --spt {spt_for_cmd} --mock-adapter dummyharness --type live_agent --heartbeat-ms 200"
    );
    let manifest_toml = format!(
        "[adapter]\nname=\"dummyharness\"\nkind=\"harness\"\nversion=\"1\"\n\
         min_spt_core_version=\"0\"\n\n[session.self]\ncommand='{self_cmd}'\n"
    );
    let src = perch::spt_home().join("srcs").join("dummyharness");
    std::fs::create_dir_all(&src).unwrap();
    std::fs::write(src.join("manifest.toml"), &manifest_toml).unwrap();
    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();

    // ── (3) The REAL daemon broker process. ──
    let brain_log = home.path().join("brain.stderr.log");
    let brain_log_file = std::fs::File::create(&brain_log).expect("create brain stderr log");
    let mut broker: Child = Command::new(&spt_bin)
        .no_window()
        .args(["daemon", "run"])
        .env("SPT_HOME", home.path())
        .stdout(Stdio::null())
        .stderr(Stdio::from(brain_log_file))
        .spawn()
        .expect("spawn spt daemon run (broker process)");
    let brain_pid = match wait_for_ready_pid(&home.path().join("brain.ready"), Duration::from_secs(30))
    {
        Some(p) => p,
        None => {
            let _ = broker.kill();
            let _ = broker.wait();
            panic!(
                "PRECONDITION: brain never came up.\n{}",
                std::fs::read_to_string(&brain_log).unwrap_or_default()
            );
        }
    };

    // ── (4) The REAL spt-hosted bringup: endpoint run --start (broker spawns the
    //    dummy into a PTY; the dummy binds its perch + heartbeats). ──
    let id = "dummy7";
    let run = {
        let mut cmd = Command::new(&spt_bin);
        cmd.no_window()
            .args(["endpoint", "run", "--adapter", "dummyharness", "--id", id, "--start"])
            .env("SPT_HOME", home.path());
        output_bounded(cmd, Duration::from_secs(45))
    };

    let self_perch = perch::resolve_perch_path(id, ParentHint::Infer);
    let status = || spt_store::info::read_info(&self_perch).and_then(|i| i.status);

    // The dummy binds its perch live within a few seconds of the broker PTY spawn.
    let mut online = false;
    let deadline = Instant::now() + Duration::from_secs(20);
    while Instant::now() < deadline {
        if status().as_deref() == Some(spt_store::liveness::STATUS_ONLINE) {
            online = true;
            break;
        }
        std::thread::sleep(Duration::from_millis(150));
    }

    // The harness pid the bind stamped — it must stay alive (heartbeating), proving
    // a long-lived PTY child (not the immediate-exit mock).
    let harness_pid = spt_store::info::read_pid(&self_perch);
    let alive_after = {
        std::thread::sleep(Duration::from_millis(700));
        harness_pid.map(spt_store::proc::is_process_alive).unwrap_or(false)
    };

    // ── (5) rc ATTACH: stream the live PTY output; assert the heartbeat arrives. ──
    let mut rc: Child = Command::new(&spt_bin)
        .no_window()
        .args(["rc", id])
        .env("SPT_HOME", home.path())
        .stdout(Stdio::piped())
        .stderr(Stdio::null())
        .stdin(Stdio::null())
        .spawn()
        .expect("spawn spt rc <id>");
    let mut rc_out = rc.stdout.take().unwrap();
    let (tx, rx) = std::sync::mpsc::channel::<String>();
    std::thread::spawn(move || {
        let mut buf = [0u8; 4096];
        let mut acc = String::new();
        loop {
            match rc_out.read(&mut buf) {
                Ok(0) => break,
                Ok(n) => {
                    acc.push_str(&String::from_utf8_lossy(&buf[..n]));
                    if acc.contains("DUMMY_HARNESS_TICK") {
                        let _ = tx.send(acc.clone());
                        break;
                    }
                }
                Err(_) => break,
            }
        }
        let _ = tx.send(acc);
    });
    let rc_saw_tick = rx
        .recv_timeout(Duration::from_secs(10))
        .map(|s| s.contains("DUMMY_HARNESS_TICK"))
        .unwrap_or(false);

    let brain_stderr = std::fs::read_to_string(&brain_log).unwrap_or_default();
    eprintln!(
        "=== DIAGNOSTIC: run_ok={} online={online} harness_pid={harness_pid:?} \
         alive_after={alive_after} rc_saw_tick={rc_saw_tick} ===\n=== endpoint run stderr ===\n{}",
        run.status.success(),
        String::from_utf8_lossy(&run.stderr),
    );

    // ── (6) Reap scoped: kill rc, the harness, the brain, daemon-stop, broker. ──
    kill_pid(rc.id());
    let _ = rc.kill();
    let _ = rc.wait();
    if let Some(p) = harness_pid {
        kill_pid(p);
    }
    let _ = {
        let mut cmd = Command::new(&spt_bin);
        cmd.no_window().args(["daemon", "stop"]).env("SPT_HOME", home.path());
        output_bounded(cmd, Duration::from_secs(20))
    };
    kill_pid(brain_pid);
    let _ = broker.kill();
    let _ = broker.wait();

    assert!(
        run.status.success(),
        "endpoint run --start must succeed: {}",
        String::from_utf8_lossy(&run.stderr)
    );
    assert!(
        online,
        "the dummy harness must bind its perch ONLINE via the real broker PTY bringup.\n\
         === brain stderr ===\n{brain_stderr}"
    );
    assert!(
        alive_after,
        "the dummy harness PTY child must STAY ALIVE (heartbeating), not exit like the \
         default mock — harness_pid={harness_pid:?}"
    );
    assert!(
        rc_saw_tick,
        "`spt rc <id>` must attach and receive the live DUMMY_HARNESS_TICK heartbeat \
         (the real broker-PTY → rc attach path).\n=== brain stderr ===\n{brain_stderr}"
    );
}

+        .map(|e| format!(".{}", e.to_string_lossy()))


    let self_cmd = format!(
        "{mock_for_cmd} --mode dummy --id {{id}} --session-id {{session_id}} \
         --spt {spt_for_cmd} --mock-adapter dummyharness --type live_agent --heartbeat-ms 200"
    );
    let manifest_toml = format!(
        "[adapter]\nname=\"dummyharness\"\nkind=\"harness\"\nversion=\"1\"\n\
         min_spt_core_version=\"0\"\n\n[session.self]\ncommand='{self_cmd}'\n"
    );
    let src = perch::spt_home().join("srcs").join("dummyharness");
    std::fs::create_dir_all(&src).unwrap();
    std::fs::write(src.join("manifest.toml"), &manifest_toml).unwrap();
    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();

    let self_cmd = format!(
        "{mock_for_cmd} --mode dummy --id {{id}} --session-id {{session_id}} \
         --spt {spt_for_cmd} --mock-adapter dummyharness --type live_agent --heartbeat-ms 200"
    );
    // A faithful live_agent fixture DECLARES `[session.psyche_init]`: cmd_bind only
    // marks status=online for `live_agent && psyche_init.is_some()` (startup.rs:464 —
    // the online signal is gated to the reconcile host predicate). A LONG-LIVED psyche
    // (a copy of `spt` run as `ready {id}`, mirroring brain_restart_psyche_dup) so the
    // brain's reconcile hosts a resident `{id}-psyche` — the same shape the Wave 2
    // VIEWER-CLOSE / psyche-reap gates need.
    let src = perch::spt_home().join("srcs").join("dummyharness");
    std::fs::create_dir_all(&src).unwrap();
    let psyche_bin = src.join(format!("psychebin{exe_suffix}"));
    std::fs::copy(&spt_bin, &psyche_bin).expect("stage the psyche binary fixture");
    let drops = home.path().join("drops");
    std::fs::create_dir_all(&drops).unwrap();
    let dir = drops.to_string_lossy().replace('\\', "/");
    let manifest_toml = format!(
        "[adapter]\nname=\"dummyharness\"\nkind=\"harness\"\nversion=\"1\"\n\
         min_spt_core_version=\"0\"\n\n[session.self]\ncommand='{self_cmd}'\n\n\
         [session]\ncommune_dir=\"{dir}\"\nsignoff_dir=\"{dir}\"\n\n\
         [session.psyche_init]\ncommand='psychebin ready {{id}}'\ncwd=\"{{psyche_dir}}\"\nkeys=[]\n"
    );
    std::fs::write(src.join("manifest.toml"), &manifest_toml).unwrap();
    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();


    let self_cmd = format!(
        "{mock_for_cmd} --mode dummy --id {{id}} --session-id {{session_id}} \
         --spt {spt_for_cmd} --mock-adapter dummyharness --type live_agent --heartbeat-ms 200"
    );
    // A faithful live_agent fixture DECLARES `[session.psyche_init]`: cmd_bind only
    // marks status=online for `live_agent && psyche_init.is_some()` (startup.rs:464 —
    // the online signal is gated to the reconcile host predicate). A LONG-LIVED psyche
    // (a copy of `spt` run as `ready {id}`, mirroring brain_restart_psyche_dup) so the
    // brain's reconcile hosts a resident `{id}-psyche` — the same shape the Wave 2
    // VIEWER-CLOSE / psyche-reap gates need.
    let src = perch::spt_home().join("srcs").join("dummyharness");
    std::fs::create_dir_all(&src).unwrap();
    let psyche_bin = src.join(format!("psychebin{exe_suffix}"));
    std::fs::copy(&spt_bin, &psyche_bin).expect("stage the psyche binary fixture");
    let drops = home.path().join("drops");
    std::fs::create_dir_all(&drops).unwrap();
    let dir = drops.to_string_lossy().replace('\\', "/");
    let manifest_toml = format!(
        "[adapter]\nname=\"dummyharness\"\nkind=\"harness\"\nversion=\"1\"\n\
         min_spt_core_version=\"0\"\n\n[session.self]\ncommand='{self_cmd}'\n\n\
         [session]\ncommune_dir=\"{dir}\"\nsignoff_dir=\"{dir}\"\n\n\
         [session.psyche_init]\ncommand='psychebin ready {{id}}'\ncwd=\"{{psyche_dir}}\"\nkeys=[]\n"
    );
    std::fs::write(src.join("manifest.toml"), &manifest_toml).unwrap();
    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();

//! Wave 1 (v0.12.1) — the DUMMY-HARNESS fixture smoke test. The permanent
//! real-harness regression fixture the v0.12.0 mock tests never had: it drives the
//! REAL spt-hosted bringup path end-to-end —
//! `spt endpoint run` → `launch_harness_brokered_in` → broker PTY → `spt rc` attach
//! — against a REAL detached `spt daemon run`, NOT an in-proc `reconcile_once` and
//! NOT a mock that exits immediately.
//!
//! The dummy harness (`mock-session --mode dummy`) binds its perch on startup (the
//! harness contract), prints a stdout heartbeat on an interval, and stays alive
//! until killed — so "broker wedged / endpoint died" is cleanly separable from "a
//! real harness failed to launch." Wave 2's lifecycle gates (VIEWER-CLOSE-DETACH,
//! ATTACH-WEDGE, daemon-stop) build on exactly this bring-up.
//!
//! Asserts: `endpoint run --start` brings the dummy up, its perch goes ONLINE, the
//! PTY child stays alive (heartbeating), and `spt rc <id>` attaches and receives the
//! live heartbeat output. Bounded; all spawned pids reaped scoped (never
//! machine-wide — shared runner).

use std::io::Read;
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Output, Stdio};
use std::time::{Duration, Instant};

mod common;
use common::CommandNoWindowExt;

use spt_store::perch::{self, ParentHint};

fn kill_pid(pid: u32) {
    #[cfg(windows)]
    let _ = Command::new("taskkill")
        .no_window()
        .args(["/PID", &pid.to_string(), "/F", "/T"])
        .output();
    #[cfg(unix)]
    let _ = Command::new("kill").args(["-9", &pid.to_string()]).output();
}

fn output_bounded(mut cmd: Command, deadline: Duration) -> Output {
    let (tx, rx) = std::sync::mpsc::channel();
    std::thread::spawn(move || {
        let _ = tx.send(cmd.output());
    });
    rx.recv_timeout(deadline)
        .expect("captured spt call must complete within its deadline")
        .expect("run spt")
}

fn ready_pid(path: &Path) -> Option<u32> {
    let s = std::fs::read_to_string(path).ok()?;
    serde_json::from_str::<serde_json::Value>(&s)
        .ok()?
        .get("pid")?
        .as_u64()
        .map(|p| p as u32)
}

fn wait_for_ready_pid(path: &Path, budget: Duration) -> Option<u32> {
    let deadline = Instant::now() + budget;
    while Instant::now() < deadline {
        if let Some(p) = ready_pid(path) {
            return Some(p);
        }
        std::thread::sleep(Duration::from_millis(50));
    }
    None
}

fn sibling_bin(name: &str) -> PathBuf {
    PathBuf::from(env!("CARGO_BIN_EXE_spt"))
        .with_file_name(format!("{name}{}", std::env::consts::EXE_SUFFIX))
}

#[test]
fn endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches() {
    // ── (1) Isolated, NET-LESS SPT_HOME. ──
    let home = tempfile::tempdir().unwrap();
    std::env::set_var("SPT_HOME", home.path());
    let identity_dir = home.path().join("identity");
    std::fs::create_dir_all(&identity_dir).unwrap();
    std::fs::write(identity_dir.join("node.key"), "not-a-valid-seed").unwrap();

    let spt_bin = PathBuf::from(env!("CARGO_BIN_EXE_spt"));
    let exe_suffix = spt_bin
        .extension()
        .map(|e| format!(".{}", e.to_string_lossy()))
        .unwrap_or_default();
    let mock_session = sibling_bin("mock-session");
    assert!(
        mock_session.exists(),
        "the dummy-harness program must be built: {} \
         (pre-build: cargo build -p mock-adapter --bin mock-session)",
        mock_session.display()
    );

    // ── (2) Register the DUMMY-HARNESS adapter: a kind=harness adapter whose
    //    [session.self] runs the long-lived dummy. The broker fills {id}/{session_id};
    //    the program token is the absolute mock-session path. The dummy shells the
    //    same `spt` to bind (via --spt). ──
    let spt_for_cmd = spt_bin.to_string_lossy().replace('\\', "/");
    let mock_for_cmd = mock_session.to_string_lossy().replace('\\', "/");
    let self_cmd = format!(
        "{mock_for_cmd} --mode dummy --id {{id}} --session-id {{session_id}} \
         --spt {spt_for_cmd} --mock-adapter dummyharness --type live_agent --heartbeat-ms 200"
    );
    let manifest_toml = format!(
        "[adapter]\nname=\"dummyharness\"\nkind=\"harness\"\nversion=\"1\"\n\
         min_spt_core_version=\"0\"\n\n[session.self]\ncommand='{self_cmd}'\n"
    );
    let src = perch::spt_home().join("srcs").join("dummyharness");
    std::fs::create_dir_all(&src).unwrap();
    std::fs::write(src.join("manifest.toml"), &manifest_toml).unwrap();
    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();

    // ── (3) The REAL daemon broker process. ──
    let brain_log = home.path().join("brain.stderr.log");
    let brain_log_file = std::fs::File::create(&brain_log).expect("create brain stderr log");
    let mut broker: Child = Command::new(&spt_bin)
        .no_window()
        .args(["daemon", "run"])
        .env("SPT_HOME", home.path())
        .stdout(Stdio::null())
        .stderr(Stdio::from(brain_log_file))
        .spawn()
        .expect("spawn spt daemon run (broker process)");
    let brain_pid = match wait_for_ready_pid(&home.path().join("brain.ready"), Duration::from_secs(30))
    {
        Some(p) => p,
        None => {
            let _ = broker.kill();
            let _ = broker.wait();
            panic!(
                "PRECONDITION: brain never came up.\n{}",
                std::fs::read_to_string(&brain_log).unwrap_or_default()
            );
        }
    };

    // ── (4) The REAL spt-hosted bringup: endpoint run --start (broker spawns the
    //    dummy into a PTY; the dummy binds its perch + heartbeats). ──
    let id = "dummy7";
    let run = {
        let mut cmd = Command::new(&spt_bin);
        cmd.no_window()
            .args(["endpoint", "run", "--adapter", "dummyharness", "--id", id, "--start"])
            .env("SPT_HOME", home.path());
        output_bounded(cmd, Duration::from_secs(45))
    };

    let self_perch = perch::resolve_perch_path(id, ParentHint::Infer);
    let status = || spt_store::info::read_info(&self_perch).and_then(|i| i.status);

    // The dummy binds its perch live within a few seconds of the broker PTY spawn.
    let mut online = false;
    let deadline = Instant::now() + Duration::from_secs(20);
    while Instant::now() < deadline {
        if status().as_deref() == Some(spt_store::liveness::STATUS_ONLINE) {
            online = true;
            break;
        }
        std::thread::sleep(Duration::from_millis(150));
    }

    // The harness pid the bind stamped — it must stay alive (heartbeating), proving
    // a long-lived PTY child (not the immediate-exit mock).
    let harness_pid = spt_store::info::read_pid(&self_perch);
    let alive_after = {
        std::thread::sleep(Duration::from_millis(700));
        harness_pid.map(spt_store::proc::is_process_alive).unwrap_or(false)
    };

    // ── (5) rc ATTACH: stream the live PTY output; assert the heartbeat arrives. ──
    let mut rc: Child = Command::new(&spt_bin)
        .no_window()
        .args(["rc", id])
        .env("SPT_HOME", home.path())
        .stdout(Stdio::piped())
        .stderr(Stdio::null())
        .stdin(Stdio::null())
        .spawn()
        .expect("spawn spt rc <id>");
    let mut rc_out = rc.stdout.take().unwrap();
    let (tx, rx) = std::sync::mpsc::channel::<String>();
    std::thread::spawn(move || {
        let mut buf = [0u8; 4096];
        let mut acc = String::new();
        loop {
            match rc_out.read(&mut buf) {
                Ok(0) => break,
                Ok(n) => {
                    acc.push_str(&String::from_utf8_lossy(&buf[..n]));
                    if acc.contains("DUMMY_HARNESS_TICK") {
                        let _ = tx.send(acc.clone());
                        break;
                    }
                }
                Err(_) => break,
            }
        }
        let _ = tx.send(acc);
    });
    let rc_saw_tick = rx
        .recv_timeout(Duration::from_secs(10))
        .map(|s| s.contains("DUMMY_HARNESS_TICK"))
        .unwrap_or(false);

    let brain_stderr = std::fs::read_to_string(&brain_log).unwrap_or_default();
    eprintln!(
        "=== DIAGNOSTIC: run_ok={} online={online} harness_pid={harness_pid:?} \
         alive_after={alive_after} rc_saw_tick={rc_saw_tick} ===\n=== endpoint run stderr ===\n{}",
        run.status.success(),
        String::from_utf8_lossy(&run.stderr),
    );

    // ── (6) Reap scoped: kill rc, the harness, the brain, daemon-stop, broker. ──
    kill_pid(rc.id());
    let _ = rc.kill();
    let _ = rc.wait();
    if let Some(p) = harness_pid {
        kill_pid(p);
    }
    let _ = {
        let mut cmd = Command::new(&spt_bin);
        cmd.no_window().args(["daemon", "stop"]).env("SPT_HOME", home.path());
        output_bounded(cmd, Duration::from_secs(20))
    };
    kill_pid(brain_pid);
    let _ = broker.kill();
    let _ = broker.wait();

    assert!(
        run.status.success(),
        "endpoint run --start must succeed: {}",
        String::from_utf8_lossy(&run.stderr)
    );
    assert!(
        online,
        "the dummy harness must bind its perch ONLINE via the real broker PTY bringup.\n\
         === brain stderr ===\n{brain_stderr}"
    );
    assert!(
        alive_after,
        "the dummy harness PTY child must STAY ALIVE (heartbeating), not exit like the \
         default mock — harness_pid={harness_pid:?}"
    );
    assert!(
        rc_saw_tick,
        "`spt rc <id>` must attach and receive the live DUMMY_HARNESS_TICK heartbeat \
         (the real broker-PTY → rc attach path).\n=== brain stderr ===\n{brain_stderr}"
    );
}

+    // A faithful live_agent fixture DECLARES `[session.psyche_init]`: cmd_bind only

+    // marks status=online for `live_agent && psyche_init.is_some()` (startup.rs:464 —

+    // the online signal is gated to the reconcile host predicate). A LONG-LIVED psyche

+    // (a copy of `spt` run as `ready {id}`, mirroring brain_restart_psyche_dup) so the

+    // brain's reconcile hosts a resident `{id}-psyche` — the same shape the Wave 2

+    // VIEWER-CLOSE / psyche-reap gates need.

+    let src = perch::spt_home().join("srcs").join("dummyharness");

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

+    let psyche_bin = src.join(format!("psychebin{exe_suffix}"));

+    std::fs::copy(&spt_bin, &psyche_bin).expect("stage the psyche binary fixture");

+    let drops = home.path().join("drops");

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

+    let dir = drops.to_string_lossy().replace('\\', "/");

-         min_spt_core_version=\"0\"\n\n[session.self]\ncommand='{self_cmd}'\n"

+         min_spt_core_version=\"0\"\n\n[session.self]\ncommand='{self_cmd}'\n\n\

+         [session]\ncommune_dir=\"{dir}\"\nsignoff_dir=\"{dir}\"\n\n\

+         [session.psyche_init]\ncommand='psychebin ready {{id}}'\ncwd=\"{{psyche_dir}}\"\nkeys=[]\n"

-    let src = perch::spt_home().join("srcs").join("dummyharness");

-    std::fs::create_dir_all(&src).unwrap();


Now fix the liveness check (use endpoint-run's reported broker-spawned harness pid, not the transient bind pid) + reap the psyche.


    // The harness pid the bind stamped — it must stay alive (heartbeating), proving
    // a long-lived PTY child (not the immediate-exit mock).
    let harness_pid = spt_store::info::read_pid(&self_perch);
    let alive_after = {
        std::thread::sleep(Duration::from_millis(700));
        harness_pid.map(spt_store::proc::is_process_alive).unwrap_or(false)
    };

    // The broker-spawned harness pid (from `ENDPOINT_RUN:… pid=Some(NNN)`) — the
    // long-lived PTY child itself, NOT the transient `spt api bind` subprocess that
    // stamps info.json's pid. It must stay alive (heartbeating).
    let run_stderr = String::from_utf8_lossy(&run.stderr).to_string();
    let harness_pid: Option<u32> = run_stderr
        .split("pid=Some(")
        .nth(1)
        .and_then(|s| s.split(')').next())
        .and_then(|s| s.trim().parse().ok());
    let alive_after = {
        std::thread::sleep(Duration::from_millis(700));
        harness_pid.map(spt_store::proc::is_process_alive).unwrap_or(false)
    };


    // The harness pid the bind stamped — it must stay alive (heartbeating), proving
    // a long-lived PTY child (not the immediate-exit mock).
    let harness_pid = spt_store::info::read_pid(&self_perch);
    let alive_after = {
        std::thread::sleep(Duration::from_millis(700));
        harness_pid.map(spt_store::proc::is_process_alive).unwrap_or(false)
    };

    // The broker-spawned harness pid (from `ENDPOINT_RUN:… pid=Some(NNN)`) — the
    // long-lived PTY child itself, NOT the transient `spt api bind` subprocess that
    // stamps info.json's pid. It must stay alive (heartbeating).
    let run_stderr = String::from_utf8_lossy(&run.stderr).to_string();
    let harness_pid: Option<u32> = run_stderr
        .split("pid=Some(")
        .nth(1)
        .and_then(|s| s.split(')').next())
        .and_then(|s| s.trim().parse().ok());
    let alive_after = {
        std::thread::sleep(Duration::from_millis(700));
        harness_pid.map(spt_store::proc::is_process_alive).unwrap_or(false)
    };

-    // The harness pid the bind stamped — it must stay alive (heartbeating), proving

-    // a long-lived PTY child (not the immediate-exit mock).

-    let harness_pid = spt_store::info::read_pid(&self_perch);

+    // The broker-spawned harness pid (from `ENDPOINT_RUN:… pid=Some(NNN)`) — the

+    // long-lived PTY child itself, NOT the transient `spt api bind` subprocess that

+    // stamps info.json's pid. It must stay alive (heartbeating).

+    let run_stderr = String::from_utf8_lossy(&run.stderr).to_string();

+    let harness_pid: Option<u32> = run_stderr

+        .and_then(|s| s.split(')').next())

+        .and_then(|s| s.trim().parse().ok());


Add psyche reap to the cleanup block.


    kill_pid(rc.id());
    let _ = rc.kill();
    let _ = rc.wait();
    if let Some(p) = harness_pid {
        kill_pid(p);
    }

    kill_pid(rc.id());
    let _ = rc.kill();
    let _ = rc.wait();
    if let Some(p) = harness_pid {
        kill_pid(p);
    }
    // The reconcile may have hosted a `{id}-psyche` — reap it too (scoped).
    let psyche_perch =
        perch::resolve_perch_path(&format!("{id}-psyche"), ParentHint::Explicit(id));
    if let Some(p) = spt_store::info::read_pid(&psyche_perch) {
        kill_pid(p);
    }


    kill_pid(rc.id());
    let _ = rc.kill();
    let _ = rc.wait();
    if let Some(p) = harness_pid {
        kill_pid(p);
    }
    // The reconcile may have hosted a `{id}-psyche` — reap it too (scoped).
    let psyche_perch =
        perch::resolve_perch_path(&format!("{id}-psyche"), ParentHint::Explicit(id));
    if let Some(p) = spt_store::info::read_pid(&psyche_perch) {
        kill_pid(p);
    }

+    // The reconcile may have hosted a `{id}-psyche` — reap it too (scoped).

+        perch::resolve_perch_path(&format!("{id}-psyche"), ParentHint::Explicit(id));

+    if let Some(p) = spt_store::info::read_pid(&psyche_perch) {


Re-run Wave 1 with psyche_init fixture + corrected pid.


Bash

cd /c/Users/decid/Documents/projects/spt-core
CARGO_TARGET_DIR=target-seam cargo test -p spt --test dummy_harness_e2e -- --test-threads=1 --nocapture > /tmp/w1b.log 2>&1
echo "EXIT=$?"
grep -nE "DIAGNOSTIC|ENDPOINT_RUN|LIVEHOST_PSYCHE|test result:|panicked|DUMMY_HARNESS|LIVE_ONLINE_FAIL|BIND|error\[" /tmp/w1b.log | head -40

Re-run Wave 1 with psyche_init + pid fix


Command running in background with ID: bv1ki8az4. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\1e7c582d-c03a-4db0-bd84-c7b3b7959ea0\tasks\bv1ki8az4.output. You will be notified when it completes. To check interim output, use Read on that file path.


Awaiting Wave 1 re-run (with psyche fixture).


ack


awaiting bv1ki8az4


<task-notification>
<task-id>bv1ki8az4</task-id>
<tool-use-id>toolu_01SA31h1CwNxQrzaT5gjgFGA</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\1e7c582d-c03a-4db0-bd84-c7b3b7959ea0\tasks\bv1ki8az4.output</output-file>
<status>completed</status>
<summary>Background command "Re-run Wave 1 with psyche_init + pid fix" completed (exit code 0)</summary>
</task-notification>


Awaiting Wave 1 re-run result.


1	EXIT=101
2	6:test endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches ... === DIAGNOSTIC: run_ok=true online=true harness_pid=Some(249772) alive_after=true rc_saw_tick=false ===
3	8:ENDPOINT_RUN:dummy7 adapter=dummyharness pid=Some(249772) session=b3de3f3a462d0719 (harness binds its perch on startup)
4	9:ENDPOINT_RUN_STARTED:dummy7 (attach with `spt rc dummy7`)
5	12:thread 'endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches' (219332) panicked at crates\spt\tests\dummy_harness_e2e.rs:271:5:
6	13:`spt rc <id>` must attach and receive the live DUMMY_HARNESS_TICK heartbeat (the real broker-PTY → rc attach path).
7	26:test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.75s
8

EXIT=101
6:test endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches ... === DIAGNOSTIC: run_ok=true online=true harness_pid=Some(249772) alive_after=true rc_saw_tick=false ===
8:ENDPOINT_RUN:dummy7 adapter=dummyharness pid=Some(249772) session=b3de3f3a462d0719 (harness binds its perch on startup)
9:ENDPOINT_RUN_STARTED:dummy7 (attach with `spt rc dummy7`)
12:thread 'endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches' (219332) panicked at crates\spt\tests\dummy_harness_e2e.rs:271:5:
13:`spt rc <id>` must attach and receive the live DUMMY_HARNESS_TICK heartbeat (the real broker-PTY → rc attach path).
26:test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.75s


Big progress: `online=true`, `alive_after=true` — bringup works (online + long-lived + psyche hosted). Only `rc_saw_tick=false`, and total runtime 2.75s means rc closed immediately (didn't stream 10s). rc attach produced nothing. Check `run_attach` failure modes (rc stderr was suppressed).


182	pub fn run_attach(endpoint_id: &str, intent: AttachIntent) -> Result<(), String> {
183	    let view = intent == AttachIntent::Viewer;
184	    // Busy-refuse rendered CLIENT-SIDE (REQ-RCVIEW-1): a plain `Control` attach to
185	    // an endpoint a DIFFERENT node already controls gets actionable guidance here
186	    // (the broker would also refuse on the wire — this is the friendly path). A
187	    // `--view` / `--take` bypasses it (watching coexists; taking displaces).
188	    if intent == AttachIntent::Control {
189	        if let Some(node) = current_driver(endpoint_id) {
190	            println!("'{endpoint_id}' is controlled by {node}.");
191	            println!("  spt rc {endpoint_id} --view   to watch read-only");
192	            println!("  spt rc {endpoint_id} --take   to take control (kicks {node})");
193	            return Ok(());
194	        }
195	    }
196	    // (a) STATUS-GATE (REQ-HAZARD-RC-ATTACH-FAILFAST): a perch with an EXPLICIT
197	    // `status=offline` is not attachable — short-circuit with clean guidance
198	    // [impl->REQ-HAZARD-RC-ATTACH-FAILFAST]
199	    // instead of the broker dance that would block on a dead session (the
200	    // infinite-blank hang). The B2 pull-reconcile / `endpoint stop` stamp a dead
201	    // spt-hosted perch offline, so this catches the common case before any IPC. An
202	    // ABSENT or status-less perch falls through to the broker path, which already
203	    // ends cleanly ("no live session for endpoint") — not a hang. (is_perch_alive
204	    // is NOT used here: it returns true for an absent info.json by interim parity.)
205	    let perch_path =
206	        spt_store::perch::resolve_perch_path(endpoint_id, spt_store::perch::ParentHint::Infer);
207	    if spt_store::info::read_info(&perch_path)
208	        .and_then(|i| i.status)
209	        .as_deref()
210	        == Some(spt_store::liveness::STATUS_OFFLINE)
211	    {
212	        println!("Endpoint '{endpoint_id}' is offline — nothing to attach to.");
213	        println!("  spt endpoint run --adapter <adapter> --id {endpoint_id}   to start it");
214	        return Ok(());
215	    }
216	    spt_daemon::daemon::ensure_running().map_err(|e| format!("daemon start: {e}"))?;
217	    // PUMP mode: the Split reader-thread carrier bounds every read by a deadline
218	    // (a `Whole` conn can't time out — nonblocking is dead on Windows named
219	    // pipes), so the single-threaded pump interleaves stdin + broker events
220	    // without wedging. The generous io_timeout bounds the setup calls (dial,
221	    // sessions, attach-open ack); the pump itself passes its own short slice.
222	    let mut brain = Brain::cold_start_pump(
223	        &spt_daemon::endpoint::broker_socket_name(),
224	        now_ms(),
225	        Duration::from_secs(10),
226	    )
227	    .map_err(|e| format!("broker connect: {e}"))?;
228
229	    let session_id = resolve_session(&mut brain, endpoint_id)
230	        .map_err(|e| format!("list sessions: {e}"))?
231	        .ok_or_else(|| format!("no live session for endpoint '{endpoint_id}'"))?;
232
233	    let ops = OpMinter::new();
234	    // Transport: a broker-minted in-process LOOPBACK connection (M12 W1.5, B1).
235	    // A QUIC self-dial is refused by iroh ("Connecting to ourself is not
236	    // supported"), so local attach cannot ride a self-dialed conn; instead the
237	    // broker wires an in-process loopback conn whose streams surface as the SAME
238	    // net-stream events the cross-node QUIC path produces. From `request_attach`
239	    // down this is byte-identical to a cross-node attach — the one-pump invariant
240	    // lives at the transport leaf inside the broker, never here. The loopback
241	    // peer's origin is this node's own id, so the target's serve gates same-node.

pub fn run_attach(endpoint_id: &str, intent: AttachIntent) -> Result<(), String> {
    let view = intent == AttachIntent::Viewer;
    // Busy-refuse rendered CLIENT-SIDE (REQ-RCVIEW-1): a plain `Control` attach to
    // an endpoint a DIFFERENT node already controls gets actionable guidance here
    // (the broker would also refuse on the wire — this is the friendly path). A
    // `--view` / `--take` bypasses it (watching coexists; taking displaces).
    if intent == AttachIntent::Control {
        if let Some(node) = current_driver(endpoint_id) {
            println!("'{endpoint_id}' is controlled by {node}.");
            println!("  spt rc {endpoint_id} --view   to watch read-only");
            println!("  spt rc {endpoint_id} --take   to take control (kicks {node})");
            return Ok(());
        }
    }
    // (a) STATUS-GATE (REQ-HAZARD-RC-ATTACH-FAILFAST): a perch with an EXPLICIT
    // `status=offline` is not attachable — short-circuit with clean guidance
    // [impl->REQ-HAZARD-RC-ATTACH-FAILFAST]
    // instead of the broker dance that would block on a dead session (the
    // infinite-blank hang). The B2 pull-reconcile / `endpoint stop` stamp a dead
    // spt-hosted perch offline, so this catches the common case before any IPC. An
    // ABSENT or status-less perch falls through to the broker path, which already
    // ends cleanly ("no live session for endpoint") — not a hang. (is_perch_alive
    // is NOT used here: it returns true for an absent info.json by interim parity.)
    let perch_path =
        spt_store::perch::resolve_perch_path(endpoint_id, spt_store::perch::ParentHint::Infer);
    if spt_store::info::read_info(&perch_path)
        .and_then(|i| i.status)
        .as_deref()
        == Some(spt_store::liveness::STATUS_OFFLINE)
    {
        println!("Endpoint '{endpoint_id}' is offline — nothing to attach to.");
        println!("  spt endpoint run --adapter <adapter> --id {endpoint_id}   to start it");
        return Ok(());
    }
    spt_daemon::daemon::ensure_running().map_err(|e| format!("daemon start: {e}"))?;
    // PUMP mode: the Split reader-thread carrier bounds every read by a deadline
    // (a `Whole` conn can't time out — nonblocking is dead on Windows named
    // pipes), so the single-threaded pump interleaves stdin + broker events
    // without wedging. The generous io_timeout bounds the setup calls (dial,
    // sessions, attach-open ack); the pump itself passes its own short slice.
    let mut brain = Brain::cold_start_pump(
        &spt_daemon::endpoint::broker_socket_name(),
        now_ms(),
        Duration::from_secs(10),
    )
    .map_err(|e| format!("broker connect: {e}"))?;

    let session_id = resolve_session(&mut brain, endpoint_id)
        .map_err(|e| format!("list sessions: {e}"))?
        .ok_or_else(|| format!("no live session for endpoint '{endpoint_id}'"))?;

    let ops = OpMinter::new();
    // Transport: a broker-minted in-process LOOPBACK connection (M12 W1.5, B1).
    // A QUIC self-dial is refused by iroh ("Connecting to ourself is not
    // supported"), so local attach cannot ride a self-dialed conn; instead the
    // broker wires an in-process loopback conn whose streams surface as the SAME
    // net-stream events the cross-node QUIC path produces. From `request_attach`
    // down this is byte-identical to a cross-node attach — the one-pump invariant
    // lives at the transport leaf inside the broker, never here. The loopback
    // peer's origin is this node's own id, so the target's serve gates same-node.


Bringup proven (online+alive+psyche). rc returned in ~2.75s = it errored or EOF'd immediately, not a 10s stream. rc stderr was suppressed — capture it to see (no-live-session vs connect-fail vs empty stream). One targeted edit + run, then checkpoint.


Edit

    let mut rc: Child = Command::new(&spt_bin)
        .no_window()
        .args(["rc", id])
        .env("SPT_HOME", home.path())
        .stdout(Stdio::piped())
        .stderr(Stdio::null())
        .stdin(Stdio::null())
        .spawn()
        .expect("spawn spt rc <id>");

    let rc_err = home.path().join("rc.stderr.log");
    let rc_err_file = std::fs::File::create(&rc_err).expect("create rc stderr log");
    let mut rc: Child = Command::new(&spt_bin)
        .no_window()
        .args(["rc", id])
        .env("SPT_HOME", home.path())
        .stdout(Stdio::piped())
        .stderr(Stdio::from(rc_err_file))
        .stdin(Stdio::null())
        .spawn()
        .expect("spawn spt rc <id>");


    let mut rc: Child = Command::new(&spt_bin)
        .no_window()
        .args(["rc", id])
        .env("SPT_HOME", home.path())
        .stdout(Stdio::piped())
        .stderr(Stdio::null())
        .stdin(Stdio::null())
        .spawn()
        .expect("spawn spt rc <id>");

    let rc_err = home.path().join("rc.stderr.log");
    let rc_err_file = std::fs::File::create(&rc_err).expect("create rc stderr log");
    let mut rc: Child = Command::new(&spt_bin)
        .no_window()
        .args(["rc", id])
        .env("SPT_HOME", home.path())
        .stdout(Stdio::piped())
        .stderr(Stdio::from(rc_err_file))
        .stdin(Stdio::null())
        .spawn()
        .expect("spawn spt rc <id>");

// ── (5) rc ATTACH: stream the live PTY output; assert the heartbeat arrives. ──

+    let rc_err = home.path().join("rc.stderr.log");

+    let rc_err_file = std::fs::File::create(&rc_err).expect("create rc stderr log");

+        .stderr(Stdio::from(rc_err_file))


    let brain_stderr = std::fs::read_to_string(&brain_log).unwrap_or_default();
    eprintln!(
        "=== DIAGNOSTIC: run_ok={} online={online} harness_pid={harness_pid:?} \
         alive_after={alive_after} rc_saw_tick={rc_saw_tick} ===\n=== endpoint run stderr ===\n{}",
        run.status.success(),
        String::from_utf8_lossy(&run.stderr),
    );

    let brain_stderr = std::fs::read_to_string(&brain_log).unwrap_or_default();
    let rc_stderr = std::fs::read_to_string(&rc_err).unwrap_or_default();
    eprintln!(
        "=== DIAGNOSTIC: run_ok={} online={online} harness_pid={harness_pid:?} \
         alive_after={alive_after} rc_saw_tick={rc_saw_tick} ===\n=== endpoint run stderr ===\n{}\
         \n=== rc stderr ===\n{rc_stderr}",
        run.status.success(),
        String::from_utf8_lossy(&run.stderr),
    );


    let brain_stderr = std::fs::read_to_string(&brain_log).unwrap_or_default();
    eprintln!(
        "=== DIAGNOSTIC: run_ok={} online={online} harness_pid={harness_pid:?} \
         alive_after={alive_after} rc_saw_tick={rc_saw_tick} ===\n=== endpoint run stderr ===\n{}",
        run.status.success(),
        String::from_utf8_lossy(&run.stderr),
    );

    let brain_stderr = std::fs::read_to_string(&brain_log).unwrap_or_default();
    let rc_stderr = std::fs::read_to_string(&rc_err).unwrap_or_default();
    eprintln!(
        "=== DIAGNOSTIC: run_ok={} online={online} harness_pid={harness_pid:?} \
         alive_after={alive_after} rc_saw_tick={rc_saw_tick} ===\n=== endpoint run stderr ===\n{}\
         \n=== rc stderr ===\n{rc_stderr}",
        run.status.success(),
        String::from_utf8_lossy(&run.stderr),
    );

+    let rc_stderr = std::fs::read_to_string(&rc_err).unwrap_or_default();

-         alive_after={alive_after} rc_saw_tick={rc_saw_tick} ===\n=== endpoint run stderr ===\n{}",

+         alive_after={alive_after} rc_saw_tick={rc_saw_tick} ===\n=== endpoint run stderr ===\n{}\

+         \n=== rc stderr ===\n{rc_stderr}",


Bash

cd /c/Users/decid/Documents/projects/spt-core
CARGO_TARGET_DIR=target-seam cargo test -p spt --test dummy_harness_e2e -- --test-threads=1 --nocapture > /tmp/w1c.log 2>&1
echo "EXIT=$?"
sed -n '/DIAGNOSTIC/,/rc stderr/p' /tmp/w1c.log | head -40
echo "=== rc stderr section ==="
grep -nA8 "=== rc stderr ===" /tmp/w1c.log | head -20

Re-run capturing rc stderr


Command running in background with ID: bcewz14n2. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\1e7c582d-c03a-4db0-bd84-c7b3b7959ea0\tasks\bcewz14n2.output. You will be notified when it completes. To check interim output, use Read on that file path.


Awaiting rc-stderr diagnostic.


ack


awaiting bcewz14n2


<task-notification>
<task-id>bcewz14n2</task-id>
<tool-use-id>toolu_01P11wAUtujhhTwZGA7iLvzL</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\1e7c582d-c03a-4db0-bd84-c7b3b7959ea0\tasks\bcewz14n2.output</output-file>
<status>completed</status>
<summary>Background command "Re-run capturing rc stderr" completed (exit code 0)</summary>
</task-notification>


Awaiting rc-stderr capture result.


1	EXIT=101
2	test endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches ... === DIAGNOSTIC: run_ok=true online=true harness_pid=Some(105076) alive_after=true rc_saw_tick=false ===
3	=== endpoint run stderr ===
4	ENDPOINT_RUN:dummy7 adapter=dummyharness pid=Some(105076) session=566819214628c2d2 (harness binds its perch on startup)
5	ENDPOINT_RUN_STARTED:dummy7 (attach with `spt rc dummy7`)
6
7	=== rc stderr ===
8	=== rc stderr section ===
9	11:=== rc stderr ===
10	12-PUMP_IPC_READER: spawned
11	13-RC_FAIL:dummy7: loopback dial: net disabled: this broker was bound without a network host
12	14-
13	15-
14	16-thread 'endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches' (246456) panicked at crates\spt\tests\dummy_harness_e2e.rs:275:5:
15	17-`spt rc <id>` must attach and receive the live DUMMY_HARNESS_TICK heartbeat (the real broker-PTY → rc attach path).
16	18-=== brain stderr ===
17	19-NODE_KEY_FAIL: identity unavailable — broker runs net-less, no retry
18

EXIT=101
test endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches ... === DIAGNOSTIC: run_ok=true online=true harness_pid=Some(105076) alive_after=true rc_saw_tick=false ===
=== endpoint run stderr ===
ENDPOINT_RUN:dummy7 adapter=dummyharness pid=Some(105076) session=566819214628c2d2 (harness binds its perch on startup)
ENDPOINT_RUN_STARTED:dummy7 (attach with `spt rc dummy7`)

=== rc stderr ===
=== rc stderr section ===
11:=== rc stderr ===
12-PUMP_IPC_READER: spawned
13-RC_FAIL:dummy7: loopback dial: net disabled: this broker was bound without a network host
14-
15-
16-thread 'endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches' (246456) panicked at crates\spt\tests\dummy_harness_e2e.rs:275:5:
17-`spt rc <id>` must attach and receive the live DUMMY_HARNESS_TICK heartbeat (the real broker-PTY → rc attach path).
18-=== brain stderr ===
19-NODE_KEY_FAIL: identity unavailable — broker runs net-less, no retry


Root: `RC_FAIL: loopback dial: net disabled: this broker was bound without a network host`. rc attach rides a broker-minted **loopback-over-net-host** conn — it needs a net-bound broker. My netless `node.key` sabotage (copied from livehost tests that don't attach) broke it. Fix: let the daemon self-init a real identity (net-enabled = the real path). Remove the netless sabotage + re-run.


Edit

    // ── (1) Isolated, NET-LESS SPT_HOME. ──
    let home = tempfile::tempdir().unwrap();
    std::env::set_var("SPT_HOME", home.path());
    let identity_dir = home.path().join("identity");
    std::fs::create_dir_all(&identity_dir).unwrap();
    std::fs::write(identity_dir.join("node.key"), "not-a-valid-seed").unwrap();

    // ── (1) Isolated SPT_HOME. NET-ENABLED (the daemon self-initializes a real node
    //    identity): `spt rc` attaches over a broker-minted LOOPBACK conn that surfaces
    //    as net-stream events, so it requires a net-bound broker — a net-less broker
    //    refuses the loopback dial ("net disabled"). Unlike the livehost/reconcile
    //    tests (which only inspect perch state), the rc-attach path needs net up. ──
    let home = tempfile::tempdir().unwrap();
    std::env::set_var("SPT_HOME", home.path());


    // ── (1) Isolated, NET-LESS SPT_HOME. ──
    let home = tempfile::tempdir().unwrap();
    std::env::set_var("SPT_HOME", home.path());
    let identity_dir = home.path().join("identity");
    std::fs::create_dir_all(&identity_dir).unwrap();
    std::fs::write(identity_dir.join("node.key"), "not-a-valid-seed").unwrap();

    // ── (1) Isolated SPT_HOME. NET-ENABLED (the daemon self-initializes a real node
    //    identity): `spt rc` attaches over a broker-minted LOOPBACK conn that surfaces
    //    as net-stream events, so it requires a net-bound broker — a net-less broker
    //    refuses the loopback dial ("net disabled"). Unlike the livehost/reconcile
    //    tests (which only inspect perch state), the rc-attach path needs net up. ──
    let home = tempfile::tempdir().unwrap();
    std::env::set_var("SPT_HOME", home.path());

-    // ── (1) Isolated, NET-LESS SPT_HOME. ──

+    // ── (1) Isolated SPT_HOME. NET-ENABLED (the daemon self-initializes a real node

+    //    identity): `spt rc` attaches over a broker-minted LOOPBACK conn that surfaces

+    //    as net-stream events, so it requires a net-bound broker — a net-less broker

+    //    refuses the loopback dial ("net disabled"). Unlike the livehost/reconcile

+    //    tests (which only inspect perch state), the rc-attach path needs net up. ──

-    let identity_dir = home.path().join("identity");

-    std::fs::create_dir_all(&identity_dir).unwrap();

-    std::fs::write(identity_dir.join("node.key"), "not-a-valid-seed").unwrap();

let spt_bin = PathBuf::from(env!("CARGO_BIN_EXE_spt"));


Bash

cd /c/Users/decid/Documents/projects/spt-core
CARGO_TARGET_DIR=target-seam cargo test -p spt --test dummy_harness_e2e -- --test-threads=1 --nocapture > /tmp/w1d.log 2>&1
echo "EXIT=$?"
grep -nE "DIAGNOSTIC|test result:|RC_FAIL|DUMMY_HARNESS_TICK|panicked" /tmp/w1d.log | head -20

Re-run Wave 1 net-enabled


Command running in background with ID: b0chex7iq. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\1e7c582d-c03a-4db0-bd84-c7b3b7959ea0\tasks\b0chex7iq.output. You will be notified when it completes. To check interim output, use Read on that file path.


Awaiting Wave 1 net-enabled re-run.


ack


awaiting b0chex7iq


<task-notification>
<task-id>b0chex7iq</task-id>
<tool-use-id>toolu_01CstU6EJsMpUwzmMFaNXYQb</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\1e7c582d-c03a-4db0-bd84-c7b3b7959ea0\tasks\b0chex7iq.output</output-file>
<status>completed</status>
<summary>Background command "Re-run Wave 1 net-enabled" completed (exit code 0)</summary>
</task-notification>


Awaiting result.


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


1	EXIT=101
2	6:test endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches ... === DIAGNOSTIC: run_ok=true online=true harness_pid=Some(103872) alive_after=true rc_saw_tick=false ===
3	15:thread 'endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches' (151452) panicked at crates\spt\tests\dummy_harness_e2e.rs:276:5:
4	16:`spt rc <id>` must attach and receive the live DUMMY_HARNESS_TICK heartbeat (the real broker-PTY → rc attach path).
5	30:test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.46s
6

EXIT=101
6:test endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches ... === DIAGNOSTIC: run_ok=true online=true harness_pid=Some(103872) alive_after=true rc_saw_tick=false ===
15:thread 'endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches' (151452) panicked at crates\spt\tests\dummy_harness_e2e.rs:276:5:
16:`spt rc <id>` must attach and receive the live DUMMY_HARNESS_TICK heartbeat (the real broker-PTY → rc attach path).
30:test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.46s


Still no tick, but ran longer (3.46s). Read the rc stderr section.


cd /c/Users/decid/Documents/projects/spt-core
grep -nA10 "=== rc stderr ===" /tmp/w1d.log | head -20
echo "=== endpoint run + brain ==="
grep -nE "ENDPOINT_RUN|RC_FAIL|PUMP_IPC|FIRST_EVENT|no live session|Subscribed|attach" /tmp/w1d.log | head -20

Read rc stderr + attach diagnostics


11:=== rc stderr ===
12-PUMP_IPC_READER: spawned
13-
14-
15-thread 'endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches' (151452) panicked at crates\spt\tests\dummy_harness_e2e.rs:276:5:
16-`spt rc <id>` must attach and receive the live DUMMY_HARNESS_TICK heartbeat (the real broker-PTY → rc attach path).
17-=== brain stderr ===
18-BRAIN_UP: connected to broker spt-daemon-broker-483475dfb359ee02.sock (pid 69476, generation 0, start-reason cold)
19-BRAIN_NET_CONSUMERS_UP: net enabled (node 06f1ecfac3ea536107ab579a1293955bcdf6846562bb5be631cc5ddc8e9f739f) — dispatcher + peer pump started
20-PUMP_IPC_READER: spawned
21-
=== endpoint run + brain ===
6:test endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches ... === DIAGNOSTIC: run_ok=true online=true harness_pid=Some(103872) alive_after=true rc_saw_tick=false ===
8:ENDPOINT_RUN:dummy7 adapter=dummyharness pid=Some(103872) session=a9f0eb491ccda932 (harness binds its perch on startup)
9:ENDPOINT_RUN_STARTED:dummy7 (attach with `spt rc dummy7`)
12:PUMP_IPC_READER: spawned
15:thread 'endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches' (151452) panicked at crates\spt\tests\dummy_harness_e2e.rs:276:5:
16:`spt rc <id>` must attach and receive the live DUMMY_HARNESS_TICK heartbeat (the real broker-PTY → rc attach path).
20:PUMP_IPC_READER: spawned
28:    endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches


Net fixed — rc now attaches (PUMP_IPC_READER spawned, no RC_FAIL), but no tick captured + rc exits fast (3.46s). Likely cause: `stdin=null` → rc's pump sees immediate stdin-EOF and detaches before streaming (test artifact, not a product bug). Give rc an open stdin (held piped handle) so it stays attached during the read window.


Edit

    let rc_err = home.path().join("rc.stderr.log");
    let rc_err_file = std::fs::File::create(&rc_err).expect("create rc stderr log");
    let mut rc: Child = Command::new(&spt_bin)
        .no_window()
        .args(["rc", id])
        .env("SPT_HOME", home.path())
        .stdout(Stdio::piped())
        .stderr(Stdio::from(rc_err_file))
        .stdin(Stdio::null())
        .spawn()
        .expect("spawn spt rc <id>");
    let mut rc_out = rc.stdout.take().unwrap();

    let rc_err = home.path().join("rc.stderr.log");
    let rc_err_file = std::fs::File::create(&rc_err).expect("create rc stderr log");
    let mut rc: Child = Command::new(&spt_bin)
        .no_window()
        .args(["rc", id])
        .env("SPT_HOME", home.path())
        .stdout(Stdio::piped())
        .stderr(Stdio::from(rc_err_file))
        .stdin(Stdio::piped()) // keep stdin OPEN — a closed stdin makes the rc pump EOF-detach before streaming
        .spawn()
        .expect("spawn spt rc <id>");
    let _rc_stdin = rc.stdin.take(); // hold the write end open for the attach window
    let mut rc_out = rc.stdout.take().unwrap();


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

    let rc_err = home.path().join("rc.stderr.log");
    let rc_err_file = std::fs::File::create(&rc_err).expect("create rc stderr log");
    let mut rc: Child = Command::new(&spt_bin)
        .no_window()
        .args(["rc", id])
        .env("SPT_HOME", home.path())
        .stdout(Stdio::piped())
        .stderr(Stdio::from(rc_err_file))
        .stdin(Stdio::null())
        .spawn()
        .expect("spawn spt rc <id>");
    let mut rc_out = rc.stdout.take().unwrap();

    let rc_err = home.path().join("rc.stderr.log");
    let rc_err_file = std::fs::File::create(&rc_err).expect("create rc stderr log");
    let mut rc: Child = Command::new(&spt_bin)
        .no_window()
        .args(["rc", id])
        .env("SPT_HOME", home.path())
        .stdout(Stdio::piped())
        .stderr(Stdio::from(rc_err_file))
        .stdin(Stdio::piped()) // keep stdin OPEN — a closed stdin makes the rc pump EOF-detach before streaming
        .spawn()
        .expect("spawn spt rc <id>");
    let _rc_stdin = rc.stdin.take(); // hold the write end open for the attach window
    let mut rc_out = rc.stdout.take().unwrap();

+        .stdin(Stdio::piped()) // keep stdin OPEN — a closed stdin makes the rc pump EOF-detach before streaming

+    let _rc_stdin = rc.stdin.take(); // hold the write end open for the attach window

let (tx, rx) = std::sync::mpsc::channel::<String>();


Bash

cd /c/Users/decid/Documents/projects/spt-core
CARGO_TARGET_DIR=target-seam cargo test -p spt --test dummy_harness_e2e -- --test-threads=1 --nocapture > /tmp/w1e.log 2>&1
echo "EXIT=$?"
grep -nE "DIAGNOSTIC|test result:" /tmp/w1e.log | head
echo "=== rc stderr ==="; grep -nA8 "=== rc stderr ===" /tmp/w1e.log | head -14

Re-run Wave 1 with rc stdin held open


Command running in background with ID: bdlvzo7p4. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\1e7c582d-c03a-4db0-bd84-c7b3b7959ea0\tasks\bdlvzo7p4.output. You will be notified when it completes. To check interim output, use Read on that file path.


Awaiting Wave 1 re-run (rc stdin held open).


ack


awaiting bdlvzo7p4


<task-notification>
<task-id>bdlvzo7p4</task-id>
<tool-use-id>toolu_01TD4uvHxKjP2VtCSxjn811c</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\1e7c582d-c03a-4db0-bd84-c7b3b7959ea0\tasks\bdlvzo7p4.output</output-file>
<status>completed</status>
<summary>Background command "Re-run Wave 1 with rc stdin held open" completed (exit code 0)</summary>
</task-notification>


1	EXIT=101
2	6:test endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches ... === DIAGNOSTIC: run_ok=true online=true harness_pid=Some(239568) alive_after=true rc_saw_tick=false ===
3	30:test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 12.76s
4	=== rc stderr ===
5	11:=== rc stderr ===
6	12-PUMP_IPC_READER: spawned
7	13-
8	14-
9	15-thread 'endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches' (58444) panicked at crates\spt\tests\dummy_harness_e2e.rs:277:5:
10	16-`spt rc <id>` must attach and receive the live DUMMY_HARNESS_TICK heartbeat (the real broker-PTY → rc attach path).
11	17-=== brain stderr ===
12	18-BRAIN_UP: connected to broker spt-daemon-broker-408d357c949bf6c6.sock (pid 125048, generation 0, start-reason cold)
13	19-BRAIN_NET_CONSUMERS_UP: net enabled (node c98b98200c6d3935865e46dec99578f2cf8cfc2df7186c8215b7a774634c4b36) — dispatcher + peer pump started
14

EXIT=101
6:test endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches ... === DIAGNOSTIC: run_ok=true online=true harness_pid=Some(239568) alive_after=true rc_saw_tick=false ===
30:test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 12.76s
=== rc stderr ===
11:=== rc stderr ===
12-PUMP_IPC_READER: spawned
13-
14-
15-thread 'endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches' (58444) panicked at crates\spt\tests\dummy_harness_e2e.rs:277:5:
16-`spt rc <id>` must attach and receive the live DUMMY_HARNESS_TICK heartbeat (the real broker-PTY → rc attach path).
17-=== brain stderr ===
18-BRAIN_UP: connected to broker spt-daemon-broker-408d357c949bf6c6.sock (pid 125048, generation 0, start-reason cold)
19-BRAIN_NET_CONSUMERS_UP: net enabled (node c98b98200c6d3935865e46dec99578f2cf8cfc2df7186c8215b7a774634c4b36) — dispatcher + peer pump started


Wave 1 now: bringup fully proven (online + alive + psyche), rc **attaches** (net-enabled, holds 10s with stdin open) — but receives **no PTY output**. That's now either the ATTACH data-plane defect surfacing or a Wave-1-scope question (does the fixture assert output-flow, or just attach-connects, leaving output to Wave 2's ATTACH-WEDGE gate?). Context is very deep — report to doyle for the scope/defect call, commune, self-clear.


cd /c/Users/decid/Documents/projects/spt-core
$OWL send doyle <<'EOF'
WAVE 1 status — bringup PROVEN, one open question (need your call):
GREEN: `endpoint run --start` brings the dummy harness up via the REAL daemon+broker PTY → online=true, harness pid stays ALIVE (heartbeating), psyche hosted. Fixture findings baked in: (a) the dummy MUST declare [session.psyche_init] or cmd_bind never marks online (startup.rs:464 gates online on live_agent && psyche_init); (b) the test MUST be net-ENABLED (real node identity) — rc attach rides a broker-minted loopback-over-net conn, a net-less broker refuses it ("net disabled"); (c) rc needs stdin held open or its pump EOF-detaches instantly.
OPEN: with all that, `spt rc <id>` ATTACHES cleanly (PUMP_IPC_READER spawned, no RC_FAIL, stays the full 10s) but receives ZERO of the dummy's DUMMY_HARNESS_TICK stdout. So the broker isn't fanning the harness PTY output to the attach. QUESTION: is that the ATTACH-WEDGE / data-plane defect itself surfacing on the fixture (→ Wave 1 asserts only bringup + attach-CONNECTS, and output-flow becomes the Wave 2 ATTACH gate), OR do you want Wave 1 to prove output-flow (→ it's a real "broker doesn't drain PTY into OutputLog for endpoint-run sessions" bug to root now)? Leaning: Wave 1 = bringup + attach-connects; output-flow is Wave 2's subject. Context very high — communing + self-clearing; will resume on your ruling. Wave 1 code is on-branch uncommitted (dummy mode + dummy_harness_e2e.rs).
EOF

Report Wave 1 status + scope question to doyle


➜ SENT:doyle


Write v0.12.1 memory + commune, then self-clear (context critical).


Write

---
name: v0121-lifecycle-realharness
description: "v0.12.1 lifecycle reopen — v0.12.0 fixes broke in REAL daemon+broker+PTY path (gated on mocks/in-proc reconcile); real-harness-gated sprint, doyle dispatch"
metadata:
  node_type: memory
  type: project
---

**v0.12.1 LIFECYCLE REOPEN (doyle dispatch ~2026-06-18).** v0.12.0 SHIPPED (counter 25) but real-harness testing (operator, claude-spt, Windows) showed the lifecycle fixes DON'T deliver in the real daemon+broker+PTY path — we gated on MOCKS + in-proc `reconcile_once` (green tests, broken reality). Branch **v0.12.1-lifecycle** off main@5f9ea23. Authoritative plan = **V0.12.1-LIFECYCLE-JIT.md ON THE BRANCH**. REQs minted registry-first (traceable-reqs.toml). PATCH release. todlando executes, doyle gates per-wave (independently re-runs each gate vs REAL dummy-harness + REAL detached daemon — NOT mock, NOT in-proc reconcile), deployah releases.

**BINDING META-FIX:** every lifecycle gate runs vs the real dummy-harness fixture + a real detached daemon. That gap = why v0.12.0 escaped.

**WAVE ORDER:** 1=dummy-harness fixture (unblocks all) · 2=lifecycle core (keystone REQ-HAZARD-VIEWER-CLOSE-DETACH + REQ-HAZARD-ATTACH-WEDGE + status-online-within-tick + daemon-stop-ends-all) · 3=picker P1/P2 (investigate root FIRST, report before fixing) · 4=REQ-ENDPOINT-LIST-MERGE-LOCAL (drop --local, bare list merges local, fix whoami; xtask gen DEFAULT target).

**WAVE 2 KEYSTONE detail:** REQ-HAZARD-VIEWER-CLOSE-DETACH = add CREATE_BREAKAWAY_FROM_JOB to BOTH daemon spawn paths (daemon.rs:707 detached_no_inherit + deelevate.rs:519 elevated) + pin each broker-spawned harness into a DAEMON-OWNED Job Object (mirror reap.rs/Breap) as backstop; pty.rs UNCHANGED (ConPTY isolation already correct). REQ-HAZARD-ATTACH-WEDGE = loopback attach output is a blocking write_all into a bounded 64KB tokio duplex (nethost.rs:1040,1090); dead operator (closed tab) stops draining → write_all blocks forever → parks a worker in the 2-WORKER net runtime (nethost.rs:640) → both saturate → every new attach stalls. FIX: loopback sends FAIL-FAST (full-buffer/BrokenPipe = ordinary per-stream err ENDS serve_attach). Folds status=online (dead endpoint offlined within one reconcile tick on abrupt child death — confirm broker session reaps so B2 sees it absent).

**doyle real-harness REPRO DATA (fold into Wave 2 gates):** (1) UNHOST-PSYCHE-REAP fails in real harness too — a DEAD endpoint left its {id}-psyche HARNESS PROCESS alive holding perch files → `endpoint purge --force` STOPPED fine but FAILED tree-remove (os error 32 file-in-use) until the psyche was scope-killed. Gate: a died/stopped spt-hosted harness must get its {id}-psyche REAPED. (2) Control-plane responsive (purge --force STOP instant on all 3) — only DATA-plane (2-worker loopback attach) wedged = confirms ATTACH-WEDGE root. (3) `ensure_running` respawns a fresh daemon the moment ANY CLI command finds none → daemon-stop gate must assert down-for-a-bounded-window WITHOUT a racing CLI call.

**WAVE 1 PROGRESS (todlando, on-branch UNCOMMITTED):** built (a) `mock-session --mode dummy` (adapters/mock/src/main.rs): binds perch (api bind) → prints DUMMY_HARNESS_TICK heartbeat on interval → stays alive til killed (the default mock EXITS immediately = why v0.12.0 never caught the lifecycle defects). (b) crates/spt/tests/dummy_harness_e2e.rs: real `spt daemon run` → `endpoint run --start` dummy → assert online + alive + rc attach. **PROVEN GREEN: online=true, harness pid stays ALIVE, psyche hosted.** Fixture findings (load-bearing, baked into the test): (i) the dummy MUST declare [session.psyche_init] (long-lived psyche `psychebin ready {id}`, mirror brain_restart_psyche_dup) — else cmd_bind never marks online (startup.rs:464 gates status=online on `live_agent && manifest.psyche_init.is_some()`); (ii) test MUST be NET-ENABLED (real node identity — do NOT write a corrupt netless node.key) because rc attach rides a broker-minted LOOPBACK-over-net conn (net-less broker refuses: "net disabled: bound without a network host"); (iii) rc child needs stdin HELD OPEN (Stdio::piped + hold handle) or its pump EOF-detaches instantly. **OPEN BLOCKER (awaiting doyle ruling):** with all 3 fixes, `spt rc <id>` ATTACHES cleanly (PUMP_IPC_READER spawned, no RC_FAIL, holds full 10s) but receives ZERO DUMMY_HARNESS_TICK output — broker not fanning the harness PTY stdout to the attach. Asked doyle: is this the ATTACH-WEDGE data-plane defect surfacing (→ Wave 1 asserts only bringup + attach-CONNECTS, output-flow = Wave 2 ATTACH gate) OR a real "broker doesn't drain PTY into OutputLog for endpoint-run sessions" bug to root now? My lean: Wave 1 = bringup + attach-connects; output-flow is Wave 2's subject. **ON RESUME: read doyle's ruling; if 'connect-only' relax the rc assertion (assert no RC_FAIL + stays attached, drop the tick-capture) → Wave 1 green → seam bar (clippy --workspace, traceable, the new test) → commit → ping doyle → Wave 2 keystone.**

**SEAM BAR (real-harness, every wave):** clippy --workspace -D warnings + traceable EXIT=0 + the REAL dummy-harness E2E (serial, target-seam, net-ENABLED). Gotchas carried from v0.12.0: pre-build `cargo build -p mock-adapter --bin mock-session` (helper bin in mock-adapter PKG, not spt) before any spt --test using it; NO `jq` in this Git Bash (use `gh --jq`); spt has NO lib (`--bin spt` for cli unit tests); xtask gen reads hardcoded target/debug (run in DEFAULT target not target-seam); SERIAL cargo only on HFENDULEAM (shared CI runner — never machine-wide kill, scope pid reaps).