Skidbladnir's build thing — what the recipe declares, and why
The code is the source (lane AG, 2026-08-30). crates/Skidbladnir-build-thing is this repo's impl BuildThing; .nornir/build-variants.toml is emitted from it and read back as authority by nothing:
cargo run -p Skidbladnir-build-thing -- emit # regenerate the file
cargo run -p Skidbladnir-build-thing -- toml # the emission, on stdout
cargo run -p Skidbladnir-build-thing -- --list # the roster and its cargo argv
tests/toml_parity.rs fails for any reason the file and the code differ.
What it declares
Four shipped executables — the CLI on the lean offline core (pki, pure-Rust ed25519, every external backend a trait seam that is OFF), the CLI with the real znippy engine linked, and the probe crate's skidbladnir-probe and skidbladnir-testsvc. Plain exe targets: Skidbladnir is a driver and an installer, it reaches BMC/Vault/registries over their own wires, and it embeds no suite sibling, so the fat/thin axis does not apply.
Seven compile-only lib rows, and they are the interesting half. They exist so a feature that GATES A TEST is compiled by something automatic instead of by a human remembering to type --features. MEASURED 2026-08-25: with only the four exe rows declared, 13 features gated tests no variant enabled — ~350 library #[test] fns and four whole tests/*.rs targets that nothing ever compiled. The cost was already paid: vmsuite::judge_active graded systemd's transitional deactivating as stopped, and the 23 pure judge tests that would have caught it sat behind matrix.
The split is by dependency stack — matrix · promote · service-drive · connected (reqwest) · federation (ureq) · cluster (tokio/tonic) · sockets — so a red row names the stack that broke. profile = dev, kind = lib, linkage = not-applicable: nothing here is shipped, and no_test_surface_row_ships_a_binary holds that both ways.
What they prove and do not prove: a brokkr variant step is a cargo BUILD, so the forge COMPILES these surfaces on every push, which kills bit-rot. It does not EXECUTE the tests. Getting the pure judges out from behind matrix so a plain cargo test runs them is still open (.nornir/testmatrix-arms.json, arm 3).
What the emission changed
The retired hand-written file offered 17 features; the manifest declares 26. The nine it never named — pack-oci, oci, machine, kvm, container, jera, testmatrix, promote-k8s, watch-refresh — were invisible to the manifest-direction rot gate. All twelve unbuilt features now carry a written reason in [build.allow_unbuilt], and the_feature_axis_gate_is_clean runs FeatureAxis::gate() over the declaration so the next unexcused feature fails there.
One of the twelve is named as a genuine gap rather than a decision: watch-refresh (the gix-backed watch/refresh path) is built by no row and exercised by no declared test surface. It is written down so it stops being invisible.
The probe crate's third binary, skidbladnir-scenario, was not mentioned in the hand-written recipe at all. It is in [build.allow_unbuilt_bins] now with its reason.
Appendix — the reasoning that used to sit between the rows
An archive, kept verbatim, in file order. 2026-08-30, lane AG.
Until this day .nornir/build-variants.toml was hand-written, and the reasoning above and between its rows was the most valuable thing in it. The file is emitted now (code → toml, one-directional), and an emission carries data, not argument — a comment typed into it is deleted by the next emit without anybody noticing. So the prose is kept here rather than lost.
New reasoning goes beside the code that acts on it — a doc comment in crates/<repo>-build-thing/src/recipe.rs — or in the section above. Never back into the toml. This appendix is an archive of what was already written, not a place to add to.
after (the file preamble)
Skidbladnir — the suite build matrix, as data (nornir-build-thing / `nornir
build`). CLOSES section-2 GAP #2 in
workspace_nordisk/.nornir/suite-build-inventory-and-scheme-2026-07-30.md:
Skidbladnir had NO build cell (hand cargo). Now covered by `nornir build --all`.
Skíðblaðnir is the airgap / systemd+OCI-ops CLI (`skidbladnir`) the whole suite
is installed BY: it wraps each member as an installable unit under a dedicated
system user. The root `skidbladnir` bin builds on the lean, offline-safe default
(`pki`, pure-Rust ed25519) — the external backends (vault / redfish / openbao /
terraform / znippy / msi-oci) are trait seams behind features, off by default so
the lean core never pulls reqwest/podman. The `probe/` sub-crate ships the
`skidbladnir-probe` + `skidbladnir-testsvc` test/probe executables.
skidbladnir is a driver/installer, not a composed suite service — it reaches
BMC/Vault/registries over their own wires when its backends are on; it embeds no
suite sibling → plain `exe` targets (the fat/thin axis does not apply).
after [build]
The feature SPACE this repo declares. The four `exe` rows draw the shipping
half; the `lib` rows at the bottom draw the half that only gates TESTS, which
`BuildVariantSet::validate` refuses to let a variant reach for unless it is
offered here first.
after ]
── skidbladnir (the CLI/daemon): the lean offline core (default `pki`) ───────
after profile = "release"
── skidbladnir with the REAL znippy archive engine compiled IN ──────────────
Off the default so the lean install path stays free of the znippy-core closure;
this variant links ../znippy for real seal/read/verify/unpack.
after profile = "release"
── skidbladnir-probe (probe/): the airgap install/verify probe tool ─────────
after profile = "release"
── skidbladnir-testsvc (probe/): the minimal installable test SERVICE ───────
after profile = "release"
── THE GATED TEST SURFACES ──────────────────────────────────────────────────
Everything below is `kind = "lib"`: these rows exist so a feature that GATES A
TEST is compiled by something automatic instead of by a human remembering to
type `--features`. They ship no binary and are not deliveries.
WHY (MEASURED 2026-08-25, `tests/gated_runs_are_declared_guard.rs`): with only
the four `exe` rows above declared, **13 features gated tests that no variant
enabled** — roughly 350 library `#[test]` fns plus four whole `tests/*.rs`
targets that nothing in this repo ever compiled. The cost of that was already
paid: `vmsuite::judge_active` graded systemd's transitional `deactivating` as
**stopped** (a `systemctl stop` → `Sleep(1500)` → collect case passed on "it
stopped" while a real service drained for 6.4 s), and the 23 pure judge tests
that would have caught it sit behind `matrix` in `src/vmsuite.rs`. Fixed in
`4be0017`; it survived because nothing ran the file.
The split is by DEPENDENCY STACK, not by taste: a row that mixed the reqwest
backends with the ureq harbour clients would go red without saying which stack
broke. Each row below is one closure that can fail on its own.
What these rows prove and do not prove: a brokkr `variant` step is
`nornir_build_thing::build_variant` — a cargo BUILD (see
`dwarves/crates/dwarves/src/runner.rs:190`). So the forge COMPILES these
surfaces on every push, which kills bit-rot; it does not execute the tests.
Making the pure judges execute on every plain `cargo test` needs them out from
behind `matrix` — the split `.nornir/testmatrix-arms.json` arm 3 has carried as
an open follow-up since 2026-08-03.
after profile = "release"
`matrix` (= `kvm` → `machine`): the KVM install matrix, the vm-systemd suite and
the korp-provision suite — 59 library tests, 3 test targets and the
`vm_systemd_suite` bench. Pulls draupnir's `seed` + `backend-tunnr` and libc.
after profile = "dev"
`promote`: the Njord-facing release/promotion API — 99 library tests plus the 17
in `tests/promote_contract.rs`. Pure serde_yaml, so it is the cheapest row here.
after profile = "dev"
`service-drive`: the drivable systemd seam (`src/service_drive.rs`) — 7 library
tests plus `tests/service_kind_live.rs`. Pulls the LEAN draupnir surface only.
after profile = "dev"
The reqwest-backed CONNECTED side: `vault` (PKI issue), `openbao` (structure
turn-on) and `download` (resumable fetch, which also gates `src/harvest.rs`).
One stack, one row — 27 library tests.
after profile = "dev"
The ureq-backed upstream-registry FEDERATION and both harbour clients that gate
their own modules underneath it (`upstream-aws`, `upstream-azure`) — 44 library
tests. A separate row from `skidbladnir-connected` because it is a different TLS
client stack entirely.
after profile = "dev"
The cluster targets: `kubernetes` (kube/k8s-openapi apply + provision) and
`talos` (the apid gRPC client) — 9 library tests over the tokio/tonic stack.
after profile = "dev"
`sockets`: socket activation / heartbeat (`src/socket.rs`) — 61 library tests,
the single largest dark surface the guard found.