nornir — the SUITE × ATOM build model
Design doc (
-designclass, see.nornir/docs.toml). Landed 2026-08-01 onfeat/four-atoms; revised 2026-08-03 to the settled build matrix. Authority:workspace_nordisk/.nornir/build-matrix-2026-08-02.md, which supersedesSUITE-MODEL.mdwhere they disagree. The declaration this describes is.nornir/build-variants.toml; that file is the source of truth and this one is the prose.
⚠ WHAT CHANGED 2026-08-03 — the set is the matrix, and nothing else
Rickard: "ta bort allt inte stämmer med matrisen". nornir's matrix row count is five, and carrying a suite tag is what makes an atom a Build Thing — so an atom that is not in the matrix does not get to sit in the declaration tagged.
nornir-thin→nornir-cli(bin and atom), and its marker featurethin→atom-cli. An atom named-thinthat measurably is not thin was a contradiction on its face; the matrix names this rownornir-cli, matching holger'scliand dwarves'dwarves-cli.nornir-watchleft the set. One feature flag on the CLI is not a delivery. The[[bin]], theinotifyfeature and the CI line all stay.nornir-mcpleft the set, and its capability moved INTO the server: Rickard, "mcp skall väl ingå inkompilerat i server!"src/bin/nornir-mcp.rsis now the library modulenornir::mcp_stdio, served bynornir-server mcp.<x>-serveris thefatatom — "everything compiled in" — so MCP belongs in it rather than beside it.nornir-uistaysmixed, and now it is MEASURED — see §1.1. The matrix asks forthin; the bytes say 76,829 storage-engine symbols. The declaration reports the fact and §5 records the gap.
nornir ships ONE delivery (suites = ["ORIGINAL"], base_suite = "ORIGINAL") and five atoms. There is exactly one build verb:
cargo xtask build --suite ORIGINAL [--atom <name>] [--list]
1. The atoms as landed
| atom | role | linkage | real bin | source |
⭐ nornir-server |
server |
fat | nornir-server |
src/bin/nornir-server.rs |
⭐ nornir |
client |
fat | nornir |
src/bin/nornir.rs |
nornir-cli |
client |
mixed | nornir-cli |
src/bin/nornir.rs |
⭐ nornir-ui |
client |
mixed | nornir-ui |
src/bin/urdr-threads.rs |
⭐ nornir-solo |
solo |
fat | nornir-solo |
src/bin/urdr-threads.rs |
Every row carries suites = ["ORIGINAL"]; there is no other suite name in this repo. tests/build_variants.rs::the_declared_set_is_exactly_the_matrix fails on a missing row AND on an extra one, and ::only_the_original_suite_exists fails on any other tag.
Two rows left on 2026-08-03 and are not coming back without a decision: nornir-watch (client/fat, bin kept, buildable by name) and nornir-mcp (tool/fat, folded into nornir-server mcp). See the tombstone at the bottom of .nornir/build-variants.toml for what each one built and what still works.
1.1 ★ linkage MEASURED, not asserted (2026-08-03, oden)
The matrix asks for <x>-ui = thin. nornir cannot honour it, and this is the evidence rather than the claim — the leanest atom built at exactly its declared flags, symbol table counted (holger's technique from server/ui/tests/thin_client.rs):
cargo build --release --bin nornir-ui --no-default-features --features viz
→ 268,655,240 bytes
datafusion 61,790 nornir::server 0
iceberg 8,841 nornir_server 0
parquet 6,198 tonic::transport::server 0
skade 1,061 LocalHolger 0
- Not
thin— 76,829 symbols of the storage engine are in the artifact, becauseskade,skade-katalog,iceberg,parquetanddatafusionare plain non-optional deps.LinkageAuditreturnsThinButLinksfor athindeclaration here: the declaration is a lie. - Not
fat— zero server entry points. The GUI reaches a server, it does not compile one in.
Storage embedded / server reached is exactly ServiceShape::Mixed. mixed had been the honest word all along; until tests/atom_linkage.rs it simply had no evidence behind it. That guard re-measures on every CI run and fails in both directions — including the day the engine becomes optional, when it will demand the declaration be upgraded to thin.
mixed is not undocumented. It is declared, parsed, rendered and dispatched on in edda/crates/nornir-build-thing: variant.rs:116 (ServiceShape::Mixed), :138 (parsed), :160 (rendered), :316 (documented as one of fat|thin|mixed|not-applicable), plugin.rs:312, linkage.rs:318, service.rs:65.
⭐ = one of the shared four-atom target quartet (server · CLI · UI · solo).
Measured — all seven built through the one verb, 2026-08-01
⚠ Historical, and the names are the names of that day. This table was taken before the 2026-08-03 revision:
nornir-thinis nownornir-cli, andnornir-watch/nornir-mcpare no longer atoms (see the header). The bytes and the--versiontext are left exactly as measured rather than retro-edited — a measurement that gets rewritten to match a later decision stops being one.
cargo xtask build --suite ORIGINAL --atom <a> for each, every one exit=0, then each binary run. This is the applied proof that the declared feature sets reach distinct artifacts; the unified test run in §2.3 cannot show it, because cargo unifies features there.
| atom | bytes | --version (after v0.5.4 (<sha>) — ) |
nornir-server |
356,626,360 | atom nornir-server · linkage fat · holger=embedded · watch=poll · server=in-process |
nornir |
379,875,168 | atom nornir · linkage fat · holger=embedded · watch=poll · server=in-process |
nornir-thin |
371,586,528 | atom nornir-thin · linkage mixed · **holger=remote** · watch=poll · server=in-process |
nornir-ui |
259,824,480 | atom nornir-ui · linkage mixed · holger=remote · watch=poll · **server=remote** |
nornir-solo |
324,990,864 | atom nornir-solo · linkage fat · holger=embedded · watch=poll · **server=in-process** |
nornir-watch |
380,182,896 | atom nornir-watch · linkage fat · holger=embedded · **watch=inotify** · server=in-process |
nornir-mcp |
342,833,816 | atom nornir-mcp · linkage fat · holger=embedded · watch=poll · **server=remote** |
Each discriminator lands in exactly the atom that declares it: embed-holger dropped only in nornir-cli/nornir-ui, inotify only in nornir-watch, server absent from nornir-ui and nornir-mcp. nornir-ui is the smallest at 259.8 MB against nornir-watch's 380.2 MB — a 120 MB spread that no descriptor could fake.
Independently, on the two GUI-source atoms:
$ strings -a nornir-solo | grep -c 'NORNIR-SOLO: gRPC listening on' → PRESENT
$ strings -a nornir-ui | grep -c 'NORNIR-SOLO: gRPC listening on' → ABSENT
⚠ The (<sha>) in VERSION_LINE can lag the working tree: build.rs does not re-run on every commit, so an incremental rebuild can stamp an older sha onto freshly compiled code. Read the sha as "at least this commit", and treat the cargo xtask build exit code plus the behaviour above as the real evidence.
Exactly one default = true per (suite, role) group: nornir-server for server, nornir for client, nornir-solo for solo, nornir-mcp for tool.
What each one is for
nornir-server— the daemon. tonic gRPC on:7878, all 20 services, the single owner of the redb workspace registry and the iceberg warehouse lock. This is what systemd runs.nornir— the CLI, the artifact most people mean by "nornir". It is aclient: it drives a server, or opens the warehouse directly; nothing deploys it as a daemon.nornir-cli(callednornir-thinuntil 2026-08-03) — the same CLI withembed-holgerdropped, so holger is reached over gRPC instead of compiled in.nornir-ui— the desktop GUI (eframe::run_native("Urðr Threads")), the leanest atom nornir has:vizand nothing else.nornir-solo— the appliance. §3.nornir-watch— the CLI plusinotify, for a tree that changes without git.nornir-mcp— the MCP stdio server every agent in the fleet talks to.
2. The defect this pass fixed
2.1 The bin collision — measured RED
Three variants (nornir, nornir-thin, nornir-watch) all declared bin = "nornir", same package, same profile, same target. Three different feature builds therefore wrote one path and the last silently overwrote the others. BuildVariantSet::check_bins() against the declaration as it stood, 2026-08-01:
3 variants emit the SAME artifact — bin 'nornir' (package nornir, profile release,
target <host>) — so the last build silently OVERWRITES the others and nothing on
disk says which one you have: nornir, nornir-thin, nornir-watch. Give each its own
`bin` name off the same source (holger: holger-mikro / holger-full), or fold them
into one variant.
Fixed holger's way: distinct [[bin]] names off one source, told apart by required-features (Cargo.toml, the [[bin]] block). check_bins() is now empty, guarded by tests/build_variants.rs::no_two_atoms_write_the_same_file.
2.2 The role lie — and the two atoms that did not exist
All three of those variants also declared role = "server". Bin nornir is the CLI. The real daemon (nornir-server) and the real GUI (urdr-threads, now nornir-ui) were built by no variant at all. So the declaration described three copies of one client, called them servers, and omitted the two artifacts a deploy actually needs. Every atom now names the bin it really produces and the role that bin really plays.
2.3 Distinct bins are only half a fix
Two identically-compiled files under different names would pass check_bins() perfectly. So nornir::atom_line(bin) (src/lib.rs) names the atom, and every launchable prints it in --version:
$ nornir --version
nornir v0.4.x (<sha>) — atom nornir · linkage fat · holger=embedded · skade=embedded · watch=poll · server=in-process
$ nornir-cli --version
nornir v0.4.x (<sha>) — atom nornir-cli · linkage mixed · holger=remote · skade=embedded · watch=poll · server=in-process
⚠ Why the atom name is a parameter, not a cfg! (fixed 2026-08-01)
atom_line originally derived the name from the discriminating features — cfg!(feature = "solo"), then cfg!(feature = "thin"), then cfg!(feature = "inotify"). That could not work, and it shipped broken.
Cargo unifies features across every bin of one package in one invocation. A two-bin probe makes it unambiguous: with [[bin]] two carrying required-features = ["alpha"] and [[bin]] one carrying none, cargo build --features alpha gives both alpha = true. So cargo test --features thin builds nornir and nornir-thin off the one source with thin on for both, and the feature-derived name reported nornir-thin from each. Measured RED under exactly that command:
left: "nornir v0.5.4 (339d6b2) — atom nornir-thin · linkage fat · holger=embedded …"
right: "nornir v0.5.4 (339d6b2) — atom nornir-thin · linkage fat · holger=embedded …"
The same defect reached a shipped artifact by the other route: nornir-ui's declared feature set names none of the three discriminators, so the fall-through else arm made the GUI atom call itself atom nornir — the CLI's name. That was measured on the real cargo xtask build --atom nornir-ui output, not in a test.
atom_line now takes env!("CARGO_BIN_NAME"), which is per-bin and cannot be unified, so a binary can only ever report the atom it is. The rest of the line (linkage / holger / watch / server) stays cfg!-derived and correctly describes the compilation the process came out of. The per-atom feature split is a property of the declared feature sets and is proven by building the atoms — the --version table in §1 is that measurement — not by a unified test run.
The same reasoning fixed the solo gate: see §3.
tests/build_variants.rs::the_atom_marker_reaches_the_binary runs all three built binaries (nornir, nornir-thin, nornir-ui) under that hostile unified build and requires each to name itself. That is the applied proof that the atom selector selects rather than decorating three copies of one file. It is #[cfg(feature = "thin")], so ci_compiles_the_atom_marker_feature (always compiled) requires .woodpecker.yml to build the feature — otherwise the file would read green having asserted nothing.
3. solo — what it means and why it is its own role
nornir-solo is the same GUI source as nornir-ui, built with solo = ["server", "viz"]. That feature lights a block at the top of src/bin/urdr-threads.rs which, before the window opens:
- resolves the bind address and bearer through
server_grpc::{bind_addr, resolve_bearer}— the one reader of each, shared with the standalone daemon; - spawns
nornir::server_grpc::run_with_tokenon a dedicated OS thread with its own tokio runtime (the GUI owns the main thread;eframe::run_nativenever returns); - polls
TcpStream::connectagainst the real bind address until it is accepted; - prints
NORNIR-SOLO: gRPC listening on 127.0.0.1:7878and points the UI half at it viaNORNIR_SERVER; - bails loudly if the socket never accepts, rather than opening a UI that claims a server it does not have.
Start it and you have the whole product: no second process, no port to configure, nothing else to install.
Why a third role rather than a flavour of the other two
- Not a
client. A client's defining question is which server am I pointed at, and it is a runtime decision the operator makes. A solo build has no answer because there is no remote. Filing it underclientputs a self-contained appliance in the same menu section as a UI that is useless without a daemon. - Not a
server. A server is headless and deployed — a unit, a port, a service account, a lifecycle somebody else owns. A solo build opens a window. Filing it underserverlets a deploy step try to run a GUI under a system user with no display.
Both mistakes are silent, which is the whole argument: the distinction changes what a consumer of the menu is allowed to do with the artifact.
Three things solo deliberately does NOT do
- It does not re-implement
nornir-server. Everything below the runtime spawn is one call to the sharedrun_with_token(LAW 5). - It does not open the workspace registry itself.
run_with_policyopens the redb registry as its single writer; a co-tenant that opened it separately would deadlock. Measured 2026-08-01: the only non-test callers ofRegistry::openaresrc/server_grpc.rsandsrc/bin/nornir.rs. The viz never touches it, so the invariant holds by construction. - It does not read
--token/--addroff argv. clap owns this process's argv and rejects unknown flags beforemainruns, soserver_grpc's argv scan can only fall through to the env.NORNIR_SERVER_ADDR/NORNIR_SERVER_TOKENare the solo configuration surface.
The divergence on a fresh box
nornir-server MUST refuse to boot without a bearer. A solo appliance whose whole promise is "nothing else to install" must not, so with no token anywhere it listens open on the resolved (loopback by default) address and says so on stderr. That is why resolve_bearer() returns Option instead of bailing.
The proof it is not vacuous
tests/solo_embeds_server.rs, headless via --robot-remote (no display needed, and the process stays alive to be probed):
solo_binary_serves_real_grpc— asserts nothing is listening first, runs the builtnornir-solo, waits for the marker, then makes a realHealth.PinggRPC call and requires the reply to carry this build's ownnornir::VERSION_LINE.the_ui_atom_serves_nothing— the anti-vacuous anchor, and the more important half: the identical probe againstnornir-uimust find nothing listening. Without it, "a port answered" could be a straynornir-serveron the box.ci_compiles_the_solo_proof— always compiled; red if.woodpecker.ymlstops building--features solo,robot-remote.
⚠ The anchor could not have worked as first written (fixed 2026-08-01)
start_embedded_server() was called under a bare #[cfg(feature = "solo")]. But solo is a package feature, and both nornir-ui and nornir-solo are built from src/bin/urdr-threads.rs in the SAME --features solo,robot-remote invocation the anchor runs in — so the block was compiled into nornir-ui too, and the anchor was probing a UI binary that really did start a server. The anchor described itself as "one feature less"; in that run it was not one feature less.
korp hit the identical wall from the other side and it is visible in bytes: its korp-ui artifact grew from 56,362,176 B (built as its own atom) to 264,616,336 B after a --features solo guard run rebuilt it with the embed. korp works around it by running its negative leg as a SECOND cargo invocation (--no-default-features --features client).
nornir fixes it by construction instead, which is stronger — the negative is proven inside the same hostile, solo-featured build:
#[cfg(feature = "solo")]
if env!("CARGO_BIN_NAME") == "nornir-solo" {
start_embedded_server()?;
}
The feature is still load-bearing: [[bin]] nornir-solo has required-features = ["solo"], so without it the appliance binary does not exist.
⚠ robot-remote did not compile at all
Both applied tests above are #[cfg(all(feature = "solo", feature = "robot-remote"))], so neither had ever run: src/viz/robot_remote.rs:79 called app.inject_test_results_for_test(Vec::new()), an injector that ce553b9 ("viz: cut the test/bench/coverage/leaderboard DISPLAY tabs") removed along with the 🧪 Test tab. A hard E0599, sitting on origin/main (measured at eab671a), invisible because robot-remote is in no default feature set and no CI command built it — the "named by a variant, compiled by nothing" debt this file's own header warns about, with a compile error inside it.
The call only ever wanted the splash side-effect, so it is now app.age_past_splash_for_test() — a new one-writer in src/viz/app.rs that also absorbs the byte-identical two-line wind-back fourteen inject_*_for_test helpers each carried (LAW 5). The --features solo,robot-remote CI step is what keeps it compiled from here on.
4. What is outside the five, and why (revised 2026-08-03)
-
nornir-cliis not an extra role — it is an extra build of theclientrole, differing fromnornirby exactly one feature that changes the artifact (embed-holgeroff). It is in the matrix and is not marked default. It was callednornir-thinuntil 2026-08-03. -
nornir-watchis the same idea (inotifyon) but is NOT in the matrix, so it is no longer a Build Thing. One feature flag on the CLI is not a delivery, and the CLI already degrades honestly without it —nornir watchreports "no inotify in this build" and names the feature. The[[bin]], the feature andsrc/knowledge/watch.rsall remain, and.woodpecker.ymlstill compiles--features inotifyso the loop cannot rot green. Build it by name:cargo build --bin nornir-watch --features inotify. -
nornir-mcpis gone as an atom, and its capability is in the server. Rickard, 2026-08-03: "mcp skall väl ingå inkompilerat i server!" It wasrole = "tool"— the only such role in the constellation — which made MCP a sixth delivery beside a five-row matrix. But<x>-serveris the atom declaredrole = server, linkage = fat: the service, state-holding, everything compiled in. MCP is a capability OF that service.
src/bin/nornir-mcp.rs → src/mcp_stdio.rs (the library module nornir::mcp_stdio, #[cfg(feature = "mcp")]), dispatched from src/bin/nornir-server.rs:
nornir-server mcp # MCP over stdio (rmcp transport::stdio) — was `nornir-mcp`
nornir-server # the gRPC daemon on :7878 — unchanged
Why a subcommand and not an RPC. MCP is stdio: the agent SPAWNS the process and speaks JSON-RPC over its stdin/stdout. The gRPC daemon owns :7878 and has no client stdin to read, so folding MCP into server_grpc would have deleted the capability while looking like it kept it. The two modes are mutually exclusive by construction — the mcp arm returns before server_grpc::run() — so one binary never holds the port and owns stdio at once. The daemon's existing SubmitMcpCalls RPC is the other direction and is untouched: it ingests this module's per-call telemetry into the warehouse mcp_requests table.
What a person runs after this change. Same protocol, same tool names, same stdio framing; only the executable changed:
claude mcp add nornir --env NORNIR_SERVER=http://localhost:7878 \
--env NORNIR_SERVER_TOKEN=<token> -- nornir-server mcp
(nornir-mcp.json in the repo root carries the same edit.) Nothing needs installing or restarting for the change to be correct — but an installed /usr/local/bin/nornir-mcp keeps working until it is replaced, because it is a binary that already exists on disk.
5. Named gaps — what could NOT be landed
- ★ No nornir atom can be
linkage = "thin"— MEASURED 2026-08-03, and this is the one open decision for Rickard.skade,skade-katalog,iceberg,parquetanddatafusionare PLAIN (non-optional) deps, so every nornir binary links the warehouse engine in-process. The build matrix asks for<x>-ui = thin; the leanest atom measures datafusion 61,790 · iceberg 8,841 · parquet 6,198 (§1.1), sothinwould be a declarationLinkageAuditreports as a lie (ThinButLinks).nornir-uiandnornir-clistaymixed, which the same measurement shows is exact rather than a hedge — zero server symbols.
The one change that would make thin reachable, and it is a project rather than a declaration edit: put skade / skade-katalog / iceberg / parquet / datafusion behind an embed-warehouse feature so a client can reach the warehouse over gRPC instead of opening it. That is the only route to the matrix's <x>-ui = thin for nornir. tests/atom_linkage.rs will go RED the day it lands, demanding the declaration be upgraded — the gap cannot be forgotten.
-
embedsis declared only onnornir-server.validate_shaperejectsembedson a non service-shaped kind ("kind 'exe' produces no composed service"). The information lives inlinkage+reaches+ the per-atom comments instead. Making the CLI/GUIkind = "service"to keep the field would be worse —servicewraps as a skidbladnirServiceSpecwith a system user and a unit, exactly the headless deploy thesolorole exists to prevent. -
The feature axis is still unconstrained.
.nornir/build-variants.tomldeclares nooffers, so nornir's ~40 features are never checked againstCargo.tomland the seventeen the atoms name are the only ones any atom builds. Recorded as debt, not closed by inventing atoms. -
nornir-viz-web(crates/nornir-viz-web,publish = false) is a separate wasm rendition built by no atom. It is a legitimate artifact KIND, not a role — left undeclared rather than given a phantom atom. -
nornir-serverstill requiresviz(itsViz.*RPCs reuseviz::knowledge). A headless server that drops egui/wgpu needs the pure-data summary extracted first. -
nornir vizcarries a deprecation shim. It resolvesnornir-uifirst and falls back to a legacyurdr-threadson PATH, so already-deployed boxes keep working until they are redeployed. Delete the shim once the fleet is redeployed.
6. The one build verb, and what was purged for it
cargo xtask build [--suite <s>] [--atom <a>] [--list] — xtask/src/main.rs. Its whole body is nornir_build_thing::suite_cli::run(root, &args): no variant name, no feature string, no cargo argv of its own. demo-wasm, deploy-pages and bench are not build paths and all stay.
Purged / rerouted:
src/viz/build_tab.rs—const HOLGER_DEMO, DELETED. A hardcoded[build]TOML literal declaring holger'sembedded/mini/full, byte-copied when holger was build-thing's first consumer, then frozen. It seeded the LIVEBuildTabState::new, so nornir's own Build tab never showed nornir's own atoms — it showed a stale copy of another repo's, and pressing BUILD shellednornir build --variant fullin nornir's root where no such variant exists. It was also a second declaration of the build space written in Rust. The live path now doesBuildVariantSet::discover(repo_root)and falls back to an explicitly labelled EMPTY surface, never a fixture. The fixture survives only inside#[cfg(test)]asDEMO_FIXTURE, alongside the pre-existingINVALID_DEMO. Guarded bythe_live_build_tab_shows_this_repos_own_atomsanda_repo_with_no_declaration_gets_an_empty_tab_not_a_fixture.nornir build --variant <x>folded ontosuite_cli::resolve. It is nornir's own product surface (the viz BUILD button shells it with--attest --seal) and it stays — but it had its OWN resolution (set.require(name)) that knew nothing about deliveries. Both front doors now go through the one resolver, and the CLI gained--suite.server_grpc:resolve_bearer()andbind_addr()extracted as the one reader of the token and the bind address, so solo and the daemon cannot disagree.install_viz_beside()extracted —install addandinstall updatecarried two identical copies of the GUI-install block, which is how they would have drifted apart over the rename.
The bin rename and every caller updated
urdr-threads → nornir-ui, matching korp → korp-ui and dwarves-demo → dwarves-ui. The product is unchanged: the window title is still Urðr Threads, and the source file keeps its name (the same call korp made, keeping path = "src/main.rs"). Callers updated:
src/bin/nornir.rs—run_vizsibling-bin resolution (+ PATH probe + legacy shim), theinstall add/install updatecopy, theinstall removesweep (which now removes both names, so a pre-rename box is not left with the old binary the shim would keep finding).Cargo.toml— the[[bin]], theviz/robot-remote/defaultfeature docs..woodpecker.yml,.cargo/config.toml.- 26
.nornir/*.mddocs,src/docs/release_book.rs,crates/nornir-viz-web,src/viz/*, fourtests/*.rs. HistoricalCHANGELOG.mdand.nornir/nordisk_plan.mdentries were left alone — they record what the binary was called at the time.
Not renamed, deliberately: the robot scene id "urdr-threads" (src/viz/shell_scene.rs, tests/robot_scene.rs, .nornir/scene-design.md) — that is the product name in a data feed, not a launchable.
7. Guards added
tests/build_variants.rs (new — nornir had no build-variants test; the only thing validating the file was one inotify question):
the_declaration_is_valid—validate_all()exactly_one_default_per_role—check_defaults()emptyno_two_atoms_write_the_same_file—check_bins()empty (seen RED, §2.1)every_declared_bin_is_a_real_cargo_target—check_bins_declared()empty againstBuildVariantSet::cargo_bin_targets()(never a hardcoded list), with two non-vacuous anchors: the oracle must find the bins we know exist, and the check must still flag one when it is removed from the oraclethe_suite_axis_is_declared_and_selects—check_suites()empty ANDdeclares_suites()true (the two are distinguishable, becausecheck_suites()is vacuous below two deliveries);--suite ORIGINALresolves to the declared atoms,--suite NOPEerrors namingORIGINALthe_four_target_atoms_are_all_present— server/CLI/UI/solo, each on its own binno_two_atoms_materialize_the_same_cargo_linethe_atom_marker_reaches_the_binary— applied output, §2.3ci_compiles_the_atom_marker_feature— anti-vacuum
tests/solo_embeds_server.rs (new) — §3.
tests/inotify_watch_wiring.rs is unchanged and still green: the --features inotify CI line it asserts is still there.
tests/rayon_free_law.rs already existed at origin/main (eab671a, landed by another pass on 2026-07-31) — the brief that asked for it to be created was working from a stale tree.