Gunnar

mikronova/draupnir

.nornir/build-thing-design.md

# draupnir's build thing — what the recipe declares, and why

**The code is the source (lane AG, 2026-08-30).** `crates/draupnir-build-thing`
is draupnir's `impl BuildThing`; `.nornir/build-variants.toml` is **emitted**
from it and read back as authority by nothing:

```
cargo run -p draupnir-build-thing -- emit      # regenerate the file
cargo run -p draupnir-build-thing -- toml      # the emission, on stdout
cargo run -p draupnir-build-thing -- --list    # the roster and its cargo argv
```

Editing the toml changes nothing; the next `emit` deletes the edit.
`tests/toml_parity.rs` fails for **any** reason the file and the code differ.

## What it declares

Three rows, and all three are the **same artifact**: draupnir ships no
executable. It is one crate with one `[lib]` and no `[[bin]]` — the
boot/provisioning library (`BootSpec`/`Machine`, validation, the three backend
seams, the fleet fan-out) that `jera` and `Skidbladnir` link in-process.

| variant | features | what it is |
|---|---|---|
| `draupnir` ⭐ | — | PURE std: trait surface, types, validation, fleet fan-out, **zero dependencies** |
| `draupnir-seed` | `seed` | plus the pure-Rust `fatfs` NoCloud seed-**image** authoring (the file-set render and seed-**dir** output are always compiled) |
| `draupnir-tunnr` | `backend-tunnr` | plus the live KVM wire over tunnr's `boot_test`; pulls `seed`, because the KVM adapter authors the seed image it attaches |

Every row says `linkage = "not-applicable"`. That is a declaration, not an
omission: a library is linked by whoever consumes it and composes nothing, so the
fat/thin axis has no answer — and an *omitted* key reads as the `fat` default,
i.e. a self-containment claim nobody checked.

## Why the recipe is this small

Because the build is. The 2026-07-30 suite inventory listed `draupnir`
(CLI/server) and `fleet_boot` (daemon) as binaries; measured against the crate,
neither exists. Those names are recorded in `recipe.rs`'s doc comment rather than
invented as phantom `[[bin]]` targets — an over-declared recipe is worse than
none, because it lies with more detail. `recipe.rs`'s own test
`no_row_declares_a_binary_because_this_repository_has_none` holds that.

## The four features nothing here builds

`backend-oci`, `backend-youki`, `backend-redfish` and `redfish-server` are real
and no row turns them on. They are in `[build.allow_unbuilt]` with the reason, so
the gap is a written decision rather than a silence. The reason is the same for
the three backends — a backend is a **consumer's** choice about which wire it
compiles in, and draupnir ships no artifact per backend to choose between.
`backend-tunnr` has a row only because it is the one backend whose dependency is
a PATH dep (`tunnr-vm`, not yet published), so whether it still compiles is a
question about *this* repository. `redfish-server` is excused separately: it is a
listening socket, deliberately not implied by `backend-redfish`, and a
client-only build must not grow a server.

`recipe.rs::every_offer_is_either_built_or_excused_by_name` makes an unexcused
gap impossible.