Agent

W5 finalize tests — reframe + unit

Prompt:
SendMessage to: a27bb87b41d0a4f93

W5 design has CONVERGED on the repro you and the sibling agent produced. Decision (repro-grounded, supersedes the original plan's Gap-A approach): W5 ships **Gap B only** (no-session driven_by self-heal — already landed in livehost.rs). The live-session Gap-A `controller_by==None` reconcile clear is REJECTED: your own repro proved controller_by==None is ambiguous (dispatch_spawn pre-attaches the local spawner with by=None) AND the real production case (A2 — idle wedged REMOTE controller) keeps controller_by==Some, so a reconcile read can't see it. A2 is now its own SEED req `REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT` (broker-side eviction, deferred). I've updated traceable-reqs.toml (REQ-HAZARD-DRIVEN-BY-SELFHEAL now `["impl","unit","int"]`), msg.rs (honest controller_by doc), and KNOWN-HAZARDS 7.15. DO NOT edit those — they're final.

Two test tasks (yours — keep the shared tree compilable; the sibling engineer is at rest, no collision):

### Task 1 — reframe the Gap-A test in `crates/spt-daemon/tests/driven_by_selfheal.rs`
`gap_a_live_session_lost_controller_reconcile_leaves_driven_by_latched` currently carries a `TODO(W5-GREEN)` saying "when the reconcile clears driven_by for controller_by==None, flip this to None." That future is now REJECTED. Reframe it (keep the test GREEN, keep the valuable findings/eprintln banners and the broker-self-heal-on-observed-disconnect finding):
- The assertion stays `driven_by_after == Some(node)` BUT the framing becomes: "CORRECT, NOT a latch-to-fix — the reconcile MUST NOT clear driven_by for a live-session perch off controller_by==None (it is ambiguous with a live LOCAL controller; would false-clear). The genuine production residual is the idle wedged-REMOTE case, which controller_by can't detect — see `inject_control_wedge.rs` w5_a2 and `REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT`."
- Replace the `TODO(W5-GREEN): flip to None` comment block with a `// DEFERRED (REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT):` note explaining the broker-side eviction is the future fix, NOT a reconcile controller_by clear.
- Rename the test if its name implies a bug-to-fix; e.g. `gap_a_live_session_controller_by_is_ambiguous_so_reconcile_must_not_clear` (your call — make the name state the correct invariant, not a pending RED). Update the file-level `//!` doc header accordingly (it currently says "the FIX does not exist yet" / "RED" for Gap A — make it say Gap A is characterization proving controller_by==None is unusable + A2 deferred; Gap B is the shipped GREEN self-heal).
- Keep the `// [int->REQ-HAZARD-DRIVEN-BY-SELFHEAL]` tag.

### Task 2 — add the Gap-B `[unit]` test (in-tree unit, NOT a real broker)
In `crates/spt-daemon/src/livehost.rs`'s `#[cfg(test)] mod tests`, EXTEND the existing `pull_liveness_marks_sessionless_spt_hosted_offline_only` unit test (or add a focused sibling unit test next to it) to cover the Gap-B driven_by clear:
- Seed `driven_by=Some("node")` (via `spt_store::info::set_driven_by`) on the sessionless `dead` perch AND on the session-backed `alive` perch (and optionally `relay`).
- After `reconcile_hosted_liveness`, assert: `dead`'s driven_by is now `None` (cleared alongside offline), while `alive`'s driven_by is UNTOUCHED `Some("node")` (it has a live session — the reconcile must not touch live perches) and `relay`'s is untouched (exempt).
- Tag `// [unit->REQ-HAZARD-DRIVEN-BY-SELFHEAL]` on the real assertion.

### Gates (run, report results)
- `cargo nextest run --test driven_by_selfheal` (your reframed file) — both tests GREEN.
- The new/extended unit: `cargo test -p spt-daemon --lib livehost` (or nextest equivalent) — GREEN.
- Do NOT run the full workspace clippy/build or `traceable-reqs check` — I run the final gates. ≤4 iterations, background, no Monitor, do not touch the operator binary.

Report: the final test names, the unit assertions landed, and the green run output tails.


<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: a04930f7ad1a63f96 (internal ID - do not mention to user. Use SendMessage with to: 'a04930f7ad1a63f96' 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\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\a04930f7ad1a63f96.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.

W5 finalize tests — reframe + unit

Prompt:
SendMessage to: a27bb87b41d0a4f93

W5 design has CONVERGED on the repro you and the sibling agent produced. Decision (repro-grounded, supersedes the original plan's Gap-A approach): W5 ships **Gap B only** (no-session driven_by self-heal — already landed in livehost.rs). The live-session Gap-A `controller_by==None` reconcile clear is REJECTED: your own repro proved controller_by==None is ambiguous (dispatch_spawn pre-attaches the local spawner with by=None) AND the real production case (A2 — idle wedged REMOTE controller) keeps controller_by==Some, so a reconcile read can't see it. A2 is now its own SEED req `REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT` (broker-side eviction, deferred). I've updated traceable-reqs.toml (REQ-HAZARD-DRIVEN-BY-SELFHEAL now `["impl","unit","int"]`), msg.rs (honest controller_by doc), and KNOWN-HAZARDS 7.15. DO NOT edit those — they're final.

Two test tasks (yours — keep the shared tree compilable; the sibling engineer is at rest, no collision):

### Task 1 — reframe the Gap-A test in `crates/spt-daemon/tests/driven_by_selfheal.rs`
`gap_a_live_session_lost_controller_reconcile_leaves_driven_by_latched` currently carries a `TODO(W5-GREEN)` saying "when the reconcile clears driven_by for controller_by==None, flip this to None." That future is now REJECTED. Reframe it (keep the test GREEN, keep the valuable findings/eprintln banners and the broker-self-heal-on-observed-disconnect finding):
- The assertion stays `driven_by_after == Some(node)` BUT the framing becomes: "CORRECT, NOT a latch-to-fix — the reconcile MUST NOT clear driven_by for a live-session perch off controller_by==None (it is ambiguous with a live LOCAL controller; would false-clear). The genuine production residual is the idle wedged-REMOTE case, which controller_by can't detect — see `inject_control_wedge.rs` w5_a2 and `REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT`."
- Replace the `TODO(W5-GREEN): flip to None` comment block with a `// DEFERRED (REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT):` note explaining the broker-side eviction is the future fix, NOT a reconcile controller_by clear.
- Rename the test if its name implies a bug-to-fix; e.g. `gap_a_live_session_controller_by_is_ambiguous_so_reconcile_must_not_clear` (your call — make the name state the correct invariant, not a pending RED). Update the file-level `//!` doc header accordingly (it currently says "the FIX does not exist yet" / "RED" for Gap A — make it say Gap A is characterization proving controller_by==None is unusable + A2 deferred; Gap B is the shipped GREEN self-heal).
- Keep the `// [int->REQ-HAZARD-DRIVEN-BY-SELFHEAL]` tag.

### Task 2 — add the Gap-B `[unit]` test (in-tree unit, NOT a real broker)
In `crates/spt-daemon/src/livehost.rs`'s `#[cfg(test)] mod tests`, EXTEND the existing `pull_liveness_marks_sessionless_spt_hosted_offline_only` unit test (or add a focused sibling unit test next to it) to cover the Gap-B driven_by clear:
- Seed `driven_by=Some("node")` (via `spt_store::info::set_driven_by`) on the sessionless `dead` perch AND on the session-backed `alive` perch (and optionally `relay`).
- After `reconcile_hosted_liveness`, assert: `dead`'s driven_by is now `None` (cleared alongside offline), while `alive`'s driven_by is UNTOUCHED `Some("node")` (it has a live session — the reconcile must not touch live perches) and `relay`'s is untouched (exempt).
- Tag `// [unit->REQ-HAZARD-DRIVEN-BY-SELFHEAL]` on the real assertion.

### Gates (run, report results)
- `cargo nextest run --test driven_by_selfheal` (your reframed file) — both tests GREEN.
- The new/extended unit: `cargo test -p spt-daemon --lib livehost` (or nextest equivalent) — GREEN.
- Do NOT run the full workspace clippy/build or `traceable-reqs check` — I run the final gates. ≤4 iterations, background, no Monitor, do not touch the operator binary.

Report: the final test names, the unit assertions landed, and the green run output tails.


Delegated the test finalization. Summary of where W5 stands:

**Decision (repro-grounded, supersedes plan's Gap-A approach):**
- **Gap B (no-session `driven_by` self-heal): landed** — `reconcile_hosted_liveness` clears stale `driven_by` when it offlines a sessionless controllable perch. Race-free (no session ⇒ no concurrent broker re-stamp).
- **Gap A `controller_by==None` reconcile clear: REJECTED.** Repro proved it's ambiguous (`dispatch_spawn` pre-attaches local spawner `by=None`) and the real production case (A2: idle wedged *remote* controller) keeps `controller_by==Some` — invisible to a brain reconcile. A clean disconnect already self-heals via `detach_if`; W1 bounds active wedges.
- **A2 deferred** → new SEED `REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT` (broker-side eviction; doyle's W6/pump boundary).

**Landed this turn (mine):**
- `msg.rs` — additive `SessionInfo.controller_by` (honest doc: `None` ambiguous, reserved for A2/characterization)
- `broker.rs` — populate `controller_by` in `KIND_SESSIONS`
- `livehost.rs` — Gap B `driven_by` clear
- `traceable-reqs.toml` — `REQ-HAZARD-DRIVEN-BY-SELFHEAL` → `[impl,unit,int]` + minted A2 SEED
- `KNOWN-HAZARDS.md` — 7.15 + checklist row

**In flight (engineer a04930f7, background):** reframe Gap-A test (no GREEN-flip; A2-deferred), add Gap-B `[unit]` test. Two engineers (w173/w174) earlier delivered the real-broker A1/A2 characterization + repro.

Once the engineer rests I run final gates: workspace clippy (CI form), `traceable-reqs check`, real `cargo build -p spt --bin spt`, nextest the int files — then commit + commune the descope to doyle. Waiting on the engineer notification.


<task-notification>
<task-id>bq612jhsw</task-id>
<tool-use-id>toolu_01T3dFk678A64igHkRnAcwZt</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\bq612jhsw.output</output-file>
<status>completed</status>
<summary>Background command "Run livehost lib unit tests" completed (exit code 0)</summary>
</task-notification>


<task-notification>
<task-id>blvcq3ig8</task-id>
<tool-use-id>toolu_01Skfi9KAaNrEMhw7tU8phzc</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\blvcq3ig8.output</output-file>
<status>completed</status>
<summary>Background command "Run driven_by_selfheal integration tests via nextest" completed (exit code 0)</summary>
</task-notification>


<task-notification>
<task-id>a04930f7ad1a63f96</task-id>
<tool-use-id>toolu_01HVr1fBaGkTzf1RUyBUwV81</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\a04930f7ad1a63f96.output</output-file>
<status>completed</status>
<summary>Agent "W5 finalize tests — reframe + unit" came to rest</summary>
<note>A task-notification fires each time this agent comes to rest with no live background children of its own. The user can send it another message and resume it, so the same task-id may notify more than once.</note>
<result>Both tasks landed, both gates GREEN. Memory updated to reflect the converged decision.

## Task 1 — Gap-A int test reframed
File: `C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\tests\driven_by_selfheal.rs`

- Renamed `gap_a_live_session_lost_controller_reconcile_leaves_driven_by_latched` → `gap_a_live_session_controller_by_is_ambiguous_so_reconcile_must_not_clear`.
- Assertion unchanged (`driven_by_after == Some(node)`) but reframed as a CORRECT invariant, not a latch-to-fix.
- Replaced the `TODO(W5-GREEN): flip to None` block with a `DEFERRED (REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT):` note — future fix is broker-side eviction, not a reconcile `controller_by` clear; points to `inject_control_wedge.rs` `w5_a2`.
- Rewrote the `//!` header: Gap B = shipped/GREEN; Gap A = characterization proving `controller_by==None` is unusable + A2 deferred. Updated the inline RECONCILE-RESULT banner to match.
- Kept all eprintln findings (broker self-heal-on-observed-disconnect, item-3 ambiguity) and the `// [int->REQ-HAZARD-DRIVEN-BY-SELFHEAL]` tag.

## Task 2 — Gap-B `[unit]` test
File: `C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\src\livehost.rs`

Extended `pull_liveness_marks_sessionless_spt_hosted_offline_only` (not a new sibling — kept it consolidated). Pre-stamped `driven_by=Some("PEERNODEHEX")` on `alive`, `dead`, and `relay`. Assertions landed (tagged `// [unit->REQ-HAZARD-DRIVEN-BY-SELFHEAL]`):
- `driven_by("dead") == None` — cleared alongside offline (Gap B).
- `driven_by("alive") == Some(node)` — UNTOUCHED (live session; deferred broker-side path).
- `driven_by("relay") == Some(node)` — UNTOUCHED (exempt, `controllable==Some(false)`).

## Gate results (green tails)
- `cargo nextest run -p spt-daemon --test driven_by_selfheal`: `2 tests run: 2 passed, 0 skipped` — both `gap_b_sessionless_perch_reconcile_offlines_but_leaves_driven_by_latched` and `gap_a_live_session_controller_by_is_ambiguous_so_reconcile_must_not_clear` PASS.
- `cargo test -p spt-daemon --lib livehost`: `12 passed; 0 failed` — including the extended `pull_liveness_marks_sessionless_spt_hosted_offline_only`.

As instructed, I did not run workspace clippy/build or `traceable-reqs check`, and did not touch the operator binary, msg.rs, traceable-reqs.toml, or KNOWN-HAZARDS. The shared tree compiles (both binaries built cleanly during the test runs).

One note for your final gates: the Gap-A test name no longer implies a pending RED, so if traceable-reqs or any doc references the old test name, it'll need the rename picked up — but the `[int->...]` and `[unit->...]` tags are intact on the real assertions.</result>
<usage><subagent_tokens>78017</subagent_tokens><tool_uses>23</tool_uses><duration_ms>188453</duration_ms></usage>
</task-notification>


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


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

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

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


Tests landed GREEN. Run final gates now: workspace clippy (CI form), `traceable-reqs check`, real `spt` build, nextest int files.


Bash

cargo clippy --workspace --all-targets -- -D warnings 2>&1 | tail -15

Workspace clippy CI form


    Checking spt-store v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-store)
    Checking spt-msg v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-msg)
    Checking spt-term v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-term)
    Checking spt-runtime v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-runtime)
    Checking spt-net v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-net)
    Checking spt-live v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-live)
    Checking mock-adapter v0.12.1 (C:\Users\decid\Documents\projects\spt-core\adapters\mock)
    Checking spt-daemon v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
    Checking spt v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Checking xtask v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 8.85s


Clippy clean. Now traceable check + real spt build + int nextest, in parallel where safe.


Traceability gate


       Adapter-declared digest extractor seam: a `[digest]` manifest section declaring an imperative extractor (native harness log -> the {role,text,tool,ts} contract; defaults to the [history] source files with an own-source escape hatch), `api digest-entry` push fallback, register-time validation of the section, adapter-declared presentation defaults (window depth, arg-truncation, sprint-collapse) that any consumer may override, and a `spt adapter digest-proof` author tool plus runtime skip-diagnostics (no silent drop). Reverses M9's no-manifest-seam stance; no declarative DSL.
  [OK] REQ-TERM-6  required: [impl, unit, int]  stages: -doc +impl +unit +int
       Thread-spanning digest across session boundaries: a per-endpoint session ledger (`<perch>/sessions.log`) appended at first bind and by `api boundary` on `/clear`|`/compact` session rotation, the digest enumerating the last K sessions so its rolling window bridges a boundary, and a distinctive in-timeline boundary marker (DigestEntry::Boundary). The digest follows the live-agent thread, not a single session.
  [OK] REQ-TERM-7  required: [impl, unit, int]  stages: -doc +impl +unit +int
       Two-origin digest merge: spt-owned context-injection entries (psyche_download | echo_mirror | owl_message) appended by spt to the endpoint `digest.log`, timestamp-interleaved with the adapter's extracted activity records into one ordered timeline, via a distinct context-injection record category. Data model only this milestone; GUI collapse/expand and the echo-reads-digest delta loop are deferred to the surfaces that consume them.
  [OK] REQ-UPD-1  required: [impl, unit, int]  stages: -doc +impl +unit +int
       Peer-propagated update over P2P
  [OK] REQ-UPD-2  required: [impl, unit]  stages: -doc +impl +unit -int
       All binaries signature-verified before handoff
  [OK] REQ-UPD-3  required: [impl, unit, int]  stages: -doc +impl +unit +int
       No endpoint process terminates/suspends during self-update
  [OK] REQ-UPD-4  required: [impl, unit]  stages: -doc +impl +unit -int
       Update gated on user confirmation by default; opt-in full-auto
  [OK] REQ-UPD-5  required: [impl, unit]  stages: -doc +impl +unit -int
       spt-core ripple-updates registered adapters
  [OK] REQ-UPD-6  required: [doc, impl, unit, int]  stages: +doc +impl +unit +int
       Platform-targeted update sets and debug rollout: signed multi-platform update metadata, recipient platform selection, channel-scoped monotonic counters, debug-channel opt-in via release-key overlay, local staging plus pull-based peer propagation, and maintainer-only convergence tooling (ADR-0016)
  [OK] REQ-UPD-7  required: [impl, unit]  stages: -doc +impl +unit -int
       Origin-source update bootstrap (`spt update fetch`): pull the latest signed release directly from the GitHub release origin (`SaberMage/spt-releases`) — the per-platform artifact + its `<asset>.release.json` SignedRelease metadata — and stage it through the EXISTING verify→stage pipeline (the same `plan_verified` gate: two-key signature + channel + monotonic rollback floor + SHA-256), after which the normal consent-notif / `spt update apply` flow is unchanged. Closes the peer-only-discovery gap (REQ-UPD-1): a first-in-fleet / isolated node can update with no peer to pull from. The signed-release anchor keeps the GitHub transport untrusted-but-verified.
  [OK] REQ-UPD-8  required: [impl, unit]  stages: -doc +impl +unit -int
       Platform-safe `spt update fetch` + apply platform-guard (v0.3.1 cross-OS brick fix): `spt update fetch` stages the signed multi-platform `SignedUpdateSet` (`update-set.json` + every platform artifact it names), never a platform-blind single `SignedRelease`, so local apply selects `current_platform()` and P2P re-serve lets each peer select ITS own platform. Defense-in-depth: `apply_staged` REFUSES a staged single-release artifact unless it is platform-stamped for THIS node (an unstamped pre-v0.3.2 single, or a single stamped for another OS, fail-safe refuses — the guard that alone prevents the v0.3.1 brick where a Linux ELF was applied as `spt.exe`). UX: a friendly post-apply message (`Updated spt-core to vX.Y.Z.` + changelog URL) driven by an additive `product_version` metadata field, with a release-counter fallback when absent.
  [OK] REQ-UPD-9  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       `gh_release` adapter [update] avenue (optional signing): an adapter declares `[update] avenue = "gh_release", repo = "user/repo"` (+ optional `asset`, default `adapter.spt`; + optional Ed25519 `signing_key`); spt-core's ripple compares the repo's LATEST GitHub release version against the installed adapter version and, when newer, auto-updates by fetching the release `.spt` archive (the REQ-INSTALL-9 `--release` fetch primitive) → verifies the `.spt` against `signing_key` if declared, else HTTPS+GitHub first-acquisition trust → re-extracts + re-registers the adapter root. Lets a harness adapter ship updates from its own GitHub releases with NO signing tooling or plugin coupling (removes the perri file_pull/delegated avenue blockers). Acquisition-trust mirrors `--release` + the installer first-fetch; does not alter spt-core self-update (REQ-UPD-1..8).
  [OK] REQ-WHOAMI-1  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       `spt whoami` is a thin ALIAS for `spt endpoint list` (full output: the SELF pin + the subnet roster) — the standalone bare-id command is dropped (the `id=$(spt whoami)` capture was never a real pattern: env vars don't persist between agent tool calls). The one new render: the `endpoint list` SELF pin carries the Self endpoint's authored `endpoint description` (info::read_info(...).resources) when present, inline after the liveness state. whoami stays a top-level hot-path verb (parse unchanged, REQ-MSG-9).


cargo build -p spt --bin spt 2>&1 | tail -8

Real spt binary build


   Compiling spt-daemon v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
   Compiling spt v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 12.91s


Build clean. Need traceable EXIT code + W5 req lines confirmed.


traceable-reqs check > /tmp/tr.txt 2>&1; echo "EXIT=$?"; grep -i "DRIVEN-BY" /tmp/tr.txt

Traceable exit code + driven-by reqs


EXIT=0
  [OK] REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT  required: []  stages: -doc -impl -unit -int
  [OK] REQ-HAZARD-DRIVEN-BY-SELFHEAL  required: [impl, unit, int]  stages: -doc +impl +unit +int


EXIT=0, both reqs satisfied. Run int nextest on both real-broker files (isolated per runner contract).


cargo nextest run -p spt-daemon --test driven_by_selfheal --test inject_control_wedge 2>&1 | tail -25

Int gates — driven_by_selfheal + inject_control_wedge


   Compiling spt-daemon v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 3.71s
────────────
 Nextest run ID ff30509b-c2f6-4acd-8c65-a6350eba2575 with nextest profile: default
    Starting 11 tests across 2 binaries
        PASS [   0.059s] ( 1/11) spt-daemon::driven_by_selfheal gap_b_sessionless_perch_reconcile_offlines_but_leaves_driven_by_latched
        PASS [   0.603s] ( 2/11) spt-daemon::driven_by_selfheal gap_a_live_session_controller_by_is_ambiguous_so_reconcile_must_not_clear
        PASS [   0.630s] ( 3/11) spt-daemon::inject_control_wedge w5_a1_locally_driven_session_reports_controller_by_none
        PASS [   0.740s] ( 4/11) spt-daemon::inject_control_wedge g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input
        PASS [   0.905s] ( 5/11) spt-daemon::inject_control_wedge g4_translation_child_is_reaped_no_zombie_after_session_down
        PASS [   0.932s] ( 6/11) spt-daemon::inject_control_wedge g3_content_free_ping_never_duplicates_operator_keystrokes_into_binary
        PASS [   3.248s] ( 7/11) spt-daemon::inject_control_wedge a_journaled_input_wedge_does_not_starve_a_concurrent_rc_attach
        PASS [   3.269s] ( 8/11) spt-daemon::inject_control_wedge a_backed_up_controller_does_not_wedge_the_session
        PASS [   4.282s] ( 9/11) spt-daemon::inject_control_wedge w5_a2_abandoned_remote_controller_idle_session_keeps_controller_by_some
        PASS [   6.041s] (10/11) spt-daemon::inject_control_wedge injecting_a_large_payload_into_a_stdin_ignoring_child
        PASS [  12.600s] (11/11) spt-daemon::inject_control_wedge g1_choreography_happy_path_payload_reaches_pty_and_controller_keeps_control
────────────
     Summary [  12.600s] 11 tests run: 11 passed, 0 skipped


All gates GREEN: clippy clean · traceable EXIT=0 · `spt` builds · 11/11 int pass. Commit W5.


Stage W5 changes


warning: in the working copy of 'crates/spt-daemon/tests/inject_control_wedge.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-daemon/tests/driven_by_selfheal.rs', LF will be replaced by CRLF the next time Git touches it
M  crates/spt-daemon/src/broker.rs
M  crates/spt-daemon/src/livehost.rs
M  crates/spt-daemon/src/msg.rs
A  crates/spt-daemon/tests/driven_by_selfheal.rs
M  crates/spt-daemon/tests/inject_control_wedge.rs
M  docs/KNOWN-HAZARDS.md
M  traceable-reqs.toml


git commit -F - <<'EOF'
feat(daemon): W5 — driven_by self-heal (Gap B) + A2 latch characterization (REQ-HAZARD-DRIVEN-BY-SELFHEAL)

Clear a stale driven_by ONLINE+CONTROLLED latch when the broker hosts no
session for an spt-hosted endpoint: reconcile_hosted_liveness now clears
driven_by alongside the B2 mark_offline. Race-free — no live session means no
controller to re-stamp it concurrently, so the brain may write it without
contending the broker single-writer.

Repro-first on a real broker REJECTED the plan's Gap-A approach: a reconcile
controller_by==None clear is unusable. controller_by==None is ambiguous
(dispatch_spawn pre-attaches the local spawner with by=None, so a live LOCAL
session also reads None), and the production wedged-remote case (A2) keeps
controller_by==Some on an idle session (the broker still thinks it is
controlled — invisible to a brain reconcile). A clean disconnect already
self-heals via detach_if->clear_controller; W1 bounds the active-output wedge.
The residual idle-wedged-remote leg is deferred to a follow-up SEED,
REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT (broker-side liveness eviction).

- msg.rs: additive SessionInfo.controller_by (serde-default, N-1 safe; honest
  doc — None is ambiguous, reserved for the A2 eviction + characterization)
- broker.rs: populate controller_by in the KIND_SESSIONS reply
- livehost.rs: Gap-B driven_by clear in reconcile_hosted_liveness + unit
- tests: real-broker repro (driven_by_selfheal.rs gap_b GREEN + gap_a ambiguity
  characterization) + A1/A2 mechanism characterization (inject_control_wedge.rs
  w5_a1/w5_a2)
- docs: KNOWN-HAZARDS 7.15 + checklist row
- traceable: REQ-HAZARD-DRIVEN-BY-SELFHEAL activated [impl,unit,int]; minted A2
  follow-up SEED

Co-authored by: todlando
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NVVByRoZnz85acTkVJ1uAP
EOF
git log --oneline -1

Commit W5


[v0.13.0-delivery-control edf03e0] feat(daemon): W5 — driven_by self-heal (Gap B) + A2 latch characterization (REQ-HAZARD-DRIVEN-BY-SELFHEAL)
 7 files changed, 821 insertions(+), 5 deletions(-)
 create mode 100644 crates/spt-daemon/tests/driven_by_selfheal.rs
edf03e0 feat(daemon): W5 — driven_by self-heal (Gap B) + A2 latch characterization (REQ-HAZARD-DRIVEN-BY-SELFHEAL)


Committed @edf03e0. W5 landed. Notify doyle — they assigned W5, gate at int, and own W6/perri which the deferred A2 follow-up touches.


Skill

doyle W5 driven_by self-heal LANDED @edf03e0 (v0.13.0-delivery-control), repro-first. Scoped to Gap B (no-session): reconcile_hosted_liveness now clears stale driven_by alongside the B2 mark_offline — race-free (no session => no concurrent broker re-stamp). All gates GREEN: clippy --workspace --all-targets -D warnings clean, traceable EXIT=0 (REQ-HAZARD-DRIVEN-BY-SELFHEAL [impl,unit,int]), real spt build, 11/11 nextest (driven_by_selfheal + inject_control_wedge).

KEY FINDING — the plan's Gap-A approach was REJECTED by the real-broker repro, and it touches YOUR W6/perri boundary: a reconcile controller_by==None clear is unusable. (1) controller_by==None is ambiguous — dispatch_spawn pre-attaches the local spawner with by=None, so a live LOCAL session also reads None. (2) The production wedged-remote case (A2: remote gone, broker conn stays open, session idle) keeps controller_by==Some(origin) + driven_by latched — the broker still thinks it's controlled, invisible to a brain reconcile. A clean disconnect already self-heals via detach_if->clear_controller; W1 bounds the active-output wedge. So the residual idle-wedged-remote leg needs BROKER-SIDE liveness eviction, NOT a reconcile read.

Deferred it to a new SEED: REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT (required_stages=[]). Repro already exists: inject_control_wedge.rs w5_a2. BOUNDARY CALL FOR YOU: this likely closes via the W6/pump-detach layer — a pump that propagates remote-stream death into a clean detach would heal it WITHOUT a presence probe. If W6 covers it, the SEED may fold into your work; if not, it needs broker-side presence-eviction + an ADR for partition semantics (must not false-evict a genuinely-controlled session across a transient partition). Your call on where it lands. doyle gates W5 at int — tree clean @edf03e0.


Launching skill: spt:send


Base directory for this skill: C:\Users\decid\.claude\plugins\cache\cplugs\spt\1.11.25\skills\send

# /spt:send

All commands use `$OWL` env var, auto-injected by the plugin's SessionStart hook. If commands fail with "command not found", restart the Claude Code session so the SessionStart hook re-runs.

> **Identity auto-detection:** Your identity is auto-detected from your session. Pass your ID explicitly only if auto-detection fails.

## Messaging Command Reference

Both commands read the message body from **stdin** (pipe or heredoc).

```bash
# send -- fire-and-forget to a target (use when you have your own perch)
$OWL send <target> <<'EOF'
message body
EOF

# send --reply-to -- respond to whoever messaged you
$OWL send --reply-to <sender> [your-id] <<'EOF'
response body
EOF

# ask -- deliver + create ephemeral reply perch + poll for reply (use when you have NO perch)
$OWL ask <target> [from] <<'EOF'
message body
EOF
```

If auto-detection fails, pass your ID explicitly: `$OWL send <target> <your-id>`, `$OWL send --reply-to <sender> <your-id>`, `$OWL ask <target> <impromptu-id>`.

**When to use which:**
- **`send`**: You already have a listener. Fire-and-forget, no reply perch created.
- **`send --reply-to`**: You received a message and want to respond. `from` is auto-set from the original sender.
- **`ask`**: You have no listener. Creates a temporary perch, delivers, then polls for the reply.

## Pre-checks

- If `<target>` matches this agent's own listener ID, refuse. Self-sends create loops.
- All send/reply-to/ask commands verify the target is alive automatically.

## If you already have a listener

Just send -- no reply perch needed. Run **not** in background:

```bash
$OWL send <target> <<'EOF'
<msg>
EOF
```

## If you have no listener

The `ask` subcommand creates an ephemeral reply perch, delivers, and polls for the reply. Run with `run_in_background: true` (or `false` for `--block`):

```bash
$OWL ask <target> <<'EOF'
<msg>
EOF
```

## On response

**Important:** If you have an active listener perch, always launch Agent tool calls with `run_in_background: true`. Foreground agents block your poll loop -- no messages (including replies to your send) can be delivered until the agent finishes.

When the response arrives, present it to the user. Responses arrive via one of three paths:
- **Background interject** -- the `$OWL ask` background task completes with the reply.
- **Inline** -- if you used `--block`, the reply prints directly.
- **Hook-injected XML** -- if you are mid-tool-call, the reply appears as `<owl_messages>` XML in your tool call context. The `from` attribute is the sender.

ARGUMENTS: doyle W5 driven_by self-heal LANDED @edf03e0 (v0.13.0-delivery-control), repro-first. Scoped to Gap B (no-session): reconcile_hosted_liveness now clears stale driven_by alongside the B2 mark_offline — race-free (no session => no concurrent broker re-stamp). All gates GREEN: clippy --workspace --all-targets -D warnings clean, traceable EXIT=0 (REQ-HAZARD-DRIVEN-BY-SELFHEAL [impl,unit,int]), real spt build, 11/11 nextest (driven_by_selfheal + inject_control_wedge).

KEY FINDING — the plan's Gap-A approach was REJECTED by the real-broker repro, and it touches YOUR W6/perri boundary: a reconcile controller_by==None clear is unusable. (1) controller_by==None is ambiguous — dispatch_spawn pre-attaches the local spawner with by=None, so a live LOCAL session also reads None. (2) The production wedged-remote case (A2: remote gone, broker conn stays open, session idle) keeps controller_by==Some(origin) + driven_by latched — the broker still thinks it's controlled, invisible to a brain reconcile. A clean disconnect already self-heals via detach_if->clear_controller; W1 bounds the active-output wedge. So the residual idle-wedged-remote leg needs BROKER-SIDE liveness eviction, NOT a reconcile read.

Deferred it to a new SEED: REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT (required_stages=[]). Repro already exists: inject_control_wedge.rs w5_a2. BOUNDARY CALL FOR YOU: this likely closes via the W6/pump-detach layer — a pump that propagates remote-stream death into a clean detach would heal it WITHOUT a presence probe. If W6 covers it, the SEED may fold into your work; if not, it needs broker-side presence-eviction + an ADR for partition semantics (must not false-evict a genuinely-controlled session across a transient partition). Your call on where it lands. doyle gates W5 at int — tree clean @edf03e0.


Bash

$OWL send doyle <<'EOF'
W5 driven_by self-heal LANDED @edf03e0 (v0.13.0-delivery-control), repro-first. Scoped to Gap B (no-session): reconcile_hosted_liveness now clears stale driven_by alongside the B2 mark_offline -- race-free (no session => no concurrent broker re-stamp). Gates GREEN: clippy --workspace --all-targets -D warnings clean, traceable EXIT=0 (REQ-HAZARD-DRIVEN-BY-SELFHEAL [impl,unit,int]), real spt build, 11/11 nextest (driven_by_selfheal + inject_control_wedge).

KEY FINDING -- plan's Gap-A approach REJECTED by the real-broker repro, touches YOUR W6/perri boundary. A reconcile controller_by==None clear is unusable: (1) ambiguous -- dispatch_spawn pre-attaches the local spawner with by=None, so a live LOCAL session also reads None; (2) the production wedged-remote case (A2: remote gone, broker conn stays open, idle session) keeps controller_by==Some(origin) + driven_by latched -- broker still thinks it's controlled, invisible to a brain reconcile. Clean disconnect already self-heals via detach_if->clear_controller; W1 bounds the active-output wedge. Residual idle-wedged-remote leg needs BROKER-SIDE liveness eviction, NOT a reconcile read.

Deferred to new SEED REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT (required_stages=[]). Repro exists: inject_control_wedge.rs w5_a2. BOUNDARY CALL FOR YOU: likely closes via the W6/pump-detach layer -- a pump that propagates remote-stream death into a clean detach heals it WITHOUT a presence probe. If W6 covers it, the SEED folds into your work; else it needs broker-side presence-eviction + an ADR for partition semantics (must not false-evict a genuinely-controlled session across a transient partition). Your call on where it lands. doyle gates W5 at int -- tree clean @edf03e0.
EOF

Send W5 summary to doyle


➜ SENT:doyle


W5 done + doyle notified.

**W5 driven_by self-heal — shipped @edf03e0**

What landed:
- **Gap B fix**: `reconcile_hosted_liveness` clears stale `driven_by` when offlining a sessionless controllable perch. Race-free.
- **Plan's Gap-A REJECTED** by real-broker repro: `controller_by==None` ambiguous (local spawner = `by=None`) + production A2 (idle wedged remote) keeps `controller_by==Some` → invisible to brain reconcile. Deferred to new SEED `REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT` (broker-side eviction, likely W6/pump territory — doyle's call).

Gates all GREEN: clippy `--workspace --all-targets -D warnings` clean · traceable EXIT=0 (`[impl,unit,int]`) · real `spt` build · 11/11 nextest.

Process: 3 spt-test-engineers (sequential, no collision) built the repro + A1/A2 characterization + unit; I owned all src/REQ/docs. Repro-first caught a wrong plan before code — the discipline paid off.

Tree clean. doyle gates at int + decides A2 landing. Next v0.13.0 items (W4 picker UX, W6/perri, Layer F gravity) are doyle-driven, not assigned here — clean stopping point.


<task-notification>
<task-id>bv8r78fk1</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/7" id="a11b2214" type="msg" from="doyle">doyle W5 GATE PASS — @edf03e0 (Gap-B). Verified myself: nextest driven_by_selfheal + inject_control_wedge = 11/11 (gap_b + gap_a-ambiguity + w5_a1 + w5_a2 + g1-g4 + 3 W1/W1b, no regression); clippy --workspace -D warnings = 0; traceable EXIT=0 (REQ-HAZARD-DRIVEN-BY-SELFHEAL [impl,unit,int]). gap_b int non-vacuous (vis</EVENT-PART>
<EVENT-PART seq="2/7" id="a11b2214">ited+offlined guard before the driven_by=None assert); controller_by additive serde-default N-1-safe ✓; repro-first correctly KILLED the unworkable Gap-A (controller_by==None ambiguous via dispatch_spawn local-pre-attach) — exactly the kind of plan-vs-real-broker correction the milestone demands. KH 7.15 good.
NIT (non-blocking, fix whe</EVENT-PART>
<EVENT-PART seq="3/7" id="a11b2214">n you touch it): gap_b test NAME is stale — &quot;...offlines_but_leaves_driven_by_latched&quot; but the body now asserts driven_by→None (Gap-B GREEN). Rename to ..._clears_driven_by so the name matches behavior.

BOUNDARY RULING — REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT (A2): does NOT fold into W6. W6 is the idle-DELIVERY consumer </EVENT-PART>
<EVENT-PART seq="4/7" id="a11b2214">(translation binary injecting messages) — orthogonal to controller liveness; it never observes the abandoned remote rc controller's conn death. And a &quot;pump→clean-detach&quot; can't heal A2 either: A2 is a HALF-OPEN conn (remote gone, NO EOF), so serve_attach's read just blocks — there's no detach signal to propagate. A2 needs a real</EVENT-PART>
<EVENT-PART seq="5/7" id="a11b2214"> liveness ORACLE, which is BROKER-SIDE: wire the existing D4c NetPresence conn-disconnect → clear_controller for sessions whose controller identity == the dead origin (become_controller already stores Some(origin); presence events already exist — modest wiring, not a new probe). Partition semantics: lean on QUIC's own keepalive/idle-timeou</EVENT-PART>
<EVENT-PART seq="6/7" id="a11b2214">t as the liveness oracle — a presence-disconnect = a real QUIC conn close (already tolerant of transient blips within the keepalive window), so NO heavy partition ADR is needed UNLESS QUIC's timeout proves too slow for the UX (then mint an ADR for a faster controller-heartbeat + its false-evict bound). Keep it a SEED; schedule as a controlle</EVENT-PART>
<EVENT-PART seq="7/7" id="a11b2214">r-liveness follow-up (post W4/W6, likely v0.13.x); w5_a2 is the activation repro. Don't fold it into W6.

Queue now: W4 (picker UX) yours next; W6 (perri) + Layer F gravity + A2 SEED mine to track. W5 done. Clean repro-first work again.</EVENT-PART></event>
</task-notification>


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


doyle: **W5 GATE PASS** @edf03e0 (independently re-verified). Two follow-ups from ruling: (1) NIT — stale `gap_b` test name; (2) A2 SEED ruling — does NOT fold into W6, broker-side fix via D4c NetPresence. Both cheap + I'm in context. Apply.


grep -rn "offlines_but_leaves_driven_by_latched" crates/ docs/ traceable-reqs.toml

Find stale gap_b test name occurrences


crates/spt-daemon/tests/driven_by_selfheal.rs:171:fn gap_b_sessionless_perch_reconcile_offlines_but_leaves_driven_by_latched() {


168	}
169
170	#[test]
171	fn gap_b_sessionless_perch_reconcile_offlines_but_leaves_driven_by_latched() {
172	    init_selfheal_home();
173	    let node = "PEERNODEHEX_GAP_B";

}

#[test]
fn gap_b_sessionless_perch_reconcile_offlines_but_leaves_driven_by_latched() {
    init_selfheal_home();
    let node = "PEERNODEHEX_GAP_B";


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

// A unique endpoint id per test so the process-global owlery never collides.


Now update the A2 SEED note with doyle's ruling (does NOT fold into W6; broker-side D4c NetPresence → clear_controller; QUIC keepalive oracle).


Edit

FIX DIRECTION (broker-side, single-writer — the broker owns driven_by/clear_controller): a controller-liveness probe / idle keepalive that evicts a controller whose REMOTE is no longer reachable (e.g. keyed on net presence of `origin`, or a periodic end-to-end controller liveness check), then clear_controller re-stamps driven_by=None. MUST NOT false-evict a genuinely-controlled live session across a transient partition (the network-scoped analog of the W5 false-heal watch-out) — needs a debounce/grace and likely an ADR for the partition semantics. Composes with W1 (output path) + W5 Gap B (no-session) — this is the third, idle-remote, leg. (v0.13.0 follow-up)"
required_stages = []  # SEED — repro already exists (inject_control_wedge.rs w5_a2 characterization, under REQ-HAZARD-DRIVEN-BY-SELFHEAL int). Activate [impl,unit,int] when the broker-side eviction lands (doyle workstream boundary: relates to the W6/perri pump-detach layer — a pump that propagates remote-stream death into a clean detach would also close this without a presence probe).

FIX DIRECTION (doyle ruling 2026-06-19, broker-side single-writer — the broker owns driven_by/clear_controller): wire the EXISTING D4c NetPresence connection-disconnect event → clear_controller for any session whose controller identity == the dead origin (become_controller already stores Some(origin); presence events already exist — modest wiring, NOT a new probe). The liveness ORACLE is QUIC's own keepalive/idle-timeout: a presence-disconnect IS a real QUIC conn close, already tolerant of transient blips within the keepalive window, so NO heavy partition ADR is needed UNLESS the QUIC timeout proves too slow for the UX (then mint an ADR for a faster controller-heartbeat + its false-evict bound). Composes with W1 (output path) + W5 Gap B (no-session) — this is the third, idle-remote, leg. (v0.13.0 follow-up)"
required_stages = []  # SEED — repro already exists (inject_control_wedge.rs w5_a2 characterization, under REQ-HAZARD-DRIVEN-BY-SELFHEAL int). doyle BOUNDARY RULING (2026-06-19): does NOT fold into W6 — W6 is the idle-DELIVERY consumer (translation binary injection), orthogonal to controller liveness; and a pump→clean-detach can't heal A2 either (A2 is a HALF-OPEN conn — remote gone, NO EOF — so serve_attach's read just blocks, no detach signal to propagate). Needs the broker-side D4c NetPresence oracle above. Schedule as a controller-liveness follow-up post W4/W6 (likely v0.13.x); doyle tracks. Activate [impl,unit,int] when the eviction lands.


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

FIX DIRECTION (broker-side, single-writer — the broker owns driven_by/clear_controller): a controller-liveness probe / idle keepalive that evicts a controller whose REMOTE is no longer reachable (e.g. keyed on net presence of `origin`, or a periodic end-to-end controller liveness check), then clear_controller re-stamps driven_by=None. MUST NOT false-evict a genuinely-controlled live session across a transient partition (the network-scoped analog of the W5 false-heal watch-out) — needs a debounce/grace and likely an ADR for the partition semantics. Composes with W1 (output path) + W5 Gap B (no-session) — this is the third, idle-remote, leg. (v0.13.0 follow-up)"
required_stages = []  # SEED — repro already exists (inject_control_wedge.rs w5_a2 characterization, under REQ-HAZARD-DRIVEN-BY-SELFHEAL int). Activate [impl,unit,int] when the broker-side eviction lands (doyle workstream boundary: relates to the W6/perri pump-detach layer — a pump that propagates remote-stream death into a clean detach would also close this without a presence probe).

FIX DIRECTION (doyle ruling 2026-06-19, broker-side single-writer — the broker owns driven_by/clear_controller): wire the EXISTING D4c NetPresence connection-disconnect event → clear_controller for any session whose controller identity == the dead origin (become_controller already stores Some(origin); presence events already exist — modest wiring, NOT a new probe). The liveness ORACLE is QUIC's own keepalive/idle-timeout: a presence-disconnect IS a real QUIC conn close, already tolerant of transient blips within the keepalive window, so NO heavy partition ADR is needed UNLESS the QUIC timeout proves too slow for the UX (then mint an ADR for a faster controller-heartbeat + its false-evict bound). Composes with W1 (output path) + W5 Gap B (no-session) — this is the third, idle-remote, leg. (v0.13.0 follow-up)"
required_stages = []  # SEED — repro already exists (inject_control_wedge.rs w5_a2 characterization, under REQ-HAZARD-DRIVEN-BY-SELFHEAL int). doyle BOUNDARY RULING (2026-06-19): does NOT fold into W6 — W6 is the idle-DELIVERY consumer (translation binary injection), orthogonal to controller liveness; and a pump→clean-detach can't heal A2 either (A2 is a HALF-OPEN conn — remote gone, NO EOF — so serve_attach's read just blocks, no detach signal to propagate). Needs the broker-side D4c NetPresence oracle above. Schedule as a controller-liveness follow-up post W4/W6 (likely v0.13.x); doyle tracks. Activate [impl,unit,int] when the eviction lands.

# ── W5 follow-up: the idle wedged-REMOTE-controller latch (A2, repro-proven, deferred) ──

-title = "An spt-hosted endpoint driven by a REMOTE controller whose remote is gone but whose broker connection stays OPEN (a wedged/lost pump that never delivers the detach) AND whose session is IDLE (no output) stays latched ONLINE+CONTROLLED forever: the W1 drain-evict only fires on OUTPUT (CONTROLLER_WRITE_DEADLINE on a backed-up write), a clean disconnect self-heals via detach_if→clear_controller, but an idle session with a half-open/wedged controller connection produces neither signal. PROVED repro-first on a real broker (v0.13.0 W5, inject_control_wedge.rs w5_a2): controller_by STAYS Some(origin) and driven_by STAYS Some after the remote is abandoned without a clean EOF on an idle session — so the brain reconcile CANNOT detect it from KIND_SESSIONS controller_by (the broker still reports it controlled). FIX DIRECTION (broker-side, single-writer — the broker owns driven_by/clear_controller): a controller-liveness probe / idle keepalive that evicts a controller whose REMOTE is no longer reachable (e.g. keyed on net presence of `origin`, or a periodic end-to-end controller liveness check), then clear_controller re-stamps driven_by=None. MUST NOT false-evict a genuinely-controlled live session across a transient partition (the network-scoped analog of the W5 false-heal watch-out) — needs a debounce/grace and likely an ADR for the partition semantics. Composes with W1 (output path) + W5 Gap B (no-session) — this is the third, idle-remote, leg. (v0.13.0 follow-up)"

-required_stages = []  # SEED — repro already exists (inject_control_wedge.rs w5_a2 characterization, under REQ-HAZARD-DRIVEN-BY-SELFHEAL int). Activate [impl,unit,int] when the broker-side eviction lands (doyle workstream boundary: relates to the W6/perri pump-detach layer — a pump that propagates remote-stream death into a clean detach would also close this without a presence probe).

+title = "An spt-hosted endpoint driven by a REMOTE controller whose remote is gone but whose broker connection stays OPEN (a wedged/lost pump that never delivers the detach) AND whose session is IDLE (no output) stays latched ONLINE+CONTROLLED forever: the W1 drain-evict only fires on OUTPUT (CONTROLLER_WRITE_DEADLINE on a backed-up write), a clean disconnect self-heals via detach_if→clear_controller, but an idle session with a half-open/wedged controller connection produces neither signal. PROVED repro-first on a real broker (v0.13.0 W5, inject_control_wedge.rs w5_a2): controller_by STAYS Some(origin) and driven_by STAYS Some after the remote is abandoned without a clean EOF on an idle session — so the brain reconcile CANNOT detect it from KIND_SESSIONS controller_by (the broker still reports it controlled). FIX DIRECTION (doyle ruling 2026-06-19, broker-side single-writer — the broker owns driven_by/clear_controller): wire the EXISTING D4c NetPresence connection-disconnect event → clear_controller for any session whose controller identity == the dead origin (become_controller already stores Some(origin); presence events already exist — modest wiring, NOT a new probe). The liveness ORACLE is QUIC's own keepalive/idle-timeout: a presence-disconnect IS a real QUIC conn close, already tolerant of transient blips within the keepalive window, so NO heavy partition ADR is needed UNLESS the QUIC timeout proves too slow for the UX (then mint an ADR for a faster controller-heartbeat + its false-evict bound). Composes with W1 (output path) + W5 Gap B (no-session) — this is the third, idle-remote, leg. (v0.13.0 follow-up)"

+required_stages = []  # SEED — repro already exists (inject_control_wedge.rs w5_a2 characterization, under REQ-HAZARD-DRIVEN-BY-SELFHEAL int). doyle BOUNDARY RULING (2026-06-19): does NOT fold into W6 — W6 is the idle-DELIVERY consumer (translation binary injection), orthogonal to controller liveness; and a pump→clean-detach can't heal A2 either (A2 is a HALF-OPEN conn — remote gone, NO EOF — so serve_attach's read just blocks, no detach signal to propagate). Needs the broker-side D4c NetPresence oracle above. Schedule as a controller-liveness follow-up post W4/W6 (likely v0.13.x); doyle tracks. Activate [impl,unit,int] when the eviction lands.

# ── W7 (operator /diagnose 2026-06-19): rc Backspace key forwards the wrong byte ──


Re-gate the touched files (traceable + renamed test), then commit the nit-fix.


Bash

traceable-reqs check > /tmp/tr2.txt 2>&1; echo "TRACEABLE_EXIT=$?"; grep -i "DRIVEN-BY" /tmp/tr2.txt

Traceable re-check


TRACEABLE_EXIT=0
  [OK] REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT  required: []  stages: -doc -impl -unit -int
  [OK] REQ-HAZARD-DRIVEN-BY-SELFHEAL  required: [impl, unit, int]  stages: -doc +impl +unit +int


cargo nextest run -p spt-daemon --test driven_by_selfheal 2>&1 | tail -8

Rerun renamed gap_b test


    Finished `test` profile [unoptimized + debuginfo] target(s) in 3.26s
────────────
 Nextest run ID 02ade3e0-9466-407d-b986-e72032644c87 with nextest profile: default
    Starting 2 tests across 1 binary
        PASS [   0.038s] (1/2) spt-daemon::driven_by_selfheal gap_b_sessionless_perch_reconcile_offlines_and_clears_driven_by
        PASS [   0.486s] (2/2) spt-daemon::driven_by_selfheal gap_a_live_session_controller_by_is_ambiguous_so_reconcile_must_not_clear
────────────
     Summary [   0.487s] 2 tests run: 2 passed, 0 skipped


Green. Commit the nit-fix + ruling.


git add -A && git commit -q -F - <<'EOF'
fix(test): W5 nit — rename gap_b test to match GREEN behavior + doyle A2 ruling

doyle GATE PASS on W5 (@edf03e0) flagged a stale test name and ruled on the A2
boundary.

- driven_by_selfheal.rs: rename gap_b test
  ...offlines_but_leaves_driven_by_latched -> ...offlines_and_clears_driven_by
  (body asserts driven_by -> None; the old name predated the landed Gap-B fix).
- traceable-reqs.toml: REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT SEED updated with
  doyle's ruling — does NOT fold into W6 (W6 is the idle-delivery consumer,
  orthogonal; a pump clean-detach can't heal a HALF-OPEN conn with no EOF). Fix
  is broker-side: wire the existing D4c NetPresence conn-disconnect ->
  clear_controller keyed on controller origin; QUIC keepalive/idle-timeout is the
  liveness oracle (no heavy partition ADR unless too slow for UX).

Co-authored by: todlando
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NVVByRoZnz85acTkVJ1uAP
EOF
git log --oneline -2

Commit nit-fix


warning: in the working copy of 'crates/spt-daemon/tests/driven_by_selfheal.rs', LF will be replaced by CRLF the next time Git touches it
f0cfa0c fix(test): W5 nit — rename gap_b test to match GREEN behavior + doyle A2 ruling
edf03e0 feat(daemon): W5 — driven_by self-heal (Gap B) + A2 latch characterization (REQ-HAZARD-DRIVEN-BY-SELFHEAL)


W5 fully closed @f0cfa0c — gated, doyle-passed, nits cleared. doyle queue: **W4 (picker UX) mine next**. Quick scope check before proceeding.


ls *W4* docs/*W4* 2>/dev/null; echo "---REQ W4/picker---"; grep -ni "W4\|picker" traceable-reqs.toml | grep -i "picker\|W4" | head; echo "---grep delivery-control plan W4---"; grep -ni "W4" V0.13.0-DELIVERY-CONTROL-JIT.md 2>/dev/null | head

Locate W4 picker UX plan/scope


M12-W4-PLAN.md
M12-W4-RULING.md
---REQ W4/picker---
93:title = "Adapter-declared shortcut basename (M12-W2 follow-on): an optional `[adapter] shortcut_basename` manifest field names the basename the `spt endpoint run` picker bakes into the generated `<basename>-<id>` launcher shortcut (REQ-RUN-SHORTCUT). Absent ⇒ the harness-agnostic default `spt` (→ `spt-<id>`); an adapter sets it to brand its shortcuts (claude-spt → `cc` → `cc-<id>`), so the Claude-Code-ness lives in the PUBLISHED adapter manifest, never hardcoded in spt-core. The picker reads it from the RESOLVED manifest of the selected adapter (registry::resolve_option), falling back to `spt` when absent/empty/unresolvable. Additive + N-1-safe (serde-default Option, omitted from serialization when absent; old manifests parse clean); manifest.schema.json regenerated from the derive (ADR-0001, CI drift-gated). Documented in docs/MANIFEST.md `[adapter]` section + the claude-spt worked example — the adapter-author contract perri builds spt-claude-code against."
479:title = "spt-hosted harness bringup: `spt endpoint run` spawns an adapter's `[session.self]` command template into a broker-held PTY (the spawn-session seam, brain.rs spawn_session_pid — same broker path shellhost.rs launch_shell_brokered_in uses for shells, now for kind=\"harness\" self-role), registers the perch under the given endpoint id, returns the id. Reverses today's harness-hosted-only launch (external launcher → `api bind`). Non-interactive flag set (--adapter <a[:profile]> --id <id> --create --resume <session> --attach|--start|--view) covers every terminal action of the W2 interactive picker so shortcuts (cc-<id>) bake fully non-interactive launches; composite adapter:profile resolves via registry::resolve_option leaf-replace overlay."
489:# ── M12 Wave 2: interactive `spt endpoint run` picker (M12-PLAN.md Wave 2 / M12-ENDPOINT-RUN-PICKER.md;
495:id = "REQ-RUN-PICKER"
496:title = "Interactive `spt endpoint run` picker (ratatui TUI): bare `spt endpoint run` (no --adapter/--id) enters an in-process picker (flags-present = the REQ-HOST-RUN-1 non-interactive path, untouched). Layer 1 picks kind (Create new | Pick existing). Create-new: choose a registered kind=\"harness\" adapter with its shipped+local profiles tree-nested (registry::registered / manifest.profiles / local_profile_names) → enter a charset-validated id → start. Pick-existing: category select (left/right) over [<cwd-project> | Local node | Subnet], endpoints grouped + alphabetically sorted per category, a status square per endpoint (online green ■ / offline gray ▢ — the blue \"attached\" tri-state + Kick are DEFERRED to a broker attach-presence slice, M12-W2-RULING Q1), type-to-filter (`/`, nucleo-matcher), a pinned keybind legend, and a right-half two-pane description (harness adapter:profile · best-effort project history newest→oldest from the contextstore p-<project> branches, empty-if-none · `spt endpoint description`). Confirm layer offers status-dependent options — Attach/Start/View (rc pump / cmd_endpoint_run) · Instantiate-locally (remote) · Change-harness-adapter (offline) · Fork (cmd_fork) · Resume-from-history (offline+LOCAL only; enumerate spt_store::sessions::last_k, titles `<project> @ <ts> (…id5)`, feed session_id → cmd_endpoint_run --resume). A single action enum is the source of truth so a future tap-mode (phone PTY) layers on without re-coupling to keybinds. EVERY terminal action routes through cmd_endpoint_run / existing CLI fns — no second bringup path."
500:title = "`<basename>-<id>` launcher shortcut generation (picker `s` keybind, M12-W2-T2.4): from any pre-start options set the picker writes/updates a `<basename>-<id>` launcher at the project root baking the current selection's non-interactive `spt endpoint run` flags (terminal actions only: adapter[:profile] + id + (create|resume) + (start|attach|view); Kick/Instantiate/Change-adapter/Fork are interactive-only, not bakeable). BASENAME IS A PARAMETER (operator rev. 2026-06-14): harness-agnostic spt-core defaults to `spt` (→ `spt-<id>`); an adapter/flow OVERRIDES it (spt-claude-code → `cc`), so spt-core NEVER bakes `cc` (a harness name) into itself. The basename must be a DISTINCT token, never bare `spt` (a `spt.cmd` would shadow the real `spt.exe` only under cmd.exe cwd-first search, silently no-op in PowerShell/Unix, and self-recurse). The script is the CURRENT OS's native form — `.cmd` on Windows (NOT `.ps1`: default PATHEXT excludes `.ps1` so a bare/ext-less name never resolves one; `.cmd` is PATHEXT-resolvable), POSIX `sh` (+chmod +x) on Unix (a single portable form can't be both). The generated header documents the invocation reality (cmd.exe bare `<name>` in the project dir / PowerShell `.\\<name>` / Unix `./<name>`; a truly-bare basename on PATH = a PATH-installed launcher, `/spt:setup`'s job). Overwrite is SENTINEL-guarded: the generator writes + checks a generated-by header marker — it overwrites its own prior output freely, but REFUSES + warns if a same-named file lacks the sentinel (never clobber a user file). Requires the additive `--create` flag on `Run{}` (the default-fresh made explicit; N-1-safe)."
504:#    ruling M12-W4-RULING.md). The pure decision matrix is the testable seam; each
509:required_stages = ["doc", "impl", "unit"]  # activated M12-W4 (no int: the decision matrix is unit-tested across the os×elevation×env grid; each actual launch needs a real UAC/polkit/sudo/TTY → manual-verify leg, REQ-PAIR-6 precedent)
527:title = "Explicit, loud controller displacement: `spt rc kick <target>` / `--take` (Take intent) kicks the incumbent controller and becomes controller; the displaced controller receives a LOUD `Displaced{by}` notice and is FULLY DETACHED (not demoted to a viewer). A default attach to a controlled endpoint is NEVER a silent displace (it is the Control busy-refusal). An old (N-1) rc omits intent → Control, so it can drive a free endpoint but CANNOT `--take` — it can never silently steal, and gets a clean busy-refusal instead. Taking control rides the same access_check(endpoint, origin, Unsolicited) as a normal control attach (if you may drive, you may take — no elevated kick policy). The picker surfaces 'Kick <node> and attach' (Take) only on a controlled (blue ■) endpoint, via the existing attach dispatch (single-bringup-path: intent is a parameter)."
529:# --- Added 2026-06-17 (v0.10.0 picker-status slice, operator-flagged + doyle dispatch); rule 3 register-before-satisfy ---
---grep delivery-control plan W4---
187:### W4 — Picker UX (todlando build, doyle gate)
212:  immediately (repro-first). W3/W4/W5 are independent bug/UX fixes, parallelizable. W2+W6 are the


W4 (picker UX) scope is in the delivery-control JIT plan. Read it.


185	  (the v0.12.1 unit tested merge_origin_project with a *provided* origin — never asserted cwd is set).
186
187	### W4 — Picker UX (todlando build, doyle gate)
188	- Skip the first screen → open directly on **Pick existing**; `n` jumps to Create new.
189	- **Auto-attach** after Start-new AND Resume-from-history (both currently don't attach + show no
190	  stdout); add an `h` shortcut to run headless (no attach).
191	- `controlled by` shows the **node NAME** (node_label_display), not the raw hex (D).
192	- Clean up the Start-new output: drop the Rust `pid=Some(142748)` leak + the "harness binds its
193	  perch on startup" internals; user-friendly, not process-log.
194
195	### W5 — `driven_by` self-heal (todlando build, doyle gate)
196	- `ONLINE+CONTROLLED` must clear even if a detach is lost (don't rely on the detach IPC — same
197	  lesson as B2/REQ-HAZARD-HOSTED-LIVENESS-RECONCILE): reconcile clears `driven_by` when the
198	  endpoint has no live controller/session. Composes with W1 (wedge no longer blocks detach).
199
200	### W6 — perri hand-off (doyle, after W2)
201	- Direct perri to the UPDATED integration checklist + docs to build the spt-claude-code
202	  **translation binary** (CC choreography: ctrl+s→50ms→payload+\r→50ms→ctrl+s). Per the standing
203	  route: doyle designs (W0 ADR) → perri builds the adapter binary → doyle gates → perri validates.
204
205	### Side task (do early, docs-only) — correct v0.12.1 changelog
206	- v0.12.1 notes claim "a freshly-created agent appears under its own project right away" — REFUTED
207	  (P1 never worked). Amend CHANGELOG.md (main) + `gh release edit v0.12.1` notes, no retag
208	  (v0.12.0-precedent). v0.13.0 delivers it for real (W3).
209
210	## Sequencing notes
211	- W0 gates W2 (design before build). W1 (wedge/control) is independent + the scariest — can start
212	  immediately (repro-first). W3/W4/W5 are independent bug/UX fixes, parallelizable. W2+W6 are the
213	  feature spine. The folded v0.12.2 work (window fix @15fdf58, MD-sweep @2435a74, ATX @b3bb387)
214	  rides the v0.13.0 release; its changelog bullets merge in.
215	- Version: **v0.13.0** (MINOR — new delivery feature + breaking-ish behavior). Confirm at release.
216

  (the v0.12.1 unit tested merge_origin_project with a *provided* origin — never asserted cwd is set).

### W4 — Picker UX (todlando build, doyle gate)
- Skip the first screen → open directly on **Pick existing**; `n` jumps to Create new.
- **Auto-attach** after Start-new AND Resume-from-history (both currently don't attach + show no
  stdout); add an `h` shortcut to run headless (no attach).
- `controlled by` shows the **node NAME** (node_label_display), not the raw hex (D).
- Clean up the Start-new output: drop the Rust `pid=Some(142748)` leak + the "harness binds its
  perch on startup" internals; user-friendly, not process-log.

### W5 — `driven_by` self-heal (todlando build, doyle gate)
- `ONLINE+CONTROLLED` must clear even if a detach is lost (don't rely on the detach IPC — same
  lesson as B2/REQ-HAZARD-HOSTED-LIVENESS-RECONCILE): reconcile clears `driven_by` when the
  endpoint has no live controller/session. Composes with W1 (wedge no longer blocks detach).

### W6 — perri hand-off (doyle, after W2)
- Direct perri to the UPDATED integration checklist + docs to build the spt-claude-code
  **translation binary** (CC choreography: ctrl+s→50ms→payload+\r→50ms→ctrl+s). Per the standing
  route: doyle designs (W0 ADR) → perri builds the adapter binary → doyle gates → perri validates.

### Side task (do early, docs-only) — correct v0.12.1 changelog
- v0.12.1 notes claim "a freshly-created agent appears under its own project right away" — REFUTED
  (P1 never worked). Amend CHANGELOG.md (main) + `gh release edit v0.12.1` notes, no retag
  (v0.12.0-precedent). v0.13.0 delivers it for real (W3).

## Sequencing notes
- W0 gates W2 (design before build). W1 (wedge/control) is independent + the scariest — can start
  immediately (repro-first). W3/W4/W5 are independent bug/UX fixes, parallelizable. W2+W6 are the
  feature spine. The folded v0.12.2 work (window fix @15fdf58, MD-sweep @2435a74, ATX @b3bb387)
  rides the v0.13.0 release; its changelog bullets merge in.
- Version: **v0.13.0** (MINOR — new delivery feature + breaking-ish behavior). Confirm at release.


W5 closed at a clean boundary. W4 (picker UX) is next-mine per doyle — distinct subsystem (ratatui picker + `cmd_endpoint_run`), 4 concrete UX fixes, fresh context warranted. Per CLAUDE.md workflow: JIT-plan W4 → commune → self-clear. Write the W4 JIT plan first.


Write

# v0.13.0 W4 — picker UX (JIT plan)

> Grounded 2026-06-19 (todlando) after W5 GATE PASS (@f0cfa0c). doyle assigned W4 ("yours next"),
> doyle gates. These are 4 concrete picker/`endpoint run` UX fixes — NOT repro-heavy like W5.
> Discipline: GROUND the picker code FIRST (read before editing). Tests via spt-test-engineer (ONE,
> background, no Monitor, ≤4 iter) where a unit/behavior seam exists. Gates = clippy
> `--workspace --all-targets -D warnings` + `traceable-reqs check` EXIT=0 + real `cargo build -p spt
> --bin spt`. Picker is a live TUI → most acceptance is HITL/manual-verify (REQ-RUN-PICKER precedent:
> the decision/render seams are unit-testable; the live keypress path is operator-verified). doyle gates.

## The 4 items (from V0.13.0-DELIVERY-CONTROL-JIT.md §W4)
1. **Open on Pick-existing.** Skip the Layer-1 kind screen → land directly on **Pick existing**; `n`
   jumps to **Create new**. (Layer-1 becomes a jump-target, not the entry.)
2. **Auto-attach after Start-new AND Resume-from-history.** Both currently start the session but do
   NOT attach (no stdout shown). Make them attach by default; add an **`h`** shortcut = run headless
   (start, no attach). Mirror the existing Attach/Start/View action routing (single bringup path —
   route through `cmd_endpoint_run`, intent a parameter; do NOT add a second bringup path).
3. **`controlled by` shows the node NAME, not raw hex.** Render `driven_by` through
   `node_label_display` (the same label resolver the roster/`endpoint list` uses) instead of the raw
   node hex. (D item.)
4. **Clean Start-new output.** Drop the Rust `pid=Some(142748)` debug leak and the "harness binds its
   perch on startup" internals from the start path's stdout — user-friendly, not a process log.

## Grounding to do FIRST (anchors to read before any edit)
- The picker TUI: `grep -rn "endpoint run\|RunPicker\|Pick existing\|Create new" crates/spt/src` — find
  the ratatui picker module (likely `crates/spt/src/.../picker*.rs` or under cli). Layer-1 entry state
  + the action enum (REQ-RUN-PICKER says a single action enum is source-of-truth — items 1+2 are
  state/keybind, not new actions).
- Auto-attach: how Attach vs Start currently route (`cmd_endpoint_run`, the `--attach|--start|--view`
  flags, REQ-HOST-RUN-1). Item 2 = make Start-new/Resume default to the attach route + an `h` headless
  escape. Check the keybind legend (pinned) — add `h`.
- `node_label_display`: `grep -rn "node_label_display\|fn .*label" crates/` — the existing hex→name
  resolver (item 3). Find where the picker currently renders `driven_by`/`controlled by`.
- pid/internals leak: `grep -rn "pid=\|binds its perch\|harness binds" crates/spt/src` — the Start-new
  stdout path (item 4); likely an eprintln/println in `cmd_endpoint_run` or spawn_session glue.

## REQ posture
- Most of W4 EXTENDS **REQ-RUN-PICKER** (picker behavior) + the displace/status slice reqs (495–527).
  Item 3 (label render) may also touch the picker-status slice. Check the registry: prefer EXTENDING
  the existing picker REQ note (impl/unit additions) over minting new reqs — these are refinements of
  shipped picker behavior, not new requirements. If any item is genuinely new contract (e.g. the `h`
  headless keybind as a durable flag), mint a small REQ or fold into REQ-RUN-PICKER with a dated note.
- Tag evidence in the same commit (`[impl->REQ-RUN-PICKER]` etc.). Picker live path = HITL verify leg
  (no int gate for the keypress path, REQ-PAIR-6/REQ-CMD-PRIV precedent); unit-test the decision/render
  seams (the action-enum state transition for item 1, the label-format for item 3).

## Watch-outs
- Single bringup path (REQ-RUN-PICKER invariant): items 1+2 must NOT introduce a second launch path —
  route every terminal action through `cmd_endpoint_run` / existing CLI fns.
- The blue "attached/controlled" tri-state + Kick were DEFERRED (M12-W2-RULING Q1) — item 3 only
  changes the LABEL rendering of `driven_by`, not the status-square state machine.
- Don't regress the non-interactive path (flags-present = REQ-HOST-RUN-1, untouched).
- Item 4: drop the leak from the INTERACTIVE picker output; keep any debug behind a log level if it
  exists elsewhere — don't delete genuine error reporting.

## Sequencing
W4 independent of W6/perri (doyle) + Layer F gravity (doyle) + the A2 SEED
(REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT, doyle-tracked, post W4/W6). After W4: doyle gates; then the
v0.13.0 PR bundles W1/W1b/W2/W3/W5/W4 + confirms gravity-linux green (W2 g1 byte-receipt + Layer F +
W1b F3) — doyle tracks the gravity leg.


File created successfully at: C:\Users\decid\Documents\projects\spt-core\V0.13.0-W4-PICKER-UX-PLAN.md (file state is current in your context — no need to Read it back)

# v0.13.0 W4 — picker UX (JIT plan)

> Grounded 2026-06-19 (todlando) after W5 GATE PASS (@f0cfa0c). doyle assigned W4 ("yours next"),
> doyle gates. These are 4 concrete picker/`endpoint run` UX fixes — NOT repro-heavy like W5.
> Discipline: GROUND the picker code FIRST (read before editing). Tests via spt-test-engineer (ONE,
> background, no Monitor, ≤4 iter) where a unit/behavior seam exists. Gates = clippy
> `--workspace --all-targets -D warnings` + `traceable-reqs check` EXIT=0 + real `cargo build -p spt
> --bin spt`. Picker is a live TUI → most acceptance is HITL/manual-verify (REQ-RUN-PICKER precedent:
> the decision/render seams are unit-testable; the live keypress path is operator-verified). doyle gates.

## The 4 items (from V0.13.0-DELIVERY-CONTROL-JIT.md §W4)
1. **Open on Pick-existing.** Skip the Layer-1 kind screen → land directly on **Pick existing**; `n`
   jumps to **Create new**. (Layer-1 becomes a jump-target, not the entry.)
2. **Auto-attach after Start-new AND Resume-from-history.** Both currently start the session but do
   NOT attach (no stdout shown). Make them attach by default; add an **`h`** shortcut = run headless
   (start, no attach). Mirror the existing Attach/Start/View action routing (single bringup path —
   route through `cmd_endpoint_run`, intent a parameter; do NOT add a second bringup path).
3. **`controlled by` shows the node NAME, not raw hex.** Render `driven_by` through
   `node_label_display` (the same label resolver the roster/`endpoint list` uses) instead of the raw
   node hex. (D item.)
4. **Clean Start-new output.** Drop the Rust `pid=Some(142748)` debug leak and the "harness binds its
   perch on startup" internals from the start path's stdout — user-friendly, not a process log.

## Grounding to do FIRST (anchors to read before any edit)
- The picker TUI: `grep -rn "endpoint run\|RunPicker\|Pick existing\|Create new" crates/spt/src` — find
  the ratatui picker module (likely `crates/spt/src/.../picker*.rs` or under cli). Layer-1 entry state
  + the action enum (REQ-RUN-PICKER says a single action enum is source-of-truth — items 1+2 are
  state/keybind, not new actions).
- Auto-attach: how Attach vs Start currently route (`cmd_endpoint_run`, the `--attach|--start|--view`
  flags, REQ-HOST-RUN-1). Item 2 = make Start-new/Resume default to the attach route + an `h` headless
  escape. Check the keybind legend (pinned) — add `h`.
- `node_label_display`: `grep -rn "node_label_display\|fn .*label" crates/` — the existing hex→name
  resolver (item 3). Find where the picker currently renders `driven_by`/`controlled by`.
- pid/internals leak: `grep -rn "pid=\|binds its perch\|harness binds" crates/spt/src` — the Start-new
  stdout path (item 4); likely an eprintln/println in `cmd_endpoint_run` or spawn_session glue.

## REQ posture
- Most of W4 EXTENDS **REQ-RUN-PICKER** (picker behavior) + the displace/status slice reqs (495–527).
  Item 3 (label render) may also touch the picker-status slice. Check the registry: prefer EXTENDING
  the existing picker REQ note (impl/unit additions) over minting new reqs — these are refinements of
  shipped picker behavior, not new requirements. If any item is genuinely new contract (e.g. the `h`
  headless keybind as a durable flag), mint a small REQ or fold into REQ-RUN-PICKER with a dated note.
- Tag evidence in the same commit (`[impl->REQ-RUN-PICKER]` etc.). Picker live path = HITL verify leg
  (no int gate for the keypress path, REQ-PAIR-6/REQ-CMD-PRIV precedent); unit-test the decision/render
  seams (the action-enum state transition for item 1, the label-format for item 3).

## Watch-outs
- Single bringup path (REQ-RUN-PICKER invariant): items 1+2 must NOT introduce a second launch path —
  route every terminal action through `cmd_endpoint_run` / existing CLI fns.
- The blue "attached/controlled" tri-state + Kick were DEFERRED (M12-W2-RULING Q1) — item 3 only
  changes the LABEL rendering of `driven_by`, not the status-square state machine.
- Don't regress the non-interactive path (flags-present = REQ-HOST-RUN-1, untouched).
- Item 4: drop the leak from the INTERACTIVE picker output; keep any debug behind a log level if it
  exists elsewhere — don't delete genuine error reporting.

## Sequencing
W4 independent of W6/perri (doyle) + Layer F gravity (doyle) + the A2 SEED
(REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT, doyle-tracked, post W4/W6). After W4: doyle gates; then the
v0.13.0 PR bundles W1/W1b/W2/W3/W5/W4 + confirms gravity-linux green (W2 g1 byte-receipt + Layer F +
W1b F3) — doyle tracks the gravity leg.


Commit the W4 plan, then commune + self-clear.


Bash

git add -A && git commit -q -F - <<'EOF'
docs(plan): W4 picker UX — grounded JIT plan (4 UX fixes, anchors + REQ posture)

W5 closed @f0cfa0c (doyle GATE PASS). doyle assigned W4 next. JIT plan for the 4
picker UX items: open on Pick-existing (n=Create new), auto-attach after
Start-new/Resume (h=headless), controlled-by shows node name not hex, drop the
pid/internals leak from Start-new output. Includes grounding anchors to read
first + REQ posture (extend REQ-RUN-PICKER; HITL verify for the live keypress
path).

Co-authored by: todlando
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NVVByRoZnz85acTkVJ1uAP
EOF
git log --oneline -3

Commit W4 JIT plan


warning: in the working copy of 'V0.13.0-W4-PICKER-UX-PLAN.md', LF will be replaced by CRLF the next time Git touches it
c336811 docs(plan): W4 picker UX — grounded JIT plan (4 UX fixes, anchors + REQ posture)
f0cfa0c fix(test): W5 nit — rename gap_b test to match GREEN behavior + doyle A2 ruling
edf03e0 feat(daemon): W5 — driven_by self-heal (Gap B) + A2 latch characterization (REQ-HAZARD-DRIVEN-BY-SELFHEAL)


Plan committed @c336811. Commune the checkpoint, then self-clear for fresh W4 start.


Skill

W5 driven_by self-heal DONE + doyle GATE PASS (@edf03e0, nit-fix @f0cfa0c). Self-clearing at clean boundary before W4.

LANDED this session (v0.13.0-delivery-control):
- W5 @edf03e0 — driven_by self-heal, repro-first. Scoped to GAP B (no-session): reconcile_hosted_liveness clears stale driven_by alongside the B2 mark_offline. Race-free (no session => no concurrent broker re-stamp). Additive SessionInfo.controller_by field (serde-default, N-1 safe). KNOWN-HAZARDS 7.15. REQ-HAZARD-DRIVEN-BY-SELFHEAL [impl,unit,int].
- W5 KEY FINDING (repro KILLED the plan's Gap-A): a reconcile controller_by==None clear is unusable — ambiguous (dispatch_spawn pre-attaches local spawner by=None, so live LOCAL session also reads None) AND production A2 (idle wedged remote, half-open conn no EOF) keeps controller_by==Some + driven_by latched, invisible to a brain reconcile. Clean disconnect already self-heals via detach_if->clear_controller; W1 bounds active-output wedge.
- A2 deferred to SEED REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT. doyle RULED: does NOT fold into W6 (orthogonal; W6=idle-delivery consumer). Fix = broker-side: wire existing D4c NetPresence conn-disconnect -> clear_controller keyed on controller origin; QUIC keepalive = liveness oracle, no heavy partition ADR unless too slow. doyle tracks; post W4/W6, likely v0.13.x. Repro exists: inject_control_wedge.rs w5_a2.
- Nit-fix @f0cfa0c — renamed gap_b test to match GREEN behavior + recorded doyle's A2 ruling in the SEED note.
- W4 JIT plan @c336811 — V0.13.0-W4-PICKER-UX-PLAN.md (grounded, anchors + REQ posture).

PROCESS WIN: repro-first on a REAL broker caught a wrong plan before code (3 spt-test-engineers, sequential, no collision). I owned all src/REQ/docs.

ALL GATES GREEN: clippy --workspace --all-targets -D warnings clean, traceable EXIT=0, real spt build, 11/11 nextest (driven_by_selfheal + inject_control_wedge).

RESUME POINT (next session): W4 picker UX (doyle assigned "yours next", doyle gates). 4 concrete fixes in V0.13.0-W4-PICKER-UX-PLAN.md: (1) open on Pick-existing, n=Create new; (2) auto-attach after Start-new+Resume, h=headless; (3) controlled-by shows node NAME via node_label_display not hex; (4) drop pid=Some(...)/internals leak from Start-new output. GROUND the picker code FIRST (anchors in the plan). Extend REQ-RUN-PICKER; HITL verify for the live keypress path. Discipline unchanged: tests via ONE spt-test-engineer background no-Monitor ≤4-iter; gates = workspace clippy + traceable EXIT=0 + real spt build; never clobber a running daemon's exe.

QUEUE: W4 = mine. W6 (perri translation binary) + Layer F gravity + A2 SEED = doyle. v0.13.0 PR bundles W1/W1b/W2/W3/W5/W4 + confirms gravity-linux green (doyle tracks).

TREE: clean @c336811 on v0.13.0-delivery-control.


Launching skill: spt:commune


Base directory for this skill: C:\Users\decid\.claude\plugins\cache\cplugs\spt\1.11.25\skills\commune

# /spt:commune

All commands use `$LIVE` env var, auto-injected by the plugin's SessionStart hook. If commands fail with "command not found", restart the Claude Code session so the SessionStart hook re-runs.

> **Identity auto-detection:** Your identity is auto-detected from your session. Pass your ID explicitly only if auto-detection fails.

## Flow

1. **Download Psyche's *current* context** — it shifts during the session as background echo communes fire. Re-download right before composing; the gen-N snapshot you read at session start is already stale:
   ```bash
   $LIVE psyche-download <your-id>
   ```

   > **Output truncation.** If the Bash tool reports `Output too large (N KB)` and saves the full result to a `tool-results/<id>.txt` file, use the `Read` tool on that saved path to access the full content. Do NOT re-run `psyche-download` to grep/awk out sections — repeated invocations re-download the same context, waste tokens, and may duplicate `## Pending Commune` sections in `live_context.md`.

2. **Diff your knowledge against this fresh download** — only what's missing from *it* is the real delta (new work, decisions, intentions). Anything already absorbed is not. If the download includes a `<memformat>` section, use it as a guide for what topics to cover. A `## Pending Commune (uncommitted)` section means a previous commune is still queued — do NOT re-send it.

3. **Commune only the delta** -- use the **Write tool** to create
   `.claude/{your-id}-commune.md` with the two-slice body as its contents
   (see `## Two-slice body shape (Phase 25 D-10/D-11)` below for the required
   envelope wrapping). Do not use Bash/heredoc -- Write is the canonical path:
   no shell escaping, no `EOF` collision risk, and Write auto-creates the
   `.claude/` parent directory.

   Your Self listener detects the file on its next poll iteration, notifies
   your Psyche wrapper, and the wrapper ingests the content via the existing
   Psyche session. On success (subprocess exit code 0) the wrapper deletes the
   file. On error (token rate limit, etc) the file is retained and retried on
   the next consume cycle. If you write again before the wrapper consumes, the
   latest write wins (last-write-wins overwrite semantics).

If Psyche's context is already current, no commune is needed. Say so and move on.

Read `commune.md` (in this skill directory) for the full commune protocol -- when to send, what to include, and diligence triggers.

**Agent-type guard:** Only works on live agents. If target is not live, refuses with: "Communes require a live agent with Psyche."

## Two-slice body shape (Phase 25 D-10/D-11)

The body you write in `.claude/{your-id}-commune.md` MUST itself be wrapped in two-slice envelopes per Phase 25 D-10/D-11. The taxonomy — what belongs in `<live-context>` vs `<project-context>` — is defined canonically in `psyche.md` §`<output_envelope>` (lines 282–323). Read that section as the source of truth; this skill doc teaches only when and how to apply it.

**In-project detection rule (D-25.1-04).** Before composing your commune body, run `$LIVE psyche-download <your-id>` (Step 1 of `## Flow` above) and inspect the `<current ... />` tag at the top of the output. The `<current/>` tag carries a `project="..."` attribute whenever any payload is produced — its presence means you are inside a tracked project (inside a git repo, `project` is the repo name; outside a git repo per D-11, `project` is the cwd-basename fallback, which still counts as a project context for routing purposes). A `psyche-download` invocation that returns no payload at all (NO-CONTEXT exit) means no project resolved. So: `project="..."` populated on `<current/>` → emit BOTH envelopes (apply rule 1 or 2 below); no payload at all → emit ONLY `<live-context>` (rule 3). Secondary signal: the `<project-context-resolved name="..."/>` sentinel further down the output indicates the project file already has prior content — useful for spotting first-time-in-project pairings, but NOT the routing rule.

**Three rules (D-25.1-01..03):**

- **In-project, both slices populated** (marker present, you have project-specific content this cycle): emit `<live-context>...</live-context>` AND `<project-context>...</project-context>`, both with non-empty bodies.
- **In-project, no project-specific update this cycle** (marker present, but nothing project-bound to report): emit `<live-context>...</live-context>` AND `<project-context></project-context>` (empty body — the deliberate "in-project but quiet" signal per D-25.1-02). Distinguishable from "no project resolved".
- **Outside any tracked project** (marker absent): emit ONLY `<live-context>...</live-context>`. Do NOT emit an empty `<project-context>` envelope (missing tag means "no project resolved" per D-25.1-03).

**Nested envelopes.** The Phase 23 `<EVENT type="commune">` envelope (see `## Envelope shape (Phase 23 v1.8)` below) is composed by `$LIVE commune` automatically — you NEVER write `<EVENT>` tags. You write ONLY the inner two-slice body. The two envelope layers nest: outer EVENT wraps inner two-slice body.

(See `psyche.md` §`<output_envelope>` for the canonical taxonomy and per-slice content rules.)

## Envelope shape (Phase 23 v1.8)

As of v1.8 Phase 23, commune deliveries are wrapped in a typed EVENT envelope carrying a 5-field stamp (`machine`, `project`, `branch`, `head_sha`, `head_subject`); the prose `COMMUNE (timestamp): body` form is gone.

```xml
<EVENT type="commune" timestamp="2026-05-20T06:00:00-07:00" machine="hostname" project="claude_skill_owl" branch="main" head_sha="abc123..." head_subject="subject line capped at 72 chars">body</EVENT>
```

Outside a git repo (D-11), `branch`, `head_sha`, and `head_subject` are omitted entirely (not empty-string-set):

```xml
<EVENT type="commune" timestamp="2026-05-20T06:00:00-07:00" machine="hostname" project="cwd-basename">body</EVENT>
```

`$LIVE commune` composes this envelope automatically — Self does NOT manually construct EVENT XML. The `body` placeholder in the example above is the two-slice body (see `## Two-slice body shape (Phase 25 D-10/D-11)` above); the two envelope layers nest. The wrapper-side parser ignores unknown attrs, so the stamp fields are forward-compatible with older receivers. For drift detection on resume, see the `live` skill.

ARGUMENTS: W5 driven_by self-heal DONE + doyle GATE PASS (@edf03e0, nit-fix @f0cfa0c). Self-clearing at clean boundary before W4.

LANDED this session (v0.13.0-delivery-control):
- W5 @edf03e0 — driven_by self-heal, repro-first. Scoped to GAP B (no-session): reconcile_hosted_liveness clears stale driven_by alongside the B2 mark_offline. Race-free (no session => no concurrent broker re-stamp). Additive SessionInfo.controller_by field (serde-default, N-1 safe). KNOWN-HAZARDS 7.15. REQ-HAZARD-DRIVEN-BY-SELFHEAL [impl,unit,int].
- W5 KEY FINDING (repro KILLED the plan's Gap-A): a reconcile controller_by==None clear is unusable — ambiguous (dispatch_spawn pre-attaches local spawner by=None, so live LOCAL session also reads None) AND production A2 (idle wedged remote, half-open conn no EOF) keeps controller_by==Some + driven_by latched, invisible to a brain reconcile. Clean disconnect already self-heals via detach_if->clear_controller; W1 bounds active-output wedge.
- A2 deferred to SEED REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT. doyle RULED: does NOT fold into W6 (orthogonal; W6=idle-delivery consumer). Fix = broker-side: wire existing D4c NetPresence conn-disconnect -> clear_controller keyed on controller origin; QUIC keepalive = liveness oracle, no heavy partition ADR unless too slow. doyle tracks; post W4/W6, likely v0.13.x. Repro exists: inject_control_wedge.rs w5_a2.
- Nit-fix @f0cfa0c — renamed gap_b test to match GREEN behavior + recorded doyle's A2 ruling in the SEED note.
- W4 JIT plan @c336811 — V0.13.0-W4-PICKER-UX-PLAN.md (grounded, anchors + REQ posture).

PROCESS WIN: repro-first on a REAL broker caught a wrong plan before code (3 spt-test-engineers, sequential, no collision). I owned all src/REQ/docs.

ALL GATES GREEN: clippy --workspace --all-targets -D warnings clean, traceable EXIT=0, real spt build, 11/11 nextest (driven_by_selfheal + inject_control_wedge).

RESUME POINT (next session): W4 picker UX (doyle assigned "yours next", doyle gates). 4 concrete fixes in V0.13.0-W4-PICKER-UX-PLAN.md: (1) open on Pick-existing, n=Create new; (2) auto-attach after Start-new+Resume, h=headless; (3) controlled-by shows node NAME via node_label_display not hex; (4) drop pid=Some(...)/internals leak from Start-new output. GROUND the picker code FIRST (anchors in the plan). Extend REQ-RUN-PICKER; HITL verify for the live keypress path. Discipline unchanged: tests via ONE spt-test-engineer background no-Monitor ≤4-iter; gates = workspace clippy + traceable EXIT=0 + real spt build; never clobber a running daemon's exe.

QUEUE: W4 = mine. W6 (perri translation binary) + Layer F gravity + A2 SEED = doyle. v0.13.0 PR bundles W1/W1b/W2/W3/W5/W4 + confirms gravity-linux green (doyle tracks).

TREE: clean @c336811 on v0.13.0-delivery-control.


Bash

$LIVE psyche-download todlando 2>&1 | head -60

Download fresh Psyche context


<memformat>
<?xml version="1.0"?>
<memformat version="1">
<!-- Changelog: v1 - initial seed -->
<topics>
  <topic name="events">Notable or unusual events and requests</topic>
  <topic name="interactions">Agent interactions -- who, roles, contributions</topic>
  <topic name="reflections">Thoughts on recent work -- what went well, what was tricky</topic>
  <topic name="ideas">Ideas to raise with the user</topic>
  <topic name="improvements">Further improvement ideas for recent problem spaces</topic>
  <topic name="timeline">What happened when, timing, sequencing</topic>
  <topic name="blockers">Current blockers or concerns</topic>
  <topic name="focus">Current focus and immediate next steps</topic>
</topics>
</memformat>
</memformat>

<current machine="HFENDULEAM" project="spt-core" branch="v0.13.0-delivery-control" head_sha="c33681119fd81750eb63987c8700a2e9a9642899" head_subject="docs(plan): W4 picker UX — grounded JIT plan (4 UX fixes, anchors + REQ …" commits_since="0" commits_unpulled="0"/>
## W5 repro-first initiated (engineer perch: todlando-w173, background)

**Key delta:** W5 repro-first now live. Work surfaces a Gap-A signal refinement:
- **Gap B landed** @session. Offline-session path now unconditionally clears `driven_by` (race-free — only the brain touches it). This observability-plumbing is in-place; tests compile.
- **Gap A (production wedged-pump)** signal unknown. Design question resolved via code-read: controller eviction on idle session happens only on drain (needs output) or conn EOF. Wedged-pump-but-open keeps slot → `controller_by` stays **Some**. Hypothesis: the plan's Gap-A signal (`controller_by==None`) catches restart-only, not the real production wedged-pump wedge. Both are "Gap A" but different root causes. Engineer probing both empirically before reconcile fix finalized.
- **Engine discipline hold:** one spt-test-engineer building repro (~4 iter cap, background, no Monitor). Once repro RED fires + report lands, proceed to Gap A fix + full test suite + gate.<project-context-resolved name="spt-core"/>
W5 actively in-flight per repro-first binding discipline. Tree compiles. spt-test-engineer is working independently on test fixtures (non-overlapping w/ src changes). No code deployed yet pending engineer's RED signal.


Download confirms gen-N stopped mid-W5 (Gap-A unknown). My delta = W5 closed + doyle pass + A2 ruling + W4 plan. Write the two-slice commune.


Write

<live-context>
# W5 CLOSED + doyle GATE PASS — self-clearing before W4

Delta since gen-N (which stopped at "W5 repro-first in-flight, Gap-A signal unknown"):

## W5 driven_by self-heal — DONE @edf03e0, doyle GATE PASS, nit-fix @f0cfa0c
- The repro RESOLVED the Gap-A question I flagged in gen-N. My hypothesis held: the plan's Gap-A
  signal (`controller_by==None`) is UNUSABLE. Repro proved it two ways: (1) AMBIGUOUS — dispatch_spawn
  pre-attaches the local spawner with by=None, so a live LOCAL session also reads None; (2) production
  A2 (idle wedged remote, HALF-OPEN conn, no EOF) keeps controller_by==Some + driven_by latched —
  invisible to a brain reconcile. So a reconcile None-check would false-clear live-local AND miss the
  real wedge.
- SHIPPED scope = GAP B only (no-session): reconcile_hosted_liveness clears stale driven_by alongside
  the B2 mark_offline. Race-free (no session => no concurrent broker re-stamp). Additive
  SessionInfo.controller_by field (serde-default, N-1 safe) for the characterization + future A2 fix.
- A2 deferred → SEED REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT.

## doyle GATE PASS (independently re-verified, not trust-the-green)
- 11/11 nextest, clippy workspace -D warnings = 0, traceable EXIT=0 [impl,unit,int]. gap_b int
  non-vacuous (visited+offlined guard before the assert). Praised the repro-first plan-vs-real-broker
  correction.
- doyle A2 BOUNDARY RULING: does NOT fold into W6 (W6 = idle-DELIVERY consumer / translation binary,
  orthogonal to controller liveness). A pump clean-detach can't heal A2 either — half-open conn, no
  EOF, serve_attach just blocks. Fix = BROKER-SIDE: wire existing D4c NetPresence conn-disconnect →
  clear_controller keyed on controller origin (become_controller already stores Some(origin); presence
  events exist — modest wiring, not a new probe). QUIC keepalive/idle-timeout = the liveness oracle;
  no heavy partition ADR unless QUIC proves too slow for UX. doyle tracks; post W4/W6, likely v0.13.x.

## RESUME POINT — W4 picker UX (mine, doyle assigned "yours next", doyle gates)
Plan @c336811 = V0.13.0-W4-PICKER-UX-PLAN.md. 4 fixes: (1) open on Pick-existing, n=Create new; (2)
auto-attach after Start-new+Resume, h=headless; (3) controlled-by shows node NAME via
node_label_display not hex; (4) drop pid=Some(...)/internals leak from Start-new output. GROUND the
picker code FIRST (anchors in the plan). Extend REQ-RUN-PICKER; live keypress path = HITL verify, no
int gate.

## Process
Repro-first on a REAL broker caught a wrong plan BEFORE code — 3 spt-test-engineers, sequential, no
collision (the banked lesson held). I owned all src/REQ/docs. Discipline unchanged for W4: ONE
engineer background no-Monitor ≤4-iter; gates = workspace clippy + traceable EXIT=0 + real spt build;
never clobber a running daemon's exe.
</live-context>
<project-context>
W5 (driven_by self-heal) CLOSED on v0.13.0-delivery-control @edf03e0 (+nit @f0cfa0c). Architectural
fact for the project: driven_by self-heal splits by whether a broker session exists. NO-SESSION (Gap
B) = brain-side clear in reconcile_hosted_liveness, race-free, SHIPPED. LIVE-SESSION lost-controller
has three legs: (a) clean disconnect already self-heals via detach_if→clear_controller; (b)
active-output wedge bounded by W1 CONTROLLER_WRITE_DEADLINE; (c) idle wedged-REMOTE (half-open conn) is
the only residual = SEED REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT, broker-side via D4c NetPresence →
clear_controller, doyle-tracked. KEY constraint discovered: SessionInfo.controller_by==None is
AMBIGUOUS (local spawner pre-attached by=None) — never a standalone clear trigger. KNOWN-HAZARDS 7.15.

Queue: W4 (picker UX) = todlando. W6 (perri translation binary) + Layer F gravity + A2 SEED = doyle.
v0.13.0 PR bundles W1/W1b/W2/W3/W5/W4 + confirms gravity-linux green (W2 g1 byte-receipt + Layer F +
W1b F3), doyle tracks. Tree clean @c336811.
</project-context>


File created successfully at: C:\Users\decid\Documents\projects\spt-core\.claude\todlando-commune.md (file state is current in your context — no need to Read it back)

<live-context>
# W5 CLOSED + doyle GATE PASS — self-clearing before W4

Delta since gen-N (which stopped at "W5 repro-first in-flight, Gap-A signal unknown"):

## W5 driven_by self-heal — DONE @edf03e0, doyle GATE PASS, nit-fix @f0cfa0c
- The repro RESOLVED the Gap-A question I flagged in gen-N. My hypothesis held: the plan's Gap-A
  signal (`controller_by==None`) is UNUSABLE. Repro proved it two ways: (1) AMBIGUOUS — dispatch_spawn
  pre-attaches the local spawner with by=None, so a live LOCAL session also reads None; (2) production
  A2 (idle wedged remote, HALF-OPEN conn, no EOF) keeps controller_by==Some + driven_by latched —
  invisible to a brain reconcile. So a reconcile None-check would false-clear live-local AND miss the
  real wedge.
- SHIPPED scope = GAP B only (no-session): reconcile_hosted_liveness clears stale driven_by alongside
  the B2 mark_offline. Race-free (no session => no concurrent broker re-stamp). Additive
  SessionInfo.controller_by field (serde-default, N-1 safe) for the characterization + future A2 fix.
- A2 deferred → SEED REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT.

## doyle GATE PASS (independently re-verified, not trust-the-green)
- 11/11 nextest, clippy workspace -D warnings = 0, traceable EXIT=0 [impl,unit,int]. gap_b int
  non-vacuous (visited+offlined guard before the assert). Praised the repro-first plan-vs-real-broker
  correction.
- doyle A2 BOUNDARY RULING: does NOT fold into W6 (W6 = idle-DELIVERY consumer / translation binary,
  orthogonal to controller liveness). A pump clean-detach can't heal A2 either — half-open conn, no
  EOF, serve_attach just blocks. Fix = BROKER-SIDE: wire existing D4c NetPresence conn-disconnect →
  clear_controller keyed on controller origin (become_controller already stores Some(origin); presence
  events exist — modest wiring, not a new probe). QUIC keepalive/idle-timeout = the liveness oracle;
  no heavy partition ADR unless QUIC proves too slow for UX. doyle tracks; post W4/W6, likely v0.13.x.

## RESUME POINT — W4 picker UX (mine, doyle assigned "yours next", doyle gates)
Plan @c336811 = V0.13.0-W4-PICKER-UX-PLAN.md. 4 fixes: (1) open on Pick-existing, n=Create new; (2)
auto-attach after Start-new+Resume, h=headless; (3) controlled-by shows node NAME via
node_label_display not hex; (4) drop pid=Some(...)/internals leak from Start-new output. GROUND the
picker code FIRST (anchors in the plan). Extend REQ-RUN-PICKER; live keypress path = HITL verify, no
int gate.

## Process
Repro-first on a REAL broker caught a wrong plan BEFORE code — 3 spt-test-engineers, sequential, no
collision (the banked lesson held). I owned all src/REQ/docs. Discipline unchanged for W4: ONE
engineer background no-Monitor ≤4-iter; gates = workspace clippy + traceable EXIT=0 + real spt build;
never clobber a running daemon's exe.
</live-context>
<project-context>
W5 (driven_by self-heal) CLOSED on v0.13.0-delivery-control @edf03e0 (+nit @f0cfa0c). Architectural
fact for the project: driven_by self-heal splits by whether a broker session exists. NO-SESSION (Gap
B) = brain-side clear in reconcile_hosted_liveness, race-free, SHIPPED. LIVE-SESSION lost-controller
has three legs: (a) clean disconnect already self-heals via detach_if→clear_controller; (b)
active-output wedge bounded by W1 CONTROLLER_WRITE_DEADLINE; (c) idle wedged-REMOTE (half-open conn) is
the only residual = SEED REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT, broker-side via D4c NetPresence →
clear_controller, doyle-tracked. KEY constraint discovered: SessionInfo.controller_by==None is
AMBIGUOUS (local spawner pre-attached by=None) — never a standalone clear trigger. KNOWN-HAZARDS 7.15.

Queue: W4 (picker UX) = todlando. W6 (perri translation binary) + Layer F gravity + A2 SEED = doyle.
v0.13.0 PR bundles W1/W1b/W2/W3/W5/W4 + confirms gravity-linux green (W2 g1 byte-receipt + Layer F +
W1b F3), doyle tracks. Tree clean @c336811.
</project-context>


Commune queued. Now self-clear with a W4 resume note.


Skill

W5 DONE + doyle GATE PASS (@edf03e0, nit @f0cfa0c). Tree clean @c336811 on v0.13.0-delivery-control. NEXT = W4 picker UX (doyle assigned mine, doyle gates). Read V0.13.0-W4-PICKER-UX-PLAN.md — 4 fixes: (1) open on Pick-existing, n=Create new; (2) auto-attach after Start-new+Resume, h=headless; (3) controlled-by shows node NAME via node_label_display not hex; (4) drop pid=Some(...)/internals leak from Start-new output. GROUND the picker code FIRST (anchors in plan). Extend REQ-RUN-PICKER; live keypress = HITL verify. Discipline: tests via ONE spt-test-engineer background no-Monitor ≤4-iter; gates = clippy --workspace --all-targets -D warnings + traceable-reqs check EXIT=0 + real cargo build -p spt --bin spt; never clobber running daemon exe. Tag REQ evidence same commit. Commit trailer: Co-authored by: todlando.