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

nordisk/edda

crates/nornir-build-thing/src/measure/tests.rs

//! The measure step against a FAKE `heimdall` (a scripted [`ProfileTool`]):
//! what Build Thing writes beside the row, what it hands the tool, how it
//! reads the tool's JSON back, and how a drift is named. The real tool runs
//! in the lane's measured run (privileged rootless podman, USER_NOTIF).

use std::cell::RefCell;
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};

use super::*;

const PIN_VERSION: &str = "0.2.0";

/// One recorded call: (args, extra env).
type Call = (Vec<String>, Vec<(String, String)>);

/// A scripted `heimdall`: it records every call and writes what the real
/// tool writes (probe → `.probe.json`; gen → `.rs`, `.json`, `.oci.json`).
struct FakeTool {
    path: PathBuf,
    version: String,
    calls: RefCell<Vec<Call>>,
    /// The calls each probe "sees" per program.
    seen: BTreeMap<String, Vec<&'static str>>,
    /// What `check` answers: (exit code, stderr).
    check: (i32, String),
}

impl FakeTool {
    fn new(dir: &Path) -> FakeTool {
        let path = dir.join("fake-heimdall");
        std::fs::write(&path, b"#!/bin/false\nfake heimdall\n").unwrap();
        FakeTool {
            path,
            version: PIN_VERSION.into(),
            calls: RefCell::new(Vec::new()),
            seen: BTreeMap::new(),
            check: (0, String::new()),
        }
    }

    fn sha(&self) -> String {
        nornir_hash::sha256_hex(&std::fs::read(&self.path).unwrap())
    }

    fn verbs(&self) -> Vec<String> {
        self.calls
            .borrow()
            .iter()
            .map(|(a, _)| a.iter().take(3).cloned().collect::<Vec<_>>().join(" "))
            .collect()
    }
}

fn arg_after(args: &[String], flag: &str) -> String {
    let i = args
        .iter()
        .position(|a| a == flag)
        .unwrap_or_else(|| panic!("{flag} in {args:?}"));
    args[i + 1].clone()
}

impl ProfileTool for FakeTool {
    fn path(&self) -> &Path {
        &self.path
    }

    fn call(&self, args: &[String], env: &[(String, String)]) -> anyhow::Result<ToolOutput> {
        self.calls.borrow_mut().push((args.to_vec(), env.to_vec()));
        if args == ["--version"] {
            return Ok(ToolOutput {
                code: 0,
                stdout: format!("heimdall {}\n", self.version),
                stderr: String::new(),
            });
        }
        assert_eq!(args[0], "profile", "{args:?}");
        let prog = args[2].clone();
        let dir = PathBuf::from(arg_after(args, "--profiles")).join(&prog);
        let libc = arg_after(args, "--libc");
        // The real tool refuses a workload.toml it cannot read: so must this one.
        let w: WorkloadToml =
            toml::from_str(&std::fs::read_to_string(dir.join("workload.toml")).unwrap()).unwrap();
        assert_eq!(w.workload, prog);
        let arch = std::env::consts::ARCH;
        let base = dir.join(format!("{arch}-{libc}"));
        match args[1].as_str() {
            "probe" => {
                let runs: Vec<_> = w
                    .run
                    .iter()
                    .map(|r| serde_json::json!({"name": r.name, "error_path": r.error_path, "exit": "exit 0"}))
                    .collect();
                let names: Vec<_> = w.run.iter().map(|r| r.name.clone()).collect();
                let calls: Vec<_> = self.seen[&prog]
                    .iter()
                    .map(|c| serde_json::json!({"syscall": c, "runs": names}))
                    .collect();
                let j = serde_json::json!({"workload": prog, "arch": arch, "libc": libc, "runs": runs, "calls": calls});
                std::fs::write(
                    base.with_extension("probe.json"),
                    serde_json::to_string_pretty(&j).unwrap(),
                )
                .unwrap();
                Ok(ToolOutput {
                    code: 0,
                    stdout: "probe: ok\n".into(),
                    stderr: String::new(),
                })
            }
            "gen" => {
                let rules: Vec<_> = self.seen[&prog]
                    .iter()
                    .map(|c| serde_json::json!({"syscall": c, "action": "allow"}))
                    .collect();
                let j = serde_json::json!({
                    "workload": prog, "arch": arch, "libc": libc, "rules": rules,
                    "review": {"runs": [], "near_miss": [{"call": "rt_sigreturn", "runs": ["x"]}], "wx": [], "bare": ["ioctl"], "unknown": [], "fixed": [], "notes": []}
                });
                std::fs::write(
                    base.with_extension("json"),
                    serde_json::to_string_pretty(&j).unwrap(),
                )
                .unwrap();
                std::fs::write(base.with_extension("rs"), "// generated\n").unwrap();
                std::fs::write(base.with_extension("oci.json"), "{}\n").unwrap();
                Ok(ToolOutput {
                    code: 0,
                    stdout: "gen: ok\n".into(),
                    stderr: String::new(),
                })
            }
            "check" => Ok(ToolOutput {
                code: self.check.0,
                stdout: String::new(),
                stderr: self.check.1.clone(),
            }),
            other => panic!("unexpected verb {other}"),
        }
    }
}

/// A repo with `.nornir/profiles/node/measure.toml`, the binaries built
/// under `target/`, and a fake tool pinned by its real sha256.
fn repo(manifest_extra: &str) -> (tempfile::TempDir, FakeTool) {
    let d = tempfile::tempdir().unwrap();
    let tool = FakeTool::new(d.path());
    let bin = d.path().join("target/x86_64-unknown-linux-musl/release");
    std::fs::create_dir_all(&bin).unwrap();
    std::fs::write(bin.join("node-aya"), b"\x7fELF fake").unwrap();
    std::fs::write(bin.join("node"), b"\x7fELF fake").unwrap();
    let row = d.path().join(".nornir/profiles/node");
    std::fs::create_dir_all(&row).unwrap();
    std::fs::write(
        row.join("measure.toml"),
        format!(
            r#"row = "node"

[tool]
version = "{PIN_VERSION}"
sha256 = "{sha}"

[[program]]
name = "tunnr-cni"
about = "the CNI plugin: the aya PID 1 under argv0 tunnr-cni"
binary = {{ musl = "{{target}}/x86_64-unknown-linux-musl/release/node-aya" }}
install_as = "tunnr-cni"
later = ["ADD success: the BPF attach to the pinned programs"]
[program.env]
RUST_BACKTRACE = "0"
[[program.run]]
name = "version"
env = {{ CNI_COMMAND = "VERSION" }}
stdin = "{{dir}}/corpus/conf.json"
expect_exit = 0
[[program.run]]
name = "add_without_netns"
error_path = true
env = {{ CNI_COMMAND = "ADD" }}
expect_exit = 1

[[program]]
name = "pid1"
binary = {{ musl = "{{target}}/x86_64-unknown-linux-musl/release/node" }}
repeat = 2
[[program.run]]
name = "not_pid_1"
error_path = true
expect_exit = 78

[[program]]
name = "rusternetes"
needs_vm = "runs only as the node's child, after PID 1 laid the archive (KVM proof)"
{manifest_extra}"#,
            sha = tool.sha()
        ),
    )
    .unwrap();
    let mut tool = tool;
    tool.seen
        .insert("tunnr-cni".into(), vec!["read", "write", "socket"]);
    tool.seen.insert("pid1".into(), vec!["write", "exit_group"]);
    (d, tool)
}

/// [`repo`], measured once (the profiles exist), the call log cleared.
fn measured(extra: &str) -> (tempfile::TempDir, FakeTool) {
    let (d, tool) = repo(extra);
    run_measure_verb(&vt("syscalls node", d.path()), d.path(), &tool).unwrap();
    tool.calls.borrow_mut().clear();
    (d, tool)
}

fn verb(args: &str) -> MeasureVerb {
    parse_measure_verb(
        &args
            .split_whitespace()
            .map(String::from)
            .collect::<Vec<_>>(),
    )
    .unwrap()
}

/// The verb with `--target <repo>/target` (the test process's own
/// `CARGO_TARGET_DIR` must not leak in).
fn vt(args: &str, root: &Path) -> MeasureVerb {
    verb(&format!(
        "{args} --target {}",
        root.join("target").display()
    ))
}

// ── the verb ────────────────────────────────────────────────────────────────

#[test]
fn the_verb_parses_row_check_program_and_libc() {
    let v = verb(
        "syscalls rusternetes-node --check --program tunnr-cni --program pid1 --libc musl --target /t",
    );
    assert_eq!(v.row, "rusternetes-node");
    assert!(v.check);
    assert_eq!(v.programs, ["tunnr-cni", "pid1"]);
    assert_eq!(v.libcs, ["musl"]);
    assert_eq!(v.target, Some(PathBuf::from("/t")));
    let plain = verb("syscalls node");
    assert!(!plain.check && plain.programs.is_empty() && plain.libcs.is_empty());
}

#[test]
fn the_verb_refuses_what_it_does_not_measure() {
    let p =
        |s: &str| parse_measure_verb(&s.split_whitespace().map(String::from).collect::<Vec<_>>());
    let e = p("coverage node").unwrap_err().to_string();
    assert!(e.contains("coverage") && e.contains("syscalls"), "{e}");
    assert!(p("syscalls").is_err());
    assert!(p("syscalls --check").is_err());
    assert!(
        p("syscalls node --libc uclibc")
            .unwrap_err()
            .to_string()
            .contains("uclibc")
    );
    assert!(
        p("syscalls node --frobnicate")
            .unwrap_err()
            .to_string()
            .contains("--frobnicate")
    );
}

// ── the manifest ────────────────────────────────────────────────────────────

#[test]
fn a_row_without_a_measure_manifest_is_refused_by_path() {
    let d = tempfile::tempdir().unwrap();
    let e = MeasureManifest::load(d.path(), "node")
        .unwrap_err()
        .to_string();
    assert!(e.contains(".nornir/profiles/node/measure.toml"), "{e}");
}

#[test]
fn the_manifest_refuses_bad_names_duplicate_runs_and_empty_programs() {
    let bad = |extra: &str| {
        let (d, _t) = repo(extra);
        MeasureManifest::load(d.path(), "node")
            .unwrap_err()
            .to_string()
    };
    let e = bad("[[program]]\nname = \"../evil\"\n[[program.run]]\nname = \"x\"\n");
    assert!(e.contains("../evil"), "{e}");
    let e = bad(
        "[[program]]\nname = \"dup\"\nbinary = { musl = \"/x\" }\n[[program.run]]\nname = \"a\"\n[[program.run]]\nname = \"a\"\n",
    );
    assert!(e.contains("dup") && e.contains("\"a\""), "{e}");
    let e = bad("[[program]]\nname = \"empty\"\nbinary = { musl = \"/x\" }\n");
    assert!(e.contains("empty") && e.contains("needs_vm"), "{e}");
    let e = bad("[[program]]\nname = \"pid1\"\nneeds_vm = \"twice\"\n");
    assert!(e.contains("pid1") && e.contains("twice"), "{e}");
    let (d, _t) = repo("");
    std::fs::write(
        d.path().join(".nornir/profiles/node/measure.toml"),
        "row = \"other\"\n[tool]\nversion = \"0.2.0\"\nsha256 = \"00\"\n",
    )
    .unwrap();
    let e = MeasureManifest::load(d.path(), "node")
        .unwrap_err()
        .to_string();
    assert!(e.contains("other") && e.contains("node"), "{e}");
}

#[test]
fn the_workload_toml_is_heimdalls_format_with_the_target_placeholder_kept() {
    let (d, _t) = repo("");
    let m = MeasureManifest::load(d.path(), "node").unwrap();
    let p = &m.programs[0];
    let text = workload_toml(&m, p).unwrap();
    assert!(text.starts_with("# GENERATED by Build Thing"), "{text}");
    assert!(text.contains("measure.toml"), "{text}");
    let w: WorkloadToml = toml::from_str(&text).unwrap();
    assert_eq!(w.workload, "tunnr-cni");
    // install_as: the tool runs the staged copy, under its argv0 name.
    assert_eq!(
        w.binary["musl"],
        "{target}/heimdall-measure/node/musl/tunnr-cni"
    );
    assert_eq!(w.run.len(), 2);
    assert_eq!(w.run[0].env["CNI_COMMAND"], "VERSION");
    assert_eq!(w.run[0].stdin.as_deref(), Some("{dir}/corpus/conf.json"));
    assert!(w.run[1].error_path);
    assert_eq!(w.env["RUST_BACKTRACE"], "0");
    // Deterministic: the same manifest, the same bytes.
    assert_eq!(text, workload_toml(&m, p).unwrap());
    let pid1 = workload_toml(&m, &m.programs[1]).unwrap();
    let w: WorkloadToml = toml::from_str(&pid1).unwrap();
    assert_eq!(
        w.binary["musl"],
        "{target}/x86_64-unknown-linux-musl/release/node"
    );
    assert_eq!(w.repeat, Some(2));
}

// ── the tool: located by name, pinned by version and sha256 ─────────────────

#[test]
fn a_missing_tool_is_refused_by_name_with_the_install_line() {
    let pin = ToolPin {
        version: PIN_VERSION.into(),
        sha256: "00".into(),
    };
    let e = locate_in(None, Some("/nonexistent-a:/nonexistent-b".into()), &pin)
        .unwrap_err()
        .to_string();
    for w in [
        "heimdall",
        "nordisk-heimdall-cli",
        PIN_VERSION,
        "cargo install",
        "HEIMDALL_TOOL",
    ] {
        assert!(e.contains(w), "{w} missing from: {e}");
    }
    let e = locate_in(Some("/nonexistent/heimdall".into()), None, &pin)
        .unwrap_err()
        .to_string();
    assert!(
        e.contains("/nonexistent/heimdall") && e.contains("HEIMDALL_TOOL"),
        "{e}"
    );
}

#[test]
fn the_tool_is_found_on_path_or_by_heimdall_tool() {
    let d = tempfile::tempdir().unwrap();
    let pin = ToolPin {
        version: PIN_VERSION.into(),
        sha256: "00".into(),
    };
    let bin = d.path().join("bin");
    std::fs::create_dir_all(&bin).unwrap();
    std::fs::write(bin.join("heimdall"), b"x").unwrap();
    let path = format!("/nonexistent:{}", bin.display());
    assert_eq!(
        locate_in(None, Some(path.into()), &pin).unwrap().path(),
        bin.join("heimdall")
    );
    let other = d.path().join("h2");
    std::fs::write(&other, b"y").unwrap();
    assert_eq!(
        locate_in(Some(other.clone().into()), None, &pin)
            .unwrap()
            .path(),
        other
    );
}

#[test]
fn the_pin_refuses_another_version_and_another_build_by_name() {
    let d = tempfile::tempdir().unwrap();
    let mut t = FakeTool::new(d.path());
    let good = ToolPin {
        version: PIN_VERSION.into(),
        sha256: t.sha(),
    };
    let id = verify_pin(&t, &good).unwrap();
    assert_eq!(id.version, PIN_VERSION);
    assert_eq!(id.sha256, t.sha());
    let other_sha = ToolPin {
        version: PIN_VERSION.into(),
        sha256: "ab".repeat(32),
    };
    let e = verify_pin(&t, &other_sha).unwrap_err().to_string();
    assert!(e.contains(&"ab".repeat(32)) && e.contains(&t.sha()), "{e}");
    t.version = "0.1.0".into();
    let e = verify_pin(&t, &good).unwrap_err().to_string();
    assert!(
        e.contains("0.1.0") && e.contains(PIN_VERSION) && e.contains("nordisk-heimdall-cli"),
        "{e}"
    );
}

// ── measure ─────────────────────────────────────────────────────────────────

#[test]
fn measure_probes_and_generates_every_program_and_writes_the_evidence() {
    let (d, tool) = repo("");
    let r = run_measure_verb(&vt("syscalls node", d.path()), d.path(), &tool).unwrap();
    assert!(r.ok(), "{}", r.render());
    let arch = std::env::consts::ARCH;
    // The tool was asked probe then gen, per program, with the libc and the
    // row's profiles dir; it never saw the VM-only program.
    assert_eq!(
        tool.verbs(),
        [
            "--version",
            "profile probe tunnr-cni",
            "profile gen tunnr-cni",
            "profile probe pid1",
            "profile gen pid1"
        ]
    );
    let calls = tool.calls.borrow();
    let (args, env) = &calls[1];
    assert_eq!(arg_after(args, "--libc"), "musl");
    assert_eq!(
        PathBuf::from(arg_after(args, "--profiles")),
        d.path().join(".nornir/profiles/node")
    );
    assert!(
        env.contains(&(
            "CARGO_TARGET_DIR".into(),
            d.path().join("target").display().to_string()
        )),
        "{env:?}"
    );
    drop(calls);
    // Beside the row: workload.toml + the four files of each program.
    let row = d.path().join(".nornir/profiles/node");
    for p in ["tunnr-cni", "pid1"] {
        for f in [
            "workload.toml".to_string(),
            format!("{arch}-musl.probe.json"),
            format!("{arch}-musl.json"),
            format!("{arch}-musl.rs"),
            format!("{arch}-musl.oci.json"),
        ] {
            assert!(row.join(p).join(&f).is_file(), "{p}/{f}");
        }
    }
    assert!(!row.join("rusternetes").exists());
    // install_as: the staged copy carries the argv0 name.
    assert!(
        d.path()
            .join("target/heimdall-measure/node/musl/tunnr-cni")
            .is_file()
    );
    // The counts are read back from the tool's JSON.
    let cni = r
        .programs
        .iter()
        .find(|p| p.program == "tunnr-cni")
        .unwrap();
    assert_eq!(
        (cni.runs, cni.calls, cni.rules, cni.near_miss, cni.bare),
        (2, 3, 3, 1, 1)
    );
    assert_eq!(cni.libc, "musl");
    assert_eq!(
        r.vm_later,
        [
            (
                "tunnr-cni".to_string(),
                "not covered yet: ADD success: the BPF attach to the pinned programs".to_string()
            ),
            (
                "rusternetes".to_string(),
                "runs only as the node's child, after PID 1 laid the archive (KVM proof)"
                    .to_string()
            )
        ]
    );
    // The evidence file: tool pin + counts + what waits for a VM; no dates.
    let ev: serde_json::Value =
        serde_json::from_str(&std::fs::read_to_string(row.join("measure.json")).unwrap()).unwrap();
    assert_eq!(ev["tool"]["crate"], "nordisk-heimdall-cli");
    assert_eq!(ev["tool"]["version"], PIN_VERSION);
    assert_eq!(ev["tool"]["sha256"], tool.sha());
    assert_eq!(ev["programs"][0]["program"], "tunnr-cni");
    assert_eq!(ev["programs"][0]["calls"], 3);
    assert_eq!(ev["vm_later"][1]["program"], "rusternetes");
    let text = r.render();
    // Usable output: what was probed, which calls, the near-miss, where the
    // profile went, how to check it, what waits for a VM.
    for w in [
        "tunnr-cni (",
        "version, add_without_netns",
        "calls (3): read socket write",
        "rt_sigreturn (by x)",
        &format!(".nornir/profiles/node/tunnr-cni/{arch}-musl.json"),
        "measure syscalls node --check",
        "rusternetes: not probed, needs a VM",
    ] {
        assert!(text.contains(w), "{w:?} missing from:\n{text}");
    }
}

#[test]
fn measure_filters_by_program_and_refuses_an_unknown_one() {
    let (d, tool) = repo("");
    run_measure_verb(
        &vt("syscalls node --program pid1", d.path()),
        d.path(),
        &tool,
    )
    .unwrap();
    assert_eq!(
        tool.verbs(),
        ["--version", "profile probe pid1", "profile gen pid1"]
    );
    let e = run_measure_verb(
        &vt("syscalls node --program nope", d.path()),
        d.path(),
        &tool,
    )
    .unwrap_err()
    .to_string();
    assert!(e.contains("nope") && e.contains("tunnr-cni"), "{e}");
}

#[test]
fn an_unbuilt_binary_is_refused_naming_the_program_and_the_path() {
    let (d, tool) = repo("");
    std::fs::remove_file(
        d.path()
            .join("target/x86_64-unknown-linux-musl/release/node"),
    )
    .unwrap();
    let e = run_measure_verb(
        &vt("syscalls node --program pid1", d.path()),
        d.path(),
        &tool,
    )
    .unwrap_err()
    .to_string();
    assert!(e.contains("pid1") && e.contains("release/node"), "{e}");
}

#[test]
fn a_pin_mismatch_stops_before_any_probe() {
    let (d, mut tool) = repo("");
    tool.version = "0.1.0".into();
    assert!(run_measure_verb(&vt("syscalls node", d.path()), d.path(), &tool).is_err());
    assert_eq!(tool.verbs(), ["--version"]);
}

#[test]
fn a_failing_probe_is_refused_with_the_tools_own_words() {
    struct Failing(FakeTool);
    impl ProfileTool for Failing {
        fn path(&self) -> &Path {
            self.0.path()
        }
        fn call(&self, args: &[String], env: &[(String, String)]) -> anyhow::Result<ToolOutput> {
            if args.get(1).map(String::as_str) == Some("probe") {
                return Ok(ToolOutput {
                    code: 3,
                    stdout: String::new(),
                    stderr:
                        "heimdall profile probe: probe run version: expected exit 0, got exit 1\n"
                            .into(),
                });
            }
            self.0.call(args, env)
        }
    }
    let (d, tool) = repo("");
    let e = run_measure_verb(&vt("syscalls node", d.path()), d.path(), &Failing(tool))
        .unwrap_err()
        .to_string();
    assert!(
        e.contains("tunnr-cni") && e.contains("expected exit 0, got exit 1"),
        "{e}"
    );
}

// ── check ───────────────────────────────────────────────────────────────────

#[test]
fn check_runs_heimdall_check_per_program_and_is_ok_when_it_is() {
    let (d, tool) = measured("");
    let r = run_measure_verb(&vt("syscalls node --check", d.path()), d.path(), &tool).unwrap();
    assert!(r.ok(), "{}", r.render());
    assert_eq!(
        tool.verbs(),
        ["--version", "profile check tunnr-cni", "profile check pid1"]
    );
    // check writes nothing.
    let ev = d.path().join(".nornir/profiles/node/measure.json");
    let before = std::fs::metadata(&ev).unwrap().modified().unwrap();
    run_measure_verb(&vt("syscalls node --check", d.path()), d.path(), &tool).unwrap();
    assert_eq!(std::fs::metadata(&ev).unwrap().modified().unwrap(), before);
}

#[test]
fn check_before_any_measure_is_refused_with_the_measure_line() {
    let (d, tool) = repo("");
    let e = run_measure_verb(&vt("syscalls node --check", d.path()), d.path(), &tool)
        .unwrap_err()
        .to_string();
    assert!(
        e.contains("tunnr-cni") && e.contains("measure syscalls node"),
        "{e}"
    );
}

#[test]
fn check_names_a_workload_toml_that_no_longer_matches_measure_toml() {
    let (d, tool) = measured("");
    let m = d.path().join(".nornir/profiles/node/measure.toml");
    let t = std::fs::read_to_string(&m)
        .unwrap()
        .replace("expect_exit = 78", "expect_exit = 77");
    std::fs::write(&m, t).unwrap();
    let r = run_measure_verb(
        &vt("syscalls node --check --program pid1", d.path()),
        d.path(),
        &tool,
    )
    .unwrap();
    assert!(!r.ok());
    assert_eq!(
        r.out_of_sync,
        [(
            "pid1".to_string(),
            ".nornir/profiles/node/pid1/workload.toml".to_string()
        )]
    );
}

#[test]
fn check_fails_on_drift_naming_the_call_and_the_test() {
    let (d, mut tool) = measured("");
    tool.check = (
        1,
        "heimdall profile check: new call sysinfo (kill_process), made by test version\n\
         heimdall profile check: new call openat arg2=0x241 (kill_process), made by test add_without_netns, version\n\
         fix: heimdall profile probe tunnr-cni && heimdall profile gen tunnr-cni, then review the diff (never widen a profile by hand)\n"
            .into(),
    );
    let r = run_measure_verb(
        &vt("syscalls node --check --program tunnr-cni", d.path()),
        d.path(),
        &tool,
    )
    .unwrap();
    assert!(!r.ok());
    assert_eq!(r.drift.len(), 2);
    assert_eq!(r.drift[0].call, "sysinfo");
    assert_eq!(r.drift[0].tests, ["version"]);
    assert_eq!(r.drift[1].call, "openat arg2=0x241");
    assert_eq!(r.drift[1].tests, ["add_without_netns", "version"]);
    let text = r.render();
    assert!(
        text.contains("DRIFT")
            && text.contains("tunnr-cni")
            && text.contains("new call sysinfo")
            && text.contains("made by test version"),
        "{text}"
    );
}

#[test]
fn check_reports_a_hand_edited_profile() {
    let (d, mut tool) = measured("");
    tool.check = (1, "heimdall profile check: /r/x86_64-musl.oci.json does not match /r/x86_64-musl.json (hand-edited or stale)\n".into());
    let r = run_measure_verb(
        &vt("syscalls node --check --program pid1", d.path()),
        d.path(),
        &tool,
    )
    .unwrap();
    assert!(!r.ok());
    assert_eq!(
        r.out_of_sync,
        [("pid1".to_string(), "/r/x86_64-musl.oci.json".to_string())]
    );
}

#[test]
fn check_refuses_when_the_tool_itself_fails() {
    let (d, mut tool) = measured("");
    tool.check = (
        3,
        "heimdall profile check: no such file x86_64-musl.json\n".into(),
    );
    let e = run_measure_verb(
        &vt("syscalls node --check --program pid1", d.path()),
        d.path(),
        &tool,
    )
    .unwrap_err()
    .to_string();
    assert!(e.contains("pid1") && e.contains("no such file"), "{e}");
}

#[test]
fn the_check_stderr_parser_reads_heimdalls_lines_exactly() {
    let (drift, oos) = parse_check_stderr(
        "p",
        "musl",
        "heimdall profile check: new call clone arg0=0x50f00 (kill_process), made by test a, b\n\
         heimdall profile check: new call nr 451 (kill_process), made by test c\n\
         something else\n",
    );
    assert_eq!(drift.len(), 2);
    assert_eq!(
        (drift[1].call.as_str(), drift[1].tests.as_slice()),
        ("nr 451", &["c".to_string()][..])
    );
    assert_eq!(drift[0].call, "clone arg0=0x50f00");
    assert_eq!(drift[0].action, "kill_process");
    assert_eq!(drift[0].tests, ["a", "b"]);
    assert!(oos.is_empty());
}

#[test]
fn a_row_without_the_rust_table_keeps_only_json_and_its_check_does_not_ask_for_it() {
    let (d, mut tool) = repo("");
    let m = d.path().join(".nornir/profiles/node/measure.toml");
    let t =
        std::fs::read_to_string(&m)
            .unwrap()
            .replacen("[tool]", "rust_table = false\n\n[tool]", 1);
    std::fs::write(&m, t).unwrap();
    let r = run_measure_verb(
        &vt("syscalls node --program pid1", d.path()),
        d.path(),
        &tool,
    )
    .unwrap();
    let arch = std::env::consts::ARCH;
    let base = d
        .path()
        .join(format!(".nornir/profiles/node/pid1/{arch}-musl"));
    assert!(base.with_extension("json").is_file() && base.with_extension("oci.json").is_file());
    assert!(!base.with_extension("rs").exists());
    assert!(!r.render().contains(", .rs"), "{}", r.render());
    // heimdall's check names the missing .rs as out of sync: not drift here.
    let rs = base.with_extension("rs").display().to_string();
    let json = base.with_extension("json").display().to_string();
    tool.check = (
        1,
        format!("heimdall profile check: {rs} does not match {json} (hand-edited or stale)\n"),
    );
    let r = run_measure_verb(
        &vt("syscalls node --check --program pid1", d.path()),
        d.path(),
        &tool,
    )
    .unwrap();
    assert!(r.ok(), "{}", r.render());
    // ...but a hand-edited .oci.json still is.
    let oci = base.with_extension("oci.json").display().to_string();
    tool.check = (
        1,
        format!(
            "heimdall profile check: {rs} does not match {json} (hand-edited or stale)\nheimdall profile check: {oci} does not match {json} (hand-edited or stale)\n"
        ),
    );
    let r = run_measure_verb(
        &vt("syscalls node --check --program pid1", d.path()),
        d.path(),
        &tool,
    )
    .unwrap();
    assert_eq!(r.out_of_sync, [("pid1".to_string(), oci)]);
}

// ── the drift gate (`measure gate`, Heimdall tooling plan step 6) ───────────

/// A second row `edge` beside `node`, same tool, one program.
fn add_row(root: &Path, tool: &FakeTool, row: &str) {
    let dir = root.join(PROFILES_DIR).join(row);
    std::fs::create_dir_all(&dir).unwrap();
    std::fs::write(
        dir.join(MANIFEST),
        format!(
            r#"row = "{row}"

[tool]
version = "{PIN_VERSION}"
sha256 = "{sha}"

[[program]]
name = "pid1"
binary = {{ musl = "{{target}}/x86_64-unknown-linux-musl/release/node" }}
[[program.run]]
name = "not_pid_1"
error_path = true
expect_exit = 78
"#,
            sha = tool.sha()
        ),
    )
    .unwrap();
}

/// The builder for a gate without `--build`: never called.
fn no_build(_root: &Path, argv: &[String], _target: &Path) -> anyhow::Result<()> {
    panic!("no build expected, got cargo {argv:?}")
}

fn gate_verb(args: &str, root: &Path) -> GateVerb {
    parse_gate_verb(
        &format!("gate {args} --target {}", root.join("target").display())
            .split_whitespace()
            .map(String::from)
            .collect::<Vec<_>>(),
    )
    .unwrap()
}

#[test]
fn the_gate_verb_parses_rows_target_and_json() {
    let v = parse_gate_verb(
        &"gate --row a --row b --json out/drift.json --target /t"
            .split_whitespace()
            .map(String::from)
            .collect::<Vec<_>>(),
    )
    .unwrap();
    assert_eq!(v.rows, vec!["a", "b"]);
    assert_eq!(v.json, Some(PathBuf::from("out/drift.json")));
    assert_eq!(v.target, Some(PathBuf::from("/t")));
    let e = parse_gate_verb(&["gate".into(), "--bogus".into()]).unwrap_err();
    assert!(format!("{e:#}").contains("unknown argument `--bogus`"), "{e:#}");
    // `measure gate` is named in the syscalls verb's refusal of another word.
    let e = parse_measure_verb(&["kernel".into()]).unwrap_err();
    assert!(format!("{e:#}").contains("measure gate"), "{e:#}");
}

#[test]
fn the_gate_finds_every_row_with_a_manifest_sorted() {
    let (d, tool) = measured("");
    add_row(d.path(), &tool, "edge");
    // a dir without a manifest is not a row
    std::fs::create_dir_all(d.path().join(PROFILES_DIR).join("notes")).unwrap();
    assert_eq!(rows_with_profiles(d.path()).unwrap(), vec!["edge", "node"]);
}

#[test]
fn a_repo_without_any_profiled_row_fails_the_gate_by_path() {
    let d = tempfile::tempdir().unwrap();
    let tool = FakeTool::new(d.path());
    let r = run_gate(&gate_verb("", d.path()), d.path(), &|_| Ok(&tool), &no_build);
    assert!(!r.ok);
    assert!(r.refused.as_deref().unwrap().contains(".nornir/profiles/<row>/measure.toml"), "{r:?}");
}

#[test]
fn the_gate_checks_every_row_and_passes_when_none_drift() {
    let (d, tool) = measured("");
    add_row(d.path(), &tool, "edge");
    run_measure_verb(&vt("syscalls edge", d.path()), d.path(), &tool).unwrap();
    tool.calls.borrow_mut().clear();
    let r = run_gate(&gate_verb("", d.path()), d.path(), &|_| Ok(&tool), &no_build);
    assert!(r.ok, "{}", r.render());
    assert_eq!(r.rows.iter().map(|g| g.row.as_str()).collect::<Vec<_>>(), vec!["edge", "node"]);
    // only checks were run: never a probe that rewrites a profile
    assert!(tool.verbs().iter().all(|v| v == "--version" || v.starts_with("profile check")), "{:?}", tool.verbs());
    let node = r.rows.iter().find(|g| g.row == "node").unwrap();
    assert_eq!(node.checked.len(), 2);
    assert_eq!(node.vm_later.iter().filter(|v| v.program == "rusternetes").count(), 1);
    assert!(r.render().contains("GATE OK"), "{}", r.render());
}

#[test]
fn the_gate_fails_on_drift_and_names_row_program_call_and_test() {
    let (d, mut tool) = measured("");
    tool.check = (
        1,
        "heimdall profile check: new call sysinfo (kill_process), made by test version, add_without_netns\n".into(),
    );
    let r = run_gate(&gate_verb("", d.path()), d.path(), &|_| Ok(&tool), &no_build);
    assert!(!r.ok);
    let text = r.render();
    assert!(text.contains("DRIFT node/tunnr-cni (musl): new call sysinfo (kill_process), made by test version, add_without_netns"), "{text}");
    assert!(text.contains("GATE FAILED"), "{text}");
    // the JSON names the same thing, field by field
    let j: serde_json::Value = serde_json::from_str(&r.to_json()).unwrap();
    assert_eq!(j["schema"], GATE_SCHEMA);
    assert_eq!(j["ok"], false);
    assert_eq!(j["rows"][0]["drift"][0]["call"], "sysinfo");
    assert_eq!(j["rows"][0]["drift"][0]["tests"][1], "add_without_netns");
}

#[test]
fn a_refused_row_fails_the_gate_but_the_other_rows_are_still_checked() {
    let (d, tool) = measured("");
    add_row(d.path(), &tool, "edge"); // never measured: no workload.toml
    let r = run_gate(&gate_verb("", d.path()), d.path(), &|_| Ok(&tool), &no_build);
    assert!(!r.ok);
    let edge = r.rows.iter().find(|g| g.row == "edge").unwrap();
    assert!(edge.refused.as_deref().unwrap().contains("measure syscalls edge"), "{edge:?}");
    let node = r.rows.iter().find(|g| g.row == "node").unwrap();
    assert!(node.ok && node.refused.is_none(), "{node:?}");
}

#[test]
fn the_gate_takes_only_the_named_rows_and_refuses_an_unknown_one() {
    let (d, tool) = measured("");
    add_row(d.path(), &tool, "edge");
    let r = run_gate(&gate_verb("--row node", d.path()), d.path(), &|_| Ok(&tool), &no_build);
    assert_eq!(r.rows.len(), 1);
    assert!(r.ok, "{}", r.render());
    let r = run_gate(&gate_verb("--row nope", d.path()), d.path(), &|_| Ok(&tool), &no_build);
    assert!(!r.ok);
    assert!(r.rows[0].refused.as_deref().unwrap().contains("nope"), "{:?}", r.rows[0]);
}

#[test]
fn a_tool_that_cannot_be_located_refuses_the_row_with_the_install_line() {
    let (d, _tool) = measured("");
    let r = run_gate(&gate_verb("", d.path()), d.path(), &|pin| {
        Err::<&FakeTool, _>(anyhow!("not on PATH: {}", install_line(pin)))
    }, &no_build);
    assert!(!r.ok);
    assert!(r.rows[0].refused.as_deref().unwrap().contains("cargo install nordisk-heimdall-cli --version 0.2.0 --locked"), "{:?}", r.rows[0]);
}

#[test]
fn the_manifest_takes_build_lines_and_refuses_one_that_is_not_cargo_build() {
    let (d, _tool) = repo("\n[[build]]\ncargo = [\"build\", \"--release\", \"-p\", \"node\"]\n");
    let m = MeasureManifest::load(d.path(), "node").unwrap();
    assert_eq!(m.build[0].cargo, vec!["build", "--release", "-p", "node"]);
    let (d, _tool) = repo("\n[[build]]\ncargo = [\"run\", \"-p\", \"node\"]\n");
    let e = MeasureManifest::load(d.path(), "node").unwrap_err();
    assert!(format!("{e:#}").contains("[[build]] cargo must start with `build`"), "{e:#}");
}

#[test]
fn gate_build_builds_each_row_first_and_a_failed_build_refuses_only_that_row() {
    let (d, tool) = measured("\n[[build]]\ncargo = [\"build\", \"-p\", \"node\"]\n");
    add_row(d.path(), &tool, "edge");
    let edge_manifest = d.path().join(PROFILES_DIR).join("edge").join(MANIFEST);
    std::fs::write(
        &edge_manifest,
        std::fs::read_to_string(&edge_manifest).unwrap()
            + "\n[[build]]\ncargo = [\"build\", \"-p\", \"broken\"]\n",
    )
    .unwrap();
    let built = RefCell::new(Vec::new());
    let builder = |_root: &Path, argv: &[String], target: &Path| -> anyhow::Result<()> {
        built.borrow_mut().push((argv.join(" "), target.to_path_buf()));
        if argv.iter().any(|a| a == "broken") {
            bail!("exited 101");
        }
        Ok(())
    };
    let r = run_gate(&gate_verb("--build", d.path()), d.path(), &|_| Ok(&tool), &builder);
    let built = built.into_inner();
    assert_eq!(built.len(), 2, "{built:?}");
    assert_eq!(built[1].0, "build -p node");
    assert_eq!(built[1].1, d.path().join("target"));
    let edge = r.rows.iter().find(|g| g.row == "edge").unwrap();
    assert!(edge.refused.as_deref().unwrap().contains("cargo build -p broken: exited 101"), "{edge:?}");
    let node = r.rows.iter().find(|g| g.row == "node").unwrap();
    assert!(node.ok, "{node:?}");
    // without --build nothing is built (no_build panics if called)
    let r = run_gate(&gate_verb("--row node", d.path()), d.path(), &|_| Ok(&tool), &no_build);
    assert!(r.ok);
}

// ── the tune verbs' library half (dwarves `tune probe|gen|check|approve`) ──

const SMOKE: &str = r#"
[[driver]]
name = "smoke"
program = "pid1"
about = "drive PID 1 through its status page"
[[driver.run]]
name = "status"
args = ["status"]
expect_exit = 0
[[driver.run]]
name = "bad_flag"
args = ["--nope"]
error_path = true
"#;

#[test]
fn the_verb_parses_gen_and_workload_and_refuses_gen_with_check() {
    let v = verb("syscalls node --gen --workload smoke --workload load");
    assert!(v.gen_only && !v.check);
    assert_eq!(v.workloads, ["smoke", "load"]);
    let e = parse_measure_verb(
        &"syscalls node --gen --check"
            .split_whitespace()
            .map(String::from)
            .collect::<Vec<_>>(),
    )
    .unwrap_err()
    .to_string();
    assert!(e.contains("--gen") && e.contains("--check"), "{e}");
}

#[test]
fn gen_regenerates_from_the_probe_evidence_without_probing() {
    let (d, tool) = measured("");
    let r = run_measure_verb(&vt("syscalls node --gen", d.path()), d.path(), &tool).unwrap();
    assert!(r.ok(), "{}", r.render());
    assert_eq!(
        tool.verbs(),
        ["--version", "profile gen tunnr-cni", "profile gen pid1"]
    );
    assert_eq!(r.programs.len(), 2);
    assert!(r.render().contains("measure syscalls node --gen"), "{}", r.render());
}

#[test]
fn gen_without_probe_evidence_is_refused_with_the_probe_line() {
    let (d, tool) = repo("");
    let e = format!(
        "{:#}",
        run_measure_verb(&vt("syscalls node --gen", d.path()), d.path(), &tool).unwrap_err()
    );
    assert!(
        e.contains("no probe evidence") && e.contains("tunnr-cni") && e.contains("probe"),
        "{e}"
    );
    assert_eq!(tool.verbs(), ["--version"]);
}

#[test]
fn a_workload_driver_adds_its_runs_to_its_program_and_is_remembered() {
    let (d, tool) = repo(SMOKE);
    let row = d.path().join(".nornir/profiles/node");
    // Without --workload the driver's runs are not probed.
    run_measure_verb(&vt("syscalls node", d.path()), d.path(), &tool).unwrap();
    let w = std::fs::read_to_string(row.join("pid1/workload.toml")).unwrap();
    assert!(!w.contains("smoke/status"), "{w}");
    // With it, they are: named <driver>/<run>, on the driver's program only.
    let r = run_measure_verb(
        &vt("syscalls node --workload smoke", d.path()),
        d.path(),
        &tool,
    )
    .unwrap();
    let w: WorkloadToml =
        toml::from_str(&std::fs::read_to_string(row.join("pid1/workload.toml")).unwrap()).unwrap();
    let names: Vec<_> = w.run.iter().map(|r| r.name.as_str()).collect();
    assert_eq!(names, ["not_pid_1", "smoke/status", "smoke/bad_flag"]);
    assert_eq!(w.run[1].args, ["status"]);
    assert!(w.run[2].error_path);
    let cni = std::fs::read_to_string(row.join("tunnr-cni/workload.toml")).unwrap();
    assert!(!cni.contains("smoke"), "{cni}");
    assert_eq!(r.workloads, ["smoke"]);
    let pid1 = r.programs.iter().find(|p| p.program == "pid1").unwrap();
    assert_eq!(pid1.runs, 3);
    // The evidence remembers the drivers, so check and gen reuse them.
    let ev: serde_json::Value =
        serde_json::from_str(&std::fs::read_to_string(row.join("measure.json")).unwrap()).unwrap();
    assert_eq!(ev["workloads"][0], "smoke");
    let c = run_measure_verb(&vt("syscalls node --check", d.path()), d.path(), &tool).unwrap();
    assert!(c.ok(), "{}", c.render());
    assert_eq!(c.workloads, ["smoke"]);
    tool.calls.borrow_mut().clear();
    let g = run_measure_verb(&vt("syscalls node --gen", d.path()), d.path(), &tool).unwrap();
    assert_eq!(g.workloads, ["smoke"]);
}

#[test]
fn a_row_without_drivers_writes_the_evidence_without_a_workloads_key() {
    let (d, _tool) = measured("");
    let ev = std::fs::read_to_string(d.path().join(".nornir/profiles/node/measure.json")).unwrap();
    assert!(!ev.contains("workloads"), "{ev}");
}

#[test]
fn an_unknown_driver_is_refused_listing_the_rows_drivers() {
    let (d, tool) = repo(SMOKE);
    let e = run_measure_verb(
        &vt("syscalls node --workload load", d.path()),
        d.path(),
        &tool,
    )
    .unwrap_err()
    .to_string();
    assert!(e.contains("no workload driver `load`") && e.contains("smoke"), "{e}");
    let (d, tool) = repo("");
    let e = run_measure_verb(
        &vt("syscalls node --workload load", d.path()),
        d.path(),
        &tool,
    )
    .unwrap_err()
    .to_string();
    assert!(e.contains("no workload driver `load`") && e.contains("[[driver]]"), "{e}");
}

#[test]
fn a_driver_must_name_a_probed_program_and_give_runs() {
    for (extra, want) in [
        (
            "[[driver]]\nname = \"x\"\nprogram = \"nope\"\n[[driver.run]]\nname = \"a\"\n",
            "program `nope`",
        ),
        (
            "[[driver]]\nname = \"x\"\nprogram = \"rusternetes\"\n[[driver.run]]\nname = \"a\"\n",
            "needs_vm",
        ),
        ("[[driver]]\nname = \"x\"\nprogram = \"pid1\"\n", "no [[driver.run]]"),
        (
            "[[driver]]\nname = \"x\"\nprogram = \"pid1\"\n[[driver.run]]\nname = \"a\"\n[[driver]]\nname = \"x\"\nprogram = \"pid1\"\n[[driver.run]]\nname = \"a\"\n",
            "twice",
        ),
    ] {
        let (d, _tool) = repo(extra);
        let e = format!("{:#}", MeasureManifest::load(d.path(), "node").unwrap_err());
        assert!(e.contains(want), "{want:?} not in {e}");
    }
}

#[test]
fn an_unbuilt_binary_names_the_rows_build_lines() {
    let (d, tool) = repo("\n[[build]]\ncargo = [\"build\", \"--release\", \"-p\", \"node\"]\n");
    std::fs::remove_file(d.path().join("target/x86_64-unknown-linux-musl/release/node")).unwrap();
    let e = run_measure_verb(&vt("syscalls node --program pid1", d.path()), d.path(), &tool)
        .unwrap_err()
        .to_string();
    assert!(e.contains("is not built") && e.contains("cargo build --release -p node"), "{e}");
}

// ── approval ────────────────────────────────────────────────────────────────

#[test]
fn a_measured_row_is_not_approved_until_approve_records_the_review() {
    let (d, _tool) = measured("");
    let arch = std::env::consts::ARCH;
    let s = approval_status(d.path(), "node").unwrap();
    assert!(!s.ok() && !s.exists);
    // .json and .oci.json of each probed program; never the .rs or .probe.json.
    let files: Vec<_> = s.files.iter().map(|f| f.file.clone()).collect();
    assert_eq!(
        files,
        [
            format!(".nornir/profiles/node/pid1/{arch}-musl.json"),
            format!(".nornir/profiles/node/pid1/{arch}-musl.oci.json"),
            format!(".nornir/profiles/node/tunnr-cni/{arch}-musl.json"),
            format!(".nornir/profiles/node/tunnr-cni/{arch}-musl.oci.json"),
        ]
    );
    assert!(s.files.iter().all(|f| f.state == ApprovalState::NotApproved));
    assert!(
        s.problems()[0].starts_with(&format!(
            "not approved: pid1 .nornir/profiles/node/pid1/{arch}-musl.json was never approved"
        )),
        "{:?}",
        s.problems()
    );

    let s = approve(d.path(), "node", "Rickard", "2026-11-01", "first review").unwrap();
    assert!(s.ok() && s.exists, "{:?}", s.problems());
    let t = std::fs::read_to_string(d.path().join(".nornir/profiles/node/approved.toml")).unwrap();
    for w in ["reviewer = \"Rickard\"", "date = \"2026-11-01\"", "note = \"first review\"", "sha256 = \"", "program = \"tunnr-cni\"", "libc = \"musl\""] {
        assert!(t.contains(w), "{w:?} missing from:\n{t}");
    }
    let f = &s.files[0];
    assert_eq!(f.state, ApprovalState::Approved);
    assert_eq!(f.reviewer.as_deref(), Some("Rickard"));
    assert_eq!(f.sha256, f.approved_sha256);
}

#[test]
fn a_profile_changed_after_approval_is_named_with_the_approval_date() {
    let (d, _tool) = measured("");
    let arch = std::env::consts::ARCH;
    approve(d.path(), "node", "Rickard", "2026-11-01", "").unwrap();
    let p = d.path().join(format!(".nornir/profiles/node/pid1/{arch}-musl.json"));
    std::fs::write(&p, "{\"rules\": []}\n").unwrap();
    let s = approval_status(d.path(), "node").unwrap();
    assert!(!s.ok());
    assert_eq!(
        s.problems(),
        [format!(
            "not approved: pid1 .nornir/profiles/node/pid1/{arch}-musl.json changed since 2026-11-01"
        )]
    );
    // A re-approval keeps the unchanged entries' reviewer and date.
    let s = approve(d.path(), "node", "Partner", "2026-11-02", "").unwrap();
    assert!(s.ok());
    let by: Vec<_> = s
        .files
        .iter()
        .map(|f| (f.file.rsplit('/').nth(1).unwrap().to_string(), f.date.clone().unwrap()))
        .collect();
    assert!(by.contains(&("pid1".into(), "2026-11-02".into())), "{by:?}");
    assert!(by.contains(&("tunnr-cni".into(), "2026-11-01".into())), "{by:?}");
}

#[test]
fn an_approved_profile_that_vanished_is_named() {
    let (d, _tool) = measured("");
    let arch = std::env::consts::ARCH;
    approve(d.path(), "node", "Rickard", "2026-11-01", "").unwrap();
    std::fs::remove_file(d.path().join(format!(".nornir/profiles/node/pid1/{arch}-musl.oci.json")))
        .unwrap();
    let s = approval_status(d.path(), "node").unwrap();
    assert_eq!(
        s.problems(),
        [format!(
            "not approved: pid1 .nornir/profiles/node/pid1/{arch}-musl.oci.json was approved on 2026-11-01 and is gone"
        )]
    );
}

#[test]
fn approve_refuses_an_email_as_reviewer_and_a_row_with_nothing_to_approve() {
    let (d, _tool) = measured("");
    let e = approve(d.path(), "node", "someone@example.org", "2026-11-01", "")
        .unwrap_err()
        .to_string();
    assert!(e.contains("never an e-mail"), "{e}");
    assert!(approve(d.path(), "node", " ", "2026-11-01", "").is_err());
    assert!(!d.path().join(".nornir/profiles/node/approved.toml").exists());
    let (d, _tool) = repo("");
    let e = approve(d.path(), "node", "Rickard", "2026-11-01", "")
        .unwrap_err()
        .to_string();
    assert!(e.contains("nothing to approve") && e.contains("measure"), "{e}");
}

#[test]
fn a_served_driver_carries_its_fields_as_data_and_adds_no_corpus_runs() {
    let served = r#"
[[driver]]
name = "pg"
program = "pid1"
command = ["scripts/drive-pg.sh", "{channel}"]
serve = ["{target}/x86_64-unknown-linux-musl/release/node", "serve"]
ready = { socket = "{channel}/.s.PGSQL.5432" }
timeout_secs = 30
stop_signal = "INT"
env = { PGDATA = "{tmp}/pg" }
image = { layout = "oci/pg", digest = "sha256:00" }
channel = true
"#;
    let (d, tool) = repo(served);
    let m = MeasureManifest::load(d.path(), "node").unwrap();
    let pg = &m.drivers[0];
    assert!(pg.served());
    assert_eq!(pg.ready, Some(Ready::Socket("{channel}/.s.PGSQL.5432".into())));
    assert_eq!(pg.image.as_ref().unwrap().digest, "sha256:00");
    assert!(pg.channel && pg.timeout_secs == Some(30));
    let r = run_measure_verb(&vt("syscalls node --workload pg", d.path()), d.path(), &tool).unwrap();
    assert_eq!(r.workloads, ["pg"]);
    let w = std::fs::read_to_string(d.path().join(".nornir/profiles/node/pid1/workload.toml")).unwrap();
    assert!(!w.contains("pg"), "{w}");
}

#[test]
fn the_gate_holds_a_reviewed_row_to_its_approval_and_names_an_unreviewed_one() {
    let (d, tool) = measured("");
    let arch = std::env::consts::ARCH;
    // never reviewed: the gate passes and says so
    let r = run_gate(&gate_verb("", d.path()), d.path(), &|_| Ok(&tool), &no_build);
    assert!(r.ok, "{}", r.render());
    assert!(!r.rows[0].reviewed);
    assert!(r.render().contains("node: not reviewed yet (dwarves tune approve node)"), "{}", r.render());
    // reviewed and unchanged: ok
    approve(d.path(), "node", "Rickard", "2026-11-01", "").unwrap();
    let r = run_gate(&gate_verb("", d.path()), d.path(), &|_| Ok(&tool), &no_build);
    assert!(r.ok && r.rows[0].reviewed, "{}", r.render());
    // a reviewed file changed after approval: the gate fails, naming file and date
    let f = d.path().join(format!(".nornir/profiles/node/pid1/{arch}-musl.oci.json"));
    std::fs::write(&f, "{\"changed\": true}\n").unwrap();
    let r = run_gate(&gate_verb("", d.path()), d.path(), &|_| Ok(&tool), &no_build);
    assert!(!r.ok);
    let text = r.render();
    assert!(
        text.contains(&format!("node: not approved: pid1 .nornir/profiles/node/pid1/{arch}-musl.oci.json changed since 2026-11-01")),
        "{text}"
    );
    let j: serde_json::Value = serde_json::from_str(&r.to_json()).unwrap();
    assert_eq!(j["rows"][0]["not_approved"].as_array().unwrap().len(), 1);
}