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

nordisk/holger

build-space/tests/suite_build_shape.rs

//! # Suite build-shape gate — holger's LINKAGE axis, VALIDATED against Cargo
//!
//! Loads holger's OWN on-disk `.nornir/build-variants.toml` through nornir's
//! build-thing (`nornir_build_thing::BuildVariantSet::discover`) and proves the
//! nordisk suite-build matrix holds for holger:
//!
//! * the whole descriptor `validate()`s (every variant, incl. the fat/thin
//!   asymmetry rule);
//! * every ROLE names exactly one normal case (`default = true`);
//! * every variant DECLARES its linkage rather than inheriting the `fat` default
//!   by silence;
//! * the `[build.downstream]` allow-list still encodes `holger -> [skade]`.
//!
//! RED-WHEN-BROKEN: the asymmetry rule and the thin-may-not-embed rule are still
//! proved directly, on synthetic variants, at the end.

use std::collections::BTreeSet;
use std::path::Path;

use nornir_build_thing::variant::ArtifactKind;
use nornir_build_thing::{BuildVariant, BuildVariantSet, ServiceShape};

/// Discover the repo's `.nornir/build-variants.toml` (repo root = the build-space
/// crate's parent dir) and assert the linkage/role/default axes.
#[test]
fn holger_build_variants_validate_with_fat_thin_service_shape() {
    let repo_root = Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .expect("holger repo root is the build-space crate's parent");

    let set = BuildVariantSet::discover(repo_root)
        .expect("read/parse .nornir/build-variants.toml")
        .expect("holger declares a .nornir/build-variants.toml");

    // The whole set validates — every variant AND the fat/thin asymmetry rule.
    set.validate_all()
        .expect("holger's declared build variants all validate");

    // The suite dep graph as data: holger is downstream of skade only. Kept as the
    // RULE even though no variant exercises it today (see the module docs).
    assert_eq!(
        set.downstream.get("holger"),
        Some(&BTreeSet::from(["skade".to_string()])),
        "[build.downstream] must encode holger -> [skade]"
    );

    // ── EVERY ROW NAMES ITS DELIVERY: one, MVP (lane HOL-PRODUCTS, 2026-10-30). ──
    assert!(
        set.declares_suites(),
        "every atom must name its delivery — suite is a COLUMN, not an optional extra"
    );
    assert_eq!(set.suites(), vec!["MVP"], "holger ships one product list");
    assert!(set.check_suites().is_empty(), "{:?}", set.check_suites());
    assert!(
        set.base_suite.is_none(),
        "one suite has no base to carve it out of"
    );

    // A row built from a sibling checkout (`manifest_dir` outside this repo: the console,
    // `holger-ui`) is held in its own repository; the checks below walk THIS repo.
    let here = |v: &BuildVariant| {
        !v.manifest_dir
            .as_deref()
            .is_some_and(|d| d.starts_with("../"))
    };

    // ── EVERY ROW RESOLVES. ──
    // Every row that compiles something must name a package cargo knows and features
    // that package actually declares; a row naming a feature that does not exist is a
    // delivery that cannot build.
    let tables = BuildVariantSet::cargo_feature_tables(repo_root)
        .expect("read the workspace's [features] tables");
    assert!(
        tables.contains_key("holger") && tables.contains_key("holger-downstream") && tables.contains_key("holger-cli"),
        "the product packages must resolve, or the check below is checking nothing; got {} packages",
        tables.len()
    );
    let mut unresolvable: Vec<String> = Vec::new();
    for v in set.variants.iter().filter(|v| here(v)) {
        let Some(pkg) = v.package.as_deref() else {
            continue;
        }; // delegating row: no cargo argv
        let Some(table) = tables.get(pkg) else {
            unresolvable.push(format!(
                "{}: package '{}' is not in the workspace",
                v.name, pkg
            ));
            continue;
        };
        for f in &v.features {
            // `dep:serde` and `egui/serde` are opaque tokens cargo resolves
            // elsewhere — they are never keys in this package's own table.
            if f.contains('/') || f.starts_with("dep:") {
                continue;
            }
            if !table.features.contains_key(f.as_str()) {
                unresolvable.push(format!(
                    "{}: feature '{}' is not declared by '{}'",
                    v.name, f, pkg
                ));
            }
        }
    }
    assert!(
        unresolvable.is_empty(),
        "rows that cannot build:\n{}",
        unresolvable.join("\n")
    );

    // ── EVERY DECLARED BIN IS A BIN CARGO ACTUALLY BUILDS. ──
    // The roster comes from WALKING THE MANIFESTS, never from a list in this test.
    let real = BuildVariantSet::cargo_bin_targets(repo_root).expect("walk holger's manifests");
    assert!(
        ["holger", "holger-downstream", "holger-cli"]
            .iter()
            .all(|b| real.iter().any(|r| r == b)),
        "the manifest walk must actually find holger's bins, or the check below compares \
         against an empty roster; got {} bins",
        real.len()
    );
    let mut local = set.clone();
    local.variants.retain(|v| here(v));
    let undeclared = local.check_bins_declared(&real);
    assert!(undeclared.is_empty(), "{}", undeclared.join("\n"));

    // ── NO TWO ROWS WRITE THE SAME FILE. ──
    let collisions = set.check_bins();
    assert!(collisions.is_empty(), "{collisions:?}");

    // ── Every (suite, role) group names exactly one normal case. ──
    assert!(
        set.check_defaults().is_empty(),
        "{:?}",
        set.check_defaults()
    );
    for (role, want) in [
        (nornir_build_thing::plugin::Role::Server, "holger"),
        (nornir_build_thing::plugin::Role::Client, "holger-cli"),
    ] {
        assert_eq!(
            set.default_in("MVP", Some(role)).map(|v| v.name.as_str()),
            Some(want),
            "(MVP, {role:?}) must name {want}"
        );
        assert_eq!(
            set.default_for_role(Some(role)).map(|v| v.name.as_str()),
            Some(want),
            "{role:?}"
        );
    }

    // ── The server rows are all fat: each owns its own state and needs no nordisk
    // sibling running. ──
    for row in [
        "holger",
        "holger-downstream",
        "mvp-server-musl",
        "mvp-server-metered",
    ] {
        let v = set
            .get(row)
            .unwrap_or_else(|| panic!("{row} variant present"));
        assert_eq!(
            v.shape,
            ServiceShape::FatService,
            "{row}: a holger server is a state owner, not a client of one"
        );
        assert!(
            v.embeds.is_empty(),
            "{row}: holger depends on no skade to embed"
        );
        assert!(
            v.reaches.is_empty(),
            "{row}: no nordisk owner needs to be running"
        );
    }

    // ── The product rows are their crates' `cargo install`: no feature list of their
    // own, so the crate's `default` is the one writer of what they carry. ──
    for row in ["holger", "holger-downstream", "holger-cli", "holger-ui"] {
        let v = set
            .get(row)
            .unwrap_or_else(|| panic!("{row} variant present"));
        assert_eq!(v.package.as_deref(), Some(row));
        assert_eq!(
            v.kind,
            ArtifactKind::Exe,
            "{row}: what `cargo install` leaves in ~/.cargo/bin"
        );
        assert!(
            v.features.is_empty() && !v.no_default_features,
            "{row} must inherit its crate's `default`, not restate it; got features={:?} no_default_features={}",
            v.features,
            v.no_default_features
        );
    }

    // ── The genuinely thin artifacts: the command-line tool and the console. Neither
    // links server-lib; every server verb goes over the wire. ──
    for row in ["holger-cli", "holger-ui"] {
        let v = set.get(row).unwrap();
        assert_eq!(v.shape, ServiceShape::ThinStandalone, "{row}");
        assert!(v.embeds.is_empty(), "{row}: thin compiles in no siblings");
        assert_eq!(
            v.reaches,
            BTreeSet::from(["holger".to_string()]),
            "{row} needs a holger running"
        );
        assert_eq!(
            v.role,
            Some(nornir_build_thing::plugin::Role::Client),
            "{row}"
        );
    }

    // ── THE egui UI TIERS ARE holger-egui's. A `ui*` row reappearing here would mean
    // the client came back WITHOUT the rules that judged it. ──
    let ui_rows: Vec<&str> = set
        .variants
        .iter()
        .map(|v| v.name.as_str())
        .filter(|n| *n == "ui" || n.starts_with("ui-"))
        .collect();
    assert!(
        ui_rows.is_empty(),
        "an egui client tier is declared here again ({ui_rows:?}); declare it in holger-egui"
    );

    // ── RED-WHEN-BROKEN proof #1: a fat holger embedding a NON-downstream member
    // (nornir is upstream/peer, absent from holger's allow-list) inverts the dep
    // graph and MUST be rejected. ──
    let inverted = BuildVariant {
        name: "holger-inverted".to_string(),
        member: Some("holger".to_string()),
        kind: ArtifactKind::Service,
        shape: ServiceShape::FatService,
        embeds: BTreeSet::from(["nornir".to_string()]),
        ..Default::default()
    };
    let err = set
        .validate(&inverted)
        .expect_err("embedding a non-downstream member must be rejected");
    assert!(
        err.to_string().contains("may not embed 'nornir'"),
        "asymmetry rule explains the rejection: {err}"
    );

    // ── RED-WHEN-BROKEN proof #2: a THIN variant that embeds anything is
    // incoherent (thin compiles in nothing) and MUST be rejected. ──
    let thin_embeds = BuildVariant {
        name: "holger-thin-but-embeds".to_string(),
        member: Some("holger".to_string()),
        kind: ArtifactKind::Service,
        shape: ServiceShape::ThinStandalone,
        embeds: BTreeSet::from(["skade".to_string()]),
        ..Default::default()
    };
    set.validate(&thin_embeds)
        .expect_err("a thin/standalone variant may not embed siblings");
}

/// **R8 — the shape-parity guard**: the metal cell and the container cell
/// start the product with the SAME argv tail. This is the test that stops the
/// two shapes drifting apart again — the measured drift (2026-08-14, the
/// fleet first-boot proof): the container declared
/// `cmd = ["start", "--config", "/data/holger.ron"]` and served, the metal
/// shape had no cmd at all, exec'd the bare entry name, printed help, and
/// PID 1 died. One product, one start incantation, both shapes.
#[test]
fn the_metal_and_container_shapes_start_the_product_identically() {
    let set = holger_build_space::build_variant_set().expect("build space loads");
    let metal = set
        .installers
        .iter()
        .find(|i| i.name == "holger-installer")
        .expect("the declared installer cell");
    let container = set
        .containers
        .iter()
        .find(|c| c.name == "holger-mvp-appliance")
        .expect("the declared container cell");
    assert!(
        !metal.cmd.is_empty(),
        "the metal shape DECLARES how the product starts"
    );
    assert_eq!(
        metal.cmd, container.cmd,
        "one product, one start incantation — a difference here is the drift \
         the fleet proof measured as a kernel panic"
    );
    // The GATE travels both shapes: the container's env and the metal's env
    // both declare HOLGER_APPLIANCE=1 — the measured drift (2026-08-14) was
    // the served metal appliance running with its tunnel dark because the
    // env had no channel at all.
    assert_eq!(
        metal.env.get("HOLGER_APPLIANCE").map(String::as_str),
        Some("1"),
        "the metal shape declares the ceremony gate"
    );
    assert_eq!(
        container.env.get("HOLGER_APPLIANCE").map(String::as_str),
        Some("1"),
        "the container shape declares the ceremony gate"
    );
    // And the declared tail actually rides the boot token.
    assert!(
        metal
            .effective_boot_params()
            .iter()
            .any(|p| p == "korp.args=start,--config,/data/holger.ron"),
        "the tail rides the installed cmdline as korp.args"
    );
}