tests/appliance_ladder.rs
//! **nornir on the appliance ladder** — the eight rungs, answered by nornir, for
//! nornir.
//!
//! The rungs are the suite's, not this file's: `nornir_testmatrix::appliance`
//! declares `APPLIANCE_PRODUCTS × APPLIANCE_PROOFS`, and nornir is one of the
//! five products. Until now nornir emitted **nothing at all** on that grid — 0
//! of 8 — which the 2026-09-10 inventory reported as eight holes. A hole says
//! "somebody has not done the work yet". For five of these rungs that is simply
//! untrue: nornir has no ISO, no image and no tunnel, and never claimed one.
//!
//! So this file answers all eight, and the answer is one of exactly two kinds:
//!
//! * **a proof** — nornir's own binary is driven and the row carries what it
//! did (`identity_ready`, `identity_minted`); or
//! * **a declared `n/a`** — the rung is not a stage nornir has, and the reason
//! is MEASURED here rather than asserted in prose: the manifest and the build
//! declaration are read, and the row only goes out when the file really says
//! what the reason claims it says.
//!
//! The third rung with a proof, `identity_survives_replacement`, is emitted
//! where it was already proven — `src/bin/nornir.rs`'s
//! `uninstall_with_no_flags_kills_data_and_keeps_the_key`, which has asserted
//! exactly that since long before this file existed. A second test re-proving it
//! would be a second implementation of the same claim.
//!
//! ## Why an n/a and not an `#[ignore]`
//!
//! An `#[ignore]`d test is a proof somebody could run on the right box. There is
//! no box on which nornir boots off an installer ISO, because nornir declares no
//! bootable target. `functional_na` says that out loud, with the reason, and the
//! rollup renders it `·` (no such stage) rather than `—` (no evidence).
use std::path::{Path, PathBuf};
use std::process::Command;
use nornir_testmatrix::{functional_na, functional_status};
/// The host form of nornir's appliance component. nornir has exactly one form —
/// it is a systemd service, not a container and not an image — so the bare
/// product name is the whole vocabulary here.
///
/// It is spelled OUT at every call site rather than passed as this constant,
/// and the constant exists to be asserted against. A computed or `const`
/// coordinate is invisible to `cargo`-free discovery — the 2026-09-10 inventory
/// measured exactly that in holger and korp, where the component lived inside a
/// helper and six proven rungs read as "nothing emits this".
const HOST: &str = "nornir-appliance";
fn repo_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
fn read(rel: &str) -> String {
let p = repo_root().join(rel);
std::fs::read_to_string(&p).unwrap_or_else(|e| panic!("read {}: {e}", p.display()))
}
/// Does this TOML declare a section of the given kind? Textual on purpose — the
/// question is line-shaped (`[[build.iso]]` alone on a line) and a parser would
/// be a second reader of a file the build thing already owns.
fn declares_section(toml: &str, section: &str) -> bool {
toml.lines().any(|l| l.trim() == section)
}
/// **Rungs 1 and 2 — the host identity ceremony, driven through the real binary.**
///
/// Two runs of `nornir key generate` against a scratch key directory:
///
/// 1. the first MINTS (`identity_minted`) — the directory is empty before and
/// holds a private + public ed25519 pair after;
/// 2. the second LOADS the same identity (`identity_ready`) — the ceremony is
/// idempotent and the public half is byte-identical, which is the property
/// that makes an identity an identity rather than a fresh key per boot.
///
/// ## Isolating the ceremony from the operator's own key — and why the env
/// override alone is NOT enough
///
/// `DWARVES_SSH_DIR` and `NORNIR_SSH_DIR` are both pointed at the scratch dir,
/// which is the first rung of `nornir_git::deploykey`'s ladder. That is
/// necessary and it is not sufficient, because `Resolution::resolve()` picks the
/// first rung that **actually has a key**, not the first rung that is
/// configured. An empty scratch dir is skipped, the ladder walks on, and it
/// arrives at `$HOME/.nornir/ssh` — the developer's real identity. MEASURED:
/// with the two env vars set and nothing else, the first `key generate` printed
/// `• already present: ~/.nornir/ssh/id_ed25519.pub` and the mint
/// assertion failed on somebody's actual private key.
///
/// So `HOME` is redirected too, into a scratch home beside the key dir — and
/// `DWARVES_SSH_CONFINE=1` removes what `HOME` cannot reach. Rungs 4 and 6 of the
/// ladder are `/home/dwarves/.ssh` and `/home/nornir/.ssh`: ABSOLUTE paths that
/// no env var neutralises. Neither user exists on this box, so the test passed
/// without the confine — by luck about this machine, not by any property of the
/// fixture. On a box with a real dwarves/nornir service install it would have
/// reached a real SERVICE key. Confined, the two explicit overrides ARE the whole
/// ladder, so the isolation holds anywhere.
///
/// A test that had to touch the operator's key to pass would be proving nothing
/// about nornir and quite a lot about the box it ran on.
#[test]
fn the_host_identity_ceremony_mints_once_and_then_loads_the_same_identity() {
let base = std::env::temp_dir().join(format!("nornir-appliance-ladder-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&base);
let dir = base.join("ssh");
let home = base.join("home");
std::fs::create_dir_all(&dir).expect("scratch key dir");
std::fs::create_dir_all(&home).expect("scratch home");
let priv_path = dir.join("id_ed25519");
let pub_path = dir.join("id_ed25519.pub");
assert!(!pub_path.exists(), "the scratch dir starts with no identity");
let run = |what: &str| -> String {
let out = Command::new(env!("CARGO_BIN_EXE_nornir"))
.args(["key", "generate"])
.env("DWARVES_SSH_DIR", &dir)
.env("NORNIR_SSH_DIR", &dir)
// The rung below the env overrides — without this the ladder reaches
// the real one. See the note above.
.env("HOME", &home)
// …and the rungs $HOME CANNOT reach: /home/dwarves/.ssh and
// /home/nornir/.ssh are ABSOLUTE. On this box neither user exists, so
// the test passed without this — but on a box with a real service
// install the ladder would walk into a real SERVICE key, and no env
// var could stop it. DWARVES_SSH_CONFINE deletes those rungs
// outright (nornir-git, edda 1b70392), which is what makes the
// isolation a property of the FIXTURE rather than of this machine.
.env("DWARVES_SSH_CONFINE", "1")
.output()
.unwrap_or_else(|e| panic!("run `nornir key generate` ({what}): {e}"));
assert!(out.status.success(), "`nornir key generate` ({what}) exited {}", out.status);
let stdout = String::from_utf8_lossy(&out.stdout).to_string();
// The ceremony must have happened in the SCRATCH dir. If the ladder ever
// walks past it again, this says so in one line instead of leaving a
// confusing assertion failure three lines down.
assert!(
!stdout.contains("/.nornir/ssh") || stdout.contains(&*dir.to_string_lossy()),
"`nornir key generate` ({what}) reached an identity outside the scratch dir {} — \
the deploykey ladder walked past the override: {stdout}",
dir.display()
);
stdout
};
let first = run("mint");
let minted = priv_path.is_file() && pub_path.is_file();
let pub1 = std::fs::read_to_string(&pub_path).unwrap_or_default();
let shaped = pub1.starts_with("ssh-ed25519 ") && pub1.contains("nornir@");
functional_status(
"nornir-appliance",
"identity_minted",
minted && shaped && first.contains("generated"),
&format!(
"host form (nornir declares no container image): `nornir key generate` minted \
{} + {} — {:?}",
priv_path.display(),
pub_path.display(),
pub1.split_whitespace().next_back().unwrap_or("")
),
);
assert!(minted, "the mint wrote both halves: {first}");
assert!(shaped, "the public half is an ed25519 OpenSSH line: {pub1:?}");
let second = run("load");
let pub2 = std::fs::read_to_string(&pub_path).unwrap_or_default();
let stable = pub2 == pub1 && !pub1.is_empty();
functional_status(
"nornir-appliance",
"identity_ready",
stable && second.contains("already present"),
&format!(
"host form: the second ceremony LOADED the same identity (public half byte-identical, \
{} bytes) instead of minting a new one",
pub1.len()
),
);
assert!(stable, "a re-run must not mint a second identity:\n{first}\n{second}");
assert!(second.contains("already present"), "and it must say so: {second}");
let _ = std::fs::remove_dir_all(&base);
}
/// **Rungs 5 and 6 — the tunnel, which nornir has not got.**
///
/// The ceremony that proves `tunnel_up_traffic_crossed` and
/// `ping_through_tunnel` lives in one crate, `wg-appliance-core`; a product that
/// does not depend on it has no tunnel to bring up. That is the measurement:
/// nornir's own manifest is read and the absence is checked, so the day nornir
/// grows a tunnel this test goes RED and the two rows stop being `n/a`.
#[test]
fn nornir_has_no_tunnel_so_the_two_tunnel_rungs_are_declared_not_applicable() {
let manifest = read("Cargo.toml");
let live_dep = |needle: &str| {
manifest
.lines()
.map(str::trim)
.filter(|l| !l.starts_with('#'))
.any(|l| l.starts_with(needle))
};
let has_ceremony = live_dep("wg-appliance-core");
let has_tunnr_vm = live_dep("tunnr-vm");
assert!(
!has_ceremony && !has_tunnr_vm,
"nornir now declares a tunnel dependency — these two rows are no longer n/a, \
they are proofs somebody owes (wg-appliance-core={has_ceremony}, tunnr-vm={has_tunnr_vm})"
);
functional_na(
"nornir-appliance",
"tunnel_up_traffic_crossed",
"nornir declares no wg-appliance-core (the crate that owns the ceremony) and no tunnr-vm — \
it is a warehouse daemon on the host network, with no tunnel to bring up",
);
functional_na(
"nornir-appliance",
"ping_through_tunnel",
"nornir declares no wg-appliance-core (the crate that owns the ceremony) and no tunnr-vm — \
there is no tunnel for an echo to cross",
);
}
/// **Rungs 3, 7 and 8 — the image and the disk, which nornir also has not got.**
///
/// `identity_survives_upgrade` is an IMAGE upgrade, `install_ok` is an installer
/// ISO laying the product onto a disk, and `secure_boot_armed` is enforcing
/// firmware accepting that ISO. All three presuppose a bootable artefact, and
/// nornir's build declaration has none: `.nornir/build-variants.toml` declares
/// `[[build.variant]]` rows and nothing else. Measured here, so the day nornir
/// declares an ISO the test goes red and three `n/a`s become three holes to fill.
#[test]
fn nornir_declares_no_bootable_artefact_so_three_rungs_are_declared_not_applicable() {
let variants = read(".nornir/build-variants.toml");
let image = declares_section(&variants, "[[build.image]]");
let iso = declares_section(&variants, "[[build.iso]]");
let installer = declares_section(&variants, "[[build.installer]]");
let vm = declares_section(&variants, "[[build.vm]]");
assert!(
declares_section(&variants, "[[build.variant]]"),
"the file was read and it really is nornir's build declaration"
);
assert!(
!image && !iso && !installer && !vm,
"nornir now declares a bootable artefact — these rows are proofs somebody owes, not n/a \
(image={image}, iso={iso}, installer={installer}, vm={vm})"
);
functional_na(
"nornir-appliance",
"identity_survives_upgrade",
"nornir declares no [[build.image]] — there is no image to upgrade; the service form's \
upgrade is `nornir install update`, and the key store it must not touch is proven by \
identity_survives_replacement",
);
functional_na(
"nornir-appliance",
"install_ok",
"nornir declares no [[build.iso]] and no [[build.installer]] in \
.nornir/build-variants.toml — it installs as a systemd unit through skidbladnir, it \
never lays itself onto a disk",
);
functional_na(
"nornir-appliance",
"secure_boot_armed",
"nornir signs no boot artefact because it declares none — see install_ok; enforcing \
firmware has nothing of nornir's to accept or refuse",
);
}
/// The file has to answer ALL EIGHT — a ladder with a quietly missing rung is
/// the same failure as the grid this whole exercise came out of.
///
/// So the roster is read from the crate that owns it and every rung is checked
/// off against what this repo emits: the two proved here, the one proved in
/// `src/bin/nornir.rs`, and the five declared `n/a`.
#[test]
fn every_rung_of_the_ladder_is_answered_by_this_repo() {
let here = Path::new(file!()).file_name().and_then(|n| n.to_str()).unwrap_or("");
assert!(!here.is_empty(), "this test knows its own file");
let answered: [(&str, &str); 8] = [
("identity_ready", "proved here"),
("identity_minted", "proved here"),
("identity_survives_replacement", "proved in src/bin/nornir.rs"),
("identity_survives_upgrade", "n/a here"),
("tunnel_up_traffic_crossed", "n/a here"),
("ping_through_tunnel", "n/a here"),
("install_ok", "n/a here"),
("secure_boot_armed", "n/a here"),
];
for proof in nornir_testmatrix::APPLIANCE_PROOFS {
assert!(
answered.iter().any(|(p, _)| *p == proof),
"the ladder grew the rung `{proof}` and nornir answers it nowhere — \
a silent hole is exactly what this file exists to prevent"
);
}
// …and the claims about OTHER files are true, not decoration.
let ladder = read("tests/appliance_ladder.rs");
for proof in ["identity_ready", "identity_minted"] {
assert!(ladder.contains(&format!("\"{proof}\"")), "{proof} is emitted in this file");
}
// The component is spelled out at the call sites, and THIS is the spelling.
assert!(
ladder.contains(&format!("\"{HOST}\"")),
"every row here carries the host component `{HOST}`"
);
let bin = read("src/bin/nornir.rs");
assert!(
bin.contains("\"identity_survives_replacement\""),
"src/bin/nornir.rs really is where the replacement rung is emitted"
);
}