Skip to content

orchestrator: add TrialBoot capability trait - #460

Merged
rusty1968 merged 1 commit into
OpenPRoT:mainfrom
9elements:add-trial-boot
Sep 17, 2026
Merged

rusty1968 merged 1 commit into
OpenPRoT:mainfrom
9elements:add-trial-boot

Conversation

@chrysh

@chrysh chrysh commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

TL;DR: Trait-only PR. TrialBoot is the commit gate activation leaves open:
keep an image that was activated but not committed once its boot was judged,
or throw it away.

Part of #377, 9elements#21.

Summary

Activating an update picks the staged image as the next thing to boot and
stops there, whether the eRoT does that itself through Updatable::activate
or a PLDM firmware device does it for the eRoT. Updatable's docs already
point at a TrialBoot capability as the other half; that trait did not
exist. This adds it to orchestrator-capabilities: is_pending, confirm,
revert.

confirm and revert take no arguments. A device has at most one trial open
at a time, the one the last activation armed, so there is nothing to name,
and which slot is which stays behind the seam as it does in Updatable.

One contract, both implementations

The same trait covers a downstream device behind interposed flash and the
eRoT's own image. They differ in who judges the boot, not in what confirm or
revert means. A device that commits on its own, a PLDM firmware device
picking its own slot, has no TrialBoot on the eRoT side, the same split
SvnFloor already makes.

Three contract points:

  • is_pending is answerable from stored state alone, because the image that
    confirms a self-update is not the image that armed it.
  • The arming counts for the next boot only, but the record of the trial
    outlives it. An image that hangs, or an eRoT that loses power during the
    trial, boots the confirmed slot again with no call from anyone; the record
    stays open until confirm or revert, so an orchestrator that rebooted
    mid-update finds the trial again instead of losing it.
  • revert also disarms. An orchestrator that drops an activation before
    resetting the device needs the next boot to run the confirmed slot even
    though the trial image never booted.

Both calls are safe to repeat: with no trial open they succeed and do
nothing, matching SvnFloor::advance.

confirm moves slot metadata only. Raising the anti-rollback floor is
SvnFloor::advance and happens later, on the UA's UpdateSecurityRevision
(DSP0267 1.3.0, command 0x22), which is what keeps revert able to fall back
to an image that still boots. See #464.

Why not a method on Updatable

For self-update, staging and confirm run in different boots of different
images. The Updatable instance that staged the payload no longer exists
when confirm runs. A method on Updatable cannot model that.

Also considered and rejected: the consuming-session shape of #459 (a
session's compile-time guarantee cannot cross a power cycle, which is the
boundary this trait exists to span), and a constructor returning
Option<Trial> (models only cold-boot recovery, not the downstream happy
path where the trial opens mid-session).

Tests

Seven host tests, both implementations driven through one generic
apply_verdict<T: TrialBoot> so the shared contract is what is under test: a
healthy downstream trial becomes confirmed, one nobody confirms falls back
yet stays open, revert before the trial boots disarms the next boot, the eRoT
confirms from the boot its trial started, an eRoT trial that never confirms
falls back on the next reset, a replayed resolution is a no-op, errors
surface through the generic seam.

Not in this PR

The implementations. What backs the trial record durably is open
(#383, #384): services/storage is still a stub, so the
eRoT-side impl has nowhere to keep the record yet.

@chrysh
chrysh force-pushed the add-trial-boot branch 12 times, most recently from 964ef7a to d883d29 Compare September 8, 2026 20:28
@chrysh
chrysh marked this pull request as ready for review September 8, 2026 20:31
Comment thread services/orchestrator/capabilities/src/trial_boot.rs Outdated
chrysh added a commit to 9elements/openprot that referenced this pull request Sep 14, 2026
Leon's review point on OpenPRoT#460: tests get read as examples, so the mock should
use the conversion everyone already knows. `faulty` stays a named constructor
because only one `From<&mut SlotRecord>` can exist.

Assisted-by: Claude
@chrysh
chrysh requested a review from leongross September 14, 2026 19:17
@chrysh
chrysh force-pushed the add-trial-boot branch 3 times, most recently from 5115b21 to d92129f Compare September 14, 2026 20:08
Activating an update proposes a boot candidate and never commits it, whether
the eRoT actuates that itself through Updatable::activate or a PLDM firmware
device does it on the eRoT's behalf. Updatable's docs already name TrialBoot
as the gate that resolves the proposal; this is that trait: is_pending,
confirm, revert, no slot ids.

One contract covers both implementors. A passive downstream device is judged
from outside over its boot-complete line; the eRoT's own image is judged by
the boot the trial started, so is_pending has to be answerable from durable
state alone. The arming applies to the next boot only, so an unresolved trial
falls back on its own, but the record stays pending until confirm or revert,
which is how an orchestrator that rebooted mid-update finds it again.

confirm moves slot metadata only. Raising the anti-rollback floor is
SvnFloor::advance and happens later, so revert always has a bootable image to
fall back to.

Assisted-by: Claude
@rusty1968
rusty1968 merged commit 3329d07 into OpenPRoT:main Sep 17, 2026
5 checks passed
@chrysh
chrysh deleted the add-trial-boot branch September 21, 2026 12:28
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.

3 participants