xtask/src/estate_media.rs
//! **`estate-media`** — Holger's release media for the ONE box (lane L21, 2026-10-18): what
//! Gunnar's kit does with `estate release-image`, in Holger's own words, for Holger's own sites.
//!
//! ```text
//! cargo xtask estate-media [mock] --where upcloud|kvm [--dev|--release] --media-to <dir> [--apply]
//! [--secrets <vault file>] [--composed] [--live-update on|off]
//! [--intel-trust <policy.ron>]
//! ```
//!
//! The ONE box's kit (private-monetize-ops `cargo xtask media`, `[products.holger.media]`) runs it
//! in this checkout. With `--apply` it:
//!
//! 1. composes the FOUR seedless media through the Build Thing ([`crate::forge_door`]), each
//! with the metadata seed marker (the box reads its pairing seed off the provider's metadata):
//! `holger-front` (holger-front-installer-metered), `holger` (holger-installer-metered, main
//! and twin), `holger-keep` and `holger-front-keep` (the same media composed to rewrite a
//! standing box in place, the update's fallback). The main and twin media (`holger`,
//! `holger-keep`) also take the estate's server declaration, `holger_ron` — holger-recipe
//! `estate_ron::medium`, staged as `.iso-out/estate-holger.ron` — and the korp root it
//! trusts, `plugin_trust_root` (lane W2): no hand step names either. The estate's own name
//! is filled in on the box from the installer's feed. `--intel-trust <policy.ron>` (lane P5)
//! stages the estate's intel trust policy — an update-bundle `TrustPolicy` pinning the
//! operator's OWN `holger.intel/v1` signers, checked here before anything is composed — as
//! `intel_trust_policy` on the same two media; the declaration's `intel.trust_policy` names
//! it on the release, and an airgap box imports signed intel at `POST /-/intel/import`
//! against it. Without it the media carry the slot empty and an intel import is refused by
//! name (`holger-import-4`): the trust root is the estate's, never the medium's;
//! 2. uploads each medium into the release bucket, content-addressed and immutable
//! (`release/<kind>/<sha256>.iso`), and each app medium's LIVE app — korp's archive beside
//! the composed `.iso` — as `release/<kind>-live/<pin>.live-app.znippy` (`holger-live`,
//! `holger-front-live`): what the ONE box's update hands the front (`UpdateTake`) so twin,
//! main and the front take the release into their live slot;
//! 3. writes the receipt `media-holger-<kind>.toml` into `--media-to`: the six objects, korp's
//! live records as `[live_apps.holger]` / `[live_apps.holger-front]`, and
//! `[release_notes."<id>"]` for each medium (label, date, the commits it carries).
//!
//! An app medium whose app is unsigned is refused by name: a Holger release without live apps
//! makes every update fall back to the keep media. Without `--apply` the plan is printed and
//! nothing is composed or uploaded. Replaces lane L13's one-off upload script.
//!
//! **DEV is the default kind** (owner 2026-10-19: "you must build NOT --release so DEBUG is on
//! EXACTLY like gunnar"). A DEV medium is composed with `--opt dev-narrate=on`: every box's
//! boot log leaves sealed over udp/51822 to the installer (holger-server's `dev_narrate`,
//! Skidbladnir `nordisk-narrate`), as Gunnar's `--dev-narrate` media do. `--release` composes
//! with `--opt dev-narrate=off` — the sender is not compiled in (`--release` IS
//! `--no-dev-narrate`) — and is the serving estate's last step, applied by an in-place
//! upgrade. The receipt says which kind it is (its header and each release note's label).
//!
//! `--composed` uploads the media ALREADY composed in `.iso-out` (by this verb's forge
//! step or `dwarves buildthing` per variant) instead of composing them again, so the exact
//! bytes a KVM install proved are the bytes that go up (lane R4). Each medium's record must
//! name this checkout's HEAD, or the medium is refused by name.
//!
//! The bucket: `mock` → the UpCloud mock's `/objsto` face on `127.0.0.1` (its API port from
//! `$MONETIZE_OPS_MOCK_PORTS`, the bench the ONE kit runs); the account → the upload pair
//! `RELEASE_MEDIA_*` read BY NAME from the vault file (`--secrets`, else `$HOLGER_RELEASE_SECRETS`,
//! else `$HOME/holger_secrets`), never printed.
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use anyhow::{bail, Context, Result};
use nornir_build_thing::variant::ArtifactKind;
use crate::forge_door;
/// One release medium: the receipt's word for it, the declared variant that composes it, and
/// whether its app is a LIVE app the boxes take (`<word>-live`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Medium {
pub word: &'static str,
pub variant: &'static str,
pub live: bool,
}
/// **The release**: the four media monetize's `lifecycle::Holger` names (front, main and twin,
/// and their keep media), the two app media carrying the live apps.
pub const MEDIA: [Medium; 4] = [
Medium {
word: "holger-front",
variant: holger_recipe::packaging::FRONT_INSTALLER_METERED,
live: true,
},
Medium {
word: "holger",
variant: holger_recipe::packaging::METERED_INSTALLER,
live: true,
},
Medium {
word: "holger-keep",
variant: holger_recipe::packaging::KEEP_MEDIUM,
live: false,
},
Medium {
word: "holger-front-keep",
variant: holger_recipe::packaging::FRONT_KEEP_MEDIUM,
live: false,
},
];
/// The live app kind of a medium (monetize `update::LIVE_SUFFIX`).
pub fn live_kind(word: &str) -> String {
format!("{word}-live")
}
/// The receipt's table of korp's live records (pmo `sells::RECEIPT_LIVE_APPS`).
pub const RECEIPT_LIVE_APPS: &str = "live_apps";
/// The receipt's table of what the build IS: its media kind (pmo `sells::RECEIPT_BUILD`).
pub const RECEIPT_BUILD: &str = "build";
/// The receipt's table of what each release IS (pmo `sells::RECEIPT_RELEASE_NOTES`).
pub const RECEIPT_RELEASE_NOTES: &str = "release_notes";
/// The marker every seedless medium is composed with: the box reads its seed off the metadata.
pub const SEED_FROM_METADATA: &str = "nordisk-pairing-seed-from = \"upcloud-metadata\"\n";
/// The mock bench's ports (private-monetize-ops `place::PORTS_ENV`) and its API port's default.
pub const MOCK_PORTS_ENV: &str = "MONETIZE_OPS_MOCK_PORTS";
pub const MOCK_API: u16 = 8791;
/// The vault file of the release bucket's upload pair.
pub const SECRETS_ENV: &str = "HOLGER_RELEASE_SECRETS";
/// The upload pair's names in it (Gunnar's kit reads the same names).
pub const UPLOAD_NAMES: [&str; 4] = [
"RELEASE_MEDIA_ENDPOINT",
"RELEASE_MEDIA_BUCKET",
"RELEASE_MEDIA_ACCESS_KEY_ID",
"RELEASE_MEDIA_SECRET_ACCESS_KEY",
];
pub const REGION_NAME: &str = "RELEASE_MEDIA_REGION";
pub const DEFAULT_REGION: &str = "europe-3";
/// What the operator (or the ONE kit) asked for.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Opts {
pub mock: bool,
pub place: String,
/// `dev` (the default: narrate ON) or `release` (narrate compiled out).
pub kind: String,
pub media_to: PathBuf,
pub apply: bool,
pub secrets: Option<PathBuf>,
/// Upload the media already in `.iso-out` (proved before upload), never compose again.
pub composed: bool,
/// **The estate's live-update switch** (`--live-update on|off`, absent: on): composed onto
/// every medium as `live_app=on|off` ([`live_update_params`]), part of the BASE.
pub live_update: bool,
/// **The estate's intel trust policy** (`--intel-trust <policy.ron>`, lane P5): staged as
/// `intel_trust_policy` for the box media. `None`: the slot stays empty.
pub intel_trust: Option<PathBuf>,
/// **The bundles door's trust policy** (`--bundles-trust <policy.ron>`, lane HOL-BUNDLES-DOOR):
/// staged as `bundles_trust_policy` for the box media. `None`: the slot stays empty, and every
/// PUT into the estate's `bundles` door is refused (`holger-bundles-1`).
pub bundles_trust: Option<PathBuf>,
}
/// Pure: **the live-update switch as a medium parameter** — `live_app=on|off` (holger-recipe
/// `LIVE_UPDATE_PARAM`, the cmdline's `korp.live_update`), the pattern's words.
pub fn live_update_params(on: bool) -> Vec<String> {
vec![format!(
"{}={}",
holger_recipe::packaging::LIVE_UPDATE_PARAM,
if on { "on" } else { "off" }
)]
}
/// Parse the verb's words; an unknown word is refused, never dropped.
pub fn parse(args: &[String]) -> Result<Opts> {
let (
mut mock,
mut place,
mut kind,
mut media_to,
mut apply,
mut secrets,
mut composed,
mut live_update,
) = (false, None, None, None, false, None, false, true);
let mut intel_trust = None;
let mut bundles_trust = None;
let mut it = args.iter();
while let Some(a) = it.next() {
let mut val = |flag: &str| {
it.next()
.cloned()
.with_context(|| format!("estate-media: {flag} takes a value"))
};
match a.as_str() {
"mock" => mock = true,
"--where" => place = Some(val("--where")?),
"--dev" | "--release" | "--dev-narrate" | "--no-dev-narrate" => {
// `--dev-narrate` is `--dev`, `--no-dev-narrate` is `--release`: a DEV medium
// narrates and a release one carries no sender, never the other two mixes.
let k = if matches!(a.as_str(), "--dev" | "--dev-narrate") { KIND_DEV } else { KIND_RELEASE }.to_string();
if kind.as_ref().is_some_and(|was| *was != k) {
bail!("estate-media: --dev or --release, not both (a DEV medium narrates; --release is --no-dev-narrate)");
}
kind = Some(k);
}
"--media-to" => media_to = Some(PathBuf::from(val("--media-to")?)),
"--secrets" => secrets = Some(PathBuf::from(val("--secrets")?)),
"--apply" => apply = true,
"--composed" => composed = true,
"--intel-trust" => intel_trust = Some(PathBuf::from(val("--intel-trust")?)),
"--bundles-trust" => bundles_trust = Some(PathBuf::from(val("--bundles-trust")?)),
"--live-update" => {
live_update = match val("--live-update")?.as_str() {
"on" => true,
"off" => false,
other => bail!("estate-media: --live-update {other}: `on` or `off`"),
}
}
other => bail!("estate-media: unknown word '{other}' (takes [mock] --where <place> [--dev|--release] --media-to <dir> [--apply] [--secrets <file>] [--composed] [--live-update on|off] [--intel-trust <policy.ron>] [--bundles-trust <policy.ron>])"),
}
}
let place = place.context("estate-media: --where <place> (upcloud, kvm)")?;
if !matches!(place.as_str(), "upcloud" | "kvm") {
bail!("estate-media: --where {place}: `upcloud` or `kvm`");
}
// DEV unless `--release` is said: test estates run DEV media, the serving estate's last
// step is the release.
let kind = kind.unwrap_or_else(|| KIND_DEV.to_string());
let media_to = media_to.context(
"estate-media: --media-to <dir> (where the ONE kit reads media-holger-<kind>.toml)",
)?;
// Already composed media carry whatever their composer staged: a policy named here would
// never reach them, and the receipt would claim a trust it does not carry.
if composed && bundles_trust.is_some() {
bail!("estate-media: --bundles-trust stages a policy for composing; --composed uploads media already composed (compose them with --param bundles_trust_policy=@<policy.ron>)");
}
if composed && intel_trust.is_some() {
bail!("estate-media: --intel-trust stages a policy for composing; --composed uploads media already composed (compose them with --param intel_trust_policy=@<policy.ron>)");
}
Ok(Opts {
mock,
place,
kind,
media_to,
apply,
secrets,
composed,
live_update,
intel_trust,
bundles_trust,
})
}
/// The DEV kind (the default): composed with the `dev-narrate` option ON.
pub const KIND_DEV: &str = "dev";
/// The RELEASE kind: the `dev-narrate` option OFF, no sender compiled in.
pub const KIND_RELEASE: &str = "release";
/// Pure: **whether a kind narrates** — DEV does, release does not.
pub fn narrates(kind: &str) -> bool {
kind == KIND_DEV
}
/// Pure: **the forge's `--opt` for a kind** — `dev-narrate=on` for DEV, `=off` for release.
pub fn forge_opts(kind: &str) -> Vec<String> {
vec![format!(
"{}={}",
holger_recipe::packaging::DEV_NARRATE_OPTION,
if narrates(kind) { "on" } else { "off" }
)]
}
/// Pure: **the payload row the forge builds for a medium of a kind** — the composer's own
/// resolution (dwarves `assemble_cell::resolve_installer_options`: the settings tree splits the
/// `--opt` line, edda `resolve_options` + `ResolvedOptions::apply_to_set`), read back off the
/// resolved set's cell. A DEV medium arrives at the payload's declared `…-narrate` row and its
/// `dev-media` profile, as Gunnar's dev media do; a release medium builds the payload row.
pub fn payload_row(medium: &str, kind: &str) -> Result<nornir_build_thing::variant::BuildVariant> {
let set = holger_recipe::declared();
let cell = set
.installers
.iter()
.find(|i| i.name == medium)
.with_context(|| format!("no [[build.installer]] cell named '{medium}'"))?;
let tree = nornir_build_thing::tree::SettingsTree::of_installer(cell, None);
let toggles = tree
.split_opts(&forge_opts(kind))
.map_err(|e| anyhow::anyhow!("installer '{medium}': {e:#}"))?
.features;
let resolved =
nornir_build_thing::options::resolve_options(&cell.options, &cell.params, &toggles)?;
let mut planned = set.clone();
resolved.apply_to_set(&cell.options, &mut planned, &cell.variant)?;
let row = &planned
.installers
.iter()
.find(|i| i.name == medium)
.context("the resolved set lost its cell")?
.variant;
Ok(planned.require(row)?.clone())
}
/// Pure: **what the receipt says the build is** — its header line and the label suffix.
pub fn kind_words(kind: &str) -> (String, &'static str) {
let n = narrates(kind);
(
format!(
"# build: kind = {kind:?}, dev_narrate = {n} ({})\n",
if n {
"every box narrates its boot log over udp/51822: a TEST estate's media"
} else {
"no narrate sender compiled in: the serving estate's media"
}
),
if n { " (DEV: narrate on)" } else { "" },
)
}
/// **The front's own time sources** — the shared `estate_clock::config::CLOUD_SOURCES`
/// (`front_sources(FrontPlace::Cloud, _)`), the words Gunnar's kit composes its front with
/// (lane G3 moved them out of both kits): every front medium is composed with `clock=` these,
/// the estate's root of time. Without them the front syncs nothing and REFUSES to sign time,
/// so no box (`peer://10.44.0.1:50123`) ever takes the estate's clock (lane R10).
pub const FRONT_CLOCK_SOURCES: &str = estate_clock::config::CLOUD_SOURCES;
/// Pure: **a composed front medium carries the clock** — its command line (in the medium's
/// bytes) names `korp.env.ESTATE_CLOCK=` [`FRONT_CLOCK_SOURCES`]. A box medium is not asked.
pub fn composed_front_has_clock(word: &str, medium: &[u8]) -> Result<()> {
if !word.contains("front") {
return Ok(());
}
let needle = format!("korp.env.ESTATE_CLOCK={FRONT_CLOCK_SOURCES}");
if medium.windows(needle.len()).any(|w| w == needle.as_bytes()) {
return Ok(());
}
bail!("[estate-media-front-clock] the composed {word} medium's command line has no `{needle}`: compose it with `--param clock={FRONT_CLOCK_SOURCES}` (without it the front never syncs and no box's clock peer signs)")
}
/// Pure: **a composed medium is this checkout's** — its record (`<variant>.medium.json`)
/// names a `head` that is a prefix of `head` (this checkout's full HEAD).
pub fn composed_is_this_head(word: &str, record: &str, head: &str) -> Result<()> {
let named = serde_json::from_str::<serde_json::Value>(record)
.ok()
.and_then(|v| v["head"].as_str().map(str::to_string))
.filter(|h| h.len() >= 7);
match named {
Some(h) if head.starts_with(&h) => Ok(()),
Some(h) => bail!("[estate-media-composed-stale] the composed {word} medium names head {h}, this checkout is {head}: compose it again"),
None => bail!("[estate-media-composed-stale] the composed {word} medium's record names no head"),
}
}
/// Pure: **a composed medium reads its own CD** (A7 row 25, Gunnar 09fe2c48, owner's order
/// 2026-09-23: "refuse when cd_driver is false"). Its record (`<variant>.medium.json`, dwarves
/// `media_steps::write_record`) says `cd_driver: true` only when the compose proved the kernel's
/// CD block (nornir-build-thing `kernel_requires::refuse_unless_reads_its_cd`). A medium that
/// cannot read the tray it is installed from boots, finds no ISO-9660 volume and parks on
/// FAILED, so `false`, a missing field or an unreadable record refuses the upload by name.
pub fn composed_reads_its_cd(word: &str, record: &str) -> Result<()> {
let v = serde_json::from_str::<serde_json::Value>(record).with_context(|| {
format!("REFUSED [medium-no-cd-driver] the {word} medium's record is not JSON")
})?;
let block = nornir_build_thing::kernel_requires::CD_BLOCK
.iter()
.map(|s| format!("{s}=y"))
.collect::<Vec<_>>()
.join(" ");
match v["cd_driver"].as_bool() {
Some(true) => Ok(()),
Some(false) => bail!("REFUSED [medium-no-cd-driver] the {word} medium cannot read its own CD (its record says cd_driver: false): rebuild the donor kernel with the CD block ({block}) and compose again"),
None => bail!("REFUSED [medium-no-cd-driver] the {word} medium's record has no cd_driver: its CD block was never proven (compose it with a dwarves that runs the CD preflight; the kernel needs {block})"),
}
}
/// Pure: **the media kind of a kind word** (`dev` | `release`, nornir-build-thing `MediaKind`).
pub fn media_kind(kind: &str) -> Result<nornir_build_thing::media_gate::MediaKind> {
nornir_build_thing::media_gate::MediaKind::parse(kind)
.with_context(|| format!("estate-media: kind {kind:?} is `dev` or `release`"))
}
/// **A medium is of the kind this upload says** (lane B4, A9 row 10): read back from the
/// medium's own bytes (the `dev-narrate` option bakes `korp.env.NORDISK_MEDIA_KIND`), so a
/// DEV medium is never uploaded and labelled as the release.
pub fn held_to_kind(word: &str, kind: &str, medium: &[u8]) -> Result<()> {
nornir_build_thing::media_gate::refuse_kind(word, media_kind(kind)?, medium)
.with_context(|| format!("[estate-media-kind] the {word} medium"))
}
/// The composed medium of `variant` in `.iso-out`, held to this checkout's HEAD and `kind`.
fn composed_medium(root: &Path, word: &str, variant: &str, kind: &str) -> Result<PathBuf> {
let iso = forge_door::iso_out(root, variant);
let bytes = std::fs::metadata(&iso)
.with_context(|| {
format!(
"[estate-media-composed] {} is absent: compose it first",
iso.display()
)
})?
.len();
if bytes == 0 {
bail!("[estate-media-composed] {} is EMPTY", iso.display());
}
let record = std::fs::read_to_string(iso.with_extension("medium.json")).with_context(|| {
format!(
"[estate-media-composed] {}: the medium record",
iso.with_extension("medium.json").display()
)
})?;
let o = std::process::Command::new("git")
.arg("-C")
.arg(root)
.args(["rev-parse", "HEAD"])
.output()
.context("git rev-parse HEAD")?;
composed_is_this_head(word, &record, String::from_utf8_lossy(&o.stdout).trim())?;
composed_reads_its_cd(word, &record)?;
let bytes = std::fs::read(&iso)
.with_context(|| format!("[estate-media-composed] {}", iso.display()))?;
composed_front_has_clock(word, &bytes)?;
held_to_kind(word, kind, &bytes)?;
Ok(iso)
}
/// The receipt's file name (pmo `sells::media_receipt("holger-<kind>")`).
pub fn receipt_name(kind: &str) -> String {
format!("media-holger-{kind}.toml")
}
/// A medium's object key.
pub fn medium_key(word: &str, sha256: &str) -> String {
format!("release/{word}/{sha256}.iso")
}
/// A live app's object key.
pub fn live_key(word: &str, pin: &str) -> String {
format!("release/{}/{pin}.live-app.znippy", live_kind(word))
}
/// korp's live record and archive beside a composed `.iso` (`<stem>.live.toml`,
/// `<stem>.live-app.znippy`).
pub fn live_companions(iso: &Path) -> (PathBuf, PathBuf) {
let dir = iso.parent().map(Path::to_path_buf).unwrap_or_default();
let stem = iso
.file_stem()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_default();
(
dir.join(format!("{stem}.live.toml")),
dir.join(format!("{stem}.live-app.znippy")),
)
}
/// The release declaration korp composed beside a `.iso` and its signature
/// (`<stem>.live-release.bundle`, `<stem>.live-release.sig`; absent: the medium declares none).
pub fn release_companions(iso: &Path) -> (PathBuf, PathBuf) {
let dir = iso.parent().map(Path::to_path_buf).unwrap_or_default();
let stem = iso
.file_stem()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_default();
(
dir.join(format!(
"{stem}.{}",
korp_installer::live::MEDIUM_RELEASE_SUFFIX
)),
dir.join(format!(
"{stem}.{}",
korp_installer::live::MEDIUM_RELEASE_SIG_SUFFIX
)),
)
}
/// korp's live record of one app medium, its four fields checked — and what the release
/// declares beside its app (lane B5): the release declaration and its signature (hex), the
/// release's own `korp.env.*` tokens.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct LiveRecord {
pub base: String,
pub app: String,
pub app_len: u64,
pub sig: String,
pub release: String,
pub release_sig: String,
pub env: Vec<String>,
}
/// Pure: **`r` with the release declaration korp composed beside its medium** (lane B5). A
/// declaration without its signature on a signed medium is refused by name: every box would
/// refuse it (`[release-unsigned]`) and fall back to the keep medium.
pub fn with_release(
word: &str,
mut r: LiveRecord,
bundle: Option<Vec<u8>>,
sig: Option<Vec<u8>>,
) -> Result<LiveRecord> {
let hex = |b: &[u8]| b.iter().map(|x| format!("{x:02x}")).collect::<String>();
if let Some(b) = bundle {
let sig = sig.with_context(|| format!("[estate-media-release-unsigned] the {word} medium's release declaration carries no signature: no box that requires signed media takes it"))?;
r.release = hex(&b);
r.release_sig = hex(&sig);
}
Ok(r)
}
/// Pure: **korp's live record**, refused by name when a field is missing or the app is unsigned
/// (a box never takes an unsigned app live, so the release would update from keep only).
pub fn live_record(word: &str, rec: &str) -> Result<LiveRecord> {
let get = |k: &str| {
rec.lines()
.find_map(|l| {
l.split_once('=')
.filter(|(key, _)| key.trim() == k)
.map(|(_, v)| v.trim().trim_matches('"').to_string())
})
.filter(|v| !v.is_empty())
};
let hex64 = |k: &str| {
get(k).filter(|h| h.len() == 64 && h.bytes().all(|b| b.is_ascii_hexdigit())).with_context(|| format!("[estate-media-live-record] the {word} medium's live record names no `{k}` (64 hex)"))
};
let (base, app) = (hex64("base")?, hex64("app")?);
let app_len = get("app_len")
.and_then(|n| n.parse::<u64>().ok())
.filter(|n| *n > 0)
.with_context(|| {
format!("[estate-media-live-record] the {word} medium's live record names no `app_len`")
})?;
let sig = get("sig").with_context(|| format!("[estate-media-unsigned] the {word} medium's app is unsigned (composed without the root seed): no box takes it live, every update would fall back to {word}'s keep medium"))?;
let env = get("env")
.map(|e| e.split_whitespace().map(str::to_string).collect())
.unwrap_or_default();
Ok(LiveRecord {
base,
app,
app_len,
sig,
env,
..LiveRecord::default()
})
}
/// One object in the bucket, as the receipt names it.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Object {
pub url: String,
pub sha256: String,
pub bytes: u64,
}
/// Pure: **the receipt** — every object (`<kind> = { object, sha256, bytes }`), then
/// `[live_apps.<kind>]` for each live app (its `<kind>-live` archive among the objects), then
/// `[release_notes."<id>"]` for each medium's release id.
pub fn receipt_text(
kind: &str,
objects: &BTreeMap<String, Object>,
live: &BTreeMap<String, LiveRecord>,
label: &str,
built: &str,
carries: &BTreeMap<String, String>,
) -> Result<String> {
for k in live.keys() {
if !objects.contains_key(&live_kind(k)) {
bail!(
"[estate-media-live-record] the {k} live app has no {} object",
live_kind(k)
);
}
}
let mut out = format!("# [estate_orders.media] of Holger's own sites: the {kind} media, one per machine kind, and their live apps (holger `cargo xtask estate-media`)\n");
let (header, suffix) = kind_words(kind);
out.push_str(&header);
let label = format!("{label}{suffix}");
for (k, o) in objects {
out.push_str(&format!(
"{k} = {{ object = {:?}, sha256 = {:?}, bytes = {} }}\n",
o.url, o.sha256, o.bytes
));
}
// The kind as data (lane B4): what the ONE box kit reads to refuse DEV media for a sold site.
out.push_str(&format!(
"\n[{RECEIPT_BUILD}]\nkind = {:?}\ndev_narrate = {}\n",
media_kind(kind)?.word(),
narrates(kind)
));
for (k, r) in live {
out.push_str(&format!(
"\n[{RECEIPT_LIVE_APPS}.{k}]\nbase = {:?}\napp = {:?}\napp_len = {}\nsig = {:?}\n",
r.base, r.app, r.app_len, r.sig
));
// Lane B5: what the release declares beside its app (the ONE box's `LiveRecord` reads
// these as optional).
if !r.release.is_empty() {
out.push_str(&format!(
"release = {:?}\nrelease_sig = {:?}\n",
r.release, r.release_sig
));
}
if !r.env.is_empty() {
out.push_str(&format!(
"env = [{}]\n",
r.env
.iter()
.map(|e| format!("{e:?}"))
.collect::<Vec<_>>()
.join(", ")
));
}
}
let carried: Vec<String> = carries
.iter()
.map(|(repo, commit)| format!("{repo:?} = {commit:?}"))
.collect();
for m in MEDIA {
if let Some(o) = objects.get(m.word) {
out.push_str(&format!("\n[{RECEIPT_RELEASE_NOTES}.{:?}]\nlabel = {label:?}\nbuilt = {built:?}\ncarries = {{ {} }}\n", o.sha256, carried.join(", ")));
}
}
Ok(out)
}
/// The release bucket's upload pair.
#[derive(Clone, PartialEq, Eq)]
pub struct Bucket {
pub endpoint: String,
pub region: String,
pub bucket: String,
pub access: String,
pub secret: String,
}
impl std::fmt::Debug for Bucket {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Bucket({} {} {})",
self.endpoint, self.region, self.bucket
)
}
}
/// The mock's bucket on `api_port` (the UpCloud mock's `/objsto` face and its bench keys).
pub fn mock_bucket(api_port: u16) -> Bucket {
Bucket {
endpoint: format!("http://127.0.0.1:{api_port}/objsto"),
region: "mock".into(),
bucket: "release".into(),
access: "mock-release-access-key".into(),
secret: "mock-release-secret-key".into(),
}
}
/// The mock's API port: `$MONETIZE_OPS_MOCK_PORTS` (api,clock,stripe,control), else the default.
pub fn mock_api_port(env: Option<&str>) -> Result<u16> {
match env.map(str::trim).filter(|v| !v.is_empty()) {
None => Ok(MOCK_API),
Some(t) => t
.split(',')
.next()
.and_then(|p| p.trim().parse::<u16>().ok())
.filter(|p| *p >= 1024)
.with_context(|| format!("{MOCK_PORTS_ENV}={t}: four ports api,clock,stripe,control")),
}
}
/// Pure: **the account's bucket** from the vault file's text, read by NAME.
pub fn bucket_of(vault: &str) -> Result<Bucket> {
let mut v = BTreeMap::new();
for l in vault
.lines()
.map(str::trim)
.filter(|l| !l.is_empty() && !l.starts_with('#'))
{
if let Some((k, val)) = l.trim_start_matches("export ").split_once('=') {
v.insert(
k.trim().to_string(),
val.trim().trim_matches('"').trim_matches('\'').to_string(),
);
}
}
let get = |k: &str| {
v.get(k).filter(|x| !x.is_empty()).cloned().with_context(|| format!("[estate-media-bucket] {k} is not in the vault file: the release bucket's upload pair"))
};
let endpoint = get(UPLOAD_NAMES[0])?.trim_end_matches('/').to_string();
if !endpoint.starts_with("https://") {
bail!("[estate-media-bucket] {} is not https://", UPLOAD_NAMES[0]);
}
Ok(Bucket {
endpoint,
region: v
.get(REGION_NAME)
.filter(|r| !r.is_empty())
.cloned()
.unwrap_or_else(|| DEFAULT_REGION.into()),
bucket: get(UPLOAD_NAMES[1])?,
access: get(UPLOAD_NAMES[2])?,
secret: get(UPLOAD_NAMES[3])?,
})
}
fn hex(b: &[u8]) -> String {
b.iter().map(|x| format!("{x:02x}")).collect()
}
/// sha256 of `bytes`, hex.
pub fn sha256_hex(bytes: &[u8]) -> String {
use sha2::Digest as _;
hex(&sha2::Sha256::digest(bytes))
}
fn hmac(key: &[u8], msg: &str) -> Vec<u8> {
use hmac::{KeyInit as _, Mac as _};
let mut m = <hmac::Hmac<sha2::Sha256>>::new_from_slice(key).expect("hmac takes any key");
m.update(msg.as_bytes());
m.finalize().into_bytes().to_vec()
}
/// Pure: **a SigV4 path-style PUT** — the url and its three headers (`x-amz-date`,
/// `x-amz-content-sha256`, `Authorization`) at `amz_date` (`YYYYMMDDTHHMMSSZ`).
pub fn put_request(
b: &Bucket,
key: &str,
payload_sha256: &str,
amz_date: &str,
) -> (String, Vec<(String, String)>) {
let scheme = if b.endpoint.starts_with("http://") {
"http://"
} else {
"https://"
};
let bare = b.endpoint.trim_start_matches(scheme).trim_end_matches('/');
let (host, prefix) = bare
.split_once('/')
.map(|(h, p)| (h.to_string(), format!("/{p}")))
.unwrap_or((bare.to_string(), String::new()));
let path = format!("{prefix}/{}/{key}", b.bucket);
let day = &amz_date[..8];
let canon = format!("PUT\n{path}\n\nhost:{host}\nx-amz-content-sha256:{payload_sha256}\nx-amz-date:{amz_date}\n\nhost;x-amz-content-sha256;x-amz-date\n{payload_sha256}");
let scope = format!("{day}/{}/s3/aws4_request", b.region);
let sts = format!(
"AWS4-HMAC-SHA256\n{amz_date}\n{scope}\n{}",
sha256_hex(canon.as_bytes())
);
let k = hmac(format!("AWS4{}", b.secret).as_bytes(), day);
let k = hmac(&hmac(&hmac(&k, &b.region), "s3"), "aws4_request");
let sig = hex(&hmac(&k, &sts));
let auth = format!("AWS4-HMAC-SHA256 Credential={}/{scope}, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature={sig}", b.access);
(
format!("{scheme}{host}{path}"),
vec![
("x-amz-date".into(), amz_date.into()),
("x-amz-content-sha256".into(), payload_sha256.into()),
("Authorization".into(), auth),
],
)
}
/// Now, `YYYYMMDDTHHMMSSZ` (UTC).
pub fn amz_now() -> String {
let s = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let (y, m, d) = civil((s / 86_400) as i64);
let r = s % 86_400;
format!(
"{y:04}{m:02}{d:02}T{:02}{:02}{:02}Z",
r / 3600,
(r % 3600) / 60,
r % 60
)
}
/// Days since the epoch → (year, month, day) (Howard Hinnant).
pub fn civil(days: i64) -> (i64, u32, u32) {
let z = days + 719_468;
let era = z.div_euclid(146_097);
let doe = z - era * 146_097;
let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
(yoe + era * 400 + i64::from(m <= 2), m, d)
}
/// **PUT one file** into the bucket; answers its url, sha256 and size.
pub fn put_file(b: &Bucket, key: &str, file: &Path) -> Result<Object> {
let bytes = std::fs::read(file).with_context(|| format!("{}", file.display()))?;
let sha = sha256_hex(&bytes);
let (url, headers) = put_request(b, key, &sha, &amz_now());
let agent: ureq::Agent = ureq::Agent::config_builder()
.http_status_as_error(false)
.timeout_global(Some(std::time::Duration::from_secs(1800)))
.build()
.into();
let mut r = agent.put(&url);
for (k, v) in &headers {
r = r.header(k.as_str(), v.as_str());
}
let st = r
.send(&bytes[..])
.map(|res| res.status().as_u16())
.with_context(|| format!("PUT {url}"))?;
if !(200..300).contains(&st) {
bail!("[estate-media-put] PUT {url}: the bucket answered HTTP {st}");
}
Ok(Object {
url,
sha256: sha,
bytes: bytes.len() as u64,
})
}
/// The release's label: `$HOLGER_RELEASE_LABEL`, else `holger <workspace version>`.
pub fn release_label(root: &Path) -> String {
if let Some(l) = std::env::var("HOLGER_RELEASE_LABEL")
.ok()
.map(|l| l.trim().to_string())
.filter(|l| !l.is_empty())
{
return l;
}
let text = std::fs::read_to_string(root.join("Cargo.toml")).unwrap_or_default();
let version = text
.split("[workspace.package]")
.nth(1)
.and_then(|t| {
t.lines().find_map(|l| {
l.trim().strip_prefix("version").map(|v| {
v.trim_start_matches([' ', '='])
.trim()
.trim_matches('"')
.to_string()
})
})
})
.unwrap_or_default();
if version.is_empty() {
"holger".into()
} else {
format!("holger {version}")
}
}
/// The commits the media compose from: each repository's HEAD (short).
pub fn release_carries(root: &Path) -> BTreeMap<String, String> {
let mut out = BTreeMap::new();
for repo in [
"holger",
"holger-ui",
"Skidbladnir",
"tunnr",
"edda",
"dwarves",
] {
let dir = gate_repo_dir(root, repo);
if let Ok(o) = std::process::Command::new("git")
.arg("-C")
.arg(&dir)
.args(["rev-parse", "--short=8", "HEAD"])
.output()
{
let h = String::from_utf8_lossy(&o.stdout).trim().to_string();
if o.status.success() && !h.is_empty() {
out.insert(repo.to_string(), h);
}
}
}
out
}
/// Today, UTC, `YYYY-MM-DD`.
pub fn utc_date() -> String {
let a = amz_now();
format!("{}-{}-{}", &a[..4], &a[4..6], &a[6..8])
}
/// Run the verb.
/// **What every seedless medium is composed from**, written into `.iso-out`: the
/// metadata seed marker, the estate's declaration ([`ESTATE_RON_FILE`], holger-recipe
/// `estate_ron::medium`), the korp root it trusts ([`KORP_ROOT_FILE`]) and, when the operator
/// gives one, the estate's intel trust policy ([`INTEL_TRUST_FILE`]).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Staged {
pub marker: PathBuf,
pub ron: PathBuf,
pub korp_root: PathBuf,
/// The staged intel trust policy, or `None`: the medium's slot stays empty.
pub intel_trust: Option<PathBuf>,
/// The staged bundles-door trust policy, or `None`: the medium's slot stays empty.
pub bundles_trust: Option<PathBuf>,
}
/// The estate declaration's file in `.iso-out`.
pub const ESTATE_RON_FILE: &str = "estate-holger.ron";
/// The korp root's file in `.iso-out`.
pub const KORP_ROOT_FILE: &str = "korp-root-ca.pem";
/// The intel trust policy's file in `.iso-out`.
pub const INTEL_TRUST_FILE: &str = "estate-intel-trust.ron";
/// The bundles door's trust policy's file in `.iso-out` (lane HOL-BUNDLES-DOOR).
pub const BUNDLES_TRUST_FILE: &str = "estate-bundles-trust.ron";
/// Pure: **an intel trust policy the box can verify against** — it parses as an update-bundle
/// `TrustPolicy` (schema, a coherent `trust_mode`, the expired posture) and does not shut out
/// `holger.intel/v1`. Refused here by name rather than on an airgap box at its first import.
pub fn intel_trust_refusal(text: &str) -> Option<String> {
let policy = match update_bundle::policy::TrustPolicy::from_ron(text) {
Ok(p) => p,
Err(e) => {
return Some(format!(
"[estate-media-intel-trust] the intel trust policy does not load: {e}"
))
}
};
if !policy.allowed_products.is_empty()
&& !policy
.allowed_products
.iter()
.any(|p| p == holger_intel::INTEL_PRODUCT)
{
return Some(format!("[estate-media-intel-trust] the intel trust policy's allowed_products ({}) leave out {}: every intel import would be refused", policy.allowed_products.join(", "), holger_intel::INTEL_PRODUCT));
}
None
}
impl Staged {
pub fn write(dir: &Path) -> Result<Self> {
std::fs::create_dir_all(dir).with_context(|| format!("{}", dir.display()))?;
let s = Self {
marker: dir.join("seed-from-metadata.toml"),
ron: dir.join(ESTATE_RON_FILE),
korp_root: dir.join(KORP_ROOT_FILE),
intel_trust: None,
bundles_trust: None,
};
std::fs::write(&s.marker, SEED_FROM_METADATA)?;
std::fs::write(&s.ron, holger_recipe::estate_ron::medium())?;
std::fs::write(&s.korp_root, holger_recipe::estate_ron::KORP_ROOT_PEM)?;
// A stale policy of an earlier run never rides a medium composed without one.
let _ = std::fs::remove_file(dir.join(INTEL_TRUST_FILE));
let _ = std::fs::remove_file(dir.join(BUNDLES_TRUST_FILE));
Ok(s)
}
/// Stage the operator's bundles-door trust policy `policy` beside the declaration (lane
/// HOL-BUNDLES-DOOR), after it loads as an update-bundle `TrustPolicy`.
pub fn with_bundles_trust(mut self, policy: &Path) -> Result<Self> {
let text = std::fs::read_to_string(policy)
.with_context(|| format!("[estate-media-bundles-trust] {}", policy.display()))?;
if let Err(e) = update_bundle::policy::TrustPolicy::from_ron(&text) {
bail!(
"[estate-media-bundles-trust] the bundles trust policy does not load: {e} ({})",
policy.display()
);
}
let dir = self
.ron
.parent()
.context("[estate-media-bundles-trust] the staging directory")?;
let staged = dir.join(BUNDLES_TRUST_FILE);
std::fs::write(&staged, text).with_context(|| format!("{}", staged.display()))?;
self.bundles_trust = Some(staged);
Ok(self)
}
/// Stage the operator's intel trust policy `policy` beside the declaration, after
/// [`intel_trust_refusal`] held it.
pub fn with_intel_trust(mut self, policy: &Path) -> Result<Self> {
let text = std::fs::read_to_string(policy)
.with_context(|| format!("[estate-media-intel-trust] {}", policy.display()))?;
if let Some(why) = intel_trust_refusal(&text) {
bail!("{why} ({})", policy.display());
}
let dir = self
.ron
.parent()
.context("[estate-media-intel-trust] the staging directory")?;
let staged = dir.join(INTEL_TRUST_FILE);
std::fs::write(&staged, text).with_context(|| format!("{}", staged.display()))?;
self.intel_trust = Some(staged);
Ok(self)
}
}
/// Pure: **the forge's parameters for `variant`** — the seed marker always, and the
/// estate's declaration and korp root where the medium DECLARES the parameter (the
/// main and twin media; the front serves no repository). Read off the declaration,
/// so a medium that takes `holger_ron` can never be composed without it (R4).
pub fn forge_params(variant: &str, s: &Staged) -> Vec<String> {
let declared = |name: &str| {
holger_recipe::declared()
.installers
.iter()
.any(|i| i.name == variant && i.params.iter().any(|p| p.name == name))
};
let mut out = vec![format!("pairing_seed=@{}", s.marker.display())];
if declared("holger_ron") {
out.push(format!("holger_ron=@{}", s.ron.display()));
}
if declared("plugin_trust_root") {
out.push(format!("plugin_trust_root=@{}", s.korp_root.display()));
}
// The estate's intel signers (lane P5), only when the operator gave them.
if let Some(policy) = s
.intel_trust
.as_ref()
.filter(|_| declared("intel_trust_policy"))
{
out.push(format!("intel_trust_policy=@{}", policy.display()));
}
// The bundles door's signers (lane HOL-BUNDLES-DOOR), only when the operator gave them.
if let Some(policy) = s
.bundles_trust
.as_ref()
.filter(|_| declared("bundles_trust_policy"))
{
out.push(format!("bundles_trust_policy=@{}", policy.display()));
}
// The front's own time sources (Gunnar's kit, R10): the estate's root of time.
if declared("clock") {
out.push(format!("clock={FRONT_CLOCK_SOURCES}"));
}
out
}
pub fn run() -> Result<()> {
let args: Vec<String> = std::env::args().skip(2).collect();
run_with(&parse(&args)?, &crate::workspace_root())
}
fn run_with(o: &Opts, root: &Path) -> Result<()> {
let receipt = o.media_to.join(receipt_name(&o.kind));
println!(
"estate-media {}--where {} --{}: {} media and {} live apps → {} (--opt {})",
if o.mock { "mock " } else { "" },
o.place,
o.kind,
MEDIA.len(),
MEDIA.iter().filter(|m| m.live).count(),
receipt.display(),
forge_opts(&o.kind).join(" ")
);
for m in MEDIA {
forge_door::declared_medium(
m.variant,
ArtifactKind::Installer,
"cargo xtask installer-iso",
)?;
let row = payload_row(m.variant, &o.kind)?;
println!(
" {:<18} ← {}{} (payload {}, profile {})",
m.word,
m.variant,
if m.live {
format!(" (+ {})", live_kind(m.word))
} else {
String::new()
},
row.name,
row.profile.label()
);
}
if !o.apply {
println!(
" PLAN ONLY — `--apply` composes the media, uploads them and writes the receipt."
);
return Ok(());
}
// [holger-no-mock-proof] (lane GATE): RELEASE media reach the account's bucket only after
// a green gate (DEV media are test2's and need none, owner 2026-11-03), for EVERY place (lane B4: `--where kvm` without `mock` uploads to the account's bucket too),
// and only from clean trees at the commits the gate proved.
if needs_gate(o) {
let pins = gate_pins(root);
if let Some(why) = gate_refusal(&root.join(GATE_RECEIPTS), &commits_of(&pins)) {
bail!("{why}");
}
pins_refusal(
&pins,
std::env::var(forge_door::DWARVES_BIN_ENV).ok().as_deref(),
o.composed,
)?;
}
// A pointer is never composed nor uploaded: composed here or already (`--composed`).
refuse_lfs_pointers(root)?;
// The front medium carries holger's console, or nothing is composed (stinky SB #48),
// and it is the console of the holger-ui the media pin (field test F11).
refuse_front_without_console(
&front_console_dist(
root,
std::env::var("HOLGER_FRONT_CONSOLE_DIST").ok().as_deref(),
),
holger_ui_head(root).as_deref(),
)?;
let bucket = if o.mock {
mock_bucket(mock_api_port(
std::env::var(MOCK_PORTS_ENV).ok().as_deref(),
)?)
} else {
let path = o.secrets.clone().or_else(|| std::env::var_os(SECRETS_ENV).filter(|v| !v.is_empty()).map(PathBuf::from)).or_else(|| std::env::var_os("HOME").map(|h| PathBuf::from(h).join("holger_secrets"))).context("[estate-media-bucket] name the vault file: --secrets <file> or $HOLGER_RELEASE_SECRETS")?;
bucket_of(
&std::fs::read_to_string(&path)
.with_context(|| format!("[estate-media-bucket] {}", path.display()))?,
)?
};
// The metadata seed marker every seedless medium is composed with, and the estate's
// server declaration (lane W2): every estate's main and twin boot the same one.
let staged = Staged::write(&root.join(".iso-out"))?;
let staged = match &o.intel_trust {
Some(policy) => staged.with_intel_trust(policy)?,
None => {
println!(" note intel trust none given: the box media carry the slot empty, an intel import is refused (holger-import-4) until a medium carries the estate's policy (--intel-trust)");
staged
}
};
let staged = match &o.bundles_trust {
Some(policy) => staged.with_bundles_trust(policy)?,
None => {
println!(" note bundles trust none given: the box media carry the slot empty, a PUT into the bundles door is refused (holger-bundles-1) until a medium carries its policy (--bundles-trust)");
staged
}
};
let mut objects = BTreeMap::new();
let mut live = BTreeMap::new();
for m in MEDIA {
let suite = forge_door::declared_medium(
m.variant,
ArtifactKind::Installer,
"cargo xtask installer-iso",
)?;
let iso = if o.composed {
println!(" note {:<18} the composed medium's narrate kind is its composer's: a {} medium is composed with --opt {}", m.word, o.kind, forge_opts(&o.kind).join(" "));
composed_medium(root, m.word, m.variant, &o.kind)?
} else {
let mut params = forge_params(m.variant, &staged);
params.extend(live_update_params(o.live_update));
// The kernel is built IN THE CHAIN (`--build kernel`, as the ONE box kit's medium does):
// a medium's kernel requirements reach the kernel only through its consumer's chained
// build, and a standalone kernel is refused at compose (lane GATE, 2026-10-21).
let ask = forge_door::Ask {
variant: Some(m.variant.to_string()),
params,
opts: forge_opts(&o.kind),
builds: vec!["kernel".to_string()],
..Default::default()
};
let iso = forge_door::run("estate-media", root, &suite, m.variant, &ask)?;
// The forge refuses a kernel without the CD block itself (dwarves preflight); its
// record is read back too, so a forge from before that preflight cannot slip one by.
let rec = iso.with_extension("medium.json");
composed_reads_its_cd(
m.word,
&std::fs::read_to_string(&rec).with_context(|| {
format!("[medium-no-cd-driver] {}: the medium record", rec.display())
})?,
)?;
held_to_kind(
m.word,
&o.kind,
&std::fs::read(&iso).with_context(|| format!("{}", iso.display()))?,
)?;
iso
};
// Read the live record BEFORE any upload: an unsigned app medium is refused whole.
let rec = if m.live {
let (rec_path, app_path) = live_companions(&iso);
let text = std::fs::read_to_string(&rec_path).with_context(|| {
format!(
"[estate-media-live-record] {}: korp's composer writes it beside the medium",
rec_path.display()
)
})?;
let (bundle, sig) = release_companions(&iso);
Some((
with_release(
m.word,
live_record(m.word, &text)?,
std::fs::read(&bundle).ok(),
std::fs::read(&sig).ok(),
)?,
app_path,
))
} else {
None
};
let sha = sha256_hex(&std::fs::read(&iso)?);
let o = put_file(&bucket, &medium_key(m.word, &sha), &iso)?;
println!(" ok {:<18} → {} ({} bytes)", m.word, o.url, o.bytes);
objects.insert(m.word.to_string(), o);
if let Some((r, app_path)) = rec {
let o = put_file(&bucket, &live_key(m.word, &r.app), &app_path)?;
if o.bytes != r.app_len {
bail!(
"[estate-media-live-record] the {} archive is {} bytes, its record says {}",
live_kind(m.word),
o.bytes,
r.app_len
);
}
println!(
" ok {:<18} → {} (pin {})",
live_kind(m.word),
o.url,
&r.app[..12]
);
objects.insert(live_kind(m.word), o);
live.insert(m.word.to_string(), r);
}
}
let text = receipt_text(
&o.kind,
&objects,
&live,
&release_label(root),
&utc_date(),
&release_carries(root),
)?;
std::fs::create_dir_all(&o.media_to).with_context(|| format!("{}", o.media_to.display()))?;
std::fs::write(&receipt, text).with_context(|| format!("{}", receipt.display()))?;
println!(
"✅ estate-media: {} names {} objects and {} live apps",
receipt.display(),
objects.len(),
live.len()
);
Ok(())
}
/// **Where the end-to-end gate's receipts are** (lane GATE, 2026-10-20): the ONE box kit's
/// `cargo xtask gate holger --where kvm --box test --apply` (private-monetize-ops) writes
/// `holger-gate-<holger>-<edda>-<Skidbladnir>.json` here after a 3-box mock lay through the
/// real ONE box path. `estate-media --where upcloud --apply` refuses without a GREEN one for
/// exactly this checkout's commits — Gunnar's `[install-no-mock-proof]`, Holger's words.
pub const GATE_RECEIPTS: &str = ".mock-receipts";
/// The repositories whose HEADs a gate receipt names (the media are composed from them):
/// holger, edda and Skidbladnir, and (lane B4, A9 row 11) tunnr — the kernel, korp-installer
/// and korp-start — and dwarves, the composer.
pub const GATE_REPOS: [&str; 5] = ["holger", "edda", "Skidbladnir", "tunnr", "dwarves"];
/// Pure: **an upload that needs the gate** — a RELEASE upload to every bucket but the mock's
/// (lane B4: an account upload with `--where kvm` skipped it). DEV media are test2's (the
/// test2.* sites run the dev kind) and need no gate (owner 2026-11-03: "I PAY for
/// test2.monetize.ignalina.dk FOR TEST. The rule is meant for prod monetize.ignalina.dk.").
pub fn needs_gate(o: &Opts) -> bool {
// Fail-closed: every kind but DEV (release, or any other) needs the gate.
!o.mock && o.kind != KIND_DEV
}
/// Where each of [`GATE_REPOS`] is checked out: holger here, dwarves at `$DWARVES_ROOT`
/// (the forge's own, [`forge_door`]) else beside it, the others beside it.
pub fn gate_repo_dir(root: &Path, repo: &str) -> PathBuf {
let beside = || root.parent().map(|p| p.join(repo)).unwrap_or_default();
match repo {
"holger" => root.to_path_buf(),
"dwarves" => std::env::var_os(forge_door::DWARVES_ROOT_ENV)
.filter(|v| !v.is_empty())
.map(PathBuf::from)
.unwrap_or_else(beside),
_ => beside(),
}
}
/// **The pin of each of [`GATE_REPOS`]**: its full HEAD and its dirty paths, untracked files
/// included (nornir-build-thing `media_gate::pin_of`).
pub fn gate_pins(root: &Path) -> Vec<nornir_build_thing::media_gate::RepoPin> {
GATE_REPOS
.iter()
.map(|r| nornir_build_thing::media_gate::pin_of(r, &gate_repo_dir(root, r)))
.collect()
}
/// Pure: the commits of `pins`, by repository.
pub fn commits_of(pins: &[nornir_build_thing::media_gate::RepoPin]) -> BTreeMap<String, String> {
pins.iter()
.map(|p| (p.repo.clone(), p.head.clone()))
.collect()
}
/// Pure: **the trees an account upload is composed from are pinned** — clean checkouts,
/// untracked files included (`[media-dirty-tree]`, `[media-not-a-checkout]`), and a forge
/// binary named by `$DWARVES_BIN` is refused when this upload composes: its commit is not the
/// dwarves checkout the gate pinned (`[estate-media-dwarves-unpinned]`).
pub fn pins_refusal(
pins: &[nornir_build_thing::media_gate::RepoPin],
dwarves_bin: Option<&str>,
composed: bool,
) -> Result<()> {
nornir_build_thing::media_gate::refuse_unpinned(pins)?;
if let Some(bin) = dwarves_bin.filter(|b| !b.trim().is_empty() && !composed) {
bail!("[estate-media-dwarves-unpinned] ${} = {bin}: an account upload composes with the dwarves checkout the gate pinned ({}), never a prebuilt forge — unset it, or upload the gate's own media with --composed", forge_door::DWARVES_BIN_ENV, forge_door::DWARVES_ROOT_ENV);
}
Ok(())
}
/// Pure: the receipt file a gate of these commits writes.
pub fn gate_receipt_name(commits: &BTreeMap<String, String>) -> String {
let short = |r: &str| {
commits
.get(r)
.map(|h| h.chars().take(12).collect::<String>())
.unwrap_or_default()
};
format!(
"holger-gate-{}-{}-{}.json",
short("holger"),
short("edda"),
short("Skidbladnir")
)
}
/// **`[holger-no-mock-proof]`**, or `None` when `dir` holds a GREEN gate receipt naming exactly
/// `commits` (`"green": true`, `"clean": true`, `"commits"` of every [`GATE_REPOS`] = these
/// full HEADs).
pub fn gate_refusal(dir: &Path, commits: &BTreeMap<String, String>) -> Option<String> {
let refuse = |w: String| {
Some(format!("REFUSED [holger-no-mock-proof] {w} — run the ONE box kit's end-to-end gate on these commits first (private-monetize-ops: `cargo xtask gate holger --where kvm --box test --apply`)"))
};
if let Some(r) = GATE_REPOS
.iter()
.find(|r| commits.get(**r).is_none_or(|h| h.len() < 40))
{
return refuse(format!(
"{r} is not a checkout here, so no gate can name its commit"
));
}
let file = dir.join(gate_receipt_name(commits));
let Ok(text) = std::fs::read_to_string(&file) else {
return refuse(format!(
"no gate receipt {} for holger {}, edda {}, Skidbladnir {}",
file.display(),
&commits["holger"][..12],
&commits["edda"][..12],
&commits["Skidbladnir"][..12]
));
};
let Ok(v) = serde_json::from_str::<serde_json::Value>(&text) else {
return refuse(format!("{} is not a gate receipt (JSON)", file.display()));
};
if let Some(r) = GATE_REPOS
.iter()
.find(|r| v["commits"][**r].as_str() != commits.get(**r).map(String::as_str))
{
return refuse(format!(
"{} names another {r} commit than this checkout's",
file.display()
));
}
if v["clean"].as_bool() != Some(true) {
return refuse(format!(
"{} does not say the gate ran over clean trees (`\"clean\": true`)",
file.display()
));
}
if v["green"].as_bool() != Some(true) {
let red: Vec<String> = v["checks"]
.as_array()
.map(|a| {
a.iter()
.filter(|c| c["pass"].as_bool() != Some(true))
.filter_map(|c| c["name"].as_str().map(str::to_string))
.collect()
})
.unwrap_or_default();
return refuse(format!(
"the gate of these commits is RED ({}): {}",
file.display(),
if red.is_empty() {
"not green".into()
} else {
red.join(", ")
}
));
}
None
}
/// Where the front's build reads holger's built console from — the same rule as
/// `holger-estate-front/build.rs` under `metered` (every front row is metered):
/// `HOLGER_FRONT_CONSOLE_DIST`, else the declared sibling `../holger-ui/dist-metered`
/// beside this checkout, holger-ui's PAID bundle (A28 M13; its open `dist/` carries no
/// sign-up, welcome or order page).
pub fn front_console_dist(root: &Path, env: Option<&str>) -> PathBuf {
match env.map(str::trim).filter(|v| !v.is_empty()) {
Some(v) => PathBuf::from(v),
None => root.parent().unwrap_or(root).join("holger-ui/dist-metered"),
}
}
/// **A front medium needs holger's console** (stinky SB #48 — the ONE-box kit's
/// no-console rule, in Holger's media door). `holger-estate-front`'s build only
/// WARNS when holger-ui's `dist/` is absent (its tests build without it) and the
/// front it ships then serves the console's API and `/login` but no console. So
/// every media run refuses first, by name, unless `dist` holds a built console: a
/// non-empty `index.html` that is not a Git LFS pointer, and the `.wasm` bundle
/// beside it. Build it with holger-ui's `cargo xtask build --variant mvp-ui-web-metered` (trunk).
///
/// **And it is the console of the pinned holger-ui** (lane hol-312fix, field test F11: 3.1.0
/// and 3.1.1 shipped the 3.0.0 console, a build left from an older run). holger-ui's trunk
/// rows write `BUILT_FROM`, the commit they built, beside `index.html`. A console with no
/// `BUILT_FROM`, or one that names another commit than `ui_head` (the HEAD of the holger-ui
/// checkout beside this one, the commit the media pin), is refused
/// (`[estate-media-front-stale-console]`). With no holger-ui checkout to compare with, the
/// console must still name its commit.
pub fn refuse_front_without_console(dist: &Path, ui_head: Option<&str>) -> Result<()> {
let index = dist.join("index.html");
let fix = "build holger-ui's metered console first (`cargo xtask build --variant mvp-ui-web-metered` in holger-ui, trunk --release) or name a built one in HOLGER_FRONT_CONSOLE_DIST";
let html = std::fs::read(&index).map_err(|e| anyhow::anyhow!("REFUSED [estate-media-front-no-console] the front medium would carry no holger-ui console: {} ({e}); {fix}", index.display()))?;
if html.iter().all(u8::is_ascii_whitespace) {
bail!("REFUSED [estate-media-front-no-console] {} is EMPTY: the front medium would carry no console; {fix}", index.display());
}
if html.starts_with(b"version https://git-lfs") {
bail!("REFUSED [estate-media-front-no-console] {} is a Git LFS pointer, not the console; {fix}", index.display());
}
let wasm = std::fs::read_dir(dist)
.map(|rd| {
rd.flatten()
.any(|e| e.path().extension().is_some_and(|x| x == "wasm"))
})
.unwrap_or(false);
if !wasm {
bail!("REFUSED [estate-media-front-no-console] {} holds an index.html and no .wasm bundle: a half-built console; {fix}", dist.display());
}
let stamp = dist.join(CONSOLE_BUILT_FROM);
let built = std::fs::read_to_string(&stamp)
.ok()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty());
let rebuild = "rebuild it from the pinned holger-ui (`cargo xtask build --variant mvp-ui-web-metered` in that checkout writes BUILT_FROM)";
match (built, ui_head) {
(None, _) => bail!("REFUSED [estate-media-front-stale-console] the console in {} names no holger-ui commit it was built from (no {CONSOLE_BUILT_FROM}): an old build; {rebuild}", dist.display()),
(Some(b), Some(h)) if b != h => bail!("REFUSED [estate-media-front-stale-console] the console in {} was built from holger-ui {b}, and the media pin holger-ui {h}; {rebuild}", dist.display()),
_ => Ok(()),
}
}
/// The stamp holger-ui's console builds write beside `index.html`: the full holger-ui commit
/// the bundle was built from (holger-ui `xtask` `BUILT_FROM`).
pub const CONSOLE_BUILT_FROM: &str = "BUILT_FROM";
/// The full HEAD commit of the holger-ui checkout the media compose from, if there is one.
pub fn holger_ui_head(root: &Path) -> Option<String> {
let o = std::process::Command::new("git")
.arg("-C")
.arg(gate_repo_dir(root, "holger-ui"))
.args(["rev-parse", "HEAD"])
.output()
.ok()?;
let h = String::from_utf8_lossy(&o.stdout).trim().to_string();
(o.status.success() && h.len() == 40).then_some(h)
}
/// **Every Git LFS pointer in what a medium packs, refused by name** (lane D13, 2026-10-20:
/// test3.holger.rs served holger.webp as a 131-byte pointer), over this checkout and every
/// sibling a medium's row mounts. The walk is edda's `nornir_build_thing::lfs` since lane G10
/// (so Gunnar's media refuse it too: dwarves' compose calls it for every installer cell);
/// this door keeps calling it, so an upload of `--composed` media is held as well.
pub fn refuse_lfs_pointers(root: &Path) -> Result<()> {
let mut roots = vec![root.to_path_buf()];
if let Some(up) = root.parent() {
roots.extend(
holger_recipe::front_siblings()
.iter()
.map(|s| up.join(s))
.filter(|p| p.is_dir()),
);
}
nornir_build_thing::lfs::refuse_lfs_pointers(&roots, &[]).context("[estate-media-lfs-pointer]")
}
#[cfg(test)]
mod tests {
/// ★ **A front medium without holger's console is refused by name** (stinky SB
/// #48, the ONE-box kit's no-console rule): the front's build only WARNS when
/// holger-ui's built `dist/` is absent and ships a front that serves no console,
/// so the media door refuses first — no `index.html`, an empty one, an LFS
/// pointer, or no `.wasm` bundle beside it.
#[test]
fn a_front_medium_without_the_console_is_refused() {
let t = std::env::temp_dir().join(format!(
"sb48-console-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
let (root, dist) = (t.join("holger"), t.join("holger-ui/dist-metered"));
std::fs::create_dir_all(&root).unwrap();
assert_eq!(super::front_console_dist(&root, None), dist, "the declared sibling, as the front's build.rs reads it under `metered`: the PAID console (A28 M13), never the open `dist/`");
assert_eq!(
super::front_console_dist(&root, Some("/x/dist")),
std::path::PathBuf::from("/x/dist"),
"HOLGER_FRONT_CONSOLE_DIST wins"
);
let e = super::refuse_front_without_console(&dist, Some(UI_HEAD))
.unwrap_err()
.to_string();
assert!(
e.contains("[estate-media-front-no-console]") && e.contains("holger-ui"),
"{e}"
);
std::fs::create_dir_all(&dist).unwrap();
std::fs::write(dist.join("index.html"), "").unwrap();
assert!(
super::refuse_front_without_console(&dist, Some(UI_HEAD)).is_err(),
"an empty index.html is no console"
);
std::fs::write(
dist.join("index.html"),
"<!doctype html><title>holger</title>",
)
.unwrap();
let e = super::refuse_front_without_console(&dist, Some(UI_HEAD))
.unwrap_err()
.to_string();
assert!(e.contains(".wasm"), "{e}");
std::fs::write(dist.join("holger-ui-web-1_bg.wasm"), b"\0asm\x01\0\0\0").unwrap();
std::fs::write(dist.join("BUILT_FROM"), format!("{UI_HEAD}\n")).unwrap();
super::refuse_front_without_console(&dist, Some(UI_HEAD)).expect("a built console");
std::fs::write(
dist.join("index.html"),
"version https://git-lfs.github.com/spec/v1\noid sha256:00\nsize 1\n",
)
.unwrap();
assert!(
super::refuse_front_without_console(&dist, Some(UI_HEAD)).is_err(),
"an LFS pointer is no console"
);
let _ = std::fs::remove_dir_all(&t);
}
/// The holger-ui commit the media of these tests pin.
const UI_HEAD: &str = "da876d0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
/// ★ **A front medium carries the console built from the pinned holger-ui commit**
/// (lane hol-312fix, field test F11: the shipped console was the 3.0.0 bundle in 3.1.0 and
/// 3.1.1, a build left over from an older run). holger-ui's trunk rows write
/// `BUILT_FROM` (the commit they built) beside `index.html`; the media door refuses a
/// console with none, or one built from another commit than the holger-ui the media pin.
#[test]
fn a_front_console_not_built_from_the_pinned_holger_ui_is_refused() {
let dist = std::env::temp_dir().join(format!(
"f11-console-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::create_dir_all(&dist).unwrap();
std::fs::write(
dist.join("index.html"),
"<!doctype html><title>holger</title>",
)
.unwrap();
std::fs::write(dist.join("holger-ui-web-1_bg.wasm"), b"\0asm\x01\0\0\0").unwrap();
// No stamp: an old build, of no known commit.
let e = super::refuse_front_without_console(&dist, Some(UI_HEAD))
.unwrap_err()
.to_string();
assert!(
e.contains("[estate-media-front-stale-console]") && e.contains("BUILT_FROM"),
"{e}"
);
// Built from another commit than the pinned holger-ui.
let old = "2f41276781c2aaaaaaaaaaaaaaaaaaaaaaaaaaaa";
std::fs::write(dist.join("BUILT_FROM"), format!("{old}\n")).unwrap();
let e = super::refuse_front_without_console(&dist, Some(UI_HEAD))
.unwrap_err()
.to_string();
assert!(
e.contains("[estate-media-front-stale-console]")
&& e.contains(old)
&& e.contains(UI_HEAD),
"{e}"
);
// Built from the pinned commit: carried.
std::fs::write(dist.join("BUILT_FROM"), format!("{UI_HEAD}\n")).unwrap();
super::refuse_front_without_console(&dist, Some(UI_HEAD)).expect("the pinned console");
// A console named in HOLGER_FRONT_CONSOLE_DIST with no holger-ui checkout to pin it
// still names a commit.
super::refuse_front_without_console(&dist, None).expect("a stamped console");
std::fs::remove_file(dist.join("BUILT_FROM")).unwrap();
assert!(super::refuse_front_without_console(&dist, None).is_err());
let _ = std::fs::remove_dir_all(&dist);
}
/// The release directory is korp's (lane B1): korp-start lays the medium's release
/// declaration there, and the shipped declaration names its korp root under it.
#[test]
fn the_release_root_is_korp_starts() {
assert_eq!(
holger_recipe::estate_ron::RELEASE_ROOT,
korp_installer::release::RUN_DIR
);
}
/// ★ Owner 2026-11-03: "I PAY for test2.monetize.ignalina.dk FOR TEST. The rule is meant
/// for prod." DEV media are test2's (test2.* sites run the dev kind); only RELEASE media,
/// holger.rs's, need the gate.
#[test]
fn a_test2_dev_upload_needs_no_mock_proof() {
let o = |words: &str| {
parse(
&words
.split_whitespace()
.map(str::to_string)
.collect::<Vec<_>>(),
)
.unwrap()
};
assert!(!needs_gate(&o("--where upcloud --media-to /r --apply")));
assert!(!needs_gate(&o(
"--where upcloud --dev --media-to /r --apply"
)));
assert!(needs_gate(&o(
"--where upcloud --release --media-to /r --apply"
)));
let mut other = o("--where upcloud --media-to /r --apply");
other.kind = "nightly".into();
assert!(
needs_gate(&other),
"fail-closed: a kind that is not DEV needs the gate"
);
}
/// ★ **Every RELEASE upload but the mock's needs the gate** (lane B4, A9 row 11): `--where kvm`
/// without `mock` uploads to the account's bucket, and skipped the gate.
#[test]
fn an_upload_to_any_bucket_but_the_mocks_needs_the_gate() {
let o = |words: &str| {
parse(
&words
.split_whitespace()
.map(str::to_string)
.collect::<Vec<_>>(),
)
.unwrap()
};
assert!(
needs_gate(&o("--where kvm --release --media-to /r --apply")),
"kvm without mock is the account's bucket"
);
assert!(needs_gate(&o(
"--where upcloud --release --media-to /r --apply"
)));
assert!(
!needs_gate(&o("mock --where kvm --release --media-to /r --apply")),
"the mock's bench bucket is the gate's own"
);
let root = std::env::temp_dir().join(format!("b4-kvm-gate-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(root.join("holger")).unwrap();
let mut kvm = o("--where kvm --release --media-to /r --apply");
kvm.secrets = Some(root.join("no-vault"));
let e = format!("{:#}", run_with(&kvm, &root.join("holger")).unwrap_err());
assert!(e.contains("[holger-no-mock-proof]"), "{e}");
let _ = std::fs::remove_dir_all(&root);
}
/// ★ **The gate receipt pins tunnr and dwarves, and a clean run** (lane B4): a receipt that
/// names another tunnr or dwarves, or does not say its trees were clean, is refused.
#[test]
fn the_gate_receipt_pins_tunnr_and_dwarves_and_clean_trees() {
assert!(
GATE_REPOS.contains(&"tunnr") && GATE_REPOS.contains(&"dwarves"),
"{GATE_REPOS:?}"
);
let dir = std::env::temp_dir().join(format!("b4-gate-pins-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let c: BTreeMap<String, String> = GATE_REPOS
.iter()
.enumerate()
.map(|(i, r)| {
(
r.to_string(),
char::from(b'a' + i as u8).to_string().repeat(40),
)
})
.collect();
let write = |tunnr: &str, clean: Option<bool>| {
let mut commits = serde_json::to_value(&c).unwrap();
commits["tunnr"] = serde_json::json!(tunnr);
let mut v = serde_json::json!({"green": true, "commits": commits, "checks": []});
if let Some(clean) = clean {
v["clean"] = serde_json::json!(clean);
}
std::fs::write(dir.join(gate_receipt_name(&c)), v.to_string()).unwrap();
};
write(&"9".repeat(40), Some(true));
assert!(gate_refusal(&dir, &c)
.unwrap()
.contains("another tunnr commit"));
write(&c["tunnr"], None);
assert!(
gate_refusal(&dir, &c).unwrap().contains("clean trees"),
"a receipt that does not say its trees were clean"
);
write(&c["tunnr"], Some(false));
assert!(gate_refusal(&dir, &c).unwrap().contains("clean trees"));
write(&c["tunnr"], Some(true));
assert_eq!(gate_refusal(&dir, &c), None);
let mut no_dwarves = c.clone();
no_dwarves.insert("dwarves".into(), String::new());
assert!(gate_refusal(&dir, &no_dwarves)
.unwrap()
.contains("dwarves is not a checkout"));
let _ = std::fs::remove_dir_all(&dir);
}
/// ★ **An account upload is composed from clean pinned trees** (lane B4): a dirty tree is
/// refused by name, and so is a prebuilt forge when this upload composes.
#[test]
fn a_dirty_tree_or_an_unpinned_forge_is_refused_before_an_account_upload() {
use nornir_build_thing::media_gate::RepoPin;
let pin = |repo: &str, dirty: &[&str]| RepoPin {
repo: repo.into(),
head: "a".repeat(40),
dirty: dirty.iter().map(|d| d.to_string()).collect(),
};
let clean: Vec<RepoPin> = GATE_REPOS.iter().map(|r| pin(r, &[])).collect();
assert!(pins_refusal(&clean, None, false).is_ok());
let mut dirty = clean.clone();
dirty[3] = pin("tunnr", &["crates/korp-installer/src/new.rs"]);
let e = format!("{:#}", pins_refusal(&dirty, None, false).unwrap_err());
assert!(
e.contains("[media-dirty-tree]")
&& e.contains("tunnr (crates/korp-installer/src/new.rs)"),
"{e}"
);
let e = format!(
"{:#}",
pins_refusal(&clean, Some("/opt/dwarves"), false).unwrap_err()
);
assert!(e.contains("[estate-media-dwarves-unpinned]"), "{e}");
assert!(
pins_refusal(&clean, Some("/opt/dwarves"), true).is_ok(),
"--composed runs no forge"
);
}
/// ★ **The kind is in the medium and in the receipt** (lane B4, A9 row 10): the DEV option
/// bakes it onto every medium, a medium of another kind is refused by name, and the
/// receipt says it as data.
#[test]
fn the_kind_is_recorded_in_the_medium_and_the_receipt() {
let opt = holger_recipe::packaging::dev_narrate_option();
assert_eq!(
opt.bakes,
Some(nornir_build_thing::media_gate::kind_bake()),
"the dev-narrate option bakes the kind"
);
for m in MEDIA {
let cell = holger_recipe::declared()
.installers
.into_iter()
.find(|i| i.name == m.variant)
.unwrap();
assert!(
cell.options.iter().any(|o| o.bakes == opt.bakes),
"{} records its kind",
m.word
);
}
let dev = b"console=ttyS0 korp.env.NORDISK_MEDIA_KIND=dev korp.live_update=on";
held_to_kind("holger", KIND_DEV, dev).unwrap();
let e = format!(
"{:#}",
held_to_kind("holger", KIND_RELEASE, dev).unwrap_err()
);
assert!(
e.contains("[media-kind-mismatch]") && e.contains("holger"),
"{e}"
);
assert!(format!(
"{:#}",
held_to_kind("holger", KIND_RELEASE, b"console=ttyS0").unwrap_err()
)
.contains("[media-kind-unrecorded]"));
let obj = Object {
url: "https://bucket.example/release/holger/1".into(),
sha256: "1".repeat(64),
bytes: 9,
};
let objects = BTreeMap::from([("holger".to_string(), obj)]);
for (kind, narrated) in [(KIND_DEV, true), (KIND_RELEASE, false)] {
let t: toml::Table = toml::from_str(
&receipt_text(
kind,
&objects,
&BTreeMap::new(),
"holger",
"2026-10-21",
&BTreeMap::new(),
)
.unwrap(),
)
.unwrap();
assert_eq!(
(
t[RECEIPT_BUILD]["kind"].as_str(),
t[RECEIPT_BUILD]["dev_narrate"].as_bool()
),
(Some(kind), Some(narrated))
);
}
}
/// ★ `[holger-no-mock-proof]` (lane GATE): no receipt, a red one, or one of other commits
/// refuses by name; a green one of exactly these commits lets the account's bucket be used.
#[test]
fn the_account_upload_needs_a_green_gate_receipt_of_these_commits() {
let dir = std::env::temp_dir().join(format!("holger-gate-receipt-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let c: BTreeMap<String, String> = GATE_REPOS
.iter()
.enumerate()
.map(|(i, r)| {
(
r.to_string(),
char::from(b'a' + i as u8).to_string().repeat(40),
)
})
.collect();
assert_eq!(
gate_receipt_name(&c),
format!(
"holger-gate-{}-{}-{}.json",
"a".repeat(12),
"b".repeat(12),
"c".repeat(12)
)
);
assert!(gate_refusal(&dir, &c)
.unwrap()
.contains("[holger-no-mock-proof] no gate receipt"));
let write = |green: bool, holger: &str| {
let mut commits = serde_json::to_value(&c).unwrap();
commits["holger"] = serde_json::json!(holger);
std::fs::write(dir.join(gate_receipt_name(&c)), serde_json::json!({"green": green, "clean": true, "commits": commits, "checks": [{"name": "clock", "pass": false}]}).to_string()).unwrap()
};
write(false, &"a".repeat(40));
let red = gate_refusal(&dir, &c).unwrap();
assert!(red.contains("is RED") && red.contains("clock"), "{red}");
write(true, &"d".repeat(40));
assert!(gate_refusal(&dir, &c)
.unwrap()
.contains("another holger commit"));
write(true, &"a".repeat(40));
assert_eq!(gate_refusal(&dir, &c), None, "green, these commits");
let mut no_edda = c.clone();
no_edda.insert("edda".into(), String::new());
assert!(gate_refusal(&dir, &no_edda)
.unwrap()
.contains("edda is not a checkout"));
let _ = std::fs::remove_dir_all(&dir);
}
/// **A Git LFS pointer is refused by name** (lane D13): the picture the front serves, a
/// pointer in a sibling, nothing from `target/` or `docs/`, and real content passes.
#[test]
fn a_git_lfs_pointer_in_what_a_medium_packs_is_named() {
let t = std::env::temp_dir().join(format!(
"d13-lfs-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
let (holger, ui) = (t.join("holger"), t.join("holger-ui"));
let pointer =
b"version https://git-lfs.github.com/spec/v1\noid sha256:a8bcf252d5\nsize 578646\n";
for (dir, name, bytes) in [
(&holger, "holger-front/assets/holger.webp", &pointer[..]),
(
&holger,
"holger-front/assets/real.png",
&b"\x89PNG\r\n\x1a\n...."[..],
),
(&holger, "target/release/build/x/pointer.webp", &pointer[..]),
(&holger, "docs/manual.pdf", &pointer[..]),
(&ui, "assets/logo.png", &pointer[..]),
] {
let p = dir.join(name);
std::fs::create_dir_all(p.parent().unwrap()).unwrap();
std::fs::write(p, bytes).unwrap();
}
let mut found = nornir_build_thing::lfs::lfs_pointers(&[holger.clone(), ui.clone()], &[]);
found.sort();
assert_eq!(
found,
vec![
holger.join("holger-front/assets/holger.webp"),
ui.join("assets/logo.png")
]
);
let e = format!("{:#}", refuse_lfs_pointers(&holger).unwrap_err());
assert!(
e.contains("[estate-media-lfs-pointer]")
&& e.contains("holger.webp")
&& e.contains("git lfs pull"),
"{e}"
);
std::fs::write(
holger.join("holger-front/assets/holger.webp"),
b"RIFF....WEBPVP8L",
)
.unwrap();
std::fs::write(ui.join("assets/logo.png"), b"\x89PNG").unwrap();
refuse_lfs_pointers(&holger).expect("real content passes");
let _ = std::fs::remove_dir_all(&t);
}
use super::*;
fn s(v: &str) -> Vec<String> {
v.split_whitespace().map(String::from).collect()
}
const H: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
const B: &str = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
fn rec(sig: &str) -> String {
format!("base = \"{B}\"\napp = \"{H}\"\napp_len = 9\nsig = \"{sig}\"\n")
}
/// ★ The ONE kit's words (pmo `[products.holger.media]`) are this verb's words.
#[test]
fn the_one_kits_argv_is_understood_and_strays_are_refused() {
let o = parse(&s("mock --where upcloud --release --media-to /r --apply")).unwrap();
assert_eq!(
o,
Opts {
mock: true,
place: "upcloud".into(),
kind: "release".into(),
media_to: "/r".into(),
apply: true,
secrets: None,
composed: false,
live_update: true,
intel_trust: None,
bundles_trust: None
}
);
assert_eq!(
parse(&s(
"--where upcloud --media-to /r --intel-trust /p/intel-trust.ron"
))
.unwrap()
.intel_trust,
Some(PathBuf::from("/p/intel-trust.ron")),
"an airgap estate's intel signers"
);
assert!(
parse(&s(
"--where upcloud --release --media-to /r --apply --composed"
))
.unwrap()
.composed
);
let o = parse(&s("--where upcloud --dev --media-to /r")).unwrap();
assert!(!o.mock && !o.apply && o.kind == "dev");
assert_eq!(receipt_name(&o.kind), "media-holger-dev.toml");
assert_eq!(
receipt_name("release"),
"media-holger-release.toml",
"pmo reads media-holger-release.toml"
);
assert_eq!(
parse(&s("--where upcloud --media-to /r")).unwrap().kind,
"dev",
"DEV unless --release is said (owner 2026-10-19)"
);
assert!(parse(&s("--where aws --release --media-to /r"))
.unwrap_err()
.to_string()
.contains("aws"));
assert!(parse(&s("--where upcloud --release"))
.unwrap_err()
.to_string()
.contains("--media-to"));
assert!(parse(&s("--where upcloud --release --media-to /r --dev"))
.unwrap_err()
.to_string()
.contains("not both"));
assert!(
parse(&s("--where upcloud --release --media-to /r --variant x"))
.unwrap_err()
.to_string()
.contains("unknown word")
);
}
/// ★ **The estate's live-update switch reaches every medium** (lane M5): `--live-update off`
/// composes `live_app=off` (korp.live_update=off on the cmdline), absent is on, a stray word
/// is refused, and the compose path asks with it.
#[test]
fn the_estates_live_update_switch_reaches_the_media() {
assert!(
!parse(&s(
"--where upcloud --release --media-to /r --live-update off"
))
.unwrap()
.live_update
);
assert!(
parse(&s(
"--where upcloud --release --media-to /r --live-update on"
))
.unwrap()
.live_update
);
assert!(
parse(&s("--where upcloud --release --media-to /r"))
.unwrap()
.live_update,
"absent: on"
);
assert!(parse(&s(
"--where upcloud --release --media-to /r --live-update maybe"
))
.unwrap_err()
.to_string()
.contains("`on` or `off`"));
assert_eq!(live_update_params(false), ["live_app=off"]);
assert_eq!(live_update_params(true), ["live_app=on"]);
for m in MEDIA {
let i = holger_recipe::declared()
.installers
.into_iter()
.find(|i| i.name == m.variant)
.unwrap();
assert!(
i.params
.iter()
.any(|p| p.name == "live_app" && p.boot_param == "korp.live_update"),
"{} declares the switch",
m.variant
);
}
let src = include_str!("estate_media.rs");
assert_eq!(
src.matches(&["params.extend(live_update_params(", "o.live_update));"].concat())
.count(),
1,
"the compose asks with the switch"
);
}
/// ★ Owner 2026-10-19: Holger's media are DEV unless `--release` is said — DEV composes with
/// the narrate sender ON (Gunnar's `--dev-narrate`), `--release` is `--no-dev-narrate`, and
/// the receipt says which kind it is.
#[test]
fn dev_is_the_default_and_narrates_and_release_compiles_the_sender_out() {
let dev = parse(&s("--where upcloud --media-to /r --apply")).unwrap();
assert_eq!(dev.kind, KIND_DEV);
assert!(narrates(&dev.kind));
assert_eq!(forge_opts(&dev.kind), ["dev-narrate=on"]);
assert_eq!(
parse(&s("--where upcloud --dev-narrate --media-to /r"))
.unwrap()
.kind,
KIND_DEV
);
let rel = parse(&s("--where upcloud --release --media-to /r")).unwrap();
assert!(!narrates(&rel.kind));
assert_eq!(forge_opts(&rel.kind), ["dev-narrate=off"]);
assert_eq!(
parse(&s("--where upcloud --no-dev-narrate --media-to /r"))
.unwrap()
.kind,
KIND_RELEASE,
"--no-dev-narrate is --release"
);
for mixed in [
"--release --dev-narrate",
"--dev --no-dev-narrate",
"--release --dev",
] {
assert!(
parse(&s(&format!("--where upcloud {mixed} --media-to /r")))
.unwrap_err()
.to_string()
.contains("not both"),
"{mixed}"
);
}
// The option the forge is given is the one every estate medium declares.
for m in MEDIA {
let i = holger_recipe::declared()
.installers
.into_iter()
.find(|i| i.name == m.variant)
.unwrap();
assert!(
i.options
.iter()
.any(|o| o.name == holger_recipe::packaging::DEV_NARRATE_OPTION && !o.default),
"{}: no default-off dev-narrate option",
m.word
);
}
let objects: BTreeMap<String, Object> = BTreeMap::from([(
"holger-keep".to_string(),
Object {
url: "https://b/x".into(),
sha256: "1".repeat(64),
bytes: 9,
},
)]);
let (none, carries) = (BTreeMap::new(), BTreeMap::new());
let d = receipt_text(
KIND_DEV,
&objects,
&none,
"holger 0.7.0",
"2026-10-19",
&carries,
)
.unwrap();
assert!(
d.contains("# build: kind = \"dev\", dev_narrate = true"),
"{d}"
);
let t: toml::Table = toml::from_str(&d).unwrap();
assert_eq!(
t[RECEIPT_RELEASE_NOTES][&"1".repeat(64)]["label"].as_str(),
Some("holger 0.7.0 (DEV: narrate on)")
);
let r = receipt_text(
KIND_RELEASE,
&objects,
&none,
"holger 0.7.0",
"2026-10-19",
&carries,
)
.unwrap();
assert!(
r.contains("dev_narrate = false") && r.contains("label = \"holger 0.7.0\""),
"{r}"
);
}
/// ★ P1 (R6's finding): a DEV medium is built exactly like Gunnar's dev media — the forge's
/// resolution of `--opt dev-narrate=on` arrives at the payload's declared `…-narrate` row and
/// its `dev-media` profile (the artifact under `<target>/dev-media/`), for the main and the
/// front medium and their keep media; a release medium builds the payload row, `release`.
#[test]
fn a_dev_medium_builds_the_narrate_row_with_the_dev_media_profile() {
use holger_recipe::packaging::{FRONT_PAYLOAD_VARIANT, METERED_PAYLOAD_VARIANT};
for m in MEDIA {
let base = if m.word.contains("front") {
FRONT_PAYLOAD_VARIANT
} else {
METERED_PAYLOAD_VARIANT
};
let dev = payload_row(m.variant, KIND_DEV).unwrap();
assert_eq!(dev.name, format!("{base}-narrate"), "{}", m.word);
assert_eq!(dev.profile.label(), "dev-media", "{}", m.word);
let args = dev.cargo_args();
assert!(
args.windows(2).any(|w| w == ["--profile", "dev-media"]),
"{}: {args:?}",
m.word
);
assert!(
dev.features.iter().any(|f| f == "dev-narrate"),
"{}",
m.word
);
let rel = payload_row(m.variant, KIND_RELEASE).unwrap();
assert_eq!(rel.name, base, "{}", m.word);
assert_eq!(rel.profile.label(), "release", "{}", m.word);
assert!(
!rel.features.iter().any(|f| f == "dev-narrate"),
"{}",
m.word
);
}
assert_eq!(
payload_row(holger_recipe::packaging::METERED_INSTALLER, KIND_DEV)
.unwrap()
.name,
"mvp-server-musl-metered-narrate"
);
assert_eq!(
payload_row(holger_recipe::packaging::FRONT_INSTALLER_METERED, KIND_DEV)
.unwrap()
.name,
"estate-front-musl-metered-narrate"
);
}
/// ★ The release is monetize `lifecycle::Holger`'s media: front, main/twin, both keeps, and
/// the two app media carry the live apps `holger-live` and `holger-front-live`; every medium is
/// a declared Holger installer, and nothing of Gunnar's is named.
#[test]
fn the_release_is_holgers_four_media_and_two_live_apps() {
let words: Vec<&str> = MEDIA.iter().map(|m| m.word).collect();
assert_eq!(
words,
["holger-front", "holger", "holger-keep", "holger-front-keep"]
);
let live: Vec<String> = MEDIA
.iter()
.filter(|m| m.live)
.map(|m| live_kind(m.word))
.collect();
assert_eq!(live, ["holger-front-live", "holger-live"]);
for m in MEDIA {
forge_door::declared_medium(m.variant, ArtifactKind::Installer, "x").unwrap();
assert!(!m.word.contains("gunnar") && !m.variant.contains("gunnar"));
}
assert_eq!(medium_key("holger", H), format!("release/holger/{H}.iso"));
assert_eq!(
live_key("holger-front", H),
format!("release/holger-front-live/{H}.live-app.znippy")
);
assert_eq!(
live_companions(Path::new("/x/.iso-out/holger-installer-metered.iso")).1,
PathBuf::from("/x/.iso-out/holger-installer-metered.live-app.znippy")
);
}
/// ★ R4: the main medium is composed WITHOUT a hand-set `holger_ron` — estate-media
/// stages the estate's declaration and the korp root itself and names them for every
/// medium that declares the parameter, never for the front.
#[test]
fn the_main_medium_is_composed_with_the_estates_declaration_and_no_hand_step() {
let dir =
std::env::temp_dir().join(format!("holger-estate-media-staged-{}", std::process::id()));
let s = Staged::write(&dir).unwrap();
assert_eq!(
std::fs::read_to_string(&s.ron).unwrap(),
holger_recipe::estate_ron::medium()
);
assert!(std::fs::read_to_string(&s.korp_root)
.unwrap()
.starts_with("-----BEGIN CERTIFICATE-----"));
for m in MEDIA {
let p = forge_params(m.variant, &s);
assert_eq!(
p[0],
format!("pairing_seed=@{}", s.marker.display()),
"{}",
m.word
);
let box_medium = m.word == "holger" || m.word == "holger-keep";
assert_eq!(
p.contains(&format!("holger_ron=@{}", s.ron.display())),
box_medium,
"{}: {p:?}",
m.word
);
assert_eq!(
p.contains(&format!("plugin_trust_root=@{}", s.korp_root.display())),
box_medium,
"{}: {p:?}",
m.word
);
// Every parameter named is one the medium declares, and every required one is named.
let i = holger_recipe::declared()
.installers
.into_iter()
.find(|i| i.name == m.variant)
.unwrap();
for given in &p {
let name = given.split('=').next().unwrap();
assert!(
i.params.iter().any(|d| d.name == name),
"{}: {name} is not declared",
m.word
);
}
for req in i.params.iter().filter(|d| d.required) {
assert!(
p.iter().any(|g| g.starts_with(&format!("{}=", req.name))),
"{}: the required {} is not named",
m.word,
req.name
);
}
}
// P5: an intel trust policy given is staged for every box medium, and only then.
assert!(
s.intel_trust.is_none(),
"no policy given: the slot stays empty"
);
let policy = dir.join("given-intel-trust.ron");
let text = intel_policy(&[]);
std::fs::write(&policy, &text).unwrap();
let s = s.with_intel_trust(&policy).unwrap();
let staged = s.intel_trust.clone().expect("staged");
assert_eq!(std::fs::read_to_string(&staged).unwrap(), text);
for m in MEDIA {
let p = forge_params(m.variant, &s);
let box_medium = m.word == "holger" || m.word == "holger-keep";
assert_eq!(
p.contains(&format!("intel_trust_policy=@{}", staged.display())),
box_medium,
"{}: {p:?}",
m.word
);
}
let _ = std::fs::remove_dir_all(&dir);
}
/// A coherent intel trust policy (pinned signers) whose `allowed_products` are `products`.
fn intel_policy(products: &[&str]) -> String {
use update_bundle::policy::{
ApprovedSigner, CertificateValidity, ExpiredPosture, TrustMode, TrustPolicy,
};
TrustPolicy {
schema_version: update_bundle::schema::POLICY_SCHEMA.into(),
trust_mode: Some(TrustMode::PinnedSigners),
trusted_roots: vec![],
certificate_validity: CertificateValidity::Enforce,
approved_signers: vec![ApprovedSigner {
fingerprint: "ab".repeat(32),
subject: None,
roles: vec!["security-reviewer".into()],
}],
required_approval_roles: vec!["security-reviewer".into()],
minimum_distinct_approvers: 1,
trusted_release_signers: vec!["cd".repeat(32)],
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: products.iter().map(|p| p.to_string()).collect(),
allowed_models: vec![],
}
.to_ron()
.unwrap()
}
/// ★ P6: the operator's intel trust policy is held BEFORE it is staged — a policy the box
/// could not load, or one that shuts out `holger.intel/v1`, is refused by name here, never
/// at an airgap box's first import; `--intel-trust` with `--composed` is refused (those
/// media carry their composer's policy), and a later run without one stages none.
#[test]
fn an_intel_trust_policy_is_held_before_it_rides_a_medium() {
assert_eq!(
intel_trust_refusal(&intel_policy(&[])),
None,
"empty allowed_products = any product"
);
assert_eq!(
intel_trust_refusal(&intel_policy(&[holger_intel::INTEL_PRODUCT, "x"])),
None
);
let why =
intel_trust_refusal(&intel_policy(&["acme.firmware/v1"])).expect("shuts intel out");
assert!(
why.starts_with("[estate-media-intel-trust]")
&& why.contains(holger_intel::INTEL_PRODUCT),
"{why}"
);
let why = intel_trust_refusal("(schema_version: \"x\")").expect("not a policy");
assert!(why.contains("does not load"), "{why}");
let unset =
intel_policy(&[]).replace("trust_mode: Some(PinnedSigners)", "trust_mode: None");
assert_ne!(unset, intel_policy(&[]), "the fixture names its trust mode");
assert!(
intel_trust_refusal(&unset).is_some(),
"an unanswered trust question is refused"
);
let dir =
std::env::temp_dir().join(format!("holger-estate-media-intel-{}", std::process::id()));
let st = Staged::write(&dir).unwrap();
let bad = dir.join("bad.ron");
std::fs::write(&bad, intel_policy(&["acme.firmware/v1"])).unwrap();
let e = st.clone().with_intel_trust(&bad).unwrap_err().to_string();
assert!(e.contains("[estate-media-intel-trust]"), "{e}");
assert!(
!dir.join(INTEL_TRUST_FILE).exists(),
"a refused policy is never staged"
);
let good = dir.join("good.ron");
std::fs::write(&good, intel_policy(&[])).unwrap();
st.with_intel_trust(&good).unwrap();
assert!(dir.join(INTEL_TRUST_FILE).exists());
let again = Staged::write(&dir).unwrap();
assert!(
again.intel_trust.is_none() && !dir.join(INTEL_TRUST_FILE).exists(),
"an earlier run's policy never rides a medium composed without one"
);
assert!(MEDIA.iter().all(|m| !forge_params(m.variant, &again)
.iter()
.any(|p| p.starts_with("intel_trust_policy="))));
let _ = std::fs::remove_dir_all(&dir);
let e = parse(&s(
"--where upcloud --media-to /r --composed --intel-trust /p.ron",
))
.unwrap_err()
.to_string();
assert!(
e.contains("--intel-trust") && e.contains("--composed"),
"{e}"
);
}
/// ★ R10 (Gunnar's kit, `FRONT_CLOCK_SOURCES`): the FRONT media are composed with the
/// estate's root of time, `clock=https://api.stripe.com/,https://www.cloudflare.com/`;
/// without it the front syncs nothing and refuses to sign time for every box
/// (`peer://10.44.0.1:50123`). The box media declare no clock and are given none.
#[test]
fn the_front_media_are_composed_with_the_clock_sources() {
let dir =
std::env::temp_dir().join(format!("holger-estate-media-clock-{}", std::process::id()));
let s = Staged::write(&dir).unwrap();
assert_eq!(
FRONT_CLOCK_SOURCES, "https://api.stripe.com/,https://www.cloudflare.com/",
"Gunnar's sources, the same words"
);
assert_eq!(
FRONT_CLOCK_SOURCES,
estate_clock::config::front_sources(
estate_clock::config::FrontPlace::Cloud,
estate_clock::config::BENCH_CLOCK_PORT
),
"the shared API's cloud sources"
);
for m in MEDIA {
let p = forge_params(m.variant, &s);
let front = m.word.contains("front");
assert_eq!(
p.contains(&format!("clock={FRONT_CLOCK_SOURCES}")),
front,
"{}: {p:?}",
m.word
);
assert_eq!(
p.iter().any(|g| g.starts_with("clock=")),
front,
"{}: {p:?}",
m.word
);
}
let _ = std::fs::remove_dir_all(&dir);
}
/// ★ R10: a composed FRONT medium is uploaded only if its command line carries the clock
/// sources (r10's first compose had none); a box medium is not asked.
#[test]
fn a_composed_front_medium_without_the_clock_is_refused() {
let with = format!("korp.appliance=lean korp.entry=holger-estate-front korp.env.ESTATE_CLOCK={FRONT_CLOCK_SOURCES} korp.require_signed=1");
let without = "korp.appliance=lean korp.entry=holger-estate-front korp.live_update=on korp.require_signed=1";
assert!(composed_front_has_clock("holger-front", with.as_bytes()).is_ok());
let e = composed_front_has_clock("holger-front-keep", without.as_bytes())
.unwrap_err()
.to_string();
assert!(
e.contains("[estate-media-front-clock]")
&& e.contains("holger-front-keep")
&& e.contains(FRONT_CLOCK_SOURCES),
"{e}"
);
assert!(
composed_front_has_clock("holger", without.as_bytes()).is_ok(),
"a box medium takes its clock from the front's offer"
);
}
/// ★ korp's record becomes `[live_apps.<kind>]`; an unsigned app is refused by name (a
/// Holger release without live apps would update from keep only), a missing field too.
#[test]
fn a_live_record_is_taken_signed_only() {
let r = live_record("holger", &rec("4b4f")).unwrap();
assert_eq!(
(r.base.as_str(), r.app.as_str(), r.app_len, r.sig.as_str()),
(B, H, 9, "4b4f")
);
assert!(live_record("holger", &rec(""))
.unwrap_err()
.to_string()
.contains("[estate-media-unsigned]"));
assert!(live_record("holger", &rec("ab").replace("app_len = 9", ""))
.unwrap_err()
.to_string()
.contains("app_len"));
assert!(live_record("holger", &rec("ab").replace(B, "short"))
.unwrap_err()
.to_string()
.contains("`base`"));
}
/// ★ **What the release declares rides its live record** (lane B5): the release's env off
/// korp's record, the declaration korp composed beside the `.iso` (hex in the receipt's
/// `[live_apps.<kind>]`, the shape the ONE box's `LiveRecord` reads); a declaration
/// without its signature on a signed medium is refused by name; a record before B5 reads
/// as none declared.
#[test]
fn a_live_record_carries_the_release_declaration_and_env() {
let text = format!(
"{}env = \"korp.env.HOLGER_DATA=/data korp.env.X=1\"\n",
rec("4b4f")
);
let r = with_release(
"holger",
live_record("holger", &text).unwrap(),
Some(b"korp".to_vec()),
Some(vec![0x77; 2]),
)
.unwrap();
assert_eq!(
(r.env.clone(), r.release.as_str(), r.release_sig.as_str()),
(
vec![
"korp.env.HOLGER_DATA=/data".to_string(),
"korp.env.X=1".to_string()
],
"6b6f7270",
"7777"
)
);
assert!(
with_release("holger", r.clone(), Some(b"korp".to_vec()), None)
.unwrap_err()
.to_string()
.contains("[estate-media-release-unsigned]")
);
let none = with_release(
"holger",
live_record("holger", &rec("4b4f")).unwrap(),
None,
None,
)
.unwrap();
assert!(none.env.is_empty() && none.release.is_empty());
let obj = |w: &str| {
(
w.to_string(),
Object {
url: format!("https://bucket.example/{w}"),
sha256: "1".repeat(64),
bytes: 9,
},
)
};
let objects: BTreeMap<String, Object> =
["holger", "holger-live"].iter().map(|w| obj(w)).collect();
let live: BTreeMap<String, LiveRecord> = [("holger".to_string(), r)].into();
let t: toml::Table = toml::from_str(
&receipt_text("release", &objects, &live, "l", "d", &BTreeMap::new()).unwrap(),
)
.unwrap();
let l = &t[RECEIPT_LIVE_APPS]["holger"];
assert_eq!(
(
l["release"].as_str(),
l["release_sig"].as_str(),
l["env"].as_array().map(|a| a.len())
),
(Some("6b6f7270"), Some("7777"), Some(2))
);
assert_eq!(
release_companions(Path::new("/x/.iso-out/holger-installer.iso")).0,
PathBuf::from(format!(
"/x/.iso-out/holger-installer.{}",
korp_installer::live::MEDIUM_RELEASE_SUFFIX
))
);
}
/// ★ The receipt pmo feeds (`sells::site_media`): six objects, both live records with their
/// archives, and a release note per medium; a live record without its archive is refused.
#[test]
fn the_receipt_names_the_media_the_live_apps_and_the_notes() {
let obj = |w: &str, sha: &str| {
(
w.to_string(),
Object {
url: format!("https://bucket.example/release/{w}/{sha}"),
sha256: sha.to_string(),
bytes: 9,
},
)
};
let objects: BTreeMap<String, Object> = [
"holger-front",
"holger",
"holger-keep",
"holger-front-keep",
"holger-live",
"holger-front-live",
]
.iter()
.enumerate()
.map(|(i, w)| obj(w, &format!("{i}").repeat(64)))
.collect();
let r = live_record("holger", &rec("4b4f")).unwrap();
let live: BTreeMap<String, LiveRecord> = [
("holger".to_string(), r.clone()),
("holger-front".to_string(), r.clone()),
]
.into();
let carries: BTreeMap<String, String> =
[("holger".to_string(), "8579df6a".to_string())].into();
let text = receipt_text(
"release",
&objects,
&live,
"holger 0.6.10",
"2026-10-18",
&carries,
)
.unwrap();
let t: toml::Table = toml::from_str(&text).unwrap();
for k in objects.keys() {
assert!(t[k.as_str()].get("object").is_some(), "{k}: {text}");
}
for k in ["holger", "holger-front"] {
let l = &t[RECEIPT_LIVE_APPS][k];
assert_eq!(
(
l["base"].as_str(),
l["app"].as_str(),
l["app_len"].as_integer(),
l["sig"].as_str()
),
(Some(B), Some(H), Some(9), Some("4b4f"))
);
}
let notes = t[RECEIPT_RELEASE_NOTES].as_table().unwrap();
assert_eq!(notes.len(), 4, "one note per medium: {text}");
assert_eq!(
notes[&"1".repeat(64)]["carries"]["holger"].as_str(),
Some("8579df6a")
);
assert!(!text.to_lowercase().contains("gunnar"), "{text}");
let mut lost = objects.clone();
lost.remove("holger-front-live");
assert!(receipt_text("release", &lost, &live, "l", "d", &carries)
.unwrap_err()
.to_string()
.contains("holger-front-live"));
}
/// ★ `--composed` takes only media composed at this HEAD (the bytes a KVM install proved).
#[test]
fn a_composed_medium_is_held_to_this_head() {
let head = "e6b1bb7c1ab2154e5ccbdfecc67134b53e5a48a6";
composed_is_this_head("holger", r#"{"head":"e6b1bb7"}"#, head).unwrap();
assert!(
composed_is_this_head("holger", r#"{"head":"8579df6"}"#, head)
.unwrap_err()
.to_string()
.contains("[estate-media-composed-stale]")
);
assert!(composed_is_this_head("holger", r#"{"iso":"x"}"#, head)
.unwrap_err()
.to_string()
.contains("names no head"));
}
/// ★ A medium that cannot read its own CD is refused before upload (A7 row 25, Gunnar
/// 09fe2c48): its record must say `cd_driver: true`; false or absent refuses by name.
#[test]
fn a_medium_that_cannot_read_its_cd_is_refused() {
composed_reads_its_cd("holger", r#"{"head":"e6b1bb7","cd_driver":true}"#).unwrap();
let e = composed_reads_its_cd("holger-front", r#"{"cd_driver":false}"#)
.unwrap_err()
.to_string();
assert!(
e.contains("[medium-no-cd-driver]")
&& e.contains("holger-front")
&& e.contains("CONFIG_BLK_DEV_SR=y"),
"{e}"
);
let e = composed_reads_its_cd("holger", r#"{"head":"e6b1bb7"}"#)
.unwrap_err()
.to_string();
assert!(
e.contains("[medium-no-cd-driver]") && e.contains("no cd_driver"),
"an unproven medium refuses too: {e}"
);
assert!(composed_reads_its_cd("holger", "not json").is_err());
}
/// ★ The bucket: the mock's bench face, or the account's pair read by name (https only).
#[test]
fn the_bucket_is_the_mocks_or_the_vaults_by_name() {
assert_eq!(mock_api_port(None).unwrap(), MOCK_API);
assert_eq!(mock_api_port(Some("8391,18591,12491,12492")).unwrap(), 8391);
assert!(mock_api_port(Some("x")).is_err());
assert_eq!(mock_bucket(8391).endpoint, "http://127.0.0.1:8391/objsto");
let v = "# vault\nRELEASE_MEDIA_ENDPOINT=https://abc.upcloudobjects.com/\nRELEASE_MEDIA_BUCKET=holger-release\nRELEASE_MEDIA_ACCESS_KEY_ID=AK\nRELEASE_MEDIA_SECRET_ACCESS_KEY=SK\n";
let b = bucket_of(v).unwrap();
assert_eq!(
(b.endpoint.as_str(), b.region.as_str(), b.bucket.as_str()),
(
"https://abc.upcloudobjects.com",
DEFAULT_REGION,
"holger-release"
)
);
assert!(
!format!("{b:?}").contains("SK"),
"the secret is never printed"
);
assert!(
bucket_of(&v.replace("RELEASE_MEDIA_BUCKET=holger-release\n", ""))
.unwrap_err()
.to_string()
.contains("RELEASE_MEDIA_BUCKET")
);
assert!(bucket_of(&v.replace("https://", "http://"))
.unwrap_err()
.to_string()
.contains("https"));
}
/// ★ SigV4: the AWS documentation's signing-key vector, and a PUT path-style under the
/// endpoint's own prefix.
#[test]
fn a_put_is_signed_sigv4_path_style() {
// AWS's published vector: kSigning for 20150830/us-east-1/iam.
let k = hmac(b"AWS4wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY", "20150830");
let k = hmac(&hmac(&hmac(&k, "us-east-1"), "iam"), "aws4_request");
assert_eq!(
hex(&k),
"c4afb1cc5771d871763a393e44b703571b55cc28424d1a5e86da6ed3c154a4b9"
);
let (url, h) = put_request(
&mock_bucket(8391),
"release/holger/x.iso",
H,
"20261018T120000Z",
);
assert_eq!(
url,
"http://127.0.0.1:8391/objsto/release/release/holger/x.iso"
);
assert_eq!(h[1], ("x-amz-content-sha256".to_string(), H.to_string()));
assert!(h[2].1.starts_with("AWS4-HMAC-SHA256 Credential=mock-release-access-key/20261018/mock/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature="), "{}", h[2].1);
assert_eq!(civil(20_744), (2026, 10, 18));
}
/// ★ `put_file` PUTs the bytes with their sha256 and answers the object; a refusing bucket
/// is refused by name.
#[test]
fn put_file_sends_the_bytes_and_reads_the_answer() {
use std::io::{Read, Write};
let l = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let port = l.local_addr().unwrap().port();
let serve = std::thread::spawn(move || {
let mut heard = Vec::new();
for status in ["200 OK", "403 Forbidden"] {
let (mut c, _) = l.accept().unwrap();
let mut buf = Vec::new();
let mut chunk = [0u8; 4096];
loop {
let n = c.read(&mut chunk).unwrap();
buf.extend_from_slice(&chunk[..n]);
let text = String::from_utf8_lossy(&buf).to_string();
if let Some(i) = text.find("\r\n\r\n") {
let len: usize = text
.lines()
.find_map(|l| {
l.to_ascii_lowercase()
.strip_prefix("content-length:")
.map(|v| v.trim().parse().unwrap())
})
.unwrap_or(0);
if buf.len() >= i + 4 + len {
break;
}
}
if n == 0 {
break;
}
}
heard.push(String::from_utf8_lossy(&buf).to_string());
c.write_all(format!("HTTP/1.1 {status}\r\ncontent-length: 0\r\n\r\n").as_bytes())
.unwrap();
}
heard
});
let d = std::env::temp_dir().join(format!("holger-estate-media-{}", std::process::id()));
std::fs::create_dir_all(&d).unwrap();
let f = d.join("m.iso");
std::fs::write(&f, b"holger medium").unwrap();
let b = mock_bucket(port);
let o = put_file(&b, "release/holger/m.iso", &f).unwrap();
assert_eq!(
(o.bytes, o.sha256.as_str()),
(13, sha256_hex(b"holger medium").as_str())
);
assert!(
o.url.ends_with("/objsto/release/release/holger/m.iso"),
"{}",
o.url
);
assert!(put_file(&b, "release/holger/m.iso", &f)
.unwrap_err()
.to_string()
.contains("HTTP 403"));
let heard = serve.join().unwrap();
assert!(
heard[0].starts_with("PUT /objsto/release/release/holger/m.iso "),
"{}",
heard[0]
);
assert!(
heard[0].contains("holger medium")
&& heard[0]
.to_ascii_lowercase()
.contains(&format!("x-amz-content-sha256: {}", o.sha256))
);
let _ = std::fs::remove_dir_all(&d);
}
}