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

nordisk/znippy

plugins/native/znippy-plugin-elf/src/tests.rs

//! One test per column, one test per refusal.

use super::*;
use crate::fixture::*;

fn row(path: &str, data: &[u8]) -> ExtensionRow {
    NativeElfPlugin::new().extract_metadata(path, data).expect("plugin claimed the member")
}

fn s<'a>(r: &'a ExtensionRow, k: &str) -> Option<&'a str> {
    match r.fields.get(k) {
        Some(ExtensionValue::Str(v)) => Some(v.as_str()),
        _ => None,
    }
}

fn n(r: &ExtensionRow, k: &str) -> Option<u32> {
    match r.fields.get(k) {
        Some(ExtensionValue::U32(v)) => Some(*v),
        _ => None,
    }
}

// ─────────────────────────── the gate ───────────────────────────

#[test]
fn a_non_elf_member_gets_no_elf_columns() {
    // The gate is the magic, not the name. A file CALLED libfoo.so that is not
    // an ELF must not acquire a single fact.
    let p = NativeElfPlugin::new();
    assert!(p.extract_metadata("usr/lib/libfoo.so", b"I am not an ELF").is_none());
    assert!(p.extract_metadata("bin/gunnar", b"#!/bin/sh\necho hi\n").is_none());
    assert!(p.extract_metadata("empty", b"").is_none());
}

#[test]
fn an_extensionless_binary_is_still_claimed() {
    // Most real binaries live in bin/ with no extension at all. Claiming by
    // extension would have missed every one of them.
    let data = Builder::default().etype(ET_EXEC).text(16).build();
    assert_eq!(s(&row("bin/gunnar", &data), "elf_machine"), Some("x86-64"));
}

// ─────────────────────── identity columns ───────────────────────

#[test]
fn member_kind_separates_a_pie_executable_from_a_shared_library() {
    // Both are ET_DYN. PT_INTERP is the only thing that tells them apart, and
    // `elf_type` alone cannot.
    let pie = Builder::default().etype(ET_DYN).interp("/lib64/ld-linux-x86-64.so.2").build();
    let lib = Builder::default().etype(ET_DYN).build();
    assert_eq!(s(&row("bin/app", &pie), "member_kind"), Some("pie-executable"));
    assert_eq!(s(&row("lib/libx.so", &lib), "member_kind"), Some("shared-object"));
    assert_eq!(s(&row("bin/app", &pie), "elf_type"), Some("dyn"));
    assert_eq!(s(&row("lib/libx.so", &lib), "elf_type"), Some("dyn"));
}

#[test]
fn member_kind_names_an_executable_a_kernel_module_and_a_core() {
    let exe = Builder::default().etype(ET_EXEC).build();
    let ko = Builder::default()
        .etype(ET_REL)
        .section(Section::new(".modinfo", SHT_PROGBITS, b"license=GPL\0".to_vec()))
        .build();
    let core = Builder::default().etype(ET_CORE).build();
    assert_eq!(s(&row("bin/x", &exe), "member_kind"), Some("executable"));
    assert_eq!(s(&row("lib/modules/x.ko", &ko), "member_kind"), Some("kernel-module"));
    assert_eq!(s(&row("crash/core.1", &core), "member_kind"), Some("core"));
}

#[test]
fn elf_build_id_is_read_from_the_note() {
    let data = Builder::default()
        .note(".note.gnu.build-id", b"GNU", 3, &[0xde, 0xad, 0xbe, 0xef, 0x01])
        .build();
    assert_eq!(s(&row("bin/x", &data), "elf_build_id"), Some("deadbeef01"));
}

#[test]
fn a_binary_without_a_build_id_leaves_the_column_null_which_is_the_finding() {
    let data = Builder::default().build();
    assert_eq!(s(&row("bin/x", &data), "elf_build_id"), None);
}

// ──────────────────────── header columns ────────────────────────

#[test]
fn elf_machine_is_the_header_not_the_path() {
    // The path screams x86_64. The header says aarch64. The header wins.
    let data = Builder::default().machine(EM_AARCH64).build();
    let r = row("target/x86_64-unknown-linux-gnu/release/gunnar", &data);
    assert_eq!(s(&r, "elf_machine"), Some("aarch64"));
}

#[test]
fn elf_class_and_osabi_come_from_e_ident() {
    let data = Builder::default().osabi(3).build(); // ELFOSABI_GNU
    let r = row("bin/x", &data);
    assert_eq!(s(&r, "elf_class"), Some("ELF64"));
    assert_eq!(s(&r, "elf_osabi"), Some("GNU"));
}

#[test]
fn elf_interp_is_null_for_a_static_binary_and_set_for_a_dynamic_one() {
    let stat = Builder::default().etype(ET_EXEC).build();
    let dyn_ = Builder::default().etype(ET_EXEC).interp("/lib/ld-musl-x86_64.so.1").build();
    assert_eq!(s(&row("bin/x", &stat), "elf_interp"), None);
    assert_eq!(s(&row("bin/y", &dyn_), "elf_interp"), Some("/lib/ld-musl-x86_64.so.1"));
}

#[test]
fn elf_abi_level_reads_the_x86_isa_property() {
    // GNU_PROPERTY_X86_ISA_1_NEEDED, bit 2 set = x86-64-v3.
    let data = Builder::default()
        .note(".note.gnu.property", b"GNU", 5, &property_desc(0xc000_8002, 0b100))
        .build();
    assert_eq!(s(&row("bin/x", &data), "elf_abi_level"), Some("x86-64-v3"));
}

#[test]
fn elf_abi_level_reads_the_aarch64_feature_property() {
    let data = Builder::default()
        .machine(EM_AARCH64)
        .note(".note.gnu.property", b"GNU", 5, &property_desc(0xc000_0000, 0b11))
        .build();
    assert_eq!(s(&row("bin/x", &data), "elf_abi_level"), Some("bti+pac"));
}

// ───────────────────── dependency column ─────────────────────

#[test]
fn elf_needed_lists_sonames_sorted() {
    let data = Builder::default().needed(&["libssl.so.3", "libc.so.6"]).build();
    assert_eq!(s(&row("bin/x", &data), "elf_needed"), Some("libc.so.6 libssl.so.3"));
}

#[test]
fn elf_needed_carries_the_symbol_version_so_libssl_3_0_x_is_findable() {
    // THE query: "which binaries link libssl 3.0.x". The soname gives the ABI
    // major; only .gnu.version_r gives the minor.
    let data = Builder::default()
        .needed(&["libssl.so.3", "libc.so.6"])
        .verneed("libssl.so.3", &["OPENSSL_3.0.0"])
        .build();
    let needed = s(&row("bin/x", &data), "elf_needed").unwrap().to_string();
    assert!(needed.contains("libssl.so.3(OPENSSL_3.0.0)"), "got {needed}");
    assert!(needed.contains("libc.so.6"), "got {needed}");
}

#[test]
fn a_static_binary_has_no_needed_column() {
    let data = Builder::default().etype(ET_EXEC).build();
    assert_eq!(s(&row("bin/x", &data), "elf_needed"), None);
}

// ─────────────────────── cost columns ───────────────────────

#[test]
fn text_bytes_counts_only_allocated_executable_sections() {
    let data = Builder::default()
        .text(1024)
        .section(Section::new(".rodata", SHT_PROGBITS, vec![0; 4096]).flags(SHF_ALLOC))
        .build();
    // 4096 bytes of rodata must not be counted as code.
    assert_eq!(n(&row("bin/x", &data), "text_bytes"), Some(1024));
}

#[test]
fn debug_bytes_and_has_debug_measure_what_a_fetcher_would_pay() {
    let with = Builder::default().text(64).debug(2048).build();
    let without = Builder::default().text(64).build();
    let r = row("bin/x", &with);
    assert_eq!(n(&r, "debug_bytes"), Some(2048));
    assert_eq!(n(&r, "has_debug"), Some(1));
    let r2 = row("bin/y", &without);
    assert_eq!(n(&r2, "debug_bytes"), Some(0));
    assert_eq!(n(&r2, "has_debug"), Some(0));
}

#[test]
fn a_debuglink_is_has_debug_with_zero_debug_bytes() {
    // "stripped, the symbols live elsewhere" — the case a single byte count
    // cannot express, and the case debuginfod exists for.
    let data = Builder::default()
        .text(64)
        .section(Section::new(".gnu_debuglink", SHT_PROGBITS, b"x.debug\0\0\0\0abcd".to_vec()))
        .build();
    let r = row("bin/x", &data);
    assert_eq!(n(&r, "has_debug"), Some(1));
    assert_eq!(n(&r, "debug_bytes"), Some(0));
}

// ───────────────────── recipe columns ─────────────────────

fn receipt_json(version: u32) -> Vec<u8> {
    format!(
        r#"{{"receipt_version":{version},
            "build_thing":{{"krate":"gunnar-build-thing","version":"0.3.1","hash":"abc123"}},
            "atom":"gunnar-installer-metered",
            "target":"iso-installer",
            "settings":{{"containers":"on","iceberg":"off"}},
            "inputs":{{"commit":"451288a0000000000000000000000000000000aa",
                       "toolchain_image":"nordisk-cargo-toolchain",
                       "toolchain_image_sha256":"3d5115baa07d03ca51eb3e44ebb86b4e64e679bd88f38e156ef9da12c35d2143"}}}}"#
    )
    .into_bytes()
}

fn with_receipt(version: u32) -> Vec<u8> {
    Builder::default()
        .section(Section::new(".nordisk.buildthing", SHT_PROGBITS, receipt_json(version)))
        .build()
}

#[test]
fn the_toolchain_image_digest_is_read_from_the_binarys_own_receipt() {
    // The column this whole format exists for.
    let r = row("bin/gunnar", &with_receipt(1));
    assert_eq!(
        s(&r, "toolchain_image_sha256"),
        Some("3d5115baa07d03ca51eb3e44ebb86b4e64e679bd88f38e156ef9da12c35d2143")
    );
    assert_eq!(s(&r, "toolchain_image"), Some("nordisk-cargo-toolchain"));
}

#[test]
fn the_recipe_coordinate_variant_target_features_and_commit_come_from_the_receipt() {
    let r = row("bin/gunnar", &with_receipt(1));
    assert_eq!(s(&r, "recipe_coordinate"), Some("gunnar-build-thing:0.3.1:abc123"));
    assert_eq!(s(&r, "recipe_variant"), Some("gunnar-installer-metered"));
    assert_eq!(s(&r, "recipe_target"), Some("iso-installer"));
    assert_eq!(s(&r, "recipe_features"), Some("containers=on iceberg=off"));
    assert_eq!(s(&r, "source_commit"), Some("451288a0000000000000000000000000000000aa"));
}

#[test]
fn a_receipt_from_the_future_is_refused_not_partially_interpreted() {
    // nornir-build-thing's own rule: a misread receipt answers the provenance
    // question WRONGLY, which is worse than not answering it.
    let r = row("bin/gunnar", &with_receipt(99));
    assert_eq!(s(&r, "recipe_coordinate"), None);
    assert_eq!(s(&r, "toolchain_image_sha256"), None);
}

#[test]
fn two_receipts_in_one_artifact_answer_neither() {
    let mut two = receipt_json(1);
    two.push(0);
    two.extend_from_slice(&receipt_json(1));
    let data = Builder::default()
        .section(Section::new(".nordisk.buildthing", SHT_PROGBITS, two))
        .build();
    assert_eq!(s(&row("bin/x", &data), "recipe_variant"), None);
}

#[test]
fn note_package_fills_what_the_receipt_did_not() {
    let note = br#"{"type":"rpm","name":"gunnar","version":"1.4.0","architecture":"x86_64",
                    "variant":"gunnar-open","sourceCommit":"deadbeef",
                    "toolchainImage":"nordisk-cargo-toolchain@sha256:ffff"}"#;
    let data = Builder::default().note(".note.package", b"FDO", 0xcafe_1a7e, note).build();
    let r = row("bin/gunnar", &data);
    assert_eq!(s(&r, "recipe_variant"), Some("gunnar-open"));
    assert_eq!(s(&r, "source_commit"), Some("deadbeef"));
    // the `name@sha256:hex` ref is split, never stored whole in a digest column
    assert_eq!(s(&r, "toolchain_image"), Some("nordisk-cargo-toolchain"));
    assert_eq!(s(&r, "toolchain_image_sha256"), Some("ffff"));
}

#[test]
fn the_receipt_wins_over_the_note() {
    let note = br#"{"variant":"gunnar-open","sourceCommit":"0000000"}"#;
    let data = Builder::default()
        .section(Section::new(".nordisk.buildthing", SHT_PROGBITS, receipt_json(1)))
        .note(".note.package", b"FDO", 0xcafe_1a7e, note)
        .build();
    let r = row("bin/gunnar", &data);
    assert_eq!(s(&r, "recipe_variant"), Some("gunnar-installer-metered"));
    assert_eq!(s(&r, "source_commit"), Some("451288a0000000000000000000000000000000aa"));
}

// ───────────────────── the audit column ─────────────────────

#[test]
fn auditable_root_is_read_from_the_dep_v0_tree() {
    let json = br#"{"packages":[{"name":"serde","version":"1.0.203","kind":"build"},
                                 {"name":"gunnar","version":"1.4.0","root":true}]}"#;
    let data = Builder::default()
        .section(Section::new(".dep-v0", SHT_PROGBITS, zlib_stored(json)))
        .build();
    assert_eq!(s(&row("bin/gunnar", &data), "auditable_root"), Some("gunnar 1.4.0"));
}

#[test]
fn a_binary_with_no_dep_v0_cannot_be_swept_and_says_so_by_being_null() {
    let data = Builder::default().build();
    assert_eq!(s(&row("bin/x", &data), "auditable_root"), None);
}

// ───────────────────── the lie columns ─────────────────────

#[test]
fn arch_claim_and_arch_lies_find_a_member_that_lies_about_its_architecture() {
    let data = Builder::default().machine(EM_AARCH64).build();
    let r = row("target/x86_64-unknown-linux-gnu/release/gunnar", &data);
    assert_eq!(s(&r, "arch_claim"), Some("x86-64"));
    assert_eq!(s(&r, "elf_machine"), Some("aarch64"));
    assert_eq!(n(&r, "arch_lies"), Some(1));
}

#[test]
fn a_truthful_path_does_not_lie_and_a_silent_path_does_not_either() {
    let data = Builder::default().machine(EM_X86_64).build();
    assert_eq!(n(&row("target/x86_64-unknown-linux-gnu/release/x", &data), "arch_lies"), Some(0));
    let r = row("bin/x", &data);
    assert_eq!(s(&r, "arch_claim"), None);
    assert_eq!(n(&r, "arch_lies"), Some(0));
}

// ───────────────────── P-4: never panic ─────────────────────

#[test]
fn a_truncated_elf_never_panics_and_still_yields_what_it_proved() {
    let full = Builder::default().etype(ET_EXEC).text(512).build();
    for cut in [4usize, 5, 8, 16, 32, 48, 63, 64, 70, 100, 200] {
        if cut > full.len() {
            continue;
        }
        // Must not panic. May or may not claim.
        let _ = NativeElfPlugin::new().extract_metadata("bin/x", &full[..cut]);
    }
    // The magic alone is enough to read class and osabi.
    let head = &full[..8];
    if let Some(r) = NativeElfPlugin::new().extract_metadata("bin/x", head) {
        assert_eq!(s(&r, "elf_class"), Some("ELF64"));
    }
}

#[test]
fn a_dep_v0_bomb_yields_a_null_column_not_an_oom() {
    // A stored-block header claiming a length its own bytes cannot supply.
    let bomb = vec![0x78, 0x01, 0x01, 0xff, 0xff, 0x00, 0x00, 0x41];
    let data = Builder::default().section(Section::new(".dep-v0", SHT_PROGBITS, bomb)).build();
    assert_eq!(s(&row("bin/x", &data), "auditable_root"), None);
}

#[test]
fn garbage_in_every_readable_section_leaves_nulls_and_keeps_the_header_facts() {
    let data = Builder::default()
        .machine(EM_RISCV)
        .section(Section::new(".nordisk.buildthing", SHT_PROGBITS, b"}{not json".to_vec()))
        .section(Section::new(".dep-v0", SHT_PROGBITS, b"\x00\x00\x00".to_vec()))
        .note(".note.package", b"FDO", 0xcafe_1a7e, b"also not json")
        .build();
    let r = row("bin/x", &data);
    assert_eq!(s(&r, "elf_machine"), Some("riscv64"));
    assert_eq!(s(&r, "recipe_variant"), None);
    assert_eq!(s(&r, "auditable_root"), None);
}

// ───────────────────── schema and registration ─────────────────────

#[test]
fn schema_and_meta_are_consistent_and_every_column_is_declared() {
    let p = NativeElfPlugin::new();
    assert_eq!(p.type_id(), ELF_TYPE_ID);
    assert_eq!(p.meta().type_id, ELF_TYPE_ID);
    assert_eq!(p.schema_fields().len(), 22);

    // P-2: a row may never carry a key the schema did not declare.
    let declared: std::collections::HashSet<String> =
        p.schema_fields().iter().map(|f| f.name().to_string()).collect();
    let data = Builder::default()
        .etype(ET_EXEC)
        .interp("/lib64/ld-linux-x86-64.so.2")
        .text(64)
        .debug(16)
        .needed(&["libc.so.6"])
        .note(".note.gnu.build-id", b"GNU", 3, &[1, 2, 3])
        .section(Section::new(".nordisk.buildthing", SHT_PROGBITS, receipt_json(1)))
        .build();
    let r = row("target/x86_64-unknown-linux-gnu/release/gunnar", &data);
    for k in r.fields.keys() {
        assert!(declared.contains(k), "undeclared column '{k}' (P-2)");
    }
}

// ═══════════════════════════ refusals ═══════════════════════════

use crate::refusal::{Coordinate, ElfRefusal, HostFacts, Linkage, RecipeVerdict, check};

fn a_host() -> HostFacts {
    HostFacts {
        machine: "x86-64".into(),
        abi_level: Some("x86-64-v2".into()),
        interpreters: vec!["/lib64/ld-linux-x86-64.so.2".into()],
    }
}

fn facts_of(data: &[u8]) -> crate::facts::ElfFacts {
    crate::facts::read(data).expect("fixture is an ELF")
}

#[test]
fn elf_machine_refuses_when_the_header_disagrees_with_the_coordinate() {
    let f = facts_of(&Builder::default().machine(EM_AARCH64).build());
    let coord = Coordinate {
        path: "target/x86_64-unknown-linux-gnu/release/gunnar".into(),
        ..Default::default()
    };
    let refusals = check(&f, &a_host(), &coord);
    let r = refusals
        .iter()
        .find(|r| matches!(r, ElfRefusal::Machine { whose: "coordinate", .. }))
        .expect("elf-machine (coordinate) must fire");
    assert_eq!(r.name(), "elf-machine");
    let text = r.to_string();
    assert!(text.starts_with("REFUSED [elf-machine] "), "{text}");
    // both sides must be named
    assert!(text.contains("aarch64") && text.contains("x86-64"), "{text}");
}

#[test]
fn elf_machine_refuses_when_the_header_disagrees_with_the_host() {
    let f = facts_of(&Builder::default().machine(EM_AARCH64).build());
    let coord = Coordinate { path: "bin/gunnar".into(), ..Default::default() };
    assert!(
        check(&f, &a_host(), &coord)
            .iter()
            .any(|r| matches!(r, ElfRefusal::Machine { whose: "host", .. })),
        "a binary for another machine must be refused by name, not started and killed"
    );
}

#[test]
fn a_matching_machine_is_not_refused() {
    let f = facts_of(&Builder::default().machine(EM_X86_64).etype(ET_EXEC).build());
    let coord = Coordinate {
        path: "target/x86_64-unknown-linux-gnu/release/x".into(),
        ..Default::default()
    };
    assert!(!check(&f, &a_host(), &coord).iter().any(|r| r.name() == "elf-machine"));
}

#[test]
fn elf_libc_refuses_a_dynamic_binary_where_the_coordinate_says_static() {
    let f = facts_of(
        &Builder::default().etype(ET_EXEC).interp("/lib64/ld-linux-x86-64.so.2").build(),
    );
    let coord = Coordinate {
        path: "bin/gunnar".into(),
        linkage: Some(Linkage::Static),
        ..Default::default()
    };
    let r = check(&f, &a_host(), &coord)
        .into_iter()
        .find(|r| matches!(r, ElfRefusal::LibcDynamicWhereStatic { .. }))
        .expect("elf-libc must fire");
    assert_eq!(r.name(), "elf-libc");
    assert!(r.to_string().contains("/lib64/ld-linux-x86-64.so.2"));
}

#[test]
fn elf_libc_refuses_an_interpreter_this_box_does_not_have() {
    let f = facts_of(&Builder::default().etype(ET_EXEC).interp("/lib/ld-musl-x86_64.so.1").build());
    let coord = Coordinate { path: "bin/gunnar".into(), ..Default::default() };
    let r = check(&f, &a_host(), &coord)
        .into_iter()
        .find(|r| matches!(r, ElfRefusal::LibcInterpreterMissing { .. }))
        .expect("elf-libc must fire for a missing interpreter");
    let text = r.to_string();
    assert!(text.contains("ld-musl"), "{text}");
    assert!(text.contains("start and die"), "the sentence must say WHY: {text}");
}

#[test]
fn an_interpreter_the_box_has_is_not_refused() {
    let f = facts_of(
        &Builder::default().etype(ET_EXEC).interp("/lib64/ld-linux-x86-64.so.2").build(),
    );
    let coord = Coordinate { path: "bin/x".into(), ..Default::default() };
    assert!(!check(&f, &a_host(), &coord).iter().any(|r| r.name() == "elf-libc"));
}

#[test]
fn elf_cpu_refuses_a_level_above_this_cpu() {
    let f = facts_of(
        &Builder::default()
            .note(".note.gnu.property", b"GNU", 5, &property_desc(0xc000_8002, 0b1000))
            .build(),
    );
    let coord = Coordinate { path: "bin/x".into(), ..Default::default() };
    let r = check(&f, &a_host(), &coord)
        .into_iter()
        .find(|r| matches!(r, ElfRefusal::Cpu { .. }))
        .expect("elf-cpu must fire");
    assert_eq!(r.name(), "elf-cpu");
    let text = r.to_string();
    assert!(text.contains("x86-64-v4") && text.contains("x86-64-v2"), "{text}");
    // it must NOT read as policy — a CPU limit is not a rule somebody chose
    assert!(text.contains("host limit, not a policy"), "{text}");
}

#[test]
fn elf_cpu_does_not_refuse_a_level_at_or_below_this_cpu() {
    let f = facts_of(
        &Builder::default()
            .note(".note.gnu.property", b"GNU", 5, &property_desc(0xc000_8002, 0b10))
            .build(),
    );
    let coord = Coordinate { path: "bin/x".into(), ..Default::default() };
    assert!(!check(&f, &a_host(), &coord).iter().any(|r| r.name() == "elf-cpu"));
}

#[test]
fn elf_recipe_refuses_a_binary_that_says_nothing_about_how_it_was_built() {
    let f = facts_of(&Builder::default().etype(ET_EXEC).build());
    let coord = Coordinate { path: "bin/x".into(), ..Default::default() };
    let r = check(&f, &a_host(), &coord)
        .into_iter()
        .find(|r| matches!(r, ElfRefusal::RecipeMissing { .. }))
        .expect("elf-recipe must fire");
    assert_eq!(r.name(), "elf-recipe");
    assert!(r.to_string().contains(".nordisk.buildthing"));
}

#[test]
fn elf_recipe_refuses_when_the_binary_contradicts_the_signed_statement() {
    let f = facts_of(&with_receipt(1));
    let coord = Coordinate {
        path: "bin/gunnar".into(),
        expect_toolchain_image_sha256: Some("f29e517c1ba34b920c1234fbd628046d64c0f10".into()),
        ..Default::default()
    };
    let r = check(&f, &a_host(), &coord)
        .into_iter()
        .find(|r| matches!(r, ElfRefusal::RecipeContradicts { field: "toolchain_image_sha256", .. }))
        .expect("elf-recipe must fire on a contradiction");
    let text = r.to_string();
    assert!(text.contains("3d5115ba") && text.contains("f29e517c"), "{text}");
    assert!(text.contains("the statement is the authority"), "{text}");
}

#[test]
fn a_recipe_that_agrees_with_the_statement_is_not_refused() {
    let f = facts_of(&with_receipt(1));
    let coord = Coordinate {
        path: "bin/gunnar".into(),
        expect_toolchain_image_sha256: Some(
            "3d5115baa07d03ca51eb3e44ebb86b4e64e679bd88f38e156ef9da12c35d2143".into(),
        ),
        expect_source_commit: Some("451288a0000000000000000000000000000000aa".into()),
        expect_recipe_coordinate: Some("gunnar-build-thing:0.3.1:abc123".into()),
        ..Default::default()
    };
    assert!(!check(&f, &a_host(), &coord).iter().any(|r| r.name() == "elf-recipe"));
}

#[test]
fn an_unchecked_toolchain_image_is_recorded_by_name_never_passed_silently() {
    // Until BuildProvenance carries the toolchain image (elf-design §6.3(b), an
    // owner decision in edda), EVERY artifact is Unchecked — and a check nobody
    // can see not running is worse than no check at all.
    let f = facts_of(&with_receipt(1));
    let coord = Coordinate { path: "bin/gunnar".into(), ..Default::default() };
    assert_eq!(crate::refusal::recipe_verdict(&f, &coord), RecipeVerdict::Unchecked);
    assert_eq!(RecipeVerdict::Unchecked.word(), "unchecked");
    // and it is NOT a refusal
    assert!(!check(&f, &a_host(), &coord).iter().any(|r| r.name() == "elf-recipe"));

    let checked = Coordinate {
        expect_toolchain_image_sha256: Some(
            "3d5115baa07d03ca51eb3e44ebb86b4e64e679bd88f38e156ef9da12c35d2143".into(),
        ),
        ..coord
    };
    assert_eq!(crate::refusal::recipe_verdict(&f, &checked), RecipeVerdict::Match);
}

#[test]
fn every_refusal_is_printed_not_only_the_first() {
    // A box that has to be rebooted once per problem is a box nobody keeps.
    let f = facts_of(
        &Builder::default()
            .machine(EM_AARCH64)
            .etype(ET_EXEC)
            .interp("/lib/ld-musl-aarch64.so.1")
            .build(),
    );
    let coord = Coordinate {
        path: "target/x86_64-unknown-linux-gnu/release/gunnar".into(),
        linkage: Some(Linkage::Static),
        ..Default::default()
    };
    let names: Vec<&str> = check(&f, &a_host(), &coord).iter().map(|r| r.name()).collect();
    assert!(names.contains(&"elf-machine"), "{names:?}");
    assert!(names.contains(&"elf-libc"), "{names:?}");
    assert!(names.contains(&"elf-recipe"), "{names:?}");
    assert!(names.len() >= 4, "{names:?}");
}

#[test]
fn the_column_and_the_refusal_are_one_rule_seen_twice() {
    // arch_lies = 1 in the index and elf-machine at start must never disagree.
    let data = Builder::default().machine(EM_AARCH64).build();
    let path = "target/x86_64-unknown-linux-gnu/release/gunnar";
    let lies = n(&row(path, &data), "arch_lies") == Some(1);
    let refused = check(
        &facts_of(&data),
        &a_host(),
        &Coordinate { path: path.into(), ..Default::default() },
    )
    .iter()
    .any(|r| matches!(r, ElfRefusal::Machine { whose: "coordinate", .. }));
    assert_eq!(lies, refused);
}

#[test]
fn the_host_detects_itself_without_shelling_out() {
    let h = HostFacts::detect();
    assert!(!h.machine.is_empty());
    // On this lane's box the ELF this test runs in is for this machine.
    assert_eq!(h.machine, "x86-64");
}