znippy's build thing — what the recipe declares, and why
The code is the source (lane AG, 2026-08-30). znippy-build-thing/ is znippy's impl BuildThing; .nornir/build-variants.toml is emitted from it and read back as authority by nothing:
cargo run -p znippy-build-thing -- emit # regenerate the file
cargo run -p znippy-build-thing -- toml # the emission, on stdout
cargo run -p znippy-build-thing -- --list # the roster and its cargo argv
znippy-build-thing/tests/toml_parity.rs fails for any reason the file and the code differ. The build DOOR is unchanged: cargo xtask build --suite ORIGINAL is suite_cli over the emitted file, and dwarves buildthing znippy comes through the same one.
What it declares
Two atoms, and no more:
| suite | atom | role | linkage | package · bin | features |
| ORIGINAL | znippy ⭐ |
tool | not-applicable | znippy · znippy |
— |
| ORIGINAL | znippy-full |
tool | not-applicable | znippy · znippy |
iceberg, sign |
The five-shape ladder does not apply. <x>-server ⭐ · <x> · <x>-cli · <x>-ui · <x>-solo is an axis about ONE question — where does the server live — and znippy has no server. It is a compressor: an archiver you run, that exits. Nothing to be thin against, nothing to embed, no port, no unit, no deploy. no_row_claims_a_server_shape_because_znippy_has_no_server holds that.
linkage = "not-applicable" is stated out loud rather than omitted, and the difference is not cosmetic: ServiceShape::default() is FatService, so an omitted key reads as a positive claim of self-containment that nobody made and nobody checked. validate_shape holds the value to embeds and reaches both being empty, so the claim is enforced.
Carrying a suites tag is what makes an atom a Build Thing (Rickard, 2026-08-03). Before that pass znippy declared nine atoms and not one carried the tag, so the whole repo sat outside the system: --suite ORIGINAL matched nothing and dwarves buildthing znippy had no delivery to ask for. every_atom_carries_the_delivery_tag keeps it that way.
Both rows write the same bin name deliberately — they are the same artifact at two feature sets, never built in one command. That is exactly why the removed znippy-cli bin had to go: a SECOND package writing the SAME file name is one artifact built twice, with whichever ran last surviving. both_atoms_are_the_same_binary_of_the_same_package pins it.
The feature axis
Clean with nothing excused, which is the strongest form the gate has: the znippy package offers iceberg and sign, and znippy-full builds both. the_feature_axis_gate_is_clean_with_nothing_excused asserts the allowlist is EMPTY, so an excuse added later is caught as the stale rot it would be.
offers covers the znippy package's own axis and stops there. The workspace's other crates declare features of their own (znippy-common: openzl, wasm-plugins, host-decompressors, sign-holger-test, testmatrix; znippy-cli: testmatrix) and those belong to their manifests — widening offers to cover them would make this recipe claim an axis it does not drive.
Appendix — the reasoning that used to sit between the rows
An archive, kept verbatim, in file order. 2026-08-30, lane AG.
Until this day .nornir/build-variants.toml was hand-written, and the reasoning above and between its rows was the most valuable thing in it. The file is emitted now (code → toml, one-directional), and an emission carries data, not argument — a comment typed into it is deleted by the next emit without anybody noticing. So the prose is kept here rather than lost.
New reasoning goes beside the code that acts on it — a doc comment in crates/<repo>-build-thing/src/recipe.rs — or in the section above. Never back into the toml. This appendix is an archive of what was already written, not a place to add to.
after (the file preamble)
znippy — the suite build matrix, as data.
Read by `nornir_build_thing::suite_cli`, which is the WHOLE body of
`cargo xtask build --suite ORIGINAL [--atom <name>]` (xtask/src/main.rs) and the
same door `dwarves buildthing znippy --suite ORIGINAL` comes through. There is no
second build path: this file names the package, the bin, the features and the
profile, and the xtask contributes none of them.
═══════════════════════════════════════════════════════════════════════════════
WHAT znippy IS, AND WHY THE FIVE-SHAPE LADDER DOES NOT APPLY
═══════════════════════════════════════════════════════════════════════════════
korp, dwarves, nornir and modgunn were reshaped on 2026-08-02/03 onto the five
shapes of `workspace_nordisk/.nornir/build-matrix-2026-08-02.md`:
<x>-server ⭐ · <x> · <x>-cli · <x>-ui · <x>-solo
That ladder is an axis about ONE question — *where does the server live* — and
znippy has no server. It is a compressor: an archiver you run, that exits. There
is nothing to be thin against, nothing to embed, no port, no unit, no deploy.
So every atom here is `role = "tool"` and `linkage = "not-applicable"`.
`linkage` is stated OUT LOUD rather than omitted, and the difference is not
cosmetic: `ServiceShape::default()` is `FatService`, so an omitted key reads as a
positive claim of self-containment that nobody made and nobody checked. The value
`not-applicable` says *this axis has no answer here* in a way a reader can tell
apart from *somebody forgot to fill this in* — and `validate_shape` holds it to
`embeds` and `reaches` both being empty, so the claim is enforced, not decorative.
znippy's library atoms already carried it before this pass; the tools now do too.
═══════════════════════════════════════════════════════════════════════════════
THE RULE THAT DECIDES WHAT MAY BE IN THIS FILE
═══════════════════════════════════════════════════════════════════════════════
Rickard, 2026-08-03: **carrying a `suites` tag is what makes an atom a Build
Thing.** Before this pass znippy declared NINE atoms and not one carried the tag,
so the whole repo sat outside the Build Thing system — `--suite ORIGINAL` matched
nothing here and `dwarves buildthing znippy` had no delivery to ask for.
The matrix Rickard approved is TWO rows, and this file is exactly those two:
SUITE ATOM ROLE LINKAGE PACKAGE BIN FEATURES
ORIGINAL znippy ⭐ tool not-applicable znippy znippy —
ORIGINAL znippy-full tool not-applicable znippy znippy iceberg, sign
── WHAT LEFT, AND WHY ────────────────────────────────────────────────────────
✗ `znippy-cli` / `znippy-cli-full` — the DUPLICATE BINARY, removed (the bin only;
the crate stays, and it is where all the code lives). Measured 2026-08-03:
* `znippy/src/main.rs` and `znippy-cli/src/main.rs` were BYTE-IDENTICAL
three-liners — sha256 24abd778… for both — each `fn main() {
znippy_cli::run().unwrap() }`.
* `znippy-cli` is the crate that holds the code (`pub fn run()`); the `znippy`
package is a wrapper added nearly a month later (crate created 2025-06-30 vs
2025-07-25) purely to get the right binary name.
* The repo's own `.nornir/` docs invoke `znippy <verb>` 50 times and
`znippy-cli <verb>` twice.
* Skidbladnir, korp-installer, korp-collectors and garmr all depend on the
`znippy` CRATE; none depends on the `znippy-cli` BINARY.
Two build points writing two files off one `run()` is the twin LAW 5 forbids,
and it had already cost something concrete: `sign` was declared ONLY on
`znippy-cli-full`, i.e. only on the name nobody uses. That is why `znippy-full`
below carries `iceberg` AND `sign` — the feature level survives the collapse
instead of quietly leaving with the binary.
✗ `znippy-common` · `znippy-compress` · `znippy-decompress` ·
`znippy-plugin-skidbladnir` · `znippy-plugin-media` — the five LIBRARY atoms.
They are dependencies and plugins, not deliverables: they are linked by whoever
consumes them, with that consumer's own feature selection, and they belong in
`Cargo.toml` (where they still are — NOTHING was deleted from the workspace).
A Build Thing is a declared BUILD POINT, and `cargo build -p znippy-common` is
not something anybody ships.
── WHY TWO ATOMS SHARE ONE `bin`, AND WHY THAT IS NOT THE dwarves DEFECT ─────
`znippy` and `znippy-full` both emit bin `znippy` from package `znippy` at profile
`release`, so `BuildVariantSet::check_bins` reports them as one artifact path. It
is RIGHT to report and WRONG to forbid, and the distinction is the point:
* The defect it was written for (korp 3× bin `korp`, nornir 3× bin `nornir`,
dwarves 3× bin `dwarves`) was shapes that had to COEXIST — a fat build and a
thin build a deployer needs side by side, that could not sit on one machine.
* These two are a FEATURE VARIANT: one binary, two feature levels, built one or
the other. That is what a variant IS. Giving them separate `[[bin]]` names
would twin `main.rs` again — the exact thing removed above.
`tests/tests/build_variants.rs` therefore does not assert `check_bins().is_empty()`
(which would be false) nor skip the axis (which would be blind). It expresses the
real rule — *a shared artifact path is legal only between feature variants of one
source: same package, same bin, same kind/role/profile/target, and pairwise-
DIFFERENT feature sets* — and drives it RED with a true twin (identical features)
and with a cross-package collision.
after [build]
The feature axis this repo offers. `validate` rejects any variant enabling a
feature not listed here, so this is an allowlist, not a description.
Both are OPT-IN and both are OFF in the ⭐ atom: `iceberg` pulls tokio + iceberg +
parquet + arrow-58 for `--meta-format iceberg` and the `seal` verb; `sign` pulls
RustCrypto for detached CMS provenance (`compress --sign` / `verify --signed`).
The lean archiver is the normal case and carries neither.
after offers = ["iceberg", "sign"]
The delivery every atom is tagged into. znippy ships ONE.
after base_suite = "ORIGINAL"
── ⭐ znippy — the lean default archiver ─────────────────────────────────────
`default = true` marks the normal case of the `tool` role; `check_defaults`
requires exactly one per (suite, role), so this is the answer to "which one do I
build if nobody said".
after linkage = "not-applicable"
Stated, not inherited: `znippy`'s Cargo `default` is `[]`, so this and
`--no-default-features` describe the same closure — and saying it explicitly means
a `default` feature added tomorrow cannot join the shipped binary unannounced.
after profile = "release"
── znippy-full — the same binary with both opt-in levels compiled in ─────────
The ONLY difference from the ⭐ atom is `features`, which is precisely what makes
it a variant rather than a second artifact.
after no_default_features = true
`sign` reaches znippy-cli's `sign` → znippy-common's `sign` (detached CMS).
`iceberg` reaches znippy-cli's `iceberg` → the znippy-iceberg backend.
Both are passthroughs declared in znippy/Cargo.toml; before 2026-08-03 the `sign`
passthrough did not exist there at all and this atom could not have been declared.