holger-recipe/src/rules.rs
//! # The rules the declaration cannot carry
//!
//! Moved here from edda's `nornir-buildspec-holger` (2026-09-16). The variants come
//! from [`crate::declared`]; what stays code in THIS module is the part that cannot
//! travel as data: the airgap and thin-client rules behind [`validate`] and
//! [`judge_artifact`], which [`crate::HolgerBuildThing`]'s `impl BuildThing`
//! delegates to.
//!
//! **Why they had to move.** Until today these lived only in edda, so holger's own
//! wasm module answered `judge_artifact` with the TRAIT DEFAULT — *"this build thing
//! declares no artifact-level invariant"*, an OK verdict for every artifact. A forge
//! loading holger's module instead of edda's would have passed a `mini` linking
//! reqwest. The keystone belongs in the repo whose artifacts it judges.
//!
//! ## History: three writers, three copies, one now
//!
//! This crate once declared the variants by hand beside holger's TOML, and a second
//! writer drifts — it did three times (the unbuilt `gui` bin, the unseen sixth
//! variant, the `henriks-*` → `ui-*` rename that left its cargo argv unbuildable). It
//! then switched to `include_str!` of a byte copy of the TOML, held by a drift test,
//! while `holger-build-space` kept a second copy. Now the Rust recipe is the only
//! writer and the TOML is its emission, so neither failure is expressible.
//! [`HolgerBuildThing::roles`] is still not overridden: the default groups on the
//! declaration's own `(suite, role)`.
//!
//! ## The airgap rule, and why it is not a table
//!
//! What is **not** data is the rule that gives the products their meaning:
//!
//! > the airgap rows ([`AIRGAP_TIERS`]) must link **no outbound HTTP client**.
//!
//! No field in any schema expresses that. It is a predicate over a built artifact's
//! dependency closure, and only holger knows which rows it applies to: the four
//! products by default, and the static appliance. It lives in
//! [`HolgerBuildThing::judge_artifact`], it runs inside the forge's wasm sandbox, and it
//! is the reason this crate is code. It discriminates: the metered rows link the box's
//! own advisory fetch (an HTTP client), and `--features online` (or `full`) adds one to
//! any product.
//! ## The CLIENT ladder left with the clients (2026-09-16)
//!
//! The four egui rungs and every rule about them — the thin-UI ladder, the
//! read-only client tiers, the panes that link an HTTP client, the engine crates a
//! thin client may not carry — moved to `holger-egui` with `server/ui` itself.
//! They are not kept here in weakened form: a rule naming a variant this repo no
//! longer declares fails OPEN (`links_no_http_client` simply answers `false` and
//! every artifact sails through un-judged), which is the exact defect
//! `every_rule_names_a_declared_variant` exists to catch. What stays is the
//! SERVER-side keystone.
//!
//! ## One definition, two link targets
//!
//! These functions are plain Rust in a plain rlib. `holger-build-thing` is the
//! `cdylib` shim that exports the plugin ABI over the same
//! [`crate::HolgerBuildThing`], so the wasm module the forge loads cannot answer
//! anything the native doors would not.
use nornir_build_thing::plugin::{
host, ArtifactFacts, BuildPlan, BuildRequest, PlanStep, RebuildRecipe, Role, Verdict,
};
use nornir_build_thing::variant::{BuildVariant, BuildVariantSet, ServiceShape};
/// holger's git remote, for the pull → rebuild → compare loop.
pub const REMOTE: &str = "ssh://git@git.gunnar.rs:2222/nordisk/holger.git";
/// The UI client's package (`server/ui`), whose bins carry `required-features`.
/// **Every Rust outbound-HTTP client an airgap tier is forbidden to link.**
///
/// `reqwest`/`ureq` are holger's actual egress paths (the OIDC `/userinfo` + `jwks_uri`
/// fetch, the `bench` driver, the `rust-remote` pull-through upstream). The rest are
/// the other well-known Rust HTTP clients, denied so a FUTURE re-introduction of
/// egress through a *different* client is caught too — the guarantee must not hinge on
/// the one crate name holger happens to use today.
///
/// `hyper`/`tonic`/`h2` are deliberately ABSENT: they are the INBOUND serve stack,
/// which every tier legitimately links. A denylist that fails a server for being a
/// server would be turned off within a week.
pub const FORBIDDEN_OUTBOUND_CLIENTS: &[&str] = &[
"reqwest",
"ureq",
"isahc",
"curl",
"surf",
"attohttpc",
"awc",
"http-client",
];
/// **The TLS backends no holger artifact may link, on ANY variant.**
///
/// Separate from [`FORBIDDEN_OUTBOUND_CLIENTS`] because it is a different
/// question with a different scope. An HTTP client is forbidden only where
/// egress is forbidden — `full` links one BY DESIGN and is refused when it does
/// not. A native TLS stack is forbidden EVERYWHERE, `full` included, and
/// holger's own declaration already says so in as many words
/// (`build-space/tests/auth_ship_small.rs:21`: *"`full` included: every tier
/// MUST link NEITHER the crate NOR openssl"*). holger is rustls, end to end.
///
/// ⚠ **This list exists because its absence is what let OpenSSL in.** The
/// third measured feature leak was `auth-oidc`/`auth-oauth2` dragging the
/// native stack into `server-local` — and `server-local` is neither an airgap
/// tier nor the federating tier, so [`judge_artifact`] fell through to a bare
/// `Verdict::ok()` and **never judged it at all**. Naming the clients without
/// naming the backends judged the wrong noun: the leak arrived as a TLS
/// backend, underneath a client that was allowed to be there.
///
/// The pre-existing assertion of this rule lives in `build-space`, which shells
/// `cargo tree` and therefore SKIPS SILENTLY where cargo is absent. This one is
/// a predicate over a closure the host supplies, so it cannot be vacuous.
pub const FORBIDDEN_TLS_BACKENDS: &[&str] = &[
"openssl",
"openssl-sys",
"native-tls",
"tokio-native-tls",
"hyper-tls",
];
/// The rows that must be provably unable to phone home: the four products with their
/// defaults and the static appliance. A request naming no row is judged by its features:
/// [`AIRGAP_FEATURES`].
pub const AIRGAP_TIERS: &[&str] = &[
"holger",
"holger-downstream",
"holger-ui",
"holger-cli",
"mvp-server-musl",
];
/// An ad-hoc feature list (no row named) is judged as airgap when it names one of these:
/// the appliance marker or the downstream gateway.
pub const AIRGAP_FEATURES: &[&str] = &["mvp-server", "downstream"];
/// **holger's declared build space** — [`crate::declared`].
///
/// Pure: no filesystem and no clock, so it works unchanged inside the wasm sandbox.
/// Rebuilt on every call rather than cached in a `static` — a plugin is asked a
/// handful of questions per load, and a lazily-initialised global would need a lock
/// the sandbox does not want.
pub fn build_variants() -> BuildVariantSet {
crate::declared()
}
/// holger's SERVER variants, in declaration order — READ off the declaration's
/// `role` key rather than restated here.
pub fn server_variants() -> Vec<String> {
variants_in_role(Role::Server)
}
/// holger's CLIENT variants, in declaration order — the UI ladder, the two agent
/// tiers and the Secure Update Gateway CLIs.
pub fn client_variants() -> Vec<String> {
variants_in_role(Role::Client)
}
fn variants_in_role(role: Role) -> Vec<String> {
build_variants()
.variants
.iter()
.filter(|v| v.role == Some(role))
.map(|v| v.name.clone())
.collect()
}
/// The fat/thin SERVICE-SHAPE of a variant — the linkage axis, orthogonal to the
/// role and to the tier.
///
/// **This used to be a `match` on variant names**, which is how it came to disagree
/// with holger's own file: the function said `full` was fat BECAUSE it embedded
/// skade and `mini` was thin BECAUSE it reached a remote skade, and an audit of the
/// real Cargo graph proved both false — **no holger server tier depends on skade in
/// any direction**. It now returns what holger declares, so the disagreement is not
/// expressible.
///
/// An undeclared variant answers [`ServiceShape::FatService`], which is the schema's
/// own default for an omitted `linkage` key.
pub fn shape_of(variant: &str) -> ServiceShape {
build_variants()
.get(variant)
.map(|v| v.shape)
.unwrap_or(ServiceShape::FatService)
}
/// True when `variant` is one of the airgap SERVER tiers — the ones that must be
/// provably unable to phone home at all.
pub fn is_airgap(variant: &str) -> bool {
AIRGAP_TIERS.contains(&variant)
}
/// True when `variant` must link **no outbound HTTP client**.
///
/// ⚠ This used to be `is_airgap(variant) || is_read_only_client(variant)`. The
/// read-only CLIENT half went to holger-egui with the rungs it judged (2026-09-16);
/// holger's own `agent` tiers are deliberately NOT here — an agent migrates from
/// Nexus, so applying the rule to them would only teach people to turn it off.
pub fn links_no_http_client(variant: &str) -> bool {
is_airgap(variant)
}
// ⚠ `roles()` IS DELIBERATELY NOT OVERRIDDEN, and that is a fix, not an omission.
//
// It used to be two hand-written `RoleGroup` literals: `Role::Server` over a
// `SERVER_VARIANTS` const, `Role::Client` over a `CLIENT_VARIANTS` const, each
// with a hard-coded `default_variant` and a hard-coded `suite: None`. Three
// separate things could drift from holger's file, and all three had:
//
// * the variant lists were 8 names against holger's 13, two of them renamed
// out of existence;
// * the defaults were restated here instead of read from `default = true`;
// * `suite: None` asserted "holger ships ONE delivery" while its TOML already
// carries an MVP tier (`mvp-server` · `mvp-bundle` · `mvp-verify`). The
// moment holger writes `suites = ["mvp"]` on those, the derived menu grows a
// second server section with its own normal case — the exact bug the suite
// axis was added to remove — whereas a frozen `None` would flatten the two
// deliveries into one section and force them to share a default.
//
// The default implementation groups on `(suite, role)` via
// `BuildVariantSet::groups`, the SAME writer `check_defaults` uses, so the menu
// and the guard cannot disagree about what a section is. What it costs is the
// prose summary per section; what it buys is that none of the above is
// expressible.
/// The plan for a tier, as DATA. holger adds nothing to the default cargo step for
/// its clients; for a SERVER tier it appends the closure resolve the airgap rule
/// needs, so the forge gathers the evidence [`judge_artifact`] will be asked about
/// rather than the plugin trying (and failing) to gather it itself.
pub fn plan(variant: &str) -> Option<BuildPlan> {
let set = crate::declared();
let v = set.get(variant)?;
// A POINTER variant (`kind = "container"` / `"installer"` / `"iso"`) carries
// no cargo argv of its own: `cargo_args()` on it evaluates to the single token
// `build`, and a plan made of that is a bare debug build of the whole
// workspace wearing the appliance's name — holger's `brokkr.toml` measured
// exactly that green on 2026-08-25. These are built by the forge's assembler
// (`nornir_build_thing::assemble`, from the `[[build.container]]` /
// `[[build.installer]]` / `[[build.iso]]` row of the same name), so the plan
// says so and plans NO cargo step rather than a wrong one. Kind-generic on
// purpose: `holger-appliance-iso` (2026-09-17) needed no change here.
if !v.kind.is_cargo() {
host::log(&format!(
"holger: '{variant}' is a {} pointer variant — no cargo step planned",
v.kind.as_str()
));
return Some(BuildPlan {
variant: variant.to_string(),
steps: Vec::new(),
env: Vec::new(),
expected_artifacts: Vec::new(),
rationale: Some(format!(
"'{variant}' is a `{kind}` pointer variant: the forge's {kind} builder \
assembles it from the [[build.{kind}]] row of the same name, not a cargo \
invocation — a cargo step here would be a bare `cargo build` wearing the \
appliance's name",
kind = v.kind.as_str()
)),
});
}
let mut plan = BuildPlan {
variant: variant.to_string(),
steps: vec![PlanStep {
name: format!("build-{variant}"),
program: "cargo".to_string(),
args: v.cargo_args(),
env: Vec::new(),
}],
env: Vec::new(),
expected_artifacts: v
.bin
.as_ref()
.map(|b| vec![format!("target/release/{b}")])
.unwrap_or_default(),
rationale: None,
};
if links_no_http_client(variant) {
let mut args = vec![
"tree".to_string(),
"-e".to_string(),
"no-dev".to_string(),
"-p".to_string(),
v.package.clone().unwrap_or_default(),
"--features".to_string(),
v.features.join(","),
];
if v.no_default_features {
args.push("--no-default-features".to_string());
}
plan.steps.push(PlanStep {
name: format!("egress-closure-{variant}"),
program: "cargo".to_string(),
args,
env: Vec::new(),
});
plan.rationale = Some(if is_airgap(variant) {
format!(
"'{variant}' is an airgap tier: the build is followed by a \
dependency-closure resolve so the zero-egress claim can be CHECKED \
rather than asserted"
)
} else {
format!(
"'{variant}' is a read-only client tier: the build is followed by a \
dependency-closure resolve so the no-HTTP-client claim can be CHECKED \
rather than asserted"
)
});
} else {
plan.rationale = Some(format!("'{variant}' materializes to one cargo invocation"));
}
host::log(&format!(
"holger: planned {} step(s) for '{variant}'",
plan.steps.len()
));
Some(plan)
}
/// The generic rules, plus the one only holger knows: **a no-egress tier may not
/// request a feature that links an HTTP client.** `served` drives HTTP, and
/// `gui`/`signin`/`tools`/`demo` each pull reqwest (and `tools` also pulls skade);
/// none belongs in a red-zone binary, and the generic validator has no way to know
/// that — `offers` lists them all as legitimate features.
pub fn validate(req: &BuildRequest) -> Verdict {
let set = crate::declared();
// Start from the declared rules (offers / exclusive / required-features /
// the fat-thin asymmetry) — no reason to re-derive what the set already knows.
let base = match &req.variant {
Some(name) => match set.get(name) {
None => {
return Verdict::refuse(format!(
"no variant '{name}'; holger declares: {}",
set.names().join(", ")
))
}
Some(v) => match set.validate(v) {
Ok(()) => Verdict::ok(),
Err(e) => Verdict::refuse(e.to_string()),
},
},
None => {
let probe = BuildVariant {
name: "<ad-hoc>".to_string(),
features: req.features.clone(),
target: req.target.clone(),
..Default::default()
};
match set.validate(&probe) {
Ok(()) => Verdict::ok(),
Err(e) => Verdict::refuse(e.to_string()),
}
}
};
// The features actually in play for this request.
let features: Vec<String> = match &req.variant {
Some(name) => set
.get(name)
.map(|v| v.features.clone())
.unwrap_or_default(),
None => req.features.clone(),
};
let named = req.variant.as_deref();
let no_egress = named.map(links_no_http_client).unwrap_or_else(|| {
AIRGAP_FEATURES
.iter()
.any(|t| features.iter().any(|f| f == t))
});
if !no_egress {
return base;
}
// holger's own rule: `served` drives HTTP and has no business in a red-zone
// binary, and the generic validator cannot know that — `offers` lists it as a
// legitimate feature. The UI pane half of this rule (`gui`/`signin`/`tools`/
// `demo` in a read-only client) went to holger-egui with the rungs it judged.
//
// The tier names in the message are JOINED from the rule list, not typed out:
// the sentence used to name `henriks-mini-holger`/`henriks-mikro-holger` and
// went on naming them for a week after holger renamed both.
let no_egress_tiers = AIRGAP_TIERS.join("`/`");
let mut v = base;
if features.iter().any(|f| f == "served") {
v = v.and(Verdict::refuse(format!(
"a no-egress tier may not enable 'served': it links an outbound HTTP path, \
and `{no_egress_tiers}` exist precisely to be provably unable to reach \
the network"
)));
}
if v.ok {
v = Verdict::ok_with(
"airgap tier — the built artifact must link no outbound HTTP client \
(checked by judge_artifact against the real dependency closure)",
);
}
v
}
/// **The airgap keystone, applied to a BUILT artifact.** The host resolves the
/// dependency closure (`cargo tree`, or by reading a pulled artifact) and hands
/// the crate names over; holger decides what they mean.
///
/// This is the check that cannot travel as data: which variants it applies to,
/// which crates count as egress, and why `hyper` does not — all holger's.
pub fn judge_artifact(facts: &ArtifactFacts) -> Verdict {
let Some(variant) = facts.variant.as_deref() else {
return Verdict::refuse("cannot judge an artifact that does not say which variant it is");
};
if facts.linked_crates.is_empty() {
return Verdict::refuse(format!(
"no dependency closure supplied for '{variant}' — an UNMEASURED zero-egress \
claim is not a zero-egress claim"
));
}
let linked: Vec<&str> = FORBIDDEN_OUTBOUND_CLIENTS
.iter()
.copied()
.filter(|c| facts.linked_crates.iter().any(|l| l == c))
.collect();
// ── The TLS backend, judged FIRST and on EVERY variant ──────────────
//
// Before the airgap/federating split, because this rule has no tiers:
// holger is rustls end to end and a native stack is wrong in `full` for
// exactly the reason it is wrong in `mini`. Judging it first also means
// the refusal names the real problem — "this links openssl-sys" —
// rather than a downstream symptom, or (as before) nothing at all.
let tls: Vec<&str> = FORBIDDEN_TLS_BACKENDS
.iter()
.copied()
.filter(|c| facts.linked_crates.iter().any(|l| l == c))
.collect();
if !tls.is_empty() {
return Verdict::refuse(format!(
"'{variant}' links native TLS backend(s): {} — holger is rustls end to end, \
on EVERY variant. This is the shape all three measured feature leaks had, \
and it is refused whatever else is true of this artifact.",
tls.join(", ")
));
}
// The THIN-UI half of this judgement (a client that links the storage engine
// is a second writer on the warehouse lock) went to holger-egui with the
// rungs it judged, 2026-09-16.
if links_no_http_client(variant) {
if linked.is_empty() {
return Verdict::ok_with(format!(
"'{variant}' is an airgap tier and links NO outbound HTTP client — it \
provably cannot phone home"
));
}
return Verdict::refuse(format!(
"'{variant}' is an airgap tier but links outbound HTTP client(s): {}. \
This artifact must not be promoted.",
linked.join(", ")
));
}
// Not an airgap row (the metered builds, the front, the media) — still JUDGED:
// the TLS rule above has run, and the verdict says what was looked at.
Verdict::ok_with(if linked.is_empty() {
format!("'{variant}' is not an airgap row, and links no outbound HTTP client and no native TLS backend")
} else {
format!(
"'{variant}' is not an airgap row, so its outbound HTTP client(s) ({}) are not \
constrained — it links no native TLS backend, which is the rule that does apply here",
linked.join(", ")
)
})
}
pub fn rebuild_recipe(variant: &str) -> Option<RebuildRecipe> {
let set = crate::declared();
let v = set.get(variant)?;
Some(RebuildRecipe {
repo: crate::REPO.to_string(),
remote: Some(REMOTE.to_string()),
rev: None,
variant: variant.to_string(),
cargo_args: v.cargo_args(),
toolchain: None,
})
}
#[cfg(test)]
mod tests {
use super::*;
/// ★ **The keep media the ONE shop upgrades a sold Holger from** (monetize lane L7,
/// `lifecycle::Holger`: main and twin keep-booted from `holger-keep`, the front from
/// `holger-front-keep`): the metered media, composed to upgrade in place — the disk's data
/// kept, the box powered off after INSTALL-OK for the shop to boot it from its disk.
#[test]
fn the_keep_media_are_the_metered_media_upgrading_in_place() {
let set = crate::declared();
for (keep, from) in [
("holger-keep", "holger-installer-metered"),
("holger-front-keep", "holger-front-installer-metered"),
] {
let k = set
.installers
.iter()
.find(|i| i.name == keep)
.unwrap_or_else(|| panic!("no {keep} row"));
let f = set.installers.iter().find(|i| i.name == from).unwrap();
assert_eq!(
(k.variant.as_str(), k.installer_variant.as_str()),
(f.variant.as_str(), f.installer_variant.as_str()),
"{keep} lays what {from} lays"
);
assert_ne!(
k.volume_id, f.volume_id,
"{keep} is resolved by its own volume id"
);
for p in ["korp.reinstall=keep", "korp.install.then=poweroff"] {
assert!(
k.boot_params.iter().any(|b| b == p),
"{keep} boots with {p}: {:?}",
k.boot_params
);
}
assert_eq!(
set.get(keep).expect("its pointer variant").kind,
ArtifactKind::Installer
);
}
}
// The delegation is what these exercise: every call below goes through the trait
// on the type the wasm shim exports, not through the free functions directly.
use crate::HolgerBuildThing;
use nornir_build_thing::plugin::BuildThing;
use nornir_build_thing::plugin::Ecosystem;
use nornir_build_thing::variant::ArtifactKind;
/// The embedded declaration parses, and the `expect` in [`build_variants`] can
/// therefore never fire in a forge. Also the floor under every other test here:
/// a set of zero variants would make most of them vacuously true.
#[test]
fn the_declaration_parses_and_is_not_empty() {
let set = build_variants();
assert!(
set.variants.len() >= 8,
"holger declared {} variant(s) — a shrunk declaration makes every roster \
assertion below vacuous",
set.variants.len()
);
assert!(
!set.offers.is_empty(),
"the feature universe must be declared"
);
}
/// **The bijection laws** (Rickard 2026-08-26: *"det måste vara i traitsen"*)
/// — conformance to `nornir_build_thing::laws` is part of the `BuildThing`
/// contract, and this is holger's call: declaration ↔ struct, menu ↔
/// declaration, option row ↔ CLI flag, decision ↔ fragment, receipt →
/// recipe, each failure naming the law and the inverse that broke.
#[test]
fn the_build_thing_obeys_the_bijection_laws() {
nornir_build_thing::laws::assert_bijective(&HolgerBuildThing);
}
/// **holger's real roster, read off its own file.** The names, their roles and
/// their bins are asserted as literals HERE on purpose: this is the one place
/// that says out loud what the mirror currently contains, so a re-copy that
/// brings a rename lands as a reviewable diff instead of a silent behaviour
/// change. It is NOT the source — `tests/no_drift_from_holgers_declaration.rs`
/// holds the mirror against holger's tree.
#[test]
fn the_declared_builds_are_holgers_real_ones() {
let set = build_variants();
assert_eq!(
set.names(),
vec![
// the four products (HOL-PRODUCTS, 2026-10-30): each its `cargo install` line
"holger",
"holger-downstream",
"holger-cli",
"holger-ui",
// the estate's server builds: the static appliance, the sold ones, the DEV row
"mvp-server-musl",
"mvp-server-metered",
"mvp-server-musl-metered",
"mvp-server-musl-metered-narrate",
// the media's pointer rows: no cargo argv, each the tagged variant its
// packaging row is payload FOR
"holger-mvp-appliance",
"holger-installer",
"holger-installer-metered",
"holger-appliance-iso",
// role = library — holger's own signed wasm guests
"plugin-allocation",
"plugin-skidbladnir",
"plugin-rust-toolchain",
// vm1: holger's OWN front program and its medium
"estate-front-musl-metered",
"estate-front-musl-metered-narrate",
"holger-front-installer-metered",
// the keep media's pointers: the ONE shop's in-place upgrade
"holger-keep",
"holger-front-keep",
// a downstream's payloads and media: the one-way and the fetching one
"downstream-musl",
"holger-downstream-oneway-installer",
"downstream-fetch-musl",
"holger-downstream-installer",
]
);
assert_eq!(
server_variants().len(),
19,
"the server and the downstream, the four estate server builds, the four media pointers, \
the front's payload, its DEV row and its medium, the two keep media, and a downstream's \
two payloads and media (one-way, fetching)"
);
assert_eq!(
client_variants().len(),
2,
"the command-line tool and the console"
);
// ── the two pointer variants, and the rows they point at ────────────────
// These are what the mirror LACKED for 13 days (edda 3068cf0 → holger
// a9ad3bf/9c1d3c9): the drift guard names a missing variant, but only an
// assertion BY NAME proves the mirror carries the settings under the row —
// two equally empty files would pass parity.
let appliance = set.get("holger-mvp-appliance").unwrap();
assert_eq!(appliance.kind, ArtifactKind::Container);
assert_eq!(
appliance.container_ref.as_deref(),
Some("holger-mvp-appliance")
);
assert!(
appliance.bin.is_none() && appliance.package.is_none(),
"a pointer carries no cargo argv"
);
assert!(
set.containers
.iter()
.any(|c| c.name == "holger-mvp-appliance"),
"the [[build.container]] row the pointer resolves to is declared"
);
let installer_ptr = set.get("holger-installer").unwrap();
assert_eq!(installer_ptr.kind, ArtifactKind::Installer);
let installer = set
.installers
.iter()
.find(|i| i.name == "holger-installer")
.expect("the [[build.installer]] row of the same name");
assert_eq!(
installer.variant, "mvp-server-musl",
"the installer lays down the static musl tier"
);
// The SETTINGS on the installer row: the SEVEN per-site params, the declared
// fixture for the hub, and the env the metal appliance boots with.
//
// `data_disks` and `reinstall` joined on 2026-09-17 — the two measured
// re-image failures private-holger-ops' REIMAGE_WARNING names, answered on
// the declaration rather than at a boot prompt on a machine with no shell.
// estate.toml already claimed the first of them existed; it did not.
let params: Vec<&str> = installer.params.iter().map(|p| p.name.as_str()).collect();
// `pairing_seed` joined on 2026-09-27 (owner plan 2a): the estate's
// nordisk-pairing seed, a STAGED FILE and never a boot param.
// `install_then` joined the same day: gunnar's blank-disk lay reboots into the
// laid disk instead of installing again (`korp.install.then`).
assert_eq!(
params,
[
"site",
"hub_conf",
"holger_ron",
"plugin_trust_root",
"intel_trust_policy",
"bundles_trust_policy",
"underlay",
"data_disks",
"pairing_seed",
"reinstall",
"install_then",
"live_app"
]
);
assert_eq!(
installer
.params
.iter()
.find(|p| p.name == "install_then")
.unwrap()
.boot_param,
"korp.install.then"
);
let metered = set
.installers
.iter()
.find(|i| i.name == "holger-installer-metered")
.expect("the metered row");
assert!(
metered.params.iter().any(|p| p.name == "install_then"),
"the paid medium takes it too"
);
let seed = installer
.params
.iter()
.find(|p| p.name == "pairing_seed")
.unwrap();
assert_eq!(
(
seed.staged_file.as_str(),
seed.boot_param.as_str(),
seed.required
),
("pairing/seed.toml", "", false),
"the seed carries a key: never on the cmdline"
);
assert_eq!(
installer
.params
.iter()
.find(|p| p.name == "data_disks")
.unwrap()
.boot_param,
"korp.data.disks",
"THE BOOT DISK IS THE LARGER ONE — and this is how a site declares which is which"
);
assert_eq!(
installer
.params
.iter()
.find(|p| p.name == "reinstall")
.unwrap()
.boot_param,
"korp.reinstall",
"RE-IMAGE AND RE-MINT GO TOGETHER — absent reads as `keep`, which is the brick"
);
// The LIVE medium beside it: one row, the installer's own payload, and it
// writes nothing — which is why neither failure above has a path through it.
let live_ptr = set.get("holger-appliance-iso").unwrap();
assert_eq!(live_ptr.kind, ArtifactKind::Iso);
assert!(
live_ptr.bin.is_none() && live_ptr.package.is_none(),
"a pointer carries no cargo argv"
);
let live = set
.isos
.iter()
.find(|i| i.name == "holger-appliance-iso")
.expect("the [[build.iso]] row of the same name");
assert_eq!(
live.from_variant.as_deref(),
Some(installer.variant.as_str()),
"ONE payload, two media"
);
assert!(live.boot_params.iter().any(|t| t == "korp.mode=live"));
assert!(
installer
.params
.iter()
.find(|p| p.name == "holger_ron")
.unwrap()
.required
);
// ★ THE DECLARATION RIDES THE RELEASE (lane B1). Seen red in R10: `holger.ron` and the
// korp root were first-boot `/data` stages, a keep medium skips the stage by design, and
// the installed main panicked on a `/data/holger.ron` that never came. Every installer
// medium — the keep medium included — lays them with the app, signed with it.
for set_installer in set
.installers
.iter()
.filter(|i| i.params.iter().any(|p| p.name == "holger_ron"))
{
for name in [
"holger_ron",
"plugin_trust_root",
"intel_trust_policy",
"bundles_trust_policy",
] {
let p = set_installer
.params
.iter()
.find(|p| p.name == name)
.unwrap_or_else(|| panic!("{}: {name}", set_installer.name));
assert_eq!(
p.lands,
nornir_build_thing::params::Landing::Release,
"{}: {name} lands on the release",
set_installer.name
);
}
}
assert_eq!(
installer
.params
.iter()
.find(|p| p.name == "underlay")
.unwrap()
.boot_param,
"korp.env.HOLGER_UNDERLAY"
);
assert_eq!(
installer
.fixtures
.keys()
.map(String::as_str)
.collect::<Vec<_>>(),
["hub_conf"],
"hub_conf declares the wg-hub fixture (holger 9c1d3c9)"
);
assert_eq!(
installer.env.get("HOLGER_APPLIANCE").map(String::as_str),
Some("1")
);
assert_eq!(installer.cmd, ["start", "--config", "/data/holger.ron"]);
// ★ THE PAYLOAD MUST BE ABLE TO SERVE SOMETHING.
//
// MEASURED 2026-09-21, lane H2, by running the fleet proof: its helper
// hand-spelled `--features mvp-server,appliance` — a SUBSET of this row —
// and the appliance it installed died at first boot with
//
// [holger-mvp-1] repository 'bundles' is `ron_repo_type: "raw"` but
// this server was built without the `fmt-raw` feature; the formats
// this binary serves are []
// Kernel panic - not syncing: Attempted to kill init!
//
// "boots but does not serve", inside the proof that exists to catch it,
// and a panic because holger is PID 1 there. The helper now takes the
// row's own `cargo_args()`; this is the other end — a payload row that
// declares NO format is a medium that installs a server serving nothing,
// whatever any RON on it says.
let payload = set
.get(installer.variant.as_str())
.expect("the payload row");
assert!(
payload.features.iter().any(|f| f.starts_with("fmt-")),
"the appliance payload '{}' declares no `fmt-*` feature, so it serves NO repository \
type at all and every appliance installed from it dies at first boot with \
holger-mvp-1: {:?}",
payload.name,
payload.features
);
assert!(
installer.options.is_empty(),
"holger declares NO installer features: underlay and hub_conf are runtime \
presence checks in server/cli/src/appliance.rs, not compile-time branches"
);
// ★★ **THE TWIN IS A ROLE OF THIS MEDIUM, NOT A MEDIUM OF ITS OWN.**
//
// Owner ruling 2026-09-21: *"gunnar twin must be built EXACTLY like the
// main, it MUST use the SAME iso !!!"* / *"DO not build a special gunnar
// TWIN iso !"*, extended to holger in the same breath. gunnar's metered
// appliance had an UN-METERED twin precisely because the twin had its own
// row; promoted, it would have enforced nothing.
//
// TWO assertions, and the second is the one that makes the first safe.
assert!(
!set.installers.iter().any(|i| i.name.contains("twin"))
&& !set.isos.iter().any(|i| i.name.contains("twin"))
&& !set.variants.iter().any(|v| v.name.contains("twin")),
"a twin MEDIUM was declared. The twin is `--param role=twin` on \
holger-installer; a second row is gunnar's un-metered-twin scar, verbatim"
);
// ★ ONE ISO, NOT ONE ROW WITH TWO COMPOSES. A role parameter here would
// mean mastering the medium twice, and "it MUST use the SAME iso" is not
// satisfied by two ISOs off one row. The role is conferred AFTER install,
// over the control plane, by `AdminService.BecomeTwin`.
assert!(
!installer
.params
.iter()
.any(|p| p.name == "role" || p.name == "primary"),
"the medium declares a role parameter, so a twin needs its own compose: \
that is two ISOs, and the ruling was ONE"
);
// ★ THE PAID MEDIUM IS THE OPEN ONE WITH ANOTHER BINARY — and nothing else.
// Built like gunnar's `gunnar-installer-metered` (`metered_twin`): only
// name, payload variant and volume id are its own; every param (the seed,
// data_disks, reinstall, hub_conf…), the env, the argv and the kernel are
// the open row's, so vm2 and vm3 boot one ISO and the role is elected.
let metered = set
.installers
.iter()
.find(|i| i.name == "holger-installer-metered")
.expect("the metered [[build.installer]] row");
assert_eq!(
set.get("holger-installer-metered").unwrap().kind,
ArtifactKind::Installer
);
assert_eq!(metered.variant, "mvp-server-musl-metered");
assert_eq!(metered.volume_id, "HOLGER_INSTALL_METERED");
let mut same = metered.clone();
same.name = installer.name.clone();
same.variant = installer.variant.clone();
same.volume_id = installer.volume_id.clone();
// ... and the DEV-NARRATE switch (default off, 2026-10-19): an estate medium's option.
assert_eq!(same.options, vec![crate::packaging::dev_narrate_option()]);
same.options = installer.options.clone();
assert_eq!(&same, installer, "the metered medium differs from the open one ONLY in name, payload, volume id and the dev-narrate option");
// The FRONT medium declares the estate's root of time (edda estate-clock):
// `clock` → korp.env.ESTATE_CLOCK, optional; the box media declare none —
// a box takes `clock_peer` + `clock_key` from the front's offer.
let front = set
.installers
.iter()
.find(|i| i.name == "holger-front-installer-metered")
.expect("the front row");
let front_params: Vec<&str> = front.params.iter().map(|p| p.name.as_str()).collect();
assert_eq!(
front_params,
[
"site",
"data_disks",
"pairing_seed",
"reinstall",
"install_then",
"live_app",
"clock"
]
);
let clock = front.params.iter().find(|p| p.name == "clock").unwrap();
assert_eq!(
(clock.boot_param.as_str(), clock.required),
("korp.env.ESTATE_CLOCK", false)
);
assert!(
!installer
.params
.iter()
.chain(&metered.params)
.any(|p| p.name.starts_with("clock")),
"a box takes its clock from the front's offer"
);
// tunnr's kernel on EVERY holger medium (owner 2026-09-27).
for donor in [
&installer.kernel_donor,
&metered.kernel_donor,
&live.kernel_donor,
] {
let d = donor.as_ref().expect("a declared kernel donor");
assert_eq!(
(d.repo.as_str(), d.kernel.as_str()),
("tunnr", "tunnr-kernel")
);
}
// ...and that kernel carries kvmclock on EVERY holger medium: the row-level
// requirement on edge `kernel` names the four paravirt wall-clock symbols
// =y (edda `clock_requires`), so `--build kernel` merges them into
// tunnr's `nuked` fragment, whose tinyconfig base leaves HYPERVISOR_GUEST
// off (measured 2026-09-28) — else the box boots on the emulated CMOS, +2 h.
let clock_rows =
|reqs: &[nornir_build_thing::requires::EdgeRequirement]| -> Vec<(String, String)> {
reqs.iter()
.filter_map(|r| match &r.what {
nornir_build_thing::requires::Requirement::Kconfig(k)
if r.edge == "kernel"
&& k.value
== nornir_build_thing::kernel_requires::KconfigValue::Y =>
{
Some((r.edge.clone(), k.symbol.clone()))
}
_ => None,
})
.collect()
};
let want: Vec<(String, String)> = [
"CONFIG_HYPERVISOR_GUEST",
"CONFIG_PARAVIRT",
"CONFIG_KVM_GUEST",
"CONFIG_PARAVIRT_CLOCK",
]
.into_iter()
.map(|s| ("kernel".to_string(), s.to_string()))
.collect();
for (name, reqs) in [
(installer.name.as_str(), &installer.requires),
(metered.name.as_str(), &metered.requires),
(front.name.as_str(), &front.requires),
(live.name.as_str(), &live.requires),
] {
let got = clock_rows(reqs);
for w in &want {
assert!(
got.contains(w),
"{name}: the kernel row must carry {}=y (kvmclock), got {got:?}",
w.1
);
}
}
// The metered payload = the open musl payload, its built-in allow-meter swapped for the
// wasm host (H2 #2), + `metered` + `admit-share` + `intel-fetch`
// (owner 2026-09-28: the admit-share row applies to every metered row) +
// `front` (2026-10-17: monetize's sale check reads `GET /-/releases`), and
// it consumes holger's own signed plugin through a declared edge.
let open_payload = set.get("mvp-server-musl").unwrap();
let paid_payload = set.get("mvp-server-musl-metered").unwrap();
// H2 #2: the open payload meters with the built-in allow-meter; the paid one
// carries the wasm host (`wasm-plugins` + `meter-guests`) and loads signed guests.
let mut plus: Vec<&str> = open_payload
.features
.iter()
.map(String::as_str)
.filter(|f| *f != "allow-meter")
.collect();
plus.push("wasm-plugins");
plus.push("meter-guests");
plus.push("metered");
plus.push("admit-share");
plus.push("front");
// `intel-fetch` (lane W4): the sold estate is online and refreshes its own
// intel; the open appliance payload stays zero-egress.
plus.push("intel-fetch");
plus.sort_unstable();
let mut got: Vec<&str> = paid_payload.features.iter().map(String::as_str).collect();
got.sort_unstable();
assert_eq!(got, plus);
assert_eq!(paid_payload.target, open_payload.target);
assert_eq!(paid_payload.bin.as_deref(), Some("holger-mvp-metered"));
let edge = paid_payload
.edges
.get("plugin")
.expect("the own-plugin edge");
assert_eq!(
(edge.repo.as_str(), edge.variant.as_str(), edge.typ),
("monetize-impl", "holger-guest", ArtifactKind::Wasm)
);
assert!(
open_payload.edges.is_empty(),
"the open payload carries no plugin"
);
// The products' bins are their crates' names; the estate's builds of the server
// share `holger-mvp` / `holger-mvp-metered` — the `target` triple separates the files.
let bins: Vec<&str> = [
"holger",
"holger-downstream",
"holger-cli",
"holger-ui",
"mvp-server-musl",
"mvp-server-metered",
"mvp-server-musl-metered",
"downstream-musl",
]
.iter()
.map(|t| set.get(t).unwrap().bin.as_deref().unwrap())
.collect();
assert_eq!(
bins,
[
"holger",
"holger-downstream",
"holger-cli",
"holger-ui",
"holger-mvp",
"holger-mvp-metered",
"holger-mvp-metered",
"holger-downstream"
]
);
for p in crate::variants::PRODUCTS {
let v = set.get(p).unwrap();
assert_eq!(
v.package.as_deref(),
Some(p),
"{p}: the row builds the crate of its name"
);
assert_eq!(
v.kind,
ArtifactKind::Exe,
"{p}: what `cargo install` leaves in ~/.cargo/bin"
);
assert!(
v.features.is_empty() && !v.no_default_features,
"{p}: its defaults"
);
}
// The sold server is a service with a member identity.
assert_eq!(
set.get("mvp-server-metered").unwrap().kind,
ArtifactKind::Service
);
assert_eq!(
set.get("mvp-server-metered").unwrap().member.as_deref(),
Some("holger")
);
// Every declared variant validates against its own set.
for v in &set.variants {
assert!(
set.validate(v).is_ok(),
"'{}': {:?}",
v.name,
set.validate(v).err()
);
}
}
/// **Every variant name this file's RULES mention must be a declared variant.**
///
/// This is the guard the `henriks-*` rename walked straight past. The rule lists
/// are holger knowledge and stay hand-written — which tiers must be airgapped is
/// not in the schema — but a rule naming a variant that does not exist is a rule
/// that fires on nothing, and it fails OPEN: `links_no_http_client` simply
/// answered `false` and every artifact sailed through un-judged.
#[test]
fn every_rule_names_a_declared_variant() {
let set = build_variants();
let declared = set.names();
let mut ghosts: Vec<(&str, &str)> = Vec::new();
for (list, name) in [(AIRGAP_TIERS.to_vec(), "AIRGAP_TIERS")] {
for v in list {
if !declared.contains(&v) {
ghosts.push((name, v));
}
}
}
assert!(
ghosts.is_empty(),
"these rules name variants holger does not declare: {ghosts:?}\n\
declared: {declared:?}"
);
// …and the rules DISCRIMINATE: a check that applies to everything or to
// nothing proves nothing either way.
assert!(
!AIRGAP_TIERS.is_empty() && AIRGAP_TIERS.len() < set.variants.len(),
"the airgap rule must apply to SOME but not all variants"
);
for p in crate::variants::PRODUCTS {
assert!(
links_no_http_client(p),
"{p}: every product's default dials nothing"
);
}
assert!(
!links_no_http_client("mvp-server-metered"),
"the sold box fetches its own advisories"
);
// An undeclared name is fat by the schema's default, not a panic.
assert_eq!(shape_of("ghost"), ServiceShape::FatService);
}
/// The menu groups by the declaration's own role axis, every variant is reachable
/// from some section, and the entries carry the ecosystem.
#[test]
fn the_menu_groups_the_servers_and_the_clients() {
let m = HolgerBuildThing.menu();
// One suite (MVP), so the menu's sections are its roles, in declaration order.
assert_eq!(
m.in_role(Role::Server),
vec![
"holger",
"holger-downstream",
"mvp-server-musl",
"mvp-server-metered",
"mvp-server-musl-metered",
"mvp-server-musl-metered-narrate",
"holger-mvp-appliance",
"holger-installer",
"holger-installer-metered",
"holger-appliance-iso",
"estate-front-musl-metered",
"estate-front-musl-metered-narrate",
"holger-front-installer-metered",
"holger-keep",
"holger-front-keep",
"downstream-musl",
"holger-downstream-oneway-installer",
"downstream-fetch-musl",
"holger-downstream-installer",
]
);
assert_eq!(m.in_role(Role::Client), vec!["holger-cli", "holger-ui"]);
// No variant is unreachable from the menu — a declared build nobody can pick
// is the same rot as a feature nobody builds. Deduplicated before the
// compare: a dual-suite atom legitimately has one entry PER delivery,
// and the claim here is coverage (nothing declared is missing, nothing
// undeclared appears), not entry count.
let set = m.set();
let mut menu: Vec<String> = m.menu_variants().iter().map(|s| s.to_string()).collect();
let mut declared: Vec<String> = set.names().iter().map(|s| s.to_string()).collect();
menu.sort();
menu.dedup();
declared.sort();
assert_eq!(
menu, declared,
"every declared variant appears in at least one role"
);
assert!(m.entries.iter().all(|e| e.ecosystem == Ecosystem::Cargo));
assert_eq!(m.role_of("holger-cli"), Some(Role::Client));
assert_eq!(m.role_of("holger"), Some(Role::Server));
}
/// **The LINKAGE axis and the NORMAL case**, both read off the declaration
/// rather than restated in Rust for a caller to trust.
#[test]
fn every_role_names_its_normal_case_and_every_entry_its_runtime_cost() {
use nornir_build_thing::plugin::Linkage;
let m = HolgerBuildThing.menu();
// Exactly one default per section — a menu with no normal case makes every
// caller guess: the server, the command-line tool and the open meter guest.
assert!(m.check_defaults().is_empty(), "{:?}", m.check_defaults());
assert_eq!(m.default_for(Role::Server), Some("holger"));
assert_eq!(m.default_for(Role::Client), Some("holger-cli"));
assert_eq!(m.default_for(Role::Library), Some("plugin-allocation"));
assert_eq!(m.entries.iter().filter(|e| e.is_default).count(), 3);
let e = |id: &str| m.entries.iter().find(|e| e.id == id).unwrap().clone();
// ⚠ THIS TEST USED TO ASSERT A FICTION, and that is why it is worth reading.
// It required `full` to say "embeds skade" and `mini` to say "NEEDS SKADE
// RUNNING" — and neither was true of the artifact. holger's server tiers have
// no skade dependency in any direction; `cargo tree -p holger`
// finds none under any tier. A test that asserts a declaration against
// ANOTHER declaration cannot catch that. What follows asserts the MEASURED
// shape instead.
//
// FAT: every server tier owns its own storage and needs no nordisk sibling
// running. "Read-only" (`mini`) is not "thin" — a read-only server is still a
// state OWNER.
for tier in server_variants() {
assert_eq!(e(&tier).linkage, Linkage::Fat, "{tier}");
assert!(
e(&tier)
.runtime
.contains("nothing else needs to be running"),
"{tier}: {}",
e(&tier).runtime
);
// And none of them claims to embed a sibling, because none does.
assert!(
!e(&tier).runtime.contains("skade"),
"{tier} must not claim a skade edge it does not have: {}",
e(&tier).runtime
);
}
// THIN, and `reaches` is where the runtime cost is stated: a holger must be
// running or the client can do nothing.
for tier in ["holger-cli", "holger-ui"] {
assert_eq!(e(tier).linkage, Linkage::Thin, "{tier}");
assert_eq!(
build_variants()
.get(tier)
.unwrap()
.reaches
.iter()
.collect::<Vec<_>>(),
vec!["holger"],
"{tier}"
);
assert!(
build_variants().get(tier).unwrap().embeds.is_empty(),
"{tier}"
);
}
// A role that marks no default is a declaration bug, and says which role.
let mut broken = m.clone();
broken.roles[0].default_variant = None;
for entry in broken.entries.iter_mut() {
if entry.id == "holger" {
entry.is_default = false;
}
}
let problems = broken.check_defaults();
assert!(
problems.iter().any(|p| p.contains("NO default")),
"{problems:?}"
);
assert!(
problems.iter().any(|p| p.contains("server")),
"{problems:?}"
);
}
/// A plan is DATA. holger never runs anything; it says what should happen and the
/// forge decides. An airgap tier's plan carries the closure resolve, so the
/// zero-egress claim can be CHECKED rather than asserted.
#[test]
fn a_plan_is_data_and_an_airgap_tier_plans_its_own_evidence() {
let bt = HolgerBuildThing;
// A row that is not airgap plans its one cargo step, and names the bin it builds.
let p = bt.plan("mvp-server-metered").unwrap();
assert_eq!(p.steps.len(), 1);
assert_eq!(p.steps[0].program, "cargo");
assert_eq!(
p.expected_artifacts,
vec!["target/release/holger-mvp-metered".to_string()]
);
// A product is airgap: build, then resolve the closure the zero-egress claim is
// CHECKED against.
let p = bt.plan("holger").unwrap();
assert_eq!(
p.expected_artifacts,
vec!["target/release/holger".to_string()]
);
assert_eq!(p.steps.len(), 2, "build, then resolve the closure");
assert_eq!(p.steps[1].args[0], "tree");
assert!(p.rationale.as_deref().unwrap().contains("CHECKED"), "{p:?}");
// Nothing in a plan is a shell.
assert!(p.steps.iter().all(|s| s.program == "cargo"));
// The static appliance is airgapped too, so it plans its own evidence.
assert_eq!(bt.plan("mvp-server-musl").unwrap().steps.len(), 2);
assert!(bt.plan("ghost").is_none());
// A POINTER variant plans NO cargo step — never the bare `cargo build` its
// empty argv would spell — and says which builder assembles it instead.
for (ptr, kind) in [
("holger-mvp-appliance", "container"),
("holger-installer", "installer"),
("holger-installer-metered", "installer"),
("holger-appliance-iso", "iso"),
] {
let p = bt.plan(ptr).unwrap();
assert!(
p.steps.is_empty(),
"{ptr} must not plan a cargo step: {p:?}"
);
assert!(p.expected_artifacts.is_empty(), "{ptr}: {p:?}");
let why = p.rationale.as_deref().unwrap();
assert!(why.contains(&format!("[[build.{kind}]]")), "{ptr}: {why}");
assert!(why.contains("pointer variant"), "{ptr}: {why}");
}
}
/// **The graph is what the fat/thin measurement looks like.** A `reaches` edge is
/// dashed — the thing must be RUNNING; an `embeds` edge is solid — it is in the
/// binary. That distinction is the thing a table communicates badly.
///
/// ⚠ This test, too, used to assert the skade fiction: `full` embedding skade and
/// `mini` reaching it. Neither edge exists in holger's Cargo graph. It now asserts
/// that the server tiers claim NO sibling edge at all, and that the dashed edge
/// belongs where it is real — on the clients that cannot work without a holger up.
#[test]
fn the_graph_shows_what_a_variant_compiles_in_and_what_it_needs_running() {
use nornir_build_thing::plugin::{EdgeKind, NodeKind};
let bt = HolgerBuildThing;
let g = bt.graph("holger");
assert_eq!(g.repo, "holger");
assert_eq!(g.root, "holger");
assert!(g.dangling_edges().is_empty(), "{:?}", g.dangling_edges());
assert!(
g.embedded().is_empty(),
"no holger server tier compiles in a sibling"
);
assert!(g.reached().is_empty(), "and none needs one running");
let g = bt.graph("holger-downstream");
assert!(
g.embedded().is_empty(),
"the downstream compiles in nothing"
);
assert!(
g.reached().is_empty(),
"…and, being a state owner, needs nothing running"
);
// The dashed edge is REAL on the clients: the tool embeds no holger core, so
// a holger must be up or its server commands can do nothing.
let g = bt.graph("holger-cli");
assert!(g.embedded().is_empty());
assert_eq!(
g.reached(),
vec!["holger"],
"the thin client needs a holger RUNNING"
);
assert!(
EdgeKind::Reaches.dashed(),
"a runtime dep is dashed, not solid"
);
assert!(!EdgeKind::Embeds.dashed(), "compiled-in is solid");
// The plan DAG and the artifact are in the picture, and the root node carries
// the variant to select — which is why this is data, not an SVG.
let g = bt.graph("holger");
let steps: Vec<&str> = g
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Step)
.map(|n| n.label.as_str())
.collect();
assert_eq!(steps.len(), 2, "build, then resolve the closure: {steps:?}");
let artifact = g
.nodes
.iter()
.find(|n| n.kind == NodeKind::Artifact)
.unwrap();
assert_eq!(artifact.label, "holger");
let root = g
.nodes
.iter()
.find(|n| n.kind == NodeKind::Variant)
.unwrap();
assert_eq!(
root.selects.as_deref(),
Some("holger"),
"a node you can click to BUILD"
);
// The server is fat: it owns its own storage and needs nothing else up. It
// embeds no NORDISK sibling either, so the detail is the plain
// self-containment line and NOT an "embeds …" claim.
assert!(
root.detail
.as_deref()
.unwrap()
.contains("nothing else needs to be running"),
"{:?}",
root.detail
);
assert!(
!root.detail.as_deref().unwrap().contains("embeds"),
"{:?}",
root.detail
);
// An unknown variant yields an EMPTY graph, not a panic and not a fake node.
let g = bt.graph("ghost");
assert!(g.nodes.is_empty() && g.edges.is_empty());
}
/// **The rev pin.** A build thing is only true for the revision it was built from.
#[test]
fn a_rev_mismatch_refuses_the_rebuild() {
use nornir_build_thing::plugin::RevMatch;
host::set_native_facts(host::NativeFacts {
rev_and_dirty: ("a".repeat(40), false),
..Default::default()
});
let prov = HolgerBuildThing.provenance();
assert_eq!(prov.repo, "holger");
assert_eq!(prov.rev, "a".repeat(40));
assert!(
!prov.check(&"a".repeat(40)).refuse_rebuild(),
"the tree it describes"
);
let m = prov.check(&"b".repeat(40));
assert!(m.refuse_rebuild(), "a different tree must be REFUSED");
assert!(matches!(m, RevMatch::Mismatch { .. }));
assert!(m.line().contains("REFUSING to rebuild"), "{}", m.line());
// An unknown revision is never treated as a match.
assert!(prov.check("").refuse_rebuild());
host::set_native_facts(host::NativeFacts::default());
}
// ⚠ TWO TESTS MOVED TO holger-egui ON 2026-09-16, with the rules they exercised:
// `the_ui_tiers_mirror_their_required_features` (each rung names the feature its
// `[[bin]]` requires) and `a_read_only_client_may_not_ask_for_an_egress_pane`
// (the client-side half of the airgap rule, the one that stops `mini = ["gui"]`
// being written again). They assert holger-egui's declaration now.
/// **The rule that is not data.** `served` is a perfectly legitimate holger
/// feature — it is in `offers` — so the generic validator accepts it. Only holger
/// knows it must never appear in an airgap tier.
#[test]
fn an_airgap_tier_may_not_ask_for_an_egress_feature() {
let bt = HolgerBuildThing;
// The generic set says yes: both features are offered and not exclusive.
let set = build_variants();
let probe = BuildVariant {
name: "probe".into(),
features: vec!["downstream".into(), "served".into()],
..Default::default()
};
assert!(
set.validate(&probe).is_ok(),
"the DECLARED rules permit this combination"
);
// holger's own rule says no.
let v = bt.validate(&BuildRequest::features(["downstream", "served"]));
assert!(
!v.ok,
"holger's build thing refuses what its declaration alone allows"
);
assert!(
v.line().contains("provably unable to reach the network"),
"{}",
v.line()
);
}
#[test]
fn a_valid_airgap_tier_is_accepted_and_says_what_still_has_to_hold() {
let v = HolgerBuildThing.validate(&BuildRequest::variant("holger"));
assert!(v.ok);
assert!(
v.line().contains("must link no outbound HTTP client"),
"{}",
v.line()
);
// The static appliance is held to the same claim.
let v = HolgerBuildThing.validate(&BuildRequest::variant("mvp-server-musl"));
assert!(v.ok, "{}", v.line());
assert!(
v.line().contains("must link no outbound HTTP client"),
"{}",
v.line()
);
}
/// The artifact-level keystone, in both directions — and the second direction is
/// what proves it discriminates rather than always passing.
#[test]
fn the_airgap_verdict_discriminates_between_the_tiers() {
let bt = HolgerBuildThing;
let facts = |variant: &str, linked: &[&str]| ArtifactFacts {
variant: Some(variant.to_string()),
file_name: format!("holger-{variant}"),
bytes: 26_700_000,
sha256: "0".repeat(64),
linked_crates: linked.iter().map(|s| s.to_string()).collect(),
};
// The products link no client.
let v = bt.judge_artifact(&facts("holger", &["serde", "hyper", "tonic"]));
assert!(v.ok, "{}", v.line());
assert!(v.line().contains("cannot phone home"), "{}", v.line());
// hyper/tonic are the INBOUND stack and must not trip the rule.
assert!(
bt.judge_artifact(&facts("holger-downstream", &["hyper", "h2", "tonic"]))
.ok
);
// The sold box is deliberately NOT under the no-egress rule: it fetches its own
// advisories.
assert!(
bt.judge_artifact(&facts("mvp-server-metered", &["ureq", "tonic"]))
.ok
);
// An airgap row that somehow linked reqwest is refused, by name.
let v = bt.judge_artifact(&facts("holger", &["serde", "reqwest"]));
assert!(!v.ok);
assert!(v.line().contains("reqwest"), "{}", v.line());
assert!(v.line().contains("must not be promoted"), "{}", v.line());
}
// ⚠ `every_ui_tier_is_thin_and_only_the_named_one_may_embed` MOVED to holger-egui
// on 2026-09-16 with `THIN_FORBIDDEN_CRATES` and `EMBEDDED_UI_VARIANT`: it holds
// an artifact's real closure to the thin rule, and the artifacts it judges are
// that repo's four rungs.
/// ★ **The leak that got in arrived as a TLS backend, and on a tier the judge
/// had no opinion about. Both halves of that are now closed.**
///
/// The third measured feature leak was `auth-oidc`/`auth-oauth2` dragging the
/// native OpenSSL stack into `server-local`. `server-local` is neither an
/// airgap tier nor `full`, so [`judge_artifact`] fell through to a bare
/// `Verdict::ok()` and returned GREEN over the one artifact it most needed to
/// look at — and `FORBIDDEN_OUTBOUND_CLIENTS` named eight HTTP clients and
/// not one TLS backend, so even a tier it DID judge would have passed.
///
/// RED against the real history: written against `server-local` because that
/// is the row it actually happened on.
#[test]
fn a_native_tls_backend_is_refused_on_every_row() {
let bt = HolgerBuildThing;
let facts = |variant: &str, linked: &[&str]| ArtifactFacts {
variant: Some(variant.to_string()),
file_name: format!("holger-{variant}"),
bytes: 26_700_000,
sha256: "0".repeat(64),
linked_crates: linked.iter().map(|s| s.to_string()).collect(),
};
// THE HISTORICAL LEAK, exactly as it shipped: an ordinary tier, an
// allowed HTTP client, and openssl-sys riding underneath it.
let v = bt.judge_artifact(&facts(
"mvp-server-metered",
&["reqwest", "openssl-sys", "hyper"],
));
assert!(
!v.ok,
"a row with openssl-sys must be refused: {}",
v.line()
);
assert!(v.line().contains("openssl-sys"), "{}", v.line());
assert!(v.line().contains("rustls end to end"), "{}", v.line());
// The tier that is ALLOWED to reach the network is not allowed a native
// stack either — this is the rule with no tiers.
let v = bt.judge_artifact(&facts(
"mvp-server-musl-metered",
&["reqwest", "ureq", "native-tls"],
));
assert!(!v.ok, "the sold box is not exempt: {}", v.line());
assert!(v.line().contains("native-tls"), "{}", v.line());
// And an airgap tier, where it would previously have been refused for the
// WRONG reason (the client) or not at all.
let v = bt.judge_artifact(&facts("holger-downstream", &["hyper", "tonic", "openssl"]));
assert!(!v.ok, "{}", v.line());
assert!(v.line().contains("openssl"), "{}", v.line());
// rustls is what holger actually links, and it is not a refusal.
assert!(
bt.judge_artifact(&facts(
"mvp-server-metered",
&["reqwest", "rustls", "hyper"]
))
.ok
);
assert!(
bt.judge_artifact(&facts("holger-downstream", &["hyper", "rustls", "tonic"]))
.ok
);
}
/// ★ **A tier the judge has no rule for is still JUDGED, and says so.**
///
/// A bare `Verdict::ok()` and "judged and accepted" are different sentences,
/// and a reader of a verdict could not tell them apart. `server-local` is the
/// tier this matters on, because it is the one an artifact-level check
/// silently skipped while OpenSSL rode in.
#[test]
fn a_row_that_is_not_airgap_is_judged_rather_than_ignored() {
let bt = HolgerBuildThing;
let facts = |variant: &str, linked: &[&str]| ArtifactFacts {
variant: Some(variant.to_string()),
file_name: format!("holger-{variant}"),
bytes: 1,
sha256: "0".repeat(64),
linked_crates: linked.iter().map(|s| s.to_string()).collect(),
};
let v = bt.judge_artifact(&facts(
"mvp-server-metered",
&["reqwest", "hyper", "rustls"],
));
assert!(v.ok, "{}", v.line());
assert!(
!v.reasons.is_empty(),
"a verdict with no reasons is 'no opinion', not 'accepted': {v:?}"
);
assert!(v.line().contains("mvp-server-metered"), "{}", v.line());
assert!(v.line().contains("not constrained"), "{}", v.line());
let v = bt.judge_artifact(&facts("mvp-ui", &["hyper", "rustls"]));
assert!(v.ok, "{}", v.line());
assert!(
v.line().contains("links no outbound HTTP client"),
"{}",
v.line()
);
}
/// An unmeasured guarantee is not a guarantee — refuse rather than green-tick.
#[test]
fn an_artifact_with_no_closure_is_refused_not_assumed_clean() {
let bt = HolgerBuildThing;
let v = bt.judge_artifact(&ArtifactFacts {
variant: Some("holger".into()),
..Default::default()
});
assert!(!v.ok);
assert!(v.line().contains("UNMEASURED"), "{}", v.line());
let v = bt.judge_artifact(&ArtifactFacts::default());
assert!(!v.ok);
assert!(
v.line().contains("does not say which variant"),
"{}",
v.line()
);
}
#[test]
fn a_rebuild_recipe_carries_what_a_rebuild_needs() {
let r = HolgerBuildThing.rebuild_recipe("mvp-server-musl").unwrap();
assert_eq!(r.repo, "holger");
assert_eq!(r.remote.as_deref(), Some(REMOTE));
assert!(r.cargo_args.contains(&"--bin".to_string()));
assert!(r.cargo_args.contains(&"holger-mvp".to_string()));
assert!(r.cargo_args.contains(&"--features".to_string()));
assert!(HolgerBuildThing.rebuild_recipe("ghost").is_none());
}
}