Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

src/release/gate.rs

//! Release gates. Each gate returns `Ok(())` on pass, `Err` on fail.
//! Generated binaries propagate the first error and abort the release.
//!
//! # DE-FORKED 2026-07-22 — dwarves owns the portable gates
//!
//! The 2026-07-14 doctrine (*release is a build op*) relocated the PURE gates to
//! [`nornir_release_core::gate`], but the nornir copies were never deleted —
//! a copied-not-moved fork that drifted for a week in both directions. It is
//! resolved the only legal way: the single implementation lives in dwarves (which
//! absorbed nornir's bug-provenance comments and 9 of its regression tests), and
//! nornir **re-exports** it below so every `release::gate::…` call site — the
//! `nornir-server` `Release.GateAll`/`gate_*` RPCs, the MCP `release_gate_*`
//! tools, the CLI — resolves unchanged against ONE implementation.
//!
//! ## Re-exported from dwarves (nornir owns NO copy)
//! `no_path_patches` · `path_dep_audit`/`PathDepFinding` ·
//! `crate_metadata_check`/`CrateMetaCheck` · `links_declarations_scan`/
//! `detect_links_conflicts`/`LinkDecl`/`LinksConflict` · `nexus_floor` ·
//! `no_regression`/`no_regression_against`/`no_regression_windowed`/`MetricStats`
//! + the window/sigma defaults · `integration_roundtrip`(`_via_cargo_test`) ·
//! `git_tag_matches_published`.
//!
//! ## STAYS in nornir (warehouse/domain-coupled — L5a: the warehouse stays here)
//! * [`semantic_link_gate`] — reads the SCIP semantic blast radius computed from
//!   the Mimir call-graph; the fact source is nornir's warehouse.
//! * [`core_saturation`]/[`core_saturation_run`]/[`is_latency_microbench`] — reason
//!   over `warehouse::iceberg::BenchTelemetryRow`, a nornir warehouse row type, and
//!   the microbench exemption list is a fact about nornir's OWN benches.
//! * [`coverage_gate`]/[`coverage_gate_all_workspaces`]/[`coverage_gate_outcome`]/
//!   [`gate_all_outcome`] — the `autonom` completeness engine nornir owns.
//! * [`tests_gate`]/[`run_tests_gate`] — verdict over warehouse `TestResultRow`s.
//! * [`render_and_stage_docs`] — drives `crate::docs`, nornir's render engine.
//! * [`selected_gates`]/[`GATE_ORDER`] — resolve nornir's `[gates]` config table.
//!
//! No new crate edge: nornir already path-deps `dwarves` (`features = ["release"]`).

use std::path::Path;

use anyhow::{Context, Result, anyhow};

use crate::bench::{BenchRun, history};

/// The PORTABLE release gates — **implemented once, in dwarves**. These were
/// forked here until 2026-07-22; the fork is deleted and this re-export is the
/// only way nornir reaches them, so the two can never drift again.
pub use nornir_release_core::gate::{
    CrateMetaCheck, DEFAULT_REGRESSION_SIGMA, DEFAULT_REGRESSION_WINDOW, LinkDecl, LinksConflict,
    MetricStats, PathDepFinding, crate_metadata_check, detect_links_conflicts,
    git_tag_matches_published, integration_roundtrip, integration_roundtrip_via_cargo_test,
    links_declarations_scan, nexus_floor, no_path_patches, no_regression, no_regression_against,
    no_regression_windowed, path_dep_audit,
};

/// Release GATE — the **SCIP semantic LINK check** (EPIC item 7). A version bump
/// that REMOVES a symbol a dependent still calls is a pre-release *link error*: once
/// the crate ships, every such dependent fails to compile/link on that undefined
/// symbol, exactly as a linker fails on an undefined reference. This gate reads the
/// semantic blast radius already computed from the warehouse call-graph
/// ([`crate::release::doctor::run_with_semantic_blast`] →
/// [`nornir_release_core::semantic_blast::SemanticBlast`]) and FAILS the release when any
/// bumped repo carries a link error — the doctor refuses to publish a tree that would
/// not link, just as it refuses a dependency cycle or a fork rider.
///
/// A merely *Changed* symbol (still resolves, may behave differently) is a WARNING,
/// not a failure — it does not block the gate. An empty blast (no dependent calls the
/// changed surface) passes trivially, so a bump with no semantic input never blocks.
pub fn semantic_link_gate(
    blast: &std::collections::BTreeMap<String, nornir_release_core::semantic_blast::SemanticBlast>,
) -> Result<()> {
    let mut lines: Vec<String> = Vec::new();
    let mut total_sites = 0usize;
    for sb in blast.values() {
        let errs = sb.link_errors();
        if errs.is_empty() {
            continue;
        }
        for imp in errs {
            total_sites += imp.call_sites.len();
            lines.push(format!(
                "  🔗 LINK ERROR: {}::{} was removed but is called by {} site(s):",
                sb.target_repo,
                imp.symbol,
                imp.call_sites.len()
            ));
            for site in &imp.call_sites {
                lines.push(format!(
                    "        {}::{} — {}:{}",
                    site.crate_name, site.caller, site.file, site.line
                ));
            }
        }
    }
    if lines.is_empty() {
        return Ok(());
    }
    Err(anyhow!(
        "release doctor: pre-release LINK ERROR — {} call site(s) invoke {} removed symbol(s); \
         publishing would break the dependents (a linker would fail the same way). \
         Restore the symbol(s), deprecate instead of removing, or update the call sites first.\n{}",
        total_sites,
        lines.iter().filter(|l| l.contains("LINK ERROR")).count(),
        lines.join("\n"),
    ))
}

// `metric_lower_is_better` (the latency-vs-throughput direction classifier) went to
// dwarves with the `no_regression` family it serves — it is crate-private there and
// nornir has no other caller.

/// The CORE-SATURATION LAW as a fraction: a parallel hot path must keep at
/// least this share of the machine's cores busy at peak. The law is "~11/12
/// cores" — 0.9 is the release floor below which under-use is a bug, not
/// "Amdahl-fine". Callers may override via the per-repo `min_core_busy_frac`.
pub const CORE_SATURATION_MIN_BUSY_FRAC: f64 = 0.9;

/// Single-thread bench-variant suffix (holger/ljar `_st`) — exempt from the
/// core-saturation gate: a deliberately single-threaded variant is *supposed*
/// to use one core. Re-exported from [`crate::bench`] so the gate and the
/// mashup docs share ONE notion of "the `_st` suffix marks a single-thread
/// variant of the same system" (the gate exempts it; the mashup keeps it a
/// distinct row).
use crate::bench::is_single_thread;

/// nornir's own **latency / IO microbenches** — deliberately serial arms that are
/// NOT parallel-compute and so, like an `_st` variant, are EXEMPT from the
/// core-saturation LAW (one busy core is correct for them, not an under-use bug):
///
/// * `nornir.dep_graph_build` / `_ext` — one `cargo metadata` spawn per repo
///   (I/O + subprocess bound) plus a warm in-memory closure-query battery. Their
///   stable, authoritative metric is `queries_per_sec` (~600K, run-to-run flat);
///   `build_ms` is host-contention wall-time that swings ~10× and must NOT be
///   read as a compute regression, nor the arm as multi-threaded (the `(mt)`
///   impression the bencher flagged as O1 does not apply).
/// * `nornir.delta_rescan` — a single-file re-parse latency measurement; its
///   headline is `speedup_vs_full`, not a core count.
///
/// The genuinely parallel nornir arms — `knowledge_scan` (all-core `syn` parse),
/// and the warehouse throughput arms — are NOT listed here and stay subject to
/// the LAW. Keeping this list in nornir (not the shared edda `_st` helper) means
/// no cross-repo bump is needed and the exemption is unit-tested here.
pub fn is_latency_microbench(bench: &str) -> bool {
    matches!(
        bench,
        "nornir.dep_graph_build" | "nornir.dep_graph_build_ext" | "nornir.delta_rescan"
    )
}

/// **Warehouse-gate: the core-saturation LAW.** Fail the release when a
/// PARALLEL bench under-used the machine's cores — peak occupancy
/// (`proc_cores_max`) below `min_busy_frac × n_cores`. The examples the law
/// names (`lbz2` 8/12, `lgz` 2.7/12) are bugs to fix, never excused as
/// "Amdahl-fine"; this gate makes that enforceable instead of display-only.
///
/// ## Why this reads `proc_cores_max` and not `cores_busy_max`
///
/// Until 2026-08-03 it read `cores_busy_max`, which comes from `/proc/stat` —
/// **the whole host**. Measured on oden (32 cores) that day: the strictly
/// single-threaded arm `gunnar.walk_1m` reported `cores_busy_max` 32/32 and
/// 99.85–99.95% CPU while the benchmark process occupied **0.56 of one core**.
/// The neighbours' load was being read as the bench's own parallelism, so this
/// gate PASSED — on an arm with no parallelism at all, and would have passed
/// forever on any shared box. A guard that cannot report red is worse than no
/// guard, because it is counted as coverage.
///
/// `proc_cores_max` is task-clock ÷ wall from `/proc/self/stat`, which is
/// host-independent. Cross-check from the same measurement:
/// `gunnar.clone_concurrent_64` genuinely occupies 10.99–12.11 of 32 cores
/// (0.37) where the old column claimed 0.94.
///
/// **What the number excludes.** It counts this process's threads plus children
/// **reaped** during the unit. An arm whose work lives in a still-running child
/// (a spawned server left up) under-reports. That shape is a bench bug of its
/// own; it is not silently absorbed here — see `child_cpu_ms`.
///
/// `telemetry` is the `bench_telemetry` rows for the run under test (caller
/// filters to the relevant `run_id`/repo). EXEMPT rows: single-thread variants
/// (`_st` suffix — deliberately serial), nornir's latency microbenches, and
/// degenerate machines (`n_cores <= 1`). Rows with `proc_cores_max == None` are
/// rows written before the measurement existed: they are UNJUDGEABLE, not
/// passing, and are reported as skipped rather than counted as green. A
/// `min_busy_frac` of `0.0` falls back to [`CORE_SATURATION_MIN_BUSY_FRAC`].
pub fn core_saturation(
    telemetry: &[crate::warehouse::iceberg::BenchTelemetryRow],
    min_busy_frac: f64,
) -> Result<()> {
    let frac = if min_busy_frac > 0.0 {
        min_busy_frac
    } else {
        CORE_SATURATION_MIN_BUSY_FRAC
    };
    let mut violations: Vec<String> = Vec::new();
    let mut unmeasured: Vec<&str> = Vec::new();
    for t in telemetry {
        // Exempt deliberately-serial variants (`_st`), nornir's own latency/IO
        // microbenches (dep_graph_build/_ext, delta_rescan — never
        // parallel-compute), and degenerate single-core hosts.
        if is_single_thread(&t.bench) || is_latency_microbench(&t.bench) || t.n_cores <= 1 {
            continue;
        }
        // NOT `unwrap_or(0.0)`: a pre-2026-08-03 row has no process-scoped
        // measurement, and defaulting it to zero would manufacture a RED that
        // no consumer could tell from a real under-use.
        let Some(peak) = t.proc_cores_max else {
            unmeasured.push(&t.bench);
            continue;
        };
        let need = frac * t.n_cores as f64;
        if peak < need {
            violations.push(format!(
                "{} peaked {:.2}/{} cores ({:.0}% < {:.0}% floor)",
                t.bench,
                peak,
                t.n_cores,
                peak / t.n_cores as f64 * 100.0,
                frac * 100.0,
            ));
        }
    }
    if !unmeasured.is_empty() {
        // Loud, not silent: a skipped row is a row this gate did not check, and
        // an operator reading "green" deserves to know how much of the run that
        // verdict actually covered.
        eprintln!(
            "core-saturation: {} row(s) SKIPPED — no process-scoped CPU telemetry \
             (written before proc_cores_max existed; re-run the bench to judge them): {}",
            unmeasured.len(),
            unmeasured.join(", "),
        );
    }
    if violations.is_empty() {
        Ok(())
    } else {
        Err(anyhow!(
            "core-saturation gate RED — {} parallel bench(es) under-used the cores \
             (the ~11/12 LAW; fix the saturation, don't excuse it as Amdahl-fine): {}",
            violations.len(),
            violations.join("; "),
        ))
    }
}

/// Run-facing form of [`core_saturation`]: check the LAW against a
/// just-executed [`BenchRun`] IN MEMORY, before it is persisted. Each result's
/// CPU/mem footprint is lifted from its `telem_*` metrics
/// ([`crate::bench::telemetry::from_metrics`]) — the SAME extraction the
/// warehouse's telemetry table uses — so the gate can veto a red run *before*
/// [`crate::warehouse::iceberg::IcebergWarehouse::append_bench_run`] makes it a
/// baseline. Results without a telemetry footprint are skipped.
pub fn core_saturation_run(run: &BenchRun, min_busy_frac: f64) -> Result<()> {
    let rows: Vec<crate::warehouse::iceberg::BenchTelemetryRow> = run
        .results
        .iter()
        .filter_map(|r| {
            let t = crate::bench::telemetry::from_metrics(&r.metrics)?;
            Some(crate::warehouse::iceberg::BenchTelemetryRow {
                run_id: String::new(),
                repo: String::new(),
                bench: r.name.clone(),
                n_cores: t.n_cores,
                cpu_pct_avg: t.cpu_pct_avg,
                cpu_pct_max: t.cpu_pct_max,
                cores_busy_avg: t.cores_busy_avg,
                cores_busy_max: t.cores_busy_max,
                mem_peak_mb: t.mem_peak_mb,
                mem_pct_max: t.mem_pct_max,
                elapsed_ms: t.elapsed_ms,
                proc_cpu_ms: t.proc_cpu_ms,
                child_cpu_ms: t.child_cpu_ms,
                proc_cores_avg: t.proc_cores_avg,
                proc_cores_max: t.proc_cores_max,
            })
        })
        .collect();
    core_saturation(&rows, min_busy_frac)
}

// ─── Cargo-pipeline gates (features 1, 4, 5, 8, 16) ─────────────────

/// Gate (autonom n-005): the completeness gate must be GREEN — every discovered
/// surface (viz tabs × {thin,fat}, CLI subcommands, MCP tools, unreached fns)
/// is covered by an inject-assert test or excused by a LIVE allowlist entry, and
/// no allowlist entry is stale. Takes the already-computed [`GateReport`] (the
/// caller has the warehouse + discrete inputs); this is the pure verdict gate.
///
/// HARD zero (not a ratchet): fails if `Gap ≠ ∅` OR any stale allowlist entry.
pub fn coverage_gate(report: &nornir_testmatrix::coverage::GateReport) -> Result<()> {
    if report.is_green() {
        return Ok(());
    }
    let mut parts: Vec<String> = Vec::new();
    if !report.gap.missing.is_empty() {
        let keys: Vec<&str> = report.gap.missing.iter().map(|n| n.id.as_str()).collect();
        parts.push(format!(
            "{} uncovered+un-allowlisted surface(s): {}",
            report.gap.missing.len(),
            keys.join(", ")
        ));
        // Bencher-coverage gate ROW (Task #39): call out hot-paths that ship with
        // NO bencher as their own line, so the perf-coverage gap reads distinctly
        // from a stale UI/CLI surface (same allowlist/burn-down mechanism).
        let no_bencher: Vec<&str> = report
            .gap
            .missing
            .iter()
            .filter(|n| n.kind == nornir_testmatrix::discover::SurfaceKind::Bencher)
            .map(|n| n.id.as_str())
            .collect();
        if !no_bencher.is_empty() {
            parts.push(format!(
                "{} hot-path(s) with NO bencher: {}",
                no_bencher.len(),
                no_bencher.join(", ")
            ));
        }
    }
    if !report.stale.is_empty() {
        let keys: Vec<&str> = report.stale.iter().map(|e| e.key.as_str()).collect();
        parts.push(format!(
            "{} stale allowlist entr(ies): {}",
            report.stale.len(),
            keys.join(", ")
        ));
    }
    Err(anyhow!(
        "autonom completeness gate RED — {}",
        parts.join("; ")
    ))
}

/// Gate (autonom S6): the CROSS-WORKSPACE completeness gate must be GREEN — every
/// served workspace's per-workspace [`coverage_gate`] passes AND (where measured)
/// every RESOLVED metro line / reachable surface is green. Mirrors
/// [`coverage_gate`] but over a [`MegaGateReport`](crate::autonom::megagate::MegaGateReport):
/// it fails on the FIRST non-green workspace, naming `(workspace, key)` so the
/// burn-down points at the exact offending surface in the exact workspace.
///
/// HARD zero per workspace (not a ratchet): an uncovered+un-allowlisted surface,
/// a stale allowlist entry, OR an unmet resolved-metro rollup in ANY workspace
/// reds the whole release. Pure verdict over the already-gathered report (the
/// caller swept the warehouse via `gather_all`); this never reads the warehouse.
pub fn coverage_gate_all_workspaces(
    report: &crate::autonom::megagate::MegaGateReport,
) -> Result<()> {
    for wg in &report.workspaces {
        // The per-workspace coverage gate — reuse the single-workspace verdict so
        // the message body (uncovered / stale keys) is identical, just prefixed.
        if let Err(e) = coverage_gate(&wg.coverage) {
            return Err(anyhow!(
                "mega completeness gate RED — workspace `{}`: {}",
                wg.workspace,
                e
            ));
        }
        // The reachable (resolved metro / LAW-9) rollup, when measured: every
        // resolved line must be green. Unresolved bin→lib lines are excluded
        // upstream (allowlistable until scip is populated — S6 step 4 risk note).
        if let Some((green, total)) = wg.reachable {
            if green != total {
                return Err(anyhow!(
                    "mega completeness gate RED — workspace `{}`: only {}/{} resolved metro \
                     lines green (an unlit gating stop on a resolved chain)",
                    wg.workspace,
                    green,
                    total
                ));
            }
        }
    }
    Ok(())
}

/// `release gate coverage <repo>` rendered as the uniform
/// [`crate::cli_outcome::CommandOutcome`] — the CLI's `state_json` for the autonom
/// completeness verdict. Pure SHAPER over the already-computed [`GateReport`] (the
/// caller gathered the surface + covered set via `gate_coverage_for_repo`); it never
/// reads the warehouse.
///
/// GATE ok-semantics: `ok ⟺ the gate PASSED` (`report.is_green()` — `Gap == ∅` and no
/// stale allowlist entry). A FAILING gate is `ok:false` with the uncovered surface +
/// stale entries surfaced in `data` (machine-readable, the actionable burn-down) — that
/// is the CORRECT verdict, not a process error. Both verdicts carry real `data`; only the
/// passing one is `is_sannr` (a red gate is a red surface, by the RAGNARÖK rule). AUT9.
pub fn coverage_gate_outcome(
    repo: &str,
    report: &nornir_testmatrix::coverage::GateReport,
) -> crate::cli_outcome::CommandOutcome {
    use crate::cli_outcome::CommandOutcome;
    let cmd = "release gate coverage";
    // Empty-is-green guard (bug #14): a report covering ZERO behaviours
    // (`total == 0`) has an empty gap and would read green — that is not a pass.
    let passed = report.is_green() && report.gap.total > 0;
    let missing: Vec<&str> = report.gap.missing.iter().map(|n| n.id.as_str()).collect();
    let stale: Vec<&str> = report.stale.iter().map(|e| e.key.as_str()).collect();
    // Bencher-coverage gate ROW (Task #39): the hot-paths shipping with NO bencher,
    // broken out so the perf-coverage dimension is machine-readable on its own.
    let bencher_missing: Vec<&str> = report
        .gap
        .missing
        .iter()
        .filter(|n| n.kind == nornir_testmatrix::discover::SurfaceKind::Bencher)
        .map(|n| n.id.as_str())
        .collect();
    let data = serde_json::json!({
        "repo": repo,
        "gate": "coverage",
        "passed": passed,
        "covered": report.gap.covered,
        "total": report.gap.total,
        "missing_count": report.gap.missing.len(),
        "stale_count": report.stale.len(),
        "missing": missing,
        "stale": stale,
        "bencher_missing_count": bencher_missing.len(),
        "bencher_missing": bencher_missing,
    });
    let human = format!("release gate coverage `{repo}`: {}", report.summary());
    CommandOutcome {
        command: cmd.into(),
        ok: passed,
        data,
        human,
    }
}

/// `release gate-all <repo>` rendered as the uniform
/// [`crate::cli_outcome::CommandOutcome`] — the CLI's `state_json` for the full
/// per-repo gate sweep. Pure SHAPER over the already-run `passed` / `failed`
/// verdicts (the caller ran every configured gate); it runs no gate itself.
///
/// GATE ok-semantics: `ok ⟺ EVERY enabled gate passed` (`failed` is empty). A run with any
/// failing gate is `ok:false`, with the failed gate names + their error messages in `data`
/// (and `human`) — the correct red verdict, not a process error. The passing verdict (with
/// ≥1 gate run) is `is_sannr`; the failing one is RED. AUT9.
pub fn gate_all_outcome(
    repo: &str,
    passed: &[String],
    failed: &[(String, String)],
) -> crate::cli_outcome::CommandOutcome {
    use crate::cli_outcome::CommandOutcome;
    use std::fmt::Write;
    let cmd = "release gate-all";
    let gate_count = passed.len() + failed.len();
    // Empty-is-green violates RAGNARÖK (bug #14): a repo with NO configured gates
    // (Gates derives Default with every flag false) ran zero gates — that is NOT a
    // pass. Require at least one gate to have run AND none failed.
    let all_pass = failed.is_empty() && gate_count > 0;
    let failed_json: Vec<serde_json::Value> = failed
        .iter()
        .map(|(n, e)| serde_json::json!({ "name": n, "error": e }))
        .collect();
    let data = serde_json::json!({
        "repo": repo,
        "passed": passed,
        "failed": failed_json,
        "passed_count": passed.len(),
        "failed_count": failed.len(),
        "gate_count": gate_count,
        "all_pass": all_pass,
    });
    let mut human = format!("=== gate-all: {repo} ===\n");
    for n in passed {
        let _ = writeln!(human, "  ✓ {n}");
    }
    for (n, e) in failed {
        let _ = writeln!(human, "  ✗ {n}: {e}");
    }
    if gate_count == 0 {
        let _ = write!(human, "NO gates configured — empty is not green");
    } else if all_pass {
        let _ = write!(human, "{} gate(s) passed", passed.len());
    } else {
        let _ = write!(human, "{} gate(s) failed", failed.len());
    }
    CommandOutcome {
        command: cmd.into(),
        ok: all_pass,
        data,
        human,
    }
}

/// Release gate (plan #5): the repo's **test matrix** must be green. Takes the
/// repo's `test_results` rows (the SAME rows `nornir test history`/`Ops.
/// RunTestMatrix` produce + persist), rolls them into per-run summaries
/// (newest-first via [`summarize_runs`]), and fails the release if:
///
///   * there is NO recorded run (a release must have exercised its tests), or
///   * the newest run is RED (any `fail`/`stalled` case), or
///   * a REGRESSION vs the last green run: a test that passed in the last green
///     run is failing/stalled in the newest run (caught even if the newest run
///     also added brand-new failures — the regression is named explicitly).
///
/// Pure over already-collected rows so it's unit-testable and identical whether
/// the rows came from an embedded warehouse read or `Ops.RunTestMatrix`. The
/// heavy part (actually RUNNING the matrix) is the caller's job — this is the
/// verdict gate, mirroring `coverage_gate`.
pub fn tests_gate(rows: &[crate::warehouse::test_results::TestResultRow]) -> Result<()> {
    use crate::warehouse::test_results::{status, summarize_runs};
    let summaries = summarize_runs(rows);
    let newest = summaries.first().ok_or_else(|| {
        anyhow!("release tests gate RED — no test run recorded; run the matrix first")
    })?;

    // Names of the newest run's red (failing/stalled) cases, for the message.
    let red_cases: Vec<String> = rows
        .iter()
        .filter(|r| r.run_id == newest.run_id && status::is_red(&r.status))
        .map(|r| format!("{}::{}", r.suite, r.test_name))
        .collect();

    // Regression check: a test green in the last green run, red in the newest.
    let regressions: Vec<String> =
        if let Some(last_green) = summaries.iter().skip(1).find(|s| s.green()) {
            use std::collections::BTreeSet;
            let was_green: BTreeSet<(&str, &str)> = rows
                .iter()
                .filter(|r| r.run_id == last_green.run_id && status::is_green(&r.status))
                .map(|r| (r.suite.as_str(), r.test_name.as_str()))
                .collect();
            rows.iter()
                .filter(|r| r.run_id == newest.run_id && status::is_red(&r.status))
                .filter(|r| was_green.contains(&(r.suite.as_str(), r.test_name.as_str())))
                .map(|r| format!("{}::{}", r.suite, r.test_name))
                .collect()
        } else {
            Vec::new()
        };

    if newest.green() {
        return Ok(());
    }
    let mut msg = format!(
        "release tests gate RED — newest run {} has {} failing/stalled case(s)",
        crate::warehouse::test_results::short_run(&newest.run_id),
        newest.failed + newest.stalled,
    );
    if !red_cases.is_empty() {
        msg.push_str(&format!(": {}", red_cases.join(", ")));
    }
    if !regressions.is_empty() {
        msg.push_str(&format!(
            "; REGRESSION (was green last green run): {}",
            regressions.join(", ")
        ));
    }
    Err(anyhow!(msg))
}

/// Plan #5 release test-DRIVE: actually RUN the repo's test matrix
/// (`run_full_matrix`) over `repo_root`, fold the fresh rows together with the
/// repo's persisted `history` rows, and apply [`tests_gate`]. The freshly-run
/// rows carry a newer `run_id`/`ts` than any history row (the matrix stamps a
/// new run id + `now()`), so they always lead in the newest-first summary — the
/// gate verdicts the run we just drove, and uses the history to catch a
/// regression vs the last green run.
///
/// HEAVY: this compiles + runs the suite. It is invoked release/on-request only
/// (the `[gates].tests` toggle), never on workspace load — same policy as the
/// rest of the heavy matrix. Returns the freshly-run rows on success so the
/// caller can persist them to the warehouse.
pub fn run_tests_gate(
    repo_root: &Path,
    history: &[crate::warehouse::test_results::TestResultRow],
    aspects: &[crate::test_matrix::Aspect],
) -> Result<Vec<crate::warehouse::test_results::TestResultRow>> {
    let fresh = crate::test_matrix::run_full_matrix(repo_root, aspects);
    if fresh.is_empty() {
        return Err(anyhow!(
            "release tests gate RED — the matrix produced no rows for {} (nothing ran)",
            repo_root.display()
        ));
    }
    // History first, fresh last — summarize_runs sorts newest-first by ts, so
    // the just-run rows lead regardless of input order; this is belt-and-braces.
    let mut all = history.to_vec();
    all.extend(fresh.iter().cloned());
    tests_gate(&all)?;
    Ok(fresh)
}

/// Outcome of [`render_and_stage_docs`].
#[derive(Debug, Default)]
pub struct RenderStageReport {
    /// Repo-relative paths that were (re)rendered AND `git add`-ed (only the ones
    /// that actually changed on disk).
    pub staged: Vec<std::path::PathBuf>,
    /// `true` if the whole-documentation book PDF was (re)built + staged.
    pub book_built: bool,
}

/// Release **render-and-stage** (the authoritative docs backstop).
///
/// Unlike the [`docs_fresh`](crate::config::Gates::docs_fresh) gate — which only
/// *checks* for drift and fails the release — this **renders** the managed docs
/// (README/CHANGELOG, with the full SVG/depgraph assets), fills the in-place
/// `.nornir/*.md` markers, builds the book PDF (under `docs-export`), and
/// `git add`s every artifact that changed. Running it *before* the `docs_fresh`
/// check guarantees a release always ships fresh docs and the subsequent check
/// passes (any remaining drift is a real bug, not just staleness).
///
/// Wire it where the other gates run; it is opt-in per repo via the same
/// `[repo.<name>.gates].docs_fresh` flag (rendering is the active half of the
/// freshness contract). The caller supplies a fully-built [`docs::Ctx`] (it owns
/// warehouse access — gates must not open the warehouse themselves).
///
/// Best-effort `git add`: a path that can't be staged (e.g. not a git repo) is
/// reported but does not fail the release — the `docs_fresh` check that follows
/// is the hard assertion.
pub fn render_and_stage_docs(
    layout: &crate::docs::RepoLayout,
    ctx: &crate::docs::Ctx,
) -> Result<RenderStageReport> {
    let mut report = RenderStageReport::default();
    let repo_root = layout.repo_root.clone();

    // What this repo asks `render` to emit (`.nornir/docs.toml`; default markdown).
    let cfg = crate::docs::DocsRenderCfg::load(layout)?;

    let mut changed: Vec<std::path::PathBuf> = Vec::new();

    if cfg.wants_markdown() {
        // Managed docs (README/CHANGELOG) → repo root, with full assets.
        for r in crate::docs::render_all(layout, ctx)? {
            if r.changed {
                changed.push(r.output.clone());
            }
        }
        // In-place marker fills for the non-managed `.nornir/*.md` sources.
        for r in crate::docs::render_sources_in_place(layout, ctx)? {
            if r.changed {
                changed.push(r.path.clone());
            }
        }
    }

    // The generated SVG/depgraph assets are tracked; stage the whole assets dir
    // so a freshly (re)rendered chart/depgraph rides along with the commit.
    let assets = layout.nornir_dir().join("assets");
    if assets.exists() {
        changed.push(assets);
    }

    // Whole-documentation book PDF (authoritative, heavy — release-only).
    #[cfg(feature = "docs-export")]
    if cfg.wants_pdf() {
        let format = crate::docs::DocFormat::parse("pdf")?;
        let (bytes, _sources) = crate::docs::build_book(&repo_root, ctx, format)?;
        let out = layout.export_path("book", format.extension());
        if let Some(parent) = out.parent() {
            std::fs::create_dir_all(parent)?;
        }
        let prev = std::fs::read(&out).ok();
        if prev.as_deref() != Some(bytes.as_slice()) {
            std::fs::write(&out, &bytes).with_context(|| format!("write {}", out.display()))?;
        }
        changed.push(out);
        report.book_built = true;
    }
    #[cfg(not(feature = "docs-export"))]
    let _ = &repo_root;

    // Stage everything that changed (best-effort) — pure-gix `git add` via the
    // `nornir-git` leaf (no subprocess). `stage_paths` takes repo-relative paths.
    for path in changed {
        if !path.exists() {
            continue;
        }
        let rel = path.strip_prefix(&repo_root).unwrap_or(&path).to_path_buf();
        let rel_str = rel.to_string_lossy().replace('\\', "/");
        let staged = crate::gitops::stage_paths(&repo_root, &[rel_str.as_str()])
            .map(|s| !s.is_empty())
            .unwrap_or(false);
        if staged {
            report.staged.push(rel);
        }
    }
    Ok(report)
}

#[cfg(test)]
mod render_stage_tests {
    use super::*;
    use tempfile::TempDir;

    fn git_init(root: &Path) {
        let ok = std::process::Command::new("git")
            .arg("-C")
            .arg(root)
            .arg("init")
            .status()
            .map(|s| s.success())
            .unwrap_or(false);
        assert!(ok, "git init failed (is git installed?)");
    }

    fn staged_files(root: &Path) -> Vec<String> {
        let out = std::process::Command::new("git")
            .arg("-C")
            .arg(root)
            .args(["diff", "--cached", "--name-only"])
            .output()
            .unwrap();
        String::from_utf8_lossy(&out.stdout)
            .lines()
            .map(|s| s.to_string())
            .collect()
    }

    /// render_and_stage_docs RENDERS the managed README from its `.nornir/`
    /// source, leaves NO drift (the `docs_fresh` check passes after), AND
    /// `git add`s the regenerated artifact. Inject a known source body; assert
    /// the rendered output carries it + the GENERATED header + is staged.
    #[test]
    fn renders_stages_and_leaves_no_drift() {
        let t = TempDir::new().unwrap();
        let root = t.path();
        git_init(root);

        let layout = crate::docs::RepoLayout::new(root);
        std::fs::create_dir_all(layout.nornir_dir()).unwrap();
        // Inject a known source with a unique sentinel.
        let sentinel = "AUTOTRIGGER-RENDER-SENTINEL-42";
        std::fs::write(
            layout.source_of("README.md"),
            format!("# Title\n\n{sentinel}\n"),
        )
        .unwrap();

        let ctx = crate::docs::Ctx::new(root, root, None)
            .with_path_dep_cloner(&crate::security::docs_path_dep_cloner);

        // Precondition: the artifact is stale (absent) ⇒ the check would FAIL.
        assert!(
            crate::docs::render_check_all(&layout, &ctx).is_err(),
            "before render, docs_fresh check must fail on the missing artifact"
        );

        // Render + stage.
        let report = render_and_stage_docs(&layout, &ctx).unwrap();

        // The README was rendered with the injected sentinel + the header.
        let rendered = std::fs::read_to_string(layout.output_of("README.md")).unwrap();
        assert!(
            rendered.contains(sentinel),
            "rendered README must carry the injected body"
        );
        assert!(
            rendered.starts_with(crate::docs::layout::GENERATED_HEADER_PREFIX),
            "rendered README must carry the GENERATED header"
        );

        // It was reported as staged AND is actually in the git index.
        assert!(
            report.staged.iter().any(|p| p.ends_with("README.md")),
            "README.md should be reported staged; got {:?}",
            report.staged
        );
        assert!(
            staged_files(root).iter().any(|f| f == "README.md"),
            "README.md must be in the git index"
        );

        // Postcondition: NO drift — the docs_fresh check now passes.
        crate::docs::render_check_all(&layout, &ctx)
            .expect("after render+stage, docs_fresh check must pass (no drift)");
    }
}

#[cfg(test)]
mod cargo_pipeline_tests {
    use super::*;

    #[test]
    fn coverage_gate_passes_green_fails_on_gap_or_stale() {
        use nornir_testmatrix::coverage::{AllowEntry, Allowlist, GateReport};
        use nornir_testmatrix::discover::{Surface, cli_commands};
        use std::collections::BTreeSet;

        let mut surface = Surface::new();
        surface.extend(cli_commands(["a", "b"])); // 2 NA nodes
        let all: BTreeSet<String> = ["cli_command:a@na", "cli_command:b@na"]
            .iter()
            .map(|s| s.to_string())
            .collect();

        // All covered + clean allowlist → green → Ok.
        let green = GateReport::compute("r", "ws", &surface, &all, &Allowlist::new());
        assert!(coverage_gate(&green).is_ok());

        // One uncovered, un-allowlisted → gap → Err.
        let covered_one: BTreeSet<String> = ["cli_command:a@na".to_string()].into_iter().collect();
        let red = GateReport::compute("r", "ws", &surface, &covered_one, &Allowlist::new());
        let err = coverage_gate(&red).unwrap_err().to_string();
        assert!(err.contains("uncovered"), "{err}");
        assert!(err.contains("b"), "names the missing surface: {err}");

        // Stale allowlist entry (all covered, but an excuse lingers) → Err.
        let stale_al = Allowlist {
            entries: vec![AllowEntry {
                key: "cli_command:a@na".into(),
                reason: "old".into(),
            }],
        };
        let stale = GateReport::compute("r", "ws", &surface, &all, &stale_al);
        let err2 = coverage_gate(&stale).unwrap_err().to_string();
        assert!(err2.contains("stale"), "{err2}");
    }

    #[test]
    fn coverage_gate_outcome_ok_when_green_red_with_gap_data_when_failing() {
        use nornir_testmatrix::coverage::{AllowEntry, Allowlist, GateReport};
        use nornir_testmatrix::discover::{Surface, cli_commands};
        use std::collections::BTreeSet;

        let mut surface = Surface::new();
        surface.extend(cli_commands(["a", "b"]));
        let all: BTreeSet<String> = ["cli_command:a@na", "cli_command:b@na"]
            .iter()
            .map(|s| s.to_string())
            .collect();

        // GREEN gate → ok=true, sannr, real counts in data.
        let green = GateReport::compute("r", "ws", &surface, &all, &Allowlist::new());
        let og = coverage_gate_outcome("nornir", &green);
        assert!(og.ok, "a passing gate is ok");
        assert!(og.is_sannr(), "a passing gate carries real verdict data");
        assert_eq!(og.command, "release gate coverage");
        assert_eq!(og.data["passed"], serde_json::json!(true));
        assert_eq!(og.data["missing_count"], serde_json::json!(0));
        assert_eq!(og.data["total"], serde_json::json!(2));

        // FAILING gate (one uncovered) → ok=false (RED), BUT the missing surface is in data.
        let covered_one: BTreeSet<String> = ["cli_command:a@na".to_string()].into_iter().collect();
        let red = GateReport::compute("r", "ws", &surface, &covered_one, &Allowlist::new());
        let orr = coverage_gate_outcome("nornir", &red);
        assert!(
            !orr.ok,
            "a failing gate is ok:false (the correct red verdict)"
        );
        assert!(!orr.is_sannr(), "a red gate is a red surface");
        assert_eq!(orr.data["passed"], serde_json::json!(false));
        assert_eq!(orr.data["missing_count"], serde_json::json!(1));
        assert_eq!(orr.data["missing"], serde_json::json!(["b"]));
        assert!(orr.human.contains("RED"));

        // Stale allowlist entry → also a failing gate, named in data.
        let stale_al = Allowlist {
            entries: vec![AllowEntry {
                key: "cli_command:a@na".into(),
                reason: "old".into(),
            }],
        };
        let stale = GateReport::compute("r", "ws", &surface, &all, &stale_al);
        let os = coverage_gate_outcome("nornir", &stale);
        assert!(!os.ok);
        assert_eq!(os.data["stale_count"], serde_json::json!(1));
        assert_eq!(os.data["stale"], serde_json::json!(["cli_command:a@na"]));
    }

    #[test]
    fn gate_all_outcome_ok_when_all_pass_red_with_failure_data() {
        // All gates passed → ok=true, sannr, the passed names in data.
        let passed = vec!["no_path_patches".to_string(), "nexus_floor".to_string()];
        let o = gate_all_outcome("nornir", &passed, &[]);
        assert!(o.ok, "no failed gates ⇒ ok");
        assert!(o.is_sannr(), "a green sweep carries the passed-gate list");
        assert_eq!(o.command, "release gate-all");
        assert_eq!(o.data["passed_count"], serde_json::json!(2));
        assert_eq!(o.data["failed_count"], serde_json::json!(0));
        assert_eq!(o.data["all_pass"], serde_json::json!(true));
        assert!(o.human.contains("2 gate(s) passed"));

        // One gate failed → ok=false (RED), with the failed gate + its error in data.
        let failed = vec![("docs_fresh".to_string(), "README drifted".to_string())];
        let r = gate_all_outcome("nornir", &["nexus_floor".to_string()], &failed);
        assert!(!r.ok, "any failed gate ⇒ ok:false");
        assert!(!r.is_sannr(), "a red sweep is a red surface");
        assert_eq!(r.data["failed_count"], serde_json::json!(1));
        assert_eq!(r.data["failed"][0]["name"], serde_json::json!("docs_fresh"));
        assert_eq!(
            r.data["failed"][0]["error"],
            serde_json::json!("README drifted")
        );
        assert!(r.human.contains("✗ docs_fresh: README drifted"));
        assert!(r.human.contains("1 gate(s) failed"));

        // Bug #14: ZERO gates ran (no [gates] in the repo) → NOT green.
        let empty = gate_all_outcome("nornir", &[], &[]);
        assert!(!empty.ok, "empty gate set must not be green");
        assert!(!empty.is_sannr(), "empty is not a true verdict");
        assert_eq!(empty.data["gate_count"], serde_json::json!(0));
        assert!(empty.human.contains("NO gates configured"));
    }

    #[test]
    fn tests_gate_passes_green_fails_on_red_and_names_regression() {
        use crate::warehouse::test_results::{TestResultRow, status};

        // No run at all → RED (a release must have exercised its tests).
        let err = tests_gate(&[]).unwrap_err().to_string();
        assert!(err.contains("no test run"), "{err}");

        // An all-green newest run → Ok.
        let green = vec![
            TestResultRow::unit("g1", "r", "r", "a::ok", status::PASS, 1.0, 100, ""),
            TestResultRow::unit("g1", "r", "r", "a::also", status::PASS, 1.0, 100, ""),
        ];
        tests_gate(&green).unwrap();

        // Newest run is RED → Err naming the failing case.
        let red = vec![
            TestResultRow::unit("g1", "r", "r", "a::ok", status::PASS, 1.0, 100, ""),
            TestResultRow::unit("r2", "r", "r", "a::ok", status::PASS, 1.0, 200, ""),
            TestResultRow::unit("r2", "r", "r", "a::broke", status::FAIL, 1.0, 200, "boom"),
        ];
        let err = tests_gate(&red).unwrap_err().to_string();
        assert!(err.contains("RED"), "{err}");
        assert!(err.contains("a::broke"), "names the failing case: {err}");

        // A REGRESSION: a::ok was green in the last green run (g1), now fails in
        // the newest run (r3) → Err must call out the regression explicitly.
        let regressed = vec![
            TestResultRow::unit("g1", "r", "r", "a::ok", status::PASS, 1.0, 100, ""),
            TestResultRow::unit(
                "r3",
                "r",
                "r",
                "a::ok",
                status::FAIL,
                1.0,
                300,
                "now broken",
            ),
        ];
        let err = tests_gate(&regressed).unwrap_err().to_string();
        assert!(err.contains("REGRESSION"), "flags the regression: {err}");
        assert!(err.contains("a::ok"), "names the regressed test: {err}");

        // A stalled (hung) newest case is also RED.
        let stalled = vec![TestResultRow::unit(
            "s1",
            "r",
            "r",
            "a::hang",
            status::STALLED,
            9e4,
            400,
            "no output 120s",
        )];
        assert!(tests_gate(&stalled).is_err(), "a stalled run is red");
    }
}

#[cfg(test)]
mod core_saturation_tests {
    use super::*;
    use crate::bench::{BenchResult, BenchRun};

    /// One bench result carrying an arbitrary set of `key=value` f64 metrics.
    fn result(name: &str, kv: &[(&str, f64)]) -> BenchResult {
        let mut r = BenchResult {
            name: name.into(),
            metrics: Default::default(),
            ..Default::default()
        };
        for (k, v) in kv {
            r.metrics
                .insert((*k).to_string(), serde_json::Value::from(*v));
        }
        r
    }

    fn run_with(results: Vec<BenchResult>) -> BenchRun {
        BenchRun {
            id: BenchRun::new_id(),
            machine_facts: None,
            date: "2026-01-01".into(),
            timestamp: Some("2026-01-01T00:00:00+00:00".into()),
            version: "0.1.0".into(),
            machine: "ci".into(),
            cores: 1,
            results,
            tests: Vec::new(),
        }
    }

    /// A telemetry row where the bench's OWN peak occupancy is `proc_max` cores
    /// of `n_cores`.
    ///
    /// The host-wide columns are deliberately pinned to **fully saturated**
    /// (`cores_busy_max = n_cores`, `cpu_pct_max = 99.9`) — the shape a loaded
    /// shared box always produces. Any test that goes green here on a serial arm
    /// is reading the host, not the bench. That is not a cosmetic choice: with
    /// the old helper (which drove `cores_busy_max` from the same argument) it
    /// was impossible to write a row that told the two apart.
    fn telem_row(
        bench: &str,
        n_cores: u32,
        proc_max: f64,
    ) -> crate::warehouse::iceberg::BenchTelemetryRow {
        crate::warehouse::iceberg::BenchTelemetryRow {
            run_id: "r".into(),
            repo: "nornir".into(),
            bench: bench.into(),
            n_cores,
            cpu_pct_avg: 99.9,
            cpu_pct_max: 99.9,
            cores_busy_avg: n_cores as f64,
            cores_busy_max: n_cores,
            mem_peak_mb: 0.0,
            mem_pct_max: 0.0,
            elapsed_ms: 1.0,
            proc_cpu_ms: Some(proc_max * 1.0),
            child_cpu_ms: Some(0.0),
            proc_cores_avg: Some(proc_max),
            proc_cores_max: Some(proc_max),
        }
    }

    #[test]
    fn core_saturation_law_fails_underused_passes_saturated() {
        // 11/12 clears the 0.9 floor; 8/12 (the lbz2 example) is a bug.
        assert!(core_saturation(&[telem_row("nornir.lgz", 12, 11.0)], 0.9).is_ok());
        let err = core_saturation(&[telem_row("nornir.lbz2", 12, 8.0)], 0.9)
            .unwrap_err()
            .to_string();
        assert!(err.contains("nornir.lbz2"), "names the offender: {err}");
        assert!(err.contains("8.00/12"), "reports the ratio: {err}");
        // The pathological lgz 2.7/12 case is also RED — and now expressible as
        // the fraction it actually is, not rounded to a whole core.
        assert!(core_saturation(&[telem_row("nornir.lgz", 12, 2.7)], 0.9).is_err());
    }

    /// **THE REGRESSION THIS GATE EXISTS TO NOT REPEAT (LAW 2).**
    ///
    /// Every row here has the host pegged: `cores_busy_max == n_cores`,
    /// `cpu_pct_max == 99.9`. That is what oden looked like on 2026-08-03 while
    /// `gunnar.walk_1m` — strictly single-threaded — occupied 0.56 of one core
    /// and this gate said PASS.
    ///
    /// So: hand the gate a fully-saturated HOST and a serial BENCH, and require
    /// RED. A gate reading `/proc/stat` cannot fail this test; that is the
    /// point of it.
    #[test]
    fn core_saturation_is_not_fooled_by_a_saturated_host() {
        // walk_1m, as measured: 0.56 cores of 32, host reading 32/32 busy.
        let row = telem_row("gunnar.walk_1m", 32, 0.56);
        assert_eq!(
            row.cores_busy_max, 32,
            "the host-wide column must be pegged"
        );
        assert!(
            (row.cores_busy_max as f64) >= 0.9 * row.n_cores as f64,
            "precondition: the OLD input would have PASSED this row"
        );
        let err = core_saturation(&[row], 0.9)
            .expect_err("a 0.56-core arm on a 32-core box must be RED")
            .to_string();
        assert!(err.contains("gunnar.walk_1m"), "{err}");
        assert!(err.contains("0.56/32"), "reports the real occupancy: {err}");

        // clone_concurrent_64, as measured: genuinely 10.99–12.11 of 32 cores
        // (saturation 0.37) where the published metric claimed 0.94. Still RED
        // at the 0.9 floor — but now for the true reason, at the true number.
        let err = core_saturation(&[telem_row("gunnar.clone_concurrent_64", 32, 11.5)], 0.9)
            .expect_err("11.5/32 is under the 0.9 floor")
            .to_string();
        assert!(err.contains("11.50/32"), "{err}");
        // And it passes once it really does saturate.
        assert!(core_saturation(&[telem_row("gunnar.clone_concurrent_64", 32, 30.0)], 0.9).is_ok());
    }

    /// History compat: a row written before `proc_cores_max` existed carries no
    /// process-scoped measurement. It must be SKIPPED as unjudgeable — neither
    /// silently passed as green nor defaulted to `0.0` and reported as a RED
    /// that never happened.
    #[test]
    fn legacy_row_without_proc_telemetry_is_skipped_not_invented() {
        let mut legacy = telem_row("nornir.par", 12, 1.0);
        legacy.proc_cpu_ms = None;
        legacy.child_cpu_ms = None;
        legacy.proc_cores_avg = None;
        legacy.proc_cores_max = None;
        assert!(
            core_saturation(&[legacy.clone()], 0.9).is_ok(),
            "an unmeasured row must not manufacture a RED"
        );
        // …but a measured sibling in the same batch is still judged, so the skip
        // never swallows a real violation.
        let err = core_saturation(&[legacy, telem_row("nornir.par2", 12, 1.0)], 0.9)
            .expect_err("the measured row is still gated")
            .to_string();
        assert!(
            err.contains("nornir.par2") && !err.contains("nornir.par "),
            "{err}"
        );
    }

    #[test]
    fn core_saturation_exempts_single_thread_variants_and_single_core() {
        // `_st` variant deliberately uses one core → exempt even at 1/12.
        assert!(core_saturation(&[telem_row("ljar.encode_st", 12, 1.0)], 0.9).is_ok());
        // The SAME bench WITHOUT the `_st` suffix at 1/12 IS a violation — proving
        // it is the shared single-thread suffix (`crate::bench::is_single_thread`,
        // the same notion the mashup keeps as a distinct row) that exempts it.
        assert!(core_saturation(&[telem_row("ljar.encode", 12, 1.0)], 0.9).is_err());
        // Degenerate single-core host → nothing to saturate.
        assert!(core_saturation(&[telem_row("ljar.encode", 1, 1.0)], 0.9).is_ok());
        // Empty telemetry (nothing measured) → trivially green.
        assert!(core_saturation(&[], 0.9).is_ok());
        // frac 0.0 falls back to the LAW default (0.9): 8/12 still RED.
        assert!(core_saturation(&[telem_row("b", 12, 8.0)], 0.0).is_err());
    }

    #[test]
    fn core_saturation_exempts_nornir_latency_microbenches() {
        // dep_graph_build/_ext + delta_rescan are latency/IO microbenches (one
        // `cargo metadata` spawn + warm closure queries), NOT parallel-compute —
        // so 1/12 cores is correct, not an under-use bug (bencher O1). Exempt.
        assert!(core_saturation(&[telem_row("nornir.dep_graph_build", 12, 1.0)], 0.9).is_ok());
        assert!(core_saturation(&[telem_row("nornir.dep_graph_build_ext", 12, 1.0)], 0.9).is_ok());
        assert!(core_saturation(&[telem_row("nornir.delta_rescan", 12, 1.0)], 0.9).is_ok());
        // But the genuinely parallel platform arms stay under the LAW: an all-core
        // `syn` scan that only used 1/12 cores is still a real violation.
        assert!(core_saturation(&[telem_row("nornir.knowledge_scan", 12, 1.0)], 0.9).is_err());
        assert!(is_latency_microbench("nornir.dep_graph_build"));
        assert!(!is_latency_microbench("nornir.knowledge_scan"));
    }

    #[test]
    fn core_saturation_run_reads_the_runs_telemetry_footprint() {
        use crate::bench::telemetry::{Telemetry, inject_into_metrics};
        // Host pegged at fully-busy in every case (see `telem_row`) so this
        // exercises the metric-map hop with the SAME trap the warehouse path
        // has: only `proc_cores_max` may move the verdict.
        let mk = |name: &str, n: u32, proc_cores: f64| {
            let mut m = serde_json::Map::new();
            let t = Telemetry {
                n_cores: n,
                cpu_pct_avg: 99.9,
                cpu_pct_max: 99.9,
                cores_busy_avg: n as f64,
                cores_busy_max: n,
                mem_peak_mb: 0.0,
                mem_pct_max: 0.0,
                elapsed_ms: 1.0,
                samples: 1,
                proc_cpu_ms: Some(proc_cores),
                child_cpu_ms: Some(0.0),
                proc_cores_avg: Some(proc_cores),
                proc_cores_max: Some(proc_cores),
                ..Default::default()
            };
            inject_into_metrics(&mut m, &t);
            BenchResult {
                name: name.into(),
                metrics: m,
                ..Default::default()
            }
        };
        // A saturated run passes; an under-used one trips the in-memory gate —
        // even though the host looked 12/12 busy in BOTH.
        assert!(core_saturation_run(&run_with(vec![mk("nornir.par", 12, 12.0)]), 0.9).is_ok());
        assert!(core_saturation_run(&run_with(vec![mk("nornir.par", 12, 5.0)]), 0.9).is_err());
        // A result with NO telemetry footprint is skipped (no false RED).
        assert!(
            core_saturation_run(&run_with(vec![result("plain", &[("x_mbs", 1.0)])]), 0.9).is_ok()
        );
    }
}

/// The CROSS-WORKSPACE completeness gate over a constructed [`MegaGateReport`] —
/// Wave-1 covered the single-workspace [`coverage_gate`] but deferred the mega
/// rollup. These build the report directly (the caller normally sweeps the
/// warehouse via `gather_all`; the gate itself is a pure verdict).
#[cfg(test)]
mod megagate_tests {
    use super::*;
    use crate::autonom::megagate::{MegaGateReport, WorkspaceGate};
    use nornir_testmatrix::coverage::{AllowEntry, Allowlist, GateReport};
    use nornir_testmatrix::discover::{Surface, cli_commands};
    use std::collections::BTreeSet;

    /// A per-workspace coverage [`GateReport`] over two CLI-command surfaces, with
    /// `cover_all` toggling whether both are covered (green) or one is left as a gap.
    fn report(ws: &str, cover_all: bool) -> GateReport {
        let mut surface = Surface::new();
        surface.extend(cli_commands(["a", "b"]));
        let covered: BTreeSet<String> = if cover_all {
            ["cli_command:a@na", "cli_command:b@na"]
                .iter()
                .map(|s| s.to_string())
                .collect()
        } else {
            ["cli_command:a@na".to_string()].into_iter().collect()
        };
        GateReport::compute("r", ws, &surface, &covered, &Allowlist::new())
    }

    /// Every workspace green ⇒ the mega gate passes.
    #[test]
    fn all_green_workspaces_pass() {
        let mega = MegaGateReport {
            run_id: "run-1".into(),
            workspaces: vec![
                WorkspaceGate::from_report("nornir", report("nornir", true)),
                WorkspaceGate::from_report("znippy", report("znippy", true)).with_reachable(3, 3),
            ],
        };
        assert!(mega.is_green());
        assert!(coverage_gate_all_workspaces(&mega).is_ok());
    }

    /// A gap in ONE workspace reds the whole release, and the error names both the
    /// offending workspace and the uncovered surface key.
    #[test]
    fn one_workspace_with_a_gap_fails_naming_it() {
        let mega = MegaGateReport {
            run_id: "run-2".into(),
            workspaces: vec![
                WorkspaceGate::from_report("nornir", report("nornir", true)),
                WorkspaceGate::from_report("znippy", report("znippy", false)), // gap on `b`
            ],
        };
        assert!(!mega.is_green());
        let err = coverage_gate_all_workspaces(&mega).unwrap_err().to_string();
        assert!(err.contains("mega completeness gate RED"), "{err}");
        assert!(
            err.contains("znippy"),
            "names the offending workspace: {err}"
        );
        assert!(err.contains('b'), "names the uncovered surface: {err}");
    }

    /// A stale allowlist entry (coverage otherwise green) also reds the mega gate.
    #[test]
    fn stale_allowlist_entry_fails() {
        let mut surface = Surface::new();
        surface.extend(cli_commands(["a", "b"]));
        let all: BTreeSet<String> = ["cli_command:a@na", "cli_command:b@na"]
            .iter()
            .map(|s| s.to_string())
            .collect();
        let stale_al = Allowlist {
            entries: vec![AllowEntry {
                key: "cli_command:a@na".into(),
                reason: "old".into(),
            }],
        };
        let coverage = GateReport::compute("r", "nornir", &surface, &all, &stale_al);
        let mega = MegaGateReport {
            run_id: "run-3".into(),
            workspaces: vec![WorkspaceGate::from_report("nornir", coverage)],
        };
        let err = coverage_gate_all_workspaces(&mega).unwrap_err().to_string();
        assert!(err.contains("stale"), "{err}");
        assert!(err.contains("nornir"), "{err}");
    }

    /// Coverage-green but a RESOLVED-metro shortfall (`green != total`) reds the
    /// mega gate with the reachable-lines message.
    #[test]
    fn unmet_reachable_rollup_fails() {
        let mega = MegaGateReport {
            run_id: "run-4".into(),
            workspaces: vec![
                WorkspaceGate::from_report("nornir", report("nornir", true)).with_reachable(2, 3),
            ],
        };
        assert!(
            !mega.is_green(),
            "an unlit resolved metro line is not green"
        );
        let err = coverage_gate_all_workspaces(&mega).unwrap_err().to_string();
        assert!(err.contains("2/3 resolved metro"), "{err}");
        assert!(err.contains("nornir"), "{err}");
    }
}

/// Names of the gates in [`selected_gates`] order — the canonical release suite.
pub const GATE_ORDER: &[&str] = &[
    "no_path_patches",
    "nexus_floor",
    "no_regression",
    "integration_roundtrip",
    "docs_fresh",
    "coverage",
    "tests",
];

/// The ONE answer to "which gates does this repo's release suite run?".
///
/// Both release-readiness surfaces walk this list: `Release.GateAll` (the 🚦
/// Release-gates pane) and `Ops.RunRelease` (the 🚀 Release button / the
/// `nornir release gate all` thin path). They used to disagree — `gate_all`
/// honoured `[repo.<name>.gates]` while `run_release` fetched the same config,
/// threw it away with a `let _ = &repo_cfg;`, and ran exactly ONE hardcoded gate
/// (`no_path_patches`). A repo that had explicitly turned `no_path_patches` OFF
/// and turned other gates ON got the one gate it disabled and none of the ones
/// it asked for, then a summary reading "N/N repo(s) ready".
///
/// **The unconfigured floor.** An all-default `[gates]` table (every flag
/// `false`) means "no gates declared", not "this repo is exempt from review". A
/// suite that ran nothing there would report a green release verdict having
/// checked nothing at all. So an empty selection falls back to
/// [`no_path_patches`] — the cheapest gate, correct for every crate, and exactly
/// what `Ops.RunRelease` already ran. A repo that declares ANY gate gets its
/// declaration verbatim, including an explicit `no_path_patches = false`.
pub fn selected_gates(g: &crate::config::Gates) -> Vec<&'static str> {
    let mut out: Vec<&'static str> = Vec::new();
    if g.no_path_patches {
        out.push("no_path_patches");
    }
    if g.nexus_floor {
        out.push("nexus_floor");
    }
    if g.no_regression {
        out.push("no_regression");
    }
    if !g.integration_roundtrip.is_empty() {
        out.push("integration_roundtrip");
    }
    if g.docs_fresh {
        out.push("docs_fresh");
    }
    if g.coverage {
        out.push("coverage");
    }
    if g.tests {
        out.push("tests");
    }
    if out.is_empty() {
        out.push("no_path_patches");
    }
    out
}

#[cfg(test)]
mod selected_gates_tests {
    use super::selected_gates;
    use crate::config::Gates;

    /// An undeclared `[gates]` table falls back to the floor gate — never to
    /// "nothing ran, therefore ready".
    #[test]
    fn unconfigured_gates_fall_back_to_the_floor() {
        assert_eq!(selected_gates(&Gates::default()), vec!["no_path_patches"]);
    }

    /// The regression this guards: a repo that turns `no_path_patches` OFF and
    /// other gates ON must get ITS gates, not the one it disabled. This is the
    /// exact shape of `workspace_holger/release/nornir.toml`'s `[repo.znippy.gates]`.
    #[test]
    fn an_explicit_off_is_honoured_and_declared_gates_are_selected() {
        let g = Gates {
            no_path_patches: false,
            no_regression: true,
            docs_fresh: true,
            ..Default::default()
        };
        let sel = selected_gates(&g);
        assert!(
            !sel.contains(&"no_path_patches"),
            "a repo that disabled no_path_patches must not have it run: {sel:?}"
        );
        assert_eq!(sel, vec!["no_regression", "docs_fresh"]);
    }

    /// Every gate the config can turn on is reachable, and the order is the
    /// declared suite order (cheap/pure gates before the heavy subprocess ones).
    #[test]
    fn every_gate_is_selectable_and_ordered() {
        let g = Gates {
            no_path_patches: true,
            nexus_floor: true,
            no_regression: true,
            integration_roundtrip: vec!["roundtrip".into()],
            docs_fresh: true,
            coverage: true,
            tests: true,
            ..Default::default()
        };
        assert_eq!(selected_gates(&g), super::GATE_ORDER.to_vec());
    }
}

/// WAREHOUSE-BACKED bench-regression gate: seed `bench_runs` in a real temporary
/// [`IcebergWarehouse`], read the history back with `query_bench_runs_async`, and
/// run [`no_regression_against`] over it — closing the "read back from Iceberg"
/// path the doc-comment describes (Wave-1's gate tests were all in-memory slices).
#[cfg(test)]
mod bench_warehouse_tests {
    use super::*;
    use crate::bench::{BenchResult, BenchRun};
    use crate::warehouse::BenchFilter;
    use crate::warehouse::iceberg::IcebergWarehouse;

    fn run(machine: &str, ts: &str, mbs: f64) -> BenchRun {
        let mut r = BenchResult {
            name: "codec".into(),
            metrics: Default::default(),
            ..Default::default()
        };
        r.metrics
            .insert("throughput_mbs".into(), serde_json::Value::from(mbs));
        BenchRun {
            id: BenchRun::new_id(),
            machine_facts: None,
            date: ts[..10].to_string(),
            timestamp: Some(ts.to_string()),
            version: "0.1.0".into(),
            machine: machine.into(),
            cores: 1,
            results: vec![r],
            tests: Vec::new(),
        }
    }

    /// Persist a jittery baseline for `ci`, read it back, and gate: a within-
    /// threshold candidate passes and a beyond-threshold one fails, verdict driven
    /// entirely off the warehouse-materialised history.
    #[test]
    fn gate_over_warehouse_history_passes_and_fails() {
        let dir = tempfile::tempdir().unwrap();
        let wh = IcebergWarehouse::open(dir.path()).unwrap();
        // Baseline: newest same-machine run is 100 MB/s (the pick_baseline anchor).
        for (ts, mbs) in [
            ("2026-02-01T00:00:00+00:00", 90.0),
            ("2026-02-02T00:00:00+00:00", 100.0),
        ] {
            wh.block_on(wh.append_bench_run_async("znippy", &run("ci", ts, mbs)))
                .unwrap();
        }
        // A different machine's run must NOT pollute the ci baseline.
        wh.block_on(wh.append_bench_run_async(
            "znippy",
            &run("other-box", "2026-02-03T00:00:00+00:00", 999.0),
        ))
        .unwrap();

        let history = wh
            .block_on(wh.query_bench_runs_async(&BenchFilter::for_repo("znippy")))
            .unwrap();
        assert!(
            history.len() >= 3,
            "all persisted runs read back: {}",
            history.len()
        );

        // 5% drop vs the newest ci baseline (100 → 95) within a 10% threshold → ok.
        let ok_candidate = run("ci", "2026-03-01T00:00:00+00:00", 95.0);
        assert!(no_regression_against(&ok_candidate, &history, 10.0).is_ok());

        // 30% drop (100 → 70) beyond the 10% threshold → regression.
        let bad_candidate = run("ci", "2026-03-02T00:00:00+00:00", 70.0);
        let err = no_regression_against(&bad_candidate, &history, 10.0)
            .unwrap_err()
            .to_string();
        assert!(err.contains("regression"), "got: {err}");
    }

    /// First run for a machine with no prior warehouse history bootstraps the
    /// baseline (nothing to regress against) ⇒ the gate passes.
    #[test]
    fn no_warehouse_baseline_for_machine_passes() {
        let dir = tempfile::tempdir().unwrap();
        let wh = IcebergWarehouse::open(dir.path()).unwrap();
        wh.block_on(wh.append_bench_run_async(
            "znippy",
            &run("other-box", "2026-02-01T00:00:00+00:00", 500.0),
        ))
        .unwrap();
        let history = wh
            .block_on(wh.query_bench_runs_async(&BenchFilter::for_repo("znippy")))
            .unwrap();
        let candidate = run("fresh-ci", "2026-03-01T00:00:00+00:00", 10.0);
        assert!(
            no_regression_against(&candidate, &history, 10.0).is_ok(),
            "no baseline ⇒ pass"
        );
    }
}

#[cfg(test)]
mod semantic_link_gate_tests {
    use super::*;
    use nornir_release_core::semantic_blast::{CallSite, ChangeKind, SemanticBlast, SymbolImpact};
    use std::collections::BTreeMap;

    fn site(cr: &str) -> CallSite {
        CallSite {
            crate_name: cr.into(),
            caller: format!("{cr}::mod::f"),
            callee: "gone".into(),
            call_kind: "call".into(),
            file: format!("{cr}/src/lib.rs"),
            line: 10,
        }
    }

    fn blast(repo: &str, symbol: &str, change: ChangeKind, sites: usize) -> SemanticBlast {
        let call_sites: Vec<CallSite> = (0..sites).map(|_| site("korp")).collect();
        SemanticBlast {
            target_repo: repo.into(),
            target_crates: vec![repo.into()],
            source: "resolved/scip".into(),
            impacts: vec![SymbolImpact {
                symbol: symbol.into(),
                change,
                call_sites,
            }],
            unused_changes: vec![],
            total_call_sites: sites,
            affected_crates: vec!["korp".into()],
        }
    }

    /// A REMOVED symbol with a live call site FAILS the gate (link error); the error
    /// message names the symbol and the failing call site.
    #[test]
    fn removed_symbol_with_call_site_fails_like_a_linker() {
        let mut map: BTreeMap<String, SemanticBlast> = BTreeMap::new();
        map.insert(
            "facett".into(),
            blast("facett", "render", ChangeKind::Removed, 2),
        );
        let res = semantic_link_gate(&map);
        let failed = res.is_err();
        let msg = res.err().map(|e| e.to_string()).unwrap_or_default();
        let names_symbol = msg.contains("facett::render") && msg.contains("LINK ERROR");

        nornir_testmatrix::functional_status(
            "release-gate",
            "semantic_link_gate_fails_on_removed",
            failed && names_symbol,
            &format!("failed={failed} msg_head={:?}", msg.lines().next()),
        );
        assert!(
            failed,
            "a removed symbol with a live call site must FAIL the release"
        );
        assert!(
            names_symbol,
            "the error names the removed symbol + LINK ERROR: {msg}"
        );
    }

    /// A merely CHANGED symbol (still resolves) is a warning, not a link error → the
    /// gate PASSES; and an empty blast passes trivially.
    #[test]
    fn changed_symbol_and_empty_blast_pass() {
        let mut map: BTreeMap<String, SemanticBlast> = BTreeMap::new();
        map.insert(
            "facett".into(),
            blast("facett", "open", ChangeKind::Changed, 3),
        );
        let changed_passes = semantic_link_gate(&map).is_ok();
        let empty_passes = semantic_link_gate(&BTreeMap::new()).is_ok();

        nornir_testmatrix::functional_status(
            "release-gate",
            "semantic_link_gate_passes_on_changed_and_empty",
            changed_passes && empty_passes,
            &format!("changed_passes={changed_passes} empty_passes={empty_passes}"),
        );
        assert!(
            changed_passes,
            "a still-resolving Changed symbol is not a link error"
        );
        assert!(
            empty_passes,
            "no semantic input ⇒ nothing to link-check ⇒ pass"
        );
    }

    /// A removed symbol that NO dependent calls (empty call_sites) is not a link error.
    #[test]
    fn removed_but_uncalled_symbol_passes() {
        let mut map: BTreeMap<String, SemanticBlast> = BTreeMap::new();
        map.insert(
            "facett".into(),
            blast("facett", "secret", ChangeKind::Removed, 0),
        );
        assert!(
            semantic_link_gate(&map).is_ok(),
            "removing a symbol nobody calls links fine"
        );
    }
}