# draupnir 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): draupnir had none. Across the whole
# constellation — 38 repos under ~/git — `nornir/brokkr.toml` was the ONLY pipeline
# declaration until this morning, when Skidbladnir became the second. So the forge
# had no job for draupnir at all: `.nornir/build-variants.toml` declared three
# variants and the only thing that ever compiled them was a human typing
# `cargo xtask build`. The cost was payable the same day this file was written —
# `Firmware` (`8f2cf8b`) and the mapping that carries it down to tunnr (`97e1271`)
# both landed today, and the mapping is `backend-tunnr`-gated, i.e. NOT in a default
# `cargo build`. Nothing but this file compiles the wire that was just laid.
#
# ── WHAT EACH STEP KIND ACTUALLY DOES (read before adding one) ──────────────────
#
# run = { variant = "…" } → `nornir_build_thing::build_variant` — a real cargo
# BUILD under `--real`
# (dwarves/crates/dwarves/src/runner.rs:217,253).
# run = { tests = "…" } → the variant's OWN argv with `build` swapped for
# `test`, through `buildgate::run_cargo` — a real cargo
# TEST run (runner.rs:174). Same package, same features,
# same profile; `--bin`/`--lib` are stripped so the
# package's whole suite runs.
# run = { container = … } → `UnwiredRunner`. FAILS. Do not declare one.
# run = { vm = … } → `UnwiredRunner`. FAILS. Do not declare one.
#
# ⚠ THE DECLARATION IS AHEAD OF THE INSTALLED FORGE. `RunKind::Tests` landed in
# dwarves `abe10d4` (2026-08-25). The `dwarves` on this box is 0.1.2 and predates
# it: `dwarves validate` from that binary REJECTS the `tests` steps below with
# "invalid: parse ./brokkr.toml". Validated instead against a `dwarves` built from
# the current source tree. Reinstall the forge; do not delete the test steps.
#
# ⚠ ONLY A CARGO KIND MAY BE NAMED. `build_variant` spawns cargo with
# `variant.cargo_args()` and branches on NOTHING
# (edda/crates/nornir-build-thing/src/provenance.rs:473) — `ArtifactKind::is_cargo()`
# is consulted only by `suite_cli`, which brokkr does not go through. A step naming a
# `kind = "container"/"vm"/"iso"/"kernel"` row therefore runs a bare `cargo build` of
# the workspace and reports it as having built that row: a false green. Every one of
# draupnir's three variants is `kind = "lib"`, so the whole build space is
# declarable here — the only repo of the four where that is true.
[pipeline]
name = "draupnir-ci"
[[pipeline.trigger]] # fire when the poll sweep sees HEAD move
kind = "poll"
events = ["push"]
[[pipeline.trigger]] # …and nightly at 03:40 (off nornir 03:00 / skidbladnir 03:20)
kind = "cron"
cron = "40 3 * * *"
# ── the shipped library, one step per DEPENDENCY STACK ───────────────────────
#
# WHY GROUPED THIS WAY: draupnir is one crate whose three variants differ by
# nothing except which backend stack is linked in, and that is exactly what a red
# needs to name. `core` is PURE std — zero dependencies — so a red there is
# draupnir's own type surface (BootSpec, Machine, Firmware, the validation, the
# fleet fan-out) and nothing else. `seed` is the pure-Rust `fatfs` NoCloud author.
# `tunnr` is the live KVM wire over `tunnr-vm`, the repo's ONE cross-repo path dep.
# The stacks are independent of each other, so each needs `core` only.
[[pipeline.stage]]
name = "build"
[[pipeline.stage.step]]
name = "core"
run = { variant = "draupnir" } # default = [] — the pure-std surface
[[pipeline.stage.step]]
name = "seed"
needs = ["core"]
run = { variant = "draupnir-seed" } # + fatfs NoCloud seed-IMAGE authoring
# THE CROSS-REPO EDGE, and the reason this file could not wait. `backend-tunnr`
# pulls `tunnr-vm` as a path dep and compiles `src/kvm.rs` — where today's
# `97e1271` maps `Firmware` onto tunnr's `ovmf_code` / `ovmf_vars_template`. A
# `cargo build` with default features does not touch that file. tunnr's own
# `47812c4` (locate_ovmf paired a 4 MB CODE with a 2 MB VARS) is on the other end
# of the same wire, in a sibling checkout with no version pin between them. This
# step is the only automatic thing that will notice when the two drift.
[[pipeline.stage.step]]
name = "tunnr"
needs = ["core"]
run = { variant = "draupnir-tunnr" } # backend-tunnr (implies seed)
# ── the suites, at the SAME features the shipped artifact is built with ──────
#
# A test step reuses its variant's argv, so `core-tests` runs the suite with the
# feature set `core` shipped and `tunnr-tests` runs it with `backend-tunnr` on.
# That matters here: `tests/boot_off_medium_proof.rs` is `#![cfg(feature =
# "backend-tunnr")]` and holds 4 `#[test]` fns that a plain `cargo test` compiles
# to an empty binary and reports as `running 0 tests … ok`.
[[pipeline.stage]]
name = "test"
[[pipeline.stage.step]]
name = "core-tests"
needs = ["core"]
run = { tests = "draupnir" }
[[pipeline.stage.step]]
name = "seed-tests"
needs = ["seed"]
run = { tests = "draupnir-seed" }
[[pipeline.stage.step]]
name = "tunnr-tests"
needs = ["tunnr"]
run = { tests = "draupnir-tunnr" }
# ── WHAT IS DELIBERATELY NOT DECLARED HERE, AND WHY ──────────────────────────
#
# FOUR features gate tests in this repo that NO `[[build.variant]]` enables, so no
# step above can name them and ~54 `#[test]` fns are compiled by nobody:
#
# feature | dark tests | offered? | what is dark
# -----------------+------------+----------+---------------------------------------
# redfish-server | 41 (21+20)| NO | tests/redfish_server_conformance.rs +
# | | | the 20 unit tests in src/redfish_server.rs
# | | | (gated at src/lib.rs:81 `pub mod`)
# backend-youki | 5 (1+4) | yes | tests/stage2_youki_start_proof.rs +
# | | | src/youki.rs (gated at src/lib.rs:84)
# backend-oci | 4 | yes | live_oci_list_and_env · live_oci_published_port
# | | | · stage1_container_start_proof
# backend-redfish | 4 | yes | tests/redfish_kvm_loop_proof.rs, which is
# | | | `#![cfg(all(redfish-server, backend-redfish,
# | | | backend-tunnr))]` — a triple no variant meets
#
# That is NOT fixable in this file: a step can only name a variant, and the fix is
# a `[[build.variant]]` in `.nornir/build-variants.toml` (plus a `[build] offers`
# entry for `redfish-server`, which `BuildVariantSet::validate` refuses without).
# Left as a declared finding rather than done silently — the OCI/youki/redfish rows
# are a change to the shared build space, not to this pipeline. Add them and the
# steps belong here.
#
# `Firmware` (`8f2cf8b`) WANTS NO VARIANT OF ITS OWN, and this is the measured
# answer, not a shrug: it is a plain `enum` in the always-compiled `src/lib.rs:377`,
# not a cargo feature. HostDefault/Uefi/SecureBoot/SecureBootEnrolled are a RUNTIME
# choice a caller passes in a `BootSpec`, so every variant above already compiles
# all four arms and there is no build axis to split. What DOES need a build point is
# the code that carries the choice to qemu, and that is `backend-tunnr` — the
# `tunnr` step above.