Skip to content

Track C: wohl-ota — verified dual-bank OTA state machine#11

Merged
avrabe merged 6 commits into
mainfrom
phase2/track-c-wohl-ota
May 20, 2026
Merged

Track C: wohl-ota — verified dual-bank OTA state machine#11
avrabe merged 6 commits into
mainfrom
phase2/track-c-wohl-ota

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented May 20, 2026

Summary

Phase 2 Track C. New crates/wohl-ota — the no_std core for over-the-air firmware update. Design-before-hardware: the state machine is proven correct in software first, then bolted onto a board later.

What's in it

Dual-bank update state machine over two slots (A/B), one Active, one Standby:

OtaState ::= Idle
           | Downloading { slot, bytes_received, total_bytes }
           | Verifying  { slot }
           | Ready      { slot }
           | Swapping
  • OtaManifest (version, size, sha256, Ed25519 signature)
  • Signature verification via a SignatureVerifier trait — no concrete crypto dependency; AlwaysAccept/AlwaysReject stubs for tests + Kani. Real Ed25519 is a later, isolated change.
  • no_std, no_alloc — fixed arrays, no Vec.

Verification

  • 4 Kani harnesses, all PASS (112 internal checks):
    • OTA-P01: Active slot is never a download destination
    • OTA-P02: swap only succeeds from Ready
    • OTA-P03: bytes_received never exceeds total_bytes
    • OTA-P04: no panic for any call sequence
  • 11 unit tests + 4 proptests pass
  • cargo +1.85.0 clippy --all-targets -- -D warnings + fmt clean

Open design questions (for reviewers)

  • SignatureVerifier::verify takes the parsed &OtaManifest — should it take canonical &[u8] instead, to decouple the trait from the struct?
  • Rollback currently flips active from Idle with no post-swap watchdog window — add a rollback-budget state?
  • Swapping is modelled as instantaneous; real hardware has a power-loss window — add begin_swap/commit_swap for durability proofs?

Radio-agnostic — independent of the transport decisions (see #10).

🤖 Generated with Claude Code

avrabe and others added 6 commits May 20, 2026 07:49
New no_std crate at crates/wohl-ota/ providing the firmware-update
state machine for Wohl sensor nodes. Pure software, hardware-free.

- enum OtaState { Idle, Downloading, Verifying, Ready, Swapping }
- Dual-bank slot model (A/B) with structural guarantee that downloads
  always target the standby slot
- SignatureVerifier trait keeps the core crypto-free; AlwaysAccept /
  AlwaysReject stubs drive tests and BMC harnesses
- Kani harnesses for OTA-P01..P04: target never active, swap only
  from Ready, byte counter bounded, no panic on any 4-op sequence
- 11 unit tests + 4 proptest properties

Backs the NodeBootloader / OTAManager / OTAManagerProcess components
declared in spar/wohl_firmware.aadl lines 89-115, 186-200.

Not yet wired into the workspace — see WORKSPACE_INTEGRATION.md in
the next commit for the orchestrator-side edits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Documents the two-line edit to the root Cargo.toml needed to enrol
wohl-ota into the workspace, plus the optional [workspace.dependencies]
entry and the CI-matrix follow-up. Kept out of the crate commit so the
orchestrator can apply workspace changes atomically with their own CI
update.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Applies the WORKSPACE_INTEGRATION.md handoff (that file is now removed):
- root Cargo.toml: add crates/wohl-ota to workspace members
- [workspace.dependencies]: add wohl-ota path entry for future consumers

Verified from the worktree:
  cargo test -p wohl-ota                                          15/15
  cargo +1.85.0 clippy -p wohl-ota --all-targets -- -D warnings    clean
  cargo +1.85.0 fmt    -p wohl-ota -- --check                      clean

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
wohl-ota ships 4 Kani harnesses (OTA-P01..P04). The CI kani job had a
hardcoded component list that didn't include the new crate, so its
proofs would not have been gated on merge. Added wohl-ota to the loop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixes two blocking review gaps in the verified OTA core.

Issue 1: verify() never compared the downloaded image against
manifest.sha256, so a correctly-signed manifest with a corrupt
payload could reach Ready. verify() now also takes the caller-
computed image digest and requires a byte-for-byte [u8;32] match
(core stays crypto-free); add OtaError::BadImageDigest and Kani
harness OTA-P05.

Issue 2: rollback() flipped the active slot from any Idle state,
even on fresh boot or twice after one swap. Track last_swap:
Option<Slot>; rollback only reverts a recorded pre-swap slot,
confirm_swap() commits the update, and a guardless second
rollback fails cleanly with OtaError::NothingToRollback. Add
Kani harness OTA-P06.

OTA-P01..P04 still pass; P04 unwind raised 5->33 for the new
32-byte digest memcmp.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The kani job name said "all 6 components" but the loop now covers 7
(wohl-ota was added). Use "all components" so the name doesn't drift
as more crates gain harnesses.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@avrabe avrabe force-pushed the phase2/track-c-wohl-ota branch from 9aa0562 to 3361785 Compare May 20, 2026 05:54
@avrabe avrabe merged commit 6ad1faa into main May 20, 2026
5 checks passed
@avrabe avrabe deleted the phase2/track-c-wohl-ota branch May 20, 2026 06:00
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