diff --git a/crates/ci_reports/src/lib.rs b/crates/ci_reports/src/lib.rs index 114fde0..70cb18d 100644 --- a/crates/ci_reports/src/lib.rs +++ b/crates/ci_reports/src/lib.rs @@ -24,6 +24,7 @@ use reference_runtime::{ use runtime_under_test::{native_resolve, RuntimeUnderTest}; use semantic_mutation::{generate_mutants, DetectionClass, MutationOutcome}; use std::collections::HashMap; +use std::io::Write; use world_model::{ Hash, Hasher, TraceDifferenceExpectation, WorldSnapshot, HIDDEN_LANES, LANES, NUM_DOMAINS, }; @@ -703,6 +704,13 @@ impl CiResults { // --------------------------------------------------------------------------- pub fn run_all(scale: Scale) -> CiResults { + run_all_to(scale, None) +} + +/// Like [`run_all`] but streams the FULL per-execution trace evidence (every +/// base execution, not a sample) to `evidence_sink` as it runs, so the merge +/// profile can retain 100% full-trace coverage without holding it in memory. +pub fn run_all_to(scale: Scale, mut evidence_sink: Option<&mut dyn Write>) -> CiResults { let cfg = EngineConfig::reference(); let rut = RuntimeUnderTest::new(); @@ -887,7 +895,23 @@ pub fn run_all(scale: Scale) -> CiResults { equiv_failures.push(format!("case {} (base) reference != runtime_under_test", i)); } merkle_leaves.push(r.replay.hash()); - // Retain a sample of FULL traces as independent evidence (finding 4). + // Full-trace evidence (finding 4): stream EVERY base execution's full + // trace + delta to the sink (100% coverage, no sampling). A small + // in-memory sample is also kept for non-streaming callers/tests. + if let Some(w) = evidence_sink.as_deref_mut() { + let _ = writeln!( + w, + "{:016x} {:016x} {:016x} {:016x} {:016x} {:016x}\t{}\t{}", + r.replay.world_seed, + r.replay.program_seed, + r.replay.contract_seed, + r.replay.perturbation_seed, + r.replay.future_hash.0, + r.replay.hash().0, + r.trace.serialize(), + r.delta.serialize(), + ); + } if trace_evidence.len() < TRACE_EVIDENCE_SAMPLE { trace_evidence.push(TraceEvidence { replay: r.replay, diff --git a/crates/ci_reports/src/main.rs b/crates/ci_reports/src/main.rs index c2ccd56..e308829 100644 --- a/crates/ci_reports/src/main.rs +++ b/crates/ci_reports/src/main.rs @@ -4,7 +4,7 @@ //! to executed work, and exits nonzero if any gate fails. use ci_reports::json::Json; -use ci_reports::{run_all, CiResults, Profile, Scale}; +use ci_reports::{run_all_to, CiResults, Profile, Scale}; use std::fs; use std::io::Write; use std::path::Path; @@ -59,28 +59,9 @@ fn write_evidence(dir: &Path, r: &CiResults) { r.provenance.engines_agree, ); fs::write(ev.join("claims.tsv"), claims).expect("write claims"); - - // Full-trace evidence (finding 4): each line is - // ws ps cs prs future leaf - // The attestor reconstructs the trace + delta, recomputes the canonical - // trace hash and the replay-record leaf, and checks the leaf is among the - // retained leaves. This is the full trace, not a summary. - let mut traces = String::from("# full-trace evidence: headertracedelta\n"); - for ev_rec in &r.trace_evidence { - let rr = &ev_rec.replay; - traces.push_str(&format!( - "{:016x} {:016x} {:016x} {:016x} {:016x} {:016x}\t{}\t{}\n", - rr.world_seed, - rr.program_seed, - rr.contract_seed, - rr.perturbation_seed, - rr.future_hash.0, - rr.hash().0, - ev_rec.trace.serialize(), - ev_rec.delta.serialize(), - )); - } - fs::write(ev.join("traces.tsv"), traces).expect("write traces"); + // Note: evidence/traces.tsv (the FULL per-execution trace corpus) is streamed + // during the run in main(), covering 100% of base executions — not written + // here from a capped sample. } fn build_reports(dir: &Path, r: &CiResults) { @@ -632,7 +613,17 @@ fn main() { } let merge = scale.profile == Profile::Merge; let start = Instant::now(); - let results = run_all(scale); + // Stream full-trace evidence for 100% of base executions straight to disk. + let ev_dir = dir.join("evidence"); + fs::create_dir_all(&ev_dir).expect("create evidence dir"); + let traces_path = ev_dir.join("traces.tsv"); + let mut traces_w = std::io::BufWriter::new(fs::File::create(&traces_path).expect("create traces")); + traces_w + .write_all(b"# full-trace evidence (100% of base executions): headertracedelta\n") + .expect("write traces header"); + let results = run_all_to(scale, Some(&mut traces_w)); + traces_w.flush().expect("flush traces"); + drop(traces_w); let elapsed = start.elapsed(); build_reports(dir, &results);