1use crate::error::{Result, EthIdError};
2use crate::verifier::VerificationResult;
3use serde::{Deserialize, Serialize};
4use std::path::{Path, PathBuf};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct AuditEntry {
8 pub session_id: String,
9 pub timestamp: chrono::DateTime<chrono::Utc>,
10 pub document_path: String,
11 pub document_hash: String,
12 pub claim: String,
13 pub result: bool,
14 pub confidence: f32,
15 pub proof_type: String,
16}
17
18pub struct AuditLog;
19
20impl AuditLog {
21 pub fn new() -> Self {
22 Self
23 }
24
25 pub fn record_verification(
26 &self,
27 session_id: &str,
28 document_path: &Path,
29 claim: &str,
30 result: &VerificationResult,
31 use_zk: bool,
32 ) -> Result<()> {
33 let entry = AuditEntry {
34 session_id: session_id.to_string(),
35 timestamp: chrono::Utc::now(),
36 document_path: document_path.display().to_string(),
37 document_hash: self.hash_file(document_path)?,
38 claim: claim.to_string(),
39 result: result.answer,
40 confidence: result.confidence,
41 proof_type: if use_zk { "zk".to_string() } else { "llm".to_string() },
42 };
43
44 self.append_entry(&entry)?;
45
46 Ok(())
47 }
48
49 fn append_entry(&self, entry: &AuditEntry) -> Result<()> {
50 let log_path = self.get_log_path()?;
51
52 let mut entries = if log_path.exists() {
53 let content = std::fs::read_to_string(&log_path)?;
54 serde_json::from_str::<Vec<AuditEntry>>(&content)
55 .unwrap_or_else(|_| Vec::new())
56 } else {
57 Vec::new()
58 };
59
60 entries.push(entry.clone());
61
62 let json = serde_json::to_string_pretty(&entries)?;
63 std::fs::write(&log_path, json)?;
64
65 tracing::debug!("Audit entry recorded: {}", entry.session_id);
66
67 Ok(())
68 }
69
70 pub fn list_entries(&self) -> Result<Vec<AuditEntry>> {
71 let log_path = self.get_log_path()?;
72
73 if !log_path.exists() {
74 return Ok(Vec::new());
75 }
76
77 let content = std::fs::read_to_string(&log_path)?;
78 let entries = serde_json::from_str(&content)?;
79
80 Ok(entries)
81 }
82
83 pub fn get_entry(&self, session_id: &str) -> Result<AuditEntry> {
84 let entries = self.list_entries()?;
85
86 entries.into_iter()
87 .find(|e| e.session_id == session_id)
88 .ok_or_else(|| EthIdError::AuditLog(
89 format!("Session not found: {}", session_id)
90 ))
91 }
92
93 fn get_log_path(&self) -> Result<PathBuf> {
94 let home_dir = dirs::home_dir()
95 .ok_or_else(|| EthIdError::AuditLog("Cannot find home directory".to_string()))?;
96
97 let audit_dir = home_dir.join(".eth-id").join("audit");
98 std::fs::create_dir_all(&audit_dir)?;
99
100 Ok(audit_dir.join("audit.json"))
101 }
102
103 fn hash_file(&self, path: &Path) -> Result<String> {
104 use sha2::{Sha256, Digest};
105
106 let content = std::fs::read(path)?;
107 let mut hasher = Sha256::new();
108 hasher.update(&content);
109 Ok(format!("{:x}", hasher.finalize()))
110 }
111}