`sandbox-build` sealed every command step as `Tier::S5`. The step is run by
`ProcessVmm::new`, which is a bare `Command::spawn` — no VMM, no guest kernel,
no dedicated vCPU, and no `/dev/kvm` check anywhere on the path. The rung it
actually reaches is `S0` ("process only; trusted code"), the floor. Build
receipts were therefore claiming hardware isolation for work that ran as an
ordinary child process on the host.
SPEC already forbids this — "A host MUST NOT seal a tier a run did not execute
at" — so this is a conformance fix, not a change of meaning.
The fix is structural rather than a `/dev/kvm` check bolted onto the one call
site, because the bug's shape is "the caller asserts a tier". Now the executor
reports one:
- `Vmm::attained_tier()` defaults to `Tier::S0`. The default is the floor on
purpose: an implementor who forgets to override under-claims, which is
survivable, instead of over-claiming, which makes a receipt a lie.
- `ProcessVmm` is genuinely dual-use — supervising `firecracker` really is S5,
supervising `cargo build` is not — and that cannot be inferred from a program
name. It is now declared: `new` is S0, `supervising_vmm` may claim S5, and
even then only where the host exposes hardware virtualization.
- `MicroVmExecutor::tier()` delegates to its VMM instead of returning a static
S5, so `Host::run_and_seal`'s executor-vs-required cross-check can no longer
be passed by a run that never touched a hypervisor.
- `sandbox-proof::prove_microvm` supervises the real firecracker binary behind
an existing `/dev/kvm` guard, so it declares `supervising_vmm` and keeps S5.
Verified: 188 workspace tests pass, conformance passes with no skips (C5
tier.json included). The one test that changed is the one that encoded the bug
in its name.