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/support/known_crates.rs

//! **A guard that names a crate that does not exist proves nothing** (A32-17, HCUT
//! 2026-10-26). The tree guards forbid crates by name; every name (or `prefix*`) must be a
//! crate that exists: a package in holger or a sibling checkout, a crate in one of their
//! `Cargo.lock`s, or one of the named crates.io crates or future crates below, each with
//! its reason. Shared by `downstream_shape.rs` and `every_shape_stays_slim.rs`
//! (tests/support/: no test target of its own).
#![allow(dead_code)]

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

/// crates.io crates the guards forbid that no tree here resolves (that is the point):
/// the internet clients a default or downstream build must never pull in.
pub const ON_CRATES_IO: &[(&str, &str)] = &[
    ("isahc", "an HTTP client on crates.io (libcurl)"),
    ("curl", "libcurl's bindings on crates.io"),
    ("attohttpc", "an HTTP client on crates.io"),
    ("surf", "an HTTP client on crates.io"),
    ("instant-acme", "an ACME client on crates.io (it dials the CA)"),
    ("hyper-tls", "hyper's native-tls connector on crates.io"),
    ("native-tls", "the platform TLS on crates.io (pulls openssl on Linux)"),
    ("openssl", "OpenSSL's bindings on crates.io"),
    ("hyper-rustls", "hyper's rustls connector on crates.io (an HTTPS client)"),
    ("rpgp", "the OpenPGP implementation's crates.io name (its crate is `pgp`)"),
    ("rmcp", "the Rust MCP SDK on crates.io: nornir's `mcp` feature links it, and an online upstream's MCP door would"),
    ("rmcp-macros", "rmcp's macros on crates.io (the `macros` feature nornir turns on)"),
];

/// **Crates of a sibling repository that is not always checked out beside holger**
/// (OX-HOLF 2026-10-27): (crate, sibling repository, why it is forbidden). The guard knows
/// them without the checkout, so it holds in a lone clone or a CI tree without the sibling.
/// It keeps its strength: when the sibling IS checked out, [`sibling_pin_problems`] fails
/// the guard unless the crate is still a package there (a rename or a removal is caught),
/// and when it is not, the guard prints each name it took on this pin.
pub const IN_A_SIBLING: &[(&str, &str, &str)] = &[
    (
        "gunnar-public-site",
        "gunnar-ui",
        "gunnar-ui's public site, SEO and IndexNow module (crates/gunnar-public-site)",
    ),
    (
        "gunnar-mcp",
        "gunnar-ui",
        "gunnar-ui's MCP connector (crates/gunnar-mcp, behind gunnar-ui-server's `mcp` feature)",
    ),
];

/// Pure: for each pin, `packages_of(repo)` gives the package names of that sibling
/// checkout, or `None` when it is not checked out. Returns (pins the checkout contradicts,
/// pins taken on trust because the sibling is absent).
pub fn pin_problems(
    pins: &[(&str, &str, &str)],
    packages_of: impl Fn(&str) -> Option<BTreeSet<String>>,
) -> (Vec<String>, Vec<String>) {
    let (mut wrong, mut trusted) = (Vec::new(), Vec::new());
    for (krate, repo, _) in pins {
        match packages_of(repo) {
            Some(pkgs) if pkgs.contains(*krate) => {}
            Some(_) => wrong.push(format!(
                "{krate}: ../{repo} is checked out and has no package `{krate}`"
            )),
            None => trusted.push(format!("{krate} (../{repo} is not checked out)")),
        }
    }
    (wrong, trusted)
}

/// The package names (only `[package]`, never a lock) under `dir`.
pub fn packages_under(dir: &Path) -> BTreeSet<String> {
    let mut files = Vec::new();
    walk(dir, 6, &mut files);
    let mut names = BTreeSet::new();
    for f in files
        .iter()
        .filter(|f| f.file_name().is_some_and(|n| n == "Cargo.toml"))
    {
        let Ok(text) = std::fs::read_to_string(f) else {
            continue;
        };
        let mut in_package = false;
        for l in text.lines().map(str::trim) {
            if l.starts_with('[') {
                in_package = l == "[package]";
            } else if in_package && l.starts_with("name = \"") {
                names.insert(
                    l.trim_start_matches("name = \"")
                        .trim_end_matches('"')
                        .to_string(),
                );
            }
        }
    }
    names
}

/// The pins checked against the siblings beside this checkout: a pin a checkout
/// contradicts fails; each pin taken on trust is printed by name.
pub fn sibling_pins_hold() {
    let parent = parent_of_repo();
    let (wrong, trusted) = pin_problems(IN_A_SIBLING, |repo| {
        let dir = parent.join(repo);
        dir.join("Cargo.toml")
            .is_file()
            .then(|| packages_under(&dir))
    });
    assert!(
        wrong.is_empty(),
        "a pinned sibling crate is gone from its checkout: {wrong:?}"
    );
    for t in trusted {
        eprintln!("known_crates: taken on its pin, not checked: {t}");
    }
}

/// **The MCP crates the guards forbid** (HCON-7; owner rule 7: no MCP in the default and the
/// downstream builds, by name). The real ones, found with `cargo tree --all-features` over
/// holger and the shared crates: no Holger graph links an MCP crate; nornir's `mcp` feature
/// links `rmcp` (and its `rmcp-macros`), gunnar-ui's `mcp` feature builds `gunnar-mcp`, and
/// Holger's own door would be `holger-mcp`.
pub const MCP: &[&str] = &["rmcp", "rmcp-macros", "holger-mcp", "gunnar-mcp"];

/// Crates not written yet that a guard already keeps out, each with why.
pub const FUTURE: &[(&str, &str)] = &[(
    "holger-mcp",
    "owner rule 7 names Holger's MCP door; it has no crate yet, and when one is written under this name no open row may link it",
)];

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

fn walk(dir: &Path, depth: usize, out: &mut Vec<PathBuf>) {
    if depth == 0 {
        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().to_string_lossy().to_string();
        if p.is_dir() {
            if name.starts_with('.') || name == "target" || name == "node_modules" {
                continue;
            }
            walk(&p, depth - 1, out);
        } else if name == "Cargo.toml" || name == "Cargo.lock" {
            out.push(p);
        }
    }
}

/// Every crate name holger and its sibling checkouts know: their packages and their locks.
pub fn known_crates() -> BTreeSet<String> {
    let names = known_under(&parent_of_repo(), 6);
    assert!(
        names.len() > 300,
        "the sibling trees name {} crates; are the siblings checked out beside holger?",
        names.len()
    );
    names
}

/// The repository root (holger itself).
pub fn holger_root() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap()
        .to_path_buf()
}

/// The crate names `dir` knows (its packages and its locks), `depth` directories down.
pub fn known_under(dir: &Path, depth: usize) -> BTreeSet<String> {
    let mut files = Vec::new();
    walk(dir, depth, &mut files);
    let mut names = BTreeSet::new();
    for f in files {
        let Ok(text) = std::fs::read_to_string(&f) else {
            continue;
        };
        let is_lock = f.file_name().is_some_and(|n| n == "Cargo.lock");
        let mut in_package = false;
        for line in text.lines() {
            let l = line.trim();
            if l.starts_with('[') {
                in_package = l == "[package]" || l == "[[package]]";
                continue;
            }
            if (in_package || is_lock) && l.starts_with("name = \"") {
                names.insert(
                    l.trim_start_matches("name = \"")
                        .trim_end_matches('"')
                        .to_string(),
                );
            }
        }
    }
    names
}

/// Pure: the forbidden names that name no known crate (and are no named crates.io or
/// future crate). A `prefix*` must match at least one known crate.
pub fn unknown<'a>(forbidden: &[&'a str], known: &BTreeSet<String>) -> Vec<&'a str> {
    let named = |n: &str| {
        ON_CRATES_IO.iter().chain(FUTURE).any(|(c, _)| *c == n)
            || IN_A_SIBLING.iter().any(|(c, _, _)| *c == n)
    };
    forbidden
        .iter()
        .copied()
        .filter(|f| match f.strip_suffix('*') {
            Some(prefix) => !known.iter().any(|k| k.starts_with(prefix)),
            None => !known.contains(*f) && !named(f),
        })
        .collect()
}