Files
Aura/crates/aura-bench/src/surfaces/fixed_cost.rs
T
claude 9eb6d6b4f6 docs(readme, guides, glossary, ledger), bench: the surviving surface everywhere
Slice 8, closing the #319 retirement's prose and bench debt. Bench argv
rides exec (fixed_cost single-run probe, campaign surfaces, and the seed
helper that still called the retired sweep at runtime — caught by the
bench run itself); all five fingerprints unchanged against the committed
baselines, confirming record-line parity through the retirement. Live
docs (README, authoring guide, project layout, glossary) describe only
exec + campaign documents + graph introspect --params; three glossary
entries explicitly mark their verb retired. Ledger amendments per the C29
discipline — C25's executor-verb-set re-settled (exec + the --override
residue), C14's dual grammar reduced to the one dispatch, C24's
document-built runs/families + gated-intake route list (lockstep with the
code comment), C12's override clause and identity anchor repointed to the
runner-layer hash computation, C18/C22/C27/C01 mention rewrites —
superseded sentences moved verbatim to the history sidecars (c25's
created). Stale dual-grammar doc comment on is_blueprint_file rewritten.

refs #319
2026-07-25 22:45:24 +02:00

123 lines
5.3 KiB
Rust

//! CLI fixed cost: the spawn floor (`aura --help`, no fingerprint — nothing is
//! computed) and a minimal data-only project exec whose fingerprint is the
//! FNV-1a hash of the exec's single stdout JSON line. Fresh scratch project per
//! repetition, so store counters start identical.
use super::{median, RepOutcome};
use crate::child::run_timed;
use crate::surfaces::campaign::build_minimal_project;
use crate::synth::fnv1a;
use std::collections::BTreeMap;
use std::path::Path;
use std::time::Instant;
/// Fingerprint of an `aura exec` record line, invariant across rebuilds: the
/// line's `manifest.commit` is the aura binary's compile-time build sha
/// (crates/aura-cli/build.rs — the invariant-8 audit trail), so hashing the
/// raw line would flip the fingerprint on EVERY new commit and report a
/// phantom correctness change at each cycle close. Parse, blank that one
/// volatile field, re-serialize (serde_json's map is sorted — deterministic
/// bytes), then hash.
pub fn run_line_fingerprint(line: &str) -> Result<String, String> {
let mut v: serde_json::Value =
serde_json::from_str(line).map_err(|e| format!("run record line parses: {e}"))?;
let commit = v
.get_mut("manifest")
.and_then(|m| m.get_mut("commit"))
.ok_or("run record line carries manifest.commit")?;
*commit = serde_json::Value::String(String::new());
let canonical = serde_json::to_string(&v).expect("re-serializing a parsed Value cannot fail");
Ok(format!("run_line_fnv={:016x}", fnv1a(canonical.as_bytes())))
}
/// Spawn samples folded into one repetition: single-digit-millisecond spawns
/// jitter well past the 10% NOTICE threshold, so each repetition reports the
/// median over a batch instead of one throw of the scheduler dice.
pub const HELP_SAMPLES: u32 = 20;
pub const RUN_SAMPLES: u32 = 10;
pub fn rep(bin: &Path) -> Result<RepOutcome, String> {
// Spawn floor: median wall over a batch of `aura --help` spawns.
let mut help_walls = Vec::new();
for _ in 0..HELP_SAMPLES {
let t = Instant::now();
let help = std::process::Command::new(bin)
.arg("--help")
.output()
.map_err(|e| format!("spawn aura --help: {e}"))?;
help_walls.push(t.elapsed().as_secs_f64() * 1000.0);
if !help.status.success() {
return Err(format!("aura --help exited {:?}", help.status.code()));
}
}
// Project-load + minimal run: one scratch project, a batch of runs, the
// median wall. The fingerprint is the FIRST run's record line — later
// runs append to the same store, so their lines legitimately differ (run
// counters), while the first run of a fresh scratch is deterministic.
let scratch = build_minimal_project()?;
let mut run_walls = Vec::new();
let mut first_line = None;
for _ in 0..RUN_SAMPLES {
let timed = run_timed(bin, &["exec", "bench_sma_a.json"], &scratch.0)?;
if timed.exit != Some(0) {
return Err(format!("aura run exited {:?}: {}", timed.exit, timed.stdout));
}
run_walls.push(timed.wall_s * 1000.0);
if first_line.is_none() {
first_line = Some(
timed
.stdout
.lines()
.next()
.ok_or("aura run must print its JSON record line")?
.to_string(),
);
}
}
let line = first_line.expect("RUN_SAMPLES >= 1 sets the first line");
let mut metrics = BTreeMap::new();
metrics.insert("help_ms".to_string(), median(help_walls));
metrics.insert("run_ms".to_string(), median(run_walls));
Ok(RepOutcome { metrics, fingerprint: run_line_fingerprint(&line)? })
}
#[cfg(test)]
mod tests {
use super::*;
/// The symptom this fixes, pinned: two record lines that differ ONLY in
/// `manifest.commit` (a rebuild at a new commit) must fingerprint
/// identically — the raw-line hash flipped here and reported a phantom
/// correctness change on the first post-commit bench run.
#[test]
fn run_line_fingerprint_is_invariant_across_build_commits() {
let at_a = r#"{"manifest":{"commit":"aaaa","seed":0},"metrics":{"total_pips":0.5}}"#;
let at_b = r#"{"manifest":{"commit":"bbbb","seed":0},"metrics":{"total_pips":0.5}}"#;
assert_eq!(
run_line_fingerprint(at_a).expect("parses"),
run_line_fingerprint(at_b).expect("parses"),
);
}
/// The fingerprint still catches what it exists for: a computed-result
/// change (any non-provenance byte) yields a different hash.
#[test]
fn run_line_fingerprint_still_detects_result_changes() {
let base = r#"{"manifest":{"commit":"aaaa","seed":0},"metrics":{"total_pips":0.5}}"#;
let drifted = r#"{"manifest":{"commit":"aaaa","seed":0},"metrics":{"total_pips":0.6}}"#;
assert_ne!(
run_line_fingerprint(base).expect("parses"),
run_line_fingerprint(drifted).expect("parses"),
);
}
/// A line without the provenance field is malformed input, not a silent
/// pass — the guard that keeps the blanking honest if the record schema
/// ever moves.
#[test]
fn run_line_fingerprint_rejects_a_line_without_manifest_commit() {
assert!(run_line_fingerprint(r#"{"metrics":{"total_pips":0.5}}"#).is_err());
}
}