282	    }
283
284	    // ── (5) THE ASSERTION: the REAL daemon brain must host the Psyche. The
285	    //    disk-observable proxy is the nested `<id>-psyche` perch's info.json
286	    //    appearing (spawn_and_bind writes it AFTER a successful psyche spawn; a
287	    //    spawn failure logs LIVEHOST_PSYCHE_FAIL and writes nothing). Bounded 25s
288	    //    poll — on a buggy tree this SHOULD time out. ──
289	    let psyche_perch = perch::resolve_perch_path(
290	        &format!("{id}-psyche"),
291	        ParentHint::Explicit(id),
292	    );
293	    let deadline = Instant::now() + Duration::from_secs(25);
294	    let mut hosted = false;
295	    while Instant::now() < deadline {
296	        if spt_store::info::read_info(&psyche_perch).is_some() {
297	            hosted = true;
298	            break;
299	        }
300	        std::thread::sleep(Duration::from_millis(200));
301	    }
302
303	    // Capture diagnostics BEFORE teardown so the report has the real markers.
304	    let brain_stderr = std::fs::read_to_string(&brain_log).unwrap_or_default();
305	    let listen_stderr = std::fs::read_to_string(&listen_log).unwrap_or_default();
306
307	    // Always surface the captured markers (the reproduction evidence).
308	    eprintln!("=== DIAGNOSTIC: hosted={hosted} brain_pid={brain_pid} ===");
309	    eprintln!("=== brain stderr (BRAIN_UP / LIVEHOST_PSYCHE / LIVEHOST_PSYCHE_FAIL) ===\n{brain_stderr}");
310	    eprintln!("=== listen stderr ===\n{listen_stderr}");
311	    eprintln!(
312	        "=== psyche perch info.json present: {} (path {}) ===",
313	        psyche_perch.join("info.json").exists(),
314	        psyche_perch.display()
315	    );
316
317	    // ── (6) Reap everything we spawned — scoped to OUR pids only. ──
318	    kill_pid(listen.id());
319	    let _ = listen.wait();
320	    kill_pid(brain_pid);
321	    let _ = broker.kill();
322	    let _ = broker.wait();
323
324	    // The harness-reachable psyche-host-health field (v0.8.1,
325	    // REQ-HAZARD-LIVEHOST-BOOT-RACE): on the SUCCESS path the live perch must carry
326	    // NO `psyche_host_error` — a successfully-hosted live agent shows clean (a prior
327	    // transient failure, if any, is cleared on the successful host).
328	    // [int->REQ-HAZARD-LIVEHOST-BOOT-RACE]
329	    let phe = spt_store::info::read_info(&self_perch).and_then(|i| i.psyche_host_error);
330
331	    assert!(
332	        hosted,
333	        "REQ-DAEMON-1: the REAL daemon brain must host the Psyche for a net-less ONLINE \
334	         live endpoint (the nested `{id}-psyche` perch must appear) within 25s — it did NOT.\n\
335	         === brain stderr (BRAIN_UP / LIVEHOST_PSYCHE / LIVEHOST_PSYCHE_FAIL land here) ===\n{}\n\
336	         === listen stderr ===\n{}",
337	        brain_stderr, listen_stderr,
338	    );
339	    assert!(
340	        phe.is_none(),
341	        "REQ-HAZARD-LIVEHOST-BOOT-RACE: a successfully-hosted live perch must carry NO \
342	         psyche_host_error (got {phe:?})",
343	    );
344	}
345

    }

    // ── (5) THE ASSERTION: the REAL daemon brain must host the Psyche. The
    //    disk-observable proxy is the nested `<id>-psyche` perch's info.json
    //    appearing (spawn_and_bind writes it AFTER a successful psyche spawn; a
    //    spawn failure logs LIVEHOST_PSYCHE_FAIL and writes nothing). Bounded 25s
    //    poll — on a buggy tree this SHOULD time out. ──
    let psyche_perch = perch::resolve_perch_path(
        &format!("{id}-psyche"),
        ParentHint::Explicit(id),
    );
    let deadline = Instant::now() + Duration::from_secs(25);
    let mut hosted = false;
    while Instant::now() < deadline {
        if spt_store::info::read_info(&psyche_perch).is_some() {
            hosted = true;
            break;
        }
        std::thread::sleep(Duration::from_millis(200));
    }

    // Capture diagnostics BEFORE teardown so the report has the real markers.
    let brain_stderr = std::fs::read_to_string(&brain_log).unwrap_or_default();
    let listen_stderr = std::fs::read_to_string(&listen_log).unwrap_or_default();

    // Always surface the captured markers (the reproduction evidence).
    eprintln!("=== DIAGNOSTIC: hosted={hosted} brain_pid={brain_pid} ===");
    eprintln!("=== brain stderr (BRAIN_UP / LIVEHOST_PSYCHE / LIVEHOST_PSYCHE_FAIL) ===\n{brain_stderr}");
    eprintln!("=== listen stderr ===\n{listen_stderr}");
    eprintln!(
        "=== psyche perch info.json present: {} (path {}) ===",
        psyche_perch.join("info.json").exists(),
        psyche_perch.display()
    );

    // ── (6) Reap everything we spawned — scoped to OUR pids only. ──
    kill_pid(listen.id());
    let _ = listen.wait();
    kill_pid(brain_pid);
    let _ = broker.kill();
    let _ = broker.wait();

    // The harness-reachable psyche-host-health field (v0.8.1,
    // REQ-HAZARD-LIVEHOST-BOOT-RACE): on the SUCCESS path the live perch must carry
    // NO `psyche_host_error` — a successfully-hosted live agent shows clean (a prior
    // transient failure, if any, is cleared on the successful host).
    // [int->REQ-HAZARD-LIVEHOST-BOOT-RACE]
    let phe = spt_store::info::read_info(&self_perch).and_then(|i| i.psyche_host_error);

    assert!(
        hosted,
        "REQ-DAEMON-1: the REAL daemon brain must host the Psyche for a net-less ONLINE \
         live endpoint (the nested `{id}-psyche` perch must appear) within 25s — it did NOT.\n\
         === brain stderr (BRAIN_UP / LIVEHOST_PSYCHE / LIVEHOST_PSYCHE_FAIL land here) ===\n{}\n\
         === listen stderr ===\n{}",
        brain_stderr, listen_stderr,
    );
    assert!(
        phe.is_none(),
        "REQ-HAZARD-LIVEHOST-BOOT-RACE: a successfully-hosted live perch must carry NO \
         psyche_host_error (got {phe:?})",
    );
}


Prompt:
<task-notification>
<task-id>a8198ed05495a911c</task-id>
<tool-use-id>toolu_01P5JXqgkqgd8EL4TdPXB1nR</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\30df62c3-0b22-4b14-88de-fef422a5554f\tasks\a8198ed05495a911c.output</output-file>
<status>completed</status>
<summary>Agent "Build v0.8.2 two REQ fixes" completed</summary>
<result>Both fixes are implemented, tested, and committed. Here is the report.

## Summary

Committed `1c05a4d` on branch `v0.8.2-templating-residency` (**not pushed** — todlando reviews + final gate). Two gater-approved fixes from perri's v0.8.1 dogfood.

### F-009 — REQ-HAZARD-TEMPLATE-ARGV-FILL (argv-element template fill)
- **`crates/spt-runtime/src/runtime.rs`**: new `fill_template_tokens` — tokenizes the TEMPLATE into argv first, then `fill_template`s EACH token, so a `{key}` value becomes exactly ONE argv element regardless of internal spaces/quotes/semicolons. Rewired `command_for` and `run_bounded_command_in`. Preserved: missing-key/empty-command errors, `{{`/`}}` behavior, and install-dir resolution of the filled `tokens[0]` program.
- **Rewired the other 3 argv callers**: `harnesshost.rs` (`prepare_harness_spawn`), `shellwake.rs` (wake_command), `shellhost.rs` (shell spawn). Single-string path/source fills (`history.rs` locate, `digest.rs` source, `command_for` cwd) correctly keep plain `fill_template`.
- **5 new units**: multi-word → 1 element; quote and semicolon → 1 element (no injection); embedded placeholder (`--flag={key}`, `pre{key}post`); missing-key/empty-command; literal-brace parity.

### F-010 — REQ-HAZARD-LIVEHOST-NONRESIDENT (Psyche residency confirm)
- **`crates/spt-daemon/src/livehost.rs`**: residency check integrated into the existing ~5s reconcile (no blocking wait). For a still-online hosted endpoint past a 5s grace, it DIRECTLY pid-probes the `{id}-psyche` perch via `spt_store::proc::is_process_alive` (NOT `is_perch_alive`, which masks the dead pid via `status=online`). Pure `classify_residency(elapsed, grace, alive) -> Resident/Pending/Failed`. On Failed: stamp the PARENT `psyche_host_error{reason: "host exited within 5s..."}` (reusing the v0.8.1 helper), mark the phantom nested perch offline, un-host, and arm a one-interval cooldown so the doomed psyche isn't re-spawned the same sweep (quiescent failed state). **Moved clear-on-success** from `host_one`'s spawn-Ok leg to the residency-Resident arm — post-F-010 a detached spawn-Ok is not a host success.
- **1 new unit** (`classify_residency`) + **1 new real-daemon E2E** `crates/spt/tests/livehost_nonresident_e2e.rs` (broker→brain, psyche resolves+spawns Ok then clap-exits 2 fast → parent stamped, no phantom online nested perch). Pids reaped scoped-only.

### Key finding / design call
The v0.8.1 boot-race POSITIVE regression uses a fast-exit (`--version`) psyche, which initially looked like it would collide with the residency check. Timing analysis proved no conflict (it reads its perch ~1s post-host, inside the 5s grace → Pending, no stamp) — so **no regression-fixture change was needed**. Both v0.8.1 E2Es stay green. The clear-on-success relocation is the one behavioral change flagged to todlando for review.

### Preflight (all PASS, EXIT 0)
- `cargo clippy --workspace --all-targets -- -D warnings` — clean
- `traceable-reqs check` — EXIT 0 (both REQs activated `["impl","unit","int"]`)
- `cargo test -p spt-runtime` 5 new template units — 5 passed
- `cargo test -p spt --test livehost_nonresident_e2e` — 1 passed
- `cargo test -p spt --test livehost_bootrace_e2e` (regression) — 1 passed
- `cargo test -p spt --test livehost_psyche_fail_e2e` (regression) — 1 passed
- `cargo test -p spt-daemon --lib livehost::/harnesshost::/shellhost::` — 17 passed
- `cargo run -p xtask -- check` — OK (no docs-drift; no CLI commands changed)

7 files, +722/−24. Not pushed.</result>
<usage><subagent_tokens>178035</subagent_tokens><tool_uses>92</tool_uses><duration_ms>1064767</duration_ms></usage>
</task-notification>


Now I see the full picture. Critical insight on the timing risk: the positive test **kills the brain right after the perch appears** (~1–2s post-host), then reads `phe`. The brain's reconcile (which stamps Failed) can only fire while alive — so a false Failed stamp needs a reconcile tick at `elapsed≥5s` *before* brain-kill, i.e. the `--version` psyche must take **≥5s just to spawn+register**. That's an extreme stall, not a 4s-margin coin-flip. So the agent's timing approach is more robust than I first judged — accepting it, but I'll flag the timing-dependence to doyle (gater's call on hardening). The clear-on-success→Resident-arm move I already endorsed (correct semantics) — approved.

Let me verify the highest-risk item myself (the runtime seam refactor) + that install-dir resolution survives:


94:pub fn fill_template(
140:pub fn fill_template_tokens(
150:        match fill_template(&tok, keys) {
296:    fn command_for(
305:        let tokens = fill_template_tokens(&role.command, keys)?;
311:            Some(dir) => resolve_program_in_dir(program, dir),
317:            cmd.current_dir(fill_template(cwd, keys)?);
374:        let label = fill_template(&role_def.command, keys)?;
482:pub fn resolve_program_in_dir(program: &str, install_dir: &Path) -> String {
501:/// ([`resolve_program_in_dir`]) so a bare program name binds to the adapter's
506:pub fn run_bounded_command_in(
515:    let tokens = fill_template_tokens(template, keys)?;
521:        Some(dir) => resolve_program_in_dir(program, dir),
569:        let out = fill_template("run --id {session_id} --name {session_name}", &k).unwrap();
576:        let e = fill_template("run --name {session_name}", &k).unwrap_err();
592:        let argv = fill_template_tokens("runner --prompt {psyche_prompt}", &k).unwrap();
610:        let argv_q = fill_template_tokens("runner --prompt {psyche_prompt}", &q).unwrap();
615:        let argv_s = fill_template_tokens("runner --prompt {psyche_prompt}", &s).unwrap();
626:        let argv = fill_template_tokens("runner --src={source} pre{source}post", &k).unwrap();
644:        let e = fill_template_tokens("runner --name {session_name}", &k).unwrap_err();
654:        let argv = fill_template_tokens("", &k).unwrap();
673:        let argv = fill_template_tokens("runner {id}-suffix", &k).unwrap();
676:        assert_eq!(fill_template("{id}-suffix", &k).unwrap(), "x-suffix");
764:        let resolved = resolve_program_in_dir("claude-spt-digest", dir.path());
774:        let resolved = resolve_program_in_dir("claude-spt-digest", dir.path());
786:        let resolved = resolve_program_in_dir("claude-spt-digest", dir.path());


140	pub fn fill_template_tokens(
141	    template: &str,
142	    keys: &BTreeMap<String, String>,
143	) -> Result<Vec<String>, RuntimeError> {
144	    let tokens = tokenize(template);
145	    let mut filled = Vec::with_capacity(tokens.len());
146	    for tok in tokens {
147	        // Fill EACH token in isolation: the value lands as one argv element and
148	        // never re-enters tokenization. Keep fill_template's missing-key error
149	        // but report the FULL template (not the single token) for operator context.
150	        match fill_template(&tok, keys) {
151	            Ok(v) => filled.push(v),
152	            Err(RuntimeError::MissingKey { key, .. }) => {
153	                return Err(RuntimeError::MissingKey {
154	                    key,
155	                    command: template.to_string(),
156	                });
157	            }
158	            Err(other) => return Err(other),
159	        }

pub fn fill_template_tokens(
    template: &str,
    keys: &BTreeMap<String, String>,
) -> Result<Vec<String>, RuntimeError> {
    let tokens = tokenize(template);
    let mut filled = Vec::with_capacity(tokens.len());
    for tok in tokens {
        // Fill EACH token in isolation: the value lands as one argv element and
        // never re-enters tokenization. Keep fill_template's missing-key error
        // but report the FULL template (not the single token) for operator context.
        match fill_template(&tok, keys) {
            Ok(v) => filled.push(v),
            Err(RuntimeError::MissingKey { key, .. }) => {
                return Err(RuntimeError::MissingKey {
                    key,
                    command: template.to_string(),
                });
            }
            Err(other) => return Err(other),
        }


`fill_template_tokens` is correct — tokenize template → fill each token in isolation → one argv element, never re-tokenized, MissingKey preserved with full-template context. Confirming the callers split program/args + apply install-dir resolution to `tokens[0]`:


305	        let tokens = fill_template_tokens(&role.command, keys)?;
306	        let (program, args) = tokens.split_first().ok_or(RuntimeError::EmptyCommand)?;
307	        // [impl->REQ-INSTALL-11] resolve a bare program token against the adapter
308	        // install dir before PATH so a `.spt` that ships its binary (e.g. the
309	        // psyche_init runner `claude-spt …`) is self-contained.
310	        let program = match &self.install_dir {
311	            Some(dir) => resolve_program_in_dir(program, dir),
312	            None => program.clone(),
313	        };
314	        let mut cmd = Command::new(&program);
315	        cmd.args(args);
316	        if let Some(cwd) = &role.cwd {
317	            cmd.current_dir(fill_template(cwd, keys)?);
318	        }
319	        for var in &role.env_remove {
320	            cmd.env_remove(var);
321	        }
322	        if let Some(guard) = &role.recursion_guard_env {
323	            cmd.env(guard, "1");
324	        }

        let tokens = fill_template_tokens(&role.command, keys)?;
        let (program, args) = tokens.split_first().ok_or(RuntimeError::EmptyCommand)?;
        // [impl->REQ-INSTALL-11] resolve a bare program token against the adapter
        // install dir before PATH so a `.spt` that ships its binary (e.g. the
        // psyche_init runner `claude-spt …`) is self-contained.
        let program = match &self.install_dir {
            Some(dir) => resolve_program_in_dir(program, dir),
            None => program.clone(),
        };
        let mut cmd = Command::new(&program);
        cmd.args(args);
        if let Some(cwd) = &role.cwd {
            cmd.current_dir(fill_template(cwd, keys)?);
        }
        for var in &role.env_remove {
            cmd.env_remove(var);
        }
        if let Some(guard) = &role.recursion_guard_env {
            cmd.env(guard, "1");
        }


`command_for` correct: `split_first` (EmptyCommand preserved) → `resolve_program_in_dir` on the program token (install-dir resolution intact) → args are pre-filled single elements → cwd uses plain `fill_template` (path, single value). The seam refactor is sound. Verification complete — the agent's work is solid; doyle does the authoritative gate read.

Now my finalize. First, fold the FLAKE-LEDGER one-liner (doyle's rider directive — v0.8.2 is the next natural change). Reading the ledger format:


1	# Flake ledger
2
3	Tests that have failed nondeterministically, with mechanism notes and hardening
4	status. Discipline: **any flake gets an entry, not a shrug** — a second
5	occurrence upgrades the entry from "observed" to "harden it." Rig flakes from
6	the two-host ladder (docs/TWO-HOST-RUNBOOK.md) land here too.
7
8	| # | test | occurrences | mechanism | status |
9	|---|------|-------------|-----------|--------|
10	| 1 | `spt-daemon brain_restart_survives_gaplessly` | 1× hosted Windows (2026-06-03, hung) | ConPTY stall under hosted-runner load | job `timeout-minutes` bounds it; not seen self-hosted |
11	| 2 | `spt-term digest_parses_a_real_pty_stream` | 3× gravity (2026-06-03; 2026-06-04 runs 26957386850, 26966738231) | sprint-collapse assert (`digest.rs`) — PTY input echo and the child's write are **concurrent writers** into the master-read stream; back-to-back `send_line`s let a later line's echo interleave mid-`Write(…)`, breaking the tool regex for that turn | HARDENED (2026-06-04): sends serialized — each line pumped back (adaptive echo-copy count, 2s/line bound) before the next, so no concurrent writer races an earlier line; failure asserts now print the raw transcript |
12	| 3 | `spt-daemon sync.rs torn_pull` + `concurrent_writes` | 1× (2026-06-03) | `wait_for_stream` deadline too tight under load | HARDENED: `wait_for_stream_except` 2s→10s; no recurrence |
13	| 4 | `spt-live context::write_context_suppresses_stale_llm` | 1× gravity (2026-06-04 run 26967808321) | test calls `write_context` (reads SPT_HOME node identity + epoch stamp) WITHOUT the `with_home` lock — a parallel `with_home` test's env swap + temp-dir teardown races the stamp's epoch write into a deleted home (`NotFound`) | HARDENED (2026-06-04): test wrapped in `with_home` — serializes on the home lock like every other SPT_HOME-touching test |
14	| 5 | `spt-daemon attach.rs:600` re-serve (`output gap: got seq 1 want 0`) | 1× kitsubito Linux (2026-06-15 run 27545857429, PR #14 stale-base CI) | re-serve replays the buffered output stream on re-attach; under load the new subscriber observed `seq 1` before `seq 0` — an ordering/timing gap in the re-serve sequence assert, not a content error. Subsystem disjoint from the triggering change (PR #14 = digest-proof CLI key-fill, cannot touch attach sequencing) | OBSERVED 1×: did NOT reproduce on the clean-base rerun (`gh pr update-branch` onto post-#15 main → green both runners). Not hardened; a 2nd occurrence upgrades to harden |
15	| 6 | `spt-term tests/stream.rs:49 bounded_backpressure_stalls_an_unconsumed_reader` | 1× hfenduleam Windows (2026-06-15 run 27545857429, PR #14 stale-base CI) | asserts an unconsumed bounded stream stalls the writer-side reader; got `16 -> 82` (reader advanced past the bound) — a timing-sensitive backpressure assert racing the bound check under hosted-runner load. Subsystem disjoint from PR #14 (digest-proof CLI) | OBSERVED 1×: did NOT reproduce on the clean-base rerun (green both runners). Not hardened; a 2nd occurrence upgrades to harden |
16	| 7 | `spt-daemon::attach attach_survives_target_brain_restart_exactly_once` | 1× kitsubito (2026-06-16 run 27595180782 attempt 1, M11-W3) | **TIMEOUT, not an assertion fail** — SLOW>60>120>180>TERMINATING, nextest TIMEOUT at the 240s cap (`attach.rs:600`). A heavy multi-process brain-restart-exactly-once test on the slow shared kitsubito box under concurrent load (n1-gate on the same runner + a parallel `ci.yml` docs-publish run firing simultaneously). W3-INDEPENDENT: Windows green end-to-end; ALL tunnel tests passed fast on Linux too (tunnel_e2e 0.941s, loopback_tunnel_backpressures_without_loss 1.091s) — a logic bug fails both runners deterministically, this timed out on one under load | observed; cleared by `gh run rerun --failed` (builds reused, no re-tag). Harden if it recurs — candidate: raise this test's per-test nextest timeout, or serialize it off the concurrent n1-gate slot |
17

# Flake ledger

Tests that have failed nondeterministically, with mechanism notes and hardening
status. Discipline: **any flake gets an entry, not a shrug** — a second
occurrence upgrades the entry from "observed" to "harden it." Rig flakes from
the two-host ladder (docs/TWO-HOST-RUNBOOK.md) land here too.

| # | test | occurrences | mechanism | status |
|---|------|-------------|-----------|--------|
| 1 | `spt-daemon brain_restart_survives_gaplessly` | 1× hosted Windows (2026-06-03, hung) | ConPTY stall under hosted-runner load | job `timeout-minutes` bounds it; not seen self-hosted |
| 2 | `spt-term digest_parses_a_real_pty_stream` | 3× gravity (2026-06-03; 2026-06-04 runs 26957386850, 26966738231) | sprint-collapse assert (`digest.rs`) — PTY input echo and the child's write are **concurrent writers** into the master-read stream; back-to-back `send_line`s let a later line's echo interleave mid-`Write(…)`, breaking the tool regex for that turn | HARDENED (2026-06-04): sends serialized — each line pumped back (adaptive echo-copy count, 2s/line bound) before the next, so no concurrent writer races an earlier line; failure asserts now print the raw transcript |
| 3 | `spt-daemon sync.rs torn_pull` + `concurrent_writes` | 1× (2026-06-03) | `wait_for_stream` deadline too tight under load | HARDENED: `wait_for_stream_except` 2s→10s; no recurrence |
| 4 | `spt-live context::write_context_suppresses_stale_llm` | 1× gravity (2026-06-04 run 26967808321) | test calls `write_context` (reads SPT_HOME node identity + epoch stamp) WITHOUT the `with_home` lock — a parallel `with_home` test's env swap + temp-dir teardown races the stamp's epoch write into a deleted home (`NotFound`) | HARDENED (2026-06-04): test wrapped in `with_home` — serializes on the home lock like every other SPT_HOME-touching test |
| 5 | `spt-daemon attach.rs:600` re-serve (`output gap: got seq 1 want 0`) | 1× kitsubito Linux (2026-06-15 run 27545857429, PR #14 stale-base CI) | re-serve replays the buffered output stream on re-attach; under load the new subscriber observed `seq 1` before `seq 0` — an ordering/timing gap in the re-serve sequence assert, not a content error. Subsystem disjoint from the triggering change (PR #14 = digest-proof CLI key-fill, cannot touch attach sequencing) | OBSERVED 1×: did NOT reproduce on the clean-base rerun (`gh pr update-branch` onto post-#15 main → green both runners). Not hardened; a 2nd occurrence upgrades to harden |
| 6 | `spt-term tests/stream.rs:49 bounded_backpressure_stalls_an_unconsumed_reader` | 1× hfenduleam Windows (2026-06-15 run 27545857429, PR #14 stale-base CI) | asserts an unconsumed bounded stream stalls the writer-side reader; got `16 -> 82` (reader advanced past the bound) — a timing-sensitive backpressure assert racing the bound check under hosted-runner load. Subsystem disjoint from PR #14 (digest-proof CLI) | OBSERVED 1×: did NOT reproduce on the clean-base rerun (green both runners). Not hardened; a 2nd occurrence upgrades to harden |
| 7 | `spt-daemon::attach attach_survives_target_brain_restart_exactly_once` | 1× kitsubito (2026-06-16 run 27595180782 attempt 1, M11-W3) | **TIMEOUT, not an assertion fail** — SLOW>60>120>180>TERMINATING, nextest TIMEOUT at the 240s cap (`attach.rs:600`). A heavy multi-process brain-restart-exactly-once test on the slow shared kitsubito box under concurrent load (n1-gate on the same runner + a parallel `ci.yml` docs-publish run firing simultaneously). W3-INDEPENDENT: Windows green end-to-end; ALL tunnel tests passed fast on Linux too (tunnel_e2e 0.941s, loopback_tunnel_backpressures_without_loss 1.091s) — a logic bug fails both runners deterministically, this timed out on one under load | observed; cleared by `gh run rerun --failed` (builds reused, no re-tag). Harden if it recurs — candidate: raise this test's per-test nextest timeout, or serialize it off the concurrent n1-gate slot |


Adding the FLAKE-LEDGER entry (#8) — doyle's rider directive, folded into this PR:


Edit

| 7 | `spt-daemon::attach attach_survives_target_brain_restart_exactly_once` | 1× kitsubito (2026-06-16 run 27595180782 attempt 1, M11-W3) | **TIMEOUT, not an assertion fail** — SLOW>60>120>180>TERMINATING, nextest TIMEOUT at the 240s cap (`attach.rs:600`). A heavy multi-process brain-restart-exactly-once test on the slow shared kitsubito box under concurrent load (n1-gate on the same runner + a parallel `ci.yml` docs-publish run firing simultaneously). W3-INDEPENDENT: Windows green end-to-end; ALL tunnel tests passed fast on Linux too (tunnel_e2e 0.941s, loopback_tunnel_backpressures_without_loss 1.091s) — a logic bug fails both runners deterministically, this timed out on one under load | observed; cleared by `gh run rerun --failed` (builds reused, no re-tag). Harden if it recurs — candidate: raise this test's per-test nextest timeout, or serialize it off the concurrent n1-gate slot |

| 7 | `spt-daemon::attach attach_survives_target_brain_restart_exactly_once` | 1× kitsubito (2026-06-16 run 27595180782 attempt 1, M11-W3) | **TIMEOUT, not an assertion fail** — SLOW>60>120>180>TERMINATING, nextest TIMEOUT at the 240s cap (`attach.rs:600`). A heavy multi-process brain-restart-exactly-once test on the slow shared kitsubito box under concurrent load (n1-gate on the same runner + a parallel `ci.yml` docs-publish run firing simultaneously). W3-INDEPENDENT: Windows green end-to-end; ALL tunnel tests passed fast on Linux too (tunnel_e2e 0.941s, loopback_tunnel_backpressures_without_loss 1.091s) — a logic bug fails both runners deterministically, this timed out on one under load | observed; cleared by `gh run rerun --failed` (builds reused, no re-tag). Harden if it recurs — candidate: raise this test's per-test nextest timeout, or serialize it off the concurrent n1-gate slot |
| 8 | CI build step `Build notify-shell` (kitsubito Linux) | 1× kitsubito (2026-06-16 run 27652755792 attempt 1, v0.8.1 PR #17) | crates.io dep-download blip during the notify-shell build — `download of config.json failed, curl failed` fetching `serde_json`: a transient registry/network fetch failure on the runner, not a code or test fault (a real dep break fails both runners deterministically; this hit one runner once) | observed; cleared by `gh run rerun --failed` (attempt 2 green, builds reused). Harden if it recurs — candidate: a cargo fetch retry / registry cache warm on the runner |


The file docs\FLAKE-LEDGER.md has been updated successfully. (file state is current in your context — no need to Read it back)

| 7 | `spt-daemon::attach attach_survives_target_brain_restart_exactly_once` | 1× kitsubito (2026-06-16 run 27595180782 attempt 1, M11-W3) | **TIMEOUT, not an assertion fail** — SLOW>60>120>180>TERMINATING, nextest TIMEOUT at the 240s cap (`attach.rs:600`). A heavy multi-process brain-restart-exactly-once test on the slow shared kitsubito box under concurrent load (n1-gate on the same runner + a parallel `ci.yml` docs-publish run firing simultaneously). W3-INDEPENDENT: Windows green end-to-end; ALL tunnel tests passed fast on Linux too (tunnel_e2e 0.941s, loopback_tunnel_backpressures_without_loss 1.091s) — a logic bug fails both runners deterministically, this timed out on one under load | observed; cleared by `gh run rerun --failed` (builds reused, no re-tag). Harden if it recurs — candidate: raise this test's per-test nextest timeout, or serialize it off the concurrent n1-gate slot |
| 8 | CI build step `Build notify-shell` (kitsubito Linux) | 1× kitsubito (2026-06-16 run 27652755792 attempt 1, v0.8.1 PR #17) | crates.io dep-download blip during the notify-shell build — `download of config.json failed, curl failed` fetching `serde_json`: a transient registry/network fetch failure on the runner, not a code or test fault (a real dep break fails both runners deterministically; this hit one runner once) | observed; cleared by `gh run rerun --failed` (attempt 2 green, builds reused). Harden if it recurs — candidate: a cargo fetch retry / registry cache warm on the runner |

# Flake ledger

Tests that have failed nondeterministically, with mechanism notes and hardening
status. Discipline: **any flake gets an entry, not a shrug** — a second
occurrence upgrades the entry from "observed" to "harden it." Rig flakes from
the two-host ladder (docs/TWO-HOST-RUNBOOK.md) land here too.

| # | test | occurrences | mechanism | status |
|---|------|-------------|-----------|--------|
| 1 | `spt-daemon brain_restart_survives_gaplessly` | 1× hosted Windows (2026-06-03, hung) | ConPTY stall under hosted-runner load | job `timeout-minutes` bounds it; not seen self-hosted |
| 2 | `spt-term digest_parses_a_real_pty_stream` | 3× gravity (2026-06-03; 2026-06-04 runs 26957386850, 26966738231) | sprint-collapse assert (`digest.rs`) — PTY input echo and the child's write are **concurrent writers** into the master-read stream; back-to-back `send_line`s let a later line's echo interleave mid-`Write(…)`, breaking the tool regex for that turn | HARDENED (2026-06-04): sends serialized — each line pumped back (adaptive echo-copy count, 2s/line bound) before the next, so no concurrent writer races an earlier line; failure asserts now print the raw transcript |
| 3 | `spt-daemon sync.rs torn_pull` + `concurrent_writes` | 1× (2026-06-03) | `wait_for_stream` deadline too tight under load | HARDENED: `wait_for_stream_except` 2s→10s; no recurrence |
| 4 | `spt-live context::write_context_suppresses_stale_llm` | 1× gravity (2026-06-04 run 26967808321) | test calls `write_context` (reads SPT_HOME node identity + epoch stamp) WITHOUT the `with_home` lock — a parallel `with_home` test's env swap + temp-dir teardown races the stamp's epoch write into a deleted home (`NotFound`) | HARDENED (2026-06-04): test wrapped in `with_home` — serializes on the home lock like every other SPT_HOME-touching test |
| 5 | `spt-daemon attach.rs:600` re-serve (`output gap: got seq 1 want 0`) | 1× kitsubito Linux (2026-06-15 run 27545857429, PR #14 stale-base CI) | re-serve replays the buffered output stream on re-attach; under load the new subscriber observed `seq 1` before `seq 0` — an ordering/timing gap in the re-serve sequence assert, not a content error. Subsystem disjoint from the triggering change (PR #14 = digest-proof CLI key-fill, cannot touch attach sequencing) | OBSERVED 1×: did NOT reproduce on the clean-base rerun (`gh pr update-branch` onto post-#15 main → green both runners). Not hardened; a 2nd occurrence upgrades to harden |
| 6 | `spt-term tests/stream.rs:49 bounded_backpressure_stalls_an_unconsumed_reader` | 1× hfenduleam Windows (2026-06-15 run 27545857429, PR #14 stale-base CI) | asserts an unconsumed bounded stream stalls the writer-side reader; got `16 -> 82` (reader advanced past the bound) — a timing-sensitive backpressure assert racing the bound check under hosted-runner load. Subsystem disjoint from PR #14 (digest-proof CLI) | OBSERVED 1×: did NOT reproduce on the clean-base rerun (green both runners). Not hardened; a 2nd occurrence upgrades to harden |
| 7 | `spt-daemon::attach attach_survives_target_brain_restart_exactly_once` | 1× kitsubito (2026-06-16 run 27595180782 attempt 1, M11-W3) | **TIMEOUT, not an assertion fail** — SLOW>60>120>180>TERMINATING, nextest TIMEOUT at the 240s cap (`attach.rs:600`). A heavy multi-process brain-restart-exactly-once test on the slow shared kitsubito box under concurrent load (n1-gate on the same runner + a parallel `ci.yml` docs-publish run firing simultaneously). W3-INDEPENDENT: Windows green end-to-end; ALL tunnel tests passed fast on Linux too (tunnel_e2e 0.941s, loopback_tunnel_backpressures_without_loss 1.091s) — a logic bug fails both runners deterministically, this timed out on one under load | observed; cleared by `gh run rerun --failed` (builds reused, no re-tag). Harden if it recurs — candidate: raise this test's per-test nextest timeout, or serialize it off the concurrent n1-gate slot |

+| 8 | CI build step `Build notify-shell` (kitsubito Linux) | 1× kitsubito (2026-06-16 run 27652755792 attempt 1, v0.8.1 PR #17) | crates.io dep-download blip during the notify-shell build — `download of config.json failed, curl failed` fetching `serde_json`: a transient registry/network fetch failure on the runner, not a code or test fault (a real dep break fails both runners deterministically; this hit one runner once) | observed; cleared by `gh run rerun --failed` (attempt 2 green, builds reused). Harden if it recurs — candidate: a cargo fetch retry / registry cache warm on the runner |


FLAKE-LEDGER #8 added. Committing it + running the gate on the final tree (clippy/traceable/xtask — fast, confirms my edit; agent already ran the full suite green on 1c05a4d):


PowerShell

git add docs/FLAKE-LEDGER.md; git commit -m @'
docs: FLAKE-LEDGER #8 — kitsubito notify-shell crates.io curl/dep-download flake

Rider on the v0.8.2 change (doyle directive: log it on the next natural change,
not a standalone PR). run 27652755792 attempt 1 (v0.8.1 PR #17): transient
crates.io fetch failure on the notify-shell build step; `gh run rerun --failed`
(attempt 2) cleared it.

Co-authored by: todlando
'@ 2>&1 | Select-Object -Last 2; Write-Output "=== HEAD ==="; git log --oneline -3; Write-Output "--- clippy ---"; cargo clippy --workspace --all-targets -- -D warnings 2>&1 | Select-Object -Last 2; Write-Output "--- traceable ---"; traceable-reqs check 2>&1 | Select-String -Pattern "REQ-HAZARD-TEMPLATE-ARGV-FILL|REQ-HAZARD-LIVEHOST-NONRESIDENT" -Context 0,0; Write-Output "TRACEABLE_EXIT=$LASTEXITCODE"; Write-Output "--- xtask ---"; cargo run -q -p xtask -- check 2>&1 | Select-Object -Last 1

Commit FLAKE-LEDGER + run clippy/traceable/xtask gate


[v0.8.2-templating-residency 3a5364f] docs: FLAKE-LEDGER #8 — kitsubito notify-shell crates.io curl/dep-download flake
 1 file changed, 1 insertion(+)
=== HEAD ===
3a5364f docs: FLAKE-LEDGER #8 — kitsubito notify-shell crates.io curl/dep-download flake
1c05a4d v0.8.2 F-009/F-010: argv-element template fill + Psyche residency confirm
3cd5e6f v0.8.2 base: mint REQ-HAZARD-TEMPLATE-ARGV-FILL + REQ-HAZARD-LIVEHOST-NONRESIDENT (registry-first)
--- clippy ---
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.64s
--- traceable ---

  [OK] REQ-HAZARD-LIVEHOST-NONRESIDENT  required: [impl, unit, int]  stages: -doc +impl +unit +int
  [OK] REQ-HAZARD-TEMPLATE-ARGV-FILL  required: [impl, unit, int]  stages: -doc +impl +unit +int
TRACEABLE_EXIT=0
--- xtask ---
xtask check: OK


FLAKE-LEDGER committed (3a5364f); clippy clean, traceable EXIT0 (both REQs [impl,unit,int]), xtask OK. Confirming the key E2Es myself (F-010 + the v0.8.1 regression):


Write-Output "=== F-010 nonresident E2E ==="; cargo test -p spt --test livehost_nonresident_e2e 2>&1 | Select-Object -Last 4; Write-Output "=== v0.8.1 bootrace regression ==="; cargo test -p spt --test livehost_bootrace_e2e 2>&1 | Select-Object -Last 4

Run F-010 + v0.8.1 regression E2Es


=== F-010 nonresident E2E ===
test online_live_agent_with_fast_exiting_psyche_stamps_parent_and_clears_phantom ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 13.33s

=== v0.8.1 bootrace regression ===
test netless_online_live_agent_is_hosted_by_the_real_daemon_brain ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.65s


Both E2Es green on my tree (F-010 stamps parent + clears phantom; v0.8.1 regression holds). **Full gate green.** Pushing + opening the PR:


git push -u origin v0.8.2-templating-residency 2>&1 | Select-Object -Last 3

Push v0.8.2 branch


branch 'v0.8.2-templating-residency' set up to track 'origin/v0.8.2-templating-residency'.
To https://github.com/SaberMage/spt-core.git
   3cd5e6f..3a5364f  v0.8.2-templating-residency -> v0.8.2-templating-residency


@'
## v0.8.2 — two perri-dogfood follow-ups (non-blocking quality)

Both REQ titles in `traceable-reqs.toml` are the full specs; both activated `[impl,unit,int]`.

### F-009 — REQ-HAZARD-TEMPLATE-ARGV-FILL (argv-element template fill)
`fill_template` filled `{key}` into the command **string** then `tokenize` whitespace-split it → a multi-word value became multiple argv tokens (and a `"`/`;` value injected). perri's `{psyche_prompt}` ("PSYCHE REVIVAL time: …") arrived as 7 stray argv tokens → the harness strict-parsed `--prompt` against the 2nd word → exit 2.

**Fix:** new `fill_template_tokens` — tokenize the **template** into argv first, then `fill_template` **each token**, so a filled value is always exactly **one** argv element (no whitespace-split, no quote/`;` injection). Rewired `command_for`, `run_bounded_command_in`, and the `harnesshost`/`shellwake`/`shellhost` argv callers. Single-string fills (history locate, digest source, cwd) correctly keep plain `fill_template`. Preserved: missing-key/empty-command errors, `{{`/`}}` non-interp, and install-dir resolution of `tokens[0]`. 5 new units.

### F-010 — REQ-HAZARD-LIVEHOST-NONRESIDENT (Psyche residency confirm)
v0.8.1's `host_one` stamped `psyche_host_error` only on `spawn_psyche` Err — not when the detached `spawn()` returns Ok but the child dies in ~1s (the F-009 bad-argv consequence): the nested `{id}-psyche` perch read `status=online` with a dead pid while the parent carried no error.

**Fix:** residency-confirm on the existing ~5s reconcile (non-blocking) — a direct `is_process_alive` pid-probe of the `{id}-psyche` perch (not the perch's own masking `status`). Pure `classify_residency(elapsed, grace, alive) → Resident/Pending/Failed`. On **Failed** (dead past a 5s grace): stamp the **parent** `psyche_host_error{reason: "host exited within <n>s…"}`, mark the phantom nested perch offline, un-host, and arm a one-interval cooldown (no same-sweep respawn of the doomed psyche). **clear-on-success moved** from the spawn-Ok leg to the **Resident** arm — post-F-010 a detached spawn-Ok is not a host success; only residency is. Parent `status=online` stays authoritative (v0.8.1 invariant). New `classify_residency` unit + new real-daemon early-exit E2E (`livehost_nonresident_e2e.rs`: real broker→brain-child, psyche exits 2 fast → parent stamped, no phantom online).

These are linked: F-009's argv-split is what *caused* F-010's fast-exit phantom; F-009 fixes the cause, F-010 surfaces any early-exit (defense-in-depth).

Also folds a one-line `docs/FLAKE-LEDGER.md` #8 (kitsubito notify-shell crates.io curl flake, rider per directive).

### Gate (all green locally)
- `cargo clippy --workspace --all-targets -D warnings` — clean
- `traceable-reqs check` — EXIT 0 (both REQs `[impl,unit,int]`)
- 5 template units + `classify_residency` unit + 17 spt-daemon lib units — pass
- E2Es: `livehost_nonresident_e2e` (F-010), `livehost_bootrace_e2e` + `livehost_psyche_fail_e2e` (v0.8.1 regressions) — pass
- `xtask check` — OK (no CLI render change)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
'@ | Out-File -FilePath $env:TEMP/v082-pr-body.md -Encoding utf8; gh pr create --base main --head v0.8.2-templating-residency --title "v0.8.2: argv-element template fill (F-009) + Psyche residency confirm (F-010)" --body-file $env:TEMP/v082-pr-body.md 2>&1 | Select-Object -Last 3

Open the v0.8.2 PR


PR **#18** opened. Pinging doyle PR-ready with the gate + the flags I owe (gater's calls):


Bash

cat <<'EOF' | "$OWL" send doyle
todlando — v0.8.2 PR-READY: https://github.com/SaberMage/spt-core/pull/18 (branch v0.8.2-templating-residency @3a5364f, base main). CI starting.

WHAT LANDED:
- F-009 TEMPLATE-ARGV-FILL: new fill_template_tokens (tokenize template FIRST, fill each token → 1 value = 1 argv element, no split/quote/semicolon injection). Rewired command_for + run_bounded_command_in + harnesshost/shellwake/shellhost. Single-string fills (history/digest/cwd) keep plain fill_template. Preserved missing-key/empty-command errors + {{/}} + install-dir resolve of tokens[0]. 5 units.
- F-010 LIVEHOST-NONRESIDENT: residency-confirm on the 5s reconcile (non-blocking) — direct is_process_alive pid-probe (NOT the perch's masking status). classify_residency→Resident/Pending/Failed. Failed → stamp parent + de-online phantom nested perch + un-host + 1-interval cooldown. clear-on-success MOVED to the Resident arm (a detached spawn-Ok isn't host success; only residency is). New real-daemon early-exit E2E.
- FLAKE-LEDGER #8 folded (your rider directive — kitsubito notify-shell curl flake).

GATE GREEN LOCALLY (re-ran on final tree): clippy --workspace --all-targets -D warnings clean · traceable EXIT0 (both REQs [impl,unit,int]) · F-010 E2E + both v0.8.1 regression E2Es pass · units pass · xtask OK.

THREE GATER NOTES (your calls):
1. EXIT-CODE TITLE NUANCE: the F-010 reason is residency-based ("host exited within <n>s") — the exact exit <code> is UNOBTAINABLE across the detached/fire-and-forget seam (handle dropped, OS reaps; reaping it = option B which crosses the daemon-authoritative-via-perch seam, rejected). Your REQ title says reason:"host exited <code> within <n>s" — the <code> isn't in the reason. You own the title; trivial reword if you want, non-blocking.
2. POSITIVE-TEST TIMING: the v0.8.1 positive E2E kept its fast-exit fixture (no change) — it reads phe after killing the brain, ~1-2s post-host, and the brain is dead before the 5s residency grace, so a false Failed needs the psyche to take >=5s just to spawn+register (extreme). Low flake risk. I considered a long-lived fixture (timing-independent Resident) — available if you'd rather lock it down. Your call.
3. clear-on-success→Resident-arm move: I approved it (correct post-F-010 semantics). Flagged for your veto if you disagree.

Over to you to code-read + gate + CI. I'll watch CI + report. deployah ships v0.8.2 (counter 18) → you ping perri.
EOF
echo "EXIT=$?"

Ping doyle PR-ready for v0.8.2 with gater notes


➜ SENT:doyle
EXIT=0


Sent. Updating memory + launching the CI watch on PR #18:


Edit

**GATE (pre-flight before pinging doyle):**

**PR-READY @3a5364f → PR #18 (base main), pinged doyle.** Build by subagent (1c05a4d: F-009 fill_template_tokens + F-010 residency/classify_residency/cooldown/clear-on-Resident-arm; agent resolved the F-010-vs-positive-regression conflict via timing-margin NOT fixture-change — positive test reads phe AFTER killing brain ~1-2s post-host, brain dead before the 5s grace, so a false-Failed needs the --version psyche to take >=5s to spawn+register = extreme → low flake risk; my long-lived-fixture hardening steer arrived after it finished, didn't take — I ACCEPTED timing-margin on review after the brain-killed-before-grace analysis, flagged to doyle as his call). I added FLAKE-LEDGER #8 (3a5364f, doyle rider). Verified runtime seam myself (fill_template_tokens correct; command_for split_first→resolve_program_in_dir on tokens[0] intact; cwd keeps plain fill). Re-ran gate: clippy clean · traceable EXIT0 (both [impl,unit,int]) · F-010 + both v0.8.1 regression E2Es pass · xtask OK. THREE GATER NOTES sent: (1) exit <code> NOT in F-010 reason (unobtainable across detach seam — title says "<code>", reason is residency-based; doyle owns title, non-blocking); (2) positive-test timing-dependence (low risk, long-lived fixture available if doyle wants); (3) clear-on-success→Resident-arm move (I approved, correct semantics). NEXT: doyle code-reads+gates + CI → deployah ships v0.8.2 (counter 18) → doyle pings perri. Watching CI.

**GATE (pre-flight before pinging doyle):**


The file C:\Users\decid\.ccs\instances\bigscreen\projects\C--Users-decid-Documents-projects-spt-core\memory\v082-templating-residency.md has been updated successfully. (file state is current in your context — no need to Read it back)

**PR-READY @3a5364f → PR #18 (base main), pinged doyle.** Build by subagent (1c05a4d: F-009 fill_template_tokens + F-010 residency/classify_residency/cooldown/clear-on-Resident-arm; agent resolved the F-010-vs-positive-regression conflict via timing-margin NOT fixture-change — positive test reads phe AFTER killing brain ~1-2s post-host, brain dead before the 5s grace, so a false-Failed needs the --version psyche to take >=5s to spawn+register = extreme → low flake risk; my long-lived-fixture hardening steer arrived after it finished, didn't take — I ACCEPTED timing-margin on review after the brain-killed-before-grace analysis, flagged to doyle as his call). I added FLAKE-LEDGER #8 (3a5364f, doyle rider). Verified runtime seam myself (fill_template_tokens correct; command_for split_first→resolve_program_in_dir on tokens[0] intact; cwd keeps plain fill). Re-ran gate: clippy clean · traceable EXIT0 (both [impl,unit,int]) · F-010 + both v0.8.1 regression E2Es pass · xtask OK. THREE GATER NOTES sent: (1) exit <code> NOT in F-010 reason (unobtainable across detach seam — title says "<code>", reason is residency-based; doyle owns title, non-blocking); (2) positive-test timing-dependence (low risk, long-lived fixture available if doyle wants); (3) clear-on-success→Resident-arm move (I approved, correct semantics). NEXT: doyle code-reads+gates + CI → deployah ships v0.8.2 (counter 18) → doyle pings perri. Watching CI.

**GATE (pre-flight before pinging doyle):**

---
name: v082-templating-residency
description: "v0.8.2 task — two perri-dogfood follow-ups (non-blocking quality): F-009 REQ-HAZARD-TEMPLATE-ARGV-FILL (tokenize-then-fill argv) + F-010 REQ-HAZARD-LIVEHOST-NONRESIDENT (residency-confirm). doyle delegation, I own waves+exec, doyle gates+release. Branch v0.8.2-templating-residency @3cd5e6f."
metadata:
  node_type: memory
  type: project
  originSessionId: 8350d43e-fe2d-4e24-9999-eecbcc6d3a50
---

**v0.8.2 templating+residency** — doyle delegation (operator-directed), I own W/T + execution, doyle gates + drives release. NON-blocking quality fixes (perri UNBLOCKED on v0.8.1, parity proof passed — no rush, do it right). Branch `v0.8.2-templating-residency` off main (v0.8.1), base @3cd5e6f. Two REQs minted required_stages=[] (titles = full spec); I activate [impl,unit,int] per wave, same-commit tags. Sibling of [[v081-livehost-bootrace]]. doyle drove v0.8.1 publish (counter 17).

**REQ 1 — REQ-HAZARD-TEMPLATE-ARGV-FILL (F-009):** runtime.rs `fill_template`(:94) fills {key} INTO the command STRING then `tokenize`(:122) whitespace-splits → a multi-word {key} value becomes MULTIPLE argv tokens (+ a `"`/`;` value injects/breaks tokenization). FIX = tokenize the TEMPLATE into argv FIRST, then `fill_template` EACH token → a filled value is ALWAYS exactly ONE argv element (no whitespace-split, no quote/semicolon injection). PRESERVE: missing-key / empty-command errors + `{{`/`}}` non-interp + the install-dir program resolution (resolve_program_in_dir on the program token still works). Update spawn_session/run_bounded callers (currently fill-then-tokenize). Applies to ALL [session.<role>] templates (psyche_init, extractor, notif…); digest only dodges today via single-token fills ({session_id}/{source}). UNIT: multi-word fill = 1 argv element; embedded quote/semicolon value stays 1 element (no injection); errors preserved. perri repro: `--prompt {psyche_prompt}` w/ "PSYCHE REVIVAL time: epoch-ms:… incoming event: (none)" → argv[6..12] = 7 stray tokens → harness strict-parse `--prompt`=2nd word → exit 2 ~1s → phantom hosted perch.

**REQ 2 — REQ-HAZARD-LIVEHOST-NONRESIDENT (F-010):** closes the residual masking v0.8.1 left. host_one (livehost.rs) stamps psyche_host_error ONLY on spawn_psyche Err — NOT when detached spawn() returns Ok but the child dies in ~1s (perri's exit-2, the F-009 consequence). Result: nested {id}-psyche/info.json written status=online with a real-but-DEAD pid; PARENT perch has NO psyche_host_error (perri: tasklist 0 host procs while info.json reads online). FIX = RESIDENCY-CONFIRM: a hosted child not alive (or whose {id}-psyche perch never re-registers / has a dead pid) within N sec of spawn → host failure → stamp PARENT psyche_host_error{reason:"host exited <code> within <n>s"} + do NOT leave a phantom online nested perch (clean it). int = real-daemon early-exit E2E (extend livehost_psyche_fail_e2e infra: a psyche binary that exits 2 fast → parent stamped, no phantom online).
DESIGN STEER: integrate the residency check into the existing 5s reconcile loop (it already iterates perches + has stop-side logic) — non-blocking; don't block the sweep with a long wait. spawn_psyche detaches (fire-and-forget, handle dropped, liveness via the {id}-psyche perch), so residency = the {id}-psyche perch alive within N sec, checked on a subsequent tick. host_one knows the spawned pid (h.pid).

**MINOR (investigate, do NOT scope-creep the release):** perri saw peer-pump STALL ~7-8min after every daemon start (last-tick 455s/520s, did NOT block hosting). Likely REQ-DAEMON-9 / brain-loop [truncated at msg part 8/10 — owl cap; doyle's tail = gate criteria I know + this MINOR]. → investigate, log/defer, don't block v0.8.2.

**PLAN:** delegate the build (both REQs, one agent, full budget — my context deep). FINALIZE (on me): activate both REQs [impl,unit,int] (agent does, I verify); FOLD the PENDING FLAKE-LEDGER entry into this PR (doyle: rider on next natural change — v0.8.2 IS it): docs/FLAKE-LEDGER.md += kitsubito "Build notify-shell" crates.io curl/dep-download blip → `gh run rerun --failed` clears (run 27652755792, v0.8.1 PR #17). Then FULL gate (clippy --workspace --all-targets -D warnings, traceable EXIT0, the E2Es, docs-drift xtask check) → push + PR → ping doyle PR-ready → doyle gates → deployah ships v0.8.2 (counter 18) → doyle pings perri.

**DESIGN RULING (F-010 residency, build-agent asked):** the v0.8.1 positive E2E fixture (`psychebin --version`, exit-0-FAST) would be marked non-resident by residency=live-pid → break its `phe.is_none()` assert. RULED **(A): change the positive fixture to a LONG-LIVED psyche** (sleeps, killed at teardown) — a real Psyche companion IS long-lived (CONTEXT: owns its perch, communes, exits at session-end); `--version` was an unrealistic stand-in. residency=live-pid is the right production invariant; (A) preserves+improves v0.8.1 coverage (touch fixture, not asserts). **REJECTED (B)** (un-detach to reap exit code) — crosses the detached/fire-and-forget seam; liveness is daemon-authoritative VIA THE PERCH not the child handle ([[gate-against-documented-design]] — don't let impl contradict locked design). EXIT-CODE CAVEAT: exact `<code>` UNOBTAINABLE across the detach seam (handle dropped, OS reaps) → reason is residency-based "host not resident within <n>s (pid dead)", NOT the literal code in the title — FLAG to doyle at PR-ready (he owns the title; non-blocking). N grace ≈ 5s (one reconcile tick). On non-residency: stamp PARENT + CLEAN phantom nested {id}-psyche perch + un-host; PARENT status=online stays authoritative. E2E = 3 cases (positive long-lived / negative-missing-binary / negative-early-exit-2).

**GATE (pre-flight before pinging doyle):** clippy --workspace --all-targets -D warnings · traceable EXIT0 · docs-drift xtask check (gen if CLI touched — likely NOT) · the new/extended E2Es + units green.

+**PR-READY @3a5364f → PR #18 (base main), pinged doyle.** Build by subagent (1c05a4d: F-009 fill_template_tokens + F-010 residency/classify_residency/cooldown/clear-on-Resident-arm; agent resolved the F-010-vs-positive-regression conflict via timing-margin NOT fixture-change — positive test reads phe AFTER killing brain ~1-2s post-host, brain dead before the 5s grace, so a false-Failed needs the --version psyche to take >=5s to spawn+register = extreme → low flake risk; my long-lived-fixture hardening steer arrived after it finished, didn't take — I ACCEPTED timing-margin on review after the brain-killed-before-grace analysis, flagged to doyle as his call). I added FLAKE-LEDGER #8 (3a5364f, doyle rider). Verified runtime seam myself (fill_template_tokens correct; command_for split_first→resolve_program_in_dir on tokens[0] intact; cwd keeps plain fill). Re-ran gate: clippy clean · traceable EXIT0 (both [impl,unit,int]) · F-010 + both v0.8.1 regression E2Es pass · xtask OK. THREE GATER NOTES sent: (1) exit <code> NOT in F-010 reason (unobtainable across detach seam — title says "<code>", reason is residency-based; doyle owns title, non-blocking); (2) positive-test timing-dependence (low risk, long-lived fixture available if doyle wants); (3) clear-on-success→Resident-arm move (I approved, correct semantics). NEXT: doyle code-reads+gates + CI → deployah ships v0.8.2 (counter 18) → doyle pings perri. Watching CI.


PowerShell

Start-Sleep -Seconds 8; gh run list --branch v0.8.2-templating-residency --limit 2 --json databaseId,status,event -q '.[] | (.databaseId|tostring) + " [" + .event + "] " + .status' 2>&1 | Select-Object -First 4

Find v0.8.2 PR CI run


27657752574 [pull_request] queued


gh run watch 27657752574 --exit-status > $env:TEMP/ci-v082.log 2>&1; Write-Output "WATCH_DONE exit=$LASTEXITCODE"; gh run view 27657752574 --json status,conclusion -q '.status + " / " + (.conclusion // "none")'

Watch v0.8.2 PR CI to completion (background)


Command running in background with ID: b062fn3uf. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\30df62c3-0b22-4b14-88de-fef422a5554f\tasks\b062fn3uf.output. You will be notified when it completes. To check interim output, use Read on that file path.


**v0.8.2 PR-ready — handed to doyle.**

- **PR #18** (branch @3a5364f, base main); CI run 27657752574 watching in background (`b062fn3uf`).
- **F-009:** `fill_template_tokens` — tokenize template first, fill each token → one value = one argv element (no split/quote/`;` injection); callers rewired; errors + `{{/}}` + install-dir resolution preserved.
- **F-010:** residency-confirm on the 5s reconcile (direct `is_process_alive` probe, `classify_residency`); Failed → stamp parent + de-online phantom + un-host + cooldown; clear-on-success moved to the Resident arm.
- FLAKE-LEDGER #8 folded (doyle's rider).
- Gate green locally (clippy workspace, traceable EXIT0 both REQs, F-010 + both v0.8.1 regression E2Es, xtask). Verified the runtime seam myself.

Sent doyle three gater-notes: exit-`<code>` unobtainable across the detach seam (residency-based reason; his title call), the positive-test timing-dependence (low risk; long-lived-fixture hardening available if he wants), and the clear-on-success→Resident-arm move (I approved).

Next: doyle code-reads + gates + CI → deployah ships v0.8.2 (counter 18) → doyle pings perri. I'll report CI when the watch completes. State durable in [[v082-templating-residency]].


<task-notification>
<task-id>be1did0ug</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/8" id="5b4425c0" type="msg" from="doyle">GATE on PR #18 = CONDITIONAL PASS, one re-spin. Code-read is strong: F-009 fill_template_tokens (tokenize-then-fill, value=1 element, errors preserved, callers rewired) ✓; F-010 residency (DIRECT is_process_alive probe not is_perch_alive, clear-moved-to-Resident-arm, de-phantom + cooldown, pure classify unit) ✓; the</EVENT-PART>
<EVENT-PART seq="2/8" id="5b4425c0"> negative real-daemon E2E (psychebin bad-subcommand → exit2 → parent stamped + phantom de-onlined, exercises the new fill order) ✓. Notes 1 &amp; 3 resolved below. Note 2 is a REQUIRED re-spin.

NOTE 3 (clear-on-Resident move): ACCEPTED, no veto — a detached spawn-Ok isn't host success; only a live resident psyche is. Correct.<br</EVENT-PART>
<EVENT-PART seq="3/8" id="5b4425c0">>
NOTE 1 (title &lt;code&gt;): you're right, the exit code is unobtainable across the detached/fire-and-forget seam (handle dropped; reaping it crosses the daemon-authoritative-via-perch seam — correctly rejected). I own the title — reword its reason clause to MATCH what the impl actually emits: change `host exited &lt;code&gt; within &</EVENT-PART>
<EVENT-PART seq="4/8" id="5b4425c0">lt;n&gt;s` → `host not resident within &lt;n&gt;s (psyche perch missing/dead pid)`. Apply it yourself in the re-spin commit (no branch race), I've ratified the wording.

NOTE 2 (positive E2E) — REQUIRED FIX: the v0.8.1 positive E2E (livehost_bootrace_e2e) uses a FAST-EXIT fixture (psychebin --version). Post-F-010 that is, by your own</EVENT-PART>
<EVENT-PART seq="5/8" id="5b4425c0"> definition, a FAILED host — it passes ONLY by killing the brain before the 5s grace fires. Two problems: (a) it's a hollow positive + flake-prone on the slow kitsubito runner (any scheduling slip past 5s before the phe-read → residency-Failed stamps → assert flips RED); (b) the RESIDENT path now has ZERO E2E coverage — nothing proves </EVENT-PART>
<EVENT-PART seq="6/8" id="5b4425c0">residency-confirm CONFIRMS a healthy psyche, clears the stamp, and does NOT false-fail a slow-but-alive one. Fix: give the positive E2E a LONG-LIVED resident psyche fixture (stays alive well past PSYCHE_RESIDENCY_GRACE — e.g. a binary that sleeps ~20s), then WAIT past grace+one reconcile tick (~7-8s) and assert: {id}-psyche pid still ALIVE (</EVENT-PART>
<EVENT-PART seq="7/8" id="5b4425c0">Resident) AND parent psyche_host_error is None. That kills the timing race AND gives the Resident-path proof. Bonus (nice-to-have, not required): induce a transient stamp first, then prove the resident host CLEARS it. Bounded + pid-scoped reaps as usual (reap the resident psyche pid too).

Re-run the FULL gate on re-spin (clippy --worksp</EVENT-PART>
<EVENT-PART seq="8/8" id="5b4425c0">ace -D warnings, traceable EXIT0, docs-drift, units + BOTH real-daemon E2Es: the new resident-positive + the nonresident-negative). Ping me PR-ready and I re-gate. Everything else is cleared — this is the one blocker.</EVENT-PART
...(truncated)</event>
</task-notification>


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


<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>


doyle gate = **CONDITIONAL PASS, one required re-spin** (Note 2). Notes 1 & 3 resolved:
- **Note 3** (clear-on-Resident move): ACCEPTED, no veto.
- **Note 1** (title `<code>`): doyle ratified the reword — reason → `host not resident within <n>s (psyche perch missing/dead pid)`; I apply it in the re-spin (title + impl reason + E2E assertion, all matching).
- **Note 2 (REQUIRED):** my acceptance of the timing-margin positive test was the weaker call — doyle's right: it's a hollow positive (post-F-010 the fast-exit fixture is a *Failed* host that only passes by killing the brain before grace) AND leaves the **Resident path with zero E2E coverage**. Fix: long-lived resident psyche fixture (sleeps ~20s, past grace), wait past grace+one tick (~7-8s), assert `{id}-psyche` pid still ALIVE (Resident) + parent `psyche_host_error` None. (This was my original hardening instinct; doyle sharpened the rationale — I should have pushed it, not accepted the margin.)

The current CI watch (on @3a5364f) is now moot — re-spin incoming. Delegating the re-spin to a fresh agent (test-infra work + reword; my context is deep), then I review + gate + push:


Agent

v0.8.2 re-spin: resident E2E + reason reword

Prompt:
Do a gater-required RE-SPIN on the spt-core Rust workspace (cwd C:\Users\decid\Documents\projects\spt-core, branch v0.8.2-templating-residency, already checked out, HEAD 3a5364f). Two changes (the gater approved everything else). Do NOT run the full test suite (hangs in preflight) — run ONLY the targeted tests named below, bounded. Ground in the existing code (read the cited files). Commit on the branch (do NOT push); report back.

BACKGROUND: v0.8.2 added F-010 Psyche residency-confirm in crates/spt-daemon/src/livehost.rs: a `classify_residency(elapsed, grace, alive) -> Resident/Pending/Failed`; on Failed it stamps the PARENT perch `psyche_host_error` (via spt_store::info::set_psyche_host_error), de-onlines the phantom nested `{id}-psyche` perch, un-hosts, and arms a cooldown. There's a residency grace constant (find it — likely `PSYCHE_RESIDENCY_GRACE` ~5s). The clear-on-success was moved to the Resident arm.

=== CHANGE 1 (Note 1 — reason reword, gater-ratified) ===
The F-010 host-failure reason string is currently something like "host exited within <n>s…". The exit code is unobtainable across the detached spawn seam, so the gater ratified this exact reason wording instead:
    host not resident within <n>s (psyche perch missing/dead pid)
where <n> is the grace in seconds (use the actual grace constant value; if the code builds the string with the number interpolated, keep that).
Apply it in THREE places so they all match:
1. crates/spt-daemon/src/livehost.rs — the reason string passed to set_psyche_host_error on the Failed path.
2. traceable-reqs.toml — REQ-HAZARD-LIVEHOST-NONRESIDENT title's reason clause (change `host exited <code> within <n>s` → `host not resident within <n>s (psyche perch missing/dead pid)`).
3. crates/spt/tests/livehost_nonresident_e2e.rs — the assertion that checks the reason text (update the substring it greps for, e.g. it now must contain "not resident" / "within").

=== CHANGE 2 (Note 2 — REQUIRED: give the positive E2E a LONG-LIVED resident psyche) ===
Problem: crates/spt/tests/livehost_bootrace_e2e.rs positive E2E uses a FAST-EXIT psyche fixture (psychebin --version, exits ~immediately). Post-F-010 that is a FAILED host by definition; the test only passes by killing the brain before the 5s grace fires — a hollow positive + flake-prone on the slow shared kitsubito CI runner, AND it leaves the RESIDENT path with ZERO E2E coverage (nothing proves residency-confirm CONFIRMS a healthy psyche, clears the stamp, and does NOT false-fail a slow-but-alive one).

FIX: give the positive E2E a LONG-LIVED resident psyche fixture that stays alive well past the residency grace (~20s), then WAIT past grace + one reconcile tick (~7-8s) and assert BOTH:
  - the `{id}-psyche` perch's pid is STILL ALIVE (use the same liveness check the impl uses — spt_store::proc::is_process_alive on the pid in the nested perch info.json) → proves Resident, and
  - the PARENT perch `psyche_host_error` is None (a resident host shows clean).
This kills the timing race AND gives the Resident-path proof.

HOW TO MAKE A LONG-LIVED PSYCHE FIXTURE (study the existing fixture setup in livehost_bootrace_e2e.rs + livehost_nonresident_e2e.rs first — how `psychebin` is created/copied into the install dir and how `[session.psyche_init].command` invokes it by BARE token for REQ-INSTALL-11 resolution): the fixture must STILL be a real binary resolved from the adapter install dir (keep REQ-INSTALL-11 fidelity), but invoked so it BLOCKS ~20s. Investigate the most robust option and pick one:
  - psychebin = a copy of the `spt` binary invoked with a BLOCKING subcommand (e.g. `spt ready <throwaway-id>` blocks as a message listener until killed — verify it blocks and needs only SPT_HOME which the test has; pick a throwaway id that won't collide), OR
  - a tiny cross-platform sleeper binary placed in the install dir (e.g. reuse an existing built helper/example in the repo, or a platform sleep), still referenced by bare token.
Pick whatever is robust + cross-platform (Windows hfenduleam + Linux kitsubito both run CI). Justify the choice in a comment.
REAP the resident psyche pid at teardown too (scoped to the pid you spawned — read it from the nested perch info.json; NEVER a machine-wide `Get-Process spt | Stop-Process`, a live broker shares this box). Keep bounded deadlines (~30s). Keep the existing `[int->...]` tags.
(Bonus, NOT required — skip unless trivial: induce a transient psyche_host_error first, then prove the resident host CLEARS it.)

=== GATE (run EXACTLY these, targeted only; report verbatim) ===
- `cargo clippy --workspace --all-targets -- -D warnings`   (exact CI invocation)
- `traceable-reqs check`   (EXIT 0)
- `cargo test -p spt --test livehost_bootrace_e2e -- --nocapture`   (the REWORKED resident-positive — must now assert Resident pid-alive + phe None)
- `cargo test -p spt --test livehost_nonresident_e2e -- --nocapture`   (negative — reason text updated)
- `cargo test -p spt --test livehost_psyche_fail_e2e -- --nocapture`   (v0.8.1 missing-binary regression — must still pass)
- `cargo test -p spt-daemon --lib livehost::`   (classify + livehost units)
- `cargo run -p xtask -- check`   (docs-drift)

THEN COMMIT on the branch (do NOT push). Message: "v0.8.2 re-spin: resident-positive E2E + residency reason reword (gater notes 1+2)". End with the trailer line exactly:
Co-authored by: todlando

REPORT BACK: commit sha, file-by-file diff summary, the long-lived-fixture mechanism you chose + why, and each gate command's verbatim result. If any gate fails, fix before committing. Do NOT push — todlando reviews + runs the final gate + pings the gater.


<owl_reachability_notice>
You have an active owl perch. Launch this Agent with run_in_background: true so you stay reachable for incoming messages while it works.
</owl_reachability_notice>


Async agent launched successfully.
agentId: aac341f2a4b7aa0da (internal ID - do not mention to user. Use SendMessage with to: 'aac341f2a4b7aa0da' to continue this agent.)
The agent is working in the background. You will be notified automatically when it completes.
Do not duplicate this agent's work — avoid working with the same files or topics it is using. Work on non-overlapping tasks, or briefly tell the user what you launched and end your response.
output_file: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\30df62c3-0b22-4b14-88de-fef422a5554f\tasks\aac341f2a4b7aa0da.output
Do NOT Read or tail this file via the shell tool — it is the full subagent JSONL transcript and reading it will overflow your context. If the user asks for progress, say the agent is still running; you'll get a completion notification.

v0.8.2 re-spin: resident E2E + reason reword

Prompt:
Do a gater-required RE-SPIN on the spt-core Rust workspace (cwd C:\Users\decid\Documents\projects\spt-core, branch v0.8.2-templating-residency, already checked out, HEAD 3a5364f). Two changes (the gater approved everything else). Do NOT run the full test suite (hangs in preflight) — run ONLY the targeted tests named below, bounded. Ground in the existing code (read the cited files). Commit on the branch (do NOT push); report back.

BACKGROUND: v0.8.2 added F-010 Psyche residency-confirm in crates/spt-daemon/src/livehost.rs: a `classify_residency(elapsed, grace, alive) -> Resident/Pending/Failed`; on Failed it stamps the PARENT perch `psyche_host_error` (via spt_store::info::set_psyche_host_error), de-onlines the phantom nested `{id}-psyche` perch, un-hosts, and arms a cooldown. There's a residency grace constant (find it — likely `PSYCHE_RESIDENCY_GRACE` ~5s). The clear-on-success was moved to the Resident arm.

=== CHANGE 1 (Note 1 — reason reword, gater-ratified) ===
The F-010 host-failure reason string is currently something like "host exited within <n>s…". The exit code is unobtainable across the detached spawn seam, so the gater ratified this exact reason wording instead:
    host not resident within <n>s (psyche perch missing/dead pid)
where <n> is the grace in seconds (use the actual grace constant value; if the code builds the string with the number interpolated, keep that).
Apply it in THREE places so they all match:
1. crates/spt-daemon/src/livehost.rs — the reason string passed to set_psyche_host_error on the Failed path.
2. traceable-reqs.toml — REQ-HAZARD-LIVEHOST-NONRESIDENT title's reason clause (change `host exited <code> within <n>s` → `host not resident within <n>s (psyche perch missing/dead pid)`).
3. crates/spt/tests/livehost_nonresident_e2e.rs — the assertion that checks the reason text (update the substring it greps for, e.g. it now must contain "not resident" / "within").

=== CHANGE 2 (Note 2 — REQUIRED: give the positive E2E a LONG-LIVED resident psyche) ===
Problem: crates/spt/tests/livehost_bootrace_e2e.rs positive E2E uses a FAST-EXIT psyche fixture (psychebin --version, exits ~immediately). Post-F-010 that is a FAILED host by definition; the test only passes by killing the brain before the 5s grace fires — a hollow positive + flake-prone on the slow shared kitsubito CI runner, AND it leaves the RESIDENT path with ZERO E2E coverage (nothing proves residency-confirm CONFIRMS a healthy psyche, clears the stamp, and does NOT false-fail a slow-but-alive one).

FIX: give the positive E2E a LONG-LIVED resident psyche fixture that stays alive well past the residency grace (~20s), then WAIT past grace + one reconcile tick (~7-8s) and assert BOTH:
  - the `{id}-psyche` perch's pid is STILL ALIVE (use the same liveness check the impl uses — spt_store::proc::is_process_alive on the pid in the nested perch info.json) → proves Resident, and
  - the PARENT perch `psyche_host_error` is None (a resident host shows clean).
This kills the timing race AND gives the Resident-path proof.

HOW TO MAKE A LONG-LIVED PSYCHE FIXTURE (study the existing fixture setup in livehost_bootrace_e2e.rs + livehost_nonresident_e2e.rs first — how `psychebin` is created/copied into the install dir and how `[session.psyche_init].command` invokes it by BARE token for REQ-INSTALL-11 resolution): the fixture must STILL be a real binary resolved from the adapter install dir (keep REQ-INSTALL-11 fidelity), but invoked so it BLOCKS ~20s. Investigate the most robust option and pick one:
  - psychebin = a copy of the `spt` binary invoked with a BLOCKING subcommand (e.g. `spt ready <throwaway-id>` blocks as a message listener until killed — verify it blocks and needs only SPT_HOME which the test has; pick a throwaway id that won't collide), OR
  - a tiny cross-platform sleeper binary placed in the install dir (e.g. reuse an existing built helper/example in the repo, or a platform sleep), still referenced by bare token.
Pick whatever is robust + cross-platform (Windows hfenduleam + Linux kitsubito both run CI). Justify the choice in a comment.
REAP the resident psyche pid at teardown too (scoped to the pid you spawned — read it from the nested perch info.json; NEVER a machine-wide `Get-Process spt | Stop-Process`, a live broker shares this box). Keep bounded deadlines (~30s). Keep the existing `[int->...]` tags.
(Bonus, NOT required — skip unless trivial: induce a transient psyche_host_error first, then prove the resident host CLEARS it.)

=== GATE (run EXACTLY these, targeted only; report verbatim) ===
- `cargo clippy --workspace --all-targets -- -D warnings`   (exact CI invocation)
- `traceable-reqs check`   (EXIT 0)
- `cargo test -p spt --test livehost_bootrace_e2e -- --nocapture`   (the REWORKED resident-positive — must now assert Resident pid-alive + phe None)
- `cargo test -p spt --test livehost_nonresident_e2e -- --nocapture`   (negative — reason text updated)
- `cargo test -p spt --test livehost_psyche_fail_e2e -- --nocapture`   (v0.8.1 missing-binary regression — must still pass)
- `cargo test -p spt-daemon --lib livehost::`   (classify + livehost units)
- `cargo run -p xtask -- check`   (docs-drift)

THEN COMMIT on the branch (do NOT push). Message: "v0.8.2 re-spin: resident-positive E2E + residency reason reword (gater notes 1+2)". End with the trailer line exactly:
Co-authored by: todlando

REPORT BACK: commit sha, file-by-file diff summary, the long-lived-fixture mechanism you chose + why, and each gate command's verbatim result. If any gate fails, fix before committing. Do NOT push — todlando reviews + runs the final gate + pings the gater.


Re-spin agent launched (`aac341f2`, background). Updating memory with doyle's gate verdict + the re-spin scope:


Edit

NEXT: doyle code-reads+gates + CI → deployah ships v0.8.2 (counter 18) → doyle pings perri. Watching CI.

**doyle GATE = CONDITIONAL PASS, 1 REQUIRED re-spin.** Code-read strong (F-009 ✓, F-010 residency/direct-probe/clear-on-Resident/de-phantom/cooldown ✓, negative E2E ✓). NOTE 3 (clear-on-Resident) ACCEPTED. NOTE 1 (title <code>): doyle ratified reword `host exited <code> within <n>s` → `host not resident within <n>s (psyche perch missing/dead pid)` — I apply in re-spin (title + impl reason + nonresident-E2E assertion, all matching). NOTE 2 (REQUIRED BLOCKER): the positive E2E's fast-exit fixture is a hollow positive (post-F-010 it's a FAILED host that only passes by killing the brain before grace) + flake-prone on kitsubito + RESIDENT path has ZERO E2E coverage. FIX: long-lived resident psyche fixture (sleeps ~20s past grace), wait past grace+1 tick (~7-8s), assert {id}-psyche pid ALIVE (Resident, is_process_alive) + parent phe None. (= my original hardening instinct; I wrongly ACCEPTED the timing-margin on review — doyle's deeper rationale = Resident-path coverage gap. LESSON: a "passing" positive test that passes for the WRONG reason (Pending not Resident) proves nothing — verify a positive asserts the RIGHT state.) RE-SPIN delegated to fresh agent aac341f2 (long-lived fixture candidate: psychebin=spt-copy invoked `ready <throwaway>` blocks, OR sleeper; keep install-dir resolution fidelity). The @3a5364f CI watch (b062fn3uf) is MOOT (re-spin supersedes). NEXT after re-spin: my full gate (clippy/traceable/docs-drift/units/BOTH E2Es) → push → ping doyle re-gate → deployah ships v0.8.2 (counter 18) → doyle pings perri.


<owl_messages>
<owl_message from="todlando-w163" priority="info">[WORKING_PERCH_NOTICE] Subagent working perch created.
Perch: todlando-w163
Agent type: general-purpose
To message it: $OWL send todlando-w163 todlando</owl_message>
</owl_messages>

[OWL SYSTEM] Informational owl messages above — no acknowledgment needed. Continue your current task.


---
name: v082-templating-residency
description: "v0.8.2 task — two perri-dogfood follow-ups (non-blocking quality): F-009 REQ-HAZARD-TEMPLATE-ARGV-FILL (tokenize-then-fill argv) + F-010 REQ-HAZARD-LIVEHOST-NONRESIDENT (residency-confirm). doyle delegation, I own waves+exec, doyle gates+release. Branch v0.8.2-templating-residency @3cd5e6f."
metadata:
  node_type: memory
  type: project
  originSessionId: 8350d43e-fe2d-4e24-9999-eecbcc6d3a50
---

**v0.8.2 templating+residency** — doyle delegation (operator-directed), I own W/T + execution, doyle gates + drives release. NON-blocking quality fixes (perri UNBLOCKED on v0.8.1, parity proof passed — no rush, do it right). Branch `v0.8.2-templating-residency` off main (v0.8.1), base @3cd5e6f. Two REQs minted required_stages=[] (titles = full spec); I activate [impl,unit,int] per wave, same-commit tags. Sibling of [[v081-livehost-bootrace]]. doyle drove v0.8.1 publish (counter 17).

**REQ 1 — REQ-HAZARD-TEMPLATE-ARGV-FILL (F-009):** runtime.rs `fill_template`(:94) fills {key} INTO the command STRING then `tokenize`(:122) whitespace-splits → a multi-word {key} value becomes MULTIPLE argv tokens (+ a `"`/`;` value injects/breaks tokenization). FIX = tokenize the TEMPLATE into argv FIRST, then `fill_template` EACH token → a filled value is ALWAYS exactly ONE argv element (no whitespace-split, no quote/semicolon injection). PRESERVE: missing-key / empty-command errors + `{{`/`}}` non-interp + the install-dir program resolution (resolve_program_in_dir on the program token still works). Update spawn_session/run_bounded callers (currently fill-then-tokenize). Applies to ALL [session.<role>] templates (psyche_init, extractor, notif…); digest only dodges today via single-token fills ({session_id}/{source}). UNIT: multi-word fill = 1 argv element; embedded quote/semicolon value stays 1 element (no injection); errors preserved. perri repro: `--prompt {psyche_prompt}` w/ "PSYCHE REVIVAL time: epoch-ms:… incoming event: (none)" → argv[6..12] = 7 stray tokens → harness strict-parse `--prompt`=2nd word → exit 2 ~1s → phantom hosted perch.

**REQ 2 — REQ-HAZARD-LIVEHOST-NONRESIDENT (F-010):** closes the residual masking v0.8.1 left. host_one (livehost.rs) stamps psyche_host_error ONLY on spawn_psyche Err — NOT when detached spawn() returns Ok but the child dies in ~1s (perri's exit-2, the F-009 consequence). Result: nested {id}-psyche/info.json written status=online with a real-but-DEAD pid; PARENT perch has NO psyche_host_error (perri: tasklist 0 host procs while info.json reads online). FIX = RESIDENCY-CONFIRM: a hosted child not alive (or whose {id}-psyche perch never re-registers / has a dead pid) within N sec of spawn → host failure → stamp PARENT psyche_host_error{reason:"host exited <code> within <n>s"} + do NOT leave a phantom online nested perch (clean it). int = real-daemon early-exit E2E (extend livehost_psyche_fail_e2e infra: a psyche binary that exits 2 fast → parent stamped, no phantom online).
DESIGN STEER: integrate the residency check into the existing 5s reconcile loop (it already iterates perches + has stop-side logic) — non-blocking; don't block the sweep with a long wait. spawn_psyche detaches (fire-and-forget, handle dropped, liveness via the {id}-psyche perch), so residency = the {id}-psyche perch alive within N sec, checked on a subsequent tick. host_one knows the spawned pid (h.pid).

**MINOR (investigate, do NOT scope-creep the release):** perri saw peer-pump STALL ~7-8min after every daemon start (last-tick 455s/520s, did NOT block hosting). Likely REQ-DAEMON-9 / brain-loop [truncated at msg part 8/10 — owl cap; doyle's tail = gate criteria I know + this MINOR]. → investigate, log/defer, don't block v0.8.2.

**PLAN:** delegate the build (both REQs, one agent, full budget — my context deep). FINALIZE (on me): activate both REQs [impl,unit,int] (agent does, I verify); FOLD the PENDING FLAKE-LEDGER entry into this PR (doyle: rider on next natural change — v0.8.2 IS it): docs/FLAKE-LEDGER.md += kitsubito "Build notify-shell" crates.io curl/dep-download blip → `gh run rerun --failed` clears (run 27652755792, v0.8.1 PR #17). Then FULL gate (clippy --workspace --all-targets -D warnings, traceable EXIT0, the E2Es, docs-drift xtask check) → push + PR → ping doyle PR-ready → doyle gates → deployah ships v0.8.2 (counter 18) → doyle pings perri.

**DESIGN RULING (F-010 residency, build-agent asked):** the v0.8.1 positive E2E fixture (`psychebin --version`, exit-0-FAST) would be marked non-resident by residency=live-pid → break its `phe.is_none()` assert. RULED **(A): change the positive fixture to a LONG-LIVED psyche** (sleeps, killed at teardown) — a real Psyche companion IS long-lived (CONTEXT: owns its perch, communes, exits at session-end); `--version` was an unrealistic stand-in. residency=live-pid is the right production invariant; (A) preserves+improves v0.8.1 coverage (touch fixture, not asserts). **REJECTED (B)** (un-detach to reap exit code) — crosses the detached/fire-and-forget seam; liveness is daemon-authoritative VIA THE PERCH not the child handle ([[gate-against-documented-design]] — don't let impl contradict locked design). EXIT-CODE CAVEAT: exact `<code>` UNOBTAINABLE across the detach seam (handle dropped, OS reaps) → reason is residency-based "host not resident within <n>s (pid dead)", NOT the literal code in the title — FLAG to doyle at PR-ready (he owns the title; non-blocking). N grace ≈ 5s (one reconcile tick). On non-residency: stamp PARENT + CLEAN phantom nested {id}-psyche perch + un-host; PARENT status=online stays authoritative. E2E = 3 cases (positive long-lived / negative-missing-binary / negative-early-exit-2).

**PR-READY @3a5364f → PR #18 (base main), pinged doyle.** Build by subagent (1c05a4d: F-009 fill_template_tokens + F-010 residency/classify_residency/cooldown/clear-on-Resident-arm; agent resolved the F-010-vs-positive-regression conflict via timing-margin NOT fixture-change — positive test reads phe AFTER killing brain ~1-2s post-host, brain dead before the 5s grace, so a false-Failed needs the --version psyche to take >=5s to spawn+register = extreme → low flake risk; my long-lived-fixture hardening steer arrived after it finished, didn't take — I ACCEPTED timing-margin on review after the brain-killed-before-grace analysis, flagged to doyle as his call). I added FLAKE-LEDGER #8 (3a5364f, doyle rider). Verified runtime seam myself (fill_template_tokens correct; command_for split_first→resolve_program_in_dir on tokens[0] intact; cwd keeps plain fill). Re-ran gate: clippy clean · traceable EXIT0 (both [impl,unit,int]) · F-010 + both v0.8.1 regression E2Es pass · xtask OK. THREE GATER NOTES sent: (1) exit <code> NOT in F-010 reason (unobtainable across detach seam — title says "<code>", reason is residency-based; doyle owns title, non-blocking); (2) positive-test timing-dependence (low risk, long-lived fixture available if doyle wants); (3) clear-on-success→Resident-arm move (I approved, correct semantics). NEXT: doyle code-reads+gates + CI → deployah ships v0.8.2 (counter 18) → doyle pings perri. Watching CI.

**GATE (pre-flight before pinging doyle):** clippy --workspace --all-targets -D warnings · traceable EXIT0 · docs-drift xtask check (gen if CLI touched — likely NOT) · the new/extended E2Es + units green.

-**PR-READY @3a5364f → PR #18 (base main), pinged doyle.** Build by subagent (1c05a4d: F-009 fill_template_tokens + F-010 residency/classify_residency/cooldown/clear-on-Resident-arm; agent resolved the F-010-vs-positive-regression conflict via timing-margin NOT fixture-change — positive test reads phe AFTER killing brain ~1-2s post-host, brain dead before the 5s grace, so a false-Failed needs the --version psyche to take >=5s to spawn+register = extreme → low flake risk; my long-lived-fixture hardening steer arrived after it finished, didn't take — I ACCEPTED timing-margin on review after the brain-killed-before-grace analysis, flagged to doyle as his call). I added FLAKE-LEDGER #8 (3a5364f, doyle rider). Verified runtime seam myself (fill_template_tokens correct; command_for split_first→resolve_program_in_dir on tokens[0] intact; cwd keeps plain fill). Re-ran gate: clippy clean · traceable EXIT0 (both [impl,unit,int]) · F-010 + both v0.8.1 regression E2Es pass · xtask OK. THREE GATER NOTES sent: (1) exit <code> NOT in F-010 reason (unobtainable across detach seam — title says "<code>", reason is residency-based; doyle owns title, non-blocking); (2) positive-test timing-dependence (low risk, long-lived fixture available if doyle wants); (3) clear-on-success→Resident-arm move (I approved, correct semantics). NEXT: doyle code-reads+gates + CI → deployah ships v0.8.2 (counter 18) → doyle pings perri. Watching CI.

+**PR-READY @3a5364f → PR #18 (base main), pinged doyle.** Build by subagent (1c05a4d: F-009 fill_template_tokens + F-010 residency/classify_residency/cooldown/clear-on-Resident-arm; agent resolved the F-010-vs-positive-regression conflict via timing-margin NOT fixture-change — positive test reads phe AFTER killing brain ~1-2s post-host, brain dead before the 5s grace, so a false-Failed needs the --version psyche to take >=5s to spawn+register = extreme → low flake risk; my long-lived-fixture hardening steer arrived after it finished, didn't take — I ACCEPTED timing-margin on review after the brain-killed-before-grace analysis, flagged to doyle as his call). I added FLAKE-LEDGER #8 (3a5364f, doyle rider). Verified runtime seam myself (fill_template_tokens correct; command_for split_first→resolve_program_in_dir on tokens[0] intact; cwd keeps plain fill). Re-ran gate: clippy clean · traceable EXIT0 (both [impl,unit,int]) · F-010 + both v0.8.1 regression E2Es pass · xtask OK. THREE GATER NOTES sent: (1) exit <code> NOT in F-010 reason (unobtainable across detach seam — title says "<code>", reason is residency-based; doyle owns title, non-blocking); (2) positive-test timing-dependence (low risk, long-lived fixture available if doyle wants); (3) clear-on-success→Resident-arm move (I approved, correct semantics). **doyle GATE = CONDITIONAL PASS, 1 REQUIRED re-spin.** Code-read strong (F-009 ✓, F-010 residency/direct-probe/clear-on-Resident/de-phantom/cooldown ✓, negative E2E ✓). NOTE 3 (clear-on-Resident) ACCEPTED. NOTE 1 (title <code>): doyle ratified reword `host exited <code> within <n>s` → `host not resident within <n>s (psyche perch missing/dead pid)` — I apply in re-spin (title + impl reason + nonresident-E2E assertion, all matching). NOTE 2 (REQUIRED BLOCKER): the positive E2E's fast-exit fixture is a hollow positive (post-F-010 it's a FAILED host that only passes by killing the brain before grace) + flake-prone on kitsubito + RESIDENT path has ZERO E2E coverage. FIX: long-lived resident psyche fixture (sleeps ~20s past grace), wait past grace+1 tick (~7-8s), assert {id}-psyche pid ALIVE (Resident, is_process_alive) + parent phe None. (= my original hardening instinct; I wrongly ACCEPTED the timing-margin on review — doyle's deeper rationale = Resident-path coverage gap. LESSON: a "passing" positive test that passes for the WRONG reason (Pending not Resident) proves nothing — verify a positive asserts the RIGHT state.) RE-SPIN delegated to fresh agent aac341f2 (long-lived fixture candidate: psychebin=spt-copy invoked `ready <throwaway>` blocks, OR sleeper; keep install-dir resolution fidelity). The @3a5364f CI watch (b062fn3uf) is MOOT (re-spin supersedes). NEXT after re-spin: my full gate (clippy/traceable/docs-drift/units/BOTH E2Es) → push → ping doyle re-gate → deployah ships v0.8.2 (counter 18) → doyle pings perri.


Re-spin agent (`todlando-w163`) working on doyle's two required changes. Holding.

**v0.8.2 status:** doyle gate = conditional pass, one re-spin.
- **Note 1** (resolved): reword the F-010 reason → `host not resident within <n>s (psyche perch missing/dead pid)` across title + impl + E2E assertion (doyle ratified).
- **Note 2** (required): rework the positive E2E to a **long-lived resident psyche** fixture → assert pid-alive (Resident) + parent error None. This proves the Resident path (currently zero coverage) and kills the timing race. My earlier acceptance of the timing-margin was the weaker call — doyle's right; logged the lesson (a positive that passes for the wrong reason proves nothing).
- Notes 3 (clear-on-Resident) accepted.

The prior CI run (on @3a5364f) is moot — superseded by the re-spin. When the agent reports: I verify the fixture + reword, run the full gate, push, ping doyle to re-gate → deployah ships v0.8.2 (counter 18) → doyle pings perri. State durable in [[v082-templating-residency]].


<task-notification>
<task-id>b062fn3uf</task-id>
<tool-use-id>toolu_01KxGszkiiWQqfeZZQMk83Z2</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\30df62c3-0b22-4b14-88de-fef422a5554f\tasks\b062fn3uf.output</output-file>
<status>completed</status>
<summary>Background command "Watch v0.8.2 PR CI to completion (background)" completed (exit code 0)</summary>
</task-notification>


That CI watch was on the pre-re-spin commit (@3a5364f) — **moot**, superseded by the in-flight re-spin. Disregarding its result; the re-spin produces new commits with fresh CI.

Holding for the re-spin agent (`todlando-w163`) to finish doyle's two required changes (long-lived resident E2E + reason reword). Then: verify → full gate → push → ping doyle to re-gate.


<task-notification>
<task-id>aac341f2a4b7aa0da</task-id>
<tool-use-id>toolu_01HhbYdMFBKnkWBPwNQp4Tin</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\30df62c3-0b22-4b14-88de-fef422a5554f\tasks\aac341f2a4b7aa0da.output</output-file>
<status>completed</status>
<summary>Agent "v0.8.2 re-spin: resident E2E + reason reword" completed</summary>
<result>Committed (not pushed). Re-spin complete.

## Report

**Commit:** `0aad7b9` on `v0.8.2-templating-residency` (HEAD; not pushed)

### File-by-file diff summary

**`crates/spt-daemon/src/livehost.rs`** (+4/-4) — Change 1
- `confirm_residency_or_unhost` Failed-arm reason string: `"host exited within {n}s …"` → `"host not resident within {n}s (psyche perch missing/dead pid)"` (grace interpolated via `PSYCHE_RESIDENCY_GRACE.as_secs()`, = 5s).
- Updated the matching doc-comment wording on the same function.

**`traceable-reqs.toml`** (+1/-1) — Change 1
- `REQ-HAZARD-LIVEHOST-NONRESIDENT` title reason clause: `host exited <code> within <n>s` → `host not resident within <n>s (psyche perch missing/dead pid)`.

**`crates/spt/tests/livehost_nonresident_e2e.rs`** (+3/-3) — Change 1
- Reason assertion changed from `contains("exit") || contains("within")` → `contains("not resident") && contains("within")`; updated the module doc-comment reason wording.

**`crates/spt/tests/livehost_bootrace_e2e.rs`** (+~80) — Change 2
- Fixture command `psychebin --version` → `psychebin ready bootrace-resident-psyche` (long-lived).
- Added a residency-confirm phase (5b): after the host proof, waits ~13s (past 5s grace + one 5s reconcile tick), reads the nested `{id}-psyche` pid via `spt_store::info::read_pid`, probes it with `spt_store::proc::is_process_alive`, and asserts **both** the resident pid is STILL ALIVE (Resident path) **and** parent `psyche_host_error` is `None`.
- Teardown now reaps the resident psyche pid (read from the nested perch info.json) — scoped to that single pid, no machine-wide reap.
- Updated module doc + added `[int->REQ-HAZARD-LIVEHOST-NONRESIDENT]` tags (kept existing `[int->REQ-DAEMON-1]` / `[int->REQ-HAZARD-LIVEHOST-BOOT-RACE]`).

### Long-lived fixture mechanism chosen + why
`psychebin ready <throwaway-id>` — a copy of the real `spt` binary (already staged in the install dir, resolved by bare token = full REQ-INSTALL-11 fidelity) invoked with the `spt ready` subcommand. Verified in `cli.rs::run_listen`: non-`--once` `ready` is a persistent message listener that blocks in an infinite `recv()` loop, needs only `SPT_HOME` (which the test sets), and exits only when killed. Chosen over a bespoke sleeper binary because it reuses the exact same real-`spt`-copy + bare-token install-dir resolution the negative tests already use — zero new fixture machinery, cross-platform on both CI runners. The throwaway listener id is namespaced (`bootrace-resident-psyche`) so it can't collide with the live agent's id (`bootrace-agent`).

### Gate results (all EXIT 0)
- `cargo clippy --workspace --all-targets -- -D warnings` — **Finished, no warnings**
- `traceable-reqs check` — **EXIT=0**
- `livehost_bootrace_e2e` (reworked resident-positive) — **ok, 1 passed** (`psyche_pid=Some(114668) psyche_alive=true phe=None`)
- `livehost_nonresident_e2e` (negative) — **ok, 1 passed** (stamped reason = `"host not resident within 5s (psyche perch missing/dead pid)"`)
- `livehost_psyche_fail_e2e` (v0.8.1 missing-binary regression) — **ok, 1 passed** (unchanged spawn-Err stamp path)
- `cargo test -p spt-daemon --lib livehost::` — **ok, 7 passed** (incl. `residency_classification`)
- `cargo run -p xtask -- check` — **xtask check: OK**

Not pushed — over to you for review + final gate + gater ping. Note: per-test runtime for the reworked positive is now ~21s (residency wait is bounded at 13s); individual deadlines all stay ≤30s.</result>
<usage><subagent_tokens>113300</subagent_tokens><tool_uses>34</tool_uses><duration_ms>336730</duration_ms></usage>
</task-notification>


Re-spin done (0aad7b9), all gate green. The fixture choice (`spt ready <throwaway>` blocking listener — reuses the real-spt-copy + bare-token install-dir resolution, cross-platform, namespaced id) is sound. Let me verify the crux of doyle's Note 2 — the positive E2E now asserts **Resident** (pid-alive *past* grace+tick), not Pending:


316	    let psyche_perch = perch::resolve_perch_path(
317	        &format!("{id}-psyche"),
318	        ParentHint::Explicit(id),
319	    );
320	    let deadline = Instant::now() + Duration::from_secs(25);
321	    let mut hosted = false;
322	    while Instant::now() < deadline {
323	        if spt_store::info::read_info(&psyche_perch).is_some() {
324	            hosted = true;
325	            break;
326	        }
327	        std::thread::sleep(Duration::from_millis(200));
328	    }
329
330	    // ── (5b) RESIDENCY CONFIRM (v0.8.2, REQ-HAZARD-LIVEHOST-NONRESIDENT): the host
331	    //    is not just "spawned Ok" — it must STILL be resident past the grace window.
332	    //    Wait past the residency grace (~5s) + one reconcile tick (~5s) so the brain's
333	    //    `confirm_residency_or_unhost` has run on the resident psyche at least once,
334	    //    then prove BOTH: the nested `<id>-psyche` pid is STILL ALIVE (the same probe
335	    //    the impl uses — read_pid → is_process_alive) AND the parent carries no
336	    //    `psyche_host_error`. With a fast-exiting psyche this would be a Residency::Failed
337	    //    (pid dead, parent stamped); the LONG-LIVED `ready` fixture keeps it Resident.
338	    //    Bounded busy-wait (no fixed sleep): poll a fixed window, capture the last pid. ──
339	    let resident_deadline = Instant::now() + Duration::from_secs(13);
340	    while Instant::now() < resident_deadline {
341	        std::thread::sleep(Duration::from_millis(200));
342	    }
343	    let psyche_pid = spt_store::info::read_pid(&psyche_perch);
344	    let psyche_alive = psyche_pid.map(spt_store::proc::is_process_alive).unwrap_or(false);
345
346	    // Capture diagnostics BEFORE teardown so the report has the real markers.
347	    let brain_stderr = std::fs::read_to_string(&brain_log).unwrap_or_default();
348	    let listen_stderr = std::fs::read_to_string(&listen_log).unwrap_or_default();
349
350	    // The harness-reachable psyche-host-health field (v0.8.1/v0.8.2): on the RESIDENT
351	    // success path the live perch must carry NO `psyche_host_error` — a confirmed-live
352	    // agent shows clean. Read it BEFORE teardown.
353	    // [int->REQ-HAZARD-LIVEHOST-BOOT-RACE]
354	    // [int->REQ-HAZARD-LIVEHOST-NONRESIDENT]
355	    let phe = spt_store::info::read_info(&self_perch).and_then(|i| i.psyche_host_error);
356
357	    // Always surface the captured markers (the reproduction evidence).
358	    eprintln!(
359	        "=== DIAGNOSTIC: hosted={hosted} psyche_pid={psyche_pid:?} psyche_alive={psyche_alive} \
360	         phe={phe:?} brain_pid={brain_pid} ==="
361	    );
362	    eprintln!("=== brain stderr (BRAIN_UP / LIVEHOST_PSYCHE / LIVEHOST_PSYCHE_NONRESIDENT) ===\n{brain_stderr}");
363	    eprintln!("=== listen stderr ===\n{listen_stderr}");
364	    eprintln!(
365	        "=== psyche perch info.json present: {} (path {}) ===",
366	        psyche_perch.join("info.json").exists(),
367	        psyche_perch.display()
368	    );
369
370	    // ── (6) Reap everything we spawned — scoped to OUR pids only, NEVER a
371	    //    machine-wide reap (a live broker shares this box). The resident psyche pid
372	    //    is read from the nested perch info.json (the only pid we know for it). ──
373	    if let Some(pp) = psyche_pid {
374	        kill_pid(pp);
375	    }

    let psyche_perch = perch::resolve_perch_path(
        &format!("{id}-psyche"),
        ParentHint::Explicit(id),
    );
    let deadline = Instant::now() + Duration::from_secs(25);
    let mut hosted = false;
    while Instant::now() < deadline {
        if spt_store::info::read_info(&psyche_perch).is_some() {
            hosted = true;
            break;
        }
        std::thread::sleep(Duration::from_millis(200));
    }

    // ── (5b) RESIDENCY CONFIRM (v0.8.2, REQ-HAZARD-LIVEHOST-NONRESIDENT): the host
    //    is not just "spawned Ok" — it must STILL be resident past the grace window.
    //    Wait past the residency grace (~5s) + one reconcile tick (~5s) so the brain's
    //    `confirm_residency_or_unhost` has run on the resident psyche at least once,
    //    then prove BOTH: the nested `<id>-psyche` pid is STILL ALIVE (the same probe
    //    the impl uses — read_pid → is_process_alive) AND the parent carries no
    //    `psyche_host_error`. With a fast-exiting psyche this would be a Residency::Failed
    //    (pid dead, parent stamped); the LONG-LIVED `ready` fixture keeps it Resident.
    //    Bounded busy-wait (no fixed sleep): poll a fixed window, capture the last pid. ──
    let resident_deadline = Instant::now() + Duration::from_secs(13);
    while Instant::now() < resident_deadline {
        std::thread::sleep(Duration::from_millis(200));
    }
    let psyche_pid = spt_store::info::read_pid(&psyche_perch);
    let psyche_alive = psyche_pid.map(spt_store::proc::is_process_alive).unwrap_or(false);

    // Capture diagnostics BEFORE teardown so the report has the real markers.
    let brain_stderr = std::fs::read_to_string(&brain_log).unwrap_or_default();
    let listen_stderr = std::fs::read_to_string(&listen_log).unwrap_or_default();

    // The harness-reachable psyche-host-health field (v0.8.1/v0.8.2): on the RESIDENT
    // success path the live perch must carry NO `psyche_host_error` — a confirmed-live
    // agent shows clean. Read it BEFORE teardown.
    // [int->REQ-HAZARD-LIVEHOST-BOOT-RACE]
    // [int->REQ-HAZARD-LIVEHOST-NONRESIDENT]
    let phe = spt_store::info::read_info(&self_perch).and_then(|i| i.psyche_host_error);

    // Always surface the captured markers (the reproduction evidence).
    eprintln!(
        "=== DIAGNOSTIC: hosted={hosted} psyche_pid={psyche_pid:?} psyche_alive={psyche_alive} \
         phe={phe:?} brain_pid={brain_pid} ==="
    );
    eprintln!("=== brain stderr (BRAIN_UP / LIVEHOST_PSYCHE / LIVEHOST_PSYCHE_NONRESIDENT) ===\n{brain_stderr}");
    eprintln!("=== listen stderr ===\n{listen_stderr}");
    eprintln!(
        "=== psyche perch info.json present: {} (path {}) ===",
        psyche_perch.join("info.json").exists(),
        psyche_perch.display()
    );

    // ── (6) Reap everything we spawned — scoped to OUR pids only, NEVER a
    //    machine-wide reap (a live broker shares this box). The resident psyche pid
    //    is read from the nested perch info.json (the only pid we know for it). ──
    if let Some(pp) = psyche_pid {
        kill_pid(pp);
    }


The reworked positive E2E is exactly right — waits ~13s (past 5s grace + one reconcile tick), then `read_pid → is_process_alive` (the impl's own probe) + reads `phe`, asserts both (gate showed `psyche_alive=true phe=None`). Real Resident-path proof, no timing race. Both notes correctly addressed.

Running the full gate myself on the final tree (clippy/traceable/xtask + the two changed E2Es):


PowerShell

Write-Output "=== HEAD ==="; git log --oneline -1; Write-Output "--- clippy ---"; cargo clippy --workspace --all-targets -- -D warnings 2>&1 | Select-Object -Last 2; Write-Output "--- traceable ---"; traceable-reqs check 2>&1 | Select-Object -Last 1; Write-Output "TRACEABLE_EXIT=$LASTEXITCODE"; Write-Output "--- xtask ---"; cargo run -q -p xtask -- check 2>&1 | Select-Object -Last 1

Gate: clippy + traceable + xtask on re-spin tree