fwmanager: table-declared boot checkpoints replace BootMonitor - #397
Conversation
|
@rusty1968 three design notes worth discussing: Timeouts stay per-checkpoint, and the SM never sees them. The device table declares a window per checkpoint (bl1 can get 200 ms while the service checkpoint gets 30 s); the boot walk consumes those windows and reports expiry as a failed attempt. I think Lou mentioned something about per checkpoint timeouts. Open question 1 — Open question 2 — where does AnswersTimeoutsThe core has no sense of time. It has no clock and never measures how long anything takes. All the timing lives in the driver The design doc explicitly says a boot-progress timeout is "Treated as a verification failure When a boot watchdog fires the core just gets told "this attempt failed" — nothing more. The device table sets a time limit per checkpoint (bl1 might get 200 ms, the service checkpoint 30 s); the boot walk runs those timers and, if one runs out, reports it as a plain failed attempt. The core never sees the limit, how long it took, or that a timer was even involved. This lines up with Lou's point on per-checkpoint timeouts: the limits live in the device table, one per checkpoint, and never become something the core knows about. Open Question 1The guiding principle: the orchestrator core should decide on its own evidence, not on device feedback. A DeviceFatal diagnose is the device classifying itself; acting on it means letting an unverifiable self-report drive control flow. The core's retry loop is precisely its own device-independent check — re-quiesce, re-verify at rest, try max_retry times, then decide. I'd rather the terminal decision rest on that than on the device's word, since how trustworthy "fatal" is depends entirely on the device. And a fast-path doesn't actually buy safety, only latency. Both ways a device can misclassify already converge to the same fail-closed state: says "fatal" when retriable → we isolate/lock something recoverable (availability loss, still fail-closed); |
5dc453a to
4d6e72b
Compare
…hecks 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>
…al ids 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>
…ence 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>
…aning 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>
Whether a device must attest before its update commits follows from what kind of device it is (iRoT-backed or symbiont — the orchestrator's ComponentKind); the CSA defines only that distinction. A second table knob could only agree with the kind or contradict it. Assisted-by: Claude:claude-fable-5 Signed-off-by: Christina Quast <christina.quast@9elements.com>
WalkVerdict now reports observation only: Failed{checkpoint, cause}
replaces Retry/Dead/retries_left — the state machine's
ComponentStatus.retry and Recovering→RecoveryFailed path already own
those decisions, and a second counter could only agree or disagree
with the first. max_retries leaves the table for the same reason: a
retry re-resets the device and re-runs the whole walk, so budgets are
per boot attempt, owned where boot attempts are owned. The device's
own judgment still flows up as FailureCause::{TimedOut,
DeviceRetriable, DeviceFatal} — the one input the retry decision
needs.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Christina Quast <christina.quast@9elements.com>
Pin three facts the docs left implicit: checkpoint timeouts are table data the walk consumes — the clockless state machine never sees a duration, a component's boot timeout is just its walk over the windows; the device table is the authority the chain is built from; and Complete maps to ComponentReady or Booted by component kind, in the shell. Assisted-by: Claude:claude-fable-5 Signed-off-by: Christina Quast <christina.quast@9elements.com>
Every schema check is per-device, so the constructors can run them all. BootCheckpoint::new and DeviceConfig::new are const fn -- board tables still build in const context, so a bad table is still a build error -- but the fields are private now, and a checkpoint or device entry that violates the schema cannot be constructed at all. The free validate() is gone with the loophole it carried: it had to be remembered, and a board table that dropped the const fence compiled fine while broken. Construction is the one gate every entry passes. Board-local checks keep the const-fence pattern (validate_signals in the mock table), reading through the new accessors. Assisted-by: Claude:claude-fable-5 Signed-off-by: Christina Quast <christina.quast@9elements.com>
Within the crate only EvidenceReader consumes BootStatus — it is the trait's return vocabulary — so the enum does not earn a module of its own. The crate-root re-export is unchanged; no import anywhere moves. Assisted-by: Claude:claude-fable-5 Signed-off-by: Christina Quast <christina.quast@9elements.com>
Traits and schema only — the walker implementing them follows in a separate PR, so this one stays reviewable on the contract alone.
What
BootMonitoris removed. ABootCheckpointnames 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.rsonly defines what that configuration has to look like.EvidenceReader<G>resolves a signal id toBootStatus— board wiring, typically one per managed device, exhaustive match, direct dispatch.BootStatusgrows device-reported verdicts:FailedRetriable(consumes retry budget immediately instead of waiting out the window) andFailedFatal(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 readsBootingforever.BootWatch/WalkVerdictis the erased seam the orchestrator polls:Waiting/Complete/Retry/Deadper device, free of device and error types.GpioBootMonitorkeeps its behavior as a plain reader (inherent method, no trait).Reading order
The first commit is the checkpoint-embedded-
fnvariant; 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
BootWalk/MonitorMapoverBootMonitor.await_bootwill need rework once this lands.Test
bazel test //services/fwmanager/...(13 api tests),bazel build //target/mock/...; device-tablevalidateruns in const context, so a bad table is a build error.Migration (for integrators)
BootMonitoris replaced byEvidenceReader<G>+BootStatus; the checkpoint schema changed shape:max_retries: 0= the single attempt is all the device gets.BootStatus::Failedsplit intoFailedRetriable/FailedFatal(device-reported verdicts; timeouts remain the observer's judgment).validate).WalkVerdict::Retrycarriesdeadline_millisof the re-armed window — schedule against it exactly likeWaiting.