holger-recipe/src/variants.rs
//! **The build variants — every `[[build.variant]]` holger declares, as Rust.**
//!
//! ONE product list (lane HOL-PRODUCTS, owner 2026-10-30), the MVP suite:
//!
//! * the four products, each one crate and its `cargo install` line: `holger` (the
//! server), `holger-downstream` (the airgapped downstream), `holger-ui` (the web
//! console, in the holger-ui repository) and `holger-cli` (the command-line tool);
//! * what only an estate needs: the static appliance builds of the server and the
//! downstream, the metered (sold) builds, holger's estate front, the three signed wasm
//! plugins and the installer media.
//!
//! `cargo xtask build --suite MVP --list` prints it; `--features full` builds the products
//! with every extra. The model: variant → features → settings → typ. Each row is one
//! `(package × bin × features × profile × target)` point with its artifact kind, linkage
//! and suite. The reasoning behind a row is on the row; its history is in git
//! (`.nornir/SUITES.md` "History" says where the old suites went). `emit` overwrites the
//! emitted `.nornir/build-variants.toml`, so new reasoning goes HERE.
use std::collections::BTreeSet;
use nornir_build_thing::plugin::Role;
use nornir_build_thing::variant::{ArtifactKind, BuildVariant, Profile, ServiceShape};
/// The server's crate (`server/cli`): `cargo install holger`, and the estate's builds.
pub const SERVER_CLI: &str = "holger";
/// The downstream's crate (`holger-downstream/`): `cargo install holger-downstream`.
pub const DOWNSTREAM: &str = "holger-downstream";
/// Its directory: a workspace of its own (its `holger` edge with `downstream` must never
/// unify into the root's server), so its rows name it as their `manifest_dir`.
pub const DOWNSTREAM_MANIFEST_DIR: &str = "holger-downstream";
/// The command-line tool's crate (`holger-cli/`): `cargo install holger-cli`.
pub const HOLGER_CLI: &str = "holger-cli";
/// The web console's crate (holger-ui's `crates/holger-ui`): `cargo install holger-ui`.
pub const HOLGER_UI: &str = "holger-ui";
/// Where the console's crate is, from this repository's root (a sibling checkout).
pub const HOLGER_UI_MANIFEST_DIR: &str = "../holger-ui/crates/holger-ui";
/// The four products, in the order the docs name them: each a row of the same name that
/// builds the crate of that name with its defaults.
pub const PRODUCTS: [&str; 4] = [SERVER_CLI, DOWNSTREAM, HOLGER_UI, HOLGER_CLI];
/// The twelve MVP formats every server product serves by default (server/cli features).
pub const MVP_FORMATS: [&str; 12] = [
"fmt-raw",
"fmt-rust",
"fmt-npm",
"fmt-maven",
"fmt-python",
"fmt-docker",
"fmt-go",
"fmt-helm",
"fmt-deb",
"fmt-rpm",
"fmt-znippy",
"fmt-build-thing",
];
/// The static musl triple the appliance payload is cross-built for.
pub const MUSL: &str = "x86_64-unknown-linux-musl";
/// The suite member the service kinds install as.
const MEMBER: &str = "holger";
/// One row, spelled with only the fields holger's declaration uses.
struct Row {
name: &'static str,
suites: &'static [&'static str],
role: Role,
is_default: bool,
package: Option<&'static str>,
bin: Option<&'static str>,
kind: ArtifactKind,
member: bool,
linkage: ServiceShape,
reaches_holger: bool,
features: &'static [&'static str],
no_default_features: bool,
target: Option<&'static str>,
release: bool,
container_ref: Option<&'static str>,
/// `sign = { purpose = "plugin" }`: the forge writes `<artifact>.p7s` and
/// `<artifact>.blake3` beside what the row built. Only the wasm guests.
sign: bool,
}
impl Row {
fn to_variant(&self) -> BuildVariant {
BuildVariant {
name: self.name.to_string(),
package: self.package.map(str::to_string),
bin: self.bin.map(str::to_string),
features: self.features.iter().map(|f| (*f).to_string()).collect(),
no_default_features: self.no_default_features,
profile: if self.release {
Profile::Release
} else {
Profile::Dev
},
target: self.target.map(str::to_string),
member: self.member.then(|| MEMBER.to_string()),
shape: self.linkage,
role: Some(self.role),
suites: self.suites.iter().map(|s| (*s).to_string()).collect(),
is_default: self.is_default,
kind: self.kind,
container_ref: self.container_ref.map(str::to_string),
sign: self.sign.then(nornir_build_thing::sign::SignSpec::default),
reaches: if self.reaches_holger {
BTreeSet::from([MEMBER.to_string()])
} else {
BTreeSet::new()
},
..BuildVariant::default()
}
}
}
const FAT: ServiceShape = ServiceShape::FatService;
const THIN: ServiceShape = ServiceShape::ThinStandalone;
/// A cargo row: package + bin, release profile, no container ref.
#[allow(clippy::too_many_arguments)]
const fn cargo(
name: &'static str,
suites: &'static [&'static str],
role: Role,
is_default: bool,
package: &'static str,
bin: &'static str,
kind: ArtifactKind,
member: bool,
linkage: ServiceShape,
reaches_holger: bool,
features: &'static [&'static str],
no_default_features: bool,
target: Option<&'static str>,
) -> Row {
Row {
name,
suites,
role,
is_default,
package: Some(package),
bin: Some(bin),
kind,
member,
linkage,
reaches_holger,
features,
no_default_features,
target,
release: true,
container_ref: None,
sign: false,
}
}
/// The wasm32 triple every guest row targets.
pub const WASM32: &str = "wasm32-unknown-unknown";
/// **A signed wasm guest row** — holger's OWN in-server plugins (`plugin/*-wasm`).
///
/// No bin (a `cdylib` package), release, `wasm32-unknown-unknown`, linkage
/// `not-applicable` said out loud (a guest reaches nothing over the wire; the
/// host decides what it may touch), role `library`, and `sign = { purpose =
/// "plugin" }` so the forge writes `<stem>.wasm.p7s` + `<stem>.wasm.blake3`
/// beside the module — the exact pair `holger-plugin-host` checks on load
/// (`guest_host::verify::ModuleTrust`, refused as `holger-plugin-6..9`).
const fn wasm_guest(name: &'static str, is_default: bool, package: &'static str) -> Row {
Row {
name,
suites: &["MVP"],
role: Role::Library,
is_default,
package: Some(package),
bin: None,
kind: ArtifactKind::Wasm,
member: false,
linkage: ServiceShape::NotApplicable,
reaches_holger: false,
features: &[],
no_default_features: false,
target: Some(WASM32),
release: true,
container_ref: None,
sign: true,
}
}
/// **Every build variant holger declares**, in declaration order (the menu and the
/// `(suite, role)` default groups iterate it, and the emitted toml follows it). The
/// console's row, the front's, the downstream medium's and the DEV rows are added by
/// [`variants`].
fn rows() -> [Row; 13] {
use ArtifactKind::{Container, Exe, Installer, Iso, Service};
use Role::{Client, Server};
const MVP: &[&str] = &["MVP"];
[
// ══ the products: each row IS its `cargo install` line (twins.rs) ═════════
//
// holger — `cargo install holger`: the server with its defaults (the twelve
// formats, the intake chain, no outbound client). The (MVP, server) normal case.
cargo(
SERVER_CLI,
MVP,
Server,
true,
SERVER_CLI,
"holger",
Exe,
false,
FAT,
false,
&[],
false,
None,
),
// holger-downstream — `cargo install holger-downstream`: the airgapped gateway. It
// trusts its one upstream's signed scan evidence (no scanner of its own) and
// reaches that upstream only, a configured line, so `reaches` stays empty.
cargo(
DOWNSTREAM,
MVP,
Server,
false,
DOWNSTREAM,
"holger-downstream",
Exe,
false,
FAT,
false,
&[],
false,
None,
),
// holger-cli — `cargo install holger-cli`: every console action as a command, and
// the offline tools (the bundle, the verifier, the courier). THIN: it reaches a
// holger at `--at` and nothing else. The (MVP, client) normal case.
cargo(
HOLGER_CLI,
MVP,
Client,
true,
HOLGER_CLI,
"holger-cli",
Exe,
false,
THIN,
true,
&[],
false,
None,
),
// ══ the estate's builds of the server ════════════════════════════════════
//
// mvp-server-musl — the STATIC appliance payload: the server for the OS-less
// installed shape (it rides `scratch` and the ISO), cross-built for musl. The
// airgap box: the passkey door (no IdP inside a bubble), the appliance ceremony
// (gated at run time by HOLGER_APPLIANCE=1), pairing, the built-in Allow meter
// (no wasm runtime), the job queue, admission and the mirror. Zero egress.
cargo(
"mvp-server-musl",
MVP,
Server,
false,
SERVER_CLI,
"holger-mvp",
Exe,
false,
FAT,
false,
&[
"mvp-server",
"appliance",
"paired",
"auth-passkey",
"sign",
"allow-meter",
"fmt-raw",
"fmt-rust",
"fmt-npm",
"fmt-maven",
"fmt-python",
"fmt-docker",
"fmt-go",
"fmt-helm",
"fmt-deb",
"fmt-rpm",
"fmt-znippy",
"fmt-build-thing",
"sync",
"jobs",
"supply-chain",
"heimdall",
"admission",
"mirror",
],
true,
Some(MUSL),
),
// mvp-server-metered — the SOLD server: the appliance's doors plus the paid state
// (the signed `EntitlementFact` monetize pushes), the paid download admission, the
// front page, the wasm host with the meter guests and the box's own advisory
// fetch. A separate row, never a feature on the open one: an artifact either has
// the door or it does not. Its own bin name keeps the two paths apart.
cargo(
"mvp-server-metered",
MVP,
Server,
false,
SERVER_CLI,
"holger-mvp-metered",
Service,
true,
FAT,
false,
&[
"mvp-server",
"appliance",
"paired",
"auth-passkey",
"sign",
"metered",
"admit-share",
"front",
"wasm-plugins",
"meter-guests",
"fmt-raw",
"fmt-rust",
"fmt-npm",
"fmt-maven",
"fmt-python",
"fmt-docker",
"fmt-go",
"fmt-helm",
"fmt-deb",
"fmt-rpm",
"fmt-znippy",
"fmt-build-thing",
"sync",
"jobs",
"supply-chain",
"heimdall",
"admission",
"intel-fetch",
"mirror",
],
true,
None,
),
// mvp-server-musl-metered — the static payload of the PAID medium
// (`holger-installer-metered`). It consumes holger's own signed monetize plugin
// (the edge `plugin → monetize-impl/holger-guest : wasm`, added in `variants()`),
// which `server/lib/build.rs` embeds and refuses by name when it is another
// commit's.
cargo(
"mvp-server-musl-metered",
MVP,
Server,
false,
SERVER_CLI,
"holger-mvp-metered",
Exe,
false,
FAT,
false,
&[
"mvp-server",
"appliance",
"paired",
"auth-passkey",
"sign",
"metered",
"admit-share",
"front",
"wasm-plugins",
"meter-guests",
"fmt-raw",
"fmt-rust",
"fmt-npm",
"fmt-maven",
"fmt-python",
"fmt-docker",
"fmt-go",
"fmt-helm",
"fmt-deb",
"fmt-rpm",
"fmt-znippy",
"fmt-build-thing",
"sync",
"jobs",
"supply-chain",
"heimdall",
"admission",
"intel-fetch",
"mirror",
],
true,
Some(MUSL),
),
// ══ the media's pointer rows (packaging.rs holds the media) ═══════════════
//
// A `kind = "container"` row carries no cargo argv; `container_ref` names its
// [[build.container]] row.
pointer(
"holger-mvp-appliance",
Container,
Some(crate::packaging::APPLIANCE),
),
// An installer or ISO row resolves its payload BY ITS OWN NAME, so it shares the
// name of its [[build.installer]] / [[build.iso]] row. One medium serves the main
// and the twin: the role is elected at bootstrap, never baked
// (`packaging::appliance_iso`).
pointer(crate::packaging::INSTALLER, Installer, None),
pointer(crate::packaging::METERED_INSTALLER, Installer, None),
pointer(crate::packaging::APPLIANCE_ISO, Iso, None),
// ══ holger's own in-server wasm plugins, built in the pinned container and SIGNED
// by the forge (`.p7s` + `.blake3`, what the plugin host verifies on load) ══
//
// plugin-allocation — the OPEN meter guest (Allow, no ceiling). The (MVP, library)
// normal case.
wasm_guest("plugin-allocation", true, "holger-plugin-allocation-wasm"),
// plugin-skidbladnir — the Skidbladnir airgap-bundle package handler.
wasm_guest(
"plugin-skidbladnir",
false,
"holger-handler-skidbladnir-wasm",
),
// plugin-rust-toolchain — the airgapped Rust-toolchain bundle handler.
wasm_guest(
"plugin-rust-toolchain",
false,
"holger-handler-rust-toolchain-wasm",
),
]
}
/// A media pointer row: no cargo argv, MVP, role server.
const fn pointer(
name: &'static str,
kind: ArtifactKind,
container_ref: Option<&'static str>,
) -> Row {
Row {
name,
suites: &["MVP"],
role: Role::Server,
is_default: false,
package: None,
bin: None,
kind,
member: false,
linkage: FAT,
reaches_holger: false,
features: &[],
no_default_features: false,
target: None,
release: false,
container_ref,
sign: false,
}
}
/// **The console's row** (`holger-ui`): `cargo install holger-ui`, built from the sibling
/// holger-ui checkout. A client of holger: the bundle's host and a same-origin pass-through
/// to the holger it is pointed at. holger-ui's own declaration builds the bundle (trunk).
fn console_variant() -> BuildVariant {
BuildVariant {
manifest_dir: Some(HOLGER_UI_MANIFEST_DIR.to_string()),
siblings: vec!["holger-ui".to_string()],
..cargo(
HOLGER_UI,
&["MVP"],
Role::Client,
false,
HOLGER_UI,
"holger-ui",
ArtifactKind::Exe,
false,
THIN,
true,
&[],
false,
None,
)
.to_variant()
}
}
/// The declared variants as nornir-build-thing's declaration type.
pub fn variants() -> Vec<BuildVariant> {
let mut out: Vec<BuildVariant> = rows()
.iter()
.map(|r| {
let mut v = r.to_variant();
// The one consumption edge a cargo row declares: the metered musl
// payload embeds holger's own signed plugin (see packaging.rs).
if v.name == crate::packaging::METERED_PAYLOAD_VARIANT {
let (feature, edge) = crate::packaging::own_plugin_edge();
v.edges.insert(feature, edge);
}
v
})
.collect();
// The console after the command-line tool's row, so the products read in a row.
let at = out
.iter()
.position(|v| v.name == HOLGER_CLI)
.map_or(out.len(), |i| i + 1);
out.insert(at, console_variant());
out.extend(front_variants());
out.extend(downstream_variants());
with_narrate_rows(&mut out);
for v in out
.iter_mut()
.filter(|v| v.package.as_deref() == Some(DOWNSTREAM))
{
v.manifest_dir = Some(DOWNSTREAM_MANIFEST_DIR.to_string());
}
out
}
/// **A downstream's four rows** (lane OX-WGDS3, 2026-10-28; lane HOL-CDS-5, 2026-10-30): two
/// payloads and their media's pointers.
///
/// * `downstream-musl` — the ONE-WAY appliance: `holger-downstream` + `pid1` (its own init
/// without pairing, the medium's network, a HOLGER-INBOX stick's bundles into its inbox);
/// it dials nothing. Medium `holger-downstream-oneway-installer`.
/// * `downstream-fetch-musl` — the FETCHING appliance: the product's gateway plus `fetch` and
/// `downstream-wg` (the tunnel to its upstream's front from a signed invite, lane
/// HOL-WGDS) and `paired`, whose PID-1 duties (the reaper, the declared NIC by DHCP, the
/// clean stop) make it the box's init; with no pairing seed staged it pairs nothing.
/// Static musl for the OS-less medium.
/// * `holger-downstream-installer` — the medium's pointer; it shares the name of its
/// [[build.installer]] row.
fn downstream_variants() -> Vec<BuildVariant> {
let row = |name: &'static str,
package: Option<&'static str>,
bin: Option<&'static str>,
kind: ArtifactKind,
features: &'static [&'static str],
target: Option<&'static str>,
release: bool| {
Row {
name,
suites: &["MVP"],
role: Role::Server,
is_default: false,
package,
bin,
kind,
member: false,
linkage: FAT,
reaches_holger: false,
features,
no_default_features: false,
target,
release,
container_ref: None,
sign: false,
}
.to_variant()
};
vec![
row(
crate::packaging::ONEWAY_PAYLOAD_VARIANT,
Some(DOWNSTREAM),
Some(crate::packaging::DOWNSTREAM_ENTRY),
ArtifactKind::Exe,
ONEWAY_MEDIUM_FEATURES,
Some(MUSL),
true,
),
row(
crate::packaging::ONEWAY_INSTALLER,
None,
None,
ArtifactKind::Installer,
&[],
None,
false,
),
row(
crate::packaging::DOWNSTREAM_PAYLOAD_VARIANT,
Some(DOWNSTREAM),
Some(crate::packaging::FETCH_ENTRY),
ArtifactKind::Exe,
DOWNSTREAM_MEDIUM_FEATURES,
Some(MUSL),
true,
),
row(
crate::packaging::DOWNSTREAM_INSTALLER,
None,
None,
ArtifactKind::Installer,
&[],
None,
false,
),
]
}
/// The features of the ONE-WAY downstream medium's payload (lane HOL-CDS-5): its own init
/// without pairing, nothing that dials; its jobs under Heimdall (lane cds4, HOL-CDS-4).
pub const ONEWAY_MEDIUM_FEATURES: &[&str] = &["pid1", "heimdall"];
/// The features of the fetching downstream medium's payload (see [`downstream_variants`]); its
/// jobs under Heimdall (lane cds4, HOL-CDS-4).
pub const DOWNSTREAM_MEDIUM_FEATURES: &[&str] = &["fetch", "downstream-wg", "paired", "heimdall"];
/// The cargo feature DEV-NARRATE is (holger and holger-estate-front alike).
pub const DEV_NARRATE: &str = "dev-narrate";
/// The cargo profile the `…-narrate` rows build with (`[profile.dev-media]`, Gunnar's).
pub const DEV_MEDIA_PROFILE: &str = "dev-media";
/// What a payload's DEV row is called: `<payload>-narrate`.
pub const NARRATE_SUFFIX: &str = "-narrate";
/// The payloads whose medium carries the `dev-narrate` option: the estate's main/twin and
/// its front (and their keep media, which compose the same payloads).
pub const NARRATED_PAYLOADS: [&str; 2] = [
crate::packaging::METERED_PAYLOAD_VARIANT,
crate::packaging::FRONT_PAYLOAD_VARIANT,
];
/// The media whose installer row declares the `dev-narrate` option (packaging.rs).
pub const NARRATED_MEDIA: [&str; 4] = [
crate::packaging::METERED_INSTALLER,
crate::packaging::FRONT_INSTALLER_METERED,
crate::packaging::KEEP_MEDIUM,
crate::packaging::FRONT_KEEP_MEDIUM,
];
/// **DEV-NARRATE's build points** (owner 2026-10-19, Gunnar's `…-narrate` rows): each
/// narrated payload keeps `dev-narrate` a SWITCH (`open`, never decided), and beside it a
/// `<payload>-narrate` row names it — the row the medium's option switched ON resolves to.
/// The release media build the payload row itself: no sender is compiled in.
fn with_narrate_rows(out: &mut Vec<BuildVariant>) {
// The four estate media's pointer rows carry the option's switch too (LAW A: an
// installer's settings tree renders back to its pointer row's `open` axis).
for v in out
.iter_mut()
.filter(|v| NARRATED_MEDIA.contains(&v.name.as_str()))
{
v.open.insert(DEV_NARRATE.to_string());
}
for name in NARRATED_PAYLOADS {
let Some(i) = out.iter().position(|v| v.name == name) else {
continue;
};
out[i].open.insert(DEV_NARRATE.to_string());
let mut dev = out[i].clone();
dev.name = format!("{name}{NARRATE_SUFFIX}");
dev.open.remove(DEV_NARRATE);
dev.features.push(DEV_NARRATE.to_string());
dev.is_default = false;
// Its own profile (Gunnar's `dev-media`): its own artifact, never the base row's.
dev.profile = Profile::Custom(DEV_MEDIA_PROFILE.to_string());
out.insert(i + 1, dev);
}
}
/// **vm1's two rows** (2026-09-27): the payload and the medium's pointer.
///
/// * `estate-front-musl-metered` — holger's OWN front program
/// (`holger-estate-front`, bin of the same name), `metered` (the bare
/// monetize, the plugin door) over its default `console`, static musl for the
/// OS-less medium. Its crate is its own workspace — it links monetize and
/// holger-ui, which the open holger resolves nothing of — so the row names
/// `manifest_dir`. Kind `exe` (it ships inside the ISO).
/// * `holger-front-installer-metered` — the medium's pointer; it shares the
/// name of its [[build.installer]] row (`packaging::front_installer_metered`).
///
/// MVP, like the metered box medium it stands beside: one estate, two media — and the two
/// keep media's pointers (`holger-keep`, `holger-front-keep`), which the ONE shop upgrades a
/// sold estate from.
fn front_variants() -> Vec<BuildVariant> {
let payload = BuildVariant {
manifest_dir: Some(crate::packaging::FRONT_MANIFEST_DIR.to_string()),
siblings: crate::front_siblings(),
..Row {
name: crate::packaging::FRONT_PAYLOAD_VARIANT,
suites: &["MVP"],
role: Role::Server,
is_default: false,
package: Some("holger-estate-front"),
bin: Some("holger-estate-front"),
kind: ArtifactKind::Exe,
member: false,
linkage: FAT,
reaches_holger: true,
// `console` NAMED (it is the crate's default): a toggled feature needs
// `no_default_features`, or the crate's default list decides behind the option.
// `public-site`: holger.rs's public face (TASKS-12 S-6, owner 2026-10-25),
// holger-ui's holger-public-site, which holds no HTTP client. The same row
// ships every sold Holger front: there `HOLGER_UI_PUBLIC_SITE=off` turns it off.
features: &["console", "metered", "public-site"],
no_default_features: true,
target: Some(MUSL),
release: true,
container_ref: None,
sign: false,
}
.to_variant()
};
let pointer = |name: &'static str| {
Row {
name,
suites: &["MVP"],
role: Role::Server,
is_default: false,
package: None,
bin: None,
kind: ArtifactKind::Installer,
member: false,
linkage: FAT,
reaches_holger: false,
features: &[],
no_default_features: false,
target: None,
release: false,
container_ref: None,
sign: false,
}
.to_variant()
};
let medium = Row {
name: crate::packaging::FRONT_INSTALLER_METERED,
suites: &["MVP"],
role: Role::Server,
is_default: false,
package: None,
bin: None,
kind: ArtifactKind::Installer,
member: false,
linkage: FAT,
reaches_holger: false,
features: &[],
no_default_features: false,
target: None,
release: false,
container_ref: None,
sign: false,
}
.to_variant();
// The keep media's pointers (L7): each shares its [[build.installer]] row's name
// (`packaging::keep_installer`, `packaging::front_keep_installer`).
vec![
payload,
medium,
pointer(crate::packaging::KEEP_MEDIUM),
pointer(crate::packaging::FRONT_KEEP_MEDIUM),
]
}