assay_core/mcp/
audit.rs

1use serde::Serialize;
2use serde_json::Value;
3use std::fs::OpenOptions;
4use std::io::Write;
5use std::path::Path;
6
7#[derive(Serialize)]
8pub struct AuditEvent {
9    pub timestamp: String, // ISO 8601
10    pub decision: String,  // "allow" | "deny" | "would_deny"
11    pub tool: Option<String>,
12    pub reason: Option<String>,
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub request_id: Option<Value>,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub agentic: Option<Value>, // The contract payload
17}
18
19pub struct AuditLog {
20    file: Option<std::fs::File>,
21}
22
23impl AuditLog {
24    pub fn new(path: Option<&Path>) -> Self {
25        let file = path.and_then(|p| OpenOptions::new().create(true).append(true).open(p).ok());
26        Self { file }
27    }
28
29    pub fn log(&mut self, event: &AuditEvent) {
30        if let Some(f) = &mut self.file {
31            if let Ok(json) = serde_json::to_string(event) {
32                writeln!(f, "{}", json).ok();
33            }
34        }
35    }
36}