aivcs_core/sandbox/
engine.rs1use super::policy::ToolPolicySet;
4use super::request::{PolicyVerdict, ToolRequest};
5
6pub fn evaluate_tool_request(policy: &ToolPolicySet, request: &ToolRequest) -> PolicyVerdict {
12 for rule in &policy.rules {
13 if rule.matches(&request.requesting_role, &request.capability) {
14 return rule.verdict();
15 }
16 }
17
18 PolicyVerdict::Denied {
20 reason: format!(
21 "no policy rule matched role={} capability={:?}",
22 request.requesting_role, request.capability,
23 ),
24 }
25}
26
27#[cfg(test)]
28mod tests {
29 use super::*;
30 use crate::role_orchestration::roles::AgentRole;
31 use crate::sandbox::capability::ToolCapability;
32 use crate::sandbox::policy::{ToolPolicyRule, ToolPolicySet};
33
34 fn make_request(role: AgentRole, capability: ToolCapability) -> ToolRequest {
35 ToolRequest {
36 tool_name: "test_tool".into(),
37 capability,
38 params: serde_json::Value::Null,
39 requesting_role: role,
40 }
41 }
42
43 #[test]
44 fn test_default_deny_when_no_rules() {
45 let policy = ToolPolicySet::empty();
46 let req = make_request(AgentRole::Coder, ToolCapability::ShellExec);
47 let v = evaluate_tool_request(&policy, &req);
48 assert!(!v.is_allowed());
49 }
50
51 #[test]
52 fn test_first_match_wins() {
53 let policy = ToolPolicySet::empty()
55 .with_rule(ToolPolicyRule::Deny {
56 role: AgentRole::Coder,
57 capability: ToolCapability::ShellExec,
58 reason: "denied first".into(),
59 })
60 .with_rule(ToolPolicyRule::Allow {
61 role: AgentRole::Coder,
62 capability: ToolCapability::ShellExec,
63 });
64
65 let req = make_request(AgentRole::Coder, ToolCapability::ShellExec);
66 match evaluate_tool_request(&policy, &req) {
67 PolicyVerdict::Denied { reason } => assert!(reason.contains("denied first")),
68 other => panic!("expected Denied, got {:?}", other),
69 }
70 }
71
72 #[test]
73 fn test_standard_dev_coder_allowed_shell() {
74 let policy = ToolPolicySet::standard_dev();
75 let req = make_request(AgentRole::Coder, ToolCapability::ShellExec);
76 assert!(evaluate_tool_request(&policy, &req).is_allowed());
77 }
78
79 #[test]
80 fn test_standard_dev_reviewer_denied_shell() {
81 let policy = ToolPolicySet::standard_dev();
82 let req = make_request(AgentRole::Reviewer, ToolCapability::ShellExec);
83 assert!(!evaluate_tool_request(&policy, &req).is_allowed());
84 }
85
86 #[test]
87 fn test_standard_dev_all_roles_denied_http_fetch() {
88 let policy = ToolPolicySet::standard_dev();
89 for role in &[
90 AgentRole::Planner,
91 AgentRole::Coder,
92 AgentRole::Reviewer,
93 AgentRole::Tester,
94 AgentRole::Fixer,
95 ] {
96 let req = make_request(role.clone(), ToolCapability::NetworkFetch);
97 assert!(
98 !evaluate_tool_request(&policy, &req).is_allowed(),
99 "expected HttpFetch denied for {role}"
100 );
101 }
102 }
103
104 #[test]
105 fn test_require_approval_verdict() {
106 let policy = ToolPolicySet::empty().with_rule(ToolPolicyRule::RequireApproval {
107 role: AgentRole::Coder,
108 capability: ToolCapability::NetworkFetch,
109 reason: "network access needs approval".into(),
110 });
111 let req = make_request(AgentRole::Coder, ToolCapability::NetworkFetch);
112 match evaluate_tool_request(&policy, &req) {
113 PolicyVerdict::RequiresApproval { reason } => {
114 assert!(reason.contains("network access"));
115 }
116 other => panic!("expected RequiresApproval, got {:?}", other),
117 }
118 }
119}