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/every_shape_stays_slim.rs

//! **Every shape stays slim** (Holger v2.0, phase H2; A31 §4.4, owner 2026-10-25).
//!
//! One table, one test: each build shape Holger ships is held to a crate-count bound
//! AND to a list of crates it must never link. The count catches a feature that rode
//! back into a shape; the names catch the one crate that matters even when the count
//! has room (a renderer, a wasm runtime, an estate crate in the plain server).
//!
//! The trees are `cargo tree -e normal --prefix none`, distinct lines with ` (*)`
//! trimmed, the same count `cargo_install_holger.rs` has always used. A row shape
//! takes its package, features and target from the declared row
//! (`holger_recipe::variants::variants()`), never a copy of them.
//!
//! A forbidden name ending in `*` is a prefix (`monetize*`).

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

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

use holger_recipe::variants::variants;

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

/// Where a shape's features come from.
#[derive(Clone, Copy)]
enum From {
    /// `cargo tree -p <package>` with these extra arguments.
    Args(&'static str, &'static [&'static str]),
    /// A declared Build Thing row, by name.
    Row(&'static str),
}

struct Shape {
    label: &'static str,
    from: From,
    /// At most this many distinct crates.
    bound: usize,
    /// Never these crates (a trailing `*` is a prefix).
    forbidden: &'static [&'static str],
}

/// The renderer of the estate board. The server serves the board as DATA (JSON and
/// `AdminService.Topology`'s rows); holger-ui draws it. No server build links it (H2 #1).
const BOARD_RENDERER: &str = "nordisk-estate-svg";

/// The wasm runtime. Only the sold rows run a wasm guest; the open rows' meter is built in
/// (`allow-meter`, H2 #2).
const WASM_RUNTIME: &[&str] = &["wasmtime*", "cranelift*"];

const OPEN_SERVER_ROW: &[&str] = &["monetize*", BOARD_RENDERER, "wasmtime*", "cranelift*"];

/// The OpenPGP verifier (rpgp): only `maven-pgp` builds it, never `online` (H2 #3).
const PGP: &str = "pgp";

/// The estate crates: only an estate row links them (H2's estate cut, server-lib `estate`).
const ESTATE: &[&str] = &["nordisk-twin", "appliance-grow", "disk-snapshot"];

/// The intake chain's crates: `jobs` is the job queue alone and carries none (H2 #3).
const INTAKE_CHAIN: &[&str] = &[
    "holger-provenance",
    "holger-sandbox",
    "holger-inspect",
    "modgunn",
];

/// The airgap rule (docs/HOLGER-V2.md): the default and the downstream build link no
/// outbound client. A source or an upstream is configured, never linked by default.
const OUTBOUND: &[&str] = &[
    "reqwest",
    "ureq",
    "hyper-rustls",
    "isahc",
    "curl",
    "attohttpc",
    "surf",
    "instant-acme",
    "hyper-tls",
    "native-tls",
    "openssl",
];

/// OWNER RULE 7: the public site / SEO (IndexNow is gunnar-public-site's module), MCP and the
/// upstream pull modules.
const NEVER_OPEN: &[&str] = &[
    "holger-front",
    "holger-public-site",
    "gunnar-public-site",
    "rmcp",
    "rmcp-macros",
    "holger-mcp",
    "gunnar-mcp",
    "holger-world-connector",
    "holger-nexus-connector",
    "holger-artifactory-connector",
    "holger-agent-lib",
];

/// The open upstream row: the default, nothing sold, no renderer, no wasm host, no outbound client.
const UPSTREAM_ROW: &[&str] = &[
    "monetize*",
    BOARD_RENDERER,
    "wasmtime*",
    "cranelift*",
    "reqwest",
    "ureq",
    "hyper-rustls",
    "isahc",
    "curl",
    "attohttpc",
    "surf",
    "instant-acme",
    "hyper-tls",
    "native-tls",
    "openssl",
    "holger-front",
    "holger-public-site",
    "gunnar-public-site",
    "rmcp",
    "rmcp-macros",
    "holger-mcp",
    "gunnar-mcp",
    "holger-world-connector",
    "holger-nexus-connector",
    "holger-artifactory-connector",
    "holger-agent-lib",
    // H2's estate cut (HCUT): the twin, the grow plane, the disk snapshot.
    "nordisk-twin",
    "appliance-grow",
    "disk-snapshot",
];

/// The open downstream row: the upstream's list, and no scanner, no PGP and no APK re-signer
/// (it trusts the upstream's signed scan evidence; `downstream_shape.rs` names the families).
const DOWNSTREAM_ROW: &[&str] = &[
    "monetize*",
    BOARD_RENDERER,
    "wasmtime*",
    "cranelift*",
    "reqwest",
    "ureq",
    "hyper-rustls",
    "isahc",
    "curl",
    "attohttpc",
    "surf",
    "instant-acme",
    "hyper-tls",
    "native-tls",
    "openssl",
    // Not `holger-sandbox`: the downstream's bundle verification runs in a job over it
    // (lane HOL-SANDBOX-TRAIT).
    "modgunn",
    "holger-inspect",
    "holger-scan-worker",
    "holger-intel",
    "pgp",
    "rsa",
    "nordisk-pairing",
    "boringtun",
    "korp-installer",
    "holger-front",
    "holger-public-site",
    "gunnar-public-site",
    "rmcp",
    "rmcp-macros",
    "holger-mcp",
    "gunnar-mcp",
    "holger-world-connector",
    "holger-nexus-connector",
    "holger-artifactory-connector",
    "holger-agent-lib",
    "nordisk-twin",
    "appliance-grow",
    "disk-snapshot",
];

/// The command-line tool's row: tonic's client half and nothing of the server, the offline
/// tools (the bundle crate, the courier without its connectors), no outbound client of its
/// own, nothing sold and none of the open server's surfaces.
/// The tool's bound (its row measured, plus room for a patch release).
const CLI_BOUND: usize = 250;

const CLI_ROW: &[&str] = &[
    "holger-server-lib",
    "axum",
    "monetize*",
    BOARD_RENDERER,
    "wasmtime*",
    "cranelift*",
    "reqwest",
    "ureq",
    "hyper-rustls",
    "isahc",
    "curl",
    "attohttpc",
    "surf",
    "instant-acme",
    "hyper-tls",
    "native-tls",
    "openssl",
    "holger-front",
    "holger-public-site",
    "gunnar-public-site",
    "rmcp",
    "rmcp-macros",
    "holger-world-connector",
    "holger-nexus-connector",
    "holger-artifactory-connector",
];

const SHAPES: &[Shape] = &[
    // The four products' rows (HOL-PRODUCTS), each its `cargo install` line; the console's
    // is held in holger-ui (`ui_row_equals_cargo_install.rs`). Measured on dwarf 2026-10-30
    // with the twelve formats: holger-cli 240, holger 344, holger-downstream 328.
    Shape {
        label: "row holger-cli",
        from: From::Row("holger-cli"),
        bound: CLI_BOUND,
        forbidden: CLI_ROW,
    },
    // Lane HOL-SANDBOX-TRAIT: holger 349 -> 359, holger-downstream 328 -> 339 (the job runner,
    // holger-sandbox in the downstream, Heimdall's heimdall-core and its older crypto line).
    Shape {
        label: "row holger",
        from: From::Row("holger"),
        bound: 365,
        forbidden: UPSTREAM_ROW,
    },
    Shape {
        label: "row holger-downstream",
        from: From::Row("holger-downstream"),
        bound: 345,
        forbidden: DOWNSTREAM_ROW,
    },
    // `cargo install holger --features online`: the pull client, and no OpenPGP (H2 #3): 381.
    // +10 since lane HOL-SANDBOX-TRAIT (386 -> 396, oden 2026-10-30): every bundle and scan is a
    // job, so the intake build links holger-job-runner and Heimdall's heimdall-core v0.1.0, whose
    // ed25519-dalek 2 / sha2 0.10 / getrandom 0.2 line sits beside the newer ones.
    // +12 since lane hol-312fix (field test F2, 400 -> 412 on build-1 2026-11-03): `online`
    // brings `jobs` (jera and the APK pull-through), the queue every pull-through runs on;
    // without it `online` loaded its sources and never pulled.
    Shape {
        label: "default + online",
        from: From::Args("holger", &["--features", "online"]),
        bound: 415,
        forbidden: &[
            "monetize*",
            BOARD_RENDERER,
            PGP,
            ESTATE[0],
            ESTATE[1],
            ESTATE[2],
        ],
    },
    // server-lib's job queue alone: no intake chain rides in with it (H2 #3).
    Shape {
        label: "server-lib jobs alone",
        from: From::Args(
            "holger-server-lib",
            &["--no-default-features", "--features", "jobs"],
        ),
        bound: 250,
        forbidden: INTAKE_CHAIN,
    },
    // server-lib's own default (with `online`): no estate crate (the twin, the grow plane,
    // the disk snapshot). 362 at the estate cut (HCUT, builder-64 2026-10-26).
    Shape {
        label: "server-lib default",
        from: From::Args("holger-server-lib", &[]),
        bound: 365,
        forbidden: ESTATE,
    },
    // server-lib with every feature on: no feature of the server library reaches the renderer.
    // 602 since HOL-NCSC's PKCS#11 evidence signer (094aa8cd, feature `pkcs11`, default off):
    // +4, cryptoki, cryptoki-sys, libloading (the module is loaded at runtime) and secrecy
    // (the PIN). Measured 598 just before it (3bf1996f), with bzip2 + libbz2-rs-sys from
    // HOL-NCSC's deny-bzip2 already in.
    Shape {
        label: "server-lib, all features",
        from: From::Args("holger-server-lib", &["--all-features"]),
        // +5 with lane HOL-HEIMDALL-02's `heimdall` (608, oden 2026-10-31): nordisk-heimdall-
        // container and -plain 0.2.0, landlock, enumflags2 and its derive. +17 with
        // HOL-HEIMDALL-04's `heimdall-wasm` (625, oden 2026-11-01): nordisk-heimdall-run and
        // -wasm, wasmtime's component macros and wit-bindgen, and toml 0.8's line.
        bound: 630,
        forbidden: &[BOARD_RENDERER],
    },
    // The static appliance (the suite's main and twin, the ISO and installer payload): 406.
    Shape {
        label: "row mvp-server-musl",
        from: From::Row("mvp-server-musl"),
        // +5 with the appliance's `heimdall` (lane HOL-HEIMDALL-03; 420 on oden 2026-10-31):
        // nordisk-heimdall-container and -plain, landlock, enumflags2 and its derive.
        bound: 425,
        forbidden: OPEN_SERVER_ROW,
    },
    // The sold MVP server row. 485 before H2.
    // +4 since lane HOL-SANDBOX-TRAIT (488 -> 492): the job runner and heimdall-core.
    Shape {
        label: "row mvp-server-metered",
        from: From::Row("mvp-server-metered"),
        // +5 with `heimdall` (lane HOL-HEIMDALL-03; 501 on oden 2026-10-31).
        bound: 505,
        forbidden: &[BOARD_RENDERER],
    },
];

/// The `cargo tree` arguments of a shape.
fn tree_args(from: From) -> Vec<String> {
    let mut a: Vec<String> = ["tree", "-e", "normal", "--prefix", "none"]
        .map(String::from)
        .to_vec();
    match from {
        From::Args(package, extra) => {
            a.extend(["-p".to_string(), package.to_string()]);
            a.extend(extra.iter().map(|s| s.to_string()));
        }
        From::Row(name) => {
            let all = variants();
            let v = all
                .iter()
                .find(|v| v.name == name)
                .unwrap_or_else(|| panic!("no declared row `{name}`"));
            let package = v
                .package
                .clone()
                .unwrap_or_else(|| panic!("row `{name}` names no package"));
            if let Some(dir) = &v.manifest_dir {
                a.extend(["--manifest-path".to_string(), format!("{dir}/Cargo.toml")]);
            }
            a.extend(["-p".to_string(), package]);
            if v.no_default_features {
                a.push("--no-default-features".into());
            }
            if !v.features.is_empty() {
                a.extend(["--features".to_string(), v.features.join(",")]);
            }
            if let Some(t) = &v.target {
                a.extend(["--target".to_string(), t.clone()]);
            }
        }
    }
    a
}

/// Pure: the distinct crates of a `--prefix none` listing.
fn crates(tree: &str) -> BTreeSet<String> {
    tree.lines()
        .map(|l| l.trim_end_matches(" (*)").trim())
        .filter(|l| !l.is_empty())
        .map(String::from)
        .collect()
}

/// Pure: the forbidden crates a listing holds.
fn forbidden_in(crates: &BTreeSet<String>, forbidden: &[&str]) -> Vec<String> {
    let names: BTreeSet<&str> = crates
        .iter()
        .filter_map(|l| l.split_whitespace().next())
        .collect();
    let mut hits = Vec::new();
    for f in forbidden {
        match f.strip_suffix('*') {
            Some(prefix) => hits.extend(
                names
                    .iter()
                    .filter(|n| n.starts_with(prefix))
                    .map(|n| n.to_string()),
            ),
            None if names.contains(f) => hits.push(f.to_string()),
            None => {}
        }
    }
    hits
}

#[test]
fn every_shape_stays_slim() {
    let mut bad = Vec::new();
    for s in SHAPES {
        let args = tree_args(s.from);
        let out = Command::new(env!("CARGO"))
            .current_dir(repo_root())
            .args(&args)
            .output()
            .expect("run cargo tree");
        if !out.status.success() {
            bad.push(format!(
                "{}: cargo {} failed:\n{}",
                s.label,
                args.join(" "),
                String::from_utf8_lossy(&out.stderr)
            ));
            continue;
        }
        let tree = String::from_utf8_lossy(&out.stdout);
        let set = crates(&tree);
        assert!(
            set.len() > 50,
            "{}: an empty tree proves nothing:\n{tree}",
            s.label
        );
        println!("{}: {} crates (bound {})", s.label, set.len(), s.bound);
        if set.len() > s.bound {
            bad.push(format!(
                "{}: {} crates, over the bound of {}: something rode back into this shape",
                s.label,
                set.len(),
                s.bound
            ));
        }
        let hits = forbidden_in(&set, s.forbidden);
        if !hits.is_empty() {
            bad.push(format!(
                "{}: links {hits:?}, which this shape must never link",
                s.label
            ));
        }
    }
    assert!(bad.is_empty(), "a shape is not slim:\n{}", bad.join("\n"));
}

#[test]
fn a_forbidden_prefix_and_a_forbidden_name_are_found_and_nothing_else() {
    let set = crates(
        "holger v2.0.0\nmonetize-embed v0.1.0 (*)\nnordisk-estate-svg v0.1.0\ntokio v1.47.1\n",
    );
    assert_eq!(
        forbidden_in(&set, &["monetize*", BOARD_RENDERER]),
        vec!["monetize-embed", "nordisk-estate-svg"]
    );
    assert!(forbidden_in(&set, &["wasmtime", "pgp"]).is_empty());
    assert!(
        WASM_RUNTIME.iter().all(|w| OPEN_SERVER_ROW.contains(w)),
        "the open rows forbid the whole wasm runtime"
    );
    for row in [UPSTREAM_ROW, DOWNSTREAM_ROW] {
        assert!(
            OUTBOUND.iter().all(|c| row.contains(c)),
            "the airgap rule: an open row forbids every outbound client"
        );
        assert!(
            OPEN_SERVER_ROW.iter().all(|c| row.contains(c)),
            "an open row forbids what every open server row forbids"
        );
        assert!(
            NEVER_OPEN.iter().all(|c| row.contains(c)),
            "owner rule 7: an open row forbids the public site, MCP, IndexNow and the pull modules"
        );
    }
}

/// OWNER RULE 7 (2026-10-26): the downstream, and the upstream's default, never carry the
/// public site / SEO, MCP, IndexNow (gunnar-public-site's module) or an upstream pull module, by name.
#[test]
fn the_open_rows_forbid_the_public_site_mcp_indexnow_and_the_pull_modules_by_name() {
    let set = crates("holger-front v2.0.0\nholger-public-site v2.0.0\nrmcp v0.8.0\ngunnar-public-site v0.1.0\nholger-world-connector v2.0.0\nholger-nexus-connector v2.0.0\nholger-artifactory-connector v2.0.0\nholger-agent-lib v2.0.0\ntokio v1.47.1\n");
    for row in [UPSTREAM_ROW, DOWNSTREAM_ROW] {
        assert_eq!(
            forbidden_in(&set, row).len(),
            8,
            "an open row lets through {:?}",
            set.iter()
                .filter(|c| !forbidden_in(&crates(c), row).iter().any(|_| true))
                .collect::<Vec<_>>()
        );
    }
}

/// ★ A32-17: every name these shapes forbid is a crate that exists, or a named crates.io
/// or future crate with its reason (`tests/support/known_crates.rs`).
#[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 s in SHAPES {
        names.extend_from_slice(s.forbidden);
    }
    let unknown = known_crates::unknown(&names, &known);
    assert!(
        unknown.is_empty(),
        "a shape forbids crates that exist nowhere: {unknown:?}"
    );
}