Skip to content

orchestrator: add IncrementalVerifier capability trait - #30

Closed
chrysh wants to merge 8 commits into
mainfrom
async-verify
Closed

orchestrator: add IncrementalVerifier capability trait#30
chrysh wants to merge 8 commits into
mainfrom
async-verify

Conversation

@chrysh

@chrysh chrysh commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

Trait-only PR. Adds IncrementalVerifier to orchestrator-capabilities:
polled, chunked image verification so the update pump never blocks on
hashing megabytes of payload. Same poll-not-block contract as Updatable.

Driver composition (the adapter that wraps a crypto backend behind this
trait, wired into the update pump) follows in a stacked PR.
Effect::AuthenticateUpdate remains stubbed in the SM until then.

Naming decisions for review

  • VerifyStep vs the sibling's StageProgress: worth mirroring as
    VerifyProgress?
  • Processing { hashed, total } lands ahead of the update-pump plan's
    Progress newtype (step 1). If the newtype arrives later, this variant
    takes a shape break. Also hashed vs the plan's done.
  • This trait supersedes the plan's step 5 (synchronous verify).
    update-pump-design.md needs a line edit after this lands.

Assisted-by: Claude

chrysh added 8 commits August 31, 2026 22:17
The PLDM firmware-device loop now notifies an UpdateEventSink once per
accepted RequestUpdate, detected as the FD's only Idle -> non-Idle
transition, after the success response is sent. Rejected requests
(already in update mode, bad transfer size) leave the state unchanged
and never notify.

The sink trait stays PLDM-flavored so this crate never depends on the
orchestrator stack. The mapping to Event::UpdateRequest lives in the
new orchestrator-pldm-adapter crate as UpdateRequestLatch, following
the same rule that keeps HAL adapters out of orchestrator-capabilities.
The latch is a bool, not a counter: the FD rejects a second
RequestUpdate while one is in progress, and an undrained latch across
update cycles coalesces into the single UpdateRequest the state
machine would act on anyway.

The firmware-update host test drives the latch end to end: the accepted
RequestUpdate latches exactly one Event::UpdateRequest, the duplicate
is rejected with AlreadyInUpdateMode and latches nothing, and no later
command in the flow latches anything.

Signed-off-by: Christina Quast <christina.quast@9elements.com>
hal-adapters moves to adapters/hal and the new PLDM adapter to
adapters/pldm, so the growing family of producer-to-orchestrator
adapter crates (HAL, PLDM, later SPDM) sits under one directory.
Crate and target names are unchanged; each adapter keeps its own
crate so composing one stack never pulls in another.

Signed-off-by: Christina Quast <christina.quast@9elements.com>
More PLDM lifecycle events are coming (cancel, transfer/verify/apply
complete, activate). Replace UpdateEventSink's per-event method with a
non_exhaustive FdEvent enum and a single FdEventSink::notify, so new
events are one variant instead of a trait break.

Every variant payload stays Copy and lifetime-free: across the DSP0267
FD surface the orchestrator-relevant edges carry only small integers;
buffer-shaped data (image chunks, package data, version strings) lands
behind FdOps and events name it instead of carrying it.

UpdateRequested is the only variant for now. The adapter latch keeps
its semantics and drops unmapped events.

Signed-off-by: Christina Quast <christina.quast@9elements.com>
Clippy's single_match lint (denied under -D warnings in presubmit)
rejects a one-arm match with a wildcard. FdEvent derives no PartialEq,
so use matches! rather than an equality check.

Signed-off-by: Christina Quast <christina.quast@9elements.com>
The four Effect::Report* variants had no consumer seam. Add a
non_exhaustive Report enum and one ReportSink::report, not a method or a
trait per variant, plus a unit impl for a board with no management side
to tell.

report returns nothing: an error channel would put reports on the
driver's fail-closed path, letting the act of reporting a contained
failure escalate it.

Trait only, composing a sink into the board follows.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Christina Quast <christina.quast@9elements.com>
Ready now means the staged payload is verified to the archetype's
discipline: a direct-flash adapter reads written pages back before
reporting Ready, a PLDM device runs its own verify step after the
transfer. This drops the planned ReadBack capability; a separate
post-Ready check would leak archetype knowledge to the caller and PLDM
devices cannot serve it at all. The commit bullet now points at the
BootConfirmed gated flow instead of a TrialBoot capability.

UpdateError gains ReadbackMismatch. Folding it into Device would hide
the one device fault a caller can act on differently: the write path
reported success and the storage still disagrees, so a slot that keeps
mismatching is worth retiring rather than retrying.

MockFlashDevice demonstrates the discipline: writes hold written still,
readback advances it, and a mismatch fails the step. MockPldmDevice
takes a verify step of its own once the transfer completes, so a
finished transfer is not yet Ready.

Assisted-by: Claude (Fable 5)
Signed-off-by: Christina Quast <christina.quast@9elements.com>
SvnFloor joins BoardCapabilities, one floor per component slot. The SVN
to advance to travels in Verdict::Authenticated: verification is the
only authenticated read of the manifest, so nothing else may tell the
floor where to go. The driver caches it per component, clears it on
rejection, and fails closed when asked to commit without it.
SvnFloorBinding names the two wirings a board can choose: Erot, where
the eRoT holds the floor, and SelfManaged for a component that tracks
its own SVN (iRoT, or a PLDM device committing internally), whose
commit is a no-op.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Christina Quast <christina.quast@9elements.com>
Polled incremental verification seam for the update path: judge a
candidate image one bounded step at a time so the single-threaded
runtime stays live while hashing megabytes of payload.

The trait lives in orchestrator-capabilities (dependency-free leaf).
start() opens a session, poll(payload) does one implementor-chosen
chunk of work and returns Processing/Authenticated/Rejected. Faults
(unreadable payload, crypto error, misuse) are Err, never a verdict,
so a check that could not run cannot forge a judgment. After an Err
the session is abandoned; only start() restores defined state.

Explicit start() (unlike Updatable's implicit idle-to-active) catches
an accidental extra poll after a verdict instead of silently
re-hashing from zero.

Assisted-by: Claude
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant