1388	    /// `next_seq` reject-gap dedup: it accepts the contiguous seq 0, then REJECTS a
1389	    /// forward jump as an `output gap`. This is the behavior `attach_skip_to_live`
1390	    /// must FLIP — proving the next test isn't vacuously green.
1391	    ///
1392	    /// Non-vacuous: if the legacy path ever stopped rejecting forward gaps, this
1393	    /// would go RED — and the contrast with the skip-to-live test would collapse.
1394	    // [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]
1395	    #[test]
1396	    fn cold_brain_still_rejects_a_forward_output_gap() {
1397	        let sid = 1u64;
1398	        let (mut brain, stub) = brain_with_stub();
1399
1400	        // Cold brain: empty session_cursors, next_seq=0 → legacy reject-gap path.
1401	        // Contiguous seq 0 is accepted and advances the cursor.
1402	        stub.feed.send(output_envelope(sid, 0, b"hello")).unwrap();
1403	        match brain.read_event().expect("seq 0 accepted") {
1404	            BrokerEvent::Output { seq, bytes, .. } => {
1405	                assert_eq!(seq, 0);
1406	                assert_eq!(bytes, b"hello");
1407	            }
1408	            other => panic!("expected Output(0), got {other:?}"),
1409	        }
1410
1411	        // A FORWARD JUMP (seq 5, want 1) is a hard `output gap` error on the legacy
1412	        // path — a cold brain treats a skipped seq as a lost chunk, never silently.
1413	        stub.feed.send(output_envelope(sid, 5, b"jumped")).unwrap();
1414	        let err = brain.read_event().expect_err("a cold brain rejects a forward gap");
1415	        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
1416	        assert!(
1417	            err.to_string().contains("output gap"),
1418	            "the legacy reject-gap path surfaces an `output gap` (got: {err})"
1419	        );
1420	    }
1421
1422	    /// KEYSTONE — after `attach_skip_to_live`, a subsequent post-eviction FORWARD
1423	    /// JUMP output seq (>> the prior cursor, the broker's live ring-tail after the
1424	    /// empty `from_seq=MAX` replay) is ACCEPTED via the snap-above dedup path, NOT
1425	    /// rejected as an `output gap` and NOT dedup-dropped. `attach_skip_to_live` is
1426	    /// what flips the brain from the legacy reject-gap path (the previous test) into
1427	    /// the resume-mode dedup-below + snap-above path (it seeds `session_cursors` and
1428	    /// resets next_seq to 0).
1429	    ///
1430	    /// Non-vacuous (three distinct mutations caught):
1431	    ///   - Drop the `session_cursors.insert(session_id, 0)` in `attach_skip_to_live`
1432	    ///     → the map stays empty → the forward jump hits the legacy reject-gap path
1433	    ///     → `read_event` errors `output gap` instead of accepting → RED.
1434	    ///   - Leave `next_seq = u64::MAX` (the from_seq) instead of resetting to 0 AND
1435	    ///     route through the legacy path → every live seq < MAX is dedup-dropped →
1436	    ///     `read_event` would block (no event) → the test's bounded reader times out.
1437	    ///   - Snap-above mis-implemented as reject-gap (`seq > cursor` → Err) → RED.
1438	    // [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]
1439	    #[test]
1440	    fn skip_to_live_accepts_a_forward_seq_jump_via_snap_above() {
1441	        let sid = 1u64;
1442	        let (mut brain, stub) = brain_with_stub();
1443
1444	        // The viewer was evicted; the serving brain skips to live. This seeds
1445	        // session_cursors[sid]=0 (snap-above armed) and next_seq=0, and writes a
1446	        // subscribe(from_seq=MAX) the stub silently drains.
1447	        brain
1448	            .attach_skip_to_live(sid, Some("operator-node"))
1449	            .expect("attach_skip_to_live");
1450	        assert_eq!(
1451	            brain.session_cursor(sid),
1452	            Some(0),
1453	            "attach_skip_to_live arms the snap-above dedup cursor at 0 (resume mode)"
1454	        );
1455
1456	        // The first LIVE frame after the empty MAX-replay carries the broker's
1457	        // current ring-tail seq — a FORWARD JUMP from the seeded 0. Snap-above must
1458	        // ACCEPT it (the broker's Mutex-held replay cannot reorder, so a forward
1459	        // jump is only ever a legitimate post-eviction ring-floor clamp).
1460	        stub.feed.send(output_envelope(sid, 12, b"LIVE-A")).unwrap();
1461	        match brain.read_event().expect("post-eviction forward jump accepted") {
1462	            BrokerEvent::Output { seq, bytes, .. } => {
1463	                assert_eq!(seq, 12, "the forward-jumped live seq is accepted, not rejected");
1464	                assert_eq!(bytes, b"LIVE-A");
1465	            }
1466	            other => panic!("expected Output(12) via snap-above, got {other:?}"),
1467	        }
1468	        assert_eq!(
1469	            brain.session_cursor(sid),
1470	            Some(13),
1471	            "snap-above advances the cursor to seq+1 after accepting the jump"
1472	        );
1473
1474	        // And from there it tracks contiguously: seq 13 accepted, the boundary
1475	        // re-send (seq 12, already delivered) is DEDUPED (not a gap, not re-emitted).
1476	        stub.feed.send(output_envelope(sid, 13, b"LIVE-B")).unwrap();
1477	        stub.feed.send(output_envelope(sid, 12, b"DUP")).unwrap(); // dedup
1478	        stub.feed.send(output_envelope(sid, 14, b"LIVE-C")).unwrap();
1479	        let mut accepted = Vec::new();
1480	        for _ in 0..2 {
1481	            match brain.read_event().expect("subsequent live frames") {
1482	                BrokerEvent::Output { seq, bytes, .. } => accepted.push((seq, bytes)),
1483	                other => panic!("expected Output, got {other:?}"),
1484	            }
1485	        }
1486	        assert_eq!(
1487	            accepted,
1488	            vec![(13u64, b"LIVE-B".to_vec()), (14u64, b"LIVE-C".to_vec())],
1489	            "after the jump the brain tracks contiguously and DEDUPS the boundary \
1490	             re-send (seq 12) — exactly-once preserved across skip-to-live"
1491	        );
1492	    }
1493	}
1494

    /// `next_seq` reject-gap dedup: it accepts the contiguous seq 0, then REJECTS a
    /// forward jump as an `output gap`. This is the behavior `attach_skip_to_live`
    /// must FLIP — proving the next test isn't vacuously green.
    ///
    /// Non-vacuous: if the legacy path ever stopped rejecting forward gaps, this
    /// would go RED — and the contrast with the skip-to-live test would collapse.
    // [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]
    #[test]
    fn cold_brain_still_rejects_a_forward_output_gap() {
        let sid = 1u64;
        let (mut brain, stub) = brain_with_stub();

        // Cold brain: empty session_cursors, next_seq=0 → legacy reject-gap path.
        // Contiguous seq 0 is accepted and advances the cursor.
        stub.feed.send(output_envelope(sid, 0, b"hello")).unwrap();
        match brain.read_event().expect("seq 0 accepted") {
            BrokerEvent::Output { seq, bytes, .. } => {
                assert_eq!(seq, 0);
                assert_eq!(bytes, b"hello");
            }
            other => panic!("expected Output(0), got {other:?}"),
        }

        // A FORWARD JUMP (seq 5, want 1) is a hard `output gap` error on the legacy
        // path — a cold brain treats a skipped seq as a lost chunk, never silently.
        stub.feed.send(output_envelope(sid, 5, b"jumped")).unwrap();
        let err = brain.read_event().expect_err("a cold brain rejects a forward gap");
        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
        assert!(
            err.to_string().contains("output gap"),
            "the legacy reject-gap path surfaces an `output gap` (got: {err})"
        );
    }

    /// KEYSTONE — after `attach_skip_to_live`, a subsequent post-eviction FORWARD
    /// JUMP output seq (>> the prior cursor, the broker's live ring-tail after the
    /// empty `from_seq=MAX` replay) is ACCEPTED via the snap-above dedup path, NOT
    /// rejected as an `output gap` and NOT dedup-dropped. `attach_skip_to_live` is
    /// what flips the brain from the legacy reject-gap path (the previous test) into
    /// the resume-mode dedup-below + snap-above path (it seeds `session_cursors` and
    /// resets next_seq to 0).
    ///
    /// Non-vacuous (three distinct mutations caught):
    ///   - Drop the `session_cursors.insert(session_id, 0)` in `attach_skip_to_live`
    ///     → the map stays empty → the forward jump hits the legacy reject-gap path
    ///     → `read_event` errors `output gap` instead of accepting → RED.
    ///   - Leave `next_seq = u64::MAX` (the from_seq) instead of resetting to 0 AND
    ///     route through the legacy path → every live seq < MAX is dedup-dropped →
    ///     `read_event` would block (no event) → the test's bounded reader times out.
    ///   - Snap-above mis-implemented as reject-gap (`seq > cursor` → Err) → RED.
    // [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]
    #[test]
    fn skip_to_live_accepts_a_forward_seq_jump_via_snap_above() {
        let sid = 1u64;
        let (mut brain, stub) = brain_with_stub();

        // The viewer was evicted; the serving brain skips to live. This seeds
        // session_cursors[sid]=0 (snap-above armed) and next_seq=0, and writes a
        // subscribe(from_seq=MAX) the stub silently drains.
        brain
            .attach_skip_to_live(sid, Some("operator-node"))
            .expect("attach_skip_to_live");
        assert_eq!(
            brain.session_cursor(sid),
            Some(0),
            "attach_skip_to_live arms the snap-above dedup cursor at 0 (resume mode)"
        );

        // The first LIVE frame after the empty MAX-replay carries the broker's
        // current ring-tail seq — a FORWARD JUMP from the seeded 0. Snap-above must
        // ACCEPT it (the broker's Mutex-held replay cannot reorder, so a forward
        // jump is only ever a legitimate post-eviction ring-floor clamp).
        stub.feed.send(output_envelope(sid, 12, b"LIVE-A")).unwrap();
        match brain.read_event().expect("post-eviction forward jump accepted") {
            BrokerEvent::Output { seq, bytes, .. } => {
                assert_eq!(seq, 12, "the forward-jumped live seq is accepted, not rejected");
                assert_eq!(bytes, b"LIVE-A");
            }
            other => panic!("expected Output(12) via snap-above, got {other:?}"),
        }
        assert_eq!(
            brain.session_cursor(sid),
            Some(13),
            "snap-above advances the cursor to seq+1 after accepting the jump"
        );

        // And from there it tracks contiguously: seq 13 accepted, the boundary
        // re-send (seq 12, already delivered) is DEDUPED (not a gap, not re-emitted).
        stub.feed.send(output_envelope(sid, 13, b"LIVE-B")).unwrap();
        stub.feed.send(output_envelope(sid, 12, b"DUP")).unwrap(); // dedup
        stub.feed.send(output_envelope(sid, 14, b"LIVE-C")).unwrap();
        let mut accepted = Vec::new();
        for _ in 0..2 {
            match brain.read_event().expect("subsequent live frames") {
                BrokerEvent::Output { seq, bytes, .. } => accepted.push((seq, bytes)),
                other => panic!("expected Output, got {other:?}"),
            }
        }
        assert_eq!(
            accepted,
            vec![(13u64, b"LIVE-B".to_vec()), (14u64, b"LIVE-C".to_vec())],
            "after the jump the brain tracks contiguously and DEDUPS the boundary \
             re-send (seq 12) — exactly-once preserved across skip-to-live"
        );
    }
}


Brain tests are strong — non-vacuous, contrast legacy reject-gap vs snap-above, verify dedup. Let me check the `brain_with_stub` helper (was it newly invented?) and the broker tests:


Bash

grep -n "fn brain_with_stub\|struct.*[Ss]tub\|brain_with_stub" crates/spt-daemon/src/brain.rs | head; echo "=== is brain_with_stub new? (git diff) ==="; git diff crates/spt-daemon/src/brain.rs | grep -c "brain_with_stub\|StubFeed\|fn brain_with_stub"

Check if brain_with_stub is new scaffolding


1338:    struct StubBroker {
1351:    fn brain_with_stub() -> (Brain, StubBroker) {
1398:        let (mut brain, stub) = brain_with_stub();
1442:        let (mut brain, stub) = brain_with_stub();
=== is brain_with_stub new? (git diff) ===
3


`brain_with_stub`/`StubBroker` is new scaffolding. Let me check it's sound and not duplicating existing brain-test infra:


1320	    /// Unique namespaced socket name per test (parallel test threads never collide).
1321	    fn unique_name() -> String {
1322	        static SEQ: AtomicU32 = AtomicU32::new(0);
1323	        format!(
1324	            "spt-daemon-brain-ut-{}-{}.sock",
1325	            std::process::id(),
1326	            SEQ.fetch_add(1, Ordering::Relaxed)
1327	        )
1328	    }
1329
1330	    /// A real `Brain` over an in-process socket pair, with a server-side stub that
1331	    /// the test drives frame-by-frame. NO broker logic, NO PTY, NO network: the
1332	    /// stub only completes the hello handshake, DRAINS anything the brain writes
1333	    /// (subscribe frames), and writes whatever envelopes the test feeds it. This is
1334	    /// the brain's REAL `read_event` / `attach_skip_to_live` dedup state machine
1335	    /// exercised end-to-end over the real codec — the cheapest harness that still
1336	    /// drives the production paths (the integration tests in `tests/` use a real
1337	    /// broker; here we isolate the brain's dedup decision).
1338	    struct StubBroker {
1339	        /// Push an envelope for the stub to write to the brain.
1340	        feed: SyncSender<Envelope>,
1341	        /// Server thread handle (drains brain writes + writes fed frames).
1342	        _server: JoinHandle<()>,
1343	    }
1344
1345	    /// Stand up the stub + a connected cold-start `Brain`. The stub binds, the brain
1346	    /// connects (cold_start sends the hello), the stub recv_hello's it, then loops:
1347	    /// it owns the server `Stream` and writes any envelope the test feeds via `feed`.
1348	    /// (We never need to READ the brain's writes for these tests — the subscribe
1349	    /// `attach_skip_to_live` sends is harmlessly left in the OS socket buffer; the
1350	    /// frames are tiny. We drop the recv half so it does not hold resources.)
1351	    fn brain_with_stub() -> (Brain, StubBroker) {
1352	        let name = unique_name();
1353	        let listener = LocalSocketTransport::bind(&name).expect("bind stub listener");
1354
1355	        // The brain connects on a thread (cold_start blocks on connect+hello).
1356	        let connect_name = name.clone();
1357	        let brain_handle =
1358	            thread::spawn(move || Brain::cold_start(&connect_name, 1).expect("brain cold_start"));
1359
1360	        let mut server = listener.accept().expect("accept the brain");
1361	        crate::transport::recv_hello(&mut server, Role::Brain).expect("stub recv_hello");
1362	        let brain = brain_handle.join().expect("brain thread");
1363
1364	        // Split: the stub writes fed frames through the send half; the recv half is
1365	        // dropped (we never read the brain's subscribe frames — they sit in the
1366	        // socket buffer, which is fine for these tiny, low-volume tests).
1367	        let (recv, mut send) = server.split();
1368	        drop(recv);
1369	        let (feed, frames) = sync_channel::<Envelope>(64);
1370	        let server_thread = thread::spawn(move || {
1371	            while let Ok(env) = frames.recv() {
1372	                if write_frame(&mut send, &env).is_err() {
1373	                    break;
1374	                }
1375	            }
1376	        });
1377
1378	        (
1379	            brain,
1380	            StubBroker {
1381	                feed,
1382	                _server: server_thread,
1383	            },
1384	        )
1385	    }
1386
1387	    /// CONTROL — a COLD brain (it never skipped-to-live) keeps the strict legacy

    /// Unique namespaced socket name per test (parallel test threads never collide).
    fn unique_name() -> String {
        static SEQ: AtomicU32 = AtomicU32::new(0);
        format!(
            "spt-daemon-brain-ut-{}-{}.sock",
            std::process::id(),
            SEQ.fetch_add(1, Ordering::Relaxed)
        )
    }

    /// A real `Brain` over an in-process socket pair, with a server-side stub that
    /// the test drives frame-by-frame. NO broker logic, NO PTY, NO network: the
    /// stub only completes the hello handshake, DRAINS anything the brain writes
    /// (subscribe frames), and writes whatever envelopes the test feeds it. This is
    /// the brain's REAL `read_event` / `attach_skip_to_live` dedup state machine
    /// exercised end-to-end over the real codec — the cheapest harness that still
    /// drives the production paths (the integration tests in `tests/` use a real
    /// broker; here we isolate the brain's dedup decision).
    struct StubBroker {
        /// Push an envelope for the stub to write to the brain.
        feed: SyncSender<Envelope>,
        /// Server thread handle (drains brain writes + writes fed frames).
        _server: JoinHandle<()>,
    }

    /// Stand up the stub + a connected cold-start `Brain`. The stub binds, the brain
    /// connects (cold_start sends the hello), the stub recv_hello's it, then loops:
    /// it owns the server `Stream` and writes any envelope the test feeds via `feed`.
    /// (We never need to READ the brain's writes for these tests — the subscribe
    /// `attach_skip_to_live` sends is harmlessly left in the OS socket buffer; the
    /// frames are tiny. We drop the recv half so it does not hold resources.)
    fn brain_with_stub() -> (Brain, StubBroker) {
        let name = unique_name();
        let listener = LocalSocketTransport::bind(&name).expect("bind stub listener");

        // The brain connects on a thread (cold_start blocks on connect+hello).
        let connect_name = name.clone();
        let brain_handle =
            thread::spawn(move || Brain::cold_start(&connect_name, 1).expect("brain cold_start"));

        let mut server = listener.accept().expect("accept the brain");
        crate::transport::recv_hello(&mut server, Role::Brain).expect("stub recv_hello");
        let brain = brain_handle.join().expect("brain thread");

        // Split: the stub writes fed frames through the send half; the recv half is
        // dropped (we never read the brain's subscribe frames — they sit in the
        // socket buffer, which is fine for these tiny, low-volume tests).
        let (recv, mut send) = server.split();
        drop(recv);
        let (feed, frames) = sync_channel::<Envelope>(64);
        let server_thread = thread::spawn(move || {
            while let Ok(env) = frames.recv() {
                if write_frame(&mut send, &env).is_err() {
                    break;
                }
            }
        });

        (
            brain,
            StubBroker {
                feed,
                _server: server_thread,
            },
        )
    }

    /// CONTROL — a COLD brain (it never skipped-to-live) keeps the strict legacy


StubBroker is sound (real transport + cold_start + codec, stubs only broker logic — legit, no existing harness here). Now the broker tests:


2328	    /// loop), join the writer, then read the wire.
2329	    ///
2330	    /// Non-vacuous: the EVICTED case asserts a marker IS on the wire with the right
2331	    /// kind+session_id; the NORMAL case asserts NO frame follows (EOF). Drop the
2332	    /// `if evicted.load() { ... }` block in `viewer_writer` and the EVICTED assert
2333	    /// flips RED (no marker, the brain would never skip-to-live); emit the marker
2334	    /// unconditionally and the NORMAL assert flips RED (a clean teardown would
2335	    /// spuriously trigger a re-subscribe).
2336	    // [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]
2337	    #[test]
2338	    fn viewer_writer_emits_evicted_marker_only_on_an_evicted_close() {
2339	        // ── EVICTED close: flag true before tx drops → marker emitted. ──
2340	        {
2341	            let (send, client, _recv) = controller_socket_pair();
2342	            let (tx, rx) = sync_channel::<Envelope>(VIEWER_CHANNEL_DEPTH);
2343	            let evicted = Arc::new(AtomicBool::new(false));
2344	            let w_evicted = Arc::clone(&evicted);
2345	            let w_send = Arc::clone(&send);
2346	            // Empty initial batch (no size/replay needed for this assertion).
2347	            let writer = thread::spawn(move || viewer_writer(w_send, 7, Vec::new(), rx, w_evicted));
2348
2349	            // The drain's eviction sequence: mark THEN drop the sink (its `tx`).
2350	            evicted.store(true, Ordering::Release);
2351	            drop(tx);
2352	            writer.join().expect("evicted writer joins");
2353
2354	            let mut client = client;
2355	            let env = read_frame(&mut client).expect("an evicted close writes a final frame");
2356	            assert_eq!(
2357	                env.kind, KIND_VIEWER_EVICTED,
2358	                "an OVERFLOW-evicted viewer's writer emits a viewer_evicted marker as \
2359	                 its final frame (skip-to-live), DISTINCT from a session-exit EOF"
2360	            );
2361	            let ev: crate::msg::ViewerEvictedEvent =
2362	                serde_json::from_value(env.payload).expect("marker payload");
2363	            assert_eq!(ev.session_id, 7, "the marker names the evicted viewer's session");
2364	        }
2365
2366	        // ── NORMAL close: flag false → no marker; the client read hits EOF. ──
2367	        {
2368	            let (send, client, _recv) = controller_socket_pair();
2369	            let (tx, rx) = sync_channel::<Envelope>(VIEWER_CHANNEL_DEPTH);
2370	            let evicted = Arc::new(AtomicBool::new(false)); // stays false
2371	            let w_evicted = Arc::clone(&evicted);
2372	            let w_send = Arc::clone(&send);
2373	            let writer = thread::spawn(move || viewer_writer(w_send, 7, Vec::new(), rx, w_evicted));
2374
2375	            // A clean teardown: drop tx with the flag still false.
2376	            drop(tx);
2377	            writer.join().expect("normal writer joins");
2378	            // Drop the broker's send-half clone so the client read sees EOF (otherwise
2379	            // the half stays open and the blocking read would hang).
2380	            drop(send);
2381
2382	            assert!(
2383	                try_read_frame(client, Duration::from_secs(2)).is_none(),
2384	                "a NORMAL teardown (evicted=false) writes NOTHING — no spurious \
2385	                 viewer_evicted marker that would trigger an uncalled-for re-subscribe"
2386	            );
2387	        }
2388	    }
2389
2390	    /// (2) `OutputLog::append` overflow-eviction SETS the sink's `evicted` flag (so
2391	    /// the writer will signal skip-to-live) — an undrained viewer that overflows ends
2392	    /// up FLAGGED, not silently removed. We retain a clone of the sink's `evicted`
2393	    /// Arc before inserting it, so we can read the flag after `append` has removed the
2394	    /// sink from the map (and dropped its own Arc). Depth-1 viewer queue + a consumer
2395	    /// (the test) that never drains → the second append overflows → evicts.
2396	    ///
2397	    /// Non-vacuous: this is the only unit that proves `append` stores `true` on the
2398	    /// eviction path. Drop the `sink.evicted.store(true, Release)` line in `append`'s
2399	    /// eviction loop and the flag stays false → the writer would exit silently and a
2400	    /// real evicted viewer would NEVER skip-to-live (it would look like a clean
2401	    /// teardown). The companion assert (the sink is removed from the map) keeps the
2402	    /// session-protection invariant honest.
2403	    // [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]
2404	    #[test]
2405	    fn append_overflow_flags_the_evicted_viewer_for_skip_to_live() {
2406	        let mut log = OutputLog::new(1, DEFAULT_LOG_CHUNKS, String::new(), (24, 80));
2407	        // A viewer whose live queue is depth-1 and which we NEVER drain (rx held,
2408	        // unread) — it cannot keep up with the drain. We do NOT use `add_viewer`
2409	        // (which spawns a real writer + a generous channel); we hand-build the sink
2410	        // with a TINY channel and retain a clone of `evicted` so we can observe it
2411	        // after `append` drops the in-map sink.
2412	        let (vsend, _vclient, _vrecv) = controller_socket_pair();
2413	        let (vtx, _vrx) = sync_channel::<Envelope>(1);
2414	        let evicted = Arc::new(AtomicBool::new(false));
2415	        let observed = Arc::clone(&evicted); // survives the sink's removal
2416	        log.viewers.insert(
2417	            0,
2418	            ViewerSink {
2419	                tx: vtx,
2420	                send: vsend,
2421	                evicted,
2422	                _writer: thread::spawn(|| {}),
2423	            },
2424	        );
2425
2426	        // First append fits the depth-1 queue (no overflow, not evicted yet).
2427	        assert_eq!(log.append(b"chunk-0"), None, "no controller; first chunk fits");
2428	        assert!(
2429	            !observed.load(Ordering::Acquire),
2430	            "a viewer keeping within its queue is NOT flagged"
2431	        );
2432	        assert_eq!(log.viewers.len(), 1, "the viewer is still attached");
2433
2434	        // Second append OVERFLOWS the (still-undrained) depth-1 queue → eviction:
2435	        // the flag is SET (so the writer skips-to-live) and the sink is removed.
2436	        assert_eq!(log.append(b"chunk-1"), None, "no controller; eviction returns no ctrl job");
2437	        assert!(
2438	            observed.load(Ordering::Acquire),
2439	            "an overflow eviction must SET the sink's `evicted` flag BEFORE dropping \
2440	             it — this is what makes the writer emit the skip-to-live marker rather \
2441	             than dying silently (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT)"
2442	        );
2443	        assert!(
2444	            log.viewers.is_empty(),
2445	            "the overflowed viewer is removed from the map (session-protection: the \
2446	             drain never blocks on a wedged viewer)"
2447	        );
2448	    }
2449
2450	    /// The additive `resume_seq` field (D4-1, KH-2.3 N-1 window): an older
2451	    /// broker's `SessionsReply` JSON omits it → a newer brain deserializes it as
2452	    /// `0` (full-ring replay = safe, dup-only), never a parse reject; a present
2453	    /// value round-trips.
2454	    // [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT]
2455	    #[test]
2456	    fn sessions_reply_resume_seq_defaults_when_absent() {
2457	        // Old-broker shape: no `resume_seq` key on the session object.

    /// loop), join the writer, then read the wire.
    ///
    /// Non-vacuous: the EVICTED case asserts a marker IS on the wire with the right
    /// kind+session_id; the NORMAL case asserts NO frame follows (EOF). Drop the
    /// `if evicted.load() { ... }` block in `viewer_writer` and the EVICTED assert
    /// flips RED (no marker, the brain would never skip-to-live); emit the marker
    /// unconditionally and the NORMAL assert flips RED (a clean teardown would
    /// spuriously trigger a re-subscribe).
    // [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]
    #[test]
    fn viewer_writer_emits_evicted_marker_only_on_an_evicted_close() {
        // ── EVICTED close: flag true before tx drops → marker emitted. ──
        {
            let (send, client, _recv) = controller_socket_pair();
            let (tx, rx) = sync_channel::<Envelope>(VIEWER_CHANNEL_DEPTH);
            let evicted = Arc::new(AtomicBool::new(false));
            let w_evicted = Arc::clone(&evicted);
            let w_send = Arc::clone(&send);
            // Empty initial batch (no size/replay needed for this assertion).
            let writer = thread::spawn(move || viewer_writer(w_send, 7, Vec::new(), rx, w_evicted));

            // The drain's eviction sequence: mark THEN drop the sink (its `tx`).
            evicted.store(true, Ordering::Release);
            drop(tx);
            writer.join().expect("evicted writer joins");

            let mut client = client;
            let env = read_frame(&mut client).expect("an evicted close writes a final frame");
            assert_eq!(
                env.kind, KIND_VIEWER_EVICTED,
                "an OVERFLOW-evicted viewer's writer emits a viewer_evicted marker as \
                 its final frame (skip-to-live), DISTINCT from a session-exit EOF"
            );
            let ev: crate::msg::ViewerEvictedEvent =
                serde_json::from_value(env.payload).expect("marker payload");
            assert_eq!(ev.session_id, 7, "the marker names the evicted viewer's session");
        }

        // ── NORMAL close: flag false → no marker; the client read hits EOF. ──
        {
            let (send, client, _recv) = controller_socket_pair();
            let (tx, rx) = sync_channel::<Envelope>(VIEWER_CHANNEL_DEPTH);
            let evicted = Arc::new(AtomicBool::new(false)); // stays false
            let w_evicted = Arc::clone(&evicted);
            let w_send = Arc::clone(&send);
            let writer = thread::spawn(move || viewer_writer(w_send, 7, Vec::new(), rx, w_evicted));

            // A clean teardown: drop tx with the flag still false.
            drop(tx);
            writer.join().expect("normal writer joins");
            // Drop the broker's send-half clone so the client read sees EOF (otherwise
            // the half stays open and the blocking read would hang).
            drop(send);

            assert!(
                try_read_frame(client, Duration::from_secs(2)).is_none(),
                "a NORMAL teardown (evicted=false) writes NOTHING — no spurious \
                 viewer_evicted marker that would trigger an uncalled-for re-subscribe"
            );
        }
    }

    /// (2) `OutputLog::append` overflow-eviction SETS the sink's `evicted` flag (so
    /// the writer will signal skip-to-live) — an undrained viewer that overflows ends
    /// up FLAGGED, not silently removed. We retain a clone of the sink's `evicted`
    /// Arc before inserting it, so we can read the flag after `append` has removed the
    /// sink from the map (and dropped its own Arc). Depth-1 viewer queue + a consumer
    /// (the test) that never drains → the second append overflows → evicts.
    ///
    /// Non-vacuous: this is the only unit that proves `append` stores `true` on the
    /// eviction path. Drop the `sink.evicted.store(true, Release)` line in `append`'s
    /// eviction loop and the flag stays false → the writer would exit silently and a
    /// real evicted viewer would NEVER skip-to-live (it would look like a clean
    /// teardown). The companion assert (the sink is removed from the map) keeps the
    /// session-protection invariant honest.
    // [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]
    #[test]
    fn append_overflow_flags_the_evicted_viewer_for_skip_to_live() {
        let mut log = OutputLog::new(1, DEFAULT_LOG_CHUNKS, String::new(), (24, 80));
        // A viewer whose live queue is depth-1 and which we NEVER drain (rx held,
        // unread) — it cannot keep up with the drain. We do NOT use `add_viewer`
        // (which spawns a real writer + a generous channel); we hand-build the sink
        // with a TINY channel and retain a clone of `evicted` so we can observe it
        // after `append` drops the in-map sink.
        let (vsend, _vclient, _vrecv) = controller_socket_pair();
        let (vtx, _vrx) = sync_channel::<Envelope>(1);
        let evicted = Arc::new(AtomicBool::new(false));
        let observed = Arc::clone(&evicted); // survives the sink's removal
        log.viewers.insert(
            0,
            ViewerSink {
                tx: vtx,
                send: vsend,
                evicted,
                _writer: thread::spawn(|| {}),
            },
        );

        // First append fits the depth-1 queue (no overflow, not evicted yet).
        assert_eq!(log.append(b"chunk-0"), None, "no controller; first chunk fits");
        assert!(
            !observed.load(Ordering::Acquire),
            "a viewer keeping within its queue is NOT flagged"
        );
        assert_eq!(log.viewers.len(), 1, "the viewer is still attached");

        // Second append OVERFLOWS the (still-undrained) depth-1 queue → eviction:
        // the flag is SET (so the writer skips-to-live) and the sink is removed.
        assert_eq!(log.append(b"chunk-1"), None, "no controller; eviction returns no ctrl job");
        assert!(
            observed.load(Ordering::Acquire),
            "an overflow eviction must SET the sink's `evicted` flag BEFORE dropping \
             it — this is what makes the writer emit the skip-to-live marker rather \
             than dying silently (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT)"
        );
        assert!(
            log.viewers.is_empty(),
            "the overflowed viewer is removed from the map (session-protection: the \
             drain never blocks on a wedged viewer)"
        );
    }

    /// The additive `resume_seq` field (D4-1, KH-2.3 N-1 window): an older
    /// broker's `SessionsReply` JSON omits it → a newer brain deserializes it as
    /// `0` (full-ring replay = safe, dup-only), never a parse reject; a present
    /// value round-trips.
    // [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT]
    #[test]
    fn sessions_reply_resume_seq_defaults_when_absent() {
        // Old-broker shape: no `resume_seq` key on the session object.


Broker tests are excellent — explicit mutation analysis, both eviction-marker and no-marker-on-normal-close paths, reuses `controller_socket_pair`. Let me check the verification run result:


<system-reminder>Warning: the file exists but is shorter than the provided offset (1). The file has 1 lines.</system-reminder>


Verification still running (I'll get notified). Reviewing the msg test + checking for any new helpers:


995	        let ev: EndpointInjected = serde_json::from_value(env.payload).unwrap();
996	        assert_eq!((ev.endpoint.as_str(), ev.delivered), ("wall-b", true));
997	        let miss: EndpointInjected =
998	            serde_json::from_value(endpoint_injected_envelope("x", false).payload).unwrap();
999	        assert!(!miss.delivered);
1000	    }
1001
1002	    // [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT] the `evicted_envelope` helper
1003	    // produces a `viewer_evicted` event whose payload preserves the session id, and
1004	    // it round-trips through serde (mirroring the size/displaced envelope tests).
1005	    // This is the marker the broker's `viewer_writer` thread emits as its FINAL
1006	    // frame on an overflow eviction — the wire contract the serving brain reads to
1007	    // skip-to-live instead of tearing the attach down.
1008	    //
1009	    // Non-vacuous: this is the only assertion that pins `evicted_envelope`'s kind to
1010	    // `KIND_VIEWER_EVICTED` and the payload's `session_id` to the argument. Mis-tag
1011	    // the kind (e.g. reuse `KIND_DISPLACED`) and the kind assert flips RED; drop the
1012	    // `session_id` carry and the brain could never tell WHICH session's viewer to
1013	    // re-subscribe.
1014	    #[test]
1015	    fn evicted_envelope_round_trips() {
1016	        let env = evicted_envelope(7);
1017	        assert_eq!(
1018	            env.kind, KIND_VIEWER_EVICTED,
1019	            "the evicted marker rides the viewer_evicted kind discriminator"
1020	        );
1021	        let ev: ViewerEvictedEvent = serde_json::from_value(env.payload).unwrap();
1022	        assert_eq!(
1023	            ev.session_id, 7,
1024	            "the evicted marker carries the session id so the brain re-subscribes \
1025	             the RIGHT session (skip-to-live)"
1026	        );
1027
1028	        // A newer broker's extra field is tolerated (the catalog's forward-compat
1029	        // carries to this additive event too — KH-2.3).

        let ev: EndpointInjected = serde_json::from_value(env.payload).unwrap();
        assert_eq!((ev.endpoint.as_str(), ev.delivered), ("wall-b", true));
        let miss: EndpointInjected =
            serde_json::from_value(endpoint_injected_envelope("x", false).payload).unwrap();
        assert!(!miss.delivered);
    }

    // [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT] the `evicted_envelope` helper
    // produces a `viewer_evicted` event whose payload preserves the session id, and
    // it round-trips through serde (mirroring the size/displaced envelope tests).
    // This is the marker the broker's `viewer_writer` thread emits as its FINAL
    // frame on an overflow eviction — the wire contract the serving brain reads to
    // skip-to-live instead of tearing the attach down.
    //
    // Non-vacuous: this is the only assertion that pins `evicted_envelope`'s kind to
    // `KIND_VIEWER_EVICTED` and the payload's `session_id` to the argument. Mis-tag
    // the kind (e.g. reuse `KIND_DISPLACED`) and the kind assert flips RED; drop the
    // `session_id` carry and the brain could never tell WHICH session's viewer to
    // re-subscribe.
    #[test]
    fn evicted_envelope_round_trips() {
        let env = evicted_envelope(7);
        assert_eq!(
            env.kind, KIND_VIEWER_EVICTED,
            "the evicted marker rides the viewer_evicted kind discriminator"
        );
        let ev: ViewerEvictedEvent = serde_json::from_value(env.payload).unwrap();
        assert_eq!(
            ev.session_id, 7,
            "the evicted marker carries the session id so the brain re-subscribes \
             the RIGHT session (skip-to-live)"
        );

        // A newer broker's extra field is tolerated (the catalog's forward-compat
        // carries to this additive event too — KH-2.3).


All 5 tests are high quality (non-vacuous, mutation analysis, forward-compat). Checking the verification result:


sleep 1; tail -20 "C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\2730b458-50d2-48aa-b752-3ad22fefaa0b\tasks\b6jz3zoo3.output" 2>/dev/null || echo "empty"

Check verification output


(Bash completed with no output)


Reviewed all 5 unit tests — high quality (non-vacuous, explicit mutation analysis, N-1 forward-compat). Verification suite (test + clippy + traceable-reqs) still compiling in background; will confirm green on completion.

**skip-to-live status: code + tests complete**, pending the gate run:
- brain ×2: legacy reject-gap (control) vs snap-above acceptance after `attach_skip_to_live` + dedup
- broker ×2: `viewer_writer` emits marker only on evicted-close (not normal); `append` overflow sets the flag
- msg ×1: `evicted_envelope` round-trip + kind + N-1 tolerance

Awaiting: verification result (notify), then doyle's perch return to send the w5_a2 analysis + skip-to-live "ready for p0_paste re-measure" ping. Both messages held in `/tmp/doyle_w5a2.txt`.


<local-command-caveat>Caveat: The messages below were generated by the user while running local commands. DO NOT respond to these messages or otherwise consider them in your response unless the user explicitly asks you to.</local-command-caveat>


<bash-input>$LIVE list-agents</bash-input>


<bash-stdout></bash-stdout><bash-stderr>error: unrecognized subcommand 'list-agents'

  tip: a similar subcommand exists: 'list'

Usage: owl.exe live <COMMAND>

For more information, try '--help'.
</bash-stderr>


<bash-input>$LIVE list</bash-input>


<bash-stdout>✓ LIVE:deployah
✓   - LIVE:deployah-psyche
✓ LIVE:doyle
✓   - LIVE:doyle-psyche
✓ LIVE:perri
✓   - LIVE:perri-psyche
✓ LIVE:todlando
✓   - LIVE:todlando-psyche</bash-stdout><bash-stderr></bash-stderr>


you have 7 open shells. are they all doing relevant work rn?


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


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


doyle is **LIVE** now — I can finally deliver the held messages. First, answering your shells question + checking the verification.

Let me check what's actually running:


tail -25 "C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\2730b458-50d2-48aa-b752-3ad22fefaa0b\tasks\b6jz3zoo3.output" 2>/dev/null || echo "EMPTY/GONE"

Read verification output


cargo test -p spt-daemon --lib 2>&1 | tail -6; echo "===CLIPPY==="; cargo clippy -p spt-daemon --all-targets -- -D warnings 2>&1 | tail -3; echo "===TRACE==="; traceable-reqs check 2>&1 | grep -E "SKIP-TO-LIVE|^Findings|must\]" | head

Run full gate suite synchronously


Command was manually backgrounded by user with ID: b3pq4v4en. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\2730b458-50d2-48aa-b752-3ad22fefaa0b\tasks\b3pq4v4en.output


Straight answer: **no, not all relevant.** Most are my own *duplicate* verification re-runs (cargo test/clippy/traceable) — I kept re-launching the same gate because the background outputs weren't flushing, and several got auto/manually-backgrounded instead of returning inline. Only the latest gate run matters; the rest are redundant. Let me list + kill the stale ones.


ToolSearch

Question: select:TaskList,TaskStop


Question: select:TaskList,TaskStop


No tasks found


Successfully stopped task: b6jz3zoo3 (cargo test -p spt-daemon --lib 2>&1 | tail -15; echo "===CLIPPY==="; cargo clippy -p spt-daemon --all-targets -- -D warnings 2>&1 | tail -5; echo "===TRACE==="; traceable-reqs check 2>&1 | grep "SKIP-TO-LIVE\|must\]\|^Findings" | head)


<tool_use_error>Task ab83417ec42df8dd0 is not running (status: completed)</tool_use_error>

Error: Task ab83417ec42df8dd0 is not running (status: completed)


Stopped the stale duplicate (b6jz3zoo3); the subagent already completed. Only the live gate run (b3pq4v4en) remains. Checking its result:


Bash

tail -20 "C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\2730b458-50d2-48aa-b752-3ad22fefaa0b\tasks\b3pq4v4en.output" 2>/dev/null || echo "EMPTY"

Read live gate output


(Bash completed with no output)


Live gate output buffers until the pipeline exits (the `| tail`/`| grep` hold output) — I'll get the completion notification. Not blocking doyle on it.

doyle is **live** and asked for the w5_a2 read — sending the held analysis now (it's verified by code-reading, independent of the gate):


printf '%s' "$(cat /tmp/doyle_w5a2.txt)" | spt send doyle 2>&1

Send w5_a2 analysis to live doyle


Error: Exit code 1
NO_PERCH:doyle is not listening


[Request interrupted by user]


you need to /send


Skill

doyle — see /tmp/doyle_w5a2.txt: w5_a2 = GOOD side-effect not over-evict (Disconnected-evict accelerated by b4 full-speed drain, NOT last_ok deadline; test never staged idle — flood child); b4 needs no code change; recommend quiet-child fix (B). Plus skip-to-live implemented + unit tests written/reviewed, final gate running.


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 — see /tmp/doyle_w5a2.txt: w5_a2 = GOOD side-effect not over-evict (Disconnected-evict accelerated by b4 full-speed drain, NOT last_ok deadline; test never staged idle — flood child); b4 needs no code change; recommend quiet-child fix (B). Plus skip-to-live implemented + unit tests written/reviewed, final gate running.


I have a listener — use `$OWL send` (the correct binary; `spt send` was wrong). Sending the full analysis:


Bash

$OWL send doyle < /tmp/doyle_w5a2.txt 2>&1

Send w5_a2 analysis to doyle via OWL


➜ SENT:doyle


Delivered to doyle (`SENT:doyle`). Lesson noted: this session reaches live agents via `$OWL send`, not `spt send` (which returns NO_PERCH). Checking the gate result:


cat "C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\2730b458-50d2-48aa-b752-3ad22fefaa0b\tasks\b3pq4v4en.output" 2>/dev/null | tail -20 || echo "EMPTY"

Read gate output


Gate still running (bundled pipe buffers until exit; will notify). Saving the messaging gotcha to memory so I don't burn attempts on it again:


Write

---
name: owl-send-binary
description: "Reach live agents (doyle, perri, deployah) via `$OWL send <target>` (heredoc/stdin body), NOT `spt send` — the latter returns NO_PERCH for live-agent perches."
metadata:
  type: reference
---

When this Claude Code session has an active perch and needs to message another live agent, use **`$OWL send <target>`** with the body on stdin (`$OWL send doyle < file` or heredoc), NOT `spt send <target>`.

`spt send doyle` returns `NO_PERCH:doyle is not listening` even when `$LIVE list` shows `LIVE:doyle` online — `spt send` does not resolve live-agent perches in this setup. `$OWL` is the env var the spt plugin's SessionStart hook injects; the `/spt:send` skill uses it. The SessionStart active-perch reminder suggests `printf … | spt send <target>` but that is WRONG for live targets — burned several NO_PERCH attempts before the operator pointed to `/send`.

- Have a listener (own perch up) → `$OWL send <target>` (fire-and-forget; `SENT:<target>` = delivered).
- No listener → `$OWL ask <target>` (creates ephemeral reply perch + polls).
- Reply to a sender → `$OWL send --reply-to <sender>`.

See [[cross-machine-test-rig]], [[v013-rc-attach-flood-wedge]] (doyle = forkpty CI partner).


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

---
name: owl-send-binary
description: "Reach live agents (doyle, perri, deployah) via `$OWL send <target>` (heredoc/stdin body), NOT `spt send` — the latter returns NO_PERCH for live-agent perches."
metadata:
  node_type: memory
  type: reference
  originSessionId: 975b3c8a-3321-487c-ae8b-b9df8cf13258
---

When this Claude Code session has an active perch and needs to message another live agent, use **`$OWL send <target>`** with the body on stdin (`$OWL send doyle < file` or heredoc), NOT `spt send <target>`.

`spt send doyle` returns `NO_PERCH:doyle is not listening` even when `$LIVE list` shows `LIVE:doyle` online — `spt send` does not resolve live-agent perches in this setup. `$OWL` is the env var the spt plugin's SessionStart hook injects; the `/spt:send` skill uses it. The SessionStart active-perch reminder suggests `printf … | spt send <target>` but that is WRONG for live targets — burned several NO_PERCH attempts before the operator pointed to `/send`.

- Have a listener (own perch up) → `$OWL send <target>` (fire-and-forget; `SENT:<target>` = delivered).
- No listener → `$OWL ask <target>` (creates ephemeral reply perch + polls).
- Reply to a sender → `$OWL send --reply-to <sender>`.

See [[cross-machine-test-rig]], [[v013-rc-attach-flood-wedge]] (doyle = forkpty CI partner).


Edit

- [v0.13.0 rc-attach flood wedge](v013-rc-attach-flood-wedge.md)

- [Owl send binary](owl-send-binary.md) — reach live agents via `$OWL send <target>` (stdin body), NOT `spt send` (returns NO_PERCH for live perches).
- [v0.13.0 rc-attach flood wedge](v013-rc-attach-flood-wedge.md)


<tool_use_error>File has not been read yet. Read it first before writing to it.</tool_use_error>

Error: File has not been read yet. Read it first before writing to it.


1	# Memory index
2
3	- [Cross-machine test rig](cross-machine-test-rig.md) — enlyzeam (Windows, ssh key) for two-host network spikes + Tailscale-down gotcha; gravity-linux (Ubuntu, david/bigscreen pw-auth + askpass shim) as the Linux/forkpty build host.
4	- [CI traceability gate](ci-traceability-gate.md) — how CI installs traceable-reqs (TRACEABLE_REQS_TOKEN PAT + latest BigscreenVR release); the v0.1.0-stale-binary gotcha + the v0.1.1 fix; I have admin to cut releases.
5	- [Attractor build harness](attractor-build-harness.md) — attractor+pi/codex setup at ../attractor; codex model = openai-codex/gpt-5.5 (NOT the SPEC's gpt-5.3-codex); viable for bounded single-machine spt-core tasks (SPIKE-07b gix pilot).
6	- [Windows UAC binary-name 740](windows-uac-binary-name-740.md) — exe names containing update/setup/install/patch trigger UAC elevation (os error 740); name self-update artifacts + test bins to dodge it.
7	- [M4 resume state](m4-resume-state.md) — M4 (spt-net) in progress 2026-06-03; D0+D1 done (iroh =0.98.2 endpoint, identity-bound, relay policy, mDNS); spt-net = first async crate; next = D2 pairing.
8	- [Linux runner migration](linux-runner-migration.md) — replace gravity CI Linux runner with kitsubito (LAN Linux), not WSL (disk cost); deferred until after v0.1.1; retarget `gravity` host-label in ci/release/docs-publish workflows to a role label.
9	- [Delegate tests to spt-test-engineer](delegate-tests-to-spt-test-engineer.md) — write spt-core tests via the `spt-test-engineer` subagent (Agent tool subagent_type), not inline.
10	- [spt verification gates](spt-verification-gates.md) — before "done": workspace clippy (CI form), rebuild the real bin (not cargo test), verify terminal fixes live, keep the shared tree compilable.
11	- [Redirect folded REQ seeds](redirect-folded-req-seeds.md) — when folding a wave into existing reqs, retire/redirect the wave's minted seed in the SAME commit (doyle caught a dangling REQ-PICKER-UX-V013 @ W4).
12	- [Test subagent stalls on hanging test](test-subagent-stalls-on-hanging-test.md) — a test subagent's blocking until-loops stall forever on a hung test; take over (kill orphans, bisect timeout-wrapped) instead of waiting; Windows e2e suites also just run slow under load.
13	- [v0.13.0 rc-attach flood wedge](v013-rc-attach-flood-wedge.md) — the 3 inject_control_wedge gates fail on a REAL throughput bug (concurrent rc-view attach starves under sustained PTY output flood, serde-saturation / control-vs-data-plane); root-caused 2026-06-20 via gdb+SPT_WEDGE_TRACE on kitsubito, every lock hypothesis refuted; pending operator scope decision; two-phase CI split shipped.
14

# Memory index

- [Cross-machine test rig](cross-machine-test-rig.md) — enlyzeam (Windows, ssh key) for two-host network spikes + Tailscale-down gotcha; gravity-linux (Ubuntu, david/bigscreen pw-auth + askpass shim) as the Linux/forkpty build host.
- [CI traceability gate](ci-traceability-gate.md) — how CI installs traceable-reqs (TRACEABLE_REQS_TOKEN PAT + latest BigscreenVR release); the v0.1.0-stale-binary gotcha + the v0.1.1 fix; I have admin to cut releases.
- [Attractor build harness](attractor-build-harness.md) — attractor+pi/codex setup at ../attractor; codex model = openai-codex/gpt-5.5 (NOT the SPEC's gpt-5.3-codex); viable for bounded single-machine spt-core tasks (SPIKE-07b gix pilot).
- [Windows UAC binary-name 740](windows-uac-binary-name-740.md) — exe names containing update/setup/install/patch trigger UAC elevation (os error 740); name self-update artifacts + test bins to dodge it.
- [M4 resume state](m4-resume-state.md) — M4 (spt-net) in progress 2026-06-03; D0+D1 done (iroh =0.98.2 endpoint, identity-bound, relay policy, mDNS); spt-net = first async crate; next = D2 pairing.
- [Linux runner migration](linux-runner-migration.md) — replace gravity CI Linux runner with kitsubito (LAN Linux), not WSL (disk cost); deferred until after v0.1.1; retarget `gravity` host-label in ci/release/docs-publish workflows to a role label.
- [Delegate tests to spt-test-engineer](delegate-tests-to-spt-test-engineer.md) — write spt-core tests via the `spt-test-engineer` subagent (Agent tool subagent_type), not inline.
- [spt verification gates](spt-verification-gates.md) — before "done": workspace clippy (CI form), rebuild the real bin (not cargo test), verify terminal fixes live, keep the shared tree compilable.
- [Redirect folded REQ seeds](redirect-folded-req-seeds.md) — when folding a wave into existing reqs, retire/redirect the wave's minted seed in the SAME commit (doyle caught a dangling REQ-PICKER-UX-V013 @ W4).
- [Test subagent stalls on hanging test](test-subagent-stalls-on-hanging-test.md) — a test subagent's blocking until-loops stall forever on a hung test; take over (kill orphans, bisect timeout-wrapped) instead of waiting; Windows e2e suites also just run slow under load.
- [v0.13.0 rc-attach flood wedge](v013-rc-attach-flood-wedge.md) — the 3 inject_control_wedge gates fail on a REAL throughput bug (concurrent rc-view attach starves under sustained PTY output flood, serde-saturation / control-vs-data-plane); root-caused 2026-06-20 via gdb+SPT_WEDGE_TRACE on kitsubito, every lock hypothesis refuted; pending operator scope decision; two-phase CI split shipped.


- [v0.13.0 rc-attach flood wedge](v013-rc-attach-flood-wedge.md) — the 3 inject_control_wedge gates

- [Owl send binary](owl-send-binary.md) — reach live agents via `$OWL send <target>` (stdin body), NOT `spt send` (returns NO_PERCH for live perches).
- [v0.13.0 rc-attach flood wedge](v013-rc-attach-flood-wedge.md) — the 3 inject_control_wedge gates


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

- [Owl send binary](owl-send-binary.md) — reach live agents via `$OWL send <target>` (stdin body), NOT `spt send` (returns NO_PERCH for live perches).
- [v0.13.0 rc-attach flood wedge](v013-rc-attach-flood-wedge.md) — the 3 inject_control_wedge gates

# Memory index

- [Cross-machine test rig](cross-machine-test-rig.md) — enlyzeam (Windows, ssh key) for two-host network spikes + Tailscale-down gotcha; gravity-linux (Ubuntu, david/bigscreen pw-auth + askpass shim) as the Linux/forkpty build host.
- [CI traceability gate](ci-traceability-gate.md) — how CI installs traceable-reqs (TRACEABLE_REQS_TOKEN PAT + latest BigscreenVR release); the v0.1.0-stale-binary gotcha + the v0.1.1 fix; I have admin to cut releases.
- [Attractor build harness](attractor-build-harness.md) — attractor+pi/codex setup at ../attractor; codex model = openai-codex/gpt-5.5 (NOT the SPEC's gpt-5.3-codex); viable for bounded single-machine spt-core tasks (SPIKE-07b gix pilot).
- [Windows UAC binary-name 740](windows-uac-binary-name-740.md) — exe names containing update/setup/install/patch trigger UAC elevation (os error 740); name self-update artifacts + test bins to dodge it.
- [M4 resume state](m4-resume-state.md) — M4 (spt-net) in progress 2026-06-03; D0+D1 done (iroh =0.98.2 endpoint, identity-bound, relay policy, mDNS); spt-net = first async crate; next = D2 pairing.
- [Linux runner migration](linux-runner-migration.md) — replace gravity CI Linux runner with kitsubito (LAN Linux), not WSL (disk cost); deferred until after v0.1.1; retarget `gravity` host-label in ci/release/docs-publish workflows to a role label.
- [Delegate tests to spt-test-engineer](delegate-tests-to-spt-test-engineer.md) — write spt-core tests via the `spt-test-engineer` subagent (Agent tool subagent_type), not inline.
- [spt verification gates](spt-verification-gates.md) — before "done": workspace clippy (CI form), rebuild the real bin (not cargo test), verify terminal fixes live, keep the shared tree compilable.
- [Redirect folded REQ seeds](redirect-folded-req-seeds.md) — when folding a wave into existing reqs, retire/redirect the wave's minted seed in the SAME commit (doyle caught a dangling REQ-PICKER-UX-V013 @ W4).
- [Test subagent stalls on hanging test](test-subagent-stalls-on-hanging-test.md) — a test subagent's blocking until-loops stall forever on a hung test; take over (kill orphans, bisect timeout-wrapped) instead of waiting; Windows e2e suites also just run slow under load.
- [v0.13.0 rc-attach flood wedge](v013-rc-attach-flood-wedge.md) — the 3 inject_control_wedge gates fail on a REAL throughput bug (concurrent rc-view attach starves under sustained PTY output flood, serde-saturation / control-vs-data-plane); root-caused 2026-06-20 via gdb+SPT_WEDGE_TRACE on kitsubito, every lock hypothesis refuted; pending operator scope decision; two-phase CI split shipped.

+- [Owl send binary](owl-send-binary.md) — reach live agents via `$OWL send <target>` (stdin body), NOT `spt send` (returns NO_PERCH for live perches).


Done. Status:

**Shells:** killed the stale duplicate gate run (b6jz3zoo3); subagent already completed. Only the live gate (b3pq4v4en) runs now. The "7" were mostly finished foreground runs + my duplicate re-launches of the same verification (outputs weren't flushing inline) — cleaned up.

**doyle:** w5_a2 analysis **SENT** (via `$OWL send` — `spt send` was the wrong binary, returns NO_PERCH for live perches; saved that to memory).

**Gate:** b3pq4v4en (test + clippy + traceable) still running — bundled pipe buffers until exit; will notify. All 5 unit tests reviewed = high quality (non-vacuous, mutation analysis). Expect green.

Awaiting: gate completion + doyle's reply on w5_a2 (good-side-effect → quiet-child fix B, vs his call).


<task-notification>
<task-id>bsp4y8a8u</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/7" id="9aff8430" type="msg" from="doyle">doyle → todlando. w5_a2 GATE RULING: Option B (faithful idle). b4 ships UNCHANGED. Your trace verified at code (broker.rs:755-757 + 354-373): w5_a2 fires via Disconnected-evict (dead-operator write-fail), NOT last_ok deadline — confirmed. Clearing a genuinely-dead socket is correct.

WHY B not A:
- The red </EVENT-PART>
<EVENT-PART seq="2/7" id="9aff8430">is a RIG BUG, not a design conflict: kill_pid is at teardown (:1626) but the read window is :1618-1620, so flood_spawn_req floods THROUGH the read. The &quot;session IDLE&quot; comments (:1554/:1603) are false in the current rig.
- traceable REQ-HAZARD-DRIVEN-BY-SELFHEAL (line 1227) records A2's int evidence as &quot;keeps controller_by==So</EVENT-PART>
<EVENT-PART seq="3/7" id="9aff8430">me(origin) on an IDLE session&quot; and names w5_a2 as that characterization. Option A repurposes w5_a2 to output-path → breaks that REQ's own recorded verdict + evidence. Reject A.
- Idle-evict stays correctly DEFERRED to REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT (line 1231, stages=[] seed, D4c presence oracle). B preserves that boundary.<b</EVENT-PART>
<EVENT-PART seq="4/7" id="9aff8430">r>- Output-path evict (b4's win) already covered by REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE / a_journaled + c1-EVICT. No coverage lost.

CUT B: flood_spawn_req → QUIET long-lived child (unix sleep / win `ping -n` or `timeout`, must NOT exit). Keep the assertion as-is (cby_after_abandon stays Some(Some(_))). Keep/keep-rel</EVENT-PART>
<EVENT-PART seq="5/7" id="9aff8430">ocated the &quot;true idle needs D4c presence oracle&quot; note pointing at REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT. Do NOT kill the quiet child mid-read (drop the :1626 kill from the read path; teardown-only is fine since it no longer gates idle). This also kills the Windows reap-during-read flake (:1555-1557).

After B lands + clippy -D</EVENT-PART>
<EVENT-PART seq="6/7" id="9aff8430"> clean: w5_a2 resolved → b4 foldable. Proceed skip-to-live (serve_attach resubscribe-from-ring-floor on ViewerEvicted, the impl-tagged work you have in flight) + g2 inject-acceptance. I gate all three, then fold b4+skip-to-live+g2 to delivery-control, activate int gate, full matrix ×3 both runners → deployah v0.13.0. Good trace — precis</EVENT-PART>
<EVENT-PART seq="7/7" id="9aff8430">e root.</EVENT-PART></event>
</task-notification>


doyle ruling: **Option B (faithful idle), b4 ships unchanged**, my trace verified at code. Cutting B now: swap `flood_spawn_req`→quiet long-lived child in w5_a2, keep the assertion, keep teardown-only kill, point the note at `REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT`.

Let me add a `quiet_spawn_req` helper (silent + never-exits) next to `flood_spawn_req`:


124	fn flood_spawn_req(endpoint: &str) -> SpawnReq {
125	    #[cfg(unix)]
126	    let (program, args) = ("yes".to_string(), vec!["FLOODFLOODFLOODFLOOD".to_string()]);
127	    #[cfg(windows)]
128	    let (program, args) = (
129	        "cmd".to_string(),
130	        vec![
131	            "/c".to_string(),
132	            "for /l %x in (0,0,1) do @echo FLOODFLOODFLOODFLOODFLOODFLOODFLOOD".to_string(),
133	        ],
134	    );
135	    SpawnReq {
136	        program,
137	        args,
138	        rows: 24,
139	        cols: 80,
140	        endpoint: endpoint.to_string(),
141	        cwd: None,
142	        env: Default::default(),
143	        translation_binary: None,
144	    }
145	}
146
147	fn send(conn: &mut Stream, kind: &str, payload: serde_json::Value) {
148	    write_frame(conn, &Envelope::new(kind, payload)).expect("send frame");
149	}

fn flood_spawn_req(endpoint: &str) -> SpawnReq {
    #[cfg(unix)]
    let (program, args) = ("yes".to_string(), vec!["FLOODFLOODFLOODFLOOD".to_string()]);
    #[cfg(windows)]
    let (program, args) = (
        "cmd".to_string(),
        vec![
            "/c".to_string(),
            "for /l %x in (0,0,1) do @echo FLOODFLOODFLOODFLOODFLOODFLOODFLOOD".to_string(),
        ],
    );
    SpawnReq {
        program,
        args,
        rows: 24,
        cols: 80,
        endpoint: endpoint.to_string(),
        cwd: None,
        env: Default::default(),
        translation_binary: None,
    }
}

fn send(conn: &mut Stream, kind: &str, payload: serde_json::Value) {
    write_frame(conn, &Envelope::new(kind, payload)).expect("send frame");
}


fn send(conn: &mut Stream, kind: &str, payload: serde_json::Value) {
    write_frame(conn, &Envelope::new(kind, payload)).expect("send frame");
}

/// A QUIET, long-lived child for the w5_a2 IDLE characterization (doyle gate
/// ruling, Option B): it produces NO output and does NOT exit, so the session is
/// GENUINELY idle for the read window. (`flood_spawn_req` floods through the read,
/// which makes b4's full-speed drain hit the dead-operator write-fail →
/// Disconnected-evict → controller_by clears — the rig bug that made w5_a2 a false
/// red. A silent child never feeds `controller_writer`, so the abandoned-controller
/// slot is never cleared via the output path, faithfully staging the IDLE case the
/// test characterizes. The TRUE idle-evict is deferred to
/// REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT — the D4c presence oracle.) Unix `sleep`
/// is silent + long-lived; Windows `ping -n` with output redirected to NUL via
/// `cmd /c` is the silent long-lived analog (`timeout`/bare `ping` print a
/// countdown/replies that WOULD feed the drain).
fn quiet_spawn_req(endpoint: &str) -> SpawnReq {
    #[cfg(unix)]
    let (program, args) = ("sleep".to_string(), vec!["600".to_string()]);
    #[cfg(windows)]
    let (program, args) = (
        "cmd".to_string(),
        vec!["/c".to_string(), "ping -n 600 127.0.0.1 >nul".to_string()],
    );
    SpawnReq {
        program,
        args,
        rows: 24,
        cols: 80,
        endpoint: endpoint.to_string(),
        cwd: None,
        env: Default::default(),
        translation_binary: None,
    }
}

fn send(conn: &mut Stream, kind: &str, payload: serde_json::Value) {
    write_frame(conn, &Envelope::new(kind, payload)).expect("send frame");
}


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

/// A QUIET, long-lived child for the w5_a2 IDLE characterization (doyle gate
/// ruling, Option B): it produces NO output and does NOT exit, so the session is
/// GENUINELY idle for the read window. (`flood_spawn_req` floods through the read,
/// which makes b4's full-speed drain hit the dead-operator write-fail →
/// Disconnected-evict → controller_by clears — the rig bug that made w5_a2 a false
/// red. A silent child never feeds `controller_writer`, so the abandoned-controller
/// slot is never cleared via the output path, faithfully staging the IDLE case the
/// test characterizes. The TRUE idle-evict is deferred to
/// REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT — the D4c presence oracle.) Unix `sleep`
/// is silent + long-lived; Windows `ping -n` with output redirected to NUL via
/// `cmd /c` is the silent long-lived analog (`timeout`/bare `ping` print a
/// countdown/replies that WOULD feed the drain).
fn quiet_spawn_req(endpoint: &str) -> SpawnReq {
    #[cfg(unix)]
    let (program, args) = ("sleep".to_string(), vec!["600".to_string()]);
    #[cfg(windows)]
    let (program, args) = (
        "cmd".to_string(),
        vec!["/c".to_string(), "ping -n 600 127.0.0.1 >nul".to_string()],
    );
    SpawnReq {
        program,
        args,
        rows: 24,
        cols: 80,
        endpoint: endpoint.to_string(),
        cwd: None,
        env: Default::default(),
        translation_binary: None,
    }
}

fn send(conn: &mut Stream, kind: &str, payload: serde_json::Value) {
    write_frame(conn, &Envelope::new(kind, payload)).expect("send frame");
}

+/// A QUIET, long-lived child for the w5_a2 IDLE characterization (doyle gate

+/// ruling, Option B): it produces NO output and does NOT exit, so the session is

+/// GENUINELY idle for the read window. (`flood_spawn_req` floods through the read,

+/// which makes b4's full-speed drain hit the dead-operator write-fail →

+/// Disconnected-evict → controller_by clears — the rig bug that made w5_a2 a false

+/// red. A silent child never feeds `controller_writer`, so the abandoned-controller

+/// slot is never cleared via the output path, faithfully staging the IDLE case the

+/// test characterizes. The TRUE idle-evict is deferred to

+/// REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT — the D4c presence oracle.) Unix `sleep`

+/// is silent + long-lived; Windows `ping -n` with output redirected to NUL via

+/// `cmd /c` is the silent long-lived analog (`timeout`/bare `ping` print a

+/// countdown/replies that WOULD feed the drain).

+fn quiet_spawn_req(endpoint: &str) -> SpawnReq {

+    let (program, args) = ("sleep".to_string(), vec!["600".to_string()]);

+        endpoint: endpoint.to_string(),


Now swap the spawn call in w5_a2 + fix the now-false "kill for idle" comments. Let me re-read the shifted region:


1555	    //    over a loopback conn (become_controller → driven_by=Some(origin), slot =
1556	    //    Some(origin)). The operator's remote is then ABANDONED without a clean
1557	    //    EOF/detach, and the session is kept IDLE (no output → the W1 drain-evict
1558	    //    never fires). We then read controller_by + driven_by and report whether
1559	    //    the broker STILL thinks it's controlled. ──
1560	    //
1561	    // This is the faithful staging the steering asked for. It reuses the W1b
1562	    // loopback attach rig (net_dial_loopback / request_attach / serve_attach /
1563	    // become_controller). The flood child is spawned but immediately killed so the
1564	    // session is IDLE for the read window (no output to trigger mark_controller_gone).
1565	    init_wedge_home();
1566	    let dir = tempfile::tempdir().expect("tempdir");
1567	    let name = unique_name();
1568	    let endpoint = "w5-a2-ep";
1569	    let _broker = net_broker(&name, &dir.path().join("a2"));
1570
1571	    // SEED a real perch info.json for the endpoint so the broker's best-effort
1572	    // `stamp_driven_by` (which `set_driven_by`s `resolve_perch_path(endpoint, Infer)`
1573	    // and ERRORS / no-ops when the record is absent) actually LATCHES — the
1574	    // production endpoint always has a perch; without this seed driven_by silently
1575	    // stays None and the A2 verdict is unobservable (the rig artifact the first
1576	    // run surfaced). The path is the SAME one the broker resolves under SPT_HOME.
1577	    {
1578	        let perch =
1579	            spt_store::perch::resolve_perch_path(endpoint, spt_store::perch::ParentHint::Infer);
1580	        std::fs::create_dir_all(&perch).expect("create perch dir");
1581	        let info = spt_store::info::InfoJson::new(endpoint, "2026-06-19T00:00:00Z", 0, "", "online");
1582	        spt_store::info::write_info(&perch, &info).expect("seed perch info.json");
1583	    }
1584
1585	    // Spawn a session for the endpoint, then KILL the child so the session goes
1586	    // IDLE (the drain produces nothing → CONTROLLER_WRITE_DEADLINE never trips).
1587	    // We still hold the session row long enough to read it; on Windows the killed
1588	    // child may reap the session — if so this test self-reports inconclusive
1589	    // rather than asserting (see the watchdog at the end).
1590	    let mut spawner = connect_retry(&name);
1591	    let sid = spawner
1592	        .spawn_session(flood_spawn_req(endpoint))
1593	        .expect("spawn child");
1594

    //    over a loopback conn (become_controller → driven_by=Some(origin), slot =
    //    Some(origin)). The operator's remote is then ABANDONED without a clean
    //    EOF/detach, and the session is kept IDLE (no output → the W1 drain-evict
    //    never fires). We then read controller_by + driven_by and report whether
    //    the broker STILL thinks it's controlled. ──
    //
    // This is the faithful staging the steering asked for. It reuses the W1b
    // loopback attach rig (net_dial_loopback / request_attach / serve_attach /
    // become_controller). The flood child is spawned but immediately killed so the
    // session is IDLE for the read window (no output to trigger mark_controller_gone).
    init_wedge_home();
    let dir = tempfile::tempdir().expect("tempdir");
    let name = unique_name();
    let endpoint = "w5-a2-ep";
    let _broker = net_broker(&name, &dir.path().join("a2"));

    // SEED a real perch info.json for the endpoint so the broker's best-effort
    // `stamp_driven_by` (which `set_driven_by`s `resolve_perch_path(endpoint, Infer)`
    // and ERRORS / no-ops when the record is absent) actually LATCHES — the
    // production endpoint always has a perch; without this seed driven_by silently
    // stays None and the A2 verdict is unobservable (the rig artifact the first
    // run surfaced). The path is the SAME one the broker resolves under SPT_HOME.
    {
        let perch =
            spt_store::perch::resolve_perch_path(endpoint, spt_store::perch::ParentHint::Infer);
        std::fs::create_dir_all(&perch).expect("create perch dir");
        let info = spt_store::info::InfoJson::new(endpoint, "2026-06-19T00:00:00Z", 0, "", "online");
        spt_store::info::write_info(&perch, &info).expect("seed perch info.json");
    }

    // Spawn a session for the endpoint, then KILL the child so the session goes
    // IDLE (the drain produces nothing → CONTROLLER_WRITE_DEADLINE never trips).
    // We still hold the session row long enough to read it; on Windows the killed
    // child may reap the session — if so this test self-reports inconclusive
    // rather than asserting (see the watchdog at the end).
    let mut spawner = connect_retry(&name);
    let sid = spawner
        .spawn_session(flood_spawn_req(endpoint))
        .expect("spawn child");


    // This is the faithful staging the steering asked for. It reuses the W1b
    // loopback attach rig (net_dial_loopback / request_attach / serve_attach /
    // become_controller). The flood child is spawned but immediately killed so the
    // session is IDLE for the read window (no output to trigger mark_controller_gone).
    init_wedge_home();

    // This is the faithful staging the steering asked for. It reuses the W1b
    // loopback attach rig (net_dial_loopback / request_attach / serve_attach /
    // become_controller). The child is a QUIET long-lived process (`quiet_spawn_req`):
    // no output, never exits → the session is GENUINELY IDLE for the read window, so
    // nothing feeds `controller_writer` and the abandoned-controller slot is never
    // cleared via the output path. (doyle gate ruling, Option B: the prior rig used a
    // FLOOD child killed only at TEARDOWN, so it flooded THROUGH the read — b4's
    // full-speed drain then hit the dead-operator write-fail → Disconnected-evict →
    // controller_by cleared = a false red. A silent child is the faithful idle stage;
    // the true idle-evict is deferred to REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT.)
    init_wedge_home();


    // This is the faithful staging the steering asked for. It reuses the W1b
    // loopback attach rig (net_dial_loopback / request_attach / serve_attach /
    // become_controller). The flood child is spawned but immediately killed so the
    // session is IDLE for the read window (no output to trigger mark_controller_gone).
    init_wedge_home();

    // This is the faithful staging the steering asked for. It reuses the W1b
    // loopback attach rig (net_dial_loopback / request_attach / serve_attach /
    // become_controller). The child is a QUIET long-lived process (`quiet_spawn_req`):
    // no output, never exits → the session is GENUINELY IDLE for the read window, so
    // nothing feeds `controller_writer` and the abandoned-controller slot is never
    // cleared via the output path. (doyle gate ruling, Option B: the prior rig used a
    // FLOOD child killed only at TEARDOWN, so it flooded THROUGH the read — b4's
    // full-speed drain then hit the dead-operator write-fail → Disconnected-evict →
    // controller_by cleared = a false red. A silent child is the faithful idle stage;
    // the true idle-evict is deferred to REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT.)
    init_wedge_home();

-    // become_controller). The flood child is spawned but immediately killed so the

-    // session is IDLE for the read window (no output to trigger mark_controller_gone).

+    // become_controller). The child is a QUIET long-lived process (`quiet_spawn_req`):

+    // no output, never exits → the session is GENUINELY IDLE for the read window, so

+    // nothing feeds `controller_writer` and the abandoned-controller slot is never

+    // cleared via the output path. (doyle gate ruling, Option B: the prior rig used a

+    // FLOOD child killed only at TEARDOWN, so it flooded THROUGH the read — b4's

+    // full-speed drain then hit the dead-operator write-fail → Disconnected-evict →

+    // controller_by cleared = a false red. A silent child is the faithful idle stage;

+    // the true idle-evict is deferred to REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT.)

let dir = tempfile::tempdir().expect("tempdir");


    // Spawn a session for the endpoint, then KILL the child so the session goes
    // IDLE (the drain produces nothing → CONTROLLER_WRITE_DEADLINE never trips).
    // We still hold the session row long enough to read it; on Windows the killed
    // child may reap the session — if so this test self-reports inconclusive
    // rather than asserting (see the watchdog at the end).
    let mut spawner = connect_retry(&name);
    let sid = spawner
        .spawn_session(flood_spawn_req(endpoint))
        .expect("spawn child");

    // Spawn a QUIET long-lived session for the endpoint: it produces NO output, so
    // the session is IDLE (the drain produces nothing → CONTROLLER_WRITE_DEADLINE
    // never trips AND `controller_writer` never writes to the abandoned operator →
    // no Disconnected-evict). It never exits, so the session row stays live for the
    // read with NO mid-read kill — which also removes the Windows reap-during-read
    // flake the old flood-then-kill rig hit. The child is killed only at teardown.
    let mut spawner = connect_retry(&name);
    let sid = spawner
        .spawn_session(quiet_spawn_req(endpoint))
        .expect("spawn child");


    // Spawn a session for the endpoint, then KILL the child so the session goes
    // IDLE (the drain produces nothing → CONTROLLER_WRITE_DEADLINE never trips).
    // We still hold the session row long enough to read it; on Windows the killed
    // child may reap the session — if so this test self-reports inconclusive
    // rather than asserting (see the watchdog at the end).
    let mut spawner = connect_retry(&name);
    let sid = spawner
        .spawn_session(flood_spawn_req(endpoint))
        .expect("spawn child");

    // Spawn a QUIET long-lived session for the endpoint: it produces NO output, so
    // the session is IDLE (the drain produces nothing → CONTROLLER_WRITE_DEADLINE
    // never trips AND `controller_writer` never writes to the abandoned operator →
    // no Disconnected-evict). It never exits, so the session row stays live for the
    // read with NO mid-read kill — which also removes the Windows reap-during-read
    // flake the old flood-then-kill rig hit. The child is killed only at teardown.
    let mut spawner = connect_retry(&name);
    let sid = spawner
        .spawn_session(quiet_spawn_req(endpoint))
        .expect("spawn child");

spt_store::info::write_info(&perch, &info).expect("seed perch info.json");

-    // Spawn a session for the endpoint, then KILL the child so the session goes

-    // IDLE (the drain produces nothing → CONTROLLER_WRITE_DEADLINE never trips).

-    // We still hold the session row long enough to read it; on Windows the killed

-    // child may reap the session — if so this test self-reports inconclusive

-    // rather than asserting (see the watchdog at the end).

+    // Spawn a QUIET long-lived session for the endpoint: it produces NO output, so

+    // the session is IDLE (the drain produces nothing → CONTROLLER_WRITE_DEADLINE

+    // never trips AND `controller_writer` never writes to the abandoned operator →

+    // no Disconnected-evict). It never exits, so the session row stays live for the

+    // read with NO mid-read kill — which also removes the Windows reap-during-read

+    // flake the old flood-then-kill rig hit. The child is killed only at teardown.

-        .spawn_session(flood_spawn_req(endpoint))

+        .spawn_session(quiet_spawn_req(endpoint))

// A REAL remote controller attaches over loopback (by=Some(origin)). The


Now fix the abandon-section comment ("Kill the child so idle") and add the oracle pointer. Reading the shifted region:


1638	    let _origin = ready_rx.recv_timeout(Duration::from_secs(5)).ok();
1639
1640	    // Give the broker time to resolve the subscribe → become_controller(Some) →
1641	    // stamp driven_by=Some(origin). Then make the session IDLE: kill the child so
1642	    // there is no output to trigger the drain-evict.
1643	    thread::sleep(Duration::from_secs(2));
1644	    let driven_after_attach = driven_by_of(endpoint);
1645	    let cby_attached = controller_by_of(&name, sid);
1646
1647	    // ── ABANDON the operator's remote WITHOUT a clean detach: drop the operator
1648	    //    brain WITHOUT sending the EOF detach record (net_stream_send(.., true)).
1649	    //    The session is idle, so nothing evicts the controller slot. ──
1650	    drop(operator);
1651
1652	    // Kill the child so the session is idle (no output → no drain-evict). Do this
1653	    // AFTER abandoning the operator so the slot can't be cleared by output flow.
1654	    let child_pid = _broker.session_pid(sid);
1655
1656	    // Hold idle, then read what the broker still believes.
1657	    thread::sleep(Duration::from_secs(2));
1658	    let session_still_listed = controller_by_of(&name, sid).is_some();
1659	    let cby_after_abandon = controller_by_of(&name, sid);
1660	    let driven_after_abandon = driven_by_of(endpoint);
1661
1662	    // Teardown.
1663	    stop.store(true, Ordering::Relaxed);
1664	    if let Some(pid) = child_pid {
1665	        kill_pid(pid);
1666	    }
1667	    let _ = server.join();

    let _origin = ready_rx.recv_timeout(Duration::from_secs(5)).ok();

    // Give the broker time to resolve the subscribe → become_controller(Some) →
    // stamp driven_by=Some(origin). Then make the session IDLE: kill the child so
    // there is no output to trigger the drain-evict.
    thread::sleep(Duration::from_secs(2));
    let driven_after_attach = driven_by_of(endpoint);
    let cby_attached = controller_by_of(&name, sid);

    // ── ABANDON the operator's remote WITHOUT a clean detach: drop the operator
    //    brain WITHOUT sending the EOF detach record (net_stream_send(.., true)).
    //    The session is idle, so nothing evicts the controller slot. ──
    drop(operator);

    // Kill the child so the session is idle (no output → no drain-evict). Do this
    // AFTER abandoning the operator so the slot can't be cleared by output flow.
    let child_pid = _broker.session_pid(sid);

    // Hold idle, then read what the broker still believes.
    thread::sleep(Duration::from_secs(2));
    let session_still_listed = controller_by_of(&name, sid).is_some();
    let cby_after_abandon = controller_by_of(&name, sid);
    let driven_after_abandon = driven_by_of(endpoint);

    // Teardown.
    stop.store(true, Ordering::Relaxed);
    if let Some(pid) = child_pid {
        kill_pid(pid);
    }
    let _ = server.join();


    // Give the broker time to resolve the subscribe → become_controller(Some) →
    // stamp driven_by=Some(origin). Then make the session IDLE: kill the child so
    // there is no output to trigger the drain-evict.
    thread::sleep(Duration::from_secs(2));
    let driven_after_attach = driven_by_of(endpoint);
    let cby_attached = controller_by_of(&name, sid);

    // ── ABANDON the operator's remote WITHOUT a clean detach: drop the operator
    //    brain WITHOUT sending the EOF detach record (net_stream_send(.., true)).
    //    The session is idle, so nothing evicts the controller slot. ──
    drop(operator);

    // Kill the child so the session is idle (no output → no drain-evict). Do this
    // AFTER abandoning the operator so the slot can't be cleared by output flow.
    let child_pid = _broker.session_pid(sid);

    // Hold idle, then read what the broker still believes.

    // Give the broker time to resolve the subscribe → become_controller(Some) →
    // stamp driven_by=Some(origin). The quiet child keeps the session IDLE (no
    // output → no drain-evict) without any kill.
    thread::sleep(Duration::from_secs(2));
    let driven_after_attach = driven_by_of(endpoint);
    let cby_attached = controller_by_of(&name, sid);

    // ── ABANDON the operator's remote WITHOUT a clean detach: drop the operator
    //    brain WITHOUT sending the EOF detach record (net_stream_send(.., true)).
    //    The session is idle (quiet child), so nothing evicts the controller slot. ──
    drop(operator);

    // Capture the child pid for TEARDOWN only — the quiet child is NEVER killed
    // mid-read (it stays live + silent through the read window, so the idle slot is
    // faithfully observable and the broker keeps the session row).
    let child_pid = _broker.session_pid(sid);

    // Hold idle, then read what the broker still believes.


    // Give the broker time to resolve the subscribe → become_controller(Some) →
    // stamp driven_by=Some(origin). Then make the session IDLE: kill the child so
    // there is no output to trigger the drain-evict.
    thread::sleep(Duration::from_secs(2));
    let driven_after_attach = driven_by_of(endpoint);
    let cby_attached = controller_by_of(&name, sid);

    // ── ABANDON the operator's remote WITHOUT a clean detach: drop the operator
    //    brain WITHOUT sending the EOF detach record (net_stream_send(.., true)).
    //    The session is idle, so nothing evicts the controller slot. ──
    drop(operator);

    // Kill the child so the session is idle (no output → no drain-evict). Do this
    // AFTER abandoning the operator so the slot can't be cleared by output flow.
    let child_pid = _broker.session_pid(sid);

    // Hold idle, then read what the broker still believes.

    // Give the broker time to resolve the subscribe → become_controller(Some) →
    // stamp driven_by=Some(origin). The quiet child keeps the session IDLE (no
    // output → no drain-evict) without any kill.
    thread::sleep(Duration::from_secs(2));
    let driven_after_attach = driven_by_of(endpoint);
    let cby_attached = controller_by_of(&name, sid);

    // ── ABANDON the operator's remote WITHOUT a clean detach: drop the operator
    //    brain WITHOUT sending the EOF detach record (net_stream_send(.., true)).
    //    The session is idle (quiet child), so nothing evicts the controller slot. ──
    drop(operator);

    // Capture the child pid for TEARDOWN only — the quiet child is NEVER killed
    // mid-read (it stays live + silent through the read window, so the idle slot is
    // faithfully observable and the broker keeps the session row).
    let child_pid = _broker.session_pid(sid);

    // Hold idle, then read what the broker still believes.

let _origin = ready_rx.recv_timeout(Duration::from_secs(5)).ok();

-    // stamp driven_by=Some(origin). Then make the session IDLE: kill the child so

-    // there is no output to trigger the drain-evict.

+    // stamp driven_by=Some(origin). The quiet child keeps the session IDLE (no

+    // output → no drain-evict) without any kill.

-    //    The session is idle, so nothing evicts the controller slot. ──

+    //    The session is idle (quiet child), so nothing evicts the controller slot. ──

-    // Kill the child so the session is idle (no output → no drain-evict). Do this

-    // AFTER abandoning the operator so the slot can't be cleared by output flow.

+    // Capture the child pid for TEARDOWN only — the quiet child is NEVER killed

+    // mid-read (it stays live + silent through the read window, so the idle slot is

+    // faithfully observable and the broker keeps the session row).


Now add the explicit `REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT` pointer in the verdict comment. Reading the assertion region:


1693	        let cleared = cby_after_abandon == Some(None);
1694	        eprintln!(
1695	            "=== W5 A2 VERDICT: controller_by==None sufficient for Gap A? {cleared} \
1696	             (false ⇒ wedged slot stays Some(origin) ⇒ the lost-detach fix must be \
1697	             broker-side eviction — a reconcile `controller_by==None` check alone \
1698	             does NOT catch this; it only catches A1 / Gap B) ==="
1699	        );
1700
1701	        // HARD CHARACTERIZATION (the A2 answer the steering asked for): the
1702	        // abandoned remote controller on an IDLE session does NOT self-clear via
1703	        // the lost-detach path — controller_by stays Some(origin). This is the
1704	        // evidence that `controller_by==None` is NOT a sufficient signal for the
1705	        // production wedged case (A2): the fix must evict the dead controller
1706	        // broker-side (e.g. a controller-liveness probe / reconcile-driven
1707	        // clear_controller), not merely read controller_by in the reconcile.
1708	        assert!(
1709	            matches!(cby_after_abandon, Some(Some(_))),
1710	            "A2: an abandoned remote controller on an IDLE session must STILL read \
1711	             controller_by == Some(origin) (the slot does not self-clear without \
1712	             output-drain-evict or a clean EOF) — proving controller_by==None is \
1713	             NOT a sufficient Gap-A signal for the production wedged-pump case; the \
1714	             lost-detach clear must be broker-side eviction. Got: {cby_after_abandon:?}"
1715	        );
1716	        // And driven_by remained latched while the broker still thinks it's
1717	        // controlled (the phantom CONTROLLED the picker would render). The seed
1718	        // makes this observable; on a run where the seed/stamp didn't take it is

        let cleared = cby_after_abandon == Some(None);
        eprintln!(
            "=== W5 A2 VERDICT: controller_by==None sufficient for Gap A? {cleared} \
             (false ⇒ wedged slot stays Some(origin) ⇒ the lost-detach fix must be \
             broker-side eviction — a reconcile `controller_by==None` check alone \
             does NOT catch this; it only catches A1 / Gap B) ==="
        );

        // HARD CHARACTERIZATION (the A2 answer the steering asked for): the
        // abandoned remote controller on an IDLE session does NOT self-clear via
        // the lost-detach path — controller_by stays Some(origin). This is the
        // evidence that `controller_by==None` is NOT a sufficient signal for the
        // production wedged case (A2): the fix must evict the dead controller
        // broker-side (e.g. a controller-liveness probe / reconcile-driven
        // clear_controller), not merely read controller_by in the reconcile.
        assert!(
            matches!(cby_after_abandon, Some(Some(_))),
            "A2: an abandoned remote controller on an IDLE session must STILL read \
             controller_by == Some(origin) (the slot does not self-clear without \
             output-drain-evict or a clean EOF) — proving controller_by==None is \
             NOT a sufficient Gap-A signal for the production wedged-pump case; the \
             lost-detach clear must be broker-side eviction. Got: {cby_after_abandon:?}"
        );
        // And driven_by remained latched while the broker still thinks it's
        // controlled (the phantom CONTROLLED the picker would render). The seed
        // makes this observable; on a run where the seed/stamp didn't take it is


        // HARD CHARACTERIZATION (the A2 answer the steering asked for): the
        // abandoned remote controller on an IDLE session does NOT self-clear via
        // the lost-detach path — controller_by stays Some(origin). This is the
        // evidence that `controller_by==None` is NOT a sufficient signal for the
        // production wedged case (A2): the fix must evict the dead controller
        // broker-side (e.g. a controller-liveness probe / reconcile-driven
        // clear_controller), not merely read controller_by in the reconcile.

        // HARD CHARACTERIZATION (the A2 answer the steering asked for): the
        // abandoned remote controller on an IDLE session does NOT self-clear via
        // the lost-detach path — controller_by stays Some(origin). This is the
        // evidence that `controller_by==None` is NOT a sufficient signal for the
        // production wedged case (A2): the fix must evict the dead controller
        // broker-side (e.g. a controller-liveness probe / reconcile-driven
        // clear_controller), not merely read controller_by in the reconcile.
        //
        // The TRUE idle-evict is DEFERRED to REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT
        // (the D4c NetPresence-disconnect → clear_controller oracle); this test
        // characterizes the gap, it does not close it. (b4's CONTROLLER_WRITE_DEADLINE
        // / Disconnected-evict closes only the OUTPUT-path abandoned controller —
        // covered by REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE — which is
        // why this stage uses a QUIET child: an output-flooding child would let b4's
        // drain hit the dead-operator write and clear the slot, masking the idle gap.)


        // HARD CHARACTERIZATION (the A2 answer the steering asked for): the
        // abandoned remote controller on an IDLE session does NOT self-clear via
        // the lost-detach path — controller_by stays Some(origin). This is the
        // evidence that `controller_by==None` is NOT a sufficient signal for the
        // production wedged case (A2): the fix must evict the dead controller
        // broker-side (e.g. a controller-liveness probe / reconcile-driven
        // clear_controller), not merely read controller_by in the reconcile.
        //
        // The TRUE idle-evict is DEFERRED to REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT
        // (the D4c NetPresence-disconnect → clear_controller oracle); this test
        // characterizes the gap, it does not close it. (b4's CONTROLLER_WRITE_DEADLINE
        // / Disconnected-evict closes only the OUTPUT-path abandoned controller —
        // covered by REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE — which is
        // why this stage uses a QUIET child: an output-flooding child would let b4's
        // drain hit the dead-operator write and clear the slot, masking the idle gap.)

+        // The TRUE idle-evict is DEFERRED to REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT

+        // (the D4c NetPresence-disconnect → clear_controller oracle); this test

+        // characterizes the gap, it does not close it. (b4's CONTROLLER_WRITE_DEADLINE

+        // / Disconnected-evict closes only the OUTPUT-path abandoned controller —

+        // covered by REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE — which is

+        // why this stage uses a QUIET child: an output-flooding child would let b4's

+        // drain hit the dead-operator write and clear the slot, masking the idle gap.)

"A2: an abandoned remote controller on an IDLE session must STILL read \


Both helpers in use (flood for a_journaled/p0_paste/g2, quiet for w5_a2). Compile-checking the test + clippy, and checking the skip-to-live gate result:


Bash

echo "=== skip-to-live gate (b3pq4v4en) ==="; cat "C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\2730b458-50d2-48aa-b752-3ad22fefaa0b\tasks\b3pq4v4en.output" 2>/dev/null | tail -12; echo "=== compile w5_a2 test + clippy ==="; cargo test -p spt-daemon --test inject_control_wedge --no-run > /tmp/wt.txt 2>&1; echo "compile EXIT=$?"; tail -4 /tmp/wt.txt

Check gate output + compile-check w5_a2 test


=== skip-to-live gate (b3pq4v4en) ===
=== compile w5_a2 test + clippy ===
compile EXIT=0
   Compiling spt-daemon v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 13.88s
  Executable tests\inject_control_wedge.rs (target\debug\deps\inject_control_wedge-728b905f7a39c795.exe)


w5_a2 test compiles clean (EXIT=0). The bundled gate (b3pq4v4en) is still pipe-buffered/stuck — superseding it with a clean run (file redirects, no pipes):


Successfully stopped task: b3pq4v4en (cargo test -p spt-daemon --lib 2>&1 | tail -6; echo "===CLIPPY==="; cargo clippy -p spt-daemon --all-targets -- -D warnings 2>&1 | tail -3; echo "===TRACE==="; traceable-reqs check 2>&1 | grep -E "SKIP-TO-LIVE|^Findings|must\]" | head)


cargo test -p spt-daemon --lib > /tmp/t.txt 2>&1; echo "TEST_EXIT=$?"; tail -8 /tmp/t.txt; echo "=== TRACE ==="; traceable-reqs check > /tmp/tr.txt 2>&1; echo "TRACE_EXIT=$?"; grep -E "SKIP-TO-LIVE|must\]" /tmp/tr.txt | head

Clean lib test + traceable run


Command running in background with ID: bphh09ok0. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\2730b458-50d2-48aa-b752-3ad22fefaa0b\tasks\bphh09ok0.output. You will be notified when it completes. To check interim output, use Read on that file path.


w5_a2 B-cut compiles clean. Test + traceable run is executing (file-redirected now, so I'll get real output); clippy next. Awaiting completion.

**w5_a2 Cut B done** (per doyle's ruling):
- Added `quiet_spawn_req` (unix `sleep 600` / win `cmd /c ping -n 600 … >nul`) — silent + never-exits
- w5_a2 uses it instead of `flood_spawn_req`; assertion unchanged (`cby_after_abandon` stays `Some(Some(_))`)
- No mid-read kill (teardown-only) → also kills the Windows reap-during-read flake
- Verdict comment now points the true idle-evict at `REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT`; notes b4's Disconnected/deadline evict covers only the output-path

Once test + clippy + traceable confirm green, I'll ack doyle (w5_a2 resolved, b4 foldable) and surface the branch as commit-ready. Standing by for the run.


it's been ~15m. status?


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


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


I'll check the run that's been executing:


echo "=== test+trace run (bphh09ok0) ==="; cat "C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\2730b458-50d2-48aa-b752-3ad22fefaa0b\tasks\bphh09ok0.output" 2>/dev/null | tail -15 || echo "EMPTY"

Read test+trace run output


=== test+trace run (bphh09ok0) ===


15m + empty = a test is **hanging** (lib tests should finish in ~30s). Killing it and bisecting the new tests timeout-bounded (per my own hung-test lesson — take over, don't wait):


Successfully stopped task: bphh09ok0 (cargo test -p spt-daemon --lib > /tmp/t.txt 2>&1; echo "TEST_EXIT=$?"; tail -8 /tmp/t.txt; echo "=== TRACE ==="; traceable-reqs check > /tmp/tr.txt 2>&1; echo "TRACE_EXIT=$?"; grep -E "SKIP-TO-LIVE|must\]" /tmp/tr.txt | head)


cd /c/Users/decid/Documents/projects/spt-core
echo "=== msg (fast, no threads) ==="; timeout 90 cargo test -p spt-daemon --lib evicted_envelope_round_trips -- --test-threads=1 > /tmp/m.txt 2>&1; echo "msg EXIT=$? (124=timeout)"; tail -6 /tmp/m.txt

Stop hung run; test msg target timeout-bounded


=== msg (fast, no threads) ===
msg EXIT=0 (124=timeout)

running 1 test
test msg::tests::evicted_envelope_round_trips ... ok

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