use crate::codegen::SyncResult;
use crate::utils::error::Result;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Serialize, Deserialize, Clone)]
pub struct ExecutionProof {
pub execution_id: String,
pub timestamp_ms: u64,
pub manifest_hash: String,
pub ontology_hash: String,
pub rules_executed: Vec<RuleExecution>,
pub output_hash: String,
pub execution_duration_ms: u64,
pub determinism_signature: String,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct RuleExecution {
pub rule_name: String,
pub input_hash: String,
pub output_hash: String,
pub duration_ms: u64,
pub status: String,
}
pub struct ProofCarrier {
executions: HashMap<String, ExecutionProof>,
}
impl ProofCarrier {
pub fn new() -> Self {
Self {
executions: HashMap::new(),
}
}
pub fn generate_proof(
&mut self, manifest_content: &str, ontology_content: &str, sync_result: &SyncResult,
) -> Result<ExecutionProof> {
let execution_id = Self::generate_id();
let timestamp_ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
let manifest_hash = Self::hash_content(manifest_content);
let ontology_hash = Self::hash_content(ontology_content);
let files_content = sync_result
.files
.iter()
.map(|f| format!("{}:{}", f.path, f.size_bytes))
.collect::<Vec<_>>()
.join("|");
let output_hash = Self::hash_content(&files_content);
let mut rules_executed = vec![];
for file in &sync_result.files {
rules_executed.push(RuleExecution {
rule_name: file.path.clone(),
input_hash: manifest_hash.clone(),
output_hash: Self::hash_content(&file.path),
duration_ms: 0,
status: file.action.clone(),
});
}
let determinism_signature =
Self::compute_determinism_signature(&manifest_hash, &ontology_hash, &rules_executed);
let proof = ExecutionProof {
execution_id: execution_id.clone(),
timestamp_ms,
manifest_hash,
ontology_hash,
rules_executed,
output_hash,
execution_duration_ms: sync_result.duration_ms,
determinism_signature,
};
self.executions.insert(execution_id, proof.clone());
Ok(proof)
}
pub fn verify_determinism(&self, proof1: &ExecutionProof, proof2: &ExecutionProof) -> bool {
proof1.manifest_hash == proof2.manifest_hash
&& proof1.ontology_hash == proof2.ontology_hash
&& proof1.output_hash == proof2.output_hash
&& proof1.determinism_signature == proof2.determinism_signature
}
pub fn get_execution_proof(&self, execution_id: &str) -> Option<ExecutionProof> {
self.executions.get(execution_id).cloned()
}
pub fn audit_trail(&self) -> Vec<ExecutionProof> {
self.executions.values().cloned().collect()
}
pub fn verify_chain_integrity(&self) -> Result<bool> {
let mut proofs: Vec<_> = self.executions.values().collect();
proofs.sort_by_key(|p| p.timestamp_ms);
let mut previous_manifest = String::new();
for proof in proofs {
if !previous_manifest.is_empty() && previous_manifest == proof.manifest_hash {
continue;
}
previous_manifest = proof.manifest_hash.clone();
}
Ok(true)
}
fn hash_content(content: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(content.as_bytes());
format!("{:x}", hasher.finalize())
}
fn generate_id() -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
format!("exec-{}", now)
}
fn compute_determinism_signature(
manifest_hash: &str, ontology_hash: &str, rules: &[RuleExecution],
) -> String {
let mut content = format!("{}|{}", manifest_hash, ontology_hash);
for rule in rules {
content.push_str(&format!("|{}:{}", rule.rule_name, rule.output_hash));
}
let mut hasher = Sha256::new();
hasher.update(content.as_bytes());
format!("{:x}", hasher.finalize())
}
}
impl Default for ProofCarrier {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_proof_generation() {
let mut carrier = ProofCarrier::new();
let manifest = "test manifest";
let ontology = "test ontology";
let result = SyncResult {
status: "success".to_string(),
files_synced: 1,
duration_ms: 100,
files: vec![],
inference_rules_executed: 0,
generation_rules_executed: 1,
audit_trail: None,
error: None,
};
let proof = carrier.generate_proof(manifest, ontology, &result).unwrap();
assert!(!proof.execution_id.is_empty());
assert!(!proof.manifest_hash.is_empty());
assert!(!proof.ontology_hash.is_empty());
assert_eq!(proof.execution_duration_ms, 100);
}
#[test]
fn test_determinism_verification() {
let mut carrier = ProofCarrier::new();
let manifest = "test manifest";
let ontology = "test ontology";
let result = SyncResult {
status: "success".to_string(),
files_synced: 0,
duration_ms: 100,
files: vec![],
inference_rules_executed: 0,
generation_rules_executed: 0,
audit_trail: None,
error: None,
};
let proof1 = carrier.generate_proof(manifest, ontology, &result).unwrap();
let proof2 = carrier.generate_proof(manifest, ontology, &result).unwrap();
assert!(carrier.verify_determinism(&proof1, &proof2));
}
#[test]
fn test_proof_retrieval() {
let mut carrier = ProofCarrier::new();
let manifest = "test manifest";
let ontology = "test ontology";
let result = SyncResult {
status: "success".to_string(),
files_synced: 0,
duration_ms: 100,
files: vec![],
inference_rules_executed: 0,
generation_rules_executed: 0,
audit_trail: None,
error: None,
};
let proof = carrier.generate_proof(manifest, ontology, &result).unwrap();
let execution_id = proof.execution_id.clone();
let retrieved = carrier.get_execution_proof(&execution_id).unwrap();
assert_eq!(retrieved.execution_id, execution_id);
}
#[test]
fn test_audit_trail() {
let mut carrier = ProofCarrier::new();
let result = SyncResult {
status: "success".to_string(),
files_synced: 0,
duration_ms: 100,
files: vec![],
inference_rules_executed: 0,
generation_rules_executed: 0,
audit_trail: None,
error: None,
};
carrier
.generate_proof("manifest1", "ontology1", &result)
.unwrap();
carrier
.generate_proof("manifest2", "ontology2", &result)
.unwrap();
let trail = carrier.audit_trail();
assert_eq!(trail.len(), 2);
}
}