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);
}
}