crates/nornir-build-thing/tests/declaration_guard.rs
//! **What ⚒ Build Thing refuses before it compiles anything — asserted here, where a
//! test run sees it, instead of on the forge in 0.3 s.**
//!
//! MEASURED 2026-10-16 on loki (⚒ Build on nornir): every cell was refused with
//! "`[build.toolchain]`: path dependencies escape this repo into sibling repo(s) the
//! declaration does not list … Add `siblings = ["Skidbladnir", "facett"]`", and the
//! build gate then said "18 of 18 declared bins were NEVER BUILT". Nothing in this
//! crate's tests asked either question, and the committed toml was not even held to
//! be the code's emission. Three guards:
//!
//! 1. the committed `.nornir/build-variants.toml` IS the code's emission, byte for
//! byte (the forge's toml road and the code road cannot disagree);
//! 2. every path dependency that leaves this repo — and every path dep of the
//! crates those land in — is in a declared `siblings` checkout (the same lexical
//! reader the container door uses, followed transitively);
//! 3. the build gate over the atoms' bins is GREEN: every `[[bin]]` cargo can see
//! is built by an atom or excused with a reason in `[build.allow_unbuilt_bins]`,
//! and no excuse names a bin an atom builds.
use std::collections::BTreeSet;
use std::path::{Component, Path, PathBuf};
use nbt::buildgate::tally_bins;
use nbt::cargo_container::workspace_escaping_path_deps;
use nbt::emit::first_difference;
use nbt::variant::BuildVariantSet;
use nornir_build_thing::emit::{declared, repo_root};
use nornir_build_thing::emitted_toml;
#[test]
fn the_committed_toml_is_the_codes_emission_byte_for_byte() {
let path = BuildVariantSet::conventional_path(&repo_root());
let committed = std::fs::read_to_string(&path).expect("read the committed declaration");
let emitted = emitted_toml().expect("the recipe emits");
if let Some(diff) = first_difference(&committed, &emitted) {
panic!(
"{} is not the code's emission. Run `cargo run -p nornir-build-thing -- emit` \
and commit the result; never edit the toml by hand. {diff}",
path.display()
);
}
}
/// `a/b/../c` → `a/c`, lexically (the dep's directory may not exist on this box).
fn normalize(p: &Path) -> PathBuf {
let mut out = PathBuf::new();
for c in p.components() {
match c {
Component::ParentDir => {
out.pop();
}
Component::CurDir => {}
other => out.push(other),
}
}
out
}
/// The `[workspace.dependencies]` path of `dep` for the crate at `dir`: the nearest
/// ancestor manifest that declares a `[workspace]`.
fn workspace_dep_path(dir: &Path, dep: &str) -> Option<PathBuf> {
let mut cur = Some(dir);
while let Some(d) = cur {
if let Some(doc) = std::fs::read_to_string(d.join("Cargo.toml"))
.ok()
.and_then(|t| t.parse::<toml::Table>().ok())
{
if let Some(ws) = doc.get("workspace") {
let rel = ws.get("dependencies")?.get(dep)?.get("path")?.as_str()?;
return Some(normalize(&d.join(rel)));
}
}
cur = d.parent();
}
None
}
/// The path dependencies cargo LOADS for a non-member crate at `dir`: its normal and
/// build deps (plain or per-target), never its dev-deps (cargo does not resolve a
/// dependency's dev-dependencies) and never its optional ones, `workspace = true`
/// entries resolved through that crate's own workspace root.
fn loaded_path_deps(dir: &Path) -> Vec<PathBuf> {
let Some(doc) = std::fs::read_to_string(dir.join("Cargo.toml"))
.ok()
.and_then(|t| t.parse::<toml::Table>().ok())
else {
return Vec::new();
};
let mut tables: Vec<&toml::Value> = Vec::new();
for key in ["dependencies", "build-dependencies"] {
tables.extend(doc.get(key));
}
if let Some(t) = doc.get("target").and_then(toml::Value::as_table) {
for v in t.values() {
for key in ["dependencies", "build-dependencies"] {
tables.extend(v.get(key));
}
}
}
let on_by_default = default_enabled(&doc);
let mut out = Vec::new();
for t in tables.into_iter().filter_map(toml::Value::as_table) {
for (dep, spec) in t {
// An OPTIONAL dep of a non-member is resolved only when a feature turns it
// on: counted when the crate's own `default` turns it on (znippy-common →
// openzl-sys-rs), skipped otherwise (draupnir's `tunnr-vm`: not in nornir's
// Cargo.lock, so no tunnr checkout is needed).
if spec.get("optional").and_then(toml::Value::as_bool) == Some(true)
&& !on_by_default.contains(dep.as_str())
{
continue;
}
if let Some(rel) = spec.get("path").and_then(toml::Value::as_str) {
out.push(normalize(&dir.join(rel)));
} else if spec.get("workspace").and_then(toml::Value::as_bool) == Some(true) {
let name = spec.get("package").and_then(toml::Value::as_str).unwrap_or(dep);
out.extend(workspace_dep_path(dir, name));
}
}
}
out
}
/// Every feature and dep name the crate's `default` feature turns on, following its
/// own `[features]` table (`default = ["openzl"]`, `openzl = ["dep:openzl-sys-rs"]`).
fn default_enabled(doc: &toml::Table) -> BTreeSet<String> {
let features = doc.get("features").and_then(toml::Value::as_table);
let mut on = BTreeSet::new();
let mut queue = vec!["default".to_string()];
while let Some(f) = queue.pop() {
if !on.insert(f.clone()) {
continue;
}
let Some(list) = features.and_then(|t| t.get(&f)).and_then(toml::Value::as_array) else {
continue;
};
for item in list.iter().filter_map(toml::Value::as_str) {
// `dep:x` and `x?/feat` / `x/feat` all name the dependency `x`.
let name = item.trim_start_matches("dep:");
let name = name.split('/').next().unwrap_or(name).trim_end_matches('?');
queue.push(name.to_string());
}
}
on
}
/// The sibling checkouts a build of this workspace reaches, by name: every path dep
/// that leaves the repo (the door's own reader, members' dev-deps included), and then
/// TRANSITIVELY the path deps cargo loads for each crate those land in. cargo reads
/// every one of those manifests, so the container must carry every one of those
/// checkouts (2026-10-16: znippy-common → `../openzl-sys-rs` was the one the direct
/// check could not see). A sibling not checked out on this box ends its branch of
/// the walk; the anchor in the test keeps it from going vacuous.
fn reached_siblings(root: &Path) -> BTreeSet<String> {
let parent = root.parent().expect("the repo has a parent").to_path_buf();
let mut names = BTreeSet::new();
let mut seen: BTreeSet<PathBuf> = BTreeSet::new();
let mut queue: Vec<PathBuf> =
workspace_escaping_path_deps(root).into_iter().map(|(_, _, r)| r).collect();
// The ROOT's `[patch.<registry>]` paths are loaded too when this repo is the build
// root — a patch is not a dependency table, but cargo reads every one of them.
if let Some(patch) = std::fs::read_to_string(root.join("Cargo.toml"))
.ok()
.and_then(|t| t.parse::<toml::Table>().ok())
.and_then(|d| d.get("patch").cloned())
{
for reg in patch.as_table().into_iter().flat_map(|t| t.values()) {
for spec in reg.as_table().into_iter().flat_map(|t| t.values()) {
if let Some(rel) = spec.get("path").and_then(toml::Value::as_str) {
queue.push(normalize(&root.join(rel)));
}
}
}
}
while let Some(dir) = queue.pop() {
if dir.starts_with(root) || !seen.insert(dir.clone()) {
continue;
}
if let Some(c) = dir.strip_prefix(&parent).ok().and_then(|r| r.components().next()) {
names.insert(c.as_os_str().to_string_lossy().into_owned());
}
queue.extend(loaded_path_deps(&dir));
}
names
}
#[test]
fn every_escaping_path_dep_lands_in_a_declared_sibling() {
let root = repo_root();
let reached = reached_siblings(&root);
// NON-VACUOUS: nornir path-deps edda in a dozen places; a reader that found
// nothing would make this test pass on any declaration.
assert!(reached.contains("edda"), "the reader found no escape into edda: {reached:?}");
let tc = declared().toolchain.expect("nornir declares [build.toolchain]");
let declared: BTreeSet<String> = tc.siblings.iter().cloned().collect();
let missing: Vec<&String> = reached.difference(&declared).collect();
assert!(
missing.is_empty(),
"path deps escape into sibling repo(s) [build.toolchain] `siblings` does not list — \
the build container would not have them and ⚒ refuses every cell: {missing:?}"
);
}
#[test]
fn the_build_gate_over_the_atoms_is_green() {
let produced: BTreeSet<String> = declared().variants.iter().filter_map(|v| v.bin.clone()).collect();
assert_eq!(produced.len(), 5, "the five atoms each build their own bin: {produced:?}");
let (tally, problems) = tally_bins(&repo_root(), produced, false).expect("the gate runs");
assert!(problems.is_empty(), "allowlist problems: {problems:?}");
assert!(tally.declared.len() > 5, "the gate saw only the atoms — vacuous: {:?}", tally.declared);
tally.verdict().unwrap_or_else(|e| panic!("{e}\n{}", tally.headline()));
}
/// **No cell links the SYSTEM openssl.** No build-toolchain image carries libssl,
/// so a cell whose graph reaches `openssl-sys` cannot link in the container —
/// MEASURED on loki 2026-10-16: nornir, nornir-server and nornir-cli failed on it,
/// through the `robot` feature's fantoccini (default `native-tls`). TLS is rustls.
/// Asked of cargo's own resolver, per cell, with the cell's exact feature set.
#[test]
fn no_cell_links_the_system_openssl() {
let set = declared();
let mut asked = 0;
for v in set.variants.iter().filter(|v| v.kind.is_cargo()) {
let Some(pkg) = &v.package else { continue };
let mut cmd = std::process::Command::new(env!("CARGO"));
cmd.current_dir(repo_root())
.args(["tree", "--offline", "-e", "normal,build", "-i", "openssl-sys", "-p", pkg]);
if v.no_default_features {
cmd.arg("--no-default-features");
}
if !v.features.is_empty() {
cmd.arg("--features").arg(v.features.join(","));
}
let out = cmd.output().expect("cargo tree runs");
let (stdout, stderr) = (String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr));
assert!(
!out.status.success() && stderr.contains("did not match any packages"),
"cell `{}` links the system openssl (openssl-sys is in its graph):\n{stdout}{stderr}",
v.name
);
asked += 1;
}
assert!(asked >= 4, "the guard asked {asked} cell(s) — it must see nornir's cargo cells");
}