Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

tests/build_variants.rs

//! **The guard over `.nornir/build-variants.toml`** — nornir's declared SUITE ×
//! ATOM build space.
//!
//! Until 2026-08-01 nornir had no such test. The only thing validating the file at
//! all was `tests/inotify_watch_wiring.rs::a_build_variant_names_the_feature`,
//! which asks one question about one feature. Everything else about the
//! declaration — that its atoms name real bins, that no two of them write the same
//! file, that exactly one is the normal case per role, that `--suite` selects —
//! was unchecked, and the file was wrong in two independent ways for months:
//!
//! ```text
//! check_bins() → 3 variants emit the SAME artifact — bin 'nornir' (package
//!   nornir, profile release, target <host>) — so the last build silently
//!   OVERWRITES the others and nothing on disk says which one you have:
//!   nornir, nornir-cli, nornir-watch.
//! ```
//!
//! …and all three of those declared `role = "server"` while `nornir` is the CLI,
//! with the real daemon (`nornir-server`) and the real GUI (`nornir-ui`) built by
//! no variant at all.
//!
//! # What each test here would catch (read before adding one)
//!
//! * [`the_declaration_is_valid`] — RED on any shape error `validate_all` knows:
//!   a `thin` atom that embeds, a fat atom embedding a non-downstream sibling, a
//!   `kind` missing its delegate table.
//! * [`exactly_one_default_per_role`] — RED when a `(suite, role)` group has zero
//!   or two `default = true`, i.e. when a caller has to guess the normal case.
//! * [`no_two_atoms_write_the_same_file`] — RED on the collision above.
//! * [`every_declared_bin_is_a_real_cargo_target`] — RED on a PHANTOM atom: a
//!   declared `bin` with no `[[bin]]` behind it. The oracle is
//!   `BuildVariantSet::cargo_bin_targets`, which walks the workspace's manifests
//!   (explicit `[[bin]] name` AND the implicit bin cargo synthesises for a
//!   `[package]` with `src/main.rs`) — never a hardcoded list, which would be the
//!   third declaration of the same thing.
//! * [`the_suite_axis_is_declared_and_selects`] — RED when the `ORIGINAL` tag is
//!   dropped from any atom (so `cargo xtask build --suite ORIGINAL` stops being
//!   the same command it is in korp/dwarves/modgunn), or when an unknown suite
//!   stops being an error.
//! * [`the_four_target_atoms_are_all_present`] — RED when the server / CLI / UI /
//!   solo quartet loses a member or one of them stops pointing at its own bin.
//! * [`the_atom_marker_reaches_the_binary`] — RED when `nornir` and `nornir-cli`
//!   stop printing different `--version` text, i.e. when the atom selector goes
//!   back to producing indistinguishable builds. This one asserts APPLIED OUTPUT:
//!   it runs the two BUILT binaries.
//!
//! Run: `cargo test --test build_variants` (the last one needs
//! `--features atom-cli`, and says so rather than skipping silently).

use std::path::{Path, PathBuf};

#[path = "support/ci_commands.rs"]
mod ci_commands;
use ci_commands::{ci_commands_listing, ci_runs};

use nornir::build::variant::BuildVariantSet;
use nornir_build_thing::plugin::Role;
use nornir_build_thing::suite_cli;

fn repo_root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}

/// The declared set, loaded through the PRODUCTION loader (`discover`), so a file
/// this test can read but the product cannot is a failure here too.
fn declared() -> BuildVariantSet {
    BuildVariantSet::discover(&repo_root())
        .expect("read .nornir/build-variants.toml")
        .expect("nornir declares a build-variant set")
}

/// nornir's delivery name. One delivery today; the constant exists so the tests
/// below read as "the declared delivery" and not as a magic string sprinkled
/// around, and so a second delivery is a one-line change here.
const SUITE: &str = "ORIGINAL";

#[test]
fn the_declaration_is_valid() {
    declared()
        .validate_all()
        .expect("every declared atom must be valid for the set");
}

#[test]
fn exactly_one_default_per_role() {
    let problems = declared().check_defaults();
    assert!(
        problems.is_empty(),
        "check_defaults() is not empty:\n{}",
        problems.join("\n")
    );
}

/// ★ **THE COLLISION GUARD.** Measured RED against the declaration as it stood on
/// 2026-08-01 (the text is in the module header); green only because `nornir`,
/// `nornir-cli` and `nornir-watch` now emit three distinct `[[bin]]` names off one
/// source, holger-style.
#[test]
fn no_two_atoms_write_the_same_file() {
    let problems = declared().check_bins();
    assert!(
        problems.is_empty(),
        "two or more atoms of one delivery emit the same artifact — the last build \
         silently overwrites the others:\n{}",
        problems.join("\n")
    );
}

/// ★ **THE PHANTOM GUARD.** Every declared `bin` must be something cargo can
/// actually build.
#[test]
fn every_declared_bin_is_a_real_cargo_target() {
    let set = declared();
    let real = BuildVariantSet::cargo_bin_targets(&repo_root())
        .expect("walk the workspace manifests for [[bin]] targets");

    // NON-VACUOUS ANCHOR (a): the oracle must have found the targets we know exist.
    // `check_bins_declared` on an EMPTY list reports rather than passing, but a list
    // that is merely INCOMPLETE would red the guard on bins that are real — so pin
    // the ones this repo definitely ships.
    // ⚠ These are BIN names, not atom names, and the two lists are no longer the
    // same — which is the point of pinning them separately. (`nornir-watch`, a
    // real `[[bin]]` no atom declared, was removed with its `inotify` feature in
    // Mímir v2 — the syn delta re-scan it drove is gone.)
    for known in [
        "nornir",
        "nornir-cli",
        "nornir-server",
        "nornir-ui",
        "nornir-solo",
    ] {
        assert!(
            real.iter().any(|b| b == known),
            "cargo_bin_targets() did not find the `{known}` [[bin]] — the oracle is \
             not reading this repo's manifests, so its verdict is vacuous. Found: {real:?}"
        );
    }
    // NON-VACUOUS ANCHOR (b): the check itself can still say no. Feed it an oracle
    // that omits one real target and require it to name that atom.
    let short: Vec<&str> = real
        .iter()
        .map(String::as_str)
        .filter(|b| *b != "nornir-ui")
        .collect();
    let seeded = set.check_bins_declared(&short);
    assert!(
        seeded.iter().any(|p| p.contains("nornir-ui")),
        "check_bins_declared did not flag `nornir-ui` when it was removed from the \
         oracle — it cannot report red, so its green means nothing. Got: {seeded:?}"
    );

    let problems = set.check_bins_declared(&real);
    assert!(
        problems.is_empty(),
        "an atom declares a bin no [[bin]] target exists for:\n{}",
        problems.join("\n")
    );
}

/// ★ **`--suite` MUST SELECT.** `--suite ORIGINAL` resolves to exactly the declared
/// atoms in declaration order; `--suite NOPE` FAILS and names the real delivery.
///
/// `check_suites()` is VACUOUS below two deliveries — with one delivery there is
/// nothing to be a subset of, so an empty result is correct and must NOT be read as
/// "checked and clean". `declares_suites()` is the thing that tells the two apart,
/// so both are asserted.
#[test]
fn the_suite_axis_is_declared_and_selects() {
    let set = declared();

    let problems = set.check_suites();
    assert!(
        problems.is_empty(),
        "check_suites() is not empty:\n{}",
        problems.join("\n")
    );
    assert!(
        set.declares_suites(),
        "no atom carries `suites = [\"{SUITE}\"]`, so the empty check_suites() above is \
         NOT APPLICABLE rather than clean — and `cargo xtask build --suite {SUITE}` (the \
         command shape korp/dwarves/modgunn/holger all answer to) errors out in nornir"
    );
    assert_eq!(
        set.suites(),
        vec![SUITE],
        "nornir ships exactly one delivery, named {SUITE}"
    );

    let declared_names: Vec<&str> = set.variants.iter().map(|v| v.name.as_str()).collect();
    let args = suite_cli::parse(&["--suite", SUITE]).expect("parse --suite");
    let (suite, chosen) = suite_cli::resolve(&set, &args).expect("resolve the declared delivery");
    assert_eq!(suite, SUITE);
    assert_eq!(
        chosen.iter().map(|v| v.name.as_str()).collect::<Vec<_>>(),
        declared_names,
        "--suite {SUITE} did not resolve to the declared atoms"
    );

    // …and an unknown one is LOUD. A selector that shrugs is the silent-green shape.
    let bad = suite_cli::parse(&["--suite", "NOPE"]).expect("parse");
    let err = suite_cli::resolve(&set, &bad).expect_err("--suite NOPE must fail");
    let msg = format!("{err:#}");
    assert!(msg.contains("no such delivery"), "{msg}");
    assert!(
        msg.contains(SUITE),
        "the error must name the real delivery: {msg}"
    );
}

/// The four-atom target as landed in nornir: a server, a CLI client, a UI client
/// and a solo appliance, each pointing at its OWN bin. RED if any of them is
/// dropped, re-roled, or re-aimed at another atom's binary — which is exactly the
/// state this file found the declaration in (three CLI copies calling themselves
/// servers, no daemon atom, no GUI atom).
#[test]
fn the_four_target_atoms_are_all_present() {
    let set = declared();
    let find = |name: &str| {
        set.get(name).unwrap_or_else(|| {
            panic!(
                "atom '{name}' is not declared. Declared: {:?}",
                set.variants.iter().map(|v| &v.name).collect::<Vec<_>>()
            )
        })
    };
    for (atom, role, bin) in [
        ("nornir-server", Role::Server, "nornir-server"),
        ("nornir", Role::Client, "nornir"),
        ("nornir-ui", Role::Client, "nornir-ui"),
        ("nornir-solo", Role::Solo, "nornir-solo"),
    ] {
        let v = find(atom);
        assert_eq!(v.role, Some(role), "atom '{atom}' has the wrong role");
        assert_eq!(
            v.bin.as_deref(),
            Some(bin),
            "atom '{atom}' points at the wrong bin"
        );
    }

    // `solo` is FAT by definition — a solo build that reaches a sibling over the
    // wire is not solo, it is a mixed client.
    let solo = find("nornir-solo");
    assert!(
        solo.features.iter().any(|f| f == "solo"),
        "the solo atom must name the `solo` feature or the [[bin]] cannot build"
    );
    assert!(
        solo.features.iter().any(|f| f == "server"),
        "the solo atom must compile the server IN — that is the whole role"
    );

    // The two-front-doors invariant: the CLI's own `nornir build --variant <x>`
    // must resolve every declared atom through the SAME suite_cli resolver the
    // xtask verb uses. Assert it can find each one by name.
    for v in &set.variants {
        let args = suite_cli::parse(&["--suite", SUITE, "--atom", &v.name]).expect("parse");
        let (_, one) = suite_cli::resolve(&set, &args)
            .unwrap_or_else(|e| panic!("resolve --atom {}: {e:#}", v.name));
        assert_eq!(one.len(), 1);
        assert_eq!(one[0].name, v.name);
    }
}

/// The feature every atom is DISCRIMINATED by must actually differ. Two atoms whose
/// materialized cargo lines are byte-identical are one atom with two names — the
/// "fat nornir and thin nornir are the same bytes" defect this file's header
/// records, one level up.
#[test]
fn no_two_atoms_materialize_the_same_cargo_line() {
    let set = declared();
    let mut seen: std::collections::BTreeMap<String, &str> = std::collections::BTreeMap::new();
    for v in &set.variants {
        let line = v.command_line();
        if let Some(other) = seen.insert(line.clone(), v.name.as_str()) {
            panic!(
                "atoms '{other}' and '{}' render the IDENTICAL cargo line: {line}",
                v.name
            );
        }
    }
    assert_eq!(seen.len(), set.variants.len());
}

// ─────────────────────────────────────────────────────────────────────────────
// APPLIED OUTPUT — the atom marker has to reach the artifact
// ─────────────────────────────────────────────────────────────────────────────

/// Run a built binary's `--version` and return stdout.
fn version_of(exe: &Path) -> String {
    let out = std::process::Command::new(exe)
        .arg("--version")
        .output()
        .unwrap_or_else(|e| panic!("run {} --version: {e}", exe.display()));
    assert!(
        out.status.success(),
        "{} --version exited {}:\n{}",
        exe.display(),
        out.status,
        String::from_utf8_lossy(&out.stderr)
    );
    String::from_utf8_lossy(&out.stdout).to_string()
}

/// ★ **THE NON-VACUOUS PROOF THAT THE ATOM SELECTOR SELECTS.**
///
/// `nornir` and `nornir-cli` are the same source file. Distinct `[[bin]]` names
/// stop them overwriting each other on disk, but that alone does not prove the
/// FEATURE difference reached the compiler — two identically-compiled files under
/// different names would pass `check_bins` perfectly. So run BOTH built binaries
/// and require their `--version` text to differ, in the specific way the
/// declaration says it should: `nornir` is fat with holger embedded, `nornir-cli`
/// is mixed with holger remote.
///
/// This test compares APPLIED OUTPUT of two real processes. It is not a
/// round-trip: nothing here reads the declaration back to itself.
#[cfg(feature = "atom-cli")]
#[test]
fn the_atom_marker_reaches_the_binary() {
    let fat = version_of(Path::new(env!("CARGO_BIN_EXE_nornir")));
    let thin = version_of(Path::new(env!("CARGO_BIN_EXE_nornir-cli")));

    assert_ne!(
        fat.trim(),
        thin.trim(),
        "`nornir --version` and `nornir-cli --version` print the SAME text — the two \
         atoms are indistinguishable at runtime, which is the defect distinct bin names \
         only half-fixed"
    );
    assert!(
        fat.contains("atom nornir ·"),
        "the fat CLI must name its atom: {fat}"
    );
    assert!(
        thin.contains("atom nornir-cli ·"),
        "the thin CLI must name its atom: {thin}"
    );

    // ⚠ AND THIS RUN IS THE HOSTILE CASE, ON PURPOSE — which is the whole reason the
    // two lines above are a real assertion and not a formality. Cargo unifies
    // features across every bin of one package in one invocation, so
    // `--features atom-cli` compiles `atom-cli` into BOTH of these binaries, `nornir`
    // included, which does not require it. Until 2026-08-01 `atom_line` derived the
    // atom name from `cfg!(feature = "atom-cli")` (then spelled `thin`), so under this
    // printed `atom nornir-cli` and the marker marked nothing:
    //
    //   left:  "nornir v0.5.4 (339d6b2) — atom nornir-cli · linkage fat · holger=embedded …"
    //   right: "nornir v0.5.4 (339d6b2) — atom nornir-cli · linkage fat · holger=embedded …"
    //
    // `atom_line` now takes `env!("CARGO_BIN_NAME")`, which is per-BIN and cannot be
    // unified. If anyone puts the name back behind a `cfg!`, this goes red here.
    //
    // The `nornir-ui` leg is the same defect caught from the other side: its
    // DECLARED feature set names none of the discriminators, so the feature-derived
    // version fell through to the `else` arm and the GUI atom shipped calling itself
    // `atom nornir`. That was measured on the real artifact, not in a test build.
    let ui = version_of(Path::new(env!("CARGO_BIN_EXE_nornir-ui")));
    assert!(
        ui.contains("atom nornir-ui ·"),
        "the GUI must name its own atom: {ui}"
    );

    // The REST of the line describes THIS compilation, and all three binaries ARE
    // this compilation, so they must agree. Demanding `holger=remote` from
    // `nornir-cli` here would demand that a process report a feature state its own
    // build did not have — the per-atom holger split is a property of the DECLARED
    // feature sets and is proven by building the atoms (see
    // `.nornir/suite-atoms-design.md`), not by this unified run.
    let want = if cfg!(feature = "embed-holger") {
        "holger=embedded"
    } else {
        "holger=remote"
    };
    for (name, text) in [("nornir", &fat), ("nornir-cli", &thin), ("nornir-ui", &ui)] {
        assert!(
            text.contains(want),
            "{name} must report the holger linkage THIS build actually has ({want}): {text}"
        );
    }
}

/// **The anti-vacuum companion.** [`the_atom_marker_reaches_the_binary`] is
/// `#[cfg(feature = "atom-cli")]`, so a plain `cargo test` compiles it out entirely and
/// the file would look green while its only applied assertion never ran. This one
/// is always compiled and requires CI to build the feature — the same shape
/// `tests/inotify_watch_wiring.rs::something_in_ci_compiles_the_feature` uses, and
/// for the same reason.
#[test]
fn ci_compiles_the_atom_marker_feature() {
    assert!(
        ci_runs("--features atom-cli"),
        "no CI COMMAND builds `--features atom-cli`, so `the_atom_marker_reaches_the_binary` \
         is compiled out of every automatic run and this file's only APPLIED assertion \
         never executes. Restore the line in .woodpecker.yml's build-test step — and note \
         that a `#` in front of it counts as gone, which is why this reads the command \
         list and not the file text (measured blind 2026-08-23). What CI does run:\n{}",
        ci_commands_listing()
    );
}

// ─────────────────────────────────────────────────────────────────────────────
// ★ THE SET IS THE MATRIX — added 2026-08-03
// ─────────────────────────────────────────────────────────────────────────────

/// The five rows of `workspace_nordisk/.nornir/build-matrix-2026-08-02.md` for
/// nornir, as `(atom, role, bin)`. The owner, 2026-08-03: *"ta bort allt inte
/// stämmer med matrisen"*.
///
/// This IS a written-down list, and every other oracle in this file is
/// deliberately walked from the manifests instead. The difference is what it is
/// for: the manifest walk answers "does the declaration describe something that
/// exists", which must never be pinned; this answers "is the declaration the set
/// The owner settled", which is exactly a pinned list or it is nothing. A guard
/// that derived the expected set FROM the declaration it is checking would be the
/// round-trip this file's header warns about.
const MATRIX: [(&str, Role, &str); 5] = [
    ("nornir-server", Role::Server, "nornir-server"),
    ("nornir", Role::Client, "nornir"),
    ("nornir-cli", Role::Client, "nornir-cli"),
    ("nornir-ui", Role::Client, "nornir-ui"),
    ("nornir-solo", Role::Solo, "nornir-solo"),
];

/// ★ **EXACTLY THE MATRIX — no more, no less.**
///
/// RED in both directions, which is the point: a MISSING row fails, and so does
/// an EXTRA one. The extra-row half is what `nornir-watch` and `nornir-mcp`
/// tripped on 2026-08-03; without it a sixth atom could be added back tomorrow
/// and every other test in this file would still pass, because all of them ask
/// "is each declared atom well-formed" and none asked "is this atom supposed to
/// exist at all".
#[test]
fn the_declared_set_is_exactly_the_matrix() {
    let set = declared();
    let got: Vec<&str> = set.variants.iter().map(|v| v.name.as_str()).collect();
    let want: Vec<&str> = MATRIX.iter().map(|(a, _, _)| *a).collect();
    assert_eq!(
        got, want,
        "the declared atom set is not the matrix (order included)"
    );

    for (atom, role, bin) in MATRIX {
        let v = set
            .get(atom)
            .unwrap_or_else(|| panic!("atom '{atom}' is not declared"));
        assert_eq!(v.role, Some(role), "atom '{atom}' has the wrong role");
        assert_eq!(
            v.bin.as_deref(),
            Some(bin),
            "atom '{atom}' points at the wrong bin"
        );
    }

    // The two that left, named so a re-add is a deliberate act with a test to
    // change and not an accident nothing notices.
    for gone in ["nornir-watch", "nornir-mcp", "nornir-thin"] {
        assert!(
            set.get(gone).is_none(),
            "atom '{gone}' is declared again. It is not in the build matrix: \
             `nornir-watch` is one feature flag on the CLI, `nornir-mcp` is a \
             capability of the fat server (`nornir-server mcp`), and `nornir-thin` \
             was renamed `nornir-cli`. Re-adding one means changing MATRIX above, \
             which means asking the owner."
        );
    }
}

/// ★ **ONLY `ORIGINAL` EXISTS.** The owner: *"det skall bara finnas suite=ORIGINAL"*.
///
/// holger is the ONE member allowed two deliveries (`MVP` + `ORIGINAL`); nornir,
/// korp, dwarves and modgunn ship one. Carrying a suite tag is what makes an atom
/// a Build Thing, so "every atom carries exactly `["ORIGINAL"]`" and "no other
/// suite name appears" are the same assertion from both ends, and both are made.
///
/// **Seen RED before being trusted** (LAW 2): the seeded case below hands
/// `check_suites` a set with a second delivery and requires it to say so.
#[test]
fn only_the_original_suite_exists() {
    let set = declared();

    assert_eq!(
        set.suites(),
        vec![SUITE],
        "nornir declares a suite other than {SUITE}"
    );
    for v in &set.variants {
        assert_eq!(
            v.suites,
            vec![SUITE.to_string()],
            "atom '{}' does not carry exactly `suites = [\"{SUITE}\"]` — carrying the \
             tag is what makes an atom a Build Thing, so an atom tagged differently \
             (or not at all) is not one",
            v.name
        );
    }
    assert_eq!(
        set.base_suite.as_deref(),
        Some(SUITE),
        "base_suite must be {SUITE}"
    );

    // NON-VACUOUS ANCHOR: the check can still say no. Seed a second delivery and
    // require `suites()` to report both — if it cannot see a second suite, the
    // equality above is proving nothing.
    let mut seeded = declared();
    seeded.variants[0].suites = vec![SUITE.to_string(), "SMUGGLED".to_string()];
    let seen = seeded.suites();
    assert!(
        seen.contains(&"SMUGGLED"),
        "BuildVariantSet::suites() did not report a second delivery that was planted \
         in it, so its agreement with [{SUITE}] above is vacuous. Got: {seen:?}"
    );
}

/// ★ **EVERY ATOM IS REACHABLE THROUGH THE DECLARED DOOR.**
///
/// A `[[bin]]` with `required-features` is SILENTLY SKIPPED by
/// `cargo build --all-targets` — it is not an error, the bin simply is not built
/// — so an atom whose declared feature set does not satisfy its bin's
/// `required-features` produces NOTHING and cargo exits 0. That is the exact
/// silent-green shape: `xtask build --suite ORIGINAL` would report success having
/// emitted no file.
///
/// The oracle is the MANIFEST (`[[bin]] required-features`), expanded through the
/// package's real `[features]` table via `BuildVariantSet::cargo_feature_tables`
/// + `FeatureTable::closure`, so a composite feature that pulls the marker in
/// transitively counts, as it does for cargo.
///
/// ⚠ **`closure`, NOT `leaf_closure` — and this guard was RED on its first run
/// for exactly that reason.** `leaf_closure` is `closure` with every composite
/// ALIAS dropped, which is right for the suite-subset rule it was written for (a
/// tier label compiles nothing by itself) and WRONG here. Cargo matches
/// `required-features` against the enabled feature NAMES, composites included: a
/// bin requiring `viz` is built when `viz` is enabled, whether or not `viz` is
/// itself a composite. Measured RED 2026-08-03 — the first version reported four
/// perfectly reachable atoms as unreachable:
///
/// ```text
/// atom 'nornir-server' builds bin 'nornir-server', whose `required-features`
///   include 'server' — and 'server' is NOT in the atom's declared feature closure.
/// atom 'nornir-ui'    … include 'viz'  — and 'viz' is NOT in the closure.
/// atom 'nornir-solo'  … include 'solo' — and 'solo' is NOT in the closure.
/// ```
///
/// All four declarations were correct; the guard was reading the wrong closure.
/// Recorded rather than quietly fixed, because "the new guard's own first red was
/// its own bug" is the most useful thing this test has to say about itself.
///
/// ⚠ FOLLOW-UP, recorded rather than silently absorbed: the `required-features`
/// READER below is nornir's own. It belongs beside
/// `BuildVariantSet::cargo_bin_targets` in `nornir-build-thing` — the one writer
/// of "what counts as this repo's manifests" — so korp, dwarves and modgunn get
/// the same guard. It is here and not there only because edda is a shared crate
/// this pass did not own.
#[test]
fn every_atom_satisfies_its_bins_required_features() {
    let set = declared();
    let tables = BuildVariantSet::cargo_feature_tables(&repo_root())
        .expect("read every package's [features] table");
    let required = bin_required_features(&repo_root());

    // NON-VACUOUS ANCHOR (a): the reader must have found the gates we know exist.
    // An empty/blind reader would make every check below trivially pass.
    for (bin, feat) in [
        ("nornir-cli", "atom-cli"),
        ("nornir-ui", "viz"),
        ("nornir-solo", "solo"),
    ] {
        let got = required.get(bin).unwrap_or_else(|| {
            panic!("no `required-features` read for [[bin]] {bin}: {required:?}")
        });
        assert!(
            got.iter().any(|f| f == feat),
            "the manifest reader did not see `{feat}` on [[bin]] {bin} — it is not \
             reading Cargo.toml, so its verdict is vacuous. Got: {got:?}"
        );
    }

    let mut problems: Vec<String> = Vec::new();
    for v in &set.variants {
        let Some(bin) = v.bin.as_deref() else {
            continue;
        };
        let Some(need) = required.get(bin) else {
            continue;
        };
        let pkg = v
            .package
            .as_deref()
            .expect("every nornir atom names its package");
        let table = tables
            .get(pkg)
            .unwrap_or_else(|| panic!("no [features] table for package {pkg}"));
        let have = table.closure(&v.features, !v.no_default_features);
        for f in need {
            if !have.contains(f) {
                problems.push(format!(
                    "atom '{}' builds bin '{bin}', whose `required-features` include \
                     '{f}' — and '{f}' is NOT in the atom's declared feature closure. \
                     cargo SKIPS such a bin silently, so this atom builds nothing and \
                     still exits 0.",
                    v.name
                ));
            }
        }
    }

    // NON-VACUOUS ANCHOR (b): the comparison itself can say no. Take a real atom,
    // strip the feature its bin requires, and require the same logic to catch it.
    {
        let v = set.get("nornir-ui").expect("nornir-ui is declared");
        let table = tables.get("nornir").expect("nornir [features]");
        let stripped: Vec<String> = v.features.iter().filter(|f| *f != "viz").cloned().collect();
        let have = table.closure(&stripped, !v.no_default_features);
        assert!(
            !have.contains("viz"),
            "removing `viz` from nornir-ui's declared features still left it in the \
             closure, so the reachability comparison above cannot fail and its green \
             means nothing"
        );
    }

    assert!(
        problems.is_empty(),
        "unreachable atom(s):\n{}",
        problems.join("\n")
    );
}

/// Every `[[bin]]`'s `required-features`, keyed by bin name, walked from the
/// workspace manifests. Mirrors the walk in
/// `BuildVariantSet::cargo_bin_targets` (see the follow-up note above).
fn bin_required_features(repo_root: &Path) -> std::collections::BTreeMap<String, Vec<String>> {
    fn walk(dir: &Path, out: &mut std::collections::BTreeMap<String, Vec<String>>, depth: usize) {
        if depth > 8 {
            return;
        }
        let Ok(rd) = std::fs::read_dir(dir) else {
            return;
        };
        for e in rd.flatten() {
            let p = e.path();
            let name = e.file_name();
            let name = name.to_string_lossy();
            if p.is_dir() {
                if matches!(name.as_ref(), "target" | ".git" | "vendor" | "node_modules")
                    || name.starts_with('.')
                {
                    continue;
                }
                walk(&p, out, depth + 1);
            } else if name == "Cargo.toml" {
                let Ok(text) = std::fs::read_to_string(&p) else {
                    continue;
                };
                let Ok(doc) = toml::from_str::<toml::Value>(&text) else {
                    continue;
                };
                let Some(bins) = doc.get("bin").and_then(|b| b.as_array()) else {
                    continue;
                };
                for b in bins {
                    let Some(n) = b.get("name").and_then(|n| n.as_str()) else {
                        continue;
                    };
                    let rf: Vec<String> = b
                        .get("required-features")
                        .and_then(|r| r.as_array())
                        .map(|a| {
                            a.iter()
                                .filter_map(|x| x.as_str().map(str::to_string))
                                .collect()
                        })
                        .unwrap_or_default();
                    out.insert(n.to_string(), rf);
                }
            }
        }
    }
    let mut out = std::collections::BTreeMap::new();
    walk(repo_root, &mut out, 0);
    out
}