Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/dwarves

.nornir/release-doctor-guide.md

Release doctor — guide

A practical, in-order how-to for releasing the whole nordisk constellation with nornir release doctor. For the model behind it see release-doctor-design.md; for the absolute-minimum recipe see release-doctor-for-idiots.md.

Every command reads the configured constellation (the [repo] set in your workspace's nornir.toml). Run them from your workspace root, or pass --config <workspace>/release/nornir.toml.

The command in one picture

doctor is one command with four escalating tiers — read-only by default, each mutating stage behind an opt-in flag:

nornir release doctor                     # ① ASSESS  (read-only)
nornir release doctor --fix               # ② FIX     (write version= on path deps)
nornir release doctor --rehearse          # ③ PROVE   (dry-publish into /sparring)
nornir release doctor --publish --bump …  # ④ PUBLISH (the real crates.io cascade)

You never reach ④ on a tree that hasn't passed ② (and ideally ③): --publish refuses while any hard blocker remains.

Step 1 — assess (read-only)

nornir release doctor
# or explicitly:
nornir release doctor --config workspace_nordisk/release/nornir.toml

Mutates nothing. Composes, in one report:

Useful knobs: --forbid arrow=56 bans an external version (repeatable); --json emits the full DoctorReport for tooling.

Step 2 — fix the mechanical blockers (--fix)

nornir release doctor --fix

Auto-applies the publish pre-flight: writes version = "…" to every path dep of a publishable crate that lacks one, sourcing the version from the dep crate's own manifest (following the path, resolving version.workspace = true), covering [target.'cfg(…)'] tables. It is formatting-preserving and idempotent — safe to re-run. It prints how many it wrote and skips anything with no resolvable version. After fixing, it re-runs the assessment so the report reflects the clean tree.

Only the "auto-fixable" (member-owned) gaps are handled. A ⛔ needs-decision gap (a non-member dep) is yours to resolve: add that repo to the release cascade, or publish it to crates.io first — the engine won't guess.

Step 3 — prove the cascade against /sparring (--rehearse)

nornir release doctor --rehearse

Escalates from advice to a real dry-publish against the embedded holger /sparring registry: cuts an ephemeral release/staging branch per repo, cargo publishes in topo order, then builds each dependent FROM /sparring. It mutates (opt-in) but self-restores every repo to its main branch afterward. Nothing hits real crates.io. A residual, non-static blocker surfaces here as a real cargo error. Refuses if the plan has a dependency cycle.

Flags: --registry <url> and --branch <name> override the default sparring registry / ephemeral branch. (nornir release stage [--execute] runs the same rehearsal standalone.)

Step 4 — rehearse the REAL crates.io cascade (no upload)

nornir release doctor --publish --publish-dry-run --bump patch

Plans the whole crates.io cascade and runs cargo publish --dry-run for every crate — end-to-end, mutating nothing (no bump, no branch, no upload). Use it as the final look before the real thing.

Step 5 — publish the constellation (--publish)

nornir release doctor --publish --bump patch

The one command. After assessing (and only with zero blockers — it bails on any path-dep-version gap, promote-block, or cycle), it publishes every public repo to crates.io in crate-topo order, deps-first, fail-stop, each recorded for release undo. Per repo it: regenerates docs (README + docs/book.pdf), runs the promote gate, bumps + commits versions, strips [patch.crates-io] on an ephemeral branch, publishes deps-first waiting for each crate to index before its dependents, and records every step. Private repos (private = true) are skipped — staged and verified, never pushed to crates.io.

--bump selects the level:

--bump effect
patch (default) / minor / major bump every publishable crate by that level before publishing
keep / none publish the CURRENT versions unchanged — the idempotent finish/resume path

crates.io rejects an already-uploaded version, so a fresh full release wants a real bump. But "already uploaded" is treated as a skip, not an error — which is what makes keep and resume work.

The --bump keep finish path

After a partial or fail-stopped run, don't re-bump everything (that churns the irreversible registry). Fix the cause, then finish with:

nornir release doctor --publish --bump keep

keep publishes the current versions and skips every crate already on crates.io — so it ships exactly the not-yet-published crates and leaves the rest untouched. It is idempotent: run it as many times as you need until the cascade is complete.

Resuming mid-cascade with --publish-from

If a fail-stop happened at, say, facett, and everything before it in topo order already shipped, resume without re-publishing the earlier repos:

nornir release doctor --publish --bump keep --publish-from facett

--publish-from <repo> skips every repo before <repo> in the topo order, then proceeds normally (skipping private repos and already-uploaded crates).

If it stops — fail-stop and undo

--publish is fail-stop: the instant a repo reports a per-crate publish failure, the cascade halts (it will not sail on to a dependent whose dependency never shipped) and prints the failing crates. Read the error, fix the cause, then resume with --bump keep (+ --publish-from if useful).

To reverse a run entirely:

nornir release undo <release-id> --yank

It un-bumps (restores the exact pre-bump manifests), restores any stripped [patch.crates-io] block, and resets the release branch. A crates.io upload is immutable, so it is yanked (with --yank) rather than deleted. With no id it undoes the most recent run; --dry-run reports without acting. The release-id is printed at the end of every real --publish run.

Not everything is automatable

The doctor solves what it safely can and explains what it can't — it never guesses. Two classes stay with a human:

Prefer to click? Drive it in the viz

nornir viz → the 🧬 nornir tab paints the publish graph with every problem overlaid on each node (⛔ held · ▼ behind · 🟡 dirty), each cycle as a dashed cut edge. The 🚀 Release button opens the 🧙 wizard — a gated Advise → Fix → Rehearse → Promote walk (you can't skip a red step without an override; Promote makes you type PROMOTE). Same engine as the CLI.

Over MCP

release_doctor is an embedded-only MCP tool — read-only advisory, run on the host (it reads the local git trees, so it's refused against a NORNIR_SERVER target). An agent calls it to see skew / dirty-trees / promotability before cutting a release.

Deeper go-live runbook and traps: release-rehearsal.md, release-publish-traps.md.