# skade's build thing — what the recipe declares, and why
**The code is the source (lane AG, 2026-08-30).** `crates/skade-build-thing` is
skade's `impl BuildThing`; `.nornir/build-variants.toml` is **emitted** from it
and read back as authority by nothing:
```
cargo run -p skade-build-thing -- emit # regenerate the file
cargo run -p skade-build-thing -- toml # the emission, on stdout
cargo run -p skade-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
Two rows, because skade ships two things:
| variant | kind | package · bin | what it is |
|---|---|---|---|
| `skade` ⭐ | lib | `skade` | the embeddable Iceberg library, with the `s3` object-store backend |
| `skade-remote` ⭐ | service | `skade-katalog-serve` · `skade-katalog` | the catalog service that owns the state |
skade is tri-modal from the *consumer's* side — compile it in and own the
`RedbCatalog`, or reach a running `skade-katalog serve` over gRPC / Iceberg-REST
— but that is the consumer's linkage axis, not skade's.
## The root invariant, as a check
**Nothing is downstream of skade.** The `[build.downstream] skade = []` allowlist
is EMPTY and PRESENT, which are different claims from an absent one, and
`nothing_is_downstream_of_skade_and_an_embed_is_refused` proves the refusal by
building a synthetic variant with `embeds = ["korp"]` and asserting `validate`
rejects it. The service row states `embeds` and `reaches` as explicitly empty
rather than omitting them.
## What the emission changed
The six declared-but-never-built features — `aws-s3`, `rustfs`, `lineage`,
`stream`, `lineage-http`, `testmatrix` — were recorded on 2026-07-22 in prose.
They carry a written reason in `[build.allow_unbuilt]` now, and
`the_feature_axis_gate_is_clean` runs `FeatureAxis::gate()` every run.
`sql` is held out by a test of its own. The DataFusion surface moved to the
sibling `skade-sql` crate on 2026-08-14 (`0369d66`), and this recipe kept
offering it until 2026-08-23 — nine days in which the ⭐ library row named a
build point cargo refused outright (*"the package `skade` does not contain this
feature: sql"*). That is exactly what a hand-maintained recipe cannot catch and
an emitted one can.
## What is deliberately NOT offered
`offers` covers the `skade` crate's own axis and stops there. Two names the
workspace declares are left out on purpose, and it is worth saying so rather than
letting the omission look like an oversight:
* **`geo-http`** — the HTTP byte-range GeoParquet reader, so a viewer fetches
only the row groups its viewport touches. That is a viewer's decision, not a
shape skade ships;
* **`bench`** — a re-export that lets the out-of-crate nornir-bench harness A/B
the partition-scan accelerator without widening the public API. It exists for a
bench harness, and `cargo publish` has it off.
The sibling crates' feature names (`object-store` in `serve/`, `uring` and `rest`
in `bench/`) belong to *their* manifests; widening `offers` to cover them would
make this recipe claim an axis it does not drive.
## One note on the dependency
`crates/skade-build-thing` is the ONE place in this repo with a **path** dep on
edda's `nornir-build-thing`. skade deliberately keeps its own dev-dependency on
the published crate, and that is unchanged. The recipe crate needs 0.1.12
vocabulary (the emitter and the schema rule; the published 0.1.11 has neither),
and it is `publish = false`, so skade's release cascade is untouched.
---
## 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)`
```text
skade — the warehouse ENGINE, and the GRAPH ROOT of the nordisk suite build
matrix (workspace_nordisk/.nornir/suite-build-matrix.md).
skade is TRI-MODAL: a consumer either compiles skade in-process and owns the
RedbCatalog directly (the embedded/fat case), or reaches a running
`skade-katalog serve` over gRPC / Iceberg-REST (the thin case). Note WHOSE axis
that is — it is the CONSUMER's linkage, not skade's. What skade itself ships is
a library and the server binary that owns the state.
⚠ THE ROOT INVARIANT: skade is the graph ROOT — NOTHING is downstream of it, so
it embeds NO sibling and reaches none. The EMPTY downstream allowlist below makes
any `embeds = [...]` on a skade variant a dep-graph inversion that
`BuildVariantSet::validate_shape` rejects.
```
### after `[build]`
```text
The feature space skade offers across its crates (the real skade features:
object-store backends + the lineage/stream/testmatrix seams). A variant feature
outside this set is rejected.
⚠ `sql` IS GONE, and this file kept offering it until 2026-08-23. The DataFusion
surface moved OUT of skade on 2026-08-14 (0369d66, "SQL moves out of skade so
skade can publish again") into the sibling `skade-sql` crate, because a git
datafusion dep with no version makes the whole crate unpublishable — see
skade-sql/FOLDING-BACK.md. `cargo check -p skade --features s3,sql` has failed
with "the package 'skade' does not contain this feature: sql" ever since, which
means the DEFAULT library variant below — the one the build menu offers — named
a build point that could not be built. Removed from both `offers` and the
variant.
⚠ DECLARED-BUT-NEVER-BUILT (2026-07-22): `aws-s3`, `rustfs`, `lineage`,
`stream`, `lineage-http` and `testmatrix` are offered here and enabled by NO
variant below, so no declared build point ever compiles them — the same rot that
let korp's `spark41` and holger's `gui` break invisibly. `geo-http` and `bench`
are real skade features that are not even offered, so they are invisible to this
space entirely. Both gaps are recorded rather than papered over: adding a variant
per combination is a deliberate act, not a silent one.
```
### after `offers = ["s3", "aws-s3", "rustfs", "lineage", "stream", "lineage-http", "testmatrix"]`
```text
The suite dep graph as an embed-allowlist: member -> siblings strictly
DOWNSTREAM of it. skade is the ROOT — its allowlist is EMPTY, so a fat variant
that tries to embed anything is rejected as a graph inversion.
```
### after `skade = []`
```text
── skade (the LIBRARY): the engine a consumer compiles IN-PROCESS. ─────────────
`skade` is a library crate (skade/skade — it declares no `[[bin]]`), so this is
`kind = "lib"`. It was previously declared as the default `exe` kind, which asked
cargo for a binary that does not exist.
`linkage = "not-applicable"` is a DECLARATION, not an omission. A library is
linked by whoever consumes it; it composes nothing and runs nowhere, so fat/thin
has no answer for it. Omitting the key would NOT say that — an omitted linkage
reads as the `fat` default, i.e. a positive claim of self-containment that
nothing checked. `validate_kind` now rejects a `lib` that leaves it unsaid.
```
### after `profile = "release"`
```text
── skade-remote (the SERVE binary): the one lock-owning `skade-katalog serve`
process that thin, lock-free consumers reach over gRPC / Iceberg-REST. ──────
⚠ THE NAME IS THE CONSUMER'S VIEW; THE LINKAGE IS THIS BINARY'S. This artifact
used to declare `shape = "thin"`, which made the menu say "needs something
running" about the thing that IS the something running. This binary OWNS the
warehouse: it opens the local state directly and needs no other service up —
which is precisely `fat` — and as the graph root it embeds and reaches nothing.
(Recorded as a finding: the handle `skade-remote` names how CONSUMERS reach it
and would read better as `skade-serve`; the rename is deferred because the handle
is a public build-menu identifier.)
```
### after `profile = "release"`
```text
── NOT declared, and why ─────────────────────────────────────────────────────
`skade-sql` (skade/skade-sql) is a real, shipped, consumed crate — korp and
modgunn both link it — but it gets NO variant here, and the reason is not
oversight. It lives in its OWN detached workspace, so no `cargo` invocation
rooted at this repo's workspace can build it: `-p skade-sql` does not match a
package here. It gets a row the day it folds back into `skade` as a feature,
which is exactly the condition skade-sql/FOLDING-BACK.md is written to detect.
⚠ THAT CONDITION HAS NOW FIRED (2026-08-24). This said `publish = false` "by
construction (its datafusion edge is a git rev, the only build on arrow 59)".
Both halves are stale: `skade-sql/Cargo.toml:47` reads `datafusion = "55"`, a
plain crates.io version, and datafusion 55.0.0 IS the arrow-59 line — verified
by FOLDING-BACK.md's own one-command test against a resolved graph (nornir's
lock: datafusion 55.0.0 from `registry+…crates.io-index`, pulling arrow-array
59.2.0). So the git-rev edge that made it unpublishable is gone, and
FOLDING-BACK.md's instruction applies: this crate is "temporary by design" and
"should be deleted, not maintained" — folded back into `skade` behind `sql`.
Left standing here rather than acted on, because deleting a crate korp and
modgunn both link is a product decision, not a cleanup.
The same detachment is why the two variants above cannot be checked from the
repo root either: `skade/` and `serve/` are detached workspaces (the deliberate
pattern that keeps skade-katalog's manifest and lockfile clean), and a
root-rooted `cargo check -p skade` reports "cannot specify features for
packages outside of workspace". Their `member` key says where they live; a
checker that honours it has to run with `--manifest-path <member>/Cargo.toml`.
```