use futures::future::BoxFuture;
use serde_json::Value;
use super::permission::RiskLevel;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ApprovalScope {
Once,
Session,
SessionAllTools,
}
#[derive(Debug, Clone)]
pub struct ApprovalRule {
pub pattern: String,
pub risk_level: RiskLevel,
pub description: Option<String>,
}
impl ApprovalRule {
pub fn new(pattern: impl Into<String>) -> Self {
Self {
pattern: pattern.into(),
risk_level: RiskLevel::Medium,
description: None,
}
}
pub fn risk(mut self, level: RiskLevel) -> Self {
self.risk_level = level;
self
}
pub fn description(mut self, desc: impl Into<String>) -> Self {
self.description = Some(desc.into());
self
}
pub fn matches(&self, tool_name: &str) -> bool {
super::pattern::matches_tool_pattern(&self.pattern, tool_name)
}
}
#[derive(Debug, Clone)]
pub enum PolicyDecision {
AutoApprove {
reason: String,
},
RequireApproval {
risk_level: RiskLevel,
prompt: String,
},
AutoDeny {
reason: String,
},
}
pub trait ApprovalPolicy: Send + Sync {
fn evaluate<'a>(&'a self, tool_name: &'a str, args: &'a Value)
-> BoxFuture<'a, PolicyDecision>;
fn record_decision(&self, tool_name: &str, args: &Value, approved: bool, scope: ApprovalScope);
fn is_cached(&self, tool_name: &str, args: &Value) -> bool;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_risk_level_requires_approval() {
assert!(!RiskLevel::Low.requires_confirmation());
assert!(!RiskLevel::Medium.requires_confirmation());
assert!(RiskLevel::High.requires_confirmation());
assert!(RiskLevel::Critical.requires_confirmation());
}
#[test]
fn test_approval_rule_exact_match() {
let rule = ApprovalRule::new("Bash").risk(RiskLevel::High);
assert!(rule.matches("Bash"));
assert!(!rule.matches("Read"));
}
#[test]
fn test_approval_rule_wildcard() {
let rule = ApprovalRule::new("*").risk(RiskLevel::Medium);
assert!(rule.matches("Bash"));
assert!(rule.matches("Read"));
assert!(rule.matches("Anything"));
}
#[test]
fn test_approval_rule_prefix_match() {
let rule = ApprovalRule::new("Bash").risk(RiskLevel::High);
assert!(rule.matches("Bash(rm:*)"));
assert!(!rule.matches("BashExtra"));
}
}