Gunnar

nordisk/Skidbladnir

.nornir/build-thing-design.md

# 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)`

```text
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]`

```text
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 `]`

```text
── skidbladnir (the CLI/daemon): the lean offline core (default `pki`) ───────
```

### after `profile = "release"`

```text
── 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"`

```text
── skidbladnir-probe (probe/): the airgap install/verify probe tool ─────────
```

### after `profile = "release"`

```text
── skidbladnir-testsvc (probe/): the minimal installable test SERVICE ───────
```

### after `profile = "release"`

```text
── 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"`

```text
`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"`

```text
`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"`

```text
`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"`

```text
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"`

```text
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"`

```text
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"`

```text
`sockets`: socket activation / heartbeat (`src/socket.rs`) — 61 library tests,
the single largest dark surface the guard found.
```