tests/build_variants.rs
//! **RED-when-broken: modgunn's declarative build matrix** —
//! `.nornir/build-variants.toml` DISCOVERS, parses and VALIDATES through
//! nornir-build-thing (`cargo xtask build`'s [`BuildVariantSet`]), and every atom it
//! declares is checked against `workspace_nordisk/.nornir/build-matrix-2026-08-02.md`
//! — the five shapes (`<x>-server` · `<x>` · `<x>-cli` · `<x>-ui` · `<x>-solo`), one
//! suite column, `ORIGINAL`.
//!
//! ## THE RULE THIS FILE ENFORCES
//!
//! The owner, 2026-08-03: *"bara de som är markerade suite=ORIGINAL i matrisen skall ha
//! built things"* — **carrying the suite tag is what claims Build-Thing status**, so a
//! tagged atom that cannot build is a promise the repo cannot keep (holger's
//! `archive ⭐`: a starred default in the docs and in no build file). And
//! *"ta bort allt inte stämmer med matrisen"* — anything not a matrix row is gone.
//!
//! modgunn declares **ONE** atom, `modgunn` (client · fat · bin `modgunn`), and the
//! other four shapes are asserted MISSING as positive claims. A gap nothing checks is
//! a phantom waiting to be re-declared by somebody copying a sibling repo's file.
//!
//! ## The four gaps, each measurable from the source rather than a matter of taste
//!
//! * **no server** — the only `TcpListener` in the crate is in `#[cfg(test)]`;
//! `src/service.rs` (which merely RENDERS a unit) is behind the `framework` feature
//! that no atom enables; and the unit it would render `ExecStart`s
//! `modgunn world-delta watch`, a verb main.rs's dispatch does not have.
//! * **no thin cli** — modgunn has no `tonic`/`prost`/`nornir-service`, so there is no
//! wire for a thin client to talk over; and its only `[[bin]]` carries
//! `required-features = ["warehouse", "scan"]` where `warehouse = ["dep:skade", …]`,
//! so the only executable modgunn can produce REQUIRES the dependency that makes it
//! fat. A thin modgunn binary is impossible by construction, not merely absent.
//! * **no UI** — zero egui/eframe/tauri/iced/slint/wasm/winit/wgpu in the whole
//! manifest. `nornir-appshell` is the serde-only About-card MODEL, not a card.
//! * **no solo** — `solo` means the UI with the server running inside it. With neither
//! half there is nothing to embed and nothing to put on screen.
//!
//! ## Removed 2026-08-03 because the matrix has no such row
//!
//! `modgunn-lib` (role `library`), `modgunn-image` (role `client`, kind `container`,
//! and no `bin`) and the `[[build.container]] modgunn-world-delta` descriptor. No
//! source was deleted: a Build Thing is a declared BUILD POINT, and holger's
//! `server/lib`, holger's `server/ui` and nornir all consume modgunn's library as an
//! ordinary cargo dependency with their own feature selection — never through that
//! atom, whose closure (`scan,provenance-verify`) matched none of theirs.
use std::collections::BTreeSet;
use std::path::Path;
use nornir_build_thing::plugin::Role;
use nornir_build_thing::suite_cli;
use nornir_build_thing::variant::{ArtifactKind, BuildVariant, BuildVariantSet, ServiceShape};
fn repo_root() -> &'static Path {
Path::new(env!("CARGO_MANIFEST_DIR"))
}
fn set() -> BuildVariantSet {
BuildVariantSet::discover(repo_root())
.expect("discover .nornir/build-variants.toml")
.expect("modgunn declares a .nornir/build-variants.toml")
}
/// **The five shapes of `build-matrix-2026-08-02.md`, as data.** `(atom, role,
/// linkage, bin)` — the ONLY rows a modgunn declaration may contain. Written here as
/// the matrix's own five names so the conformance guard below compares a declaration
/// against the matrix and not against itself.
const MATRIX_ROWS: [(&str, Role, ServiceShape, &str); 5] = [
(
"modgunn-server",
Role::Server,
ServiceShape::FatService,
"modgunn-server",
),
("modgunn", Role::Client, ServiceShape::FatService, "modgunn"),
(
"modgunn-cli",
Role::Client,
ServiceShape::ThinStandalone,
"modgunn-cli",
),
(
"modgunn-ui",
Role::Client,
ServiceShape::ThinStandalone,
"modgunn-ui",
),
(
"modgunn-solo",
Role::Solo,
ServiceShape::FatService,
"modgunn-solo",
),
];
/// Every problem with a declaration measured against [`MATRIX_ROWS`]: a variant whose
/// name is not a matrix atom, or whose role / linkage / bin disagrees with its row.
///
/// This is the check that implements *"ta bort allt inte stämmer med matrisen"*, and
/// it is deliberately a function over an arbitrary set so the RED cases below can
/// drive it with the declarations that were REMOVED and watch it report them.
fn matrix_problems(set: &BuildVariantSet) -> Vec<String> {
let mut out = Vec::new();
for v in &set.variants {
let Some(row) = MATRIX_ROWS.iter().find(|(n, ..)| *n == v.name) else {
out.push(format!(
"atom '{}' (role {:?}, kind {:?}) is not a row of the build matrix — the five \
shapes are {}",
v.name,
v.role,
v.kind,
MATRIX_ROWS
.iter()
.map(|(n, ..)| *n)
.collect::<Vec<_>>()
.join(", ")
));
continue;
};
let (name, role, linkage, bin) = row;
if v.role != Some(*role) {
out.push(format!(
"atom '{name}': role {:?} but the matrix says {role:?}",
v.role
));
}
if v.shape != *linkage {
out.push(format!(
"atom '{name}': linkage {:?} but the matrix says {linkage:?}",
v.shape
));
}
if v.bin.as_deref() != Some(*bin) {
out.push(format!(
"atom '{name}': bin {:?} but the matrix says '{bin}'",
v.bin
));
}
}
out
}
/// ★ **EVERY DECLARED ATOM IS A MATRIX ROW, AND CARRIES THE SUITE TAG.**
///
/// The suite tag is what claims Build-Thing status, so this test and
/// [`every_declared_bin_is_a_real_cargo_target`] together are the whole contract:
/// what is tagged is in the matrix, and what is in the matrix can actually be built.
#[test]
fn every_declared_atom_is_a_matrix_row() {
let set = set();
assert_eq!(
set.names(),
vec!["modgunn"],
"modgunn declares exactly the atoms it can build: the fat client, and nothing else"
);
assert!(
matrix_problems(&set).is_empty(),
"{:?}",
matrix_problems(&set)
);
// Non-vacuous: the guard must actually have inspected a variant. An empty
// declaration would pass the line above for the wrong reason.
assert_eq!(set.variants.len(), 1, "one atom inspected, not zero");
// The container axis is EMPTY. `[[build.container]]` cannot carry a `suites` tag
// at all (`BuildVariantSet::suites()` / `declares_suites()` iterate `self.variants`
// only), so under the rule "the tag is what makes it a Build Thing" a declared
// container is a cell outside the set. modgunn's `modgunn-world-delta` image
// descriptor was removed on 2026-08-03 rather than left as an untagged orphan.
assert!(
set.container_names().is_empty(),
"containers are not on the suite axis; declared: {:?}",
set.container_names()
);
// RED-WHEN-BROKEN (a): the two declarations that were REMOVED, driven through the
// same guard — `modgunn-lib` is a role the matrix does not have, and
// `modgunn-image` is a client with no bin.
let removed = BuildVariantSet::from_toml_str(
r#"
[build]
base_suite = "ORIGINAL"
[[build.variant]]
name = "modgunn"
role = "client"
default = true
suites = ["ORIGINAL"]
package = "modgunn"
bin = "modgunn"
kind = "exe"
linkage = "fat"
profile = "release"
features = ["scan", "warehouse"]
[[build.variant]]
name = "modgunn-lib"
role = "library"
default = true
suites = ["ORIGINAL"]
package = "modgunn"
kind = "lib"
linkage = "not-applicable"
profile = "release"
features = ["scan"]
[[build.variant]]
name = "modgunn-image"
role = "client"
suites = ["ORIGINAL"]
kind = "container"
linkage = "fat"
container_ref = "modgunn-world-delta"
"#,
)
.expect("parse the pre-2026-08-03 declaration");
let problems = matrix_problems(&removed);
assert_eq!(
problems.len(),
2,
"both off-matrix atoms reported: {problems:?}"
);
assert!(
problems[0].contains("'modgunn-lib' (role Some(Library)"),
"{}",
problems[0]
);
assert!(
problems[0].contains("not a row of the build matrix"),
"{}",
problems[0]
);
assert!(problems[1].contains("'modgunn-image'"), "{}", problems[1]);
// RED-WHEN-BROKEN (b): a MATRIX-NAMED atom that lies about its own row. This is the
// case the name check alone cannot see — `modgunn` declared thin would pass a
// "is it in the list" test and be false.
let lying = BuildVariantSet::from_toml_str(
r#"
[build]
base_suite = "ORIGINAL"
[[build.variant]]
name = "modgunn"
role = "server"
default = true
suites = ["ORIGINAL"]
package = "modgunn"
bin = "modgunn-daemon"
kind = "exe"
linkage = "thin"
profile = "release"
features = ["scan"]
"#,
)
.expect("parse");
let problems = matrix_problems(&lying);
assert_eq!(
problems.len(),
3,
"role + linkage + bin all reported: {problems:?}"
);
assert!(
problems[0].contains("role Some(Server) but the matrix says Client"),
"{}",
problems[0]
);
assert!(
problems[1].contains("linkage ThinStandalone but the matrix says FatService"),
"{}",
problems[1]
);
assert!(
problems[2].contains("bin Some(\"modgunn-daemon\") but the matrix says 'modgunn'"),
"{}",
problems[2]
);
}
/// The `.nornir/build-variants.toml` discovers, validates, and materializes the exact
/// cargo argv `cargo xtask build --suite ORIGINAL` runs.
#[test]
fn build_variants_discover_and_validate() {
let set = set();
// The whole declared matrix validates (offers / required-features / fat-thin
// shape + the suite asymmetry graph + kind coherence).
set.validate_all()
.expect("modgunn's declared build variants validate");
// Every ROLE names exactly one normal case — a menu with no default makes the
// caller guess.
assert!(
set.check_defaults().is_empty(),
"{:?}",
set.check_defaults()
);
// ── ⭐ THE FAT CLIENT: matrix row `ORIGINAL modgunn client fat modgunn`. ────
let cli = set.get("modgunn").expect("the ⭐ fat-client atom");
assert_eq!(
cli.role,
Some(Role::Client),
"a batch CLI that exits is driven, not deployed"
);
assert_eq!(cli.member.as_deref(), Some("modgunn"));
assert_eq!(
cli.kind,
ArtifactKind::Exe,
"a plain executable, not a daemon skidbladnir installs"
);
assert_eq!(
cli.shape,
ServiceShape::FatService,
"warehouse ⇒ dep:skade is compiled IN — it works with no server running, which is what \
the matrix means by a fat client"
);
assert_eq!(cli.bin.as_deref(), Some("modgunn"));
assert_eq!(cli.suites, vec!["ORIGINAL".to_string()]);
assert!(
cli.features.contains(&"scip".to_string()),
"Engine B present"
);
assert!(
cli.features.contains(&"warehouse".to_string()),
"warehouse seam"
);
assert!(
cli.features.contains(&"rustsec".to_string()),
"advisory-db import"
);
// `embeds` is EMPTY, and that is not a retreat from the skade claim. Cargo.toml
// defines `warehouse = ["dep:skade", …]`, so the feature list two lines up IS
// the embed, and `linkage = "fat"` is the self-containment claim. `validate_kind`
// forbids `embeds` on a non-service kind, and the duplicate key was the thing
// worth losing — not the fact.
assert!(
cli.embeds.is_empty(),
"kind 'exe' composes no service (validate_kind)"
);
assert!(
cli.reaches.is_empty(),
"modgunn has no tonic/prost/nornir-service — there is no wire to reach over"
);
// `provenance-verify` is named EXPLICITLY. It is in modgunn's Cargo `default`,
// so an older declaration compiled it (and its nornir-build-thing +
// znippy-common/sign closure) without ever saying so: the declared feature set
// was a strict subset of what was actually built.
assert!(
cli.no_default_features,
"the closure is stated, not inherited"
);
assert!(
cli.features.contains(&"provenance-verify".to_string()),
"a default feature that is compiled must be declared"
);
// The ArtifactKind seam materializes the exact cargo argv the xtask hands cargo.
assert_eq!(
cli.cargo_args(),
vec![
"build",
"-p",
"modgunn",
"--bin",
"modgunn",
"--features",
"scan,scip,warehouse,rustsec,provenance-verify",
"--no-default-features",
"--profile",
"release",
],
"the fat client materializes its cargo build argv"
);
// RED-WHEN-BROKEN: a `lib` that leaves the linkage unsaid inherits the `fat`
// default — a positive claim of self-containment nobody checked. `validate`
// rejects it, which is the machinery that made `modgunn-lib` declarable at all
// and is still worth having live.
let mut silent = cli.clone();
silent.name = "modgunn-lib".into();
silent.kind = ArtifactKind::Lib;
silent.bin = None;
silent.shape = ServiceShape::FatService;
let err = set
.validate(&silent)
.expect_err("a lib may not claim a linkage");
assert!(err.to_string().contains("not-applicable"), "{err}");
modgunn::functional_status(
"modgunn/build-variants",
"matrix_conformance",
set.names() == vec!["modgunn"]
&& matrix_problems(&set).is_empty()
&& cli.kind == ArtifactKind::Exe
&& cli.role == Some(Role::Client)
&& cli.shape == ServiceShape::FatService
&& cli.bin.as_deref() == Some("modgunn")
&& cli.embeds.is_empty()
&& set.container_names().is_empty()
&& set.check_defaults().is_empty(),
"modgunn declares exactly one Build Thing and it is a row of build-matrix-2026-08-02: the \
ORIGINAL fat client `modgunn`",
);
}
/// ★ **THE FOUR GAPS, ASSERTED AS POSITIVE CLAIMS.** modgunn has no daemon, no wire,
/// no UI and therefore no solo build. Four of the matrix's five shapes are fiction for
/// this member, and naming them beats shipping stubs that build and do nothing.
#[test]
fn the_four_missing_atoms_are_declared_missing() {
let set = set();
// ── no server ─────────────────────────────────────────────────────────────
assert_eq!(
set.default_for_role(Some(Role::Server)),
None,
"modgunn declares a SERVER atom, but it has no daemon: no listener outside \
#[cfg(test)], the service module is behind a feature no atom builds, and the \
unit it renders ExecStarts a verb that does not exist"
);
assert!(
set.variants.iter().all(|v| v.role != Some(Role::Server)),
"no modgunn variant may claim the server role: {:?}",
set.variants
.iter()
.filter(|v| v.role == Some(Role::Server))
.map(|v| &v.name)
.collect::<Vec<_>>()
);
// ── no solo ───────────────────────────────────────────────────────────────
assert!(
set.variants.iter().all(|v| v.role != Some(Role::Solo)),
"solo = UI + server in one binary; modgunn has neither half"
);
// ── no thin client of any kind ────────────────────────────────────────────
assert!(
set.variants
.iter()
.all(|v| v.shape != ServiceShape::ThinStandalone),
"a thin modgunn binary is impossible by construction: the only [[bin]] requires \
`warehouse`, and `warehouse = [\"dep:skade\", …]` IS the fat dependency"
);
// The one atom that IS there, named as the normal case of its role.
assert_eq!(
set.default_for_role(Some(Role::Client))
.map(|v| v.name.as_str()),
Some("modgunn"),
"the fat client is the normal case of the only role modgunn actually fills"
);
// ── the manifest is the oracle for the -cli and -ui gaps, read as TEXT so a
// dependency added tomorrow trips this and forces the atom to be declared ──
let manifest =
std::fs::read_to_string(repo_root().join("Cargo.toml")).expect("read Cargo.toml");
let dep_lines: Vec<&str> = manifest
.lines()
.map(str::trim)
.filter(|l| !l.starts_with('#') && !l.is_empty())
.collect();
// Non-vacuous: the filter must have left real dependency lines behind. If the
// read or the comment-strip silently emptied this, every loop below would pass
// over nothing — the exact silent-green shape these gaps exist to prevent.
assert!(
dep_lines.iter().any(|l| l.starts_with("znippy-zoomies")),
"the manifest scan found no known dependency line — the oracle is empty: {} lines",
dep_lines.len()
);
for gui in [
"egui",
"eframe",
"tauri",
"iced",
"slint",
"wasm-bindgen",
"winit",
"wgpu",
] {
for line in &dep_lines {
assert!(
!line.starts_with(gui),
"modgunn declared a UI dependency ('{gui}') — the `modgunn-ui` gap is no longer a \
gap, so declare the atom instead of leaving this test lying: {line}"
);
}
}
for wire in ["tonic", "prost", "nornir-service"] {
for line in &dep_lines {
assert!(
!line.starts_with(wire),
"modgunn declared a gRPC dependency ('{wire}') — a THIN client is now possible, so \
declare `modgunn-cli` instead of leaving this test lying: {line}"
);
}
}
// The bin's `required-features` is the structural half of the `-cli` gap: the only
// executable modgunn ships cannot be built without the dependency that makes it
// fat. Read from the manifest, not asserted from memory.
assert!(
manifest.contains(r#"required-features = ["warehouse", "scan"]"#),
"the only [[bin]] still requires the fat `warehouse` feature — if that changed, a thin \
modgunn binary may now be possible and `modgunn-cli` must be reconsidered"
);
}
/// ★ **No two atoms may write the same file** ([`BuildVariantSet::check_bins`]).
///
/// modgunn is CLEAN on this axis and always was — edda's cross-repo collision
/// sweep (2026-08-01) found `korp` (3 variants → bin `korp`), `nornir` (3 →
/// `nornir`) and `dwarves` (2 → `dwarves`), and modgunn in none of them.
///
/// But modgunn's emptiness is STRUCTURAL, not earned: exactly one atom declares a
/// `bin` at all, so there is nothing that could collide. An assertion that can only
/// ever pass is not a guard, so the negative case below drives the same method on a
/// deliberately-colliding declaration and asserts it REPORTS — otherwise a future
/// second binary could reintroduce the defect under a green test.
#[test]
fn no_two_atoms_emit_the_same_binary() {
let set = set();
assert!(set.check_bins().is_empty(), "{:?}", set.check_bins());
// RED-WHEN-BROKEN: the same declaration with a second atom on the same bin.
let colliding = BuildVariantSet::from_toml_str(
r#"
[build]
base_suite = "ORIGINAL"
[[build.variant]]
name = "modgunn"
role = "client"
default = true
suites = ["ORIGINAL"]
package = "modgunn"
bin = "modgunn"
kind = "exe"
profile = "release"
features = ["scan", "warehouse"]
[[build.variant]]
name = "modgunn-lean"
role = "client"
suites = ["ORIGINAL"]
package = "modgunn"
bin = "modgunn"
kind = "exe"
profile = "release"
features = ["scan", "warehouse", "scip"]
"#,
)
.expect("parse the colliding twin");
let problems = colliding.check_bins();
assert_eq!(
problems.len(),
1,
"one collision, one message: {problems:?}"
);
assert!(problems[0].contains("SAME artifact"), "{}", problems[0]);
assert!(
problems[0].contains("modgunn, modgunn-lean"),
"it names both: {}",
problems[0]
);
}
/// ★ **Every declared `bin` must be a `[[bin]]` cargo can actually build**
/// ([`BuildVariantSet::check_bins_declared`]) — the phantom-bin guard, and the half of
/// the Build-Thing contract that says a tagged atom is buildable.
///
/// The real target list is READ OUT of the manifests by
/// [`BuildVariantSet::cargo_bin_targets`], never hardcoded here: a hardcoded twin
/// is exactly the drift this guard exists to remove (korp renamed `korp` → `korp-ui`
/// in Cargo.toml and left three variants — and its own test's asserted argv string —
/// pointing at a target that no longer existed).
#[test]
fn every_declared_bin_is_a_real_cargo_target() {
let set = set();
let real = BuildVariantSet::cargo_bin_targets(repo_root()).expect("walk the manifests");
// Non-vacuous: the walk must actually have found modgunn's one `[[bin]]`.
assert!(
real.contains(&"modgunn".to_string()),
"the manifest walk found: {real:?}"
);
assert!(
set.check_bins_declared(&real).is_empty(),
"{:?}",
set.check_bins_declared(&real)
);
// The four missing shapes are missing FROM THE MANIFEST TOO, which is why they
// are not declared: had any of them been tagged, this guard would have failed.
for phantom in [
"modgunn-server",
"modgunn-cli",
"modgunn-ui",
"modgunn-solo",
] {
assert!(
!real.contains(&phantom.to_string()),
"'{phantom}' is now a real cargo target — declare the matrix atom for it"
);
}
// RED-WHEN-BROKEN, twice over. (a) A real target list that does NOT contain the
// declared bin must be reported…
let wrong = set.check_bins_declared(&["modgunn-server"]);
assert_eq!(wrong.len(), 1, "{wrong:?}");
assert!(wrong[0].contains("declares bin 'modgunn'"), "{}", wrong[0]);
assert!(
wrong[0].contains("no `[[bin]]` target exists"),
"{}",
wrong[0]
);
// …(b) and an EMPTY oracle must be reported rather than passing vacuously, which
// is what would happen if the manifest walk silently found nothing.
let empty: [&str; 0] = [];
let vacuous = set.check_bins_declared(&empty);
assert_eq!(vacuous.len(), 1, "{vacuous:?}");
assert!(vacuous[0].contains("VACUOUSLY"), "{}", vacuous[0]);
}
/// ★ **The suite tag** — `suites = ["ORIGINAL"]` on every atom, `base_suite` in
/// `[build]`, and `ORIGINAL` is the only name that exists here.
///
/// [`check_suites`] returns empty here and that is CORRECT, not a pass: below two
/// deliveries there is nothing to be a subset of. The two assertions together are
/// the point — `check_suites().is_empty()` alone cannot tell "checked and fine"
/// from "nothing was declared to check", and [`declares_suites`] is what says
/// which. Asserting only the first is the silent-green shape this pass exists to
/// end.
///
/// [`check_suites`]: BuildVariantSet::check_suites
/// [`declares_suites`]: BuildVariantSet::declares_suites
#[test]
fn every_atom_is_tagged_into_the_one_delivery() {
let set = set();
assert!(set.check_suites().is_empty(), "{:?}", set.check_suites());
assert!(
set.declares_suites(),
"check_suites() is empty EITHER because the tags are fine OR because there are \
none — this is what tells the two apart"
);
assert_eq!(
set.suites(),
vec!["ORIGINAL"],
"one delivery, named — and only one"
);
assert_eq!(set.base_suite.as_deref(), Some("ORIGINAL"));
// Every atom, not just some.
for v in &set.variants {
assert_eq!(
v.suites,
vec!["ORIGINAL".to_string()],
"atom '{}' is untagged, so it would silently join the repo's UNNAMED delivery \
and `--suite ORIGINAL` would not build it — and under the rule 'the tag is what \
makes it a Build Thing' it would not be one at all",
v.name
);
}
// RED-WHEN-BROKEN: a blank suite name is a delivery with no name, which is
// indistinguishable from declaring none.
let blank = BuildVariantSet::from_toml_str(
"[build]\nbase_suite = \"ORIGINAL\"\n\
[[build.variant]]\nname = \"modgunn\"\nsuites = [\"\"]\ndefault = true\n",
)
.expect("parse");
let problems = blank.check_suites();
assert_eq!(problems.len(), 1, "{problems:?}");
assert!(problems[0].contains("BLANK suite name"), "{}", problems[0]);
}
/// ★ **`--suite` must SELECT.** `--suite ORIGINAL` resolves to exactly the declared
/// atoms; `--suite NOPE` FAILS LOUDLY and names the real delivery. A selector that
/// ignores an unknown name is the silent-green shape — it would keep a
/// "we build the ORIGINAL suite nightly" claim green while building anything at all.
#[test]
fn the_one_build_verb_resolves_this_repos_delivery() {
let set = set();
let args = suite_cli::parse(&["--suite", "ORIGINAL"]).expect("parse");
let (suite, atoms) = suite_cli::resolve(&set, &args).expect("ORIGINAL resolves");
assert_eq!(suite, "ORIGINAL");
assert_eq!(
atoms.iter().map(|v| v.name.as_str()).collect::<Vec<_>>(),
vec!["modgunn"],
"exactly the declared atoms, in declaration order"
);
// One atom by name, and the argv the xtask will hand cargo.
let one = suite_cli::parse(&["--suite", "ORIGINAL", "--atom", "modgunn"]).expect("parse");
let (_, just_cli) = suite_cli::resolve(&set, &one).expect("the ⭐ atom resolves");
assert_eq!(just_cli.len(), 1);
assert_eq!(
just_cli[0].command_line(),
"cargo build -p modgunn --bin modgunn --features scan,scip,warehouse,rustsec,provenance-verify \
--no-default-features --profile release"
);
// RED-WHEN-BROKEN: an unknown delivery is a hard error that lists the real one.
// `MVP` is holger's second delivery and is NOT a modgunn suite — asking for it
// here must fail rather than quietly build the one delivery that exists.
for unknown in ["NOPE", "MVP"] {
let bad = suite_cli::parse(&["--suite", unknown]).expect("parse");
let err = suite_cli::resolve(&set, &bad).expect_err("an unknown suite must not shrug");
let msg = err.to_string();
assert!(msg.contains("no such delivery"), "{msg}");
assert!(
msg.contains("ORIGINAL"),
"it must name the declared delivery: {msg}"
);
}
// …and so is an unknown atom. Each of the four fictions must fail HERE rather
// than resolve to something that builds nothing.
for ghost in [
"modgunn-server",
"modgunn-cli",
"modgunn-ui",
"modgunn-solo",
"modgunn-lib",
"modgunn-image",
] {
// `--atom` is the deprecated spelling of `--variant`; suite_cli accepts it but
// NORMALIZES its rejection to the canonical vocabulary, so the phantom comes
// back as "no such variant" (not "no such atom"). Assert the wording the code
// actually emits today — a stale "atom" here made this guard red-when-fine.
let g = suite_cli::parse(&["--suite", "ORIGINAL", "--atom", ghost]).expect("parse");
let err = suite_cli::resolve(&set, &g).expect_err("a phantom atom must not resolve");
assert!(err.to_string().contains("no such variant"), "{ghost}: {err}");
}
}
/// **The headline asymmetry.** modgunn is the cycle-breaker: nornir and holger
/// DEPEND ON it, so they are upstream. A fat modgunn that embedded either would
/// invert the suite dep graph — `validate` MUST reject it. (Red-when-broken: if
/// the `[build.downstream]` allowlist dropped, this stops rejecting.)
///
/// Note this is the ONLY exercise of `[build.downstream]`: no real variant declares
/// `embeds` (kind `exe` forbids it, and `features = [… warehouse …]` already IS the
/// skade embed). The allowlist stays declared and stays checked HERE, which is stated
/// rather than left to be discovered.
#[test]
fn embedding_an_upstream_member_is_rejected() {
let set = set();
let inverted = BuildVariant {
name: "modgunn-inverted".into(),
member: Some("modgunn".into()),
kind: ArtifactKind::Service,
shape: ServiceShape::FatService,
embeds: BTreeSet::from(["nornir".to_string()]), // nornir is UPSTREAM of modgunn
profile: nornir_build_thing::variant::Profile::Release,
..Default::default()
};
let err = set
.validate(&inverted)
.expect_err("modgunn must NOT embed nornir (graph inversion)");
let msg = err.to_string();
assert!(msg.contains("may not embed 'nornir'"), "message: {msg}");
assert!(msg.contains("DOWNSTREAM"), "explains the rule: {msg}");
}