use eyre::Result; use crate::{permissions::file::RuleFile, tools::PermissibleToolCall}; pub(crate) struct PermissionRequest<'t> { call: &'t (dyn PermissibleToolCall + Send + Sync), } impl<'t> PermissionRequest<'t> { pub fn new(call: &'t (dyn PermissibleToolCall + Send + Sync)) -> Self { Self { call } } } pub(crate) enum PermissionResponse { Allowed, Denied, Ask, } pub(crate) struct PermissionChecker { files: Vec, } impl PermissionChecker { pub fn new(files: Vec) -> Self { Self { files } } pub async fn check<'t>( &self, request: &'t PermissionRequest<'t>, ) -> Result { // Files are in order from deepest to shallowest, so we can stop at the first match. // Within a file, the priority is ask -> deny -> allow // The first rule type that matches is the one that applies, even if a later rule would contradict it. for file in &self.files { for rule in &file.content.permissions.ask { if request.call.matches_rule(rule) { tracing::debug!( "Permission 'ASK' by rule: {} in file: {}", rule, file.path.display() ); return Ok(PermissionResponse::Ask); } } for rule in &file.content.permissions.deny { if request.call.matches_rule(rule) { tracing::debug!( "Permission 'DENY' by rule: {} in file: {}", rule, file.path.display() ); return Ok(PermissionResponse::Denied); } } if request.call.all_covered_by(&file.content.permissions.allow) { tracing::debug!( "Permission 'ALLOW' by rules in file: {}", file.path.display() ); return Ok(PermissionResponse::Allowed); } } Ok(PermissionResponse::Ask) } }