---
name: w5-driven-by-selfheal
description: W5 driven_by self-heal — CONVERGED — ships Gap B only (GREEN, unit+int); Gap A reconcile clear REJECTED (controller_by==None ambiguous) reframed as invariant; A2 deferred to REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT.
metadata:
  type: project
---

REQ-HAZARD-DRIVEN-BY-SELFHEAL repro lives in `crates/spt-daemon/tests/driven_by_selfheal.rs`
(real broker + real PTY, calls `reconcile_hosted_liveness` directly with a broker-derived
live-session set; process-global SPT_HOME via OnceLock, nextest-only runner contract).

**Why this matters for future test work on W5/picker-latch:**

- **Gap B (sessionless perch) was ALREADY FIXED** on branch `v0.13.0-delivery-control` when
  the repro was written (2026-06-19) — `reconcile_hosted_liveness` (livehost.rs ~538) already
  clears `driven_by` via `set_driven_by(perch,None)` alongside `mark_offline`, logging
  `DRIVEN_BY_SELFHEAL_OFFLINE`. The plan/prompt expected RED; reality was GREEN. The Gap-B test
  pins the GREEN behavior (do NOT re-encode the latched expectation).
  **How to apply:** before writing a "repro-first RED" test, RUN it against the current tree —
  the fix may already be landed on the delivery branch. Surface the deviation, don't fake RED.

- **Gap A reconcile-side clear is REJECTED (CONVERGED 2026-06-19) — NOT a bug-to-fix.** W5
  ships Gap B ONLY. The reconcile MUST NOT clear `driven_by` for a live-session perch off a
  `controller_by==None` read (ambiguous with a live LOCAL controller → would false-clear). The
  Gap-A int test in `driven_by_selfheal.rs` was reframed from a RED latch to a GREEN INVARIANT
  test: name is now `gap_a_live_session_controller_by_is_ambiguous_so_reconcile_must_not_clear`,
  assertion stays `driven_by_after==Some(node)` but framed as correct. The genuine production
  residual (A2: idle abandoned-REMOTE controller, keeps `controller_by==Some`) is its own
  SEED/DEFERRED req `REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT` (broker-side controller eviction).
  REQ-HAZARD-DRIVEN-BY-SELFHEAL is now `["impl","unit","int"]`; Gap-B has both a `[unit]` in
  livehost.rs `pull_liveness_marks_sessionless_spt_hosted_offline_only` (extended to seed
  driven_by on dead/alive/relay; dead clears, alive+relay untouched) and the `[int]` in the
  driven_by_selfheal.rs Gap-B test.

- **controller_by==None is AMBIGUOUS (item 3 finding):** `dispatch_spawn` pre-attaches the
  spawner as the LOCAL controller with `by=None`, so `OutputLog::controller_by()` returns None
  even for a LIVE, locally-controlled session. So `SessionInfo.controller_by==None` cannot
  distinguish a lost controller from a live local one. NO path observed where driven_by is Some
  while controller_by is Some. The W5 fix must NOT false-clear on controller_by==None alone —
  prefer routing the live-session clear through the broker (single writer, can see an
  occupied-but-local slot).
  **How to apply:** any future Gap-A GREEN test must include a still-genuinely-controlled live
  session that must NOT clear (the false-heal guard).

- **The broker self-heals driven_by on an OBSERVED controller disconnect:** dropping the
  controller socket triggers per-conn `detach_if`→`clear_controller`→`stamp_driven_by`, which
  re-stamps driven_by=None. So a plain socket-drop does NOT reproduce a reconcile Gap-A — the
  genuine Gap-A is the brain-restart leftover where that re-stamp NEVER fired (re-stamp the
  stale marker on disk to reproduce it; a fresh in-process Broker has NO sessions to re-adopt,
  so the literal "fresh broker over stale perch" cannot be built at the in-process API level).
