# Changelog
## 0.4.38 (2026-06-15)
Reclaims good work orphaned when 0.4.36 was yanked (published from a pre-wave
base, regression). Layered on top of 0.4.37 (the full 5-agent wave):
- **fix(release): `docs_fresh` gate must not require a bench run**. `run_gate_all`
hard-propagated the "no bench runs in iceberg …" error, so a repo with no
`nornir-bench.rs` (e.g. nornir itself) false-failed `docs_fresh` even with zero
doc drift. `assemble_and_check` now takes `Option<&BenchRun>` — generated READMEs
pass via the marker, hand-written ones assert the `v<version>` headline only when
a run names the version. Gate + CLI pass the run as `Option` (the real dogfood
gate bug).
- **feat(autonom): `enumerate_surface(view, cli_commands) -> Surface`** — the
single-call surface enumerator (collapses `SurfaceInputs` assembly +
`knowledge_to_rows` + `build_surface_and_covered`), closing the n-000 "no unified
entry point" gap.
- **feat(viz): distinct staleness badge** — `Staleness::{Fresh, ServerDirty}` in
`viz/ops_tabs.rs`; the WorkspacePanel renders a distinct freshness badge (green ✓
fresh / amber ⚠ server-dirty) and surfaces `staleness`+`badge` in `state_json`
(n-003). Legacy " ⚠ uncommitted" marker preserved.
All three carry inject-assert tests. The nornir-airgap demo-component work from the
same side branch was SKIPPED: the wave already landed the equivalent
`ComponentSpec` registry/`lookup` infrastructure on `main` (via a different idiom),
so re-applying would have duplicated `registry()`/`lookup()`.
## 0.4.29 (2026-06-14)
- **fix `cargo install nornir` (no --locked) alloc-no-stdlib version conflict**: pin `alloc-no-stdlib = ">=2.0.4, <3"` as a direct dependency to prevent the fresh resolver from simultaneously selecting alloc-no-stdlib 2.0.4 (required by `brotli 8.0.3` directly as `"2.0"`) and 3.0.0 (pulled in by `brotli-decompressor 5.0.2` / `alloc-stdlib 0.2.3`, which widened their constraints to `">=2.0.4, <4"`). Without this pin the two versions end up in the same compilation unit with incompatible `alloc::Allocator<u8>` trait impls, producing ~36 E0277 errors in `brotli/src/enc/writer.rs`. The `<3` upper bound matches brotli's own direct constraint.
## 0.4.28 (2026-06-14)
- **All feature flags default-on**: `robot`, `vector`, `embed-tract`, `gen-candle`, `gen-ollama` added to default features — `cargo build` now includes the robot-UI tester, vector/embedding, candle LLM backend, and ollama client without extra flags. `embed-ort-rocm` and `gen-mistralrs` remain opt-in (hardware-specific / heavy).
## 0.4.27 (2026-06-14)
- **EPIC R6 — richer live-drive for the robot-UI tester**: widget-level robot control beyond tab/workspace clicking, with the SAME verbs working in-process (`egui_kittest`) and over the live `viz.click` control channel (CLI/GUI parity LAW). `nornir-robotui` gains `RobotSession::set_field(target, value)` (set any text/checkbox/slider — role-aware: clear+type / set-if-differs / AccessKit `SetValue` — addressed by label **or** node id), `click_by_id(id)` (click by AccessKit node id when labels collide/are empty), and `inject_events(events)` (feed an arbitrary `Vec<egui::Event>` for one frame). New `Target{Label,Id}` / `FieldValue{Text,Bool,Number}` types; `Action` grows `ClickById`/`SetField`/`InjectEvents` and `Assert` grows `FieldValue`/`FieldChecked` (read the value/toggle off the rendered widget) — all plain data, still TOML-able. Discovery/assert helpers `find_id`/`node_id`/`field_value`/`field_checked`. Live-channel parity: `VizCommand` grows `set_field{name,value}` + `click_id` (a stable `"<tab>.<field|button>"` key, since AccessKit ids aren't stable across viz runs); `apply_command` routes them to the owning tab; the **Funnel** tab wires intake/planner/demo fields+buttons (`set_field_for_drive`/`click_for_drive` run the same code paths the GUI buttons do). MCP `viz.click` exposes `set_field="name=value"` + `click_id`. The Funnel paste-in textbox is now `labelled_by` its "paste:" label (robot-addressable) and `state_json["funnel"]["intake"]` exposes the literal text. inject-assert tests: 6 new `nornir-robotui` unit tests (text/bool/number set_field, click_by_id, inject_events, scenario incl. a deliberately-wrong assertion → red cell) + 2 against the REAL viz (`tests/robot_viz.rs`): set the Funnel paste-in field, submit it by AccessKit id, and inject a raw Focus+Text event sequence, asserting `state_json` reflects each. New `NORNIR_VIZ_NO_DURABLE_ACTIONLOG` env skips the durable action-log warehouse sink so an in-process consumer (the Funnel tab / the R6 test) can open the catalog. Docs: `.nornir/robotui-richer-livedrive.md` design + guide.
- **LAW4b — live-app self-screenshot** (`ViewportCommand::Screenshot`): `VizCommand::Screenshot` + `ScreenshotRequest` on the control channel, Ctrl+Shift+S keyboard shortcut, `nornir viz --screenshot <path>` CLI flag, warehouse `robot_test_results` blob persist, `state_json["screenshot"]` with `pending`/`last_path`. Docs: `.nornir/viz-law4b-screenshot.md`. 4 inject-assert tests (`tests/viz_law4b_screenshot.rs`).
- **N-flip — un-ignore the 2 stale `viz_md_matrix` drivers**: rebase funnel cell + flip now-wired thin cells so the multi-dim matrix (40 cells × fat/thin × data-present/absent) goes fully green in the release gate.
- **C6g — MCP tooling for test/bench**: `test_run`/`test_status`/`test_results`/`test_stop` + `bench_run`/`bench_stop` MCP tools, enabling agents to trigger and observe test/bench runs over the standard `nornir-mcp` interface.
- **D2h — facett-grid inline editing**: type-aware inline editors (text/number/bool/enum) + validation → change events; wired into `facett-grid` (D2). Builds on D2g keyboard nav.
- **J4 — circuit-board flow SVGs in every repo**: `korp_flow.svg`/`knut_flow.svg`/`skade_flow.svg`/`facett_flow.svg` authored in circuit-board house style, embedded in each repo's `.nornir/design.md`, committed + pushed.
- **L5 — Test pane matrix shines**: per-repo × per-aspect matrix with health scores, trend sparklines, drill-down, and `state_json["test"]["matrix"]` (flat `"repo/aspect"` cells dict with `pass`/`fail`/`skip`/`health`/`trend`; backward-compat `grid` kept). Inject-assert: `test_pane_exposes_l5_matrix_cells` (7 tests + wgpu snapshot). Docs: `.nornir/viz-test-pane-shine.md`.
- **L6 — Test pane live data**: `nornir test all` → `test_results` → matrix auto-populates when `nornir viz` opens; the `query_test_results` → `Matrix::build` → `state_json["test"]["matrix"]` read path is end-to-end wired.
## 0.4.26 (2026-06-13)
- **EPIC ROBOTUI** — new reusable **robot-UI testing framework** crate `nornir-robotui` (a lean sibling of `nornir-testmatrix`, published 0.1.0): auto-click a real egui app, scroll, press buttons, type — and watch the test matrix fill. Three seams: **drive** via `egui_kittest` (AccessKit) behind a `RobotSession` (`click_label`/`click_button`/`type_into`/`key`/`scroll`/`drag`/`step`/`run`, and a `snapshot` feature for wgpu PNG proof); **observe** via the one-method `Observable` trait (`state_json`, the same blob the viz dumps to `$NORNIR_VIZ_STATE`) with `Assert::State{pointer,expected}`/`StateContains`/`LabelVisible`/`Drew`; **emit** via `nornir-testmatrix` — a `Scenario` of `Step{action,assert,label}` → `run_scenario` returns `Vec<TestResultRow>` (suite=scenario, aspect=`robot-ui`, one `run_id`), so one call drives every button and fills the matrix. The `snapshot` feature gates the wgpu/image stack so the core stays light. Reference consumer `tests/robot_viz.rs` drives the REAL `UrdrThreadsApp`: real AccessKit tab-header clicks switch `state_json["tab"]`, the seeded Test run renders after the click, and a 6-step scenario produces 6 green `robot-ui` rows that round-trip through a `JsonFileSink`. Reusable by korp/facett-demo/mannequin/viz; `.nornir/robot-ui.md` design + idiot guide + flow SVG. (nornir depends on it dev-only — the published crate is unchanged.)
## 0.4.25 (2026-06-13)
- **EPIC FUNNEL-INTAKE** — paste errors/ideas into the funnel + browse history, via viz AND CLI (`.nornir/funnel-intake.md`).
- **FI1 paste-in**: a viz Funnel-tab paste strip (💡 idea / 🐞 error toggle + multiline textbox + Submit; local `Store` or `Funnel.SubmitIdea` RPC) and CLI parity `nornir funnel idea "…"` + `nornir funnel error - < report.txt` (a shared `funnel_intake`; text from `--file` → arg (`-` = stdin) → stdin pipe).
- **FI2 history**: a viz history side-panel (newest-first, kind/status filter chips, error rows tinted, click → triage→plan lineage) + `nornir funnel history [--kind error|idea] [--status …] [--table]` + a new `Funnel.History` RPC — all over one shared `funnel::history` projection (derives untriaged→accepted/dropped→planned + plan lineage) so CLI/server/viz never disagree.
- **FI3 errors first-class**: a new `ItemKind` (`idea`|`error`) on the `IdeaSubmitted` event + `Idea`, persisted as a migration-safe optional `item_kind` column (field 28) on `funnel_events`; an error triages → a plan node with the existing `funnel plan` verb, exactly like an idea. The pre-FI 27-col table auto-evolves to 28 on first write via `ensure_table_schema`.
- Viz `state_json` exposes `intake` + `history` blocks (LAW 6). inject-assert tests: history derivation+filters, both-kind Iceberg round-trip, 27→28 schema evolution, and the viz tab submit→history→filter.
## 0.4.24 (2026-06-13)
- **fix `Telemetry.SubmitBakeoff`/`SubmitTestResults` schema-mismatch** (shipped broken in 0.4.22/0.4.23): submitting a bake-off against a live server failed with `number of columns(16) must match number of fields(13) in schema`. Cause: the server's `agent_model_runs` table was created by an *older* binary with only 13 columns (before the `agent`/`cost_usd`/`mcp_tool_calls` matrix+economics fields), but a current row produces 16; `test_results` had the same risk (8 vs 10, pre-EPIC-L `aspect`/`metric`). Per the Iceberg schema-evolution LAW (`.nornir/warehouse-schema.md`), the write path now **auto-evolves** a stale table forward with an Iceberg **add-column** migration before appending: new `warehouse::iceberg::ensure_table_schema` diffs current vs canonical columns and, when any are missing, commits `AddSchema`+`SetCurrentSchema` via skade's `RedbCatalog::commit_table` (iceberg-rust 0.9.1 has no public schema-mutation Transaction). Old data files are untouched (add-column is metadata-only; old rows read null), and every read path already defaults the optional columns. Inject-assert regression tests reproduce a stale 13-col / 8-col table, append a full row, and assert the table evolves AND the new columns read back.
## 0.4.23 (2026-06-13)
- **`Telemetry.SubmitBakeoff` + `Telemetry.SubmitTestResults` RPCs** (mirror Bench.Submit): a client can now fill the live server's `agent_model_runs` (🏆 Leaderboard) and `test_results` (🧪 Test) — the server holds the warehouse lock. `nornir bakeoff demo`/`run` submit to the server when `NORNIR_SERVER` is set (so the Leaderboard fills on oden, no empty THIN tab). Test-CLI submit wiring (TestSink) is the remaining follow-up; the RPC + client fn are in.
## 0.4.22 (2026-06-13)
- **docs `book` image resolver** (`src/docs/book.rs`): assembled chapters now resolve each markdown image **source-relative first** (the markdown/web convention — `assets/x.svg` in a `.nornir/*.md` = `.nornir/assets/x.svg`), falling back to **root-relative** (`.nornir/assets/x.svg` as-written), emitting whichever exists as a root-relative path for the typst mount. Fixes nornir's OWN `book.pdf` failing to render ("file not found … assets/…") while keeping holger's `.nornir/assets/…` refs working. nornir's `docs/book.pdf` regenerated (7.6 MB, all `.nornir/*.md` + plan + wish-lists with SVG charts embedded).
## 0.4.21 (2026-06-13)
Fixes the co-maintainer's nornir-mcp install/first-run friction report (thank you):
- **B1**: `mcp` is now a **default** feature → a plain `cargo install nornir` ships the **`nornir-mcp`** binary. The documented `claude mcp add … -- nornir-mcp` works with no `--features mcp` (before: missing binary → silent "Failed to connect").
- **B2** (main blocker): **`nornir-mcp` no longer needs a local `nornir.toml` in client mode** — when `NORNIR_SERVER` is set it falls back to a default bootstrap and lets the server drive. A pure remote-client machine starts fine.
- **B3**: config discovery no longer hardcodes the personal **`workspace_holger`** path — it walks up for a plain `nornir.toml` (cargo-style; mono-repo `workspace_*/release/nornir.toml` is a generic fallback for any `workspace_*`). Error message + storage-default comment de-personalized + made actionable.
- **B4**: `nornir-mcp --help` / `--version` work **before** any config load (were blocked by B2).
- docs: `mcp-from-claude-code.md` — plain-install line, a "Failed to connect → run nornir-mcp directly" debugging section, and "NORNIR_SERVER is gRPC, don't curl it."
## 0.4.20 (2026-06-13)
- **client-mode MCP telemetry reaches the server** (fixes viz 📞 "0 calls / not used" when the MCP is configured): new `Telemetry.SubmitMcpCalls` RPC. A thin `nornir-mcp` (which can't write the server-locked warehouse) now drain-batches its per-call telemetry and submits it to the server, which appends to the served workspace's `mcp_requests`. Before this, client-mode telemetry was dropped, so the viz always read 0 even with a busy MCP.
## 0.4.19 (2026-06-13)
- **viz pre-flight popup is no longer tiresome**: the "0 MCP calls" notice and (in thin mode) the local cargo/rustup checks are now **non-blocking** — they live in `state_json` / the 📞 tab, they never raise the centered popup over the content. The popup is reserved for genuine capability blockers for THIS process (a missing local toolchain in fat/embedded mode). New `Check.blocking` + `Preflight::run(thin)`; thin clients don't nag about a local cargo they don't use (the SERVER runs scans/benches). Known gap surfaced: client-mode MCP telemetry isn't recorded to the server yet — see 0.4.20 submit RPCs.
## 0.4.18 (2026-06-13)
- **viz is built in by default**: `viz` is now in the `default` feature set, so a plain `cargo install nornir` builds + installs the `urdr-threads` viz binary — `nornir viz` works out of the box with no `--features viz`. (Previously `nornir viz` execed a `urdr-threads` that a default install never built → "exec urdr-threads: No such file or directory".) Lean lib/bench consumers still use `default-features = false` to drop the eframe/egui/wgpu stack.
## 0.4.17 (2026-06-13)
- **viz workspace×function matrix** (`tests/viz_workspace_matrix.rs`): the missing test axis. One ephemeral server monitors 3 workspaces, each seeded with DISTINCT data; asserts every workspace's every data-bearing function (Release/Warehouse/Timeline/Leaderboard/Security) retrieves its OWN counts over the live thin RPCs, AND that switching the picker re-scopes every function with **zero stale leak** (the bug class behind the 0.4.15 Security fix). New `App::switch_workspace_for_test` hook drives the picker's exact code path.
- **heavy tests run by DEFAULT** in the release gate: `run_cargo_test` now passes `--include-ignored` unless you opt out with `nornir --skip-heavy` (or `NORNIR_SKIP_HEAVY=1`). Skipping heavy runs silently was letting regressions through. Back-compat: `NORNIR_HEAVY_TESTS=0` still skips.
- **matrix in nornir's release**: `NORNIR_TEST_FEATURES=viz,server` makes the test gate compile + run the feature-gated viz matrix (`--features` placed correctly before `--`).
## 0.4.16 (2026-06-13)
- **viz pre-flight popup**: after the splash, a 🚦 popup runs capability checks and surfaces problems the old build hid. Missing `cargo`/`rustup` → a one-click "Install rustup for me" button (the single sanctioned shell-out, gated behind an explicit click; nornir is otherwise 100% Rust). **Zero MCP calls recorded** → a warning that explains how to wire nornir's 56 tools into Claude Code, with a 📋 copy button for the exact `claude mcp add` command (built from the live connection — server + token, no hardcoded secrets). `state_json["preflight"]` for introspection; inject-assert tests.
- **version on the splash**: `client_build()` (version + git SHA + build time) now paints on the launch splash, not just the ℹ About popup — a stale client is obvious the moment it opens.
- **multi-workspace MCP**: `workspaces_list` now returns EVERY workspace WITH its repos — the full workspace×repo surface in one call, so an agent sees all repos across all workspaces without switching first. `workspace_use <name>` switches focus at runtime (no restart); `NORNIR_WORKSPACE` is now an optional default, never a hard lock.
- **SBOM scan hardening**: `security::components` no longer dies with a cryptic `read …/Cargo.lock` when a library crate gitignores its lockfile. It runs `cargo generate-lockfile` to synthesize one, and if cargo is genuinely absent returns a clear, actionable message instead of a raw gRPC Internal.
- ops: the `nornir` systemd service user gets a rustup toolchain + `~/.cargo/bin` on its PATH (via the `path.conf` drop-in), so server-side Security scans and bench runs can shell out to cargo.
## 0.4.15 (2026-06-13)
- **viz fix**: switching the workspace picker now re-scopes the 🛡 Security tab. It cached the previously selected workspace's repo set at construction with no setter, so after a switch its scan buttons still listed the old workspace's repos. `SecurityTab::set_workspace` (root + members, clears any stale scan) is now called from `App::switch_workspace` alongside Knowledge. Inject-assert test: switch A→B, assert rendered repos follow B and A never leaks.
## 0.4.14 — "Heimdall" (2026-06-13)
Modes, the warehouse on home disk, a viz that actually fills against a live server, and a robot that can drive it.
- **Home-derived data root** `~/.nornir` (server: `/home/nornir/.nornir`), **no path env-vars** — `nornir root`. **Per-workspace** layout `workspaces/<name>/{git,builds,funnel}`; fat ≡ server.
- **`nornir viz` mode auto-resolution** — no config → THIN (auto-connect a local server, token from `~/.nornir/token`); config → FAT. `--server <addr>` / `--token`. Tested across every start permutation.
- **Eager `nornir workspace add`** = register + fetch + build inline (server ≡ user; poll loop = the automated re-run).
- **Build stamp** — git SHA + build time in the window title + ℹ About popup, so a fresh client is obvious.
- **viz thin-mode RPCs** — `Viz.ReleaseEvents` · `Viz.BakeoffResults` · `Viz.Knowledge` (server-side scan, verified live). Release/Leaderboard/Knowledge now read over gRPC, not stubs.
- **C8/D3** — facett palette across **every** pane + a 🎨 picker; generic widgets in `facett_ui`.
- **H5** — agent × model **bake-off matrix** + `nornir bakeoff demo` + the 🏆 grid.
- **MCP** — all **56 tools functionally tested** (real in/out) + a coverage gate; **robot-UI MCP** `viz.state`/`viz.click` (drive + observe the viz).
- **N5** viz action trail → warehouse · **P1.3** live Bench pane (cores-busy/N) · **F1** incremental republish (changed ∪ dependents) · **multi-dim viz test** (tab × mode × data).
- LAW 0: warehouse on fast local disk, never T9.
## 0.4.12 — "Yggdrasil" (2026-06-12)
The big viz + warehouse build-out.
- **release_events DAG** in the warehouse + `nornir release events` CLI + viz **Release tab** (op-log + dep-graph lights up per component).
- **Standardized test-matrix**: `nornir test run/all/history` (nextest/cargo) → `test_results` table → viz **🧪 Test pane** (live green/red) + stall-watchdog.
- **Deep transitive DepGraph** (explode + direct/transitive coloring + pan/zoom/click-collapse); dep graphs now render in `.nornir/` design docs.
- **Knowledge** tab: async background scan (5s stall → ~0) + rescan + workspace re-scope.
- **Funnel demo**: `nornir funnel demo/nuke` (fake git repos) + viz demo strip + 16-thread concurrency test.
- **Bench CPU/mem telemetry** → `bench_telemetry` table + live `cores-busy/N`; **interchangeable embedding model** registry.
- **Lock-tolerant catalog reads** (CLI no longer wedges the live server); **MCP 56-tool test harness** (fixed a runtime hang).
- About popup with viz + server versions; egui-native screenshot-proof test harness.
All notable changes to **nornir** are recorded here. This project adheres to
[Semantic Versioning](https://semver.org).
## 0.4.10 — "Gjallarhorn" (2026-06-12)
Heimdall's horn: the bencher now *announces* what it's doing.
### Benchmarks — progress logging
- Each benchmark unit logs a stable, greppable line as it runs:
`bench: <suite>/<case> (mt|st) starting…` then
`bench: <suite>/<case> → <N> ops/s (<elapsed>s)` (or `✗ failed: …`). Both the
multi-core and single-core (`_st`, affinity-pinned) passes are labelled.
- When `NORNIR_VIZ_ACTIONLOG` is set, the bencher **appends** each line to the same
action-log file the viz tails (kind `[BENCH]`), so a running benchmark is watchable
live in the viz LiveRun action-trail — no coupling to the (feature-gated) viz crate.
## 0.4.9 — "Ratatoskr" (2026-06-11)
The messenger-squirrel release: the action log ran up and down the render tree and
surfaced the crashes hiding in it.
### Viz — crash hardening (from the action-log drive)
- **Data/Warehouse tab out-of-bounds panic fixed** — `chart_val`/`chart_label`
column indices persisted across frames; picking a chart column on a wide table
then switching to a narrower one left a stale index that indexed `columns[i]`
out of bounds. Both indices are now clamped to `< ncols` every frame.
- **UTF-8 char-boundary panics fixed** — `short`/`short_sha`/sha-slice helpers sliced
by raw byte index (`&s[..12]`); a multibyte snapshot-id / tag / ref straddling that
boundary panicked. Now truncate on char boundaries.
- **Mutex-poison cascade fixed** — UI-thread `lock().unwrap()` on shared stream/scan
state crashed the whole render if a background thread unwound while holding the lock;
the UI side now recovers via `into_inner()` and degrades gracefully.
- **Topo-sort hardening** — `graph.rs` indeg unwrap → guarded `saturating_sub`, no
usize-underflow panic if the build-order invariant is ever violated.
- **Toolbar tab buttons on two explicit rows** (7 + 6) so the window needn't be wide.
### Time machine
- **Content-addressed blob dedup** in the snapshot/index path — blobs are stored once by
sha256 (content rows) with per-file manifest rows referencing them; restore reconstructs
across snapshots. Shrinks the warehouse when files repeat across releases.
### CI / docs
- Pure-Rust GitHub/Forgejo CI workflow added; doc book regenerated (md + pdf).
## 0.4.8 — "Vörðr" (2026-06-11)
The guardian-spirit follow-up: the watcher now works less + sees more.
### Server / republish
- **Incremental republish** — the knowledge scan skips members whose HEAD SHA is
already recorded (git-SHA idempotency ledger), so a workspace republish driven by
one member moving no longer re-scans the unchanged ones. Safe (no
affected_by_change graph-scoping); first republish full, then incremental.
- **Security SBOM pre-warm on republish** + **SBOM components from the warehouse
deep-scan** (falls back to live cargo-metadata), **path-dep sibling
materialization** (server-side cargo-metadata + licenses for znippy/knut),
content-addressed **blob-dedup** helper, and a **knowledge-scan SHA ledger**.
### CLI parity
- **`nornir mimir <op>`** — all 10 dependency-graph ops (deps-of / dependents-of /
affected / build-order / dep-path / external-users / mermaid / repo-overview /
changed-since-release / security-scan), embedded or via the `Mimir` RPCs.
- **`nornir diagram <renderer>`** — all 8 data-only Mermaid/Markdown renderers.
### Viz
- **Total user-action debug log** — `$NORNIR_VIZ_ACTIONLOG` file + a toggleable
overlay + a per-action stderr trail (`nornir-viz ACTION …`), folded into the
introspection state — every click/query/RPC is traceable headlessly.
- **2-row button layout**; **3D volumetric fog** (atmospheric attenuation) + density slider.
### Bench / tests
- **Single-core `_st` automation** (`NORNIR_BENCH_PIN` / `NORNIR_BENCH_ST`) — the
parallel-speedup denominator, no deps.
- Release-gate **test coverage** (nexus_floor / integration_roundtrip / docs_fresh).
## 0.4.7 — "Heimdall" (2026-06-11)
The watchman release: nornir now guards every workspace it weaves — a pure-Rust,
server-side, airgap-capable security scanner; agent-facing MCP write tools; and a
3D call-graph that finally clusters by community.
### Security
- **`nornir security scan`** — pure-Rust SBOM / vulnerability / license producer.
Emits a CycloneDX 1.5 SBOM. Routes through the server when `NORNIR_SERVER` is
set (`Mimir.SecurityScan` RPC): the server scans its own `git/<repo>`, so the
viz needs no local sources.
- **🛡 Security viz tab** — workspace-driven deep SBOM / vuln / license scan.
Repo buttons come from `Workspaces.Get`, so it works without `--config` and
with no free-text paths.
- **Warehouse-first vuln cache** (`vuln_findings`, Iceberg) keyed by the immutable
`crate@version` with a **7-day TTL** refresh — warm scans are zero-network.
- **Offline / airgap advisory database** — `nornir security update-db` mirrors
rustsec/advisory-db (via gix, pure Rust). The offline matcher is
pre-release-aware (`semver`) and matches OSV exactly; set
`NORNIR_ADVISORY_DB_URL` for a true-airgap mirror. OSV degrades gracefully
offline.
- **Robust dependency resolution** — falls back to `Cargo.lock` when
`cargo metadata` cannot run, so every workspace (incl. path-dep members) scans.
### MCP
- **Multi-workspace** — `workspaces_list` + `workspace_use {name}`: one MCP server
serves every workspace with no restart; `repos_list` falls back to registry
members.
- **Agent write tools** — `workspace_register`, `sync_now` (push-to-rebuild:
force fetch + republish now), and `index_status` (snapshot + per-member
SHA / synced / freshness). These proxy the existing `Workspaces` RPCs, routed by
the active workspace.
### Viz
- **8 data-only diagram renderers** (`src/viz/diagram.rs`) — Mermaid/Markdown
counterparts of the Timeline / DepGraph / Gates / Funnel / Bench / TimeTravel
tabs, rendered headlessly from a `Timeline` (no warehouse open) for docs/CLI.
- **3D CallGraph layouts** — seven selectable layouts: cluster z-shells, 3D force,
topological (call depth), spectral, MDS, centrality (hubs front), and helix.
- **Louvain communities** — the cluster-z layout now groups by Louvain modularity
instead of plain connected components.
- **Edge depth fog** — far call-graph edges recede into the icy background.
- Repos populate from the server (`Workspaces.Get`) in remote mode, so repo-driven
tabs work without `--config`.
### Fixes
- Release lane nodes use the pinned-snapshot timestamp instead of `Utc::now()`,
fixing chronological ordering of the release timeline.
## 0.4.6
- Depends on **skade-katalog** (renamed from nornir-catalog).
## 0.4.5
- skade-backed warehouse + MCP tool-call telemetry + 📞 viz tab.
## 0.4.4
- viz Funnel + Search tabs; nornir-catalog 0.1.4 dependency.