Skip to main content

harness_core/
permissions.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::future::Future;
4use std::pin::Pin;
5use std::sync::Arc;
6
7/// Hook decision. Mirrors `PermissionDecision` in the TS harness-core.
8/// Autonomous tools treat `Ask` as `Deny` at the tool boundary.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum PermissionDecision {
12    Allow,
13    AllowOnce,
14    Deny,
15    Ask,
16}
17
18/// Inputs handed to the permission hook. Shape-compatible with the TS
19/// `PermissionQuery` (slightly relaxed: `metadata` is a free-form JSON
20/// object since different tools carry different fields).
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct PermissionQuery {
23    pub tool: String,
24    pub path: String,
25    pub action: String,
26    pub always_patterns: Vec<String>,
27    #[serde(default)]
28    pub metadata: Value,
29}
30
31/// Pluggable hook surface. The `dyn` future keeps the trait
32/// object-safe so sessions can hold a boxed hook without committing to a
33/// specific async runtime in the trait.
34pub type PermissionHook = Arc<
35    dyn Fn(PermissionQuery) -> Pin<Box<dyn Future<Output = PermissionDecision> + Send>>
36        + Send
37        + Sync,
38>;
39
40/// Session-scoped permission policy. Mirrors `PermissionPolicy` in TS:
41/// workspace `roots`, `sensitive_patterns` for deny lists, optional
42/// `hook` for allow/deny decisions, and a `bypass_workspace_guard`
43/// escape hatch for callers that own their own fence.
44#[derive(Clone)]
45pub struct PermissionPolicy {
46    pub roots: Vec<String>,
47    pub sensitive_patterns: Vec<String>,
48    pub hook: Option<PermissionHook>,
49    pub bypass_workspace_guard: bool,
50}
51
52impl PermissionPolicy {
53    pub fn new(roots: impl IntoIterator<Item = String>) -> Self {
54        Self {
55            roots: roots.into_iter().collect(),
56            sensitive_patterns: Vec::new(),
57            hook: None,
58            bypass_workspace_guard: false,
59        }
60    }
61
62    pub fn with_sensitive_patterns(
63        mut self,
64        patterns: impl IntoIterator<Item = String>,
65    ) -> Self {
66        self.sensitive_patterns = patterns.into_iter().collect();
67        self
68    }
69}
70
71impl std::fmt::Debug for PermissionPolicy {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        f.debug_struct("PermissionPolicy")
74            .field("roots", &self.roots)
75            .field("sensitive_patterns", &self.sensitive_patterns)
76            .field("has_hook", &self.hook.is_some())
77            .field("bypass_workspace_guard", &self.bypass_workspace_guard)
78            .finish()
79    }
80}