Gunnar

nordisk/knut

brokkr.toml

# knut 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): knut had none. Across the whole
# constellation — 38 repos under ~/git — `nornir/brokkr.toml` was the ONLY pipeline
# declaration until Skidbladnir got one this morning. knut declares FIVE variants
# in `.nornir/build-variants.toml` and the forge could plan none of them: five
# shipped artifacts that existed only when a human remembered to type cargo.
#
# WHICH STEPS BUILD AND WHICH RUN — say it once, plainly.
#
#   `run = { variant = … }`  BUILDS. `nornir_build_thing::build_variant`, a real
#                            cargo build under `--real`, producing the artifact.
#   `run = { tests = … }`    RUNS. `RunKind::Tests` (dwarves `abe10d4`) takes the
#                            SAME variant's `cargo_args()` with `build` swapped for
#                            `test` — same package, same features, same profile —
#                            through `buildgate::run_cargo`. It names a variant
#                            rather than carrying its own feature list on purpose:
#                            a test run whose features can drift from the build's
#                            tests something the forge does not ship.
#
# `RunKind::Container` and `RunKind::Vm` still fall through to `UnwiredRunner`, so
# nothing here declares one. Until `abe10d4` there was no word for a test run at
# all and every pipeline in the constellation declared compile coverage only.
#
# ⚠ VALIDATED AGAINST dwarves 0.1.5 (abe10d4173d0), built from source. The binary
# INSTALLED on this box is 0.1.2 (2026-07-17), which predates `RunKind::Tests` and
# REJECTS this file outright — `dwarves validate` prints "invalid: parse …" with no
# further detail. That is a stale validator, not a bad declaration; the file is
# deliberately ahead of the installed forge. Rebuild/reinstall dwarves before
# reading a red from it.
#
# Every `run = …` names a real row in `.nornir/build-variants.toml`. The build
# steps carry no `needs` between them: they are four independent artifacts in three
# packages, and an invented edge would stop one being attempted when another
# breaks. Each test step needs only its own build.
#
# ⚠ TWO VARIANTS GET A BUILD AND NO TEST RUN, and the reason is measured, not
# squeamishness:
#
#   knut-bifrost-wasm-guest  `target = "wasm32-unknown-unknown"`. A `tests` step is
#                            `cargo test --target wasm32-unknown-unknown`, which
#                            needs a wasm test runner this forge does not have —
#                            and the crate holds zero `#[test]` fns anyway. It is
#                            also EXCLUDED from the workspace on purpose, so the
#                            native build never pulls it. Build coverage only.
#   knut-popviz              `gui = […, "autostart"]`, and `autostart` is
#                            `graphar-falkordb/autostart` — the GUI STARTS A
#                            FalkorDB. `crates/knut-popviz/tests/falkor_parity.rs`
#                            reaches for exactly that. A test step here would ask a
#                            forge worker for a container runtime that is not part
#                            of the contract, and a red from a missing daemon reads
#                            as a broken suite. Build coverage only, deliberately.
#
# ⚠ DARK TESTS — the largest hole of the six repos in this lane. Counted on this
# checkout 2026-08-25: **165 test fns behind 14 features that NO declared variant
# enables**, so nothing compiles them and nothing runs them.
#
# 62 of those are the shape `nornir_testmatrix::audit_repo` CANNOT SEE, because it
# walks test TARGETS and these are unit tests inside a LIBRARY, behind
# `#[cfg(feature = "x")] pub mod` in a `src/lib.rs`:
#
#   knut-pipelines   facett-adapter 9 · grpc 8 · airflow 6 · web 5 · gui 2
#   knut-bifrost     spark 6 · metrics-http 3 · wasm 2
#   knut-thund       rel2graph 3 · sql 3 · native 3
#   knut-graphdb     falkordb 3 · cozo 3
#   graphar-falkordb autostart 6
#
# The other 103 are gated integration TARGETS — visible to the audit, still run by
# nobody: knut-thund native 54 · sql 13 · knut-bifrost skade 14 · doris 11 ·
# graphar-falkordb autostart 5 · knut-pipelines grpc 2 · gui 2 · knut-bifrost
# spark 1 · wasm 1.
#
# ⚠ ONE OF THOSE LOOKS COVERED AND IS NOT, so it is named rather than assumed:
# `bifrost`'s `cli` DOES pull `airflow` transitively — but that is **knut-bifrost's**
# `airflow`, and the 6 dark tests are behind **knut-pipelines'** feature of the same
# name (`crates/knut-pipelines/src/lib.rs:117`). Two crates, one word. Same trap
# with `gui`: the `knut-popviz` variant enables **knut-popviz's** `gui`, not
# knut-pipelines' (`src/lib.rs:103`, 2 more dark tests).
#
# ⚠ AND HALF OF IT IS NOT EVEN IN THE DECLARED FEATURE SPACE. `[build] offers`
# lists sixteen features across three crates (knut-bifrost, knut-popviz,
# knut-pipelines) plus `testmatrix`. Ten of the fourteen dark features above are
# absent from it entirely — `flight`, `metrics-http`, `doris` (knut-bifrost);
# `rel2graph`, `dsl`, `sql`, `native` (knut-thund); `falkordb`, `cozo`
# (knut-graphdb); `orc` (graphar). `BuildVariantSet::validate` refuses a variant
# reaching for a capability the repo has not offered, so those cannot be covered
# without widening `offers` first. knut-thund, knut-graphdb and graphar have no
# presence in the build space at all, and knut-thund alone accounts for 76 of the
# dark tests (native 54 + sql 13 in test targets, plus 9 library unit tests).
#
# Closing any of this means new `[[build.variant]]` rows (and, for ten of them, new
# `offers` entries), and a variant is a claim about what knut SHIPS — a human's
# call, not this file's. Written down so it stops being invisible.

[pipeline]
name = "knut-ci"

[[pipeline.trigger]]              # fire when the poll sweep sees HEAD move
kind = "poll"
events = ["push"]

[[pipeline.trigger]]              # …and nightly at 04:40 (off nornir 03:00 / skidbladnir 03:20 / modgunn 03:40 / edda 04:00 / skade 04:20)
kind = "cron"
cron = "40 4 * * *"

# ── the shipped artifacts ────────────────────────────────────────────────────
[[pipeline.stage]]
name = "build"

[[pipeline.stage.step]]
name = "knutpipe"
# ⭐ the pipeline CLI — the only binary a plain `cargo build` actually produces.
run = { variant = "knutpipe" }

[[pipeline.stage.step]]
name = "spark-server"
# The Spark Connect + SDP server. `server = ["draupnir/backend-oci", "dep:draupnir",
# "dep:tokio", "dep:zbus"]` — the container-manager path, off by default so the
# plain build never pulls it. Same package as `knutpipe`, a different feature set.
run = { variant = "knut-spark-server" }

[[pipeline.stage.step]]
name = "bifrost"
# The transform CLI. `cli = ["dep:clap", "dep:knut-tracing", "airflow"]`, so `cli`
# transitively pulls `airflow` (→ reqwest).
run = { variant = "bifrost" }

[[pipeline.stage.step]]
name = "bifrost-falkordb"
# `bifrost watch` + `bifrost falkordb install|k8s|…` (skidbladnir), no Doris sink.
# `falkordb-service = ["cli", "skade-rest", "dep:skidbladnir", "dep:anyhow"]`.
run = { variant = "bifrost-falkordb" }

[[pipeline.stage.step]]
name = "bifrost-falkordb-aistor"
# One connector per build: bifrost-falkordb + `catalog-aistor` — MinIO AIStor (`/_iceberg`, SigV4 `s3tables`). Each row
# is its own cfg combination, so each is built to prove it still compiles alone.
run = { variant = "bifrost-falkordb-aistor" }

[[pipeline.stage.step]]
name = "bifrost-falkordb-polaris"
# One connector per build: bifrost-falkordb + `catalog-polaris` — Apache Polaris. Each row
# is its own cfg combination, so each is built to prove it still compiles alone.
run = { variant = "bifrost-falkordb-polaris" }

[[pipeline.stage.step]]
name = "bifrost-falkordb-unity"
# One connector per build: bifrost-falkordb + `catalog-unity` — Unity Catalog. Each row
# is its own cfg combination, so each is built to prove it still compiles alone.
run = { variant = "bifrost-falkordb-unity" }

[[pipeline.stage.step]]
name = "bifrost-falkordb-gravitino"
# One connector per build: bifrost-falkordb + `catalog-gravitino` — Apache Gravitino. Each row
# is its own cfg combination, so each is built to prove it still compiles alone.
run = { variant = "bifrost-falkordb-gravitino" }

[[pipeline.stage.step]]
name = "bifrost-falkordb-grpc"
# One connector per build: bifrost-falkordb + `skade-grpc` — a remote skade-katalog over gRPC (tonic + rustls). Each row
# is its own cfg combination, so each is built to prove it still compiles alone.
run = { variant = "bifrost-falkordb-grpc" }

[[pipeline.stage.step]]
name = "bifrost-full"
# Every sink and every catalog: `service` + `catalogs` + `skade-grpc`.
run = { variant = "bifrost-full" }

[[pipeline.stage.step]]
name = "popviz"
# The population viz. Build only — see the note above: its `gui` feature starts a
# FalkorDB, which is a runtime dependency this pipeline may not assume.
run = { variant = "knut-popviz" }

[[pipeline.stage.step]]
name = "wasm-guest"
# The WASM transform guest, `--target wasm32-unknown-unknown`. Build only — no
# wasm test runner here, and the crate holds no tests.
run = { variant = "knut-bifrost-wasm-guest" }

# ── the suites those feature sets light up ───────────────────────────────────
[[pipeline.stage]]
name = "test"

[[pipeline.stage.step]]
name = "knutpipe-tests"
needs = ["knutpipe"]
# `cargo test -p knut-pipelines --release` — 81 `#[test]`/`#[tokio::test]` fns in
# the crate, in the default feature set the ⭐ binary ships with.
run = { tests = "knutpipe" }

[[pipeline.stage.step]]
name = "spark-server-tests"
needs = ["spark-server"]
# The SAME package with `--features server` — a different compilation of
# knut-pipelines, and therefore a different suite: `src/lib.rs:133` gates
# `pub mod server` on it, so those tests exist only in this configuration.
run = { tests = "knut-spark-server" }

[[pipeline.stage.step]]
name = "bifrost-tests"
needs = ["bifrost"]
# `cargo test -p knut-bifrost --features cli --release` — 153 test fns, the largest
# suite in the repo, and `cli` transitively lights `airflow` with it.
run = { tests = "bifrost" }

[[pipeline.stage.step]]
name = "bifrost-falkordb-tests"
needs = ["bifrost-falkordb"]
# The lean service closure — the cfg path where `doris_cli` does not exist.
run = { tests = "bifrost-falkordb" }

[[pipeline.stage.step]]
name = "bifrost-full-tests"
needs = ["bifrost-full"]
# The one compilation where every connector's tests exist (catalog_args,
# delivery's Grpc locator, the doris and falkordb service verbs).
run = { tests = "bifrost-full" }