# suite atoms — design
> **Class:** `design` · **Function axis:** `f0` the four-atom model in modgunn · `f1` the three
> gaps, measured · `f2` the `role = "server"` correction · `f3` the extra KINDS kept · `f4` the ONE
> build verb · `f5` the guards, seen RED · `f6` the `required-features` rot, measured · `f7` the
> matrix is the law: three declarations removed (2026-08-03).
> Strict: only the shape we run. Authority:
> `workspace_nordisk/.nornir/SUITE-MODEL.md`. The declaration itself is
> [`build-variants.toml`](build-variants.toml); the guard is
> [`tests/build_variants.rs`](../tests/build_variants.rs).
Everything dated in this document was measured on **2026-08-01**, on modgunn `a3f10a5`
(branch `feat/four-atoms`, worktree `/home/rickard/git/modgunn-wt-atoms`), against
edda `nornir-build-thing 0.1.4` at `bdd2f78`.
> ## ⚠ SUPERSEDED IN PART — 2026-08-03
>
> Two things changed after this document was written, and both are settled by
> `workspace_nordisk/.nornir/build-matrix-2026-08-02.md` plus Rickard's rule of 2026-08-03.
>
> **1. The model is FIVE shapes, not four.** The matrix adds the **bare `<x>` as the FAT client**
> — all functionality compiled in, no gRPC, works with no server running — and moves `<x>-cli` to
> bin `<x>-cli`. modgunn's one atom is that fat client, so the atom itself did not change; the row
> it fills did.
>
> **2. `modgunn-lib`, `modgunn-image` and the `modgunn-world-delta` container are GONE.** Rickard:
> *"bara de som är markerade suite=ORIGINAL i matrisen skall ha built things"* and *"ta bort allt
> inte stämmer med matrisen"*. Carrying the `suites` tag is what CLAIMS Build-Thing status, and the
> matrix has no `library` row and no container row. Section **f7** at the end of this document
> records what was removed, what it cost and what still depends on the crate.
>
> Everything else below — the measurements, the corrections, the RED proofs — stands as the
> 2026-08-01 record and is left unedited on purpose.
## f0 — the four-atom model, and what modgunn actually has
The constellation aims each member repo at FOUR atoms off ONE delivery (⚠ five as of the
2026-08-02 matrix — see the banner above):
| atom | role | linkage | bin |
|---|---|---|---|
| `<x>-server` ⭐ | server | fat | `<x>-server` |
| `<x>-cli` | client | thin | `<x>` |
| `<x>-ui` | client | thin | `<x>-ui` |
| `<x>-solo` | **solo** | fat | `<x>-solo` |
`solo` is UI + server embedded in ONE binary: start it and it puts the UI up with the server
running inside itself, nothing else to install. It is its own ROLE and not a flavour of the other
two, because the two mis-filings are symmetric and both silent — filed under `client` it sits in a
menu next to a thin UI that is useless without a daemon, and filed under `server` a deploy step
picks it up and tries to run a GUI under a system user with no display.
**modgunn fills exactly one of the four.** Here is the whole space as landed:
| atom | role | kind | linkage | bin | verdict |
|---|---|---|---|---|---|
| `modgunn` ⭐ | **client** | `exe` | fat | `modgunn` | the one binary. Was `modgunn-fat`, role CORRECTED from `server` |
| `modgunn-lib` | library | `lib` | not-applicable | — | was `modgunn-thin`, renamed for honesty. ⚠ **REMOVED 2026-08-03 — f7** |
| `modgunn-image` | client | `container` | fat | — | the OCI deploy image. ⚠ **REMOVED 2026-08-03 — f7** |
| `<x>-server` | — | — | — | — | **GAP — modgunn has no daemon** (f1) |
| `<x>-ui` | — | — | — | — | **GAP — modgunn has no UI at all** (f1) |
| `<x>-solo` | — | — | — | — | **GAP — no UI and no server ⇒ nothing to embed** (f1) |
**1 of 4 = 25%**, and the honest number matters more than the shape of the table. The four atoms
are a TARGET, not a quota; declaring an atom that points at a binary which does not exist is the
exact rot this pass exists to end. **A named gap beats a phantom atom.**
## f1 — the three gaps, measured
### No server
Three independent measurements, each sufficient on its own:
1. **No listener, no loop, no `serve`.** `grep -rn 'TcpListener|UnixListener|tokio::spawn|\.serve\('
src/` over the whole crate returns exactly ONE hit: `src/service.rs:171`, inside
`#[cfg(test)] mod tests`, where a test binds a throwaway port to prove skidbladnir's health
probe can see an OPEN socket. No shipping code path in modgunn ever listens on anything.
`src/main.rs` is a hand-rolled slice-pattern dispatch over `std::env::args()`
(`src/main.rs:84-127`) and every arm returns.
2. **The service module is not in the shipped artifact.** `src/service.rs` is
`#[cfg(feature = "framework")]` (`src/lib.rs:56-57`), and the `modgunn` atom does not enable
`framework`. Run the binary this declaration actually builds:
```console
$ ./modgunn service --unit
the Tools menu / demo / service lifecycle need the shared framework crates (jera + skidbladnir):
rebuild with `--features framework` (add `framework-oci` to run the demo container live).
`modgunn about` works without it.
$ echo $?
2
```
So the tempting defence — "`kind = "service"` is fine, the artifact really is wrapped as a
skidbladnir `ServiceSpec` by `modgunn service --unit`" — is **false for the artifact this
declaration builds**. And even with `--features framework` on, `cmd_service`
(`src/main.rs:264-285`) only RENDERS a unit file or dumps JSON. It starts nothing.
3. **The declared `ExecStart` names a verb that does not exist.** `modgunn_service_spec` sets
`spec.args = ["world-delta", "watch"]` (`src/service.rs:75`). There is no `["world-delta", ..]`
arm anywhere in main.rs's dispatch — `grep -n '"world-delta"' src/main.rs` returns nothing. On
the built binary:
```console
$ ./modgunn world-delta watch
usage:
modgunn about [--json]
…
$ echo $?
2
```
A systemd unit rendered from that spec would `ExecStart` a process that prints usage and exits
non-zero. `src/service.rs:13-20` says as much itself: "it has no long-running `serve` loop yet …
standing up the watcher daemon itself is deferred".
### No UI
Zero `egui` / `eframe` / `wasm-bindgen` / `tauri` / `iced` / `slint` anywhere in modgunn's
`Cargo.toml`. The one thing that looks like a UI dep is not one: `nornir-appshell`
(`Cargo.toml:105-113`) is the constellation-wide About-card **MODEL**, deliberately serde-only, so
that the shared `facett-about` egui card *elsewhere* can render modgunn's identity without modgunn
linking egui. An About-card model is not a card.
### No solo
`solo` means the UI with the server running inside it. With neither half there is nothing to embed
and nothing to put on screen. This gap is not a decision that could have gone the other way; it is
entailed by the other two.
**The gaps are asserted, not just written down.** `tests/build_variants.rs::
the_four_missing_atoms_are_declared_missing` (renamed from `…three…` in f7, when the matrix's fifth
shape made `<x>-cli` a fourth named gap) asserts `default_for_role(Server) == None`, that no variant
carries `Role::Server` or `Role::Solo`, that no variant is `ThinStandalone`, and that no UI crate
and no gRPC crate appears as a dependency line in `Cargo.toml`. A gap that nothing checks turns back
into a phantom the moment somebody copies a sibling repo's declaration — and every one of those
assertions was seen RED (f5, f7).
## f2 — the `role = "server"` correction, and why `kind` moved too
`modgunn-fat` declared `role = "server"`, `kind = "service"`, `embeds = ["skade"]`. The role is
disproved three times over by f1. The **kind** needed to move with it, and that was the one real
decision in this pass, because edda's `validate_kind` forbids `embeds` on
`ArtifactKind::Exe` — only the service-shaped kinds (`Service`, `Container`) may carry compiled-in
siblings — and modgunn genuinely does compile skade in.
The two live options were:
* **keep `kind = "service"`, correct only the role.** Rejected. `ArtifactKind::Service` is not a
synonym for "a binary": it means a daemon skidbladnir installs under a dedicated system user,
with a unit and a health probe. Keeping it preserves the exact phantom f1 disproves, and a deploy
consumer reading the menu would install a unit whose `ExecStart` verb is not implemented. The
usual justification for it ("but `modgunn service --unit` wraps it as a ServiceSpec") is measured
false above — that code is not in the artifact.
* **move to `kind = "exe"` and drop `embeds`.** Taken. The worry is that dropping `embeds` becomes
a false claim about what is in the binary — it does not, because the fact was already stated
twice in the same declaration: `warehouse` is DEFINED in `Cargo.toml:32` as
`["dep:skade", "dep:tokio", …]`, so `features = [… "warehouse" …]` **is** the skade embed, and
`linkage = "fat"` is the self-containment claim itself. What was lost is a duplicate key, not a
fact. (Dropping the linkage *too* would have been the genuinely dishonest option.)
`BuildVariant::cargo_args()` is byte-identical for `Exe` and `Service` — only `Lib` adds an
argument (`--lib`). **The correction changed the declaration and not one byte of the build.**
One consequence is stated rather than left to be discovered: `[build.downstream]` is now exercised
only by `tests/build_variants.rs::embedding_an_upstream_member_is_rejected`, which builds a
synthetic `embeds = ["nornir"]` variant, because no real atom declares `embeds` any more. The
asymmetry guard is still live and still red-when-broken; it is simply no longer reachable from the
real declaration.
### The rename `modgunn-thin` → `modgunn-lib`
"thin" is a LINKAGE word, and this variant's linkage is `not-applicable`. The name asserted a
position on an axis the artifact declares it is not on. The `kind` was already `lib`; the name now
agrees with it. (For the older, larger defect — when `modgunn-thin` was a `service` whose rendered
argv built literally nothing because the bin's `required-features` were unmet — see the header of
[`build-variants.toml`](build-variants.toml).)
## f3 — what was kept that is outside the four, and why
Both extras are legitimate artifact **KINDS**, not extra roles:
* **`modgunn-lib` (`kind = "lib"`).** This is modgunn's actual product for its two biggest
consumers: holger's policy gate and static-holger link `dep:modgunn` with `default-features =
false` and take Engine A only. A library is built, published and never run, so the fat/thin axis
has no answer for it — hence `linkage = "not-applicable"`, which `validate_kind` requires
explicitly rather than accepting silence (an omitted linkage reads as the `fat` default, i.e. a
positive claim of self-containment nobody made).
* **`modgunn-image` (`kind = "container"`).** A delegating node onto the `modgunn-world-delta`
`[[build.container]]` descriptor, which chains the ⭐ atom's binary into a
`debian:bookworm-slim` layer. It materializes a Containerfile, not a cargo argv, and
`xtask build` reports-and-skips it rather than pretending to have built it.
Its role was corrected too, `server` → `client`, for the same reason as the ⭐ atom's and one level
up: the image's `entrypoint` is `/usr/local/bin/modgunn` and its `cmd` is `["warehouse", "stats"]`.
That is a **batch job in a container** that prints stats and exits. A container is not a daemon
just because it is a container.
## f4 — the ONE build verb, and what it replaced
modgunn had **no xtask crate, no CI config and no Makefile** before this pass. Its build recipes
lived only in `.nornir/*.md` prose — and prose that names a cargo invocation IS a second
declaration, which is why three of those files had drifted from the TOML.
Added:
* `xtask/` — one verb, whose entire body is `nornir_build_thing::suite_cli::run(repo_root, &args)`.
It contains no variant name, no feature string and no bin name of its own.
* `.cargo/config.toml` — `xtask = "run --quiet --manifest-path xtask/Cargo.toml --"`.
**Why `--manifest-path` and not `-p xtask`.** modgunn is a single-crate repo whose root
`Cargo.toml` has a `[package]` and no `[workspace]`. Adding `members = [".", "xtask"]` would make
the root a real workspace, and a workspace unifies features across members — the shipped crate's
resolution would then depend on what a build tool pulls in. modgunn's whole point is a LEAN closure
(holger and static-holger must not acquire an arrow/iceberg tree through it), so `xtask/Cargo.toml`
carries its own empty `[workspace]` table and is self-rooting, the same shape as korp's
`crates/*` leaves. Verified after the change:
```console
$ cargo metadata --no-deps --format-version 1 | jq -r '.workspace_members[]'
path+file:///home/rickard/git/modgunn-wt-atoms#modgunn@0.0.5
```
One member, unchanged. xtask is invisible to modgunn's resolution.
```console
$ cargo xtask build --suite ORIGINAL --list
delivery 'ORIGINAL' — 3 atom(s):
modgunn client exe modgunn
modgunn-lib library lib <default bin>
modgunn-image client container <default bin>
```
**Purged / rerouted** (every caller of the old recipes, all inside this repo):
| file | was | now |
|---|---|---|
| `.nornir/framework-guide.md:87-88,101-108` | a `nornir build --variant modgunn-{fat,thin,image}` block; `modgunn-thin` described as a `service`/thin that "reaches the warehouse over gRPC"; the fat argv missing `provenance-verify` and `--no-default-features` | the atom table + `cargo xtask build --suite ORIGINAL [--atom …]`, with the real argv |
| `.nornir/framework-design.md:74-92,125` | the same `Service`/`ThinStandalone`/gRPC table; the test description naming the old variants | the corrected table, an explicit ⚠ note that the old one was false, and the new guard list |
| `.nornir/design.md:188-194` | "Three variants — `modgunn-fat` (Engine A+B service…), `modgunn-thin` (lean scan-only service, reaches the warehouse over gRPC)…" | the three atoms as landed + a pointer to the gaps |
| `Cargo.toml:216` | "through `nornir build`'s `BuildVariantSet`" | "through `cargo xtask build`'s" |
**No Rust anywhere hardcodes modgunn's variant names** outside `tests/build_variants.rs` —
confirmed by grepping the repo for `modgunn-fat` / `modgunn-thin` / `modgunn-image` /
`modgunn-world-delta` across every extension. There was no second build path in shipping Rust to
delete, because there was no build tooling at all. The feature-axis teaching recipes in
`guide.md` / `for-idiots.md` / `README-full.md` (`cargo build --features scip`, etc.) were left
alone deliberately: they teach the raw cargo feature axis, they name no variant, and
`framework-guide.md` already distinguishes the two.
### The bin-collision defect: modgunn was clean, and that is not the same as guarded
edda's cross-repo sweep found three siblings emitting one file from several atoms —
`korp` (3 variants → bin `korp`), `nornir` (3 → `nornir`), `dwarves` (2 → `dwarves`) — so the last
build silently overwrites the others and nothing on disk says which one you have. **modgunn appears
in none of them**, and `set.check_bins()` is empty here.
But modgunn's emptiness is STRUCTURAL, not earned: exactly one atom declares a `bin` at all, so
there is nothing that *could* collide, and an assertion that can only pass is not a guard. The test
therefore also drives `check_bins()` against a deliberately-colliding twin declaration and asserts
it REPORTS (f5, case 1).
## f5 — every guard seen RED
Each guard's INPUT was flipped, the test re-run, and the real failure text captured. All six were
restored and the suite is green (`7 passed; 0 failed`).
**1. `check_bins()` — a second atom on bin `modgunn`:**
```text
["delivery 'ORIGINAL': 2 variants emit the SAME artifact — bin 'modgunn' (package modgunn,
profile release, target <host>) — so the last build silently OVERWRITES the others and nothing on
disk says which one you have: modgunn, modgunn-lean. Give each its own `bin` name off the same
source (holger: holger-mikro / holger-full), or fold them into one variant."]
```
**2. `check_bins_declared()` — the declared bin renamed to a target that does not exist:**
```text
["variant 'modgunn' declares bin 'modgunn-server', which no `[[bin]]` target exists for —
`cargo build --bin modgunn-server` cannot succeed. Declared targets: modgunn"]
```
The oracle is READ out of the manifests by `BuildVariantSet::cargo_bin_targets(repo_root)`, never
hardcoded — a hardcoded twin is exactly the drift this guard removes (korp renamed `korp` →
`korp-ui` in `Cargo.toml` and left three variants, *and its own test's asserted argv string*,
pointing at a target that no longer existed). The test additionally asserts that an EMPTY oracle is
reported rather than passing vacuously, and that the walk really found `modgunn`.
**3. `declares_suites()` — every `suites = ["ORIGINAL"]` tag removed:**
```text
check_suites() is empty EITHER because the tags are fine OR because there are none — this is what
tells the two apart
```
This is the point of asserting both: `check_suites()` is VACUOUS below two deliveries (with one
delivery there is nothing to be a subset of) and correctly returns empty either way.
**4. `suite_cli::resolve` — the delivery renamed to `MAIN`:**
```text
ORIGINAL resolves: --suite ORIGINAL: no such delivery. Declared: MAIN
```
and in the other direction, `--suite NOPE` against the real declaration fails with
`--suite NOPE: no such delivery. Declared: ORIGINAL`. A selector that shrugs at a name it does not
recognise is the silent-green shape.
**5. The server GAP — `role = "server"` re-declared on the ⭐ atom:**
```text
assertion `left == right` failed: modgunn declares a SERVER atom, but it has no daemon: no listener
outside #[cfg(test)], the service module is behind a feature no atom builds, and the unit it
renders ExecStarts a verb that does not exist
left: Some(BuildVariant { name: "modgunn", … role: Some(Server), … })
right: None
```
**6. The UI GAP — `egui = "0.29"` added to `[dependencies]`:**
```text
modgunn declared a UI dependency ('egui') — the `<x>-ui` gap is no longer a gap, so declare the
atom instead of leaving this test lying: egui = "0.29"
```
### The atoms BUILD, by name, and land on disk
Not `--all-targets` (a `required-features` mismatch makes cargo skip a target SILENTLY — exactly
how holger's `gui` rotted), and not an exit code. The argv came from
`set.get("modgunn").command_line()`, materialized by the verb:
```console
$ cargo xtask build --suite ORIGINAL
── ORIGINAL · modgunn · cargo build -p modgunn --bin modgunn --features scan,scip,warehouse,rustsec,provenance-verify --no-default-features --profile release
Finished `release` profile [optimized] target(s) in 1m 10s
── ORIGINAL · modgunn-lib · cargo build -p modgunn --lib --features scan,provenance-verify --no-default-features --profile release
Finished `release` profile [optimized] target(s) in 28.69s
not cargo builds, skipped (use the image/vm/iso builders): modgunn-image (container)
built: modgunn, modgunn-lib
```
| atom | artifact on disk | bytes |
|---|---|---|
| `modgunn` | `/home/rickard/scratch/cargo/modgunn-atoms/release/modgunn` | 39 475 336 |
| `modgunn-lib` | `/home/rickard/scratch/cargo/modgunn-atoms/release/libmodgunn.rlib` | 6 119 858 |
```console
$ file modgunn
modgunn: ELF 64-bit LSB pie executable, x86-64 … BuildID[sha1]=aa5a3a91…
$ ./modgunn --version
modgunn v0.0.5 (a3f10a5)
```
### One unrelated RED found on the way, and fixed
Running the suite under the features the CLI tests need
(`cargo test --features test-support,scip,manifests,framework,rustsec`) turned up a test that was
red before this pass touched anything:
```text
tests/cli_framework.rs:37: assertion `left == right` failed
left: String("0.0.5 (a3f10a5)")
right: "0.0.5"
```
`about::identity()` builds its version from `version_with_hash()` (`src/about.rs:50`) per the
build-version-githash-law, and the in-crate unit test at `src/about.rs:84` already asserts it with
`starts_with` + `contains`. The CLI test had drifted to a bare `==` against `CARGO_PKG_VERSION`,
so it was red on every checkout with git metadata. Corrected to the same shape as the unit test —
prefix plus hash, which is the actual claim. Full suite after: **388 passed, 0 failed**.
## f6 — the `required-features` rot, measured (and 2 of 6 were BROKEN)
modgunn `offers` eleven features and its atoms build five of them. The other six —
`warehouse-sql`, `manifests`, `framework`, `framework-oci`, `testmatrix`, `test-support` — are
enabled by NO declared build point. That had been recorded as prose since 2026-07-22 and never
measured. It was measured today, one feature at a time, with
`cargo check -p modgunn --features <f>`:
| feature | before | after |
|---|---|---|
| `warehouse-sql` | ✗ **BROKEN** | ✓ |
| `manifests` | ✓ | ✓ |
| `framework` | ✓ | ✓ |
| `framework-oci` | ✓ | ✓ |
| `testmatrix` | ✓ | ✓ |
| `test-support` | ✗ **BROKEN** | ✓ |
**Two of six — 33% — did not compile.** Both failed identically:
```text
src/main.rs:822:38: error[E0599]: no method named `reachable_vuln_findings` found for struct
`Warehouse` in the current scope
error: could not compile `modgunn` (bin "modgunn") due to 1 previous error
```
The root cause is larger than those two features. `warehouse-sql ⇒ warehouse` and
`test-support = ["warehouse"]`, and `warehouse` alone already satisfies the bin's
`required-features = ["warehouse", "scan"]` (`scan` rides in `default`), so cargo BUILT the binary
— in a configuration no declared atom ever produces, because the ⭐ atom always turns `scip` on as
well. `Warehouse::reachable_vuln_findings` is `#[cfg(feature = "scip")]` (`src/warehouse.rs:1635`)
but `cmd_reachable_vuln` was not, so **`warehouse && !scip` did not compile**. The plainest
demonstration is not a never-built feature at all:
```console
$ cargo check -p modgunn --features warehouse # the recipe Cargo.toml:238 and guide.md:18 document
error[E0599]: no method named `reachable_vuln_findings` found for struct `Warehouse`
```
**Fixed** by gating `cmd_reachable_vuln` on `scip` and adding the `#[cfg(not(feature = "scip"))]`
fallback that mirrors `cmd_import_deps` / `cmd_ingest` — the verb now says what it needs instead of
breaking the build. All six re-measured green afterwards.
**No atom was invented for any of the six.** An atom is a thing somebody SHIPS, and none of these
is shipped as an artifact of its own; declaring one to make the table look busier would be the same
phantom this pass exists to remove. `manifests` and `framework` are the two that carry real shipped
CLI verbs (`modgunn ingest`; `modgunn tools|demo|service`) and are the first candidates the day
modgunn ships a second delivery. The measured status now lives in
[`build-variants.toml`](build-variants.toml) with today's date, not as undated prose.
That the ⭐ atom's feature set is the ONLY combination anyone ever builds is itself the finding: a
feature nobody builds is a feature that breaks silently, and here it took a four-atom audit rather
than a user to notice.
## The suite tag
Every atom carries `suites = ["ORIGINAL"]` and `[build]` carries `base_suite = "ORIGINAL"`. modgunn
ships ONE delivery, so the tag looks redundant. It is not: it makes ONE command shape work in every
member repo —
```console
$ cargo xtask build --suite ORIGINAL # korp · dwarves · nornir · modgunn
```
`ORIGINAL` is the ONLY suite name in this repo, and `suite_cli::resolve` fails loudly on any other
(the guard drives it with holger's `MVP` as well as with a nonsense name, so "some other member's
delivery" is not a special case that quietly works here). Without the tag, the one member that
ships two deliveries would need a `--suite` the others reject, and every tool, doc and CI recipe
would carry two shapes forever. With it, adding a second delivery to modgunn later is a DECLARATION
change and nothing else.
## What could NOT be landed
* **`modgunn-server`, `modgunn-ui`, `modgunn-solo`** — the three gaps of f1. Landing them would
mean writing a daemon and a UI, which is product work, not a declaration pass. What this pass
*did* land is that the gaps are now asserted, so they cannot quietly become phantoms again.
* **The world-delta watcher daemon** the existing `ServiceSpec` describes remains deferred
(`src/service.rs:13-20`). Until it exists, `src/service.rs` declares a contract nothing fulfils —
the spec renders a unit whose `ExecStart` verb is unimplemented. That is now written down here
rather than only in a module comment; it was NOT fixed, because implementing a watcher is out of
scope for a build-declaration pass.
* **`workspace_nordisk/.nornir/`** carries three tables naming `modgunn-fat` / `modgunn-thin`
(`SUITE-MODEL.md:75`, `release-state-and-build-defaults-2026-07-27.md:99`,
`suite-build-inventory-and-scheme-2026-07-30.md:168-170`). They were left alone: that repo is the
shared authority being edited concurrently by the korp / dwarves / nornir passes, and a
four-way write to one file is how a merge eats somebody's work. Reported instead of edited.
## f7 — the matrix is the law: three declarations removed (2026-08-03)
Measured on this checkout, branch `feat/matrix-original-atoms` off modgunn `b501766`, against
edda `nornir-build-thing 0.1.4`.
Rickard, 2026-08-03: *"bara de som är markerade suite=ORIGINAL i matrisen skall ha built things"*
and *"ta bort allt inte stämmer med matrisen"*. **Carrying the `suites` tag is what claims
Build-Thing status**, so an atom that carries it and cannot build is not a documentation nit — it
is the `archive ⭐` defect (holger's starred default that lives in `SUITES.md` and in no build
file, so nothing can build it). The matrix has five rows, none of them `library` and none of them a
container, so three declarations went.
### What was removed, what it built, and what it cost
| removed | what it rendered | who used it | what stops working |
|---|---|---|---|
| `modgunn-lib` (`role = library`, `kind = lib`) | `cargo build -p modgunn --lib --features scan,provenance-verify --no-default-features --profile release` | **nobody** — see below | `cargo xtask build --suite ORIGINAL --atom modgunn-lib` now errors `no such atom` |
| `modgunn-image` (`role = client`, `kind = container`, **no bin**) | nothing runnable of its own; delegated to the descriptor below | nobody | the image can no longer be asked for by atom name |
| `[[build.container]] modgunn-world-delta` | the OCI descriptor: `debian:bookworm-slim` + `/usr/local/bin/modgunn`, `cmd = ["warehouse","stats"]`, tag `modgunn-world-delta:0.0.5` | nobody | the image can no longer be materialized FROM THE DECLARATION |
**Nothing was deleted from `src/`.** A Build Thing is a declared BUILD POINT; removing one removes
a way to ASK for an artifact, not the artifact's source. `[[bin]] modgunn`, the lib target, every
module and every feature are untouched.
**`modgunn-lib` in particular — the library is not going anywhere.** Fleet grep, 2026-08-03:
```
holger/server/lib/Cargo.toml:93 modgunn = { …, default-features = false, features = ["scan"] }
holger/server/ui/Cargo.toml:99 modgunn = { …, default-features = false, features = ["scan", "warehouse"] }
nornir/Cargo.toml:673 modgunn = { path = "../modgunn", features = ["warehouse", "rustsec", "scip"] }
modgunn/bench/Cargo.toml:24 modgunn = { path = "..", default-features = false, features = ["scan"] }
```
Every one of those is an ordinary cargo dependency edge that resolves modgunn's lib with its OWN
feature selection. **None of them matches the removed atom's closure** (`scan,provenance-verify`),
so `modgunn-lib` was a build point no consumer's build ever took. Removing the atom changes no
consumer's bytes.
**The container, measured before removal:** a fleet-wide grep for `modgunn-image` /
`modgunn-world-delta` found hits ONLY in modgunn's own `.nornir/*.md` + `tests/` and in three
`workspace_nordisk/.nornir/*.md` inventory tables. No CI recipe, no script, no other repo's
declaration. And nothing of modgunn is installed or running on oden (`which modgunn`, `/usr/local/bin`,
`systemctl` system + user, `ps` — all empty), so no running system depended on it.
### The container-axis question — STATED, NOT ANSWERED
`[[build.container]]` **cannot carry a `suites` tag today**: `BuildVariantSet::suites()` and
`declares_suites()` (`edda/crates/nornir-build-thing/src/variant.rs:1346,1352`) iterate
`self.variants` only, so containers are not on the suite axis in the data model at all. Under the
rule "the tag is what makes it a Build Thing", an untagged container is a cell outside the set —
which is consistent with removing it, and is why no edda change was made to force the other answer.
**Whether modgunn should ship a deploy image as a first-class Build Thing is Rickard's call**; it
would require edda to put containers on the suite axis first.
### f7's guards, every one seen RED
`tests/build_variants.rs` gained `every_declared_atom_is_a_matrix_row`, which compares the
declaration against the matrix's five `(atom, role, linkage, bin)` rows — written out as the
matrix's own names, so the guard checks a declaration against the matrix and not against itself.
Seven RED cases, all run on this checkout, all restored green afterwards (`8 passed; 0 failed`):
1. **the two removed atoms re-added** → `left: ["modgunn", "modgunn-lib"] right: ["modgunn"]`, plus
`atom 'modgunn-lib' (role Some(Library), kind Lib) is not a row of the build matrix` and the
same for `modgunn-image` from the in-test synthetic declaration.
2. **the container descriptor re-added alone** →
`containers are not on the suite axis; declared: ["modgunn-world-delta"]`.
3. **the `[[bin]]` renamed out from under the declaration** (`modgunn` → `modgunn-renamed`) →
`the manifest walk found: ["modgunn-renamed", "xtask"]`. The oracle is WALKED from the manifests
by `BuildVariantSet::cargo_bin_targets`, never written down here.
4. **the `suites` tag removed** → `check_suites() is empty EITHER because the tags are fine OR
because there are none — this is what tells the two apart`.
5. **a UI dependency added** (`egui = { version = "0.29", optional = true }`) →
`modgunn declared a UI dependency ('egui') — the modgunn-ui gap is no longer a gap, so declare
the atom instead of leaving this test lying`.
6. **a gRPC dependency added** (`tonic = "0.12"`) → `modgunn declared a gRPC dependency ('tonic') —
a THIN client is now possible, so declare modgunn-cli`.
7. **the manifest oracle emptied** (Cargo.toml reduced to two lines) → `the manifest scan found no
known dependency line — the oracle is empty: 2 lines`. Case 7 is the guard on the guard: without
it, cases 5 and 6 would pass vacuously the day the manifest read silently returned nothing.
### The four shapes that remain FICTION, and why none was stubbed
`modgunn-server`, `modgunn-cli`, `modgunn-ui`, `modgunn-solo`. Re-measured 2026-08-03, not recalled:
zero `tonic`/`prost`/`nornir-service` in the manifest; zero egui/eframe/tauri/iced/slint/wasm/winit/
wgpu; the only `TcpListener` in the crate is inside `#[cfg(test)]`; `grep -c '"world-delta"'
src/main.rs` = 0 against a `ServiceSpec` whose `ExecStart` args are exactly that. Making any of them
real is PRODUCT work — a daemon, a wire, a window — not a declaration pass, and a tagged stub would
be the very defect this section exists to remove.