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

//! **`cargo install holger` gives you the Holger server** (owner 2026-10-22).
//!
//! The crates.io name `holger` is `server/cli`. These checks hold the package
//! installable with plain cargo (no Build Thing, no xtask, no pre-step), keep the
//! DEFAULT install free of any monetize crate, keep every server variant row
//! reachable as `cargo install holger --features …`, and keep the README saying so.
//! If one goes red, `cargo install holger` (or the line the README gives) broke.

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

use holger_recipe::variants::SERVER_CLI;

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

fn toml_at(p: &Path) -> toml::Value {
    let text = std::fs::read_to_string(p).unwrap_or_else(|e| panic!("read {}: {e}", p.display()));
    toml::from_str(&text).unwrap_or_else(|e| panic!("parse {}: {e}", p.display()))
}

fn cli_manifest() -> toml::Value {
    toml_at(&repo_root().join("server/cli/Cargo.toml"))
}

/// The package is `holger`, above the old crates.io `holger` 0.2.1, and its
/// `holger` and `holger-server` bins build with the default features alone.
#[test]
fn the_crates_io_name_holger_is_the_server() {
    assert_eq!(
        SERVER_CLI, "holger",
        "the recipe's server package is the crates.io name"
    );
    let m = cli_manifest();
    let pkg = &m["package"];
    assert_eq!(pkg["name"].as_str(), Some("holger"));
    // `version.workspace = true` (H1: every Holger crate is on the root's workspace version).
    let version = match pkg["version"].as_str() {
        Some(v) => v.to_string(),
        None => {
            assert_eq!(
                pkg["version"]["workspace"].as_bool(),
                Some(true),
                "holger's version is its own or the workspace's"
            );
            toml_at(&repo_root().join("Cargo.toml"))["workspace"]["package"]["version"]
                .as_str()
                .unwrap()
                .to_string()
        }
    };
    let v: Vec<u64> = version.split('.').map(|p| p.parse().unwrap()).collect();
    assert!(
        v > vec![0, 2, 1],
        "holger {v:?} must be above the 0.2.1 already on crates.io"
    );
    let bins = m["bin"].as_array().unwrap();
    // Exactly ONE bin builds by default — `holger` — so a plain build links one binary.
    let default_bins: Vec<&str> = bins
        .iter()
        .filter(|b| b.get("required-features").is_none())
        .filter_map(|b| b["name"].as_str())
        .collect();
    assert_eq!(
        default_bins,
        ["holger"],
        "the default build must link `holger` and nothing else"
    );
    // `holger-server` is the same program under its old name, opt-in.
    let alias = bins
        .iter()
        .find(|b| b["name"].as_str() == Some("holger-server"))
        .expect("[[bin]] holger-server");
    assert_eq!(
        alias["required-features"]
            .as_array()
            .unwrap()
            .iter()
            .filter_map(|f| f.as_str())
            .collect::<Vec<_>>(),
        ["holger-server-alias"]
    );
    // No build script: plain cargo builds it, nothing has to run first.
    assert!(!repo_root().join("server/cli/build.rs").exists());
    assert!(pkg.get("build").is_none());
}

/// Features that carry money or the shop: opt-in only, never in `default`.
/// The default is the small `server-local` build: no bench, no wasm plugins, no
/// money (owner 2026-10-22). The intake chain IS in it since 2026-10-23 (Holger G0:
/// tier 1's RON configures the steps, so a plain `cargo build` must carry them), and
/// `online` and monetize stay out.
const NEVER_DEFAULT: &[&str] = &[
    "metered",
    "admit-share",
    "meter-guests",
    "paired",
    "dev-narrate",
    "appliance",
    "online",
    "wasm-plugins",
    "allow-meter",
    "jobs",
    "mirror",
    "full",
    "testmatrix",
    "intel-fetch",
];

#[test]
fn the_default_features_carry_no_money() {
    let m = cli_manifest();
    let feats = m["features"].as_table().unwrap();
    let mut seen = BTreeSet::new();
    let mut q: Vec<String> = feats["default"]
        .as_array()
        .unwrap()
        .iter()
        .map(|f| f.as_str().unwrap().to_string())
        .collect();
    while let Some(f) = q.pop() {
        if !seen.insert(f.clone()) {
            continue;
        }
        if let Some(sub) = feats.get(&f).and_then(|v| v.as_array()) {
            q.extend(
                sub.iter()
                    .filter_map(|s| s.as_str())
                    .filter(|s| !s.contains('/') && !s.starts_with("dep:"))
                    .map(String::from),
            );
        }
    }
    for f in NEVER_DEFAULT {
        assert!(
            !seen.contains(*f),
            "`{f}` is reached by holger's default features; it must stay opt-in"
        );
    }
}

/// **The small default** (owner 2026-10-22): a plain `cargo build` / `cargo install
/// holger` is the server you can start and reach over HTTPS with a token, and the raw
/// and the rust (cargo) repositories. **Plus the intake chain** (owner 2026-10-23,
/// Holger G0): `secure_update`, which carries `supply-chain`, so the tier 1 RON's
/// `steps:` work in the plain build. Everything else is opt-in.
#[test]
fn the_default_is_the_small_server() {
    let m = cli_manifest();
    let default: Vec<&str> = m["features"]["default"]
        .as_array()
        .unwrap()
        .iter()
        .filter_map(|f| f.as_str())
        .collect();
    // H3 on H2 (2026-10-26): the small core is `downstream-core`; `downstream` is the
    // on-prem gateway and stays out of the default.
    // B4 (A31 7.5): `systemd`, `holger install` / `holger service` through skidbladnir-systemd
    // alone, is in the default (server/cli tests/systemd_flavour.rs).
    // HOL-MVP-FORMATS + HOL-PRODUCTS (2026-10-30): the twelve MVP formats.
    let mut want = vec!["downstream-core"];
    want.extend(holger_recipe::variants::MVP_FORMATS);
    // HOL-CDS: the server fetches from another Holger (`fetch`) and pushes into a diode folder
    // when its RON says so (`export-push`); only the one-way `downstream` leaves `fetch` out.
    want.extend([
        "secure_update",
        "sign",
        "sync",
        "fetch",
        "export-push",
        "systemd",
    ]);
    assert_eq!(default, want);
}

/// **One name for the small core, and the extras are named** (H2 #3; HOL-PRODUCTS
/// 2026-10-30): the core is `downstream-core` (server-lib's `downstream`, the bearer and
/// mTLS doors); the old tier names `mini` and `embedded` are gone; `fmt-all` is the old name
/// of `all-formats`; `full` is `all-formats` + `online` + `federation`.
#[test]
fn the_core_has_one_name_and_the_extras_are_named() {
    let feats = |m: &toml::Value, f: &str| -> Vec<String> {
        m["features"]
            .get(f)
            .unwrap_or_else(|| panic!("no feature `{f}`"))
            .as_array()
            .unwrap()
            .iter()
            .filter_map(|x| x.as_str().map(String::from))
            .collect()
    };
    let cli = cli_manifest();
    assert_eq!(feats(&cli, "downstream-core"), ["server-lib/downstream"]);
    assert_eq!(
        feats(&cli, "downstream").first().map(String::as_str),
        Some("downstream-core"),
        "the gateway turns the core on"
    );
    assert_eq!(
        feats(&cli, "mvp-server"),
        ["downstream-core", "fetch", "export-push", "secure_update"]
    );
    assert!(
        !feats(&cli, "downstream").iter().any(|f| f == "fetch"),
        "the downstream is one-way: no `fetch`"
    );
    assert_eq!(feats(&cli, "fmt-all"), ["all-formats"]);
    assert_eq!(feats(&cli, "all-formats"), ["server-lib/fmt-all"]);
    assert!(feats(&cli, "federation")
        .iter()
        .any(|f| f == "server-lib/federation"));
    for gone in ["mini", "embedded"] {
        assert!(
            cli["features"].get(gone).is_none(),
            "server/cli `{gone}` is retired"
        );
    }
    let lib = toml_at(&repo_root().join("server/lib/Cargo.toml"));
    assert_eq!(feats(&lib, "downstream"), ["auth-bearer", "auth-mtls"]);
    assert!(
        lib["features"].get("embedded").is_none(),
        "server-lib `embedded` is retired"
    );
}

/// **`online` is the pull client, not OpenPGP** (H2 #3): the CLI's `online` no longer
/// carries `maven-pgp`; `full` names it, and `jobs` is the job queue alone.
#[test]
fn online_carries_no_pgp_and_jobs_carries_no_intake_chain() {
    let feats = |m: &toml::Value, f: &str| -> Vec<String> {
        m["features"][f]
            .as_array()
            .unwrap()
            .iter()
            .filter_map(|x| x.as_str().map(String::from))
            .collect()
    };
    let cli = cli_manifest();
    assert!(
        !feats(&cli, "online").iter().any(|f| f.contains("pgp")),
        "online: {:?}",
        feats(&cli, "online")
    );
    assert!(
        feats(&cli, "full").iter().any(|f| f == "maven-pgp"),
        "full names maven-pgp"
    );
    let lib = toml_at(&repo_root().join("server/lib/Cargo.toml"));
    let jobs = feats(&lib, "jobs");
    assert!(
        !jobs
            .iter()
            .any(|f| f == "supply-chain" || f == "admission" || f == "intake"),
        "server-lib jobs: {jobs:?}"
    );
    // H3 on H2: the queue alone; the APK re-signing key is `pull-apk`'s.
    assert!(
        jobs.is_empty(),
        "server-lib jobs is the job queue alone: {jobs:?}"
    );
    assert_eq!(
        feats(&lib, "pull-apk"),
        ["jobs", "dep:rsa", "dep:rand_core"]
    );
    assert!(
        feats(&cli, "jobs")
            .iter()
            .any(|f| f == "server-lib/pull-apk"),
        "the upstream rows' jobs keeps the APK pull-through"
    );
}

/// The default tree stays small: measured at 290 crates on 2026-10-22 (oden, the small default),
/// 282 on 2026-10-23 before the intake chain joined the default, 316 after it, and 336 with
/// G3a's provenance verifier in the chain (builder-64, `cargo tree -p holger -e normal`,
/// distinct packages), 338 after H3 and 332 after H2's estate cut (HCUT 2026-10-26: no
/// nordisk-twin, appliance-grow or disk-snapshot). The bound leaves room for a few, not for
/// a feature that rode back into `default`.
/// 356 on main with the twelve MVP formats and WG-PSK's edges (HOL-MVP-FORMATS's merge,
/// 2026-10-30).
/// 362 on main fe3e2f75 (2026-10-31, CI-RED; 349 at 8a36e6f8 with the same siblings), every one
/// a real edge of the default server: holger-index-rows (HOL-PERF-2's intake indexes),
/// holger-job-runner and nordisk-heimdall-core with ed25519-dalek, ed25519, signature, sha2
/// 0.10, digest 0.10, block-buffer, crypto-common and generic-array (HOL-SANDBOX-TRAIT: a
/// scan worker's result is verified against its pinned key), and bzip2 with libbz2-rs-sys
/// (holger-inspect and holger-provenance read .bz2 members).
const DEFAULT_TREE_BOUND: usize = 366;

#[test]
fn the_default_tree_stays_small() {
    let out = Command::new(env!("CARGO"))
        .current_dir(repo_root())
        .args(["tree", "-p", "holger", "-e", "normal", "--prefix", "none"])
        .output()
        .expect("run cargo tree");
    assert!(
        out.status.success(),
        "cargo tree -p holger failed:\n{}",
        String::from_utf8_lossy(&out.stderr)
    );
    let tree = String::from_utf8_lossy(&out.stdout);
    let crates: BTreeSet<&str> = tree
        .lines()
        .map(|l| l.trim_end_matches(" (*)"))
        .filter(|l| !l.is_empty())
        .collect();
    assert!(crates.len() > 50, "an empty tree proves nothing:\n{tree}");
    assert!(
        crates.len() <= DEFAULT_TREE_BOUND,
        "the default `cargo install holger` links {} crates, over the bound of {DEFAULT_TREE_BOUND}: \
         something rode back into `default`",
        crates.len()
    );
}

/// **The owner's check (a):** `cargo tree -p holger -e normal` with the default
/// features lists no crate whose name contains `monetize`.
#[test]
fn the_default_install_links_no_monetize_crate() {
    let out = Command::new(env!("CARGO"))
        .current_dir(repo_root())
        .args(["tree", "-p", "holger", "-e", "normal", "--prefix", "none"])
        .output()
        .expect("run cargo tree");
    assert!(
        out.status.success(),
        "cargo tree -p holger failed:\n{}",
        String::from_utf8_lossy(&out.stderr)
    );
    let tree = String::from_utf8_lossy(&out.stdout);
    assert!(
        tree.lines().count() > 50,
        "an empty tree proves nothing:\n{tree}"
    );
    let money: BTreeSet<&str> = tree
        .lines()
        .filter_map(|l| l.split_whitespace().next())
        .filter(|name| name.contains("monetize"))
        .collect();
    assert!(
        money.is_empty(),
        "the default `cargo install holger` links {money:?}"
    );
}

/// crates.io refuses a path dependency without a version (optional ones too), so
/// every normal and build edge reachable from `holger`, across the sibling
/// checkouts, carries one. Dev-dependencies without a version are stripped on
/// publish and are allowed.
#[test]
fn every_edge_holger_publishes_has_a_version() {
    let mut seen = BTreeSet::new();
    let mut missing = Vec::new();
    let mut q = vec![repo_root().join("server/cli")];
    while let Some(dir) = q.pop() {
        let dir = dir.canonicalize().unwrap_or(dir);
        if !seen.insert(dir.clone()) {
            continue;
        }
        let m = toml_at(&dir.join("Cargo.toml"));
        let name = m["package"]["name"].as_str().unwrap_or("?").to_string();
        let mut tables: Vec<&toml::value::Table> = Vec::new();
        for key in ["dependencies", "build-dependencies"] {
            if let Some(t) = m.get(key).and_then(|t| t.as_table()) {
                tables.push(t);
            }
        }
        if let Some(targets) = m.get("target").and_then(|t| t.as_table()) {
            for t in targets.values() {
                for key in ["dependencies", "build-dependencies"] {
                    if let Some(t) = t.get(key).and_then(|t| t.as_table()) {
                        tables.push(t);
                    }
                }
            }
        }
        for t in tables {
            for (key, spec) in t {
                let Some(path) = spec.get("path").and_then(|p| p.as_str()) else {
                    continue;
                };
                if spec.get("version").is_none() {
                    missing.push(format!("{name} -> {key} (path {path})"));
                }
                let sub = dir.join(path);
                if sub.join("Cargo.toml").is_file() {
                    q.push(sub);
                }
            }
        }
    }
    assert!(seen.len() > 20, "the walk saw only {} crates", seen.len());
    assert!(
        missing.is_empty(),
        "path dependencies crates.io would refuse (no version):\n{}",
        missing.join("\n")
    );
}

/// The install chapter (docs/v2/02-install.md) gives the first start. The products'
/// install lines are the README's one table (`rows_equal_cargo_install.rs`).
#[test]
fn the_install_chapter_gives_the_first_start() {
    for readme in ["docs/v2/02-install.md"] {
        let text = std::fs::read_to_string(repo_root().join(readme)).unwrap();
        for must in [
            "cargo install holger",
            "cargo build --release",
            "target/release/holger",
            "http://127.0.0.1:18444/healthz",
            "holger start --config",
        ] {
            assert!(
                text.contains(must),
                "{readme} lacks the first start ({must:?})"
            );
        }
    }
}

/// A plain `cargo build` / `cargo build --release` at the repo root builds the server
/// package and nothing else (`default-members`), so a fresh clone gets
/// `target/release/holger` without compiling the whole workspace.
#[test]
fn the_root_build_is_the_server_only() {
    let root = toml_at(&repo_root().join("Cargo.toml"));
    let dm: Vec<&str> = root["workspace"]["default-members"]
        .as_array()
        .expect("[workspace] default-members")
        .iter()
        .filter_map(|v| v.as_str())
        .collect();
    assert_eq!(dm, ["server/cli"]);
    // What cargo itself makes of it: `workspace_default_members` is what a bare
    // `cargo build` selects — the `holger` package alone.
    let out = Command::new(env!("CARGO"))
        .current_dir(repo_root())
        .args(["metadata", "--format-version", "1", "--no-deps"])
        .output()
        .expect("run cargo metadata");
    assert!(
        out.status.success(),
        "cargo metadata failed:\n{}",
        String::from_utf8_lossy(&out.stderr)
    );
    let meta = String::from_utf8_lossy(&out.stdout);
    let key = "\"workspace_default_members\":[";
    let at = meta
        .find(key)
        .expect("cargo metadata names the default members")
        + key.len();
    let list = &meta[at..at + meta[at..].find(']').unwrap()];
    let members: Vec<&str> = list.split(',').filter(|m| !m.is_empty()).collect();
    assert_eq!(members.len(), 1, "a bare `cargo build` selects {members:?}");
    assert!(
        members[0].contains("server/cli#holger@"),
        "a bare `cargo build` selects {members:?}"
    );
}

/// **Every crate `cargo install holger` needs can be published** (OX-VARIANTS 2026-10-28).
///
/// The README's variant table is `cargo install holger …` (crates.io), and docs/HOLGER-V2.md
/// H1 promises it from crates.io. crates.io serves only the old `holger` 0.2.1 today, and
/// `cargo publish -p holger` refuses while any path dependency of it (optional ones
/// included: every feature of the table must resolve) is `publish = false`. So: walk the
/// path dependencies of `server/cli` (normal and build, every target; dev-dependencies are
/// stripped by a publish) and require each to be publishable.
#[test]
fn every_crate_cargo_install_holger_needs_is_publishable() {
    let mut seen = BTreeSet::new();
    // The three products of this repository (the console's is held in holger-ui).
    let mut queue = vec![
        repo_root().join("server/cli"),
        repo_root().join("holger-downstream"),
        repo_root().join("holger-cli"),
    ];
    let mut bad = Vec::new();
    while let Some(dir) = queue.pop() {
        let dir = dir
            .canonicalize()
            .unwrap_or_else(|e| panic!("{}: {e}", dir.display()));
        if !seen.insert(dir.clone()) {
            continue;
        }
        let m = toml_at(&dir.join("Cargo.toml"));
        let name = m["package"]["name"].as_str().unwrap().to_string();
        let publish = m["package"].get("publish");
        let off = match publish {
            Some(toml::Value::Boolean(b)) => !b,
            Some(toml::Value::Array(a)) => a.is_empty(),
            _ => false,
        };
        if off {
            bad.push(format!(
                "{name} ({}) is `publish = false`",
                dir.strip_prefix(repo_root().canonicalize().unwrap())
                    .unwrap_or(&dir)
                    .display()
            ));
        }
        let mut tables: Vec<&toml::Value> = ["dependencies", "build-dependencies"]
            .iter()
            .filter_map(|t| m.get(*t))
            .collect();
        if let Some(targets) = m.get("target").and_then(|t| t.as_table()) {
            for t in targets.values() {
                tables.extend(
                    ["dependencies", "build-dependencies"]
                        .iter()
                        .filter_map(|k| t.get(*k)),
                );
            }
        }
        for table in tables {
            for (dep, spec) in table.as_table().unwrap() {
                if let Some(p) = spec.get("path").and_then(|p| p.as_str()) {
                    assert!(
                        spec.get("version").is_some(),
                        "{name}: path dependency `{dep}` has no version, so it cannot be published"
                    );
                    queue.push(dir.join(p));
                }
            }
        }
    }
    assert!(
        seen.len() > 10,
        "the walk found the server's crates: {seen:?}"
    );
    assert!(
        bad.is_empty(),
        "`cargo publish -p holger` is refused while these stay unpublishable:\n  {}",
        bad.join("\n  ")
    );
}

/// The crate directories of `server/cli`'s path closure (normal and build dependencies,
/// every target, optional ones included): the crates `cargo publish -p holger` needs.
fn publish_closure_dirs() -> BTreeSet<PathBuf> {
    let mut seen = BTreeSet::new();
    let mut queue = vec![repo_root().join("server/cli")];
    while let Some(dir) = queue.pop() {
        let dir = dir
            .canonicalize()
            .unwrap_or_else(|e| panic!("{}: {e}", dir.display()));
        if !seen.insert(dir.clone()) {
            continue;
        }
        let m = toml_at(&dir.join("Cargo.toml"));
        let mut tables: Vec<&toml::Value> = ["dependencies", "build-dependencies"]
            .iter()
            .filter_map(|t| m.get(*t))
            .collect();
        if let Some(targets) = m.get("target").and_then(|t| t.as_table()) {
            for t in targets.values() {
                tables.extend(
                    ["dependencies", "build-dependencies"]
                        .iter()
                        .filter_map(|k| t.get(*k)),
                );
            }
        }
        for table in tables {
            for spec in table.as_table().unwrap().values() {
                if let Some(p) = spec.get("path").and_then(|p| p.as_str()) {
                    queue.push(dir.join(p));
                }
            }
        }
    }
    seen
}

fn rs_files(dir: &Path, out: &mut Vec<PathBuf>) {
    for e in std::fs::read_dir(dir).unwrap_or_else(|e| panic!("{}: {e}", dir.display())) {
        let p = e.unwrap().path();
        if p.is_dir() {
            rs_files(&p, out);
        } else if p.extension().is_some_and(|x| x == "rs") {
            out.push(p);
        }
    }
}

/// **A published crate compiles only files inside it** (PUB-HOLGER 2026-10-28).
///
/// `cargo publish` packs a crate's own directory and verifies the package alone, so an
/// `include_str!`/`include_bytes!` that climbs out of the crate (`../../../../distribution/…`)
/// builds in the repository and fails on crates.io: `cargo publish --dry-run -p
/// holger-server-lib` refused `src/sandbox/media.rs` reading
/// `distribution/scan-worker/clamd.conf`. Every library and build-script source of the
/// publish closure is checked; test code (a `tests.rs`, a file under `src/tests/`, or any
/// line after the file's first `#[cfg(test)]`) is not compiled by the verify and is left out.
#[test]
fn every_file_a_published_crate_compiles_in_is_inside_it() {
    let mut bad = Vec::new();
    for dir in publish_closure_dirs() {
        let mut files = Vec::new();
        if dir.join("src").is_dir() {
            rs_files(&dir.join("src"), &mut files);
        }
        if dir.join("build.rs").is_file() {
            files.push(dir.join("build.rs"));
        }
        for f in files {
            let rel = f.strip_prefix(&dir).unwrap().to_path_buf();
            if rel.file_name().is_some_and(|n| n == "tests.rs") || rel.starts_with("src/tests") {
                continue;
            }
            let text = std::fs::read_to_string(&f).unwrap();
            for (n, line) in text.lines().enumerate() {
                if line.trim_start().starts_with("#[cfg(test)]") {
                    break;
                }
                for mac in ["include_str!(\"", "include_bytes!(\""] {
                    let mut rest = line;
                    while let Some(at) = rest.find(mac) {
                        let lit = &rest[at + mac.len()..];
                        let Some(end) = lit.find('"') else { break };
                        let target = f.parent().unwrap().join(&lit[..end]);
                        let inside = target
                            .canonicalize()
                            .map(|t| t.starts_with(&dir))
                            .unwrap_or(false);
                        if !inside {
                            bad.push(format!(
                                "{}:{}: {}",
                                dir.join(&rel)
                                    .strip_prefix(repo_root().canonicalize().unwrap())
                                    .unwrap_or(&f)
                                    .display(),
                                n + 1,
                                &lit[..end]
                            ));
                        }
                        rest = &lit[end..];
                    }
                }
            }
        }
    }
    assert!(
        bad.is_empty(),
        "a published crate reads a file outside itself (crates.io cannot build it):\n  {}",
        bad.join("\n  ")
    );
}