orchestrator-sm: move tests to sibling tests.rs

Extract the unit test module into src/tests.rs via mod tests; and add it to
the library srcs. No behavior change; lib.rs drops to ~800 lines.
diff --git a/services/orchestrator/sm/BUILD.bazel b/services/orchestrator/sm/BUILD.bazel
index 444bde8..212638a 100644
--- a/services/orchestrator/sm/BUILD.bazel
+++ b/services/orchestrator/sm/BUILD.bazel
@@ -5,7 +5,10 @@
 
 rust_library(
     name = "orchestrator_sm",
-    srcs = ["src/lib.rs"],
+    srcs = [
+        "src/lib.rs",
+        "src/tests.rs",
+    ],
     crate_name = "openprot_orchestrator_sm",
     edition = "2024",
     visibility = ["//visibility:public"],
diff --git a/services/orchestrator/sm/src/lib.rs b/services/orchestrator/sm/src/lib.rs
index e02b137..d527a80 100644
--- a/services/orchestrator/sm/src/lib.rs
+++ b/services/orchestrator/sm/src/lib.rs
@@ -805,837 +805,4 @@
 }
 
 #[cfg(test)]
-mod tests {
-    extern crate std;
-
-    use super::*;
-    use std::vec::Vec;
-
-    const C0: ComponentId = ComponentId::new(0);
-    const C1: ComponentId = ComponentId::new(1);
-    const C2: ComponentId = ComponentId::new(2);
-
-    const BOOT: Event = Event::PowerGood(PowerOnResult::Provisioned);
-
-    const CAPACITY: usize = 8;
-    const ECAP: usize = CAPACITY + 2;
-    const MAX_RETRY: u8 = 3;
-
-    fn chain(
-        ids: &[(ComponentId, ComponentAttrs)],
-    ) -> heapless::Vec<(ComponentId, ComponentAttrs), CAPACITY> {
-        let mut c = heapless::Vec::new();
-        for &entry in ids {
-            c.push(entry).expect("chain within CAPACITY");
-        }
-        c
-    }
-
-    fn passive_required(
-        ids: &[ComponentId],
-    ) -> heapless::Vec<(ComponentId, ComponentAttrs), CAPACITY> {
-        chain(
-            &ids.iter()
-                .map(|&id| (id, ComponentAttrs::passive_required()))
-                .collect::<std::vec::Vec<_>>(),
-        )
-    }
-
-    struct Recorder {
-        recorded: Vec<Effect>,
-    }
-
-    impl Recorder {
-        fn new() -> Self {
-            Self {
-                recorded: Vec::new(),
-            }
-        }
-    }
-
-    impl Platform for Recorder {
-        fn execute(&mut self, effect: Effect) {
-            self.recorded.push(effect);
-        }
-    }
-
-    fn drive(
-        chain: heapless::Vec<(ComponentId, ComponentAttrs), CAPACITY>,
-        script: &[Event],
-    ) -> (Vec<Effect>, State) {
-        let mut orch = Orchestrator::<CAPACITY, ECAP>::new(chain, MAX_RETRY);
-        let mut platform = Recorder::new();
-        for &event in script {
-            orch.dispatch(&mut platform, event);
-        }
-        (platform.recorded, orch.state())
-    }
-
-    /// INV1/INV2/INV3: provisioned power-on walks the chain in order; no
-    /// component is released before its eRoT-side verification passes.
-    #[test]
-    fn cold_boot_walks_chain_in_order() {
-        let (effects, state) = drive(
-            passive_required(&[C0, C1]),
-            &[
-                BOOT,
-                Event::VerificationPassed(C0),
-                Event::VerificationPassed(C1),
-            ],
-        );
-        assert_eq!(
-            effects,
-            std::vec![
-                Effect::ReadFirmware(C0),
-                Effect::VerifyFirmware(C0),
-                Effect::ReleaseReset(C0),
-                Effect::ReadFirmware(C1),
-                Effect::VerifyFirmware(C1),
-                Effect::ReleaseReset(C1),
-            ],
-        );
-        assert_eq!(state, State::Ready);
-    }
-
-    /// Unprovisioned power-on latches immediately.
-    #[test]
-    fn unprovisioned_boot_locks_down() {
-        let (effects, state) = drive(
-            passive_required(&[C0]),
-            &[Event::PowerGood(PowerOnResult::Unprovisioned)],
-        );
-        assert_eq!(effects, std::vec![Effect::LatchLockdown]);
-        assert_eq!(state, State::Locked);
-    }
-
-    /// INV11: SelfVerificationFailed latches immediately without entering
-    /// PreSupervision.
-    #[test]
-    fn self_verification_failure_latches_immediately() {
-        let (effects, state) = drive(
-            passive_required(&[C0]),
-            &[Event::PowerGood(PowerOnResult::SelfVerificationFailed)],
-        );
-        assert_eq!(effects, std::vec![Effect::LatchLockdown]);
-        assert_eq!(state, State::Locked);
-    }
-
-    /// INV6: AttestationChallenge is answerable from every SupervisingPlatform state.
-    #[test]
-    fn attestation_shared_across_supervising_platform_states() {
-        let (effects, state) = drive(
-            passive_required(&[C0]),
-            &[
-                BOOT,
-                Event::VerificationPassed(C0),
-                Event::AttestationChallenge,
-            ],
-        );
-        assert_eq!(effects.last(), Some(&Effect::SignAttestation));
-        assert_eq!(state, State::Ready);
-
-        let (effects, state) = drive(
-            passive_required(&[C0]),
-            &[
-                BOOT,
-                Event::VerificationPassed(C0),
-                Event::UpdateRequest,
-                Event::AttestationChallenge,
-            ],
-        );
-        assert_eq!(effects.last(), Some(&Effect::SignAttestation));
-        assert_eq!(state, State::Updating);
-    }
-
-    /// INV4: a rejected update rolls back via DiscardStaged and never enters
-    /// Recovering.
-    #[test]
-    fn update_rollback_is_not_recovery() {
-        let (effects, state) = drive(
-            passive_required(&[C0]),
-            &[
-                BOOT,
-                Event::VerificationPassed(C0),
-                Event::UpdateRequest,
-                Event::UpdateRejected,
-            ],
-        );
-        let tail = &effects[effects.len() - 3..];
-        assert_eq!(
-            tail,
-            &[
-                Effect::AuthenticateUpdate,
-                Effect::StageUpdate,
-                Effect::DiscardStaged
-            ],
-        );
-        assert_eq!(state, State::Ready);
-        assert!(!effects.contains(&Effect::LatchLockdown));
-    }
-
-    /// INV5: runtime corruption targets the named component and re-walks from
-    /// the top after restore.
-    #[test]
-    fn runtime_corruption_targets_component_and_rewalks() {
-        let (effects, state) = drive(
-            passive_required(&[C0, C1]),
-            &[
-                BOOT,
-                Event::VerificationPassed(C0),
-                Event::VerificationPassed(C1),
-                Event::CorruptionDetected(C1),
-                Event::Restored(C1),
-            ],
-        );
-        let tail = &effects[effects.len() - 2..];
-        assert_eq!(
-            tail,
-            &[Effect::ReadFirmware(C0), Effect::VerifyFirmware(C0)]
-        );
-        assert_eq!(state, State::PreSupervision);
-    }
-
-    /// `PreSupervision` reacts to `CorruptionDetected` directly (via
-    /// [`Rot::handle_corruption`]), even though it isn't linked to
-    /// `SupervisingPlatform` (so `AttestationChallenge` is still discarded
-    /// there — a separate question). CSA defines no mechanism guaranteeing a
-    /// corruption report exists for an already-released component's *live,
-    /// executing* state (at-rest/NVM-polling is scoped to "at
-    /// rest"/"between boots", not an in-progress boot's chain walk) — but
-    /// that only means such a report isn't guaranteed to arrive, not that one
-    /// should be ignored if it does.
-    #[test]
-    fn corruption_during_presupervision_selfloop_triggers_recovery() {
-        let (effects, state) = drive(
-            passive_required(&[C0, C1, C2]),
-            &[
-                BOOT,
-                Event::VerificationPassed(C0), // released; walk continues (still PreSupervision)
-                Event::CorruptionDetected(C0), // C0 already released, but caught anyway
-            ],
-        );
-        assert_eq!(state, State::Recovering);
-        assert!(effects.contains(&Effect::RestoreGoldenImage(C0)));
-    }
-
-    /// INV7 (feedback-as-data): after MAX_RETRY restores the core self-emits
-    /// RecoveryFailed and latches to Locked without any external RecoveryFailed
-    /// in the script.
-    #[test]
-    fn retry_cap_self_latches_via_emit() {
-        let mut script = std::vec![BOOT, Event::VerificationPassed(C0)];
-        script.push(Event::CorruptionDetected(C0));
-        for _ in 0..(MAX_RETRY - 1) {
-            script.push(Event::Restored(C0));
-            script.push(Event::VerificationFailed(C0));
-        }
-        script.push(Event::Restored(C0));
-
-        let (effects, state) = drive(passive_required(&[C0]), &script);
-
-        assert!(!script.contains(&Event::RecoveryFailed));
-        assert_eq!(state, State::Locked);
-        assert_eq!(effects.last(), Some(&Effect::LatchLockdown));
-    }
-
-    /// INV7: retry count resets after a successful recovery so a later episode
-    /// starts from zero.
-    #[test]
-    fn retry_count_resets_after_successful_recovery() {
-        let mut c = heapless::Vec::<(ComponentId, ComponentAttrs), CAPACITY>::new();
-        c.push((C0, ComponentAttrs::passive_required()))
-            .expect("fits");
-        let mut orch = Orchestrator::<CAPACITY, ECAP>::new(c, 2);
-        let mut effects = Vec::new();
-
-        for ev in [
-            BOOT,
-            Event::VerificationPassed(C0),
-            Event::CorruptionDetected(C0),
-            Event::Restored(C0),
-            Event::VerificationPassed(C0),
-        ] {
-            orch.dispatch_with(ev, |e| effects.push(e));
-        }
-        assert_eq!(orch.state(), State::Ready);
-
-        let start = effects.len();
-        for ev in [
-            Event::CorruptionDetected(C0),
-            Event::Restored(C0),
-            Event::VerificationPassed(C0),
-        ] {
-            orch.dispatch_with(ev, |e| effects.push(e));
-        }
-        assert_eq!(orch.state(), State::Ready);
-        assert!(!effects[start..].contains(&Effect::LatchLockdown));
-    }
-
-    /// Retry budgets are **per component**, not a single global counter. Two
-    /// required components each fail exactly once (well under `max_retry = 2`)
-    /// in an interleaved recovery sequence before the chain finally settles.
-    /// Under a shared global counter the second failure would push the count to
-    /// the cap and latch the platform to `Locked`; per-component counting lets
-    /// each device use its own budget, so the walk reaches `Ready`.
-    #[test]
-    fn retry_budget_is_per_component() {
-        let mut c = heapless::Vec::<(ComponentId, ComponentAttrs), CAPACITY>::new();
-        c.push((C0, ComponentAttrs::passive_required())).expect("fits");
-        c.push((C1, ComponentAttrs::passive_required())).expect("fits");
-        let mut orch = Orchestrator::<CAPACITY, ECAP>::new(c, 2);
-        let mut effects = Vec::new();
-
-        for ev in [
-            BOOT,
-            Event::VerificationFailed(C0), // C0 fails once → Recovering
-            Event::Restored(C0),           // C0 count = 1 (< 2) → re-walk
-            Event::VerificationPassed(C0), // C0 recovered → its streak clears
-            Event::VerificationFailed(C1), // C1 fails once → Recovering
-            Event::Restored(C1),           // C1 count = 1 (< 2); global would be 2 → latch
-            Event::VerificationPassed(C0), // re-walk restarts at the top
-            Event::VerificationPassed(C1), // chain done → Ready
-        ] {
-            orch.dispatch_with(ev, |e| effects.push(e));
-        }
-
-        assert_eq!(orch.state(), State::Ready);
-        assert!(!effects.contains(&Effect::LatchLockdown));
-    }
-
-    /// Board-supplied retry cap: max_retry = 1 latches on the first failed
-    /// restore.
-    #[test]
-    fn custom_retry_cap_latches_sooner() {
-        let mut c = heapless::Vec::<(ComponentId, ComponentAttrs), CAPACITY>::new();
-        c.push((C0, ComponentAttrs::passive_required()))
-            .expect("fits");
-        let mut orch = Orchestrator::<CAPACITY, ECAP>::new(c, 1);
-        let mut effects = Vec::new();
-        for ev in [
-            BOOT,
-            Event::VerificationPassed(C0),
-            Event::CorruptionDetected(C0),
-            Event::Restored(C0),
-        ] {
-            orch.dispatch_with(ev, |e| effects.push(e));
-        }
-        assert_eq!(orch.state(), State::Locked);
-        assert_eq!(effects.last(), Some(&Effect::LatchLockdown));
-    }
-
-    /// Three-component chain uses N=3; walks all three to Ready.
-    #[test]
-    fn custom_capacity_walks_full_chain() {
-        let mut c = heapless::Vec::<(ComponentId, ComponentAttrs), 3>::new();
-        for &id in &[C0, C1, C2] {
-            c.push((id, ComponentAttrs::passive_required()))
-                .expect("3 fits");
-        }
-        let mut orch = Orchestrator::<3, 5>::new(c, MAX_RETRY);
-        let mut effects = Vec::new();
-        for ev in [
-            BOOT,
-            Event::VerificationPassed(C0),
-            Event::VerificationPassed(C1),
-            Event::VerificationPassed(C2),
-        ] {
-            orch.dispatch_with(ev, |e| effects.push(e));
-        }
-        assert_eq!(orch.state(), State::Ready);
-        assert_eq!(effects.last(), Some(&Effect::ReleaseReset(C2)));
-    }
-
-    /// INV10: Active component gates the chain walk — cursor does not advance
-    /// until ComponentReady arrives.
-    #[test]
-    fn active_component_gates_on_component_ready() {
-        let (effects, state) = drive(
-            chain(&[
-                (C0, ComponentAttrs::active_required()),
-                (C1, ComponentAttrs::passive_required()),
-            ]),
-            &[BOOT, Event::VerificationPassed(C0)],
-        );
-        assert_eq!(state, State::AwaitingReady);
-        assert!(effects.contains(&Effect::ReleaseReset(C0)));
-        assert!(effects.contains(&Effect::ReadFirmware(C1)));
-
-        let (effects2, state2) = drive(
-            chain(&[
-                (C0, ComponentAttrs::active_required()),
-                (C1, ComponentAttrs::passive_required()),
-            ]),
-            &[
-                BOOT,
-                Event::VerificationPassed(C0),
-                Event::ComponentReady(C0),
-                Event::VerificationPassed(C1),
-            ],
-        );
-        assert_eq!(state2, State::Ready);
-        assert!(effects2.contains(&Effect::ReleaseReset(C1)));
-    }
-
-    /// INV9: a ComponentReady for the wrong id is silently ignored.
-    #[test]
-    fn spurious_component_ready_is_ignored() {
-        let (effects, state) = drive(
-            chain(&[
-                (C0, ComponentAttrs::active_required()),
-                (C1, ComponentAttrs::passive_required()),
-            ]),
-            &[
-                BOOT,
-                Event::VerificationPassed(C0),
-                Event::ComponentReady(C1), // wrong id
-            ],
-        );
-        assert_eq!(state, State::AwaitingReady);
-        assert!(!effects.contains(&Effect::ReleaseReset(C1)));
-    }
-
-    /// INV12: AttestationChallenge is handled in AwaitingReady.
-    #[test]
-    fn attestation_in_awaiting_ready() {
-        let (effects, state) = drive(
-            chain(&[
-                (C0, ComponentAttrs::active_required()),
-                (C1, ComponentAttrs::passive_required()),
-            ]),
-            &[
-                BOOT,
-                Event::VerificationPassed(C0),
-                Event::AttestationChallenge,
-            ],
-        );
-        assert_eq!(state, State::AwaitingReady);
-        assert_eq!(effects.last(), Some(&Effect::SignAttestation));
-    }
-
-    /// Isolable component: every `VerificationFailed` is retried through a full
-    /// recovery episode first; only once retries are exhausted does the
-    /// component get held in reset and the walk continue to `Ready`.
-    #[test]
-    fn isolable_component_exhausts_recovery_then_skips() {
-        let mut script = std::vec![BOOT, Event::VerificationPassed(C0)];
-        for _ in 0..MAX_RETRY {
-            script.push(Event::VerificationFailed(C1));
-            script.push(Event::Restored(C1));
-            script.push(Event::VerificationPassed(C0));
-        }
-        let (effects, state) = drive(
-            chain(&[
-                (C0, ComponentAttrs::passive_required()),
-                (C1, ComponentAttrs::passive_isolable()),
-            ]),
-            &script,
-        );
-        assert_eq!(state, State::Ready);
-        // C1 must never be released.
-        assert!(!effects.contains(&Effect::ReleaseReset(C1)));
-        // Recovery IS attempted before C1 is classified and held.
-        assert!(effects.contains(&Effect::RestoreGoldenImage(C1)));
-        assert!(effects.contains(&Effect::AssertReset(C1)));
-        assert!(!effects.contains(&Effect::LatchLockdown));
-    }
-
-    /// Isolable Active component failure in AwaitingReady: retried through a
-    /// full recovery episode, then held once exhausted; the walk still
-    /// reaches Ready once the remaining chain (past the held component) drains.
-    #[test]
-    fn isolable_active_component_exhausted_in_awaiting_ready_skips() {
-        // C0 Active required, C1 Active isolable.
-        let mut script = std::vec![BOOT, Event::VerificationPassed(C0)]; // → AwaitingReady; spec ReadFirmware(C1)
-        for _ in 0..MAX_RETRY {
-            script.push(Event::VerificationFailed(C1));
-            script.push(Event::Restored(C1));
-            script.push(Event::VerificationPassed(C0)); // re-walk restarts at C0 each episode
-        }
-        let (effects, state) = drive(
-            chain(&[
-                (C0, ComponentAttrs::active_required()),
-                (C1, ComponentAttrs::active_isolable()),
-            ]),
-            &script,
-        );
-        assert_eq!(state, State::Ready);
-        assert!(!effects.contains(&Effect::ReleaseReset(C1)));
-        assert!(effects.contains(&Effect::RestoreGoldenImage(C1)));
-        assert!(effects.contains(&Effect::AssertReset(C1)));
-    }
-
-    /// Runtime corruption of an `Isolable` component gates the component
-    /// (AssertReset) but does not trigger recovery — the machine stays in Ready.
-    #[test]
-    fn isolable_runtime_corruption_is_ignored() {
-        let (effects, state) = drive(
-            chain(&[
-                (C0, ComponentAttrs::passive_required()),
-                (C1, ComponentAttrs::passive_isolable()),
-            ]),
-            &[
-                BOOT,
-                Event::VerificationPassed(C0),
-                Event::VerificationPassed(C1),
-                Event::CorruptionDetected(C1), // Isolable → gate, no recovery
-            ],
-        );
-        assert_eq!(state, State::Ready);
-        assert!(effects.contains(&Effect::AssertReset(C1)));
-        assert!(!effects.contains(&Effect::RestoreGoldenImage(C1)));
-        assert!(!effects.contains(&Effect::LatchLockdown));
-    }
-
-    /// Runtime corruption of an `Isolable` component holds it in reset: it is
-    /// added to `held`, so a later re-walk triggered by a *required*
-    /// component's recovery skips it instead of re-releasing a component we
-    /// already found corrupt.
-    #[test]
-    fn isolable_runtime_corruption_holds_across_rewalk() {
-        let (effects, state) = drive(
-            chain(&[
-                (C0, ComponentAttrs::passive_required()),
-                (C1, ComponentAttrs::passive_isolable()),
-            ]),
-            &[
-                BOOT,
-                Event::VerificationPassed(C0),
-                Event::VerificationPassed(C1),
-                Event::CorruptionDetected(C1), // isolable → gate + hold
-                Event::CorruptionDetected(C0), // required → Recovering
-                Event::Restored(C0),           // re-walk from top
-                Event::VerificationPassed(C0), // C1 stays held → chain done
-            ],
-        );
-        assert_eq!(state, State::Ready);
-        // C1 is released exactly once (the initial walk); the post-corruption
-        // re-walk must not release it again.
-        assert_eq!(
-            effects
-                .iter()
-                .filter(|e| **e == Effect::ReleaseReset(C1))
-                .count(),
-            1,
-        );
-        assert!(effects.contains(&Effect::AssertReset(C1)));
-    }
-
-    /// A durable gate must survive a return to `Ready`. The gate set is *not*
-    /// cleared on `Ready` entry, so a component isolated by policy stays gated
-    /// across an intervening return to `Ready` and a later chain walk never
-    /// re-reads or re-releases it. Extends the single-walk
-    /// `..._holds_across_rewalk` case with a *second* return to `Ready` — the
-    /// exact point where the old `held.clear()` dropped the gate.
-    #[test]
-    fn gate_survives_return_to_ready() {
-        let (effects, state) = drive(
-            chain(&[
-                (C0, ComponentAttrs::passive_required()),
-                (C1, ComponentAttrs::passive_isolable()),
-            ]),
-            &[
-                BOOT,
-                Event::VerificationPassed(C0),
-                Event::VerificationPassed(C1), // walk 1 done → Ready
-                Event::CorruptionDetected(C1), // isolable → gate + hold C1
-                Event::CorruptionDetected(C0), // required → Recovering
-                Event::Restored(C0),           // walk 2 from top
-                Event::VerificationPassed(C0), // C1 gated skip → Ready (gate must persist)
-                Event::CorruptionDetected(C0), // required → Recovering again
-                Event::Restored(C0),           // walk 3 from top
-                Event::VerificationPassed(C0), // C1 still gated → chain done → Ready
-            ],
-        );
-        assert_eq!(state, State::Ready);
-        // C1 was read/verified and released exactly once, on walk 1. If the
-        // gate were dropped at `Ready`, walk 3 would re-read and re-release it.
-        assert_eq!(
-            effects
-                .iter()
-                .filter(|e| **e == Effect::ReadFirmware(C1))
-                .count(),
-            1,
-        );
-        assert_eq!(
-            effects
-                .iter()
-                .filter(|e| **e == Effect::ReleaseReset(C1))
-                .count(),
-            1,
-        );
-    }
-
-    /// Runtime corruption of a `Required` component still triggers
-    /// recovery as before.
-    #[test]
-    fn required_runtime_corruption_triggers_recovery() {
-        let (effects, state) = drive(
-            chain(&[
-                (C0, ComponentAttrs::passive_required()),
-                (C1, ComponentAttrs::passive_isolable()),
-            ]),
-            &[
-                BOOT,
-                Event::VerificationPassed(C0),
-                Event::VerificationPassed(C1),
-                Event::CorruptionDetected(C0), // required → Recovering
-            ],
-        );
-        assert_eq!(state, State::Recovering);
-        assert!(effects.contains(&Effect::RestoreGoldenImage(C0)));
-    }
-
-    /// Runtime corruption of a `Cascading` component must gate the whole
-    /// cascade, not just the reported component. The runtime-corruption path
-    /// and the recovery-exhaustion path share one `gate_by_policy` source of
-    /// truth, so `Cascading` cascades in both. C2 `depends_on` C1; corrupting
-    /// C1 must assert reset on C1 **and** C2.
-    #[test]
-    fn cascading_runtime_corruption_cascades() {
-        let (effects, state) = drive(
-            chain(&[
-                (C0, ComponentAttrs::passive_required()),
-                (C1, ComponentAttrs::passive_cascading()),
-                (C2, ComponentAttrs::passive_required().with_depends_on(C1)),
-            ]),
-            &[
-                BOOT,
-                Event::VerificationPassed(C0),
-                Event::VerificationPassed(C1),
-                Event::VerificationPassed(C2), // walk done → Ready
-                Event::CorruptionDetected(C1), // Cascading → gate C1 and its dependents
-            ],
-        );
-        assert_eq!(state, State::Ready);
-        // The whole cascade is gated: C1 (the root) and C2 (its dependent).
-        assert!(effects.contains(&Effect::AssertReset(C1)));
-        assert!(effects.contains(&Effect::AssertReset(C2)));
-        // No recovery is started for a non-required corruption.
-        assert!(!effects.contains(&Effect::RestoreGoldenImage(C1)));
-        assert!(!effects.contains(&Effect::LatchLockdown));
-    }
-
-    /// Boot-time VerificationFailed on a required component → Recovering.
-    /// (Distinct from CorruptionDetected: this is a failed eRoT-side check
-    /// before the component is ever released from reset.)
-    #[test]
-    fn boot_failure_required_enters_recovering() {
-        let (effects, state) = drive(
-            passive_required(&[C0, C1]),
-            &[BOOT, Event::VerificationFailed(C0)],
-        );
-        assert_eq!(state, State::Recovering);
-        assert!(effects.contains(&Effect::RestoreGoldenImage(C0)));
-        // Component must never be released when its eRoT check failed.
-        assert!(!effects.contains(&Effect::ReleaseReset(C0)));
-    }
-
-    /// Full boot-failure recovery cycle: VerificationFailed → Recovering →
-    /// Restored → re-walk from top → VerificationPassed → Ready.
-    #[test]
-    fn boot_failure_recovery_cycle_completes() {
-        let (effects, state) = drive(
-            passive_required(&[C0]),
-            &[
-                BOOT,
-                Event::VerificationFailed(C0),
-                Event::Restored(C0),
-                Event::VerificationPassed(C0),
-            ],
-        );
-        assert_eq!(state, State::Ready);
-        assert!(effects.contains(&Effect::RestoreGoldenImage(C0)));
-        // ReleaseReset only after the recovery re-walk passes.
-        assert!(effects.contains(&Effect::ReleaseReset(C0)));
-    }
-
-    /// VerificationFailed (required) on a speculative check while the machine
-    /// is in AwaitingReady → enters Recovering without releasing the component.
-    #[test]
-    fn required_failure_in_awaiting_ready_enters_recovering() {
-        let (effects, state) = drive(
-            chain(&[
-                (C0, ComponentAttrs::active_required()),
-                (C1, ComponentAttrs::passive_required()),
-            ]),
-            &[
-                BOOT,
-                Event::VerificationPassed(C0), // → AwaitingReady; spec check of C1 starts
-                Event::VerificationFailed(C1), // required → Recovering
-            ],
-        );
-        assert_eq!(state, State::Recovering);
-        assert!(effects.contains(&Effect::RestoreGoldenImage(C1)));
-        assert!(!effects.contains(&Effect::ReleaseReset(C1)));
-    }
-
-    /// CorruptionDetected while in AwaitingReady (required component) →
-    /// Recovering via the SupervisingPlatform superstate handler.
-    #[test]
-    fn corruption_in_awaiting_ready_triggers_recovery() {
-        let (effects, state) = drive(
-            chain(&[
-                (C0, ComponentAttrs::active_required()),
-                (C1, ComponentAttrs::passive_required()),
-            ]),
-            &[
-                BOOT,
-                Event::VerificationPassed(C0), // → AwaitingReady
-                Event::CorruptionDetected(C0),
-            ],
-        );
-        assert_eq!(state, State::Recovering);
-        assert!(effects.contains(&Effect::RestoreGoldenImage(C0)));
-    }
-
-    /// CorruptionDetected while in Updating (required component) → Recovering
-    /// via the SupervisingPlatform superstate handler.
-    #[test]
-    fn corruption_in_updating_triggers_recovery() {
-        let (effects, state) = drive(
-            passive_required(&[C0]),
-            &[
-                BOOT,
-                Event::VerificationPassed(C0),
-                Event::UpdateRequest,
-                Event::CorruptionDetected(C0),
-            ],
-        );
-        assert_eq!(state, State::Recovering);
-        assert!(effects.contains(&Effect::RestoreGoldenImage(C0)));
-    }
-
-    /// UpdateVerified activates the staged image and returns to Ready.
-    /// (Complements update_rollback_is_not_recovery which tests UpdateRejected.)
-    #[test]
-    fn update_verified_activates_update() {
-        let (effects, state) = drive(
-            passive_required(&[C0]),
-            &[
-                BOOT,
-                Event::VerificationPassed(C0),
-                Event::UpdateRequest,
-                Event::UpdateVerified,
-            ],
-        );
-        assert_eq!(state, State::Ready);
-        assert!(effects.contains(&Effect::ActivateUpdate));
-        assert!(!effects.contains(&Effect::DiscardStaged));
-        assert!(!effects.contains(&Effect::RestoreGoldenImage(C0)));
-    }
-
-    /// Locked is a terminal state: no effects are produced in response to any
-    /// event after the machine latches.
-    #[test]
-    fn locked_is_terminal() {
-        let mut c: heapless::Vec<(ComponentId, ComponentAttrs), CAPACITY> =
-            heapless::Vec::new();
-        c.push((C0, ComponentAttrs::passive_required())).unwrap();
-        // max_retry = 1 so the first failed restore latches immediately.
-        let mut orch = Orchestrator::<CAPACITY, ECAP>::new(c, 1);
-        let mut effects: Vec<Effect> = Vec::new();
-
-        for ev in [BOOT, Event::VerificationFailed(C0), Event::Restored(C0)] {
-            orch.dispatch_with(ev, |e| effects.push(e));
-        }
-        assert_eq!(orch.state(), State::Locked);
-
-        let count_before = effects.len();
-        for ev in [
-            BOOT,
-            Event::VerificationPassed(C0),
-            Event::AttestationChallenge,
-            Event::UpdateRequest,
-            Event::CorruptionDetected(C0),
-        ] {
-            orch.dispatch_with(ev, |e| effects.push(e));
-        }
-        assert_eq!(
-            effects.len(),
-            count_before,
-            "Locked state must produce no effects"
-        );
-    }
-
-    /// An Isolable component at the head of the chain exhausts its recovery
-    /// retries, gets held, and the walk continues to the remaining required
-    /// components.
-    #[test]
-    fn isolable_first_component_exhausts_then_walk_continues() {
-        let mut script = std::vec![BOOT];
-        for _ in 0..MAX_RETRY {
-            script.push(Event::VerificationFailed(C0));
-            script.push(Event::Restored(C0));
-        }
-        script.push(Event::VerificationPassed(C1));
-        let (effects, state) = drive(
-            chain(&[
-                (C0, ComponentAttrs::passive_isolable()),
-                (C1, ComponentAttrs::passive_required()),
-            ]),
-            &script,
-        );
-        assert_eq!(state, State::Ready);
-        assert!(!effects.contains(&Effect::ReleaseReset(C0)));
-        assert!(effects.contains(&Effect::ReleaseReset(C1)));
-        // Recovery IS attempted before C0 is classified and held.
-        assert!(effects.contains(&Effect::RestoreGoldenImage(C0)));
-        assert!(effects.contains(&Effect::AssertReset(C0)));
-    }
-
-    /// The speculative read emits ReleaseReset · ReadFirmware · VerifyFirmware
-    /// all in the same handler as VerificationPassed for an Active component,
-    /// before ComponentReady has arrived. Verifies both presence and order.
-    #[test]
-    fn speculative_read_effects_are_emitted_together() {
-        let mut orch = Orchestrator::<CAPACITY, ECAP>::new(
-            chain(&[
-                (C0, ComponentAttrs::active_required()),
-                (C1, ComponentAttrs::passive_required()),
-            ]),
-            MAX_RETRY,
-        );
-        let mut effects: Vec<Effect> = Vec::new();
-
-        orch.dispatch_with(BOOT, |e| effects.push(e));
-        assert_eq!(
-            effects,
-            std::vec![Effect::ReadFirmware(C0), Effect::VerifyFirmware(C0)],
-        );
-
-        effects.clear();
-        orch.dispatch_with(Event::VerificationPassed(C0), |e| effects.push(e));
-        // All three effects emitted in the same handler, before ComponentReady.
-        assert_eq!(
-            effects,
-            std::vec![
-                Effect::ReleaseReset(C0),
-                Effect::ReadFirmware(C1),
-                Effect::VerifyFirmware(C1),
-            ],
-        );
-        assert_eq!(orch.state(), State::AwaitingReady);
-    }
-
-    /// A chain with a single Active component goes directly to Ready on
-    /// VerificationPassed — no AwaitingReady, no ComponentReady required.
-    /// This exercises the `chain done` branch of PreSupervision for an
-    /// Active component (distinct from the multi-component Active path which
-    /// transitions to AwaitingReady).
-    #[test]
-    fn single_active_chain_goes_directly_to_ready() {
-        let mut c: heapless::Vec<(ComponentId, ComponentAttrs), CAPACITY> =
-            heapless::Vec::new();
-        c.push((C0, ComponentAttrs::active_required())).unwrap();
-        let (effects, state) = drive(c, &[BOOT, Event::VerificationPassed(C0)]);
-        assert_eq!(state, State::Ready);
-        assert_eq!(
-            effects,
-            std::vec![
-                Effect::ReadFirmware(C0),
-                Effect::VerifyFirmware(C0),
-                Effect::ReleaseReset(C0),
-            ],
-        );
-    }
-}
+mod tests;
diff --git a/services/orchestrator/sm/src/tests.rs b/services/orchestrator/sm/src/tests.rs
new file mode 100644
index 0000000..ba303e9
--- /dev/null
+++ b/services/orchestrator/sm/src/tests.rs
@@ -0,0 +1,833 @@
+// Licensed under the Apache-2.0 license
+// SPDX-License-Identifier: Apache-2.0
+
+extern crate std;
+
+use super::*;
+use std::vec::Vec;
+
+const C0: ComponentId = ComponentId::new(0);
+const C1: ComponentId = ComponentId::new(1);
+const C2: ComponentId = ComponentId::new(2);
+
+const BOOT: Event = Event::PowerGood(PowerOnResult::Provisioned);
+
+const CAPACITY: usize = 8;
+const ECAP: usize = CAPACITY + 2;
+const MAX_RETRY: u8 = 3;
+
+fn chain(
+    ids: &[(ComponentId, ComponentAttrs)],
+) -> heapless::Vec<(ComponentId, ComponentAttrs), CAPACITY> {
+    let mut c = heapless::Vec::new();
+    for &entry in ids {
+        c.push(entry).expect("chain within CAPACITY");
+    }
+    c
+}
+
+fn passive_required(ids: &[ComponentId]) -> heapless::Vec<(ComponentId, ComponentAttrs), CAPACITY> {
+    chain(
+        &ids.iter()
+            .map(|&id| (id, ComponentAttrs::passive_required()))
+            .collect::<std::vec::Vec<_>>(),
+    )
+}
+
+struct Recorder {
+    recorded: Vec<Effect>,
+}
+
+impl Recorder {
+    fn new() -> Self {
+        Self {
+            recorded: Vec::new(),
+        }
+    }
+}
+
+impl Platform for Recorder {
+    fn execute(&mut self, effect: Effect) {
+        self.recorded.push(effect);
+    }
+}
+
+fn drive(
+    chain: heapless::Vec<(ComponentId, ComponentAttrs), CAPACITY>,
+    script: &[Event],
+) -> (Vec<Effect>, State) {
+    let mut orch = Orchestrator::<CAPACITY, ECAP>::new(chain, MAX_RETRY);
+    let mut platform = Recorder::new();
+    for &event in script {
+        orch.dispatch(&mut platform, event);
+    }
+    (platform.recorded, orch.state())
+}
+
+/// INV1/INV2/INV3: provisioned power-on walks the chain in order; no
+/// component is released before its eRoT-side verification passes.
+#[test]
+fn cold_boot_walks_chain_in_order() {
+    let (effects, state) = drive(
+        passive_required(&[C0, C1]),
+        &[
+            BOOT,
+            Event::VerificationPassed(C0),
+            Event::VerificationPassed(C1),
+        ],
+    );
+    assert_eq!(
+        effects,
+        std::vec![
+            Effect::ReadFirmware(C0),
+            Effect::VerifyFirmware(C0),
+            Effect::ReleaseReset(C0),
+            Effect::ReadFirmware(C1),
+            Effect::VerifyFirmware(C1),
+            Effect::ReleaseReset(C1),
+        ],
+    );
+    assert_eq!(state, State::Ready);
+}
+
+/// Unprovisioned power-on latches immediately.
+#[test]
+fn unprovisioned_boot_locks_down() {
+    let (effects, state) = drive(
+        passive_required(&[C0]),
+        &[Event::PowerGood(PowerOnResult::Unprovisioned)],
+    );
+    assert_eq!(effects, std::vec![Effect::LatchLockdown]);
+    assert_eq!(state, State::Locked);
+}
+
+/// INV11: SelfVerificationFailed latches immediately without entering
+/// PreSupervision.
+#[test]
+fn self_verification_failure_latches_immediately() {
+    let (effects, state) = drive(
+        passive_required(&[C0]),
+        &[Event::PowerGood(PowerOnResult::SelfVerificationFailed)],
+    );
+    assert_eq!(effects, std::vec![Effect::LatchLockdown]);
+    assert_eq!(state, State::Locked);
+}
+
+/// INV6: AttestationChallenge is answerable from every SupervisingPlatform state.
+#[test]
+fn attestation_shared_across_supervising_platform_states() {
+    let (effects, state) = drive(
+        passive_required(&[C0]),
+        &[
+            BOOT,
+            Event::VerificationPassed(C0),
+            Event::AttestationChallenge,
+        ],
+    );
+    assert_eq!(effects.last(), Some(&Effect::SignAttestation));
+    assert_eq!(state, State::Ready);
+
+    let (effects, state) = drive(
+        passive_required(&[C0]),
+        &[
+            BOOT,
+            Event::VerificationPassed(C0),
+            Event::UpdateRequest,
+            Event::AttestationChallenge,
+        ],
+    );
+    assert_eq!(effects.last(), Some(&Effect::SignAttestation));
+    assert_eq!(state, State::Updating);
+}
+
+/// INV4: a rejected update rolls back via DiscardStaged and never enters
+/// Recovering.
+#[test]
+fn update_rollback_is_not_recovery() {
+    let (effects, state) = drive(
+        passive_required(&[C0]),
+        &[
+            BOOT,
+            Event::VerificationPassed(C0),
+            Event::UpdateRequest,
+            Event::UpdateRejected,
+        ],
+    );
+    let tail = &effects[effects.len() - 3..];
+    assert_eq!(
+        tail,
+        &[
+            Effect::AuthenticateUpdate,
+            Effect::StageUpdate,
+            Effect::DiscardStaged
+        ],
+    );
+    assert_eq!(state, State::Ready);
+    assert!(!effects.contains(&Effect::LatchLockdown));
+}
+
+/// INV5: runtime corruption targets the named component and re-walks from
+/// the top after restore.
+#[test]
+fn runtime_corruption_targets_component_and_rewalks() {
+    let (effects, state) = drive(
+        passive_required(&[C0, C1]),
+        &[
+            BOOT,
+            Event::VerificationPassed(C0),
+            Event::VerificationPassed(C1),
+            Event::CorruptionDetected(C1),
+            Event::Restored(C1),
+        ],
+    );
+    let tail = &effects[effects.len() - 2..];
+    assert_eq!(
+        tail,
+        &[Effect::ReadFirmware(C0), Effect::VerifyFirmware(C0)]
+    );
+    assert_eq!(state, State::PreSupervision);
+}
+
+/// `PreSupervision` reacts to `CorruptionDetected` directly (via
+/// [`Rot::handle_corruption`]), even though it isn't linked to
+/// `SupervisingPlatform` (so `AttestationChallenge` is still discarded
+/// there — a separate question). CSA defines no mechanism guaranteeing a
+/// corruption report exists for an already-released component's *live,
+/// executing* state (at-rest/NVM-polling is scoped to "at
+/// rest"/"between boots", not an in-progress boot's chain walk) — but
+/// that only means such a report isn't guaranteed to arrive, not that one
+/// should be ignored if it does.
+#[test]
+fn corruption_during_presupervision_selfloop_triggers_recovery() {
+    let (effects, state) = drive(
+        passive_required(&[C0, C1, C2]),
+        &[
+            BOOT,
+            Event::VerificationPassed(C0), // released; walk continues (still PreSupervision)
+            Event::CorruptionDetected(C0), // C0 already released, but caught anyway
+        ],
+    );
+    assert_eq!(state, State::Recovering);
+    assert!(effects.contains(&Effect::RestoreGoldenImage(C0)));
+}
+
+/// INV7 (feedback-as-data): after MAX_RETRY restores the core self-emits
+/// RecoveryFailed and latches to Locked without any external RecoveryFailed
+/// in the script.
+#[test]
+fn retry_cap_self_latches_via_emit() {
+    let mut script = std::vec![BOOT, Event::VerificationPassed(C0)];
+    script.push(Event::CorruptionDetected(C0));
+    for _ in 0..(MAX_RETRY - 1) {
+        script.push(Event::Restored(C0));
+        script.push(Event::VerificationFailed(C0));
+    }
+    script.push(Event::Restored(C0));
+
+    let (effects, state) = drive(passive_required(&[C0]), &script);
+
+    assert!(!script.contains(&Event::RecoveryFailed));
+    assert_eq!(state, State::Locked);
+    assert_eq!(effects.last(), Some(&Effect::LatchLockdown));
+}
+
+/// INV7: retry count resets after a successful recovery so a later episode
+/// starts from zero.
+#[test]
+fn retry_count_resets_after_successful_recovery() {
+    let mut c = heapless::Vec::<(ComponentId, ComponentAttrs), CAPACITY>::new();
+    c.push((C0, ComponentAttrs::passive_required()))
+        .expect("fits");
+    let mut orch = Orchestrator::<CAPACITY, ECAP>::new(c, 2);
+    let mut effects = Vec::new();
+
+    for ev in [
+        BOOT,
+        Event::VerificationPassed(C0),
+        Event::CorruptionDetected(C0),
+        Event::Restored(C0),
+        Event::VerificationPassed(C0),
+    ] {
+        orch.dispatch_with(ev, |e| effects.push(e));
+    }
+    assert_eq!(orch.state(), State::Ready);
+
+    let start = effects.len();
+    for ev in [
+        Event::CorruptionDetected(C0),
+        Event::Restored(C0),
+        Event::VerificationPassed(C0),
+    ] {
+        orch.dispatch_with(ev, |e| effects.push(e));
+    }
+    assert_eq!(orch.state(), State::Ready);
+    assert!(!effects[start..].contains(&Effect::LatchLockdown));
+}
+
+/// Retry budgets are **per component**, not a single global counter. Two
+/// required components each fail exactly once (well under `max_retry = 2`)
+/// in an interleaved recovery sequence before the chain finally settles.
+/// Under a shared global counter the second failure would push the count to
+/// the cap and latch the platform to `Locked`; per-component counting lets
+/// each device use its own budget, so the walk reaches `Ready`.
+#[test]
+fn retry_budget_is_per_component() {
+    let mut c = heapless::Vec::<(ComponentId, ComponentAttrs), CAPACITY>::new();
+    c.push((C0, ComponentAttrs::passive_required()))
+        .expect("fits");
+    c.push((C1, ComponentAttrs::passive_required()))
+        .expect("fits");
+    let mut orch = Orchestrator::<CAPACITY, ECAP>::new(c, 2);
+    let mut effects = Vec::new();
+
+    for ev in [
+        BOOT,
+        Event::VerificationFailed(C0), // C0 fails once → Recovering
+        Event::Restored(C0),           // C0 count = 1 (< 2) → re-walk
+        Event::VerificationPassed(C0), // C0 recovered → its streak clears
+        Event::VerificationFailed(C1), // C1 fails once → Recovering
+        Event::Restored(C1),           // C1 count = 1 (< 2); global would be 2 → latch
+        Event::VerificationPassed(C0), // re-walk restarts at the top
+        Event::VerificationPassed(C1), // chain done → Ready
+    ] {
+        orch.dispatch_with(ev, |e| effects.push(e));
+    }
+
+    assert_eq!(orch.state(), State::Ready);
+    assert!(!effects.contains(&Effect::LatchLockdown));
+}
+
+/// Board-supplied retry cap: max_retry = 1 latches on the first failed
+/// restore.
+#[test]
+fn custom_retry_cap_latches_sooner() {
+    let mut c = heapless::Vec::<(ComponentId, ComponentAttrs), CAPACITY>::new();
+    c.push((C0, ComponentAttrs::passive_required()))
+        .expect("fits");
+    let mut orch = Orchestrator::<CAPACITY, ECAP>::new(c, 1);
+    let mut effects = Vec::new();
+    for ev in [
+        BOOT,
+        Event::VerificationPassed(C0),
+        Event::CorruptionDetected(C0),
+        Event::Restored(C0),
+    ] {
+        orch.dispatch_with(ev, |e| effects.push(e));
+    }
+    assert_eq!(orch.state(), State::Locked);
+    assert_eq!(effects.last(), Some(&Effect::LatchLockdown));
+}
+
+/// Three-component chain uses N=3; walks all three to Ready.
+#[test]
+fn custom_capacity_walks_full_chain() {
+    let mut c = heapless::Vec::<(ComponentId, ComponentAttrs), 3>::new();
+    for &id in &[C0, C1, C2] {
+        c.push((id, ComponentAttrs::passive_required()))
+            .expect("3 fits");
+    }
+    let mut orch = Orchestrator::<3, 5>::new(c, MAX_RETRY);
+    let mut effects = Vec::new();
+    for ev in [
+        BOOT,
+        Event::VerificationPassed(C0),
+        Event::VerificationPassed(C1),
+        Event::VerificationPassed(C2),
+    ] {
+        orch.dispatch_with(ev, |e| effects.push(e));
+    }
+    assert_eq!(orch.state(), State::Ready);
+    assert_eq!(effects.last(), Some(&Effect::ReleaseReset(C2)));
+}
+
+/// INV10: Active component gates the chain walk — cursor does not advance
+/// until ComponentReady arrives.
+#[test]
+fn active_component_gates_on_component_ready() {
+    let (effects, state) = drive(
+        chain(&[
+            (C0, ComponentAttrs::active_required()),
+            (C1, ComponentAttrs::passive_required()),
+        ]),
+        &[BOOT, Event::VerificationPassed(C0)],
+    );
+    assert_eq!(state, State::AwaitingReady);
+    assert!(effects.contains(&Effect::ReleaseReset(C0)));
+    assert!(effects.contains(&Effect::ReadFirmware(C1)));
+
+    let (effects2, state2) = drive(
+        chain(&[
+            (C0, ComponentAttrs::active_required()),
+            (C1, ComponentAttrs::passive_required()),
+        ]),
+        &[
+            BOOT,
+            Event::VerificationPassed(C0),
+            Event::ComponentReady(C0),
+            Event::VerificationPassed(C1),
+        ],
+    );
+    assert_eq!(state2, State::Ready);
+    assert!(effects2.contains(&Effect::ReleaseReset(C1)));
+}
+
+/// INV9: a ComponentReady for the wrong id is silently ignored.
+#[test]
+fn spurious_component_ready_is_ignored() {
+    let (effects, state) = drive(
+        chain(&[
+            (C0, ComponentAttrs::active_required()),
+            (C1, ComponentAttrs::passive_required()),
+        ]),
+        &[
+            BOOT,
+            Event::VerificationPassed(C0),
+            Event::ComponentReady(C1), // wrong id
+        ],
+    );
+    assert_eq!(state, State::AwaitingReady);
+    assert!(!effects.contains(&Effect::ReleaseReset(C1)));
+}
+
+/// INV12: AttestationChallenge is handled in AwaitingReady.
+#[test]
+fn attestation_in_awaiting_ready() {
+    let (effects, state) = drive(
+        chain(&[
+            (C0, ComponentAttrs::active_required()),
+            (C1, ComponentAttrs::passive_required()),
+        ]),
+        &[
+            BOOT,
+            Event::VerificationPassed(C0),
+            Event::AttestationChallenge,
+        ],
+    );
+    assert_eq!(state, State::AwaitingReady);
+    assert_eq!(effects.last(), Some(&Effect::SignAttestation));
+}
+
+/// Isolable component: every `VerificationFailed` is retried through a full
+/// recovery episode first; only once retries are exhausted does the
+/// component get held in reset and the walk continue to `Ready`.
+#[test]
+fn isolable_component_exhausts_recovery_then_skips() {
+    let mut script = std::vec![BOOT, Event::VerificationPassed(C0)];
+    for _ in 0..MAX_RETRY {
+        script.push(Event::VerificationFailed(C1));
+        script.push(Event::Restored(C1));
+        script.push(Event::VerificationPassed(C0));
+    }
+    let (effects, state) = drive(
+        chain(&[
+            (C0, ComponentAttrs::passive_required()),
+            (C1, ComponentAttrs::passive_isolable()),
+        ]),
+        &script,
+    );
+    assert_eq!(state, State::Ready);
+    // C1 must never be released.
+    assert!(!effects.contains(&Effect::ReleaseReset(C1)));
+    // Recovery IS attempted before C1 is classified and held.
+    assert!(effects.contains(&Effect::RestoreGoldenImage(C1)));
+    assert!(effects.contains(&Effect::AssertReset(C1)));
+    assert!(!effects.contains(&Effect::LatchLockdown));
+}
+
+/// Isolable Active component failure in AwaitingReady: retried through a
+/// full recovery episode, then held once exhausted; the walk still
+/// reaches Ready once the remaining chain (past the held component) drains.
+#[test]
+fn isolable_active_component_exhausted_in_awaiting_ready_skips() {
+    // C0 Active required, C1 Active isolable.
+    let mut script = std::vec![BOOT, Event::VerificationPassed(C0)]; // → AwaitingReady; spec ReadFirmware(C1)
+    for _ in 0..MAX_RETRY {
+        script.push(Event::VerificationFailed(C1));
+        script.push(Event::Restored(C1));
+        script.push(Event::VerificationPassed(C0)); // re-walk restarts at C0 each episode
+    }
+    let (effects, state) = drive(
+        chain(&[
+            (C0, ComponentAttrs::active_required()),
+            (C1, ComponentAttrs::active_isolable()),
+        ]),
+        &script,
+    );
+    assert_eq!(state, State::Ready);
+    assert!(!effects.contains(&Effect::ReleaseReset(C1)));
+    assert!(effects.contains(&Effect::RestoreGoldenImage(C1)));
+    assert!(effects.contains(&Effect::AssertReset(C1)));
+}
+
+/// Runtime corruption of an `Isolable` component gates the component
+/// (AssertReset) but does not trigger recovery — the machine stays in Ready.
+#[test]
+fn isolable_runtime_corruption_is_ignored() {
+    let (effects, state) = drive(
+        chain(&[
+            (C0, ComponentAttrs::passive_required()),
+            (C1, ComponentAttrs::passive_isolable()),
+        ]),
+        &[
+            BOOT,
+            Event::VerificationPassed(C0),
+            Event::VerificationPassed(C1),
+            Event::CorruptionDetected(C1), // Isolable → gate, no recovery
+        ],
+    );
+    assert_eq!(state, State::Ready);
+    assert!(effects.contains(&Effect::AssertReset(C1)));
+    assert!(!effects.contains(&Effect::RestoreGoldenImage(C1)));
+    assert!(!effects.contains(&Effect::LatchLockdown));
+}
+
+/// Runtime corruption of an `Isolable` component holds it in reset: it is
+/// added to `held`, so a later re-walk triggered by a *required*
+/// component's recovery skips it instead of re-releasing a component we
+/// already found corrupt.
+#[test]
+fn isolable_runtime_corruption_holds_across_rewalk() {
+    let (effects, state) = drive(
+        chain(&[
+            (C0, ComponentAttrs::passive_required()),
+            (C1, ComponentAttrs::passive_isolable()),
+        ]),
+        &[
+            BOOT,
+            Event::VerificationPassed(C0),
+            Event::VerificationPassed(C1),
+            Event::CorruptionDetected(C1), // isolable → gate + hold
+            Event::CorruptionDetected(C0), // required → Recovering
+            Event::Restored(C0),           // re-walk from top
+            Event::VerificationPassed(C0), // C1 stays held → chain done
+        ],
+    );
+    assert_eq!(state, State::Ready);
+    // C1 is released exactly once (the initial walk); the post-corruption
+    // re-walk must not release it again.
+    assert_eq!(
+        effects
+            .iter()
+            .filter(|e| **e == Effect::ReleaseReset(C1))
+            .count(),
+        1,
+    );
+    assert!(effects.contains(&Effect::AssertReset(C1)));
+}
+
+/// A durable gate must survive a return to `Ready`. The gate set is *not*
+/// cleared on `Ready` entry, so a component isolated by policy stays gated
+/// across an intervening return to `Ready` and a later chain walk never
+/// re-reads or re-releases it. Extends the single-walk
+/// `..._holds_across_rewalk` case with a *second* return to `Ready` — the
+/// exact point where the old `held.clear()` dropped the gate.
+#[test]
+fn gate_survives_return_to_ready() {
+    let (effects, state) = drive(
+        chain(&[
+            (C0, ComponentAttrs::passive_required()),
+            (C1, ComponentAttrs::passive_isolable()),
+        ]),
+        &[
+            BOOT,
+            Event::VerificationPassed(C0),
+            Event::VerificationPassed(C1), // walk 1 done → Ready
+            Event::CorruptionDetected(C1), // isolable → gate + hold C1
+            Event::CorruptionDetected(C0), // required → Recovering
+            Event::Restored(C0),           // walk 2 from top
+            Event::VerificationPassed(C0), // C1 gated skip → Ready (gate must persist)
+            Event::CorruptionDetected(C0), // required → Recovering again
+            Event::Restored(C0),           // walk 3 from top
+            Event::VerificationPassed(C0), // C1 still gated → chain done → Ready
+        ],
+    );
+    assert_eq!(state, State::Ready);
+    // C1 was read/verified and released exactly once, on walk 1. If the
+    // gate were dropped at `Ready`, walk 3 would re-read and re-release it.
+    assert_eq!(
+        effects
+            .iter()
+            .filter(|e| **e == Effect::ReadFirmware(C1))
+            .count(),
+        1,
+    );
+    assert_eq!(
+        effects
+            .iter()
+            .filter(|e| **e == Effect::ReleaseReset(C1))
+            .count(),
+        1,
+    );
+}
+
+/// Runtime corruption of a `Required` component still triggers
+/// recovery as before.
+#[test]
+fn required_runtime_corruption_triggers_recovery() {
+    let (effects, state) = drive(
+        chain(&[
+            (C0, ComponentAttrs::passive_required()),
+            (C1, ComponentAttrs::passive_isolable()),
+        ]),
+        &[
+            BOOT,
+            Event::VerificationPassed(C0),
+            Event::VerificationPassed(C1),
+            Event::CorruptionDetected(C0), // required → Recovering
+        ],
+    );
+    assert_eq!(state, State::Recovering);
+    assert!(effects.contains(&Effect::RestoreGoldenImage(C0)));
+}
+
+/// Runtime corruption of a `Cascading` component must gate the whole
+/// cascade, not just the reported component. The runtime-corruption path
+/// and the recovery-exhaustion path share one `gate_by_policy` source of
+/// truth, so `Cascading` cascades in both. C2 `depends_on` C1; corrupting
+/// C1 must assert reset on C1 **and** C2.
+#[test]
+fn cascading_runtime_corruption_cascades() {
+    let (effects, state) = drive(
+        chain(&[
+            (C0, ComponentAttrs::passive_required()),
+            (C1, ComponentAttrs::passive_cascading()),
+            (C2, ComponentAttrs::passive_required().with_depends_on(C1)),
+        ]),
+        &[
+            BOOT,
+            Event::VerificationPassed(C0),
+            Event::VerificationPassed(C1),
+            Event::VerificationPassed(C2), // walk done → Ready
+            Event::CorruptionDetected(C1), // Cascading → gate C1 and its dependents
+        ],
+    );
+    assert_eq!(state, State::Ready);
+    // The whole cascade is gated: C1 (the root) and C2 (its dependent).
+    assert!(effects.contains(&Effect::AssertReset(C1)));
+    assert!(effects.contains(&Effect::AssertReset(C2)));
+    // No recovery is started for a non-required corruption.
+    assert!(!effects.contains(&Effect::RestoreGoldenImage(C1)));
+    assert!(!effects.contains(&Effect::LatchLockdown));
+}
+
+/// Boot-time VerificationFailed on a required component → Recovering.
+/// (Distinct from CorruptionDetected: this is a failed eRoT-side check
+/// before the component is ever released from reset.)
+#[test]
+fn boot_failure_required_enters_recovering() {
+    let (effects, state) = drive(
+        passive_required(&[C0, C1]),
+        &[BOOT, Event::VerificationFailed(C0)],
+    );
+    assert_eq!(state, State::Recovering);
+    assert!(effects.contains(&Effect::RestoreGoldenImage(C0)));
+    // Component must never be released when its eRoT check failed.
+    assert!(!effects.contains(&Effect::ReleaseReset(C0)));
+}
+
+/// Full boot-failure recovery cycle: VerificationFailed → Recovering →
+/// Restored → re-walk from top → VerificationPassed → Ready.
+#[test]
+fn boot_failure_recovery_cycle_completes() {
+    let (effects, state) = drive(
+        passive_required(&[C0]),
+        &[
+            BOOT,
+            Event::VerificationFailed(C0),
+            Event::Restored(C0),
+            Event::VerificationPassed(C0),
+        ],
+    );
+    assert_eq!(state, State::Ready);
+    assert!(effects.contains(&Effect::RestoreGoldenImage(C0)));
+    // ReleaseReset only after the recovery re-walk passes.
+    assert!(effects.contains(&Effect::ReleaseReset(C0)));
+}
+
+/// VerificationFailed (required) on a speculative check while the machine
+/// is in AwaitingReady → enters Recovering without releasing the component.
+#[test]
+fn required_failure_in_awaiting_ready_enters_recovering() {
+    let (effects, state) = drive(
+        chain(&[
+            (C0, ComponentAttrs::active_required()),
+            (C1, ComponentAttrs::passive_required()),
+        ]),
+        &[
+            BOOT,
+            Event::VerificationPassed(C0), // → AwaitingReady; spec check of C1 starts
+            Event::VerificationFailed(C1), // required → Recovering
+        ],
+    );
+    assert_eq!(state, State::Recovering);
+    assert!(effects.contains(&Effect::RestoreGoldenImage(C1)));
+    assert!(!effects.contains(&Effect::ReleaseReset(C1)));
+}
+
+/// CorruptionDetected while in AwaitingReady (required component) →
+/// Recovering via the SupervisingPlatform superstate handler.
+#[test]
+fn corruption_in_awaiting_ready_triggers_recovery() {
+    let (effects, state) = drive(
+        chain(&[
+            (C0, ComponentAttrs::active_required()),
+            (C1, ComponentAttrs::passive_required()),
+        ]),
+        &[
+            BOOT,
+            Event::VerificationPassed(C0), // → AwaitingReady
+            Event::CorruptionDetected(C0),
+        ],
+    );
+    assert_eq!(state, State::Recovering);
+    assert!(effects.contains(&Effect::RestoreGoldenImage(C0)));
+}
+
+/// CorruptionDetected while in Updating (required component) → Recovering
+/// via the SupervisingPlatform superstate handler.
+#[test]
+fn corruption_in_updating_triggers_recovery() {
+    let (effects, state) = drive(
+        passive_required(&[C0]),
+        &[
+            BOOT,
+            Event::VerificationPassed(C0),
+            Event::UpdateRequest,
+            Event::CorruptionDetected(C0),
+        ],
+    );
+    assert_eq!(state, State::Recovering);
+    assert!(effects.contains(&Effect::RestoreGoldenImage(C0)));
+}
+
+/// UpdateVerified activates the staged image and returns to Ready.
+/// (Complements update_rollback_is_not_recovery which tests UpdateRejected.)
+#[test]
+fn update_verified_activates_update() {
+    let (effects, state) = drive(
+        passive_required(&[C0]),
+        &[
+            BOOT,
+            Event::VerificationPassed(C0),
+            Event::UpdateRequest,
+            Event::UpdateVerified,
+        ],
+    );
+    assert_eq!(state, State::Ready);
+    assert!(effects.contains(&Effect::ActivateUpdate));
+    assert!(!effects.contains(&Effect::DiscardStaged));
+    assert!(!effects.contains(&Effect::RestoreGoldenImage(C0)));
+}
+
+/// Locked is a terminal state: no effects are produced in response to any
+/// event after the machine latches.
+#[test]
+fn locked_is_terminal() {
+    let mut c: heapless::Vec<(ComponentId, ComponentAttrs), CAPACITY> = heapless::Vec::new();
+    c.push((C0, ComponentAttrs::passive_required())).unwrap();
+    // max_retry = 1 so the first failed restore latches immediately.
+    let mut orch = Orchestrator::<CAPACITY, ECAP>::new(c, 1);
+    let mut effects: Vec<Effect> = Vec::new();
+
+    for ev in [BOOT, Event::VerificationFailed(C0), Event::Restored(C0)] {
+        orch.dispatch_with(ev, |e| effects.push(e));
+    }
+    assert_eq!(orch.state(), State::Locked);
+
+    let count_before = effects.len();
+    for ev in [
+        BOOT,
+        Event::VerificationPassed(C0),
+        Event::AttestationChallenge,
+        Event::UpdateRequest,
+        Event::CorruptionDetected(C0),
+    ] {
+        orch.dispatch_with(ev, |e| effects.push(e));
+    }
+    assert_eq!(
+        effects.len(),
+        count_before,
+        "Locked state must produce no effects"
+    );
+}
+
+/// An Isolable component at the head of the chain exhausts its recovery
+/// retries, gets held, and the walk continues to the remaining required
+/// components.
+#[test]
+fn isolable_first_component_exhausts_then_walk_continues() {
+    let mut script = std::vec![BOOT];
+    for _ in 0..MAX_RETRY {
+        script.push(Event::VerificationFailed(C0));
+        script.push(Event::Restored(C0));
+    }
+    script.push(Event::VerificationPassed(C1));
+    let (effects, state) = drive(
+        chain(&[
+            (C0, ComponentAttrs::passive_isolable()),
+            (C1, ComponentAttrs::passive_required()),
+        ]),
+        &script,
+    );
+    assert_eq!(state, State::Ready);
+    assert!(!effects.contains(&Effect::ReleaseReset(C0)));
+    assert!(effects.contains(&Effect::ReleaseReset(C1)));
+    // Recovery IS attempted before C0 is classified and held.
+    assert!(effects.contains(&Effect::RestoreGoldenImage(C0)));
+    assert!(effects.contains(&Effect::AssertReset(C0)));
+}
+
+/// The speculative read emits ReleaseReset · ReadFirmware · VerifyFirmware
+/// all in the same handler as VerificationPassed for an Active component,
+/// before ComponentReady has arrived. Verifies both presence and order.
+#[test]
+fn speculative_read_effects_are_emitted_together() {
+    let mut orch = Orchestrator::<CAPACITY, ECAP>::new(
+        chain(&[
+            (C0, ComponentAttrs::active_required()),
+            (C1, ComponentAttrs::passive_required()),
+        ]),
+        MAX_RETRY,
+    );
+    let mut effects: Vec<Effect> = Vec::new();
+
+    orch.dispatch_with(BOOT, |e| effects.push(e));
+    assert_eq!(
+        effects,
+        std::vec![Effect::ReadFirmware(C0), Effect::VerifyFirmware(C0)],
+    );
+
+    effects.clear();
+    orch.dispatch_with(Event::VerificationPassed(C0), |e| effects.push(e));
+    // All three effects emitted in the same handler, before ComponentReady.
+    assert_eq!(
+        effects,
+        std::vec![
+            Effect::ReleaseReset(C0),
+            Effect::ReadFirmware(C1),
+            Effect::VerifyFirmware(C1),
+        ],
+    );
+    assert_eq!(orch.state(), State::AwaitingReady);
+}
+
+/// A chain with a single Active component goes directly to Ready on
+/// VerificationPassed — no AwaitingReady, no ComponentReady required.
+/// This exercises the `chain done` branch of PreSupervision for an
+/// Active component (distinct from the multi-component Active path which
+/// transitions to AwaitingReady).
+#[test]
+fn single_active_chain_goes_directly_to_ready() {
+    let mut c: heapless::Vec<(ComponentId, ComponentAttrs), CAPACITY> = heapless::Vec::new();
+    c.push((C0, ComponentAttrs::active_required())).unwrap();
+    let (effects, state) = drive(c, &[BOOT, Event::VerificationPassed(C0)]);
+    assert_eq!(state, State::Ready);
+    assert_eq!(
+        effects,
+        std::vec![
+            Effect::ReadFirmware(C0),
+            Effect::VerifyFirmware(C0),
+            Effect::ReleaseReset(C0),
+        ],
+    );
+}