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

nordisk/edda

crates/nornir-bench/src/lib.rs

//! # nornir-bench
//!
//! Benchmark harness — project-agnostic.
//!
//! Provides a uniform run/result envelope plus an append-only JSONL
//! history. Repo-specific metrics live in [`BenchResult::metrics`] as
//! a free-form JSON object, so one envelope serves any project.
//!
//! nornir re-exports this as `nornir::bench` (plus the `register_bench!` /
//! `register_bench_ordered!` macros), so every `crate::bench::…` call site
//! resolves unchanged. The live progress trail is written directly to
//! `$NORNIR_VIZ_ACTIONLOG` in nornir's `viz::action_log` line format (no egui /
//! viz dependency is pulled in).

pub mod api;
pub mod assets;
pub mod diff;
pub mod history;
pub mod legacy;
/// **What the box IS** — the stable identity of the host, beside the id.
/// Distinct from [`telemetry`], which is what a RUN experienced.
pub mod machine;
pub mod progress;
/// The OUTER runner — find, build, run and parse a repo's `nornir-bench`
/// example. Behind the off-by-default `runner` feature so a consumer that only
/// wants the envelope links neither `toml` nor a subprocess. See
/// [`runner`](runner) for why the forge and the warehouse share one copy of it.
#[cfg(feature = "runner")]
pub mod runner;
pub mod selftest;
pub mod telemetry;

use serde::{Deserialize, Serialize};
pub use uuid::Uuid;

/// Process-wide serial lock for tests that mutate GLOBAL state (the
/// `NORNIR_VIZ_ACTIONLOG` env var). `cargo test` is multi-threaded by default,
/// so two tests touching the same env var race; such tests take
/// `let _g = crate::serial_guard();` to run one-at-a-time. Poison-tolerant.
#[cfg(test)]
pub(crate) fn serial_guard() -> std::sync::MutexGuard<'static, ()> {
    static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
    LOCK.lock().unwrap_or_else(|e| e.into_inner())
}

/// Where a [`BenchResult`]'s numbers came from — the capabilities/source tag
/// (issue #13). A **measured** result (the default) is a timed benchmark:
/// throughput/latency the harness clocked. A **static** result is a
/// capabilities probe — machine facts captured with NO timed work (core count,
/// SIMD flags, cache sizes, feature toggles). The tag lets a consumer tell a
/// static-capability row apart from a real measurement without inspecting the
/// (free-form) metric keys, and lets the no-regression gate SKIP static rows
/// (a machine-fact "drop" is not a perf regression).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum BenchSource {
    /// A timed benchmark measurement (the default).
    #[default]
    Measured,
    /// A static capabilities probe — machine facts, not a timed measurement.
    Static,
}

impl BenchSource {
    /// True for the default (`Measured`) — used by `skip_serializing_if` so the
    /// tag is omitted for ordinary measured rows (byte-for-byte backward-compat
    /// JSON: legacy `bench_history.jsonl` lines and every existing writer stay
    /// unchanged on the wire).
    pub fn is_measured(&self) -> bool {
        matches!(self, BenchSource::Measured)
    }
}

/// A single benchmark result. Repo-specific metrics live in `metrics`
/// so both holger (`holger_ops_sec` / `nexus_ops_sec`) and znippy
/// (`compress_mbs` / `decompress_mbs` / `files`) can share the same
/// run envelope.
///
/// `source` (issue #13) tags whether the row is a timed measurement or a
/// static-capabilities probe. It is `#[serde(default)]` (legacy JSONL lines
/// lacking it read as `Measured`) and skipped on the wire when `Measured`, so
/// adding it changes nothing for existing measured rows.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BenchResult {
    pub name: String,
    #[serde(default, skip_serializing_if = "BenchSource::is_measured")]
    pub source: BenchSource,
    #[serde(flatten)]
    pub metrics: serde_json::Map<String, serde_json::Value>,
}

impl BenchResult {
    /// A **measured** result — a timed benchmark (the default source). The
    /// canonical constructor for a bencher's timed numbers.
    pub fn measured(
        name: impl Into<String>,
        metrics: serde_json::Map<String, serde_json::Value>,
    ) -> Self {
        Self { name: name.into(), source: BenchSource::Measured, metrics }
    }

    /// A **static-capabilities** result — machine facts captured with no timed
    /// work (the writer for issue #13's seam). The row carries the same
    /// free-form `metrics` map (e.g. `n_cores`, `avx512`, `l3_kb`) but is
    /// tagged [`BenchSource::Static`] so consumers can distinguish it.
    pub fn static_capabilities(
        name: impl Into<String>,
        metrics: serde_json::Map<String, serde_json::Value>,
    ) -> Self {
        Self { name: name.into(), source: BenchSource::Static, metrics }
    }

    /// True iff this row is a static-capabilities probe (not a timed measurement).
    pub fn is_static(&self) -> bool {
        matches!(self.source, BenchSource::Static)
    }
}

/// The canonical **static-capabilities writer** (issue #13): probe the host for
/// machine facts (no timed work) and emit them as a [`BenchSource::Static`]
/// [`BenchResult`] named `name` (conventionally `<repo>.capabilities`). Sits
/// alongside a run's measured rows so a consumer knows the machine the numbers
/// were taken on, while the no-regression gate skips it (a machine-fact change
/// is not a perf regression). All facts are numeric so they survive the
/// warehouse's numeric metric ingest; booleans are encoded 1.0/0.0.
pub fn machine_capabilities(name: impl Into<String>) -> BenchResult {
    let n_cores = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1) as f64;
    let mut m = serde_json::Map::new();
    m.insert("n_cores".into(), serde_json::json!(n_cores));
    m.insert("pointer_width_bits".into(), serde_json::json!((usize::BITS) as f64));
    // Compile-time target facts — static by definition (the binary's own build).
    m.insert(
        "target_x86_64".into(),
        serde_json::json!(if cfg!(target_arch = "x86_64") { 1.0 } else { 0.0 }),
    );
    m.insert(
        "target_aarch64".into(),
        serde_json::json!(if cfg!(target_arch = "aarch64") { 1.0 } else { 0.0 }),
    );
    BenchResult::static_capabilities(name, m)
}

/// The single-thread bench-variant suffix. A bench name ending in `_st`
/// (e.g. `zoomies-lgz_st`) is the deliberately single-threaded variant of the
/// SAME system as its un-suffixed sibling (`zoomies-lgz`). It is written by the
/// single-core pass in [`api`] (which appends this suffix to every result +
/// test name of a serial-pinned pass). Two nornir subsystems must agree on this
/// one notion — hence the shared constant/helpers here rather than a literal
/// `"_st"` duplicated at each site:
/// - the **core-saturation gate** EXEMPTS `_st` rows (serial by design — one
///   busy core is correct, not an under-use bug);
/// - the **mashup docs** treat an `_st` row as a DISTINCT row of the same base
///   system (the single-core-vs-multi-core story), grouped under that system.
pub const SINGLE_THREAD_SUFFIX: &str = "_st";

/// True when a bench name is a single-thread (`_st`) variant — the deliberately
/// serial sibling of an un-suffixed base bench. See [`SINGLE_THREAD_SUFFIX`].
pub fn is_single_thread(name: &str) -> bool {
    name.ends_with(SINGLE_THREAD_SUFFIX)
}

/// Strip the single-thread (`_st`) suffix, yielding the BASE bench name a
/// variant shares with its un-suffixed sibling (`zoomies-lgz_st` →
/// `zoomies-lgz`; a name without the suffix is returned unchanged). Lets a
/// consumer keep an `_st` row distinct while still relating it to its base
/// system. See [`SINGLE_THREAD_SUFFIX`].
pub fn strip_single_thread(name: &str) -> &str {
    name.strip_suffix(SINGLE_THREAD_SUFFIX).unwrap_or(name)
}

/// Pass/fail outcome of a single test that ran alongside the bench.
///
/// `tests` lives next to `results` on a [`BenchRun`] so the no-regression
/// gate can fail a release both for scalar drops *and* for any test
/// flipping red. Optional fields (`duration_ms`, `message`) carry the
/// extra detail when the runner provides it (criterion / `cargo test`
/// JSON output etc.).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestOutcome {
    pub name: String,
    pub passed: bool,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub duration_ms: Option<f64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub message: Option<String>,
}

/// One full bench run — one line in `bench_history.jsonl`.
///
/// Backward-compat fields:
/// - `version`, `machine`, `cores` are `#[serde(default)]` so legacy
///   entries (holger 2026-05-27 lines lacking machine/version/cores;
///   znippy lines lacking machine) deserialize cleanly. Going forward
///   all newly written runs must populate them — `history::append`
///   rejects empty `machine`.
/// - `date` (YYYY-MM-DD string) is retained for legacy compatibility.
///   New writers should also populate `timestamp` (RFC 3339, UTC) which
///   becomes the canonical time column in the Iceberg `bench_runs`
///   table once the warehouse phase lands.
/// - `tests` defaults to empty so existing JSONL parses; new runs are
///   expected to populate it.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenchRun {
    /// **The run's identity, produced HERE, by the producer, at measurement
    /// time** — a UUID **v7**, which is time-ordered: the id sorts the way the
    /// date sorts and rows written minutes apart cluster in storage.
    ///
    /// Until 2026-09-20 this did not exist. The uuid was minted SERVER-SIDE by
    /// `nornir`'s `append_bench_run_async` and returned, so the receipt on disk
    /// had no identity at all, with three measured consequences:
    /// - a second upload of the same run made a second row, so a failed submit
    ///   could not be retried;
    /// - gunnar's `bench-landed` guard had to join receipts to warehouse rows
    ///   on a GUESS (timestamp truncated to the second plus the arm-name set),
    ///   because the two stores disagree on sub-second precision;
    /// - 16 of 46 receipts never reached the warehouse and could not be
    ///   repaired afterwards, because nothing identified them.
    ///
    /// `#[serde(default)]` yields [`Uuid::nil`] for a line written before this
    /// field existed, so every `bench_history.jsonl` already on disk still
    /// parses. A nil id means "legacy, no identity" — see [`BenchRun::has_id`];
    /// consumers must fall back to the old heuristic join for those and MUST
    /// NOT treat nil as a real id (every legacy row would collide).
    #[serde(default)]
    pub id: Uuid,
    pub date: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub timestamp: Option<String>,
    #[serde(default)]
    pub version: String,
    #[serde(default)]
    pub machine: String,
    #[serde(default)]
    pub cores: u32,
    /// **What the BOX is** — hostname, CPU model, physical cores, logical
    /// threads, total RAM, GPU, kernel, OS. Distinct from `machine` (the
    /// operator's label) and from [`telemetry::Telemetry`] (what the RUN
    /// experienced). `None` on a legacy line and on a producer that chose not
    /// to look. See [`machine::Machine`].
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub machine_facts: Option<machine::Machine>,
    pub results: Vec<BenchResult>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub tests: Vec<TestOutcome>,
}

impl BenchRun {
    /// True when this run carries a real producer-minted id. False for a line
    /// written before the field existed (nil id) — such a run must be joined
    /// heuristically and must never be deduplicated on its id, because every
    /// legacy line has the same one.
    pub fn has_id(&self) -> bool {
        !self.id.is_nil()
    }

    /// Mint a fresh v7 id. Call this exactly where a run is MADE.
    pub fn new_id() -> Uuid {
        Uuid::now_v7()
    }

    /// The id as it rides a **non-JSON** wire (the gRPC `BenchRun.id` field,
    /// which is a `string` because proto has no uuid type). A nil id — a legacy
    /// receipt with no identity — is the EMPTY STRING, never
    /// `"00000000-0000-…"`: the empty string is what tells the far side "this
    /// one cannot be deduplicated", and a printed nil uuid would look like a
    /// real key that every legacy run shares.
    ///
    /// One copy of the spelling, here, because five call sites encode it
    /// (`server_grpc`, `mcp_stdio`, the CLI's submit and history decoders) and
    /// a sixth would eventually disagree with the rest.
    pub fn id_wire(&self) -> String {
        if self.has_id() { self.id.to_string() } else { String::new() }
    }

    /// Inverse of [`BenchRun::id_wire`]. An empty or unparsable field yields
    /// the nil id, i.e. "no identity" — never an error, because an old peer
    /// that does not send the field must still be able to submit a run.
    pub fn id_from_wire(s: &str) -> Uuid {
        Uuid::parse_str(s).unwrap_or_else(|_| Uuid::nil())
    }

    /// [`BenchRun::machine_facts`] as JSON for the same non-JSON wire. Empty
    /// string when the producer did not look at the box.
    pub fn machine_facts_wire(&self) -> String {
        self.machine_facts
            .as_ref()
            .and_then(|m| serde_json::to_string(m).ok())
            .unwrap_or_default()
    }

    /// Inverse of [`BenchRun::machine_facts_wire`]. Unparsable JSON yields
    /// `None` — the facts are context, never a reason to drop a measurement.
    pub fn machine_facts_from_wire(s: &str) -> Option<machine::Machine> {
        if s.trim().is_empty() {
            return None;
        }
        serde_json::from_str(s).ok()
    }

    pub fn find(&self, name: &str) -> Option<&BenchResult> {
        self.results.iter().find(|r| r.name == name)
    }

    /// True iff every test in `tests` passed. An empty `tests` vec is
    /// treated as "no tests run" → returns true.
    pub fn all_tests_passed(&self) -> bool {
        self.tests.iter().all(|t| t.passed)
    }

    /// Convenience: list the names of any failed tests.
    pub fn failed_tests(&self) -> Vec<&str> {
        self.tests
            .iter()
            .filter(|t| !t.passed)
            .map(|t| t.name.as_str())
            .collect()
    }
}

/// Which way is "better" for a metric — used by the `benches` doc renderer
/// to **bold the winning cell** in a comparison row (e.g. ours vs a legacy
/// tool). `High` = bigger is better (throughput), `Low` = smaller is better
/// (latency/time), `Neutral` = not a competition (counts, ratios) → never
/// bolded.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MetricDirection {
    High,
    Low,
    Neutral,
}

/// Resolve a metric's direction: an explicit `overrides` entry wins (the
/// `benches` renderer fills this from a `best=metric:low` marker arg); otherwise
/// fall back to the unit implied by the metric name (the `_mbs` / `_ms` / …
/// convention — see [`unit_of`]).
pub fn direction_of(
    overrides: &std::collections::HashMap<String, MetricDirection>,
    metric: &str,
) -> MetricDirection {
    if let Some(d) = overrides.get(metric) {
        return *d;
    }
    unit_of(metric).map(|u| u.direction).unwrap_or(MetricDirection::Neutral)
}

/// A recognised metric unit and its natural direction. Two metric cells are
/// "comparable" (compete in a row, eligible for bolding) iff they share the
/// same `unit.name`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Unit {
    pub name: &'static str,
    pub direction: MetricDirection,
}

/// Recognise the trailing unit of a metric key. Covers both the
/// `<corpus>_<suffix>` convention (`_mbs`/`_ms`/`_pct`) and the longhand
/// holger-style keys (`mb_per_sec`/`ops_per_sec`/`seconds`). Returns `None`
/// for plain counts (`bytes`, `ops`, `files`) — those never bold.
pub fn unit_of(metric: &str) -> Option<Unit> {
    use MetricDirection::*;
    let m = metric.to_ascii_lowercase();
    // (suffix, unit-name, direction) — order matters: longest/most-specific first.
    const TABLE: &[(&str, &str, MetricDirection)] = &[
        ("mb_per_sec", "mbs", High),
        ("mbps", "mbs", High),
        ("_mbs", "mbs", High),
        ("gb_per_sec", "gbs", High),
        ("_gbs", "gbs", High),
        ("ops_per_sec", "ops_sec", High),
        ("ops_sec", "ops_sec", High),
        ("_ops_sec", "ops_sec", High),
        // generic throughput (rows_per_sec, commits_per_sec, …) — more is better.
        ("_per_sec", "per_sec", High),
        ("seconds", "secs", Low),
        ("_secs", "secs", Low),
        // bare seconds columns (build_s, query_s, total_s) — less is better.
        ("_s", "secs", Low),
        ("_ms", "ms", Low),
        ("_us", "us", Low),
        ("_ns", "ns", Low),
        ("_pct", "pct", Neutral),
        ("_x", "x", Neutral),
        ("speedup", "x", Neutral),
    ];
    for (suffix, name, dir) in TABLE {
        if m == *suffix || m.ends_with(suffix) {
            return Some(Unit { name, direction: *dir });
        }
    }
    None
}

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

    #[test]
    fn static_and_measured_are_distinguishable() {
        let mut m = serde_json::Map::new();
        m.insert("n_cores".into(), serde_json::json!(12));
        let cap = BenchResult::static_capabilities("nornir.capabilities", m.clone());
        let meas = BenchResult::measured("nornir.dep_graph_build", m);
        assert!(cap.is_static(), "capabilities probe is static");
        assert!(!meas.is_static(), "a measured bench is not static");
        assert_eq!(cap.source, BenchSource::Static);
        assert_eq!(meas.source, BenchSource::Measured);
    }

    #[test]
    fn measured_tag_is_omitted_on_the_wire_but_static_survives_roundtrip() {
        // Backward-compat: a Measured row serializes WITHOUT a `source` key, so
        // legacy readers/JSONL are unaffected.
        let meas = BenchResult::measured("b", serde_json::Map::new());
        let j = serde_json::to_value(&meas).unwrap();
        assert!(j.get("source").is_none(), "Measured omits the tag: {j}");

        // A Static row emits `"source":"static"` and round-trips back to Static.
        let mut m = serde_json::Map::new();
        m.insert("avx512".into(), serde_json::json!(1.0));
        let cap = BenchResult::static_capabilities("caps", m);
        let s = serde_json::to_string(&cap).unwrap();
        assert!(s.contains("\"source\":\"static\""), "Static emits the tag: {s}");
        let back: BenchResult = serde_json::from_str(&s).unwrap();
        assert!(back.is_static());
        assert_eq!(back.metrics.get("avx512"), Some(&serde_json::json!(1.0)));
    }

    #[test]
    fn legacy_line_without_source_reads_as_measured() {
        // A pre-#13 JSONL result (no `source` key) must default to Measured.
        let back: BenchResult =
            serde_json::from_str(r#"{"name":"b","x_mbs":1.0}"#).unwrap();
        assert_eq!(back.source, BenchSource::Measured);
        assert!(!back.is_static());
        assert_eq!(back.metrics.get("x_mbs"), Some(&serde_json::json!(1.0)));
    }
}

#[cfg(test)]
mod direction_tests {
    use super::*;
    use std::collections::HashMap;

    #[test]
    fn unit_and_direction_from_name() {
        assert_eq!(unit_of("ljar_mbs").unwrap().name, "mbs");
        assert_eq!(unit_of("unzip_mbs").unwrap().direction, MetricDirection::High);
        assert_eq!(unit_of("mb_per_sec").unwrap().name, "mbs");
        assert_eq!(unit_of("holger_ops_sec").unwrap().name, "ops_sec");
        assert_eq!(unit_of("decode_ms").unwrap().direction, MetricDirection::Low);
        assert_eq!(unit_of("seconds").unwrap().direction, MetricDirection::Low);
        assert_eq!(unit_of("speedup_x").unwrap().direction, MetricDirection::Neutral);
        // plain counts have no unit → never bolded
        assert!(unit_of("bytes").is_none());
        assert!(unit_of("files").is_none());
    }

    #[test]
    fn explicit_override_wins() {
        let mut o = HashMap::new();
        o.insert("weird_metric".to_string(), MetricDirection::Low);
        assert_eq!(direction_of(&o, "weird_metric"), MetricDirection::Low);
        // unlisted → suffix fallback
        assert_eq!(direction_of(&o, "ljar_mbs"), MetricDirection::High);
        assert_eq!(direction_of(&o, "count"), MetricDirection::Neutral);
    }
}

/// `bench history-show` rendered as a uniform [`nornir_cli_outcome::CommandOutcome`]
/// — the shared DATA FACE for the CLI (`run_bench`) and, by parity, the viz Bench
/// tab. `runs` arrive from the canonical source (warehouse `query_bench_runs` in fat
/// mode, the `Bench.History` RPC in thin mode); this fn only SHAPES + renders them,
/// it does not fetch (CommandOutcome is presentation, never a transport). Newest
/// first, optional `limit` cap. `ok ⟺ ≥1 run` (RAGNARÖK: an empty history is RED,
/// never silently green). AUT9 — see `.nornir/cli-command-contract.md`.
pub fn history_outcome(
    repo: &str,
    mut runs: Vec<BenchRun>,
    limit: usize,
) -> nornir_cli_outcome::CommandOutcome {
    use nornir_cli_outcome::CommandOutcome;
    // Newest first (timestamp if present, else date), then optional cap.
    runs.sort_by(|x, y| {
        let kx = x.timestamp.as_deref().unwrap_or(&x.date);
        let ky = y.timestamp.as_deref().unwrap_or(&y.date);
        ky.cmp(kx)
    });
    if limit > 0 {
        runs.truncate(limit);
    }
    if runs.is_empty() {
        return CommandOutcome::fail(
            "bench history-show",
            format!("no bench runs recorded for `{repo}`"),
        );
    }
    // The elaborate per-run summary the CLI used to print inline (now shared).
    let mut human = format!("{repo} — {} run(s)", runs.len());
    for r in &runs {
        let date = if r.date.is_empty() { "-" } else { r.date.as_str() };
        let machine = if r.machine.is_empty() { "-" } else { r.machine.as_str() };
        human.push_str(&format!("\n\nv{} · {} · {} cores · {}", r.version, machine, r.cores, date));
        let mut scalars: Vec<String> = Vec::new();
        for res in &r.results {
            for (k, v) in &res.metrics {
                if let Some(f) = v.as_f64() {
                    scalars.push(format!("{}.{k}={f:.2}", res.name));
                }
            }
        }
        scalars.sort();
        if scalars.is_empty() {
            human.push_str("\n  metrics: (none)");
        } else {
            human.push_str(&format!("\n  metrics: {}", scalars.join("  ")));
        }
        if r.tests.is_empty() {
            human.push_str("\n  tests:   (none recorded)");
        } else {
            let passed = r.tests.iter().filter(|t| t.passed).count();
            let failed = r.tests.len() - passed;
            human.push_str(&format!("\n  tests:   {passed} passed, {failed} failed"));
            for t in r.tests.iter().filter(|t| !t.passed) {
                let msg = t.message.as_deref().unwrap_or("");
                human.push_str(&format!("\n           ✗ {} {}", t.name, msg));
            }
        }
    }
    let data = serde_json::json!({ "repo": repo, "runs": runs });
    CommandOutcome::ok("bench history-show", data, human)
}

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

    fn sample_run(date: &str, machine: &str) -> BenchRun {
        BenchRun {
            id: crate::Uuid::nil(),
            machine_facts: None,
            date: date.to_string(),
            timestamp: Some(format!("{date}T00:00:00Z")),
            version: "0.1.0".into(),
            machine: machine.into(),
            cores: 8,
            results: Vec::new(),
            tests: Vec::new(),
        }
    }

    #[test]
    fn empty_history_is_red_not_silently_green() {
        let o = history_outcome("holger", Vec::new(), 0);
        assert_eq!(o.command, "bench history-show");
        assert!(!o.is_sannr(), "empty bench history must be RED (RAGNARÖK)");
        assert!(o.human.contains("no bench runs"));
    }

    #[test]
    fn real_runs_are_sannr_newest_first_and_limited() {
        let runs = vec![sample_run("2026-06-01", "ryzen"), sample_run("2026-06-03", "epyc")];
        let o = history_outcome("holger", runs, 1);
        assert!(o.is_sannr(), "a real run is a true (sannr) outcome");
        let arr = o.data["runs"].as_array().unwrap();
        assert_eq!(arr.len(), 1, "limit=1 truncates");
        assert_eq!(arr[0]["machine"], serde_json::json!("epyc"), "newest (2026-06-03) first");
        assert_eq!(o.data["repo"], serde_json::json!("holger"));
    }
}

/// Identity and machine facts on the envelope — the R6 contract.
#[cfg(test)]
mod identity_tests {
    use super::*;

    /// HISTORY COMPAT, the one that must never break: every line already in a
    /// `bench_history.jsonl` predates `id` and `machine_facts`. It must still
    /// parse, and it must report `has_id() == false` so a consumer falls back
    /// to the heuristic join instead of deduplicating on a nil id (which every
    /// legacy line shares and which would collapse them all into one).
    #[test]
    fn a_legacy_line_without_an_id_still_parses_and_says_it_has_none() {
        let legacy = r#"{"date":"2026-05-27","version":"0.6.4","machine":"ryzen-9-7900","cores":32,"results":[{"name":"rust_crate_st","ops_sec":420072}]}"#;
        let run: BenchRun = serde_json::from_str(legacy).unwrap();
        assert!(run.id.is_nil());
        assert!(!run.has_id(), "a legacy line must NOT claim an identity");
        assert!(run.machine_facts.is_none());
        assert_eq!(run.results.len(), 1);
    }

    /// The id is v7 — time-ordered. Two ids minted in sequence must compare in
    /// that order, which is the property that makes the id sort like the date.
    #[test]
    fn the_id_is_v7_and_sorts_in_mint_order() {
        let a = BenchRun::new_id();
        std::thread::sleep(std::time::Duration::from_millis(2));
        let b = BenchRun::new_id();
        assert_eq!(a.get_version_num(), 7, "must be v7, not v4");
        assert_eq!(b.get_version_num(), 7);
        assert!(a < b, "v7 ids must sort in mint order: {a} !< {b}");
    }

    /// A run made by the producer carries a real id and real machine facts, and
    /// both survive the JSON hop the receipt takes (bench binary → stdout →
    /// `bench_history.jsonl` → warehouse).
    #[test]
    fn a_produced_run_round_trips_its_id_and_machine_through_json() {
        let run = BenchRun {
            id: BenchRun::new_id(),
            date: "2026-09-20".into(),
            timestamp: Some("2026-09-20T10:00:00Z".into()),
            version: "1.2.3".into(),
            machine: "loki".into(),
            cores: 12,
            machine_facts: Some(machine::Machine::detect()),
            results: Vec::new(),
            tests: Vec::new(),
        };
        let s = serde_json::to_string(&run).unwrap();
        let back: BenchRun = serde_json::from_str(&s).unwrap();
        assert_eq!(back.id, run.id);
        assert!(back.has_id());
        assert_eq!(back.machine_facts, run.machine_facts);
        assert!(s.contains("\"id\":"), "the id must be ON the receipt: {s}");
    }
}