release / deps — dwarves consumes the edda leaves — design
Class:
design· Function axis:f0the two ACT surfaces (the whole) ·f1release(nornir-release: waves + skip) ·f2deps(nornir-depgraph: graph + layout) ·f3the leaf/conductor line (what stays in nornir) Strict: this file states only what the consumption IS — its parts and why. Sibling how-to: release-deps-consume-guide.md; zero-knowledge walkthrough: release-deps-consume-for-idiots.md.
dwarves is the constellation's CI/CD that ACTS (parity spec §"nornir keeps vs dwarves takes"): read/understand stays in nornir, run/act moves to dwarves.
AMENDED 2026-07-14 (PRE-PROMPT L5a). release is a build/publish operation → it is now dwarves-owned end-to-end. dwarves plans the waves AND drives the crates.io publish cascade (
f4, the [execute] runner inrelease.rs). Only the version / dep / history FACTS and the warehouse-KNOWLEDGE analyses (doctor's semantic-blast, regression root-cause) stay in nornir, read by gRPC (one-directionaldwarves→nornir, no cycle). The prior wording — "the actual publish stays in nornir" — is superseded.
Two such acts — run a release and show the suite dependency graph — are backed by warehouse-free logic that already lives in shared edda leaves. Until now only nornir path-depped those leaves, so dwarves' own manifests couldn't run them. This wires dwarves to CONSUME them (Law L5 — reuse, never twin):
nornir-release— source-drift skip (can_skip_republish) + the deps-first publish-wave planner (plan_waves,select_republish).nornir-depgraph— the cross-repo graph, topological build order, and the pure (egui-free) dependency-VIEW layout.
The system as one — the function map (f0)
| part | lives in | what it is |
f1 release |
crates/dwarves/src/release.rs |
discover_members (parse manifests) → plan_waves (the leaf) → plan folds in can_skip/select_republish → ReleasePlan |
f4 publish ACT |
crates/dwarves/src/release.rs |
publish_script (deps-first cargo publish argv per wave) → execute over the swappable PublishRunner seam (CargoPublishRunner real; recorder in tests). Halts on a failed publish; index-waits between waves. dwarves-owned |
f2 deps |
crates/dwarves/src/depview.rs |
discover_repos → WorkspaceGraph::build_from_members (the leaf) → DepGraphLayout::build → suite_state_json (build order + layout) |
| CLI | crates/dwarves/src/main.rs |
dwarves release <path> [--execute --allow-dirty --no-verify] · dwarves deps <dir> (behind --features release) |
f3 facts line |
nornir | version/dep/history FACTS + warehouse-KNOWLEDGE analyses (doctor semantic-blast, regression root-cause) stay in nornir (warehouse) — read over gRPC |
Why it is shaped this way
-
Consume the leaf, never twin it (L5). dwarves calls
nornir_release::waves::plan_wavesandsource_drift::can_skip_republish/select_republish, andnornir_depgraph::WorkspaceGraph/layout::DepGraphLayout— the same functions nornir calls. dwarves adds only the thin glue (manifest discovery + a CLI print), never a second planner or a second graph builder. So nornir and dwarves render the identical suite dep view and compute the identical release order. -
The leaf/conductor line is load-bearing (§L5a, amended). A leaf is warehouse-FREE and edda-shareable (the wave + skip MATH). The publish ACT (
cargo publishdeps-first cascade) is dwarves ORCHESTRATION — it lives in the conductor (release.rs), not the leaf and not nornir. Only the warehouse-COUPLED FACTS — the live source-drift assessment tied to warehouse-recorded published versions, the doctor semantic-blast, regression root-cause — stay in nornir and are read over gRPC. This keeps the graph one-directional (dwarves→nornir): dwarves never crate-deps nornir, so no cycle.dwarves releaseDEFAULTS to a dry-run (prints thecargo publishscript);--executeruns the real cascade behind the session's crates.io guardrail. -
Additive + feature-gated. Behind a new off-by-default
releasefeature (the leaves pullcargo_metadata/ureq/flate2), so the lean/published forge and nornir's path-dep pay nothing.dwarves release/depsprint a clean "rebuild with --features release" when the feature is off — the same pattern asserve/repo. -
Real, exercised wiring — not a dead dep line. The pure leaf calls are asserted from dwarves (RED-when-broken): deps-first waves + unpublishable-drop, the
can_skip_republishcontract (published+unchanged only), the reverse-dep cascade, and a two/three-repobuild_order+ layoutstate_json. Thef4publish ACT is driven headless over a recordingPublishRunner(no cargo, no network):publish_scriptis deps-first and carries the flags;executeruns the cascade deps-first, index-waits between waves, and halts on a failed publish so a dependent never publishes against a broken dep (RED-when-broken). A CLI smoke runsdwarves releaseon a real workspace. -
The pre-upload verdict set is THREE, not two (2026-09-02). Beside skip and drift,
dwarves releasecomputes a SIZE verdict in the same pass and before any upload:release::probe_package_sizespackages each crate through the existingrelease_doctor::publish::tarball_stats(ONE tarball reader in the crate — the bloat gate and the size halt share it, L5) and weighs the compressed.crateagainstrelease::RegistryLimits. Over the limit ⇒release::size_gaterefuses the whole cascade, the drift halt's shape, naming the crate, the size, the limit and its source, and the top-N biggest directories + files from the same walk. The limit is a DOCUMENTED constant with date and provenance (CRATES_IO_MAX_UPLOAD_BYTES, 10 MiB) because no registry endpoint publishes it, overridable by flag/env and honestlyNonefor an unknown registry — never a guessed number that refuses a legal upload. Why here and not at upload time: crates.io answers an oversized upload with a bare 503 that names no size, so the fact must be measured locally or it is not knowable. The mid-cascade 503 is translated against the same measurement (release::explain_503): oversized ⇒ named and NOT retried, in-limit ⇒ transient and retried like a 429, unmeasured ⇒ said plainly. The same pass yieldsrelease::dirty_files_in_package, which turns cargo's "N files in the working directory contain changes" into the names of the dirty files that actually ship.
Place in the nordisk constellation
This is the canonical §L5a (amended) case made real for dwarves: the release MATH lives in edda/nornir-release + nornir-depgraph, the ORCHESTRATION (release-DAG, publish cascade) is dwarves-owned, and the FACTS (published versions, warehouse history, doctor analyses) stay in nornir — read over gRPC, with no nornir crate dep (no cycle).
Deferred
- Live drift assessment on the CLI.
dwarves releaseplans the waves + states the skip contract but does not yet fetch crates.io max-versions to fill each crate'sDriftVerdict— the CLI over-approximates (every publishable crate republishes). Thecan_skip/select_republishskip+cascade logic is proven from dwarves in tests; a gRPC read of nornir's warehouse-recorded published versions is the wiring that narrows the CLI cascade to only the drifted crates. - The heavy doctor/gate engine is not yet lifted. The publish ACT is dwarves-owned; the warehouse-semantic-blast doctor + the extended gate set remain a nornir warehouse service reached over gRPC (they are warehouse-KNOWLEDGE, nornir's "remembers" domain). A dedicated
dwarves release doctorthat consults them over gRPC is the next step. - Real publish gated.
--executeruns the real cascade; the session's crates.io guardrail governs whether it is exercised (dry-run is always available + tested). depsscans immediate subdirs. A directory of side-by-side repos; a deeper discovery (globbed roots, a manifest of repo paths) is a later refinement.- PDFs (
docs/*.pdf) left for the Loki bencher'snornir docs bookrender.