use alloc::{string::String, vec::Vec};
use urge_core::decision::Verdict;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct AuditEntry {
pub seq: u64,
pub timestamp_ns: u64,
pub expression: String,
pub permitted: bool,
pub confidence: u8,
pub paradigm_count: u8,
pub conflicts: u8,
pub formal_notation: String,
#[cfg(feature = "serde")]
pub trace_json: Option<String>,
pub correlation_id: Option<String>,
}
pub struct AuditLog {
entries: Vec<AuditEntry>,
seq: u64,
}
impl AuditLog {
pub fn new() -> Self {
AuditLog {
entries: Vec::new(),
seq: 0,
}
}
pub fn record(
&mut self,
expression: &str,
verdict: &Verdict,
timestamp_ns: u64,
correlation_id: Option<&str>,
) -> u64 {
let seq = self.seq;
self.seq += 1;
self.entries.push(AuditEntry {
seq,
timestamp_ns,
expression: expression.into(),
permitted: verdict.valid,
confidence: verdict.confidence.0,
paradigm_count: urge_core::engine::Paradigm::ALL
.iter()
.filter(|&&p| verdict.paradigms_evaluated.contains(p))
.count() as u8,
conflicts: verdict.cross_validation.conflicts_detected,
formal_notation: verdict.formal_notation.clone(),
#[cfg(feature = "serde")]
trace_json: None, correlation_id: correlation_id.map(Into::into),
});
seq
}
pub fn entries_since(&self, after_seq: u64) -> &[AuditEntry] {
let start = self
.entries
.iter()
.position(|e| e.seq > after_seq)
.unwrap_or(self.entries.len());
&self.entries[start..]
}
pub fn total_entries(&self) -> u64 {
self.seq
}
pub fn permitted_count(&self) -> usize {
self.entries.iter().filter(|e| e.permitted).count()
}
pub fn denied_count(&self) -> usize {
self.entries.iter().filter(|e| !e.permitted).count()
}
#[cfg(feature = "serde")]
pub fn to_ndjson(&self) -> String {
self.entries
.iter()
.filter_map(|e| serde_json::to_string(e).ok())
.collect::<Vec<_>>()
.join("\n")
}
}
impl Default for AuditLog {
fn default() -> Self {
Self::new()
}
}