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

nordisk/holger

holger-recipe/tests/downstream_shape.rs

//! **The downstream is the smallest secure build** (Holger v2.0 H3, A31 §6, owner 2026-10-25).
//!
//! `cargo install holger-downstream` is the on-prem, air-gapped gateway (its own crate since
//! HOL-PRODUCTS, 2026-10-30, over the server's `downstream` feature): it takes packages from
//! ONE upstream Holger (a signed bundle or a sync), verifies them, and serves the build
//! systems. It runs no scanner, fetches no new trust, renders no board and pulls from no
//! internet registry, so none of those crates may be linked, and it is smaller than the
//! server's default build (the airgap rule).
//!
//! The trees are `cargo tree -e normal --prefix none`, distinct lines with ` (*)`
//! trimmed, the same count `cargo_install_holger.rs` uses for the default.

use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
use std::process::Command;

#[path = "support/known_crates.rs"]
mod known_crates;

fn repo_root() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap()
        .to_path_buf()
}

/// The crates the downstream must never link, by family. A trailing `*` is a prefix.
const FORBIDDEN: &[(&str, &[&str])] = &[
    ("the wasm host", &["wasmtime*", "cranelift*"]),
    (
        "a scanner (it trusts the upstream's signed scan evidence)",
        // Not `holger-sandbox`: since lane HOL-SANDBOX-TRAIT the downstream verifies every
        // bundle that comes in in a job (holger-job-runner over holger-sandbox), and no
        // scanner rides with it.
        &[
            "modgunn",
            "holger-inspect",
            "holger-scan-worker",
            "holger-intel",
            "spdx",
        ],
    ),
    (
        "PGP (no new trust is fetched or parsed here)",
        &["pgp", "rpgp"],
    ),
    (
        "the estate",
        &[
            "nordisk-pairing",
            "boringtun",
            "korp-installer",
            "nordisk-twin",
            "appliance-grow",
            "disk-snapshot",
        ],
    ),
    (
        "the board renderer (the server serves board DATA)",
        &["nordisk-estate-svg"],
    ),
    ("money", &["monetize*", "lettre"]),
    (
        "an internet pull client",
        &[
            "reqwest",
            "ureq",
            "hyper-rustls",
            "isahc",
            "curl",
            "attohttpc",
            "surf",
            "instant-acme",
            "hyper-tls",
            "native-tls",
            "openssl",
        ],
    ),
    (
        "an upstream pull module (Nexus, Artifactory, the public registries)",
        &[
            "holger-world-connector",
            "holger-nexus-connector",
            "holger-artifactory-connector",
            "holger-agent-lib",
        ],
    ),
    // IndexNow has no crate of its own: it is gunnar-public-site's `indexnow` module (A32-17).
    (
        "the public site / SEO / IndexNow",
        &["holger-front", "holger-public-site", "gunnar-public-site"],
    ),
    ("MCP", &["rmcp", "rmcp-macros", "holger-mcp", "gunnar-mcp"]),
    ("the APK re-signer", &["rsa"]),
];

/// The estate crates (H2's estate cut, HCUT 2026-10-26): the twin over mutual TLS
/// (Skidbladnir `nordisk-twin`), the grow plane (edda `appliance-grow`) and the admin
/// view's disks (edda `disk-snapshot`). server-lib links them only with its default-off
/// `estate` feature, which the estate rows (`paired`, `appliance`, `grow-runtime`,
/// `metered`) turn on; neither the default (upstream) build nor the downstream has them.
const ESTATE: &[&str] = &["nordisk-twin", "appliance-grow", "disk-snapshot"];

/// The crates no build with `downstream` may link, whatever else is on: the airgap rule
/// (an internet pull client), money, the wasm host and PGP.
const NEVER_WITH_DOWNSTREAM: &[(&str, &[&str])] = &[
    (
        "an internet pull client",
        &[
            "reqwest",
            "ureq",
            "hyper-rustls",
            "isahc",
            "curl",
            "attohttpc",
            "surf",
            "instant-acme",
            "hyper-tls",
            "native-tls",
            "openssl",
        ],
    ),
    ("money", &["monetize*", "lettre"]),
    ("the wasm host", &["wasmtime*", "cranelift*"]),
    ("PGP", &["pgp", "rpgp"]),
];

fn tree(args: &[&str]) -> BTreeSet<String> {
    tree_of("holger", args)
}

/// `--manifest-path` of a package that is a workspace of its own (`holger-downstream`).
fn manifest_of(package: &str) -> Vec<&'static str> {
    if package == "holger-downstream" {
        vec!["--manifest-path", "holger-downstream/Cargo.toml"]
    } else {
        Vec::new()
    }
}

fn tree_of(package: &str, args: &[&str]) -> BTreeSet<String> {
    let mut a = vec!["tree"];
    a.extend(manifest_of(package));
    a.extend(["-p", package, "-e", "normal", "--prefix", "none"]);
    a.extend_from_slice(args);
    let out = Command::new(env!("CARGO"))
        .current_dir(repo_root())
        .args(&a)
        .output()
        .expect("run cargo tree");
    assert!(
        out.status.success(),
        "cargo {} failed:\n{}",
        a.join(" "),
        String::from_utf8_lossy(&out.stderr)
    );
    let set: BTreeSet<String> = String::from_utf8_lossy(&out.stdout)
        .lines()
        .map(|l| l.trim_end_matches(" (*)").trim())
        .filter(|l| !l.is_empty())
        .map(String::from)
        .collect();
    assert!(
        set.len() > 50,
        "an empty tree proves nothing: cargo {}",
        a.join(" ")
    );
    set
}

/// Pure: the forbidden crates a listing holds, each with the family it belongs to.
fn forbidden_in(set: &BTreeSet<String>) -> Vec<String> {
    forbidden_by(set, FORBIDDEN)
}

fn forbidden_by(set: &BTreeSet<String>, forbidden: &[(&str, &[&str])]) -> Vec<String> {
    let names: BTreeSet<&str> = set
        .iter()
        .filter_map(|l| l.split_whitespace().next())
        .collect();
    let mut hits = Vec::new();
    for (family, crates) in forbidden {
        for f in *crates {
            let found: Vec<&str> = match f.strip_suffix('*') {
                Some(prefix) => names
                    .iter()
                    .copied()
                    .filter(|n| n.starts_with(prefix))
                    .collect(),
                None => names.iter().copied().filter(|n| n == f).collect(),
            };
            hits.extend(found.into_iter().map(|n| format!("{n} ({family})")));
        }
    }
    hits
}

/// `cargo install holger-downstream`.
fn downstream() -> BTreeSet<String> {
    tree_of("holger-downstream", &[])
}

#[test]
fn the_downstream_links_no_scanner_no_wasm_no_estate_no_renderer_and_no_internet_client() {
    let hits = forbidden_in(&downstream());
    assert!(
        hits.is_empty(),
        "the downstream build links crates it must never link:\n  {}",
        hits.join("\n  ")
    );
}

/// Smaller than the server's default tree, and bounded (`cargo tree -e normal`, distinct
/// lines; 339 and 356 on main after HOL-MVP-FORMATS and WG-PSK, 2026-10-30). It drops the
/// scanners (modgunn, sandbox, inspect, intel, spdx, zip) and adds sync and its rules.
const DOWNSTREAM_TREE_BOUND: usize = 342;

#[test]
fn the_downstream_is_smaller_than_the_default_and_bounded() {
    let (down, default) = (downstream().len(), tree(&[]).len());
    println!("downstream: {down} crates; default: {default} crates");
    assert!(down < default, "the downstream links {down} crates, the default {default}: the downstream must stay smaller");
    assert!(down <= DOWNSTREAM_TREE_BOUND, "the downstream links {down} crates, above its bound {DOWNSTREAM_TREE_BOUND}: what rode in?");
}

fn estate_in(set: &BTreeSet<String>) -> Vec<String> {
    forbidden_by(set, &[("the estate", ESTATE)])
}

/// The default (upstream) `cargo install holger` links none of the estate crates (H2's
/// estate cut): the twin, the grow plane and the disk snapshot are the estate rows' only.
#[test]
fn the_default_links_no_estate_crate() {
    let hits = estate_in(&tree(&[]));
    assert!(
        hits.is_empty(),
        "the default `cargo install holger` links estate crates:\n  {}",
        hits.join("\n  ")
    );
    let hits = estate_in(&downstream());
    assert!(
        hits.is_empty(),
        "`cargo install holger-downstream` links estate crates:\n  {}",
        hits.join("\n  ")
    );
}

/// The estate rows still link every estate crate: the cut moved them behind a feature,
/// it did not drop them from the estate (the main, the twin, the sold box).
#[test]
fn the_estate_rows_still_link_the_estate() {
    let rows: Vec<_> = holger_recipe::variants::variants()
        .into_iter()
        .filter(|v| {
            v.package.as_deref() == Some("holger")
                && v.features.iter().any(|f| f == "paired" || f == "appliance")
        })
        .collect();
    assert!(
        rows.len() >= 4,
        "the estate rows (paired or appliance) are declared: {:?}",
        rows.iter().map(|v| &v.name).collect::<Vec<_>>()
    );
    for v in rows {
        let mut a = Vec::new();
        if v.no_default_features {
            a.push("--no-default-features".to_string());
        }
        a.extend(["--features".to_string(), v.features.join(",")]);
        let set = tree(&a.iter().map(String::as_str).collect::<Vec<_>>());
        let names: BTreeSet<&str> = set
            .iter()
            .filter_map(|l| l.split_whitespace().next())
            .collect();
        for c in ESTATE {
            assert!(
                names.contains(c),
                "the estate row `{}` no longer links `{c}`",
                v.name
            );
        }
    }
}

/// **The H4 sync link pulls no HTTP client** (B1): server-lib's `sync` alone, and the
/// downstream with it, reach the upstream over the tonic/hyper the server already has,
/// never ureq or reqwest.
#[test]
fn the_sync_link_links_no_http_client() {
    let out = Command::new(env!("CARGO"))
        .current_dir(repo_root())
        .args([
            "tree",
            "-p",
            "holger-server-lib",
            "-e",
            "normal",
            "--prefix",
            "none",
            "--no-default-features",
            "--features",
            "sync",
        ])
        .output()
        .expect("run cargo tree");
    assert!(
        out.status.success(),
        "cargo tree -p holger-server-lib --features sync: {}",
        String::from_utf8_lossy(&out.stderr)
    );
    let set: BTreeSet<String> = String::from_utf8_lossy(&out.stdout)
        .lines()
        .map(|l| l.trim_end_matches(" (*)").trim().to_string())
        .filter(|l| !l.is_empty())
        .collect();
    assert!(
        set.iter().any(|l| l.starts_with("holger-sync ")),
        "the tree carries the sync protocol"
    );
    assert!(
        set.iter().any(|l| l.starts_with("tonic ")),
        "the sync link's transport is tonic"
    );
    let hits = forbidden_by(
        &set,
        &[("an HTTP client", &["ureq", "reqwest", "hyper-rustls"])],
    );
    assert!(
        hits.is_empty(),
        "server-lib `sync` links:\n  {}",
        hits.join("\n  ")
    );
    let hits = forbidden_by(&downstream(), &[("an HTTP client", &["ureq", "reqwest"])]);
    assert!(
        hits.is_empty(),
        "the downstream (with sync) links:\n  {}",
        hits.join("\n  ")
    );
}

/// The verbs the on-prem gateway needs are in it: the bundle import, the intake chain's
/// wire verb (quarantine, release, approvals) and the audit log; the export is not.
#[test]
fn the_downstream_carries_import_intake_and_audit_and_not_export() {
    let m: toml::Value = toml::from_str(
        &std::fs::read_to_string(repo_root().join("server/cli/Cargo.toml")).unwrap(),
    )
    .unwrap();
    let f = |name: &str| -> Vec<String> {
        m["features"][name]
            .as_array()
            .unwrap_or_else(|| panic!("no feature `{name}`"))
            .iter()
            .map(|v| v.as_str().unwrap().to_string())
            .collect()
    };
    let down = f("downstream");
    // The small core (H2's server-lib `downstream`, through `downstream-core`), the
    // gateway's rules, intake, the bundle verbs, sync (the quarantine of an invalidated
    // package) and the build systems' formats: the twelve MVP formats, server/cli's own
    // passthroughs (HOL-MVP-FORMATS 2026-10-30; `tests/mvp_formats.rs` pins the set).
    for want in [
        "downstream-core",
        "server-lib/gateway",
        "intake",
        "bundle-verbs",
        "sync",
        "sign",
        "fmt-rust",
        "fmt-maven",
        "fmt-python",
        "fmt-docker",
        "fmt-go",
    ] {
        assert!(
            down.iter().any(|d| d == want),
            "`downstream` must name `{want}`: {down:?}"
        );
    }
    assert_eq!(
        f("downstream-core"),
        ["server-lib/downstream"],
        "the small core is server-lib's `downstream` (H2)"
    );
    for never in [
        "supply-chain",
        "secure_update",
        "online",
        "mirror",
        "maven-pgp",
        "wasm-plugins",
        "paired",
        "metered",
    ] {
        assert!(
            !down.iter().any(|d| d == never),
            "`downstream` must not name `{never}`: {down:?}"
        );
    }
}

#[test]
fn a_forbidden_prefix_and_name_are_found_with_their_family() {
    let set: BTreeSet<String> = [
        "holger v2.0.0",
        "wasmtime-environ v1.0.0",
        "modgunn v0.0.8",
        "tokio v1.47.1",
    ]
    .iter()
    .map(|s| s.to_string())
    .collect();
    assert_eq!(
        forbidden_in(&set),
        vec![
            "wasmtime-environ (the wasm host)".to_string(),
            "modgunn (a scanner (it trusts the upstream's signed scan evidence))".to_string()
        ]
    );
}

// ══ OWNER RULE 7 (2026-10-26, owner-decisions-1026 item 7): "airgap = the DOWNSTREAM builds" ══
//
// The downstream may never contain the public site / SEO, MCP, IndexNow or any internet pull
// client, by crate AND by feature. Its ONE allowed outbound is its configured upstream Holger.

/// The holger and server-lib features no downstream build may turn on: the internet pull
/// modules (`online`: the crates.io/PyPI/npm/Maven Central/OCI/Go pull-through clients;
/// `migration`: the Nexus/Artifactory pull; `intel-fetch`/`intel-refresh`: the advisory
/// fetch; `pull-apk`: the APK pull-through; `maven-pgp`: Central's `.asc`) and the public
/// site (`front`, `public-site`).
const FORBIDDEN_FEATURES: &[(&str, &[&str])] = &[
    (
        "holger",
        &[
            "online",
            "migration",
            "intel-fetch",
            "maven-pgp",
            "front",
            "full",
        ],
    ),
    (
        "holger-server-lib",
        &[
            "online",
            "migration",
            "intel-refresh",
            "pull-apk",
            "maven-pgp",
            "front",
        ],
    ),
];

/// Pure: the forbidden features a `cargo tree -e features` listing turns on.
fn forbidden_features_in(listing: &str) -> Vec<String> {
    let mut hits = BTreeSet::new();
    for line in listing.lines() {
        let line = line.trim_end_matches(" (*)").trim();
        let Some((pkg, rest)) = line.split_once(" feature \"") else {
            continue;
        };
        // `online" (command-line)`: the name ends at its closing quote.
        let feat = rest.split('"').next().unwrap_or(rest);
        for (p, feats) in FORBIDDEN_FEATURES {
            if pkg == *p && feats.contains(&feat) {
                hits.insert(format!("{pkg}/{feat}"));
            }
        }
    }
    hits.into_iter().collect()
}

fn features_of(args: &[&str]) -> String {
    features_of_package("holger", args)
}

fn features_of_package(package: &str, args: &[&str]) -> String {
    // `-e features,no-dev -i holger-server-lib`: the features holger and server-lib turn on
    // in the build (dev-dependencies left out), one `<pkg> feature "<f>"` line each.
    let mut a = vec!["tree"];
    a.extend(manifest_of(package));
    a.extend([
        "-p",
        package,
        "-e",
        "features,no-dev",
        "-i",
        "holger-server-lib",
        "--prefix",
        "none",
    ]);
    a.extend_from_slice(args);
    let out = Command::new(env!("CARGO"))
        .current_dir(repo_root())
        .args(&a)
        .output()
        .expect("run cargo tree");
    assert!(
        out.status.success(),
        "cargo {} failed:\n{}",
        a.join(" "),
        String::from_utf8_lossy(&out.stderr)
    );
    String::from_utf8_lossy(&out.stdout).into_owned()
}

#[test]
fn the_guard_names_the_public_site_mcp_indexnow_and_the_pull_modules() {
    let set: BTreeSet<String> = [
        "holger-front v2.0.0",
        "holger-public-site v2.0.0",
        "rmcp v0.8.0",
        "gunnar-public-site v0.1.0",
        "holger-world-connector v2.0.0",
        "holger-agent-lib v2.0.0",
        "reqwest v0.12.28",
        "tokio v1.47.1",
    ]
    .iter()
    .map(|s| s.to_string())
    .collect();
    let hits = forbidden_in(&set);
    for name in [
        "holger-front",
        "holger-public-site",
        "rmcp",
        "gunnar-public-site",
        "holger-world-connector",
        "holger-agent-lib",
        "reqwest",
    ] {
        assert!(
            hits.iter().any(|h| h.starts_with(&format!("{name} ("))),
            "the downstream guard does not name `{name}`: {hits:?}"
        );
    }
    let listing = "holger feature \"online\" (command-line)\nholger-server-lib feature \"front\" (*)\nholger-server-lib feature \"intake\"\n";
    assert_eq!(
        forbidden_features_in(listing),
        ["holger-server-lib/front", "holger/online"]
    );
}

#[test]
fn a_downstream_with_the_public_site_turned_on_is_refused_by_name() {
    let hits = forbidden_in(&tree(&[
        "--no-default-features",
        "--features",
        "downstream,front",
    ]));
    // (the server crate's `downstream` feature is what `holger-downstream` turns on)
    assert!(
        hits.iter().any(|h| h.starts_with("holger-front (")),
        "downstream + front links the public page and the guard passes: {hits:?}"
    );
}

#[test]
fn a_downstream_with_the_internet_pull_turned_on_is_refused_by_feature() {
    let hits = forbidden_features_in(&features_of_package(
        "holger-downstream",
        &["--features", "online"],
    ));
    assert!(hits.iter().any(|h| h.ends_with("/online")), "`holger-downstream --features online` turns the pull-through on and the guard passes: {hits:?}");
}

#[test]
fn the_downstream_turns_on_no_forbidden_feature() {
    let hits = forbidden_features_in(&features_of_package("holger-downstream", &[]));
    assert!(hits.is_empty(), "the downstream turns on {hits:?}");
}

/// The downstream's ONE outbound: the gRPC client of its configured upstream Holger (the
/// sync pull over tonic, on hyper). Allowed by name; nothing else that dials out is linked.
const ALLOWED_OUTBOUND: &[(&str, &str)] = &[
    (
        "tonic",
        "the gRPC client of the one configured upstream Holger (the sync pull)",
    ),
    ("hyper", "tonic's HTTP/2 transport to that upstream"),
    ("hyper-util", "tonic's connector to that upstream"),
];

#[test]
fn the_only_outbound_the_downstream_links_is_its_upstream_holger_line() {
    let set = downstream();
    let names: BTreeSet<&str> = set
        .iter()
        .filter_map(|l| l.split_whitespace().next())
        .collect();
    for (c, why) in ALLOWED_OUTBOUND {
        assert!(
            names.contains(c),
            "`{c}` is allowed ({why}) but no longer linked: drop the allowance"
        );
        assert!(
            !FORBIDDEN.iter().any(|(_, cs)| cs.contains(c)),
            "`{c}` is both allowed and forbidden"
        );
    }
}

/// The online UPSTREAM carries the pull clients and the public site only behind their
/// default-off features: its DEFAULT build (`cargo install holger`) carries none of them.
#[test]
fn the_upstreams_default_build_carries_none_of_them_either() {
    let mut hits = forbidden_by(&tree(&[]), FORBIDDEN);
    // The default is the upstream: it scans (modgunn, the sandbox) and keeps the estate
    // crates the downstream also still links; those families are the downstream's own.
    hits.retain(|h| !h.contains("a scanner") && !h.contains("the estate"));
    hits.extend(forbidden_features_in(&features_of(&[])));
    assert!(
        hits.is_empty(),
        "`cargo install holger` (the default) carries:\n  {}",
        hits.join("\n  ")
    );
}

/// ★ A32-17: every name the guards forbid is a crate that exists (holger or a sibling's
/// package, a crate in one of their locks) or a named crates.io or future crate with its
/// reason (`tests/support/known_crates.rs`). A guard naming a crate that does not exist
/// proves nothing.
#[test]
fn every_forbidden_name_is_a_crate_that_exists() {
    known_crates::sibling_pins_hold();
    let known = known_crates::known_crates();
    let mut names: Vec<&str> = Vec::new();
    for (_, crates) in FORBIDDEN.iter().chain(NEVER_WITH_DOWNSTREAM) {
        names.extend_from_slice(crates);
    }
    names.extend_from_slice(ESTATE);
    let unknown = known_crates::unknown(&names, &known);
    assert!(
        unknown.is_empty(),
        "the downstream guard names crates that exist nowhere: {unknown:?}"
    );
}

/// ★ OX-HOLF: the guard holds in a checkout of holger with NO sibling beside it (a lone
/// clone, a CI tree without gunnar-ui): every forbidden name is known from holger's own
/// packages and locks, or is a named crates.io, sibling or future crate with its reason.
#[test]
fn every_forbidden_name_is_known_without_any_sibling_checked_out() {
    let holger_only = known_crates::known_under(&known_crates::holger_root(), 5);
    assert!(
        holger_only.len() > 300,
        "holger alone names {} crates",
        holger_only.len()
    );
    let mut names: Vec<&str> = Vec::new();
    for (_, crates) in FORBIDDEN.iter().chain(NEVER_WITH_DOWNSTREAM) {
        names.extend_from_slice(crates);
    }
    names.extend_from_slice(ESTATE);
    let unknown = known_crates::unknown(&names, &holger_only);
    assert!(
        unknown.is_empty(),
        "without the siblings the downstream guard cannot tell these names exist: {unknown:?}"
    );
}

/// ★ OX-HOLF: a pinned sibling crate is checked whenever its sibling is checked out: a
/// checkout without the crate fails the guard, and an absent sibling is reported, not silent.
#[test]
fn a_pinned_sibling_crate_is_checked_when_its_sibling_is_there() {
    use std::collections::BTreeSet;
    let pins = [("gunnar-public-site", "gunnar-ui", "the public site")];
    let renamed = |_: &str| Some(BTreeSet::from(["gunnar-site".to_string()]));
    let (wrong, trusted) = known_crates::pin_problems(&pins, renamed);
    assert_eq!(
        (wrong.len(), trusted.len()),
        (1, 0),
        "a sibling that renamed the crate passes: {wrong:?}"
    );
    let present = |_: &str| Some(BTreeSet::from(["gunnar-public-site".to_string()]));
    assert_eq!(known_crates::pin_problems(&pins, present), (vec![], vec![]));
    let (wrong, trusted) = known_crates::pin_problems(&pins, |_| None);
    assert!(
        wrong.is_empty() && trusted == ["gunnar-public-site (../gunnar-ui is not checked out)"],
        "{trusted:?}"
    );
}

/// ★ HCUT: every estate component that takes holger-server-lib names `estate` (or a feature
/// that implies it: `metered`, `grow-runtime`): the front's twin test booted a server
/// without the twin when a merge dropped it (front:default and front:metered went red).
#[test]
fn estate_edges_name_the_estate() {
    for manifest in ["holger-estate-front/Cargo.toml", "estate-tests/Cargo.toml"] {
        let m: toml::Value =
            toml::from_str(&std::fs::read_to_string(repo_root().join(manifest)).unwrap()).unwrap();
        let mut edges = Vec::new();
        for table in ["dependencies", "dev-dependencies"] {
            if let Some(d) = m.get(table).and_then(|t| t.get("holger-server-lib")) {
                edges.push((table, d.clone()));
            }
        }
        assert!(!edges.is_empty(), "{manifest} takes holger-server-lib");
        for (table, d) in edges {
            let f: Vec<&str> = d
                .get("features")
                .and_then(|f| f.as_array())
                .map(|a| a.iter().filter_map(|v| v.as_str()).collect())
                .unwrap_or_default();
            assert!(
                f.iter()
                    .any(|x| ["estate", "metered", "grow-runtime"].contains(x)),
                "{manifest} [{table}] holger-server-lib must name `estate`: {f:?}"
            );
        }
    }
}

/// ★ HCON-7 (owner rule 7: no MCP in the default and the downstream builds, by name): the MCP
/// family names the REAL MCP crates, and each is known whatever siblings are checked out:
/// `rmcp` (+ `rmcp-macros`), the Rust MCP SDK on crates.io that nornir's `mcp` feature links
/// and an online upstream's MCP door would use; `gunnar-mcp`, gunnar-ui's MCP connector; and
/// `holger-mcp`, Holger's MCP door when it is written. `rust-mcp-schema` was no MCP door: it
/// reached nornir's lock only through the mistralrs LLM runtime, so the guard was red on any
/// checkout without nornir beside it, and it named nothing an MCP door would link.
#[test]
fn the_mcp_family_names_the_real_mcp_crates() {
    let mcp = FORBIDDEN
        .iter()
        .find(|(why, _)| *why == "MCP")
        .map(|(_, c)| *c)
        .expect("the MCP family");
    assert_eq!(
        mcp,
        known_crates::MCP,
        "the guard's MCP names are the real ones"
    );
    let nothing_checked_out = std::collections::BTreeSet::new();
    assert!(
        known_crates::unknown(mcp, &nothing_checked_out).is_empty(),
        "each MCP name is known with its reason, sibling or none: {:?}",
        known_crates::unknown(mcp, &nothing_checked_out)
    );
}