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

nordisk/holger

server/cli/tests/promote_cli.rs

//! **`holger-server promote` end-to-end robot** — the LOCAL promotion verb, driven
//! as an operator drives it: spawn the real binary, inject args, assert the
//! observable verdict in stdout + the bytes on disk.
//!
//! `PromotionService.PromoteArtifact` had a real engine and tests behind it but NO
//! consumer in any shipped binary — an operator could not trigger a DEV→PROD
//! promotion through anything. This test is the proof that the wired CLI path
//! actually promotes, and that its two refusals are honest:
//!
//! * `promote --execute` SEALS into the destination's immutable PROD store, at the
//!   blake3 content address, with the OS user recorded as `promoted_by`.
//! * the DEFAULT (no `--execute`) is a dry run that writes **nothing** — the
//!   irreversibility guard.
//! * a policy-gate BLOCK refuses with the artifact never sealed, and the refusal
//!   carries the **typed** `holger-promote-2` code (assert the CODE, never a
//!   prose substring — the message is free to be reworded, the code is frozen).
//! * a missing source artifact refuses with the typed `holger-promote-7`.

use std::path::Path;
use std::process::Command;

/// The package's own binary, provided by cargo to integration tests.
const BIN: &str = env!("CARGO_BIN_EXE_holger");

/// A one-repo file-backed store config. `audit.dir` is ABSOLUTE so the data root
/// (its parent) — and therefore the PROD store — is deterministic under the test's
/// tempdir instead of the process CWD.
fn write_config(path: &Path, repo: &str, data_dir: &Path, audit_dir: &Path) {
    write_config_with(path, repo, data_dir, audit_dir, "")
}

/// [`write_config`] plus EXTRA RON — a `promotion:` block, which is where the
/// policy gate lives since 2026-09-20 (it used to be three environment
/// variables, which an appliance with no shell cannot set).
fn write_config_with(path: &Path, repo: &str, data_dir: &Path, audit_dir: &Path, extra: &str) {
    let ron = format!(
        r#"(
    exposed_endpoints: [ ( ron_name: "e", ron_url: "127.0.0.1:0", ron_http_url: None ) ],
    storage_endpoints: [],
    repositories: [
        ( ron_name: "{repo}", ron_repo_type: "rust", ron_data_dir: Some("{data}"), ron_base_url: Some("http://x"), ron_upstreams: [], ron_in: None, ron_out: None ),
    ],
    audit: ( dir: "{audit}", enabled: true ),
    {extra}
)
"#,
        data = data_dir.display(),
        audit = audit_dir.display(),
        extra = extra,
    );
    std::fs::write(path, ron).expect("write config");
}

/// [`write_config_with`] for a config that declares SEVERAL repositories — what a
/// ladder needs, since every rung's repository must be declared in the config
/// that owns the ladder.
fn write_config_repos(
    path: &Path,
    repos: &[(&str, std::path::PathBuf)],
    audit_dir: &Path,
    extra: &str,
) {
    let decls: String = repos
        .iter()
        .map(|(name, dir)| {
            format!(
                "        ( ron_name: \"{name}\", ron_repo_type: \"rust\", ron_data_dir: Some(\"{}\"), ron_base_url: Some(\"http://x\"), ron_upstreams: [], ron_in: None, ron_out: None ),\n",
                dir.display()
            )
        })
        .collect();
    let ron = format!(
        r#"(
    exposed_endpoints: [ ( ron_name: "e", ron_url: "127.0.0.1:0", ron_http_url: None ) ],
    storage_endpoints: [],
    repositories: [
{decls}    ],
    audit: ( dir: "{audit}", enabled: true ),
    {extra}
)
"#,
        audit = audit_dir.display(),
    );
    std::fs::write(path, ron).expect("write config");
}
/// Seed one artifact into a file-backed store through the SAME engine the server
/// uses (no hand-laid files that could drift from the backend's real layout).
fn seed(config: &Path, repo: &str, name: &str, version: &str, body: &[u8]) {
    let mut holger = server_lib::read_ron_config(config).expect("parse seed config");
    holger.instantiate_backends().expect("backends");
    server_lib::wire_holger(&mut holger).expect("wire");
    let id = server_lib::ArtifactId {
        namespace: None,
        name: name.into(),
        version: version.into(),
    };
    holger
        .put(repo, &id, body)
        .expect("seed artifact into source repo");
}

/// Run `holger-server promote …` with extra env, returning (success, merged output).
fn promote(args: &[&str], env: &[(&str, &str)]) -> (bool, String) {
    let mut cmd = Command::new(BIN);
    cmd.arg("promote").args(args);
    // Neutralise any ambient gate config from the developer's shell so each test
    // sees exactly the gates it declares.
    for k in [
        "HOLGER_PROMOTE_ENFORCE",
        "HOLGER_PROMOTE_ADVISORY_DB",
        "HOLGER_PROMOTE_DENY_LICENSES",
        "HOLGER_PROMOTE_REQUIRE_PROPERTY",
        "HOLGER_DATA",
    ] {
        cmd.env_remove(k);
    }
    for (k, v) in env {
        cmd.env(k, v);
    }
    let out = cmd.output().expect("spawn holger-server promote");
    let merged = format!(
        "{}{}",
        String::from_utf8_lossy(&out.stdout),
        String::from_utf8_lossy(&out.stderr)
    );
    (out.status.success(), merged)
}

/// The sealed-ref path the engine writes:
/// `<prod>/refs/<to_repo>/<namespace>__<name>__<version>.ref` (the engine's
/// `coord_key` shape; namespace empty here, hence the leading `__`).
fn ref_path(prod_root: &Path, to_repo: &str, name: &str, version: &str) -> std::path::PathBuf {
    prod_root
        .join("refs")
        .join(to_repo)
        .join(format!("__{name}__{version}.ref"))
}

/// A DEV store + a PROD store + a seeded artifact — the fixture every case starts from.
struct Fixture {
    _dir: tempfile::TempDir,
    dev_cfg: std::path::PathBuf,
    prod_cfg: std::path::PathBuf,
    prod_root: std::path::PathBuf,
    body: &'static [u8],
}

fn fixture() -> Fixture {
    let dir = tempfile::tempdir().expect("tempdir");
    let root = dir.path();
    let dev_cfg = root.join("dev.ron");
    let prod_cfg = root.join("prod.ron");
    write_config(
        &dev_cfg,
        "mirror",
        &root.join("dev/data"),
        &root.join("dev/audit"),
    );
    write_config(
        &prod_cfg,
        "mirror",
        &root.join("prod/data"),
        &root.join("prod/audit"),
    );
    let body: &'static [u8] = b"serde-1.0.0-bytes";
    seed(&dev_cfg, "mirror", "serde", "1.0.0", body);
    Fixture {
        prod_root: root.join("prod").join("holger-prod-store"),
        _dir: dir,
        dev_cfg,
        prod_cfg,
        body,
    }
}

/// **The wiring proof.** `promote --execute` moves the artifact DEV → PROD: the ref
/// is sealed under the destination repo, the blob lands at its blake3 content
/// address, and the reported address is that hash. RED if the verb stops sealing,
/// seals to the wrong coordinate, or garbles the bytes.
#[test]
fn promote_execute_seals_the_artifact_into_the_prod_store_at_its_content_address() {
    let f = fixture();
    let expected = blake3::hash(f.body).to_hex().to_string();

    let (ok, out) = promote(
        &[
            "--from",
            f.dev_cfg.to_str().unwrap(),
            "--to",
            f.prod_cfg.to_str().unwrap(),
            "--repo",
            "mirror",
            "--name",
            "serde",
            "--version",
            "1.0.0",
            "--execute",
        ],
        &[],
    );
    assert!(ok, "promote --execute must succeed; output:\n{out}");
    assert!(out.contains("SEALED"), "reports the seal; output:\n{out}");
    assert!(
        out.contains(&expected),
        "reports the blake3 content address {expected}; output:\n{out}"
    );

    // The ref IS the seal.
    let r = ref_path(&f.prod_root, "mirror", "serde", "1.0.0");
    assert!(r.is_file(), "sealed ref must exist at {}", r.display());
    let record = std::fs::read_to_string(&r).expect("read ref");
    assert!(
        record.contains(&expected),
        "the promotion record carries the content address: {record}"
    );

    // The bytes are stored under the content address, byte-identical.
    let blob = f.prod_root.join("blobs").join(&expected);
    assert!(
        blob.is_file(),
        "content-addressed blob must exist at {}",
        blob.display()
    );
    assert_eq!(
        std::fs::read(&blob).unwrap(),
        f.body,
        "promoted bytes must be byte-identical"
    );
}

/// **Dry-run is the DEFAULT and writes NOTHING.** A promotion is irreversible, so
/// omitting `--execute` must preview only. RED if the default ever starts sealing.
#[test]
fn promote_without_execute_is_a_dry_run_that_writes_nothing() {
    let f = fixture();

    let (ok, out) = promote(
        &[
            "--from",
            f.dev_cfg.to_str().unwrap(),
            "--to",
            f.prod_cfg.to_str().unwrap(),
            "--repo",
            "mirror",
            "--name",
            "serde",
            "--version",
            "1.0.0",
        ],
        &[],
    );
    assert!(ok, "a dry run is a success, not an error; output:\n{out}");
    assert!(
        out.contains("dry-run"),
        "says it is a dry run; output:\n{out}"
    );
    assert!(
        out.contains("would seal"),
        "previews the seal; output:\n{out}"
    );
    assert!(
        !out.contains("SEALED"),
        "must NOT claim a seal; output:\n{out}"
    );

    // The load-bearing half: nothing on disk.
    assert!(
        !ref_path(&f.prod_root, "mirror", "serde", "1.0.0").exists(),
        "dry run must not write the ref"
    );
    let blob = f
        .prod_root
        .join("blobs")
        .join(blake3::hash(f.body).to_hex().to_string());
    assert!(!blob.exists(), "dry run must not write the blob");
}

/// The OSV feed this suite blocks on: it names exactly the coordinate the
/// fixture promotes.
const OSV_FEED: &str = r#"{
  "id": "OSV-TEST-0001",
  "summary": "test advisory that must block the promotion",
  "affected": [
    {
      "package": { "ecosystem": "crates.io", "name": "serde" },
      "versions": ["1.0.0"]
    }
  ],
  "severity": [ { "type": "CVSS_V3", "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" } ]
}"#;

/// **A policy BLOCK refuses, and says so with the FROZEN code.** The destination
/// declares a gated rung and an advisory feed naming this exact name@version, so
/// the gate must refuse BEFORE anything is sealed. Asserts the typed
/// `holger-promote-2` (`PROMOTE_POLICY_BLOCKED`) — the code is the contract; the
/// prose is not.
///
/// **The gate is declared in the CONFIG, not in the environment.** It moved there
/// on 2026-09-20 (`Holger::promotion_engine`): an appliance has no shell. This
/// test was written against the environment spelling and therefore stopped
/// gating anything — measured on main `1138143`, it printed `SEALED`. The
/// environment spelling is now a refusal of its own, below.
#[test]
fn a_policy_blocked_promotion_refuses_with_the_typed_code_and_seals_nothing() {
    let dir = tempfile::tempdir().expect("tempdir");
    let root = dir.path();
    let osv = root.join("advisories.json");
    std::fs::write(&osv, OSV_FEED).expect("write advisory feed");

    // The destination's OWN policy: one gated rung, running the `osv` gate over
    // that feed. This is the shape a real deployment writes.
    let dev_cfg = root.join("dev.ron");
    let prod_cfg = root.join("prod.ron");
    write_config(
        &dev_cfg,
        "mirror",
        &root.join("dev/data"),
        &root.join("dev/audit"),
    );
    write_config_repos(
        &prod_cfg,
        &[
            ("mirror", root.join("prod/mirror")),
            ("release", root.join("prod/data")),
        ],
        &root.join("prod/audit"),
        &format!(
            r#"promotion: Some((
        ladder_name: "rust",
        stages: [ ( name: "dev", repo: "mirror" ), ( name: "prod", repo: "release", gated: true, gates: ["osv"] ) ],
        advisory_db: Some("{osv}"),
    )),"#,
            osv = osv.display()
        ),
    );
    let body: &[u8] = b"serde-1.0.0-bytes";
    seed(&dev_cfg, "mirror", "serde", "1.0.0", body);
    let prod_root = root.join("prod").join("holger-prod-store");

    let (ok, out) = promote(
        &[
            "--from",
            dev_cfg.to_str().unwrap(),
            "--to",
            prod_cfg.to_str().unwrap(),
            "--repo",
            "mirror",
            "--to-repo",
            "release",
            "--name",
            "serde",
            "--version",
            "1.0.0",
            "--execute",
        ],
        &[],
    );

    assert!(
        !ok,
        "a blocked promotion must FAIL, not succeed quietly; output:\n{out}"
    );
    assert!(
        !out.contains("SEALED"),
        "nothing may be sealed; output:\n{out}"
    );
    assert!(
        out.contains(server_lib::errcode::PROMOTE_POLICY_BLOCKED.code),
        "refusal must carry the typed code {}; output:\n{out}",
        server_lib::errcode::PROMOTE_POLICY_BLOCKED.code
    );
    assert!(
        out.contains("OSV-TEST-0001"),
        "refusal names the finding that blocked it; output:\n{out}"
    );
    // Fail-closed: a refused promotion writes NOTHING.
    assert!(
        !ref_path(&prod_root, "release", "serde", "1.0.0").exists(),
        "a blocked promotion must not seal the ref"
    );
    assert!(
        !prod_root
            .join("blobs")
            .join(blake3::hash(body).to_hex().to_string())
            .exists(),
        "a blocked promotion must not write the blob"
    );
}

/// ★ **Asking for the gate the OLD way refuses — it does not seal unscanned.**
///
/// `HOLGER_PROMOTE_ENFORCE` and friends no longer configure anything: the keys
/// live in the RON `promotion:` block. A caller still setting them asked for
/// enforcement this run would not perform, and until 2026-09-22 the verb sealed
/// anyway and said `SEALED`. Unreachable configuration that reads as enforcement
/// is worse than none, so the verb refuses by name and writes nothing.
///
/// RED-when-broken: drop the check in `run_promote` and this test gets `SEALED`.
#[test]
fn the_retired_environment_spelling_refuses_rather_than_sealing_unscanned() {
    let f = fixture();
    let osv = f._dir.path().join("advisories.json");
    std::fs::write(&osv, OSV_FEED).expect("write advisory feed");

    let (ok, out) = promote(
        &[
            "--from",
            f.dev_cfg.to_str().unwrap(),
            "--to",
            f.prod_cfg.to_str().unwrap(),
            "--repo",
            "mirror",
            "--name",
            "serde",
            "--version",
            "1.0.0",
            "--execute",
        ],
        &[
            ("HOLGER_PROMOTE_ENFORCE", "block"),
            ("HOLGER_PROMOTE_ADVISORY_DB", osv.to_str().unwrap()),
        ],
    );

    assert!(!ok, "an unenforceable request must FAIL; output:\n{out}");
    assert!(
        !out.contains("SEALED"),
        "nothing may be sealed; output:\n{out}"
    );
    assert!(
        out.contains(server_lib::errcode::PROMOTE_POLICY_BLOCKED.code),
        "the refusal carries the typed code; output:\n{out}"
    );
    assert!(
        out.contains("HOLGER_PROMOTE_ENFORCE") && out.contains("promotion:"),
        "the refusal names the keys and where they live now; output:\n{out}"
    );
    assert!(
        !ref_path(&f.prod_root, "mirror", "serde", "1.0.0").exists(),
        "an unenforceable promotion must not seal"
    );
}

/// **A missing source artifact refuses with the FROZEN code.** The served path
/// answers `not_found`; the local path raises `holger-promote-7` with the artifact
/// and repo named, instead of a bare "no such file".
#[test]
fn promoting_an_artifact_absent_from_the_source_repo_refuses_with_the_typed_code() {
    let f = fixture();

    let (ok, out) = promote(
        &[
            "--from",
            f.dev_cfg.to_str().unwrap(),
            "--to",
            f.prod_cfg.to_str().unwrap(),
            "--repo",
            "mirror",
            "--name",
            "ghost",
            "--version",
            "9.9.9",
            "--execute",
        ],
        &[],
    );

    assert!(!ok, "a missing artifact must FAIL; output:\n{out}");
    assert!(
        out.contains(server_lib::errcode::PROMOTE_SOURCE_NOT_FOUND.code),
        "refusal must carry the typed code {}; output:\n{out}",
        server_lib::errcode::PROMOTE_SOURCE_NOT_FOUND.code
    );
    assert!(out.contains("ghost"), "names the artifact; output:\n{out}");
    assert!(
        out.contains("mirror"),
        "names the source repo; output:\n{out}"
    );
}

/// **Immutability holds through the CLI.** A second promotion of a coordinate that
/// is already sealed is refused with `holger-promote-1`, and the original ref is
/// left untouched — the local path cannot overwrite a sealed generation.
#[test]
fn re_promoting_a_sealed_coordinate_is_refused_as_already_promoted() {
    let f = fixture();
    let args = [
        "--from",
        f.dev_cfg.to_str().unwrap(),
        "--to",
        f.prod_cfg.to_str().unwrap(),
        "--repo",
        "mirror",
        "--name",
        "serde",
        "--version",
        "1.0.0",
        "--execute",
    ];
    let (ok, out) = promote(&args, &[]);
    assert!(ok, "first promotion seals; output:\n{out}");
    let r = ref_path(&f.prod_root, "mirror", "serde", "1.0.0");
    let first = std::fs::read_to_string(&r).expect("read sealed ref");

    let (ok2, out2) = promote(&args, &[]);
    assert!(
        !ok2,
        "the second promotion must be REFUSED; output:\n{out2}"
    );
    assert!(
        out2.contains(server_lib::errcode::PROMOTE_ALREADY_PROMOTED.code),
        "refusal must carry the typed code {}; output:\n{out2}",
        server_lib::errcode::PROMOTE_ALREADY_PROMOTED.code
    );
    assert_eq!(
        std::fs::read_to_string(&r).expect("re-read ref"),
        first,
        "the sealed record must be untouched by the refused re-promotion"
    );
}