Gunnar

nordisk/holger

brokkr.toml

# holger CI/build pipeline — driven by the dwarves forge (brokkr descriptor +
# sindri scheduler). `dwarves validate` / `dwarves plan` / `dwarves run [--real]`.
#
# ── WHY THIS FILE EXISTS (MEASURED 2026-08-25) ───────────────────────────────
#
# holger had none, and holger declares the LARGEST build space in the
# constellation. Across all 38 repos under `~/git`, `nornir/brokkr.toml` was the
# only pipeline declaration until `Skidbladnir` became the second (`83ec551`) the
# same day. So the repository with the most declared build points had none of
# them built by a machine: `.nornir/build-variants.toml` was a catalogue somebody
# had to work through by hand.
#
# ⚠ **23 rows is not 23 variants.** `grep -c '^\[\[build\.'` counts the sugar
# sections too. The real census:
#
#   16  [[build.variant]]        — the only rows a `run = {…}` step can name
#    7  sugar rows               — 1 container, 1 container.layers,
#                                  1 installer, 4 installer.params
#   ── of the 16 variants, 14 are cargo builds and 2 are packaging cells ──────
#
# All 14 cargo rows are declared below. Every one of them.
#
# ── ⚠ WHAT EACH STEP KIND ACTUALLY DOES ──────────────────────────────────────
#
# `run = { variant = … }` is `nornir_build_thing::build_variant` — `set.require`,
# `set.validate`, then ONE `cargo build --message-format=json` over the variant's
# own `cargo_args()`, content-hashing the executables and capturing a provenance
# attestation. Zero shell. **It is a BUILD. It runs no test.**
#
# `run = { tests = … }` is `RealRunner::test_one` — the SAME variant's
# `cargo_args()` with `build` swapped for `test` and `--bin`/`--lib` dropped, run
# through `buildgate::run_cargo`. Same package, same features, same
# `--no-default-features`, same profile: a test run whose features could drift
# from the build's would be testing something the forge does not ship. **It
# compiles AND runs**, so it strictly subsumes a `variant` step on the same row.
#
# ⚠ This kind did not exist until dwarves `ab58b12` (2026-08-25) — *"the forge
# could not run a test, and a pipeline had no word to ask it with"*. The
# `dwarves` / `dwarves-server` binaries installed in `~/.cargo/bin` at the time
# of writing are **0.1.2, built 2026-07-17**, and they reject `tests` at PARSE
# time (`invalid: parse …`). This file is deliberately AHEAD of them: it is
# validated against `dwarves` built from `abe10d4`, and it will fail a stale
# validator until the forge box reinstalls. Dropping the `tests` steps to please
# an old binary would have made this file lie about what holger checks.
#
# `RunKind::Container` and `RunKind::Vm` still fall through to `UnwiredRunner`,
# which fails by design (`dwarves-forge-4`). Nothing here declares one.
#
# ── ⚠ WHY THE TWO PACKAGING CELLS ARE NOT DECLARED (a gap in the forge) ──────
#
# `holger-mvp-appliance` (kind = "container") and `holger-installer`
# (kind = "installer") are real declared variants and are deliberately ABSENT.
#
# MEASURED: `RunKind::Variant` reaches `build_variant`, which only ever runs
# cargo. Nothing in `dwarves/crates/dwarves/src/runner.rs` or `src/brokkr/` calls
# `nornir_build_thing::assemble::assemble` — the function that actually
# materializes a container or an installer. Both rows declare no `package`, no
# `bin`, no `features` and no `profile`, so `cargo_args()` evaluates to the
# single token `build`: a bare DEBUG build of the whole workspace. `set.validate`
# passes them, cargo succeeds, and the step reports GREEN — a declaration that
# the appliance image was produced when nothing of the sort happened.
#
# Both CHAIN `mvp-server-musl` (`[[build.container.layers]] from_variant` and
# `[[build.installer]] variant`), and that row IS built below, so the
# compilation they depend on is covered. Only the packaging is not.
#
# ── HOW THE STEPS ARE GROUPED, AND WHY THAT AXIS ─────────────────────────────
#
# holger offers two candidate axes and only one of them is informative.
#
# The SUITE axis (`ORIGINAL` / `MVP`) is a delivery label: `agent` is in
# both, so a red under it would not name a component. The axis that
# actually predicts what broke is the **PACKAGE plus the dependency stack it
# sits on** — the three families holger's cargo rows fall into, each with its
# own third-party tail, each able to break without the others:
#
#   stage    package               the stack a red names
#   tiers    holger     server-lib, the auth providers, skidbladnir
#                                  federation (the three EXCLUSIVE deployment
#                                  tiers `dwarves holger-build` resolves)
#   mvp      holger +   the MVP suite: secure_update / wg-appliance
#            holger-update-bundle  ceremony / the static musl artifact
#   agent    holger-agent-cli      the agent, and `online` = reqwest/ureq
#
# Within each stage the CHEAPEST row is the root and the rest fan out from it, so
# a broken common core reports once instead of fourteen times, and each `needs`
# edge is chosen so the red between two rows names the feature that separates
# them:
#
#   mini      needs embedded        → the tier cfg, nothing else
#   mvp-server needs mini           → mvp-server = mini + secure_update
#   mvp-musl  needs mvp-server      → identical features, only the TRIPLE differs,
#                                     so a red names the musl toolchain
#   agent-online needs agent        → `online` alone
#
# ── AND WHICH STEPS RUN TESTS, WHICH ONLY BUILD ──────────────────────────────
#
# The rule, applied consistently: **run the tests of the WIDEST resolution in
# each family; BUILD the narrower rows, because what a narrower row asserts is a
# LINKAGE or feature-boundary claim, and that is a build property.**
#
# The three tiers are the exception that proves it: `exclusive = [["embedded",
# "mini", "full"]]` means they are not nested, they are three DIFFERENT
# compilations of one source tree, so no one of them subsumes another's suite.
# All three run.
#
#   step             kind    why this kind
#   embedded         tests   an exclusive tier — its own compilation
#   mini             tests   an exclusive tier — its own compilation
#   full             tests   an exclusive tier, and the widest: server-lib/full
#                            pulls federation + all four auth providers, and the
#                            4 library tests in server/lib/src/upstream/
#   server-default   build   the ONLY row without `no_default_features` — what it
#                            proves is that `default = [mini, online, bench,
#                            secure_update]` still RESOLVES, which is a build fact
#   mvp-server       build   feature-delta row (mini + secure_update); suite ⊂ mini's
#   mvp-server-musl  build   cross-triple static artifact — the point is the
#                            binary, and its tests would run under musl for nothing
#   bundle           tests   -p holger-update-bundle, the network-free signed
#                            bundle core + offline verifier
#   verify           build   same package as `bundle`, second bin — testing it
#                            would be the identical `cargo test` twice
#   agent            build   no features — the bare agent links
#   agent-online     tests   `online` is additive over `agent`, so this is the
#                            wider of the two
#
# ── ⚠ WHAT THIS FILE STILL DOES NOT COVER (measured, not guessed) ────────────
#
# **1. Most of the workspace, and it is not this file's fault.** `RunKind::Tests`
# is PACKAGE-SCOPED by construction: it inherits the variant's own `cargo_args()`,
# which carries `-p <package>`, so a `tests` step runs exactly one package's
# suite. holger's 16 variants name FOUR packages — `holger`,
# `holger-server-ui`, `holger-agent-cli`, `holger-update-bundle`. MEASURED
# 2026-08-25 by counting `#[test]`/`#[tokio::test]` across every member's
# `src` + `tests`:
#
#    827 tests in 33 workspace members
#    210 of them reachable (server-ui 129 + update-bundle 44 + server-cli 23 +
#        agent-cli 14) — **25%**
#    617 in 29 members that NO variant names, so no step here can ever run them.
#        The largest by far is **holger-server-lib at 297** — 36% of the whole
#        suite, and the crate the three tiers are tiers OF. Then the 18
#        `server/repository/*` backends (~180 between them), mannequin 23,
#        build-space 22, xtask 21, holger-build 17, traits 14, agent-lib 13,
#        airgap 10, bench-scenarios 7, and the three agent connectors.
#
# Fixing that means declaring `kind = "lib"` variants for the library crates —
# a change to `.nornir/build-variants.toml`, not to this file. (Note for whoever
# does: a `kind = "lib"` row with no `linkage` is REFUSED, because an omitted
# linkage reads as `fat`; it wants `linkage = "not-applicable"`.)
#
# **2. Seven features gate library tests that NO declared variant enables**, so no
# step here can name them. holger has **no `[build.allow_unbuilt]` at all**, so unlike
# gunnar these are not even excused — they are simply undeclared:
#
#   holger-bench-scenarios (the crate declares NO `default`, so every one of its
#   features is off unless something names it):
#     retention-planner   1 test   bench-scenarios/src/retention.rs
#     audit-append        1 test   bench-scenarios/src/audit.rs
#     secure-update       1 test   bench-scenarios/src/secure_update.rs
#     nexus, artifactory  —        they co-gate `served.rs` with `served`; see below
#     promote-ledger      0 tests  (a feature nothing builds, but no test behind it)
#   holger-server-ui:
#     robot-remote        2 tests  server/ui/src/robot_remote.rs
#
#   ⇒ 7 features; **5 library tests that compile under no resolution at all.**
#
# ⚠ `served` is the interesting one, and it is NOT in that list — for a reason
# worth writing down, because it defeats the obvious way to audit this. It IS
# enabled, but not by any variant and not by any `[features]` table: BOTH
# `server/cli/Cargo.toml:99` and `server/ui/Cargo.toml:110` carry
# `features = ["served"]` **on the dependency edge itself**. So a scan that reads
# `[build] offers` plus the top-level crate's feature closure — which is what
# `feature_closure` and Skidbladnir's `gated_runs_are_declared_guard` both do —
# reports `served` as dark and is wrong. Dependency-edge features are a THIRD
# source of "what is switched on", invisible to both. `served.rs`'s 2 tests
# therefore compile whenever `bench` is on; they still never RUN, for the
# package-scoping reason in (1).
#
# `robot-remote` is not in `[build] offers` at all, so a variant reaching for it
# would be REFUSED by `BuildVariantSet::validate` until `offers` gains it first.
#
# Closing (2) needs a `bench-scenarios` variant and a `robot-remote` UI variant
# in `.nornir/build-variants.toml`, plus the `offers` entries they require. Like
# (1), that is a change to the delivery axis of the constellation's largest build
# space, so it is left for a ruling rather than taken here. Recorded so the
# numbers are not lost.

[pipeline]
name = "holger-ci"

[[pipeline.trigger]]              # fire when the poll sweep sees HEAD move
kind = "poll"
events = ["push"]

[[pipeline.trigger]]              # …and nightly at 04:00 (off nornir 03:00, skidbladnir 03:20, gunnar 03:40)
kind = "cron"
cron = "0 4 * * *"

# ── the three deployment tiers ───────────────────────────────────────────────
#
# `exclusive = [["embedded", "mini", "full"]]`: ONE source tree built three ways,
# each producing its own distinctly-named binary. This is the axis dwarves names
# in its own right — `dwarves holger-build` resolves exactly these three against
# this repo's declared build-space (`crates/dwarves/src/holger_build.rs`,
# `HOLGER_TIERS` / `HOLGER_BINS`) — and it is the only stage here where every row
# runs its tests, because mutually-exclusive features mean no tier's suite
# contains another's.
[[pipeline.stage]]
name = "tiers"

[[pipeline.stage.step]]
name = "embedded"                        # the thinnest tier, so the stage root
run = { tests = "embedded" }
artifacts = ["target/release/holger-embedded"]

[[pipeline.stage.step]]
name = "mini"
needs = ["embedded"]
run = { tests = "mini" }
artifacts = ["target/release/holger-mikro"]

# The widest server resolution: `full` = server-lib/full + online + bench +
# secure_update, and server-lib/full is the only thing that turns on
# `federation`, which pulls skidbladnir with `upstream-azure` + `upstream-aws`.
# A red here with `mini` green names the federation stack.
[[pipeline.stage.step]]
name = "full"
needs = ["embedded"]
run = { tests = "full" }
artifacts = ["target/release/holger-full"]

# The ONE row in the whole file that does not say `no_default_features = true`.
# Every other server row spells its features out, so none of them would notice
# `default = ["mini", "online", "bench", "secure_update"]` failing to resolve.
# This step is the only thing that would.
[[pipeline.stage.step]]
name = "server-default"
needs = ["embedded"]
run = { variant = "server-local" }
artifacts = ["target/release/holger-server"]

# ── the MVP suite ────────────────────────────────────────────────────────────
#
# A separate stage from `tiers` because `suites = ["MVP"]` here is not a label —
# these rows carry the Secure Update Gateway and the appliance ceremony, and they
# ship on their own cadence. A red in this stage with `tiers` green says the MVP
# delivery broke and the ORIGINAL one did not.
[[pipeline.stage]]
name = "mvp"

# Its closure is `mini` + `appliance` + `secure_update`, and the `mini` row above
# already carries `mini` + `appliance` — so the delta between these two steps is
# exactly `secure_update`, and that is what a red here with `mini` green names.
[[pipeline.stage.step]]
name = "mvp-server"
needs = ["mini"]
run = { variant = "mvp-server" }
artifacts = ["target/release/holger-mvp"]

# IDENTICAL features to `mvp-server`; the only difference is
# `target = "x86_64-unknown-linux-musl"`. That makes it the sharpest diagnostic
# in the file: green above and red here can only be the cross toolchain, never
# the source. It is also the payload both packaging cells chain.
[[pipeline.stage.step]]
name = "mvp-server-musl"
needs = ["mvp-server"]
run = { variant = "mvp-server-musl" }
artifacts = ["target/x86_64-unknown-linux-musl/release/holger-mvp"]

# `-p holger-update-bundle`: the NETWORK-FREE signed `holger.update-bundle/v1`
# core and its offline verifier. No server, no UI, no connector, no reqwest — so
# it needs nothing above it and is its own root.
[[pipeline.stage.step]]
name = "bundle"
run = { tests = "mvp-bundle" }
artifacts = ["target/release/holger-bundle"]

[[pipeline.stage.step]]
name = "verify"
needs = ["bundle"]
run = { variant = "mvp-verify" }
artifacts = ["target/release/holger-verify"]

# ── the agent ────────────────────────────────────────────────────────────────
#
# `-p holger-agent-cli`. Two rows separated by ONE feature, so this stage is the
# cheapest boundary in the file to read: `online` = `agent-lib/online`, the
# reqwest/ureq tail. Build the offline row, test the online one — `online` is
# additive, so the wider run contains the narrower one's suite.
[[pipeline.stage]]
name = "agent"

[[pipeline.stage.step]]
name = "agent-offline"
run = { variant = "agent" }
artifacts = ["target/release/holger-agent"]

[[pipeline.stage.step]]
name = "agent-online"
needs = ["agent-offline"]
run = { tests = "agent-online" }
artifacts = ["target/release/holger-agent-online"]