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

//! **The box's import doors** (lane W4): signed intel bundles and signed
//! update / airgap bundles reach a box that has no shell.
//!
//! A Holger box runs as PID 1 and has no shell, so `holger-server intel import`
//! and `holger-server bundle import` could not be run on it. Every door below
//! runs ONE body of code, the same one those two CLI verbs now call:
//!
//! | door | intel bundle | update / airgap bundle |
//! |---|---|---|
//! | gRPC | `AdminService.ImportIntelBundle` (client stream) | `AdminService.ImportBundle` (client stream) |
//! | HTTP | `POST /-/intel/import` | `POST /-/bundle/import` |
//! | drop directory | `<data_root>/intel.inbox`, swept by a jera job | `<data_root>/bundle.inbox`, swept by a jera job |
//! | CLI | `holger-server intel import` | `holger-server bundle import` |
//!
//! **Who may import.** A Repository Admin or a Security Approver
//! ([`Verb::Import`]); anyone else is refused by name (`holger-duty-2`). With
//! RBAC off every caller holds every duty, as everywhere else in the intake
//! chain. The drop directories act as the box itself (the operator who can put a
//! file on the data disk already holds the box).
//!
//! **What an import does** — in this order, fail-closed at every step, the
//! order `bundle import` has kept since B3:
//!
//! 1. verify the bundle in a `verify-bundle` JOB (lane HOL-SANDBOX-TRAIT,
//!    [`crate::job_runner`]): the server links the file into the job's input and
//!    never opens it; the job checks the release signature from the control members
//!    before any payload is decompressed (`load_bundle_verified`), every member, and
//!    an export's evidence attestations, and hands back the verified files. The
//!    server believes only the job's signed result (the pinned per-boot key, the job
//!    id it issued, the listed outputs) and keeps the bytes that verified;
//! 2. a refused job is audited with its reason and answered by name;
//! 3. verify the whole bundle against the trust policy and the estate's clock;
//! 4. hold it to the anti-rollback floor. An absent floor is established only by a
//!    first import an ADMIN authorized on this side for exactly this vendor, product and
//!    sequence ([`ImportDoor::allow_first_import`]: `holger bundle allow-first-import`,
//!    `POST /-/bundle/first-import`; lane HOL-CDS, NCSC gap 2); otherwise it refuses
//!    (`holger-import-9`). There is no caller flag and no inbox marker any more
//!    (`holger-import-10`);
//! 5. write the audit record BEFORE anything lands; no record, no import;
//! 6. intel: make the advisory set the live local database, which the next scan
//!    reads. Bundle: land every artifact AVAILABLE **through the intake chain**
//!    ([`IntakeChain::land_imported`]), never into a raw repository's data
//!    directory. An airgap export lands each of its artifacts with the evidence
//!    and history it carries; any other update bundle lands as one artifact, the
//!    signed bundle file itself, at `<supplier>/<product>/<release>.holger.znippy`
//!    in the production repository.
//!
//! **The removed bypass.** `bundle import --repo <raw>` wrote the bundle straight
//! into a raw repository's `ron_data_dir`, past every intake state. That path is
//! gone; naming it is refused by name ([`IMPORT_RAW_BYPASS_REMOVED`]), and so is
//! a bundle import on a server with no intake chain.
//!
//! **The drop directories.** An operator copies a bundle onto the data disk
//! through the estate's file path. The sweep job imports every file in the
//! inbox, oldest name first, and moves it to `archive/` (imported) or `refused/`
//! (with a `.why.txt` beside it that names the refusal). A file still being
//! copied is written under a name starting with `.` or ending in `.part` and
//! renamed when complete; the sweep skips both. A `FIRST-IMPORT` marker file is
//! no authorization: the sweep moves it to `refused/` with its why (`holger-import-10`).
//!
//! [`Verb::Import`]: crate::promote::duties::Verb::Import
//! [`IntakeChain::land_imported`]: crate::promote::intake::IntakeChain::land_imported
//! [`IMPORT_RAW_BYPASS_REMOVED`]: holger_errcode::IMPORT_RAW_BYPASS_REMOVED

use crate::lock::LockOrRecover as _;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};

use holger_errcode::ErrCode;
use serde::{Deserialize, Serialize};
#[cfg(feature = "intake")]
use update_bundle::container::{atomic_write, load_bundle_verified};
use update_bundle::core::BundleCore;
#[cfg(feature = "intake")]
use update_bundle::digest::sha256_hex;
#[cfg(feature = "intake")]
use update_bundle::receipt::ImportReceipt;
#[cfg(feature = "intake")]
use update_bundle::rollback::{AcceptedEntry, AcceptedState, Admissibility, RollbackStore};
#[cfg(feature = "intake")]
use update_bundle::schema::CORE_JSON;
#[cfg(feature = "intake")]
use update_bundle::verify::verify_member_map_with_time;
use update_bundle::verify::TargetSpec;
use update_bundle::TrustPolicy;

#[cfg(feature = "intake")]
use crate::audit::TrailKind;
use crate::audit::{AuditAction, AuditEvent, AuditLog, AuditMode};
#[cfg(feature = "intake")]
use crate::auth::{AuthConfig, AuthIdentity};
use crate::intel::IntelConfig;
#[cfg(feature = "intake")]
use crate::promote::airgap;
#[cfg(feature = "intake")]
use crate::promote::duties::{self, DutyError, Verb};
#[cfg(feature = "intake")]
use crate::promote::intake::{ImportedArtifact, IntakeChain, IntakeEntry, LandOutcome};

/// The intel drop directory under the data root.
pub const INTEL_INBOX: &str = "intel.inbox";
/// The update / airgap bundle drop directory under the data root (the one-way inbox,
/// [`crate::boundary::INBOX_DIR`]).
pub const BUNDLE_INBOX: &str = crate::boundary::INBOX_DIR;
/// The source name an airgap import's provenance check reads its gate by (lane G3a).
pub const AIRGAP_SOURCE: &str = "airgap";
/// The REMOVED inbox marker (lane HOL-CDS): it once let the next import establish an
/// absent floor. A file of this name is refused now (`holger-import-10`).
pub const FIRST_IMPORT_MARKER: &str = "FIRST-IMPORT";
/// Where the admins' first-import authorizations live, under the data root.
pub const FIRST_IMPORT_DIR: &str = "holger-first-import";
/// The authorizations file in [`FIRST_IMPORT_DIR`].
pub const FIRST_IMPORT_GRANTS: &str = "grants.json";

/// **An admin's first-import authorization** (lane HOL-CDS, NCSC gap 2): this vendor's
/// product may establish its absent anti-rollback floor with exactly this sequence. Used
/// once; audited when given and when used.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FirstImportGrant {
    pub vendor: String,
    pub product: String,
    pub sequence: u64,
    /// Who authorized it.
    pub by: String,
    /// Unix seconds.
    pub at: i64,
}

/// The refusal of a caller's `first_import` flag (or an inbox marker), by name.
pub fn first_import_flag_refusal(what: &str) -> ImportError {
    ImportError::new(
        &holger_errcode::IMPORT_FIRST_FLAG_REMOVED,
        400,
        format!(
            "{what} is removed: the first import is an admin's action on this side, for one vendor, product and \
             sequence (`holger bundle allow-first-import --vendor V --product P --sequence N`, the console, or \
             `POST /-/bundle/first-import`); then import the bundle as usual"
        ),
    )
}
/// Where an imported inbox file is moved.
pub const ARCHIVE_DIR: &str = "archive";
/// Where a refused inbox file is moved, with a `.why.txt` beside it.
pub const REFUSED_DIR: &str = "refused";
/// The product of a scanner signature bundle (`holger_intel::sigs::SIGS_PRODUCT`, lane
/// HOL-NCSC): `bundle import` installs it into the sandboxed clamd's database.
pub const SCANNER_SIGS_PRODUCT: &str = "holger.scanner-sigs/v1";
/// Where the network doors stage a streamed bundle before it is imported.
pub const STAGING_DIR: &str = ".holger-import-staging";
/// The anti-rollback floor of imported update bundles, under the data root.
pub const UPDATE_STATE_DIR: &str = "holger-update-state";
/// The import receipts of update bundles, under the data root.
pub const RECEIPTS_DIR: &str = "holger-import-receipts";
/// How often the server looks into the drop directories. A look is a
/// directory listing; a sweep job is queued only when a file waits.
pub const INBOX_POLL: std::time::Duration = std::time::Duration::from_secs(2);
/// How often an online box refreshes its intel (the first tick is at boot).
pub const INTEL_REFRESH_EVERY: std::time::Duration = std::time::Duration::from_secs(24 * 60 * 60);

// ───────────────────────── the estate's clock ─────────────────────────

/// **The bundle verifier's time source: the ESTATE's clock** (`crate::clock`,
/// edda `estate-clock`). Every expiry the verifier checks is checked against
/// [`crate::clock::now_unix_ms`], and the verdict names the source.
///
/// * a verified answer stands behind the clock → trusted, the peer's time;
/// * no clock source is configured at all → the system clock, recorded
///   `estate-clock (UNTRUSTED …)` in the verdict;
/// * a source IS configured and nothing verified answered → REFUSED
///   ([`TimeError::Unverified`](update_bundle::time::TimeError::Unverified)): a
///   box told to follow a clock that cannot is not a box without one.
pub struct EstateTime;

impl update_bundle::time::TrustedTime for EstateTime {
    fn source_name(&self) -> &'static str {
        crate::clock::SOURCE_NAME
    }
    fn is_trusted(&self) -> bool {
        crate::clock::is_trusted()
    }
    fn now(
        &self,
    ) -> std::result::Result<chrono::DateTime<chrono::Utc>, update_bundle::time::TimeError> {
        use update_bundle::time::TimeError;
        if !crate::clock::is_trusted() {
            let last = || {
                crate::clock::last_summary()
                    .unwrap_or_else(|| "no sync has run in this process".into())
            };
            match crate::clock::config_now() {
                Ok(crate::clock::Config::Sources { .. }) => {
                    return Err(TimeError::Unverified(last()))
                }
                Err(why) => return Err(TimeError::Unverified(why)),
                Ok(_) => {}
            }
        }
        let ms = crate::clock::now_unix_ms();
        chrono::DateTime::<chrono::Utc>::from_timestamp_millis(ms).ok_or(TimeError::OutOfRange(ms))
    }
}

#[cfg(feature = "intake")]
fn estate_now() -> chrono::DateTime<chrono::Utc> {
    chrono::DateTime::<chrono::Utc>::from_timestamp_millis(crate::clock::now_unix_ms())
        .unwrap_or_else(chrono::Utc::now)
}

// ───────────────────────── the two kinds ─────────────────────────

/// Which import.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ImportKind {
    /// A `holger.intel/v1` bundle into the local advisory database.
    Intel,
    /// An update or airgap bundle into the intake chain.
    Bundle,
}

impl ImportKind {
    /// The drop directory's name under the data root.
    pub fn inbox_name(self) -> &'static str {
        match self {
            ImportKind::Intel => INTEL_INBOX,
            ImportKind::Bundle => BUNDLE_INBOX,
        }
    }

    /// The word the duty refusal names (`import an intel bundle`).
    pub fn what(self) -> &'static str {
        match self {
            ImportKind::Intel => "intel",
            ImportKind::Bundle => "update",
        }
    }

    pub fn as_str(self) -> &'static str {
        match self {
            ImportKind::Intel => "intel",
            ImportKind::Bundle => "bundle",
        }
    }
}

// ───────────────────────── refusals ─────────────────────────

/// An import refused by name. Nothing landed.
#[derive(Debug, Clone)]
pub struct ImportError {
    pub code: &'static ErrCode,
    pub message: String,
    /// The HTTP status a door answers with.
    pub status: u16,
}

impl ImportError {
    pub(crate) fn new(code: &'static ErrCode, status: u16, message: impl Into<String>) -> Self {
        Self {
            code,
            message: message.into(),
            status,
        }
    }

    fn refused(message: impl std::fmt::Display) -> Self {
        Self::new(&holger_errcode::IMPORT_REFUSED, 422, format!("{message}"))
    }

    /// `[holger-<sub>-<n>] message`.
    pub fn tagged(&self) -> String {
        self.code.tag(&self.message)
    }
}

impl std::fmt::Display for ImportError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&self.tagged())
    }
}

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

#[cfg(feature = "intake")]
impl From<DutyError> for ImportError {
    fn from(e: DutyError) -> Self {
        ImportError {
            code: e.code(),
            status: e.status(),
            message: e.to_string(),
        }
    }
}

/// The refusal of the removed raw-directory path, by name.
pub fn raw_bypass_refusal(repo: &str) -> ImportError {
    ImportError::new(
        &holger_errcode::IMPORT_RAW_BYPASS_REMOVED,
        410,
        format!(
            "`bundle import --repo {repo}` wrote the bundle straight into the raw repository's data \
             directory and bypassed the intake chain; that path is removed. Declare `promotion.intake` \
             and import without --repo: every artifact then lands AVAILABLE through intake."
        ),
    )
}

// ───────────────────────── answers ─────────────────────────

/// What an intel import did.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IntelImported {
    /// `new` or `idempotent-reimport`.
    pub outcome: String,
    pub bundle_id: String,
    pub sequence: u64,
    /// `YYYY-MM-DD` of the advisory snapshot.
    pub snapshot_at: String,
    pub advisories: u64,
    pub database: String,
    pub checks: Vec<String>,
}

/// One artifact an update bundle landed.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LandedArtifact {
    pub artifact: String,
    pub sha256: String,
    /// `AVAILABLE`.
    pub state: String,
    pub repository: String,
    /// `landed` or `already-available`.
    pub outcome: String,
}

/// What a bundle import did.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BundleImported {
    /// `new` or `idempotent-reimport`.
    pub outcome: String,
    pub bundle_id: String,
    pub sequence: u64,
    /// `airgap-export` or `update-bundle`.
    pub kind: String,
    pub artifacts: Vec<LandedArtifact>,
    pub receipt: String,
    pub checks: Vec<String>,
    /// H4: the bundle carried a sync manifest and it was applied; the signed ack's file.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub sync_ack: Option<String>,
    /// H4: the packages the carried manifest's invalidations held here.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub sync_held: Vec<String>,
}

/// What one inbox sweep did.
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct InboxSweep {
    pub imported: Vec<String>,
    pub refused: Vec<(String, String)>,
}

impl InboxSweep {
    pub fn summary(&self) -> String {
        format!(
            "imported {} [{}], refused {} [{}]",
            self.imported.len(),
            self.imported.join(", "),
            self.refused.len(),
            self.refused
                .iter()
                .map(|(f, why)| format!("{f}: {why}"))
                .collect::<Vec<_>>()
                .join("; ")
        )
    }
}

// ───────────────────────── the door ─────────────────────────

/// The intel audit sink: the server's audit log, fail-closed.
#[cfg(feature = "import-door")]
struct ServerAudit<'a> {
    log: &'a Arc<dyn AuditLog>,
    actor: &'a str,
    source: &'a str,
}

#[cfg(feature = "import-door")]
impl holger_intel::ImportAudit for ServerAudit<'_> {
    fn ready(&self) -> anyhow::Result<()> {
        if self.log.mode() == AuditMode::Off {
            anyhow::bail!("audit is off or its directory could not be opened");
        }
        Ok(())
    }
    fn record(&self, r: &holger_intel::ImportRecord) -> anyhow::Result<()> {
        let ev = AuditEvent::new(
            self.actor,
            AuditAction::Other,
            "intel",
            format!("{}/{}", r.supplier, r.bundle_id),
            self.source,
            200,
            r.advisories,
        )
        .with_detail(format!("intel-import: {}", r.detail()));
        self.log
            .record(ev)
            .and_then(|()| self.log.flush())
            .map_err(|e| anyhow::anyhow!("{e:?}"))
    }
}

/// **The import door** — one per served process, shared by both network doors
/// and the inbox jobs; the CLI builds its own over the same config.
pub struct ImportDoor {
    intel: Option<IntelConfig>,
    #[cfg(feature = "intake")]
    intake: Option<Arc<IntakeChain>>,
    audit: Arc<dyn AuditLog>,
    #[cfg(feature = "intake")]
    attest: Option<Arc<crate::attest::AttestStore>>,
    /// The server's ONE job runner (lane HOL-SANDBOX-TRAIT): every bundle is verified
    /// in a `verify-bundle` job, never in this process.
    #[cfg(feature = "intake")]
    jobs: Option<Arc<crate::job_runner::Jobs>>,
    #[cfg_attr(not(feature = "intake"), allow(dead_code))]
    bundle_policy: Option<PathBuf>,
    data_root: PathBuf,
    /// One import at a time per process: the floor is read, judged and moved
    /// as one step.
    serial: Mutex<()>,
    /// The monotone time floor every judgement here is taken at (lane HOL-NCSC).
    time_floor: crate::time_floor::TimeFloor,
}

impl ImportDoor {
    /// The door over `holger`'s config: its `intel:` block, its intake chain,
    /// its audit sink and attestation store, under its data root.
    pub fn of(holger: &crate::Holger) -> anyhow::Result<Self> {
        let audit = holger.audit_log()?;
        #[cfg(feature = "intake")]
        let intake = holger.intake_chain(audit.clone())?;
        #[cfg(feature = "intake")]
        let bundle_policy = intake
            .as_ref()
            .and_then(|c| c.settings().trust_policy.clone())
            .map(PathBuf::from);
        #[cfg(not(feature = "intake"))]
        let bundle_policy = None;
        Ok(Self {
            intel: holger.intel.clone(),
            #[cfg(feature = "intake")]
            intake,
            #[cfg(feature = "intake")]
            attest: holger.attestation_store()?,
            #[cfg(feature = "intake")]
            jobs: holger.jobs()?,
            audit,
            bundle_policy,
            time_floor: crate::time_floor::TimeFloor::open(&holger.data_root())
                .map_err(|e| anyhow::anyhow!("{e}"))?,
            data_root: holger.data_root(),
            serial: Mutex::new(()),
        })
    }

    /// The same door over `jobs` (a test's runner).
    #[cfg(feature = "intake")]
    pub fn with_jobs(mut self, jobs: Arc<crate::job_runner::Jobs>) -> Self {
        self.jobs = Some(jobs);
        self
    }

    /// The estate's clock judged at this box's time floor (lane HOL-NCSC).
    #[cfg_attr(not(feature = "intake"), allow(dead_code))]
    fn time(&self) -> crate::time_floor::FlooredTime<'_> {
        crate::time_floor::FlooredTime::new(
            &self.time_floor,
            &EstateTime,
            Some(Arc::clone(&self.audit)),
        )
    }

    /// The time floor (its file, its value), for a status line.
    pub fn time_floor(&self) -> &crate::time_floor::TimeFloor {
        &self.time_floor
    }

    /// Raise the floor to a signed time this import accepted; a floor that cannot be kept is
    /// logged (the import already stands).
    #[cfg_attr(not(feature = "intake"), allow(dead_code))]
    fn raise_floor(&self, unix_ms: i64, source: &str) -> bool {
        match self.time_floor.raise(unix_ms, source) {
            Ok(raised) => raised,
            Err(e) => {
                log::error!("{e}");
                false
            }
        }
    }

    /// **A signed time token, verified** (`holger.time-token/v1`): it raises the time floor
    /// and lands nothing. Audited first; a refusal is audited by `import_bundle` (HOL-NCSC).
    #[cfg(feature = "intake")]
    fn accept_time_token(
        &self,
        members: &update_bundle::MemberMap,
        core: &BundleCore,
        policy: &TrustPolicy,
        actor: &str,
        source: &str,
    ) -> Result<BundleImported, ImportError> {
        if self.audit.mode() == AuditMode::Off {
            return Err(ImportError::new(&holger_errcode::AIRGAP_AUDIT_FAILED, 409, "time token refused: the audit log is off, and a time that leaves no audit record is not taken"));
        }
        let report = verify_member_map_with_time(
            members,
            policy,
            &TargetSpec::default(),
            None,
            &self.time(),
        )
        .map_err(|e| ImportError::refused(format!("time token refused: {}", e.code().tag(&e))))?;
        let ms = crate::time_floor::token_time(members, core)
            .map_err(|why| ImportError::new(&holger_errcode::TIME_TOKEN_REFUSED, 422, why))?;
        let ev = AuditEvent::new(
            actor,
            AuditAction::Other,
            "",
            core.bundle_id.clone(),
            source,
            200,
            0,
        )
        .with_detail(format!(
            "time-token: bundle_id={} time={} digest={}",
            core.bundle_id,
            core.created_at.to_rfc3339(),
            report.candidate_digest
        ));
        self.audit
            .record(ev)
            .and_then(|()| self.audit.flush())
            .map_err(|e| {
                ImportError::new(
                    &holger_errcode::AIRGAP_AUDIT_FAILED,
                    500,
                    format!("time token refused: its audit record could not be written ({e:?})"),
                )
            })?;
        let raised = self
            .time_floor
            .raise(ms, &format!("the time token {}", core.bundle_id))
            .map_err(|why| ImportError::new(&holger_errcode::TIME_FLOOR_UNREADABLE, 500, why))?;
        let said = format!(
            "TIME FLOOR {} {} (the signed time token {}, signer verified)",
            if raised {
                "raised to"
            } else {
                "already at or past"
            },
            core.created_at.to_rfc3339(),
            core.bundle_id
        );
        Ok(BundleImported {
            outcome: if raised {
                "time-floor-raised".into()
            } else {
                "time-floor-unchanged".into()
            },
            bundle_id: core.bundle_id.clone(),
            sequence: core.sequence,
            kind: "time-token".into(),
            artifacts: vec![],
            receipt: String::new(),
            checks: vec![said],
            sync_ack: None,
            sync_held: vec![],
        })
    }

    /// Does this server have an intake chain a bundle import can land in?
    pub fn has_intake(&self) -> bool {
        #[cfg(feature = "intake")]
        {
            self.intake.is_some()
        }
        #[cfg(not(feature = "intake"))]
        {
            false
        }
    }

    pub fn data_root(&self) -> &Path {
        &self.data_root
    }

    /// The drop directory for `kind`.
    pub fn inbox(&self, kind: ImportKind) -> PathBuf {
        self.data_root.join(kind.inbox_name())
    }

    /// Create the drop directories this config can use (best effort), under an
    /// ABSOLUTE data root only: a relative one is the process's working
    /// directory, and the drop directories are not made in whatever directory a
    /// test or an embedded caller happened to start in.
    pub fn prepare_inboxes(&self) {
        if !self.data_root.is_absolute() {
            return;
        }
        if self.intel.is_some() {
            let _ = std::fs::create_dir_all(self.inbox(ImportKind::Intel));
        }
        if self.has_intake() {
            let _ = std::fs::create_dir_all(self.inbox(ImportKind::Bundle));
        }
    }

    /// **May `identity` import a `kind` bundle?** Repository Admin or Security
    /// Approver; the refusal names the principal, its duties and the verb.
    #[cfg(feature = "intake")]
    pub fn authorize(
        auth: &AuthConfig,
        identity: Option<&AuthIdentity>,
        kind: ImportKind,
    ) -> Result<String, ImportError> {
        let principal = duties::principal_of(identity);
        duties::authorize(
            &duties::duties_of(auth, identity),
            &principal,
            kind.what(),
            Verb::Import(kind.what()),
        )?;
        Ok(principal)
    }

    /// **May `identity` authorize a first import?** A Repository Admin only (an admin's
    /// action on the secure side); the refusal names the principal and its duties.
    #[cfg(feature = "intake")]
    pub fn authorize_admin(
        auth: &AuthConfig,
        identity: Option<&AuthIdentity>,
    ) -> Result<String, ImportError> {
        let principal = duties::principal_of(identity);
        duties::authorize(
            &duties::duties_of(auth, identity),
            &principal,
            "a first import",
            Verb::AllowFirstImport,
        )?;
        Ok(principal)
    }

    /// A fresh file under the staging directory, removed when the guard drops.
    pub fn stage(&self) -> Result<StagedBundle, ImportError> {
        let dir = self.data_root.join(STAGING_DIR);
        std::fs::create_dir_all(&dir)
            .map_err(|e| staging(format!("create {}: {e}", dir.display())))?;
        static N: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
        let n = N.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
        let path = dir.join(format!(
            "{}-{}-{n}.holger.znippy",
            std::process::id(),
            crate::audit::now_nanos()
        ));
        let file = std::fs::OpenOptions::new()
            .write(true)
            .create_new(true)
            .open(&path)
            .map_err(|e| staging(format!("create {}: {e}", path.display())))?;
        Ok(StagedBundle {
            path,
            file: Some(file),
        })
    }

    fn grants_path(&self) -> PathBuf {
        self.data_root
            .join(FIRST_IMPORT_DIR)
            .join(FIRST_IMPORT_GRANTS)
    }

    /// The standing first-import authorizations.
    pub fn first_import_grants(&self) -> Vec<FirstImportGrant> {
        std::fs::read(self.grants_path())
            .ok()
            .and_then(|b| serde_json::from_slice(&b).ok())
            .unwrap_or_default()
    }

    fn save_grants(&self, grants: &[FirstImportGrant]) -> Result<(), ImportError> {
        let body = serde_json::to_vec_pretty(grants)
            .map_err(|e| grant_refused(500, format!("the authorizations: {e}")))?;
        update_bundle::container::atomic_write(&self.grants_path(), &body)
            .map_err(|e| grant_refused(500, format!("write {}: {e}", self.grants_path().display())))
    }

    /// Does an absent floor of `(vendor, product)` stand? `false` when one is recorded.
    #[cfg(feature = "intake")]
    fn floor_absent(&self, vendor: &str, product: &str) -> bool {
        #[cfg(feature = "import-door")]
        if product == holger_intel::INTEL_PRODUCT {
            if let Some(intel) = &self.intel {
                return holger_intel::LocalIntelDb::open(&intel.db_dir)
                    .installed()
                    .ok()
                    .flatten()
                    .is_none();
            }
        }
        RollbackStore::open(self.data_root.join(UPDATE_STATE_DIR))
            .floor(vendor, product)
            .map(|f| f.accepted().is_none())
            .unwrap_or(false)
    }

    /// ★ **An admin authorizes the first import** of `vendor`'s `product` at `sequence`
    /// (lane HOL-CDS, NCSC gap 2). Refused (`holger-import-11`) when a field is blank, the
    /// sequence is 0, a floor already stands, or the audit record cannot be written. It
    /// replaces an earlier authorization of the same vendor and product. The caller has
    /// already been held to the admin duty ([`Self::authorize_admin`]; the CLI is the box's
    /// operator).
    #[cfg(feature = "intake")]
    pub fn allow_first_import(
        &self,
        vendor: &str,
        product: &str,
        sequence: u64,
        actor: &str,
        source: &str,
    ) -> Result<FirstImportGrant, ImportError> {
        let (vendor, product) = (vendor.trim(), product.trim());
        if vendor.is_empty() || product.is_empty() || sequence == 0 {
            return Err(grant_refused(422, format!("a first-import authorization names a vendor, a product and a sequence from 1 (got {vendor:?}, {product:?}, {sequence})")));
        }
        if self.audit.mode() == AuditMode::Off {
            return Err(grant_refused(409, "the audit log is off, and an authorization that leaves no audit record is not given".into()));
        }
        let _one = self.serial.lock_or_recover();
        if !self.floor_absent(vendor, product) {
            return Err(grant_refused(409, format!("the anti-rollback floor of {vendor}/{product} already stands; a first import is for an absent floor only")));
        }
        let ev = AuditEvent::new(actor, AuditAction::Other, "imports", format!("{vendor}/{product}"), source, 200, sequence)
            .with_detail(format!("first-import authorized: vendor {vendor} product {product} sequence {sequence} by {actor} (trust.first_import: Admin)"));
        self.audit
            .record(ev)
            .and_then(|()| self.audit.flush())
            .map_err(|e| {
                grant_refused(
                    500,
                    format!("the authorization could not be audited ({e:?}), so it was not given"),
                )
            })?;
        let grant = FirstImportGrant {
            vendor: vendor.into(),
            product: product.into(),
            sequence,
            by: actor.into(),
            at: crate::audit::now_nanos() / 1_000_000_000,
        };
        let mut grants: Vec<FirstImportGrant> = self
            .first_import_grants()
            .into_iter()
            .filter(|g| !(g.vendor == vendor && g.product == product))
            .collect();
        grants.push(grant.clone());
        self.save_grants(&grants)?;
        Ok(grant)
    }

    /// May this bundle establish its absent floor? The grant for exactly this vendor,
    /// product and sequence, or the refusal that names the admin's action.
    #[cfg(feature = "intake")]
    fn first_import_grant(
        &self,
        vendor: &str,
        product: &str,
        sequence: u64,
    ) -> Result<FirstImportGrant, ImportError> {
        let grants = self.first_import_grants();
        if let Some(g) = grants
            .iter()
            .find(|g| g.vendor == vendor && g.product == product && g.sequence == sequence)
        {
            return Ok(g.clone());
        }
        let other = grants
            .iter()
            .find(|g| g.vendor == vendor && g.product == product)
            .map(|g| {
                format!(
                    " (an admin authorized sequence {}, not {sequence})",
                    g.sequence
                )
            })
            .unwrap_or_default();
        Err(ImportError::new(
            &holger_errcode::IMPORT_FIRST_NOT_AUTHORIZED,
            409,
            format!(
                "no anti-rollback floor stands for {vendor}/{product}, and no admin authorized its first import at sequence {sequence}{other}. \
                 An admin on this side runs `holger bundle allow-first-import --vendor {vendor} --product {product} --sequence {sequence}` \
                 (or the console, `POST /-/bundle/first-import`), then the bundle is imported again"
            ),
        ))
    }

    /// The grant is used: remove it and audit that the floor was established by it.
    #[cfg(feature = "intake")]
    fn spend_grant(&self, g: &FirstImportGrant, bundle_id: &str, actor: &str, source: &str) {
        let left: Vec<FirstImportGrant> = self
            .first_import_grants()
            .into_iter()
            .filter(|x| x != g)
            .collect();
        if let Err(e) = self.save_grants(&left) {
            log::warn!(
                "first import: the used authorization could not be removed: {}",
                e.tagged()
            );
        }
        let ev = AuditEvent::new(actor, AuditAction::Other, "imports", format!("{}/{}", g.vendor, g.product), source, 200, g.sequence).with_detail(format!(
            "first-import: bundle {bundle_id} established the floor of {}/{} at sequence {}, authorized by {} at {}",
            g.vendor, g.product, g.sequence, g.by, g.at
        ));
        let _ = self.audit.record(ev).and_then(|()| self.audit.flush());
    }

    /// Import `kind` from `bundle` as `actor` from `source`.
    pub fn import(
        &self,
        kind: ImportKind,
        bundle: &Path,
        actor: &str,
        source: &str,
    ) -> Result<String, ImportError> {
        match kind {
            ImportKind::Intel => self.import_intel(bundle, None, actor, source).map(|r| {
                format!(
                    "IMPORTED ({}) {} seq {} dated {}: {} advisories",
                    r.outcome, r.bundle_id, r.sequence, r.snapshot_at, r.advisories
                )
            }),
            ImportKind::Bundle => self
                .import_bundle(bundle, None, &TargetSpec::default(), actor, source)
                .map(|r| {
                    format!(
                        "IMPORTED ({}) {} seq {}: {} artifact(s)",
                        r.outcome,
                        r.bundle_id,
                        r.sequence,
                        r.artifacts.len()
                    )
                }),
        }
    }

    // ── intel ─────────────────────────────────────────────────────────────

    /// **Import an intel bundle**: verify, floor, audit first, then make its
    /// advisory set the live local database. `policy` overrides
    /// `intel.trust_policy`.
    /// A build without the intel database (the downstream) refuses an intel bundle by name.
    #[cfg(not(feature = "import-door"))]
    pub fn import_intel(
        &self,
        _bundle: &Path,
        _policy: Option<&TrustPolicy>,
        _actor: &str,
        _source: &str,
    ) -> Result<IntelImported, ImportError> {
        Err(ImportError::refused("this build carries no intel database (a downstream trusts the upstream's signed scan evidence)"))
    }

    #[cfg(feature = "import-door")]
    pub fn import_intel(
        &self,
        bundle: &Path,
        policy: Option<&TrustPolicy>,
        actor: &str,
        source: &str,
    ) -> Result<IntelImported, ImportError> {
        let r = self.import_intel_as(bundle, policy, None, actor, source);
        if let Err(e) = &r {
            self.audit_refusal("intel import", bundle, e, actor, source);
        }
        r
    }

    /// HOL-NCSC (NCSC P1 item 5): **a refused import leaves its audit row**, with its code and
    /// the file: a signature, a floor, a core.json, refused intel, an inbox refusal. Best
    /// effort (an import refused because its audit sink is off has nowhere to write).
    fn audit_refusal(&self, what: &str, bundle: &Path, e: &ImportError, actor: &str, source: &str) {
        let name = bundle
            .file_name()
            .map(|n| n.to_string_lossy().into_owned())
            .unwrap_or_default();
        let ev = AuditEvent::new(
            actor,
            AuditAction::Other,
            "",
            name.clone(),
            source,
            e.status,
            0,
        )
        .with_detail(format!("{what} refused: {name}: {}", e.tagged()));
        if let Err(w) = self.audit.record(ev).and_then(|()| self.audit.flush()) {
            log::error!(
                "{what} of {name} refused ({}); its audit row could not be written: {w:?}",
                e.tagged()
            );
        }
    }

    /// [`Self::import_intel`]; `own_first` is the box's OWN refresh establishing its own
    /// floor (`intel.refresh`), never a caller's word.
    #[cfg(feature = "import-door")]
    fn import_intel_as(
        &self,
        bundle: &Path,
        policy: Option<&TrustPolicy>,
        own_first: Option<bool>,
        actor: &str,
        source: &str,
    ) -> Result<IntelImported, ImportError> {
        let Some(intel) = &self.intel else {
            return Err(ImportError::new(
                &holger_errcode::IMPORT_NO_INTEL,
                409,
                "this server declares no `intel:` block; name the local advisory database there (db_dir)",
            ));
        };
        intel.validate().map_err(ImportError::refused)?;
        let loaded;
        let policy = match policy {
            Some(p) => p,
            None => {
                loaded = load_policy(
                    intel.trust_policy.as_deref().map(Path::new),
                    "`intel.trust_policy`",
                )?;
                &loaded
            }
        };
        policy
            .expired_posture()
            .map_err(|e| ImportError::new(e.code(), 422, e.to_string()))?;
        // B3 first: an import that cannot be audited is refused for that, before its floor is
        // judged (an absent floor's `holger-import-9` would hide the real reason).
        if self.audit.mode() == AuditMode::Off {
            return Err(ImportError::refused(
                "intel import refused: the audit log is off or could not be opened, and an import that leaves no audit record is not allowed",
            ));
        }
        // An absent floor: the admin's grant for this bundle, or the refusal by name.
        let grant = match own_first {
            Some(_) => None,
            None if holger_intel::LocalIntelDb::open(&intel.db_dir)
                .installed()
                .ok()
                .flatten()
                .is_none() =>
            {
                let members = load_bundle_verified(bundle, policy).map_err(|e| {
                    ImportError::refused(format!("intel import refused: {}", e.code().tag(&e)))
                })?;
                // Every member's digest against the release-signed manifest first: a tampered
                // bundle is refused for what it is (`holger-import-5`), before its floor is judged.
                verify_member_map_with_time(
                    &members,
                    policy,
                    &TargetSpec::default(),
                    None,
                    &self.time(),
                )
                .map_err(|e| {
                    ImportError::refused(format!(
                        "intel import refused: bundle verification failed: {}",
                        e.code().tag(&e)
                    ))
                })?;
                let core = BundleCore::from_json_bytes(
                    members
                        .get(CORE_JSON)
                        .ok_or_else(|| ImportError::refused("intel bundle has no core.json"))?,
                )
                .map_err(|e| ImportError::refused(format!("core.json: {e}")))?;
                Some((
                    self.first_import_grant(&core.supplier, &core.product, core.sequence)?,
                    core.bundle_id,
                ))
            }
            None => None,
        };
        let first_import = own_first.unwrap_or(grant.is_some());
        let _one = self.serial.lock_or_recover();
        let db = holger_intel::LocalIntelDb::open(&intel.db_dir);
        let audit = ServerAudit {
            log: &self.audit,
            actor,
            source,
        };
        let out = holger_intel::import(
            &holger_intel::ImportRequest {
                bundle,
                policy,
                db: &db,
                first_import,
            },
            &self.time(),
            &audit,
        )
        .map_err(|e| ImportError::refused(format!("intel import refused: {e:#}")))?;
        self.raise_floor(
            out.record.snapshot_at.timestamp_millis(),
            &format!("the intel bundle {}", out.record.bundle_id),
        );
        drop(_one);
        if let Some((g, id)) = &grant {
            self.spend_grant(g, id, actor, source);
        }
        Ok(IntelImported {
            outcome: out.record.outcome.clone(),
            bundle_id: out.record.bundle_id.clone(),
            sequence: out.record.sequence,
            snapshot_at: out.record.snapshot_at.format("%Y-%m-%d").to_string(),
            advisories: out.record.advisories,
            database: db.root().display().to_string(),
            checks: out.checks,
        })
    }

    /// **`intel.refresh`**: fetch OSV, NVD and KEV (or `intel.feed_mirror`),
    /// normalize them, sign the set into a `holger.intel/v1` bundle with the box's
    /// OWN refresh signer, and land it through [`Self::import_intel`] — the same
    /// verify, anti-rollback floor and audit-first path a carried bundle takes.
    /// Fail-closed: a feed that cannot be fetched or read lands nothing, and the
    /// database stays as it was. Refused by name, with nothing dialled, on a box
    /// that may not fetch (`intel.offline: true`, `network: Offline`, a build
    /// without `intel-refresh`).
    ///
    /// The refresh signer (two approver roles and a release signer, ed25519, under
    /// `<intel.db_dir>/refresh-signer/`) is minted on the first refresh and trusted
    /// by an in-memory policy for `holger.intel/v1` only; `intel.trust_policy`,
    /// which carried bundles verify against, is not widened by it. The floor is the
    /// one carried bundles share (sequence `YYYYMMDDHHMMSS` of the fetch).
    #[cfg(feature = "import-door")]
    pub fn refresh_intel(
        &self,
        net: &holger_intel::IntelNet,
    ) -> Result<IntelImported, ImportError> {
        let Some(intel) = &self.intel else {
            return Err(ImportError::new(
                &holger_errcode::IMPORT_NO_INTEL,
                409,
                "this server declares no `intel:` block to refresh",
            ));
        };
        let off = |why: &str| {
            ImportError::new(
                &holger_errcode::IMPORT_REFRESH_OFF,
                409,
                format!("intel.refresh does not run: {why}; nothing was dialled"),
            )
        };
        if intel.offline {
            return Err(off(
                "`intel.offline: true` (this box takes intel only as a signed bundle)",
            ));
        }
        if crate::egress::is_offline() {
            return Err(off("this holger is `network: Offline`"));
        }
        if net.is_offline() {
            return Err(off(
                "this build links no intel fetcher (the `intel-refresh` feature)",
            ));
        }
        #[cfg(not(feature = "intel-refresh"))]
        {
            Err(off(
                "this build links no intel fetcher (the `intel-refresh` feature)",
            ))
        }
        #[cfg(feature = "intel-refresh")]
        {
            use holger_intel::chrono::{Datelike, Duration, Utc};
            let failed =
                |why: String| ImportError::new(&holger_errcode::IMPORT_REFRESH_FAILED, 502, why);
            let now = Utc::now();
            let specs = refresh::feed_specs(intel, net, now.year())
                .map_err(|e| failed(format!("{e:#}")))?;
            let mut raw = Vec::with_capacity(specs.len());
            for spec in specs {
                let bytes = net
                    .get(&spec.uri)
                    .map_err(|e| failed(format!("fetch {}: {e}", spec.uri)))?;
                raw.push(holger_intel::RawFeed {
                    spec,
                    bytes,
                    fetched_at: Utc::now(),
                });
            }
            let set = holger_intel::feeds::normalize(&raw, now)
                .map_err(|e| failed(format!("the feeds do not read: {e:#}")))?;
            let signer =
                refresh::Signer::open_or_mint(&Path::new(&intel.db_dir).join(refresh::SIGNER_DIR))
                    .map_err(|e| failed(format!("the refresh signer: {e:#}")))?;
            let policy = signer.policy();
            let out = self.stage()?;
            let path = out.path.clone();
            drop(out);
            let staged = StagedBundle {
                path: path.clone(),
                file: None,
            };
            holger_intel::build_bundle(
                &set,
                holger_intel::BuildOptions {
                    sequence: holger_intel::bundle::sequence_for(now),
                    created_at: now,
                    expires_at: Some(
                        now + Duration::days(i64::from(intel.block_after_days.max(1))),
                    ),
                    supplier: holger_intel::DEFAULT_SUPPLIER.into(),
                    producer: format!("holger-server intel.refresh {}", env!("CARGO_PKG_VERSION")),
                    policy: policy.clone(),
                    approvals: signer.approvals(),
                    release: signer.release(),
                    out: path.clone(),
                },
            )
            .map_err(|e| failed(format!("sign the fetched set: {e:#}")))?;
            let first = holger_intel::LocalIntelDb::open(&intel.db_dir)
                .installed()
                .ok()
                .flatten()
                .is_none();
            let r = self.import_intel_as(
                &staged.path,
                Some(&policy),
                Some(first),
                "intel-refresh",
                "local:intel.refresh",
            );
            drop(staged);
            r
        }
    }

    // ── update / airgap bundles ───────────────────────────────────────────

    /// **Import an update or airgap bundle** — in a build without the supply
    /// chain there is no intake chain to land it in, so it is refused by name.
    #[cfg(not(feature = "intake"))]
    pub fn import_bundle(
        &self,
        _bundle: &Path,
        _policy: Option<&TrustPolicy>,
        _target: &TargetSpec,
        _actor: &str,
        _source: &str,
    ) -> Result<BundleImported, ImportError> {
        Err(ImportError::new(
            &holger_errcode::IMPORT_NO_INTAKE,
            409,
            "this build carries no intake chain (build with the `supply-chain` feature); a bundle import \
             lands its artifacts only through intake, never into a raw repository's data directory",
        ))
    }

    /// **The provenance an airgap bundle carried, verified again on this side**
    /// (lane G3a), when this side's intake runs the provenance scanner. The
    /// subject's source is [`AIRGAP_SOURCE`], so a `sources` gate of that name (or
    /// the default gate) decides. Pure: nothing is written; the caller audits.
    #[cfg(all(feature = "intake", not(feature = "scanners")))]
    fn judge_carried_provenance(
        _chain: &IntakeChain,
        _v: &airgap::VerifiedArtifact,
    ) -> Option<(String, holger_provenance::ProvenanceReport)> {
        // A downstream runs no provenance scanner of its own: the carried
        // provenance was judged upstream, and that verdict is in the signed evidence.
        None
    }

    #[cfg(feature = "scanners")]
    fn judge_carried_provenance(
        chain: &IntakeChain,
        v: &airgap::VerifiedArtifact,
    ) -> Option<(String, holger_provenance::ProvenanceReport)> {
        let p = chain.scanner().and_then(|s| s.provenance())?;
        let production = chain.settings().production.clone();
        let file =
            v.id.name
                .rsplit('/')
                .next()
                .unwrap_or(&v.id.name)
                .to_string();
        let subject = crate::scanner::ScanSubject::new(&file, "raw", &v.bytes)
            .with_source(AIRGAP_SOURCE)
            .with_repo(&production);
        Some(p.report_with(&subject, &v.provenance))
    }

    /// The audit line of one carried-provenance verdict: `scanned` when it
    /// passed or warned, `policy_denied` (the refusal) when it refused.
    #[cfg(feature = "intake")]
    fn provenance_event(
        chain: &IntakeChain,
        v: &airgap::VerifiedArtifact,
        by: &str,
        report: &holger_provenance::ProvenanceReport,
        actor: &str,
        source: &str,
    ) -> AuditEvent {
        let refused = report.verdict == holger_provenance::Verdict::Block;
        AuditEvent::new(
            actor,
            AuditAction::Scan,
            chain.settings().production.clone(),
            IntakeEntry::label_of(&v.id),
            source,
            if refused { 403 } else { 200 },
            v.bytes.len() as u64,
        )
        .with_detail(format!(
            "provenance verified again on the airgap side ({}): source {by}: {}",
            holger_provenance::SCANNER_ID,
            report.detail
        ))
        .on_trail(
            if refused {
                TrailKind::PolicyDenied
            } else {
                TrailKind::Scanned
            },
            v.sha256.clone(),
        )
    }

    /// The SCANNED line's evidence for a carried-provenance verdict that did not refuse.
    #[cfg(feature = "intake")]
    fn provenance_evidence(
        v: &airgap::VerifiedArtifact,
        report: &holger_provenance::ProvenanceReport,
    ) -> crate::promote::intake::Evidence {
        use crate::promote::intake::{Evidence, EvidenceKind, EvidenceVerdict};
        let verdict = if report.verdict == holger_provenance::Verdict::Warn {
            EvidenceVerdict::Warn
        } else {
            EvidenceVerdict::Pass
        };
        Evidence::new(
            EvidenceKind::Provenance,
            format!("provenance:{}", holger_provenance::SCANNER_ID),
            verdict,
        )
        .with_reference(format!(
            "airgap side, sha256:{}: {}",
            v.sha256, report.detail
        ))
    }

    /// ★ **Install a verified scanner signature bundle** (lane HOL-NCSC): its members are
    /// signature files only (`holger-sigs-1` otherwise), the audit record is written first,
    /// then the floor moves, then the files land over the set in `<data_root>/sandbox/
    /// signatures` (`holger_intel::sigs::SigsDb`) and clamd is asked to reload.
    #[cfg(feature = "intake")]
    #[allow(clippy::too_many_arguments)]
    fn land_scanner_sigs(
        &self,
        members: &std::collections::BTreeMap<String, Vec<u8>>,
        core: &BundleCore,
        report: &update_bundle::VerifyReport,
        admissibility: Admissibility,
        grant: Option<&FirstImportGrant>,
        bytes: u64,
        rollback: &RollbackStore,
        actor: &str,
        source: &str,
    ) -> Result<BundleImported, ImportError> {
        #[cfg(not(feature = "scanners"))]
        {
            let _ = (
                members,
                core,
                report,
                admissibility,
                grant,
                bytes,
                rollback,
                actor,
                source,
            );
            Err(ImportError::new(&holger_errcode::SIGS_NOT_BUILT, 409, "scanner signature import refused: this build carries no scanners (build with `scanners`)"))
        }
        #[cfg(feature = "scanners")]
        {
            use holger_intel::sigs::SigsDb;
            let refused = |w: String| {
                ImportError::new(
                    &holger_errcode::SIGS_MEMBER_REFUSED,
                    422,
                    format!("scanner signature import refused: {w}"),
                )
            };
            let files = holger_intel::sigs::files_of(members).map_err(refused)?;
            let names: Vec<String> = files.iter().map(|f| f.rel()).collect();
            let outcome = match admissibility {
                Admissibility::New => "new",
                Admissibility::IdempotentReimport => "idempotent-reimport",
            };
            // Audit first: no record, no install, and the floor stays.
            let ev = AuditEvent::new(
                actor,
                AuditAction::Other,
                "scanner-sigs",
                format!(
                    "{}/{}/{}",
                    core.supplier, core.product, core.release_version
                ),
                source,
                200,
                bytes,
            )
            .with_detail(format!(
                "scanner-sigs-import: bundle_id={} seq={} files={} digest={} outcome={outcome}",
                core.bundle_id,
                core.sequence,
                names.join(","),
                report.candidate_digest
            ));
            self.audit.record(ev).and_then(|()| self.audit.flush()).map_err(|e| {
                ImportError::new(&holger_errcode::AIRGAP_AUDIT_FAILED, 500, format!("scanner signature import refused: its audit record could not be written ({e:?}); nothing changed"))
            })?;
            let now = chrono::Utc::now();
            rollback
                .record(
                    &core.supplier,
                    &core.product,
                    &AcceptedEntry {
                        sequence: core.sequence,
                        bundle_id: core.bundle_id.clone(),
                        core_digest: report.candidate_digest.clone(),
                        accepted_at: now,
                    },
                )
                .map_err(|e| {
                    ImportError::refused(format!(
                        "the anti-rollback floor could not be written: {e}"
                    ))
                })?;
            let layout = holger_sandbox::media::Layout::under(&self.data_root);
            let db = SigsDb::open(layout.signatures());
            let installed = db.install(&core.bundle_id, core.sequence, &core.supplier, &files, now).map_err(|e| {
                ImportError::new(&holger_errcode::SIGS_INSTALL_FAILED, 500, format!("scanner signature import: {e:#}; the floor moved, so import the same bundle again to finish it"))
            })?;
            if let Some(g) = grant {
                self.spend_grant(g, &core.bundle_id, actor, source);
            }
            let socket = layout.clamd_socket();
            let reload = if socket.exists() {
                match crate::sandbox::clamd::reload(&socket) {
                    Ok(()) => "clamd reloads its database".to_string(),
                    Err(e) => {
                        format!("clamd did not reload ({e}); it reads the set at its next start")
                    }
                }
            } else {
                "no sandboxed clamd runs here; it reads the set at its start".to_string()
            };
            log::warn!(
                "scanner signatures: {} seq {} installed {} file(s) ({}); {reload}",
                core.bundle_id,
                core.sequence,
                files.len(),
                names.join(", ")
            );
            let mut checks = report.checks.clone();
            checks.push(format!(
                "scanner-sigs: INSTALLED {} file(s) into {} ({}); the set now holds {} file(s)",
                files.len(),
                db.root().display(),
                names.join(", "),
                installed.files.len()
            ));
            checks.push(format!("scanner-sigs: {reload}"));
            Ok(BundleImported {
                outcome: outcome.to_string(),
                bundle_id: core.bundle_id.clone(),
                sequence: core.sequence,
                kind: "scanner-sigs".into(),
                artifacts: Vec::new(),
                receipt: db.installed_path().display().to_string(),
                checks,
                sync_ack: None,
                sync_held: Vec::new(),
            })
        }
    }

    /// ★ **Install a verified Sigstore trust root** (lane HOL-OCI-EVIDENCE): the bundle (signed by
    /// the identities `promotion.intake.trust_policy` pins) must carry a trust root that reads;
    /// the audit record is written first, then the floor moves, then it is packed where the
    /// floor's `rules.trust_root` names (else `<data_root>/holger-trust/`), and the provenance
    /// scanner and the pull-through re-read it at once.
    #[cfg(feature = "intake")]
    #[allow(clippy::too_many_arguments)]
    fn land_trust_root(
        &self,
        chain: &Arc<IntakeChain>,
        members: &std::collections::BTreeMap<String, Vec<u8>>,
        core: &BundleCore,
        report: &update_bundle::VerifyReport,
        admissibility: Admissibility,
        grant: Option<&FirstImportGrant>,
        bytes: u64,
        rollback: &RollbackStore,
        actor: &str,
        source: &str,
    ) -> Result<BundleImported, ImportError> {
        let root_rel = format!(
            "{}{}",
            update_bundle::schema::PAYLOAD_PREFIX,
            holger_provenance::trust::TRUSTED_ROOT_REL
        );
        let root = members.get(&root_rel).ok_or_else(|| {
            ImportError::refused(format!("the trust bundle carries no {root_rel}"))
        })?;
        holger_provenance::TrustRoot::from_trusted_root_json(root).map_err(|e| {
            ImportError::new(
                &holger_errcode::TRUST_ROOT_SEED_REFUSED,
                422,
                format!("trust root import refused: {}", e.tagged()),
            )
        })?;
        let outcome = match admissibility {
            Admissibility::New => "new",
            Admissibility::IdempotentReimport => "idempotent-reimport",
        };
        let root_sha = crate::attest::ArtifactDigest::of_sha256(root)
            .hex()
            .to_string();
        // Audit first: no record, no install, and the floor stays.
        let ev = AuditEvent::new(actor, AuditAction::Other, "sigstore-trust", format!("{}/{}/{}", core.supplier, core.product, core.release_version), source, 200, bytes).with_detail(format!(
            "sigstore-trust-import: bundle_id={} seq={} trusted_root=sha256:{root_sha} digest={} outcome={outcome}",
            core.bundle_id, core.sequence, report.candidate_digest
        ));
        self.audit.record(ev).and_then(|()| self.audit.flush()).map_err(|e| {
            ImportError::new(&holger_errcode::AIRGAP_AUDIT_FAILED, 500, format!("trust root import refused: its audit record could not be written ({e:?}); nothing changed"))
        })?;
        let now = chrono::Utc::now();
        rollback
            .record(
                &core.supplier,
                &core.product,
                &AcceptedEntry {
                    sequence: core.sequence,
                    bundle_id: core.bundle_id.clone(),
                    core_digest: report.candidate_digest.clone(),
                    accepted_at: now,
                },
            )
            .map_err(|e| {
                ImportError::refused(format!("the anti-rollback floor could not be written: {e}"))
            })?;
        // The configured trust root, else the data root's bundle; without scanners there is
        // no configuration to read (and clippy refuses an `unwrap_or_else` on a known None).
        #[cfg(feature = "scanners")]
        let target: PathBuf = chain
            .scanner()
            .and_then(|p| p.provenance())
            .and_then(|p| p.trust_root_path())
            .map(PathBuf::from)
            .unwrap_or_else(|| crate::sigstore_trust::bundle_path(&self.data_root));
        #[cfg(not(feature = "scanners"))]
        let target: PathBuf = crate::sigstore_trust::bundle_path(&self.data_root);
        let installed = crate::sigstore_trust::install(&self.data_root, &target, members, core)
            .map_err(|e| ImportError::new(&holger_errcode::TRUST_ROOT_SEED_REFUSED, 500, format!("trust root import: {e}; the floor moved, so import the same bundle again to finish it")))?;
        if let Some(g) = grant {
            self.spend_grant(g, &core.bundle_id, actor, source);
        }
        let read = chain
            .reload_trust()
            .map_err(|e| ImportError::new(&holger_errcode::TRUST_ROOT_RELOAD_FAILED, 500, format!("trust root import: installed at {}, and the scanners could not read it: {e}", target.display())))?
            .unwrap_or_else(|| "no scanner here reads a trust root; it is read at the next start".into());
        log::warn!("sigstore trust root: {} seq {} installed at {} (trusted_root sha256:{root_sha}); {read}", core.bundle_id, core.sequence, target.display());
        let mut checks = report.checks.clone();
        checks.push(format!(
            "sigstore-trust: INSTALLED seq {} at {} (trusted_root sha256:{root_sha})",
            installed.sequence,
            target.display()
        ));
        checks.push(format!("sigstore-trust: {read}"));
        Ok(BundleImported {
            outcome: outcome.to_string(),
            bundle_id: core.bundle_id.clone(),
            sequence: core.sequence,
            kind: "sigstore-trust".into(),
            artifacts: Vec::new(),
            receipt: crate::sigstore_trust::installed_path(&self.data_root)
                .display()
                .to_string(),
            checks,
            sync_ack: None,
            sync_held: Vec::new(),
        })
    }

    /// **Import an update or airgap bundle** and land every artifact AVAILABLE
    /// through the intake chain. `policy` overrides `promotion.intake.trust_policy`. A
    /// refusal leaves its audit row (HOL-NCSC).
    #[cfg(feature = "intake")]
    pub fn import_bundle(
        &self,
        bundle: &Path,
        policy: Option<&TrustPolicy>,
        target: &TargetSpec,
        actor: &str,
        source: &str,
    ) -> Result<BundleImported, ImportError> {
        let r = self.import_bundle_unaudited(bundle, policy, target, actor, source);
        if let Err(e) = &r {
            self.audit_refusal("bundle import", bundle, e, actor, source);
        }
        r
    }

    /// ★ **A bundle published into the bundles door** (lane HOL-BUNDLES-DOOR): verified in a
    /// `verify-bundle` job against the door's trust policy (`promotion.intake.bundles_trust_policy`,
    /// else `trust_policy`): the release signature, the approvals, every member, its expiry at
    /// the time floor. Nothing lands and no floor moves: the bundle is stored as it is for the
    /// downstreams that mirror the door, which import it under their own policy. Every refusal
    /// is `holger-bundles-1` with the reason, and audited; an accepted one is audited too.
    #[cfg(feature = "intake")]
    pub fn verify_published(
        &self,
        bundle: &Path,
        actor: &str,
        source: &str,
    ) -> Result<String, ImportError> {
        let refused = |why: String| ImportError::new(&holger_errcode::BUNDLES_REFUSED, 422, why);
        let r = (|| {
            let path = self
                .intake
                .as_ref()
                .and_then(|c| c.bundles_trust_policy())
                .map(PathBuf::from);
            let policy = load_policy(path.as_deref(), "`promotion.intake.bundles_trust_policy`")
                .map_err(|e| refused(e.tagged()))?;
            policy
                .expired_posture()
                .map_err(|e| refused(e.to_string()))?;
            let target = TargetSpec::default();
            let (members, _manifest, _bytes) = self
                .verify_in_job(bundle, &policy, &target, actor, source)
                .map_err(|e| refused(e.tagged()))?;
            let core = BundleCore::from_json_bytes(
                members
                    .get(CORE_JSON)
                    .ok_or_else(|| refused("the bundle has no core.json".into()))?,
            )
            .map_err(|e| refused(format!("core.json: {e}")))?;
            verify_member_map_with_time(&members, &policy, &target, None, &self.time()).map_err(
                |e| refused(format!("bundle verification failed: {}", e.code().tag(&e))),
            )?;
            Ok(format!(
                "{} {} {} sequence {} ({})",
                core.supplier, core.product, core.release_version, core.sequence, core.bundle_id
            ))
        })();
        match &r {
            Err(e) => self.audit_refusal("bundle publish", bundle, e, actor, source),
            Ok(what) => {
                let name = bundle
                    .file_name()
                    .map(|n| n.to_string_lossy().into_owned())
                    .unwrap_or_default();
                let ev = AuditEvent::new(actor, AuditAction::Other, "", name, source, 201, 0)
                    .with_detail(format!("bundle published: verified {what}"));
                if let Err(e) = self.audit.record(ev).and_then(|()| self.audit.flush()) {
                    return Err(ImportError::new(
                        &holger_errcode::AIRGAP_AUDIT_FAILED,
                        409,
                        format!(
                            "bundle publish refused: its audit row could not be written: {e:?}"
                        ),
                    ));
                }
            }
        }
        r
    }

    #[cfg(feature = "intake")]
    fn import_bundle_unaudited(
        &self,
        bundle: &Path,
        policy: Option<&TrustPolicy>,
        target: &TargetSpec,
        actor: &str,
        source: &str,
    ) -> Result<BundleImported, ImportError> {
        let Some(chain) = self.intake.clone() else {
            return Err(ImportError::new(
                &holger_errcode::IMPORT_NO_INTAKE,
                409,
                "this server declares no intake chain (`promotion.intake`); a bundle import lands its \
                 artifacts only through intake, never into a raw repository's data directory",
            ));
        };
        let loaded;
        let policy = match policy {
            Some(p) => p,
            None => {
                loaded = load_policy(
                    self.bundle_policy.as_deref(),
                    "`promotion.intake.trust_policy`",
                )?;
                &loaded
            }
        };
        policy
            .expired_posture()
            .map_err(|e| ImportError::new(e.code(), 422, e.to_string()))?;
        // Lane HOL-SANDBOX-TRAIT: the bundle is verified in a `verify-bundle` job (the
        // release signature before any payload is decompressed, every member, an
        // export's evidence attestations), never opened here. What comes back is the
        // job's verified files, checked against its signed result; the bytes that
        // verified are the bytes kept.
        let (members, manifest, bundle_bytes) =
            self.verify_in_job(bundle, policy, target, actor, source)?;
        let _one = self.serial.lock_or_recover();
        let core = BundleCore::from_json_bytes(
            members
                .get(CORE_JSON)
                .ok_or_else(|| ImportError::refused("bundle has no core.json"))?,
        )
        .map_err(|e| ImportError::refused(format!("core.json: {e}")))?;
        // HOL-NCSC: a signed time token is carried in like any bundle; it raises the time
        // floor and lands nothing (no anti-rollback floor, so no first-import grant).
        if core.product == crate::time_floor::TIME_TOKEN_PRODUCT {
            return self.accept_time_token(&members, &core, policy, actor, source);
        }
        let rollback = RollbackStore::open(self.data_root.join(UPDATE_STATE_DIR));
        let floor = rollback.floor(&core.supplier, &core.product).map_err(|e| {
            ImportError::refused(format!("the anti-rollback floor could not be read: {e}"))
        })?;
        let state = floor
            .accepted()
            .cloned()
            .unwrap_or_else(AcceptedState::empty);
        let report =
            verify_member_map_with_time(&members, policy, target, Some(&state), &self.time())
                .map_err(|e| {
                    ImportError::refused(format!(
                        "bundle verification failed: {}",
                        e.code().tag(&e)
                    ))
                })?;
        // B3 first: an import that cannot be audited is refused before anything else is judged.
        if self.audit.mode() == AuditMode::Off {
            return Err(ImportError::new(
                &holger_errcode::AIRGAP_AUDIT_FAILED,
                409,
                "bundle import refused: the audit log is off or could not be opened, and an import that \
                 leaves no audit record is not allowed",
            ));
        }
        // NCSC gap 2: an absent floor is established only with an admin's grant for exactly
        // this vendor, product and sequence.
        let grant = match floor.accepted() {
            Some(_) => None,
            None => Some(self.first_import_grant(&core.supplier, &core.product, core.sequence)?),
        };
        let first_import = grant.is_some();
        let admissibility = floor
            .check_admissible(&core, &report.candidate_digest, first_import)
            .map_err(|e| ImportError::refused(e.code().tag(&e).to_string()))?;
        // HOL-NCSC: a scanner signature bundle installs its files, it lands no artifact.
        if core.product == SCANNER_SIGS_PRODUCT {
            return self.land_scanner_sigs(
                &members,
                &core,
                &report,
                admissibility,
                grant.as_ref(),
                manifest.bundle_bytes,
                &rollback,
                actor,
                source,
            );
        }
        // HOL-OCI-EVIDENCE: a Sigstore trust root installs over the box's, and is read at once.
        if core.product == crate::sigstore_trust::TRUST_PRODUCT {
            return self.land_trust_root(
                &chain,
                &members,
                &core,
                &report,
                admissibility,
                grant.as_ref(),
                manifest.bundle_bytes,
                &rollback,
                actor,
                source,
            );
        }

        // Everything the landing needs is verified now, before anything is written.
        let export = core.source_provenance.source_kind == airgap::SOURCE_KIND;
        let verified = if export {
            let payloads: Vec<airgap::CorePayload> = core
                .payloads
                .iter()
                .map(|p| airgap::CorePayload {
                    path: p.relative_path.clone(),
                    sha256: p.sha256.clone(),
                    blake3: p.blake3.clone(),
                    size: p.size_bytes,
                })
                .collect();
            let (_, verified) = airgap::verify_import(&members, &payloads, self.attest.as_deref())
                .map_err(|e| ImportError {
                    code: e.code(),
                    status: 422,
                    message: format!("bundle import refused: {e}"),
                })?;
            verified
        } else {
            Vec::new()
        };

        // H4: a sync manifest the bundle carries is verified NOW, before anything
        // lands (an untrusted signer or a rollback refuses the whole import), and
        // applied after the landing. A Holger that trusts no upstream ignores it.
        #[cfg(feature = "sync")]
        let sync_doc: Option<(Arc<crate::sync::SyncHub>, Vec<u8>)> = match (
            export,
            members.get(&format!("evidence/{}", holger_sync::BUNDLE_MANIFEST)),
            chain.sync().filter(|h| h.is_downstream()),
        ) {
            (true, Some(bytes), Some(hub)) => {
                hub.check_manifest(bytes).map_err(|e| ImportError {
                    code: e.code,
                    status: e.status,
                    message: format!("bundle import refused: its sync manifest: {}", e.message),
                })?;
                Some((hub, bytes.clone()))
            }
            (true, Some(_), None) => {
                log::info!("bundle import: the bundle carries a sync manifest; this Holger trusts no upstream (`sync.upstreams`), so it is not applied");
                None
            }
            _ => None,
        };

        // B3: no audit record, no import.
        if self.audit.mode() == AuditMode::Off {
            return Err(ImportError::new(
                &holger_errcode::AIRGAP_AUDIT_FAILED,
                409,
                "bundle import refused: the audit log is off or could not be opened, and an import that \
                 leaves no audit record is not allowed",
            ));
        }
        // Lane G3a: the provenance that came with the bytes is verified AGAIN
        // here, offline, under this side's own policy and trust root, for every
        // artifact BEFORE the import writes anything. A refusal writes only
        // itself: no hash_verified, no airgap_imported, nothing lands.
        let mut judged = Vec::new();
        for v in &verified {
            let Some((by, report)) = Self::judge_carried_provenance(&chain, v) else {
                judged.push(None);
                continue;
            };
            if report.verdict == holger_provenance::Verdict::Block {
                let ev = Self::provenance_event(&chain, v, &by, &report, actor, source);
                self.audit
                    .record(ev)
                    .and_then(|()| self.audit.flush())
                    .map_err(|e| {
                        ImportError::new(
                            &holger_errcode::AIRGAP_AUDIT_FAILED,
                            500,
                            format!(
                                "bundle import refused: its refusal could not be audited ({e:?})"
                            ),
                        )
                    })?;
                let code = report
                    .code
                    .as_deref()
                    .and_then(holger_errcode::by_code)
                    .unwrap_or(&holger_errcode::PROVENANCE_MISSING);
                return Err(ImportError::new(
                    code,
                    422,
                    format!(
                        "bundle import refused: '{}': {}",
                        IntakeEntry::label_of(&v.id),
                        report.detail
                    ),
                ));
            }
            judged.push(Some((by, report)));
        }
        let outcome = match admissibility {
            Admissibility::IdempotentReimport => "idempotent-reimport",
            Admissibility::New => "new",
        };
        let bundle_sha = manifest.bundle_sha256.clone();
        let production = chain.settings().production.clone();
        let mut events = vec![AuditEvent::new(
            actor,
            AuditAction::Other,
            production.clone(),
            format!(
                "{}/{}/{}",
                core.supplier, core.product, core.release_version
            ),
            source,
            200,
            manifest.bundle_bytes,
        )
        .with_detail(format!(
            "bundle-import: bundle_id={} seq={} digest={} outcome={outcome} into the intake chain",
            core.bundle_id, core.sequence, report.candidate_digest
        ))
        .on_trail(TrailKind::AirgapImported, bundle_sha.clone())];
        if !export {
            events.push(
                AuditEvent::new(
                    actor,
                    AuditAction::Other,
                    production.clone(),
                    bundle_label(&core),
                    source,
                    200,
                    manifest.bundle_bytes,
                )
                .with_detail(format!(
                    "bundle-import: hash verified: bundle {} matches its release-signed manifest",
                    core.bundle_id
                ))
                .on_trail(TrailKind::HashVerified, bundle_sha.clone()),
            );
        }
        for p in &core.payloads {
            let sha = p.sha256.trim().to_ascii_lowercase();
            let label = format!("{}/{}", core.bundle_id, p.relative_path);
            events.push(
                AuditEvent::new(actor, AuditAction::Other, production.clone(), label.clone(), source, 200, p.size_bytes)
                    .with_detail(format!("bundle-import: hash verified: payload {} matches the signed manifest of bundle {}", p.relative_path, core.bundle_id))
                    .on_trail(TrailKind::HashVerified, sha.clone()),
            );
            events.push(
                AuditEvent::new(
                    actor,
                    AuditAction::Other,
                    production.clone(),
                    label,
                    source,
                    200,
                    p.size_bytes,
                )
                .with_detail(format!(
                    "bundle-import: imported on the airgap side from bundle {} seq {} ({outcome})",
                    core.bundle_id, core.sequence
                ))
                .on_trail(TrailKind::AirgapImported, sha),
            );
        }
        events
            .into_iter()
            .try_for_each(|ev| self.audit.record(ev))
            .and_then(|()| self.audit.flush())
            .map_err(|e| {
                ImportError::new(
                    &holger_errcode::AIRGAP_AUDIT_FAILED,
                    500,
                    format!("bundle import refused: its audit record could not be written ({e:?}); nothing landed and the floor did not move"),
                )
            })?;

        // Land through the intake chain, each artifact audited first.
        let mut landed = Vec::new();
        let mut land = |imp: &ImportedArtifact<'_>| -> Result<(), ImportError> {
            let label = crate::promote::intake::IntakeEntry::label_of(&imp.id);
            let sha = sha256_hex(imp.bytes);
            let row = match chain.land_imported(imp).map_err(|e| ImportError {
                code: e.code(),
                status: e.status(),
                message: format!("bundle import refused: {e}"),
            })? {
                LandOutcome::Landed(e) => LandedArtifact {
                    artifact: label,
                    sha256: e.sha256,
                    state: e.to.name().into(),
                    repository: e.repo,
                    outcome: "landed".into(),
                },
                LandOutcome::AlreadyAvailable => LandedArtifact {
                    artifact: label,
                    sha256: sha,
                    state: "AVAILABLE".into(),
                    repository: production.clone(),
                    outcome: "already-available".into(),
                },
            };
            landed.push(row);
            Ok(())
        };
        if export {
            for (v, judged) in verified.iter().zip(judged) {
                // The carried provenance's (passing) verdict: on the trail, then
                // on the SCANNED line.
                let mut local_evidence = Vec::new();
                if let Some((by, report)) = judged {
                    self.audit
                        .record(Self::provenance_event(&chain, v, &by, &report, actor, source))
                        .map_err(|e| ImportError::new(&holger_errcode::AIRGAP_AUDIT_FAILED, 500, format!("bundle import refused: the provenance check's audit record could not be written ({e:?})")))?;
                    local_evidence.push(Self::provenance_evidence(v, &report));
                }
                // Keep the verified attestations on this side too, so an Auditor
                // reads them here. They are NOT re-recorded as a scan.
                if let Some(store) = &self.attest {
                    let digest = crate::attest::ArtifactDigest::of_sha256(&v.bytes);
                    for env in &v.envelopes {
                        store
                            .accept(env, std::slice::from_ref(&v.signer_cert), Some(&digest))
                            .map_err(|e| {
                                ImportError::new(
                                    &holger_errcode::AIRGAP_ATTESTATION_INVALID,
                                    422,
                                    format!("bundle import refused: {}", e.tagged()),
                                )
                            })?;
                    }
                }
                land(&ImportedArtifact {
                    local_evidence,
                    id: v.id.clone(),
                    bytes: &v.bytes,
                    actor: actor.to_string(),
                    bundle_id: core.bundle_id.clone(),
                    candidate_digest: report.candidate_digest.clone(),
                    release_signer: report.release_signer_fingerprint.clone(),
                    online_history: v.online_history.clone(),
                    verified: "bundle release signature, per-artifact sha256/blake3/size, evidence attestations".into(),
                    index_row: v.index_row.clone(),
                })?;
            }
        } else {
            land(&ImportedArtifact {
                id: traits::ArtifactId {
                    namespace: None,
                    name: bundle_label(&core),
                    version: String::new(),
                },
                bytes: bundle_bytes.as_deref().ok_or_else(|| {
                    ImportError::refused(
                        "the verify-bundle job handed back no bundle bytes for an update bundle",
                    )
                })?,
                actor: actor.to_string(),
                bundle_id: core.bundle_id.clone(),
                candidate_digest: report.candidate_digest.clone(),
                release_signer: report.release_signer_fingerprint.clone(),
                online_history: Vec::<IntakeEntry>::new(),
                verified:
                    "bundle release signature, every member digest against the signed manifest"
                        .into(),
                local_evidence: Vec::new(),
                index_row: None,
            })?;
        }

        // The floor moves only after everything landed.
        rollback
            .record(
                &core.supplier,
                &core.product,
                &AcceptedEntry {
                    sequence: core.sequence,
                    bundle_id: core.bundle_id.clone(),
                    core_digest: report.candidate_digest.clone(),
                    accepted_at: estate_now(),
                },
            )
            .map_err(|e| {
                ImportError::refused(format!(
                    "the anti-rollback floor could not be recorded: {e}"
                ))
            })?;
        if let Some(g) = &grant {
            self.spend_grant(g, &core.bundle_id, actor, source);
        }
        let receipt = ImportReceipt {
            bundle_id: core.bundle_id.clone(),
            sequence: core.sequence,
            supplier: core.supplier.clone(),
            product: core.product.clone(),
            release_version: core.release_version.clone(),
            candidate_digest: report.candidate_digest.clone(),
            release_signer_fingerprint: report.release_signer_fingerprint.clone(),
            imported_at: estate_now(),
            outcome: outcome.to_string(),
            bundle_sha256: bundle_sha,
        };
        let receipts = self.data_root.join(RECEIPTS_DIR);
        let _ = std::fs::create_dir_all(&receipts);
        let receipt_path = receipts.join(format!("{}.json", sanitize(&core.bundle_id)));
        let body = serde_json::to_vec_pretty(&receipt)
            .map_err(|e| ImportError::refused(format!("receipt: {e}")))?;
        atomic_write(&receipt_path, &body)
            .map_err(|e| ImportError::refused(format!("receipt: {e}")))?;
        #[cfg(feature = "sync")]
        let (sync_ack, sync_held) = match sync_doc {
            Some((hub, bytes)) => {
                let a = hub
                    .apply(&bytes, &format!("bundle {} seq {}", core.bundle_id, core.sequence), actor, source)
                    .map_err(|e| ImportError { code: e.code, status: e.status, message: format!("bundle import: the artifacts landed, and its sync manifest was not applied: {}", e.message) })?;
                #[cfg(feature = "fetch")]
                let ack = Some(a.ack_path.display().to_string());
                // A one-way Holger signs no receipt: nothing goes back.
                #[cfg(not(feature = "fetch"))]
                let ack = None;
                (ack, a.quarantined)
            }
            None => (None, Vec::new()),
        };
        #[cfg(not(feature = "sync"))]
        let (sync_ack, sync_held): (Option<String>, Vec<String>) = (None, Vec::new());
        // HOL-NCSC: the accepted bundle's signed time raises the time floor.
        self.raise_floor(
            core.created_at.timestamp_millis(),
            &format!("the bundle {} seq {}", core.bundle_id, core.sequence),
        );
        Ok(BundleImported {
            outcome: outcome.into(),
            bundle_id: core.bundle_id.clone(),
            sequence: core.sequence,
            kind: if export {
                "airgap-export"
            } else {
                "update-bundle"
            }
            .into(),
            artifacts: landed,
            receipt: receipt_path.display().to_string(),
            checks: report.checks.clone(),
            sync_ack,
            sync_held,
        })
    }

    /// ★ **Verify `bundle` in a `verify-bundle` job** (lane HOL-SANDBOX-TRAIT): link it
    /// into the job's input (no open), with this side's trust policy and attestation
    /// trust beside it; run the job through the server's ONE runner; believe only the
    /// signed result ([`crate::job_runner::Jobs::run`]); read back its verified
    /// files. A refusal (the job's, or of its result) is audited with its reason and
    /// answered by name.
    #[cfg(feature = "intake")]
    #[allow(clippy::type_complexity)]
    fn verify_in_job(
        &self,
        bundle: &Path,
        policy: &TrustPolicy,
        target: &TargetSpec,
        actor: &str,
        source: &str,
    ) -> Result<
        (
            update_bundle::MemberMap,
            crate::job_runner::verify_bundle::Manifest,
            Option<Vec<u8>>,
        ),
        ImportError,
    > {
        use crate::job_runner::heimdall_core::{Job, JobId, Limits, Nonce, Policy, Verdict};
        use crate::job_runner::{
            holger_job_runner as runner, verify_bundle as vb, JobDirs, JOBS_DIR,
        };
        let Some(jobs) = self.jobs.clone() else {
            return Err(ImportError::new(
                &holger_errcode::SANDBOX_JOB_RUN_FAILED,
                503,
                "this server has no job runner, and a bundle is verified only in a job",
            ));
        };
        let file = bundle
            .file_name()
            .map(|n| n.to_string_lossy().into_owned())
            .unwrap_or_default();
        let failed = |code: &'static ErrCode, status: u16, message: String, job: &str| {
            let ev = AuditEvent::new(
                actor,
                AuditAction::Other,
                "imports",
                file.clone(),
                source,
                status,
                0,
            )
            .with_detail(format!(
                "bundle-import refused by the verify-bundle job {job} (runner {}): {message}",
                jobs.features().runner
            ));
            if let Err(e) = self.audit.record(ev).and_then(|()| self.audit.flush()) {
                log::warn!("bundle import: the refusal of {file} could not be audited: {e:?}");
            }
            ImportError::new(code, status, message)
        };
        let id = JobId::random();
        let dirs = JobDirs::new(&self.data_root.join(JOBS_DIR), &id)
            .map_err(|e| staging(format!("the job's directories: {e}")))?;
        crate::job_runner::link_input(bundle, &dirs.input, vb::BUNDLE_FILE)
            .map_err(|e| staging(format!("put {} into the job: {e}", bundle.display())))?;
        let policy_json =
            serde_json::to_vec(policy).map_err(|e| staging(format!("the trust policy: {e}")))?;
        std::fs::write(dirs.input.join(vb::POLICY_FILE), policy_json)
            .map_err(|e| staging(format!("the job's trust policy: {e}")))?;
        if let Some(a) = &self.attest {
            let doc = serde_json::to_vec(&a.trust_doc())
                .map_err(|e| staging(format!("the attestation trust: {e}")))?;
            std::fs::write(dirs.input.join(vb::ATTEST_FILE), doc)
                .map_err(|e| staging(format!("the job's attestation trust: {e}")))?;
        }
        // Lane HOL-HEIMDALL-04: with Heimdall's released guests laid and pinned, the job verifies
        // the bundle in the split wasm guest (the guests a read path, their root in the input).
        let mut params = vb::Params::at(target, &self.time());
        let mut read = Vec::new();
        match jobs.guests() {
            Some(Ok(g)) => {
                std::fs::write(dirs.input.join(vb::GUEST_ROOT_FILE), &g.root_der)
                    .map_err(|e| staging(format!("the guests' trust root: {e}")))?;
                params.guest = Some(vb::GuestParam { read: read.len() });
                read.push(g.dir.clone());
            }
            Some(Err(why)) => {
                return Err(failed(
                    &holger_errcode::SANDBOX_JOB_RUN_FAILED,
                    503,
                    format!("the verify-bundle guests are not the pinned ones, so no bundle is verified: {why}"),
                    &id.to_hex(),
                ))
            }
            None => {}
        }
        let params = serde_json::to_value(params).map_err(|e| staging(e.to_string()))?;
        let job = Job {
            id,
            nonce: Nonce::random(),
            kind: runner::worker(&[vb::PROGRAM]),
            input_dir: dirs.input.clone(),
            output_dir: dirs.output.clone(),
            params,
            policy: Policy {
                read,
                limits: Limits {
                    wall: std::time::Duration::from_secs(600),
                    memory_bytes: 4 << 30,
                    cpus: 1.0,
                    pids: 64,
                    ..Default::default()
                },
                ..Default::default()
            },
        };
        let result = jobs.run(job).map_err(|f| {
            failed(
                f.code,
                if f.code == &holger_errcode::SANDBOX_JOB_RUN_FAILED {
                    503
                } else {
                    422
                },
                f.message,
                &id.to_hex(),
            )
        })?;
        if let Verdict::Refused { code, reason } = &result.verdict {
            let c = holger_errcode::by_code(code).unwrap_or(&holger_errcode::IMPORT_REFUSED);
            return Err(failed(c, 422, reason.clone(), &id.to_hex()));
        }
        vb::read_outputs(&dirs.output).map_err(|e| {
            failed(
                &holger_errcode::SANDBOX_JOB_ANSWER_INVALID,
                422,
                format!("the verify-bundle job's outputs: {e}"),
                &id.to_hex(),
            )
        })
    }

    /// **The verified `core.json` of `bundle` under `policy`**, from a verify-bundle job
    /// (`holger bundle allow-first-import --from-bundle`: a field of a bundle is believed
    /// only once it verified, and the bundle is opened only in the job).
    #[cfg(feature = "intake")]
    /// A server with no intake chain has no job runner: there (an intel-only box, whose
    /// intel import is not yet a job) the signature is checked here, as before.
    pub fn verified_core(
        &self,
        bundle: &Path,
        policy: &TrustPolicy,
        actor: &str,
        source: &str,
    ) -> Result<BundleCore, ImportError> {
        let members = match &self.jobs {
            Some(_) => {
                self.verify_in_job(bundle, policy, &TargetSpec::default(), actor, source)?
                    .0
            }
            None => load_bundle_verified(bundle, policy).map_err(|e| {
                ImportError::refused(format!("bundle verification failed: {}", e.code().tag(&e)))
            })?,
        };
        BundleCore::from_json_bytes(
            members
                .get(CORE_JSON)
                .ok_or_else(|| ImportError::refused("bundle has no core.json"))?,
        )
        .map_err(|e| ImportError::refused(format!("core.json: {e}")))
    }

    // ── the drop directories ──────────────────────────────────────────────

    /// The files waiting in `kind`'s inbox, oldest name first.
    pub fn pending(&self, kind: ImportKind) -> Vec<PathBuf> {
        let mut out: Vec<PathBuf> = std::fs::read_dir(self.inbox(kind))
            .into_iter()
            .flatten()
            .flatten()
            .map(|e| e.path())
            .filter(|p| p.is_file())
            .filter(|p| {
                let name = p
                    .file_name()
                    .map(|n| n.to_string_lossy().into_owned())
                    .unwrap_or_default();
                !name.starts_with('.') && !name.ends_with(".part")
            })
            .collect();
        out.sort();
        out
    }

    /// **Sweep `kind`'s inbox**: import every waiting file, then move it to
    /// `archive/` or `refused/` (with its `.why.txt`). The jera job runs this.
    pub fn sweep(&self, kind: ImportKind) -> anyhow::Result<InboxSweep> {
        let inbox = self.inbox(kind);
        let mut sweep = InboxSweep::default();
        for file in self.pending(kind) {
            let name = file
                .file_name()
                .map(|n| n.to_string_lossy().into_owned())
                .unwrap_or_default();
            let stamp = crate::audit::now_nanos() / 1_000_000_000;
            let result = if name == FIRST_IMPORT_MARKER {
                let e = first_import_flag_refusal("the inbox's `FIRST-IMPORT` marker");
                self.audit_refusal(
                    &format!("{} inbox", kind.as_str()),
                    &file,
                    &e,
                    "inbox",
                    "local:inbox",
                );
                Err(e)
            } else {
                self.import(
                    kind,
                    &file,
                    &format!("{}-inbox", kind.as_str()),
                    "local:inbox",
                )
            };
            match result {
                Ok(line) => {
                    log::info!("{}: {name}: {line}", kind.inbox_name());
                    move_into(&file, &inbox.join(ARCHIVE_DIR), &format!("{stamp}-{name}"))?;
                    sweep.imported.push(name);
                }
                Err(e) => {
                    let why = e.tagged();
                    log::warn!("{}: {name} REFUSED: {why}", kind.inbox_name());
                    let dest =
                        move_into(&file, &inbox.join(REFUSED_DIR), &format!("{stamp}-{name}"))?;
                    let _ = std::fs::write(dest.with_extension("why.txt"), format!("{why}\n"));
                    sweep.refused.push((name, why));
                }
            }
        }
        Ok(sweep)
    }
}

/// Move `file` into `dir` as `name`.
fn move_into(file: &Path, dir: &Path, name: &str) -> anyhow::Result<PathBuf> {
    std::fs::create_dir_all(dir)?;
    let dest = dir.join(name);
    std::fs::rename(file, &dest)
        .map_err(|e| anyhow::anyhow!("move {} to {}: {e}", file.display(), dest.display()))?;
    Ok(dest)
}

fn grant_refused(status: u16, why: String) -> ImportError {
    ImportError::new(&holger_errcode::IMPORT_FIRST_GRANT_REFUSED, status, why)
}

fn staging(why: String) -> ImportError {
    ImportError::new(&holger_errcode::IMPORT_STAGING_FAILED, 500, why)
}

fn load_policy(path: Option<&Path>, setting: &str) -> Result<TrustPolicy, ImportError> {
    let path = path.ok_or_else(|| {
        ImportError::new(
            &holger_errcode::IMPORT_NO_POLICY,
            409,
            format!("no trust policy: set {setting} (or pass --policy to the CLI)"),
        )
    })?;
    let text = std::fs::read_to_string(path).map_err(|e| {
        ImportError::new(
            &holger_errcode::IMPORT_NO_POLICY,
            409,
            format!("trust policy {}: {e}", path.display()),
        )
    })?;
    TrustPolicy::from_ron(&text).map_err(|e| {
        ImportError::new(
            e.code(),
            409,
            format!("trust policy {}: {e}", path.display()),
        )
    })
}

/// Where an update bundle that is not an airgap export lands in production:
/// `<supplier>/<product>/<release>.holger.znippy`.
pub fn bundle_label(core: &BundleCore) -> String {
    format!(
        "{}/{}/{}.holger.znippy",
        sanitize(&core.supplier),
        sanitize(&core.product),
        sanitize(&core.release_version)
    )
}

fn sanitize(seg: &str) -> String {
    let s: String = seg
        .chars()
        .map(|c| {
            if c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-') {
                c
            } else {
                '_'
            }
        })
        .collect();
    if s.is_empty() || s == "." || s == ".." {
        "_".into()
    } else {
        s
    }
}

/// A staged bundle file, removed when dropped.
pub struct StagedBundle {
    pub path: PathBuf,
    file: Option<std::fs::File>,
}

impl StagedBundle {
    /// Append a piece of the streamed bundle.
    pub fn write(&mut self, bytes: &[u8]) -> Result<(), ImportError> {
        use std::io::Write;
        match self.file.as_mut() {
            Some(f) => f
                .write_all(bytes)
                .map_err(|e| staging(format!("write the staged bundle: {e}"))),
            None => Err(staging("the staged bundle is already closed".into())),
        }
    }

    /// Flush and close the file; the path stays until the guard drops.
    pub fn finish(&mut self) -> Result<(), ImportError> {
        if let Some(f) = self.file.take() {
            f.sync_all()
                .map_err(|e| staging(format!("flush the staged bundle: {e}")))?;
        }
        Ok(())
    }
}

impl Drop for StagedBundle {
    fn drop(&mut self) {
        self.file.take();
        let _ = std::fs::remove_file(&self.path);
    }
}

/// The refresh's feed list and its signer (`intel-refresh` only).
#[cfg(feature = "intel-refresh")]
mod refresh {
    use std::path::{Path, PathBuf};

    use anyhow::{bail, Context, Result};
    use holger_intel::feeds::{FeedKind, FeedSpec, DEFAULT_ECOSYSTEMS};
    use holger_intel::SignerFiles;
    use update_bundle::digest::sha256_hex;
    use update_bundle::policy::{
        ApprovedSigner, CertificateValidity, ExpiredPosture, TrustMode, TrustPolicy,
    };
    use update_bundle::schema::POLICY_SCHEMA;

    use crate::intel::IntelConfig;

    /// Where the box keeps its refresh signer, under `intel.db_dir`.
    pub const SIGNER_DIR: &str = "refresh-signer";
    const ROLES: [&str; 3] = ["security-reviewer", "system-owner", "release"];

    /// The feeds to fetch: the mirror's `index.txt`, or the public set.
    pub fn feed_specs(
        intel: &IntelConfig,
        net: &holger_intel::IntelNet,
        year: i32,
    ) -> Result<Vec<FeedSpec>> {
        let Some(mirror) = intel
            .feed_mirror
            .as_deref()
            .map(|m| m.trim_end_matches('/'))
        else {
            let ecosystems: Vec<String> = if intel.ecosystems.is_empty() {
                DEFAULT_ECOSYSTEMS.iter().map(|s| s.to_string()).collect()
            } else {
                intel.ecosystems.clone()
            };
            return Ok(FeedSpec::default_set(&ecosystems, year));
        };
        let index = net
            .get(&format!("{mirror}/index.txt"))
            .map_err(|e| anyhow::anyhow!("fetch {mirror}/index.txt: {e}"))?;
        let index = String::from_utf8(index).context("the mirror's index.txt is not UTF-8")?;
        let mut out = Vec::new();
        for name in index
            .lines()
            .map(str::trim)
            .filter(|l| !l.is_empty() && !l.starts_with('#'))
        {
            if name.contains('/') || name.starts_with('.') {
                bail!("the mirror's index names {name:?}, which is not a plain feed file name");
            }
            let stem = name.strip_suffix(".gz").unwrap_or(name);
            let stem = stem
                .strip_suffix(".zip")
                .or_else(|| stem.strip_suffix(".json"))
                .unwrap_or(stem);
            let kind = if let Some(eco) = stem.strip_prefix("osv-") {
                FeedKind::Osv {
                    ecosystem: eco.to_string(),
                }
            } else if let Some(n) = stem.strip_prefix("nvd-") {
                FeedKind::Nvd {
                    name: n.to_string(),
                }
            } else if stem == "kev" {
                FeedKind::Kev
            } else {
                bail!("the mirror's index names {name:?}, which is no feed (osv-<Ecosystem>, nvd-<name>, kev)");
            };
            out.push(FeedSpec {
                kind,
                uri: format!("{mirror}/{name}"),
            });
        }
        if !out.iter().any(|s| matches!(s.kind, FeedKind::Osv { .. })) {
            bail!("the mirror's index names no OSV feed, so the set would scan everything clean");
        }
        Ok(out)
    }

    /// The box's own refresh signer: three ed25519 identities on the data disk.
    pub struct Signer {
        files: Vec<(String, SignerFiles, String)>,
    }

    impl Signer {
        pub fn open_or_mint(dir: &Path) -> Result<Self> {
            std::fs::create_dir_all(dir).with_context(|| format!("mkdir {}", dir.display()))?;
            let mut files = Vec::new();
            for role in ROLES {
                let (cert, key): (PathBuf, PathBuf) = (
                    dir.join(format!("{role}.der")),
                    dir.join(format!("{role}.pk8")),
                );
                if !cert.exists() || !key.exists() {
                    let kp = rcgen::KeyPair::generate_for(&rcgen::PKCS_ED25519)
                        .context("mint an ed25519 key")?;
                    let mut params =
                        rcgen::CertificateParams::new(vec![]).context("cert params")?;
                    params.distinguished_name.push(
                        rcgen::DnType::CommonName,
                        format!("holger intel.refresh {role}"),
                    );
                    let c = params.self_signed(&kp).context("self-sign")?;
                    write_private(&key, &kp.serialize_der())?;
                    std::fs::write(&cert, c.der())
                        .with_context(|| format!("write {}", cert.display()))?;
                }
                let sf = SignerFiles::read(&cert, &key)?;
                let fp = sha256_hex(&sf.cert_der);
                files.push((role.to_string(), sf, fp));
            }
            Ok(Self { files })
        }

        fn fp(&self, role: &str) -> String {
            self.files
                .iter()
                .find(|(r, ..)| r == role)
                .map(|(_, _, f)| f.clone())
                .unwrap_or_default()
        }

        fn files(&self, role: &str) -> SignerFiles {
            let (_, f, _) = self.files.iter().find(|(r, ..)| r == role).expect("minted");
            SignerFiles {
                cert_der: f.cert_der.clone(),
                key_pkcs8: f.key_pkcs8.clone(),
            }
        }

        pub fn approvals(&self) -> Vec<(String, SignerFiles)> {
            vec![
                ("security-reviewer".into(), self.files("security-reviewer")),
                ("system-owner".into(), self.files("system-owner")),
            ]
        }

        pub fn release(&self) -> SignerFiles {
            self.files("release")
        }

        /// The policy that trusts exactly this signer, for intel only.
        pub fn policy(&self) -> TrustPolicy {
            TrustPolicy {
                schema_version: POLICY_SCHEMA.into(),
                trust_mode: Some(TrustMode::PinnedSigners),
                trusted_roots: vec![],
                certificate_validity: CertificateValidity::Enforce,
                approved_signers: vec![
                    ApprovedSigner {
                        fingerprint: self.fp("security-reviewer"),
                        subject: None,
                        roles: vec!["security-reviewer".into()],
                    },
                    ApprovedSigner {
                        fingerprint: self.fp("system-owner"),
                        subject: None,
                        roles: vec!["system-owner".into()],
                    },
                ],
                required_approval_roles: vec!["security-reviewer".into(), "system-owner".into()],
                minimum_distinct_approvers: 2,
                trusted_release_signers: vec![self.fp("release")],
                release_signer_must_be_distinct: true,
                maximum_bundle_age_secs: None,
                require_expiry: true,
                on_expired_metadata: Some(ExpiredPosture::Refuse),
                require_sbom: false,
                require_vex: false,
                require_scan: false,
                allowed_products: vec![holger_intel::INTEL_PRODUCT.into()],
                allowed_models: vec![],
            }
        }
    }

    fn write_private(path: &Path, bytes: &[u8]) -> Result<()> {
        use std::io::Write;
        let mut o = std::fs::OpenOptions::new();
        o.write(true).create_new(true);
        #[cfg(unix)]
        {
            use std::os::unix::fs::OpenOptionsExt;
            o.mode(0o600);
        }
        let mut f = o
            .open(path)
            .with_context(|| format!("create {}", path.display()))?;
        f.write_all(bytes)
            .with_context(|| format!("write {}", path.display()))
    }
}

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

    #[test]
    fn the_raw_bypass_is_refused_by_name() {
        let e = raw_bypass_refusal("bundles");
        assert!(e.tagged().starts_with("[holger-import-1]"), "{e}");
        assert!(e.tagged().contains("--repo bundles"), "{e}");
    }

    #[test]
    fn a_landing_path_segment_cannot_climb() {
        assert_eq!(sanitize("acme corp"), "acme_corp");
        assert_eq!(sanitize(".."), "_");
        assert_eq!(sanitize("../1.2"), ".._1.2");
        assert_eq!(sanitize(""), "_");
    }
}