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

//! **A produced FILE SET is a build target** — the descriptor behind
//! [`ArtifactKind::Package`](crate::variant::ArtifactKind::Package).
//!
//! Every other kind in this crate produces ONE thing and names it: a binary, an
//! image reference, a `bzImage`, a mastered `.iso`. A distribution package does
//! not. facett's pip release is
//!
//! ```text
//! facet_wrapped_web-0.1.19-py3-none-any.whl     the wheel
//! facet_wrapped_web-0.1.19.tar.gz               the sdist
//! SHA256SUMS                                    what `sha256sum -c` reads
//! provenance.json                               hashes of the PRODUCED BYTES
//! ```
//!
//! — four files that are one artifact, because **any three of them without the
//! fourth is not a release anybody can check**. A wheel with no `SHA256SUMS` is a
//! file you have to trust the machine that made it. A `SHA256SUMS` with no
//! provenance says the bytes are the bytes, not which commit or toolchain made
//! them. So the SET is the unit this descriptor declares, and
//! [`PackageBuild::required_members`] is where the declaration says which members
//! must be present for the build to have succeeded.
//!
//! # Why this is not `Wasm`, and not `Exe`
//!
//! `Wasm` was the near-miss and it is worth writing down, because facett's wheel
//! carries a wasm module and `kind = "wasm"` therefore looks right. It would make
//! the forge build a bare `.wasm` and stop — no wasm-bindgen, no wheel, no
//! read-back of the produced zip, no release set. `Exe`/`Lib` are wrong for a
//! simpler reason: `cargo build` does not build a wheel at all. A package is
//! assembled by its FORMAT'S own tool, inside a pinned container, and the only
//! thing cargo contributes is inputs.
//!
//! # The provenance rule, and why it is a rule
//!
//! **A package's provenance hashes the PRODUCED BYTES, never the recipe.** The
//! distinction is not pedantry: a record built from the declaration describes what
//! was *asked for*, and two builds of one declaration can differ — facett measured
//! exactly that when a feature flip grew its wasm from 9 920 451 to 11 604 884
//! bytes with the declaration unchanged. [`BuildProvenance::capture`](crate::provenance::BuildProvenance::capture)
//! is handed the artefact PATHS and hashes what is actually on disk; this module
//! is the seam that hands it the right paths and refuses if a required member is
//! not among them.
//!
//! # One writer, and where it came from
//!
//! [`write_sha256sums`] was facett's, in `facett-ops/src/pip.rs` (then `xtask/`), and it worked. It is
//! here because the DOOR needs it too: `dwarves buildthing facett --variant
//! facet-wrapped-web` and `cargo xtask publish-pip` must leave the same release
//! set behind, and the way to guarantee that is one writer both paths route
//! through (LAW 5) rather than a guard watching two copies agree. facett's xtask
//! calls this function now; the behaviour, the format and the refusals are its
//! own, moved verbatim and with its tests.
//!
//! # Declaration
//!
//! ```toml
//! [[build.package]]
//! name = "facet-wrapped-web"
//! format = "pip"
//! distribution = "facet_wrapped_web"
//! # repo-relative, where the produced set lands:
//! out_dir = "pkg-facet-wrapped/dist"
//! # the package SOURCE the build is run over (repo-relative):
//! source = "pkg-facet-wrapped"
//! # an sdist is part of this release, not just a wheel:
//! sdist = true
//! # the pinned toolchain image the format's tool runs inside — no python,
//! # node or ruby ever touches the host:
//! image = "nordisk-pip-toolchain:1"
//! # build outputs staged INTO the package source before it is built, as
//! # `<repo-relative source>:<path under the staged package>` pairs:
//! inputs = [
//!     "facet-wrapped/pkg/facet_wrapped_bg.wasm:src/facet_wrapped_web/assets/facet_wrapped_bg.wasm",
//! ]
//! ```
//!
//! # Honest scope
//!
//! The descriptor, its validation, the member classification, the release-set
//! writer and the produced-set verification are REAL and tested here with files on
//! disk — no container, no network. The BUILD itself is the
//! [`PackageBuilder`] seam: this crate ships no packaging tool and links none, the
//! same way it links no ISO composer. A consumer that can run one injects it
//! ([`Builders::with_package`](crate::assemble::Builders::with_package)); a
//! consumer that cannot gets a refusal that names the door.

use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::time::Duration;

use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};

/// The checksum file a downloader runs `sha256sum -c` against.
pub const SUMS_FILE: &str = "SHA256SUMS";

/// The provenance record written beside the artefacts.
pub const PROVENANCE_FILE: &str = "provenance.json";

/// **What a package is packaged AS.** An enum and not a string because the format
/// decides the member set: a pip release has a wheel and an sdist, a crate release
/// has a `.crate`, and a consumer switching on a free-text field would fall
/// through for a spelling nobody checked.
///
/// One arm today. That is the honest state — facett's wheel is the only package in
/// the constellation — and it is an enum rather than a bool so the second one is an
/// arm rather than a rewrite.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PackageFormat {
    /// A PEP 517 python distribution: a `.whl`, optionally a `.tar.gz` sdist.
    #[default]
    Pip,
}

impl PackageFormat {
    /// The label the declaration spells.
    pub fn as_str(self) -> &'static str {
        match self {
            PackageFormat::Pip => "pip",
        }
    }

    /// The tool that assembles this format, for a refusal that can name it.
    pub fn tool(self) -> &'static str {
        match self {
            PackageFormat::Pip => "python -m build",
        }
    }
}

/// **What one produced file IS** — the member set is what makes a package a set
/// rather than a file.
///
/// `Other` exists so a produced file nobody classified is CARRIED rather than
/// dropped: `write_sha256sums` lists only artefacts, but a verification that
/// silently ignored an unexpected file would be unable to say a build wrote
/// something it did not declare.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PackageMember {
    /// The installable distribution — a `.whl` for pip.
    Wheel,
    /// The source distribution — a `.tar.gz` for pip.
    Sdist,
    /// The `SHA256SUMS` manifest.
    Sums,
    /// The provenance record over the produced bytes.
    Provenance,
    /// A produced file this descriptor does not classify.
    Other,
}

impl PackageMember {
    /// The label a refusal prints.
    pub fn as_str(self) -> &'static str {
        match self {
            PackageMember::Wheel => "wheel",
            PackageMember::Sdist => "sdist",
            PackageMember::Sums => SUMS_FILE,
            PackageMember::Provenance => PROVENANCE_FILE,
            PackageMember::Other => "other",
        }
    }

    /// **Which member a produced filename is.** By SUFFIX and exact name, never by
    /// position or by readdir order — a downloader has the directory and nothing
    /// else, and so does this.
    pub fn classify(format: PackageFormat, name: &str) -> PackageMember {
        if name == SUMS_FILE {
            return PackageMember::Sums;
        }
        if name == PROVENANCE_FILE {
            return PackageMember::Provenance;
        }
        match format {
            PackageFormat::Pip => {
                if name.ends_with(".whl") {
                    PackageMember::Wheel
                } else if name.ends_with(".tar.gz") {
                    PackageMember::Sdist
                } else {
                    PackageMember::Other
                }
            }
        }
    }

    /// True for the members `SHA256SUMS` lists: the distributions themselves.
    ///
    /// The manifest never lists ITSELF (a self-hash is unverifiable) and never
    /// lists the provenance record (which quotes the manifest's own subjects), and
    /// it never lists a stray file — a scratch file in the directory makes
    /// `sha256sum -c` fail for a reason that has nothing to do with the release.
    pub fn is_distribution(self) -> bool {
        matches!(self, PackageMember::Wheel | PackageMember::Sdist)
    }
}

/// **A distribution package build** — the typed descriptor behind a
/// `kind = "package"` variant. Pure serde, dependency-light, like every other cell
/// in this crate.
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct PackageBuild {
    /// The cell handle — the SAME name as the variant that declares
    /// `kind = "package"` (the Iso/Installer/Kernel contract, no `*_ref` field).
    pub name: String,
    /// What it is packaged as.
    #[serde(default)]
    pub format: PackageFormat,
    /// The distribution name as the index knows it (`facet_wrapped_web`).
    #[serde(default, skip_serializing_if = "String::is_empty")]
    pub distribution: String,
    /// Repo-relative directory the produced set lands in.
    #[serde(default, skip_serializing_if = "String::is_empty")]
    pub out_dir: String,
    /// Repo-relative package source the format's tool is run over.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub source: Option<String>,
    /// **An sdist is part of this release.** A separate flag and not inferred from
    /// the format, because a wheel-only release is legitimate and a build that
    /// quietly produced no sdist when one was expected is exactly the kind of
    /// missing member this descriptor exists to refuse.
    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
    pub sdist: bool,
    /// The pinned toolchain image the format's tool runs inside. No python, node
    /// or ruby ever runs on the host; the image is where the tool lives.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub image: Option<String>,
    /// Build outputs staged INTO the package source before it is built, as
    /// `<repo-relative source>:<path under the staged package>` pairs.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub inputs: Vec<String>,
    /// Cargo variants whose artifacts this package carries — the `from_variant`
    /// chain the container/iso cells use, so the forge builds them first.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub from_variant: Vec<String>,
    /// **The wasm cell whose bundle this package stages** — the producer of the
    /// `inputs`, named so the forge can run it FIRST.
    ///
    /// A separate edge from [`from_variant`](Self::from_variant), and the distinction
    /// is the entire lesson of 2026-09-06. `from_variant` means "cargo-build this
    /// variant", and cargo does not produce a wasm BUNDLE: `cargo build -p
    /// facet-wrapped --target wasm32-unknown-unknown` writes ONE file,
    /// `facet_wrapped.wasm` (11 647 012 B), while the wheel stages
    /// `facet_wrapped_bg.wasm` (10 116 976 B — bindgen's rewrite), a JS glue module
    /// and a manifest. Chaining with `from_variant` would build an 11.6 MB file
    /// nothing consumes and then refuse for the same missing inputs one step later,
    /// **with a success in between**. `from_wasm` names a cell whose declared
    /// STAGES produce those files.
    ///
    /// The edge is CHECKED, not merely recorded:
    /// [`validate_package`](crate::variant::BuildVariantSet::validate_package) holds
    /// every `inputs` source to the named cell's
    /// [`outputs`](crate::wasm::WasmBuild::outputs). A chain nobody checks is the
    /// same defect with a nicer name.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub from_wasm: Option<String>,
}

impl PackageBuild {
    /// **The members that MUST be present for this build to have succeeded.**
    ///
    /// A wheel, the `SHA256SUMS` a downloader checks it with, and the provenance
    /// over the produced bytes are ALWAYS required — those three are what makes the
    /// output a release rather than a file. The sdist is required only when the
    /// declaration says so.
    pub fn required_members(&self) -> Vec<PackageMember> {
        let mut m = vec![PackageMember::Wheel];
        if self.sdist {
            m.push(PackageMember::Sdist);
        }
        m.push(PackageMember::Sums);
        m.push(PackageMember::Provenance);
        m
    }

    /// The absolute directory the produced set lands in.
    pub fn out_dir(&self, repo_root: &Path) -> PathBuf {
        repo_root.join(&self.out_dir)
    }

    /// The `<source>:<dest>` halves of one `inputs` row, or a refusal naming the
    /// row. Split on the LAST colon so a Windows-ish or versioned source path
    /// cannot silently take the separator's place.
    pub fn input_pair(&self, row: &str) -> Result<(String, String)> {
        let (src, dst) = row.rsplit_once(':').ok_or_else(|| {
            anyhow!(
                "package '{}': input {row:?} is not `<repo-relative source>:<path under \
                 the staged package>` — a row with no destination would stage a file \
                 the package's own layout never names",
                self.name
            )
        })?;
        if src.trim().is_empty() || dst.trim().is_empty() {
            return Err(anyhow!(
                "package '{}': input {row:?} has an empty half",
                self.name
            ));
        }
        Ok((src.trim().to_string(), dst.trim().to_string()))
    }

    /// Shape-only validation (no disk).
    pub fn validate(&self) -> Result<()> {
        if self.name.trim().is_empty() {
            return Err(anyhow!("package build: empty name"));
        }
        if self.distribution.trim().is_empty() {
            return Err(anyhow!(
                "package '{}': `distribution` is REQUIRED — it is the name the index \
                 and every `pip install` say, and a release whose distribution name is \
                 whatever the builder happened to emit is not a release anybody can ask \
                 for",
                self.name
            ));
        }
        if self.out_dir.trim().is_empty() {
            return Err(anyhow!(
                "package '{}': `out_dir` is REQUIRED — it is where the produced set \
                 lands, and it is what the provenance hashes",
                self.name
            ));
        }
        if Path::new(&self.out_dir).is_absolute() {
            return Err(anyhow!(
                "package '{}': `out_dir` {:?} is absolute — it must be repo-relative, \
                 so the same declaration means the same directory in every checkout",
                self.name,
                self.out_dir
            ));
        }
        for row in &self.inputs {
            self.input_pair(row)?;
        }
        Ok(())
    }

    /// Validation against a checkout: everything [`validate`](Self::validate) does,
    /// plus the declared `source` really being in THIS tree.
    ///
    /// The container-kind precedent: a declaration naming a directory that is not
    /// here is refused BEFORE anything is staged or a container starts, so the
    /// failure reads as "the declaration is broken" instead of as an engine error
    /// several minutes later.
    pub fn validate_in(&self, repo_root: &Path) -> Result<()> {
        self.validate()?;
        if let Some(src) = &self.source {
            let p = repo_root.join(src);
            if !p.is_dir() {
                return Err(anyhow!(
                    "package '{}': source {src:?} is not in this checkout ({})",
                    self.name,
                    p.display()
                ));
            }
        }
        Ok(())
    }

    /// The canonical byte string this cell's content id is taken over — every axis
    /// that changes the PRODUCED SET, and nothing that changes only how the build is
    /// run. The kernel cell's lesson (`preset` and `base` missing here gave two
    /// different kernels one content address) applied before it can be repeated.
    pub fn canonical_bytes(&self) -> Vec<u8> {
        let mut s = format!(
            "package#name:{}#format:{}#dist:{}#out:{}#src:{}#sdist:{}#image:{}",
            self.name,
            self.format.as_str(),
            self.distribution,
            self.out_dir,
            self.source.as_deref().unwrap_or(""),
            self.sdist,
            self.image.as_deref().unwrap_or(""),
        );
        for i in &self.inputs {
            s.push_str("#input:");
            s.push_str(i);
        }
        for v in &self.from_variant {
            s.push_str("#from:");
            s.push_str(v);
        }
        if let Some(w) = &self.from_wasm {
            s.push_str("#fromwasm:");
            s.push_str(w);
        }
        s.into_bytes()
    }

    /// The content id of this declaration.
    pub fn content_id(&self) -> String {
        nornir_hash::sha256_hex(&self.canonical_bytes())
    }
}

/// Lowercase hex sha256 — the ONE spelling the manifest writer and its tests both
/// use, so a test cannot agree with a bug by hashing the same wrong way.
#[must_use]
pub fn sha256_hex(bytes: &[u8]) -> String {
    nornir_hash::sha256_hex(bytes)
}

/// **Write `SHA256SUMS` beside the artefacts** and return its path.
///
/// `sha256sum -c` format exactly: `<64 hex><two spaces><bare filename>`. The name
/// is bare and not a path, because the file has to be checkable from the directory
/// it ships in — a downloader has the wheel and this file and nothing else.
///
/// The manifest never lists ITSELF (a self-hash is unverifiable), never lists the
/// provenance record, and never lists a non-artefact: a stray file makes
/// `sha256sum -c` fail for a reason that has nothing to do with the release.
/// Rewriting is idempotent — a publish verb is re-run constantly, and a manifest
/// that grew a line per run would still check clean while quietly ceasing to be a
/// listing.
///
/// An empty directory is an ERROR, not an empty manifest: an empty `SHA256SUMS`
/// passes `sha256sum -c` trivially, checking nothing and saying so to nobody.
///
/// Moved here from facett's pip module (now `facett-ops/src/pip.rs`) on 2026-09-06 so the DOOR and the
/// xtask leave the same release set behind — one writer, not two agreeing copies.
pub fn write_sha256sums(format: PackageFormat, dist: &Path) -> Result<PathBuf> {
    let mut entries: Vec<(String, String)> = Vec::new();
    for e in std::fs::read_dir(dist)
        .map_err(|e| anyhow!("read {}: {e}", dist.display()))?
    {
        let e = e.map_err(|e| anyhow!("read entry: {e}"))?;
        if !e.file_type().map(|t| t.is_file()).unwrap_or(false) {
            continue;
        }
        let name = e.file_name().to_string_lossy().into_owned();
        if !PackageMember::classify(format, &name).is_distribution() {
            continue;
        }
        let bytes = std::fs::read(e.path()).map_err(|err| anyhow!("read {name}: {err}"))?;
        entries.push((sha256_hex(&bytes), name));
    }
    if entries.is_empty() {
        return Err(anyhow!(
            "{} holds no {} distribution — an empty SHA256SUMS passes `sha256sum -c` \
             trivially and proves nothing",
            dist.display(),
            format.as_str()
        ));
    }
    // Sorted by name so the file is stable across runs regardless of readdir order.
    entries.sort_by(|a, b| a.1.cmp(&b.1));
    let body: String =
        entries.iter().map(|(h, n)| format!("{h}  {n}\n")).collect::<Vec<_>>().concat();
    let out = dist.join(SUMS_FILE);
    std::fs::write(&out, body).map_err(|e| anyhow!("write {}: {e}", out.display()))?;
    Ok(out)
}

/// **Write the provenance record beside the artefacts** — over the PRODUCED BYTES.
///
/// This is the rule the `package` kind exists to keep, and it is a function rather
/// than a paragraph so no door can keep it differently.
/// [`BuildProvenance::capture`](crate::provenance::BuildProvenance::capture) is
/// handed the artefact PATHS and content-hashes what is actually on disk. A record
/// built from the DECLARATION would describe what was *asked for*, and two builds
/// of one declaration can differ — facett measured exactly that, its wasm growing
/// 9 920 451 → 11 604 884 bytes from a feature flip with the declaration unchanged.
///
/// `toolchain` is the caller's because the toolchain that produced a wheel is the
/// CONTAINER's, and `Toolchain::detect()` here would attest to the host's rustc,
/// which did not build the artefact being described. facett's verb passes the pinned
/// image's id; a caller that cannot name one passes what it has and says so.
///
/// Moved here from facett's pip module (now `facett-ops/src/pip.rs`) on 2026-09-06, with its reasoning.
/// It is not a copy: `provenance.json` is a REQUIRED MEMBER of the release, so the
/// door has to write it too, and two writers of one member is how the two doors
/// drift.
pub fn write_provenance(
    pkg: &PackageBuild,
    variant: &crate::variant::BuildVariant,
    repo: &str,
    repo_root: &Path,
    toolchain: crate::provenance::Toolchain,
    artifacts: &[PathBuf],
) -> Result<PathBuf> {
    use crate::provenance::{BuildProvenance, HostInfo, Outcome};

    let record = BuildProvenance::capture(
        variant,
        repo,
        repo_root,
        toolchain,
        HostInfo::detect(),
        Outcome::Success,
        artifacts,
    )?;
    let out = pkg.out_dir(repo_root).join(PROVENANCE_FILE);
    std::fs::write(&out, record.to_json()?)
        .map_err(|e| anyhow!("write {}: {e}", out.display()))?;
    Ok(out)
}

/// The [`BuildVariant`](crate::variant::BuildVariant) a package cell's provenance
/// is captured against, when the caller has no declared row to hand.
///
/// It is synthesised from the CELL — same name, `kind = "package"` — so the record
/// says what actually ran. A door that has the real declared row should pass THAT
/// instead: it carries the profile, the features and the target the release was cut
/// with, and a synthesised row carries none of them.
pub fn variant_for(pkg: &PackageBuild) -> crate::variant::BuildVariant {
    crate::variant::BuildVariant {
        name: pkg.name.clone(),
        kind: crate::variant::ArtifactKind::Package,
        ..Default::default()
    }
}

/// **What is actually in the produced directory**, member → the files that are it.
///
/// A read of the disk and nothing else — the thing a verification has to be built
/// on, because "the tool exited 0" is not evidence that a file exists.
pub fn produced_members(
    format: PackageFormat,
    dist: &Path,
) -> Result<BTreeMap<PackageMember, Vec<PathBuf>>> {
    let mut out: BTreeMap<PackageMember, Vec<PathBuf>> = BTreeMap::new();
    for e in std::fs::read_dir(dist)
        .map_err(|e| anyhow!("the build produced no {}: {e}", dist.display()))?
    {
        let e = e.map_err(|e| anyhow!("read entry: {e}"))?;
        if !e.file_type().map(|t| t.is_file()).unwrap_or(false) {
            continue;
        }
        let name = e.file_name().to_string_lossy().into_owned();
        out.entry(PackageMember::classify(format, &name)).or_default().push(e.path());
    }
    for v in out.values_mut() {
        v.sort();
    }
    Ok(out)
}

/// **The produced set is the declared set** — or a refusal naming every member
/// that is missing, and what it would have been called.
///
/// Returns the distribution paths, in member order, for provenance to hash. An
/// empty return is impossible: a wheel is always required, so a directory with no
/// wheel refuses here rather than being sealed as a release of nothing.
pub fn verify_produced(pkg: &PackageBuild, dist: &Path) -> Result<Vec<PathBuf>> {
    let found = produced_members(pkg.format, dist)?;
    let mut missing: Vec<&'static str> = Vec::new();
    for m in pkg.required_members() {
        if found.get(&m).map(|v| v.is_empty()).unwrap_or(true) {
            missing.push(m.as_str());
        }
    }
    if !missing.is_empty() {
        let present: Vec<String> = found
            .values()
            .flatten()
            .filter_map(|p| p.file_name().map(|f| f.to_string_lossy().into_owned()))
            .collect();
        return Err(anyhow!(
            "package '{}': the build left {} in {} but the declared set is missing {} — \
             a release with a member missing is not checkable, and reporting it as \
             produced is how a build that wrote nothing exits 0 (present: {})",
            pkg.name,
            if present.is_empty() { "nothing".to_string() } else { format!("{} file(s)", present.len()) },
            dist.display(),
            missing.join(", "),
            if present.is_empty() { "—".to_string() } else { present.join(", ") }
        ));
    }
    let mut paths: Vec<PathBuf> = Vec::new();
    for m in [PackageMember::Wheel, PackageMember::Sdist] {
        if let Some(v) = found.get(&m) {
            paths.extend(v.iter().cloned());
        }
    }
    Ok(paths)
}

/// **The seam a packaging tool fills.**
///
/// This crate ships no implementation and links no packaging tool, exactly as it
/// links no ISO composer: assembling a wheel needs a python toolchain in a
/// container, which is a capability a leaf's xtask must not have to pull in to
/// consume these types. A consumer that HAS one injects it through
/// [`Builders::with_package`](crate::assemble::Builders::with_package).
///
/// The implementor's contract: produce the declared set into
/// [`PackageBuild::out_dir`] and return the DISTRIBUTION paths it wrote. It does
/// not write `SHA256SUMS` or the provenance record — [`write_sha256sums`] and
/// [`crate::provenance::BuildProvenance::capture`] are the one writer for each, so
/// every door leaves the same release set behind.
pub trait PackageBuilder {
    /// One line naming what assembles the package — recorded in the attestation as
    /// the engine, the way a container build records its daemon.
    fn engine(&self) -> &str;
    /// Build `pkg` and return the produced distribution paths.
    fn build(&self, pkg: &PackageBuild, repo_root: &Path) -> Result<Vec<PathBuf>>;
}

#[cfg(test)]
mod tests {
    use super::*;

    fn a_pkg() -> PackageBuild {
        PackageBuild {
            name: "facet-wrapped-web".into(),
            format: PackageFormat::Pip,
            distribution: "facet_wrapped_web".into(),
            out_dir: "pkg-facet-wrapped/dist".into(),
            sdist: true,
            ..PackageBuild::default()
        }
    }

    fn scratch(tag: &str) -> PathBuf {
        let base = std::env::var("CARGO_TARGET_TMPDIR")
            .map(PathBuf::from)
            .unwrap_or_else(|_| std::env::temp_dir());
        let d = base.join(format!("pkgcell-{tag}"));
        let _ = std::fs::remove_dir_all(&d);
        std::fs::create_dir_all(&d).unwrap();
        d
    }

    /// The set is the unit: a wheel alone is not a release.
    #[test]
    fn the_required_set_is_wheel_sums_and_provenance_and_the_sdist_when_declared() {
        assert_eq!(
            a_pkg().required_members(),
            vec![
                PackageMember::Wheel,
                PackageMember::Sdist,
                PackageMember::Sums,
                PackageMember::Provenance
            ]
        );
        let mut wheel_only = a_pkg();
        wheel_only.sdist = false;
        assert_eq!(
            wheel_only.required_members(),
            vec![PackageMember::Wheel, PackageMember::Sums, PackageMember::Provenance],
            "an sdist is required only when the declaration asks for one"
        );
    }

    /// Classification is by suffix and exact name.
    #[test]
    fn a_produced_file_is_classified_by_what_it_is() {
        use PackageFormat::Pip;
        assert_eq!(PackageMember::classify(Pip, "a-1.0-py3-none-any.whl"), PackageMember::Wheel);
        assert_eq!(PackageMember::classify(Pip, "a-1.0.tar.gz"), PackageMember::Sdist);
        assert_eq!(PackageMember::classify(Pip, SUMS_FILE), PackageMember::Sums);
        assert_eq!(PackageMember::classify(Pip, PROVENANCE_FILE), PackageMember::Provenance);
        assert_eq!(PackageMember::classify(Pip, "build.log"), PackageMember::Other);
        // Only the distributions are listed in the manifest.
        assert!(PackageMember::Wheel.is_distribution());
        assert!(PackageMember::Sdist.is_distribution());
        assert!(!PackageMember::Sums.is_distribution());
        assert!(!PackageMember::Provenance.is_distribution());
    }

    /// The manifest hashes the BYTES: a one-byte change of the same length moves it.
    #[test]
    fn the_manifest_hashes_the_bytes_and_lists_neither_itself_nor_a_stray_file() {
        let d = scratch("sums");
        std::fs::write(d.join("w-1.0-py3-none-any.whl"), b"aaaa").unwrap();
        std::fs::write(d.join("w-1.0.tar.gz"), b"sdist bytes").unwrap();
        std::fs::write(d.join("build.log"), b"noise").unwrap();
        std::fs::write(d.join(PROVENANCE_FILE), b"{}").unwrap();

        let first =
            std::fs::read_to_string(write_sha256sums(PackageFormat::Pip, &d).unwrap()).unwrap();
        let lines: Vec<&str> = first.lines().collect();
        assert_eq!(lines.len(), 2, "only the distributions are listed:\n{first}");
        for l in &lines {
            let (h, n) = l.split_once("  ").unwrap_or_else(|| panic!("bad line `{l}`"));
            assert_eq!(h.len(), 64);
            assert!(!n.contains('/'), "the name must be bare: `{l}`");
            assert_eq!(h, sha256_hex(&std::fs::read(d.join(n)).unwrap()));
        }
        assert!(!first.contains(SUMS_FILE), "the manifest listed itself");
        assert!(!first.contains(PROVENANCE_FILE), "the manifest listed the provenance");
        assert!(!first.contains("build.log"), "the manifest listed a stray file");

        // A one-byte change of the SAME LENGTH: a manifest keyed on name or size
        // would be identical here.
        std::fs::write(d.join("w-1.0-py3-none-any.whl"), b"aaab").unwrap();
        let second =
            std::fs::read_to_string(write_sha256sums(PackageFormat::Pip, &d).unwrap()).unwrap();
        assert_ne!(first, second, "a one-byte change left the manifest identical");
        // Idempotent: a re-run replaces, never appends.
        let third =
            std::fs::read_to_string(write_sha256sums(PackageFormat::Pip, &d).unwrap()).unwrap();
        assert_eq!(second, third, "a re-run grew the manifest");
    }

    /// An empty directory refuses rather than writing a manifest that checks nothing.
    #[test]
    fn an_empty_directory_is_a_refusal_and_not_an_empty_manifest() {
        let d = scratch("empty");
        std::fs::write(d.join("build.log"), b"noise").unwrap();
        let err = write_sha256sums(PackageFormat::Pip, &d).expect_err("must refuse");
        assert!(format!("{err:#}").contains("proves nothing"), "{err:#}");
    }

    /// LAW 2 on `verify_produced`: it is driven RED, member by member, before it is
    /// trusted to say a set is complete.
    #[test]
    fn a_missing_member_is_named_and_a_complete_set_passes() {
        let pkg = a_pkg();
        let d = scratch("verify");

        // Nothing at all.
        let err = verify_produced(&pkg, &d).expect_err("an empty dir is not a release");
        let m = format!("{err:#}");
        assert!(m.contains("wheel") && m.contains(SUMS_FILE) && m.contains(PROVENANCE_FILE), "{m}");

        // The wheel alone — the shape a build that "exited 0" leaves.
        std::fs::write(d.join("w-1.0-py3-none-any.whl"), b"wheel").unwrap();
        let m = format!("{:#}", verify_produced(&pkg, &d).expect_err("a wheel alone"));
        assert!(!m.contains("missing wheel"), "the wheel is present: {m}");
        assert!(m.contains("sdist"), "the declared sdist is missing and unnamed: {m}");

        // + sdist and sums, still no provenance.
        std::fs::write(d.join("w-1.0.tar.gz"), b"sdist").unwrap();
        write_sha256sums(PackageFormat::Pip, &d).unwrap();
        let m = format!("{:#}", verify_produced(&pkg, &d).expect_err("no provenance"));
        assert!(m.contains(PROVENANCE_FILE), "{m}");

        // The complete set: the distributions come back, in member order.
        std::fs::write(d.join(PROVENANCE_FILE), b"{}").unwrap();
        let paths = verify_produced(&pkg, &d).expect("a complete set");
        let names: Vec<String> =
            paths.iter().map(|p| p.file_name().unwrap().to_string_lossy().into_owned()).collect();
        assert_eq!(names, vec!["w-1.0-py3-none-any.whl", "w-1.0.tar.gz"]);
    }

    /// The content id moves with every axis that changes the produced set — the
    /// kernel cell's lesson, applied before it can be repeated.
    #[test]
    fn the_content_id_moves_with_what_changes_the_produced_set() {
        let base = a_pkg();
        let id = base.content_id();
        for mutate in [
            (|p: &mut PackageBuild| p.distribution = "other".into()) as fn(&mut PackageBuild),
            |p: &mut PackageBuild| p.out_dir = "elsewhere".into(),
            |p: &mut PackageBuild| p.sdist = false,
            |p: &mut PackageBuild| p.image = Some("pinned:2".into()),
            |p: &mut PackageBuild| p.inputs.push("a:b".into()),
            |p: &mut PackageBuild| p.from_variant.push("v".into()),
            |p: &mut PackageBuild| p.source = Some("pkg".into()),
        ] {
            let mut m = base.clone();
            mutate(&mut m);
            assert_ne!(m.content_id(), id, "a changed axis left the content id identical");
        }
        // And it does NOT move for a mutation of nothing — otherwise the check above
        // would pass for a hash of the clock.
        assert_eq!(base.clone().content_id(), id);
    }

    /// Validation refuses the declarations that cannot describe a release.
    #[test]
    fn a_declaration_that_cannot_name_its_release_is_refused_by_name() {
        for (mutate, needle) in [
            ((|p: &mut PackageBuild| p.distribution.clear()) as fn(&mut PackageBuild), "distribution"),
            (|p: &mut PackageBuild| p.out_dir.clear(), "out_dir"),
            (|p: &mut PackageBuild| p.out_dir = "/abs".into(), "absolute"),
            (|p: &mut PackageBuild| p.inputs.push("nocolon".into()), "nocolon"),
            (|p: &mut PackageBuild| p.name.clear(), "empty name"),
        ] {
            let mut p = a_pkg();
            mutate(&mut p);
            let err = p.validate().expect_err("must refuse");
            assert!(format!("{err:#}").contains(needle), "{err:#} does not name {needle}");
        }
        a_pkg().validate().expect("the declaration under test is valid");
    }

    /// The input rows split on the LAST colon.
    #[test]
    fn an_input_row_splits_source_from_destination() {
        let p = a_pkg();
        assert_eq!(
            p.input_pair("a/b.wasm:src/pkg/assets/b.wasm").unwrap(),
            ("a/b.wasm".to_string(), "src/pkg/assets/b.wasm".to_string())
        );
        assert!(p.input_pair("nodest:").is_err());
        assert!(p.input_pair(":nosrc").is_err());
    }
}

// ─────────────────────────────────────────────────────────────────────────────
// THE CONTAINER BUILDER — the production `PackageBuilder`, over a fake-able seam
// ─────────────────────────────────────────────────────────────────────────────

/// The bound on an in-container package build. `python -m build` on a staged
/// package is seconds; ten minutes is the "something is wrong" line, not a budget.
pub const DEFAULT_BUILD_TIMEOUT: Duration = Duration::from_secs(600);

/// The memory ceiling a package build container runs under. A build container is a
/// GUEST, never a host process — the same rule the kernel cell's `mem_limit_mb`
/// carries, and for the same 2026-08-20 reason.
pub const DEFAULT_MEM_LIMIT_MB: u32 = 2048;

/// **The container run this build needs** — everything the engine is told, as
/// data, so a test can assert on it without a daemon.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PackageRunRequest {
    /// The instance name (`package-<cell>`).
    pub name: String,
    /// The pinned toolchain image reference.
    pub image: String,
    /// The `/bin/sh -eu` script the format's tool is invoked by.
    pub script: String,
    /// Host bind mounts, podman `-v` strings — exactly one: `<stage>:/pkg`.
    pub binds: Vec<String>,
    /// The bound; overrun must stop the container and return an error.
    pub timeout: Duration,
    /// **Always false.** A package build runs airgapped, and it is not a field the
    /// declaration can turn on: a build that may reach the network resolves its own
    /// backend at build time, which makes the pinned image a suggestion. facett's
    /// verb measured exactly this — with isolation, `python -m build` creates a venv
    /// and DOWNLOADS setuptools, so the backend is a different version every run.
    /// The field exists so the request is complete and readable, not so it can vary.
    pub network: bool,
    /// The memory bound in MiB.
    pub mem_limit_mb: u32,
}

/// What came back from the container run. A non-zero exit is a VALUE here; the
/// orchestrator turns it into a named refusal, so the policy lives in one place.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct PackageRunOutcome {
    /// The container's exit code (`Some(0)` = clean), `None` if the engine reported none.
    pub exit_code: Option<i64>,
    /// Captured stdout lines.
    pub stdout: Vec<String>,
    /// Captured stderr lines.
    pub stderr: Vec<String>,
}

/// **The container seam** the package builder drives — one method, so a test
/// injects a fake and proves the orchestration (and every red arm) with no daemon.
/// The production impl is `DraupnirPackageRunner` (feature `package-build`).
pub trait PackageContainerRunner {
    /// Run the build container to completion within `req.timeout`.
    fn run(&self, req: &PackageRunRequest) -> Result<PackageRunOutcome>;
    /// The engine label the provenance records.
    fn engine(&self) -> &str {
        "draupnir-oci"
    }
    /// Build (or resolve) the pinned toolchain image and return the reference to
    /// run. The default is "the declaration's reference, as written" — correct for a
    /// runner handed an image that already exists, and the honest answer for one
    /// that cannot mint.
    fn resolve_image(&self, image: &str, _repo_root: &Path) -> Result<String> {
        Ok(image.to_string())
    }
}

/// **How the package SOURCE becomes the tree the container builds** — a seam,
/// because staging is where a repo's own rules live.
///
/// [`DeclaredStager`] is the general case and does exactly what the declaration
/// says: copy `source`, then land each `inputs` row. It is what the FORGE uses,
/// because the forge holds no dependency on any app and cannot know an app's rules.
///
/// # The residual, named rather than hidden
///
/// A repo may have preconditions on the staged bytes that no declaration can
/// express. facett has one and it is load-bearing: `facett-ops/src/jsguard.rs` refuses
/// to build a wheel around a loader page that DRAWS — banning `fillRect`,
/// `createElementNS` and charting libraries by name while ALLOWING `getContext`
/// inside the `facet_canvas_api` probe, because that probe is the read-back proving
/// the renderer is WebGPU. That rule needs to know which function is the probe. It
/// is facett's, it cannot live in this crate, and a forge running [`DeclaredStager`]
/// therefore does not run it.
///
/// So the two doors are NOT identical, and this is the difference, stated:
/// `cargo xtask publish-pip` stages through facett's own stager and enforces the
/// guard; `dwarves buildthing facett --variant facet-wrapped-web` stages through
/// the declared one and does not. [`PackageStager::describe`] exists so the build
/// log SAYS which stager ran — a difference a reader can see beats a difference
/// nobody wrote down. Closing it means moving the rule into facett's build-thing
/// `validate`, which the forge already has a mechanism to call over the wasm plugin
/// ABI; that is a facett row, not this seam's.
pub trait PackageStager {
    /// One line naming this stager, printed into the build log.
    fn describe(&self) -> String;
    /// Stage the package into `stage` (already created and empty).
    fn stage(&self, pkg: &PackageBuild, repo_root: &Path, stage: &Path) -> Result<()>;
}

/// Copy a directory tree. Small, explicit, and REFUSES symlinks rather than
/// following them into somewhere outside the package.
///
/// The refusal is not caution for its own sake: a staged package is bytes that
/// become a distribution, and a link is an instruction to include something the
/// declaration never named.
pub fn copy_tree(from: &Path, to: &Path) -> Result<()> {
    copy_tree_except(from, to, None)
}

/// [`copy_tree`], skipping one subtree.
///
/// `skip` is an ABSOLUTE path under `from` that must not be copied, and it exists
/// for one measured reason: a package's `out_dir` is frequently INSIDE its
/// `source` (facett's is `pkg-facet-wrapped/dist` under `pkg-facet-wrapped`), so a
/// stager that copied the source whole would stage **the previous release** into
/// the new one. The wheel from the last version ends up inside this version's
/// SDIST — shipped, listed by nothing, and impossible to explain to whoever finds
/// it. Found by `a_previous_releases_leftovers_are_cleared_from_the_out_dir`, which
/// was written expecting the clear of `out_dir` to be enough. It is not: clearing
/// the destination does nothing about a copy that has already been staged.
pub fn copy_tree_except(from: &Path, to: &Path, skip: Option<&Path>) -> Result<()> {
    std::fs::create_dir_all(to).map_err(|e| anyhow!("create {}: {e}", to.display()))?;
    for entry in std::fs::read_dir(from).map_err(|e| anyhow!("read {}: {e}", from.display()))? {
        let entry = entry.map_err(|e| anyhow!("read entry in {}: {e}", from.display()))?;
        let path = entry.path();
        if skip.is_some_and(|s| path == s) {
            continue;
        }
        let meta = entry
            .file_type()
            .map_err(|e| anyhow!("stat {}: {e}", path.display()))?;
        let dst = to.join(entry.file_name());
        if meta.is_symlink() {
            return Err(anyhow!(
                "{} is a symlink — the staged package must be plain bytes, not a link \
                 out of it",
                path.display()
            ));
        } else if meta.is_dir() {
            copy_tree_except(&path, &dst, skip)?;
        } else {
            std::fs::copy(&path, &dst)
                .map_err(|e| anyhow!("copy {} → {}: {e}", path.display(), dst.display()))?;
        }
    }
    Ok(())
}

/// **The stager the declaration describes, and nothing more** — the forge's.
///
/// Copies `source` into the stage, then lands every `inputs` row at the path under
/// the staged package the row names. An input that is missing or EMPTY is refused
/// by name before a container starts: packaging a zero-byte build output is how a
/// green build ships a wheel that loads nothing.
pub struct DeclaredStager;

impl PackageStager for DeclaredStager {
    fn describe(&self) -> String {
        "the declared stager (source + inputs; no repo-specific preconditions)".to_string()
    }

    fn stage(&self, pkg: &PackageBuild, repo_root: &Path, stage: &Path) -> Result<()> {
        let src = pkg.source.as_deref().ok_or_else(|| {
            anyhow!(
                "package '{}': no `source` — the declared stager copies a package \
                 source directory, and there is nothing to copy",
                pkg.name
            )
        })?;
        // The out_dir is SKIPPED when it lies under the source. It holds the
        // PREVIOUS release, and staging it would put the last version's wheel
        // inside this version's sdist. See `copy_tree_except`.
        let out = pkg.out_dir(repo_root);
        copy_tree_except(&repo_root.join(src), stage, Some(out.as_path()))?;
        for row in &pkg.inputs {
            let (from, to) = pkg.input_pair(row)?;
            let from = repo_root.join(&from);
            let meta = std::fs::metadata(&from).map_err(|e| {
                anyhow!(
                    "package '{}': input {} is not here ({e}).\n\nIt is a BUILD \
                     OUTPUT this package stages, not a file in the source — so it \
                     exists only after whatever produces it has been run, and this \
                     builder does not run that step. Nothing was staged and no \
                     container was started.",
                    pkg.name,
                    from.display()
                )
            })?;
            if meta.len() == 0 {
                return Err(anyhow!(
                    "package '{}': input {} is 0 bytes — packaging an empty build \
                     output is how a green build ships a distribution that loads \
                     nothing",
                    pkg.name,
                    from.display()
                ));
            }
            let dst = stage.join(&to);
            if let Some(parent) = dst.parent() {
                std::fs::create_dir_all(parent)
                    .map_err(|e| anyhow!("create {}: {e}", parent.display()))?;
            }
            std::fs::copy(&from, &dst)
                .map_err(|e| anyhow!("stage {} → {}: {e}", from.display(), dst.display()))?;
        }
        Ok(())
    }
}

/// The scratch tree a package is staged into: `<repo_root>/target/package-build/<cell>`.
///
/// **Outside the package source on purpose.** The script this design replaces
/// (facett's `make_wheel.py`) copied built wasm INTO a tracked source directory on
/// every run, i.e. wrote multi-megabyte build output into the repo. Every lane on
/// oden has been bitten by a shared checkout left dirty by somebody else's build.
/// The source is read FROM, never written to.
pub fn stage_dir(repo_root: &Path, pkg: &PackageBuild) -> PathBuf {
    repo_root.join("target").join("package-build").join(&pkg.name)
}

/// **The build script, from the declaration.**
///
/// `SOURCE_DATE_EPOCH` makes archive entry timestamps a function of the recipe
/// rather than of the minute the build ran, and `--no-isolation` is not a shortcut:
/// with isolation `python -m build` creates a venv and downloads its backend, which
/// cannot happen on `--network none` and would make the backend a different version
/// every run. The image carries the exact backend instead.
pub fn build_script(pkg: &PackageBuild, epoch: i64) -> String {
    match pkg.format {
        PackageFormat::Pip => {
            let dists = if pkg.sdist { "--wheel --sdist" } else { "--wheel" };
            format!(
                "export SOURCE_DATE_EPOCH={epoch}; \
                 cd /pkg && rm -rf dist && \
                 python3 -m build {dists} --no-isolation --outdir /pkg/dist && \
                 ls -l /pkg/dist"
            )
        }
    }
}

/// **The production [`PackageBuilder`]: stage, run the format's tool in the pinned
/// image with the network OFF, copy the produced distributions out.**
///
/// It writes no `SHA256SUMS` and no provenance — [`write_sha256sums`] and
/// [`crate::provenance::BuildProvenance::capture`] own those, driven by
/// [`crate::image_build::build_package`], so every door leaves the same release set
/// behind rather than each writing its own (LAW 5).
pub struct ContainerPackageBuilder<'a> {
    /// The container seam.
    pub runner: &'a dyn PackageContainerRunner,
    /// How the source becomes the staged tree — see [`PackageStager`] for the
    /// difference between the forge's stager and a repo's own.
    pub stager: &'a dyn PackageStager,
    /// The `SOURCE_DATE_EPOCH` the build stamps. A recipe constant, never `now()`:
    /// an archive whose entry timestamps come from the clock is a different file
    /// every run for no reason anybody declared.
    pub epoch: i64,
}

impl PackageBuilder for ContainerPackageBuilder<'_> {
    fn engine(&self) -> &str {
        self.runner.engine()
    }

    fn build(&self, pkg: &PackageBuild, repo_root: &Path) -> Result<Vec<PathBuf>> {
        let image = pkg.image.as_deref().ok_or_else(|| {
            anyhow!(
                "package '{}': no `image` — the format's tool ({}) runs INSIDE a \
                 pinned container and never on the host, so a cell with no image \
                 names no toolchain to run in",
                pkg.name,
                pkg.format.tool()
            )
        })?;
        let image = self.runner.resolve_image(image, repo_root)?;

        // 1. The stage, cleared and rebuilt so a previous run's leftovers cannot
        //    ride into this distribution.
        let stage = stage_dir(repo_root, pkg);
        if stage.exists() {
            std::fs::remove_dir_all(&stage)
                .map_err(|e| anyhow!("clear {}: {e}", stage.display()))?;
        }
        std::fs::create_dir_all(&stage)
            .map_err(|e| anyhow!("create {}: {e}", stage.display()))?;
        println!("package '{}': staging via {}", pkg.name, self.stager.describe());
        self.stager.stage(pkg, repo_root, &stage)?;

        // 2. The run.
        let req = PackageRunRequest {
            name: format!("package-{}", pkg.name),
            image: image.clone(),
            script: build_script(pkg, self.epoch),
            binds: vec![format!("{}:/pkg", stage.display())],
            timeout: DEFAULT_BUILD_TIMEOUT,
            network: false,
            mem_limit_mb: DEFAULT_MEM_LIMIT_MB,
        };
        let out = self.runner.run(&req)?;
        if out.exit_code != Some(0) {
            // Say what the build said. A red with no reason is what makes people
            // re-run a build to find out why it broke.
            let tail = |v: &[String], n: usize| -> String {
                v.iter().rev().take(n).rev().cloned().collect::<Vec<_>>().join("\n")
            };
            return Err(anyhow!(
                "package '{}': {} exited {:?} inside {image}\n{}\n{}",
                pkg.name,
                pkg.format.tool(),
                out.exit_code,
                tail(&out.stdout, 20),
                tail(&out.stderr, 30)
            ));
        }

        // 3. Copy the produced distributions out to where a release reads them.
        //    The out_dir is CLEARED first: a stale wheel from a previous version
        //    sitting beside the new one would be listed in `SHA256SUMS` and shipped
        //    as part of this release.
        let dist = pkg.out_dir(repo_root);
        if dist.exists() {
            std::fs::remove_dir_all(&dist).map_err(|e| anyhow!("clear {}: {e}", dist.display()))?;
        }
        std::fs::create_dir_all(&dist).map_err(|e| anyhow!("create {}: {e}", dist.display()))?;
        let produced = stage.join("dist");
        let mut out_paths: Vec<PathBuf> = Vec::new();
        for e in std::fs::read_dir(&produced).map_err(|e| {
            anyhow!(
                "package '{}': {} exited 0 but wrote no dist/ ({}): {e}",
                pkg.name,
                pkg.format.tool(),
                produced.display()
            )
        })? {
            let e = e.map_err(|e| anyhow!("read dist entry: {e}"))?;
            if !e.file_type().map(|t| t.is_file()).unwrap_or(false) {
                continue;
            }
            let dst = dist.join(e.file_name());
            std::fs::copy(e.path(), &dst)
                .map_err(|err| anyhow!("copy out {}: {err}", e.path().display()))?;
            if PackageMember::classify(pkg.format, &e.file_name().to_string_lossy())
                .is_distribution()
            {
                out_paths.push(dst);
            }
        }
        out_paths.sort();
        Ok(out_paths)
    }
}

#[cfg(test)]
mod container_tests {
    use super::*;
    use std::cell::RefCell;

    /// A runner that records the request and writes whatever the test says the
    /// container produced. No daemon, and every red arm reachable.
    struct FakeRunner {
        seen: RefCell<Vec<PackageRunRequest>>,
        /// `(filename, bytes)` written into the bind's `dist/`.
        writes: Vec<(String, Vec<u8>)>,
        exit: Option<i64>,
    }

    impl FakeRunner {
        fn ok(writes: Vec<(&str, &str)>) -> Self {
            FakeRunner {
                seen: RefCell::new(Vec::new()),
                writes: writes
                    .into_iter()
                    .map(|(n, b)| (n.to_string(), b.as_bytes().to_vec()))
                    .collect(),
                exit: Some(0),
            }
        }
    }

    impl PackageContainerRunner for FakeRunner {
        fn run(&self, req: &PackageRunRequest) -> Result<PackageRunOutcome> {
            self.seen.borrow_mut().push(req.clone());
            if self.exit == Some(0) {
                let stage = req.binds[0].split(':').next().unwrap();
                let dist = Path::new(stage).join("dist");
                std::fs::create_dir_all(&dist).unwrap();
                for (n, b) in &self.writes {
                    std::fs::write(dist.join(n), b).unwrap();
                }
            }
            Ok(PackageRunOutcome {
                exit_code: self.exit,
                stdout: vec!["building".into()],
                stderr: vec!["boom".into()],
            })
        }
        fn engine(&self) -> &str {
            "fake"
        }
    }

    fn scratch(tag: &str) -> PathBuf {
        let base = std::env::var("CARGO_TARGET_TMPDIR")
            .map(PathBuf::from)
            .unwrap_or_else(|_| std::env::temp_dir());
        let d = base.join(format!("pkgbuild-{tag}"));
        let _ = std::fs::remove_dir_all(&d);
        std::fs::create_dir_all(&d).unwrap();
        d
    }

    /// A repo with a package source and one build output to stage in.
    fn a_repo(tag: &str) -> (PathBuf, PackageBuild) {
        let root = scratch(tag);
        std::fs::create_dir_all(root.join("pkg/src/w/assets")).unwrap();
        std::fs::write(root.join("pkg/pyproject.toml"), b"[project]\nname='w'\n").unwrap();
        std::fs::write(root.join("pkg/src/w/__init__.py"), b"").unwrap();
        std::fs::create_dir_all(root.join("built")).unwrap();
        std::fs::write(root.join("built/w_bg.wasm"), b"\0asm\x01\0\0\0").unwrap();
        let pkg = PackageBuild {
            name: "w-web".into(),
            format: PackageFormat::Pip,
            distribution: "w".into(),
            out_dir: "pkg/dist".into(),
            source: Some("pkg".into()),
            sdist: true,
            image: Some("pinned-toolchain:1".into()),
            inputs: vec!["built/w_bg.wasm:src/w/assets/w_bg.wasm".into()],
            from_variant: Vec::new(),
            from_wasm: None,
        };
        (root, pkg)
    }

    /// The whole run, and every fact about it a reader would want asserted.
    #[test]
    fn a_package_build_stages_runs_airgapped_and_copies_the_distributions_out() {
        let (root, pkg) = a_repo("full");
        let runner = FakeRunner::ok(vec![("w-1.0-py3-none-any.whl", "WHEEL"), ("w-1.0.tar.gz", "SDIST")]);
        let builder = ContainerPackageBuilder { runner: &runner, stager: &DeclaredStager, epoch: 1_788_566_400 };

        let produced = builder.build(&pkg, &root).expect("builds");

        // The distributions, and ONLY the distributions, come back.
        let names: Vec<String> = produced
            .iter()
            .map(|p| p.file_name().unwrap().to_string_lossy().into_owned())
            .collect();
        assert_eq!(names, vec!["w-1.0-py3-none-any.whl", "w-1.0.tar.gz"]);
        // They are really on disk, in the declared out_dir, with the produced bytes.
        assert_eq!(std::fs::read(root.join("pkg/dist/w-1.0-py3-none-any.whl")).unwrap(), b"WHEEL");

        let seen = runner.seen.borrow();
        let req = &seen[0];
        // AIRGAPPED. Not a default a declaration can flip: see `PackageRunRequest::network`.
        assert!(!req.network, "a package build reached the network");
        assert_eq!(req.image, "pinned-toolchain:1");
        assert!(req.script.contains("SOURCE_DATE_EPOCH=1788566400"), "{}", req.script);
        assert!(req.script.contains("--no-isolation"), "{}", req.script);
        assert!(req.script.contains("--wheel --sdist"), "{}", req.script);
        assert_eq!(req.mem_limit_mb, DEFAULT_MEM_LIMIT_MB, "a build container with no ceiling");

        // The stage is OUTSIDE the source, and the source was not written to.
        let stage = stage_dir(&root, &pkg);
        assert!(stage.starts_with(root.join("target")), "{}", stage.display());
        assert!(
            !root.join("pkg/src/w/assets/w_bg.wasm").exists(),
            "the build wrote its output into the tracked source tree"
        );
        // …and the input really landed in the STAGE.
        assert_eq!(
            std::fs::read(stage.join("src/w/assets/w_bg.wasm")).unwrap(),
            b"\0asm\x01\0\0\0"
        );
    }

    /// A wheel-only release asks for a wheel only — the sdist flag reaches the tool.
    #[test]
    fn a_wheel_only_declaration_does_not_ask_for_an_sdist() {
        let (root, mut pkg) = a_repo("wheelonly");
        pkg.sdist = false;
        let runner = FakeRunner::ok(vec![("w-1.0-py3-none-any.whl", "WHEEL")]);
        let builder = ContainerPackageBuilder { runner: &runner, stager: &DeclaredStager, epoch: 7 };
        builder.build(&pkg, &root).expect("builds");
        let seen = runner.seen.borrow();
        assert!(seen[0].script.contains("--wheel "), "{}", seen[0].script);
        assert!(!seen[0].script.contains("--sdist"), "{}", seen[0].script);
    }

    /// Every red arm, driven. Each of these is a way a build can look green.
    #[test]
    fn the_refusals_are_named_and_each_one_can_fire() {
        let (root, pkg) = a_repo("red");

        // ① exited 0 and wrote NOTHING. The classic false success.
        let empty = FakeRunner::ok(vec![]);
        let b = ContainerPackageBuilder { runner: &empty, stager: &DeclaredStager, epoch: 1 };
        // An empty dist/ is not an error at THIS layer (the dir exists, it is just
        // empty) — it comes back as an empty distribution list, and
        // `verify_produced` is what refuses. Assert the shape that makes that true.
        assert!(b.build(&pkg, &root).expect("no error here").is_empty());
        assert!(
            verify_produced(&pkg, &pkg.out_dir(&root)).is_err(),
            "a build that produced nothing was not refused anywhere"
        );

        // ② a non-zero exit is named, WITH what the tool said.
        let mut failing = FakeRunner::ok(vec![]);
        failing.exit = Some(2);
        let b = ContainerPackageBuilder { runner: &failing, stager: &DeclaredStager, epoch: 1 };
        let e = format!("{:#}", b.build(&pkg, &root).expect_err("must refuse"));
        assert!(e.contains("exited Some(2)") && e.contains("boom"), "{e}");

        // ③ no pinned image ⇒ refused before anything runs.
        let mut unpinned = pkg.clone();
        unpinned.image = None;
        let ok = FakeRunner::ok(vec![]);
        let b = ContainerPackageBuilder { runner: &ok, stager: &DeclaredStager, epoch: 1 };
        let e = format!("{:#}", b.build(&unpinned, &root).expect_err("must refuse"));
        assert!(e.contains("runs INSIDE a pinned container"), "{e}");

        // ④ an EMPTY input is refused by name — packaging a zero-byte build output
        //    is how a green build ships a wheel that loads nothing.
        std::fs::write(root.join("built/w_bg.wasm"), b"").unwrap();
        let b = ContainerPackageBuilder { runner: &ok, stager: &DeclaredStager, epoch: 1 };
        let e = format!("{:#}", b.build(&pkg, &root).expect_err("must refuse"));
        assert!(e.contains("0 bytes"), "{e}");

        // ⑤ a MISSING input names the path.
        std::fs::remove_file(root.join("built/w_bg.wasm")).unwrap();
        let e = format!("{:#}", b.build(&pkg, &root).expect_err("must refuse"));
        assert!(e.contains("w_bg.wasm"), "{e}");
    }

    /// **A previous release must not ride into this one — and it can ride in TWO
    /// ways, which is what this test found.**
    ///
    /// The obvious one is the destination: a 0.9 wheel left in `out_dir` would be
    /// listed by `SHA256SUMS` and taken by a downloader as part of the 1.0 release.
    /// Clearing `out_dir` handles that.
    ///
    /// The one this test was NOT written for, and found anyway: facett's `out_dir`
    /// is `pkg-facet-wrapped/dist`, INSIDE its `source` `pkg-facet-wrapped`. So the
    /// stager copied the old release INTO THE STAGE, and `python -m build` then put
    /// last version's wheel inside this version's SDIST — shipped, listed by
    /// nothing, and unexplainable to whoever eventually opened the tarball. Clearing
    /// the destination does nothing about a copy already staged, which is why the
    /// fix is in `copy_tree_except` and why this test asserts on the STAGE too.
    #[test]
    fn a_previous_release_rides_into_neither_the_out_dir_nor_the_sdist() {
        let (root, pkg) = a_repo("stale");
        std::fs::create_dir_all(root.join("pkg/dist")).unwrap();
        std::fs::write(root.join("pkg/dist/w-0.9-py3-none-any.whl"), b"OLD").unwrap();

        let runner = FakeRunner::ok(vec![("w-1.0-py3-none-any.whl", "NEW")]);
        let b = ContainerPackageBuilder { runner: &runner, stager: &DeclaredStager, epoch: 1 };
        b.build(&pkg, &root).expect("builds");

        assert!(
            !root.join("pkg/dist/w-0.9-py3-none-any.whl").exists(),
            "the 0.9 wheel survived in the out_dir — SHA256SUMS would list it and a \
             downloader would take it as part of this release"
        );
        // THE SECOND WAY: it must never have reached the staged package at all.
        let staged_old = stage_dir(&root, &pkg).join("dist").join("w-0.9-py3-none-any.whl");
        assert!(
            !staged_old.exists(),
            "the 0.9 wheel was STAGED ({}) — `python -m build` would package the \
             previous release inside this release's sdist",
            staged_old.display()
        );
        // …and the rest of the source did reach it, so the skip is a skip and not a
        // stager that quietly copied nothing.
        assert!(stage_dir(&root, &pkg).join("pyproject.toml").is_file(), "nothing was staged");
    }

    /// The stager is a SEAM, and the log says which one ran. The difference between
    /// the forge's stager and a repo's own is real (facett's jsguard), so it must be
    /// visible rather than assumed.
    #[test]
    fn the_stager_is_named_and_a_repo_can_supply_its_own() {
        struct Guarded;
        impl PackageStager for Guarded {
            fn describe(&self) -> String {
                "facett's stager (wasm bundle check + the no-JS-graphics guard)".into()
            }
            fn stage(&self, _p: &PackageBuild, _r: &Path, _s: &Path) -> Result<()> {
                Err(anyhow!("the loader page draws: fillRect at line 12"))
            }
        }
        let (root, pkg) = a_repo("stager");
        let runner = FakeRunner::ok(vec![]);
        let b = ContainerPackageBuilder { runner: &runner, stager: &Guarded, epoch: 1 };
        let e = format!("{:#}", b.build(&pkg, &root).expect_err("the guard refuses"));
        assert!(e.contains("fillRect"), "{e}");
        assert!(
            runner.seen.borrow().is_empty(),
            "the container ran anyway — a staging refusal must happen BEFORE the build"
        );
        assert!(DeclaredStager.describe().contains("no repo-specific"));
    }
}

// ─────────────────────────────────────────────────────────────────────────────
// THE LIVE LEG — feature `package-build`
// ─────────────────────────────────────────────────────────────────────────────

/// **The production [`PackageContainerRunner`]**, over draupnir's `backend-oci`
/// engine — the SAME bollard leg the kernel builder and dwarves already drive,
/// reused rather than re-rolled.
///
/// It also MINTS the toolchain image reproducibly when handed a containerfile,
/// through `build_image_reproducible_pinned`: an image id is the digest of the
/// image CONFIG, so with the timestamp parameter set it is a function of the recipe
/// rather than of the minute the build ran. That is what makes `image` a pin
/// instead of the identity of one minting event.
#[cfg(feature = "package-build")]
pub struct DraupnirPackageRunner {
    boot: draupnir::container::ContainerBoot,
}

#[cfg(feature = "package-build")]
impl Default for DraupnirPackageRunner {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(feature = "package-build")]
impl DraupnirPackageRunner {
    /// A runner over a fresh draupnir container backend.
    pub fn new() -> Self {
        DraupnirPackageRunner { boot: draupnir::container::ContainerBoot::new() }
    }
}

#[cfg(feature = "package-build")]
impl PackageContainerRunner for DraupnirPackageRunner {
    fn engine(&self) -> &str {
        "draupnir-oci"
    }

    fn run(&self, req: &PackageRunRequest) -> Result<PackageRunOutcome> {
        let mut spec = draupnir::BootSpec::container(&crate::cargo_container::scoped_container_name(&req.name, &req.binds), &req.image)
            .with_cmd(["/bin/sh", "-eu", "-c", req.script.as_str()])
            .with_mem_limit_mb(req.mem_limit_mb);
        if !req.network {
            spec = spec.with_net(draupnir::NetMode::None);
        }
        spec.validate().map_err(|e| anyhow!("package build spec: {e}"))?;
        let opts = draupnir::container::RunOptions::bounded(req.timeout, Duration::from_secs(1));
        let out = self
            .boot
            .run_to_completion_with_binds(&spec, &opts, &req.binds)
            .map_err(|e| anyhow!("package build container: {e}"))?;
        Ok(PackageRunOutcome {
            exit_code: out.exit_code,
            stdout: out.stdout,
            stderr: out.stderr,
        })
    }
}