Release & history
How a release is run, what it captures, and how the warehouse lets you read any past release back. This is the Urðr chapter — what has been — wired to the present: a release isn't just "publish the crates", it's the moment nornir pins a complete, SHA-keyed snapshot of the workspace into the warehouse so the whole state is queryable and restorable forever after.
The release run
nornir release run walks the workspace in dependency order (the topological order recorded in the dep_graph_edges Iceberg table — dependencies first) and, per repo, runs a fixed sequence. It stops at the first failure; every repo processed up to and including the failure is recorded.
nornir release run # every configured repo, in dep order
nornir release run --repo holger # just one member
Per-repo phases, in order — each gated by a --skip-* flag:
- test —
cargo test --workspace(per-test outcomes captured). - bench — runs the repo's
examples/nornir-bench.rs; theBenchRun(results + per-test outcomes) is persisted tobench_runs. A failing bench-test aborts the release. - docs render — regenerates
README.mdfrom the freshly-persisted bench row. - gate-all — runs every gate enabled in
nornir.toml(regression floor, guard-intact, crate metadata, path-dep versions, …). A red gate aborts. - capture — see below; the fat-client capture of every source-fact projection at this repo's release SHA.
- push —
git commit+git pushof the regenerated docs toorigin, so the published crates' git-SHA lineage matches the public remote. - publish —
cargo publishwalks the repo'spublish_order; crates already on crates.io are detected and skipped (idempotent).
The capture phase — "nothing computed is discarded"
nornir is a fat client: wherever it computes a projection of the source, that projection must land in the warehouse — never computed-and-thrown-away. The release run's capture phase (step 5, under --skip-snapshot) enforces this. For each repo, at its release git SHA, it persists, best-effort and non-fatal (a capture hiccup never fails an otherwise-green release):
| Projection | Table(s) | Notes |
| knowledge map | symbol_facts / call_edges / feature_gate_facts / git_heat_facts |
syn symbols + call graph + cfg-gates, plus gix per-file commit heat |
| code index | index_snapshots / index_blobs |
the workspace Tantivy (BM25) index, byte-for-byte pinned to the SHA — the Urðr time-machine capture |
| semantic vectors | vector_* |
embedded + materialized when this build has an embedder (embed-tract/embed-ort); a skip-hint otherwise |
| DWARF facts | dwarf_snapshots / dwarf_blobs |
symbols + inline-call edges from each binary the test/bench build produced (discovered via cargo metadata; prefers target/release, falls back to target/debug) |
| bench | bench_runs |
captured earlier, in the bench phase |
The index and DWARF snapshot ids are pinned into the release_lineage row (tantivy_snapshot_id / dwarf_snapshot_id), so a release row points directly at the exact searchable index and code-fact snapshots that shipped.
Idempotent per (repo, git_sha). Re-running a release at an unchanged SHA doesn't redo or re-store the expensive projections: the capture phase looks up whether the warehouse already has an index / DWARF snapshot for this repo's SHA and, if so, reuses it (and its lineage pin) instead of re-walking the index or re-parsing the binary's DWARF — which matters because iceberg-rust 0.9 has no expire_snapshots/compaction, so the cheapest way to bound warehouse growth is to not write redundant data. Vectors are already idempotent (only changed chunks re-embed). Pass --recapture to force a fresh capture. (Knowledge facts still append per run — they're small and carry no git_sha to key on; a capture side-index to dedup them is a tracked follow-up.)
The same complete capture is available outside a release as a single verb:
nornir map <repo> # knowledge scan + index build + index snapshot + vectors, all keyed to HEAD
And every producer verb now persists by default (so the everyday dev loop also never discards): knowledge scan and introspect symbols save unless you pass --no-save; index build auto-snapshots unless you pass --no-snapshot.
Roadmap (tracked in
plan.md). Minor follow-ups remain: the release run writes lineage rows only for green repos (a failed repo aborts before its row is written, sorelease traceover real runs currently sees greens), andpublished_versionsis left empty (the publish step's outcomes don't carry resolved versions yet). Thegimli_snapshot_idpin (col 17) and rustdoc-JSONapicapture are future lower-level/code-fact work.
Reading history back
Because every fact is an Iceberg row keyed by git SHA, "the workspace as of release X" is a warehouse read, not a git walk or a rebuild.
- Restore the search index at any SHA — rehydrate the exact index that shipped:
nornir index restore --repo holger # latest snapshot
nornir index restore --repo holger --sha <sha> # pinned to a release
-
Semantic / lexical search at a past SHA —
vector_*andindex_snapshotsare columnar reads, so searching an old release never re-embeds or re-indexes (see vector.md, warehouse.md). -
Release lineage — the
release_lineagetable records one row per repo per release: release id, workspace, timestamp, the pinneddep_graph_snapshot_id, build-order index, git SHA / branch / dirty flag, gate status, test pass/fail counts, dry-run flag, and the publishedcrate@versionset. -
Bench history —
nornir bench history-show <repo>reads the per-SHAbench_runsrows; docs history vianornir docs history.
Regression trace — last-good → first-bad
Because gate outcomes are historized per SHA, finding when something broke is a scan over the warehouse, with no rebuild:
nornir release trace --repo holger # human-readable
nornir release trace --repo holger --json
It finds the last green release and the first red one for the repo (the gate's last-good → first-bad boundary) and bounds the suspect commit range — the crossover the three Norns sit on: what was good, what is broken, and the commits in between to inspect.
Flags (release run)
All capture/publish steps are on by default; each --skip-* turns one off.
| Flag | Effect |
--repo <name> |
one member instead of every configured repo |
--workspace <name> |
which dep-graph snapshots to order by (default workspace_holger) |
--skip-tests |
skip cargo test --workspace |
--skip-bench |
skip the nornir-bench example |
--skip-render-docs |
leave README.md untouched |
--skip-gates |
skip gate-all |
--skip-snapshot |
skip the warehouse capture phase (knowledge + index + vectors) |
--skip-push |
don't commit/push regenerated docs |
--skip-publish |
don't cargo publish the publish_order |
--dry-run-publish |
pass --dry-run to every cargo publish (validate end-to-end without shipping) |
Live progress is written as append-only ndjson under <workspace>/.nornir/logs/ — consumed by nornir-server's release-progress SSE stream and the urdr-threads visualizer's live pane.