Phase 8 C (1/n): QEMU harness now preserves NVRAM across runs

Scoping Phase C (the MINT word) surfaced a real blocker: "mint one
Zuse, ever" needs the cert to survive reboots, but the qemu target
copied a fresh, pristine OVMF_VARS.fd on every invocation -- a
UEFI-NVRAM-based cert would never persist under this project's own
normal test workflow. Digging further, aarch64 had no persistent NVRAM
store at all (single -bios arg, no split VARS pflash like amd64/riscv64).

Fixed rather than switching storage substrates: amd64/riscv64 now only
copy the VARS template if the destination doesn't already exist, so
`clean` (which deletes the whole build tree) is the bleach step and a
bare `make qemu` preserves NVRAM -- matching the existing "always clean
before qemu" acceptance convention exactly. aarch64 restructured to
split CODE(ro)/VARS(rw) pflash drives matching the other two, with a
graceful fallback to the old -bios mode on hosts without split firmware.

Also resolves two design questions before any cert code: Zuse doesn't
need Milestone 6's CA (that's the capsule-signing chain, a separate
trust domain -- Zuse is a self-sovereign instance-local root of trust),
and flags that this session's own earlier vm_zuse_cert_install() storage
(16 bytes) is too small for a real Ed25519 keypair.

Verified: all three architectures boot clean to ok> with the new pflash
arrangement, Stadium conservation intact, no panics or guest errors.
Infrastructure-only -- no cert code yet. Documented in FABRIC-3.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
This commit is contained in:
Robert Allan James
2026-08-26 15:18:15 -04:00
co-authored by Claude Sonnet 5
parent 53e6c5709f
commit f223a31cec
7 changed files with 27186 additions and 4 deletions
+43
View File
@@ -432,6 +432,49 @@ decisions get added here, not to `FABRIC-2.md`. Follow the same discipline `FABR
keypair needs to chain to the Milestone 6 offline root CA or is a self-sovereign
instance-local root of trust.
**Phase C scoping, 2026-08-26.** Three findings before any code:
1. **Resolved, not a real conflict: Zuse doesn't need Milestone 6's CA.** That CA chain is
specifically for *capsule/code signing* (root → snakeoil intermediate → per-capsule
Ed25519 signatures verified at capsule-load time) — a different trust domain from *user
identity*. The vision's own framing ("we mint one and only one Zuse user and blow a fuse
... the only way around is a new system") already implies Zuse's authority comes from
being the unique first-boot mint on *this instance*, not from an external chain.
**Decided: Zuse is a self-sovereign, instance-local root of trust**, keypair generated
on-device from real entropy (Phase A+B). Regular users, minted later via `MINT`, get
certs signed by *Zuse's* key, not the Milestone 6 CA — two independent PKI domains.
2. **A real gap in this session's own earlier work:** `vm_zuse_cert_install(vm, lo, hi)`
(the very first change this session made, before Phase A existed) only holds two
`uint64_t` (16 bytes) — sized against the old placeholder `ZUSE-CERT-LO`/`HI` FORTH-cell
design, not against what a real Ed25519 keypair needs (32-byte pubkey alone, well over
100 bytes for a full cert). Needs expanding before Phase C can store anything real.
3. **A genuine blocker, found by asking where the cert would actually live:** "mint once,
ever" requires surviving reboots, but `Makefile.starkernel`'s `qemu` target copied a
fresh, pristine `OVMF_VARS.fd` on *every* invocation (not just after `clean`) — so a
UEFI-NVRAM-based cert (the real-hardware-compatible option, and this codebase already has
a live `SetVariable`/`GetVariable` precedent via `SF_VAR_REBOOT_TRIES`/`SF_VAR_BOOT_ARGS`)
would never actually persist under this project's own normal test workflow. Digging
further: aarch64's `qemu` recipe had no persistent NVRAM store *at all* — a single
combined `-bios $AAVMF_CODE` argument, no separate writable VARS pflash drive like
amd64/riscv64 have. **Decided (on request): fix the harness rather than switch
substrates.** amd64/riscv64: the VARS-template copy is now conditional on the destination
not already existing, so `clean` (which deletes the whole `build/$(ARCH)/kernel` tree,
`OVMF_VARS.fd`/`RISCV_VARS.fd` included) is the bleach step, and a bare `make qemu` now
preserves NVRAM across runs — exactly matching the existing "always pass `clean` before
`qemu`" acceptance convention, no new bleach script needed. aarch64: restructured to
split CODE(ro)/VARS(rw) pflash drives matching the other two (host has
`/usr/share/AAVMF/AAVMF_VARS.fd` alongside the existing `AAVMF_CODE.fd`), with a
graceful fallback to the old single-`-bios` mode (and a console note) on a host that only
has non-split firmware packaged, so this doesn't regress environments without one.
**Verified live:** all three architectures still boot clean to `ok>` with the new
pflash arrangement, Stadium conservation intact, no panics or guest errors — this is
infrastructure-only (no cert code yet), so a plain boot-regression check is the right
verification tier.
**Still open:** the actual cert struct (expanding past the 16-byte placeholder), the
first-boot mint-vs-already-minted boot sequence using `SetVariable`/`GetVariable`, and the
`MINT` word itself.
### From FABRIC-2.md §X, Milestone 5 — Console/VM key-match binding
- [ ] Settle the still-open question: reuse `ACL-PIN`/`acl_allow` directly, or build a