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 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 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 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 let mut previous_manifest = String::new();
114
115 for proof in proofs {
116 if !previous_manifest.is_empty() && previous_manifest == proof.manifest_hash {
121 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}