build-space/tests/suite_build_shape.rs
//! # Suite build-shape gate — holger's LINKAGE axis, VALIDATED against Cargo
//!
//! Loads holger's OWN on-disk `.nornir/build-variants.toml` through nornir's
//! build-thing (`nornir_build_thing::BuildVariantSet::discover`) and proves the
//! nordisk suite-build matrix holds for holger:
//!
//! * the whole descriptor `validate()`s (every variant, incl. the fat/thin
//! asymmetry rule);
//! * every ROLE names exactly one normal case (`default = true`);
//! * every variant DECLARES its linkage rather than inheriting the `fat` default
//! by silence;
//! * the `[build.downstream]` allow-list still encodes `holger -> [skade]`.
//!
//! RED-WHEN-BROKEN: the asymmetry rule and the thin-may-not-embed rule are still
//! proved directly, on synthetic variants, at the end.
use std::collections::BTreeSet;
use std::path::Path;
use nornir_build_thing::variant::ArtifactKind;
use nornir_build_thing::{BuildVariant, BuildVariantSet, ServiceShape};
/// Discover the repo's `.nornir/build-variants.toml` (repo root = the build-space
/// crate's parent dir) and assert the linkage/role/default axes.
#[test]
fn holger_build_variants_validate_with_fat_thin_service_shape() {
let repo_root = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("holger repo root is the build-space crate's parent");
let set = BuildVariantSet::discover(repo_root)
.expect("read/parse .nornir/build-variants.toml")
.expect("holger declares a .nornir/build-variants.toml");
// The whole set validates — every variant AND the fat/thin asymmetry rule.
set.validate_all()
.expect("holger's declared build variants all validate");
// The suite dep graph as data: holger is downstream of skade only. Kept as the
// RULE even though no variant exercises it today (see the module docs).
assert_eq!(
set.downstream.get("holger"),
Some(&BTreeSet::from(["skade".to_string()])),
"[build.downstream] must encode holger -> [skade]"
);
// ── EVERY ROW NAMES ITS DELIVERY: one, MVP (lane HOL-PRODUCTS, 2026-10-30). ──
assert!(
set.declares_suites(),
"every atom must name its delivery — suite is a COLUMN, not an optional extra"
);
assert_eq!(set.suites(), vec!["MVP"], "holger ships one product list");
assert!(set.check_suites().is_empty(), "{:?}", set.check_suites());
assert!(
set.base_suite.is_none(),
"one suite has no base to carve it out of"
);
// A row built from a sibling checkout (`manifest_dir` outside this repo: the console,
// `holger-ui`) is held in its own repository; the checks below walk THIS repo.
let here = |v: &BuildVariant| {
!v.manifest_dir
.as_deref()
.is_some_and(|d| d.starts_with("../"))
};
// ── EVERY ROW RESOLVES. ──
// Every row that compiles something must name a package cargo knows and features
// that package actually declares; a row naming a feature that does not exist is a
// delivery that cannot build.
let tables = BuildVariantSet::cargo_feature_tables(repo_root)
.expect("read the workspace's [features] tables");
assert!(
tables.contains_key("holger") && tables.contains_key("holger-downstream") && tables.contains_key("holger-cli"),
"the product packages must resolve, or the check below is checking nothing; got {} packages",
tables.len()
);
let mut unresolvable: Vec<String> = Vec::new();
for v in set.variants.iter().filter(|v| here(v)) {
let Some(pkg) = v.package.as_deref() else {
continue;
}; // delegating row: no cargo argv
let Some(table) = tables.get(pkg) else {
unresolvable.push(format!(
"{}: package '{}' is not in the workspace",
v.name, pkg
));
continue;
};
for f in &v.features {
// `dep:serde` and `egui/serde` are opaque tokens cargo resolves
// elsewhere — they are never keys in this package's own table.
if f.contains('/') || f.starts_with("dep:") {
continue;
}
if !table.features.contains_key(f.as_str()) {
unresolvable.push(format!(
"{}: feature '{}' is not declared by '{}'",
v.name, f, pkg
));
}
}
}
assert!(
unresolvable.is_empty(),
"rows that cannot build:\n{}",
unresolvable.join("\n")
);
// ── EVERY DECLARED BIN IS A BIN CARGO ACTUALLY BUILDS. ──
// The roster comes from WALKING THE MANIFESTS, never from a list in this test.
let real = BuildVariantSet::cargo_bin_targets(repo_root).expect("walk holger's manifests");
assert!(
["holger", "holger-downstream", "holger-cli"]
.iter()
.all(|b| real.iter().any(|r| r == b)),
"the manifest walk must actually find holger's bins, or the check below compares \
against an empty roster; got {} bins",
real.len()
);
let mut local = set.clone();
local.variants.retain(|v| here(v));
let undeclared = local.check_bins_declared(&real);
assert!(undeclared.is_empty(), "{}", undeclared.join("\n"));
// ── NO TWO ROWS WRITE THE SAME FILE. ──
let collisions = set.check_bins();
assert!(collisions.is_empty(), "{collisions:?}");
// ── Every (suite, role) group names exactly one normal case. ──
assert!(
set.check_defaults().is_empty(),
"{:?}",
set.check_defaults()
);
for (role, want) in [
(nornir_build_thing::plugin::Role::Server, "holger"),
(nornir_build_thing::plugin::Role::Client, "holger-cli"),
] {
assert_eq!(
set.default_in("MVP", Some(role)).map(|v| v.name.as_str()),
Some(want),
"(MVP, {role:?}) must name {want}"
);
assert_eq!(
set.default_for_role(Some(role)).map(|v| v.name.as_str()),
Some(want),
"{role:?}"
);
}
// ── The server rows are all fat: each owns its own state and needs no nordisk
// sibling running. ──
for row in [
"holger",
"holger-downstream",
"mvp-server-musl",
"mvp-server-metered",
] {
let v = set
.get(row)
.unwrap_or_else(|| panic!("{row} variant present"));
assert_eq!(
v.shape,
ServiceShape::FatService,
"{row}: a holger server is a state owner, not a client of one"
);
assert!(
v.embeds.is_empty(),
"{row}: holger depends on no skade to embed"
);
assert!(
v.reaches.is_empty(),
"{row}: no nordisk owner needs to be running"
);
}
// ── The product rows are their crates' `cargo install`: no feature list of their
// own, so the crate's `default` is the one writer of what they carry. ──
for row in ["holger", "holger-downstream", "holger-cli", "holger-ui"] {
let v = set
.get(row)
.unwrap_or_else(|| panic!("{row} variant present"));
assert_eq!(v.package.as_deref(), Some(row));
assert_eq!(
v.kind,
ArtifactKind::Exe,
"{row}: what `cargo install` leaves in ~/.cargo/bin"
);
assert!(
v.features.is_empty() && !v.no_default_features,
"{row} must inherit its crate's `default`, not restate it; got features={:?} no_default_features={}",
v.features,
v.no_default_features
);
}
// ── The genuinely thin artifacts: the command-line tool and the console. Neither
// links server-lib; every server verb goes over the wire. ──
for row in ["holger-cli", "holger-ui"] {
let v = set.get(row).unwrap();
assert_eq!(v.shape, ServiceShape::ThinStandalone, "{row}");
assert!(v.embeds.is_empty(), "{row}: thin compiles in no siblings");
assert_eq!(
v.reaches,
BTreeSet::from(["holger".to_string()]),
"{row} needs a holger running"
);
assert_eq!(
v.role,
Some(nornir_build_thing::plugin::Role::Client),
"{row}"
);
}
// ── THE egui UI TIERS ARE holger-egui's. A `ui*` row reappearing here would mean
// the client came back WITHOUT the rules that judged it. ──
let ui_rows: Vec<&str> = set
.variants
.iter()
.map(|v| v.name.as_str())
.filter(|n| *n == "ui" || n.starts_with("ui-"))
.collect();
assert!(
ui_rows.is_empty(),
"an egui client tier is declared here again ({ui_rows:?}); declare it in holger-egui"
);
// ── RED-WHEN-BROKEN proof #1: a fat holger embedding a NON-downstream member
// (nornir is upstream/peer, absent from holger's allow-list) inverts the dep
// graph and MUST be rejected. ──
let inverted = BuildVariant {
name: "holger-inverted".to_string(),
member: Some("holger".to_string()),
kind: ArtifactKind::Service,
shape: ServiceShape::FatService,
embeds: BTreeSet::from(["nornir".to_string()]),
..Default::default()
};
let err = set
.validate(&inverted)
.expect_err("embedding a non-downstream member must be rejected");
assert!(
err.to_string().contains("may not embed 'nornir'"),
"asymmetry rule explains the rejection: {err}"
);
// ── RED-WHEN-BROKEN proof #2: a THIN variant that embeds anything is
// incoherent (thin compiles in nothing) and MUST be rejected. ──
let thin_embeds = BuildVariant {
name: "holger-thin-but-embeds".to_string(),
member: Some("holger".to_string()),
kind: ArtifactKind::Service,
shape: ServiceShape::ThinStandalone,
embeds: BTreeSet::from(["skade".to_string()]),
..Default::default()
};
set.validate(&thin_embeds)
.expect_err("a thin/standalone variant may not embed siblings");
}
/// **R8 — the shape-parity guard**: the metal cell and the container cell
/// start the product with the SAME argv tail. This is the test that stops the
/// two shapes drifting apart again — the measured drift (2026-08-14, the
/// fleet first-boot proof): the container declared
/// `cmd = ["start", "--config", "/data/holger.ron"]` and served, the metal
/// shape had no cmd at all, exec'd the bare entry name, printed help, and
/// PID 1 died. One product, one start incantation, both shapes.
#[test]
fn the_metal_and_container_shapes_start_the_product_identically() {
let set = holger_build_space::build_variant_set().expect("build space loads");
let metal = set
.installers
.iter()
.find(|i| i.name == "holger-installer")
.expect("the declared installer cell");
let container = set
.containers
.iter()
.find(|c| c.name == "holger-mvp-appliance")
.expect("the declared container cell");
assert!(
!metal.cmd.is_empty(),
"the metal shape DECLARES how the product starts"
);
assert_eq!(
metal.cmd, container.cmd,
"one product, one start incantation — a difference here is the drift \
the fleet proof measured as a kernel panic"
);
// The GATE travels both shapes: the container's env and the metal's env
// both declare HOLGER_APPLIANCE=1 — the measured drift (2026-08-14) was
// the served metal appliance running with its tunnel dark because the
// env had no channel at all.
assert_eq!(
metal.env.get("HOLGER_APPLIANCE").map(String::as_str),
Some("1"),
"the metal shape declares the ceremony gate"
);
assert_eq!(
container.env.get("HOLGER_APPLIANCE").map(String::as_str),
Some("1"),
"the container shape declares the ceremony gate"
);
// And the declared tail actually rides the boot token.
assert!(
metal
.effective_boot_params()
.iter()
.any(|p| p == "korp.args=start,--config,/data/holger.ron"),
"the tail rides the installed cmdline as korp.args"
);
}