orchestrator: Compose boot-walk supervision from the board's BootWatch capability

ReleaseReset arms the component's walk, AssertReset stops it, and the
new PlatformDriver::poll_boot_walks maps terminal verdicts to events:
Complete becomes ComponentReady (Active) or Booted (Passive), Failed
becomes Timeout regardless of cause — retry budgeting is the SM's. A
finished walk stops being watched, so each verdict is delivered once;
while everything waits, the poll carries the earliest walk deadline as
the run loop's next wake-up.

BootWatch gains arm(): a retry re-release starts a fresh walk, and
since reset actuation has no clock, the attempt starts at the next
poll's now_millis. The Board supplies one walk and one ComponentKind
per component, from the same table as the SM's chain.

The driver README still described take_event and NotImplemented, both
gone; rewritten to the current execute/fail-closed shape.

Signed-off-by: Christina Quast <christina.quast@9elements.com>
diff --git a/services/orchestrator/capabilities/src/boot_watch.rs b/services/orchestrator/capabilities/src/boot_watch.rs
index de55354..66c703c 100644
--- a/services/orchestrator/capabilities/src/boot_watch.rs
+++ b/services/orchestrator/capabilities/src/boot_watch.rs
@@ -12,6 +12,13 @@
 /// its walks in an enum and matches, without touching anything below the
 /// seam.
 pub trait BootWatch {
+    /// Starts a fresh attempt: previous progress is discarded and the walk
+    /// judges from its first checkpoint again. The driver calls this on
+    /// every reset release, retries included. Takes no timestamp — reset
+    /// actuation has no clock; the attempt starts at the next
+    /// [`poll`](BootWatch::poll)'s `now_millis`.
+    fn arm(&mut self);
+
     /// Judges the walk at `now_millis` (monotonic). Never sleeps — time is
     /// injected, so every decision is host-testable.
     fn poll(&mut self, now_millis: u64) -> WalkVerdict;
@@ -86,6 +93,10 @@
     }
 
     impl BootWatch for ScriptedWalk {
+        fn arm(&mut self) {
+            self.next = 0;
+        }
+
         fn poll(&mut self, _now_millis: u64) -> WalkVerdict {
             let v = self.verdicts[self.next];
             self.next += 1;
diff --git a/services/orchestrator/driver/README.md b/services/orchestrator/driver/README.md
index 51e03b3..2cb47ce 100644
--- a/services/orchestrator/driver/README.md
+++ b/services/orchestrator/driver/README.md
@@ -4,24 +4,33 @@
 # orchestrator platform driver (`openprot_orchestrator_driver`)
 
 The effect-executing layer around the orchestrator state machine. `PlatformDriver`
-implements the SM's `Platform` seam: one method per `Effect`, each
+implements the SM's `Platform` seam: one executor per `Effect`, each
 documenting its obligation from the platform-boundary contract
-([orchestrator-model.md §6](../../../docs/src/design/orchestrator/orchestrator-model.md)). Unimplemented executors return
-`DriverError::NotImplemented`; the SM fail-closes on them.
+([orchestrator-model.md §6](../../../docs/src/design/orchestrator/orchestrator-model.md)).
+An effect whose capability is not composed yet returns `EffectError` from
+`execute`; the SM fail-closes on it.
 
 Everything device-specific arrives through the seams in `board.rs`
-(`ImageSource`, `Verifier`, bundled in `Board`); executor-produced events
-return to the SM via `PlatformDriver::take_event`. The event loop dispatches an
-outside event, then keeps dispatching what the executors produced until
-`take_event` returns `None`:
+(`ImageSource`, `Verifier`, `BootControl`, `BootWatch`, bundled in `Board`).
+Synchronous results (the verification verdict) return through `execute` and
+settle within the same dispatch run — there is no driver-side event queue.
+
+Boot-walk verdicts are the one asynchronous read. `ReleaseReset` arms the
+component's walk; the run loop polls and dispatches until quiet, then sleeps
+until the earliest walk deadline:
 
 ```rust
 orch.dispatch(&mut driver, event);
-while let Some(ev) = driver.take_event() {
-    orch.dispatch(&mut driver, ev);
+loop {
+    let poll = driver.poll_boot_walks(now_millis);
+    match poll.event {
+        Some(ev) => orch.dispatch(&mut driver, ev),
+        None => break, // poll.next_deadline_millis = next wake-up
+    }
 }
 ```
 
-Implemented executors: `read_firmware`, `verify_firmware`. Everything else
-returns `NotImplemented` until its pillar lands (boot walk, recovery,
-update path, attestation, reporting).
+Implemented executors: `ReadFirmware`, `VerifyFirmware`, `ReleaseReset`
+(arms the boot walk), `AssertReset` (stops it). Everything else fails closed
+until its pillar lands (recovery, update path, attestation, reporting,
+lockdown latch).
diff --git a/services/orchestrator/driver/src/board.rs b/services/orchestrator/driver/src/board.rs
index d1c2508..78ca53d 100644
--- a/services/orchestrator/driver/src/board.rs
+++ b/services/orchestrator/driver/src/board.rs
@@ -4,9 +4,9 @@
 //! What the board supplies to the driver: traits and wiring data only.
 //! Boards (or test mocks) implement these.
 
-use openprot_orchestrator_sm::ComponentId;
+use openprot_orchestrator_sm::{ComponentId, ComponentKind};
 
-pub use orchestrator_capabilities::BootControl;
+pub use orchestrator_capabilities::{BootControl, BootWatch};
 
 /// Access to one component's active firmware image, however it is reached —
 /// interposed flash, a PLDM/MCTP transfer, a RAM copy in tests.
@@ -99,7 +99,9 @@
     type Verifier: Verifier;
     /// Reset actuation for the managed components.
     type BootControl: BootControl;
-    // Later seams: Evidence (checkpoint walk), Recovery, Staging.
+    /// Boot-checkpoint supervision for the managed components.
+    type BootWatch: BootWatch;
+    // Later seams: Recovery, Staging.
 }
 
 /// Everything the board supplies, built once at bring-up and handed to
@@ -112,11 +114,14 @@
 ///     type Image = SpiFlashImage;         // interposed flash, offsets from the slot layout
 ///     type Verifier = ManifestVerifier;   // signature + SVN via the crypto engine
 ///     type BootControl = ExtrstGpio;      // per-component reset line
+///     type BootWatch = CheckpointWalk;    // GPIO checkpoint walk over the boot window
 /// }
 /// let board = Board::<Ast1060Board, 2> {
 ///     images: [bmc_image, cpld_image],
 ///     verifier,
 ///     boot_controls: [bmc_reset, cpld_reset],
+///     boot_watches: [bmc_walk, cpld_walk],
+///     component_kinds: [ComponentKind::Active, ComponentKind::Passive],
 /// };
 /// ```
 pub struct Board<B: BoardCapabilities, const N: usize> {
@@ -128,5 +133,12 @@
     /// `boot_controls[i]` actuates `ComponentId(i)`'s reset, same indexing
     /// as `images`.
     pub boot_controls: [B::BootControl; N],
-    // Later seams add fields, e.g. evidence: [B::Evidence; N].
+    /// `boot_watches[i]` supervises `ComponentId(i)`'s boot walk, same
+    /// indexing as `images`.
+    pub boot_watches: [B::BootWatch; N],
+    /// `component_kinds[i]` classifies `ComponentId(i)`: a completed walk becomes
+    /// `ComponentReady` for `Active`, `Booted` for `Passive`. Comes from
+    /// the same board table as the SM's chain, so both sides agree.
+    pub component_kinds: [ComponentKind; N],
+    // Later seams add fields, e.g. recovery: [B::Recovery; N].
 }
diff --git a/services/orchestrator/driver/src/driver.rs b/services/orchestrator/driver/src/driver.rs
index 8eb16f8..20dd800 100644
--- a/services/orchestrator/driver/src/driver.rs
+++ b/services/orchestrator/driver/src/driver.rs
@@ -4,10 +4,10 @@
 //! The [`PlatformDriver`]: one executor method per [`Effect`] variant, routed from
 //! the SM through the [`Platform`] impl.
 
-use openprot_orchestrator_sm::{ComponentId, Effect, EffectError, Event, Platform};
+use openprot_orchestrator_sm::{ComponentId, ComponentKind, Effect, EffectError, Event, Platform};
 
 use crate::board::{Board, BoardCapabilities, ImageSource, Verdict, Verifier};
-use orchestrator_capabilities::BootControl;
+use orchestrator_capabilities::{BootControl, BootWatch, WalkVerdict};
 
 /// Why the driver could not carry out an effect.
 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
@@ -45,6 +45,11 @@
     board: Board<B, N>,
     /// Component whose image is staged (source opened) for verification.
     staged: Option<ComponentId>,
+    /// `watching[i]`: `ComponentId(i)` is out of reset with a walk in
+    /// flight. Set on `ReleaseReset`, cleared on `AssertReset` and on a
+    /// terminal verdict. Only watched walks are polled, so a finished or
+    /// quiesced walk emits no stale event.
+    watching: [bool; N],
 }
 
 impl<B: BoardCapabilities, const N: usize> PlatformDriver<B, N> {
@@ -52,6 +57,7 @@
         Self {
             board,
             staged: None,
+            watching: [false; N],
         }
     }
 
@@ -101,22 +107,95 @@
             .ok_or(DriverError::UnknownComponent)
     }
 
-    /// Release `id` from reset. The boot-checkpoint walk that feeds back
-    /// `Event::ComponentReady(id)`/`Event::Booted(id)`/`Event::Timeout(id)`
-    /// belongs to the BootWatch seam, not yet composed.
+    /// Release `id` from reset and arm its boot walk;
+    /// [`poll_boot_walks`](Self::poll_boot_walks) feeds the verdict back
+    /// as `ComponentReady(id)`/`Booted(id)`/`Timeout(id)`. Arms on every
+    /// release: a retry re-release starts a fresh walk.
     pub fn release_reset(&mut self, id: ComponentId) -> Result<(), DriverError> {
         self.boot_control(id)?
             .release()
-            .map_err(|_| DriverError::BootControlFault)
+            .map_err(|_| DriverError::BootControlFault)?;
+        let idx = id.get() as usize;
+        // In bounds: boot_control(id) above already rejected unknown ids.
+        self.board.boot_watches[idx].arm();
+        self.watching[idx] = true;
+        Ok(())
     }
 
     /// Hold `id` in reset — a durable quiesce, not a pulse; at-rest
-    /// verification and the recovery re-walk depend on it.
+    /// verification and the recovery re-walk depend on it. Also stops the
+    /// boot walk: a held device produces no boot signal, so polling it
+    /// could only yield a stale `Timeout`.
     pub fn assert_reset(&mut self, id: ComponentId) -> Result<(), DriverError> {
         self.boot_control(id)?
             .hold_in_reset()
-            .map_err(|_| DriverError::BootControlFault)
+            .map_err(|_| DriverError::BootControlFault)?;
+        self.watching[id.get() as usize] = false;
+        Ok(())
     }
+
+    /// Polls every watched walk at `now_millis` and returns the first
+    /// terminal verdict as its event: [`WalkVerdict::Complete`] becomes
+    /// `ComponentReady(id)` (`Active`) or `Booted(id)` (`Passive`),
+    /// [`WalkVerdict::Failed`] becomes `Timeout(id)` regardless of cause —
+    /// retry budgeting is the SM's. The finished walk stops being watched;
+    /// each verdict is delivered once.
+    ///
+    /// Returns at the first event; drain by calling until
+    /// [`BootWalkPoll::event`] is `None`. Only that last poll carries a
+    /// complete [`next_deadline_millis`](BootWalkPoll::next_deadline_millis)
+    /// — the earliest deadline among the still-waiting walks.
+    pub fn poll_boot_walks(&mut self, now_millis: u64) -> BootWalkPoll {
+        let mut next_deadline_millis: Option<u64> = None;
+        for idx in 0..N {
+            if !self.watching[idx] {
+                continue;
+            }
+            let id = ComponentId::new(idx as u8);
+            match self.board.boot_watches[idx].poll(now_millis) {
+                WalkVerdict::Waiting { deadline_millis } => {
+                    next_deadline_millis = Some(match next_deadline_millis {
+                        Some(d) => d.min(deadline_millis),
+                        None => deadline_millis,
+                    });
+                }
+                WalkVerdict::Complete => {
+                    self.watching[idx] = false;
+                    let event = match self.board.component_kinds[idx] {
+                        ComponentKind::Active => Event::ComponentReady(id),
+                        ComponentKind::Passive => Event::Booted(id),
+                    };
+                    return BootWalkPoll {
+                        event: Some(event),
+                        next_deadline_millis,
+                    };
+                }
+                WalkVerdict::Failed { .. } => {
+                    self.watching[idx] = false;
+                    return BootWalkPoll {
+                        event: Some(Event::Timeout(id)),
+                        next_deadline_millis,
+                    };
+                }
+            }
+        }
+        BootWalkPoll {
+            event: None,
+            next_deadline_millis,
+        }
+    }
+}
+
+/// One [`PlatformDriver::poll_boot_walks`] round.
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub struct BootWalkPoll {
+    /// The first terminal verdict's event; `None` when every watched walk
+    /// is still waiting.
+    pub event: Option<Event>,
+    /// Earliest deadline among walks seen waiting this round. Complete only
+    /// when [`event`](Self::event) is `None`: an early return skips the
+    /// walks after the finished one.
+    pub next_deadline_millis: Option<u64>,
 }
 
 impl<B: BoardCapabilities, const N: usize> Platform for PlatformDriver<B, N> {
diff --git a/services/orchestrator/driver/src/lib.rs b/services/orchestrator/driver/src/lib.rs
index 0d2456c..d251b46 100644
--- a/services/orchestrator/driver/src/lib.rs
+++ b/services/orchestrator/driver/src/lib.rs
@@ -15,10 +15,15 @@
 //! driver-side event queue.
 //!
 //! Everything device-specific arrives through the seams in [`board`]:
-//! image access ([`ImageSource`]), image judgment ([`Verifier`]) and reset
-//! actuation ([`orchestrator_capabilities::BootControl`]), bundled in one
+//! image access ([`ImageSource`]), image judgment ([`Verifier`]), reset
+//! actuation ([`orchestrator_capabilities::BootControl`]) and boot
+//! supervision ([`orchestrator_capabilities::BootWatch`]), bundled in one
 //! [`Board`] built by the board's composition crate.
 //!
+//! Boot-walk verdicts are the one asynchronous read: the run loop calls
+//! [`PlatformDriver::poll_boot_walks`] and dispatches the returned events
+//! (`ComponentReady`/`Booted`/`Timeout`) into the SM.
+//!
 //! [`Platform`]: openprot_orchestrator_sm::Platform
 
 #![no_std]
@@ -30,4 +35,4 @@
 mod tests;
 
 pub use board::{Board, BoardCapabilities, ImageSource, Verdict, Verifier};
-pub use driver::{DriverError, PlatformDriver};
+pub use driver::{BootWalkPoll, DriverError, PlatformDriver};
diff --git a/services/orchestrator/driver/src/tests.rs b/services/orchestrator/driver/src/tests.rs
index 5790489..f98851a 100644
--- a/services/orchestrator/driver/src/tests.rs
+++ b/services/orchestrator/driver/src/tests.rs
@@ -5,8 +5,9 @@
 
 use crate::*;
 use openprot_orchestrator_sm::{
-    ComponentAttrs, ComponentId, Event, Orchestrator, PowerOnResult, State,
+    ComponentAttrs, ComponentId, ComponentKind, Event, Orchestrator, PowerOnResult, State,
 };
+use orchestrator_capabilities::{BootWatch, FailureCause, WalkVerdict};
 
 const C0: ComponentId = ComponentId::new(0);
 
@@ -175,6 +176,54 @@
     }
 }
 
+/// Boot walk without a device; scripted verdicts. An exhausted script
+/// holds its last verdict; an empty script waits forever. `arm` rewinds
+/// to the script start, so a fresh attempt is observable from the
+/// verdicts alone — no poll or arm counters needed.
+struct MockWalk {
+    verdicts: std::vec::Vec<WalkVerdict>,
+    next: usize,
+}
+
+const IDLE_DEADLINE: u64 = 60_000;
+
+impl MockWalk {
+    fn scripted(verdicts: std::vec::Vec<WalkVerdict>) -> Self {
+        Self { verdicts, next: 0 }
+    }
+
+    /// A walk that reports "still waiting" forever.
+    fn idle() -> Self {
+        Self::scripted(std::vec::Vec::new())
+    }
+}
+
+impl BootWatch for MockWalk {
+    fn arm(&mut self) {
+        self.next = 0;
+    }
+
+    fn poll(&mut self, _now_millis: u64) -> WalkVerdict {
+        match self.verdicts.get(self.next) {
+            Some(v) => {
+                self.next += 1;
+                *v
+            }
+            // Exhausted: repeat the last verdict, like a real finished
+            // walk. A driver bug that re-polls one then shows up as a
+            // duplicate event in the exactly-once assertions instead of
+            // panicking here.
+            None => self
+                .verdicts
+                .last()
+                .copied()
+                .unwrap_or(WalkVerdict::Waiting {
+                    deadline_millis: IDLE_DEADLINE,
+                }),
+        }
+    }
+}
+
 /// The test board's type choices.
 struct MockBoard;
 
@@ -182,6 +231,7 @@
     type Image = MemImage;
     type Verifier = XorVerifier;
     type BootControl = MockReset;
+    type BootWatch = MockWalk;
 }
 
 fn driver(images: [MemImage; 1]) -> PlatformDriver<MockBoard, 1> {
@@ -189,6 +239,8 @@
         images,
         verifier: XorVerifier { fault: false },
         boot_controls: [MockReset::new()],
+        boot_watches: [MockWalk::idle()],
+        component_kinds: [ComponentKind::Passive],
     })
 }
 
@@ -270,6 +322,8 @@
         images: [MemImage::holding(valid_image())],
         verifier: XorVerifier { fault: true },
         boot_controls: [MockReset::new()],
+        boot_watches: [MockWalk::idle()],
+        component_kinds: [ComponentKind::Passive],
     });
 
     orch.dispatch(&mut driver, Event::PowerGood(PowerOnResult::Provisioned));
@@ -288,6 +342,8 @@
         ],
         verifier: XorVerifier { fault: false },
         boot_controls: [MockReset::new(), MockReset::new()],
+        boot_watches: [MockWalk::idle(), MockWalk::idle()],
+        component_kinds: [ComponentKind::Passive, ComponentKind::Passive],
     });
 
     driver.stage_firmware(C0).unwrap();
@@ -324,6 +380,8 @@
         images: [MemImage::holding(valid_image())],
         verifier: XorVerifier { fault: false },
         boot_controls: [control],
+        boot_watches: [MockWalk::idle()],
+        component_kinds: [ComponentKind::Passive],
     });
 
     driver.release_reset(C0).unwrap();
@@ -355,6 +413,8 @@
         images: [MemImage::holding(valid_image())],
         verifier: XorVerifier { fault: false },
         boot_controls: [control],
+        boot_watches: [MockWalk::idle()],
+        component_kinds: [ComponentKind::Passive],
     });
 
     assert_eq!(driver.release_reset(C0), Err(DriverError::BootControlFault));
@@ -416,6 +476,7 @@
     type Image = MemImage;
     type Verifier = LineWatchingVerifier;
     type BootControl = MockReset;
+    type BootWatch = MockWalk;
 }
 
 // The at-rest guarantee end to end: the component is still held while its
@@ -433,6 +494,8 @@
             held_during_verify: held_during_verify.clone(),
         },
         boot_controls: [control],
+        boot_watches: [MockWalk::idle()],
+        component_kinds: [ComponentKind::Passive],
     });
     let mut orch = orchestrator();
 
@@ -457,6 +520,8 @@
         images: [MemImage::holding(valid_image())],
         verifier: XorVerifier { fault: false },
         boot_controls: [control],
+        boot_watches: [MockWalk::idle()],
+        component_kinds: [ComponentKind::Passive],
     });
     let mut orch = orchestrator();
 
@@ -465,3 +530,245 @@
     assert_eq!(orch.state(), State::Locked);
     assert!(held.get(), "never left reset");
 }
+
+// ---------------------------------------------------------------------------
+// Boot-walk supervision.
+// ---------------------------------------------------------------------------
+
+/// A 2-component driver with per-component scripted walks and kinds;
+/// everything else is the happy-path mock.
+fn walk_driver(
+    walks: [MockWalk; 2],
+    component_kinds: [ComponentKind; 2],
+) -> PlatformDriver<MockBoard, 2> {
+    PlatformDriver::new(Board {
+        images: [
+            MemImage::holding(valid_image()),
+            MemImage::holding(valid_image()),
+        ],
+        verifier: XorVerifier { fault: false },
+        boot_controls: [MockReset::new(), MockReset::new()],
+        boot_watches: walks,
+        component_kinds,
+    })
+}
+
+// A completed walk becomes ComponentReady for Active, Booted for Passive.
+// One event per call, drained in index order; a finished walk never
+// reports twice.
+#[test]
+fn completed_walks_report_by_kind() {
+    let mut driver = walk_driver(
+        [
+            MockWalk::scripted(std::vec![WalkVerdict::Complete]),
+            MockWalk::scripted(std::vec![WalkVerdict::Complete]),
+        ],
+        [ComponentKind::Active, ComponentKind::Passive],
+    );
+    driver.release_reset(C0).unwrap();
+    driver.release_reset(C1).unwrap();
+
+    assert_eq!(
+        driver.poll_boot_walks(0).event,
+        Some(Event::ComponentReady(C0))
+    );
+    assert_eq!(driver.poll_boot_walks(0).event, Some(Event::Booted(C1)));
+
+    let quiet = driver.poll_boot_walks(0);
+    assert_eq!(quiet.event, None, "verdicts are delivered exactly once");
+    assert_eq!(quiet.next_deadline_millis, None, "no walk left waiting");
+}
+
+// A failed walk becomes Timeout(id) regardless of cause — the retry
+// decision is the SM's.
+// TODO: the SM only knows Timeout, so DeviceFatal still spends retry
+// budget. Add a fatal, unrecoverable-error event to the SM in a later PR.
+#[test]
+fn failed_walks_map_to_timeout() {
+    let mut driver = walk_driver(
+        [
+            MockWalk::scripted(std::vec![WalkVerdict::Failed {
+                checkpoint: "heartbeat",
+                cause: FailureCause::TimedOut,
+            }]),
+            MockWalk::scripted(std::vec![WalkVerdict::Failed {
+                checkpoint: "self-test",
+                cause: FailureCause::DeviceFatal,
+            }]),
+        ],
+        [ComponentKind::Active, ComponentKind::Passive],
+    );
+    driver.release_reset(C0).unwrap();
+    driver.release_reset(C1).unwrap();
+
+    assert_eq!(driver.poll_boot_walks(0).event, Some(Event::Timeout(C0)));
+    assert_eq!(driver.poll_boot_walks(0).event, Some(Event::Timeout(C1)));
+    assert_eq!(driver.poll_boot_walks(0).event, None);
+}
+
+// An event-carrying poll returns before visiting later walks, so its
+// deadline is partial and must not be trusted; the drain's final,
+// event-free poll visits every remaining walk and reports the earliest
+// deadline.
+#[test]
+fn deadline_is_authoritative_only_when_no_event() {
+    let mut driver = walk_driver(
+        [
+            MockWalk::scripted(std::vec![WalkVerdict::Complete]),
+            MockWalk::scripted(std::vec![WalkVerdict::Waiting {
+                deadline_millis: 1_000,
+            }]),
+        ],
+        [ComponentKind::Passive, ComponentKind::Passive],
+    );
+    driver.release_reset(C0).unwrap();
+    driver.release_reset(C1).unwrap();
+
+    let first = driver.poll_boot_walks(0);
+    assert_eq!(first.event, Some(Event::Booted(C0)));
+    assert_eq!(
+        first.next_deadline_millis, None,
+        "returned before the waiting walk was visited"
+    );
+
+    let last = driver.poll_boot_walks(0);
+    assert_eq!(last.event, None);
+    assert_eq!(last.next_deadline_millis, Some(1_000));
+}
+
+// While every watched walk waits, the poll carries the earliest deadline
+// as the run loop's next wake-up.
+#[test]
+fn waiting_walks_report_the_earliest_deadline() {
+    let mut driver = walk_driver(
+        [
+            MockWalk::scripted(std::vec![WalkVerdict::Waiting {
+                deadline_millis: 9_000,
+            }]),
+            MockWalk::scripted(std::vec![WalkVerdict::Waiting {
+                deadline_millis: 4_000,
+            }]),
+        ],
+        [ComponentKind::Passive, ComponentKind::Passive],
+    );
+    driver.release_reset(C0).unwrap();
+    driver.release_reset(C1).unwrap();
+
+    let poll = driver.poll_boot_walks(0);
+    assert_eq!(poll.event, None);
+    assert_eq!(poll.next_deadline_millis, Some(4_000));
+}
+
+// A walk is watched only between release and terminal verdict. The script's
+// terminal verdict would surface as an event if the gate were missing: no
+// event before release, no stale event after assert_reset, and the verdict
+// still arrives once the device is actually released.
+#[test]
+fn only_released_components_are_watched() {
+    let mut driver = walk_driver(
+        [
+            MockWalk::scripted(std::vec![WalkVerdict::Complete]),
+            MockWalk::idle(),
+        ],
+        [ComponentKind::Passive, ComponentKind::Passive],
+    );
+
+    assert_eq!(
+        driver.poll_boot_walks(0).event,
+        None,
+        "unreleased: no event"
+    );
+
+    driver.release_reset(C0).unwrap();
+    driver.assert_reset(C0).unwrap();
+    assert_eq!(
+        driver.poll_boot_walks(0).event,
+        None,
+        "back in reset: no stale event"
+    );
+
+    driver.release_reset(C0).unwrap();
+    assert_eq!(driver.poll_boot_walks(0).event, Some(Event::Booted(C0)));
+}
+
+// Every release re-arms the walk: a retry judges a new attempt from the
+// first checkpoint, not the failed one resumed. With a script of
+// [Failed, Complete], a resumed walk would report Complete on the second
+// attempt; a fresh one reports Failed again.
+#[test]
+fn rerelease_arms_a_fresh_walk() {
+    let mut driver = walk_driver(
+        [
+            MockWalk::scripted(std::vec![
+                WalkVerdict::Failed {
+                    checkpoint: "heartbeat",
+                    cause: FailureCause::TimedOut,
+                },
+                WalkVerdict::Complete,
+            ]),
+            MockWalk::idle(),
+        ],
+        [ComponentKind::Passive, ComponentKind::Passive],
+    );
+
+    driver.release_reset(C0).unwrap();
+    assert_eq!(driver.poll_boot_walks(0).event, Some(Event::Timeout(C0)));
+
+    driver.release_reset(C0).unwrap();
+    assert_eq!(
+        driver.poll_boot_walks(0).event,
+        Some(Event::Timeout(C0)),
+        "fresh attempt from the first checkpoint, not the old walk resumed"
+    );
+}
+
+// End to end: a passive component is released speculatively (Ready), its
+// walk completes, and the Booted event settles cleanly.
+#[test]
+fn booted_walk_settles_in_ready() {
+    let mut orch = orchestrator();
+    let mut driver = PlatformDriver::<MockBoard, 1>::new(Board {
+        images: [MemImage::holding(valid_image())],
+        verifier: XorVerifier { fault: false },
+        boot_controls: [MockReset::new()],
+        boot_watches: [MockWalk::scripted(std::vec![WalkVerdict::Complete])],
+        component_kinds: [ComponentKind::Passive],
+    });
+
+    orch.dispatch(&mut driver, Event::PowerGood(PowerOnResult::Provisioned));
+    assert_eq!(orch.state(), State::Ready);
+
+    let event = driver.poll_boot_walks(0).event.expect("walk completed");
+    assert_eq!(event, Event::Booted(C0));
+    orch.dispatch(&mut driver, event);
+
+    assert_eq!(orch.state(), State::Ready);
+}
+
+// End to end, failure path: the released component never reports in, its
+// Timeout enters recovery, and with no recovery capability composed yet
+// the machine fails closed. The Recovery PR replaces this test with the
+// recovery-path one — its failure there is the reminder.
+#[test]
+fn boot_timeout_fails_closed_without_recovery() {
+    let mut orch = orchestrator();
+    let mut driver = PlatformDriver::<MockBoard, 1>::new(Board {
+        images: [MemImage::holding(valid_image())],
+        verifier: XorVerifier { fault: false },
+        boot_controls: [MockReset::new()],
+        boot_watches: [MockWalk::scripted(std::vec![WalkVerdict::Failed {
+            checkpoint: "heartbeat",
+            cause: FailureCause::TimedOut,
+        }])],
+        component_kinds: [ComponentKind::Passive],
+    });
+
+    orch.dispatch(&mut driver, Event::PowerGood(PowerOnResult::Provisioned));
+    assert_eq!(orch.state(), State::Ready);
+
+    let event = driver.poll_boot_walks(0).event.expect("walk failed");
+    assert_eq!(event, Event::Timeout(C0));
+    orch.dispatch(&mut driver, event);
+
+    assert_eq!(orch.state(), State::Locked);
+}