Gunnar

nordisk/dwarves

.nornir/aut5-surface-coverage-gate.md

<!-- nornir:class: history -->
# AUT5 — the surface_coverage release-blocking GATE (design + guide)

> The completeness gate, wired into the release pipeline. A release goes **RED** if any
> UI/CLI/MCP/function surface has no covering test row — i.e. `Surface − Covered ≠ ∅` — or if any
> allowlist excuse is stale. HARD zero, not a ratchet. Built 2026-06-15 (branch
> `feat/aut5-coverage-gate`, folds into nornir 0.4.40). Parent epic: [autonom.md](../../nornir/.nornir/autonom.md);
> plan: [nordisk_plan.md](../../nornir/.nornir/nordisk_plan.md) AUT5.

## 1. The verdict equation

```
Surface   = every {viz tab × (thin,fat), CLI subcommand, MCP tool, unreached fn}   ← discovered, never listed
Covered   = surfaces with a covering test row (test-reachable handler / fn closure over call_edges)
Allowlist = .nornir/autonom-allow.toml  (live, reasoned excuses — burn down by deletion)
GATE RED  ⟺  (Surface − Covered − Allowlist ≠ ∅)  OR  (any stale allowlist entry)
```

A *stale* allowlist entry is an excuse whose surface is now covered, or whose surface no longer
exists — the excuse must not outlive its surface, so it re-reds the gate until deleted.

## 2. What AUT5 added (vs what already existed)

The PURE model (`nornir_testmatrix::coverage::{GateReport, CoverageRow, …}`), the Iceberg table
(`src/warehouse/surface_coverage.rs`), the feeder (`src/autonom.rs::{enumerate_surface, gather,
persist, build_surface_and_covered}`), the verdict fn (`release::gate::coverage_gate`), the CLI
`nornir test coverage`, and the MCP tools `test_coverage` / `release_gate_coverage` were already
shipped (AUT2/0.4.38). **What was missing was the release-BLOCKING wiring** — `coverage_gate` was
never called from the gate set the release runs. AUT5 closes exactly that gap:

| piece | file | change |
|---|---|---|
| gate flag | `src/config.rs` | `[gates].coverage: bool` (LIGHT — no suite run, may default-on per repo) |
| wiring helper | `src/autonom.rs` | `gate_coverage_for_repo()` — gather → persist → return `GateState` (one call shared by CLI/server/MCP) |
| CLI gate-all | `src/bin/nornir.rs` `run_gate_all` | runs `coverage` inline when `[gates].coverage` |
| CLI per-gate | `src/bin/nornir.rs` | `ReleaseOp::GateCoverage` + `release gate coverage` + the `Gate{name}` dispatcher (`"coverage"`) |
| server gate-all | `src/bin/nornir-server.rs` `gate_all` RPC | `coverage` arm (spawn_blocking; canonical CLI fallback — no live clap tree) |

The CLI `nornir test coverage`, the MCP `test_coverage`/`release_gate_coverage`, and the viz
*Test pane VIEW* are pre-existing; the viz coverage VIEW work stays a follow-up (AUT5 avoided
`src/viz/` entirely to not collide with the in-flight Architecture tab).

## 3. Red/green conditions (operator's view)

- **GREEN** ⟺ every discovered surface is wired (test-reachable) OR excused by a live
  `.nornir/autonom-allow.toml` entry, and no excuse is stale.
- **RED** when a surface has no covering row and no excuse → the message names the uncovered
  surfaces (`autonom completeness gate RED — N uncovered+un-allowlisted surface(s): …`).
- **RED** when an allowlist entry is stale → the message names the stale keys.

To unblock a release: either wire a real inject-assert test for the surface, or
`nornir test coverage --seed-allowlist` to excuse the *current* gap with a reason, then burn it
down by deleting entries as tests land.

## 4. CLI + MCP parity

| action | CLI | MCP |
|---|---|---|
| report surface vs covered + list gaps | `nornir test coverage [--json]` | `test_coverage` |
| run the release-blocking verdict | `nornir release gate coverage` / `release gate-all` | `release_gate_coverage` |
| re-seed the burn-down allowlist | `nornir test coverage --seed-allowlist` | (CLI only — writes a checked-in file) |

The remote path runs `coverage` through `release gate-all` (the server owns the warehouse), exactly
like the heavy `tests` gate — there is no dedicated per-gate `gate_coverage` RPC.

## 5. Tests (inject-assert)

`src/autonom.rs::aut5_gate_red_when_surface_uncovered_green_when_allowlisted` drives the WIRING
end-to-end over a real Iceberg warehouse:

1. Persist a real knowledge scan: a test root reaches `wired`, but `orphan_fn` is unreached →
   `orphan_fn` is the surface gap.
2. `gate_coverage_for_repo` + `coverage_gate` → assert **RED** and that the message **names
   `orphan_fn`**.
3. Seed the allowlist with every gap → assert **GREEN**.
4. Add a stale entry (a ghost surface) → assert **RED** again, message contains `stale`.

The pure verdict (`coverage_gate_passes_green_fails_on_gap_or_stale`) and the table round-trip
(`append_query_round_trip_exact_verdicts`) remain green. The MCP coverage tools stay covered by
`mcp_every_tool_exercised_and_asserted`.

## 6. Enabling the gate

```toml
# workspace_<name>/release/nornir.toml — per repo
[repo.<name>.gates]
coverage = true   # release-blocking: RED if Surface − Covered ≠ ∅ or a stale excuse
```

It is LIGHT (reads the warehouse `surface_coverage` map; no `cargo build`), so unlike the heavy
`tests` gate it can run on the local CLI path and default-on per repo.