Skip to main content

lean_ctx/cli/
audit_report.rs

1use std::collections::HashMap;
2
3use crate::core::audit_trail::{AuditEntry, AuditEventType};
4
5pub fn generate_report() -> String {
6    let entries = crate::core::audit_trail::load_recent(10000);
7    let chain = crate::core::audit_trail::verify_chain();
8
9    let mut report = String::new();
10    report.push_str("# lean-ctx Compliance Report\n\n");
11    report.push_str(&format!("Generated: {}\n", chrono::Utc::now().to_rfc3339()));
12    report.push_str(&format!("Audit Trail Entries: {}\n", entries.len()));
13    report.push_str(&format!(
14        "Chain Integrity: {}\n\n",
15        if chain.valid { "VALID" } else { "BROKEN" }
16    ));
17
18    let mut by_agent: HashMap<String, Vec<&AuditEntry>> = HashMap::new();
19    for e in &entries {
20        by_agent.entry(e.agent_id.clone()).or_default().push(e);
21    }
22
23    report.push_str("## Per-Agent Summary\n\n");
24    for (agent, agent_entries) in &by_agent {
25        let tool_calls = agent_entries
26            .iter()
27            .filter(|e| matches!(e.event_type, AuditEventType::ToolCall))
28            .count();
29        let denials = agent_entries
30            .iter()
31            .filter(|e| matches!(e.event_type, AuditEventType::ToolDenied))
32            .count();
33        report.push_str(&format!("### Agent: {agent}\n"));
34        report.push_str(&format!("- Tool calls: {tool_calls}\n"));
35        report.push_str(&format!("- Denials: {denials}\n\n"));
36    }
37
38    let security_events: Vec<_> = entries
39        .iter()
40        .filter(|e| !matches!(e.event_type, AuditEventType::ToolCall))
41        .collect();
42    report.push_str(&format!(
43        "## Security Events ({} total)\n\n",
44        security_events.len()
45    ));
46    for e in security_events.iter().take(50) {
47        report.push_str(&format!(
48            "- [{}] {:?} tool={} agent={}\n",
49            e.timestamp, e.event_type, e.tool, e.agent_id
50        ));
51    }
52
53    report.push_str("\n\n");
54    report.push_str(&crate::core::owasp_alignment::summary());
55
56    report
57}