Gunnar

nordisk/korp-collectors

.nornir/build-thing-design.md

# korp-collectors' build thing — what the recipe declares, and why

**The code is the source (lane AG, 2026-08-30).**
`crates/korp-collectors-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 korp-collectors-build-thing -- emit      # regenerate the file
cargo run -p korp-collectors-build-thing -- toml      # the emission, on stdout
cargo run -p korp-collectors-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

One package, one shipped `[[bin]]`, one library. Four rows:

| variant | kind | features | what it is |
|---|---|---|---|
| `korp-collectors` ⭐ | exe | `parsers` | the driver on the LIGHT surface: DCAT-AP-SE RDF, PxWeb/JSON-stat2, ENTSO-E XML, REST/JSON, Trafikverket → Arrow |
| `korp-collectors-osm` | exe | `osm` | plus the OSM geometry connectors and the `RegionSource` fetch seam |
| `korp-collectors-osm-daemon` | exe | `osm-daemon` | plus the region-cut daemon: planet → region catalog → the lettered a–g cut → ONLINE, and the post-condition suite that decides whether a fresh extract may replace the one being served |
| `korp-collectors-lib` ⭐ | lib | `parsers` | the parser/registry library a consumer links in-process |

`linkage = "not-applicable"` on the library row is a declaration, not an
omission. The driver rows are plain exes: korp-collectors reaches its sources
over HTTP, embeds no suite sibling, and is not itself a suite service.

## What the hand-written file got wrong, and the emission exposes

* it listed `daemon` in `offers` and nothing built it, with **no excuse** — the
  precise shape of rot the gate exists for;
* it never mentioned **eleven** of the crate's fifteen features at all, so the
  manifest-direction gate had nothing to ask about them;
* it never mentioned the second `[[bin]]`, **`korp-chain-init`** — the KVM
  guest's PID 1 for the a–e chain drill. It is in `[build.allow_unbuilt_bins]`
  now, with the reason (`tests/osm_chain_kvm.rs` builds it behind `kvm-guest`; a
  delivery never does, because a restart is not a `drop()` and `st_blocks` is a
  property of a real filesystem).

The inventory's one "CLI" — `collectors_parse` — is
`[[bench]]` targets, and `the_only_binary_a_row_names_is_the_one_that_exists`
holds the recipe to that.

## The feature axis

Fifteen features; four built; eleven excused **by name** in
`[build.allow_unbuilt]`, each saying what the feature is and why nothing here
compiles it. The three recurring reasons are worth stating once:

* a **sink** (`skade-sink`, `znippy`) is the consumer's choice — the same
  collector feeds a memory sink, a znippy archive or Iceberg;
* a **runtime substrate** (`service`, `systemd`) is the deployer's choice, and
  every byte of it routes through Skidbladnir;
* a **per-source collector** (`csv`, `mainframe`, `gtfs`, `gribnc`) is a
  deployment decision; pulling every parser into one binary is not a decision
  this repo has taken.

`recipe.rs::the_feature_axis_gate_is_clean` runs
`FeatureAxis::gate()` over the declaration, so a new feature with no row and no
excuse fails here rather than rotting — and a stale excuse (a feature a row now
builds) fails too.