From f5d5b1a1706ec690299127dc9d1242dda51c04e2 Mon Sep 17 00:00:00 2001 From: Christina Quast Date: Mon, 21 Sep 2026 17:30:35 +0200 Subject: [PATCH] orchestrator: Rename signal/G to probe/P in boot checkpoints The generic was called G ("signal") when it only described GPIO-ready lines. Real boards also poll progress registers, MCTP readiness and heartbeat latches, so the narrower name was misleading. P ("probe") covers every kind of health indicator the evidence reader can sample. Renames: BootCheckpoint to

, signal field/accessor to probe, EvidenceReader to

, fn read(signal:) to fn read(probe:), MockSignal to MockProbe, and all doc references. Adds a GPIO wiring example to BootCheckpoint's doc comment. Assisted-by: Claude --- .../orchestrator/capabilities/src/evidence.rs | 55 ++++++------ services/orchestrator/config/src/lib.rs | 86 +++++++++++++------ services/orchestrator/test/devices.rs | 22 ++--- 3 files changed, 96 insertions(+), 67 deletions(-) diff --git a/services/orchestrator/capabilities/src/evidence.rs b/services/orchestrator/capabilities/src/evidence.rs index be3e85e0a..299e38344 100644 --- a/services/orchestrator/capabilities/src/evidence.rs +++ b/services/orchestrator/capabilities/src/evidence.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 //! Evidence reading: the boot-liveness vocabulary and the reader that -//! resolves a board-defined signal id to it. +//! resolves a board-defined probe to it. /// Liveness of a managed device's boot: Boot Confirmation only — whether /// the device came up, never what booted (that is attestation). @@ -30,63 +30,62 @@ pub enum BootStatus { FailedFatal, } -/// Reads a device's boot evidence, one signal at a time. +/// Reads a device's boot evidence, one probe at a time. /// -/// Implemented by board wiring — typically once per managed device, so -/// each device's boot walk borrows only its own reader. `G` is the -/// board's signal vocabulary; an exhaustive `match` on it keeps dispatch -/// direct and makes a forgotten signal a compile error, not a runtime +/// Implemented by board wiring, typically once per managed device, so +/// each device's boot walk borrows only its own reader. `P` is the +/// board's probe vocabulary; an exhaustive `match` on it keeps dispatch +/// direct and makes a forgotten probe a compile error, not a runtime /// hole. /// -/// The status must describe the **current** boot cycle — see +/// The status must describe the *current* boot cycle, see /// [`BootStatus`] for the latching contract (evidence is cleared by the /// reset path, never by the reader). /// /// # Wiring a concrete reader /// /// Concrete readers (e.g. `GpioBootMonitor` in `orchestrator-hal-adapters`) -/// stay signal-agnostic — an adapter crate cannot know a board's `G`. +/// stay probe-agnostic, an adapter crate cannot know a board's `P`. /// The board impl owns the match; the hardware binding is made once, at -/// construction, and the signal id just proves the right reader was -/// wired: +/// construction, and the probe just proves the right reader was wired: /// /// ```ignore -/// /// bmc wiring: one ready line behind the board's signal vocabulary. -/// struct BmcReader<'a, P: GpioPort> { +/// /// bmc wiring: one ready line behind the board's probe vocabulary. +/// struct BmcReader<'a, Port: GpioPort> { /// // (port, pin, polarity) bound at bring-up from the table's Gpio(12). -/// ready: GpioBootMonitor<'a, P>, +/// ready: GpioBootMonitor<'a, Port>, /// } /// -/// impl EvidenceReader for BmcReader<'_, P> +/// impl EvidenceReader for BmcReader<'_, Port> /// where -/// P::Error: 'static, +/// Port::Error: 'static, /// { -/// type Error = MonitorError; +/// type Error = MonitorError; /// -/// fn read(&mut self, signal: &MockSignal) -> Result { -/// match signal { -/// MockSignal::Gpio(_) => self.ready.boot_status(), +/// fn read(&mut self, probe: &MockProbe) -> Result { +/// match probe { +/// MockProbe::Gpio(_) => self.ready.boot_status(), /// other => unreachable!("bmc reader wired to {other:?}"), /// } /// } /// } /// ``` -pub trait EvidenceReader { +pub trait EvidenceReader

{ /// The error type reported by this reader. /// /// Requires [`core::error::Error`] (in `core` since Rust 1.81) so the /// orchestrator gets `Display` and a `source()` cause chain, not just - /// a `Debug` dump. Error categories stay implementation-defined — + /// a `Debug` dump. Error categories stay implementation-defined, /// this crate names no error vocabulary of its own. type Error: core::error::Error; - /// Returns the current liveness evidence for `signal`. + /// Returns the current liveness evidence for `probe`. /// /// # Errors /// - /// Returns an error if the evidence channel behind `signal` cannot be + /// Returns an error if the evidence channel behind `probe` cannot be /// read. - fn read(&mut self, signal: &G) -> Result; + fn read(&mut self, probe: &P) -> Result; } #[cfg(test)] @@ -128,11 +127,11 @@ mod tests { impl EvidenceReader for SocReader { type Error = RegFault; - fn read(&mut self, signal: &TestSignal) -> Result { + fn read(&mut self, probe: &TestSignal) -> Result { if self.fail { return Err(RegFault); } - let TestSignal::Progress(threshold) = *signal; + let TestSignal::Progress(threshold) = *probe; Ok(match self.level { POISON => BootStatus::FailedFatal, TRANSIENT => BootStatus::FailedRetriable, @@ -269,11 +268,11 @@ mod tests { impl EvidenceReader for MockNicEndpoint { type Error = MctpFault; - fn read(&mut self, signal: &NicSignal) -> Result { + fn read(&mut self, probe: &NicSignal) -> Result { if self.bus_fault { return Err(MctpFault); } - match signal { + match probe { NicSignal::MctpReady => { if let Some(code) = self.fault_code { return Ok(match code { diff --git a/services/orchestrator/config/src/lib.rs b/services/orchestrator/config/src/lib.rs index dae78ac7d..7b0a9a07d 100644 --- a/services/orchestrator/config/src/lib.rs +++ b/services/orchestrator/config/src/lib.rs @@ -12,66 +12,96 @@ #![cfg_attr(not(test), no_std)] -/// One boot checkpoint: a signal the orchestrator waits for, and how long +/// One boot checkpoint: a probe the orchestrator evaluates, and how long /// it waits. Retry policy is deliberately not table data: a retry /// re-resets the device and re-runs the whole walk, so budgets are /// per boot attempt and owned by the orchestrator state machine. /// -/// The signal is a board-defined id — the schema attaches no meaning to -/// it and names no signal kinds. Each board defines its own vocabulary (a -/// small enum: a GPIO line, a progress-register threshold, a message-path -/// readiness) and gives it meaning in its `EvidenceReader`. The id is a -/// defunctionalized evidence check: data in the table instead of a -/// function, so the table stays printable, comparable, const-checkable — +/// The probe is board-defined, the schema attaches no meaning to it and +/// names no probe kinds. Each board defines its own vocabulary (a small +/// enum: a GPIO line, a progress-register threshold, a message-path +/// readiness) and gives it meaning in its `EvidenceReader`. The probe is +/// a defunctionalized evidence check: data in the table instead of a +/// function, so the table stays printable, comparable, const-checkable, /// and could one day be generated instead of written. /// /// Fields are private so a checkpoint that violates the schema is /// unrepresentable: [`new`](Self::new) is the only way in, and it checks. +/// +/// # Example: three GPIO checkpoints +/// +/// A BMC behind three GPIO ready lines (bl1 on pin 4, kernel on pin 5, +/// service on pin 6), all on the same SGPIOM bank. Each probe variant +/// maps to one `GpioBootMonitor` in the board's `EvidenceReader`, and +/// the walker (`CheckpointWalk`) walks them in declaration order. +/// +/// ```ignore +/// #[derive(Debug, Clone, Copy)] +/// enum BmcProbe { Bl1, Kernel, Service } +/// +/// const BMC: DeviceConfig = DeviceConfig::new( +/// "bmc", 0, +/// &[ +/// BootCheckpoint::new("bl1", BmcProbe::Bl1, Duration::from_millis(500)), +/// BootCheckpoint::new("kernel", BmcProbe::Kernel, Duration::from_secs(5)), +/// BootCheckpoint::new("service", BmcProbe::Service, Duration::from_secs(30)), +/// ], +/// ); +/// +/// // Pin binding at bring-up: one GpioBootMonitor per probe. +/// let bl1 = GpioBootMonitor::new(&sgpiom, Mask(1 << 4), ActivePolarity::ActiveHigh); +/// let kernel = GpioBootMonitor::new(&sgpiom, Mask(1 << 5), ActivePolarity::ActiveHigh); +/// let service = GpioBootMonitor::new(&sgpiom, Mask(1 << 6), ActivePolarity::ActiveHigh); +/// +/// // The board's EvidenceReader dispatches probe to monitor. +/// // See EvidenceReader's docs for the full impl pattern. +/// let bmc_walk = CheckpointWalk::new(bmc_reader, BMC.checkpoints()); +/// ``` #[derive(Debug, Clone, Copy)] -pub struct BootCheckpoint { +pub struct BootCheckpoint

{ name: &'static str, - signal: G, + probe: P, timeout: core::time::Duration, } -impl BootCheckpoint { +impl

BootCheckpoint

{ /// Declares a checkpoint. `const`, so board tables run the checks at /// build time. /// /// # Panics /// - /// Panics — a build error in const context — if `name` is empty or + /// Panics, a build error in const context, if `name` is empty or /// `timeout` is zero. #[must_use] - pub const fn new(name: &'static str, signal: G, timeout: core::time::Duration) -> Self { + pub const fn new(name: &'static str, probe: P, timeout: core::time::Duration) -> Self { assert!(!name.is_empty(), "checkpoint name must not be empty"); assert!(!timeout.is_zero(), "checkpoint timeout must not be zero"); Self { name, - signal, + probe, timeout, } } - /// Names the checkpoint in failure reports ("bl1", "kernel", …). + /// Names the checkpoint in failure reports ("bl1", "kernel", ...). /// Unique within a device's checkpoint list. #[must_use] pub const fn name(&self) -> &'static str { self.name } - /// Board-defined signal id, resolved by the board's `EvidenceReader` - /// (in `orchestrator-capabilities`). An id rather than a function, so - /// the table stays pure data — the type-level docs say why. + /// Board-defined probe, resolved by the board's `EvidenceReader` + /// (in `orchestrator-capabilities`). Data rather than a function, so + /// the table stays pure data, the type-level docs say why. #[must_use] - pub const fn signal(&self) -> &G { - &self.signal + pub const fn probe(&self) -> &P { + &self.probe } /// Window for one attempt at this checkpoint. Expiry is the boot /// walk's own judgment; hung devices report nothing. /// - /// The orchestrator state machine never sees this value — it is + /// The orchestrator state machine never sees this value, it is /// clockless. The walk consumes the windows and reports expiry as a /// failed attempt; a component's whole boot timeout is nothing more /// than its walk over these windows, in order. @@ -85,8 +115,8 @@ impl BootCheckpoint { /// /// Generic over the board's reset signal type `R` (which must match the /// `ResetId` of the reset controller behind the board's `BootControl` -/// implementation) and its boot-signal vocabulary `G`, for the same -/// reason: signal ids are board-specific. +/// implementation) and its boot-probe vocabulary `P`, for the same +/// reason: probes are board-specific. /// /// Deliberately says nothing about attestation or commit requirements: /// those follow from what kind of device this is (iRoT-backed or @@ -96,13 +126,13 @@ impl BootCheckpoint { /// Fields are private so a device entry that violates the schema is /// unrepresentable: [`new`](Self::new) is the only way in, and it checks. #[derive(Debug, Clone, Copy)] -pub struct DeviceConfig { +pub struct DeviceConfig { name: &'static str, reset_signal: R, - checkpoints: &'static [BootCheckpoint], + checkpoints: &'static [BootCheckpoint

], } -impl DeviceConfig { +impl DeviceConfig { /// Declares a managed device. `const`, so board tables run the checks /// at build time. /// @@ -116,7 +146,7 @@ impl DeviceConfig { pub const fn new( name: &'static str, reset_signal: R, - checkpoints: &'static [BootCheckpoint], + checkpoints: &'static [BootCheckpoint

], ) -> Self { assert!(!name.is_empty(), "device name must not be empty"); assert!( @@ -159,7 +189,7 @@ impl DeviceConfig { /// window expires fails the attempt — whether to retry or recover is /// the orchestrator's decision, not table data. #[must_use] - pub const fn checkpoints(&self) -> &'static [BootCheckpoint] { + pub const fn checkpoints(&self) -> &'static [BootCheckpoint

] { self.checkpoints } } @@ -204,7 +234,7 @@ mod tests { assert_eq!(*device.reset_signal(), 0); assert_eq!(device.checkpoints().len(), 1); assert_eq!(device.checkpoints()[0].name(), "boot-complete"); - assert_eq!(*device.checkpoints()[0].signal(), 0); + assert_eq!(*device.checkpoints()[0].probe(), 0); assert_eq!(device.checkpoints()[0].timeout(), Duration::from_secs(1)); } diff --git a/services/orchestrator/test/devices.rs b/services/orchestrator/test/devices.rs index 98802697e..9415a1e1e 100644 --- a/services/orchestrator/test/devices.rs +++ b/services/orchestrator/test/devices.rs @@ -11,10 +11,10 @@ use core::time::Duration; use orchestrator_config::{BootCheckpoint, DeviceConfig}; -/// The mock board's boot-signal vocabulary. The schema carries these +/// The mock board's boot-probe vocabulary. The schema carries these /// opaquely; only this board's `EvidenceReader` gives them meaning. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum MockSignal { +pub enum MockProbe { /// A boot-complete GPIO line, by index. Gpio(u8), /// The device's MCTP endpoint answers as ready. @@ -30,7 +30,7 @@ pub enum MockSignal { /// /// The mock board's reset controller addresses reset lines by plain index, /// so the reset id type is `u8`. -pub const MANAGED_DEVICES: &[DeviceConfig] = &[ +pub const MANAGED_DEVICES: &[DeviceConfig] = &[ // Direct-flash SPI device (BMC archetype): the eRoT fronts its flash. // Single checkpoint: it raises a boot-complete GPIO. DeviceConfig::new( @@ -38,7 +38,7 @@ pub const MANAGED_DEVICES: &[DeviceConfig] = &[ 7, &[BootCheckpoint::new( "boot-complete", - MockSignal::Gpio(12), + MockProbe::Gpio(12), Duration::from_secs(90), )], ), @@ -49,24 +49,24 @@ pub const MANAGED_DEVICES: &[DeviceConfig] = &[ "nic", 3, &[ - BootCheckpoint::new("mctp-ready", MockSignal::MctpReady, Duration::from_secs(20)), - BootCheckpoint::new("heartbeat", MockSignal::Heartbeat, Duration::from_secs(10)), + BootCheckpoint::new("mctp-ready", MockProbe::MctpReady, Duration::from_secs(20)), + BootCheckpoint::new("heartbeat", MockProbe::Heartbeat, Duration::from_secs(10)), ], ), ]; /// Board-local checks the schema constructors cannot do — they know the /// schema's shape, not this board's meanings. Const-fence pattern: a bad -/// signal fails the build. -const fn validate_signals(devices: &[DeviceConfig]) { +/// probe fails the build. +const fn validate_probes(devices: &[DeviceConfig]) { let mut i = 0; while i < devices.len() { let checkpoints = devices[i].checkpoints(); let mut c = 0; while c < checkpoints.len() { - if let MockSignal::Gpio(line) = *checkpoints[c].signal() { + if let MockProbe::Gpio(line) = *checkpoints[c].probe() { // The mock ready-line bank packs 32 lines, SGPIO-style. - assert!(line < 32, "gpio signal names a line outside the bank"); + assert!(line < 32, "gpio probe names a line outside the bank"); } c += 1; } @@ -74,4 +74,4 @@ const fn validate_signals(devices: &[DeviceConfig]) { } } -const _: () = validate_signals(MANAGED_DEVICES); +const _: () = validate_probes(MANAGED_DEVICES);