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

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

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

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

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

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

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

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