ggen_core/codegen/
execution_proof.rs

1use crate::codegen::SyncResult;
2use ggen_utils::error::Result;
3use serde::{Deserialize, Serialize};
4use sha2::{Digest, Sha256};
5use std::collections::HashMap;
6use std::time::{SystemTime, UNIX_EPOCH};
7
8#[derive(Serialize, Deserialize, Clone)]
9pub struct ExecutionProof {
10    pub execution_id: String,
11    pub timestamp_ms: u64,
12    pub manifest_hash: String,
13    pub ontology_hash: String,
14    pub rules_executed: Vec<RuleExecution>,
15    pub output_hash: String,
16    pub execution_duration_ms: u64,
17    pub determinism_signature: String,
18}
19
20#[derive(Serialize, Deserialize, Clone)]
21pub struct RuleExecution {
22    pub rule_name: String,
23    pub input_hash: String,
24    pub output_hash: String,
25    pub duration_ms: u64,
26    pub status: String,
27}
28
29pub struct ProofCarrier {
30    executions: HashMap<String, ExecutionProof>,
31}
32
33impl ProofCarrier {
34    pub fn new() -> Self {
35        Self {
36            executions: HashMap::new(),
37        }
38    }
39
40    pub fn generate_proof(
41        &mut self, manifest_content: &str, ontology_content: &str, sync_result: &SyncResult,
42    ) -> Result<ExecutionProof> {
43        let execution_id = Self::generate_id();
44        let timestamp_ms = SystemTime::now()
45            .duration_since(UNIX_EPOCH)
46            .map(|d| d.as_millis() as u64)
47            .unwrap_or(0);
48
49        let manifest_hash = Self::hash_content(manifest_content);
50        let ontology_hash = Self::hash_content(ontology_content);
51
52        // Hash all output files
53        let files_content = sync_result
54            .files
55            .iter()
56            .map(|f| format!("{}:{}", f.path, f.size_bytes))
57            .collect::<Vec<_>>()
58            .join("|");
59        let output_hash = Self::hash_content(&files_content);
60
61        let mut rules_executed = vec![];
62        for file in &sync_result.files {
63            rules_executed.push(RuleExecution {
64                rule_name: file.path.clone(),
65                input_hash: manifest_hash.clone(),
66                output_hash: Self::hash_content(&file.path),
67                duration_ms: 0,
68                status: file.action.clone(),
69            });
70        }
71
72        // Create determinism signature
73        let determinism_signature =
74            Self::compute_determinism_signature(&manifest_hash, &ontology_hash, &rules_executed);
75
76        let proof = ExecutionProof {
77            execution_id: execution_id.clone(),
78            timestamp_ms,
79            manifest_hash,
80            ontology_hash,
81            rules_executed,
82            output_hash,
83            execution_duration_ms: sync_result.duration_ms,
84            determinism_signature,
85        };
86
87        self.executions.insert(execution_id, proof.clone());
88        Ok(proof)
89    }
90
91    pub fn verify_determinism(&self, proof1: &ExecutionProof, proof2: &ExecutionProof) -> bool {
92        // Two executions are deterministic if they produce identical output
93        // given the same manifest and ontology
94        proof1.manifest_hash == proof2.manifest_hash
95            && proof1.ontology_hash == proof2.ontology_hash
96            && proof1.output_hash == proof2.output_hash
97            && proof1.determinism_signature == proof2.determinism_signature
98    }
99
100    pub fn get_execution_proof(&self, execution_id: &str) -> Option<ExecutionProof> {
101        self.executions.get(execution_id).cloned()
102    }
103
104    pub fn audit_trail(&self) -> Vec<ExecutionProof> {
105        self.executions.values().cloned().collect()
106    }
107
108    pub fn verify_chain_integrity(&self) -> Result<bool> {
109        let mut proofs: Vec<_> = self.executions.values().collect();
110        proofs.sort_by_key(|p| p.timestamp_ms);
111
112        // Check that each execution is deterministic with the same inputs
113        let mut previous_manifest = String::new();
114
115        for proof in proofs {
116            // In a real implementation, we would verify that:
117            // 1. If manifest/ontology unchanged, output must be identical
118            // 2. Hashes form a consistent chain
119
120            if !previous_manifest.is_empty() && previous_manifest == proof.manifest_hash {
121                // Manifest unchanged, output should match previous
122                continue;
123            }
124
125            previous_manifest = proof.manifest_hash.clone();
126        }
127
128        Ok(true)
129    }
130
131    fn hash_content(content: &str) -> String {
132        let mut hasher = Sha256::new();
133        hasher.update(content.as_bytes());
134        format!("{:x}", hasher.finalize())
135    }
136
137    fn generate_id() -> String {
138        use std::time::{SystemTime, UNIX_EPOCH};
139        let now = SystemTime::now()
140            .duration_since(UNIX_EPOCH)
141            .unwrap()
142            .as_nanos();
143        format!("exec-{}", now)
144    }
145
146    fn compute_determinism_signature(
147        manifest_hash: &str, ontology_hash: &str, rules: &[RuleExecution],
148    ) -> String {
149        let mut content = format!("{}|{}", manifest_hash, ontology_hash);
150
151        for rule in rules {
152            content.push_str(&format!("|{}:{}", rule.rule_name, rule.output_hash));
153        }
154
155        let mut hasher = Sha256::new();
156        hasher.update(content.as_bytes());
157        format!("{:x}", hasher.finalize())
158    }
159}
160
161impl Default for ProofCarrier {
162    fn default() -> Self {
163        Self::new()
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    #[test]
172    fn test_proof_generation() {
173        let mut carrier = ProofCarrier::new();
174        let manifest = "test manifest";
175        let ontology = "test ontology";
176        let result = SyncResult {
177            status: "success".to_string(),
178            files_synced: 1,
179            duration_ms: 100,
180            files: vec![],
181            inference_rules_executed: 0,
182            generation_rules_executed: 1,
183            audit_trail: None,
184            error: None,
185        };
186
187        let proof = carrier.generate_proof(manifest, ontology, &result).unwrap();
188
189        assert!(!proof.execution_id.is_empty());
190        assert!(!proof.manifest_hash.is_empty());
191        assert!(!proof.ontology_hash.is_empty());
192        assert_eq!(proof.execution_duration_ms, 100);
193    }
194
195    #[test]
196    fn test_determinism_verification() {
197        let mut carrier = ProofCarrier::new();
198        let manifest = "test manifest";
199        let ontology = "test ontology";
200        let result = SyncResult {
201            status: "success".to_string(),
202            files_synced: 0,
203            duration_ms: 100,
204            files: vec![],
205            inference_rules_executed: 0,
206            generation_rules_executed: 0,
207            audit_trail: None,
208            error: None,
209        };
210
211        let proof1 = carrier.generate_proof(manifest, ontology, &result).unwrap();
212        let proof2 = carrier.generate_proof(manifest, ontology, &result).unwrap();
213
214        assert!(carrier.verify_determinism(&proof1, &proof2));
215    }
216
217    #[test]
218    fn test_proof_retrieval() {
219        let mut carrier = ProofCarrier::new();
220        let manifest = "test manifest";
221        let ontology = "test ontology";
222        let result = SyncResult {
223            status: "success".to_string(),
224            files_synced: 0,
225            duration_ms: 100,
226            files: vec![],
227            inference_rules_executed: 0,
228            generation_rules_executed: 0,
229            audit_trail: None,
230            error: None,
231        };
232
233        let proof = carrier.generate_proof(manifest, ontology, &result).unwrap();
234        let execution_id = proof.execution_id.clone();
235
236        let retrieved = carrier.get_execution_proof(&execution_id).unwrap();
237        assert_eq!(retrieved.execution_id, execution_id);
238    }
239
240    #[test]
241    fn test_audit_trail() {
242        let mut carrier = ProofCarrier::new();
243        let result = SyncResult {
244            status: "success".to_string(),
245            files_synced: 0,
246            duration_ms: 100,
247            files: vec![],
248            inference_rules_executed: 0,
249            generation_rules_executed: 0,
250            audit_trail: None,
251            error: None,
252        };
253
254        carrier
255            .generate_proof("manifest1", "ontology1", &result)
256            .unwrap();
257        carrier
258            .generate_proof("manifest2", "ontology2", &result)
259            .unwrap();
260
261        let trail = carrier.audit_trail();
262        assert_eq!(trail.len(), 2);
263    }
264}