matrix — reasoning
Class:
reasoning. Why the machine + matrix layer is shaped this way. The descriptive shape is inmatrix-design.md. Absorbs Part C of the formerinstall-tree-design.md.
Why boot a real machine to test an install
§B's unit tests prove a renderer produced a plausible string. They cannot prove that the string, written into a real Alpine or Debian guest, actually brought the service up under that guest's real init/PID1. Only a real machine has a real systemd/OpenRC, a real netns, a real /proc. So the matrix boots an actual KVM guest per cell and asserts "installed + started + healthy" from inside it — the difference between "the renderer looks right" and "the service is running".
Why a machine-injected probe instead of general guest-exec
Asserting install success needs to read the guest's state (is the unit active? what PID? is the port open? does /health answer?). A general "run arbitrary commands in the guest" channel (qemu-guest-agent exec, or an ssh key seeded via cloud-init) is heavier and, for Isolated guests, awkward. Instead we inject one tiny static probe that knows how to introspect the service it was shipped with and emits a single typed ProbeReport line. This sidesteps the need for arbitrary in-guest exec for the assert step: the guest self-installs (cloud-init) and self-reports (the probe). The probe converts "did the install work?" from a human eyeballing journalctl into a typed row the matrix asserts on.
Why virtio-serial write-back, not the network
A true airgap test runs the guest under NetMode::Isolated — no NIC at all. So the report cannot come back over the network. The probe writes its JSON to a virtio-serial port (/dev/virtio-ports/org.skidbladnir.report) that the host reads as a plain file — works with zero egress. This is the airgap-correct channel: the same isolation the real deployment runs under, in the test. The rejected alternatives (a shared network, an ssh tunnel) would either break isolation or fail to model it.
Why pure-Rust FAT images, not genisoimage
The cloud-init seed and the payload (probe + testsvc + units) ride into the guest as disk images. Building those with genisoimage/mkisofs would re-introduce a shell-out to a C tool the airgap posture forbids. So they are authored as FAT12/16 images in Rust (fatfs, in-memory), labelled cidata (NoCloud seed) and skiddata (payload). qemu attaches them read-only; the guest mounts vfat (universal). No external image tool anywhere in the path.
Why base images are booted under snapshot=on
Each cell must start from a pristine OS and must not corrupt the shared base qcow2 (many cells reuse it). snapshot=on per drive gives every boot a throwaway copy-on-write overlay — the base image is never mutated, cells are independent, and a crashed cell cannot poison the next. It also makes the matrix safe to run repeatedly against a read-only image store.
Why report to the warehouse (nornir is the verdict engine)
The matrix sinks each cell's ProbeReport to probe_reports.jsonl and an AirgapEvent (op = matrix_probe) to airgap_events. This lets nornir's completeness gate ingest install coverage as a (backend × os) surface: green ⟺ every implemented backend has a passing install + probe on at least one OS. Skidbladnir stays the producer of ground-truth rows; nornir stays the verdict engine. Making the install tree's completeness a mechanically-checked datum — not a claim — is the same honesty law as renderer_for returning None (see install-reasoning.md).