nornir's build thing — and why it is the ONE repo that has no recipe crate
Status: BLOCKED on a name, not on the model. 2026-08-30, lane AG.
Nine repos got an impl BuildThing this lane and their .nornir/build-variants.toml became an emission. nornir is the one that did not, and the reason is worth writing down, because it is not a shortcoming of nornir's build and it will not go away by trying harder.
The convention
dwarves' forge (dwarves::build_thing::code::recipe_crate) finds a repo's recipe by package name: it looks for a package named exactly <repo>-build-thing, under crates/ or the repo root, and refuses anything else BY NAME. Then code::take builds it with
cargo build -p <package> --lib --release --target wasm32-unknown-unknown
Why that cannot work here
This repo is nornir. So its recipe crate would have to be named nornir-build-thing — which is the name of edda's engine crate, the one every recipe in the constellation is written against, and the one nornir already depends on twice.
That is not a hypothetical collision. MEASURED in this checkout, 2026-08-30, before any recipe crate exists:
$ cargo pkgid nornir-build-thing
error: specification `nornir-build-thing` is ambiguous
help: re-run this command with one of the following specifications
nornir-build-thing@0.1.11 # the registry copy, via published transitives
nornir-build-thing@0.1.12 # the path dep on ../edda/crates/nornir-build-thing
-p nornir-build-thing is already ambiguous in nornir's workspace. A third package of that name — a local member at 0.1.0 — makes it worse, and the forge's build command is exactly the spec that cannot resolve. A recipe crate the forge cannot build is not a recipe crate.
The obvious dodges do not survive contact:
| attempt | result |
rename the engine dep (nbt = { package = "nornir-build-thing", … }) inside the recipe crate |
compiles, with extern crate nbt as nornir_build_thing; for the export_build_thing! paths — but -p nornir-build-thing in the workspace is still ambiguous, so the forge still cannot build it |
name the recipe crate something else (nornir-recipe) |
the repo gets the whole local win — one declaration, an emitted toml, a byte-for-byte parity guard — and the forge finds nothing, because it matches on the name |
[lib] name = "…" to disambiguate |
the cdylib's file name is derived from the PACKAGE name, so this hides the .wasm from the loader instead |
What has to change first, and it is one decision
The convention keys on a NAME, and one repo's name collides with the engine's. Two ways out, and they are the forge's call, not nornir's:
- A second accepted name.
recipe_cratealready reads each candidate's manifest and compares[package] name; teaching it to accept<repo>-recipeas well as<repo>-build-thingis a few lines. It is still a convention keyed on a name, but it is a general one, not a table. - Find the recipe by what it IS, not what it is called — the cdylib in the repo whose manifest depends on
nornir-build-thingand is notnornir-build-thingitself. That is a fact rather than a spelling, and it is the answer that survives the next collision as well as this one. It needs the ambiguity handled at the build too (-p <name>@<version>, which the manifest scan already knows).
Until one of those lands, this file is nornir's declaration of record, beside the hand-written .nornir/build-variants.toml — which stays hand-written, and is the last one in the constellation that is.
What nornir's build would declare
Recorded here so the work is a transcription and not a rediscovery when the name question is answered. From the current .nornir/build-variants.toml: the suite atoms of the five-shape matrix that nornir genuinely has, over the workspace members nornir (root), crates/nornir-selftest, crates/nornir-guard, crates/nornir-jobs, crates/nornir-introspect, crates/nornir-viz-web, crates/bindrift-fixtures, nornir-demo and xtask. Nothing about that shape resists variant → features → settings/sub-settings → typ; it is only the crate NAME that has nowhere to go.
CORRECTION — 2026-08-30, lane AR: the forge's spec RESOLVES. The blocker is cargo pkgid's, and only cargo pkgid's.
The conclusion above does not follow from the measurement above. The measurement is real and reproduces exactly; it is a measurement of cargo pkgid, and it was used to predict the behaviour of cargo build -p. Those two do not share a matcher, and the difference is precisely the thing the recipe crate would introduce: a workspace member.
What was re-measured
cargo pkgid nornir-build-thing in this checkout, 2026-08-30 — AG's red, verbatim:
error: specification `nornir-build-thing` is ambiguous
help: re-run this command with one of the following specifications
nornir-build-thing@0.1.11 # registry, pulled in by modgunn 0.0.6 (a path sibling)
nornir-build-thing@0.1.12 # the path dep on ../edda/crates/nornir-build-thing
cargo tree -i names the source of the registry copy: nornir-build-thing 0.1.11 ← modgunn 0.0.6 (path) ← holger-server-lib 0.6.10 (path) ← nornir. Neither copy is a member of nornir's workspace (cargo metadata --no-deps lists 9 members, none of them named nornir-build-thing), and that is why the spec is ambiguous: with no member to prefer, cargo has two equal candidates and refuses to guess.
The three-copy experiment
A synthetic workspace built to mirror this repo's shape exactly — a workspace MEMBER named X, a non-member path copy of X outside the workspace, and a registry copy of X pulled in through a path sibling. Three packages, one name, in one resolve graph:
| command | result |
cargo pkgid X |
ambiguous, all three listed |
cargo tree -p X |
resolves to the workspace member |
cargo build -p X --lib |
compiles the workspace member |
cargo build -p X --lib --release --target wasm32-unknown-unknown |
compiles the workspace member, no output-filename collision |
The last row is dwarves::build_thing::code::take's command verbatim. The emitted .wasm was checked for its exports rather than trusted from cargo's own progress line: it exports the member's recipe_marker and not the engine copy's engine_marker. cargo build --workspace in the same tree also builds all three copies without a filename collision.
So a recipe crate at nornir/crates/nornir-build-thing would be found by recipe_crate (it matches on [package] name) and built by code::take. The discovery convention does not need a second accepted name, and it does not need to be re-keyed onto "what the crate IS". Option 1 and option 2 in the table above are still reasonable designs — but neither of them is required, and nornir is not blocked.
What IS still true, and it is the opposite of what publishing would fix
cargo pkgid/cargo tree -p stay ambiguous for the bare name in any of these shapes. That is a nuisance for a human at a shell; it is not on the forge's path.
And publishing nornir-build-thing 0.1.12 does not cure the ambiguity — it makes the remaining disambiguation strictly worse. Measured on the same synthetic, with the path copy's version set equal to the registry copy's:
# versions differ (today): `pkgid name` ambiguous
# `pkgid name@0.1.12` resolves cleanly to the path copy
# versions equal (after publish): `pkgid name` ambiguous
# `pkgid name@1.0.0` ALSO ambiguous
# the only spec left is the fully source-qualified one:
# path+file:///…#name@1.0.0 vs registry+https://…#name@1.0.0
Publishing 0.1.12 is needed for other reasons — dwarves cannot publish at all while three of its crates declare nornir-build-thing = "0.1.12" against a registry whose maximum is 0.1.11, which release_gates calls an UNSATISFIABLE requirement — but it is irrelevant to this file's blocker in one direction and harmful in the other. Do not publish it expecting nornir's recipe crate to become buildable; it already would be.
Lane AR did not touch
edda/crates/nornir-build-thing(lane AP owns it) and did not write nornir's recipe crate — that is the owning lane's call, now that the reason not to has been withdrawn.