Skip to main content

cairn_runtime/
policy.rs

1use serde_json::Value;
2
3use crate::tool::{Effect, ToolSpec};
4
5#[derive(Debug, Clone, PartialEq)]
6pub enum Decision {
7    Allow,
8    Deny { reason: String },
9    RequireApproval { reason: String },
10}
11
12/// Decides, before execution, whether a tool call may run.
13pub trait Policy: Send + Sync {
14    fn decide(&self, spec: &ToolSpec, input: &Value) -> Decision;
15}
16
17impl<F> Policy for F
18where
19    F: Fn(&ToolSpec, &Value) -> Decision + Send + Sync,
20{
21    fn decide(&self, spec: &ToolSpec, input: &Value) -> Decision {
22        self(spec, input)
23    }
24}
25
26/// Requires human approval for every call at or above `approve_at`.
27/// The default gates irreversible tools.
28#[derive(Debug, Clone)]
29pub struct EffectPolicy {
30    pub approve_at: Option<Effect>,
31}
32
33impl Default for EffectPolicy {
34    fn default() -> Self {
35        Self { approve_at: Some(Effect::Irreversible) }
36    }
37}
38
39impl Policy for EffectPolicy {
40    fn decide(&self, spec: &ToolSpec, _input: &Value) -> Decision {
41        match self.approve_at {
42            Some(level) if spec.effect >= level => {
43                Decision::RequireApproval { reason: format!("`{}` has {:?} effect", spec.name, spec.effect) }
44            }
45            _ => Decision::Allow,
46        }
47    }
48}
49
50pub struct AllowAll;
51
52impl Policy for AllowAll {
53    fn decide(&self, _spec: &ToolSpec, _input: &Value) -> Decision {
54        Decision::Allow
55    }
56}