Skip to content

config: Build the chain and the kinds from the device table - #526

Merged
chrysh merged 1 commit into
OpenPRoT:ocp-global-demo-wipfrom
9elements:one-device-table
Oct 3, 2026
Merged

chrysh merged 1 commit into
OpenPRoT:ocp-global-demo-wipfrom
9elements:one-device-table

Conversation

@chrysh

@chrysh chrysh commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Closes #375.

ComponentKind was written out twice: once in ComponentAttrs on the orchestrator's chain, once in Board::component_kinds. Nothing checked they matched. Disagree and the machine waits in AwaitingReady for a readiness the driver never sends, the boot watchdog fires, and the component goes round the recovery loop until its retries run out.

DeviceConfig now carries the ComponentAttrs for its device, and chain_of turns a table into the chain the machine runs on. Board no longer holds the kinds at all: the driver reads them off the chain entries it was built with, so the second copy is gone rather than hidden.

It holds ComponentAttrs whole rather than copying its four fields, so there is one definition of what a component's policy is and a field added there reaches the table with no change in config.

bring_up takes the entries once and returns both halves, and it is the only way to a driver from outside the crate. A board cannot hand the two sides different lists because it only passes one.

Table order assigns the ids: devices[i] is ComponentId(i), which is also how the driver indexes every per-component array. chain_of checks the depends_on links while it has the whole table, because a dependency the chain does not contain is dropped at the dispatch boundary and would silently never cascade, and it bounds the table at what Chain takes.

config gains a dependency on sm for the vocabulary. The other direction would put a dependency on the decision core, which stays free of them.

Every existing table now states its kinds, which meant deciding them: the mock NIC is active_required because it is SPDM-capable and has an iRoT, the mock BMC and the ast1060 BMC are passive_required because they have none.

Where the guarantee stops

Orchestrator::new and Chain::try_from stay public. The state machine's own tests and the ast10x0 runtime harness build synthetic chains of varying size, and narrowing that to win an encapsulation point would cost more than it buys. So going around bring_up is possible, but it is visible: there is no longer an API through which the two lists can quietly disagree.

The position rule is checked in two places for two reasons. A table that came through chain_of is in position by construction, at build time. The assert in PlatformDriver::new catches hand-built entries at bring-up, before anything leaves reset, because the driver indexes every per-component array by id.get() and an entry out of position would address the wrong component's reset line, flash and floor.

One correspondence stays hand-authored: Board's per-component arrays. A hardware handle carries no identity, so there is nothing to derive "board.images[1] belongs to the BMC" from, and nothing to check it against. A board that composes its arrays in a different order than its table still boots the wrong device, and bring_up cannot catch it. That is tracked as #529; it is a different problem from two lists of devices disagreeing, which is what this closes.

ComponentKind was written out twice: once in ComponentAttrs on the
orchestrator's chain, once in Board::component_kinds. Nothing checked
they matched. Disagree and the machine waits in AwaitingReady for a
readiness the driver never sends, the boot watchdog fires, and the
component goes round the recovery loop until its retries run out.

DeviceConfig now carries the ComponentAttrs for its device, and chain_of
turns a table into the chain the machine runs on. Board no longer holds
the kinds at all: the driver reads them off the chain entries it was
built with, so the second copy is gone rather than hidden.

Holding ComponentAttrs whole rather than copying its four fields keeps
one definition of what a component's policy is. A field added there
reaches the table with no change here.

chain_of returns a ChainEntries newtype with a private field. The only
way to get one is through chain_of, and boards declare it as a const
item, so the validation (nonempty, at most u8::MAX, no forward or self
deps) runs at compile time. A bad table is a build error, never a
runtime panic.

bring_up takes &'static ChainEntries and is infallible: the const fence
already guarantees everything Chain::try_from would check, so the conversion
uses an expect rather than returning a Result. The old
bring_up_reports_a_chain_the_machine_refuses test is gone because the
scenario is now unrepresentable.

Table order assigns the ids: devices[i] is ComponentId(i), which is also
how the driver indexes every per-component array. A table that came
through chain_of is in position by construction; the check in
PlatformDriver::new catches synthetic entries in the driver's own tests.
chain_of also checks the depends_on links
while it has the whole table, because a dependency the chain does not
contain would silently never cascade.

Orchestrator::new and Chain::try_from stay public: the state machine's
own tests and the ast10x0 runtime harness build synthetic chains, and
narrowing that to win an encapsulation point would cost more than it
buys.

config gains a dependency on sm for the vocabulary. driver gains a
dependency on config for ChainEntries.

Assisted-by: Claude
@chrysh
chrysh marked this pull request as ready for review October 3, 2026 19:31
@chrysh
chrysh merged commit 0c729e9 into OpenPRoT:ocp-global-demo-wip Oct 3, 2026
4 of 5 checks passed
@chrysh
chrysh deleted the one-device-table branch October 3, 2026 19:31
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