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

//! **The build history — `znippy-build-thing`.** The attestation half of the
//! 2026-08-06 design, and the step that happens *after* the build and *before*
//! the release.
//!
//! The receipt half already works: a built artifact carries a [`Receipt`] in
//! [`RECEIPT_SECTION`] naming the recipe that built it and every resolved
//! setting. This module is the other direction.
//!
//! ```text
//! 1  the recipe crate is published    wave-0, before the thing it builds
//! 2  the build runs                   the artifact gets its receipt
//! 3  znippy-build-thing is created    history / audit log, BEFORE release
//! 4  release                          the artifact goes out carrying itself
//! ```
//!
//! # What it is: an attestation, not an index
//!
//! ```text
//! A1..An   ->  recipe + version + hash    in the section, self-contained
//! znippy   ->  the recipe + params + hash(A1..An)    aggregate, derived
//! ```
//!
//! One [`BuildAttestation`] says *recipe R with parameters P produced artifact
//! hash H*. Anyone holding the artifact can check it: hash the binary, compare.
//! No trust is required and nothing has to be online. That is the difference
//! between an attestation and an index — an index has to be believed.
//!
//! It also answers the one question an artifact never can. An artifact says
//! *which recipe built me*; the history says **what did this recipe build**,
//! which is exactly the question a CVE in a recipe asks.
//!
//! # No back-pointer, and there cannot be one
//!
//! Nothing in the artifact points at the package. The package is written after
//! the build, so the artifact's own hash does not exist until the artifact is
//! finished — a forward pointer would be a chicken-and-egg. Everything points
//! one way: the artifact names its recipe, the package names the artifact.
//!
//! # Derived, never a source of truth
//!
//! Lose the package and it regenerates from the artifacts. That is not an
//! aspiration here: [`HistoryPackage::from_artifacts`] is the **only**
//! constructor, so creating and regenerating are the same code path and cannot
//! drift. A package is a pure function of the *bytes* of the artifacts it
//! covers — see [`ArtifactRef`] for why the file name is deliberately not in it.
//!
//! # The package layout
//!
//! Files inside the `.znippy`, and the whole contract with holger's serving
//! backend. holger maps HTTP paths onto these paths and parses none of them —
//! it serves bytes, so there is no shared type and nothing to drift.
//!
//! ```text
//! index.json                     the manifest: one row per attestation
//! attestations/<sha256>.json     one attestation per artifact, by its hash
//! by-recipe/<krate>              artifact hashes this recipe built, any version
//! by-recipe/<krate>/<version>    artifact hashes this exact recipe version built
//! by-atom/<atom>                 artifact hashes carrying this atom name
//! ```
//!
//! The `by-*` files are **derived**, and [`HistoryPackage::from_files`]
//! recomputes them rather than reading them: a hand-edited lookup that disagrees
//! with the attestations is a package that lies about what a recipe built, which
//! is the one question this package exists to answer.
//!
//! [`RECEIPT_SECTION`]: crate::contract::RECEIPT_SECTION

use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::path::{Path, PathBuf};

use serde::{Deserialize, Serialize};

use crate::contract::Receipt;
use crate::receipt::read_receipt;

/// The package kind, written into [`INDEX_PATH`] and named by holger's backend.
pub const PACKAGE_KIND: &str = "znippy-build-thing";

/// The package schema version. A reader meeting a higher number refuses rather
/// than guessing, for the same reason the receipt does: a misread history
/// answers "what did this recipe build" *wrongly*, and a wrong answer to that
/// question during a CVE sweep is worse than no answer.
pub const PACKAGE_VERSION: u32 = 1;

/// The attestation schema version, written into every [`BuildAttestation`].
pub const ATTESTATION_VERSION: u32 = 1;

/// The manifest's path inside the archive.
pub const INDEX_PATH: &str = "index.json";

/// The directory holding one attestation per artifact hash.
pub const ATTESTATION_DIR: &str = "attestations";

/// The directory holding the derived recipe → artifacts lookup.
pub const BY_RECIPE_DIR: &str = "by-recipe";

/// The directory holding the derived atom → artifacts lookup.
pub const BY_ATOM_DIR: &str = "by-atom";

// ── the attested artifact ────────────────────────────────────────────────────

/// The artifact an attestation is about: its content hash and its size.
///
/// # Why there is no file name here
///
/// A file name is not a property of the thing built, it is a property of where
/// somebody put it. Putting it in the attestation would make regeneration
/// path-dependent — copy the binary, rename it, regenerate, and you get a
/// different package for the same artifact. The claim being made is about
/// *bytes*: recipe R produced these bytes. The name the bytes are sitting under
/// is the caller's business, and the atom they belong to is already in the
/// receipt.
///
/// `bytes` is carried because it costs nothing and makes a truncated download
/// visible before the hash is computed over 16 MB.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ArtifactRef {
    /// Lowercase-hex SHA-256 over the whole artifact file.
    pub sha256: String,
    /// The artifact's size in bytes.
    pub bytes: u64,
}

// ── the symbol audit, carried as text ────────────────────────────────────────

/// **The proof, travelling as text instead of as a symbol table.**
///
/// `[profile.release] strip = true` deletes the symbol table from the delivered
/// artifact. [`receipt_proof`](crate::receipt_proof) then answers `NotMeasured`
/// for it — correctly, and forever — which means the thing we hand a customer
/// carries a claim nobody can check. Measured on gunnar-server, oden 2026-08-06:
///
/// ```text
/// strip = true          16 001 760 B   NotMeasured   no symbol table
/// strip = "debuginfo"   19 190 152 B   Proven        26 075 symbols
///                       +3 188 392 B = +19,9 %
/// ```
///
/// The SLSA answer, and the one the owner took: keep the artifact small, run the
/// audit at build time on a binary that still has its symbols, and let the
/// **verdict** travel. That is what an attestation is — the builder testifies to
/// what it saw, rather than every receiver re-measuring.
///
/// # It is anchored to bytes, not to a file name
///
/// [`SymbolProof::code`] is the SHA-256 over the *allocated* sections — see
/// [`code_digest`](crate::code_digest). Strip removes nothing allocated, so the
/// unstripped binary that was audited and the stripped artifact that ships carry
/// the same number, and [`attest_proven`] refuses to write a proof when they do
/// not. That is what makes "we audited the binary this came from" a measurement
/// rather than a claim about a path.
///
/// The receipt is inside that digest too: `.nordisk.buildthing` is an allocated
/// section, so an equal code digest already means the two binaries carry the
/// same receipt, byte for byte.
///
/// # What is genuinely lost, and it is written down here
///
/// [`SymbolProof::outsider`] is not prose about the design — it is the field an
/// artifact's holder reads to find out what they can do themselves. With
/// `recomputable = false` they can still verify every digest in this attestation
/// against the bytes in their hand; they cannot re-run the symbol audit, because
/// the symbols are gone. That is the trade, and it is in the data.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SymbolProof {
    /// The verdict [`audit_artifact`](crate::receipt_proof::audit_artifact)
    /// reached, verbatim.
    pub verdict: crate::receipt_proof::ReceiptVerdict,
    /// One entry per receipt setting the symbol table can check.
    pub claims: Vec<ProofClaim>,
    /// The fat/thin audit's own sentence, when the receipt declared `linkage`.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub linkage: Option<String>,
    /// How many symbols the audited binary carried. `0` cannot happen: a binary
    /// with no symbols is [`Unprovable::NotMeasured`] and no proof is written.
    pub symbols: usize,
    /// The binary the audit actually read — hash and size, never a path.
    pub audited: ArtifactRef,
    /// The allocated-section digest. Equal for the audited binary and the
    /// shipped artifact, by construction: [`attest_proven`] refuses otherwise.
    pub code: crate::code_digest::CodeDigest,
    /// Whether a holder of the shipped artifact could redo this audit.
    ///
    /// `true` only when the shipped artifact still has a symbol table, i.e. it
    /// *is* the audited binary. `false` under `strip = true`, which is the
    /// normal delivery and the whole reason this type exists.
    pub recomputable: bool,
    /// What someone holding only the artifact can and cannot check.
    pub outsider: Outsider,
}

/// One receipt claim, measured, in a form that survives the symbol table's
/// deletion.
///
/// `evidence` carries the families and their counts (`wgpu×5287, naga×2828`)
/// rather than a bare verdict: a `Disproven` with nothing behind it is an
/// accusation, and the person reading it a year later cannot act on it.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProofClaim {
    /// The settings key as the receipt spells it, `feature.` prefix and all.
    pub key: String,
    /// The value the receipt records.
    pub claimed: String,
    /// What the binary said about it.
    pub verdict: crate::receipt_proof::ClaimVerdict,
    /// The families counted, `wgpu×5287, naga×2828`. Empty when none matched —
    /// which is the evidence for a `false` claim that holds.
    pub evidence: String,
    /// The sentence a report prints, as the audit itself worded it.
    pub line: String,
}

/// **What a holder of the artifact can and cannot verify for themselves.**
///
/// Written into every proof rather than left to a design document, because the
/// person who needs it is holding a binary and a JSON file in 2029 and has
/// neither the tree nor the PLAN. Both lists are derived by [`Outsider::of`]
/// from the proof's own fields, so they cannot say one thing while the fields
/// say another — [`BuildAttestation::verify`] rechecks exactly that.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Outsider {
    /// Checks that need only the artifact and this attestation.
    pub can: Vec<String>,
    /// Checks that are no longer possible, and why.
    pub cannot: Vec<String>,
}

impl Outsider {
    /// The two lists for a proof in this state. The **one** writer.
    #[must_use]
    pub fn of(recomputable: bool, verdict: crate::receipt_proof::ReceiptVerdict) -> Self {
        let mut can = vec![
            "hash the artifact and compare it to `artifact.sha256` — this attestation is \
             about those bytes or about nothing"
                .to_string(),
            "read the receipt out of the artifact's .nordisk.buildthing section with an \
             object parser and compare it to `receipt`, without running the artifact"
                .to_string(),
            "recompute `proof.code` over the artifact's allocated sections and compare it \
             to the value here — that is what ties this audit to these instructions"
                .to_string(),
        ];
        let mut cannot = Vec::new();
        if recomputable {
            can.push(
                "re-run the symbol audit: this artifact still carries its symbol table, so \
                 `proof.verdict` is reproducible rather than testimony"
                    .to_string(),
            );
        } else {
            cannot.push(
                "re-run the symbol audit. The artifact was stripped, its symbol table is \
                 gone, and `proof.verdict` is the builder's testimony about a binary you do \
                 not have. Re-deriving it independently means rebuilding from `receipt` with \
                 strip off"
                    .to_string(),
            );
            cannot.push(format!(
                "tell this artifact apart from one whose receipt was never checked, from the \
                 artifact alone: audited it reports {}, unaudited it would report \
                 not-measured, and both look identical once stripped",
                verdict_word(verdict),
            ));
        }
        cannot.push(
            "learn who built it. This is an attestation, not a signature — it says two \
             files carry the same program, never who made them"
                .to_string(),
        );
        Self { can, cannot }
    }
}

/// The verdict's own serde spelling, so a message and the JSON never disagree.
fn verdict_word(v: crate::receipt_proof::ReceiptVerdict) -> String {
    serde_json::to_string(&v).unwrap_or_else(|_| "\"?\"".to_string())
}

impl SymbolProof {
    /// Whether the receipt survived contact with the binary.
    ///
    /// Only `Proven` passes. `NotMeasured` cannot even reach this type —
    /// [`attest_proven`] refuses to write it — and every other verdict is a fact
    /// about a build that did not come out as declared.
    #[must_use]
    pub fn holds(&self) -> bool {
        self.verdict.holds()
    }

    /// The lines a report prints: the headline, then one per claim.
    #[must_use]
    pub fn lines(&self) -> Vec<String> {
        let short = |h: &str| h.chars().take(12).collect::<String>();
        let mut out = vec![format!(
            "symbol proof: {} over {} symbols of unstripped {} ({} B), code {}{}",
            verdict_word(self.verdict),
            self.symbols,
            short(&self.audited.sha256),
            self.audited.bytes,
            short(&self.code.sha256),
            if self.recomputable {
                ""
            } else {
                " — NOT recomputable from the shipped artifact, which is stripped"
            }
        )];
        out.extend(self.claims.iter().map(|c| c.line.clone()));
        if let Some(l) = &self.linkage {
            out.push(l.clone());
        }
        out
    }
}

/// A reason a build-time symbol audit could not be turned into a proof.
///
/// Each one is a wiring mistake that would otherwise produce an attestation
/// saying "audited" over an audit that did not happen — worse than no
/// attestation, because it reads as evidence.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Unprovable {
    /// The shipped artifact could not be attested at all — see [`Unattestable`].
    Unattestable(Unattestable),
    /// One of the two files could not be parsed, or has no allocated sections.
    Unreadable {
        /// Which file.
        artifact: PathBuf,
        /// Why.
        why: String,
    },
    /// The audited binary and the shipped artifact are **not the same program**.
    /// Their allocated sections differ, so the audit says nothing about what
    /// ships.
    DifferentProgram {
        /// The digest of the binary that was audited.
        audited: String,
        /// The digest of the artifact that ships.
        shipped: String,
        /// Which allocated sections differ, by name — the diagnosis.
        sections: Vec<String>,
    },
    /// The binary handed in as the audited one has no symbol table either, so
    /// nothing was measured. **Never written as a proof.**
    NotMeasured {
        /// The binary that was supposed to carry symbols.
        audited: PathBuf,
    },
    /// [`deliver_stripped`] could not produce the shipped artifact.
    StripFailed {
        /// Where the shipped artifact was to be written.
        shipped: PathBuf,
        /// Why.
        why: String,
    },
}

impl fmt::Display for Unprovable {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Unattestable(u) => write!(f, "{u}"),
            Self::Unreadable { artifact, why } => {
                write!(f, "{}: NOT MEASURED: {why}", artifact.display())
            }
            Self::DifferentProgram {
                audited,
                shipped,
                sections,
            } => write!(
                f,
                "the audited binary and the shipped artifact are different programs — \
                 allocated sections hash to {audited} and {shipped}, differing in [{}]. An \
                 audit of one says nothing about the other, so no proof is written",
                sections.join(", ")
            ),
            Self::NotMeasured { audited } => write!(
                f,
                "{}: has no symbol table, so the audit measured nothing. A proof saying \
                 `audited` over an audit that did not happen reads as evidence and is not — \
                 build the audited copy with strip off",
                audited.display()
            ),
            Self::StripFailed { shipped, why } => write!(
                f,
                "could not produce the stripped artifact at {}: {why}",
                shipped.display()
            ),
        }
    }
}

/// The strippers tried, in order, by [`deliver_stripped`].
///
/// Each one removes sections from an existing file and re-writes the section
/// header table. **None of them re-links**, which is the entire property being
/// relied on: allocated sections come through byte-identical, so the artifact
/// that ships is provably the program that was audited.
pub const STRIPPERS: &[(&str, &[&str])] = &[
    ("llvm-strip", &["--strip-all"]),
    ("strip", &["--strip-all"]),
    ("objcopy", &["--strip-all"]),
];

/// **Build once, audit, then strip — the delivery path that cannot drift.**
///
/// Copies `audited` to `shipped`, removes the symbol table from the copy, and
/// attests the pair with [`attest_proven`].
///
/// # Why this exists rather than "just build it twice"
///
/// Because building it twice does not produce the same program, and that was
/// measured rather than feared. One fixture, one directory, one source, the only
/// difference `[profile.release] strip`, oden 2026-08-06:
///
/// ```text
/// strip = false  ->  strip = true      .text DIFFERS, .data.rel.ro DIFFERS
///                                      same size, same address, other bytes
/// strip = false  ->  objcopy --strip-all
///                                      EVERY allocated section identical,
///                                      306 984 B against rustc's 306 992 B
/// ```
///
/// So `strip` is not only a link-time deletion in this toolchain: flipping it
/// changes the code that comes out. An audit of an unstripped *rebuild* is
/// therefore an audit of a different program, and [`attest_proven`] would
/// correctly refuse it — leaving the delivery unprovable again. Stripping the
/// audited binary itself removes the rebuild, and with it the whole question.
///
/// A second build was also the slower answer: this way the delivery compiles
/// once.
///
/// # `[profile.release] strip = true` stays where it is
///
/// It is what a plain `cargo build --release` should keep doing. The delivery
/// path overrides it for its one compilation (`CARGO_PROFILE_RELEASE_STRIP=none`)
/// and does the stripping here, so the artifact that goes out is the same size —
/// eight bytes smaller, measured — and is anchored to an audit.
///
/// # Errors
///
/// [`Unprovable::StripFailed`] when the copy or the stripper fails, and anything
/// [`attest_proven`] refuses.
pub fn deliver_stripped(audited: &Path, shipped: &Path) -> Result<BuildAttestation, Unprovable> {
    let fail = |why: String| Unprovable::StripFailed {
        shipped: shipped.to_path_buf(),
        why,
    };
    std::fs::copy(audited, shipped).map_err(|e| {
        fail(format!(
            "copy {} -> {}: {e}",
            audited.display(),
            shipped.display()
        ))
    })?;

    let mut tried = Vec::new();
    let stripped = STRIPPERS.iter().any(|(tool, args)| {
        match std::process::Command::new(tool)
            .args(*args)
            .arg(shipped)
            .output()
        {
            Ok(out) if out.status.success() => true,
            Ok(out) => {
                tried.push(format!(
                    "{tool}: {}",
                    String::from_utf8_lossy(&out.stderr).trim()
                ));
                false
            }
            Err(e) => {
                tried.push(format!("{tool}: {e}"));
                false
            }
        }
    });
    if !stripped {
        return Err(fail(format!(
            "no stripper worked — tried {}",
            tried.join("; ")
        )));
    }
    attest_proven(shipped, audited)
}

impl std::error::Error for Unprovable {}

/// **Attest a shipped artifact, and carry the symbol audit of the unstripped
/// binary it came from.**
///
/// The build-time call. `shipped` is what goes out — stripped, small, and
/// unprovable on its own. `audited` is the same program built with the symbol
/// table left in; the two are tied together by
/// [`code_digest`](crate::code_digest::code_digest), never by their names.
///
/// A `Disproven` audit is **returned, not refused**: a receipt that lies is the
/// single most valuable thing this lane can produce, and it has to reach the
/// caller as data. The caller fails the build on `!proof_holds()`. What is
/// refused is an audit that never happened — see [`Unprovable`].
///
/// # Errors
///
/// [`Unprovable`], one variant per way the proof would be a lie about itself.
pub fn attest_proven(shipped: &Path, audited: &Path) -> Result<BuildAttestation, Unprovable> {
    use crate::code_digest::code_digest;
    use crate::linkage::demangled_symbols;
    use crate::receipt_proof::{audit_artifact, ReceiptVerdict};

    let mut attestation = attest(shipped).map_err(Unprovable::Unattestable)?;

    let shipped_code = code_digest(shipped).map_err(|e| Unprovable::Unreadable {
        artifact: shipped.to_path_buf(),
        why: format!("{e:#}"),
    })?;
    let audited_code = code_digest(audited).map_err(|e| Unprovable::Unreadable {
        artifact: audited.to_path_buf(),
        why: format!("{e:#}"),
    })?;
    if !shipped_code.same_program(&audited_code) {
        return Err(Unprovable::DifferentProgram {
            audited: audited_code.to_string(),
            shipped: shipped_code.to_string(),
            // Named, because "different" over two 16 MB files is not a finding
            // anyone can act on and the section list always is. Best-effort: the
            // refusal stands whether or not the second parse succeeds.
            sections: crate::code_digest::differing_sections(audited, shipped).unwrap_or_default(),
        });
    }

    // The audit reads the binary that still has symbols. Everything measured
    // here comes out of `receipt_proof`, which comes out of `linkage`: one
    // counting rule, so there is no second answer to drift from it.
    let audit = audit_artifact(audited);
    let verdict = audit.verdict();
    if verdict == ReceiptVerdict::NotMeasured {
        return Err(Unprovable::NotMeasured {
            audited: audited.to_path_buf(),
        });
    }

    let audited_bytes = std::fs::read(audited).map_err(|e| Unprovable::Unreadable {
        artifact: audited.to_path_buf(),
        why: e.to_string(),
    })?;

    // Asked of the SHIPPED artifact, which is the only thing an outsider holds.
    let recomputable = demangled_symbols(shipped).is_some();

    attestation.proof = Some(SymbolProof {
        verdict,
        claims: audit
            .claims
            .iter()
            .map(|c| ProofClaim {
                key: c.key.clone(),
                claimed: c.raw.clone(),
                verdict: c.verdict(),
                evidence: c.families(),
                line: c.line(),
            })
            .collect(),
        linkage: audit
            .linkage
            .as_ref()
            .map(crate::linkage::LinkageAudit::line),
        symbols: audit.symbols,
        audited: ArtifactRef {
            sha256: nornir_hash::sha256_hex(&audited_bytes),
            bytes: audited_bytes.len() as u64,
        },
        code: shipped_code,
        recomputable,
        outsider: Outsider::of(recomputable, verdict),
    });
    Ok(attestation)
}

/// One attestation: *this recipe, with these parameters, produced these bytes.*
///
/// The receipt is carried **verbatim** as it was read out of the artifact rather
/// than restated field by field. Restating it would be a second writer of the
/// same facts, and a second writer drifts; carrying it verbatim also makes
/// regeneration byte-identical by construction, because the bytes came out of
/// the artifact both times.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BuildAttestation {
    /// [`ATTESTATION_VERSION`] at the time of writing.
    pub attestation_version: u32,
    /// The receipt, exactly as the artifact carries it.
    pub receipt: Receipt,
    /// What the recipe produced.
    pub artifact: ArtifactRef,
    /// **The symbol audit, performed at build time and carried as text.**
    ///
    /// `None` for an artifact attested from its bytes alone — which is what
    /// [`HistoryPackage::from_artifacts`] always produces, because a stripped
    /// artifact has no symbol table left to audit. Present only when the builder
    /// went through [`attest_proven`] with the unstripped binary in hand.
    ///
    /// Skipped when absent so an attestation without a proof serialises to
    /// exactly the bytes it did before this field existed.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub proof: Option<SymbolProof>,
}

impl BuildAttestation {
    /// The archive path this attestation is stored at.
    #[must_use]
    pub fn path(&self) -> String {
        format!("{ATTESTATION_DIR}/{}.json", self.artifact.sha256)
    }

    /// Check this attestation against an artifact on disk.
    ///
    /// This is the whole point of the package being an attestation: the check
    /// needs the artifact and nothing else — no registry, no network, no
    /// signature to trust.
    #[must_use]
    pub fn verify(&self, artifact: &Path) -> AttestationVerdict {
        let bytes = match std::fs::read(artifact) {
            Ok(b) => b,
            Err(e) => {
                return AttestationVerdict::Unreadable(format!("{}: {e}", artifact.display()));
            }
        };
        let actual = nornir_hash::sha256_hex(&bytes);
        if actual != self.artifact.sha256 {
            return AttestationVerdict::ArtifactMismatch {
                attested: self.artifact.sha256.clone(),
                actual,
            };
        }
        // The receipt is inside the bytes we just hashed, so a matching hash
        // already implies a matching receipt — unless the *package* was edited.
        // That is the case this arm exists for, and it is cheap.
        match read_receipt(artifact) {
            Ok(Some(r)) if r == self.receipt => {}
            Ok(Some(_)) => return AttestationVerdict::ReceiptMismatch,
            Ok(None) => {
                return AttestationVerdict::Unreadable(format!(
                    "{} carries no receipt, but its hash matches an attestation",
                    artifact.display()
                ));
            }
            Err(e) => {
                return AttestationVerdict::Unreadable(format!("{}: {e}", artifact.display()));
            }
        }
        // And the proof, when there is one. Same reasoning one level down: a
        // matching artifact hash does not prove the *proof* was not edited, and
        // the proof is the one part of the package nobody downstream can
        // recompute. So both halves are rechecked here — the code digest against
        // the artifact's own sections, and the outsider notice against the
        // fields it is supposed to describe.
        if let Some(p) = &self.proof {
            let actual = match crate::code_digest::code_digest(artifact) {
                Ok(d) => d,
                Err(e) => {
                    return AttestationVerdict::Unreadable(format!(
                        "{}: {e:#}",
                        artifact.display()
                    ));
                }
            };
            if !actual.same_program(&p.code) {
                return AttestationVerdict::CodeMismatch {
                    attested: p.code.to_string(),
                    actual: actual.to_string(),
                };
            }
            if p.outsider != Outsider::of(p.recomputable, p.verdict) {
                return AttestationVerdict::ProofTampered;
            }
        }
        AttestationVerdict::Attested
    }

    /// Whether the receipt was PROVEN against a symbol table.
    ///
    /// Deliberately not folded into [`verify`](Self::verify): "is this the
    /// artifact this attestation is about" and "did this artifact's receipt
    /// survive its symbol table" are two questions, and an artifact can be
    /// unambiguously the right one and still carry a receipt that lies.
    ///
    /// `false` when there is no proof at all — an unmeasured claim is not a
    /// verified one, and a missing proof is exactly that.
    #[must_use]
    pub fn proof_holds(&self) -> bool {
        self.proof.as_ref().is_some_and(SymbolProof::holds)
    }
}

/// What checking an attestation against an artifact concluded.
///
/// [`AttestationVerdict::Unreadable`] is deliberately not a pass and not a
/// failure of the artifact: an unmeasured check is never an approved check.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AttestationVerdict {
    /// The bytes hash to the attested value and carry the attested receipt.
    Attested,
    /// The artifact is not the one attested.
    ArtifactMismatch {
        /// The hash the package claims.
        attested: String,
        /// The hash the bytes actually have.
        actual: String,
    },
    /// The hash matched but the receipt in the artifact is not the attested
    /// one — the package was edited after it was written.
    ReceiptMismatch,
    /// The artifact's allocated sections do not hash to the digest the proof was
    /// anchored to: the audit in this attestation was performed on a different
    /// program.
    CodeMismatch {
        /// The digest the proof claims.
        attested: String,
        /// The digest the artifact's sections actually have.
        actual: String,
    },
    /// The proof's outsider notice does not describe the proof's own fields —
    /// somebody edited the prose without the verdict, or the other way round.
    ProofTampered,
    /// Nothing was measured. Never a pass.
    Unreadable(String),
}

impl AttestationVerdict {
    /// `true` only for [`AttestationVerdict::Attested`].
    #[must_use]
    pub const fn holds(&self) -> bool {
        matches!(self, Self::Attested)
    }
}

impl fmt::Display for AttestationVerdict {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Attested => write!(f, "ATTESTED"),
            Self::ArtifactMismatch { attested, actual } => write!(
                f,
                "ARTIFACT MISMATCH: the package attests {attested} and the bytes hash to {actual}"
            ),
            Self::ReceiptMismatch => write!(
                f,
                "RECEIPT MISMATCH: the artifact's own receipt is not the attested one — \
                 the package was edited after it was written"
            ),
            Self::CodeMismatch { attested, actual } => write!(
                f,
                "CODE MISMATCH: the proof was anchored to {attested} and this artifact's \
                 allocated sections are {actual} — the symbol audit in this attestation was \
                 performed on a different program"
            ),
            Self::ProofTampered => write!(
                f,
                "PROOF TAMPERED: the outsider notice does not describe the proof's own \
                 verdict and recomputability — one of the two was edited"
            ),
            Self::Unreadable(why) => write!(f, "NOT MEASURED: {why}"),
        }
    }
}

// ── why an artifact cannot be attested ───────────────────────────────────────

/// A reason an artifact was not put in the package, named rather than skipped.
///
/// Every variant is a build whose history would be silently missing. A history
/// that quietly covers four of five artifacts is worse than one that covers
/// none, because the fifth looks attested until somebody asks.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Unattestable {
    /// The artifact carries no receipt at all. Either it was not built by a
    /// build thing, or the writer was not wired in.
    NoReceipt(PathBuf),
    /// The receipt names a recipe by version but not by content hash, so the
    /// attestation could not say *which bytes of the recipe* ran.
    UnpinnedRecipe {
        /// The artifact.
        artifact: PathBuf,
        /// The coordinate as the receipt spells it.
        coordinate: String,
    },
    /// A receipt field that becomes an archive path holds a path separator or a
    /// `..` component. Refused rather than sanitised — a name that has to be
    /// repaired to be safe is a name nobody meant to write.
    UnsafeName {
        /// The artifact.
        artifact: PathBuf,
        /// Which field.
        field: &'static str,
        /// The offending value.
        value: String,
    },
    /// The file could not be read or parsed as an object file. Distinct from
    /// [`Unattestable::NoReceipt`]: nothing was measured.
    Unreadable {
        /// The artifact.
        artifact: PathBuf,
        /// Why.
        why: String,
    },
}

impl fmt::Display for Unattestable {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::NoReceipt(p) => write!(
                f,
                "{}: carries no {} section — nothing attests what built it",
                p.display(),
                crate::contract::RECEIPT_SECTION
            ),
            Self::UnpinnedRecipe {
                artifact,
                coordinate,
            } => write!(
                f,
                "{}: receipt names {coordinate} with no content hash — the version says which \
                 recipe was intended, only the hash says which one ran",
                artifact.display()
            ),
            Self::UnsafeName {
                artifact,
                field,
                value,
            } => write!(
                f,
                "{}: receipt field `{field}` is {value:?}, which cannot be an archive path",
                artifact.display()
            ),
            Self::Unreadable { artifact, why } => {
                write!(f, "{}: NOT MEASURED: {why}", artifact.display())
            }
        }
    }
}

/// Is `s` safe to use as a single archive path component?
fn safe_component(s: &str) -> bool {
    !s.is_empty()
        && s != "."
        && s != ".."
        && !s.contains('/')
        && !s.contains('\\')
        && !s.contains('\0')
}

// ── the package ──────────────────────────────────────────────────────────────

/// One row of [`INDEX_PATH`]. Flat on purpose: the manifest is a table a human
/// or a shell can read, and the full attestation is one fetch away at `path`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IndexRow {
    /// The artifact's content hash — the package's primary key.
    pub artifact_sha256: String,
    /// The artifact's size in bytes.
    pub artifact_bytes: u64,
    /// The recipe coordinate, `krate:version:hash`.
    pub build_thing: String,
    /// The recipe crate alone, so a CVE sweep does not have to split a string.
    pub build_thing_krate: String,
    /// The recipe version alone.
    pub build_thing_version: String,
    /// The atom the artifact is.
    pub atom: String,
    /// The delivery target (`exe`, `container`, `iso-installer`).
    pub target: String,
    /// Where the full attestation sits inside the archive.
    pub path: String,
}

/// The manifest at [`INDEX_PATH`].
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Index {
    /// Always [`PACKAGE_KIND`].
    pub kind: String,
    /// [`PACKAGE_VERSION`] at the time of writing.
    pub package_version: u32,
    /// One row per attestation, sorted by `artifact_sha256`.
    pub attestations: Vec<IndexRow>,
}

/// dwarves' build history: the attestations for a set of built artifacts.
///
/// Entries are kept sorted by artifact hash and deduplicated, so two callers
/// handing in the same artifacts in different orders produce byte-identical
/// package files. That is what makes "lose it and regenerate it" a check you can
/// run rather than a promise.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct HistoryPackage {
    entries: Vec<BuildAttestation>,
}

impl HistoryPackage {
    /// **The only constructor**, and therefore both the create path and the
    /// regeneration path.
    ///
    /// Returns the package alongside every artifact that could not be attested
    /// and why. The refusals are returned rather than logged because the caller
    /// — the release gate — has to be able to fail on them.
    ///
    /// Artifacts that hash alike are the same bytes and therefore carry the same
    /// receipt, so handing the same artifact in twice is not an error; it
    /// collapses to one entry.
    #[must_use]
    pub fn from_artifacts<P: AsRef<Path>>(artifacts: &[P]) -> (Self, Vec<Unattestable>) {
        let mut by_hash: BTreeMap<String, BuildAttestation> = BTreeMap::new();
        let mut refused = Vec::new();
        for p in artifacts {
            let path = p.as_ref();
            match attest(path) {
                Ok(a) => {
                    by_hash.insert(a.artifact.sha256.clone(), a);
                }
                Err(u) => refused.push(u),
            }
        }
        (
            Self {
                entries: by_hash.into_values().collect(),
            },
            refused,
        )
    }

    /// A package over attestations already built — the path a **proof** takes.
    ///
    /// [`from_artifacts`](Self::from_artifacts) stays the pure function of the
    /// artifacts' bytes, and that is exactly why it can never carry a proof: the
    /// bytes it is handed are stripped, and the symbols are gone. A builder that
    /// ran [`attest_proven`] holds something the bytes no longer contain, and
    /// this is where it puts it.
    ///
    /// The consequence is stated rather than hidden: **regenerating a package
    /// from the shipped artifacts recovers everything except the proofs.**
    /// `history_end_to_end.rs` asserts that in both directions, so the day the
    /// proof becomes recomputable the test says so.
    ///
    /// Same de-duplication as the other constructor: attestations naming the
    /// same artifact hash collapse, and the entries come out sorted so two
    /// orders produce byte-identical files.
    #[must_use]
    pub fn from_attestations(attestations: Vec<BuildAttestation>) -> Self {
        let by_hash: BTreeMap<String, BuildAttestation> = attestations
            .into_iter()
            .map(|a| (a.artifact.sha256.clone(), a))
            .collect();
        Self {
            entries: by_hash.into_values().collect(),
        }
    }

    /// Every attestation, sorted by artifact hash.
    #[must_use]
    pub fn attestations(&self) -> &[BuildAttestation] {
        &self.entries
    }

    /// How many artifacts this history covers.
    #[must_use]
    pub fn len(&self) -> usize {
        self.entries.len()
    }

    /// `true` when the history covers nothing.
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }

    /// The attestation for an artifact hash, if the history covers it.
    #[must_use]
    pub fn by_artifact(&self, sha256: &str) -> Option<&BuildAttestation> {
        self.entries
            .iter()
            .find(|a| a.artifact.sha256.eq_ignore_ascii_case(sha256))
    }

    /// **What did this recipe build?** — the question the artifact cannot answer.
    ///
    /// `version` of `None` means every version of the recipe crate, which is the
    /// shape a CVE sweep wants: the advisory names a crate and a range, not one
    /// version.
    #[must_use]
    pub fn by_recipe(&self, krate: &str, version: Option<&str>) -> Vec<&BuildAttestation> {
        self.entries
            .iter()
            .filter(|a| a.receipt.build_thing.krate == krate)
            .filter(|a| version.is_none_or(|v| a.receipt.build_thing.version == v))
            .collect()
    }

    /// Every artifact built for a given atom.
    #[must_use]
    pub fn by_atom(&self, atom: &str) -> Vec<&BuildAttestation> {
        self.entries
            .iter()
            .filter(|a| a.receipt.atom == atom)
            .collect()
    }

    /// The manifest.
    #[must_use]
    pub fn index(&self) -> Index {
        Index {
            kind: PACKAGE_KIND.to_string(),
            package_version: PACKAGE_VERSION,
            attestations: self
                .entries
                .iter()
                .map(|a| IndexRow {
                    artifact_sha256: a.artifact.sha256.clone(),
                    artifact_bytes: a.artifact.bytes,
                    build_thing: a.receipt.build_thing.to_string(),
                    build_thing_krate: a.receipt.build_thing.krate.clone(),
                    build_thing_version: a.receipt.build_thing.version.clone(),
                    atom: a.receipt.atom.clone(),
                    target: a.receipt.target.clone(),
                    path: a.path(),
                })
                .collect(),
        }
    }

    /// The package's files, ready for `znippy_common::create_archive`.
    ///
    /// This crate stays dependency-light and seals nothing itself: it returns
    /// `(path, bytes)` pairs, which is exactly what znippy's archive writer
    /// takes. The sealing happens in the crate that already links znippy.
    ///
    /// Sorted by path, so two runs over the same artifacts write the same
    /// archive.
    ///
    /// # Errors
    ///
    /// Only if an attestation fails to serialise, which would be a bug in this
    /// crate rather than anything about the input.
    pub fn files(&self) -> serde_json::Result<Vec<(String, Vec<u8>)>> {
        let mut files: Vec<(String, Vec<u8>)> = Vec::with_capacity(self.entries.len() + 8);
        files.push((
            INDEX_PATH.to_string(),
            serde_json::to_vec_pretty(&self.index())?,
        ));
        for a in &self.entries {
            files.push((a.path(), serde_json::to_vec_pretty(a)?));
        }
        for (path, hashes) in self.derived_lookups() {
            files.push((path, hashes.join("\n").into_bytes()));
        }
        files.sort_by(|a, b| a.0.cmp(&b.0));
        Ok(files)
    }

    /// The `by-recipe/…` and `by-atom/…` files, as path → sorted artifact hashes.
    ///
    /// Kept as one function because it is called twice: once to write the files
    /// and once, in [`HistoryPackage::from_files`], to check the ones that were
    /// written. A second implementation of this is a second answer to "what did
    /// this recipe build".
    fn derived_lookups(&self) -> BTreeMap<String, Vec<String>> {
        let mut out: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
        for a in &self.entries {
            let c = &a.receipt.build_thing;
            let sha = a.artifact.sha256.clone();
            out.entry(format!("{BY_RECIPE_DIR}/{}", c.krate))
                .or_default()
                .insert(sha.clone());
            out.entry(format!("{BY_RECIPE_DIR}/{}/{}", c.krate, c.version))
                .or_default()
                .insert(sha.clone());
            out.entry(format!("{BY_ATOM_DIR}/{}", a.receipt.atom))
                .or_default()
                .insert(sha);
        }
        out.into_iter()
            .map(|(k, v)| (k, v.into_iter().collect()))
            .collect()
    }

    /// Read a package back from the files of an archive.
    ///
    /// The derived lookups are **recomputed and compared**, not trusted. A
    /// package whose `by-recipe/…` disagrees with its attestations is refused,
    /// because that file is the answer to the only question the package is
    /// consulted for during a CVE sweep, and a wrong answer there is worse than
    /// no package at all.
    ///
    /// # Errors
    ///
    /// When the manifest is missing, is not this kind of package, is from a
    /// future schema, references an attestation that is absent, holds an
    /// attestation stored under the wrong hash, or carries a derived lookup that
    /// does not match the attestations.
    pub fn from_files(files: &[(String, Vec<u8>)]) -> Result<Self, PackageError> {
        let index_bytes = files
            .iter()
            .find(|(p, _)| p == INDEX_PATH)
            .map(|(_, b)| b)
            .ok_or(PackageError::NoIndex)?;
        let index: Index = serde_json::from_slice(index_bytes)
            .map_err(|e| PackageError::Malformed(format!("{INDEX_PATH}: {e}")))?;
        if index.kind != PACKAGE_KIND {
            return Err(PackageError::WrongKind(index.kind));
        }
        if index.package_version > PACKAGE_VERSION {
            return Err(PackageError::FromTheFuture(index.package_version));
        }

        let mut entries = Vec::with_capacity(index.attestations.len());
        for row in &index.attestations {
            let bytes = files
                .iter()
                .find(|(p, _)| *p == row.path)
                .map(|(_, b)| b)
                .ok_or_else(|| PackageError::MissingAttestation(row.path.clone()))?;
            let a: BuildAttestation = serde_json::from_slice(bytes)
                .map_err(|e| PackageError::Malformed(format!("{}: {e}", row.path)))?;
            if a.attestation_version > ATTESTATION_VERSION {
                return Err(PackageError::FromTheFuture(a.attestation_version));
            }
            if a.artifact.sha256 != row.artifact_sha256 || a.path() != row.path {
                return Err(PackageError::Misfiled {
                    path: row.path.clone(),
                    holds: a.artifact.sha256.clone(),
                });
            }
            entries.push(a);
        }
        entries.sort_by(|a, b| a.artifact.sha256.cmp(&b.artifact.sha256));
        let package = Self { entries };

        for (path, expected) in package.derived_lookups() {
            let found = files
                .iter()
                .find(|(p, _)| *p == path)
                .map(|(_, b)| b)
                .ok_or_else(|| PackageError::MissingLookup(path.clone()))?;
            let found: Vec<String> = String::from_utf8_lossy(found)
                .lines()
                .map(str::to_string)
                .filter(|l| !l.is_empty())
                .collect();
            if found != expected {
                return Err(PackageError::LookupDisagrees {
                    path,
                    says: found.len(),
                    attestations_say: expected.len(),
                });
            }
        }
        Ok(package)
    }

    /// Does this history cover the artifact on disk, and does the artifact
    /// agree?
    ///
    /// This is the release gate's question, and it is asked of the *bytes about
    /// to be released* rather than of a build log.
    #[must_use]
    pub fn verify_artifact(&self, artifact: &Path) -> AttestationVerdict {
        let bytes = match std::fs::read(artifact) {
            Ok(b) => b,
            Err(e) => {
                return AttestationVerdict::Unreadable(format!("{}: {e}", artifact.display()));
            }
        };
        let sha = nornir_hash::sha256_hex(&bytes);
        match self.by_artifact(&sha) {
            Some(a) => a.verify(artifact),
            None => AttestationVerdict::Unreadable(format!(
                "{} hashes to {sha}, which this history does not cover — \
                 the package is written after the build and before the release, \
                 so an uncovered artifact means the order was not kept",
                artifact.display()
            )),
        }
    }
}

/// Why a package could not be read back.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PackageError {
    /// No [`INDEX_PATH`] in the archive.
    NoIndex,
    /// The manifest names a different package kind.
    WrongKind(String),
    /// A schema version this reader does not understand.
    FromTheFuture(u32),
    /// The manifest points at an attestation the archive does not hold.
    MissingAttestation(String),
    /// An attestation is stored under a path that is not its own hash.
    Misfiled {
        /// The path it was found at.
        path: String,
        /// The hash it actually attests.
        holds: String,
    },
    /// A derived lookup the writer would have produced is absent.
    MissingLookup(String),
    /// A derived lookup does not match the attestations it summarises.
    LookupDisagrees {
        /// Which lookup.
        path: String,
        /// How many hashes the stored file lists.
        says: usize,
        /// How many the attestations imply.
        attestations_say: usize,
    },
    /// JSON that is not what it claims to be.
    Malformed(String),
}

impl fmt::Display for PackageError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::NoIndex => write!(f, "no {INDEX_PATH} — this is not a {PACKAGE_KIND} package"),
            Self::WrongKind(k) => write!(f, "{INDEX_PATH} says kind {k:?}, not {PACKAGE_KIND:?}"),
            Self::FromTheFuture(v) => write!(
                f,
                "schema version {v} is newer than this reader understands \
                 (package {PACKAGE_VERSION}, attestation {ATTESTATION_VERSION})"
            ),
            Self::MissingAttestation(p) => write!(f, "{INDEX_PATH} points at {p}, which is absent"),
            Self::Misfiled { path, holds } => {
                write!(f, "{path} holds an attestation for {holds}")
            }
            Self::MissingLookup(p) => write!(f, "the derived lookup {p} is absent"),
            Self::LookupDisagrees {
                path,
                says,
                attestations_say,
            } => write!(
                f,
                "{path} lists {says} artifact(s) and the attestations imply \
                 {attestations_say} — a lookup that disagrees with the attestations answers \
                 \"what did this recipe build\" wrongly"
            ),
            Self::Malformed(w) => write!(f, "malformed: {w}"),
        }
    }
}

impl std::error::Error for PackageError {}

// ── one artifact ─────────────────────────────────────────────────────────────

/// Attest a single built artifact: read its receipt, hash its bytes.
///
/// # Errors
///
/// [`Unattestable`], one variant per reason a build would otherwise be missing
/// from the history without anyone noticing.
pub fn attest(artifact: &Path) -> Result<BuildAttestation, Unattestable> {
    let bytes = std::fs::read(artifact).map_err(|e| Unattestable::Unreadable {
        artifact: artifact.to_path_buf(),
        why: e.to_string(),
    })?;
    let receipt = match read_receipt(artifact) {
        Ok(Some(r)) => r,
        Ok(None) => return Err(Unattestable::NoReceipt(artifact.to_path_buf())),
        Err(e) => {
            return Err(Unattestable::Unreadable {
                artifact: artifact.to_path_buf(),
                why: format!("{e:#}"),
            });
        }
    };
    attest_receipt(artifact, receipt, &bytes)
}

/// The pure half of [`attest`]: everything except touching the filesystem.
///
/// Split out so the refusals can be tested without a compiler, and so a caller
/// that already holds the bytes does not read them twice.
///
/// # Errors
///
/// [`Unattestable::UnpinnedRecipe`] and [`Unattestable::UnsafeName`].
pub fn attest_receipt(
    artifact: &Path,
    receipt: Receipt,
    bytes: &[u8],
) -> Result<BuildAttestation, Unattestable> {
    if receipt.build_thing.hash.is_none() {
        return Err(Unattestable::UnpinnedRecipe {
            artifact: artifact.to_path_buf(),
            coordinate: receipt.build_thing.to_string(),
        });
    }
    for (field, value) in [
        ("build_thing.krate", &receipt.build_thing.krate),
        ("build_thing.version", &receipt.build_thing.version),
        ("atom", &receipt.atom),
    ] {
        if !safe_component(value) {
            return Err(Unattestable::UnsafeName {
                artifact: artifact.to_path_buf(),
                field,
                value: value.clone(),
            });
        }
    }
    Ok(BuildAttestation {
        attestation_version: ATTESTATION_VERSION,
        artifact: ArtifactRef {
            sha256: nornir_hash::sha256_hex(bytes),
            bytes: bytes.len() as u64,
        },
        receipt,
        // No proof from bytes alone. A stripped artifact has no symbol table, so
        // there is nothing here to audit; the proof is attached by
        // `attest_proven`, which is handed the unstripped binary as well.
        proof: None,
    })
}

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

    fn receipt(krate: &str, version: &str, hash: Option<&str>, atom: &str) -> Receipt {
        let mut settings = BTreeMap::new();
        settings.insert("feature.gpu".to_string(), "false".to_string());
        let mut inputs = BTreeMap::new();
        inputs.insert(
            "target".to_string(),
            "x86_64-unknown-linux-musl".to_string(),
        );
        Receipt {
            receipt_version: RECEIPT_VERSION,
            build_thing: Coordinate {
                krate: krate.to_string(),
                version: version.to_string(),
                hash: hash.map(str::to_string),
            },
            atom: atom.to_string(),
            target: "exe".to_string(),
            settings,
            inputs,
        }
    }

    /// A package over synthetic bytes. `attest_receipt` is the same function the
    /// real path uses; only the reading of the receipt out of an ELF is skipped,
    /// and that half is covered by `tests/history_end_to_end.rs` against real
    /// binaries built by a real linker.
    fn package(items: &[(Receipt, &[u8])]) -> HistoryPackage {
        let mut entries: Vec<BuildAttestation> = items
            .iter()
            .map(|(r, b)| attest_receipt(Path::new("synthetic"), r.clone(), b).expect("attestable"))
            .collect();
        entries.sort_by(|a, b| a.artifact.sha256.cmp(&b.artifact.sha256));
        entries.dedup_by(|a, b| a.artifact.sha256 == b.artifact.sha256);
        HistoryPackage { entries }
    }

    /// The package is a pure function of the artifact bytes: the same inputs in
    /// the opposite order seal the same files.
    ///
    /// This is "lose it and it regenerates" as an assertion.
    #[test]
    fn the_same_artifacts_in_a_different_order_produce_the_same_files() {
        let a = (
            receipt(
                "gunnar-build-thing",
                "0.1.0",
                Some("e484a4"),
                "gunnar-server",
            ),
            b"AAAA".as_slice(),
        );
        let b = (
            receipt("korp-build-thing", "0.3.1", Some("a91f4c"), "korp-ui"),
            b"BBBB".as_slice(),
        );
        let forward = package(&[a.clone(), b.clone()]).files().expect("files");
        let reverse = package(&[b, a]).files().expect("files");
        assert_eq!(forward, reverse, "insertion order leaked into the package");
    }

    /// LAW 2 on the guard above: it can tell two different packages apart.
    ///
    /// Without this, `the_same_artifacts_in_a_different_order…` would pass just
    /// as happily if `files()` returned a constant.
    #[test]
    fn the_determinism_check_reports_red_on_two_different_packages() {
        let a = receipt(
            "gunnar-build-thing",
            "0.1.0",
            Some("e484a4"),
            "gunnar-server",
        );
        let one = package(&[(a.clone(), b"AAAA")]).files().expect("files");
        let two = package(&[(a, b"BBBB")]).files().expect("files");
        assert_ne!(
            one, two,
            "the comparison calls two different packages equal, so it can never fail"
        );
    }

    /// A package survives being written out and read back, and the read side
    /// recomputes the lookups rather than trusting them.
    #[test]
    fn a_package_survives_the_round_trip_through_its_own_files() {
        let p = package(&[
            (
                receipt(
                    "gunnar-build-thing",
                    "0.1.0",
                    Some("e484a4"),
                    "gunnar-server",
                ),
                b"AAAA",
            ),
            (
                receipt("gunnar-build-thing", "0.1.0", Some("e484a4"), "gunnar-solo"),
                b"BBBB",
            ),
            (
                receipt("korp-build-thing", "0.3.1", Some("a91f4c"), "korp-ui"),
                b"CCCC",
            ),
        ]);
        let files = p.files().expect("files");
        let back = HistoryPackage::from_files(&files).expect("read back");
        assert_eq!(back, p, "a package did not survive its own round trip");
        assert_eq!(back.len(), 3);
    }

    /// **What did this recipe build?** — both scopes, and the CVE-shaped one
    /// (crate, any version) is the one that must span versions.
    #[test]
    fn a_recipe_lookup_answers_what_that_recipe_built() {
        let p = package(&[
            (
                receipt(
                    "gunnar-build-thing",
                    "0.1.0",
                    Some("e484a4"),
                    "gunnar-server",
                ),
                b"AAAA",
            ),
            (
                receipt(
                    "gunnar-build-thing",
                    "0.2.0",
                    Some("ffff11"),
                    "gunnar-server",
                ),
                b"BBBB",
            ),
            (
                receipt("korp-build-thing", "0.3.1", Some("a91f4c"), "korp-ui"),
                b"CCCC",
            ),
        ]);
        assert_eq!(p.by_recipe("gunnar-build-thing", None).len(), 2);
        assert_eq!(p.by_recipe("gunnar-build-thing", Some("0.1.0")).len(), 1);
        assert_eq!(p.by_recipe("korp-build-thing", None).len(), 1);
        assert_eq!(
            p.by_recipe("does-not-exist", None).len(),
            0,
            "a recipe nothing was built with must answer empty, not everything"
        );
        assert_eq!(p.by_atom("gunnar-server").len(), 2);
    }

    /// A hand-edited `by-recipe/…` is refused, and refused BY NAME.
    ///
    /// This is the whole reason `from_files` recomputes: that file is what a CVE
    /// sweep reads, and a package that under-reports there hides an affected
    /// artifact.
    #[test]
    fn a_tampered_recipe_lookup_is_refused() {
        let p = package(&[
            (
                receipt(
                    "gunnar-build-thing",
                    "0.1.0",
                    Some("e484a4"),
                    "gunnar-server",
                ),
                b"AAAA",
            ),
            (
                receipt("gunnar-build-thing", "0.1.0", Some("e484a4"), "gunnar-solo"),
                b"BBBB",
            ),
        ]);
        let mut files = p.files().expect("files");
        let target = format!("{BY_RECIPE_DIR}/gunnar-build-thing");
        let row = files
            .iter_mut()
            .find(|(path, _)| *path == target)
            .expect("the lookup must exist for this test to mean anything");
        let before = String::from_utf8(row.1.clone()).unwrap();
        assert_eq!(
            before.lines().count(),
            2,
            "fixture must list both artifacts"
        );
        // Drop one hash — the shape of an under-reporting sweep.
        row.1 = before.lines().next().unwrap().as_bytes().to_vec();

        match HistoryPackage::from_files(&files) {
            Err(PackageError::LookupDisagrees {
                path,
                says,
                attestations_say,
            }) => {
                assert_eq!(path, target);
                assert_eq!((says, attestations_say), (1, 2));
            }
            other => panic!("a truncated recipe lookup was accepted: {other:?}"),
        }
    }

    /// LAW 2: the untampered package must pass the same door, or the test above
    /// would pass on a `from_files` that refuses everything.
    #[test]
    fn the_untampered_package_passes_the_door_the_tampered_one_fails() {
        let p = package(&[
            (
                receipt(
                    "gunnar-build-thing",
                    "0.1.0",
                    Some("e484a4"),
                    "gunnar-server",
                ),
                b"AAAA",
            ),
            (
                receipt("gunnar-build-thing", "0.1.0", Some("e484a4"), "gunnar-solo"),
                b"BBBB",
            ),
        ]);
        HistoryPackage::from_files(&p.files().expect("files")).expect("clean package must read");
    }

    /// An attestation moved to another file name is refused: the path IS the
    /// primary key, and a misfiled one makes a hash lookup answer with somebody
    /// else's build.
    #[test]
    fn a_misfiled_attestation_is_refused() {
        let p = package(&[
            (
                receipt(
                    "gunnar-build-thing",
                    "0.1.0",
                    Some("e484a4"),
                    "gunnar-server",
                ),
                b"AAAA",
            ),
            (
                receipt("gunnar-build-thing", "0.1.0", Some("e484a4"), "gunnar-solo"),
                b"BBBB",
            ),
        ]);
        let mut files = p.files().expect("files");
        // Swap the two attestation bodies, keeping the paths.
        let paths: Vec<String> = p
            .attestations()
            .iter()
            .map(BuildAttestation::path)
            .collect();
        let a = files
            .iter()
            .find(|(x, _)| *x == paths[0])
            .unwrap()
            .1
            .clone();
        let b = files
            .iter()
            .find(|(x, _)| *x == paths[1])
            .unwrap()
            .1
            .clone();
        for (path, body) in files.iter_mut() {
            if *path == paths[0] {
                *body = b.clone();
            } else if *path == paths[1] {
                *body = a.clone();
            }
        }
        assert!(
            matches!(
                HistoryPackage::from_files(&files),
                Err(PackageError::Misfiled { .. })
            ),
            "two attestations swapped under each other's hash were accepted"
        );
    }

    /// A recipe named only by version cannot be attested. The version says which
    /// recipe was *intended*; only the hash says which one ran.
    #[test]
    fn an_unpinned_recipe_is_refused_by_name() {
        let r = receipt("gunnar-build-thing", "0.1.0", None, "gunnar-server");
        match attest_receipt(Path::new("a"), r, b"AAAA") {
            Err(Unattestable::UnpinnedRecipe { coordinate, .. }) => {
                assert_eq!(coordinate, "gunnar-build-thing:0.1.0");
            }
            other => panic!("an unpinned recipe was attested: {other:?}"),
        }
        // LAW 2: the same receipt WITH a hash goes through, so the refusal is
        // about the hash and not about the fixture.
        let ok = receipt(
            "gunnar-build-thing",
            "0.1.0",
            Some("e484a4"),
            "gunnar-server",
        );
        attest_receipt(Path::new("a"), ok, b"AAAA").expect("a pinned recipe must attest");
    }

    /// Every receipt field that becomes a path component is refused when it
    /// cannot be one. Sanitising instead would silently file an attestation
    /// somewhere nobody looks.
    #[test]
    fn a_receipt_field_that_would_escape_the_archive_is_refused() {
        for (field, krate, version, atom) in [
            ("build_thing.krate", "../../etc", "0.1.0", "a"),
            ("build_thing.version", "k", "0.1.0/../..", "a"),
            ("atom", "k", "0.1.0", ".."),
            ("atom", "k", "0.1.0", ""),
        ] {
            let r = receipt(krate, version, Some("e484a4"), atom);
            match attest_receipt(Path::new("a"), r, b"AAAA") {
                Err(Unattestable::UnsafeName { field: got, .. }) => {
                    assert_eq!(
                        got, field,
                        "wrong field blamed for {krate}/{version}/{atom}"
                    );
                }
                other => panic!("{krate}/{version}/{atom} was accepted: {other:?}"),
            }
        }
    }

    /// The index is sorted by hash and every row points at a file that exists.
    #[test]
    fn every_index_row_points_at_a_file_the_package_actually_holds() {
        let p = package(&[
            (
                receipt(
                    "gunnar-build-thing",
                    "0.1.0",
                    Some("e484a4"),
                    "gunnar-server",
                ),
                b"AAAA",
            ),
            (
                receipt("korp-build-thing", "0.3.1", Some("a91f4c"), "korp-ui"),
                b"BBBB",
            ),
            (
                receipt("holger-build-thing", "1.0.0", Some("cccccc"), "holger-full"),
                b"CCCC",
            ),
        ]);
        let files = p.files().expect("files");
        let paths: Vec<&String> = files.iter().map(|(path, _)| path).collect();
        assert!(
            paths.windows(2).all(|w| w[0] <= w[1]),
            "the package's files are not path-sorted, so two runs can seal two archives: {paths:?}"
        );
        let index = p.index();
        assert_eq!(index.kind, PACKAGE_KIND);
        assert!(
            index
                .attestations
                .windows(2)
                .all(|w| w[0].artifact_sha256 <= w[1].artifact_sha256),
            "index rows are not sorted by artifact hash"
        );
        for row in &index.attestations {
            assert!(
                files.iter().any(|(path, _)| *path == row.path),
                "index row points at {}, which the package does not hold",
                row.path
            );
        }
    }

    /// A package from a future schema is refused rather than partially read.
    #[test]
    fn a_package_from_the_future_is_refused() {
        let p = package(&[(
            receipt(
                "gunnar-build-thing",
                "0.1.0",
                Some("e484a4"),
                "gunnar-server",
            ),
            b"AAAA",
        )]);
        let mut files = p.files().expect("files");
        let mut index = p.index();
        index.package_version = PACKAGE_VERSION + 1;
        for (path, body) in files.iter_mut() {
            if path == INDEX_PATH {
                *body = serde_json::to_vec_pretty(&index).unwrap();
            }
        }
        assert!(matches!(
            HistoryPackage::from_files(&files),
            Err(PackageError::FromTheFuture(_))
        ));
    }

    /// An empty history is a package, not an error — and it reads back empty
    /// rather than as "no package".
    #[test]
    fn an_empty_history_round_trips_as_an_empty_history() {
        let p = HistoryPackage::default();
        let files = p.files().expect("files");
        assert_eq!(
            files.len(),
            1,
            "an empty package is its manifest and nothing else"
        );
        let back = HistoryPackage::from_files(&files).expect("read back");
        assert!(back.is_empty());
    }
}