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

nordisk/holger

server/lib/src/sources.rs

//! **Tier 1's RON: `sources:`, `steps:` and `rules:`** (Holger G0, owner order 2026-10-23).
//!
//! One shape for where packages come from and what they pass on the way in:
//!
//! ```ron
//! sources: [
//!     (name: "team", kind: upload, gates: (license: (deny: ["AGPL-3.0-only"]))),
//!     (name: "vendor", kind: upload, importers: ["ci"], gates: (provenance: require(slsa_min: 2))),
//!     (name: "crates-io", kind: crates, url: "https://index.crates.io/"),  // `online,jobs`: through the steps
//!     (name: "npmjs", kind: npm, url: "https://registry.npmjs.org/"),
//!     (name: "central", kind: maven, url: "https://repo1.maven.org/maven2/"),
//!     (name: "upstream", kind: holger, url: "http://up:50051",
//!      repositories: [(ron_from: "cargo", ron_to: "cargo")]),
//! ],
//! steps: (approvals: 2, scan: [vulnerability, license]),
//! rules: (require_signed: false, cve_max: high, deny_licenses: ["GPL-3.0-only"]),
//! ```
//!
//! * `sources` lowers onto the shapes that already exist: an `upload` source is a door
//!   into the intake chain's `incoming` repository, `crates` a `rust-remote` repository
//!   named as an upstream of the repositories in `serves`, and `holger` a
//!   `mirror.ron_sources` entry.
//! * `steps` is the new spelling of `promotion:` with its `intake:` block: the four
//!   intake repositories (`incoming`, `quarantine`, `approved`, `production`), the
//!   scanners, the approvals, the per-state policy, and the default `gates` every
//!   source starts from.
//! * `rules` is the GLOBAL FLOOR. It holds for every source. A source's `gates` (and
//!   the `steps.gates` defaults) may only TIGHTEN it; a gate that would loosen it is
//!   refused at load, by name, with the rule it breaks.
//!
//! Every block refuses an unknown key by name.
//!
//! An artifact's EFFECTIVE POLICY is the floor plus its source's gates
//! ([`EffectivePolicy`]). It is computed when the artifact is ingested, kept on every
//! line of its intake record, and checked at SCANNED against the scanners' evidence
//! ([`EffectivePolicy::refusals`]); a refusal names the rule and who set it (the
//! floor, `steps.gates`, or the source).
//!
//! The provenance fields are lane G3a's shape (`holger-provenance`): `ProvenanceGate`
//! is `require(…) | prefer | off`, and the floor's provenance fields are flattened into
//! `rules`. Until G3a's provenance scanner is in this build a required signature FAILS
//! CLOSED by name, and the default floor does not require one
//! ([`PROVENANCE_IN_BUILD`]).

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

/// **Whether this build carries a provenance scanner** (lane G3a). The default floor
/// requires a signature exactly when the build can check one: a security gateway's
/// default is to fail closed on every rule it can check, and to say which rules it
/// cannot check yet (README "Tier 1 in five minutes"). True since lane G3a's verifier
/// (`holger-provenance`) is the chain's provenance slot: the default floor requires a
/// signature.
pub const PROVENANCE_IN_BUILD: bool = true;

/// One entry of `sources:`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Source {
    /// The source's name. An upload source's artifacts carry it on their record.
    pub name: String,
    pub kind: SourceKind,
    /// `holger`: the upstream Holger's gRPC endpoint. `python`/`oci`/`npm`/`maven`/`crates`/
    /// `apk`: the index or registry pulled through the steps. Absent on a remote kind: the
    /// source SERVES ONLY ([`Source::serves_only`]). Refused on `upload`.
    #[serde(default)]
    pub url: Option<String>,
    /// `upload`: the principals that may upload as this source. Empty = any Importer.
    #[serde(default)]
    pub importers: Vec<String>,
    /// `crates`: the local repositories that pull through this source.
    #[serde(default)]
    pub serves: Vec<String>,
    /// `holger`: the credential the mirror presents (`Anonymous`, `Tls`, …).
    /// `python`/`oci`/`npm`/`maven`/`crates`: a basic login `(username: "…", password_env: "…")`.
    #[serde(default)]
    pub credential: Option<SourceCredential>,
    /// `python`: which files of the newest release are pulled (`sdist`, or a part of
    /// the file name). `python`/`oci` fields below pass to the pull-through as written.
    #[serde(default)]
    pub select: Vec<String>,
    /// `python`: at most this many files per release.
    #[serde(default)]
    pub max_files: Option<usize>,
    /// `oci`: the platforms kept from an image index (`linux/amd64`). Empty = all.
    #[serde(default)]
    pub platforms: Vec<String>,
    /// A pull-through source: the cap on one package, in bytes.
    #[serde(default)]
    pub max_bytes: Option<u64>,
    /// A pull-through source: how old a page's or tag's last pull may be before it is asked again.
    #[serde(default)]
    pub refresh_secs: Option<u64>,
    /// A pull-through source: how long a request waits for its pull before it is
    /// answered "not yet" (default 0; 30 s for `maven`: Maven before 3.9.5 does not retry a 503,
    /// 3.9.5 and later retry it three times).
    #[serde(default)]
    pub wait_secs: Option<u64>,
    /// `holger`: how often the mirror cycle runs, in seconds.
    #[serde(default)]
    pub every_secs: Option<u64>,
    /// `holger`: which upstream repository lands in which local one.
    #[serde(default)]
    pub repositories: Vec<crate::mirror_cycle::MirrorPair>,
    /// This source's gates: they tighten the floor for every artifact from it.
    #[cfg(feature = "intake")]
    #[serde(default)]
    pub gates: Option<Gates>,
    /// **Per-source automatic approval** (for the pull-through lane): `off` (the default)
    /// or `when(provenance: require, cve_max: medium)`. It decides only the human
    /// approval step and never skips a scan or a gate; the floor must allow it
    /// (`rules: (allow_auto_approve: true)`).
    #[cfg(feature = "intake")]
    #[serde(default)]
    pub auto_approve: AutoApprove,
}

/// A source's `credential`: the mirror's, or a remote's basic login.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SourceCredential {
    Mirror(crate::mirror_cycle::MirrorCredential),
    Login(RemoteLogin),
}

/// A remote source's basic login. The password is read from the environment or a
/// file at boot and never written anywhere.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RemoteLogin {
    pub username: String,
    #[serde(default)]
    pub password_env: Option<String>,
    #[serde(default)]
    pub password_file: Option<String>,
}

/// What a source is.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SourceKind {
    /// Packages pushed into the intake chain's `incoming` repository.
    Upload,
    /// A cargo sparse index (crates.io). With `url` it is pulled THROUGH the steps
    /// (`pull_intake`, `--features online,jobs`); with `serves` it is the old
    /// `rust-remote` upstream of those repositories, served as fetched; with neither it
    /// serves only ([`Source::serves_only`]).
    Crates,
    /// An upstream Holger, mirrored on a timer (`mirror.ron_sources`).
    Holger,
    /// A PEP 503 / PEP 691 index (PyPI, Chainguard Libraries for Python), pulled THROUGH
    /// the steps (`pull_intake`); needs `--features online,jobs`. Without `url` it serves
    /// only ([`Source::serves_only`]), as do the next three.
    Python,
    /// An npm registry (registry.npmjs.org, Chainguard Libraries for JavaScript), pulled
    /// THROUGH the steps; needs `--features online,jobs`.
    Npm,
    /// A Maven 2 repository (Maven Central, Chainguard Libraries for Java), pulled
    /// THROUGH the steps; needs `--features online,jobs`.
    Maven,
    /// An OCI registry (`cgr.dev`), pulled THROUGH the steps; needs `--features online,jobs`.
    Oci,
    /// APK packages (Wolfi, Chainguard, Alpine). With `url` (the repository,
    /// `https://packages.wolfi.dev/os`) they are pulled THROUGH the steps (`pull_intake::apk`,
    /// lane T8; `--features online,jobs`), each held against the upstream's signed
    /// `APKINDEX.tar.gz`. Without `url` they are uploaded into the steps like `upload`, their
    /// provenance the signed index read from `<data_root>/holger-provenance/apk/<name>/`
    /// (lane G3). Either way the index verifies against the apk keys the trust bundle pins.
    Apk,
}

impl SourceKind {
    pub fn name(self) -> &'static str {
        match self {
            SourceKind::Upload => "upload",
            SourceKind::Crates => "crates",
            SourceKind::Holger => "holger",
            SourceKind::Python => "python",
            SourceKind::Npm => "npm",
            SourceKind::Maven => "maven",
            SourceKind::Oci => "oci",
            SourceKind::Apk => "apk",
        }
    }

    /// A source whose artifacts are pushed into the steps (`upload`, `apk`).
    pub fn is_upload(self) -> bool {
        matches!(self, SourceKind::Upload | SourceKind::Apk)
    }

    /// A kind whose every source with a `url` the pull-through ingests into the steps as
    /// `pull-through:<name>` (`crates` decides per source, [`Source::pulls_into_the_chain`]).
    pub fn pulls_into_the_chain(self) -> bool {
        matches!(
            self,
            SourceKind::Python | SourceKind::Oci | SourceKind::Npm | SourceKind::Maven
        )
    }
}

impl Source {
    /// A remote that the pull-through ingests into the steps as `pull-through:<name>`:
    /// `python`, `oci`, `npm`, `maven`, `crates` (without `serves`) or `apk`, with a `url`.
    pub fn pulls_into_the_chain(&self) -> bool {
        self.url.is_some() && (self.is_remote() || self.kind == SourceKind::Apk)
    }

    /// ★ **A source that SERVES ONLY** (a downstream's view, H3; field test F9): `python`,
    /// `oci`, `npm`, `maven` or `crates` (without `serves`) with no `url`. The AVAILABLE set
    /// that arrived under its name (a signed bundle, a sync from the upstream Holger) is
    /// served with its kind's own index (`/v2/<production>/<name>/…`,
    /// `/<production>/<name>/…`) and a miss is never fetched (`holger-downstream-4`), so it
    /// needs no `online` build. It lowers onto a `pull_through` source with an empty `url`.
    pub fn serves_only(&self) -> bool {
        self.url.is_none() && self.is_remote()
    }

    /// A kind the pull-through serves under the source's name: `python`, `oci`, `npm`,
    /// `maven`, and `crates` without `serves`.
    fn is_remote(&self) -> bool {
        self.kind.pulls_into_the_chain()
            || (self.kind == SourceKind::Crates && self.serves.is_empty())
    }

    /// A source whose artifacts are pushed into the steps: `upload`, and `apk` without `url`.
    pub fn is_upload(&self) -> bool {
        self.kind.is_upload() && !self.pulls_into_the_chain()
    }

    /// A pull-through or mirror source whose bytes are served or copied as fetched and
    /// do not pass the steps (A18 §2.6 gap 5): `holger`, and `crates` with `serves`.
    pub fn skips_the_chain(&self) -> bool {
        self.kind == SourceKind::Holger
            || (self.kind == SourceKind::Crates && !self.serves.is_empty())
    }
}

/// **How Holger reads its RON**: with `implicit_some`, so an optional field is written
/// as its value (`rules: (cve_max: high)`, `gates: (provenance: prefer)`) and the old
/// `Some(...)` spelling still loads.
pub fn ron_options() -> ron::Options {
    ron::Options::default().with_default_extension(ron::extensions::Extensions::IMPLICIT_SOME)
}

/// Refuse what a source list cannot mean, by name. `online` is whether this build
/// carries the crates.io pull-through. A source that serves only ([`Source::serves_only`])
/// fetches nothing, so it needs no `online` build, only the `jobs` build whose doors serve it.
pub fn validate_sources(sources: &[Source], online: bool) -> Result<()> {
    let mut seen = std::collections::BTreeSet::new();
    for s in sources {
        let n = &s.name;
        if n.trim().is_empty() || n.contains('/') {
            bail!("config `sources`: a source needs a name without '/' (got {n:?})");
        }
        if !seen.insert(n.as_str()) {
            bail!("config `sources`: two sources are named '{n}'; a name is one source");
        }
        let kind = s.kind.name();
        if s.kind == SourceKind::Crates && !s.serves.is_empty() && s.url.is_some() {
            bail!(
                "config `sources`: source '{n}' (kind `crates`) has both `url` (pulled through the \
                 steps) and `serves` (the old upstream served as fetched); keep one"
            );
        }
        match s.kind {
            _ if s.pulls_into_the_chain() && !(online && cfg!(feature = "jobs")) => bail!(
                "config `sources`: source '{n}' has kind `{kind}`, a pull-through into the steps, \
                 which needs the `online` and `jobs` build (`cargo build --release --features \
                 online,jobs`)"
            ),
            _ if s.serves_only() && !cfg!(all(feature = "jobs", feature = "intake")) => bail!(
                "config `sources`: source '{n}' (kind `{kind}`) names no `url`: it serves only what \
                 arrived under its name (a downstream's view of what its upstream sent), and the \
                 doors that serve it are in the `jobs` build (every `holger-downstream`; \
                 `cargo build --release --features jobs`)"
            ),
            SourceKind::Crates if !online && s.skips_the_chain() => bail!(
                "config `sources`: source '{n}' has kind `crates`, the crates.io pull-through, \
                 which needs the `online` build (`cargo build --release --features online`)"
            ),
            _ => {}
        }
        if s.serves_only() {
            #[cfg_attr(not(feature = "intake"), allow(unused_mut))]
            let mut shaped = vec![
                ("select", !s.select.is_empty()),
                ("max_files", s.max_files.is_some()),
                ("platforms", !s.platforms.is_empty()),
                ("max_bytes", s.max_bytes.is_some()),
                ("refresh_secs", s.refresh_secs.is_some()),
                ("wait_secs", s.wait_secs.is_some()),
                ("credential", s.credential.is_some()),
                ("importers", !s.importers.is_empty()),
                ("every_secs", s.every_secs.is_some()),
                ("repositories", !s.repositories.is_empty()),
            ];
            // What passes this side's steps from it: nothing (it passed the upstream's).
            #[cfg(feature = "intake")]
            shaped.extend([
                ("gates", s.gates.is_some()),
                (
                    "auto_approve",
                    matches!(s.auto_approve, AutoApprove::When { .. }),
                ),
            ]);
            if let Some((field, _)) = shaped.iter().find(|(_, set)| *set) {
                bail!(
                    "config `sources`: source '{n}' (kind `{kind}`) names no `url`: it serves only \
                     what arrived under its name and fetches nothing, so it takes no `{field}`"
                );
            }
        }
        let not_here = |field: &str, set: bool| -> Result<()> {
            if set {
                bail!("config `sources`: source '{n}' (kind `{kind}`) takes no `{field}`");
            }
            Ok(())
        };
        let remote = s.pulls_into_the_chain();
        not_here(
            "url",
            s.url.is_some() && s.kind != SourceKind::Holger && !remote,
        )?;
        not_here(
            "select",
            !s.select.is_empty() && s.kind != SourceKind::Python,
        )?;
        not_here(
            "max_files",
            s.max_files.is_some() && s.kind != SourceKind::Python,
        )?;
        not_here(
            "platforms",
            !s.platforms.is_empty() && s.kind != SourceKind::Oci,
        )?;
        not_here("max_bytes", s.max_bytes.is_some() && !remote)?;
        not_here("refresh_secs", s.refresh_secs.is_some() && !remote)?;
        not_here("wait_secs", s.wait_secs.is_some() && !remote)?;
        match (&s.credential, s.kind) {
            (Some(SourceCredential::Login(_)), _) if !remote => {
                bail!("config `sources`: source '{n}' (kind `{kind}`) takes no basic login")
            }
            (Some(SourceCredential::Mirror(_)), k) if k != SourceKind::Holger => {
                bail!("config `sources`: source '{n}' (kind `{kind}`) takes `credential: (username: …)`, not a mirror credential")
            }
            _ => {}
        }
        not_here("importers", !s.importers.is_empty() && !s.is_upload())?;
        not_here(
            "serves",
            !s.serves.is_empty() && s.kind != SourceKind::Crates,
        )?;
        let mirror_only = s.kind != SourceKind::Holger;
        not_here(
            "credential",
            s.credential.is_some() && mirror_only && !remote,
        )?;
        not_here("every_secs", s.every_secs.is_some() && mirror_only)?;
        not_here("repositories", !s.repositories.is_empty() && mirror_only)?;
        if s.kind == SourceKind::Holger && s.url.is_none() {
            bail!("config `sources`: source '{n}' (kind `holger`) needs `url`, the upstream's gRPC endpoint");
        }
    }
    Ok(())
}

#[cfg(feature = "intake")]
pub use policy::*;

#[cfg(feature = "intake")]
mod policy {
    use super::*;
    use crate::scanner::{FindingSeverity, ScanEvidence, ScanVerdict, ScannerKind};

    /// The licence judgement a policy re-reads at SCANNED; a build without scanners
    /// has none (H3).
    #[cfg(feature = "scanners")]
    pub type LicenseJudgement = crate::scanner::license::LicenseJudgement;
    #[cfg(not(feature = "scanners"))]
    pub type LicenseJudgement = std::convert::Infallible;

    // ── the shared vocabulary ────────────────────────────────────────────────

    /// `on` | `off`.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
    #[serde(rename_all = "snake_case")]
    pub enum Switch {
        On,
        Off,
    }

    /// `secrets:` (lane HOLGER-G3): what a secret found in an artifact means.
    /// `block` refuses every high or critical finding of the secrets scanner, by
    /// name; `warn` records them; `off` lets them pass (the record still lists
    /// them). Medium and lower findings (a bare `.env`, a high-entropy string next
    /// to a secret-like name) always warn. Strictness: `off` < `warn` < `block`.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
    #[serde(rename_all = "snake_case")]
    pub enum SecretsMode {
        Off,
        Warn,
        Block,
    }

    impl SecretsMode {
        pub fn as_str(self) -> &'static str {
            match self {
                SecretsMode::Off => "off",
                SecretsMode::Warn => "warn",
                SecretsMode::Block => "block",
            }
        }
    }

    /// `require` | `off`.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
    #[serde(rename_all = "snake_case")]
    pub enum Need {
        Require,
        Off,
    }

    /// The provenance vocabulary is lane G3a's (`holger-provenance`): one shape, one parser.
    pub use holger_provenance::{
        check_tightens, effective, BuilderRule, Identity, KeyAlg, ProvenanceFloor, ProvenanceGate,
        TrustedKey,
    };

    /// How strict a provenance mode is, so "the strictest of two" is a comparison.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
    #[serde(rename_all = "snake_case")]
    pub enum ProvenanceMode {
        Off,
        Prefer,
        Require,
    }

    /// A source's `auto_approve:`.
    #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
    #[serde(rename_all = "snake_case", deny_unknown_fields)]
    pub enum AutoApprove {
        /// A person approves. The default.
        #[default]
        Off,
        /// Approve automatically when the scan evidence meets these conditions, which may
        /// only be as strict as the artifact's effective policy or stricter.
        When {
            /// `require`: a `pass` from the provenance verifier is on the SCANNED
            /// record. Absent = what the artifact's policy already demands.
            #[serde(default)]
            provenance: Option<Need>,
            /// The highest advisory severity the scan may report. Absent = the policy's.
            #[serde(default)]
            cve_max: Option<FindingSeverity>,
        },
    }

    /// The actor a pull-through source ingests as.
    pub fn pull_through_actor(source: &str) -> String {
        format!("pull-through:{source}")
    }

    /// The automatic approver of a source; never equal to its importer.
    pub fn auto_approver(source: &str) -> String {
        format!("auto-approve:{source}")
    }

    /// An effective auto-approval rule: its conditions, already at least as strict as the
    /// artifact's policy.
    #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
    pub struct AutoApproveRule {
        pub provenance: ProvenanceMode,
        pub cve_max: FindingSeverity,
        /// The approver it votes as: `auto-approve:<source>`.
        pub approver: String,
    }

    /// `cve: (max: high)`: the highest advisory severity an artifact may carry.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
    #[serde(deny_unknown_fields)]
    pub struct CveGate {
        pub max: FindingSeverity,
    }

    /// `license: (deny: [...])`: SPDX ids refused on top of the floor's.
    #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
    #[serde(deny_unknown_fields)]
    pub struct LicenseGate {
        #[serde(default)]
        pub deny: Vec<String>,
    }

    /// A source's `gates:` (and `steps.gates`, the defaults). Every field is optional;
    /// absent = what the floor (or `steps.gates`) says.
    #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
    #[serde(deny_unknown_fields)]
    pub struct Gates {
        #[serde(default)]
        pub provenance: Option<ProvenanceGate>,
        #[serde(default)]
        pub sbom: Option<Need>,
        #[serde(default)]
        pub malware: Option<Switch>,
        #[serde(default)]
        pub license: Option<LicenseGate>,
        #[serde(default)]
        pub cve: Option<CveGate>,
        /// `block | warn | off`: what a secret found in the artifact means.
        #[serde(default)]
        pub secrets: Option<SecretsMode>,
        /// File formats (extensions, `pickle` for the pickle family) refused at ingest.
        #[serde(default)]
        pub block_formats: Vec<String>,
    }

    // ── the floor ─────────────────────────────────────────────────────────────

    fn default_cve_max() -> FindingSeverity {
        FindingSeverity::High
    }
    fn default_on() -> Switch {
        Switch::On
    }
    fn default_secrets() -> SecretsMode {
        SecretsMode::Block
    }
    fn default_block_formats() -> Vec<String> {
        vec!["pickle".into()]
    }
    fn default_signed() -> bool {
        PROVENANCE_IN_BUILD
    }

    /// **`rules:`, the global floor.** Every field's default is the security gateway's
    /// default, so writing one rule never loosens another.
    #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
    #[serde(deny_unknown_fields)]
    pub struct Rules {
        /// Nothing unsigned comes in. Default: on when this build checks signatures.
        #[serde(default = "default_signed")]
        pub require_signed: bool,
        /// Every artifact carries verified provenance. Default as `require_signed`.
        #[serde(default = "default_signed")]
        pub require_provenance: bool,
        /// The least SLSA build level. 0 = no level asked.
        #[serde(default)]
        pub slsa_min: u8,
        /// The signer identities trusted at all; a source may only narrow them.
        #[serde(default)]
        pub identities: Vec<Identity>,
        /// The SLSA builders trusted at all: `"id"` or `(id: "…", level: N)`.
        #[serde(default)]
        pub builders: Vec<BuilderRule>,
        #[serde(default)]
        pub keys: Vec<TrustedKey>,
        /// The signed sigstore trust bundle the provenance scanner verifies against.
        #[serde(default)]
        pub trust_root: Option<String>,
        /// Every artifact needs an SBOM.
        #[serde(default)]
        pub require_sbom: bool,
        /// File inspection and every configured malware engine must pass. Default on.
        #[serde(default = "default_on")]
        pub malware: Switch,
        /// The highest advisory severity allowed. Default `high` (critical is refused).
        #[serde(default = "default_cve_max")]
        pub cve_max: FindingSeverity,
        /// What a secret found in an artifact means: `block` (the default: high and
        /// critical findings are refused), `warn` or `off`.
        #[serde(default = "default_secrets")]
        pub secrets: SecretsMode,
        /// SPDX ids refused everywhere.
        #[serde(default)]
        pub deny_licenses: Vec<String>,
        /// Formats refused everywhere. Default `["pickle"]`.
        #[serde(default = "default_block_formats")]
        pub block_formats: Vec<String>,
        /// Whether any source may approve automatically (`auto_approve: when(...)`). Default
        /// false: auto-approval needs this AND the source asking for it.
        #[serde(default)]
        pub allow_auto_approve: bool,
        /// A pull-through or mirror source serves bytes that skip the steps. Default
        /// false: such a source is refused at load until the floor says it accepts that.
        #[serde(default)]
        pub allow_unchecked_sources: bool,
        /// **A native executable inside a crate** (HOL-DOGFOOD-4, owner 2026-11-03). `false`
        /// (the default): the inspection refuses it, as before. `true`: it comes in with a
        /// warning on the package page (the inspection's crate rule becomes `executables: warn`).
        #[serde(default)]
        pub allow_native_executables: bool,
        /// **Test-fixture keys** (HOL-DOGFOOD-7, owner 2026-11-03). `false` (the default): a
        /// high or critical secret refuses the package wherever it is. `true`: a secret found only
        /// under a `tests/` or `fixtures/` directory is a warning; one anywhere else still refuses.
        #[serde(default)]
        pub allow_test_fixture_keys: bool,
        /// **An advisory pinned only in a crate's own `Cargo.lock`** (HOL-DOGFOOD-6, owner
        /// 2026-11-03): `warn` (the default) puts it on the package page as a warning, never
        /// a refusal (cargo never reads a dependency's lock); `refuse` judges it as before.
        /// Lowers onto `promotion.intake.scan.crate_lock_advisories`.
        #[serde(default)]
        pub crate_lock_advisories: crate::scanner::CrateLockAdvisories,
        /// **Every scan runs in the scan sandbox** (lane SANDBOX-SCAN, owner
        /// 2026-10-23): `required` (the default) or `off`, which only a test build
        /// accepts (`holger-sandbox-6`). Lowers onto `promotion.intake.rules.sandbox`.
        #[serde(default)]
        pub sandbox: crate::job_runner::SandboxRule,
    }

    impl Default for Rules {
        fn default() -> Self {
            Self {
                require_signed: default_signed(),
                require_provenance: default_signed(),
                slsa_min: 0,
                identities: Vec::new(),
                builders: Vec::new(),
                keys: Vec::new(),
                trust_root: None,
                require_sbom: false,
                malware: Switch::On,
                cve_max: default_cve_max(),
                secrets: default_secrets(),
                deny_licenses: Vec::new(),
                block_formats: default_block_formats(),
                allow_auto_approve: false,
                allow_unchecked_sources: false,
                allow_native_executables: false,
                allow_test_fixture_keys: false,
                crate_lock_advisories: crate::scanner::CrateLockAdvisories::Warn,
                sandbox: crate::job_runner::SandboxRule::Required,
            }
        }
    }

    impl Rules {
        /// The provenance mode the floor demands.
        pub fn provenance_mode(&self) -> ProvenanceMode {
            if self.require_signed || self.require_provenance || self.slsa_min > 0 {
                ProvenanceMode::Require
            } else {
                ProvenanceMode::Off
            }
        }

        fn validate(&self) -> Result<()> {
            for i in &self.identities {
                i.validate()
                    .map_err(|e| anyhow::anyhow!("config `rules.identities`: {e}"))?;
            }
            Ok(())
        }

        /// **The provenance half of the floor**, as G3a's verifier takes it.
        pub fn provenance_floor(&self) -> ProvenanceFloor {
            ProvenanceFloor {
                require_signed: self.require_signed,
                require_provenance: self.require_provenance,
                slsa_min: self.slsa_min,
                identities: self.identities.clone(),
                builders: self.builders.clone(),
                keys: self.keys.clone(),
                trust_root: self.trust_root.clone(),
            }
        }
    }

    // ── steps ─────────────────────────────────────────────────────────────────

    fn s(v: &str) -> String {
        v.to_string()
    }
    fn d_incoming() -> String {
        s("incoming")
    }
    fn d_quarantine() -> String {
        s("quarantine")
    }
    fn d_approved() -> String {
        s("approved")
    }
    fn d_production() -> String {
        s("production")
    }
    fn two() -> u32 {
        2
    }

    /// **`steps:`, the new spelling of `promotion:` + `promotion.intake`.**
    #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
    #[serde(deny_unknown_fields)]
    pub struct Steps {
        #[serde(default = "d_incoming")]
        pub incoming: String,
        #[serde(default = "d_quarantine")]
        pub quarantine: String,
        #[serde(default = "d_approved")]
        pub approved: String,
        #[serde(default = "d_production")]
        pub production: String,
        /// Scanner kinds that must report for EVERY artifact (the floor adds its own).
        #[serde(default)]
        pub scan: Vec<ScannerKind>,
        /// Distinct approvers APPROVED needs; never the importer. Default 2 (two-person
        /// approval, lane HOL-NCSC); `approvals: 1` is a choice the file writes down.
        #[serde(default = "two")]
        pub approvals: u32,
        /// The policy per state, as `promotion.intake.transitions`.
        #[serde(default)]
        pub transitions: Vec<crate::promote::intake::TransitionPolicy>,
        /// The file and archive inspector (and the optional ClamAV / YARA engines).
        #[cfg(feature = "scanners")]
        #[serde(default)]
        pub inspect: holger_inspect::InspectConfig,
        /// The advisory snapshot the vulnerability scanner reads (OSV JSON).
        #[serde(default)]
        pub advisory_db: Option<String>,
        /// SPDX ids allowed (empty = every id not denied).
        #[serde(default)]
        pub allow_licenses: Vec<String>,
        /// What an unknown licence gets: `Hold` (default), `Block` or `Pass`.
        #[cfg(feature = "scanners")]
        #[serde(default)]
        pub unknown_license: crate::scanner::license::UnknownLicense,
        /// The bundle trust policy the import doors verify against.
        #[serde(default)]
        pub trust_policy: Option<String>,
        /// The chain's npm door (a `ron_repo_type: "npm"` repository).
        #[serde(default)]
        pub npm: Option<String>,
        /// The chain's cargo door (a `ron_repo_type: "rust"` repository with no storage, lane
        /// HOL-CARGO-PUBLISH-DOOR).
        #[serde(default)]
        pub cargo: Option<String>,
        /// The chain's bundles door (a `raw` repository, lane HOL-BUNDLES-DOOR): a PUT there is
        /// a signed Holger bundle, verified against `bundles_trust_policy` (else `trust_policy`).
        #[serde(default)]
        pub bundles: Option<String>,
        /// The bundles door's trust policy (`holger.bundle-trust-policy/v1` RON).
        #[serde(default)]
        pub bundles_trust_policy: Option<String>,
        /// The `oci` registries whose pushes are NOT scanned (`scan: off`); every other
        /// `oci` repository takes its pushes into the chain. Lowers onto
        /// `promotion.intake.oci_push` (lane HOL-OCI-EVIDENCE).
        #[serde(default)]
        pub oci_push: Vec<crate::promote::oci_push::OciPushRule>,
        /// `promotion.intake.unscanned` (lane HOL-FIELD-2 P19).
        #[serde(default)]
        pub unscanned: Vec<String>,
        /// The gates every source starts from; they may only tighten the floor.
        #[serde(default)]
        pub gates: Gates,
        /// The secrets scanner's rule file (`(rules: "/path.ron")`); absent: the
        /// built-in set. Lowers onto `promotion.intake.secrets`.
        #[cfg(feature = "scanners")]
        #[serde(default)]
        pub secrets: Option<crate::scanner::secrets::SecretsSettings>,
        /// The scan sandbox: the engine, the worker image, its limits (SANDBOX-SCAN).
        /// Absent: the defaults. Lowers onto `promotion.intake.sandbox`.
        #[serde(default)]
        pub sandbox: Option<holger_sandbox::SandboxConfig>,
    }

    impl Steps {
        /// The `promotion:` block this spelling means. `floor` adds its licence denials
        /// and the scanner kinds its rules need.
        #[cfg_attr(not(feature = "scanners"), allow(unused_variables))]
        pub fn lower(&self, floor: &Rules, sources: &[Source]) -> crate::PromotionSettings {
            let stages = [
                &self.incoming,
                &self.quarantine,
                &self.approved,
                &self.production,
            ]
            .into_iter()
            .map(|r| crate::promote::Stage::new(r.clone(), r.clone()))
            .collect();
            let mut required = self.scan.clone();
            for k in floor_needs(floor) {
                if !required.contains(&k) {
                    required.push(k);
                }
            }
            crate::PromotionSettings {
                ladder_name: "intake".into(),
                stages,
                advisory_db: self.advisory_db.clone(),
                deny_licenses: floor.deny_licenses.clone(),
                allow_licenses: self.allow_licenses.clone(),
                #[cfg(feature = "scanners")]
                unknown_license: self.unknown_license,
                ingest_gates: Vec::new(),
                require_attestation: None,
                require_property: None,
                provenance: Default::default(),
                allow_skipping: false,
                intake: Some(crate::promote::intake::IntakeSettings {
                    incoming: self.incoming.clone(),
                    quarantine: self.quarantine.clone(),
                    approved: self.approved.clone(),
                    production: self.production.clone(),
                    transitions: self.transitions.clone(),
                    #[cfg(feature = "scanners")]
                    inspect: inspect_for(&self.inspect, floor),
                    #[cfg(feature = "scanners")]
                    secrets: self.secrets.clone(),
                    rules: crate::job_runner::IntakeRules {
                        sandbox: floor.sandbox,
                    },
                    sandbox: self.sandbox.clone(),
                    // A build without scanners scans nothing: `steps:` there is the chain's
                    // states, approvals and audit only.
                    #[cfg(not(feature = "scanners"))]
                    scan: None,
                    #[cfg(feature = "scanners")]
                    scan: Some(crate::promote::intake::IntakeScanSettings {
                        required,
                        // G3a's verifier: the floor's provenance half, each source's
                        // `gates.provenance`, and `steps.gates.provenance` for the rest.
                        provenance: Some(crate::scanner::provenance::ProvenanceScanSettings {
                            rules: floor.provenance_floor(),
                            sources: sources
                                .iter()
                                .filter_map(|s| {
                                    let g = s.gates.as_ref().and_then(|g| g.provenance.clone());
                                    g.or_else(|| self.gates.provenance.clone())
                                        .map(|g| (s.name.clone(), g))
                                })
                                .collect(),
                            default: self.gates.provenance.clone().unwrap_or(ProvenanceGate::Off),
                            inbox: None,
                            apk_sources: sources
                                .iter()
                                .filter(|s| s.kind == SourceKind::Apk && s.is_upload())
                                .map(|s| s.name.clone())
                                .collect(),
                        }),
                        crate_lock_advisories: floor.crate_lock_advisories,
                    }),
                    approvals: self.approvals,
                    trust_policy: self.trust_policy.clone(),
                    oci_push: self.oci_push.clone(),
                    unscanned: self.unscanned.clone(),
                    npm: self.npm.clone(),
                    cargo: self.cargo.clone(),
                    bundles: self.bundles.clone(),
                    bundles_trust_policy: self.bundles_trust_policy.clone(),
                }),
            }
        }
    }

    /// **The chain's inspection under the floor** (HOL-DOGFOOD-4): with
    /// `allow_native_executables: true` a crate's native executable warns instead of refusing
    /// (an operator's own crate policy that already allows or warns stays as written).
    #[cfg(feature = "scanners")]
    fn inspect_for(
        inspect: &holger_inspect::InspectConfig,
        floor: &Rules,
    ) -> holger_inspect::InspectConfig {
        use holger_inspect::{ExecPolicy, Format, Rule};
        let mut i = inspect.clone();
        if floor.allow_native_executables {
            let p = i
                .policies
                .entry(Format::Crate)
                .or_insert_with(|| ExecPolicy::default_for(Format::Crate));
            if p.executables == Rule::Block {
                p.executables = Rule::Warn;
            }
        }
        i
    }

    /// **Is a secret finding's place a test fixture?** (HOL-DOGFOOD-7): its summary ends
    /// `at <archive>!<path>[:<line>]`, and the path has a `tests` or `fixtures` directory.
    fn in_test_fixture(summary: &str) -> bool {
        let Some((_, at)) = summary.rsplit_once(" at ") else {
            return false;
        };
        let inner = at.rsplit('!').next().unwrap_or(at);
        let path = match inner.rsplit_once(':') {
            Some((p, line)) if !line.is_empty() && line.bytes().all(|b| b.is_ascii_digit()) => p,
            _ => inner,
        };
        let segs: Vec<&str> = path.split('/').collect();
        segs[..segs.len().saturating_sub(1)]
            .iter()
            .any(|s| *s == "tests" || *s == "fixtures")
    }

    /// The scanner kinds the floor's rules need on every artifact.
    fn floor_needs(floor: &Rules) -> Vec<ScannerKind> {
        let mut k = vec![ScannerKind::Inspection, ScannerKind::Vulnerability];
        if floor.require_sbom {
            k.push(ScannerKind::Sbom);
        }
        if floor.provenance_mode() == ProvenanceMode::Require {
            k.push(ScannerKind::Provenance);
        }
        if floor.secrets != SecretsMode::Off {
            k.push(ScannerKind::Secrets);
        }
        k
    }

    // ── the effective policy ──────────────────────────────────────────────────

    /// One rule's value and WHO set it: `floor`, `steps.gates`, or `source '<name>'`.
    #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
    pub struct Bound<T> {
        pub value: T,
        pub by: String,
    }

    fn bound<T>(value: T, by: &str) -> Bound<T> {
        Bound {
            value,
            by: by.to_string(),
        }
    }

    /// **An artifact's effective policy**: the floor plus its source's gates. Kept on
    /// every line of the artifact's intake record and shown by GetArtifactState and
    /// `/-/intake/state`.
    #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
    pub struct EffectivePolicy {
        /// The source the artifact came from.
        pub source: String,
        pub provenance: Bound<ProvenanceMode>,
        pub slsa_min: Bound<u8>,
        #[serde(default)]
        pub identities: Vec<Identity>,
        #[serde(default)]
        pub builders: Vec<BuilderRule>,
        #[serde(default)]
        pub keys: Vec<String>,
        pub sbom: Bound<bool>,
        pub malware: Bound<bool>,
        pub cve_max: Bound<FindingSeverity>,
        /// What a secret found means (lane HOLGER-G3).
        #[serde(default = "default_secrets_bound")]
        pub secrets: Bound<SecretsMode>,
        /// HOL-DOGFOOD-7: the floor's `allow_test_fixture_keys` (a secret found only under
        /// `tests/` or `fixtures/` is a warning, not a refusal).
        #[serde(default)]
        pub test_fixture_keys: bool,
        /// Each denied id with who denied it.
        #[serde(default)]
        pub deny_licenses: Vec<Bound<String>>,
        #[serde(default)]
        pub block_formats: Vec<Bound<String>>,
        /// The source's automatic approval, when the floor allows it and the source asks.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        pub auto_approve: Option<AutoApproveRule>,
    }

    fn default_secrets_bound() -> Bound<SecretsMode> {
        bound(default_secrets(), "floor")
    }

    fn sev_rank(s: FindingSeverity) -> u8 {
        match s {
            FindingSeverity::Info => 0,
            FindingSeverity::Low => 1,
            FindingSeverity::Medium => 2,
            FindingSeverity::High => 3,
            FindingSeverity::Critical => 4,
        }
    }

    fn sev_name(s: FindingSeverity) -> &'static str {
        match s {
            FindingSeverity::Info => "info",
            FindingSeverity::Low => "low",
            FindingSeverity::Medium => "medium",
            FindingSeverity::High => "high",
            FindingSeverity::Critical => "critical",
        }
    }

    fn mode_name(m: ProvenanceMode) -> &'static str {
        match m {
            ProvenanceMode::Off => "off",
            ProvenanceMode::Prefer => "prefer",
            ProvenanceMode::Require => "require",
        }
    }

    /// The floor rule a provenance mode answers to, for the refusal's words.
    fn provenance_rule(floor: &Rules) -> String {
        if floor.require_signed {
            "require_signed: true".into()
        } else if floor.require_provenance {
            "require_provenance: true".into()
        } else {
            format!("slsa_min: {}", floor.slsa_min)
        }
    }

    impl EffectivePolicy {
        /// The floor alone, as a source with no gates gets it.
        pub fn floor(source: &str, floor: &Rules) -> Self {
            Self {
                source: source.to_string(),
                provenance: bound(
                    floor.provenance_mode(),
                    &if floor.provenance_mode() == ProvenanceMode::Require {
                        format!("floor `{}`", provenance_rule(floor))
                    } else {
                        "floor".to_string()
                    },
                ),
                slsa_min: bound(floor.slsa_min, "floor"),
                identities: floor.identities.clone(),
                builders: floor.builders.clone(),
                keys: floor.keys.iter().map(|k| k.id.clone()).collect(),
                sbom: bound(floor.require_sbom, "floor"),
                malware: bound(floor.malware == Switch::On, "floor"),
                cve_max: bound(floor.cve_max, "floor"),
                secrets: bound(floor.secrets, "floor"),
                test_fixture_keys: floor.allow_test_fixture_keys,
                deny_licenses: floor
                    .deny_licenses
                    .iter()
                    .map(|l| bound(l.clone(), "floor"))
                    .collect(),
                block_formats: floor
                    .block_formats
                    .iter()
                    .map(|f| bound(f.clone(), "floor"))
                    .collect(),
                auto_approve: None,
            }
        }

        /// **Apply a source's `auto_approve`.** Refused when the floor does not allow it,
        /// or when a condition is looser than this policy.
        pub fn with_auto_approve(
            mut self,
            a: &AutoApprove,
            by: &str,
            floor: &Rules,
        ) -> Result<Self> {
            let AutoApprove::When {
                provenance,
                cve_max,
            } = a
            else {
                return Ok(self);
            };
            if !floor.allow_auto_approve {
                bail!(
                    "config `{by}: auto_approve: when(...)` needs the floor's `rules: (allow_auto_approve: \
                     true)`; the floor forbids automatic approval"
                );
            }
            let mode = match provenance {
                None => self.provenance.value,
                Some(Need::Off) => ProvenanceMode::Off,
                Some(Need::Require) => ProvenanceMode::Require,
            };
            if mode < self.provenance.value {
                bail!(
                    "config `{by}: auto_approve.provenance: {}` is looser than the artifact's \
                     `provenance: {}` ({}); auto-approval may only be stricter",
                    mode_name(mode),
                    mode_name(self.provenance.value),
                    self.provenance.by
                );
            }
            let max = cve_max.unwrap_or(self.cve_max.value);
            if sev_rank(max) > sev_rank(self.cve_max.value) {
                bail!(
                    "config `{by}: auto_approve.cve_max: {}` is looser than `cve_max: {}` ({}); \
                     auto-approval may only be stricter",
                    sev_name(max),
                    sev_name(self.cve_max.value),
                    self.cve_max.by
                );
            }
            self.auto_approve = Some(AutoApproveRule {
                provenance: mode,
                cve_max: max,
                approver: auto_approver(&self.source),
            });
            Ok(self)
        }

        /// **Does this artifact's scan evidence meet its source's auto-approval?** The
        /// approver to vote as when it does. It decides only the approval vote; with
        /// `approvals: 2` it counts as at most one of the two.
        pub fn auto_approval(&self, records: &[ScanEvidence], importer: &str) -> Option<String> {
            let rule = self.auto_approve.as_ref()?;
            if rule.approver == importer {
                return None;
            }
            if rule.provenance == ProvenanceMode::Require
                && !records
                    .iter()
                    .any(|r| r.kind == ScannerKind::Provenance && r.verdict == ScanVerdict::Pass)
            {
                return None;
            }
            let vuln: Vec<&ScanEvidence> = records
                .iter()
                .filter(|r| r.kind == ScannerKind::Vulnerability)
                .collect();
            if vuln.is_empty()
                || vuln
                    .iter()
                    .any(|r| !matches!(r.verdict, ScanVerdict::Pass | ScanVerdict::Warn))
            {
                return None;
            }
            if vuln
                .iter()
                .flat_map(|r| &r.findings)
                .any(|f| sev_rank(f.severity) > sev_rank(rule.cve_max))
            {
                return None;
            }
            Some(rule.approver.clone())
        }

        /// **Apply `gates` on top, tightening only.** A gate that would loosen the
        /// floor is an error naming the gate, `by`, and the rule it breaks.
        pub fn tighten(mut self, gates: &Gates, by: &str, floor: &Rules) -> Result<Self> {
            let at = format!("{by}: gates");
            if let Some(p) = &gates.provenance {
                let mode = match p {
                    ProvenanceGate::Off => ProvenanceMode::Off,
                    ProvenanceGate::Prefer => ProvenanceMode::Prefer,
                    ProvenanceGate::Require { .. } => ProvenanceMode::Require,
                };
                if mode < self.provenance.value {
                    bail!(
                        "config `{at}.provenance: {}` loosens the floor rule `{}` (which needs \
                         `{}`); a source may only tighten the floor",
                        mode_name(mode),
                        provenance_rule(floor),
                        mode_name(self.provenance.value)
                    );
                }
                // G3a's own check: slsa_min, identities, builders and keys only narrow the
                // floor, and a `require` names who signs.
                let pf = floor.provenance_floor();
                check_tightens(&pf, p, by)
                    .map_err(|e| anyhow::anyhow!("config `{at}.provenance`: {e}"))?;
                if mode > self.provenance.value {
                    self.provenance = bound(mode, by);
                }
                let eff = effective(&pf, p);
                if eff.slsa_min > self.slsa_min.value {
                    self.slsa_min = bound(eff.slsa_min, by);
                }
                self.identities = eff.identities;
                self.builders = eff.builders;
                self.keys = eff.keys.iter().map(|k| k.id.clone()).collect();
            }
            if let Some(n) = gates.sbom {
                let want = n == Need::Require;
                if !want && self.sbom.value {
                    bail!(
                        "config `{at}.sbom: off` loosens the rule `require_sbom: true` ({})",
                        self.sbom.by
                    );
                }
                if want && !self.sbom.value {
                    self.sbom = bound(true, by);
                }
            }
            if let Some(m) = gates.malware {
                let want = m == Switch::On;
                if !want && self.malware.value {
                    bail!(
                        "config `{at}.malware: off` loosens the rule `malware: on` ({})",
                        self.malware.by
                    );
                }
                if want && !self.malware.value {
                    self.malware = bound(true, by);
                }
            }
            if let Some(c) = gates.cve {
                if sev_rank(c.max) > sev_rank(self.cve_max.value) {
                    bail!(
                        "config `{at}.cve.max: {}` loosens the rule `cve_max: {}` ({})",
                        sev_name(c.max),
                        sev_name(self.cve_max.value),
                        self.cve_max.by
                    );
                }
                if sev_rank(c.max) < sev_rank(self.cve_max.value) {
                    self.cve_max = bound(c.max, by);
                }
            }
            if let Some(m) = gates.secrets {
                if m < self.secrets.value {
                    bail!(
                        "config `{at}.secrets: {}` loosens the rule `secrets: {}` ({}); a source may only tighten the floor",
                        m.as_str(),
                        self.secrets.value.as_str(),
                        self.secrets.by
                    );
                }
                if m > self.secrets.value {
                    self.secrets = bound(m, by);
                }
            }
            if let Some(l) = &gates.license {
                for id in &l.deny {
                    if !self
                        .deny_licenses
                        .iter()
                        .any(|b| b.value.eq_ignore_ascii_case(id))
                    {
                        self.deny_licenses.push(bound(id.clone(), by));
                    }
                }
            }
            for f in &gates.block_formats {
                if !self
                    .block_formats
                    .iter()
                    .any(|b| b.value.eq_ignore_ascii_case(f))
                {
                    self.block_formats.push(bound(f.clone(), by));
                }
            }
            Ok(self)
        }

        /// **Refused at ingest?** A blocked format, by its extension (`pickle` names the
        /// pickle family).
        #[cfg(feature = "scanners")]
        pub fn ingest_refusal(&self, file_name: &str) -> Option<String> {
            let lower = file_name.to_ascii_lowercase();
            for b in &self.block_formats {
                let f = b.value.to_ascii_lowercase();
                let hit = if f == "pickle" {
                    crate::scanner::model::PICKLE_EXTENSIONS
                        .iter()
                        .any(|e| lower.ends_with(&format!(".{e}")))
                } else {
                    lower.ends_with(&format!(".{}", f.trim_start_matches('.')))
                };
                if hit {
                    return Some(format!(
                        "rule `block_formats: [\"{}\"]` ({}) refuses '{file_name}' from source '{}'",
                        b.value, b.by, self.source
                    ));
                }
            }
            None
        }

        /// **What this policy refuses at SCANNED**, from the scanners' records. Each
        /// reason names the rule and who set it. `license` re-judges the declared
        /// licence against this policy's denials (it is `None` when nothing is denied).
        pub fn refusals(
            &self,
            records: &[ScanEvidence],
            license: Option<&LicenseJudgement>,
        ) -> Vec<String> {
            let mut out = Vec::new();
            let src = &self.source;
            let ok = |r: &&ScanEvidence| matches!(r.verdict, ScanVerdict::Pass | ScanVerdict::Warn);
            let of = |k: ScannerKind| records.iter().filter(move |r| r.kind == k);
            let why = |k: ScannerKind| -> String {
                of(k)
                    .map(|r| format!("{} {}: {}", r.scanner_id, r.verdict, r.detail))
                    .collect::<Vec<_>>()
                    .join("; ")
            };
            let named = |rule: String, by: &str, what: String| {
                format!("rule `{rule}` ({by}) for source '{src}': {what}")
            };

            if (self.provenance.value == ProvenanceMode::Require || self.slsa_min.value > 0)
                && !of(ScannerKind::Provenance).any(|r| r.verdict == ScanVerdict::Pass)
            {
                let detail = why(ScannerKind::Provenance);
                out.push(named(
                    "provenance: require".into(),
                    &self.provenance.by,
                    format!(
                        "no verified signature or provenance{}",
                        if detail.is_empty() {
                            " (this build has no provenance scanner)".into()
                        } else {
                            format!(" ({detail})")
                        }
                    ),
                ));
            }
            if self.sbom.value && !of(ScannerKind::Sbom).any(|r| ok(&r)) {
                out.push(named(
                    "require_sbom: true".into(),
                    &self.sbom.by,
                    format!("no SBOM ({})", why(ScannerKind::Sbom)),
                ));
            }
            if self.malware.value {
                for k in [ScannerKind::Inspection, ScannerKind::Malware] {
                    for r in of(k).filter(|r| r.verdict == ScanVerdict::Block) {
                        out.push(named(
                            "malware: on".into(),
                            &self.malware.by,
                            format!("{} blocked: {}", r.scanner_id, r.detail),
                        ));
                    }
                }
                if !of(ScannerKind::Inspection).any(|r| ok(&r)) {
                    out.push(named(
                        "malware: on".into(),
                        &self.malware.by,
                        format!(
                            "file inspection did not pass ({})",
                            why(ScannerKind::Inspection)
                        ),
                    ));
                }
            }
            let max = self.cve_max.value;
            for r in of(ScannerKind::Vulnerability) {
                let over: Vec<String> = r
                    .findings
                    .iter()
                    .filter(|f| sev_rank(f.severity) > sev_rank(max))
                    // HOL-DOGFOOD-6: an advisory pinned only in a crate's own Cargo.lock
                    // under `crate_lock_advisories: warn` is a warning, never over `cve_max`.
                    .filter(|f| !f.summary.contains(crate::scanner::LOCK_ONLY_WORDS))
                    .map(|f| format!("{} ({})", f.id, sev_name(f.severity)))
                    .collect();
                if !over.is_empty() {
                    out.push(named(
                        format!("cve_max: {}", sev_name(max)),
                        &self.cve_max.by,
                        format!("advisories above it: {}", over.join(", ")),
                    ));
                }
            }
            if self.secrets.value == SecretsMode::Block {
                let found: Vec<String> = of(ScannerKind::Secrets)
                    .flat_map(|r| &r.findings)
                    .filter(|f| sev_rank(f.severity) >= sev_rank(FindingSeverity::High))
                    .filter(|f| !(self.test_fixture_keys && in_test_fixture(&f.summary)))
                    .map(|f| f.summary.clone())
                    .collect();
                if !found.is_empty() {
                    let more = if found.len() > 5 {
                        format!(" and {} more", found.len() - 5)
                    } else {
                        String::new()
                    };
                    out.push(named(
                        "secrets: block".into(),
                        &self.secrets.by,
                        format!(
                            "{} secret(s) found: {}{more}",
                            found.len(),
                            found.iter().take(5).cloned().collect::<Vec<_>>().join("; ")
                        ),
                    ));
                }
            }
            #[cfg(not(feature = "scanners"))]
            let _ = license;
            #[cfg(feature = "scanners")]
            if let Some(j) = license {
                if let crate::scanner::license::LicenseVerdict::Denied(ids) = &j.verdict {
                    for id in ids {
                        let by = self
                            .deny_licenses
                            .iter()
                            .find(|b| {
                                id.to_ascii_lowercase().starts_with(
                                    &b.value
                                        .to_ascii_lowercase()
                                        .replace("-only", "")
                                        .replace("-or-later", ""),
                                )
                            })
                            .map(|b| b.by.as_str())
                            .unwrap_or("floor");
                        out.push(named(
                            "deny_licenses".into(),
                            by,
                            format!("licence {id} is denied"),
                        ));
                    }
                }
            }
            out
        }

        /// One line for the audit trail.
        pub fn summary(&self) -> String {
            let deny: Vec<&str> = self
                .deny_licenses
                .iter()
                .map(|b| b.value.as_str())
                .collect();
            let fmts: Vec<&str> = self
                .block_formats
                .iter()
                .map(|b| b.value.as_str())
                .collect();
            format!(
                "source={} provenance={}({}) slsa_min={} sbom={} malware={} cve_max={}({}) secrets={}({}) deny_licenses={deny:?} block_formats={fmts:?}",
                self.source,
                mode_name(self.provenance.value),
                self.provenance.by,
                self.slsa_min.value,
                self.sbom.value,
                if self.malware.value { "on" } else { "off" },
                sev_name(self.cve_max.value),
                self.cve_max.by,
                self.secrets.value.as_str(),
                self.secrets.by,
            )
        }
    }

    // ── the per-source policies a chain holds ─────────────────────────────────

    /// **The effective policy of every upload source**, built once at load, and how an
    /// upload finds its source.
    #[derive(Debug, Clone)]
    pub struct SourcePolicies {
        uploads: Vec<(Source, EffectivePolicy)>,
        /// `python` / `oci` sources: the pull-through ingests as `pull-through:<name>`.
        remotes: Vec<(Source, EffectivePolicy)>,
        /// The floor plus `steps.gates`, for a pull-through source `sources:` does not name.
        defaults: EffectivePolicy,
    }

    impl SourcePolicies {
        /// Validate `sources`, `steps.gates` and `rules` together and compute each
        /// upload source's effective policy. With no upload source declared, uploads
        /// come from one implicit source named `upload`.
        pub fn build(sources: &[Source], steps: &Steps, floor: &Rules) -> Result<Self> {
            floor.validate()?;
            let defaults =
                EffectivePolicy::floor("", floor).tighten(&steps.gates, "steps.gates", floor)?;
            for s in sources.iter().filter(|s| s.skips_the_chain()) {
                if !floor.allow_unchecked_sources {
                    bail!(
                        "config `sources`: source '{}' (kind `{}`) serves its bytes as fetched, \
                         outside the steps, so the floor cannot hold for it; the floor refuses \
                         such a source unless `rules: (allow_unchecked_sources: true)` says it \
                         accepts that",
                        s.name,
                        s.kind.name()
                    );
                }
                if s.gates.is_some() {
                    bail!(
                        "config `sources`: source '{}' (kind `{}`) has `gates`, but its bytes do \
                         not pass the steps, so no gate could run on them",
                        s.name,
                        s.kind.name()
                    );
                }
            }
            let mut uploads = Vec::new();
            let mut remotes = Vec::new();
            for s in sources.iter().filter(|s| s.pulls_into_the_chain()) {
                let mut p = defaults.clone();
                p.source = s.name.clone();
                let by = format!("source '{}'", s.name);
                if let Some(g) = &s.gates {
                    p = p.tighten(g, &by, floor)?;
                }
                // A29 row 11 (owner): a container image is never auto-approved, whatever
                // the floor says; `pull_intake::never_auto_approved` holds the same at run time.
                if s.kind == SourceKind::Oci && matches!(s.auto_approve, AutoApprove::When { .. }) {
                    bail!(
                        "config `sources`: source '{}' is an OCI registry, and a container image is never \
                         auto-approved (owner, A29 row 11): every image waits for its approvers, whatever \
                         `allow_auto_approve` says; set the source's `auto_approve: off`",
                        s.name
                    );
                }
                p = p.with_auto_approve(&s.auto_approve, &by, floor)?;
                remotes.push((s.clone(), p));
            }
            for s in sources.iter().filter(|s| s.is_upload()) {
                let mut p = defaults.clone();
                p.source = s.name.clone();
                let by = format!("source '{}'", s.name);
                if let Some(g) = &s.gates {
                    p = p.tighten(g, &by, floor)?;
                }
                p = p.with_auto_approve(&s.auto_approve, &by, floor)?;
                uploads.push((s.clone(), p));
            }
            if uploads.is_empty() {
                let mut p = defaults.clone();
                p.source = "upload".into();
                let implicit = Source {
                    name: "upload".into(),
                    kind: SourceKind::Upload,
                    url: None,
                    importers: Vec::new(),
                    serves: Vec::new(),
                    credential: None,
                    every_secs: None,
                    repositories: Vec::new(),
                    select: Vec::new(),
                    max_files: None,
                    platforms: Vec::new(),
                    max_bytes: None,
                    refresh_secs: None,
                    wait_secs: None,
                    gates: None,
                    auto_approve: AutoApprove::Off,
                };
                uploads.push((implicit, p));
            }
            Ok(Self {
                uploads,
                remotes,
                defaults,
            })
        }

        pub fn is_empty(&self) -> bool {
            self.uploads.is_empty()
        }

        /// **Which source an upload is from.** One upload source: every upload. Several:
        /// the first path segment of the artifact's name names it (`<source>/<file>`).
        /// A source with `importers` takes uploads only from those principals.
        pub fn resolve(
            &self,
            actor: &str,
            artifact_name: &str,
        ) -> std::result::Result<EffectivePolicy, String> {
            // A package the pull-through fetched: its remote source decides.
            if let Some(name) = actor.strip_prefix("pull-through:") {
                return Ok(match self.remotes.iter().find(|(s, _)| s.name == name) {
                    Some((_, p)) => p.clone(),
                    None => EffectivePolicy {
                        source: name.to_string(),
                        ..self.defaults.clone()
                    },
                });
            }
            let pick = if self.uploads.len() == 1 {
                &self.uploads[0]
            } else {
                let first = artifact_name
                    .trim_start_matches('/')
                    .split('/')
                    .next()
                    .unwrap_or("");
                match self.uploads.iter().find(|(s, _)| s.name == first) {
                    Some(u) => u,
                    None => {
                        let names: Vec<&str> =
                            self.uploads.iter().map(|(s, _)| s.name.as_str()).collect();
                        return Err(format!(
                            "'{artifact_name}' names no upload source; with several upload sources \
                             the path starts with the source's name ({})",
                            names.join(", ")
                        ));
                    }
                }
            };
            let (s, p) = pick;
            if !s.importers.is_empty() && !s.importers.iter().any(|i| i == actor) {
                return Err(format!(
                    "principal '{actor}' may not upload as source '{}' (its importers: {})",
                    s.name,
                    s.importers.join(", ")
                ));
            }
            Ok(p.clone())
        }

        /// **The effective policy of the source named `name`**, upload or remote. The
        /// implicit upload source (`upload`, or the board's `uploads` row) is the one upload
        /// source when there is only one.
        pub fn named(&self, name: &str) -> Option<&EffectivePolicy> {
            if let Some((_, p)) = self
                .uploads
                .iter()
                .chain(self.remotes.iter())
                .find(|(s, _)| s.name == name)
            {
                return Some(p);
            }
            match (name, self.uploads.as_slice()) {
                ("upload" | "uploads", [(_, p)]) => Some(p),
                _ => None,
            }
        }

        /// The effective policy of a `python` / `oci` source.
        pub fn remote(&self, name: &str) -> Option<&EffectivePolicy> {
            self.remotes
                .iter()
                .find(|(s, _)| s.name == name)
                .map(|(_, p)| p)
        }
    }

    #[cfg(all(test, feature = "scanners"))]
    mod tests {
        use super::*;

        fn rules(r: &str) -> Rules {
            ron_options()
                .from_str(r)
                .unwrap_or_else(|e| panic!("{r}: {e}"))
        }
        fn gates(g: &str) -> Gates {
            ron_options()
                .from_str(g)
                .unwrap_or_else(|e| panic!("{g}: {e}"))
        }
        fn upload(name: &str, g: Option<&str>) -> Source {
            Source {
                name: name.into(),
                kind: SourceKind::Upload,
                url: None,
                importers: Vec::new(),
                serves: Vec::new(),
                credential: None,
                every_secs: None,
                repositories: Vec::new(),
                select: Vec::new(),
                max_files: None,
                platforms: Vec::new(),
                max_bytes: None,
                refresh_secs: None,
                wait_secs: None,
                gates: g.map(gates),
                auto_approve: AutoApprove::Off,
            }
        }
        /// ★ **HOL-DOGFOOD-4 and -7 are floor settings, default NO** (owner 2026-11-03). A
        /// native executable inside a crate is refused by the inspection, and a high secret found
        /// in a package is refused, unless the floor says otherwise:
        /// `allow_native_executables: true` makes the crate rule `executables: warn` (the package
        /// page shows the warning), and `allow_test_fixture_keys: true` lets a key found only
        /// under `tests/` or `fixtures/` through as a warning; a key anywhere else still refuses.
        #[cfg(feature = "scanners")]
        #[test]
        fn native_executables_and_test_fixture_keys_refuse_unless_the_floor_allows_them() {
            use crate::scanner::{FindingSeverity as S, ScanFinding};
            use holger_inspect::{ExecPolicy, Format, Rule};
            let crate_exec = |floor: &Rules| -> Rule {
                let lowered = steps().lower(floor, &[upload("vendor", None)]);
                let inspect = lowered.intake.expect("steps lower onto an intake").inspect;
                inspect
                    .policies
                    .get(&Format::Crate)
                    .map(|p| p.executables)
                    .unwrap_or(ExecPolicy::default_for(Format::Crate).executables)
            };
            assert_eq!(
                crate_exec(&Rules::default()),
                Rule::Block,
                "default: refused"
            );
            assert_eq!(
                crate_exec(&rules("(allow_native_executables: true)")),
                Rule::Warn,
                "allowed: a warning"
            );

            let rec = |findings: Vec<ScanFinding>| ScanEvidence {
                scanner_id: "holger-secrets".into(),
                scanner_version: "1".into(),
                kind: ScannerKind::Secrets,
                verdict: ScanVerdict::Warn,
                findings,
                inputs: Vec::new(),
                document: None,
                detail: "d".into(),
                required: true,
                intel_sequence: None,
                hold: false,
            };
            let fixture = ScanFinding::new(
                "secret:rsa-private-key",
                S::High,
                "an RSA private key (high) at rsa-0.9.8.crate!rsa-0.9.8/tests/examples/key.pem",
            );
            let fixture2 = ScanFinding::new(
                "secret:stripe-live",
                S::Critical,
                "a Stripe live secret key (critical) at nornir-vault-0.2.0.crate!nornir-vault-0.2.0/fixtures/k.rs:289",
            );
            let real = ScanFinding::new(
                "secret:aws-key",
                S::High,
                "an AWS access key (high) at x-1.0.crate!x-1.0/src/lib.rs:3",
            );
            let sec = |p: &EffectivePolicy, r: &[ScanEvidence]| -> Vec<String> {
                p.refusals(r, None)
                    .into_iter()
                    .filter(|x| x.contains("secrets: "))
                    .collect()
            };
            let strict = EffectivePolicy::floor("crates", &Rules::default());
            let only_fixtures = vec![rec(vec![fixture.clone(), fixture2.clone()])];
            assert_eq!(
                sec(&strict, &only_fixtures).len(),
                1,
                "default: a fixture key refuses"
            );
            let allowing =
                EffectivePolicy::floor("crates", &rules("(allow_test_fixture_keys: true)"));
            assert!(
                sec(&allowing, &only_fixtures).is_empty(),
                "allowed: keys only under tests/ or fixtures/ pass (as the scan's warning)"
            );
            let mixed = vec![rec(vec![fixture, real])];
            let r = sec(&allowing, &mixed);
            assert!(
                r.len() == 1 && r[0].contains("src/lib.rs:3") && !r[0].contains("key.pem"),
                "a key outside tests/ and fixtures/ still refuses, and names only itself: {r:?}"
            );
        }

        fn steps() -> Steps {
            ron_options().from_str("()").unwrap()
        }

        #[test]
        fn an_apk_source_is_an_upload_source_whose_index_is_its_provenance() {
            let src: Source = ron_options()
                .from_str(r#"(name: "wolfi", kind: apk, gates: (provenance: require(apk_keys: ["wolfi-signing.rsa.pub"])))"#)
                .unwrap();
            assert_eq!(src.kind, SourceKind::Apk);
            validate_sources(std::slice::from_ref(&src), false).unwrap();
            let floor = Rules::default();
            let p = SourcePolicies::build(std::slice::from_ref(&src), &steps(), &floor).unwrap();
            assert_eq!(
                p.resolve("alice", "wolfi/libffi-3.4.2-r0.apk")
                    .unwrap()
                    .source,
                "wolfi"
            );
            let lowered = steps().lower(&floor, std::slice::from_ref(&src));
            let prov = lowered.intake.unwrap().scan.unwrap().provenance.unwrap();
            assert_eq!(prov.apk_sources, vec!["wolfi".to_string()]);
            assert!(
                matches!(&prov.sources["wolfi"], ProvenanceGate::Require { apk_keys, .. } if apk_keys == &["wolfi-signing.rsa.pub".to_string()])
            );
        }

        /// ★ F9 (the field test of 2026-11-01): **a remote kind with no `url` serves only**
        /// (a downstream's view of what its upstream sent), so it needs no `online` build: only
        /// the `jobs` build whose doors serve it. With a `url` it pulls into the steps and still
        /// needs `online` and `jobs`.
        #[test]
        fn a_remote_source_with_no_url_serves_only_and_needs_no_online_build() {
            for kind in ["oci", "python", "npm", "maven", "crates"] {
                let src: Source = ron_options()
                    .from_str(&format!(r#"(name: "s", kind: {kind})"#))
                    .unwrap();
                assert!(!src.pulls_into_the_chain(), "{kind}: it fetches nothing");
                let r = validate_sources(std::slice::from_ref(&src), false);
                if cfg!(feature = "jobs") {
                    r.unwrap_or_else(|e| panic!("{kind}: {e}"));
                } else {
                    let e = r.unwrap_err().to_string();
                    assert!(
                        e.contains("no `url`") && e.contains("`jobs`"),
                        "{kind}: {e}"
                    );
                }
                let with_url = Source {
                    url: Some("https://registry.example/".into()),
                    ..src
                };
                assert!(with_url.pulls_into_the_chain(), "{kind}");
                let e = validate_sources(&[with_url], false)
                    .unwrap_err()
                    .to_string();
                assert!(
                    e.contains("needs the `online` and `jobs` build"),
                    "{kind}: {e}"
                );
            }
        }

        #[test]
        fn the_owners_example_gates_parse() {
            let g = gates(
                r#"(provenance: require(identity: (issuer: "https://token.actions.githubusercontent.com", subject: "repo:a/b"), slsa_min: 2),
                    sbom: require, malware: on, license: (deny: ["AGPL-3.0-only"]), cve: (max: high))"#,
            );
            assert!(matches!(
                g.provenance,
                Some(ProvenanceGate::Require {
                    slsa_min: Some(2),
                    ..
                })
            ));
            assert_eq!(
                gates("(provenance: prefer)").provenance,
                Some(ProvenanceGate::Prefer)
            );
            assert_eq!(
                gates("(provenance: off)").provenance,
                Some(ProvenanceGate::Off)
            );
        }

        #[test]
        fn unknown_keys_are_refused_by_name_in_every_block() {
            for (what, r) in [
                (
                    "cev",
                    ron_options()
                        .from_str::<Gates>("(cev: (max: high))")
                        .map(|_| ()),
                ),
                (
                    "slsa",
                    ron_options()
                        .from_str::<Gates>("(provenance: require(slsa: 2))")
                        .map(|_| ()),
                ),
                (
                    "requre_signed",
                    ron_options()
                        .from_str::<Rules>("(requre_signed: true)")
                        .map(|_| ()),
                ),
                (
                    "aprovals",
                    ron_options().from_str::<Steps>("(aprovals: 2)").map(|_| ()),
                ),
                (
                    "gatez",
                    ron_options()
                        .from_str::<Source>(r#"(name: "a", kind: upload, gatez: ())"#)
                        .map(|_| ()),
                ),
                (
                    "maximum",
                    ron_options()
                        .from_str::<Gates>("(cve: (maximum: high))")
                        .map(|_| ()),
                ),
            ] {
                let e = r.expect_err(what).to_string();
                assert!(e.contains(what), "{what}: {e}");
            }
        }

        #[test]
        fn the_default_floor_fails_closed_on_what_this_build_checks() {
            let r = Rules::default();
            assert_eq!(r, rules("()"), "an empty `rules: ()` is the default floor");
            assert_eq!(r.malware, Switch::On);
            assert_eq!(r.cve_max, FindingSeverity::High);
            assert_eq!(r.block_formats, vec!["pickle".to_string()]);
            assert!(!r.allow_unchecked_sources);
            assert_eq!(r.require_signed, PROVENANCE_IN_BUILD);
            // Writing one rule keeps the others at the gateway's default.
            let one = rules("(require_sbom: true)");
            assert_eq!(one.cve_max, FindingSeverity::High);
            assert_eq!(one.malware, Switch::On);
        }

        #[test]
        fn a_source_cannot_loosen_the_floor_and_the_refusal_names_the_rule() {
            let floor = rules("(require_signed: true)");
            for (g, want) in [
                ("(provenance: off)", "require_signed: true"),
                ("(provenance: prefer)", "require_signed: true"),
                ("(malware: off)", "malware: on"),
                ("(cve: (max: critical))", "cve_max: high"),
                (
                    r#"(provenance: require(identity: (issuer: "https://i", subject: "a"), slsa_min: 0))"#,
                    "",
                ),
            ] {
                let r = SourcePolicies::build(&[upload("pypi", Some(g))], &steps(), &floor);
                if want.is_empty() {
                    r.expect("slsa_min 0 equals the floor's 0: not a loosening");
                    continue;
                }
                let e = r.expect_err(g).to_string();
                assert!(
                    e.contains("source 'pypi'") && e.contains(want) && e.contains("loosens"),
                    "{g}: {e}"
                );
            }
            let e = SourcePolicies::build(
                &[upload("a", Some(r#"(provenance: require(identity: (issuer: "https://i", subject: "a"), slsa_min: 1))"#))],
                &steps(),
                &rules("(slsa_min: 2)"),
            )
            .unwrap_err()
            .to_string();
            assert!(e.contains("slsa_min 1 is below the floor's 2"), "{e}");
            let e = SourcePolicies::build(
                &[upload("a", Some("(sbom: off)"))],
                &steps(),
                &rules("(require_sbom: true)"),
            )
            .unwrap_err()
            .to_string();
            assert!(e.contains("require_sbom: true"), "{e}");
        }

        #[test]
        fn steps_gates_cannot_loosen_the_floor_either() {
            let s: Steps = ron_options()
                .from_str("(gates: (cve: (max: critical)))")
                .unwrap();
            let e = SourcePolicies::build(&[], &s, &Rules::default())
                .unwrap_err()
                .to_string();
            assert!(
                e.contains("steps.gates") && e.contains("cve_max: high"),
                "{e}"
            );
        }

        #[test]
        fn a_source_can_tighten_the_floor_and_the_policy_says_who() {
            let p = SourcePolicies::build(
                &[upload("chainguard", Some(r#"(provenance: require(identity: (issuer: "https://i", subject: "a"), slsa_min: 2), sbom: require, cve: (max: medium), license: (deny: ["AGPL-3.0-only"]), block_formats: ["exe"])"#))],
                &steps(),
                &rules(r#"(deny_licenses: ["GPL-3.0-only"])"#),
            )
            .unwrap();
            let e = p.resolve("ida", "pkg.whl").unwrap();
            assert_eq!(e.source, "chainguard");
            assert_eq!(
                e.provenance.value,
                ProvenanceMode::Require,
                "the default floor requires it already"
            );
            assert_eq!(e.slsa_min, bound(2, "source 'chainguard'"));
            assert!(e.sbom.value);
            assert_eq!(
                e.cve_max,
                bound(FindingSeverity::Medium, "source 'chainguard'")
            );
            assert!(e
                .deny_licenses
                .contains(&bound("GPL-3.0-only".into(), "floor")));
            assert!(e
                .deny_licenses
                .contains(&bound("AGPL-3.0-only".into(), "source 'chainguard'")));
            assert!(e
                .ingest_refusal("tool.exe")
                .unwrap()
                .contains("source 'chainguard'"));
            assert!(
                e.ingest_refusal("model.pkl").unwrap().contains("floor"),
                "pickle is in the default floor"
            );
            assert!(e.ingest_refusal("pkg.whl").is_none());
        }

        #[test]
        fn identities_and_keys_may_only_narrow_the_floors_lists() {
            let floor = rules(
                r#"(identities: [(issuer: "https://i", subject: "a")], keys: [(id: "k1", alg: ed25519, public_key: "PEM")])"#,
            );
            let bad = r#"(provenance: require(identity: (issuer: "https://i", subject: "b")))"#;
            let e = SourcePolicies::build(&[upload("s", Some(bad))], &steps(), &floor)
                .unwrap_err()
                .to_string();
            assert!(e.contains("the floor's `identities` allow"), "{e}");
            let e = SourcePolicies::build(
                &[upload("s", Some(r#"(provenance: require(keys: ["k2"]))"#))],
                &steps(),
                &floor,
            )
            .unwrap_err()
            .to_string();
            assert!(e.contains("not defined in the floor's `keys`"), "{e}");
            SourcePolicies::build(&[upload("s", Some(r#"(provenance: require(keys: ["k1"], identity: (issuer: "https://i", subject: "a")))"#))], &steps(), &floor)
                .expect("a subset narrows");
        }

        #[test]
        fn several_upload_sources_are_told_apart_by_the_first_path_segment() {
            let mut vendor = upload("vendor", None);
            vendor.importers = vec!["ci".into()];
            let p =
                SourcePolicies::build(&[upload("team", None), vendor], &steps(), &Rules::default())
                    .unwrap();
            assert_eq!(p.resolve("ida", "team/pkg-1.0.txt").unwrap().source, "team");
            assert!(p
                .resolve("ida", "other/pkg-1.0.txt")
                .unwrap_err()
                .contains("names no upload source"));
            assert!(p
                .resolve("ida", "vendor/x.txt")
                .unwrap_err()
                .contains("may not upload as source 'vendor'"));
            assert_eq!(p.resolve("ci", "vendor/x.txt").unwrap().source, "vendor");
        }

        #[test]
        fn a_source_that_skips_the_chain_is_refused_unless_the_floor_accepts_it() {
            let mirror = Source {
                kind: SourceKind::Holger,
                url: Some("http://up:50051".into()),
                ..upload("up", None)
            };
            let e =
                SourcePolicies::build(std::slice::from_ref(&mirror), &steps(), &Rules::default())
                    .unwrap_err()
                    .to_string();
            assert!(e.contains("allow_unchecked_sources"), "{e}");
            SourcePolicies::build(
                &[mirror],
                &steps(),
                &rules("(allow_unchecked_sources: true)"),
            )
            .expect("accepted by the floor");
        }

        #[test]
        fn auto_approval_needs_the_floor_and_may_only_be_stricter() {
            let mut s = upload("pypi", None);
            s.auto_approve = ron_options()
                .from_str("when(provenance: require, cve_max: medium)")
                .unwrap();
            let e = SourcePolicies::build(std::slice::from_ref(&s), &steps(), &Rules::default())
                .unwrap_err()
                .to_string();
            assert!(e.contains("allow_auto_approve"), "{e}");
            let floor = rules("(allow_auto_approve: true)");
            let p = SourcePolicies::build(std::slice::from_ref(&s), &steps(), &floor).unwrap();
            let rule = p
                .resolve("x", "p")
                .unwrap()
                .auto_approve
                .clone()
                .expect("the rule");
            assert_eq!(rule.approver, "auto-approve:pypi");
            assert_eq!(rule.cve_max, FindingSeverity::Medium);
            s.auto_approve = ron_options().from_str("when(cve_max: critical)").unwrap();
            let e = SourcePolicies::build(std::slice::from_ref(&s), &steps(), &floor)
                .unwrap_err()
                .to_string();
            assert!(
                e.contains("auto_approve.cve_max: critical") && e.contains("stricter"),
                "{e}"
            );
            let s2 = Source {
                auto_approve: AutoApprove::Off,
                ..upload(
                    "pypi",
                    Some(r#"(provenance: require(identity: (issuer: "https://i", subject: "a")))"#),
                )
            };
            let mut s3 = s2.clone();
            s3.auto_approve = ron_options().from_str("when(provenance: off)").unwrap();
            let e = SourcePolicies::build(&[s3], &steps(), &floor)
                .unwrap_err()
                .to_string();
            assert!(e.contains("auto_approve.provenance: off"), "{e}");
            assert_eq!(pull_through_actor("pypi"), "pull-through:pypi");
            assert!(ron_options()
                .from_str::<AutoApprove>("when(cve: medium)")
                .unwrap_err()
                .to_string()
                .contains("cve"));
        }

        #[test]
        fn the_refusals_name_the_rule_and_who_set_it() {
            use crate::scanner::{FindingSeverity as S, ScanFinding};
            let rec = |kind, verdict, findings: Vec<ScanFinding>| ScanEvidence {
                scanner_id: "x".into(),
                scanner_version: "1".into(),
                kind,
                verdict,
                findings,
                inputs: Vec::new(),
                document: None,
                detail: "d".into(),
                required: false,
                intel_sequence: None,
                hold: false,
            };
            let records = vec![
                rec(ScannerKind::Inspection, ScanVerdict::Pass, vec![]),
                rec(
                    ScannerKind::Vulnerability,
                    ScanVerdict::Warn,
                    vec![ScanFinding::new("GHSA-1", S::High, "h")],
                ),
                rec(ScannerKind::Provenance, ScanVerdict::Error, vec![]),
            ];
            let strict = SourcePolicies::build(
                &[upload(
                    "strict",
                    Some(r#"(provenance: require(identity: (issuer: "https://i", subject: "a")), cve: (max: medium))"#),
                )],
                &steps(),
                &rules("(require_signed: false, require_provenance: false)"),
            )
            .unwrap();
            let r = strict.resolve("a", "p").unwrap().refusals(&records, None);
            assert!(
                r.iter()
                    .any(|x| x.contains("provenance: require") && x.contains("source 'strict'")),
                "{r:?}"
            );
            assert!(
                r.iter()
                    .any(|x| x.contains("cve_max: medium") && x.contains("GHSA-1")),
                "{r:?}"
            );
            let open = SourcePolicies::build(
                &[upload("open", Some("(provenance: prefer)"))],
                &steps(),
                &rules("(require_signed: false, require_provenance: false)"),
            )
            .unwrap();
            assert!(
                open.resolve("a", "p")
                    .unwrap()
                    .refusals(&records, None)
                    .is_empty(),
                "high is within the floor's cve_max"
            );
            // Auto-approval reads the same records: a high advisory is over `cve_max: medium`.
            let floor = rules(
                "(allow_auto_approve: true, require_signed: false, require_provenance: false)",
            );
            let mut src = upload("pypi", None);
            src.auto_approve = ron_options().from_str("when(cve_max: medium)").unwrap();
            let p = SourcePolicies::build(std::slice::from_ref(&src), &steps(), &floor).unwrap();
            assert_eq!(
                p.resolve("a", "x")
                    .unwrap()
                    .auto_approval(&records, "pull-through:pypi"),
                None
            );
            src.auto_approve = ron_options().from_str("when(cve_max: high)").unwrap();
            let p = SourcePolicies::build(&[src], &steps(), &floor).unwrap();
            let e = p.resolve("a", "x").unwrap();
            assert_eq!(
                e.auto_approval(&records, "pull-through:pypi").as_deref(),
                Some("auto-approve:pypi")
            );
            assert_eq!(
                e.auto_approval(&records, "auto-approve:pypi"),
                None,
                "never the importer"
            );
        }

        #[test]
        fn the_secrets_gate_tightens_only_and_block_refuses_high_findings_by_name() {
            use crate::scanner::{FindingSeverity as S, ScanFinding};
            // The default floor blocks; a source may not loosen it.
            assert_eq!(Rules::default().secrets, SecretsMode::Block);
            let e = SourcePolicies::build(
                &[upload("team", Some("(secrets: warn)"))],
                &steps(),
                &Rules::default(),
            )
            .unwrap_err()
            .to_string();
            assert!(e.contains("secrets: warn") && e.contains("loosens"), "{e}");
            // A floor of `warn` lets one source tighten to `block` and another stay.
            let floor = rules("(secrets: warn)");
            let p = SourcePolicies::build(
                &[
                    upload("vendor", Some("(secrets: block)")),
                    upload("team", None),
                ],
                &steps(),
                &floor,
            )
            .unwrap();
            let vendor = p.resolve("a", "vendor/x.whl").unwrap();
            let team = p.resolve("a", "team/x.whl").unwrap();
            assert_eq!(
                (vendor.secrets.value, vendor.secrets.by.as_str()),
                (SecretsMode::Block, "source 'vendor'")
            );
            assert_eq!(team.secrets.value, SecretsMode::Warn);
            assert!(
                vendor.summary().contains("secrets=block(source 'vendor')"),
                "{}",
                vendor.summary()
            );

            let rec = |findings: Vec<ScanFinding>| ScanEvidence {
                scanner_id: "holger-secrets".into(),
                scanner_version: "1".into(),
                kind: ScannerKind::Secrets,
                verdict: if findings.is_empty() {
                    ScanVerdict::Pass
                } else {
                    ScanVerdict::Warn
                },
                findings,
                inputs: Vec::new(),
                document: None,
                detail: "d".into(),
                required: true,
                intel_sequence: None,
                hold: false,
            };
            let high = vec![rec(vec![ScanFinding::new(
                "secret:github-token",
                S::High,
                "a GitHub token (high) at x.whl!a.py:3",
            )])];
            let medium = vec![rec(vec![ScanFinding::new(
                "secret:dotenv-file",
                S::Medium,
                "a .env file (medium) at x.whl!.env",
            )])];
            // Only the secrets rule is under test here (no inspection record is handed in).
            let sec = |p: &EffectivePolicy, r: &[ScanEvidence]| -> Vec<String> {
                p.refusals(r, None)
                    .into_iter()
                    .filter(|x| x.contains("secrets: "))
                    .collect()
            };
            let r = sec(&vendor, &high);
            assert_eq!(r.len(), 1, "{r:?}");
            assert!(
                r[0].contains("rule `secrets: block` (source 'vendor')")
                    && r[0].contains("x.whl!a.py:3"),
                "{}",
                r[0]
            );
            assert!(sec(&team, &high).is_empty(), "warn records, never refuses");
            assert!(
                sec(&vendor, &medium).is_empty(),
                "medium findings always warn"
            );
            // `off` on the floor is honoured, and the secrets kind is then not required.
            let off = rules("(secrets: off)");
            assert!(sec(&EffectivePolicy::floor("x", &off), &high).is_empty());
            assert!(!floor_needs(&off).contains(&ScannerKind::Secrets));
            assert!(floor_needs(&Rules::default()).contains(&ScannerKind::Secrets));
        }
    }
}