Add the microvm guest networking plan #30

Merged
vnprc merged 3 commits from agent/microvm-guest-networking into master 2026-08-01 03:45:08 +01:00
Member

Adds the plan for the third slice of milestone 4 in microvm-framework-adoption.md: the guest network interface a selected microvm archetype declares, and the proof that it carries no address.

The interesting half is the negative one. A libvirt guest gets its addressing from the host's libvirt XML, so allod/archetypes sets no address anywhere today; a microvm guest has no equivalent and needs the interface declared in the guest itself. Contract 15 lets exactly one machine value across that boundary — the inventory MAC — and machines.<name>.ip sits one attribute away from machines.<name>.mac in the same attrset. The plan keys its scanner needles on that adjacency.

The plan also records a deferral rather than hiding it: the QEMU argument list is where the interface finally lands, and building the runner realises the guest's erofs store disk, which is the whole system closure. That is not affordable inside nix flake check on the public builder, so the scan runs at evaluation over every option that feeds the renderer, and the built-artifact scan is assigned to the nested-boot slice that builds a runner anyway.

Risk

R0 — plan text only, no code. The plan itself scores the implementation R2 Medium on the same gate as the volumes slice: it declares an interface and validates none of its runtime behavior, so nothing here licenses enabling a machine.

Validation

Receipts across allod/archetypes, allod/vm, allod/nexus, allod/inventory and the pinned upstream microvm.nix source were read directly rather than carried over from the parent plan. A separate review pass against the tree is running and its findings will land on this branch before merge.

Refs allod/strategy#20

Adds the plan for the third slice of milestone 4 in `microvm-framework-adoption.md`: the guest network interface a selected microvm archetype declares, and the proof that it carries no address. The interesting half is the negative one. A libvirt guest gets its addressing from the host's libvirt XML, so `allod/archetypes` sets no address anywhere today; a microvm guest has no equivalent and needs the interface declared in the guest itself. Contract 15 lets exactly one machine value across that boundary — the inventory MAC — and `machines.<name>.ip` sits one attribute away from `machines.<name>.mac` in the same attrset. The plan keys its scanner needles on that adjacency. The plan also records a deferral rather than hiding it: the QEMU argument list is where the interface finally lands, and building the runner realises the guest's erofs store disk, which is the whole system closure. That is not affordable inside `nix flake check` on the public builder, so the scan runs at evaluation over every option that feeds the renderer, and the built-artifact scan is assigned to the nested-boot slice that builds a runner anyway. ## Risk R0 — plan text only, no code. The plan itself scores the implementation R2 Medium on the same gate as the volumes slice: it declares an interface and validates none of its runtime behavior, so nothing here licenses enabling a machine. ## Validation Receipts across `allod/archetypes`, `allod/vm`, `allod/nexus`, `allod/inventory` and the pinned upstream microvm.nix source were read directly rather than carried over from the parent plan. A separate review pass against the tree is running and its findings will land on this branch before merge. Refs allod/strategy#20
A QEMU command render costs about 0.5 GB, not the ~40 MB an earlier
probe reported. The command embeds config.microvm.storeDisk and the
divergence guard asserts on that exact path, so rendering or guarding
forces the erofs store-image derivation and walks the guest whole store
closure. Discarding string context removes the build, not the
evaluation that produced the path.

At that cost the planned nine fixtures were unaffordable, and the slice
shipped four: one privacy fixture of fourteen interface entries
reaching nineteen separately pinned diagnostics, one for the empty
interface list, one for the reconstruction guard, and an address
sabotage spliced onto a dev fixture that already exists. Record the
rule that bounds a merge, so a later slice does not read it as licence
to hide a case behind a fixture failing for any reason.

Also record the measured 44 s and 4.83 GB peak of the iteration loop.

Refs allod/strategy#20
Author
Member

Two claims in this plan were measured wrong, and the implementation has now landed with the real numbers. Pushed as 57f0d4a, following the parent plan's rule that where an implementation measurement contradicts a plan sentence, the measurement wins. This is a factual correction, not a review pass — no contract changed and no assertion was dropped.

A QEMU command render costs about 0.5 GB, not ~40 MB. Interface Contracts 7(a) reported 2.78 s / 635 MB before against 2.88 s / 678 MB after. The command embeds config.microvm.storeDisk, and the divergence guard asserts on that exact path, so rendering or guarding forces the erofs store-image derivation and with it a walk of the guest's whole store closure. unsafeDiscardStringContext removes the build, not the evaluation that produced the path — that is where the original probe went wrong, by measuring a shape that never forced storeDisk.

The implementation already reflects the real cost: the rendered command is passed to each reader as an argument rather than re-rendered per reader, so a scanned configuration pays for one render; and the passing scan is spent on the privacy fixture, with the address sabotage spliced onto a dev fixture that exists anyway for the image-root findings.

Nine fixtures were still unaffordable; the slice shipped four. Interface Contracts 8 planned merges down to nine. What shipped: one privacy fixture carrying fourteen interface entries and reaching nineteen separately pinned diagnostics; one for the empty interface list, which is the single shape that cannot share a fixture with any entry; one for the reconstruction guard; and the address sabotage, which costs no evaluation at all. Three of the four cost a new evaluation.

I added the rule that bounds this, because a merge target is easy to read as licence: a fixture may absorb another only while every needle stays anchored to its own reporter, so deleting one rule turns the fixture red for that rule alone. A merge that leaves a case provable only by the fixture failing for some reason is not affordable at any memory price.

Also recorded: the iteration loop's measured cost, 44 s and 4.83 GB peak for nix eval --raw .#checks.x86_64-linux.runtime-module-selection.drvPath.

The implementation is allod/archetypes#33, which reports the same numbers. This branch is three commits behind master and touches only its own new file, so it merges cleanly; merging is a human action.

Two claims in this plan were measured wrong, and the implementation has now landed with the real numbers. Pushed as `57f0d4a`, following the parent plan's rule that where an implementation measurement contradicts a plan sentence, the measurement wins. This is a factual correction, not a review pass — no contract changed and no assertion was dropped. **A QEMU command render costs about 0.5 GB, not ~40 MB.** Interface Contracts 7(a) reported 2.78 s / 635 MB before against 2.88 s / 678 MB after. The command embeds `config.microvm.storeDisk`, and the divergence guard asserts on that exact path, so rendering *or* guarding forces the erofs store-image derivation and with it a walk of the guest's whole store closure. `unsafeDiscardStringContext` removes the build, not the evaluation that produced the path — that is where the original probe went wrong, by measuring a shape that never forced `storeDisk`. The implementation already reflects the real cost: the rendered command is passed to each reader as an argument rather than re-rendered per reader, so a scanned configuration pays for one render; and the passing scan is spent on the privacy fixture, with the address sabotage spliced onto a dev fixture that exists anyway for the image-root findings. **Nine fixtures were still unaffordable; the slice shipped four.** Interface Contracts 8 planned merges down to nine. What shipped: one privacy fixture carrying fourteen interface entries and reaching nineteen separately pinned diagnostics; one for the empty interface list, which is the single shape that cannot share a fixture with any entry; one for the reconstruction guard; and the address sabotage, which costs no evaluation at all. Three of the four cost a new evaluation. I added the rule that bounds this, because a merge target is easy to read as licence: a fixture may absorb another only while every needle stays anchored to its own reporter, so deleting one rule turns the fixture red for that rule alone. A merge that leaves a case provable only by the fixture failing for *some* reason is not affordable at any memory price. Also recorded: the iteration loop's measured cost, 44 s and 4.83 GB peak for `nix eval --raw .#checks.x86_64-linux.runtime-module-selection.drvPath`. The implementation is allod/archetypes#33, which reports the same numbers. This branch is three commits behind master and touches only its own new file, so it merges cleanly; merging is a human action.
vnprc approved these changes 2026-08-01 03:43:36 +01:00
Dismissed
vnprc force-pushed agent/microvm-guest-networking from 57f0d4a156 to ed5d0d9d96 2026-08-01 03:43:43 +01:00 Compare
vnprc approved these changes 2026-08-01 03:45:02 +01:00
vnprc merged commit ed5d0d9d96 into master 2026-08-01 03:45:08 +01:00
vnprc deleted branch agent/microvm-guest-networking 2026-08-01 03:45:08 +01:00
Sign in to join this conversation.
No description provided.