Skip to content

fwmanager: table-declared boot checkpoints replace BootMonitor - #397

Open
chrysh wants to merge 8 commits into
OpenPRoT:mainfrom
9elements:add-boot-walk
Open

fwmanager: table-declared boot checkpoints replace BootMonitor#397
chrysh wants to merge 8 commits into
OpenPRoT:mainfrom
9elements:add-boot-walk

Conversation

@chrysh

@chrysh chrysh commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Traits and schema only — the walker implementing them follows in a separate PR, so this one stays reviewable on the contract alone.

What

  • BootMonitor is removed. A BootCheckpoint names its evidence as a board-defined signal id (signal: G, timeout, retry budget) — the schema names no signal kinds, and everything that is config is declared in the board device table (target/mock/devices.rs); config.rs only defines what that configuration has to look like.
  • EvidenceReader<G> resolves a signal id to BootStatus — board wiring, typically one per managed device, exhaustive match, direct dispatch.
  • BootStatus grows device-reported verdicts: FailedRetriable (consumes retry budget immediately instead of waiting out the window) and FailedFatal (ends the boot regardless of budget). Timeouts stay the observer's own judgment — hung devices report nothing; the reader tests pin this down with an MCTP-shaped mock where silence reads Booting forever.
  • BootWatch/WalkVerdict is the erased seam the orchestrator polls: Waiting/Complete/Retry/Dead per device, free of device and error types.
  • GpioBootMonitor keeps its behavior as a plain reader (inherent method, no trait).

Reading order

The first commit is the checkpoint-embedded-fn variant; the second defunctionalizes it into signal ids and records why (table stays pure data, borrows stay per-device, dispatch stays direct). The pair is kept deliberately as the design record — review the combined diff if you only want the endpoint. Commits three and four add the failure verdicts and the message-path reader tests.

Supersedes

Test

bazel test //services/fwmanager/... (13 api tests), bazel build //target/mock/...; device-table validate runs in const context, so a bad table is a build error.

Migration (for integrators)

BootMonitor is replaced by EvidenceReader<G> + BootStatus; the checkpoint schema changed shape:

// before: one monitor per device, checkpoint carries a schema-level signal kind
BootCheckpoint { name: "boot-complete", signal: BootSignal::GpioBootComplete(12),
                 window: Duration::from_secs(90) }
impl BootMonitor for MyMonitor { fn boot_status(&self) -> Result<BootStatus, E> }

// after: signal id is the board's own enum; window -> timeout; new max_retries
BootCheckpoint { name: "boot-complete", signal: MySignal::Gpio(12),
                 timeout: Duration::from_secs(90), max_retries: 1 }
impl EvidenceReader<MySignal> for MyBmcReader {
    fn read(&mut self, signal: &MySignal) -> Result<BootStatus, E>
}
  • max_retries: 0 = the single attempt is all the device gets.
  • BootStatus::Failed split into FailedRetriable / FailedFatal (device-reported verdicts; timeouts remain the observer's judgment).
  • Checkpoint names must be unique per device (build-time validate).
  • WalkVerdict::Retry carries deadline_millis of the re-armed window — schedule against it exactly like Waiting.

chrysh added 8 commits August 5, 2026 22:16
A BootCheckpoint is timing policy plus its own evidence check: a
capture-less fn handed the board's device context, so the channel
underneath never leaks past the check and an unobservable checkpoint
is unrepresentable. config.rs defines the schema (BootSignal is gone);
the board table declares the checkpoints against its own context and
error types. BootStatus stays as the shared vocabulary and absorbs
the latch-cleared-by-reset contract; GpioBootMonitor keeps its
behavior as a plain reader. BootWatch/WalkVerdict is the erased seam
the orchestrator polls — timeout and retry-budget judgment lands with
the walker that implements it.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Christina Quast <christina.quast@9elements.com>
The embedded fn was the more general shape, but the generality went
unused while its costs did not: the table stopped being pure data
(unprintable, unvalidatable on mechanisms, never generatable), every
check shared one &mut board context, and dispatch went indirect. A
signal id is the same check defunctionalized: data in the table, an
exhaustive match in the board's EvidenceReader — typically one per
device, so each walk borrows only its own reader. Boot-evidence
mechanisms per board are a closed set; when one can't be named, that
is a new variant in that board's enum, not an API change.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Christina Quast <christina.quast@9elements.com>
A device that knows it failed should end the wait early, and one that
knows a retry is pointless should say so, instead of the orchestrator
burning its window and budget to find out. BootStatus::Failed splits
into FailedRetriable (consumes budget immediately) and FailedFatal
(ends the boot regardless of budget). Timeouts stay the orchestrator's
own judgment — hung devices report nothing — and channel trouble stays
in the reader's Error, distinct from a device-reported verdict.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Christina Quast <christina.quast@9elements.com>
A timeout is never on the wire: a hung endpoint reads Booting forever,
and only the orchestrator's clock turns silence into a verdict. The
message path carries the active verdicts (device failure codes) and
channel trouble, each on its own channel.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Christina Quast <christina.quast@9elements.com>
Retry re-arms the window, but the caller had no way to know until
when — it would have had to reach into the checkpoint's timeout and
do the walker's arithmetic itself. Retry now carries deadline_millis
exactly like Waiting: one scheduling rule for both verdicts.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Christina Quast <christina.quast@9elements.com>
Adapter crates cannot implement EvidenceReader themselves — a board's
signal vocabulary G is not theirs to know. Show the intended shape on
the trait: the board impl owns the match, the hardware binding is made
once at construction, the signal id proves the right reader was wired.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Christina Quast <christina.quast@9elements.com>
Failure reports identify a checkpoint by name, so a duplicate within a
device would make them ambiguous — validate now rejects it at build
time (str comparison by hand: == on &str is not const). Also state
explicitly that max_retries=0 means the one attempt is all the device
gets.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Christina Quast <christina.quast@9elements.com>
The signal field now says on the spot why it is an id and who resolves
it, and validate points at the mock table, which demonstrates the
board-local const fence for checks the generic validate cannot do
(gpio line within the bank).

Assisted-by: Claude:claude-fable-5
Signed-off-by: Christina Quast <christina.quast@9elements.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant