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

nordisk/monetize

crates/monetize-build-thing/src/lib.rs

//! # monetize-build-thing — monetize's build space, declared in Rust
//!
//! LAW: **the code is the source.** [`MonetizeBuildThing`] is the only
//! declaration of what monetize builds. `.nornir/build-variants.toml` is
//! EMITTED from it (`cargo run --manifest-path crates/monetize-build-thing/Cargo.toml -- emit`).
//!
//! Model: **variant → features → settings/sub-settings → typ.**
//!
//! ## The deliveries (`suites`)
//!
//! | suite        | variant                      | builds |
//! |--------------|------------------------------|--------|
//! | both         | `monetize-admin`             | the ONE box's program (server and console processes), musl static; the server of both deliveries |
//! | `ORIGINAL`   | `monetize-admin-installer`   | its medium |
//! | both         | `monetize-ui-server`         | the operator console's BFF, musl static; never its role's default |
//! | both         | `monetize-ui-web`            | the console's browser bundle (wasm), claimed by `[[build.web_app]]` → `crates/monetize-ui-web/dist` |
//!
//! The standalone `monetize-server` rows (`monetize-server` ORIGINAL, `monetize-server-commercial`
//! COMMERCIAL: the server a front used to run) went in v2.0 P2 (A25 2.6): no estate runs a
//! monetize, and the ONE box is monetize-admin.
//!
//! ORIGINAL is tagged like every nordisk product, so `--suite ORIGINAL` is one
//! command shape everywhere. The BASE is COMMERCIAL (`base_suite`), so the
//! subset rule (features(ORIGINAL) ⊆ features(COMMERCIAL)) stays checked. Not
//! `independent_suites`: that would switch the check off, and the installed
//! forge (dwarves 6ef408a) cannot even parse the key.
//!
//! ## No plugin, not even as an edge
//!
//! OWNER RULING 2026-09-25: monetize's Build Thing carries NO plugin; plugins
//! are received at RUNTIME. The product guests (gunnar, holger) are
//! built and signed in the private `monetize-impl` (its rows `gunnar-guest`,
//! `holger-guest`, `sign = { purpose = "plugin" }`) and handed to
//! a RUNNING server: `[products.<id>] guest = …` plus the `.p7s`, taken in by
//! the server's guest watcher without a restart
//! (`.nornir/01-products-as-guests.md`, "Guests on the fly").
//!
//! So no row names a private checkout. The consumption edges this crate used
//! to carry (to monetize-impl's guest rows) are gone: they made the whole declaration
//! refuse to load on a box whose `monetize-impl` checkout declares no build
//! space (`[dwarves-buildthing-4]`), and `holger-plugin` was the guest holger's
//! APPLIANCE loads, never a product guest this server serves.
//!
//! The deliveries stay two: the paid delivery is the open program plus signed guests delivered
//! at runtime, and a delivery name is what a release and a receipt say.

use std::collections::{BTreeMap, BTreeSet};
use std::path::{Path, PathBuf};

use anyhow::Result;
use nornir_build_thing::cargo_container::CargoToolchain;
use nornir_build_thing::emit;
use nornir_build_thing::plugin::{BuildThing, Manifest, Role, ABI_VERSION};
use nornir_build_thing::variant::{
    ArtifactKind, BuildVariant, BuildVariantSet, Profile, ServiceShape,
};

/// The checkout this declares.
pub const REPO: &str = "monetize";
/// This crate.
pub const SOURCE_CRATE: &str = "monetize-build-thing";
/// The open delivery — the base every nordisk product declares.
pub const ORIGINAL: &str = "ORIGINAL";
/// The paid delivery: the same server; its plugins arrive at runtime.
pub const COMMERCIAL: &str = "COMMERCIAL";

/// The server package and its bin.
pub const SERVER_PACKAGE: &str = "monetize-server";
/// The target the front runs: a static musl binary (the front is a glibc-2.39
/// box and must never depend on which glibc built the artifact).
pub const MUSL: &str = "x86_64-unknown-linux-musl";

/// The pinned toolchain image (same digest gunnar/holger/monetize-impl build in).
pub const TOOLCHAIN_IMAGE: &str = "nordisk-cargo-toolchain";
/// Its MANIFEST digest.
pub const TOOLCHAIN_SHA256: &str =
    "86d575e874677da3eb9aff3ba6e38ca600781ee9b7948a9df43ef89a6213c762";
/// What the image reports; `rust-toolchain.toml` names the same compiler.
pub const RUST_VERSION: &str = "1.97.1";
/// Sibling checkouts the repo's path dependencies reach, transitively
/// (`cargo_container::sibling_closure`, held equal by a test).
pub const SIBLINGS: &[&str] = &[
    "Skidbladnir",
    "draupnir",
    "edda",
    "facett",
    "funnel-core",
    "holger",
    "holger-ui",
    "knut",
    "korp-collectors",
    "modgunn",
    "nornir",
    // holger main (d77cf3f) reaches znippy's `znippy-common`, which path-depends on the
    // openzl-sys-rs checkout: measured by `the_declared_siblings_cover_the_reach`.
    "openzl-sys-rs",
    "skade",
    "tunnr",
    "znippy",
    "znippy-zoomies",
];

/// monetize's build thing.
#[derive(Debug, Clone, Copy, Default)]
pub struct MonetizeBuildThing;

/// The operator console's BFF package and its bin.
pub const UI_SERVER_PACKAGE: &str = "monetize-ui-server";
/// The operator console's browser half (Leptos → wasm).
pub const UI_WEB_PACKAGE: &str = "monetize-ui-web";
/// Where the browser package lives, repository-relative: its `index.html`,
/// and the bundle directory the web-app step writes ([`UI_WEB_DIST`]).
pub const UI_WEB_DIR: &str = "crates/monetize-ui-web";
/// The built console, repository-relative — what the front medium embeds
/// (gunnar-front `build.rs`, sibling `monetize/crates/monetize-ui-web/dist`).
pub const UI_WEB_DIST: &str = "crates/monetize-ui-web/dist";
/// The browser target.
pub const WASM: &str = "wasm32-unknown-unknown";

/// **The operator console's BFF** (`monetize-ui-server`): musl static, like
/// the server it fronts — the front runs it beside monetize-server on
/// loopback `127.0.0.1:8091`. Shipped in BOTH deliveries (the console is not a
/// plugin), never the default of its role: the server is.
fn ui_server() -> BuildVariant {
    BuildVariant {
        name: UI_SERVER_PACKAGE.to_string(),
        package: Some(UI_SERVER_PACKAGE.to_string()),
        bin: Some(UI_SERVER_PACKAGE.to_string()),
        features: Vec::new(),
        profile: Profile::Release,
        target: Some(MUSL.to_string()),
        kind: ArtifactKind::Exe,
        shape: ServiceShape::NotApplicable,
        role: Some(Role::Server),
        suites: vec![ORIGINAL.to_string(), COMMERCIAL.to_string()],
        is_default: false,
        ..BuildVariant::default()
    }
}

/// **The operator console's browser bundle** (`monetize-ui-web`, wasm) —
/// claimed by the `[[build.web_app]]` of the same name ([`web_apps`]), which
/// turns it into `dist/` in the pinned toolchain, no host trunk.
fn ui_web() -> BuildVariant {
    BuildVariant {
        name: UI_WEB_PACKAGE.to_string(),
        package: Some(UI_WEB_PACKAGE.to_string()),
        bin: Some(UI_WEB_PACKAGE.to_string()),
        features: Vec::new(),
        profile: Profile::Release,
        target: Some(WASM.to_string()),
        kind: ArtifactKind::Wasm,
        // THIN: a browser bundle that reaches the BFF over HTTP and compiles
        // nothing of it in (`validate_shape`: a row with a sibling edge
        // declares the axis).
        shape: ServiceShape::ThinStandalone,
        role: Some(Role::Client),
        suites: vec![ORIGINAL.to_string(), COMMERCIAL.to_string()],
        is_default: true,
        reaches: [UI_SERVER_PACKAGE.to_string()].into_iter().collect(),
        ..BuildVariant::default()
    }
}

/// **The monetize-admin appliance's program** (owner 2026-09-28): the ONE
/// operator console of every estate's monetize, PID 1 on a tunnr kernel with no
/// shell (`crates/monetize-admin`, its OWN workspace — it links the installer
/// and the web face, which monetize's workspace resolves nothing of — so the row
/// names `manifest_dir`). It embeds the console bundle the web-app row writes
/// ([`UI_WEB_DIST`]), so that row is built first. ONE delivery (ORIGINAL):
/// the box is the operator's, not a product delivery, and a medium is composed
/// by naming its one suite (`--suite ORIGINAL`, what the ops kit reads off the
/// row).
pub const ADMIN_PACKAGE: &str = "monetize-admin";
/// Its crate, repository-relative.
pub const ADMIN_MANIFEST_DIR: &str = "crates/monetize-admin";
/// The medium it ships in (and the medium's pointer variant).
pub const ADMIN_INSTALLER: &str = "monetize-admin-installer";
/// The medium's volume id — its own program, its own id.
pub const ADMIN_VOLUME_ID: &str = "MONETIZE_ADMIN_INSTALL";

fn admin() -> BuildVariant {
    BuildVariant {
        name: ADMIN_PACKAGE.to_string(),
        package: Some(ADMIN_PACKAGE.to_string()),
        bin: Some(ADMIN_PACKAGE.to_string()),
        manifest_dir: Some(ADMIN_MANIFEST_DIR.to_string()),
        features: Vec::new(),
        profile: Profile::Release,
        target: Some(MUSL.to_string()),
        kind: ArtifactKind::Exe,
        shape: ServiceShape::NotApplicable,
        role: Some(Role::Server),
        // The ONE box's program is the server of both deliveries (v2.0 P2 12.6, A25 2.6: the
        // standalone monetize-server rows went; no estate runs a monetize).
        suites: vec![ORIGINAL.to_string(), COMMERCIAL.to_string()],
        is_default: true,
        ..BuildVariant::default()
    }
}

/// The medium's pointer row — it shares the name of its `[[build.installer]]`.
fn admin_medium() -> BuildVariant {
    BuildVariant {
        name: ADMIN_INSTALLER.to_string(),
        kind: ArtifactKind::Installer,
        shape: ServiceShape::NotApplicable,
        role: Some(Role::Server),
        suites: vec![ORIGINAL.to_string()],
        is_default: false,
        ..BuildVariant::default()
    }
}

/// **monetize-admin-installer** — the admin box's medium: `monetize-admin` as
/// PID 1 (`korp.entry`), the tunnr kernel as its donor (`tunnr/tunnr-kernel`,
/// the one kernel every nordisk front boots), /data on the system disk's P3.
///
/// Its kernel rows are gunnar's FRONT medium's, symbol for symbol (hot-plug,
/// the paravirt wall clock, the network and TUN for the installer's
/// WireGuard link) and its data group the same (xfs, io_uring): the donor's
/// pin is per requirement fragment, and a medium asking a different set is
/// refused "the built kernel is not the CHOSEN kernel" — so the admin box boots
/// the very kernel the estates' fronts boot.
///
/// Parameters (site facts, never defaults): `pairing_seed` (a seed of kind
/// `front`, staged at `/data/pairing/seed.toml`), `reinstall`, `install_then`,
/// `log_udp`, `clock` (the box's own time sources, `korp.env.ESTATE_CLOCK`).
/// The rest — the web host, the certificate or ACME, the break-glass
/// token, the estates — is the installer's FEED.
#[must_use]
pub fn admin_installer() -> nornir_build_thing::installer::InstallerBuild {
    use nornir_build_thing::installer::{InstallerBuild, KernelDonor, MediumBoot, PayloadKind};
    use nornir_build_thing::kernel_requires::KconfigReq;
    use nornir_build_thing::params::{ParamKind, ParamSpec};
    use nornir_build_thing::requires::{EdgeRequirement, Requirement};
    let kconfig = [
        ("CONFIG_HOTPLUG_PCI", "the hot-plug core: gunnar's front medium's kernel row, kept so the donor's fragment is the fronts' own"),
        ("CONFIG_HOTPLUG_PCI_ACPI", "acpiphp: the firmware-driven eject path (the fronts' kernel row)"),
        ("CONFIG_PCIEPORTBUS", "binds the PCIe root ports (the fronts' kernel row)"),
        ("CONFIG_HOTPLUG_PCI_PCIE", "pciehp: native PCIe hot-plug (the fronts' kernel row)"),
        ("CONFIG_HOTPLUG_PCI_SHPC", "shpchp: a conventional pci-bridge slot (the fronts' kernel row)"),
        ("CONFIG_PCI_MSI", "MSI, without which pciehp is dead (the fronts' kernel row)"),
        ("CONFIG_HYPERVISOR_GUEST", "the gate of every paravirt guest feature: without it the wall clock is the emulated CMOS, and a passkey ceremony and a signed call are both judged by the clock"),
        ("CONFIG_PARAVIRT", "the paravirt op vectors kvmclock installs itself through"),
        ("CONFIG_KVM_GUEST", "kvmclock's guest half"),
        ("CONFIG_PARAVIRT_CLOCK", "the paravirt clocksource: every SSHSIG call carries its time and is refused outside the skew window"),
        ("CONFIG_NET", "the box serves HTTPS and dials every estate's monetize: no network stack, no socket"),
        ("CONFIG_INET", "IPv4, and the installer link's outer UDP"),
        ("CONFIG_NETDEVICES", "the virtio NICs"),
        ("CONFIG_NET_CORE", "the network device core"),
        ("CONFIG_TUN", "boringtun, the userspace WireGuard of the installer's link, opens /dev/net/tun"),
    ];
    let text = |name: &str, label: &str, boot_param: &str, staged: &str| ParamSpec {
        name: name.to_string(),
        label: label.to_string(),
        kind: ParamKind::Text,
        required: false,
        boot_param: boot_param.to_string(),
        staged_file: staged.to_string(),
        // The pairing seed lands on /data on the first boot (nornir-build-thing's default).
        lands: nornir_build_thing::params::Landing::Data,
    };
    InstallerBuild {
        name: ADMIN_INSTALLER.to_string(),
        variant: ADMIN_PACKAGE.to_string(),
        volume_id: ADMIN_VOLUME_ID.to_string(),
        payload: PayloadKind::EmbeddedIso,
        target_disk_gib: 16,
        gpu: false,
        boot_params: ["console=tty0", "console=ttyS0,115200", "panic=10", "korp.appliance=lean", &format!("korp.entry={ADMIN_PACKAGE}")].map(str::to_string).to_vec(),
        env: BTreeMap::from([("MONETIZE_ADMIN_DATA".to_string(), "/data".to_string())]),
        kernel_donor: Some(KernelDonor { repo: "tunnr".to_string(), kernel: "tunnr-kernel".to_string() }),
        data: nornir_build_thing::data::Data { fs: nornir_build_thing::data::DataFs::Xfs, io: nornir_build_thing::data::DataIo::Uring },
        medium_boot: MediumBoot::Hybrid,
        params: vec![
            ParamSpec { kind: ParamKind::Endpoint, ..text("log_udp", "Boot-narration UDP sink for a console-less VM (debug; a.b.c.d:port)", "korp.log_udp", "") },
            text("reinstall", "Reinstall policy for a disk that already carries data: keep (upgrade, /data survives — the ledger and monetize's signing key with it) | wipe (lay it fresh: an EMPTY ledger and a NEW signing key every estate must be fed again)", "korp.reinstall", ""),
            text("install_then", "After INSTALL-OK: reboot (a person at the box) | poweroff (a cloud re-image tool detaches the medium)", "korp.install.then", ""),
            text("pairing_seed", "Pairing seed (nordisk-pairing seed.toml: estate, front, installer CA, bootstrap cert+key)", "", "pairing/seed.toml"),
            text("clock", "The box's clock sources (estate-clock ESTATE_CLOCK: https://…,https://… on the cloud; the bench's https://10.0.2.100:<port>/ on a mock). The ONE box has no front to follow: unset, it keeps whatever clock it booted with", "korp.env.ESTATE_CLOCK", ""),
        ],
        requires: kconfig.iter().map(|(sym, why)| EdgeRequirement { edge: "kernel".to_string(), what: Requirement::Kconfig(KconfigReq::y(sym, why)) }).collect(),
        ..InstallerBuild::default()
    }
}

/// Every variant, in declaration order.
#[must_use]
pub fn variants() -> Vec<BuildVariant> {
    vec![ui_server(), ui_web(), admin(), admin_medium()]
}

/// **`[[build.web_app]]`** — the console bundle built through the Build
/// Thing's web-app step (nornir-build-thing `web_app`): the wasm row's cargo
/// build in the pinned container, wasm-bindgen as a library at the version the
/// lock pins, the page assembled in Rust from `crates/monetize-ui-web/index.html`
/// (its `data-trunk` links: rust, css, icon, copy-file). Until 2026-09-28 the
/// only road to `dist/` was host `trunk`, and nothing in the install path ran
/// it, so the front medium carried no monetize console at all.
#[must_use]
pub fn web_apps() -> Vec<nornir_build_thing::web_app::WebAppBuild> {
    vec![nornir_build_thing::web_app::WebAppBuild {
        name: UI_WEB_PACKAGE.to_string(),
        out_dir: UI_WEB_DIST.to_string(),
        index: format!("{UI_WEB_DIR}/index.html"),
    }]
}

/// The toolchain every row compiles in — never the host.
#[must_use]
pub fn toolchain() -> CargoToolchain {
    CargoToolchain {
        image: TOOLCHAIN_IMAGE.to_string(),
        image_sha256: TOOLCHAIN_SHA256.to_string(),
        rust_version: RUST_VERSION.to_string(),
        targets: vec![
            "x86_64-unknown-linux-gnu".to_string(),
            MUSL.to_string(),
            "wasm32-unknown-unknown".to_string(),
            // korp-boot, the monetize-admin medium's first stage (MEASURED on
            // the mock 2026-09-29: "needs the x86_64-unknown-uefi rust-std").
            // The image carries it; gunnar's and holger's declarations say so.
            "x86_64-unknown-uefi".to_string(),
        ],
        timeout_secs: 3600,
        mem_limit_mb: 8192,
        cpus: 8,
        siblings: SIBLINGS.iter().map(|s| (*s).to_string()).collect(),
        ..CargoToolchain::default()
    }
}

/// The feature universe rows may draw from — the server's own features.
fn offers() -> BTreeSet<String> {
    ["invoice", "sandbox", "testmatrix"]
        .iter()
        .map(|s| (*s).to_string())
        .collect()
}

impl BuildThing for MonetizeBuildThing {
    fn manifest(&self) -> Manifest {
        Manifest {
            abi: ABI_VERSION,
            repo: REPO.to_string(),
            source_crate: SOURCE_CRATE.to_string(),
            summary: "monetize: the license/metering server, musl static for the front — \
                      ORIGINAL and COMMERCIAL, the same open server that links no product; \
                      plugins are signed guests it takes in at runtime"
                .to_string(),
        }
    }

    fn variants(&self) -> BuildVariantSet {
        BuildVariantSet {
            base_suite: Some(COMMERCIAL.to_string()),
            offers: offers(),
            exclusive: Vec::new(),
            required: BTreeMap::new(),
            downstream: BTreeMap::new(),
            variants: variants(),
            web_apps: web_apps(),
            installers: vec![admin_installer()],
            toolchain: Some(toolchain()),
            ..BuildVariantSet::default()
        }
    }
}

/// The header every emission starts with.
pub const EMITTED_HEADER: &str = "\
# monetize — .nornir/build-variants.toml — EMITTED, NOT SOURCE.
#
# Generated by `cargo run --manifest-path crates/monetize-build-thing/Cargo.toml -- emit`
# from `crates/monetize-build-thing` (`impl BuildThing` in src/lib.rs). The code is the
# only source; editing this file changes NOTHING — re-run `emit` after a change.
#
# Model: variant -> features -> settings/sub-settings -> typ.
#
# Deliveries: ORIGINAL and COMMERCIAL, the same open server. No plugin is built here:
# plugins are signed guests built in monetize-impl and taken in by a RUNNING server
# (owner ruling 2026-09-25).";

/// The set this crate declares — the only source.
#[must_use]
pub fn declared() -> BuildVariantSet {
    MonetizeBuildThing.variants()
}

/// What `.nornir/build-variants.toml` must contain.
pub fn emitted_toml() -> Result<String> {
    emit::emitted_toml(EMITTED_HEADER, &MonetizeBuildThing)
}

/// Rewrite `<repo_root>/.nornir/build-variants.toml` from the code.
pub fn write_emitted(repo_root: &Path) -> Result<PathBuf> {
    emit::write_emitted(EMITTED_HEADER, &MonetizeBuildThing, repo_root)
}

/// The repo root: two up from this crate (`crates/<this>`).
#[must_use]
pub fn repo_root() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .and_then(Path::parent)
        .expect("crates/monetize-build-thing has two parents")
        .to_path_buf()
}

/// Refuse when the committed file is not the code's emission.
pub fn require_committed_is_emitted(repo_root: &Path) -> Result<()> {
    let path = BuildVariantSet::conventional_path(repo_root);
    let committed = std::fs::read_to_string(&path)
        .map_err(|e| anyhow::anyhow!("read {}: {e}", path.display()))?;
    if let Some(diff) = emit::first_difference(&committed, &emitted_toml()?) {
        anyhow::bail!(
            "{} is not the code's emission ({diff}) — run \
             `cargo run --manifest-path crates/monetize-build-thing/Cargo.toml -- emit`",
            path.display()
        );
    }
    Ok(())
}

nornir_build_thing::export_build_thing!(MonetizeBuildThing);