        let running = prior_version.unwrap_or(version.saturating_sub(1));
        eprintln!("BRAIN_ROLLBACK: update v{version} failed readiness — rolling back to v{running} ({rollback_binary}), quarantining v{version}");
        env.record_rolled_back(*version, running, rollback_binary);
    }
}

        let running = prior_version.unwrap_or(version.saturating_sub(1));
        eprintln!("BRAIN_ROLLBACK: update v{version} failed readiness — rolling back to v{running} ({rollback_binary}), quarantining v{version}");
        env.record_rolled_back(*version, running, rollback_binary);
    }
}

/// Promotion bytes-gate verdict (KH 6.11, `REQ-HAZARD-BRAIN-RESPAWN-PATH`).
enum BytesGate {
    /// Candidate is running the staged bytes — promote.
    Match,
    /// Candidate is ready but running the WRONG bytes — fail the trial, roll back.
    Mismatch,
    /// Either hash absent (pre-metadata release / missing breadcrumb) — degrade
    /// to readiness-only promotion (N-1-safe).
    Unverified,
}

/// Compare the just-ready candidate's stamped `exe_hash` against the staged
/// artifact hash for `version` on this platform (KH 6.11). Readiness alone is
/// NOT proof the new bytes run — on Linux a brain respawned onto the renamed
/// old binary still signals ready (the v0.4.1 kitsubito false-success). On
/// either hash absent the gate degrades to readiness-only but emits
/// `PROMOTE_BYTES_UNVERIFIED` so a disarmed gate stays field-diagnosable.
// [impl->REQ-HAZARD-BRAIN-RESPAWN-PATH]
fn bytes_gate(env: &dyn TrialEnv, version: u64) -> BytesGate {
    match (env.staged_artifact_hash(version), env.ready_exe_hash()) {
        (Some(expected), Some(actual)) if expected == actual => BytesGate::Match,
        (Some(_), Some(_)) => BytesGate::Mismatch,
        (expected, _) => {
            let which = if expected.is_none() {
                "artifact-hash-absent"
            } else {
                "ready-hash-absent"
            };
            eprintln!("PROMOTE_BYTES_UNVERIFIED:{version}:{which}");
            BytesGate::Unverified
        }
    }
}


deployah is authorized to publish when it's ready. just message them


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

        let running = prior_version.unwrap_or(version.saturating_sub(1));
        eprintln!("BRAIN_ROLLBACK: update v{version} failed readiness — rolling back to v{running} ({rollback_binary}), quarantining v{version}");
        env.record_rolled_back(*version, running, rollback_binary);
    }
}

/// Promotion bytes-gate verdict (KH 6.11, `REQ-HAZARD-BRAIN-RESPAWN-PATH`).
enum BytesGate {
    /// Candidate is running the staged bytes — promote.
    Match,
    /// Candidate is ready but running the WRONG bytes — fail the trial, roll back.
    Mismatch,
    /// Either hash absent (pre-metadata release / missing breadcrumb) — degrade
    /// to readiness-only promotion (N-1-safe).
    Unverified,
}

/// Compare the just-ready candidate's stamped `exe_hash` against the staged
/// artifact hash for `version` on this platform (KH 6.11). Readiness alone is
/// NOT proof the new bytes run — on Linux a brain respawned onto the renamed
/// old binary still signals ready (the v0.4.1 kitsubito false-success). On
/// either hash absent the gate degrades to readiness-only but emits
/// `PROMOTE_BYTES_UNVERIFIED` so a disarmed gate stays field-diagnosable.
// [impl->REQ-HAZARD-BRAIN-RESPAWN-PATH]
fn bytes_gate(env: &dyn TrialEnv, version: u64) -> BytesGate {
    match (env.staged_artifact_hash(version), env.ready_exe_hash()) {
        (Some(expected), Some(actual)) if expected == actual => BytesGate::Match,
        (Some(_), Some(_)) => BytesGate::Mismatch,
        (expected, _) => {
            let which = if expected.is_none() {
                "artifact-hash-absent"
            } else {
                "ready-hash-absent"
            };
            eprintln!("PROMOTE_BYTES_UNVERIFIED:{version}:{which}");
            BytesGate::Unverified
        }
    }
}

+/// Promotion bytes-gate verdict (KH 6.11, `REQ-HAZARD-BRAIN-RESPAWN-PATH`).

+    /// Candidate is running the staged bytes — promote.

+    /// Candidate is ready but running the WRONG bytes — fail the trial, roll back.

+    /// Either hash absent (pre-metadata release / missing breadcrumb) — degrade

+    /// to readiness-only promotion (N-1-safe).

+/// Compare the just-ready candidate's stamped `exe_hash` against the staged

+/// artifact hash for `version` on this platform (KH 6.11). Readiness alone is

+/// NOT proof the new bytes run — on Linux a brain respawned onto the renamed

+/// old binary still signals ready (the v0.4.1 kitsubito false-success). On

+/// either hash absent the gate degrades to readiness-only but emits

+/// `PROMOTE_BYTES_UNVERIFIED` so a disarmed gate stays field-diagnosable.

+fn bytes_gate(env: &dyn TrialEnv, version: u64) -> BytesGate {

+    match (env.staged_artifact_hash(version), env.ready_exe_hash()) {

+        (Some(expected), Some(actual)) if expected == actual => BytesGate::Match,

+        (Some(_), Some(_)) => BytesGate::Mismatch,

+            let which = if expected.is_none() {

+            eprintln!("PROMOTE_BYTES_UNVERIFIED:{version}:{which}");

/// Spawn the production brain child: re-exec `daemon brain` as a plain

/// (non-detached) managed child, so it inherits the broker's environment —

/// including `SPT_HOME` — and dies with no orphaning. NOT `spawn_detached`


Now the Promoted-arm bytes-gate:


Edit

                        TrialStep::Promoted => {
                            if let Some(AppliedRecord::AppliedPending { version, .. }) = &record {
                                eprintln!("BRAIN_PROMOTED: candidate v{version} signalled ready (generation {generation}) — swap accepted");
                                env.record_promoted(*version);
                            }
                            pre_ready_exits = 0;
                            backoff = base;
                            supervise = true; // keep supervising the now-accepted child
                        }

                        TrialStep::Promoted => {
                            // Bytes-gate (KH 6.11): readiness != new-bytes. Promote
                            // only if the candidate is running the staged bytes; a
                            // ready-but-wrong-bytes candidate (e.g. a Linux respawn
                            // onto the renamed old binary) fails the trial → kill +
                            // rollback, never a falsely-`applied` record.
                            let version = match &record {
                                Some(AppliedRecord::AppliedPending { version, .. }) => Some(*version),
                                _ => None,
                            };
                            if let Some(version) = version {
                                if let BytesGate::Mismatch = bytes_gate(env, version) {
                                    eprintln!("BRAIN_PROMOTE_REJECTED: candidate v{version} ready but running the WRONG bytes (generation {generation}) — rolling back (KH 6.11)");
                                    let _ = child.kill();
                                    let _ = child.wait();
                                    rollback(env, &record);
                                    pre_ready_exits = 0;
                                    generation += 1;
                                    reason = StartReason::Crash;
                                    backoff = base;
                                    continue;
                                }
                                eprintln!("BRAIN_PROMOTED: candidate v{version} signalled ready (generation {generation}) — swap accepted");
                                env.record_promoted(version);
                            }
                            pre_ready_exits = 0;
                            backoff = base;
                            supervise = true; // keep supervising the now-accepted child
                        }


                        TrialStep::Promoted => {
                            if let Some(AppliedRecord::AppliedPending { version, .. }) = &record {
                                eprintln!("BRAIN_PROMOTED: candidate v{version} signalled ready (generation {generation}) — swap accepted");
                                env.record_promoted(*version);
                            }
                            pre_ready_exits = 0;
                            backoff = base;
                            supervise = true; // keep supervising the now-accepted child
                        }

                        TrialStep::Promoted => {
                            // Bytes-gate (KH 6.11): readiness != new-bytes. Promote
                            // only if the candidate is running the staged bytes; a
                            // ready-but-wrong-bytes candidate (e.g. a Linux respawn
                            // onto the renamed old binary) fails the trial → kill +
                            // rollback, never a falsely-`applied` record.
                            let version = match &record {
                                Some(AppliedRecord::AppliedPending { version, .. }) => Some(*version),
                                _ => None,
                            };
                            if let Some(version) = version {
                                if let BytesGate::Mismatch = bytes_gate(env, version) {
                                    eprintln!("BRAIN_PROMOTE_REJECTED: candidate v{version} ready but running the WRONG bytes (generation {generation}) — rolling back (KH 6.11)");
                                    let _ = child.kill();
                                    let _ = child.wait();
                                    rollback(env, &record);
                                    pre_ready_exits = 0;
                                    generation += 1;
                                    reason = StartReason::Crash;
                                    backoff = base;
                                    continue;
                                }
                                eprintln!("BRAIN_PROMOTED: candidate v{version} signalled ready (generation {generation}) — swap accepted");
                                env.record_promoted(version);
                            }
                            pre_ready_exits = 0;
                            backoff = base;
                            supervise = true; // keep supervising the now-accepted child
                        }

-                            if let Some(AppliedRecord::AppliedPending { version, .. }) = &record {

+                            // Bytes-gate (KH 6.11): readiness != new-bytes. Promote

+                            // only if the candidate is running the staged bytes; a

+                            // ready-but-wrong-bytes candidate (e.g. a Linux respawn

+                            // onto the renamed old binary) fails the trial → kill +

+                            let version = match &record {

+                                Some(AppliedRecord::AppliedPending { version, .. }) => Some(*version),

+                            if let Some(version) = version {

+                                if let BytesGate::Mismatch = bytes_gate(env, version) {

+                                    eprintln!("BRAIN_PROMOTE_REJECTED: candidate v{version} ready but running the WRONG bytes (generation {generation}) — rolling back (KH 6.11)");

+                                    let _ = child.kill();

+                                    let _ = child.wait();

+                                    rollback(env, &record);

+                                    pre_ready_exits = 0;

+                                    generation += 1;

+                                    reason = StartReason::Crash;

+                                    backoff = base;

+                                    continue;

-                                env.record_promoted(*version);

+                                env.record_promoted(version);


Half 1 — `spawn_brain_child` signature/body/doc:


/// `binary` is the **record-driven selection** (D6-2): `None` spawns the default
/// `current_exe` (the accepted / candidate binary), `Some(path)` spawns the
/// rollback binary (the `.old-N` last-known-good) — the supervisor chooses, no
/// file is renamed at the failure instant.
fn spawn_brain_child(
    generation: u64,
    reason: StartReason,
    binary: Option<&Path>,
) -> io::Result<Child> {
    let exe = match binary {
        Some(p) => p.to_path_buf(),
        None => std::env::current_exe()?,
    };
    let mut cmd = Command::new(exe);

/// `binary` is the **record-driven selection** (D6-2): `None` spawns the
/// accepted / candidate binary (`canonical`, below), `Some(path)` spawns the
/// rollback binary (the `.old-N` last-known-good) — the supervisor chooses, no
/// file is renamed at the failure instant.
///
/// `canonical` is the canonical exe path captured ONCE at broker start
/// (`spawn_brain_supervisor`). The `None` default uses it — **never** a
/// per-spawn `std::env::current_exe()` — because on Linux `current_exe()`
/// (`readlink /proc/self/exe`) is inode-tracking and follows the `apply` rename
/// (`spt` → `spt.old-N`), which would respawn the brain onto the OLD bytes
/// (KH 6.11). Falls back to `current_exe()` only if the t=0 capture failed
/// (degrade-safe — restores pre-fix behavior rather than failing to spawn).
// [impl->REQ-HAZARD-BRAIN-RESPAWN-PATH]
fn spawn_brain_child(
    generation: u64,
    reason: StartReason,
    binary: Option<&Path>,
    canonical: Option<&Path>,
) -> io::Result<Child> {
    let exe = match binary {
        Some(p) => p.to_path_buf(),
        None => match canonical {
            Some(c) => c.to_path_buf(),
            None => std::env::current_exe()?,
        },
    };
    let mut cmd = Command::new(exe);


/// `binary` is the **record-driven selection** (D6-2): `None` spawns the default
/// `current_exe` (the accepted / candidate binary), `Some(path)` spawns the
/// rollback binary (the `.old-N` last-known-good) — the supervisor chooses, no
/// file is renamed at the failure instant.
fn spawn_brain_child(
    generation: u64,
    reason: StartReason,
    binary: Option<&Path>,
) -> io::Result<Child> {
    let exe = match binary {
        Some(p) => p.to_path_buf(),
        None => std::env::current_exe()?,
    };
    let mut cmd = Command::new(exe);

/// `binary` is the **record-driven selection** (D6-2): `None` spawns the
/// accepted / candidate binary (`canonical`, below), `Some(path)` spawns the
/// rollback binary (the `.old-N` last-known-good) — the supervisor chooses, no
/// file is renamed at the failure instant.
///
/// `canonical` is the canonical exe path captured ONCE at broker start
/// (`spawn_brain_supervisor`). The `None` default uses it — **never** a
/// per-spawn `std::env::current_exe()` — because on Linux `current_exe()`
/// (`readlink /proc/self/exe`) is inode-tracking and follows the `apply` rename
/// (`spt` → `spt.old-N`), which would respawn the brain onto the OLD bytes
/// (KH 6.11). Falls back to `current_exe()` only if the t=0 capture failed
/// (degrade-safe — restores pre-fix behavior rather than failing to spawn).
// [impl->REQ-HAZARD-BRAIN-RESPAWN-PATH]
fn spawn_brain_child(
    generation: u64,
    reason: StartReason,
    binary: Option<&Path>,
    canonical: Option<&Path>,
) -> io::Result<Child> {
    let exe = match binary {
        Some(p) => p.to_path_buf(),
        None => match canonical {
            Some(c) => c.to_path_buf(),
            None => std::env::current_exe()?,
        },
    };
    let mut cmd = Command::new(exe);

/// (that path rebuilds the Windows env block and would drop `SPT_HOME`). The

/// broker stamps `{generation, start-reason}` onto the argv (ADR-0018 V2).

-/// `binary` is the **record-driven selection** (D6-2): `None` spawns the default

-/// `current_exe` (the accepted / candidate binary), `Some(path)` spawns the

+/// `binary` is the **record-driven selection** (D6-2): `None` spawns the

+/// accepted / candidate binary (`canonical`, below), `Some(path)` spawns the

+/// `canonical` is the canonical exe path captured ONCE at broker start

+/// (`spawn_brain_supervisor`). The `None` default uses it — **never** a

+/// per-spawn `std::env::current_exe()` — because on Linux `current_exe()`

+/// (`readlink /proc/self/exe`) is inode-tracking and follows the `apply` rename

+/// (`spt` → `spt.old-N`), which would respawn the brain onto the OLD bytes

+/// (KH 6.11). Falls back to `current_exe()` only if the t=0 capture failed

+/// (degrade-safe — restores pre-fix behavior rather than failing to spawn).

-        None => std::env::current_exe()?,

+            Some(c) => c.to_path_buf(),

+            None => std::env::current_exe()?,


Half 1 — `spawn_brain_supervisor` canonical capture:


    let restart = Arc::new(BrainRestart::new());
    let restart_sup = Arc::clone(&restart);
    let handle = thread::spawn(move || {
        let env = ProductionTrialEnv;
        supervise_brain(
            &stop,
            &restart_sup,
            SUPERVISE_BACKOFF_BASE,
            &env,
            SUPERVISE_HEALTHY_RUN,
            spawn_brain_child,
        );
    });
    (handle, restart)

    let restart = Arc::new(BrainRestart::new());
    let restart_sup = Arc::clone(&restart);
    // KH 6.11: capture the canonical brain binary path ONCE here, at broker
    // start, before any `apply` can rename it under us. Linux current_exe()
    // (readlink /proc/self/exe) is inode-tracking and would later follow the
    // apply-rename (spt → spt.old-N) to the OLD bytes; capturing at t=0 pins the
    // canonical path string, matching Windows' path-at-start semantics. The one
    // true race — an apply landing in the microseconds between broker exec and
    // this line — is vanishingly rare and backstopped by the promotion
    // bytes-gate (a wrong-bytes trial rolls back loudly, never promotes).
    let canonical_exe = std::env::current_exe().ok();
    let handle = thread::spawn(move || {
        let env = ProductionTrialEnv;
        supervise_brain(
            &stop,
            &restart_sup,
            SUPERVISE_BACKOFF_BASE,
            &env,
            SUPERVISE_HEALTHY_RUN,
            |generation, reason, binary| {
                spawn_brain_child(generation, reason, binary, canonical_exe.as_deref())
            },
        );
    });
    (handle, restart)


    let restart = Arc::new(BrainRestart::new());
    let restart_sup = Arc::clone(&restart);
    let handle = thread::spawn(move || {
        let env = ProductionTrialEnv;
        supervise_brain(
            &stop,
            &restart_sup,
            SUPERVISE_BACKOFF_BASE,
            &env,
            SUPERVISE_HEALTHY_RUN,
            spawn_brain_child,
        );
    });
    (handle, restart)

    let restart = Arc::new(BrainRestart::new());
    let restart_sup = Arc::clone(&restart);
    // KH 6.11: capture the canonical brain binary path ONCE here, at broker
    // start, before any `apply` can rename it under us. Linux current_exe()
    // (readlink /proc/self/exe) is inode-tracking and would later follow the
    // apply-rename (spt → spt.old-N) to the OLD bytes; capturing at t=0 pins the
    // canonical path string, matching Windows' path-at-start semantics. The one
    // true race — an apply landing in the microseconds between broker exec and
    // this line — is vanishingly rare and backstopped by the promotion
    // bytes-gate (a wrong-bytes trial rolls back loudly, never promotes).
    let canonical_exe = std::env::current_exe().ok();
    let handle = thread::spawn(move || {
        let env = ProductionTrialEnv;
        supervise_brain(
            &stop,
            &restart_sup,
            SUPERVISE_BACKOFF_BASE,
            &env,
            SUPERVISE_HEALTHY_RUN,
            |generation, reason, binary| {
                spawn_brain_child(generation, reason, binary, canonical_exe.as_deref())
            },
        );
    });
    (handle, restart)

pub fn spawn_brain_supervisor(stop: Arc<AtomicBool>) -> (thread::JoinHandle<()>, Arc<BrainRestart>) {

+    // KH 6.11: capture the canonical brain binary path ONCE here, at broker

+    // start, before any `apply` can rename it under us. Linux current_exe()

+    // (readlink /proc/self/exe) is inode-tracking and would later follow the

+    // apply-rename (spt → spt.old-N) to the OLD bytes; capturing at t=0 pins the

+    // canonical path string, matching Windows' path-at-start semantics. The one

+    // true race — an apply landing in the microseconds between broker exec and

+    // this line — is vanishingly rare and backstopped by the promotion

+    // bytes-gate (a wrong-bytes trial rolls back loudly, never promotes).

+    let canonical_exe = std::env::current_exe().ok();

+            |generation, reason, binary| {

+                spawn_brain_child(generation, reason, binary, canonical_exe.as_deref())


Now the test fake `TrialEnv` impls must satisfy the two new methods (else `cargo test` won't compile). Finding them.


Bash

grep -n "impl TrialEnv\|struct.*Env\|fn record_rolled_back\|fn ready_generation\|spawn_brain_child(" crates/spt-daemon/src/brainproc.rs | sed -n '1,40p'

Find test fake TrialEnv impls


360:pub fn ready_generation_at(path: &std::path::Path) -> Option<u64> {
393:    fn ready_generation(&self) -> Option<u64>;
406:    fn record_rolled_back(&self, quarantine_version: u64, running_version: u64, rollback_binary: &str);
420:pub struct NoTrialEnv;
422:impl TrialEnv for NoTrialEnv {
426:    fn ready_generation(&self) -> Option<u64> {
431:    fn record_rolled_back(&self, _q: u64, _r: u64, _bin: &str) {}
438:pub struct ProductionTrialEnv;
446:impl TrialEnv for ProductionTrialEnv {
450:    fn ready_generation(&self) -> Option<u64> {
464:    fn record_rolled_back(&self, quarantine_version: u64, running_version: u64, rollback_binary: &str) {
890:fn spawn_brain_child(
963:                spawn_brain_child(generation, reason, binary, canonical_exe.as_deref())
1196:    fn ready_generation_parses_stamp_and_fails_safe() {
1222:    fn ready_generation_at_reads_file_and_absent_is_none() {
1280:    struct TestTrialEnv {
1317:    impl TrialEnv for TestTrialEnv {
1321:        fn ready_generation(&self) -> Option<u64> {
1332:        fn record_rolled_back(&self, q: u64, r: u64, bin: &str) {


Two fakes need the methods (`NoTrialEnv` l.422, `TestTrialEnv` l.1317). For T2, satisfy them returning `None` (preserves existing behavior — degrade to readiness-only); T3 extends `TestTrialEnv` with hash fields for the truth table. Reading both.


418	/// ready" and every effect is a no-op. Used where there is no readiness gate to
419	/// drive — the supervisor then behaves exactly as the pre-D6 respawn loop.
420	pub struct NoTrialEnv;
421
422	impl TrialEnv for NoTrialEnv {
423	    fn applied_state(&self) -> Option<AppliedRecord> {
424	        None
425	    }
426	    fn ready_generation(&self) -> Option<u64> {
427	        None
428	    }
429	    fn clear_ready(&self) {}
430	    fn record_promoted(&self, _version: u64) {}
431	    fn record_rolled_back(&self, _q: u64, _r: u64, _bin: &str) {}
432	}
433
434	/// Production [`TrialEnv`]: reads/writes the canonical `spt_home` durable files
435	/// (the `releases` relcache + the `brain.ready` breadcrumb) and fires the

/// ready" and every effect is a no-op. Used where there is no readiness gate to
/// drive — the supervisor then behaves exactly as the pre-D6 respawn loop.
pub struct NoTrialEnv;

impl TrialEnv for NoTrialEnv {
    fn applied_state(&self) -> Option<AppliedRecord> {
        None
    }
    fn ready_generation(&self) -> Option<u64> {
        None
    }
    fn clear_ready(&self) {}
    fn record_promoted(&self, _version: u64) {}
    fn record_rolled_back(&self, _q: u64, _r: u64, _bin: &str) {}
}

/// Production [`TrialEnv`]: reads/writes the canonical `spt_home` durable files
/// (the `releases` relcache + the `brain.ready` breadcrumb) and fires the


1280	    struct TestTrialEnv {
1281	        state: Mutex<Option<AppliedRecord>>,
1282	        ready: Mutex<Option<u64>>,
1283	        clears: AtomicU32,
1284	        promotions: Mutex<Vec<u64>>,
1285	        rollbacks: Mutex<Vec<(u64, u64, String)>>,
1286	        notifs: AtomicU32,
1287	    }
1288
1289	    impl TestTrialEnv {
1290	        fn pending(version: u64, rollback_binary: &str) -> Self {
1291	            TestTrialEnv {
1292	                state: Mutex::new(Some(AppliedRecord::AppliedPending {
1293	                    version,
1294	                    rollback_binary: rollback_binary.to_string(),
1295	                    candidate_started_ms: 0,
1296	                    prior_version: None,
1297	                })),
1298	                ready: Mutex::new(None),
1299	                clears: AtomicU32::new(0),
1300	                promotions: Mutex::new(Vec::new()),
1301	                rollbacks: Mutex::new(Vec::new()),
1302	                notifs: AtomicU32::new(0),
1303	            }
1304	        }
1305
1306	        fn rolled_back(quarantine: u64, running: u64, rollback_binary: &str) -> Self {
1307	            let env = TestTrialEnv::pending(quarantine, rollback_binary);
1308	            *env.state.lock().unwrap() = Some(AppliedRecord::RolledBack {
1309	                quarantine_version: quarantine,
1310	                running_version: running,
1311	                rollback_binary: rollback_binary.to_string(),
1312	            });
1313	            env
1314	        }
1315	    }
1316
1317	    impl TrialEnv for TestTrialEnv {
1318	        fn applied_state(&self) -> Option<AppliedRecord> {
1319	            self.state.lock().unwrap().clone()
1320	        }
1321	        fn ready_generation(&self) -> Option<u64> {
1322	            *self.ready.lock().unwrap()
1323	        }
1324	        fn clear_ready(&self) {
1325	            *self.ready.lock().unwrap() = None;
1326	            self.clears.fetch_add(1, Ordering::Relaxed);
1327	        }
1328	        fn record_promoted(&self, version: u64) {
1329	            self.promotions.lock().unwrap().push(version);
1330	            *self.state.lock().unwrap() = Some(AppliedRecord::Applied { version });
1331	        }
1332	        fn record_rolled_back(&self, q: u64, r: u64, bin: &str) {
1333	            self.rollbacks.lock().unwrap().push((q, r, bin.to_string()));
1334	            *self.state.lock().unwrap() = Some(AppliedRecord::RolledBack {
1335	                quarantine_version: q,
1336	                running_version: r,
1337	                rollback_binary: bin.to_string(),
1338	            });
1339	            // The capturing notif sink: the loud rollback notif fired.

    struct TestTrialEnv {
        state: Mutex<Option<AppliedRecord>>,
        ready: Mutex<Option<u64>>,
        clears: AtomicU32,
        promotions: Mutex<Vec<u64>>,
        rollbacks: Mutex<Vec<(u64, u64, String)>>,
        notifs: AtomicU32,
    }

    impl TestTrialEnv {
        fn pending(version: u64, rollback_binary: &str) -> Self {
            TestTrialEnv {
                state: Mutex::new(Some(AppliedRecord::AppliedPending {
                    version,
                    rollback_binary: rollback_binary.to_string(),
                    candidate_started_ms: 0,
                    prior_version: None,
                })),
                ready: Mutex::new(None),
                clears: AtomicU32::new(0),
                promotions: Mutex::new(Vec::new()),
                rollbacks: Mutex::new(Vec::new()),
                notifs: AtomicU32::new(0),
            }
        }

        fn rolled_back(quarantine: u64, running: u64, rollback_binary: &str) -> Self {
            let env = TestTrialEnv::pending(quarantine, rollback_binary);
            *env.state.lock().unwrap() = Some(AppliedRecord::RolledBack {
                quarantine_version: quarantine,
                running_version: running,
                rollback_binary: rollback_binary.to_string(),
            });
            env
        }
    }

    impl TrialEnv for TestTrialEnv {
        fn applied_state(&self) -> Option<AppliedRecord> {
            self.state.lock().unwrap().clone()
        }
        fn ready_generation(&self) -> Option<u64> {
            *self.ready.lock().unwrap()
        }
        fn clear_ready(&self) {
            *self.ready.lock().unwrap() = None;
            self.clears.fetch_add(1, Ordering::Relaxed);
        }
        fn record_promoted(&self, version: u64) {
            self.promotions.lock().unwrap().push(version);
            *self.state.lock().unwrap() = Some(AppliedRecord::Applied { version });
        }
        fn record_rolled_back(&self, q: u64, r: u64, bin: &str) {
            self.rollbacks.lock().unwrap().push((q, r, bin.to_string()));
            *self.state.lock().unwrap() = Some(AppliedRecord::RolledBack {
                quarantine_version: q,
                running_version: r,
                rollback_binary: bin.to_string(),
            });
            // The capturing notif sink: the loud rollback notif fired.


T2 — satisfy `NoTrialEnv` (None/None → readiness-only) and add hash fields to `TestTrialEnv` now (so T3 reuses them; defaults preserve existing tests).


Edit

    fn clear_ready(&self) {}
    fn record_promoted(&self, _version: u64) {}
    fn record_rolled_back(&self, _q: u64, _r: u64, _bin: &str) {}
}

    fn clear_ready(&self) {}
    fn record_promoted(&self, _version: u64) {}
    fn record_rolled_back(&self, _q: u64, _r: u64, _bin: &str) {}
    fn ready_exe_hash(&self) -> Option<String> {
        None
    }
    fn staged_artifact_hash(&self, _version: u64) -> Option<String> {
        None
    }
}


    fn clear_ready(&self) {}
    fn record_promoted(&self, _version: u64) {}
    fn record_rolled_back(&self, _q: u64, _r: u64, _bin: &str) {}
}

    fn clear_ready(&self) {}
    fn record_promoted(&self, _version: u64) {}
    fn record_rolled_back(&self, _q: u64, _r: u64, _bin: &str) {}
    fn ready_exe_hash(&self) -> Option<String> {
        None
    }
    fn staged_artifact_hash(&self, _version: u64) -> Option<String> {
        None
    }
}

+    fn ready_exe_hash(&self) -> Option<String> {

+    fn staged_artifact_hash(&self, _version: u64) -> Option<String> {

/// Production [`TrialEnv`]: reads/writes the canonical `spt_home` durable files


    struct TestTrialEnv {
        state: Mutex<Option<AppliedRecord>>,
        ready: Mutex<Option<u64>>,
        clears: AtomicU32,
        promotions: Mutex<Vec<u64>>,
        rollbacks: Mutex<Vec<(u64, u64, String)>>,
        notifs: AtomicU32,
    }

    impl TestTrialEnv {
        fn pending(version: u64, rollback_binary: &str) -> Self {
            TestTrialEnv {
                state: Mutex::new(Some(AppliedRecord::AppliedPending {
                    version,
                    rollback_binary: rollback_binary.to_string(),
                    candidate_started_ms: 0,
                    prior_version: None,
                })),
                ready: Mutex::new(None),
                clears: AtomicU32::new(0),
                promotions: Mutex::new(Vec::new()),
                rollbacks: Mutex::new(Vec::new()),
                notifs: AtomicU32::new(0),
            }
        }

    struct TestTrialEnv {
        state: Mutex<Option<AppliedRecord>>,
        ready: Mutex<Option<u64>>,
        clears: AtomicU32,
        promotions: Mutex<Vec<u64>>,
        rollbacks: Mutex<Vec<(u64, u64, String)>>,
        notifs: AtomicU32,
        // Bytes-gate fakes (KH 6.11): default None/None → Unverified → degrade to
        // readiness-only, preserving the pre-gate promotion behavior every other
        // test relies on. The truth-table tests set these explicitly.
        ready_hash: Mutex<Option<String>>,
        staged_hash: Mutex<Option<String>>,
    }

    impl TestTrialEnv {
        fn pending(version: u64, rollback_binary: &str) -> Self {
            TestTrialEnv {
                state: Mutex::new(Some(AppliedRecord::AppliedPending {
                    version,
                    rollback_binary: rollback_binary.to_string(),
                    candidate_started_ms: 0,
                    prior_version: None,
                })),
                ready: Mutex::new(None),
                clears: AtomicU32::new(0),
                promotions: Mutex::new(Vec::new()),
                rollbacks: Mutex::new(Vec::new()),
                notifs: AtomicU32::new(0),
                ready_hash: Mutex::new(None),
                staged_hash: Mutex::new(None),
            }
        }

        /// Arm the bytes-gate: the candidate's stamped hash + the staged hash.
        fn with_hashes(self, ready: Option<&str>, staged: Option<&str>) -> Self {
            *self.ready_hash.lock().unwrap() = ready.map(str::to_string);
            *self.staged_hash.lock().unwrap() = staged.map(str::to_string);
            self
        }


    struct TestTrialEnv {
        state: Mutex<Option<AppliedRecord>>,
        ready: Mutex<Option<u64>>,
        clears: AtomicU32,
        promotions: Mutex<Vec<u64>>,
        rollbacks: Mutex<Vec<(u64, u64, String)>>,
        notifs: AtomicU32,
    }

    impl TestTrialEnv {
        fn pending(version: u64, rollback_binary: &str) -> Self {
            TestTrialEnv {
                state: Mutex::new(Some(AppliedRecord::AppliedPending {
                    version,
                    rollback_binary: rollback_binary.to_string(),
                    candidate_started_ms: 0,
                    prior_version: None,
                })),
                ready: Mutex::new(None),
                clears: AtomicU32::new(0),
                promotions: Mutex::new(Vec::new()),
                rollbacks: Mutex::new(Vec::new()),
                notifs: AtomicU32::new(0),
            }
        }

    struct TestTrialEnv {
        state: Mutex<Option<AppliedRecord>>,
        ready: Mutex<Option<u64>>,
        clears: AtomicU32,
        promotions: Mutex<Vec<u64>>,
        rollbacks: Mutex<Vec<(u64, u64, String)>>,
        notifs: AtomicU32,
        // Bytes-gate fakes (KH 6.11): default None/None → Unverified → degrade to
        // readiness-only, preserving the pre-gate promotion behavior every other
        // test relies on. The truth-table tests set these explicitly.
        ready_hash: Mutex<Option<String>>,
        staged_hash: Mutex<Option<String>>,
    }

    impl TestTrialEnv {
        fn pending(version: u64, rollback_binary: &str) -> Self {
            TestTrialEnv {
                state: Mutex::new(Some(AppliedRecord::AppliedPending {
                    version,
                    rollback_binary: rollback_binary.to_string(),
                    candidate_started_ms: 0,
                    prior_version: None,
                })),
                ready: Mutex::new(None),
                clears: AtomicU32::new(0),
                promotions: Mutex::new(Vec::new()),
                rollbacks: Mutex::new(Vec::new()),
                notifs: AtomicU32::new(0),
                ready_hash: Mutex::new(None),
                staged_hash: Mutex::new(None),
            }
        }

        /// Arm the bytes-gate: the candidate's stamped hash + the staged hash.
        fn with_hashes(self, ready: Option<&str>, staged: Option<&str>) -> Self {
            *self.ready_hash.lock().unwrap() = ready.map(str::to_string);
            *self.staged_hash.lock().unwrap() = staged.map(str::to_string);
            self
        }

+        // Bytes-gate fakes (KH 6.11): default None/None → Unverified → degrade to

+        // readiness-only, preserving the pre-gate promotion behavior every other

+        // test relies on. The truth-table tests set these explicitly.

+        ready_hash: Mutex<Option<String>>,

+        staged_hash: Mutex<Option<String>>,

+                ready_hash: Mutex::new(None),

+                staged_hash: Mutex::new(None),

+        /// Arm the bytes-gate: the candidate's stamped hash + the staged hash.

+        fn with_hashes(self, ready: Option<&str>, staged: Option<&str>) -> Self {

+            *self.ready_hash.lock().unwrap() = ready.map(str::to_string);

+            *self.staged_hash.lock().unwrap() = staged.map(str::to_string);

fn rolled_back(quarantine: u64, running: u64, rollback_binary: &str) -> Self {

let env = TestTrialEnv::pending(quarantine, rollback_binary);

*env.state.lock().unwrap() = Some(AppliedRecord::RolledBack {


Adding the two methods to `TestTrialEnv`'s impl. Reading its tail to anchor.


1345	            *self.ready.lock().unwrap() = None;
1346	            self.clears.fetch_add(1, Ordering::Relaxed);
1347	        }
1348	        fn record_promoted(&self, version: u64) {
1349	            self.promotions.lock().unwrap().push(version);
1350	            *self.state.lock().unwrap() = Some(AppliedRecord::Applied { version });
1351	        }
1352	        fn record_rolled_back(&self, q: u64, r: u64, bin: &str) {
1353	            self.rollbacks.lock().unwrap().push((q, r, bin.to_string()));
1354	            *self.state.lock().unwrap() = Some(AppliedRecord::RolledBack {
1355	                quarantine_version: q,
1356	                running_version: r,
1357	                rollback_binary: bin.to_string(),
1358	            });
1359	            // The capturing notif sink: the loud rollback notif fired.
1360	            self.notifs.fetch_add(1, Ordering::Relaxed);
1361	        }
1362	    }
1363
1364	    /// Cross-platform "is this pid still alive?" for the A11 never-two-live-brains

            *self.ready.lock().unwrap() = None;
            self.clears.fetch_add(1, Ordering::Relaxed);
        }
        fn record_promoted(&self, version: u64) {
            self.promotions.lock().unwrap().push(version);
            *self.state.lock().unwrap() = Some(AppliedRecord::Applied { version });
        }
        fn record_rolled_back(&self, q: u64, r: u64, bin: &str) {
            self.rollbacks.lock().unwrap().push((q, r, bin.to_string()));
            *self.state.lock().unwrap() = Some(AppliedRecord::RolledBack {
                quarantine_version: q,
                running_version: r,
                rollback_binary: bin.to_string(),
            });
            // The capturing notif sink: the loud rollback notif fired.
            self.notifs.fetch_add(1, Ordering::Relaxed);
        }
    }

    /// Cross-platform "is this pid still alive?" for the A11 never-two-live-brains


            // The capturing notif sink: the loud rollback notif fired.
            self.notifs.fetch_add(1, Ordering::Relaxed);
        }
    }

            // The capturing notif sink: the loud rollback notif fired.
            self.notifs.fetch_add(1, Ordering::Relaxed);
        }
        fn ready_exe_hash(&self) -> Option<String> {
            self.ready_hash.lock().unwrap().clone()
        }
        fn staged_artifact_hash(&self, _version: u64) -> Option<String> {
            self.staged_hash.lock().unwrap().clone()
        }
    }


            // The capturing notif sink: the loud rollback notif fired.
            self.notifs.fetch_add(1, Ordering::Relaxed);
        }
    }

            // The capturing notif sink: the loud rollback notif fired.
            self.notifs.fetch_add(1, Ordering::Relaxed);
        }
        fn ready_exe_hash(&self) -> Option<String> {
            self.ready_hash.lock().unwrap().clone()
        }
        fn staged_artifact_hash(&self, _version: u64) -> Option<String> {
            self.staged_hash.lock().unwrap().clone()
        }
    }

+        fn ready_exe_hash(&self) -> Option<String> {

+            self.ready_hash.lock().unwrap().clone()

+        fn staged_artifact_hash(&self, _version: u64) -> Option<String> {

+            self.staged_hash.lock().unwrap().clone()

/// Cross-platform "is this pid still alive?" for the A11 never-two-live-brains


T2 edits complete (Half 1 + Half 2 + fakes). Building daemon crate for fast feedback, then full gates.


Bash

cargo build -p spt-daemon 2>&1 | tail -25; echo "BUILD-EXIT:${PIPESTATUS[0]}"

Build spt-daemon (T2 fast check)


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


Builds clean. Running daemon tests + clippy (verify Half 1/2 didn't break existing trial tests).


cargo test -p spt-daemon 2>&1 | tail -20; echo "TEST-EXIT:${PIPESTATUS[0]}"; echo "=== clippy ==="; cargo clippy -p spt-daemon --all-targets -- -D warnings 2>&1 | tail -6; echo "CLIPPY-EXIT:${PIPESTATUS[0]}"

Daemon tests + clippy (T2)


running 2 tests
test wan_message_lands_exactly_once_under_transport_origin ... ok
test receiver_restart_replays_feed_without_double_delivery ... ok

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

     Running tests\xfer.rs (target\debug\deps\xfer-40561703eef5d61d.exe)

running 2 tests
test push_survives_target_brain_restart_exactly_once ... ok
test fetch_lands_byte_identical_with_progress_both_ends ... ok

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

   Doc-tests spt_daemon

running 0 tests

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

TEST-EXIT:0
=== clippy ===
     |            ^^^^^^^^^^^
     |
     = note: `-D dead-code` implied by `-D warnings`
     = help: to override `-D warnings` add `#[allow(dead_code)]`

error: could not compile `spt-daemon` (lib test) due to 1 previous error
CLIPPY-EXIT:101


Clippy `-D dead-code` — likely `with_hashes` (unused until T3) and/or unread `BytesGate` variants. Confirming exact warning.


cargo clippy -p spt-daemon --all-targets -- -D warnings 2>&1 | grep -B2 -A6 "never\|dead_code\|error\[" | head -40

Exact dead-code warning


    Checking spt-daemon v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
error: method `with_hashes` is never used
    --> crates\spt-daemon\src\brainproc.rs:1320:12
     |
1300 |     impl TestTrialEnv {
     |     ----------------- method in this implementation
...
1320 |         fn with_hashes(self, ready: Option<&str>, staged: Option<&str>) -> Self {
--
     |
     = note: `-D dead-code` implied by `-D warnings`
     = help: to override `-D warnings` add `#[allow(dead_code)]`

error: could not compile `spt-daemon` (lib test) due to 1 previous error


Only `with_hashes` (T3 will use it). Folding T3 (impl+unit one commit). First refactoring the path-selection into a pure testable helper, then writing the truth-table tests. Looking at an existing promote-path test to mirror the integration case.


grep -n "fn .*promote\|fn .*trial\|Promoted\|ready.lock\|\*env.ready\|set.*ready\|promotions.lock().unwrap()" crates/spt-daemon/src/brainproc.rs | sed -n '1,30p'

Find a promote-path test to mirror


403:    fn record_promoted(&self, version: u64);
430:    fn record_promoted(&self, _version: u64) {}
462:    fn record_promoted(&self, version: u64) {
551:    Promoted,
570:fn run_trial(
585:            return TrialStep::Promoted;
691:                        TrialStep::Promoted => {
1342:            *self.ready.lock().unwrap()
1345:            *self.ready.lock().unwrap() = None;
1348:        fn record_promoted(&self, version: u64) {
1349:            self.promotions.lock().unwrap().push(version);
1398:    fn trial_promotes_on_ready_then_supervises_the_accepted_binary() {
1420:                    *env_c.ready.lock().unwrap() = Some(gen);
1428:        assert_eq!(env.promotions.lock().unwrap().as_slice(), &[7], "promoted once");
1445:    fn trial_rolls_back_after_budget_of_pre_ready_exits() {
1477:        assert!(env.promotions.lock().unwrap().is_empty(), "never promoted");
1500:    fn trial_kills_alive_never_ready_candidate_before_rollback() {
1537:        assert!(env.promotions.lock().unwrap().is_empty(), "never promoted");
1546:    fn stale_generation_minus_one_ready_never_promotes() {
1568:                *env_c.ready.lock().unwrap() = Some(gen - 1);
1573:            env.promotions.lock().unwrap().is_empty(),
1595:        *env.ready.lock().unwrap() = Some(0);
1619:            env.promotions.lock().unwrap().is_empty(),
1705:        assert!(env.promotions.lock().unwrap().is_empty());


`trial_promotes_on_ready...` (l.1398) is the template. Reading it.


1396	    // [unit->REQ-HAZARD-BROKER-PROCESS-ISOLATION]
1397	    #[test]
1398	    fn trial_promotes_on_ready_then_supervises_the_accepted_binary() {
1399	        let env = Arc::new(TestTrialEnv::pending(7, "/good/spt.old-7"));
1400	        let stop = Arc::new(AtomicBool::new(false));
1401	        let restart = BrainRestart::new();
1402	        let spawns = Arc::new(Mutex::new(Vec::<(u64, StartReason, Option<String>)>::new()));
1403	        let env_c = Arc::clone(&env);
1404	        let spawns_c = Arc::clone(&spawns);
1405	        let stop_c = Arc::clone(&stop);
1406	        supervise_brain(
1407	            &stop,
1408	            &restart,
1409	            Duration::from_millis(5),
1410	            env.as_ref(),
1411	            Duration::from_secs(5),
1412	            move |gen, reason, binary| {
1413	                let n = {
1414	                    let mut s = spawns_c.lock().unwrap();
1415	                    s.push((gen, reason, binary.map(|p| p.display().to_string())));
1416	                    s.len()
1417	                };
1418	                if n == 1 {
1419	                    // The trial candidate signals ready for its generation.
1420	                    *env_c.ready.lock().unwrap() = Some(gen);
1421	                } else {
1422	                    // The post-promotion respawn (the accepted binary): end here.
1423	                    stop_c.store(true, Ordering::Relaxed);
1424	                }
1425	                fast_child()
1426	            },
1427	        );
1428	        assert_eq!(env.promotions.lock().unwrap().as_slice(), &[7], "promoted once");
1429	        assert!(env.rollbacks.lock().unwrap().is_empty(), "no rollback");
1430	        assert_eq!(env.notifs.load(Ordering::Relaxed), 0, "no rollback notif");
1431	        assert!(env.clears.load(Ordering::Relaxed) >= 1, "ready cleared before the trial spawn");
1432	        let s = spawns.lock().unwrap();
1433	        assert_eq!(s[0].1, StartReason::Cold, "first spawn is the cold trial candidate");
1434	        assert_eq!(s[0].2, None, "candidate spawns the default current_exe");
1435	        assert_eq!(s[1].2, None, "a later crash respawns the SAME accepted binary, not a rollback");
1436	    }
1437
1438	    /// (2/A8) Bounded retry → rollback: a candidate that exits pre-ready
1439	    /// `TRIAL_PREREADY_BUDGET` consecutive times rolls back. The trial latch is
1440	    /// the RECORD, not the reason — the `Crash` respawns stay on trial and count
1441	    /// toward K (A8). On rollback the record flips to `RolledBack`, selection
1442	    /// switches to the rollback binary, and the capturing notif sink fired once.
1443	    // [unit->REQ-HAZARD-BROKER-PROCESS-ISOLATION]

    // [unit->REQ-HAZARD-BROKER-PROCESS-ISOLATION]
    #[test]
    fn trial_promotes_on_ready_then_supervises_the_accepted_binary() {
        let env = Arc::new(TestTrialEnv::pending(7, "/good/spt.old-7"));
        let stop = Arc::new(AtomicBool::new(false));
        let restart = BrainRestart::new();
        let spawns = Arc::new(Mutex::new(Vec::<(u64, StartReason, Option<String>)>::new()));
        let env_c = Arc::clone(&env);
        let spawns_c = Arc::clone(&spawns);
        let stop_c = Arc::clone(&stop);
        supervise_brain(
            &stop,
            &restart,
            Duration::from_millis(5),
            env.as_ref(),
            Duration::from_secs(5),
            move |gen, reason, binary| {
                let n = {
                    let mut s = spawns_c.lock().unwrap();
                    s.push((gen, reason, binary.map(|p| p.display().to_string())));
                    s.len()
                };
                if n == 1 {
                    // The trial candidate signals ready for its generation.
                    *env_c.ready.lock().unwrap() = Some(gen);
                } else {
                    // The post-promotion respawn (the accepted binary): end here.
                    stop_c.store(true, Ordering::Relaxed);
                }
                fast_child()
            },
        );
        assert_eq!(env.promotions.lock().unwrap().as_slice(), &[7], "promoted once");
        assert!(env.rollbacks.lock().unwrap().is_empty(), "no rollback");
        assert_eq!(env.notifs.load(Ordering::Relaxed), 0, "no rollback notif");
        assert!(env.clears.load(Ordering::Relaxed) >= 1, "ready cleared before the trial spawn");
        let s = spawns.lock().unwrap();
        assert_eq!(s[0].1, StartReason::Cold, "first spawn is the cold trial candidate");
        assert_eq!(s[0].2, None, "candidate spawns the default current_exe");
        assert_eq!(s[1].2, None, "a later crash respawns the SAME accepted binary, not a rollback");
    }

    /// (2/A8) Bounded retry → rollback: a candidate that exits pre-ready
    /// `TRIAL_PREREADY_BUDGET` consecutive times rolls back. The trial latch is
    /// the RECORD, not the reason — the `Crash` respawns stay on trial and count
    /// toward K (A8). On rollback the record flips to `RolledBack`, selection
    /// switches to the rollback binary, and the capturing notif sink fired once.
    // [unit->REQ-HAZARD-BROKER-PROCESS-ISOLATION]


Refactoring path-selection into a pure `select_brain_exe` helper (testable), then the tests.


Edit

    let exe = match binary {
        Some(p) => p.to_path_buf(),
        None => match canonical {
            Some(c) => c.to_path_buf(),
            None => std::env::current_exe()?,
        },
    };
    let mut cmd = Command::new(exe);

    let exe = select_brain_exe(binary, canonical)?;
    let mut cmd = Command::new(exe);


    let exe = match binary {
        Some(p) => p.to_path_buf(),
        None => match canonical {
            Some(c) => c.to_path_buf(),
            None => std::env::current_exe()?,
        },
    };
    let mut cmd = Command::new(exe);

    let exe = select_brain_exe(binary, canonical)?;
    let mut cmd = Command::new(exe);

-            Some(c) => c.to_path_buf(),

-            None => std::env::current_exe()?,

+    let exe = select_brain_exe(binary, canonical)?;

// The brain is a background logic process — it must never flash a console


// [impl->REQ-HAZARD-BRAIN-RESPAWN-PATH]
fn spawn_brain_child(
    generation: u64,
    reason: StartReason,
    binary: Option<&Path>,
    canonical: Option<&Path>,
) -> io::Result<Child> {

/// Resolve which binary the brain spawns (KH 6.11). `Some(rollback)` wins (the
/// record-driven `.old-N` selection); else the `canonical` path captured at
/// broker start; else — only if that capture failed — degrade to a per-spawn
/// `current_exe()`. The point of the fix: NEVER a per-spawn `current_exe()`
/// when a canonical path is available, because on Linux `current_exe()` follows
/// the `apply` rename to the old binary.
// [impl->REQ-HAZARD-BRAIN-RESPAWN-PATH]
fn select_brain_exe(binary: Option<&Path>, canonical: Option<&Path>) -> io::Result<PathBuf> {
    match binary {
        Some(p) => Ok(p.to_path_buf()),
        None => match canonical {
            Some(c) => Ok(c.to_path_buf()),
            None => std::env::current_exe(),
        },
    }
}

// [impl->REQ-HAZARD-BRAIN-RESPAWN-PATH]
fn spawn_brain_child(
    generation: u64,
    reason: StartReason,
    binary: Option<&Path>,
    canonical: Option<&Path>,
) -> io::Result<Child> {


/// Resolve which binary the brain spawns (KH 6.11). `Some(rollback)` wins (the
/// record-driven `.old-N` selection); else the `canonical` path captured at
/// broker start; else — only if that capture failed — degrade to a per-spawn
/// `current_exe()`. The point of the fix: NEVER a per-spawn `current_exe()`
/// when a canonical path is available, because on Linux `current_exe()` follows
/// the `apply` rename to the old binary.
// [impl->REQ-HAZARD-BRAIN-RESPAWN-PATH]
fn select_brain_exe(binary: Option<&Path>, canonical: Option<&Path>) -> io::Result<PathBuf> {
    match binary {
        Some(p) => Ok(p.to_path_buf()),
        None => match canonical {
            Some(c) => Ok(c.to_path_buf()),
            None => std::env::current_exe(),
        },
    }
}

// [impl->REQ-HAZARD-BRAIN-RESPAWN-PATH]
fn spawn_brain_child(
    generation: u64,
    reason: StartReason,
    binary: Option<&Path>,
    canonical: Option<&Path>,
) -> io::Result<Child> {

/// (`spt` → `spt.old-N`), which would respawn the brain onto the OLD bytes

/// (KH 6.11). Falls back to `current_exe()` only if the t=0 capture failed

/// (degrade-safe — restores pre-fix behavior rather than failing to spawn).

+/// Resolve which binary the brain spawns (KH 6.11). `Some(rollback)` wins (the

+/// record-driven `.old-N` selection); else the `canonical` path captured at

+/// broker start; else — only if that capture failed — degrade to a per-spawn

+/// `current_exe()`. The point of the fix: NEVER a per-spawn `current_exe()`

+/// when a canonical path is available, because on Linux `current_exe()` follows

+/// the `apply` rename to the old binary.

+fn select_brain_exe(binary: Option<&Path>, canonical: Option<&Path>) -> io::Result<PathBuf> {

+        Some(p) => Ok(p.to_path_buf()),

+            Some(c) => Ok(c.to_path_buf()),

+            None => std::env::current_exe(),


Adding T3 tests: `select_brain_exe` selection, `bytes_gate` truth table, and two supervise integration tests (wrong-bytes→rollback, matching→promote).


/// (2/A8) Bounded retry → rollback: a candidate that exits pre-ready

    /// (KH 6.11 — Half 1) Spawn-path selection: the `None` default resolves the
    /// CANONICAL path captured at broker start, NOT a per-spawn current_exe (the
    /// Linux apply-rename trap). `Some(.old-N)` rollback selection wins; only a
    /// failed t=0 capture (canonical=None) degrades to current_exe.
    // [unit->REQ-HAZARD-BRAIN-RESPAWN-PATH]
    #[test]
    fn select_brain_exe_prefers_canonical_over_per_spawn_current_exe() {
        let canonical = Path::new("/install/spt");
        let rollback = Path::new("/install/spt.old-9");
        assert_eq!(
            select_brain_exe(None, Some(canonical)).unwrap(),
            canonical.to_path_buf(),
            "None must spawn the captured canonical path, not per-spawn current_exe"
        );
        assert_eq!(
            select_brain_exe(Some(rollback), Some(canonical)).unwrap(),
            rollback.to_path_buf(),
            "Some(.old-N) rollback selection is honored over canonical"
        );
        assert_eq!(
            select_brain_exe(None, None).unwrap(),
            std::env::current_exe().unwrap(),
            "no canonical capture degrades to current_exe (pre-fix behavior)"
        );
    }

    /// (KH 6.11 — Half 2) Promotion bytes-gate truth table: match→Match,
    /// mismatch→Mismatch, either-hash-absent→Unverified (degrade to
    /// readiness-only, N-1-safe).
    // [unit->REQ-HAZARD-BRAIN-RESPAWN-PATH]
    #[test]
    fn bytes_gate_truth_table() {
        let m = TestTrialEnv::pending(8, "/g/spt.old-8").with_hashes(Some("abc"), Some("abc"));
        assert!(matches!(bytes_gate(&m, 8), BytesGate::Match));
        let x = TestTrialEnv::pending(8, "/g/spt.old-8").with_hashes(Some("abc"), Some("xyz"));
        assert!(matches!(bytes_gate(&x, 8), BytesGate::Mismatch));
        let ready_absent = TestTrialEnv::pending(8, "/g/spt.old-8").with_hashes(None, Some("abc"));
        assert!(matches!(bytes_gate(&ready_absent, 8), BytesGate::Unverified));
        let staged_absent = TestTrialEnv::pending(8, "/g/spt.old-8").with_hashes(Some("abc"), None);
        assert!(matches!(bytes_gate(&staged_absent, 8), BytesGate::Unverified));
    }

    /// (KH 6.11 — Half 2 integration) A candidate that signals ready but is
    /// running the WRONG bytes does NOT promote — the gate kills it, rolls back
    /// to the known-good binary, and fires the loud notif (the v0.4.1 kitsubito
    /// false-success, now caught at the gate).
    // [unit->REQ-HAZARD-BRAIN-RESPAWN-PATH]
    #[test]
    fn ready_but_wrong_bytes_rolls_back_never_promotes() {
        let env = Arc::new(
            TestTrialEnv::pending(8, "/good/spt.old-8")
                .with_hashes(Some("oldbytes"), Some("newbytes")),
        );
        let stop = Arc::new(AtomicBool::new(false));
        let restart = BrainRestart::new();
        let env_c = Arc::clone(&env);
        let stop_c = Arc::clone(&stop);
        let spawns = Arc::new(AtomicU32::new(0));
        let spawns_c = Arc::clone(&spawns);
        supervise_brain(
            &stop,
            &restart,
            Duration::from_millis(5),
            env.as_ref(),
            Duration::from_secs(5),
            move |gen, _reason, _binary| {
                let n = spawns_c.fetch_add(1, Ordering::Relaxed) + 1;
                if n == 1 {
                    *env_c.ready.lock().unwrap() = Some(gen);
                } else {
                    stop_c.store(true, Ordering::Relaxed);
                }
                fast_child()
            },
        );
        assert!(
            env.promotions.lock().unwrap().is_empty(),
            "wrong-bytes candidate never promotes"
        );
        assert_eq!(
            env.rollbacks.lock().unwrap().len(),
            1,
            "rolled back once on the bytes mismatch"
        );
        assert_eq!(env.notifs.load(Ordering::Relaxed), 1, "loud rollback notif fired");
    }

    /// (KH 6.11 — Half 2 integration) A candidate ready AND running the staged
    /// bytes promotes normally — the gate is transparent on the happy path.
    // [unit->REQ-HAZARD-BRAIN-RESPAWN-PATH]
    #[test]
    fn ready_with_matching_bytes_promotes() {
        let env = Arc::new(
            TestTrialEnv::pending(8, "/good/spt.old-8")
                .with_hashes(Some("samebytes"), Some("samebytes")),
        );
        let stop = Arc::new(AtomicBool::new(false));
        let restart = BrainRestart::new();
        let env_c = Arc::clone(&env);
        let stop_c = Arc::clone(&stop);
        let spawns = Arc::new(AtomicU32::new(0));
        let spawns_c = Arc::clone(&spawns);
        supervise_brain(
            &stop,
            &restart,
            Duration::from_millis(5),
            env.as_ref(),
            Duration::from_secs(5),
            move |gen, _reason, _binary| {
                let n = spawns_c.fetch_add(1, Ordering::Relaxed) + 1;
                if n == 1 {
                    *env_c.ready.lock().unwrap() = Some(gen);
                } else {
                    stop_c.store(true, Ordering::Relaxed);
                }
                fast_child()
            },
        );
        assert_eq!(
            env.promotions.lock().unwrap().as_slice(),
            &[8],
            "matching bytes → promote"
        );
        assert!(
            env.rollbacks.lock().unwrap().is_empty(),
            "no rollback on a bytes match"
        );
    }

    /// (2/A8) Bounded retry → rollback: a candidate that exits pre-ready


    /// (KH 6.11 — Half 1) Spawn-path selection: the `None` default resolves the
    /// CANONICAL path captured at broker start, NOT a per-spawn current_exe (the
    /// Linux apply-rename trap). `Some(.old-N)` rollback selection wins; only a
    /// failed t=0 capture (canonical=None) degrades to current_exe.
    // [unit->REQ-HAZARD-BRAIN-RESPAWN-PATH]
    #[test]
    fn select_brain_exe_prefers_canonical_over_per_spawn_current_exe() {
        let canonical = Path::new("/install/spt");
        let rollback = Path::new("/install/spt.old-9");
        assert_eq!(
            select_brain_exe(None, Some(canonical)).unwrap(),
            canonical.to_path_buf(),
            "None must spawn the captured canonical path, not per-spawn current_exe"
        );
        assert_eq!(
            select_brain_exe(Some(rollback), Some(canonical)).unwrap(),
            rollback.to_path_buf(),
            "Some(.old-N) rollback selection is honored over canonical"
        );
        assert_eq!(
            select_brain_exe(None, None).unwrap(),
            std::env::current_exe().unwrap(),
            "no canonical capture degrades to current_exe (pre-fix behavior)"
        );
    }

    /// (KH 6.11 — Half 2) Promotion bytes-gate truth table: match→Match,
    /// mismatch→Mismatch, either-hash-absent→Unverified (degrade to
    /// readiness-only, N-1-safe).
    // [unit->REQ-HAZARD-BRAIN-RESPAWN-PATH]
    #[test]
    fn bytes_gate_truth_table() {
        let m = TestTrialEnv::pending(8, "/g/spt.old-8").with_hashes(Some("abc"), Some("abc"));
        assert!(matches!(bytes_gate(&m, 8), BytesGate::Match));
        let x = TestTrialEnv::pending(8, "/g/spt.old-8").with_hashes(Some("abc"), Some("xyz"));
        assert!(matches!(bytes_gate(&x, 8), BytesGate::Mismatch));
        let ready_absent = TestTrialEnv::pending(8, "/g/spt.old-8").with_hashes(None, Some("abc"));
        assert!(matches!(bytes_gate(&ready_absent, 8), BytesGate::Unverified));
        let staged_absent = TestTrialEnv::pending(8, "/g/spt.old-8").with_hashes(Some("abc"), None);
        assert!(matches!(bytes_gate(&staged_absent, 8), BytesGate::Unverified));
    }

    /// (KH 6.11 — Half 2 integration) A candidate that signals ready but is
    /// running the WRONG bytes does NOT promote — the gate kills it, rolls back
    /// to the known-good binary, and fires the loud notif (the v0.4.1 kitsubito
    /// false-success, now caught at the gate).
    // [unit->REQ-HAZARD-BRAIN-RESPAWN-PATH]
    #[test]
    fn ready_but_wrong_bytes_rolls_back_never_promotes() {
        let env = Arc::new(
            TestTrialEnv::pending(8, "/good/spt.old-8")
                .with_hashes(Some("oldbytes"), Some("newbytes")),
        );
        let stop = Arc::new(AtomicBool::new(false));
        let restart = BrainRestart::new();
        let env_c = Arc::clone(&env);
        let stop_c = Arc::clone(&stop);
        let spawns = Arc::new(AtomicU32::new(0));
        let spawns_c = Arc::clone(&spawns);
        supervise_brain(
            &stop,
            &restart,
            Duration::from_millis(5),
            env.as_ref(),
            Duration::from_secs(5),
            move |gen, _reason, _binary| {
                let n = spawns_c.fetch_add(1, Ordering::Relaxed) + 1;
                if n == 1 {
                    *env_c.ready.lock().unwrap() = Some(gen);
                } else {
                    stop_c.store(true, Ordering::Relaxed);
                }
                fast_child()
            },
        );
        assert!(
            env.promotions.lock().unwrap().is_empty(),
            "wrong-bytes candidate never promotes"
        );
        assert_eq!(
            env.rollbacks.lock().unwrap().len(),
            1,
            "rolled back once on the bytes mismatch"
        );
        assert_eq!(env.notifs.load(Ordering::Relaxed), 1, "loud rollback notif fired");
    }

    /// (KH 6.11 — Half 2 integration) A candidate ready AND running the staged
    /// bytes promotes normally — the gate is transparent on the happy path.
    // [unit->REQ-HAZARD-BRAIN-RESPAWN-PATH]
    #[test]
    fn ready_with_matching_bytes_promotes() {
        let env = Arc::new(
            TestTrialEnv::pending(8, "/good/spt.old-8")
                .with_hashes(Some("samebytes"), Some("samebytes")),
        );
        let stop = Arc::new(AtomicBool::new(false));
        let restart = BrainRestart::new();
        let env_c = Arc::clone(&env);
        let stop_c = Arc::clone(&stop);
        let spawns = Arc::new(AtomicU32::new(0));
        let spawns_c = Arc::clone(&spawns);
        supervise_brain(
            &stop,
            &restart,
            Duration::from_millis(5),
            env.as_ref(),
            Duration::from_secs(5),
            move |gen, _reason, _binary| {
                let n = spawns_c.fetch_add(1, Ordering::Relaxed) + 1;
                if n == 1 {
                    *env_c.ready.lock().unwrap() = Some(gen);
                } else {
                    stop_c.store(true, Ordering::Relaxed);
                }
                fast_child()
            },
        );
        assert_eq!(
            env.promotions.lock().unwrap().as_slice(),
            &[8],
            "matching bytes → promote"
        );
        assert!(
            env.rollbacks.lock().unwrap().is_empty(),
            "no rollback on a bytes match"
        );
    }

    /// (2/A8) Bounded retry → rollback: a candidate that exits pre-ready

assert_eq!(s[1].2, None, "a later crash respawns the SAME accepted binary, not a rollback");

+    /// (KH 6.11 — Half 1) Spawn-path selection: the `None` default resolves the

+    /// CANONICAL path captured at broker start, NOT a per-spawn current_exe (the

+    /// Linux apply-rename trap). `Some(.old-N)` rollback selection wins; only a

+    /// failed t=0 capture (canonical=None) degrades to current_exe.

+    // [unit->REQ-HAZARD-BRAIN-RESPAWN-PATH]

+    fn select_brain_exe_prefers_canonical_over_per_spawn_current_exe() {

+        let canonical = Path::new("/install/spt");

+        let rollback = Path::new("/install/spt.old-9");

+            select_brain_exe(None, Some(canonical)).unwrap(),

+            "None must spawn the captured canonical path, not per-spawn current_exe"

+            select_brain_exe(Some(rollback), Some(canonical)).unwrap(),

+            "Some(.old-N) rollback selection is honored over canonical"

+            select_brain_exe(None, None).unwrap(),

+            std::env::current_exe().unwrap(),

+            "no canonical capture degrades to current_exe (pre-fix behavior)"

+    /// (KH 6.11 — Half 2) Promotion bytes-gate truth table: match→Match,

+    /// mismatch→Mismatch, either-hash-absent→Unverified (degrade to

+        let m = TestTrialEnv::pending(8, "/g/spt.old-8").with_hashes(Some("abc"), Some("abc"));

+        assert!(matches!(bytes_gate(&m, 8), BytesGate::Match));

+        let x = TestTrialEnv::pending(8, "/g/spt.old-8").with_hashes(Some("abc"), Some("xyz"));

+        assert!(matches!(bytes_gate(&x, 8), BytesGate::Mismatch));

+        let ready_absent = TestTrialEnv::pending(8, "/g/spt.old-8").with_hashes(None, Some("abc"));

+        assert!(matches!(bytes_gate(&ready_absent, 8), BytesGate::Unverified));

+        let staged_absent = TestTrialEnv::pending(8, "/g/spt.old-8").with_hashes(Some("abc"), None);

+        assert!(matches!(bytes_gate(&staged_absent, 8), BytesGate::Unverified));

+    /// (KH 6.11 — Half 2 integration) A candidate that signals ready but is

+    /// running the WRONG bytes does NOT promote — the gate kills it, rolls back

+    /// to the known-good binary, and fires the loud notif (the v0.4.1 kitsubito

+    /// false-success, now caught at the gate).

+    fn ready_but_wrong_bytes_rolls_back_never_promotes() {

+            TestTrialEnv::pending(8, "/good/spt.old-8")

+                .with_hashes(Some("oldbytes"), Some("newbytes")),

+        let stop = Arc::new(AtomicBool::new(false));

+        let restart = BrainRestart::new();

+        let stop_c = Arc::clone(&stop);

+        let spawns = Arc::new(AtomicU32::new(0));

+        let spawns_c = Arc::clone(&spawns);

+            move |gen, _reason, _binary| {

+                let n = spawns_c.fetch_add(1, Ordering::Relaxed) + 1;

+                    *env_c.ready.lock().unwrap() = Some(gen);

+                    stop_c.store(true, Ordering::Relaxed);

+            env.promotions.lock().unwrap().is_empty(),

+            "wrong-bytes candidate never promotes"

+            env.rollbacks.lock().unwrap().len(),

+            "rolled back once on the bytes mismatch"

+        assert_eq!(env.notifs.load(Ordering::Relaxed), 1, "loud rollback notif fired");

+    /// (KH 6.11 — Half 2 integration) A candidate ready AND running the staged

+    /// bytes promotes normally — the gate is transparent on the happy path.

+    fn ready_with_matching_bytes_promotes() {

+                .with_hashes(Some("samebytes"), Some("samebytes")),

+        let restart = BrainRestart::new();

+        let stop_c = Arc::clone(&stop);

+        let spawns_c = Arc::clone(&spawns);

+            move |gen, _reason, _binary| {

+            env.promotions.lock().unwrap().as_slice(),

+            env.rollbacks.lock().unwrap().is_empty(),

+            "no rollback on a bytes match"

/// `TRIAL_PREREADY_BUDGET` consecutive times rolls back. The trial latch is

/// the RECORD, not the reason — the `Crash` respawns stay on trial and count


T2+T3 in. Running daemon tests + clippy.


Bash

cargo test -p spt-daemon 2>&1 | grep -iE "respawn|bytes_gate|select_brain|wrong_bytes|matching_bytes|test result|FAILED|error\[" | head -30; echo "TEST-EXIT:${PIPESTATUS[0]}"; echo "=== clippy ==="; cargo clippy -p spt-daemon --all-targets -- -D warnings 2>&1 | tail -4; echo "CLIPPY-EXIT:${PIPESTATUS[0]}"

Daemon tests + clippy (T2+T3)


test brainproc::tests::bytes_gate_truth_table ... ok
test brainproc::tests::select_brain_exe_prefers_canonical_over_per_spawn_current_exe ... ok
test brainproc::tests::planned_restart_respawns_with_update_reason ... ok
test brainproc::tests::ready_but_wrong_bytes_rolls_back_never_promotes ... ok
test effect::tests::failed_effect_is_not_recorded_applied ... ok
test brainproc::tests::ready_with_matching_bytes_promotes ... ok
test brainproc::tests::supervisor_respawns_brain_until_stopped ... ok
test relay::tests::respawn_loses_no_spooled_message ... ok
test shellwake::tests::watcher_crash_exits_respawn_then_give_up ... ok
test result: ok. 276 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.09s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.29s
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.20s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.86s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.14s
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.40s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.19s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.12s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.34s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.54s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.28s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.29s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.21s
test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.44s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.06s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.60s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.15s
TEST-EXIT:0
=== clippy ===
    Checking spt-daemon v0.4.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.45s
CLIPPY-EXIT:0


All green — 276 daemon tests + 4 new, clippy clean. Bumping required_stages to `[doc,impl,unit]` and running the full workspace gate suite for the T2+T3 commit.


Edit

required_stages = ["doc"]  # activated v0.4.2 (2026-06-11): minted from the v0.4.1 fleet-roll blocker. doc = KNOWN-HAZARDS 6.11 + ADR-0018 Q3 amendment. impl/unit/int activate per task (V042-PLAN.md):

required_stages = ["doc", "impl", "unit"]  # activated v0.4.2 (2026-06-11): minted from the v0.4.1 fleet-roll blocker. doc = KNOWN-HAZARDS 6.11 + ADR-0018 Q3 amendment. int activates with T4 (rule 5):


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

title = "The broker respawns the brain onto the APPLIED bytes, not the renamed old binary: the candidate-binary default is the canonical exe path captured ONCE at broker start, never a per-spawn std::env::current_exe() — on Linux current_exe (readlink /proc/self/exe) is inode-tracking and follows the `apply` rename (spt -> spt.old-N), so a resident broker would respawn the brain onto OLD bytes while recording `applied` (Windows GetModuleFileName is path-at-start, so Windows was green; ADR-0018 Q3 silently assumed path-string semantics). Backstop: promotion gates on bytes — a trial promotes only if brain.ready exe_hash == the staged artifact hash for this platform, else auto-rollback + loud notif (readiness != new-bytes was the false-success that recorded applied:8 over a v0.4.0 brain on kitsubito, 2026-06-11). KNOWN-HAZARDS 6.11."

-required_stages = ["doc"]  # activated v0.4.2 (2026-06-11): minted from the v0.4.1 fleet-roll blocker. doc = KNOWN-HAZARDS 6.11 + ADR-0018 Q3 amendment. impl/unit/int activate per task (V042-PLAN.md): impl = brainproc.rs canonical-exe capture at spawn_brain_supervisor + promotion bytes-gate in supervise_brain Promoted arm (TrialEnv ready_exe_hash/staged_artifact_hash); unit = spawn-path selection (captured-default-for-None vs per-spawn) + promotion-gate truth table (match->promote, mismatch->rollback+notif, either-absent->loud PROMOTE_BYTES_UNVERIFIED degrade-to-readiness); int = the brain_survive rename-under-supervisor sibling (real in-place rename P->P.old-N + new bytes at P through the production None selection, assert respawned exe_hash == new bytes; fails Linux pre-fix)

+required_stages = ["doc", "impl", "unit"]  # activated v0.4.2 (2026-06-11): minted from the v0.4.1 fleet-roll blocker. doc = KNOWN-HAZARDS 6.11 + ADR-0018 Q3 amendment. int activates with T4 (rule 5): impl = brainproc.rs canonical-exe capture at spawn_brain_supervisor + promotion bytes-gate in supervise_brain Promoted arm (TrialEnv ready_exe_hash/staged_artifact_hash); unit = spawn-path selection (captured-default-for-None vs per-spawn) + promotion-gate truth table (match->promote, mismatch->rollback+notif, either-absent->loud PROMOTE_BYTES_UNVERIFIED degrade-to-readiness); int = the brain_survive rename-under-supervisor sibling (real in-place rename P->P.old-N + new bytes at P through the production None selection, assert respawned exe_hash == new bytes; fails Linux pre-fix)

# --- Added 2026-06-03 from grill-with-docs (ADR-0012, hazard 7.3); inactive until M4-D7.5 ---


Bash

echo "=== build workspace ==="; cargo build --workspace 2>&1 | tail -2 && \
echo "=== test workspace ===" && cargo test --workspace 2>&1 | grep -iE "test result: FAILED|FAILED|error\[|^error" | head; echo "test-rc:${PIPESTATUS[0]}" && \
echo "=== clippy workspace ===" && cargo clippy --workspace --all-targets -- -D warnings 2>&1 | tail -3 && \
echo "=== no-default ===" && cargo build --workspace --no-default-features 2>&1 | tail -2 && \
echo "=== traceable ===" && traceable-reqs check >/dev/null 2>&1; echo "TR:$?" && \
echo "=== xtask check ===" && cargo run -p xtask -- check 2>&1 | tail -2

Full workspace gate suite (T2+T3)


Command running in background with ID: b76180bn2. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\c570fe63-c413-4a17-bb14-ee75e02a53bd\tasks\b76180bn2.output. You will be notified when it completes. To check interim output, use Read on that file path.


Gate suite running. Writing T4 (the int E2E) — reading `brain_survive.rs` to mirror its harness for a faithful in-place-rename sibling.