1use crate::merkle::Hash;
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6use uuid::Uuid;
7
8#[cfg(feature = "algoswitch")]
9use vex_algoswitch as algoswitch;
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
13pub enum AuditEventType {
14 AgentCreated,
15 AgentExecuted,
16 DebateStarted,
17 DebateRound,
18 DebateConcluded,
19 ConsensusReached,
20 ContextStored,
21 PaymentInitiated,
22 PaymentCompleted,
23 PolicyUpdate,
25 ModelUpgrade,
26 AnomalousBehavior,
27 HumanOverride,
28 Custom(String),
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
33pub enum ActorType {
34 Bot(Uuid),
36 Human(String),
38 #[default]
40 System,
41}
42
43impl ActorType {
44 pub fn pseudonymize(&self) -> Self {
46 match self {
47 Self::Human(id) => {
48 use sha2::{Digest, Sha256};
49 let mut hasher = Sha256::new();
50 hasher.update(id.as_bytes());
51 Self::Human(hex::encode(hasher.finalize()))
52 }
53 other => other.clone(),
54 }
55 }
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
60pub struct Signature {
61 pub signer_id: String,
62 pub signed_at: DateTime<Utc>,
63 pub signature_hex: String,
64}
65
66impl Signature {
67 pub fn create(
68 signer_id: impl Into<String>,
69 message: &[u8],
70 signing_key: &ed25519_dalek::SigningKey,
71 ) -> Self {
72 use ed25519_dalek::Signer;
73 let signature = signing_key.sign(message);
74
75 Self {
76 signer_id: signer_id.into(),
77 signed_at: Utc::now(),
78 signature_hex: hex::encode(signature.to_bytes()),
79 }
80 }
81
82 pub fn verify(
83 &self,
84 message: &[u8],
85 verifying_key: &ed25519_dalek::VerifyingKey,
86 ) -> Result<bool, String> {
87 let sig_bytes = match hex::decode(&self.signature_hex) {
88 Ok(bytes) => bytes,
89 Err(_) => return Ok(false),
90 };
91
92 let sig_array: [u8; 64] = match sig_bytes.try_into() {
93 Ok(arr) => arr,
94 Err(_) => return Ok(false),
95 };
96
97 let signature = ed25519_dalek::Signature::from_bytes(&sig_array);
98
99 match verifying_key.verify_strict(message, &signature) {
100 Ok(()) => Ok(true),
101 Err(e) => Err(format!("Signature verification failed: {}", e)),
102 }
103 }
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct AuditEvent {
109 pub id: Uuid,
110 pub event_type: AuditEventType,
111 pub timestamp: DateTime<Utc>,
112 pub agent_id: Option<Uuid>,
113 pub data: serde_json::Value,
114 pub hash: Hash,
115 pub previous_hash: Option<Hash>,
116 pub sequence_number: u64,
117
118 pub actor: ActorType,
120 pub rationale: Option<String>,
121 pub policy_version: Option<String>,
122 pub data_provenance_hash: Option<Hash>,
123 pub human_review_required: bool,
124 pub approval_signatures: Vec<Signature>,
125}
126
127pub struct HashParams<'a> {
129 pub event_type: &'a AuditEventType,
130 pub timestamp: chrono::DateTime<Utc>,
131 pub sequence_number: u64,
132 pub data: &'a serde_json::Value,
133 pub actor: &'a ActorType,
134 pub rationale: &'a Option<String>,
135 pub policy_version: &'a Option<String>,
136 pub data_provenance_hash: &'a Option<Hash>,
137 pub human_review_required: bool,
138 pub approval_count: usize,
139}
140
141impl AuditEvent {
142 const SENSITIVE_FIELDS: &'static [&'static str] = &[
144 "password",
145 "secret",
146 "token",
147 "api_key",
148 "apikey",
149 "key",
150 "authorization",
151 "auth",
152 "credential",
153 "private_key",
154 "privatekey",
155 ];
156
157 pub fn new(
159 event_type: AuditEventType,
160 agent_id: Option<Uuid>,
161 data: serde_json::Value,
162 sequence_number: u64,
163 ) -> Self {
164 let id = Uuid::new_v4();
165 let timestamp = Utc::now();
166
167 let data = Self::sanitize_data(data);
169
170 let actor = ActorType::System;
172 let rationale: Option<String> = None;
173 let policy_version: Option<String> = None;
174 let data_provenance_hash: Option<Hash> = None;
175 let human_review_required = false;
176 let approval_signatures: Vec<Signature> = Vec::new();
177
178 let hash = Self::compute_hash(HashParams {
180 event_type: &event_type,
181 timestamp,
182 sequence_number,
183 data: &data,
184 actor: &actor,
185 rationale: &rationale,
186 policy_version: &policy_version,
187 data_provenance_hash: &data_provenance_hash,
188 human_review_required,
189 approval_count: approval_signatures.len(),
190 });
191
192 Self {
193 id,
194 event_type,
195 timestamp,
196 agent_id,
197 data,
198 hash,
199 previous_hash: None,
200 sequence_number,
201 actor,
202 rationale,
203 policy_version,
204 data_provenance_hash,
205 human_review_required,
206 approval_signatures,
207 }
208 }
209
210 pub fn sanitize_data(value: serde_json::Value) -> serde_json::Value {
212 match value {
213 serde_json::Value::Object(mut map) => {
214 for key in map.keys().cloned().collect::<Vec<_>>() {
215 let lower_key = key.to_lowercase();
216 if Self::SENSITIVE_FIELDS.iter().any(|f| lower_key.contains(f)) {
217 map.insert(key, serde_json::Value::String("[REDACTED]".to_string()));
218 } else if let Some(v) = map.remove(&key) {
219 map.insert(key, Self::sanitize_data(v));
220 }
221 }
222 serde_json::Value::Object(map)
223 }
224 serde_json::Value::Array(arr) => {
225 serde_json::Value::Array(arr.into_iter().map(Self::sanitize_data).collect())
226 }
227 other => other,
228 }
229 }
230
231 pub fn chained(
233 event_type: AuditEventType,
234 agent_id: Option<Uuid>,
235 data: serde_json::Value,
236 previous_hash: Hash,
237 sequence_number: u64,
238 ) -> Self {
239 let mut event = Self::new(event_type, agent_id, data, sequence_number);
240 event.previous_hash = Some(previous_hash.clone());
241 event.hash = Self::compute_chained_hash(&event.hash, &previous_hash, sequence_number);
243 event
244 }
245
246 pub fn compute_hash(params: HashParams) -> Hash {
247 let content = format!(
248 "{:?}:{}:{}:{:?}:{:?}:{:?}:{:?}:{:?}:{}:{}",
249 params.event_type,
250 params.timestamp.timestamp(),
251 params.sequence_number,
252 params.data,
253 params.actor,
254 params.rationale,
255 params.policy_version,
256 params.data_provenance_hash.as_ref().map(|h| h.to_hex()),
257 params.human_review_required,
258 params.approval_count,
259 );
260 Hash::digest(content.as_bytes())
261 }
262
263 pub fn compute_chained_hash(base_hash: &Hash, prev_hash: &Hash, sequence: u64) -> Hash {
264 let content = format!("{}:{}:{}", base_hash, prev_hash, sequence);
265 Hash::digest(content.as_bytes())
266 }
267
268 #[cfg(feature = "algoswitch")]
270 pub fn compute_optimized_hash(params: HashParams) -> u64 {
271 let content = format!(
272 "{:?}:{}:{}:{:?}:{:?}:{:?}:{}",
273 params.event_type,
274 params.timestamp.timestamp(),
275 params.sequence_number,
276 params.data,
277 params.actor,
278 params.rationale,
279 params.approval_count,
280 );
281 algoswitch::select_hash(content.as_bytes()).0
282 }
283}