test_history MCP tool + release test-drive gate
Plan #5. Two additions, both built additively (no proto change) so they merge cleanly alongside concurrent proto/viz work:
- a
test_historyMCP tool — read a repo's per-run test summaries; and - a release test-DRIVE gate — a release actually runs the repo's test matrix and fails on red / regression.
Companion of bench_history (the exact template) and the existing test_results / build-free test_inventory machinery (.nornir/test-matrix.md, .nornir/test-matrix-always-visible.md).
1. test_history MCP tool
mcp__nornir__test_history — src/bin/nornir-mcp.rs : NornirMcp::test_history.
Input: { repo : string, limit? : usize }. Output: a JSON array of per-run summaries, newest-first — the SAME per-run rollup nornir test history <repo> prints, one object per run:
[ { "run_id", "repo", "ts_micros",
"passed", "failed", "ignored", "stalled", "skipped", "listed",
"total", "green" }, … ]
green and total are folded in by the canonical serializer so an agent never recomputes them.
Two modes (embedded XOR client), mirroring bench_history
- Embedded (
NORNIR_SERVERunset): open the warehouse read-only (lock-tolerant, likenornir test history),query_test_resultsscoped byRepo(repo)— falling back toRun(repo)when no repo matches (same selector the CLI accepts) — thensummarize_runs+ cap tolimit. - Client (
NORNIR_SERVERset): the server owns the warehouse lock, so the tool pulls everytest_resultsrow over the existingViz.TestResultsRPC and applies the SAME pure scope+summarize (test_history_summaries). No new RPC — reusingViz.TestResultskeeps the change additive.
The scope+summarize+limit logic is pure and shared (test_history_summaries + summarize_runs + runs_to_json), so the two modes return byte-identical JSON (proven in tests).
Canonical serialization
nornir_testmatrix::model::runs_to_json (re-exported as nornir::warehouse::test_results::runs_to_json) renders a &[RunSummary] newest-first with the derived green/total flags. RunSummary gained Serialize/Deserialize for the RPC round-trip.
2. Release test-DRIVE gate (plan #5b)
A release must EXERCISE + check its tests, not just read old results.
release::gate::tests_gate(rows)— the pure verdict over already-collectedtest_resultsrows. Fails if there is no run, the newest run is RED (anyfail/stalled), or a test that was green in the last green run is now red (a regression, named explicitly). Mirrorscoverage_gate.release::gate::run_tests_gate(repo_root, history, aspects)— the heavy drive: it runs the matrix (test_matrix::run_full_matrix, the same engineOps.RunTestMatrixuses), folds the fresh rows with the repo's persisted history, appliestests_gate, and returns the fresh rows so the caller persists them to the warehouse.
Wired into release_gate_all (the MCP embedded path + the server Ops.GateAll handler) behind the per-repo [gates].tests = true toggle. HEAVY — it compiles
- runs the suite — so it is invoked release/on-request only (the gate call), never on workspace load, consistent with the heavy-test policy. The freshly-run rows are persisted (
append_test_results_blocking) so the 🧪 Test pane andtest_historysee the release run.
Tests (inject-assert)
nornir-testmatrixmodel:runs_to_jsonnewest-first + green/total folding (model unit tests).src/release/gate.rs::tests_gate_passes_green_fails_on_red_and_names_regression— green→Ok, no-run→red, red→Err naming the case, regression→Err naming the regressed test, stalled→red.tests/release_gates.rs::tests_gate_green_pass_red_fail_regression_through_public_api(fast, public API) +run_tests_gate_drives_matrix_and_passes_green_fails_red(#[ignore]heavy: scaffolds a passing + a failing cargo crate, drives the matrix end-to-end, asserts green passes / red fails).tests/support/mcp_harness.rsseeds twoholgertest runs (older green, newer red) and exercisestest_history, asserting the newest (red) run leads and carriesgreen:false— covered bymcp_every_tool_exercised_and_asserted(coverage gate goes RED if the tool is ever unexercised).
CLI ⇄ MCP ⇄ viz parity
- CLI:
nornir test history <repo>(+--json) — pre-existing. - MCP:
test_history— this addition. - viz: 🧪 Test tab (
summarize_runs/RunSummary) — pre-existing; the new gate's freshly-run rows flow into it via the warehouse.