orchestrator: Compose reset actuation from the board's BootControl capability

release_reset and assert_reset now delegate to a per-component
orchestrator_capabilities::BootControl supplied by the board, instead of
returning NotImplemented. BoardCapabilities gains a BootControl associated
type and Board a boot_controls array, following the composition pattern
already used for ImageSource and Verifier.

Signed-off-by: Christina Quast <christina.quast@9elements.com>
diff --git a/services/orchestrator/driver/BUILD.bazel b/services/orchestrator/driver/BUILD.bazel
index b9ee0aa..0d3485a 100644
--- a/services/orchestrator/driver/BUILD.bazel
+++ b/services/orchestrator/driver/BUILD.bazel
@@ -15,6 +15,7 @@
     edition = "2024",
     visibility = ["//visibility:public"],
     deps = [
+        "//services/orchestrator/capabilities:orchestrator_capabilities",
         "//services/orchestrator/sm:orchestrator_sm",
         "@rust_crates//:heapless",
     ],
diff --git a/services/orchestrator/driver/src/board.rs b/services/orchestrator/driver/src/board.rs
index 5c16706..81e06cc 100644
--- a/services/orchestrator/driver/src/board.rs
+++ b/services/orchestrator/driver/src/board.rs
@@ -5,6 +5,7 @@
 //! Boards (or test mocks) implement these.
 
 use openprot_orchestrator_sm::ComponentId;
+use orchestrator_capabilities::BootControl;
 
 /// Access to one component's active firmware image, however it is reached —
 /// interposed flash, a PLDM/MCTP transfer, a RAM copy in tests.
@@ -93,8 +94,9 @@
     type Image: ImageSource;
     /// Judges images for every component.
     type Verifier: Verifier;
-    // Later seams: Reset (release/assert_reset), Evidence (checkpoint
-    // walk), Recovery, Staging.
+    /// Reset actuation for the managed components.
+    type BootControl: BootControl;
+    // Later seams: Evidence (checkpoint walk), Recovery, Staging.
 }
 
 /// Everything the board supplies, built once at bring-up and handed to
@@ -104,12 +106,14 @@
 /// ```ignore
 /// struct Ast1060Board;
 /// impl BoardCapabilities for Ast1060Board {
-///     type Image = SpiFlashImage;       // interposed flash, offsets from the slot layout
-///     type Verifier = ManifestVerifier; // signature + SVN via the crypto engine
+///     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
 /// }
 /// let board = Board::<Ast1060Board, 2> {
 ///     images: [bmc_image, cpld_image],
 ///     verifier,
+///     boot_controls: [bmc_reset, cpld_reset],
 /// };
 /// ```
 pub struct Board<B: BoardCapabilities, const N: usize> {
@@ -118,5 +122,8 @@
     pub images: [B::Image; N],
     /// Judges images for every component.
     pub verifier: B::Verifier,
-    // Later seams add fields, e.g. resets: [B::Reset; N].
+    /// `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].
 }
diff --git a/services/orchestrator/driver/src/driver.rs b/services/orchestrator/driver/src/driver.rs
index d9bbdde..559f58d 100644
--- a/services/orchestrator/driver/src/driver.rs
+++ b/services/orchestrator/driver/src/driver.rs
@@ -7,6 +7,7 @@
 use openprot_orchestrator_sm::{ComponentId, Effect, EffectError, Event, Platform};
 
 use crate::board::{Board, BoardCapabilities, ImageSource, Verdict, Verifier};
+use orchestrator_capabilities::BootControl;
 
 /// Queue bound. Executors produce at most one event per effect, the event loop
 /// drains it after every dispatch, and the largest SM effect batch today is
@@ -29,6 +30,8 @@
     /// The verifier could not perform the check (a failed image is a
     /// [`Verdict`], not an error).
     VerifierFault,
+    /// The component's boot control could not actuate the reset line.
+    BootControlFault,
     /// The event queue overflowed; dropping events breaks the SM's
     /// honest-feedback contract.
     QueueFull,
@@ -42,6 +45,7 @@
             DriverError::ImageUnavailable => "image source could not be opened",
             DriverError::NotStaged => "no image staged for this component",
             DriverError::VerifierFault => "verifier could not perform the check",
+            DriverError::BootControlFault => "boot control could not actuate the reset",
             DriverError::QueueFull => "event queue full",
         })
     }
@@ -118,17 +122,29 @@
         })
     }
 
-    /// Release `id` from reset, then walk its boot checkpoints; feed back
-    /// one `Event::ComponentReady(id)` (Active) or `Event::Booted(id)`
-    /// (Passive), or `Event::Timeout(id)` on window expiry.
-    pub fn release_reset(&mut self, _id: ComponentId) -> Result<(), DriverError> {
-        Err(DriverError::NotImplemented)
+    /// `id`'s reset actuator.
+    fn boot_control(&mut self, id: ComponentId) -> Result<&mut B::BootControl, DriverError> {
+        self.board
+            .boot_controls
+            .get_mut(id.get() as usize)
+            .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.
+    pub fn release_reset(&mut self, id: ComponentId) -> Result<(), DriverError> {
+        self.boot_control(id)?
+            .release()
+            .map_err(|_| DriverError::BootControlFault)
     }
 
     /// Hold `id` in reset — a durable quiesce, not a pulse; at-rest
     /// verification and the recovery re-walk depend on it.
-    pub fn assert_reset(&mut self, _id: ComponentId) -> Result<(), DriverError> {
-        Err(DriverError::NotImplemented)
+    pub fn assert_reset(&mut self, id: ComponentId) -> Result<(), DriverError> {
+        self.boot_control(id)?
+            .hold_in_reset()
+            .map_err(|_| DriverError::BootControlFault)
     }
 
     /// Restore `id` from its configured recovery source (the mechanism is
diff --git a/services/orchestrator/driver/src/lib.rs b/services/orchestrator/driver/src/lib.rs
index ff93b89..9b3a3c8 100644
--- a/services/orchestrator/driver/src/lib.rs
+++ b/services/orchestrator/driver/src/lib.rs
@@ -14,8 +14,9 @@
 //! via [`PlatformDriver::take_event`] and dispatches each.
 //!
 //! Everything device-specific arrives through the seams in [`board`]:
-//! image access ([`ImageSource`]) and image judgment ([`Verifier`]),
-//! bundled in one [`Board`] built by the board's composition crate.
+//! image access ([`ImageSource`]), image judgment ([`Verifier`]) and reset
+//! actuation ([`orchestrator_capabilities::BootControl`]), bundled in one
+//! [`Board`] built by the board's composition crate.
 //!
 //! [`Platform`]: openprot_orchestrator_sm::Platform
 
diff --git a/services/orchestrator/driver/src/tests.rs b/services/orchestrator/driver/src/tests.rs
index cb9c0f3..07ee2e2 100644
--- a/services/orchestrator/driver/src/tests.rs
+++ b/services/orchestrator/driver/src/tests.rs
@@ -128,18 +128,67 @@
     }
 }
 
+#[derive(Debug)]
+struct ResetFault;
+
+impl core::fmt::Display for ResetFault {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        f.write_str("reset line fault")
+    }
+}
+
+impl core::error::Error for ResetFault {}
+
+/// Reset actuation without a HAL; `held` is shared so tests can observe the
+/// line after the control moves into the driver.
+struct MockReset {
+    held: std::rc::Rc<core::cell::Cell<bool>>,
+    fail: bool,
+}
+
+impl MockReset {
+    fn new() -> Self {
+        Self {
+            held: std::rc::Rc::new(core::cell::Cell::new(true)),
+            fail: false,
+        }
+    }
+}
+
+impl orchestrator_capabilities::BootControl for MockReset {
+    type Error = ResetFault;
+
+    fn hold_in_reset(&mut self) -> Result<(), ResetFault> {
+        if self.fail {
+            return Err(ResetFault);
+        }
+        self.held.set(true);
+        Ok(())
+    }
+
+    fn release(&mut self) -> Result<(), ResetFault> {
+        if self.fail {
+            return Err(ResetFault);
+        }
+        self.held.set(false);
+        Ok(())
+    }
+}
+
 /// The test board's type choices.
 struct MockBoard;
 
 impl BoardCapabilities for MockBoard {
     type Image = MemImage;
     type Verifier = XorVerifier;
+    type BootControl = MockReset;
 }
 
 fn driver(images: [MemImage; 1]) -> PlatformDriver<MockBoard, 1> {
     PlatformDriver::new(Board {
         images,
         verifier: XorVerifier { fault: false },
+        boot_controls: [MockReset::new()],
     })
 }
 
@@ -222,6 +271,7 @@
     let mut driver = PlatformDriver::<MockBoard, 1>::new(Board {
         images: [MemImage::holding(valid_image())],
         verifier: XorVerifier { fault: true },
+        boot_controls: [MockReset::new()],
     });
 
     orch.dispatch(&mut driver, Event::PowerGood(PowerOnResult::Provisioned));
@@ -240,6 +290,7 @@
             MemImage::holding(valid_image()),
         ],
         verifier: XorVerifier { fault: false },
+        boot_controls: [MockReset::new(), MockReset::new()],
     });
 
     driver.stage_firmware(C0).unwrap();
@@ -268,6 +319,51 @@
     );
 }
 
+#[test]
+fn reset_release_and_assert_reach_the_boot_control() {
+    let control = MockReset::new();
+    let held = control.held.clone();
+    let mut driver = PlatformDriver::<MockBoard, 1>::new(Board {
+        images: [MemImage::holding(valid_image())],
+        verifier: XorVerifier { fault: false },
+        boot_controls: [control],
+    });
+
+    driver.release_reset(C0).unwrap();
+    assert!(!held.get());
+
+    driver.assert_reset(C0).unwrap();
+    assert!(held.get());
+}
+
+#[test]
+fn reset_of_unknown_component_is_refused() {
+    let mut driver = driver([MemImage::holding(valid_image())]);
+
+    assert_eq!(
+        driver.release_reset(ComponentId::new(9)),
+        Err(DriverError::UnknownComponent)
+    );
+    assert_eq!(
+        driver.assert_reset(ComponentId::new(9)),
+        Err(DriverError::UnknownComponent)
+    );
+}
+
+#[test]
+fn reset_line_fault_is_reported() {
+    let mut control = MockReset::new();
+    control.fail = true;
+    let mut driver = PlatformDriver::<MockBoard, 1>::new(Board {
+        images: [MemImage::holding(valid_image())],
+        verifier: XorVerifier { fault: false },
+        boot_controls: [control],
+    });
+
+    assert_eq!(driver.release_reset(C0), Err(DriverError::BootControlFault));
+    assert_eq!(driver.assert_reset(C0), Err(DriverError::BootControlFault));
+}
+
 // Undrained verdicts eventually fill the queue; the overflow is reported,
 // not silently dropped.
 #[test]