use std::collections::VecDeque;
#[derive(Debug, Clone)]
pub struct DenialRecord {
pub tool_name: String,
pub tool_use_id: String,
pub reason: String,
pub timestamp: String,
pub input_summary: String,
}
pub struct DenialTracker {
records: VecDeque<DenialRecord>,
max_records: usize,
total_denials: usize,
}
impl DenialTracker {
pub fn new(max_records: usize) -> Self {
Self {
records: VecDeque::new(),
max_records,
total_denials: 0,
}
}
pub fn record(
&mut self,
tool_name: &str,
tool_use_id: &str,
reason: &str,
input: &serde_json::Value,
) {
let summary = summarize_input(tool_name, input);
self.records.push_back(DenialRecord {
tool_name: tool_name.to_string(),
tool_use_id: tool_use_id.to_string(),
reason: reason.to_string(),
timestamp: chrono::Utc::now().to_rfc3339(),
input_summary: summary,
});
self.total_denials += 1;
while self.records.len() > self.max_records {
self.records.pop_front();
}
}
pub fn denials(&self) -> &VecDeque<DenialRecord> {
&self.records
}
pub fn total(&self) -> usize {
self.total_denials
}
pub fn clear(&mut self) {
self.records.clear();
self.total_denials = 0;
}
pub fn denials_for_tool(&self, tool_name: &str) -> Vec<&DenialRecord> {
self.records
.iter()
.filter(|r| r.tool_name == tool_name)
.collect()
}
}
fn summarize_input(tool_name: &str, input: &serde_json::Value) -> String {
match tool_name {
"Bash" => input
.get("command")
.and_then(|v| v.as_str())
.unwrap_or("")
.chars()
.take(100)
.collect(),
"FileWrite" | "FileEdit" | "FileRead" => input
.get("file_path")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string(),
_ => serde_json::to_string(input)
.unwrap_or_default()
.chars()
.take(100)
.collect(),
}
}