1use alloc::{string::String, vec::Vec};
8use urge_core::decision::Verdict;
9
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone)]
15#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
16pub struct AuditEntry {
17 pub seq: u64,
19 pub timestamp_ns: u64,
21 pub expression: String,
23 pub permitted: bool,
25 pub confidence: u8,
27 pub paradigm_count: u8,
29 pub conflicts: u8,
31 pub formal_notation: String,
33 #[cfg(feature = "serde")]
35 pub trace_json: Option<String>,
36 pub correlation_id: Option<String>,
38}
39
40pub struct AuditLog {
42 entries: Vec<AuditEntry>,
43 seq: u64,
44}
45
46impl AuditLog {
47 pub fn new() -> Self {
48 AuditLog {
49 entries: Vec::new(),
50 seq: 0,
51 }
52 }
53
54 pub fn record(
56 &mut self,
57 expression: &str,
58 verdict: &Verdict,
59 timestamp_ns: u64,
60 correlation_id: Option<&str>,
61 ) -> u64 {
62 let seq = self.seq;
63 self.seq += 1;
64
65 self.entries.push(AuditEntry {
66 seq,
67 timestamp_ns,
68 expression: expression.into(),
69 permitted: verdict.valid,
70 confidence: verdict.confidence.0,
71 paradigm_count: urge_core::engine::Paradigm::ALL
72 .iter()
73 .filter(|&&p| verdict.paradigms_evaluated.contains(p))
74 .count() as u8,
75 conflicts: verdict.cross_validation.conflicts_detected,
76 formal_notation: verdict.formal_notation.clone(),
77 #[cfg(feature = "serde")]
78 trace_json: None, correlation_id: correlation_id.map(Into::into),
80 });
81
82 seq
83 }
84
85 pub fn entries_since(&self, after_seq: u64) -> &[AuditEntry] {
87 let start = self
88 .entries
89 .iter()
90 .position(|e| e.seq > after_seq)
91 .unwrap_or(self.entries.len());
92 &self.entries[start..]
93 }
94
95 pub fn total_entries(&self) -> u64 {
96 self.seq
97 }
98 pub fn permitted_count(&self) -> usize {
99 self.entries.iter().filter(|e| e.permitted).count()
100 }
101 pub fn denied_count(&self) -> usize {
102 self.entries.iter().filter(|e| !e.permitted).count()
103 }
104
105 #[cfg(feature = "serde")]
107 pub fn to_ndjson(&self) -> String {
108 self.entries
109 .iter()
110 .filter_map(|e| serde_json::to_string(e).ok())
111 .collect::<Vec<_>>()
112 .join("\n")
113 }
114}
115
116impl Default for AuditLog {
117 fn default() -> Self {
118 Self::new()
119 }
120}