wifi-densepose/vendor/ruvector/crates/cognitum-gate-tilezero/src/replay.rs

393 lines
12 KiB
Rust

//! Deterministic replay for auditing and debugging
//!
//! This module provides the ability to replay gate decisions for audit purposes,
//! ensuring that the same inputs produce the same outputs deterministically.
use crate::{GateDecision, WitnessReceipt, WitnessSummary};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// Result of replaying a decision
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReplayResult {
/// The replayed decision
pub decision: GateDecision,
/// Whether the replay matched the original
pub matched: bool,
/// Original decision from receipt
pub original_decision: GateDecision,
/// State snapshot at decision time
pub state_snapshot: WitnessSummary,
/// Differences if any
pub differences: Vec<ReplayDifference>,
}
/// A difference found during replay
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReplayDifference {
/// Field that differs
pub field: String,
/// Original value
pub original: String,
/// Replayed value
pub replayed: String,
}
/// Snapshot of state for replay
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StateSnapshot {
/// Sequence number
pub sequence: u64,
/// Timestamp
pub timestamp: u64,
/// Global min-cut value
pub global_min_cut: f64,
/// Aggregate e-value
pub aggregate_e_value: f64,
/// Minimum coherence
pub min_coherence: i16,
/// Tile states
pub tile_states: HashMap<u8, TileSnapshot>,
}
/// Snapshot of a single tile's state
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TileSnapshot {
/// Tile ID
pub tile_id: u8,
/// Coherence
pub coherence: i16,
/// E-value
pub e_value: f32,
/// Boundary edge count
pub boundary_edges: usize,
}
/// Engine for replaying decisions
pub struct ReplayEngine {
/// Checkpoints for state restoration
checkpoints: HashMap<u64, StateSnapshot>,
/// Checkpoint interval
checkpoint_interval: u64,
}
impl ReplayEngine {
/// Create a new replay engine
pub fn new(checkpoint_interval: u64) -> Self {
Self {
checkpoints: HashMap::new(),
checkpoint_interval,
}
}
/// Save a checkpoint
pub fn save_checkpoint(&mut self, sequence: u64, snapshot: StateSnapshot) {
if sequence % self.checkpoint_interval == 0 {
self.checkpoints.insert(sequence, snapshot);
}
}
/// Find the nearest checkpoint before a sequence
pub fn find_nearest_checkpoint(&self, sequence: u64) -> Option<(u64, &StateSnapshot)> {
self.checkpoints
.iter()
.filter(|(seq, _)| **seq <= sequence)
.max_by_key(|(seq, _)| *seq)
.map(|(seq, snap)| (*seq, snap))
}
/// Replay a decision from a receipt
pub fn replay(&self, receipt: &WitnessReceipt) -> ReplayResult {
// Get the witness summary from the receipt
let summary = &receipt.witness_summary;
// Reconstruct the decision based on the witness data
let replayed_decision = self.reconstruct_decision(summary);
// Compare with original
let original_decision = receipt.token.decision;
let matched = replayed_decision == original_decision;
let mut differences = Vec::new();
if !matched {
differences.push(ReplayDifference {
field: "decision".to_string(),
original: format!("{:?}", original_decision),
replayed: format!("{:?}", replayed_decision),
});
}
ReplayResult {
decision: replayed_decision,
matched,
original_decision,
state_snapshot: summary.clone(),
differences,
}
}
/// Reconstruct decision from witness summary
fn reconstruct_decision(&self, summary: &WitnessSummary) -> GateDecision {
// Apply the same three-filter logic as in TileZero
// 1. Structural filter
if summary.structural.partition == "fragile" {
return GateDecision::Deny;
}
// 2. Evidence filter
if summary.evidential.verdict == "reject" {
return GateDecision::Deny;
}
if summary.evidential.verdict == "continue" {
return GateDecision::Defer;
}
// 3. Prediction filter
if summary.predictive.set_size > 20 {
return GateDecision::Defer;
}
GateDecision::Permit
}
/// Verify a sequence of receipts for consistency
pub fn verify_sequence(&self, receipts: &[WitnessReceipt]) -> SequenceVerification {
let mut results = Vec::new();
let mut all_matched = true;
for receipt in receipts {
let result = self.replay(receipt);
if !result.matched {
all_matched = false;
}
results.push((receipt.sequence, result));
}
SequenceVerification {
total_receipts: receipts.len(),
all_matched,
results,
}
}
/// Export checkpoint for external storage
pub fn export_checkpoint(&self, sequence: u64) -> Option<Vec<u8>> {
self.checkpoints
.get(&sequence)
.and_then(|snap| serde_json::to_vec(snap).ok())
}
/// Import checkpoint from external storage
pub fn import_checkpoint(&mut self, sequence: u64, data: &[u8]) -> Result<(), ReplayError> {
let snapshot: StateSnapshot =
serde_json::from_slice(data).map_err(|_| ReplayError::InvalidCheckpoint)?;
self.checkpoints.insert(sequence, snapshot);
Ok(())
}
/// Clear old checkpoints to manage memory
pub fn prune_before(&mut self, sequence: u64) {
self.checkpoints.retain(|seq, _| *seq >= sequence);
}
/// Get checkpoint count
pub fn checkpoint_count(&self) -> usize {
self.checkpoints.len()
}
}
impl Default for ReplayEngine {
fn default() -> Self {
Self::new(100)
}
}
/// Result of verifying a sequence of receipts
#[derive(Debug)]
pub struct SequenceVerification {
/// Total number of receipts verified
pub total_receipts: usize,
/// Whether all replays matched
pub all_matched: bool,
/// Individual results
pub results: Vec<(u64, ReplayResult)>,
}
impl SequenceVerification {
/// Get the mismatches
pub fn mismatches(&self) -> impl Iterator<Item = &(u64, ReplayResult)> {
self.results.iter().filter(|(_, r)| !r.matched)
}
/// Get mismatch count
pub fn mismatch_count(&self) -> usize {
self.results.iter().filter(|(_, r)| !r.matched).count()
}
}
/// Error during replay
#[derive(Debug, thiserror::Error)]
pub enum ReplayError {
#[error("Receipt not found for sequence {sequence}")]
ReceiptNotFound { sequence: u64 },
#[error("Checkpoint not found for sequence {sequence}")]
CheckpointNotFound { sequence: u64 },
#[error("Invalid checkpoint data")]
InvalidCheckpoint,
#[error("State reconstruction failed: {reason}")]
ReconstructionFailed { reason: String },
#[error("Hash chain verification failed at sequence {sequence}")]
ChainVerificationFailed { sequence: u64 },
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
EvidentialWitness, PermitToken, PredictiveWitness, StructuralWitness, TimestampProof,
};
fn create_test_receipt(sequence: u64, decision: GateDecision) -> WitnessReceipt {
WitnessReceipt {
sequence,
token: PermitToken {
decision,
action_id: format!("action-{}", sequence),
timestamp: 1000 + sequence,
ttl_ns: 60000,
witness_hash: [0u8; 32],
sequence,
signature: [0u8; 64],
},
previous_hash: [0u8; 32],
witness_summary: WitnessSummary {
structural: StructuralWitness {
cut_value: 10.0,
partition: "stable".to_string(),
critical_edges: 0,
boundary: vec![],
},
predictive: PredictiveWitness {
set_size: 5,
coverage: 0.9,
},
evidential: EvidentialWitness {
e_value: 100.0,
verdict: "accept".to_string(),
},
},
timestamp_proof: TimestampProof {
timestamp: 1000 + sequence,
previous_receipt_hash: [0u8; 32],
merkle_root: [0u8; 32],
},
}
}
#[test]
fn test_replay_matching() {
let engine = ReplayEngine::new(100);
let receipt = create_test_receipt(0, GateDecision::Permit);
let result = engine.replay(&receipt);
assert!(result.matched);
assert_eq!(result.decision, GateDecision::Permit);
}
#[test]
fn test_replay_mismatch() {
let engine = ReplayEngine::new(100);
let mut receipt = create_test_receipt(0, GateDecision::Permit);
// Modify the witness to indicate a deny condition
receipt.witness_summary.structural.partition = "fragile".to_string();
let result = engine.replay(&receipt);
assert!(!result.matched);
assert_eq!(result.decision, GateDecision::Deny);
assert!(!result.differences.is_empty());
}
#[test]
fn test_checkpoint_save_load() {
let mut engine = ReplayEngine::new(10);
let snapshot = StateSnapshot {
sequence: 0,
timestamp: 1000,
global_min_cut: 10.0,
aggregate_e_value: 100.0,
min_coherence: 256,
tile_states: HashMap::new(),
};
engine.save_checkpoint(0, snapshot.clone());
assert_eq!(engine.checkpoint_count(), 1);
let (seq, found) = engine.find_nearest_checkpoint(5).unwrap();
assert_eq!(seq, 0);
assert_eq!(found.global_min_cut, 10.0);
}
#[test]
fn test_sequence_verification() {
let engine = ReplayEngine::new(100);
let receipts = vec![
create_test_receipt(0, GateDecision::Permit),
create_test_receipt(1, GateDecision::Permit),
create_test_receipt(2, GateDecision::Permit),
];
let verification = engine.verify_sequence(&receipts);
assert_eq!(verification.total_receipts, 3);
assert!(verification.all_matched);
assert_eq!(verification.mismatch_count(), 0);
}
#[test]
fn test_prune_checkpoints() {
let mut engine = ReplayEngine::new(10);
for i in (0..100).step_by(10) {
let snapshot = StateSnapshot {
sequence: i as u64,
timestamp: 1000 + i as u64,
global_min_cut: 10.0,
aggregate_e_value: 100.0,
min_coherence: 256,
tile_states: HashMap::new(),
};
engine.save_checkpoint(i as u64, snapshot);
}
assert_eq!(engine.checkpoint_count(), 10);
engine.prune_before(50);
assert_eq!(engine.checkpoint_count(), 5);
}
#[test]
fn test_checkpoint_export_import() {
let mut engine = ReplayEngine::new(10);
let snapshot = StateSnapshot {
sequence: 0,
timestamp: 1000,
global_min_cut: 10.0,
aggregate_e_value: 100.0,
min_coherence: 256,
tile_states: HashMap::new(),
};
engine.save_checkpoint(0, snapshot);
let exported = engine.export_checkpoint(0).unwrap();
let mut engine2 = ReplayEngine::new(10);
engine2.import_checkpoint(0, &exported).unwrap();
assert_eq!(engine2.checkpoint_count(), 1);
}
}