// Audit: decision trail with ring buffer and JSON export.
//
// Modeled after OS /proc auditing: every Gate decision is recorded
// with timestamp, request details, verdict, and reason. The ring buffer
// provides bounded memory with automatic eviction of oldest entries.
///|
/// One recorded decision.
#derive(Eq, Debug)
pub struct AuditEntry {
timestamp : Int64
tool : String
action : String
resource : String
verdict : String
reason : String
}
///|
pub extend AuditEntry with Eq::{not_equal, equal}
///|
pub extend AuditEntry with @moonbitlang/core/debug.Debug::{to_repr}
///|
#derive(Eq, Debug)
pub struct AuditTrail {
entries : Array[AuditEntry]
max_size : Int32
}
///|
pub extend AuditTrail with Eq::{not_equal, equal}
///|
pub extend AuditTrail with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// Create a new AuditTrail with the given maximum size (ring buffer).
pub fn AuditTrail::new(max_size~ : Int32) -> AuditTrail {
{ entries: [], max_size, }
}
///|
/// Record a decision into the trail. Oldest entry is evicted when full.
pub fn AuditTrail::record(
self : AuditTrail,
tool : String,
action : String,
resource : String,
verdict : String,
reason : String,
) -> AuditTrail {
let entry = AuditEntry::new(tool, action, resource, verdict, reason)
if self.entries.length() >= self.max_size {
let mut trimmed : Array[AuditEntry] = []
for i = 1; i < self.entries.length(); i = i + 1 {
trimmed = trimmed + [self.entries[i]]
}
{ entries: trimmed + [entry], max_size: self.max_size, }
} else {
{ entries: self.entries + [entry], max_size: self.max_size, }
}
}
///|
/// Return all recorded entries.
pub fn AuditTrail::all(self : AuditTrail) -> Array[AuditEntry] {
self.entries
}
///|
/// Return the number of recorded entries.
pub fn AuditTrail::count(self : AuditTrail) -> Int32 {
self.entries.length()
}
///|
/// Create a new AuditEntry.
pub fn AuditEntry::new(
tool : String,
action : String,
resource : String,
verdict : String,
reason : String,
) -> AuditEntry {
let ts = current_timestamp()
{ timestamp: ts, tool, action, resource, verdict, reason, }
}
///|
/// Current time in milliseconds. In a real implementation this would use
/// system time; here it returns 0 for deterministic testing.
fn current_timestamp() -> Int64 {
0L
}