1use crate::audit::{AuditLogger, SecurityEvent};
4use crate::score::RiskScorer;
5use std::sync::Arc;
6
7#[derive(Clone)]
9pub struct TRonQuery {
10 pub(crate) audit: Arc<AuditLogger>,
11}
12
13impl TRonQuery {
14 pub async fn recent_events(&self, limit: usize) -> Vec<SecurityEvent> {
16 self.audit.recent(limit).await
17 }
18
19 pub async fn agent_risk_score(&self, agent_id: &str) -> f64 {
21 RiskScorer::score(&self.audit, agent_id).await
22 }
23
24 pub async fn total_events(&self) -> usize {
26 self.audit.total_count().await
27 }
28
29 pub async fn total_denials(&self) -> usize {
31 self.audit.deny_count().await
32 }
33
34 pub async fn agent_audit(&self, agent_id: &str, limit: usize) -> Vec<SecurityEvent> {
36 self.audit.agent_events(agent_id, limit).await
37 }
38
39 pub fn verify_chain(&self) -> libro::Result<()> {
41 self.audit.verify_chain()
42 }
43
44 #[must_use]
46 pub fn chain_review(&self) -> libro::ChainReview {
47 self.audit.chain_review()
48 }
49
50 #[must_use]
52 pub fn chain_len(&self) -> usize {
53 self.audit.chain_len()
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use crate::{DefaultAction, TRon, TRonConfig};
60
61 fn permissive_config() -> TRonConfig {
62 TRonConfig {
63 default_unknown_agent: DefaultAction::Allow,
64 default_unknown_tool: DefaultAction::Allow,
65 scan_payloads: false,
66 analyze_patterns: false,
67 ..Default::default()
68 }
69 }
70
71 #[tokio::test]
72 async fn query_api_initial_state() {
73 let tron = TRon::new(TRonConfig::default());
74 let query = tron.query();
75 assert_eq!(query.total_events().await, 0);
76 assert_eq!(query.total_denials().await, 0);
77 assert!(query.recent_events(10).await.is_empty());
78 }
79
80 #[tokio::test]
81 async fn query_after_checks() {
82 let tron = TRon::new(permissive_config());
83 let query = tron.query();
84
85 let call = crate::gate::ToolCall {
87 agent_id: "agent-1".to_string(),
88 tool_name: "tarang_probe".to_string(),
89 params: serde_json::json!({}),
90 timestamp: chrono::Utc::now(),
91 };
92 for _ in 0..5 {
93 tron.check(&call).await;
94 }
95
96 assert_eq!(query.total_events().await, 5);
97 assert_eq!(query.total_denials().await, 0);
98
99 let events = query.recent_events(3).await;
100 assert_eq!(events.len(), 3);
101 }
102
103 #[tokio::test]
104 async fn query_risk_score_after_denials() {
105 let tron = TRon::new(TRonConfig::default());
106 let query = tron.query();
107
108 let call = crate::gate::ToolCall {
110 agent_id: "bad-agent".to_string(),
111 tool_name: "anything".to_string(),
112 params: serde_json::json!({}),
113 timestamp: chrono::Utc::now(),
114 };
115 for _ in 0..5 {
116 let v = tron.check(&call).await;
117 assert!(v.is_denied());
118 }
119
120 assert_eq!(query.total_denials().await, 5);
121 assert_eq!(query.agent_risk_score("bad-agent").await, 1.0);
122 assert_eq!(query.agent_risk_score("nobody").await, 0.0);
123 }
124
125 #[tokio::test]
126 async fn query_agent_audit_trail() {
127 let tron = TRon::new(TRonConfig::default());
128 let query = tron.query();
129
130 let call_a = crate::gate::ToolCall {
132 agent_id: "agent-a".to_string(),
133 tool_name: "tool".to_string(),
134 params: serde_json::json!({}),
135 timestamp: chrono::Utc::now(),
136 };
137 let call_b = crate::gate::ToolCall {
138 agent_id: "agent-b".to_string(),
139 tool_name: "tool".to_string(),
140 params: serde_json::json!({}),
141 timestamp: chrono::Utc::now(),
142 };
143 for _ in 0..3 {
144 tron.check(&call_a).await;
145 }
146 for _ in 0..7 {
147 tron.check(&call_b).await;
148 }
149
150 let trail_a = query.agent_audit("agent-a", 100).await;
151 let trail_b = query.agent_audit("agent-b", 100).await;
152 assert_eq!(trail_a.len(), 3);
153 assert_eq!(trail_b.len(), 7);
154
155 assert_eq!(query.agent_audit("agent-b", 2).await.len(), 2);
157 }
158
159 #[tokio::test]
160 async fn query_chain_verification() {
161 let tron = TRon::new(permissive_config());
162 let query = tron.query();
163
164 let call = crate::gate::ToolCall {
165 agent_id: "agent-1".to_string(),
166 tool_name: "tool".to_string(),
167 params: serde_json::json!({}),
168 timestamp: chrono::Utc::now(),
169 };
170 for _ in 0..10 {
171 tron.check(&call).await;
172 }
173
174 assert!(query.verify_chain().is_ok());
175 assert_eq!(query.chain_len(), 10);
176
177 let review = query.chain_review();
178 assert_eq!(review.entry_count, 10);
179 }
180}