use crate::trace::DecisionTrace;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReplayResult {
pub original_trace_id: Uuid,
pub replayed_trace_id: Uuid,
pub is_deterministic: bool,
pub divergences: Vec<Divergence>,
pub summary: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Divergence {
pub step: usize,
pub field: String,
pub original_value: String,
pub replayed_value: String,
}
pub fn compare_traces(original: &DecisionTrace, replayed: &DecisionTrace) -> ReplayResult {
let mut divergences = Vec::new();
if original.reasoning_steps.len() != replayed.reasoning_steps.len() {
divergences.push(Divergence {
step: 0,
field: "step_count".into(),
original_value: original.reasoning_steps.len().to_string(),
replayed_value: replayed.reasoning_steps.len().to_string(),
});
}
let min_steps = original
.reasoning_steps
.len()
.min(replayed.reasoning_steps.len());
for i in 0..min_steps {
let orig = &original.reasoning_steps[i];
let replay = &replayed.reasoning_steps[i];
if orig.output != replay.output {
divergences.push(Divergence {
step: i + 1,
field: "output".into(),
original_value: orig.output.clone(),
replayed_value: replay.output.clone(),
});
}
if (orig.confidence - replay.confidence).abs() > 0.01 {
divergences.push(Divergence {
step: i + 1,
field: "confidence".into(),
original_value: format!("{:.4}", orig.confidence),
replayed_value: format!("{:.4}", replay.confidence),
});
}
}
if original.output_summary != replayed.output_summary {
divergences.push(Divergence {
step: 0,
field: "output_summary".into(),
original_value: original.output_summary.clone(),
replayed_value: replayed.output_summary.clone(),
});
}
let is_deterministic = divergences.is_empty();
let summary = if is_deterministic {
"Replay verified: decision is deterministic".into()
} else {
format!("Replay found {} divergences", divergences.len())
};
ReplayResult {
original_trace_id: original.trace_id,
replayed_trace_id: replayed.trace_id,
is_deterministic,
divergences,
summary,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::trace::*;
use ucm_graph_core::entity::EntityId;
#[test]
fn test_deterministic_replay() {
let trigger = Uuid::now_v7();
let changed = vec![EntityId::local("src/auth.ts", "validate")];
let trace1 = trace_impact_analysis(trigger, 10, &changed, 2, 3, 5, 42);
let trace2 = trace_impact_analysis(trigger, 10, &changed, 2, 3, 5, 43);
let result = compare_traces(&trace1, &trace2);
assert!(result.is_deterministic);
}
#[test]
fn test_divergent_replay() {
let trigger = Uuid::now_v7();
let changed = vec![EntityId::local("src/auth.ts", "validate")];
let trace1 = trace_impact_analysis(trigger, 10, &changed, 2, 3, 5, 42);
let trace2 = trace_impact_analysis(trigger, 10, &changed, 3, 4, 3, 43);
let result = compare_traces(&trace1, &trace2);
assert!(!result.is_deterministic);
assert!(!result.divergences.is_empty());
}
}