# matrix — reasoning
> **Class:** `reasoning`. Why the machine + matrix layer is shaped this way. The descriptive shape
> is in [`matrix-design.md`](matrix-design.md). Absorbs Part C of the former `install-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`](install-reasoning.md)).