<!-- nornir:class: guide -->
# autonom — the self-discovering completeness gate (design + guide)
> **autonom** is the autonomous, self-discovering test-matrix framework. It turns the C6
> `nornir test` *results recorder* into a PROVABLE completeness *gate*: **green ⟺ every surface
> is wired and works.** Reusable across all nordisk (engine in `nornir-testmatrix`). Forged
> 2026-06-14 with Rickard. Plan: [nordisk_plan.md](nordisk_plan.md) EPIC AUTONOM (AUT1…AUT8).
## 1. The problem it kills
A results matrix only records the tests you *happen* to run, so an **unwired surface ships green**
— e.g. the 🧪 Test tab showed `Test.Results RPC TODO` in thin mode while every test passed, and
Rickard found it by hand. The #1 recurring failure. autonom makes that **impossible**: the matrix
**discovers its own surface from the code** (never a hand-maintained list, which drifts) and fails
the build when any discovered surface has no covering, asserting test.
## 2. The completeness equation
```
Surface = every fn/tab/cmd/tool in {viz(thin+fat), CLI, MCP, core fns} ← discovered, not listed
Covered = every surface reached by an inject-assert test ← from call_edges / tools/list / registry
Gap = Surface − Covered − Allowlist
GATE: Gap == ∅ (else the build/release fails, and the gap is shown in viz + CLI)
```
## 3. Discovery — no hand lists (the anti-drift core)
Generalize the **proven** MCP gate (`tests/support/mcp_harness.rs` already self-discovers 56 tools
over `tools/list` and fails on any uncovered tool — "no silent gaps") to the whole surface:
| surface | enumerator (read via nornir MCP, never grep) |
|---|---|
| facett components | `{T : impl Facet for T}` ∩ `registry()` (the [discovery contract](../../facett/.nornir/component-contract.md)) |
| viz tabs | the tab enum × **{thin, fat}** |
| CLI | clap subcommand introspection |
| MCP tools | `tools/list` *(done — the template)* |
| functions | `symbol_facts − test-reachable-closure(call_edges)` |
## 4. The thin/fat axis (the key the old matrix missed)
Every data surface has **two** entrypoints — `load()` (fat/embedded) and `fetch_*()` (thin/RPC).
**Both** must be covered, and the invariant is **thin == fat parity**: the same warehouse must
produce an identical `state_json` through either path. That single property catches the whole
Test/Bench-tab "RPC TODO" bug class.
## 5. Per-surface precondition — the FC contract
A test can only drive+observe a surface if it's structured for it. That's the
[**FC-1…FC-9 component contract**](../../facett/.nornir/component-contract.md): serializable
`state()`, AccessKit labels on every interactive/painted widget, injected clock (determinism),
explicit `pending` quiescence. FC is what makes autonom's assertions *possible*.
## 6. Assertions
- inject real data + assert `state()`/AccessKit — **never "didn't panic"** (LAW 1).
- **thin == fat parity** (§4).
- **sentinel-ban**: no rendered surface (`state_json` + AccessKit labels) may contain
`TODO` / `not wired` / `not yet` / `coming soon` — any unwired surface outs itself.
- 3 layers: **property** (`proptest` over `update`), **interaction** (`egui_kittest` by label/role),
**perception** (headless PNG, LAW 4).
## 7. Always-visible matrix (AUT1 — shipped 2026-06-14)
The 🧪 Test grid seeds its axes from the **expected surface** — `workspace repos × ASPECT_DEFAULT`
(`Matrix::build_with_expected` + `ASPECT_DEFAULT`, `src/viz/test_matrix_grid.rs`; wired in
`test_tab` via `set_repos`, seeded at construct + workspace switch in `app.rs`). So the matrix is
**populated with "not-run-yet" cells before any run**, not an empty grid. Data-driven (repos from
the workspace, aspects from the standard set) — not a hand list.
## 8. Coverage → warehouse (schema-evolving)
A new `surface_coverage` table (one row per `surface × mode × workspace`, same shape as
`tests/mcp_tool_coverage.json`) added via **iceberg/skade schema evolution** (no history rewrite,
old rows stay valid via serde defaults — exactly how `aspect`/`metric` were added in L2). Enforced
as a release gate (`Gap == ∅`), with a `nornir test coverage` CLI + a viz view + an MCP tool (parity).
## 9. Freshness
The release-blocking gate runs against the **working tree** (always fresh). The thin-client
**staleness badge** uses a working-tree **content digest** (gix, in-process) vs the indexed SHA —
because a commit SHA cannot detect uncommitted edits. `index_status` → `{HEAD sha, dirty digest,
clean|dirty}`.
## 10. Reusable for all nordisk (AUT8)
The engine lives in `nornir-testmatrix` (std+serde+anyhow — lean). Each repo plugs its enumerators
and ships FC-compliant components; nornir hosts the iceberg-backed sink + the symbol-graph/viz/CLI
enumerators. Target: nornir + znippy + skade + facett + korp + knut + holger all run autonom; results
land in the warehouse.
See the dead-simple walkthrough in [autonom-for-idiots.md](autonom-for-idiots.md).