crates/gunnar-recipe/tests/appliance_reachable.rs
//! **Every appliance variant gunnar DECLARES must be administrable once installed.**
//!
//! `reachability::check_reachable` is the rule; this is the rule applied to the
//! declaration actually on disk, loaded by `BuildVariantSet::load_file` — the
//! same loader dwarves' forge uses — so what is asserted here is what the forge
//! would build, not a fixture that resembles it.
//!
//! The defect it was written against was measured on installed metal, not
//! inferred: an appliance from `gunnar-installer` booted, took a DHCP lease,
//! answered `SSH-2.0-gunnar_1.1.3`, and logged *"Nobody can reach this server"*
//! on every start, because both appliance variants declared `--no-grpc` and
//! neither declared an admin key.
//!
//! # And then it passed a medium that admits nobody, onto a public release page
//!
//! The rule it enforced was `disables_control_plane(argv) &&
//! !declares_admin_key(argv)` — a test of the argv's TEXT. The ISO variant spelled
//! `--admin-key @/data/admin.pub` unconditionally while its `admin_key` parameter
//! was `required = false`, so the literal satisfied the gate and no file had to
//! exist. `gunnar-appliance-iso-thin-nowg.iso` (16 611 328 B, release `v1.1.3`)
//! was BOOTED on 2026-08-27 and printed `reading the public key from
//! /data/admin.pub: No such file or directory`.
//!
//! So this file now passes the cell's TYP and its declared PARAMETER ROSTER to
//! the rule, because that is what decides whether the file the argv reads is
//! guaranteed to exist. The population guard below moved with it: the interesting
//! property is no longer "both variants say `--no-grpc`" (they no longer do — the
//! ISO takes the TOFU door) but "each variant leaves a DIFFERENT door open", which
//! is what makes the two subjects two subjects.
use std::path::{Path, PathBuf};
use gunnar_recipe::reachability::{
admin_key_argument, app_argv, binds_control_plane_offbox, check_reachable,
disables_control_plane,
};
use nornir_build_thing::params::ParamSpec;
use nornir_build_thing::variant::{ArtifactKind, BuildVariantSet};
fn repo_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(Path::parent)
.expect("crates/gunnar-build-thing has two ancestors")
.to_path_buf()
}
fn from_toml() -> BuildVariantSet {
let path = BuildVariantSet::conventional_path(&repo_root());
BuildVariantSet::load_file(&path).unwrap_or_else(|e| panic!("load {}: {e:#}", path.display()))
}
/// `(name, typ, argv, declared params, boot params)` of one appliance variant.
type Appliance = (
String,
ArtifactKind,
Vec<String>,
Vec<ParamSpec>,
Vec<String>,
);
/// Every appliance variant the file declares — the one place this test file
/// reads the declaration, so the population guard and the gate cannot be
/// looking at two different sets.
fn appliances() -> Vec<Appliance> {
let set = from_toml();
let mut out = Vec::new();
for iso in &set.isos {
// The RAW boot params, not `app_argv` — `GUNNAR_NET` is a kernel
// cmdline `korp.env.` assignment, not one of the app's own arguments,
// so it is gone by the time the argv exists.
out.push((
iso.name.clone(),
ArtifactKind::Iso,
app_argv(&iso.boot_params, &[]),
iso.params.clone(),
iso.boot_params.clone(),
));
}
// The FRONT medium (2026-09-25) is not a git appliance: its payload is
// `gunnar-front`, which has no control plane to administer. Its own guard
// is `the_front_medium_lays_the_front_and_no_git_server` below.
for inst in set.installers.iter().filter(|i| {
i.variant != gunnar_recipe::FRONT_PAYLOAD_VARIANT
&& i.variant != gunnar_recipe::FRONT_METERED_PAYLOAD_VARIANT
}) {
out.push((
inst.name.clone(),
ArtifactKind::Installer,
app_argv(&inst.boot_params, &inst.cmd),
inst.params.clone(),
inst.boot_params.clone(),
));
}
out
}
/// Measured on this file's own subject matter.
///
/// A gate over "every appliance variant" is vacuous if the loader hands back zero
/// appliance variants — it would pass a repo that declares no ISO and no installer
/// at all, which is precisely the shape of a check that can never fail. This pins
/// the population AND pins that the two subjects are genuinely different cases:
/// one takes the TOFU door (control plane up, bound off the loopback, no admin
/// key), the other takes the seeded-key door (`--no-grpc` plus a `required`
/// parameter). If both ever collapsed onto one shape, the gate below would be
/// testing one case twice and reporting it as two.
#[test]
fn the_population_this_gate_runs_over_is_not_empty_and_holds_both_doors() {
let set = from_toml();
assert_eq!(
set.isos.len(),
1,
"gunnar declares one [[build.iso]] variant"
);
// **The population BY NAME, not by count.**
//
// A count is the right guard — a variant that silently disappeared is
// exactly what this test exists to catch — and a bare count is the wrong
// way to spell it. This assertion read `3` and failed `left: 4 / right: 3`
// for two days after `c12aac70` (2026-09-02) added `gunnar-installer-aws`,
// because that message tells the next reader a number changed and nothing
// about WHICH variant arrived or whether it was supposed to. The names make
// the diff say it, and adding a variant now fails with the new name in the
// output.
let mut declared: Vec<&str> = set.installers.iter().map(|i| i.name.as_str()).collect();
declared.sort_unstable();
let mut expected = vec![
gunnar_recipe::SEALED_INSTALLER,
gunnar_recipe::OPEN_INSTALLER,
gunnar_recipe::GCE_INSTALLER,
gunnar_recipe::AWS_INSTALLER,
gunnar_recipe::AZURE_INSTALLER,
gunnar_recipe::METERED_INSTALLER,
gunnar_recipe::METERED_AWS_INSTALLER,
gunnar_recipe::METERED_AZURE_INSTALLER,
gunnar_recipe::FRONT_INSTALLER,
gunnar_recipe::FRONT_METERED_INSTALLER,
];
expected.sort_unstable();
assert_eq!(
declared, expected,
"gunnar's [[build.installer]] roster moved. The eight are: the sealed installer, its \
keyless twin (2026-08-28), the keyless twin's virtio-scsi twin for a GCE-shaped \
machine (2026-08-30), the AWS Nitro twin (`c12aac70`, 2026-09-02), the \
Azure Hyper-V twin (2026-09-05) and the METERED twin (2026-09-05) — the first row \
to name `gunnar-server-metered`, which had existed since the entitlement work with no \
medium able to carry it — and the metered twins of the AWS and Azure rows \
(2026-09-05), the same binary on the other two clouds' machines. A ninth row, \
`gunnar-twin`, stood here from 2026-09-15 to 2026-09-17 and was DELETED: a twin is \
a ROLE of an appliance, conferred over the wire (Bootstrap.BecomeTwin), not a medium, and as a medium it was \
the keyless lineage's clone — so the metered appliance's twin ran the UNMETERED \
binary and a promotion would have dropped entitlement enforcement in silence. The \
FRONT medium `gunnar-front-installer-open` (2026-09-25) is a row and not an \
appliance: vm1's own medium, payload `gunnar-front` (owner: main and twin share \
the gunnar medium, the front boots its own); its paid twin `gunnar-front-installer-metered` (2026-09-25) is the same medium with the `metered` payload. If \
you added one, name it here; if one vanished, that is \
the bug this catches"
);
let mut tofu = Vec::new();
let mut seeded = Vec::new();
for (name, _kind, argv, _params, _bp) in appliances() {
if admin_key_argument(&argv).is_some() {
assert!(
disables_control_plane(&argv),
"{name}: a seeded-key appliance also closes the bootstrap window"
);
seeded.push(name);
} else {
assert!(
binds_control_plane_offbox(&argv),
"{name}: a TOFU appliance must bind the control plane off the loopback — \
gunnar has no shell and no port forwarding, so 127.0.0.1:50051 is nobody's \
door. argv: {argv:?}"
);
tofu.push(name);
}
}
assert_eq!(
tofu,
vec![
"gunnar-appliance-iso".to_string(),
gunnar_recipe::OPEN_INSTALLER.to_string(),
gunnar_recipe::GCE_INSTALLER.to_string(),
gunnar_recipe::AWS_INSTALLER.to_string(),
gunnar_recipe::AZURE_INSTALLER.to_string(),
gunnar_recipe::METERED_INSTALLER.to_string(),
gunnar_recipe::METERED_AWS_INSTALLER.to_string(),
gunnar_recipe::METERED_AZURE_INSTALLER.to_string(),
],
"the LIVE medium (RAM state) takes TOFU, and so does the KEYLESS installer — the \
release-page medium whose customer's key is unknown at build time (2026-08-28) — \
and so does its virtio-scsi twin, which is the SAME door on a different machine \
machine: virtio-scsi for GCE (2026-08-30) and Nitro for AWS (`c12aac70`, \
2026-09-02). The METERED twin is here too, and belongs here: metering is a \
property of the BINARY, not of the bootstrap door — the customer's ADMIN key is \
still unknown at build time, and the monetize key it does carry is a public \
VERIFYING key that authenticates nobody to the control plane. Every keyless \
medium belongs in this list, so a new twin is added HERE in the same commit that \
declares it"
);
assert_eq!(
seeded,
vec!["gunnar-installer".to_string()],
"the PERSISTENT medium (customer's disk) is the one sealed to a named key"
);
}
/// The gate, applied to the declaration on disk.
///
/// Red on the declaration that shipped as v1.1.3:
///
/// ```text
/// appliance variant 'gunnar-appliance-iso' reads its bootstrap admin from
/// `--admin-key @/data/admin.pub`, but NO declared parameter is `required = true`
/// AND lands there — so a build that supplies nothing is legal and the medium it
/// produces boots a server that dies on `reading the public key from
/// /data/admin.pub: No such file or directory`
/// ```
#[test]
fn every_declared_appliance_variant_can_be_administered() {
for (name, kind, argv, params, boot_params) in appliances() {
check_reachable(&name, kind, &argv, ¶ms, &boot_params)
.unwrap_or_else(|e| panic!("{e:#}"));
}
}
/// **The green above must be capable of red on THIS file's own inputs**, not
/// merely on hand-built fixtures in the unit tests. Each mutation below is one
/// edit away from the declaration on disk, and each must be refused — otherwise
/// `every_declared_appliance_variant_can_be_administered` is a green nobody has
/// seen red.
#[test]
fn one_edit_to_the_real_declaration_is_enough_to_make_the_gate_fire() {
let all = appliances();
// 1. The ISO goes back to the published v1.1.3 argv, with the roster it
// shipped beside it. This is the exact medium on the release page.
let (iso_name, iso_kind, _, _, iso_bp) = all
.iter()
.find(|(_, k, _, _, _)| *k == ArtifactKind::Iso)
.expect("one iso");
let published = app_argv(
&[
"korp.entry=gunnar",
"--",
"serve",
"--no-grpc",
"--admin-key",
"@/data/admin.pub",
]
.map(str::to_string),
&[],
);
let shipped_roster = vec![ParamSpec {
lands: nornir_build_thing::params::Landing::Data,
name: "admin_key".into(),
label: "Bootstrap-admin (OpenSSH-publik nyckel)".into(),
kind: nornir_build_thing::params::ParamKind::Text,
required: false,
boot_param: String::new(),
staged_file: "admin.pub".into(),
}];
let err = check_reachable(iso_name, *iso_kind, &published, &shipped_roster, iso_bp)
.expect_err("the published v1.1.3 medium must not build");
assert!(err.to_string().contains("/data/admin.pub"), "{err}");
// 2. The ISO keeps TOFU but loses the explicit bind — the naive fix, which
// ships a control plane nothing off the box can reach.
let (_, _, iso_argv, iso_params, iso_bp2) = all
.iter()
.find(|(_, k, _, _, _)| *k == ArtifactKind::Iso)
.expect("one iso");
let unbound: Vec<String> = {
let mut v = iso_argv.clone();
if let Some(i) = v.iter().position(|a| a == "--grpc-bind") {
v.drain(i..=i + 1);
}
v
};
assert_ne!(
&unbound, iso_argv,
"the real declaration does carry --grpc-bind"
);
let err = check_reachable(iso_name, *iso_kind, &unbound, iso_params, iso_bp2)
.expect_err("a loopback control plane admits nobody");
assert!(err.to_string().contains("LOOPBACK"), "{err}");
// 3. The INSTALLER's admin_key drops to `required = false` — the one field
// whose flip is what let the ISO's defect through.
let (inst_name, inst_kind, inst_argv, inst_params, inst_bp) = all
.iter()
.find(|(n, k, _, _, _)| *k == ArtifactKind::Installer && n == "gunnar-installer")
.expect("the sealed installer");
let relaxed: Vec<ParamSpec> = inst_params
.iter()
.map(|p| {
let mut p = p.clone();
if p.name == "admin_key" {
p.required = false;
}
p
})
.collect();
assert_ne!(
&relaxed, inst_params,
"the real installer does require admin_key"
);
let err = check_reachable(inst_name, *inst_kind, inst_argv, &relaxed, inst_bp)
.expect_err("an optional admin key is no guarantee");
assert!(err.to_string().contains("required = true"), "{err}");
// 4. The INSTALLER's admin_key keeps `required = true` but its staged file
// moves one directory — the I7 defect, on the other medium.
let misplaced: Vec<ParamSpec> = inst_params
.iter()
.map(|p| {
let mut p = p.clone();
if p.name == "admin_key" {
p.staged_file = format!("etc/{}", p.staged_file);
}
p
})
.collect();
let err = check_reachable(inst_name, *inst_kind, inst_argv, &misplaced, inst_bp)
.expect_err("a required key landing somewhere else is not this argv's key");
assert!(err.to_string().contains("/data/etc/admin.pub"), "{err}");
// 5. The KEYLESS installer gets `--no-grpc` put back. It has no `admin_key`
// parameter at all (an optional one nothing reads would be the v1.1.3
// defect in a new place), so this is the 2026-08-25 brick: a persistent
// server that authorizes nobody, forever. Refused by name.
let (open_name, open_kind, open_argv, open_params, open_bp) = all
.iter()
.find(|(n, _, _, _, _)| n == gunnar_recipe::OPEN_INSTALLER)
.expect("the keyless installer");
assert!(
!open_params.iter().any(|p| p.name == "admin_key"),
"the keyless installer declares no admin_key at all: {open_params:?}"
);
let sealed_again: Vec<String> = {
let mut v = open_argv.clone();
v.push("--no-grpc".to_string());
v
};
let err = check_reachable(open_name, *open_kind, &sealed_again, open_params, open_bp)
.expect_err("--no-grpc with no key behind it is the 2026-08-25 brick");
assert!(err.to_string().contains("no control plane at all"), "{err}");
// 6. The KEYLESS installer loses its explicit bind — the naive TOFU, on the
// persistent medium this time. Loopback on a box with no shell: nobody's.
let unbound_open: Vec<String> = {
let mut v = open_argv.clone();
if let Some(i) = v.iter().position(|a| a == "--grpc-bind") {
v.drain(i..=i + 1);
}
v
};
assert_ne!(
&unbound_open, open_argv,
"the real keyless declaration does carry --grpc-bind"
);
let err = check_reachable(open_name, *open_kind, &unbound_open, open_params, open_bp)
.expect_err("a loopback control plane admits nobody");
assert!(err.to_string().contains("LOOPBACK"), "{err}");
}
/// **The front medium lays the front and nothing of the git server** (owner
/// 2026-09-25: "twin/main gunnar share ISO, but NOT the ui/monetize"). Its
/// payload is `gunnar-front`, its entry execs that, it carries no control-plane
/// argv and no appliance gate, and it reaches the installer the way the gunnar
/// medium does: the same pairing seed, the same donor kernel, the same kernel
/// requirements (the gunnar medium's options' rows moved onto it).
#[test]
fn the_front_medium_lays_the_front_and_no_git_server() {
let set = from_toml();
let by = |n: &str| {
set.installers
.iter()
.find(|i| i.name == n)
.unwrap_or_else(|| panic!("{n} is not declared"))
};
let front = by(gunnar_recipe::FRONT_INSTALLER);
let open = by(gunnar_recipe::OPEN_INSTALLER);
assert_eq!(front.variant, gunnar_recipe::FRONT_PAYLOAD_VARIANT);
assert_eq!(front.volume_id, gunnar_recipe::FRONT_VOLUME_ID);
assert!(
front
.boot_params
.iter()
.any(|p| p == gunnar_recipe::FRONT_ENTRY),
"{:?}",
front.boot_params
);
assert!(
!front
.boot_params
.iter()
.any(|p| p.starts_with("korp.args=") || p.contains("GUNNAR_APPLIANCE")),
"no git server argv, no appliance gate: {:?}",
front.boot_params
);
assert!(
front
.params
.iter()
.any(|p| p.name == gunnar_recipe::PAIRING_SEED_PARAM
&& p.staged_file == gunnar_recipe::PAIRING_SEED_STAGED_FILE),
"the front reaches the installer with the same seed"
);
assert_eq!(
front.kernel_donor, open.kernel_donor,
"the same donor kernel as the gunnar medium"
);
let symbols = |i: &nornir_build_thing::installer::InstallerBuild| {
let mut s: Vec<String> = i
.requires
.iter()
.map(|r| format!("{:?}", r.what))
.filter_map(|w| {
w.split("symbol: \"")
.nth(1)
.map(|t| t.split('"').next().unwrap_or("").to_string())
})
.collect();
s.extend(
i.options
.iter()
.flat_map(|o| o.kernel_requires.iter().map(|k| k.symbol.clone())),
);
s.sort();
s.dedup();
s
};
assert!(
symbols(front).iter().any(|s| s == "CONFIG_TUN")
&& symbols(front).iter().any(|s| s == "CONFIG_KVM_GUEST"),
"the closure was read: {:?}",
symbols(front)
);
assert_eq!(
symbols(front),
symbols(open),
"the front's kernel closure is the gunnar medium's"
);
// The paid front is the same medium with the metered payload: the CLI hole's reach is the
// site's feed, never the medium's (RA1-14).
let paid = by(gunnar_recipe::FRONT_METERED_INSTALLER);
assert_eq!(paid.variant, gunnar_recipe::FRONT_METERED_PAYLOAD_VARIANT);
assert_eq!(paid.volume_id, gunnar_recipe::FRONT_METERED_VOLUME_ID);
assert_eq!(
(&paid.boot_params, &paid.params, &paid.kernel_donor),
(&front.boot_params, &front.params, &front.kernel_donor),
"the paid front differs from the open one by its payload only"
);
// And the gunnar media never lay the front.
for i in set.installers.iter().filter(|i| {
i.name != gunnar_recipe::FRONT_INSTALLER && i.name != gunnar_recipe::FRONT_METERED_INSTALLER
}) {
assert_ne!(
i.variant,
gunnar_recipe::FRONT_PAYLOAD_VARIANT,
"{} lays the front",
i.name
);
assert!(
!i.boot_params
.iter()
.any(|p| p == gunnar_recipe::FRONT_ENTRY),
"{} execs the front",
i.name
);
}
}