1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum PolicyDecision {
12 Allow,
14 Deny(String),
16 RequiresApproval(String),
18 RateLimited { retry_after_secs: u64 },
20}
21
22impl PolicyDecision {
23 pub fn is_allowed(&self) -> bool {
25 matches!(self, PolicyDecision::Allow)
26 }
27
28 pub fn label(&self) -> &'static str {
30 match self {
31 PolicyDecision::Allow => "allow",
32 PolicyDecision::Deny(_) => "deny",
33 PolicyDecision::RequiresApproval(_) => "requires_approval",
34 PolicyDecision::RateLimited { .. } => "rate_limited",
35 }
36 }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
41#[serde(rename_all = "snake_case")]
42pub enum TrustTier {
43 VerifiedPartner,
45 Trusted,
47 Standard,
49 Probationary,
51 Untrusted,
53}
54
55impl TrustTier {
56 pub fn from_score(score: u32) -> Self {
58 match score {
59 900..=1000 => TrustTier::VerifiedPartner,
60 700..=899 => TrustTier::Trusted,
61 500..=699 => TrustTier::Standard,
62 300..=499 => TrustTier::Probationary,
63 _ => TrustTier::Untrusted,
64 }
65 }
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct TrustScore {
71 pub agent_id: String,
72 pub score: u32,
73 pub tier: TrustTier,
74 pub interactions: u64,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct AuditEntry {
80 pub seq: u64,
81 pub timestamp: String,
82 pub agent_id: String,
83 pub action: String,
84 pub decision: String,
85 pub previous_hash: String,
86 pub hash: String,
87}
88
89#[derive(Debug, Default, Serialize, Deserialize)]
91pub struct AuditFilter {
92 pub agent_id: Option<String>,
93 pub action: Option<String>,
94 pub decision: Option<String>,
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
99#[serde(rename_all = "snake_case")]
100pub enum ConflictResolutionStrategy {
101 DenyOverrides,
103 AllowOverrides,
105 #[default]
107 PriorityFirstMatch,
108 MostSpecificWins,
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
114#[serde(rename_all = "snake_case")]
115pub enum PolicyScope {
116 #[default]
118 Global,
119 Tenant,
121 Agent,
123}
124
125impl PolicyScope {
126 pub fn specificity(self) -> u32 {
128 match self {
129 PolicyScope::Global => 0,
130 PolicyScope::Tenant => 1,
131 PolicyScope::Agent => 2,
132 }
133 }
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct CandidateDecision {
139 pub decision: PolicyDecision,
140 pub priority: u32,
141 pub scope: PolicyScope,
142 pub rule_name: String,
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct ResolutionResult {
148 pub winning_decision: PolicyDecision,
149 pub strategy_used: ConflictResolutionStrategy,
150 pub conflict_detected: bool,
151 pub candidates_evaluated: usize,
152}
153
154#[derive(Debug, Clone)]
156pub struct GovernanceResult {
157 pub decision: PolicyDecision,
158 pub trust_score: TrustScore,
159 pub audit_entry: AuditEntry,
160 pub allowed: bool,
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_policy_decision_is_allowed_allow() {
169 assert!(PolicyDecision::Allow.is_allowed());
170 }
171
172 #[test]
173 fn test_policy_decision_is_allowed_deny() {
174 assert!(!PolicyDecision::Deny("reason".to_string()).is_allowed());
175 }
176
177 #[test]
178 fn test_policy_decision_is_allowed_requires_approval() {
179 assert!(!PolicyDecision::RequiresApproval("reason".to_string()).is_allowed());
180 }
181
182 #[test]
183 fn test_policy_decision_is_allowed_rate_limited() {
184 assert!(!PolicyDecision::RateLimited {
185 retry_after_secs: 10
186 }
187 .is_allowed());
188 }
189
190 #[test]
191 fn test_policy_decision_label_allow() {
192 assert_eq!(PolicyDecision::Allow.label(), "allow");
193 }
194
195 #[test]
196 fn test_policy_decision_label_deny() {
197 assert_eq!(PolicyDecision::Deny("x".to_string()).label(), "deny");
198 }
199
200 #[test]
201 fn test_policy_decision_label_requires_approval() {
202 assert_eq!(
203 PolicyDecision::RequiresApproval("x".to_string()).label(),
204 "requires_approval"
205 );
206 }
207
208 #[test]
209 fn test_policy_decision_label_rate_limited() {
210 assert_eq!(
211 PolicyDecision::RateLimited {
212 retry_after_secs: 5
213 }
214 .label(),
215 "rate_limited"
216 );
217 }
218
219 #[test]
220 fn test_trust_tier_boundary_0() {
221 assert_eq!(TrustTier::from_score(0), TrustTier::Untrusted);
222 }
223
224 #[test]
225 fn test_trust_tier_boundary_299() {
226 assert_eq!(TrustTier::from_score(299), TrustTier::Untrusted);
227 }
228
229 #[test]
230 fn test_trust_tier_boundary_300() {
231 assert_eq!(TrustTier::from_score(300), TrustTier::Probationary);
232 }
233
234 #[test]
235 fn test_trust_tier_boundary_499() {
236 assert_eq!(TrustTier::from_score(499), TrustTier::Probationary);
237 }
238
239 #[test]
240 fn test_trust_tier_boundary_500() {
241 assert_eq!(TrustTier::from_score(500), TrustTier::Standard);
242 }
243
244 #[test]
245 fn test_trust_tier_boundary_699() {
246 assert_eq!(TrustTier::from_score(699), TrustTier::Standard);
247 }
248
249 #[test]
250 fn test_trust_tier_boundary_700() {
251 assert_eq!(TrustTier::from_score(700), TrustTier::Trusted);
252 }
253
254 #[test]
255 fn test_trust_tier_boundary_899() {
256 assert_eq!(TrustTier::from_score(899), TrustTier::Trusted);
257 }
258
259 #[test]
260 fn test_trust_tier_boundary_900() {
261 assert_eq!(TrustTier::from_score(900), TrustTier::VerifiedPartner);
262 }
263
264 #[test]
265 fn test_trust_tier_boundary_1000() {
266 assert_eq!(TrustTier::from_score(1000), TrustTier::VerifiedPartner);
267 }
268
269 #[test]
270 fn test_trust_score_serialization_roundtrip() {
271 let score = TrustScore {
272 agent_id: "agent-1".to_string(),
273 score: 750,
274 tier: TrustTier::Trusted,
275 interactions: 42,
276 };
277 let json = serde_json::to_string(&score).unwrap();
278 let deserialized: TrustScore = serde_json::from_str(&json).unwrap();
279 assert_eq!(deserialized.agent_id, "agent-1");
280 assert_eq!(deserialized.score, 750);
281 assert_eq!(deserialized.tier, TrustTier::Trusted);
282 assert_eq!(deserialized.interactions, 42);
283 }
284
285 #[test]
286 fn test_audit_entry_serialization_roundtrip() {
287 let entry = AuditEntry {
288 seq: 0,
289 timestamp: "2025-01-01T00:00:00Z".to_string(),
290 agent_id: "agent-1".to_string(),
291 action: "data.read".to_string(),
292 decision: "allow".to_string(),
293 previous_hash: "".to_string(),
294 hash: "abc123".to_string(),
295 };
296 let json = serde_json::to_string(&entry).unwrap();
297 let deserialized: AuditEntry = serde_json::from_str(&json).unwrap();
298 assert_eq!(deserialized.seq, 0);
299 assert_eq!(deserialized.agent_id, "agent-1");
300 assert_eq!(deserialized.action, "data.read");
301 assert_eq!(deserialized.decision, "allow");
302 assert_eq!(deserialized.hash, "abc123");
303 }
304
305 #[test]
306 fn test_policy_decision_serialization_allow() {
307 let d = PolicyDecision::Allow;
308 let json = serde_json::to_string(&d).unwrap();
309 let back: PolicyDecision = serde_json::from_str(&json).unwrap();
310 assert_eq!(back, PolicyDecision::Allow);
311 }
312
313 #[test]
314 fn test_policy_decision_serialization_deny() {
315 let d = PolicyDecision::Deny("blocked".to_string());
316 let json = serde_json::to_string(&d).unwrap();
317 let back: PolicyDecision = serde_json::from_str(&json).unwrap();
318 assert_eq!(back, PolicyDecision::Deny("blocked".to_string()));
319 }
320
321 #[test]
322 fn test_policy_decision_serialization_requires_approval() {
323 let d = PolicyDecision::RequiresApproval("needs review".to_string());
324 let json = serde_json::to_string(&d).unwrap();
325 let back: PolicyDecision = serde_json::from_str(&json).unwrap();
326 assert_eq!(
327 back,
328 PolicyDecision::RequiresApproval("needs review".to_string())
329 );
330 }
331
332 #[test]
333 fn test_policy_decision_serialization_rate_limited() {
334 let d = PolicyDecision::RateLimited {
335 retry_after_secs: 30,
336 };
337 let json = serde_json::to_string(&d).unwrap();
338 let back: PolicyDecision = serde_json::from_str(&json).unwrap();
339 assert_eq!(
340 back,
341 PolicyDecision::RateLimited {
342 retry_after_secs: 30
343 }
344 );
345 }
346
347 #[test]
348 fn test_governance_result_fields_populated() {
349 let result = GovernanceResult {
350 allowed: true,
351 decision: PolicyDecision::Allow,
352 trust_score: TrustScore {
353 agent_id: "agent-1".to_string(),
354 score: 500,
355 tier: TrustTier::Standard,
356 interactions: 0,
357 },
358 audit_entry: AuditEntry {
359 seq: 0,
360 timestamp: "2025-01-01T00:00:00Z".to_string(),
361 agent_id: "agent-1".to_string(),
362 action: "test".to_string(),
363 decision: "allow".to_string(),
364 previous_hash: "".to_string(),
365 hash: "abc".to_string(),
366 },
367 };
368 assert!(result.allowed);
369 assert_eq!(result.decision, PolicyDecision::Allow);
370 assert_eq!(result.trust_score.agent_id, "agent-1");
371 assert_eq!(result.audit_entry.action, "test");
372 }
373
374 #[test]
375 fn test_audit_filter_default_has_all_none() {
376 let filter = AuditFilter::default();
377 assert!(filter.agent_id.is_none());
378 assert!(filter.action.is_none());
379 assert!(filter.decision.is_none());
380 }
381
382 #[test]
383 fn test_trust_tier_partial_eq() {
384 assert_eq!(TrustTier::Standard, TrustTier::Standard);
385 assert_ne!(TrustTier::Standard, TrustTier::Trusted);
386 assert_eq!(TrustTier::VerifiedPartner, TrustTier::VerifiedPartner);
387 assert_ne!(TrustTier::Untrusted, TrustTier::Probationary);
388 }
389}