Select the guest module from the inventory runtime fact #22

Closed
opened 2026-07-31 02:53:02 +01:00 by vnprc-agent · 1 comment

Make the VM builders compose the guest module the inventory runtime fact selects, so a machine that declares microvm actually builds as one. Every piece this needs already exists and is merged — the runtime fact, both guest modules, the host runtime — and nothing joins them, so runtime is currently a label with no effect on what gets built.

Primary goals:

  • Dispatch the guest module on the runtime fact — a machine declaring libvirt composes the qemu guest, a machine declaring microvm composes the microvm guest, and neither is chosen by name or by default.
  • Make the label load-bearing — a machine that says microvm and builds a libvirt guest should become impossible, rather than the current silent outcome.
  • Cover both runtimes in the builders — the dev and privacy builders share one module list, so the selection belongs there once, not per archetype.
  • Prove the selection, not just the wiring — a check that reads the built system's own runtime marker back and compares it to the machine's declared fact.

Current state and specifics

flake.nix:60 defines sharedModules, and flake.nix:61 hard-codes vm.nixosModules.qemuGuest as the first entry. Both mkDevVm (flake.nix:175) and mkPrivacyVm (flake.nix:228) build their module list from it, so every machine gets the libvirt guest regardless of what inventory says.

Everything the swap needs is already exported and merged:

  • inventory.lib.vmRuntimes is a checked machine → runtime map. Forcing it runs inventory's fail-closed assertions, so a missing, non-string, or unknown runtime is an evaluation error in the consumer rather than a silent default.
  • allod/vm exports nixosModules.qemuGuest and nixosModules.microvmGuest as a matched pair, each already bundling its own upstream module.
  • Each guest module declares its own identity: modules/qemu-guest.nix sets allod.vm.guestRuntimes = [ "libvirt" ] and modules/microvm-guest.nix sets [ "microvm" ], and modules/guest-base.nix already asserts exactly one runtime module is composed.

That last point is what makes the check cheap. The built system can be asked which runtime it actually composed, so the check is a comparison between two independently derived facts — the inventory declaration and the guest module's own marker — rather than a restatement of the builder's if.

A machine on the microvm runtime is currently provisioned and running as an ordinary qemu guest with the label attached, which is the concrete symptom.

Scope

In scope: the guest-module selection in sharedModules, and a check pairing each machine's declared runtime against its built system's allod.vm.guestRuntimes.

Out of scope, and each already tracked or delivered elsewhere: the host-side runtime and per-VM isolation; the persistent volume declarations a microvm guest needs before it can carry state; runtime-aware credential delivery; and the rotation tools' runtime dispatch, which is allod/nexus issues 21 and 22. Those consume the interface this issue makes real, so they stay blocked until a machine can actually build as a microVM.

Provisioning and lifecycle scripts are also out of scope. They still drive libvirt unconditionally, so a machine that starts building as a microVM does not thereby become deployable by the existing host scripts; that seam needs its own issue once this lands.

Validation

  • A machine declaring libvirt produces a byte-identical system to today. This is the one that matters most — the fleet is entirely on libvirt, so the change must be a no-op for every existing machine, provable by comparing derivation paths before and after rather than by inspection.
  • A machine declaring microvm composes the microvm guest, and its built system reports allod.vm.guestRuntimes = [ "microvm" ].
  • The pairing check fails when a fixture's declared runtime and composed guest module disagree, demonstrated by sabotaging one and pinning the failure to that diagnostic rather than to evaluation failing for some other reason.
  • An unknown or absent runtime still fails through inventory's existing assertions, not through a new default in the builder.
Make the VM builders compose the guest module the inventory `runtime` fact selects, so a machine that declares `microvm` actually builds as one. Every piece this needs already exists and is merged — the runtime fact, both guest modules, the host runtime — and nothing joins them, so `runtime` is currently a label with no effect on what gets built. Primary goals: - **Dispatch the guest module on the runtime fact** — a machine declaring `libvirt` composes the qemu guest, a machine declaring `microvm` composes the microvm guest, and neither is chosen by name or by default. - **Make the label load-bearing** — a machine that says `microvm` and builds a libvirt guest should become impossible, rather than the current silent outcome. - **Cover both runtimes in the builders** — the dev and privacy builders share one module list, so the selection belongs there once, not per archetype. - **Prove the selection, not just the wiring** — a check that reads the built system's own runtime marker back and compares it to the machine's declared fact. ### Current state and specifics `flake.nix:60` defines `sharedModules`, and `flake.nix:61` hard-codes `vm.nixosModules.qemuGuest` as the first entry. Both `mkDevVm` (`flake.nix:175`) and `mkPrivacyVm` (`flake.nix:228`) build their module list from it, so every machine gets the libvirt guest regardless of what inventory says. Everything the swap needs is already exported and merged: - `inventory.lib.vmRuntimes` is a checked machine → runtime map. Forcing it runs inventory's fail-closed assertions, so a missing, non-string, or unknown runtime is an evaluation error in the consumer rather than a silent default. - `allod/vm` exports `nixosModules.qemuGuest` and `nixosModules.microvmGuest` as a matched pair, each already bundling its own upstream module. - Each guest module declares its own identity: `modules/qemu-guest.nix` sets `allod.vm.guestRuntimes = [ "libvirt" ]` and `modules/microvm-guest.nix` sets `[ "microvm" ]`, and `modules/guest-base.nix` already asserts exactly one runtime module is composed. That last point is what makes the check cheap. The built system can be asked which runtime it actually composed, so the check is a comparison between two independently derived facts — the inventory declaration and the guest module's own marker — rather than a restatement of the builder's `if`. A machine on the microvm runtime is currently provisioned and running as an ordinary qemu guest with the label attached, which is the concrete symptom. ### Scope In scope: the guest-module selection in `sharedModules`, and a check pairing each machine's declared runtime against its built system's `allod.vm.guestRuntimes`. Out of scope, and each already tracked or delivered elsewhere: the host-side runtime and per-VM isolation; the persistent volume declarations a microvm guest needs before it can carry state; runtime-aware credential delivery; and the rotation tools' runtime dispatch, which is allod/nexus issues 21 and 22. Those consume the interface this issue makes real, so they stay blocked until a machine can actually build as a microVM. Provisioning and lifecycle scripts are also out of scope. They still drive libvirt unconditionally, so a machine that starts building as a microVM does not thereby become deployable by the existing host scripts; that seam needs its own issue once this lands. ### Validation - A machine declaring `libvirt` produces a byte-identical system to today. This is the one that matters most — the fleet is entirely on libvirt, so the change must be a no-op for every existing machine, provable by comparing derivation paths before and after rather than by inspection. - A machine declaring `microvm` composes the microvm guest, and its built system reports `allod.vm.guestRuntimes = [ "microvm" ]`. - The pairing check fails when a fixture's declared runtime and composed guest module disagree, demonstrated by sabotaging one and pinning the failure to that diagnostic rather than to evaluation failing for some other reason. - An unknown or absent runtime still fails through inventory's existing assertions, not through a new default in the builder.
Member

Implemented in #23. Three claims in this issue did not survive checking, recorded here so the issue text can be corrected rather than read as accurate later.

inventory.lib.vmRuntimes does not exist. Not on master, not anywhere in history, not on the unmerged agent/inventory-runtime-fact branch. allod/inventory exports machines, lib.machines, lib.supportedPlatforms, lib.vmSpecsJson, and checks. The capability this issue wanted it for is real and available by another path: reading inventory.machines.<name>.runtime forces the full four-assertion validation chain through inventory's checkedMachines trip-wire, so a missing, non-string, or unknown runtime is still an evaluation error in the consumer. The implementation uses that.

The scope boundary contradicts the validation criteria. This issue puts "the persistent volume declarations a microvm guest needs before it can carry state" out of scope, while requiring that a machine declaring microvm composes the microvm guest and reports allod.vm.guestRuntimes = [ "microvm" ]. Composing microvmGuest without a /nix/var/nix volume fails evaluation on allod/vm's contract 6a, so the second is unreachable under the first. Resolved by having the check fixture supply a placeholder volume while the builder supplies none — the selection is proven without the state milestone, and a real machine selecting microvm still fails closed until contract 13 lands.

The prerequisites were understated. "Every piece this needs already exists and is merged" is true of the source repos but not of archetypes, whose lock pinned inventory at efe4a7c (predating the runtime fact — no machine there has the attribute) and vm at 2acc559 (predating the guest-module split — no microvmGuest to select). Two input bumps were required before any of the selection could be written. They turned out to be a clean no-op: nixpkgs and disko are identical at both vm revisions, and all three machine derivations are unchanged.

One adjacent defect found in allod/inventory while verifying this, not fixed in #23 since it belongs to that repo: inventory/flake.nix:37-38 claims "The runtime enum's microvm branch is covered by the mutation fixtures below." It is not. The only fixture carrying "microvm" sets it on nexus, a hypervisor, and runtimeDiagnostics never compares hypervisor runtimes against validRuntimes. Deleting "microvm" from validRuntimes would leave every inventory check green. flake.nix:306-308, flake.nix:438, and README.md:71-77 carry the same stale claim that the public examples cover both enum values; both have been libvirt since commit 15ad552. Worth its own issue.

Implemented in #23. Three claims in this issue did not survive checking, recorded here so the issue text can be corrected rather than read as accurate later. **`inventory.lib.vmRuntimes` does not exist.** Not on `master`, not anywhere in history, not on the unmerged `agent/inventory-runtime-fact` branch. `allod/inventory` exports `machines`, `lib.machines`, `lib.supportedPlatforms`, `lib.vmSpecsJson`, and `checks`. The capability this issue wanted it for is real and available by another path: reading `inventory.machines.<name>.runtime` forces the full four-assertion validation chain through inventory's `checkedMachines` trip-wire, so a missing, non-string, or unknown runtime is still an evaluation error in the consumer. The implementation uses that. **The scope boundary contradicts the validation criteria.** This issue puts "the persistent volume declarations a microvm guest needs before it can carry state" out of scope, while requiring that a machine declaring `microvm` composes the microvm guest and reports `allod.vm.guestRuntimes = [ "microvm" ]`. Composing `microvmGuest` without a `/nix/var/nix` volume fails evaluation on allod/vm's contract 6a, so the second is unreachable under the first. Resolved by having the check fixture supply a placeholder volume while the builder supplies none — the selection is proven without the state milestone, and a real machine selecting `microvm` still fails closed until contract 13 lands. **The prerequisites were understated.** "Every piece this needs already exists and is merged" is true of the source repos but not of `archetypes`, whose lock pinned `inventory` at `efe4a7c` (predating the runtime fact — no machine there has the attribute) and `vm` at `2acc559` (predating the guest-module split — no `microvmGuest` to select). Two input bumps were required before any of the selection could be written. They turned out to be a clean no-op: `nixpkgs` and `disko` are identical at both `vm` revisions, and all three machine derivations are unchanged. One adjacent defect found in `allod/inventory` while verifying this, **not fixed in #23** since it belongs to that repo: `inventory/flake.nix:37-38` claims "The runtime enum's microvm branch is covered by the mutation fixtures below." It is not. The only fixture carrying `"microvm"` sets it on `nexus`, a hypervisor, and `runtimeDiagnostics` never compares hypervisor runtimes against `validRuntimes`. Deleting `"microvm"` from `validRuntimes` would leave every inventory check green. `flake.nix:306-308`, `flake.nix:438`, and `README.md:71-77` carry the same stale claim that the public examples cover both enum values; both have been `libvirt` since commit `15ad552`. Worth its own issue.
vnprc closed this issue 2026-07-31 06:14:39 +01:00
Sign in to join this conversation.
No description provided.