Skip to main content

deepstrike_core/governance/
audit.rs

1use compact_str::CompactString;
2
3use crate::types::message::ToolCall;
4use crate::types::policy::GovernanceVerdict;
5
6/// A single audit log entry.
7#[derive(Debug, Clone)]
8pub struct AuditEntry {
9    pub tool_name: CompactString,
10    pub call_id: CompactString,
11    pub verdict: &'static str,
12    pub stage: Option<&'static str>,
13    pub reason: Option<String>,
14    pub timestamp_ms: u64,
15}
16
17/// In-memory audit log for governance decisions.
18pub struct AuditLog {
19    entries: Vec<AuditEntry>,
20    current_time_ms: u64,
21}
22
23impl AuditLog {
24    pub fn new() -> Self {
25        Self {
26            entries: Vec::new(),
27            current_time_ms: 0,
28        }
29    }
30
31    pub fn set_time(&mut self, now_ms: u64) {
32        self.current_time_ms = now_ms;
33    }
34
35    pub fn record_allow(&mut self, call: &ToolCall) {
36        self.entries.push(AuditEntry {
37            tool_name: call.name.clone(),
38            call_id: call.id.clone(),
39            verdict: "allow",
40            stage: None,
41            reason: None,
42            timestamp_ms: self.current_time_ms,
43        });
44    }
45
46    pub fn record_deny(&mut self, call: &ToolCall, verdict: &GovernanceVerdict) {
47        let (stage, reason) = match verdict {
48            GovernanceVerdict::Deny { stage, reason } => (Some(*stage), Some(reason.clone())),
49            GovernanceVerdict::RateLimited { retry_after_ms } => (
50                Some("rate_limit"),
51                Some(format!("retry after {}ms", retry_after_ms)),
52            ),
53            GovernanceVerdict::AskUser { reason } => (Some("permission"), Some(reason.clone())),
54            GovernanceVerdict::Allow => (None, None),
55        };
56        self.entries.push(AuditEntry {
57            tool_name: call.name.clone(),
58            call_id: call.id.clone(),
59            verdict: "deny",
60            stage,
61            reason,
62            timestamp_ms: self.current_time_ms,
63        });
64    }
65
66    pub fn entries(&self) -> &[AuditEntry] {
67        &self.entries
68    }
69
70    pub fn len(&self) -> usize {
71        self.entries.len()
72    }
73
74    pub fn is_empty(&self) -> bool {
75        self.entries.is_empty()
76    }
77
78    pub fn clear(&mut self) {
79        self.entries.clear();
80    }
81}
82
83impl Default for AuditLog {
84    fn default() -> Self {
85        Self::new()
86    }
87}