Finding 4: full-trace evidence — reconstructed and re-derived, not summarized
Removes the deferral. Each sampled execution's FULL trace (every graph edge, count, weight) and delta are persisted as evidence; the independent attestor reconstructs them, recomputes the canonical trace hash and delta hash, re-derives the replay-record leaf, and confirms it is among the retained Merkle leaves the root is built from. The behavior-fingerprint hash is re-derived from features (not trusted), and f64 divergence is stored bit-exact for an identical hash. - trace_model: ExecutionTrace::serialize/deserialize (round-trips canonical_hash; total on garbage). Test: full_trace_serialize_roundtrips_canonical_hash. - world_model: WorldDelta::serialize/deserialize (round-trips hash). - ci_reports: retains TRACE_EVIDENCE_SAMPLE full traces; writes evidence/traces.tsv. - attestation: depends on trace_model/world_model; reconstructs each trace, recomputes the leaf, requires it to match the claimed leaf AND be a retained leaf. Negative control: tampered_trace_breaks_attestation (corrupting the full trace, leaving the claimed leaf, fails attestation). - merge-gates: requires evidence/traces.tsv; the separate attest step verifies it. End-to-end (fast profile): 256/256 full traces reconstructed and re-derived to retained leaves; recomputed root matches the claim over 6600 leaves. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -659,8 +659,24 @@ pub struct CiResults {
|
||||
/// Every Merkle leaf (per-execution replay hash) actually produced. Retained
|
||||
/// so the root can be independently recomputed from the leaves (finding 3).
|
||||
pub merkle_leaves: Vec<Hash>,
|
||||
/// A sample of FULL execution traces + deltas + seeds (finding 4). The
|
||||
/// attestor reconstructs each trace, recomputes its canonical hash and the
|
||||
/// replay-record leaf, and checks the leaf is in `merkle_leaves` — proving
|
||||
/// the leaves are backed by full trace structure, not a summary.
|
||||
pub trace_evidence: Vec<TraceEvidence>,
|
||||
}
|
||||
|
||||
/// One sampled full-trace evidence record.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TraceEvidence {
|
||||
pub replay: trace_model::ReplayRecord,
|
||||
pub trace: trace_model::ExecutionTrace,
|
||||
pub delta: world_model::WorldDelta,
|
||||
}
|
||||
|
||||
/// How many full traces to retain as evidence.
|
||||
pub const TRACE_EVIDENCE_SAMPLE: usize = 256;
|
||||
|
||||
impl CiResults {
|
||||
pub fn all_failures(&self) -> Vec<(&'static str, &Vec<String>)> {
|
||||
vec![
|
||||
@@ -746,6 +762,7 @@ pub fn run_all(scale: Scale) -> CiResults {
|
||||
// Provenance leaves: one per actually-committed execution (base + every
|
||||
// perturbation), so the Merkle root binds to all compared executions.
|
||||
let mut merkle_leaves: Vec<Hash> = Vec::with_capacity(scale.executions);
|
||||
let mut trace_evidence: Vec<TraceEvidence> = Vec::with_capacity(TRACE_EVIDENCE_SAMPLE);
|
||||
let mut actual_executions = 0usize;
|
||||
let mut worlds_generated = 0usize;
|
||||
let mut programs_generated = 0usize;
|
||||
@@ -870,6 +887,14 @@ 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).
|
||||
if trace_evidence.len() < TRACE_EVIDENCE_SAMPLE {
|
||||
trace_evidence.push(TraceEvidence {
|
||||
replay: r.replay,
|
||||
trace: r.trace.clone(),
|
||||
delta: r.delta.clone(),
|
||||
});
|
||||
}
|
||||
for (pinput, pref_canon, pleaf) in &pert_execs {
|
||||
let prut = rut.resolve(pinput.clone());
|
||||
equiv_total += 1;
|
||||
@@ -1212,6 +1237,7 @@ pub fn run_all(scale: Scale) -> CiResults {
|
||||
replay,
|
||||
coverage,
|
||||
merkle_leaves: retained_leaves,
|
||||
trace_evidence,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,28 @@ 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 <TAB> <full trace> <TAB> <full delta>
|
||||
// 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: header<TAB>trace<TAB>delta\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");
|
||||
}
|
||||
|
||||
fn build_reports(dir: &Path, r: &CiResults) {
|
||||
|
||||
Reference in New Issue
Block a user