crates/gunnar-recipe/tests/toml_parity.rs
//! **`.nornir/build-variants.toml` IS the code's emission — and this test fails
//! for any reason it is not.**
//!
//! # What this file used to be, and why that was not enough
//!
//! Until 2026-08-30 it was a *parity* test in the literal sense: the
//! toml on disk was hand-maintained, `recipe.rs` was a hand-maintained mirror of
//! it, and this file loaded both and compared them. That comparison was green
//! for four pin generations while it was **comparing nothing**:
//!
//! * An audit measured **two whole groups** — `[build.installer.target]` (the
//! machine) and `[build.installer.data]` (what /data is laid with) — present
//! in the toml and absent from `recipe.rs`. Parity passed, because the pinned
//! loader dropped them on the way in: a mirror compared against a reader that
//! discards what it cannot parse agrees about nothing, loudly.
//! * A later audit measured the same file **refused outright** by the published 0.1.10
//! reader over one additive `kernel_requires` key, and wrote its
//! `[nornir-build-thing] schema` header by hand — which is what a file with no
//! writer looks like.
//!
//! The law is that
//! there is ONE declaration and the file is its projection. So the assertions
//! here are:
//!
//! 1. [`the_committed_toml_is_the_codes_emission_byte_for_byte`] — the file is
//! `cargo run -p gunnar-build-thing -- emit`'s output, byte for byte. This is
//! the assertion that fails **for any reason** the two differ: a row added to
//! the code and not re-emitted, a hand edit, a reordering, a comment typed
//! into the output.
//! 2. [`an_edited_toml_has_zero_effect_on_what_the_recipe_declares`] — the file
//! is inert. An edit is visible to a *reader* and invisible to the recipe,
//! which is what "never read back as authority" means, measured.
//! 3. [`a_group_the_reader_would_drop_fails_instead_of_being_dropped`] — the
//! exact defect the first audit found, put back and seen red: a group at a path the
//! loader ignores is now a REFUSAL (R3, `schema::dropped_keys`), so it can
//! neither be emitted nor read.
//!
//! Everything below those is a **value** assertion on load-bearing spellings —
//! the ceremony gate, the bootstrap admin, the kernel donors, the boot mark.
//! They are not implied by (1): (1) says the file matches the code, and these
//! say the code still declares the thing that was expensive to learn.
use std::path::PathBuf;
use gunnar_recipe::{emitted_toml, GunnarBuildThing};
use nornir_build_thing::emit::first_difference;
use nornir_build_thing::plugin::BuildThing;
use nornir_build_thing::variant::BuildVariantSet;
/// gunnar's repo root, from this crate's manifest directory.
fn repo_root() -> PathBuf {
gunnar_recipe::emit_recipe::repo_root()
}
/// The committed bytes.
fn committed() -> String {
let path = BuildVariantSet::conventional_path(&repo_root());
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()))
}
/// The set the toml on disk declares, loaded by `nornir-build-thing`'s own
/// loader rather than by a second parser written here.
fn from_toml() -> BuildVariantSet {
let path = BuildVariantSet::conventional_path(&repo_root());
BuildVariantSet::load_file(&path).unwrap_or_else(|e| panic!("load {}: {e:#}", path.display()))
}
// ── 1. THE FILE IS THE EMISSION ─────────────────────────────────────────────
/// **The one assertion that fails for any reason the file and the code differ.**
///
/// The fix is never to edit the toml. It is `cargo run -p gunnar-build-thing --
/// emit` and commit the result — and if the emission does not say what you
/// wanted the file to say, the recipe is missing something and *that* is the
/// bug.
#[test]
fn the_committed_toml_is_the_codes_emission_byte_for_byte() {
let emitted = emitted_toml().expect("the recipe emits");
let committed = committed();
if let Some(diff) = first_difference(&committed, &emitted) {
panic!(
"{} is not the code's emission — the code is the source. Run \
`cargo run -p gunnar-build-thing -- emit` and commit the result; never edit \
the toml by hand. {diff}",
BuildVariantSet::conventional_path(&repo_root()).display()
);
}
// And the emission reads back, through the one loader, to the declaration —
// R2 and R3 both run inside `from_toml_str`, so a file that is readable here
// is readable by every door at or above the schema it declares.
let back = BuildVariantSet::from_toml_str(&emitted).expect("the emission parses back");
assert_eq!(back, GunnarBuildThing.variants());
back.validate_all().expect("and it validates");
}
/// **The toml is inert.** An edit changes what a *reader* sees and changes
/// nothing about what the recipe declares.
///
/// The perturbation is asserted to be visible first: without that arm this test
/// would pass on a file the reader never opened, which is precisely the shape of
/// green the old parity test had.
#[test]
fn an_edited_toml_has_zero_effect_on_what_the_recipe_declares() {
let declared = GunnarBuildThing.variants();
let edited = emitted_toml().unwrap().replace(
"name = \"gunnar-server\"",
"name = \"gunnar-server-edited\"",
);
let read = BuildVariantSet::from_toml_str(&edited).expect("still parses");
assert_ne!(
read, declared,
"the edit must be visible to a reader, else this test proves nothing"
);
assert_eq!(
GunnarBuildThing.variants(),
declared,
"the recipe read the file — it must not"
);
}
/// **The audited defect, put back: a group the reader would DROP now fails.**
///
/// `InstallerBuild` is not `deny_unknown_fields` — deliberately, because an
/// additive key must not make a whole recipe unloadable for an older reader
/// (R1/R2). That openness is exactly how `[build.installer.target]` and
/// `[build.installer.data]` went four generations unread while this test was
/// green. R3 closes it inside the one loader: `from_toml_str` reports every
/// `build.*` path serde ignored and refuses on any.
///
/// So: take the real emission, add a group at a path the loader has no field
/// for, and assert the load REFUSES and names the path. Delete
/// `schema::refuse_dropped` and this goes green with the group silently gone —
/// which is the whole of the old bug.
#[test]
fn a_group_the_reader_would_drop_fails_instead_of_being_dropped() {
let emitted = emitted_toml().unwrap();
BuildVariantSet::from_toml_str(&emitted).expect("the real emission loads");
// A group at an OPEN type's path: `InstallerBuild` accepts unknown keys at
// the serde level, so nothing but R3 can see this.
let with_a_dropped_group =
format!("{emitted}\n[build.installer.a_group_no_reader_has_a_field_for]\nfs = \"xfs\"\n");
let err = BuildVariantSet::from_toml_str(&with_a_dropped_group)
.expect_err("a group the reader would drop must be a refusal, not a silence")
.to_string();
assert!(
err.contains("a_group_no_reader_has_a_field_for"),
"the refusal must NAME the dropped path — an unnamed one is the silence \
it replaces: {err}"
);
}
// ── 2. THE LOADER IS READING WHAT THIS TEST THINKS IT IS ────────────────────
/// Measured, not asserted, on every comparison in this file.
///
/// Two empty sets compare equal. If `load_file` ever returned a default — a
/// renamed file, a moved repo root, a schema change that drops every key — the
/// assertions would go green having compared nothing to nothing. This pins both
/// sides to the shape they actually have, and watches the comparison fail on a
/// set that differs by exactly one field.
#[test]
fn the_comparison_reports_red_on_a_set_that_differs_by_one_field() {
let toml = from_toml();
// Fifteen since 2026-09-05 (`gunnar-installer-metered`, the first medium to
// compose the metered binary); fourteen since 2026-09-04
// (`gunnar-server-metered`, `gunnar-server-metered-thin`, the plan-enforcing
// build points of the entitlement work); twelve since the AWS Nitro row
// (`gunnar-installer-aws`).
// Eleven since 2026-08-30 (`gunnar-installer-gce`, the virtio-scsi medium);
// ten since 2026-08-28 (`gunnar-installer-open`, the keyless installer);
// nine since 2026-08-27 (`gunnar-server-thin`, the build point
// `--opt wireguard=off` arrives at). The literal is a loader check, not a
// preference: a set that came back short would make every comparison below
// green about a file it never read.
// Sixteen since 2026-09-05 (`gunnar-installer-azure`, the Hyper-V medium).
// Eighteen since 2026-09-05 (`gunnar-installer-metered-{aws,azure}`, the
// metered twins of the two cloud rows).
// Nineteen 2026-09-15 → 2026-09-17 (`gunnar-twin`, the twin appliance) —
// and one FEWER for ever since, because a twin is a role of an appliance and
// not a medium: the row's `variant` was `gunnar-server`, so the metered
// appliance's twin was unmetered and a promotion would have served every
// suspended tenant. OWNER RULING 2026-09-21: one medium, no twin ISO.
//
// TWENTY-ONE since 2026-09-20 (`gunnar-clock-musl`, the FRONT host's clock
// daemon). It is the row `[build.allow_unbuilt]`'s `bin` entry had been
// excusing the absence of — "no variant row fits it until a front-host atom
// exists" — while `private-gunnar-ops` built that binary from a cargo line
// on whatever box typed `cargo xtask front`. `Atom::GunnarClock` exists now,
// so the excuse is deleted and this is the row that replaced it.
// TWENTY-FIVE since 2026-09-25: the two FRONT media (vm1's own) and their
// payloads — gunnar-front + gunnar-front-installer-open (lane D), and
// gunnar-front-metered + gunnar-front-installer-metered (the paid front).
// TWENTY-NINE since 2026-09-30: the four DEV-NARRATE `…-narrate` payload
// rows, the build points the media's `dev-narrate` option (default OFF)
// arrives at when a dev medium turns it ON.
// THIRTY since 2026-10-11: `gunnar-musl`, the SIGNED client-only CLI every
// site serves under `/download/` (E4).
// TWENTY-NINE again since 2026-10-21 (lane L10): `gunnar-clock-musl` deleted, the
// gunnar-clock crate moved to edda estate-clock and the front keeps its clock
// as a thread of `gunnar-front`.
// THIRTY-ONE since 2026-10-28 (OX-VARIANTS): `gunnar-open` and `gunnar-open-client`,
// the crates.io package `gunnar` as `cargo install gunnar` builds it.
// TWENTY-SEVEN since 2026-10-30 (GUN-PRODUCTS): the three products `gunnar`,
// `gunnar-ui` and `gunnar-cli` and the estate's builds; seven rows retired
// (`gunnar` the client-only git binary, the old `gunnar-cli`, `gunnar-musl`,
// `gunnar-solo`, `gunnar-service`, `gunnar-open`, `gunnar-open-client`).
assert_eq!(
toml.variants.len(),
27,
"the toml parsed to {} variants; it declares twenty-seven (GUN-PRODUCTS 2026-10-30: \
the three products and the estate's builds; thirty-one before), so the loader is \
not reading what this test thinks it is",
toml.variants.len()
);
assert!(
!toml.offers.is_empty(),
"the toml parsed to an empty feature space"
);
let mut perturbed = toml.clone();
perturbed.variants.pop().expect("a variant to drop");
assert_ne!(
perturbed, toml,
"BuildVariantSet compares equal after a variant was removed, so the \
assertions here can never report red"
);
let mut renamed = toml.clone();
renamed.variants[0].name.push('x');
assert_ne!(
renamed, toml,
"BuildVariantSet compares equal after a rename"
);
let mut unfeatured = toml.clone();
unfeatured.variants[0].features.clear();
assert_ne!(
unfeatured, toml,
"BuildVariantSet compares equal after a variant lost its features"
);
let mut uninstalled = toml.clone();
uninstalled.installers.clear();
assert_ne!(
uninstalled, toml,
"BuildVariantSet compares equal after the installer variants were removed"
);
}
// ── 3. THE LOAD-BEARING VALUES ──────────────────────────────────────────────
/// **The ceremony gate, the bootstrap admin, and every parameter's landing.**
///
/// These are values that were expensive to learn and cheap to lose. Assertion
/// (1) holds the file to the code; this holds the code to what was measured.
#[test]
fn the_installer_declares_the_gate_the_admin_and_no_secret_on_the_cmdline() {
let toml = from_toml();
assert_eq!(
toml.installers.len(),
10,
"ten installer variants — the two FRONT media (gunnar-front-installer-open and -metered, 2026-09-25, vm1's own) and eight before them: the sealed one, its keyless twin (2026-08-28), the \
twin's virtio-scsi twin for a GCE-shaped machine (2026-08-30), the AWS \
Nitro twin (2026-09-02), the Azure Hyper-V twin (2026-09-05), the METERED twin \
(2026-09-05) and the metered twins of the AWS and Azure rows (2026-09-05). NINE \
between 2026-09-15 and 2026-09-17, when `gunnar-twin` was a row of its own — \
deleted, because a twin is a ROLE of an appliance and a twin row cloned from the \
KEYLESS lineage made the metered appliance's twin UNMETERED, which a promotion \
would have shipped in silence (`tests/twin_is_the_same_medium.rs`); the \
loader parsed {}",
toml.installers.len()
);
let inst = toml
.installers
.iter()
.find(|i| i.name == "gunnar-installer")
.expect("the sealed installer is declared");
// The `korp.env.` spelling, NEVER the bare one: `carry_site_tokens` forwards
// only "korp."-prefixed tokens to the installed disk, so a bare
// `GUNNAR_APPLIANCE=1` here would satisfy a laxer assertion while the
// installed appliance boots ungated — the exact defect the 2026-08-15 drive
// caught. This asserts the token that actually reaches the metal.
assert!(
inst.boot_params
.iter()
.any(|p| p == "korp.env.GUNNAR_APPLIANCE=1"),
"the ceremony gate must survive the load — without it the installed \
appliance mints no identity and prints no key: {:?}",
inst.boot_params
);
assert!(
!inst.boot_params.iter().any(|p| p == "GUNNAR_APPLIANCE=1"),
"the BARE gate form is the measured 2026-08-15 defect — an env var for \
the installer's PID 1 that never reaches the installed disk: {:?}",
inst.boot_params
);
assert!(
!inst
.boot_params
.iter()
.any(|p| p.contains("PrivateKey") || p.starts_with("GUNNAR_WG=")),
"no secret and no seeded tunnel on a WORLD-READABLE /proc/cmdline: {:?}",
inst.boot_params
);
// ── The METERED medium's cmdline ────────────────────────────────────────
//
// The one row that puts KEY MATERIAL on a world-readable /proc/cmdline, so
// it is the row this assertion exists for. Two things are measured on the
// token that actually reaches the metal:
//
// 1. The key is THERE and is a 64-hex literal. A path would name a file
// nothing was allowed to create — `korp-installer` stages onto /data only
// under `korp.reinstall=wipe` and the ESP is read-only at runtime — so a
// medium carrying a path boots a metered gunnar that cannot verify a
// single fact. Asserting merely that `--monetize-pubkey` appears would
// pass on exactly that broken medium, so the VALUE is asserted too.
// 2. It is a PUBLIC verifying key and nothing else. 64 hex characters is a
// 32-byte Ed25519 public key; an OpenSSH private key, a `PrivateKey=`
// line or a base64 blob has no business here and could not ride a
// whitespace-split cmdline anyway.
let metered = toml
.installers
.iter()
.find(|i| i.name == gunnar_recipe::METERED_INSTALLER)
.expect("the metered installer is declared");
assert_eq!(
metered.variant,
gunnar_recipe::METERED_VARIANT,
"the metered medium must compose the METERED binary — composed from the community \
`gunnar-server` it would carry no entitlement code at all and serve every \
namespace Free, which is the failure that has no symptom"
);
// Since 2026-09-20 the pair rides `korp.env.*` rather than the `korp.args`
// argv — still the kernel command line, and still the only channel into a
// booting gunnar on a keep-upgrade, but a token a composing door can
// REPLACE, which an argv is not (clap's argv beats its own `env`, and a
// landed cmdline parameter appends where `korp.args` must stay one token).
// `gunnar_recipe::metered_env` carries the full measurement.
let key = metered
.env
.get(gunnar_recipe::MONETIZE_PUBKEY_ENV)
.unwrap_or_else(|| {
panic!(
"the metered medium declares a monetize key: {:?}",
metered.env
)
});
assert_eq!(
key,
gunnar_recipe::MONETIZE_PUBKEY,
"the declared row must carry monetize's key ITSELF as its DEFAULT; a path \
there names a file the installer was never allowed to write"
);
assert!(
key.len() == 64 && key.chars().all(|c| c.is_ascii_hexdigit()),
"the key on the cmdline must be the 64 hex characters of a 32-byte Ed25519 \
PUBLIC key, and nothing longer: {key}"
);
assert_eq!(
metered
.env
.get(gunnar_recipe::BILLING_URL_ENV)
.map(String::as_str),
Some(gunnar_recipe::recipe::METERED_BILLING_URL),
"the renew URL is MANDATORY beside the key — a medium with one half of the \
pair made gunnar refuse to start, and gunnar is PID 1: {:?}",
metered.env
);
// WWW-CANON (owner 2026-10-30): the site is www.gunnar.rs; the medium's placeholder names it.
assert_eq!(
gunnar_recipe::recipe::METERED_BILLING_URL,
"https://www.gunnar.rs/billing/{tenant}"
);
assert!(
metered
.params
.iter()
.any(|p| p.name == gunnar_recipe::MONETIZE_PUBKEY_PARAM),
"the metered row must DECLARE the key as a parameter, or no door can hand it \
the front's live one and the default above goes stale the day a new estate \
is laid: {:?}",
metered.params.iter().map(|p| &p.name).collect::<Vec<_>>()
);
assert!(
!metered.boot_params.iter().any(|p| p.contains("PrivateKey")
|| p.contains("PRIVATE KEY")
|| p.starts_with("GUNNAR_WG=")),
"no PRIVATE key on a world-readable /proc/cmdline, on this row least of all: {:?}",
metered.boot_params
);
let names: Vec<&str> = inst.params.iter().map(|p| p.name.as_str()).collect();
assert_eq!(
names,
[
"hub_conf",
"admin_key",
"static_ip",
"gateway",
"log_udp",
// 2026-09-14: where "now" comes from, a site value (`gunnar-clock`).
"clock",
"clock_key",
"reinstall",
// 2026-09-15: an orchestrated medium powers off after INSTALL-OK.
"install_then",
// 2026-10-12: the per-estate live-update switch (`korp.live_update`, part of the BASE).
"live_app" // No `role` and no `data_disks` since 2026-09-22 (one medium; the role
// over the wire; the installer elects its own disks).
],
"the declared parameters, in order — `params` is a Vec and the mirror \
compares positionally"
);
// `admin_key` is REQUIRED, and that is the half the reachability gate cannot
// see: the gate proves the argv NAMES `--admin-key`, this proves a build that
// names it and supplies nothing is refused by the framework's own
// missing-required rule. Measured under KVM 2026-08-25: an installed
// appliance with neither logged "Nobody can reach this server" on every start
// and served anyway.
let admin = inst
.params
.iter()
.find(|p| p.name == "admin_key")
.expect("the installer declares a bootstrap admin");
assert!(
admin.required,
"a PERSISTENT appliance may not be mastered without a bootstrap admin"
);
assert_eq!(
admin.staged_file, "admin.pub",
"an OpenSSH key line has spaces and cannot ride a whitespace-split cmdline; \
it lands as a staged file, which is also where the argv's `@/data/admin.pub` \
points"
);
assert!(
admin.boot_param.is_empty(),
"and NOT as a boot param, which would truncate it at the key type"
);
for p in &inst.params {
assert!(
p.validate().is_ok(),
"parameter '{}' does not validate: {:?}",
p.name,
p.validate().err().map(|e| format!("{e:#}"))
);
// Every landing that is a boot param must be `korp.`-prefixed, or
// `carry_site_tokens` drops it between the installer's boot and the
// installed disk — the measured 2026-08-15 defect, arriving by a new road.
assert!(
p.boot_param.is_empty() || p.boot_param.starts_with("korp."),
"parameter '{}' lands on the cmdline as {:?}, which `carry_site_tokens` \
will not forward to the installed disk",
p.name,
p.boot_param
);
}
}
/// **The LIVE medium wears gunnar's mark too — not only the installer.**
///
/// MEASURED 2026-08-18 on the laptop: the live boot showed korp's raven on the
/// graphics console while gunnar's own banner went to serial. The installer
/// variant got its `boot-logo` on 2026-08-16; the live iso variant did not — and
/// edda's live composition additionally ignored the key (fixed edda-side
/// 2026-08-20, `korp_installer::forge::compose_first_stage`). This holds the
/// DECLARATION half: the live variant names the same mark the installer bakes,
/// and the file is on disk.
#[test]
fn the_live_iso_variant_wears_gunnars_mark_and_it_is_the_installers_mark() {
let toml = from_toml();
let iso = toml
.isos
.iter()
.find(|i| i.name == "gunnar-appliance-iso")
.expect("the live iso variant is declared");
assert_eq!(
iso.boot_logo.as_deref(),
Some("assets/gunnar.webp"),
"the LIVE medium must declare gunnar's mark — without it korp's raven is \
what a live boot shows (measured on the laptop 2026-08-18): {:?}",
iso.boot_logo
);
assert_eq!(
iso.boot_logo, toml.installers[0].boot_logo,
"one product, one mark: the live medium and the installer wear the same file"
);
let mark = repo_root().join(iso.boot_logo.as_deref().unwrap());
assert!(
mark.is_file(),
"the declared mark must exist on disk (it is a build input, committed): {}",
mark.display()
);
}
/// **Every medium names its KERNEL DONOR, and there is exactly one donor.**
///
/// RED-FIRST 2026-08-26: before these declarations existed, a BYGG on
/// `gunnar-installer` composed a medium whose bzImage was the BUILD BOX's
/// `/boot/vmlinuz` — "a kernel nobody in this repository chose" — while tunnr's
/// declared, pinned `tunnr-kernel` sat built in `.kernel-out/` with nothing
/// pointing at it. The live ISO had the same hole for a day longer (wave 3).
///
/// Since the emitter landed this is a LOADED-VALUE assertion only. The raw-text arm this
/// test used to carry existed because the pinned loader was blind to the field
/// and a raw read was the honest guard; the pin is a path dep on 0.1.12 now
/// (see the audits above), the field loads, and the file is the code's emission — so a
/// spelling check would be checking `toml::to_string_pretty` rather than gunnar.
#[test]
fn both_media_declare_the_same_kernel_donor_and_a_client_declares_none() {
let set = from_toml();
let mirror = gunnar_recipe::iso_kernel_donor();
let iso = set
.isos
.iter()
.find(|i| i.name == "gunnar-appliance-iso")
.expect("the live iso is declared");
assert_eq!(
iso.kernel_donor.as_ref(),
Some(&mirror),
"the live medium boots whatever kernel the build box has unless it declares a donor"
);
let installer_donor = set
.installers
.iter()
.find(|i| i.name == "gunnar-installer")
.and_then(|i| i.kernel_donor.clone())
.expect("the installer declares its donor");
assert_eq!(
(
installer_donor.repo.as_str(),
installer_donor.kernel.as_str()
),
("tunnr", "tunnr-kernel")
);
assert_eq!(
mirror, installer_donor,
"two media, two kernels — the live iso drifted from the installer"
);
// One kernel per repository, answered by name for every medium and for
// neither client.
assert_eq!(
gunnar_recipe::kernel_donor_of("gunnar-appliance-iso"),
Some(mirror.clone())
);
assert_eq!(
gunnar_recipe::kernel_donor_of("gunnar-installer"),
Some(mirror.clone())
);
assert_eq!(
gunnar_recipe::kernel_donor_of(gunnar_recipe::OPEN_INSTALLER),
Some(mirror.clone()),
"the keyless twin boots the same kernel"
);
assert_eq!(gunnar_recipe::kernel_donor_of("gunnar-cli"), None);
}
/// **A DEFAULT group is not written down, and that is the emission telling the
/// truth.**
///
/// `[build.installer.target]` was one of the two groups the audit found in the
/// toml and not in the code. It reaches `recipe.rs` now — and a later audit
/// measured what it actually says: `firmware = both`, `consoles = [vga, serial]`,
/// `buses = [ide-cdrom, virtio-blk]`, `nic = virtio`, which is `Target::default()`
/// **exactly**. `InstallerBuild::target` is
/// `skip_serializing_if = "Target::is_default"`, so the emission omits it.
///
/// That is correct and it is worth an assertion, because the omission looks
/// exactly like the loss it replaces: the group is DECLARED (the code carries
/// it, and `--list-options` prints it as RECIPE-ONLY), it simply changes nothing
/// for THOSE media.
///
/// **That day arrived on 2026-08-30** — exactly as the sentence that
/// used to end this comment predicted: `gunnar-installer-gce` declares
/// `buses = [virtio-blk, virtio-scsi]` because GCE's Persistent Disk is
/// virtio-scsi, the table appeared in the emission with nobody adding it by
/// hand, and this test now says BOTH halves: which media are still the default
/// machine, and which is not and why.
#[test]
fn the_appliance_media_declare_the_default_machine_and_the_gce_medium_does_not() {
use nornir_build_thing::target::Bus;
let set = from_toml();
// The two UpCloud-shaped media: still exactly `Target::default()`, still
// emitting nothing. This half is the ORIGINAL assertion, unchanged, and it
// is what keeps the appliance's 2.4 MB kernel from paying for another
// cloud's disk.
for name in ["gunnar-installer", "gunnar-installer-open"] {
let i = set.installers.iter().find(|i| i.name == name).expect(name);
assert!(
i.target.is_default(),
"'{name}' must stay at the default machine"
);
}
// And the day the doc comment above promised has come (2026-08-30):
// `gunnar-installer-gce` declares a machine that DIFFERS, so the table is in
// the file — one table, for one row, with the bus that is the whole reason.
let gce = set
.installers
.iter()
.find(|i| i.name == "gunnar-installer-gce")
.expect("the virtio-scsi medium is declared");
assert!(!gce.target.is_default());
assert!(
gce.target.buses.contains(&Bus::VirtioScsi),
"{:?}",
gce.target.buses
);
assert!(
!gce.target.buses.contains(&Bus::IdeCdrom),
"GCE attaches no cdrom; carrying ATA for a device it does not have is the bloat \
this row exists to avoid"
);
assert_eq!(
gce.kernel_donor.as_ref().map(|d| d.kernel.as_str()),
Some("tunnr-kernel-scsi"),
"the shared donor's `nuked` preset vetoes CONFIG_SCSI by name"
);
// And its AWS Nitro sibling: NVMe boot disk, ENA NIC, the
// nvme donor, and no cdrom either.
let aws = set
.installers
.iter()
.find(|i| i.name == "gunnar-installer-aws")
.expect("the NVMe/ENA medium is declared");
assert!(!aws.target.is_default());
let aws_t = aws.target.effective();
assert!(aws_t.buses.contains(&Bus::Nvme), "{:?}", aws_t.buses);
assert!(
!aws_t.buses.contains(&Bus::IdeCdrom) && !aws_t.buses.contains(&Bus::VirtioScsi),
"Nitro has neither cdrom nor scsi"
);
assert_eq!(aws_t.nic, nornir_build_thing::target::Nic::Ena);
assert_eq!(
aws.target.preset.as_deref(),
Some("aws-nitro"),
"declared by preset since 2026-09-05"
);
assert_eq!(
aws.kernel_donor.as_ref().map(|d| d.kernel.as_str()),
Some("tunnr-kernel-nvme"),
"the slim preset vetoes neither CONFIG_BLK_DEV_NVME nor CONFIG_ENA_ETHERNET"
);
// And the Azure sibling (2026-09-05): Hyper-V synthetic SCSI + NIC over
// VMBus, the hyperv donor, UEFI only (a Gen2 VM has no CSM), no cdrom.
let azure = set
.installers
.iter()
.find(|i| i.name == "gunnar-installer-azure")
.expect("the Hyper-V medium is declared");
assert!(!azure.target.is_default());
let azure_t = azure.target.effective();
assert_eq!(
azure.target.preset.as_deref(),
Some("azure-gen2"),
"declared by preset since 2026-09-05"
);
assert_eq!(azure_t.buses, vec![Bus::HvStorvsc], "{:?}", azure_t.buses);
assert_eq!(azure_t.nic, nornir_build_thing::target::Nic::HvNetvsc);
assert_eq!(azure_t.firmware, nornir_build_thing::target::Firmware::Uefi);
assert_eq!(
azure.kernel_donor.as_ref().map(|d| d.kernel.as_str()),
Some("tunnr-kernel-hyperv"),
"the slim preset vetoes none of HYPERV / HYPERV_STORAGE / HYPERV_NET"
);
let tables = committed().matches("[build.installer.target]").count();
let differing = set
.installers
.iter()
.filter(|i| !i.target.is_default())
.count();
assert_eq!(
tables, differing,
"one emitted table per row whose machine differs, and no other"
);
assert_eq!(
tables, 5,
"five media now differ from the default machine: gce (virtio-scsi), aws (nvme/ena), \
azure (hv-storvsc/hv-netvsc), and the metered twins of aws and azure, which keep \
their open twin's machine"
);
// And the group is still a declaration, not an absence: the code carries it.
let declared = GunnarBuildThing.variants();
assert_eq!(
declared.installers.len(),
set.installers.len(),
"the code and the file declare the same installers"
);
}
/// Every variant the file declares became a variant, and none was invented.
///
/// Implied by the byte-for-byte assertion; stated separately because it is the
/// property the migration had to preserve, and a reader looking for that
/// guarantee should not have to derive it from a `PartialEq`.
#[test]
fn every_toml_variant_became_a_variant_and_no_variant_was_invented() {
let toml = from_toml();
for v in &toml.variants {
let c = gunnar_recipe::by_name(&v.name)
.unwrap_or_else(|| panic!("the toml declares '{}' and no variant carries it", v.name));
assert_eq!(c.name, v.name);
}
for c in gunnar_recipe::VARIANTS {
assert!(
toml.get(c.name).is_some(),
"variant '{}' is in no declaration",
c.name
);
}
assert_eq!(gunnar_recipe::VARIANTS.len(), toml.variants.len());
}
/// **The installer's KERNEL DONOR is declared — and this pin's loader is blind
/// to it, which is exactly why the guard reads the raw file.**
///
/// RED-FIRST 2026-08-26 (under the standing order to make the fixes already
/// known, the day the
/// donor vocabulary landed in edda): before the declaration below existed, a
/// BYGG on `gunnar-installer` composed a medium whose bzImage was the BUILD
/// BOX's `/boot/vmlinuz` — "a kernel nobody in this repository chose", this
/// file's own words — while tunnr's declared, pinned `tunnr-kernel` sat built
/// in `.kernel-out/` with nothing pointing at it. `[build.installer.kernel_donor]
/// repo=tunnr kernel=tunnr-kernel` is the wiring: the forge (edda ≥0.1.8)
/// resolves the donor's built, pin-verified bzImage on BOTH doors (⚒ BYGG and
/// `dwarves buildthing gunnar --variant gunnar-installer`), and `--opt
/// kernel.*` is judged against the DONOR's declaration.
///
/// Until 2026-08-26 this crate pinned `nornir-build-thing` **0.1.7 from the
/// registry**, whose `InstallerBuild` had no `kernel_donor` field — the loader
/// ignored the table benignly, the whole-struct parity assertion above could not
/// hold the declaration, and a raw read of the file was the honest guard. The
/// same day 0.1.9 (the first release carrying the field) reached crates.io and
/// the pin advanced: the mirror in `recipe.rs` now declares the donor too, the
/// parity assertion holds it like every other field, and what this test keeps
/// is the load-bearing spellings plus the LOADED value — a reformat that changes
/// them should be looked at, not absorbed.
#[test]
fn the_installer_declares_its_kernel_donor_and_the_raw_file_is_the_guard() {
let path = BuildVariantSet::conventional_path(&repo_root());
let raw =
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
// The table, with both halves. Not a regex — the spellings are load-bearing
// and a reformat that changes them should be looked at, not absorbed.
assert!(
raw.contains("[build.installer.kernel_donor]"),
"the installer variant declares no [build.installer.kernel_donor] — the composed \
medium is back to booting whatever kernel the build box has"
);
assert!(
raw.contains("repo = \"tunnr\""),
"the kernel donor names no repo — the reference resolves nothing"
);
assert!(
raw.contains("kernel = \"tunnr-kernel\""),
"the kernel donor names no [[build.kernel]] — the reference resolves nothing"
);
// …and the loader SEES it: the registry pin advanced to 0.1.9 on 2026-08-26
// (the first `InstallerBuild` with the field), so the donor is a loaded fact,
// not a raw-file spelling. From here the whole-struct parity assertion above
// holds the two mirrors equal on this field like every other — this test keeps
// only the spellings (load-bearing, reformat-visible) and the loaded value.
let set = from_toml();
let variant = set
.installers
.iter()
.find(|i| i.name == "gunnar-installer")
.expect("the installer variant is declared");
let donor = variant
.kernel_donor
.as_ref()
.expect("the loader carries the kernel donor");
assert_eq!(
(donor.repo.as_str(), donor.kernel.as_str()),
("tunnr", "tunnr-kernel")
);
}
/// **The LIVE medium's KERNEL DONOR is declared — and this pin's loader is blind
/// to it, so the raw file is the guard, held to the Rust mirror.**
///
/// RED-FIRST 2026-08-26 (wave 3, the iso-edge work): wave 2 landed the iso edge in
/// edda (`61e0d53`, `IsoBuild.kernel_donor`) and dwarves (`0abb7a8`, the CLI
/// door reads it), and the acceptance said "`gunnar-appliance-iso` consumes
/// `tunnr/tunnr-kernel`" — but gunnar's own toml carried only the INSTALLER's
/// edge (`[build.installer.kernel_donor]`), so a live ISO composed on a box still
/// packed the box's `/boot/vmlinuz`. This test was written against that tree
/// and failed on its first assertion; the table below it is what turned it.
///
/// The shape is the installer's under the 0.1.7 pin, reused on purpose: this
/// crate pins `nornir-build-thing` 0.1.9 from the registry, whose `IsoBuild` has
/// no `kernel_donor` field — the loader ignores the table benignly and the
/// whole-struct parity assertion above cannot hold it. So the Rust mirror is
/// `recipe::iso_kernel_donor()`, and this test holds the toml's table to it in
/// BOTH directions: the table's `repo`/`kernel` must equal the mirror's, and the
/// mirror must name the same donor the installer names (one kernel per
/// repository, chosen once). Edit either half alone and this goes red. Since the
/// 0.1.10 pin the LOADED value is asserted too, and `tests/iso_edge_linked.rs`
/// is the test that was RED until it could be.
#[test]
fn the_live_iso_declares_its_kernel_donor_and_the_raw_file_is_the_guard() {
let path = BuildVariantSet::conventional_path(&repo_root());
let raw =
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
// The table exists, once — a HEADER LINE, not a mention in a comment (the
// file talks about the table by name several times; a table is a line that
// is nothing but the header).
let header = "[build.iso.kernel_donor]";
let lines: Vec<&str> = raw.lines().collect();
let starts: Vec<usize> = lines
.iter()
.enumerate()
.filter(|(_, l)| l.trim() == header)
.map(|(i, _)| i)
.collect();
assert_eq!(
starts.len(),
1,
"the live iso variant declares {} [build.iso.kernel_donor] table(s); the composed \
medium boots whatever kernel the build box has unless it declares exactly one",
starts.len()
);
// Its body: the lines up to the next table header. Not a toml parser — the
// one loader this crate trusts is the registry 0.1.9 one, which drops this
// table, and a second parser here would be a second opinion.
let body: Vec<&str> = lines[starts[0] + 1..]
.iter()
.copied()
.take_while(|l| !l.trim_start().starts_with('['))
.map(str::trim)
.filter(|l| !l.is_empty() && !l.starts_with('#'))
.collect();
let mirror = gunnar_recipe::iso_kernel_donor();
let want_repo = format!("repo = \"{}\"", mirror.repo);
let want_kernel = format!("kernel = \"{}\"", mirror.kernel);
assert!(
body.contains(&want_repo.as_str()),
"the iso's kernel donor table says {body:?}; the Rust mirror says {want_repo}"
);
assert!(
body.contains(&want_kernel.as_str()),
"the iso's kernel donor table says {body:?}; the Rust mirror says {want_kernel}"
);
assert_eq!(
body.len(),
2,
"a kernel donor is exactly repo + kernel: {body:?}"
);
// One kernel per repository: the live medium and the installer name the SAME
// donor, and `kernel_donor_of` answers both by name and neither for an exe.
let set = from_toml();
let installer_donor = set
.installers
.iter()
.find(|i| i.name == "gunnar-installer")
.and_then(|i| i.kernel_donor.clone())
.expect("the installer declares its donor (the test above holds it)");
assert_eq!(
mirror, installer_donor,
"two media, two kernels — the live iso drifted from the installer"
);
assert_eq!(
gunnar_recipe::kernel_donor_of("gunnar-appliance-iso"),
Some(mirror.clone())
);
assert_eq!(
gunnar_recipe::kernel_donor_of("gunnar-installer"),
Some(mirror.clone())
);
assert_eq!(
gunnar_recipe::kernel_donor_of(gunnar_recipe::OPEN_INSTALLER),
Some(mirror.clone()),
"the keyless twin boots the same kernel"
);
assert_eq!(gunnar_recipe::kernel_donor_of("gunnar-cli"), None);
// …and the loader carries it too, since the 0.1.10 pin.
{
let iso = set
.isos
.iter()
.find(|i| i.name == "gunnar-appliance-iso")
.expect("the live iso is declared");
assert_eq!(
iso.kernel_donor.as_ref(),
Some(&mirror),
"the loader carries the iso's donor"
);
}
}
// ── 8. THE METERED CLOUD TWINS ──────────────────────────────────────────────
/// **Each metered twin is its open twin with the binary swapped and the key
/// added — and NOTHING else moved.**
///
/// Three twins (2026-09-05): `gunnar-installer-metered` of `-open` (UpCloud),
/// `-metered-aws` of `-aws`, `-metered-azure` of `-azure`. The property that
/// matters is the same for all three and is spelled by FIELD NAME rather than
/// as `assert_ne!`: a twin that also lost its kernel donor, its preset or its
/// `medium_boot = gpt` would still be "different from its open twin" and would
/// still carry the key, and would park on the cloud it names. So the set of
/// fields that differ is asserted exactly, and within `boot_params` only the
/// `korp.args=` token — where the argv gains `--monetize-pubkey <key>` and
/// changes in no other way.
///
/// Rows are read from the TOML the loader parsed, not from the code, so this
/// also holds the emission: a twin the emitter flattened (a lost
/// `[build.installer.target]` table, say) fails here by name.
#[test]
fn every_metered_twin_differs_from_its_open_twin_by_binary_key_name_and_volume_only() {
let toml = from_toml();
let row = |name: &str| {
toml.installers
.iter()
.find(|i| i.name == name)
.unwrap_or_else(|| panic!("`{name}` is declared"))
};
let pairs = [
(
gunnar_recipe::METERED_INSTALLER,
gunnar_recipe::OPEN_INSTALLER,
gunnar_recipe::METERED_VOLUME_ID,
),
(
gunnar_recipe::METERED_AWS_INSTALLER,
gunnar_recipe::AWS_INSTALLER,
gunnar_recipe::METERED_AWS_VOLUME_ID,
),
(
gunnar_recipe::METERED_AZURE_INSTALLER,
gunnar_recipe::AZURE_INSTALLER,
gunnar_recipe::METERED_AZURE_VOLUME_ID,
),
];
for (metered_name, open_name, volume_id) in pairs {
let m = row(metered_name);
let o = row(open_name);
// The field-by-field diff, by name. Every field of `InstallerBuild` is
// listed so a field added to the struct later is a compile-time nudge
// to decide whether a twin may move it.
let mut differ: Vec<&str> = Vec::new();
macro_rules! field {
($f:ident) => {
if m.$f != o.$f {
differ.push(stringify!($f));
}
};
}
field!(name);
field!(variant);
field!(installer_variant);
field!(volume_id);
field!(payload);
field!(container_backend);
field!(target_disk_gib);
field!(gpu);
field!(boot_params);
field!(cmd);
field!(env);
field!(boot_logo);
field!(kernel_donor);
field!(target);
field!(data);
field!(medium_boot);
field!(edges);
field!(presets);
field!(applied_preset);
field!(params);
field!(fixtures);
assert_eq!(
differ,
["name", "variant", "volume_id", "env", "params"],
"`{metered_name}` must be `{open_name}` with the binary swapped, its own volume \
id, the monetize pair in `env` and the parameter that replaces the key — and \
nothing else. A metered twin that moves its donor, preset, firmware or \
medium_boot is a medium for a machine that does not exist. Fields that differ: \
{differ:?}"
);
// The three that moved, each to what it must be.
assert_eq!(
m.variant,
gunnar_recipe::METERED_VARIANT,
"{metered_name}: the METERED binary"
);
assert_eq!(m.volume_id, volume_id, "{metered_name}: its own volume id");
assert_ne!(
m.volume_id, o.volume_id,
"{metered_name}: installtest resolves a medium to its cell by volume id FIRST"
);
// The argv is the open row's, byte for byte. What makes this medium the
// metered one is the BINARY it packs and the `env` pair below — and the
// pair is in `env` and not on the argv precisely so that a composing
// door can replace the key (`gunnar_recipe::metered_env`).
assert_eq!(
m.boot_params, o.boot_params,
"{metered_name}: the metered medium's argv IS the open one — metering is a \
property of the binary and of the monetize pair, not of the door"
);
// The monetize pair, from the ONE writer. The URL is not decoration:
// `gunnar-server` has no default for it and refuses at boot when given a
// key without one, and gunnar is PID 1 — so a medium carrying only the
// key kernel-panics on first boot. Measured 2026-09-08, as an outage.
// `metered_env` writes both halves or neither, so it cannot recur.
assert_eq!(
m.env,
gunnar_recipe::metered_env(gunnar_recipe::MONETIZE_PUBKEY),
"{metered_name}: the key and the renew URL that key requires, out of one writer"
);
assert!(
o.env.is_empty(),
"{open_name}: the community medium names no monetize key"
);
// The settings the front's live key arrives through: the open row's
// parameters plus exactly these two, appended in this order — the key's
// door, and `monetize_vital` (owner ruling 2026-09-22, 180b067: default
// true on every metered row). The test predates the second and read
// red on gunnar-live from then until 2026-09-27.
let mut want = o.params.clone();
want.push(gunnar_recipe::monetize_pubkey_param());
want.push(gunnar_recipe::monetize_vital_param());
// …and the usage share's two (owner 2026-09-28): the budget, default
// `none` in `metered_env`, and the window.
want.push(gunnar_recipe::admit_limit_param());
want.push(gunnar_recipe::admit_window_param());
assert_eq!(
m.params,
want,
"{metered_name}: the open row's parameters plus `{}` — the declared door a \
composing ladder hands the front's MONETIZE_SIGNING_PUBKEY through, so the \
default above can never go stale unnoticed — and `{}`",
gunnar_recipe::MONETIZE_PUBKEY_PARAM,
gunnar_recipe::MONETIZE_VITAL_PARAM
);
}
}
// ── the twin's memory bound rides the ROLE, and no medium ───────────────────
/// **The twin's memory bound is in CODE, and NO installer row — and no role — bakes it.**
///
/// MEASURED (`crates/gunnar-server/tests/twin_snapshot_memory.rs`, and lane T3's one-medium
/// run of 2026-09-21): with explode/graph off alone a 512 MiB snapshot install grew the heap
/// +133 MiB; two resolver workers, a 16 MiB lookahead and a 16 MiB explode budget brought it
/// to +36 MiB, under the 64 MiB ceiling — and a twin installed WITHOUT the explode token held
/// five `objects.exploded` files it may not hold. The bound is real; what changed is where it
/// is decided.
///
/// The five `korp.env.ZNIPPY_*` tokens were on a `gunnar-twin` installer ROW until the owner
/// ruling of 2026-09-21 (*"gunnar twin must be built EXACTLY like the main, it MUST use the
/// SAME iso"*) deleted the row, and then on the twin ROLE until `znippy-plugin-git` 0.1.5
/// shipped `set_explode_default` / `set_explode_budget_default` /
/// `set_resolve_workers_default` / `set_resolve_lookahead_default` beside
/// `set_graph_cache_default`. gunnar now calls them from `Engine::hold_twin_memory_bound`, so
/// **a twin needs nothing on its command line at all** — which is the only shape in which
/// "the same medium, no per-box parameter" is literally true.
///
/// So the assertion is now the empty one, in both places at once: the
/// code's declared rows and the committed toml. A token reappearing anywhere is either a
/// medium that is a twin or a per-box parameter, and both are the shape the ruling forbids.
#[test]
fn the_twin_memory_bound_is_on_the_role_and_no_row_bakes_it() {
let spelled = [
"korp.env.ZNIPPY_GIT_EXPLODE=off",
"korp.env.ZNIPPY_GIT_GRAPH_CACHE=off",
"korp.env.ZNIPPY_GIT_RESOLVE_WORKERS=2",
"korp.env.ZNIPPY_GIT_RESOLVE_LOOKAHEAD_BYTES=16777216",
"korp.env.ZNIPPY_GIT_EXPLODE_BUDGET=16777216",
];
// And nothing is a twin by declaration.
for (source, set) in [
("the recipe", GunnarBuildThing.variants()),
("the committed toml", from_toml()),
] {
for row in &set.installers {
for token in spelled {
let key = token.split('=').next().unwrap();
let carried: Vec<_> = row
.boot_params
.iter()
.filter(|p| p.split('=').next() == Some(key))
.collect();
assert!(
carried.is_empty(),
"{source}: row {} bakes {carried:?}. gunnar sets this in code now; a row \
that carries it is a medium that is a twin, and there are no twin media",
row.name
);
}
assert!(
!row.boot_params
.iter()
.any(|p| p.contains("--role,twin") || p.starts_with("korp.env.GUNNAR_ROLE=")),
"{source}: row {} declares itself a twin",
row.name
);
}
}
}