Orchestrator spec and reference impl
diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md
index bf6ff0e..b7c13b5 100644
--- a/docs/src/SUMMARY.md
+++ b/docs/src/SUMMARY.md
@@ -32,3 +32,6 @@
 * [Design](./design/README.md)
   * [Pigweed Integration Overview](./design/pigweed-overview.md)
   * [pw_kernel IPC](./design/pw-kernel-ipc.md)
+  * [Orchestrator](./design/orchestrator/orchestrator-overview.md)
+    * [Verification Model](./design/orchestrator/orchestrator-model.md)
+    * [State Machine](./design/orchestrator/orchestrator-machine.md)
diff --git a/docs/src/design/README.md b/docs/src/design/README.md
index f7c767f..5326ea9 100644
--- a/docs/src/design/README.md
+++ b/docs/src/design/README.md
@@ -13,3 +13,7 @@
 -   [**pw_kernel IPC**](./pw-kernel-ipc.md): How to declare and use channel
     objects to communicate between two `pw_kernel` userspace processes.
     Worked example lives at `target/veer/ipc/`.
+-   [**Orchestrator**](./orchestrator/orchestrator-overview.md): The eRoT boot-sequence state
+    machine (`services/orchestrator/sm`). Covers the two-tier firmware
+    verification model (`ComponentAttrs`, eRoT gate, iRoT gate), the
+    verification boundary, and the full state/transition table.
diff --git a/docs/src/design/orchestrator/orchestrator-machine.md b/docs/src/design/orchestrator/orchestrator-machine.md
new file mode 100644
index 0000000..dc679b4
--- /dev/null
+++ b/docs/src/design/orchestrator/orchestrator-machine.md
@@ -0,0 +1,227 @@
+# State Machine
+
+This document describes the state machine that lives in
+`services/orchestrator/sm/src/lib.rs`: its states, shared storage, entry
+actions, transition table, and the `Operational` superstate.
+
+```mermaid
+stateDiagram-v2
+    [*] --> PowerOnReset
+
+    PowerOnReset --> VerifyingPlatform : PowerGood(Provisioned)
+    PowerOnReset --> Locked             : PowerGood(Unprovisioned)
+    PowerOnReset --> Locked             : PowerGood(SelfVerificationFailed)
+
+    VerifyingPlatform --> VerifyingPlatform : VerificationPassed [more, Passive]\n/ ReleaseReset · ReadFirmware · VerifyFirmware
+    VerifyingPlatform --> AwaitingReady     : VerificationPassed [more, Active]\n/ ReleaseReset · ReadFirmware · VerifyFirmware
+    VerifyingPlatform --> Ready             : VerificationPassed [chain done]\n/ ReleaseReset
+    VerifyingPlatform --> VerifyingPlatform : VerificationFailed [optional]\n(skip — held in reset)
+    VerifyingPlatform --> Recovering        : VerificationFailed [required]\n/ RestoreGoldenImage
+
+    AwaitingReady --> AwaitingReady : VerificationPassed [more]\n/ ReleaseReset · ReadFirmware · VerifyFirmware
+    AwaitingReady --> Ready         : ComponentReady [chain done or cursor past end]
+    AwaitingReady --> AwaitingReady : ComponentReady [more]
+    AwaitingReady --> AwaitingReady : VerificationFailed [optional, iRoT pending]
+    AwaitingReady --> Ready         : VerificationFailed [optional, no iRoT pending, chain done]
+    AwaitingReady --> Recovering    : VerificationFailed [required]\n/ RestoreGoldenImage
+
+    state Operational {
+        [*]           --> Ready
+        Ready         --> Updating      : UpdateRequest\n/ AuthenticateUpdate · StageUpdate
+        Updating      --> Ready         : UpdateVerified / ActivateUpdate
+        Updating      --> Ready         : UpdateRejected / DiscardStaged
+        Ready         --> Recovering    : CorruptionDetected\n/ RestoreGoldenImage
+        Updating      --> Recovering    : CorruptionDetected\n/ RestoreGoldenImage
+        AwaitingReady --> Recovering    : CorruptionDetected\n/ RestoreGoldenImage
+    }
+
+    Recovering --> VerifyingPlatform : Restored [retry < max_retry]
+    Recovering --> Locked    : Restored [retry ≥ max_retry]\n(self-emits RecoveryFailed)\n/ LatchLockdown
+    Locked     --> Locked    : (terminal — all events ignored)
+```
+
+---
+
+## Shared storage — `Rot<N>`
+
+Every handler receives a `&mut Rot<N>` alongside the event and the `Sink`. This
+struct is `statig`'s *shared storage*: a single allocation that persists across
+events and is visible to every state and superstate. States carry no data; all
+mutable state lives here.
+
+| Field | Type | Purpose |
+|---|---|---|
+| `chain` | `Vec<(ComponentId, ComponentAttrs), N>` | Ordered trust chain, supplied by the shell at construction time. Never mutated after build. |
+| `cursor` | `u8` | Index of the component currently under verification. Reset to 0 on every `VerifyingPlatform` entry. Advances on each `VerificationPassed` (and on optional `VerificationFailed`) via `Outcome::Handled`. |
+| `failed` | `Option<ComponentId>` | The component that triggered the current recovery episode; `None` while healthy. Set on required `VerificationFailed` or `CorruptionDetected`. |
+| `retry_count` | `u8` | Number of consecutive failed restore attempts. Cleared to 0 in `Ready`'s entry action — consecutive only (INV7). |
+| `max_retry` | `u8` | Shell-chosen ceiling for `retry_count`. When `retry_count >= max_retry` the machine self-emits `RecoveryFailed` instead of re-walking the chain. |
+| `awaiting` | `Option<ComponentId>` | The `Active` component whose iRoT readiness is currently outstanding. `Some` only while in `AwaitingReady`; `None` everywhere else (INV9). |
+
+The effect buffer is deliberately **absent** from `Rot`. Effects flow through the
+`Sink` (the `statig` context), which the orchestrator creates fresh for every
+event and drains afterward.
+
+---
+
+## Context — `Sink`
+
+The only thing a handler can do to the outside world is call `ctx.emit(effect)`.
+`Sink` is an append-only `heapless::Vec<Effect, EFFECT_CAP>`. It can push; it
+cannot pull, read, or do I/O. The orchestrator owns a fresh `Sink` per dispatch
+and reads the effects out after `handle_with_context` returns.
+
+---
+
+## States
+
+### `PowerOnReset`
+
+The machine's initial state. The first event is always `PowerGood(PowerOnResult)`.
+
+**Entry action**: none.
+
+| Event | Guard | Effects | Next state |
+|---|---|---|---|
+| `PowerGood(Provisioned)` | — | — | `VerifyingPlatform` |
+| `PowerGood(Unprovisioned)` | — | — | `Locked` |
+| `PowerGood(SelfVerificationFailed)` | — | — | `Locked` |
+| anything else | — | — | `Outcome::Super` (top level — discarded) |
+
+---
+
+### `VerifyingPlatform`
+
+Walks the trust chain component-by-component. The cursor advances on each
+`VerificationPassed` (or optional `VerificationFailed`) using `Outcome::Handled`
+rather than a self-transition — a self-transition would re-run the entry action
+and reset the cursor.
+
+**Entry action**: reset `cursor` to 0, `awaiting` to `None`, emit
+`ReadFirmware(chain[0])` + `VerifyFirmware(chain[0])`.
+
+| Event | Guard | Effects | Next state |
+|---|---|---|---|
+| `VerificationPassed(id)` | more, current `Passive` | `ReleaseReset` · `ReadFirmware(next)` · `VerifyFirmware(next)` | `Handled` (cursor ++) |
+| `VerificationPassed(id)` | more, current `Active` | `ReleaseReset` · `ReadFirmware(next)` · `VerifyFirmware(next)` | `AwaitingReady` (awaiting = Some(id)) |
+| `VerificationPassed(id)` | chain done | `ReleaseReset(id)` | `Ready` |
+| `VerificationFailed(id)` | `attrs.required` | — | `Recovering` (failed = Some(id)) |
+| `VerificationFailed(id)` | `!attrs.required` | — | `Handled` (skip; cursor ++; if chain done → `Ready`) |
+| anything else | — | — | `Outcome::Super` → `Operational` |
+
+---
+
+### `AwaitingReady`
+
+Reached when an `Active` component passes eRoT authentication. The machine waits
+here until the component's iRoT signals readiness via `ComponentReady`. The
+speculative eRoT check for the next component (`ReadFirmware` + `VerifyFirmware`)
+was already emitted by the `VerifyingPlatform` handler that triggered this
+transition.
+
+**Entry action**: none.
+
+| Event | Guard | Effects | Next state |
+|---|---|---|---|
+| `ComponentReady(id)` | `id != awaiting` | — | `Handled` (stale/spurious — ignore, INV9) |
+| `ComponentReady(id)` | `id == awaiting`, cursor in bounds | — | `Handled` (clear awaiting) |
+| `ComponentReady(id)` | `id == awaiting`, cursor past end | — | `Ready` |
+| `VerificationPassed(id)` | more | `ReleaseReset` · `ReadFirmware(next)` · `VerifyFirmware(next)` | `Handled` (cursor ++) |
+| `VerificationPassed(id)` | chain done | `ReleaseReset(id)` | `Ready` |
+| `VerificationFailed(id)` | `attrs.required` | — | `Recovering` (failed = Some(id), awaiting = None) |
+| `VerificationFailed(id)` | `!attrs.required`, iRoT pending | — | `Handled` (skip; cursor ++) |
+| `VerificationFailed(id)` | `!attrs.required`, no iRoT pending, chain done | — | `Ready` |
+| anything else | — | — | `Outcome::Super` → `Operational` |
+
+`ComponentReady` and `VerificationPassed` are independent and may arrive in
+either order. Both must be seen before the walk advances. `awaiting` tracks
+whether `ComponentReady` is still outstanding; the state itself tracks whether
+`VerificationPassed` is still outstanding.
+
+---
+
+### `Ready`
+
+Normal operational state: the full chain has been verified, all required
+components are released, and the machine handles attestation, update requests,
+and corruption events.
+
+**Entry action**: reset `retry_count` to 0 (makes the cap count *consecutive*
+failures — INV7).
+
+| Event | Guard | Effects | Next state |
+|---|---|---|---|
+| `UpdateRequest` | — | — | `Updating` |
+| anything else | — | — | `Outcome::Super` → `Operational` |
+
+---
+
+### `Updating`
+
+An update is in progress.
+
+**Entry action**: emit `AuthenticateUpdate` + `StageUpdate`.
+
+| Event | Guard | Effects | Next state |
+|---|---|---|---|
+| `UpdateVerified` | — | `ActivateUpdate` | `Ready` |
+| `UpdateRejected` | — | `DiscardStaged` | `Ready` (rejected update is not corruption — INV4) |
+| anything else | — | — | `Outcome::Super` → `Operational` |
+
+---
+
+### `Recovering`
+
+The machine is attempting to restore a corrupted or rejected component.
+
+**Entry action**: emit `RestoreGoldenImage(rot.failed)` — exactly the named
+component, not the whole chain (INV5).
+
+| Event | Guard | Effects | Next state |
+|---|---|---|---|
+| `Restored(_)` | `retry_count + 1 < max_retry` | — | `VerifyingPlatform` (re-walk from top) |
+| `Restored(_)` | `retry_count + 1 >= max_retry` | `Effect::Emit(RecoveryFailed)` | `Handled` (orchestrator queues `RecoveryFailed` next — INV7) |
+| `RecoveryFailed` | — | — | `Locked` |
+| anything else | — | — | `Outcome::Super` → `Operational` |
+
+`Effect::Emit(RecoveryFailed)` is the *feedback-as-data* mechanism: the core
+produces the event internally, the orchestrator intercepts and re-dispatches it
+before returning, and the decision is visible in the effect trace.
+
+---
+
+### `Locked`
+
+Terminal state. All events are discarded.
+
+**Entry action**: emit `LatchLockdown` — instruct the shell to hold all
+components in reset permanently.
+
+---
+
+## Superstate — `Operational`
+
+`Ready`, `Updating`, `Recovering`, and `AwaitingReady` share this superstate.
+When a leaf state returns `Outcome::Super`, `statig` calls the superstate handler.
+
+| Event | Effects | Next state |
+|---|---|---|
+| `AttestationChallenge` | `SignAttestation` | `Handled` (no transition — INV6) |
+| `CorruptionDetected(id)` | — | `Recovering` (failed = Some(id) — INV5) |
+| anything else | — | `Outcome::Super` (discarded) |
+
+---
+
+## `statig` integration
+
+The machine uses `statig` 0.4.1 with hand-written trait impls — no proc-macros.
+
+| Trait | Implemented by | Role |
+|---|---|---|
+| `IntoStateMachine` | `Rot<N>` | Declares associated types and `initial() -> State`. |
+| `StatigState<Rot<N>>` | `State` | `call_handler`, `call_entry_action`, `superstate`. |
+| `StatigSuperstate<Rot<N>>` | `Superstate<'_>` | `call_handler` for events that fell through from a leaf state. |
+
+`initial()` is a `fn() -> State` with no `self`, so the machine always starts
+in `PowerOnReset`. The shell-supplied `PowerGood(PowerOnResult)` event is the
+first real branching point.
diff --git a/docs/src/design/orchestrator/orchestrator-model.md b/docs/src/design/orchestrator/orchestrator-model.md
new file mode 100644
index 0000000..03b3be5
--- /dev/null
+++ b/docs/src/design/orchestrator/orchestrator-model.md
@@ -0,0 +1,244 @@
+# Verification Model
+
+This document describes how platform firmware verification is modelled in the
+orchestrator state machine (`services/orchestrator/sm`): the problem it solves,
+the types that carry the domain, the states that sequence the work, and the
+boundary between the pure core and the platform shell that executes it.
+
+---
+
+## 1. The Problem
+
+The eRoT (external Root of Trust — the discrete RoT device, e.g. on a DC-SCM)
+must verify every platform component's firmware before releasing it from reset.
+Two independent mechanisms do this:
+
+1. **eRoT-side**: the eRoT reads the component's firmware image from the SPI
+   flash it controls, verifies the signature and SVN against a Reference
+   Integrity Manifest (RIM/PFM), and only then releases the component from reset.
+
+2. **iRoT-side**: components with an integrated Root of Trust (e.g. a BMC SoC
+   or CPU with Caliptra) perform their own independent local self-verification
+   after reset. The eRoT must wait for this local check to complete before
+   treating the component as trusted and advancing to the next one in the chain.
+
+Components that have no integrated iRoT (e.g. a NIC) rely solely on the
+eRoT-side check. The eRoT can advance immediately after releasing them.
+
+This two-tier model — eRoT gate + optional iRoT gate — is the core problem the
+verification states solve. It is grounded directly in the CSA architecture boot
+sequence: "The eRoT and the iRoT provide complementary guarantees: the eRoT
+controls whether a component is released from reset; the iRoT controls whether
+the component's own firmware executes."
+
+The **verification boundary** is the interface between the platform shell and the
+pure state-machine core. Only verdicts cross it: the shell performs all
+cryptographic work (reading flash, checking signatures and SVN) and then signals
+the outcome via an event. The core never sees raw firmware data or hash values —
+it only acts on the resulting `VerificationPassed` or `VerificationFailed`. This
+keeps the core free of I/O and testable without hardware.
+
+---
+
+## 2. Domain Types
+
+### `ComponentKind`
+
+Classifies the iRoT gate for a component. Supplied by the shell at chain-build
+time; the core never derives it.
+
+```
+Active  — has an integrated iRoT (e.g. Caliptra); both eRoT and iRoT checks apply
+Passive — no integrated iRoT; only the eRoT check applies
+```
+
+### `ComponentAttrs`
+
+Per-component attributes that combine two orthogonal axes:
+
+```rust
+pub struct ComponentAttrs {
+    pub kind: ComponentKind,  // iRoT gate: Active | Passive
+    pub required: bool,       // failure policy: true = recover, false = skip
+}
+```
+
+| `kind` | `required` | `VerificationFailed` behaviour |
+|---|---|---|
+| Active / Passive | `true` | → `Recovering`; component held in reset; chain walk halts |
+| Active / Passive | `false` | component held in reset; cursor advances; chain walk continues |
+
+A `required: false` component that fails verification is **never** released from
+reset — releasing a component whose firmware failed verification would mean
+running untrusted code, which breaks the trust invariant regardless of the
+recovery policy.
+
+Convenience constructors: `ComponentAttrs::active_required()`,
+`passive_required()`, `active_optional()`, `passive_optional()`.
+
+### `ComponentId`
+
+An opaque `u8` the core carries and equality-compares but never inspects. The
+shell decides which id maps to which physical device.
+
+### Events that cross the verification boundary
+
+| Event | Direction | Meaning |
+|---|---|---|
+| `VerificationPassed(ComponentId)` | shell → core | The eRoT-side check passed: signature and SVN valid. |
+| `VerificationFailed(ComponentId)` | shell → core | The eRoT-side check failed: image rejected. |
+| `ComponentReady(ComponentId)` | shell → core | An `Active` component's integrated iRoT has finished its local verification and the component is operational (e.g. MCTP channel established). |
+
+### Effects the core emits for verification work
+
+| Effect | Meaning |
+|---|---|
+| `ReadFirmware(ComponentId)` | Ask the shell to read the component's firmware image from eRoT-controlled flash. |
+| `VerifyFirmware(ComponentId)` | Ask the shell to verify the image against the RIM/PFM. The shell responds with `VerificationPassed` or `VerificationFailed`. |
+| `ReleaseReset(ComponentId)` | Release the named component from reset. Emitted only after `VerificationPassed`. |
+
+These are descriptions, not actions. The shell's `Platform::execute` carries
+them out; the core never touches hardware.
+
+---
+
+## 3. Sequencing by `ComponentAttrs`
+
+### Active → Passive (happy path)
+
+```
+chain: [(C0, {Active, required}), (C1, {Passive, required})]
+
+VerifyingPlatform (entry):
+  emit ReadFirmware(C0)
+  emit VerifyFirmware(C0)
+
+VerificationPassed(C0):           ← eRoT check done
+  emit ReleaseReset(C0)
+  emit ReadFirmware(C1)           ← speculative eRoT check of next
+  emit VerifyFirmware(C1)
+  cursor = 1, awaiting = Some(C0)
+  → AwaitingReady
+
+ComponentReady(C0):               ← C0's iRoT done
+  awaiting = None
+  Handled (stay in AwaitingReady, wait for VerificationPassed(C1))
+
+VerificationPassed(C1):           ← speculative eRoT check resolved
+  emit ReleaseReset(C1)
+  chain done → Ready
+```
+
+### Optional component failure (skip, continue)
+
+```
+chain: [(BMC, {Active, required}), (NIC, {Passive, optional})]
+
+VerificationPassed(BMC):
+  emit ReleaseReset(BMC)
+  emit ReadFirmware(NIC)
+  emit VerifyFirmware(NIC)
+  awaiting = Some(BMC) → AwaitingReady
+
+VerificationFailed(NIC):          ← NIC firmware rejected; optional → skip
+  NIC stays held in reset
+  cursor advances past end
+  awaiting is still Some(BMC) → stay in AwaitingReady
+
+ComponentReady(BMC):              ← BMC iRoT done; cursor past end → Ready
+  awaiting = None → Ready
+```
+
+### Concrete example: BMC (Active, required) → HOST (Active, required) → NIC (Passive, optional)
+
+This matches the CSA single-node boot sequence.
+
+```
+chain: [(BMC, {Active, required}), (HOST, {Active, required}), (NIC, {Passive, optional})]
+
+VerifyingPlatform (entry):
+  emit ReadFirmware(BMC)
+  emit VerifyFirmware(BMC)          ← eRoT reads and checks BMC firmware from SPI flash
+
+VerificationPassed(BMC):            ← eRoT: BMC firmware signature + SVN valid
+  emit ReleaseReset(BMC)            ← eRoT releases BMC from reset; Caliptra iRoT runs
+  emit ReadFirmware(HOST)           ← speculative: eRoT starts HOST firmware check
+  emit VerifyFirmware(HOST)           while BMC's Caliptra iRoT is still booting
+  cursor = 1, awaiting = Some(BMC)
+  → AwaitingReady
+
+ComponentReady(BMC):                ← BMC Caliptra iRoT done; MCTP channel up
+  awaiting = None
+  Handled (still in AwaitingReady, waiting for VerificationPassed(HOST))
+
+VerificationPassed(HOST):           ← eRoT: HOST firmware signature + SVN valid
+  emit ReleaseReset(HOST)           ← eRoT releases HOST from reset; Caliptra iRoT runs
+  emit ReadFirmware(NIC)            ← speculative: eRoT starts NIC firmware check
+  emit VerifyFirmware(NIC)            while HOST's Caliptra iRoT is still booting
+  cursor = 2
+  Handled (stay in AwaitingReady — still waiting on ComponentReady(HOST) and/or NIC result)
+
+ComponentReady(HOST):               ← HOST Caliptra iRoT done; BIOS/UEFI executing
+  awaiting = None
+  Handled
+
+VerificationPassed(NIC):            ← eRoT: NIC firmware valid (Passive — no iRoT gate)
+  emit ReleaseReset(NIC)
+  chain done → Ready
+```
+
+If NIC fails verification instead:
+```
+VerificationFailed(NIC):            ← NIC optional → skip; NIC stays held in reset
+  cursor = 3 (past end)
+  awaiting = None (already cleared by ComponentReady(HOST))
+  → Ready
+```
+
+---
+
+## 4. The Speculative Read Pattern
+
+When an `Active` component passes eRoT verification the core does three things
+in the same handler, before transitioning to `AwaitingReady`:
+
+```
+emit ReleaseReset(current)
+emit ReadFirmware(next)        ← speculative: next eRoT check starts immediately
+emit VerifyFirmware(next)      ← while current's iRoT is still booting
+cursor += 1
+awaiting = Some(current)
+→ Transition(AwaitingReady)
+```
+
+This overlaps the integrated iRoT boot time of the current component with the
+eRoT firmware read of the next. The two checks are independent (different
+hardware paths), so the overlap is safe.
+
+---
+
+## 5. The Platform Boundary
+
+The core never reads flash, never checks signatures, never observes reset lines.
+It only emits descriptions. The complete split:
+
+| Responsibility | Core (`sm/src/lib.rs`) | Shell (`Platform` impl) |
+|---|---|---|
+| Chain order and `ComponentAttrs` | reads from `Rot.chain`, set by shell at startup | decides and provides |
+| Read firmware image | emits `ReadFirmware(id)` | executes: eRoT reads via SPI interposition, I3C, or other transport |
+| Verify signature / SVN | emits `VerifyFirmware(id)` | executes: eRoT checks against RIM/PFM; responds with `VerificationPassed` or `VerificationFailed` |
+| Release from reset | emits `ReleaseReset(id)` | executes: eRoT drives reset GPIO or equivalent |
+| Detect iRoT readiness | waits for `ComponentReady(id)` event | observes: integrated iRoT signals readiness (MCTP channel-up, GPIO, etc.); calls `dispatch` |
+| Required vs optional failure policy | checks `attrs.required` in handler | none — policy is encoded in the chain at startup |
+
+---
+
+## 6. What This Model Does Not Cover
+
+- **Self-verification of the eRoT firmware itself**: happens one boot layer down
+  (eRoT ROM + measuring bootloader) before this machine runs. The result is
+  delivered as `PowerOnResult` in `Event::PowerGood`.
+- **Attestation** (`AttestationChallenge` / `SignAttestation`): handled in the
+  `Operational` superstate, not part of the boot-time verification chain.
+- **Firmware update verification** (`AuthenticateUpdate`): handled in the
+  `Updating` state, distinct from boot-time chain verification.
diff --git a/docs/src/design/orchestrator/orchestrator-overview.md b/docs/src/design/orchestrator/orchestrator-overview.md
new file mode 100644
index 0000000..c782f50
--- /dev/null
+++ b/docs/src/design/orchestrator/orchestrator-overview.md
@@ -0,0 +1,50 @@
+# Orchestrator State Machine
+
+The orchestrator is the eRoT's boot-sequence controller. It walks the platform
+trust chain — verifying each component's firmware and releasing it from reset in
+order — and then governs the operational lifecycle (attestation, firmware update,
+corruption recovery).
+
+It lives in `services/orchestrator/sm` as a pure state machine: it never touches
+hardware directly. Every action is described as an [`Effect`] value that the
+surrounding shell carries out; every piece of outside information arrives as an
+[`Event`]. This keeps the core testable without hardware and free of I/O.
+
+## Documents
+
+- [**Verification Model**](./orchestrator-model.md): The two-tier firmware
+  verification model (eRoT gate + optional iRoT gate), the verification
+  boundary, `ComponentAttrs`, and concrete sequencing examples.
+- [**State Machine**](./orchestrator-machine.md): All states, shared storage, entry
+  actions, transition table, and the `Operational` superstate.
+
+## Design Principles
+
+**Effects, not actions.** Handlers call `ctx.emit(Effect::…)` to describe what
+should happen. The shell's `Platform::execute` carries it out. The core never
+reads flash, drives a GPIO, or opens a channel.
+
+**Reads as events.** The core never reads OTP, UFM, or any provisioning store.
+Outside information (power-on result, verification verdicts, iRoT readiness
+signals) arrives in event payloads.
+
+**Feedback as data.** Internal follow-up signals (e.g. the retry-cap lockdown
+`RecoveryFailed`) are emitted as `Effect::Emit(event)`. The orchestrator queues
+and handles them immediately, making them visible in the effect trace rather than
+hiding them as implicit state changes.
+
+**Board-supplied policy.** The core hard-codes no deployment-specific values.
+The shell supplies the trust chain (component ids, kinds, and required/optional
+policy) and the recovery-retry cap at startup.
+
+## Relationship to CSA Architecture
+
+The state machine is a direct implementation of the boot sequence described in
+the CSA architecture document:
+
+| CSA concept | State machine encoding |
+|---|---|
+| eRoT holds component in reset until firmware verified | `VerifyingPlatform` emits `ReleaseReset` only on `VerificationPassed` |
+| Component with Caliptra iRoT requires two independent checks | `ComponentKind::Active` → `AwaitingReady` until `ComponentReady` |
+| Passive component (no iRoT): eRoT check only | `ComponentKind::Passive` → advance immediately after `ReleaseReset` |
+| Optional component: failure skips, not blocks | `ComponentAttrs::required = false` → advance without `Recovering` |
diff --git a/services/orchestrator/sm/BUILD.bazel b/services/orchestrator/sm/BUILD.bazel
new file mode 100644
index 0000000..444bde8
--- /dev/null
+++ b/services/orchestrator/sm/BUILD.bazel
@@ -0,0 +1,22 @@
+# Licensed under the Apache-2.0 license
+# SPDX-License-Identifier: Apache-2.0
+
+load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
+
+rust_library(
+    name = "orchestrator_sm",
+    srcs = ["src/lib.rs"],
+    crate_name = "openprot_orchestrator_sm",
+    edition = "2024",
+    visibility = ["//visibility:public"],
+    deps = [
+        "@rust_crates//:heapless",
+        "@rust_crates//:statig",
+    ],
+)
+
+rust_test(
+    name = "orchestrator_sm_test",
+    crate = ":orchestrator_sm",
+    edition = "2024",
+)
diff --git a/services/orchestrator/sm/README.md b/services/orchestrator/sm/README.md
new file mode 100644
index 0000000..c801643
--- /dev/null
+++ b/services/orchestrator/sm/README.md
@@ -0,0 +1,55 @@
+<!-- Licensed under the Apache-2.0 license -->
+<!-- SPDX-License-Identifier: Apache-2.0 -->
+
+# orchestrator state machine (`openprot_orchestrator_sm`)
+
+Pure-reducer eRoT boot-sequence state machine. Walks the platform trust chain
+— verifying each component's firmware and releasing it from reset in order —
+then governs the operational lifecycle (attestation, firmware update, corruption
+recovery).
+
+**No I/O, no hardware.** Every action is an [`Effect`] the surrounding shell
+carries out. Every piece of outside information arrives as an [`Event`].
+
+## Key types
+
+| Type | Role |
+|---|---|
+| `ComponentId` | Opaque `u8` — the shell maps it to hardware; the core never inspects it. |
+| `ComponentKind` | `Active` (eRoT + iRoT gates) or `Passive` (eRoT gate only). |
+| `ComponentAttrs` | `kind` + `required`: if `false`, a failed component is skipped (held in reset) rather than triggering recovery. |
+| `Orchestrator<N>` | Public handle for the caller's event loop. Call `dispatch` or `dispatch_with` once per event. |
+| `Platform` | Implement this to carry out effects (drives reset GPIOs, reads flash, etc.). |
+
+## Usage
+
+```rust
+use openprot_orchestrator_sm::{
+    ComponentAttrs, ComponentId, Orchestrator, Event, PowerOnResult, State,
+};
+
+const CAPACITY: usize = 3;
+const BMC:  ComponentId = ComponentId::new(0);
+const HOST: ComponentId = ComponentId::new(1);
+const NIC:  ComponentId = ComponentId::new(2);
+
+let mut chain = heapless::Vec::<_, CAPACITY>::new();
+let _ = chain.push((BMC,  ComponentAttrs::active_required()));
+let _ = chain.push((HOST, ComponentAttrs::active_required()));
+let _ = chain.push((NIC,  ComponentAttrs::passive_optional()));
+
+let mut orch = Orchestrator::new(chain, /*max_retry=*/ 3);
+let mut board = MyBoard;
+
+orch.dispatch(&mut board, Event::PowerGood(PowerOnResult::Provisioned));
+// ...deliver VerificationPassed / ComponentReady events as they arrive...
+assert_eq!(orch.state(), State::Ready);
+```
+
+## Design docs
+
+Full domain model, verification boundary, and state transition tables are in the
+OpenPRoT book:
+
+- `docs/src/design/orchestrator/verification-model.md`
+- `docs/src/design/orchestrator/state-machine.md`
diff --git a/services/orchestrator/sm/src/lib.rs b/services/orchestrator/sm/src/lib.rs
new file mode 100644
index 0000000..4d9fe9e
--- /dev/null
+++ b/services/orchestrator/sm/src/lib.rs
@@ -0,0 +1,867 @@
+//! `openprot_orchestrator_sm` — the eRoT boot-sequence state machine.
+//!
+//! This is the pure-reducer core ported from `rot_reducer`. It describes side
+//! effects as [`Effect`] values rather than performing them; the surrounding
+//! OpenPRoT shell carries them out via a [`Platform`] impl. No concrete hardware
+//! appears here — the machine is generic over an opaque [`ComponentId`].
+//!
+//! See `docs/verification-model.md` and `docs/state-machine.md` in the
+//! `rot_reducer` workspace for the full domain context and design rationale.
+//!
+//! Three invariants define the boundary:
+//!   1. **Effects flow through [`Sink`]** — fresh per event, drained afterward.
+//!   2. **Feedback as data ([`Effect::Emit`])** — follow-up events are effects,
+//!      visible in the trace; used for the retry cap (INV7).
+//!   3. **Reads as events** — outside information arrives in [`Event`] payloads;
+//!      the core never reads anything directly.
+
+#![no_std]
+#![forbid(unsafe_code)]
+
+use core::marker::PhantomData;
+
+use statig::blocking::{
+    IntoStateMachine, IntoStateMachineExt as _, State as StatigState, StateMachine,
+    Superstate as StatigSuperstate,
+};
+use statig::Outcome;
+
+// Internal capacities — these follow from how the machine works, not from the
+// deployment. The board owns CAPACITY (chain length) and max_retry.
+
+/// Max effects one event can emit. The busiest handler emits 3; 8 is plenty.
+const EFFECT_CAP: usize = 8;
+
+/// Max pending events while settling one outside event (original + Emit follow-ups).
+const PENDING_CAP: usize = 8;
+
+/// An opaque identifier for one platform component. The core never inspects it;
+/// the board layer decides which real hardware each id refers to.
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub struct ComponentId(u8);
+
+impl ComponentId {
+    pub const fn new(id: u8) -> Self {
+        Self(id)
+    }
+
+    pub const fn get(self) -> u8 {
+        self.0
+    }
+}
+
+/// How a component in the trust chain is classified. The board supplies one
+/// [`ComponentKind`] per [`ComponentId`] when building the chain.
+///
+/// Corresponds directly to the two-tier model in the CSA architecture document:
+/// `Active` = eRoT gate + iRoT gate; `Passive` = eRoT gate only.
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum ComponentKind {
+    /// Has an integrated iRoT (e.g. Caliptra). Both eRoT-side (signature + SVN)
+    /// and iRoT-side (local self-verification) checks apply. The machine waits in
+    /// [`State::AwaitingReady`] for [`Event::ComponentReady`] before advancing.
+    Active,
+    /// No integrated iRoT. The eRoT's signature + SVN check is the only gate.
+    /// The chain walk advances immediately after `ReleaseReset`.
+    Passive,
+}
+
+/// Per-component attributes supplied by the board at chain-build time.
+///
+/// Two orthogonal axes:
+/// - [`kind`](ComponentAttrs::kind): controls the iRoT gate (Active vs Passive).
+/// - [`required`](ComponentAttrs::required): controls failure policy.
+///   * `true` — verification failure triggers recovery and halts the chain walk.
+///   * `false` — verification failure holds the component in reset and skips it;
+///     the chain walk continues to the next component without recovery.
+///
+/// A `required: false` component is never released from reset on failure — running
+/// untrusted firmware would break the trust invariant regardless of policy.
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub struct ComponentAttrs {
+    pub kind: ComponentKind,
+    pub required: bool,
+}
+
+impl ComponentAttrs {
+    pub const fn active_required() -> Self {
+        Self { kind: ComponentKind::Active, required: true }
+    }
+    pub const fn passive_required() -> Self {
+        Self { kind: ComponentKind::Passive, required: true }
+    }
+    pub const fn active_optional() -> Self {
+        Self { kind: ComponentKind::Active, required: false }
+    }
+    pub const fn passive_optional() -> Self {
+        Self { kind: ComponentKind::Passive, required: false }
+    }
+}
+
+/// The result of the board's power-on checks, delivered inside [`Event::PowerGood`].
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum PowerOnResult {
+    /// Self-verified and provisioned.
+    Provisioned,
+    /// Self-verified but not provisioned — cannot act as a RoT.
+    Unprovisioned,
+    /// Self-verification failed — latches immediately to [`State::Locked`].
+    SelfVerificationFailed,
+}
+
+/// Everything the outside world can tell the state machine.
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum Event {
+    /// Power-on, carrying the shell's self-verification and provisioning result.
+    PowerGood(PowerOnResult),
+    VerificationPassed(ComponentId),
+    VerificationFailed(ComponentId),
+    /// An `Active` component's iRoT has finished local verification and is ready
+    /// (e.g. MCTP channel established).
+    ComponentReady(ComponentId),
+    AttestationChallenge,
+    UpdateRequest,
+    UpdateVerified,
+    UpdateRejected,
+    CorruptionDetected(ComponentId),
+    Restored(ComponentId),
+    RecoveryFailed,
+}
+
+/// Everything the state machine can ask the outside world to do.
+///
+/// [`Effect::Emit`] is the sole internal effect: the orchestrator catches it and
+/// queues the carried event for immediate handling, making follow-up events
+/// visible in the effect trace instead of hidden state changes.
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum Effect {
+    ReadFirmware(ComponentId),
+    VerifyFirmware(ComponentId),
+    ReleaseReset(ComponentId),
+    SignAttestation,
+    AuthenticateUpdate,
+    StageUpdate,
+    ActivateUpdate,
+    DiscardStaged,
+    RestoreGoldenImage(ComponentId),
+    LatchLockdown,
+    /// Internal only — tells the orchestrator to handle this event next.
+    /// Never forwarded to a [`Platform`].
+    Emit(Event),
+}
+
+/// The states the machine can be in. None carry data; all mutable state lives
+/// in [`Rot`] shared storage.
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum State {
+    PowerOnReset,
+    VerifyingPlatform,
+    /// eRoT has released an `Active` component; waiting for its iRoT to finish
+    /// local verification and signal [`Event::ComponentReady`].
+    AwaitingReady,
+    Ready,
+    Updating,
+    Recovering,
+    Locked,
+}
+
+/// Group state shared by the operational states.
+#[derive(Debug)]
+pub enum Superstate<'sub> {
+    Operational(PhantomData<&'sub ()>),
+}
+
+/// The effect buffer handed to every handler (statig's `Context`).
+///
+/// The only thing a handler can do to the outside world is call `emit`. The
+/// orchestrator gives each event a fresh `Sink` and drains it afterward.
+pub struct Sink {
+    effects: heapless::Vec<Effect, EFFECT_CAP>,
+}
+
+impl Sink {
+    fn new() -> Self {
+        Self {
+            effects: heapless::Vec::new(),
+        }
+    }
+
+    /// Append one effect. Overflow is silently dropped rather than panicking
+    /// (`no_std` safety); overflow means a logic bug.
+    pub fn emit(&mut self, effect: Effect) {
+        let _ = self.effects.push(effect);
+    }
+
+    pub fn effects(&self) -> &[Effect] {
+        &self.effects
+    }
+}
+
+/// Shared storage: data that persists across events. `N` is the chain capacity
+/// — a board choice; the core sets no default.
+pub struct Rot<const N: usize> {
+    chain: heapless::Vec<(ComponentId, ComponentAttrs), N>,
+    cursor: u8,
+    failed: Option<ComponentId>,
+    retry_count: u8,
+    max_retry: u8,
+    /// The `Active` component whose iRoT readiness is outstanding. `Some` only
+    /// while in `AwaitingReady` (INV9).
+    awaiting: Option<ComponentId>,
+}
+
+impl<const N: usize> Rot<N> {
+    pub fn new(chain: heapless::Vec<(ComponentId, ComponentAttrs), N>, max_retry: u8) -> Self {
+        Self {
+            chain,
+            cursor: 0,
+            failed: None,
+            retry_count: 0,
+            max_retry,
+            awaiting: None,
+        }
+    }
+}
+
+impl<const N: usize> IntoStateMachine for Rot<N> {
+    type Event<'evt> = Event;
+    type Context<'ctx> = Sink;
+    type State = State;
+    type Superstate<'sub> = Superstate<'sub>;
+
+    fn initial() -> State {
+        State::PowerOnReset
+    }
+}
+
+impl<const N: usize> StatigState<Rot<N>> for State {
+    fn call_handler(&mut self, rot: &mut Rot<N>, event: &Event, ctx: &mut Sink) -> Outcome<State> {
+        match self {
+            State::PowerOnReset => match event {
+                Event::PowerGood(PowerOnResult::Provisioned) => {
+                    Outcome::Transition(State::VerifyingPlatform)
+                }
+                Event::PowerGood(PowerOnResult::Unprovisioned) => {
+                    Outcome::Transition(State::Locked)
+                }
+                Event::PowerGood(PowerOnResult::SelfVerificationFailed) => {
+                    Outcome::Transition(State::Locked)
+                }
+                _ => Outcome::Super,
+            },
+
+            // Cursor walk via Outcome::Handled — a self-transition would reset cursor.
+            State::VerifyingPlatform => match event {
+                Event::VerificationPassed(id) => {
+                    ctx.emit(Effect::ReleaseReset(*id));
+                    let current_attrs = rot.chain[rot.cursor as usize].1;
+                    let next_idx = (rot.cursor as usize) + 1;
+                    if next_idx < rot.chain.len() {
+                        let (next_id, _) = rot.chain[next_idx];
+                        rot.cursor += 1;
+                        // Speculative: start next eRoT check while current Active iRoT boots.
+                        ctx.emit(Effect::ReadFirmware(next_id));
+                        ctx.emit(Effect::VerifyFirmware(next_id));
+                        match current_attrs.kind {
+                            ComponentKind::Active => {
+                                rot.awaiting = Some(*id);
+                                Outcome::Transition(State::AwaitingReady)
+                            }
+                            ComponentKind::Passive => Outcome::Handled,
+                        }
+                    } else {
+                        Outcome::Transition(State::Ready)
+                    }
+                }
+                Event::VerificationFailed(id) => {
+                    let attrs = rot.chain[rot.cursor as usize].1;
+                    if attrs.required {
+                        rot.failed = Some(*id);
+                        Outcome::Transition(State::Recovering)
+                    } else {
+                        // Optional: hold in reset, skip, advance walk.
+                        let next_idx = (rot.cursor as usize) + 1;
+                        rot.cursor += 1;
+                        if next_idx < rot.chain.len() {
+                            let (next_id, _) = rot.chain[next_idx];
+                            ctx.emit(Effect::ReadFirmware(next_id));
+                            ctx.emit(Effect::VerifyFirmware(next_id));
+                            Outcome::Handled
+                        } else {
+                            Outcome::Transition(State::Ready)
+                        }
+                    }
+                }
+                _ => Outcome::Super,
+            },
+
+            State::AwaitingReady => match event {
+                Event::ComponentReady(id) => {
+                    if rot.awaiting != Some(*id) {
+                        return Outcome::Handled; // spurious / stale (INV9)
+                    }
+                    rot.awaiting = None;
+                    // If cursor is past the end, the last component was skipped
+                    // (optional failure) — nothing left to verify, we're done.
+                    if (rot.cursor as usize) >= rot.chain.len() {
+                        Outcome::Transition(State::Ready)
+                    } else {
+                        Outcome::Handled
+                    }
+                }
+                Event::VerificationPassed(id) => {
+                    ctx.emit(Effect::ReleaseReset(*id));
+                    let next_idx = (rot.cursor as usize) + 1;
+                    if next_idx < rot.chain.len() {
+                        let (next_id, _) = rot.chain[next_idx];
+                        rot.cursor += 1;
+                        ctx.emit(Effect::ReadFirmware(next_id));
+                        ctx.emit(Effect::VerifyFirmware(next_id));
+                        Outcome::Handled
+                    } else {
+                        Outcome::Transition(State::Ready)
+                    }
+                }
+                Event::VerificationFailed(id) => {
+                    let attrs = rot.chain[rot.cursor as usize].1;
+                    if attrs.required {
+                        rot.failed = Some(*id);
+                        rot.awaiting = None;
+                        Outcome::Transition(State::Recovering)
+                    } else {
+                        // Optional: hold in reset, skip, advance walk.
+                        let next_idx = (rot.cursor as usize) + 1;
+                        rot.cursor += 1;
+                        if next_idx < rot.chain.len() {
+                            let (next_id, _) = rot.chain[next_idx];
+                            ctx.emit(Effect::ReadFirmware(next_id));
+                            ctx.emit(Effect::VerifyFirmware(next_id));
+                            Outcome::Handled
+                        } else if rot.awaiting.is_none() {
+                            // No iRoT gate pending — done.
+                            Outcome::Transition(State::Ready)
+                        } else {
+                            // Still waiting for ComponentReady; it will fire Ready.
+                            Outcome::Handled
+                        }
+                    }
+                }
+                _ => Outcome::Super,
+            },
+
+            State::Ready => match event {
+                Event::UpdateRequest => Outcome::Transition(State::Updating),
+                _ => Outcome::Super,
+            },
+
+            State::Updating => match event {
+                Event::UpdateVerified => {
+                    ctx.emit(Effect::ActivateUpdate);
+                    Outcome::Transition(State::Ready)
+                }
+                Event::UpdateRejected => {
+                    ctx.emit(Effect::DiscardStaged);
+                    Outcome::Transition(State::Ready)
+                }
+                _ => Outcome::Super,
+            },
+
+            State::Recovering => match event {
+                Event::Restored(_) => {
+                    rot.retry_count = rot.retry_count.saturating_add(1);
+                    if rot.retry_count >= rot.max_retry {
+                        ctx.emit(Effect::Emit(Event::RecoveryFailed));
+                        Outcome::Handled
+                    } else {
+                        Outcome::Transition(State::VerifyingPlatform)
+                    }
+                }
+                Event::RecoveryFailed => Outcome::Transition(State::Locked),
+                _ => Outcome::Super,
+            },
+
+            State::Locked => Outcome::Super,
+        }
+    }
+
+    fn call_entry_action(&mut self, rot: &mut Rot<N>, ctx: &mut Sink) {
+        match self {
+            State::VerifyingPlatform => {
+                rot.cursor = 0;
+                rot.awaiting = None;
+                if let Some(&(first_id, _)) = rot.chain.first() {
+                    ctx.emit(Effect::ReadFirmware(first_id));
+                    ctx.emit(Effect::VerifyFirmware(first_id));
+                }
+            }
+            State::Updating => {
+                ctx.emit(Effect::AuthenticateUpdate);
+                ctx.emit(Effect::StageUpdate);
+            }
+            State::Recovering => {
+                if let Some(failed) = rot.failed {
+                    ctx.emit(Effect::RestoreGoldenImage(failed));
+                }
+            }
+            State::Locked => {
+                ctx.emit(Effect::LatchLockdown);
+            }
+            State::Ready => {
+                rot.retry_count = 0;
+            }
+            _ => {}
+        }
+    }
+
+    fn superstate(&mut self) -> Option<Superstate<'_>> {
+        match self {
+            State::Ready | State::Updating | State::Recovering | State::AwaitingReady => {
+                Some(Superstate::Operational(PhantomData))
+            }
+            _ => None,
+        }
+    }
+}
+
+impl<const N: usize> StatigSuperstate<Rot<N>> for Superstate<'_> {
+    fn call_handler(&mut self, rot: &mut Rot<N>, event: &Event, ctx: &mut Sink) -> Outcome<State> {
+        match self {
+            Superstate::Operational(_) => match event {
+                Event::AttestationChallenge => {
+                    ctx.emit(Effect::SignAttestation);
+                    Outcome::Handled
+                }
+                Event::CorruptionDetected(id) => {
+                    rot.failed = Some(*id);
+                    Outcome::Transition(State::Recovering)
+                }
+                _ => Outcome::Super,
+            },
+        }
+    }
+}
+
+/// Outward connection to the platform. Carry out one effect. Never called with
+/// [`Effect::Emit`] — the orchestrator consumes those internally.
+pub trait Platform {
+    fn execute(&mut self, effect: Effect);
+}
+
+/// A handle for a caller's own event loop. Wraps the statig machine so callers
+/// only depend on this crate, never on statig types directly.
+pub struct Orchestrator<const N: usize> {
+    machine: StateMachine<Rot<N>>,
+}
+
+impl<const N: usize> Orchestrator<N> {
+    pub fn new(chain: heapless::Vec<(ComponentId, ComponentAttrs), N>, max_retry: u8) -> Self {
+        Self {
+            machine: Rot::new(chain, max_retry).state_machine(),
+        }
+    }
+
+    pub fn state(&self) -> State {
+        *self.machine.state()
+    }
+
+    /// Handle one event all the way through — including any [`Effect::Emit`]
+    /// follow-ups — calling `on_effect` for each external effect in order.
+    pub fn dispatch_with(&mut self, event: Event, mut on_effect: impl FnMut(Effect)) {
+        let mut pending: heapless::Vec<Event, PENDING_CAP> = heapless::Vec::new();
+        let _ = pending.push(event);
+
+        let mut i = 0;
+        while i < pending.len() {
+            let ev = pending[i];
+            i += 1;
+
+            let mut buf = Sink::new();
+            self.machine.handle_with_context(&ev, &mut buf);
+
+            for &effect in buf.effects() {
+                match effect {
+                    Effect::Emit(internal) => {
+                        let _ = pending.push(internal);
+                    }
+                    external => on_effect(external),
+                }
+            }
+        }
+    }
+
+    /// Same as [`dispatch_with`] but routes effects to a [`Platform`].
+    pub fn dispatch(&mut self, platform: &mut impl Platform, event: Event) {
+        self.dispatch_with(event, |effect| platform.execute(effect));
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    extern crate std;
+
+    use super::*;
+    use std::vec::Vec;
+
+    const C0: ComponentId = ComponentId::new(0);
+    const C1: ComponentId = ComponentId::new(1);
+    const C2: ComponentId = ComponentId::new(2);
+
+    const BOOT: Event = Event::PowerGood(PowerOnResult::Provisioned);
+
+    const CAPACITY: usize = 8;
+    const MAX_RETRY: u8 = 3;
+
+    fn chain(
+        ids: &[(ComponentId, ComponentAttrs)],
+    ) -> heapless::Vec<(ComponentId, ComponentAttrs), CAPACITY> {
+        let mut c = heapless::Vec::new();
+        for &entry in ids {
+            c.push(entry).expect("chain within CAPACITY");
+        }
+        c
+    }
+
+    fn passive_required(
+        ids: &[ComponentId],
+    ) -> heapless::Vec<(ComponentId, ComponentAttrs), CAPACITY> {
+        chain(
+            &ids.iter()
+                .map(|&id| (id, ComponentAttrs::passive_required()))
+                .collect::<std::vec::Vec<_>>(),
+        )
+    }
+
+    struct Recorder {
+        recorded: Vec<Effect>,
+    }
+
+    impl Recorder {
+        fn new() -> Self {
+            Self { recorded: Vec::new() }
+        }
+    }
+
+    impl Platform for Recorder {
+        fn execute(&mut self, effect: Effect) {
+            self.recorded.push(effect);
+        }
+    }
+
+    fn drive(
+        chain: heapless::Vec<(ComponentId, ComponentAttrs), CAPACITY>,
+        script: &[Event],
+    ) -> (Vec<Effect>, State) {
+        let mut orch = Orchestrator::new(chain, MAX_RETRY);
+        let mut platform = Recorder::new();
+        for &event in script {
+            orch.dispatch(&mut platform, event);
+        }
+        (platform.recorded, orch.state())
+    }
+
+    /// INV1/INV2/INV3: provisioned power-on walks the chain in order; no
+    /// component is released before its eRoT-side verification passes.
+    #[test]
+    fn cold_boot_walks_chain_in_order() {
+        let (effects, state) = drive(
+            passive_required(&[C0, C1]),
+            &[BOOT, Event::VerificationPassed(C0), Event::VerificationPassed(C1)],
+        );
+        assert_eq!(
+            effects,
+            std::vec![
+                Effect::ReadFirmware(C0),
+                Effect::VerifyFirmware(C0),
+                Effect::ReleaseReset(C0),
+                Effect::ReadFirmware(C1),
+                Effect::VerifyFirmware(C1),
+                Effect::ReleaseReset(C1),
+            ],
+        );
+        assert_eq!(state, State::Ready);
+    }
+
+    /// Unprovisioned power-on latches immediately.
+    #[test]
+    fn unprovisioned_boot_locks_down() {
+        let (effects, state) = drive(
+            passive_required(&[C0]),
+            &[Event::PowerGood(PowerOnResult::Unprovisioned)],
+        );
+        assert_eq!(effects, std::vec![Effect::LatchLockdown]);
+        assert_eq!(state, State::Locked);
+    }
+
+    /// INV11: SelfVerificationFailed latches immediately without entering
+    /// VerifyingPlatform.
+    #[test]
+    fn self_verification_failure_latches_immediately() {
+        let (effects, state) = drive(
+            passive_required(&[C0]),
+            &[Event::PowerGood(PowerOnResult::SelfVerificationFailed)],
+        );
+        assert_eq!(effects, std::vec![Effect::LatchLockdown]);
+        assert_eq!(state, State::Locked);
+    }
+
+    /// INV6: AttestationChallenge is answerable from every Operational state.
+    #[test]
+    fn attestation_shared_across_operational_states() {
+        let (effects, state) = drive(
+            passive_required(&[C0]),
+            &[BOOT, Event::VerificationPassed(C0), Event::AttestationChallenge],
+        );
+        assert_eq!(effects.last(), Some(&Effect::SignAttestation));
+        assert_eq!(state, State::Ready);
+
+        let (effects, state) = drive(
+            passive_required(&[C0]),
+            &[
+                BOOT,
+                Event::VerificationPassed(C0),
+                Event::UpdateRequest,
+                Event::AttestationChallenge,
+            ],
+        );
+        assert_eq!(effects.last(), Some(&Effect::SignAttestation));
+        assert_eq!(state, State::Updating);
+    }
+
+    /// INV4: a rejected update rolls back via DiscardStaged and never enters
+    /// Recovering.
+    #[test]
+    fn update_rollback_is_not_recovery() {
+        let (effects, state) = drive(
+            passive_required(&[C0]),
+            &[
+                BOOT,
+                Event::VerificationPassed(C0),
+                Event::UpdateRequest,
+                Event::UpdateRejected,
+            ],
+        );
+        let tail = &effects[effects.len() - 3..];
+        assert_eq!(
+            tail,
+            &[Effect::AuthenticateUpdate, Effect::StageUpdate, Effect::DiscardStaged],
+        );
+        assert_eq!(state, State::Ready);
+        assert!(!effects.contains(&Effect::LatchLockdown));
+    }
+
+    /// INV5: runtime corruption targets the named component and re-walks from
+    /// the top after restore.
+    #[test]
+    fn runtime_corruption_targets_component_and_rewalks() {
+        let (effects, state) = drive(
+            passive_required(&[C0, C1]),
+            &[
+                BOOT,
+                Event::VerificationPassed(C0),
+                Event::VerificationPassed(C1),
+                Event::CorruptionDetected(C1),
+                Event::Restored(C1),
+            ],
+        );
+        let tail = &effects[effects.len() - 2..];
+        assert_eq!(tail, &[Effect::ReadFirmware(C0), Effect::VerifyFirmware(C0)]);
+        assert_eq!(state, State::VerifyingPlatform);
+    }
+
+    /// INV7 (feedback-as-data): after MAX_RETRY restores the core self-emits
+    /// RecoveryFailed and latches to Locked without any external RecoveryFailed
+    /// in the script.
+    #[test]
+    fn retry_cap_self_latches_via_emit() {
+        let mut script = std::vec![BOOT, Event::VerificationPassed(C0)];
+        script.push(Event::CorruptionDetected(C0));
+        for _ in 0..(MAX_RETRY - 1) {
+            script.push(Event::Restored(C0));
+            script.push(Event::VerificationFailed(C0));
+        }
+        script.push(Event::Restored(C0));
+
+        let (effects, state) = drive(passive_required(&[C0]), &script);
+
+        assert!(!script.contains(&Event::RecoveryFailed));
+        assert_eq!(state, State::Locked);
+        assert_eq!(effects.last(), Some(&Effect::LatchLockdown));
+    }
+
+    /// INV7: retry count resets after a successful recovery so a later episode
+    /// starts from zero.
+    #[test]
+    fn retry_count_resets_after_successful_recovery() {
+        let mut c = heapless::Vec::<(ComponentId, ComponentAttrs), CAPACITY>::new();
+        c.push((C0, ComponentAttrs::passive_required())).expect("fits");
+        let mut orch = Orchestrator::new(c, 2);
+        let mut effects = Vec::new();
+
+        for ev in [
+            BOOT,
+            Event::VerificationPassed(C0),
+            Event::CorruptionDetected(C0),
+            Event::Restored(C0),
+            Event::VerificationPassed(C0),
+        ] {
+            orch.dispatch_with(ev, |e| effects.push(e));
+        }
+        assert_eq!(orch.state(), State::Ready);
+
+        let start = effects.len();
+        for ev in [
+            Event::CorruptionDetected(C0),
+            Event::Restored(C0),
+            Event::VerificationPassed(C0),
+        ] {
+            orch.dispatch_with(ev, |e| effects.push(e));
+        }
+        assert_eq!(orch.state(), State::Ready);
+        assert!(!effects[start..].contains(&Effect::LatchLockdown));
+    }
+
+    /// Board-supplied retry cap: max_retry = 1 latches on the first failed
+    /// restore.
+    #[test]
+    fn custom_retry_cap_latches_sooner() {
+        let mut c = heapless::Vec::<(ComponentId, ComponentAttrs), CAPACITY>::new();
+        c.push((C0, ComponentAttrs::passive_required())).expect("fits");
+        let mut orch = Orchestrator::new(c, 1);
+        let mut effects = Vec::new();
+        for ev in [
+            BOOT,
+            Event::VerificationPassed(C0),
+            Event::CorruptionDetected(C0),
+            Event::Restored(C0),
+        ] {
+            orch.dispatch_with(ev, |e| effects.push(e));
+        }
+        assert_eq!(orch.state(), State::Locked);
+        assert_eq!(effects.last(), Some(&Effect::LatchLockdown));
+    }
+
+    /// Three-component chain uses N=3; walks all three to Ready.
+    #[test]
+    fn custom_capacity_walks_full_chain() {
+        let mut c = heapless::Vec::<(ComponentId, ComponentAttrs), 3>::new();
+        for &id in &[C0, C1, C2] {
+            c.push((id, ComponentAttrs::passive_required())).expect("3 fits");
+        }
+        let mut orch = Orchestrator::new(c, MAX_RETRY);
+        let mut effects = Vec::new();
+        for ev in [
+            BOOT,
+            Event::VerificationPassed(C0),
+            Event::VerificationPassed(C1),
+            Event::VerificationPassed(C2),
+        ] {
+            orch.dispatch_with(ev, |e| effects.push(e));
+        }
+        assert_eq!(orch.state(), State::Ready);
+        assert_eq!(effects.last(), Some(&Effect::ReleaseReset(C2)));
+    }
+
+    /// INV10: Active component gates the chain walk — cursor does not advance
+    /// until ComponentReady arrives.
+    #[test]
+    fn active_component_gates_on_component_ready() {
+        let (effects, state) = drive(
+            chain(&[
+                (C0, ComponentAttrs::active_required()),
+                (C1, ComponentAttrs::passive_required()),
+            ]),
+            &[BOOT, Event::VerificationPassed(C0)],
+        );
+        assert_eq!(state, State::AwaitingReady);
+        assert!(effects.contains(&Effect::ReleaseReset(C0)));
+        assert!(effects.contains(&Effect::ReadFirmware(C1)));
+
+        let (effects2, state2) = drive(
+            chain(&[
+                (C0, ComponentAttrs::active_required()),
+                (C1, ComponentAttrs::passive_required()),
+            ]),
+            &[
+                BOOT,
+                Event::VerificationPassed(C0),
+                Event::ComponentReady(C0),
+                Event::VerificationPassed(C1),
+            ],
+        );
+        assert_eq!(state2, State::Ready);
+        assert!(effects2.contains(&Effect::ReleaseReset(C1)));
+    }
+
+    /// INV9: a ComponentReady for the wrong id is silently ignored.
+    #[test]
+    fn spurious_component_ready_is_ignored() {
+        let (effects, state) = drive(
+            chain(&[
+                (C0, ComponentAttrs::active_required()),
+                (C1, ComponentAttrs::passive_required()),
+            ]),
+            &[
+                BOOT,
+                Event::VerificationPassed(C0),
+                Event::ComponentReady(C1), // wrong id
+            ],
+        );
+        assert_eq!(state, State::AwaitingReady);
+        assert!(!effects.contains(&Effect::ReleaseReset(C1)));
+    }
+
+    /// INV12: AttestationChallenge is handled in AwaitingReady.
+    #[test]
+    fn attestation_in_awaiting_ready() {
+        let (effects, state) = drive(
+            chain(&[
+                (C0, ComponentAttrs::active_required()),
+                (C1, ComponentAttrs::passive_required()),
+            ]),
+            &[BOOT, Event::VerificationPassed(C0), Event::AttestationChallenge],
+        );
+        assert_eq!(state, State::AwaitingReady);
+        assert_eq!(effects.last(), Some(&Effect::SignAttestation));
+    }
+
+    /// Optional component: VerificationFailed skips it (held in reset), chain
+    /// continues to Ready.
+    #[test]
+    fn optional_component_failure_skips_and_continues() {
+        let (effects, state) = drive(
+            chain(&[
+                (C0, ComponentAttrs::passive_required()),
+                (C1, ComponentAttrs::passive_optional()),
+            ]),
+            &[BOOT, Event::VerificationPassed(C0), Event::VerificationFailed(C1)],
+        );
+        assert_eq!(state, State::Ready);
+        // C1 must never be released.
+        assert!(!effects.contains(&Effect::ReleaseReset(C1)));
+        // No recovery triggered.
+        assert!(!effects.contains(&Effect::RestoreGoldenImage(C1)));
+        assert!(!effects.contains(&Effect::LatchLockdown));
+    }
+
+    /// Optional Active component failure in AwaitingReady: skipped, held in
+    /// reset, chain reaches Ready once ComponentReady clears awaiting.
+    #[test]
+    fn optional_active_failure_in_awaiting_ready_skips() {
+        // C0 Active required, C1 Active optional.
+        let (effects, state) = drive(
+            chain(&[
+                (C0, ComponentAttrs::active_required()),
+                (C1, ComponentAttrs::active_optional()),
+            ]),
+            &[
+                BOOT,
+                Event::VerificationPassed(C0),   // → AwaitingReady; spec ReadFirmware(C1)
+                Event::VerificationFailed(C1),   // optional → skip C1
+                Event::ComponentReady(C0),        // iRoT gate clears; cursor past end → Ready
+            ],
+        );
+        assert_eq!(state, State::Ready);
+        assert!(!effects.contains(&Effect::ReleaseReset(C1)));
+        assert!(!effects.contains(&Effect::RestoreGoldenImage(C1)));
+    }
+}
diff --git a/third_party/crates_io/Cargo.lock b/third_party/crates_io/Cargo.lock
index 3af825c..f99ec31 100644
--- a/third_party/crates_io/Cargo.lock
+++ b/third_party/crates_io/Cargo.lock
@@ -158,14 +158,14 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.117",
+ "syn 2.0.119",
 ]
 
 [[package]]
 name = "bitflags"
-version = "2.12.1"
+version = "2.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a"
+checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
 
 [[package]]
 name = "block-buffer"
@@ -184,9 +184,9 @@
 
 [[package]]
 name = "bytes"
-version = "1.11.1"
+version = "1.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
+checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
 
 [[package]]
 name = "cfg-if"
@@ -206,9 +206,9 @@
 
 [[package]]
 name = "clap"
-version = "4.6.1"
+version = "4.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
+checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -216,9 +216,9 @@
 
 [[package]]
 name = "clap_builder"
-version = "4.6.0"
+version = "4.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
+checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b"
 dependencies = [
  "anstream",
  "anstyle",
@@ -236,7 +236,7 @@
  "heck",
  "proc-macro2",
  "quote",
- "syn 2.0.117",
+ "syn 2.0.119",
 ]
 
 [[package]]
@@ -293,7 +293,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.117",
+ "syn 2.0.119",
 ]
 
 [[package]]
@@ -603,7 +603,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.117",
+ "syn 2.0.119",
 ]
 
 [[package]]
@@ -822,24 +822,24 @@
 
 [[package]]
 name = "log"
-version = "0.4.31"
+version = "0.4.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f"
+checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
 
 [[package]]
 name = "mctp"
 version = "0.2.0"
-source = "git+https://github.com/CodeConstruct/mctp-rs.git?branch=main#b134e145f93d634dff7eb9f2a01559273c687365"
+source = "git+https://github.com/CodeConstruct/mctp-rs.git?branch=main#574e3a9889fe09954f9e08b10c72e7ea9e156dd0"
 
 [[package]]
 name = "mctp"
 version = "0.2.0"
-source = "git+https://github.com/CodeConstruct/mctp-rs.git#b134e145f93d634dff7eb9f2a01559273c687365"
+source = "git+https://github.com/CodeConstruct/mctp-rs.git#574e3a9889fe09954f9e08b10c72e7ea9e156dd0"
 
 [[package]]
 name = "mctp-estack"
 version = "0.1.0"
-source = "git+https://github.com/CodeConstruct/mctp-rs.git?branch=main#b134e145f93d634dff7eb9f2a01559273c687365"
+source = "git+https://github.com/CodeConstruct/mctp-rs.git?branch=main#574e3a9889fe09954f9e08b10c72e7ea9e156dd0"
 dependencies = [
  "crc",
  "embedded-io",
@@ -862,9 +862,9 @@
 
 [[package]]
 name = "memchr"
-version = "2.8.1"
+version = "2.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
 
 [[package]]
 name = "memo-map"
@@ -883,9 +883,9 @@
 
 [[package]]
 name = "minijinja"
-version = "2.20.0"
+version = "2.21.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2929e494b2280e1e18959bb2e121da03347ae896896fdfaceaab43c88a02803f"
+checksum = "cb3d648e68cea56d9858d535ee28f9538404e2dd8cb08ed0bd05dca379477f39"
 dependencies = [
  "memo-map",
  "serde",
@@ -909,9 +909,9 @@
 
 [[package]]
 name = "mio"
-version = "1.2.1"
+version = "1.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
+checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
 dependencies = [
  "libc",
  "wasi",
@@ -1042,9 +1042,9 @@
 
 [[package]]
 name = "pest"
-version = "2.8.6"
+version = "2.8.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662"
+checksum = "47627dd7305c6a2d6c8c6bcd24c5a4c17dbbf425f4f9c5313e724b38fc9782e9"
 dependencies = [
  "memchr",
  "ucd-trie",
@@ -1052,9 +1052,9 @@
 
 [[package]]
 name = "pest_derive"
-version = "2.8.6"
+version = "2.8.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77"
+checksum = "4b4254325ecad416ab689e27ba51da03ba01a9632bc6e108f5fe7c3c4ad29d58"
 dependencies = [
  "pest",
  "pest_generator",
@@ -1062,25 +1062,24 @@
 
 [[package]]
 name = "pest_generator"
-version = "2.8.6"
+version = "2.8.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f"
+checksum = "6c4c0e91ead7a8f7acecbca6f003fc2e8282b1dbe2dd9c9d2f16aba42995e0a7"
 dependencies = [
  "pest",
  "pest_meta",
  "proc-macro2",
  "quote",
- "syn 2.0.117",
+ "syn 2.0.119",
 ]
 
 [[package]]
 name = "pest_meta"
-version = "2.8.6"
+version = "2.8.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220"
+checksum = "f9744bc48116fee06334924bb5f2bad41eed5e89bd26e29b0b799f9a3f82c210"
 dependencies = [
  "pest",
- "sha2",
 ]
 
 [[package]]
@@ -1139,14 +1138,14 @@
  "itertools",
  "proc-macro2",
  "quote",
- "syn 2.0.117",
+ "syn 2.0.119",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.45"
+version = "1.0.46"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
+checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
 dependencies = [
  "proc-macro2",
 ]
@@ -1226,7 +1225,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.117",
+ "syn 2.0.119",
 ]
 
 [[package]]
@@ -1237,7 +1236,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.117",
+ "syn 2.0.119",
 ]
 
 [[package]]
@@ -1264,7 +1263,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.117",
+ "syn 2.0.119",
 ]
 
 [[package]]
@@ -1340,9 +1339,10 @@
  "sha3",
  "smlang",
  "spdm-lib",
+ "statig",
  "subtle",
  "syn 1.0.109",
- "syn 2.0.117",
+ "syn 2.0.119",
  "thiserror",
  "tock-registers",
  "tokio",
@@ -1354,9 +1354,9 @@
 
 [[package]]
 name = "rustc-demangle"
-version = "0.1.27"
+version = "0.1.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
+checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb"
 
 [[package]]
 name = "rustc_version"
@@ -1450,7 +1450,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.117",
+ "syn 2.0.119",
 ]
 
 [[package]]
@@ -1507,9 +1507,9 @@
 
 [[package]]
 name = "simd-adler32"
-version = "0.3.9"
+version = "0.3.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
+checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
 
 [[package]]
 name = "slab"
@@ -1519,9 +1519,9 @@
 
 [[package]]
 name = "smallvec"
-version = "1.15.1"
+version = "1.15.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
 
 [[package]]
 name = "smbus-pec"
@@ -1555,9 +1555,9 @@
 
 [[package]]
 name = "socket2"
-version = "0.6.4"
+version = "0.6.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
+checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
 dependencies = [
  "libc",
  "windows-sys",
@@ -1579,6 +1579,12 @@
 checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
 
 [[package]]
+name = "statig"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03c04b4a9f2d66294d63bdd8df834caad9f8e181997c3cf766b6b4f6d12d4fbc"
+
+[[package]]
 name = "string_morph"
 version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1609,9 +1615,9 @@
 
 [[package]]
 name = "syn"
-version = "2.0.117"
+version = "2.0.119"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
+checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1645,7 +1651,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.117",
+ "syn 2.0.119",
 ]
 
 [[package]]
@@ -1655,9 +1661,9 @@
 
 [[package]]
 name = "tokio"
-version = "1.52.3"
+version = "1.53.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
+checksum = "d988bcd52dbe076d3d46903332f58c912b87a2c49b1428419a5845154762ffee"
 dependencies = [
  "bytes",
  "libc",
@@ -1672,13 +1678,13 @@
 
 [[package]]
 name = "tokio-macros"
-version = "2.7.0"
+version = "2.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
+checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.117",
+ "syn 2.0.119",
 ]
 
 [[package]]
@@ -1736,9 +1742,9 @@
 
 [[package]]
 name = "uuid"
-version = "1.23.2"
+version = "1.24.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7"
+checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239"
 
 [[package]]
 name = "vcell"
@@ -1790,40 +1796,40 @@
 
 [[package]]
 name = "zerocopy"
-version = "0.8.50"
+version = "0.8.54"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1"
+checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19"
 dependencies = [
  "zerocopy-derive",
 ]
 
 [[package]]
 name = "zerocopy-derive"
-version = "0.8.50"
+version = "0.8.54"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639"
+checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.117",
+ "syn 2.0.119",
 ]
 
 [[package]]
 name = "zeroize"
-version = "1.8.2"
+version = "1.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
+checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
 dependencies = [
  "zeroize_derive",
 ]
 
 [[package]]
 name = "zeroize_derive"
-version = "1.4.3"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
+checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.117",
+ "syn 2.0.119",
 ]
diff --git a/third_party/crates_io/Cargo.toml b/third_party/crates_io/Cargo.toml
index 7833db3..20f3b80 100644
--- a/third_party/crates_io/Cargo.toml
+++ b/third_party/crates_io/Cargo.toml
@@ -32,6 +32,7 @@
 serde = { version = "1.0.219", default-features = false, features = ["derive"] }
 serde_derive = "1.0.228"
 smlang = { version = "0.8.0", default-features = false }
+statig = { version = "0.4.1", default-features = false }
 syn = { version = "2.0.104", features = ["full", "extra-traits"] }
 syn1 = { package = "syn", version = "1.0.109", features = ["full", "extra-traits"] }
 tock-registers = "0.9.0"