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

//! **The receipt says what was chosen; the symbol table says what happened.**
//!
//! The design's three levels, and the only one that is evidence:
//!
//! ```text
//! declared    you picked the pattern
//! validated   the build thing said the pattern is legal
//! proven      the symbol table of the built binary shows it came out that way
//! ```
//!
//! A `gpu = false` that nevertheless links wgpu/naga/ash is **failed here**, by
//! measurement, rather than believed. Two independent sources — the receipt
//! written at compile time and the symbol table produced by the linker — and the
//! whole value is that they can disagree.
//!
//! # This module measures nothing itself
//!
//! Every symbol read goes through [`linkage`](crate::linkage): the same pure-Rust
//! `object` parse, the same demangling, the same destructor-glue exclusion, the
//! same [`count_families`](crate::linkage::count_families). A second counting rule
//! would be a second answer, and the two would drift the first time one of them
//! learned something. What this module adds is the *mapping* from a receipt's
//! settings to families, and the verdict.
//!
//! # "Not measured" is never "passed"
//!
//! Inherited deliberately from [`LinkageVerdict`](crate::linkage::LinkageVerdict).
//! An artifact that will not parse, a receipt that will not decode, and a receipt
//! with nothing provable in it are three distinct non-passing outcomes — see
//! [`ReceiptVerdict`]. Collapsing any of them into a pass is the failure mode that
//! makes a guard green forever.

use std::path::{Path, PathBuf};

use crate::contract::Receipt;
use crate::linkage::{count_families, demangled_symbols, LinkageAudit, ServiceShape};

/// A receipt setting whose truth is readable in a binary's symbol table.
///
/// The list is short on purpose and grows by **measurement**. Every family below
/// was counted in real artifacts on oden on 2026-08-06 (`nm -C`, then the same
/// numbers reproduced through [`count_families`]):
///
/// | family | dwarves-ui (links wgpu) | dwarves-full (no GPU) |
/// |---|---|---|
/// | `wgpu` | 5 287 | 0 |
/// | `naga` | 2 828 | 0 |
/// | `ash::vk` | 261 | 0 |
/// | `nvml` | 0 | 0 |
///
/// A family nobody has ever seen match is a guard that cannot report red, so a
/// candidate goes in only after it has been counted somewhere non-zero — `nvml`
/// is carried because NVML is named in the design and is a distinctive token, and
/// it is recorded here that it was 0 in both artifacts measured.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ProvableClaim {
    /// The receipt settings key, without any `feature.` prefix.
    pub key: &'static str,
    /// Demangled symbol substrings that mean the thing is linked in.
    pub families: &'static [&'static str],
    /// What the families are, for the sentence a report prints.
    pub what: &'static str,
}

/// The GPU stack, as it appears in a demangled symbol table.
///
/// **`ash::vk`, never `ash`.** The bare word is a substring of `core::hash::`,
/// which a real GUI binary carries 510 times (dwarves-ui, measured 2026-08-06):
/// a family of `ash` would report a GPU stack in every Rust program ever
/// compiled. That is not a hypothetical — it is the difference between a guard
/// and a guard that is always red, and always-red is switched off within a week.
pub const GPU_FAMILIES: &[&str] = &["wgpu", "naga", "ash::vk", "nvml"];

/// Every settings key this crate can prove or disprove from a binary.
pub const PROVABLE_CLAIMS: &[ProvableClaim] = &[ProvableClaim {
    key: "gpu",
    families: GPU_FAMILIES,
    what: "a GPU stack",
}];

/// The settings key whose claim is checked by the full
/// [`LinkageAudit`](crate::linkage::LinkageAudit) rather than by a family count.
pub const LINKAGE_KEY: &str = "linkage";

/// The claim a settings key makes, if this crate can check it.
///
/// A `feature.` prefix is stripped: [`ReceiptBuilder::from_cargo_env`] records the
/// resolved feature universe as `feature.gpu`, and a hand-written setting is
/// plain `gpu`. Both name the same axis, so both are checked.
///
/// [`ReceiptBuilder::from_cargo_env`]: crate::receipt::ReceiptBuilder::from_cargo_env
#[must_use]
pub fn claim_for(key: &str) -> Option<&'static ProvableClaim> {
    let bare = key.strip_prefix("feature.").unwrap_or(key);
    PROVABLE_CLAIMS.iter().find(|c| c.key == bare)
}

/// What a single claim's measurement came to.
///
/// Serialisable because the verdict has to **travel**: `strip = true` means the
/// delivered artifact has no symbol table, so nobody downstream can recompute
/// this — the builder measures once and the answer rides along in a
/// [`SymbolProof`](crate::history::SymbolProof). Carried as this enum rather than
/// as a string so the writer and the reader cannot spell it differently.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ClaimVerdict {
    /// The binary could not be read. **Never a pass.**
    NotMeasured,
    /// The setting is not `true`/`false`, so there is no claim to check. Not a
    /// pass: a provable key holding an unprovable value is a receipt that has
    /// stopped saying anything, which is worse than one that never claimed.
    NotBoolean,
    /// Claimed and present, or disclaimed and absent. The receipt is true.
    Proven,
    /// The binary contradicts the receipt, in either direction.
    Disproven,
}

impl ClaimVerdict {
    /// Whether the claim survived contact with the binary.
    #[must_use]
    pub fn holds(self) -> bool {
        matches!(self, ClaimVerdict::Proven)
    }
}

/// One receipt setting, measured against the binary that carries it.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClaimAudit {
    /// The settings key as it appears in the receipt, prefix and all.
    pub key: String,
    /// The value the receipt records.
    pub raw: String,
    /// The claim, parsed. `None` when `raw` is not a boolean.
    pub claimed: Option<bool>,
    /// What the families are, for a message.
    pub what: &'static str,
    /// Families measurably present as non-glue linkage, as `(family, count)`.
    pub found: Vec<(String, usize)>,
    /// Families whose only evidence was compiler destructor glue. Reported, never
    /// counted — see [`crate::linkage::is_compiler_destructor_glue`].
    pub glue_only: Vec<(String, usize)>,
    /// Whether the symbol table could be read at all.
    pub measured: bool,
}

impl ClaimAudit {
    /// Measure `key = raw` against an already-read symbol list.
    ///
    /// Takes the symbols rather than a path so one artifact is parsed once for
    /// every claim it carries, and so this is testable against real symbol text
    /// without a binary to hand.
    ///
    /// An **empty** symbol list is `NotMeasured`, not "found nothing". A stripped
    /// binary would otherwise prove every `= false` claim ever written, by having
    /// nothing to show. [`demangled_symbols`] already returns `None` in that case;
    /// this is the same rule stated at the second door, because a direct caller
    /// with a symbol list in hand is exactly who would hand over an empty one.
    #[must_use]
    pub fn measure(
        key: &str,
        raw: &str,
        claim: &ProvableClaim,
        symbols: Option<&[String]>,
    ) -> ClaimAudit {
        let symbols = symbols.filter(|s| !s.is_empty());
        let counts = symbols.map(|s| count_families(s, claim.families));
        ClaimAudit {
            key: key.to_string(),
            raw: raw.to_string(),
            claimed: match raw.trim().to_ascii_lowercase().as_str() {
                "true" | "yes" | "on" | "1" => Some(true),
                "false" | "no" | "off" | "0" => Some(false),
                _ => None,
            },
            what: claim.what,
            found: counts.as_ref().map(|c| c.found.clone()).unwrap_or_default(),
            glue_only: counts
                .as_ref()
                .map(|c| c.glue_only.clone())
                .unwrap_or_default(),
            measured: symbols.is_some(),
        }
    }

    /// The verdict. See [`ClaimVerdict`].
    ///
    /// Both directions fail. `gpu = false` that links wgpu is the lie the design
    /// names, and `gpu = true` that links nothing is the same lie told the other
    /// way — and it is the one that makes the first check vacuous, exactly as an
    /// [`EmbeddedButEmpty`](crate::linkage::LinkageVerdict::EmbeddedButEmpty)
    /// variant does.
    #[must_use]
    pub fn verdict(&self) -> ClaimVerdict {
        if !self.measured {
            return ClaimVerdict::NotMeasured;
        }
        let Some(claimed) = self.claimed else {
            return ClaimVerdict::NotBoolean;
        };
        if claimed == !self.found.is_empty() {
            ClaimVerdict::Proven
        } else {
            ClaimVerdict::Disproven
        }
    }

    /// Whether the claim holds.
    #[must_use]
    pub fn holds(&self) -> bool {
        self.verdict().holds()
    }

    /// `wgpu×5287, naga×2828` — the families present, for a message.
    #[must_use]
    pub fn families(&self) -> String {
        self.found
            .iter()
            .map(|(s, n)| format!("{s}×{n}"))
            .collect::<Vec<_>>()
            .join(", ")
    }

    /// The sentence a report prints.
    #[must_use]
    pub fn line(&self) -> String {
        match self.verdict() {
            ClaimVerdict::NotMeasured => format!(
                "{}: NOT MEASURED — the binary's symbol table could not be read, so the \
                 receipt's claim is unchecked",
                self.key
            ),
            ClaimVerdict::NotBoolean => format!(
                "{}: value {:?} is not true/false — nothing to prove",
                self.key, self.raw
            ),
            ClaimVerdict::Proven => {
                if self.claimed == Some(true) {
                    format!(
                        "{} = true and the binary links {} — {} ✓",
                        self.key,
                        self.what,
                        self.families()
                    )
                } else {
                    format!(
                        "{} = false and the binary links no {} ✓",
                        self.key, self.what
                    )
                }
            }
            ClaimVerdict::Disproven => {
                if self.claimed == Some(true) {
                    format!(
                        "{} = true but the binary links NO {} — the claim is unproven, and \
                         whatever check it anchors has become vacuous",
                        self.key, self.what
                    )
                } else {
                    format!(
                        "{} = false but the binary LINKS {} — the receipt is a lie ({})",
                        self.key,
                        self.what,
                        self.families()
                    )
                }
            }
        }
    }
}

/// The verdict for a whole artifact.
///
/// Five outcomes, and only one of them passes. The four that do not are kept
/// apart because a caller acts differently on each: a missing receipt is a build
/// that never wrote one, a malformed receipt is a build that wrote a broken one,
/// and an unreadable artifact is this host's problem rather than the build's.
///
/// Serialisable for the reason [`ClaimVerdict`] is: the audit happens once, on a
/// binary that still has a symbol table, and the verdict travels as text to
/// everyone holding the stripped artifact.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ReceiptVerdict {
    /// The artifact could not be read or parsed as an object file.
    NotMeasured,
    /// The artifact parsed and carries no [`RECEIPT_SECTION`].
    ///
    /// [`RECEIPT_SECTION`]: crate::contract::RECEIPT_SECTION
    NoReceipt,
    /// The section is there and does not decode — see
    /// [`decode`](crate::receipt::decode).
    MalformedReceipt,
    /// A receipt was read, the binary was measured, and nothing in the receipt is
    /// checkable against a symbol table. **Not a pass**: an audit with no subject
    /// reports green forever.
    NothingProvable,
    /// At least one claim is contradicted by the binary.
    Disproven,
    /// Every provable claim was measured and agrees with the binary.
    Proven,
}

impl ReceiptVerdict {
    /// Whether the artifact's receipt survived contact with the artifact.
    #[must_use]
    pub fn holds(self) -> bool {
        matches!(self, ReceiptVerdict::Proven)
    }
}

/// A receipt and the binary it claims to describe, measured against each other.
#[derive(Debug, Clone)]
pub struct ReceiptAudit {
    /// The artifact audited.
    pub artifact: PathBuf,
    /// Its receipt, when there was one that decoded.
    pub receipt: Option<Receipt>,
    /// Why the receipt could not be obtained, when it could not.
    pub receipt_error: Option<String>,
    /// One entry per receipt setting this crate can check.
    pub claims: Vec<ClaimAudit>,
    /// The full fat/thin audit, when the receipt declared `linkage`.
    pub linkage: Option<LinkageAudit>,
    /// How many symbols were read. `0` with `measured` true is a stripped binary,
    /// which is a fact about the artifact and not a failure to measure.
    pub symbols: usize,
    /// Whether the symbol table could be read at all.
    pub measured: bool,
}

impl ReceiptAudit {
    /// The verdict. See [`ReceiptVerdict`].
    #[must_use]
    pub fn verdict(&self) -> ReceiptVerdict {
        if self.receipt_error.is_some() {
            // "Cannot read the file at all" and "read it, the receipt is broken"
            // are told apart by whether the symbol read also failed.
            return if self.measured {
                ReceiptVerdict::MalformedReceipt
            } else {
                ReceiptVerdict::NotMeasured
            };
        }
        if !self.measured {
            return ReceiptVerdict::NotMeasured;
        }
        if self.receipt.is_none() {
            return ReceiptVerdict::NoReceipt;
        }
        if self.claims.is_empty() && self.linkage.is_none() {
            return ReceiptVerdict::NothingProvable;
        }
        let claims_hold = self.claims.iter().all(ClaimAudit::holds);
        let linkage_holds = self.linkage.as_ref().is_none_or(LinkageAudit::holds);
        if claims_hold && linkage_holds {
            ReceiptVerdict::Proven
        } else {
            ReceiptVerdict::Disproven
        }
    }

    /// Whether the receipt holds.
    #[must_use]
    pub fn holds(&self) -> bool {
        self.verdict().holds()
    }

    /// The lines a report prints: one headline, then one per measured claim.
    #[must_use]
    pub fn lines(&self) -> Vec<String> {
        let name = self.artifact.display();
        let mut out = vec![match self.verdict() {
            ReceiptVerdict::NotMeasured => format!(
                "{name}: NOT MEASURED — {} — an unchecked claim is not a verified one",
                self.receipt_error
                    .as_deref()
                    .unwrap_or("the artifact is not a readable object file")
            ),
            ReceiptVerdict::NoReceipt => {
                format!("{name}: carries NO receipt — how it was built is unanswerable")
            }
            ReceiptVerdict::MalformedReceipt => format!(
                "{name}: receipt MALFORMED — {}",
                self.receipt_error.as_deref().unwrap_or("?")
            ),
            ReceiptVerdict::NothingProvable => format!(
                "{name}: receipt read, {} symbols measured, but it claims NOTHING a symbol \
                 table can check — the audit has no subject",
                self.symbols
            ),
            ReceiptVerdict::Disproven => {
                format!("{name}: the binary CONTRADICTS its receipt")
            }
            ReceiptVerdict::Proven => {
                format!("{name}: receipt proven against {} symbols ✓", self.symbols)
            }
        }];
        out.extend(self.claims.iter().map(ClaimAudit::line));
        if let Some(l) = &self.linkage {
            out.push(l.line());
        }
        out
    }
}

/// **Read an artifact's receipt and check it against the artifact.**
///
/// Never executes the artifact: the receipt comes out of a section and the proof
/// comes out of the symbol table, both through the `object` parser. This works on
/// a binary for an architecture the host could not run.
///
/// The artifact is parsed **once** for the symbol table and once for the section,
/// which is the only duplication and is the `object` crate's own read.
#[must_use]
pub fn audit_artifact(artifact: &Path) -> ReceiptAudit {
    let symbols = demangled_symbols(artifact);
    let measured = symbols.is_some();
    let symbol_count = symbols.as_ref().map_or(0, Vec::len);

    let (receipt, receipt_error) = match crate::receipt::read_receipt(artifact) {
        Ok(r) => (r, None),
        Err(e) => (None, Some(format!("{e:#}"))),
    };

    let mut claims = Vec::new();
    let mut linkage = None;
    if let Some(r) = &receipt {
        for (key, raw) in &r.settings {
            if let Some(claim) = claim_for(key) {
                claims.push(ClaimAudit::measure(key, raw, claim, symbols.as_deref()));
            }
        }
        // `linkage` is not a family count — it is the full fat/thin audit, run
        // through the module that owns it rather than reimplemented here.
        if let Some(raw) = r.settings.get(LINKAGE_KEY) {
            let shape = ServiceShape::from(raw.clone());
            linkage = LinkageAudit::of(&r.atom, shape, artifact);
        }
    }

    ReceiptAudit {
        artifact: artifact.to_path_buf(),
        receipt,
        receipt_error,
        claims,
        linkage,
        symbols: symbol_count,
        measured,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::contract::{Coordinate, RECEIPT_VERSION};
    use std::collections::BTreeMap;

    /// A real, non-glue symbol out of `nm -C dwarves-ui` (oden, 2026-08-06) —
    /// the binary that carries 5 287 `wgpu`, 2 828 `naga` and 261 `ash::vk`
    /// symbols. Verbatim, because a fixture invented to match the predicate
    /// proves only that the predicate matches its own fixture.
    const REAL_GPU: &str = "wgpu_hal::hal_usage_error::<ash::vk::enums::Result>";

    /// A second real one, from naga's validator, with no `ash` in it at all.
    const REAL_NAGA: &str = "<naga::diagnostic_filter::Severity>::report_diag::<naga::span::WithSpan<naga::valid::function::FunctionError>, <naga::valid::analyzer::FunctionInfo>::process_block::{closure#0}>";

    /// A real symbol from the SAME binary that contains `ash::` and is not a GPU
    /// symbol. This is the one that decides `ash::vk` over `ash`.
    const REAL_HASH: &str = "<hashbrown::map::HashMap<egui::id::Id, emath::rect::Rect, core::hash::BuildHasherDefault<nohash_hasher::NoHashHasher<egui::id::Id>>>>::retain::<<egui::memory::Focus>::find_widget_in_direction::{closure#0}>";

    fn syms(v: &[&str]) -> Vec<String> {
        v.iter().map(|s| (*s).to_string()).collect()
    }

    fn gpu_claim() -> &'static ProvableClaim {
        claim_for("gpu").expect("gpu is provable")
    }

    /// **LAW 2, the headline red.** `gpu = false` on a binary that links wgpu is
    /// FAILED, and the failure names the families it counted.
    #[test]
    fn a_gpu_false_receipt_over_a_gpu_binary_is_disproven() {
        let s = syms(&[REAL_GPU, REAL_NAGA]);
        let a = ClaimAudit::measure("feature.gpu", "false", gpu_claim(), Some(&s));
        assert_eq!(a.verdict(), ClaimVerdict::Disproven);
        assert!(!a.holds());
        assert!(a.line().contains("the receipt is a lie"), "{}", a.line());
        assert!(
            a.line().contains("wgpu×1"),
            "the count is named: {}",
            a.line()
        );
        assert!(a.line().contains("naga×1"), "{}", a.line());
        assert!(a.line().contains("ash::vk×1"), "{}", a.line());
    }

    /// The same claim over a binary with no GPU stack passes — so the check above
    /// is not simply always red.
    #[test]
    fn a_gpu_false_receipt_over_a_gpu_free_binary_is_proven() {
        let s = syms(&[REAL_HASH]);
        let a = ClaimAudit::measure("feature.gpu", "false", gpu_claim(), Some(&s));
        assert_eq!(
            a.verdict(),
            ClaimVerdict::Proven,
            "found={:?} — `core::hash::` must not read as a GPU stack",
            a.found
        );
        assert!(a.holds());
    }

    /// **The family-choice measurement, asserted rather than asserted-about.**
    ///
    /// `ash` as a family matches a real `core::hash::BuildHasherDefault` symbol;
    /// `ash::vk` does not. dwarves-ui carries 510 `core::hash::` symbols, so the
    /// bare word would report a GPU stack in every Rust binary compiled.
    #[test]
    fn the_bare_word_ash_would_match_core_hash_and_ash_vk_does_not() {
        let s = syms(&[REAL_HASH]);
        assert_eq!(
            count_families(&s, &["ash"]).found,
            vec![("ash".to_string(), 1)],
            "if this stops matching, the reason `ash::vk` is spelled out has gone"
        );
        assert!(
            count_families(&s, &["ash::vk"]).found.is_empty(),
            "`ash::vk` must not fire on core::hash::"
        );
        assert!(
            GPU_FAMILIES.contains(&"ash::vk") && !GPU_FAMILIES.contains(&"ash"),
            "the family list must carry the qualified form"
        );
    }

    /// The other direction of the same lie: `gpu = true` linking nothing.
    ///
    /// Without this, the `gpu = false` check could be satisfied forever by
    /// building nothing that links a GPU — the vacuous-guard shape that
    /// `EmbeddedButEmpty` exists for in the linkage audit.
    #[test]
    fn a_gpu_true_receipt_over_a_gpu_free_binary_is_disproven_too() {
        let s = syms(&[REAL_HASH]);
        let a = ClaimAudit::measure("gpu", "true", gpu_claim(), Some(&s));
        assert_eq!(a.verdict(), ClaimVerdict::Disproven);
        assert!(a.line().contains("vacuous"), "{}", a.line());

        let ok = ClaimAudit::measure("gpu", "true", gpu_claim(), Some(&syms(&[REAL_GPU])));
        assert_eq!(ok.verdict(), ClaimVerdict::Proven);
    }

    /// An unmeasured claim never passes, whatever it claimed.
    #[test]
    fn an_unmeasured_claim_never_passes() {
        for raw in ["true", "false", "maybe"] {
            let a = ClaimAudit::measure("gpu", raw, gpu_claim(), None);
            assert_eq!(a.verdict(), ClaimVerdict::NotMeasured, "{raw}");
            assert!(!a.holds(), "{raw}");
            assert!(a.line().contains("NOT MEASURED"), "{}", a.line());
        }
    }

    /// A provable key holding an unprovable value does not pass either.
    #[test]
    fn a_provable_key_with_a_non_boolean_value_does_not_pass() {
        let a = ClaimAudit::measure("gpu", "vulkan", gpu_claim(), Some(&syms(&[REAL_HASH])));
        assert_eq!(a.verdict(), ClaimVerdict::NotBoolean);
        assert!(!a.holds());
    }

    /// Both spellings of the same axis are checked, and an unrelated key is not.
    #[test]
    fn a_feature_prefixed_key_names_the_same_axis() {
        assert_eq!(claim_for("gpu"), claim_for("feature.gpu"));
        assert!(claim_for("gpu").is_some());
        assert!(claim_for("base").is_none());
        assert!(claim_for("feature.default").is_none());
    }

    fn receipt_with(settings: &[(&str, &str)]) -> Receipt {
        Receipt {
            receipt_version: RECEIPT_VERSION,
            build_thing: Coordinate::parse("korp-build-thing:0.3.1:a91f4c2e").unwrap(),
            atom: "korp-server".into(),
            target: "exe".into(),
            settings: settings
                .iter()
                .map(|(k, v)| ((*k).to_string(), (*v).to_string()))
                .collect(),
            inputs: BTreeMap::new(),
        }
    }

    fn audit_of(receipt: Option<Receipt>, measured: bool, claims: Vec<ClaimAudit>) -> ReceiptAudit {
        ReceiptAudit {
            artifact: PathBuf::from("/nope"),
            receipt,
            receipt_error: None,
            claims,
            linkage: None,
            symbols: if measured { 1 } else { 0 },
            measured,
        }
    }

    /// **The whole-artifact verdict, every non-passing outcome watched.**
    #[test]
    fn only_a_proven_receipt_passes() {
        let s = syms(&[REAL_HASH]);
        let good = ClaimAudit::measure("gpu", "false", gpu_claim(), Some(&s));
        assert!(good.holds());

        let proven = audit_of(
            Some(receipt_with(&[("gpu", "false")])),
            true,
            vec![good.clone()],
        );
        assert_eq!(proven.verdict(), ReceiptVerdict::Proven);
        assert!(proven.holds());

        // …and each way of not passing.
        let bad = ClaimAudit::measure("gpu", "false", gpu_claim(), Some(&syms(&[REAL_GPU])));
        let disproven = audit_of(Some(receipt_with(&[("gpu", "false")])), true, vec![bad]);
        assert_eq!(disproven.verdict(), ReceiptVerdict::Disproven);
        assert!(!disproven.holds());

        let unread = audit_of(Some(receipt_with(&[("gpu", "false")])), false, vec![good]);
        assert_eq!(unread.verdict(), ReceiptVerdict::NotMeasured);
        assert!(!unread.holds());

        let none = audit_of(None, true, vec![]);
        assert_eq!(none.verdict(), ReceiptVerdict::NoReceipt);
        assert!(!none.holds());

        // A receipt full of settings that claim nothing checkable.
        let nothing = audit_of(Some(receipt_with(&[("base", "tunnr")])), true, vec![]);
        assert_eq!(
            nothing.verdict(),
            ReceiptVerdict::NothingProvable,
            "an audit with no subject must not report green"
        );
        assert!(!nothing.holds());
        assert!(
            nothing.lines()[0].contains("no subject"),
            "{:?}",
            nothing.lines()
        );

        let mut broken = audit_of(None, true, vec![]);
        broken.receipt_error = Some("not a receipt".into());
        assert_eq!(broken.verdict(), ReceiptVerdict::MalformedReceipt);
        assert!(!broken.holds());
    }

    /// A `linkage` setting is routed through the audit that owns fat/thin, not a
    /// second family count here.
    #[test]
    fn a_linkage_setting_is_answered_by_the_linkage_audit() {
        // The running test binary genuinely links this crate, so a `thin`
        // declaration over the constellation's forbidden families is measurable.
        let me = std::env::current_exe().expect("test binary");
        let a = audit_artifact(&me);
        assert!(a.measured, "the test binary parses as an object file");
        assert!(a.symbols > 0, "and it has a symbol table: {}", a.symbols);
        // No receipt is embedded in the test binary, so the verdict is NoReceipt —
        // stated here so this test's subject is unambiguous.
        assert_eq!(a.verdict(), ReceiptVerdict::NoReceipt);
    }

    /// **The real-binary red.** The running test binary demonstrably contains
    /// this crate's own symbols, so a receipt claiming a family that is really
    /// there must be disproven when it says `false`.
    ///
    /// This is the guard pointed at a binary it did not construct: if the symbol
    /// read silently returned nothing, the `false` claim would pass and this
    /// test would go green — so the paired `true` assertion is what makes it able
    /// to report red.
    #[test]
    fn a_false_claim_about_a_family_really_in_this_binary_is_disproven() {
        let me = std::env::current_exe().expect("test binary");
        let Some(symbols) = demangled_symbols(&me) else {
            panic!("the running test binary must parse as an object file");
        };
        static SELF_CLAIM: ProvableClaim = ProvableClaim {
            key: "gpu",
            families: &["nornir_build_thing"],
            what: "this crate",
        };
        let n = count_families(&symbols, SELF_CLAIM.families)
            .found
            .first()
            .map_or(0, |(_, n)| *n);
        assert!(n > 0, "this crate's own symbols are in its test binary");

        let lying = ClaimAudit::measure("gpu", "false", &SELF_CLAIM, Some(&symbols));
        assert_eq!(
            lying.verdict(),
            ClaimVerdict::Disproven,
            "a `false` over {n} real symbols must fail"
        );
        let honest = ClaimAudit::measure("gpu", "true", &SELF_CLAIM, Some(&symbols));
        assert_eq!(honest.verdict(), ClaimVerdict::Proven);
    }
}