# Skidbladnir 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): Skidbladnir had none. Across the whole
# constellation — 38 repos under ~/git — `nornir/brokkr.toml` was the ONLY pipeline
# declaration, so the forge had no job for this repo at all and nothing was built or
# gated except by a human at a terminal. The cost was paid the same day:
# `vmsuite::judge_active` graded systemd's transitional `deactivating` as **stopped**
# (fixed in `4be0017`), and the 23 pure judge tests that would have caught it live
# behind `--features matrix`, which nothing declared a run of.
#
# Every `run = { variant = … }` below names a row in `.nornir/build-variants.toml`;
# `tests/gated_runs_are_declared_guard.rs` fails this repo's own `cargo test` if a
# feature that gates a test is enabled by no variant, or by a variant no step here
# names. The two files cannot drift apart silently any more.
#
# What a `variant` step IS: `nornir_build_thing::build_variant` — a real cargo build
# under `--real` (`dwarves/crates/dwarves/src/runner.rs:190`). It is NOT a test run;
# `RunKind::Container`/`RunKind::Vm` fall through to a fallback runner that is
# unwired today, so declaring a `cargo test` container step here would be a
# declaration the forge cannot honour. The gated rows are therefore compiled, not
# executed — which kills bit-rot in every dark surface and is honestly less than
# running them.
[pipeline]
name = "skidbladnir-ci"
[[pipeline.trigger]] # fire when the poll sweep sees HEAD move
kind = "poll"
events = ["push"]
[[pipeline.trigger]] # …and nightly at 03:20 (off nornir's 03:00)
kind = "cron"
cron = "20 3 * * *"
# ── the shipping artifacts ───────────────────────────────────────────────────
[[pipeline.stage]]
name = "build"
[[pipeline.stage.step]]
name = "cli"
run = { variant = "skidbladnir" } # the lean offline core (default `pki`)
[[pipeline.stage.step]]
name = "cli-znippy"
needs = ["cli"]
run = { variant = "skidbladnir-znippy" } # the real znippy archive engine linked in
[[pipeline.stage.step]]
name = "probe"
needs = ["cli"]
run = { variant = "skidbladnir-probe" }
[[pipeline.stage.step]]
name = "testsvc"
needs = ["cli"]
run = { variant = "skidbladnir-testsvc" }
# ── the surfaces that only exist behind a feature gate ───────────────────────
# One step per dependency stack, so a red names the stack that broke. Each needs
# `cli` only — they are independent of one another and of each other's failures.
[[pipeline.stage]]
name = "gated-surfaces"
[[pipeline.stage.step]]
name = "matrix" # KVM install matrix + vm-systemd suite + korpprov
needs = ["cli"]
run = { variant = "skidbladnir-matrix" }
[[pipeline.stage.step]]
name = "promote"
needs = ["cli"]
run = { variant = "skidbladnir-promote" }
[[pipeline.stage.step]]
name = "service-drive"
needs = ["cli"]
run = { variant = "skidbladnir-service-drive" }
[[pipeline.stage.step]]
name = "connected" # reqwest: vault + openbao + download
needs = ["cli"]
run = { variant = "skidbladnir-connected" }
[[pipeline.stage.step]]
name = "federation" # ureq: the harbour clients
needs = ["cli"]
run = { variant = "skidbladnir-federation" }
[[pipeline.stage.step]]
name = "cluster" # kube + talos apid
needs = ["cli"]
run = { variant = "skidbladnir-cluster" }
[[pipeline.stage.step]]
name = "sockets"
needs = ["cli"]
run = { variant = "skidbladnir-sockets" }