Skip to main content

defect_cli/
policy.rs

1//! Maps a [`SandboxMode`] to a concrete [`SandboxPolicy`] instance.
2
3use std::sync::Arc;
4
5use defect_agent::policy::{
6    AskWritesPolicy, DenyAllPolicy, ModeCatalog, OpenPolicy, PolicyMode, ReadOnlyPolicy,
7    SandboxPolicy,
8};
9use defect_config::SandboxMode;
10
11/// Selects the policy implementation based on `[sandbox].mode`.
12pub fn build_policy(mode: SandboxMode) -> Arc<dyn SandboxPolicy> {
13    match mode {
14        SandboxMode::ReadOnly => Arc::new(ReadOnlyPolicy),
15        SandboxMode::AskWrites => Arc::new(AskWritesPolicy::new()),
16        SandboxMode::Open => Arc::new(OpenPolicy),
17        SandboxMode::DenyAll => Arc::new(DenyAllPolicy),
18    }
19}
20
21/// All sandbox modes in a fixed display order (read-only → ask-writes → open → deny-all).
22/// `current` marks the currently selected item, mapping to the ACP `SessionModeState`.
23///
24/// Exposes **all** 4 modes to the client (`session/set_mode` can switch between them).
25/// Mode IDs use [`SandboxMode::as_str`] — the same strings as the `[sandbox].mode` value
26/// in the config file, providing a single source of truth.
27pub fn build_mode_catalog(current: SandboxMode) -> ModeCatalog {
28    let modes = [
29        (
30            SandboxMode::ReadOnly,
31            "Read-only",
32            "Allow read-only tools only; deny all writes, execution, and network access.",
33        ),
34        (
35            SandboxMode::AskWrites,
36            "Ask before writes",
37            "Allow reads directly; ask for each write, execution, and network action, with the choice to allow once or always.",
38        ),
39        (
40            SandboxMode::Open,
41            "Open",
42            "Allow everything without asking. Suitable for trusted environments / fully automated runs.",
43        ),
44        (
45            SandboxMode::DenyAll,
46            "Deny all",
47            "Deny everything. For dry runs / look-but-don't-touch.",
48        ),
49    ]
50    .into_iter()
51    .map(|(mode, name, desc)| PolicyMode {
52        id: mode.as_str().to_string(),
53        name: name.to_string(),
54        description: Some(desc.to_string()),
55        policy: build_policy(mode),
56    })
57    .collect::<Vec<_>>();
58
59    // Invariant: `current` must match one of the four modes above (`SandboxMode` is a
60    // closed enum), so `ModeCatalog::new` always returns `Some` — if it doesn't, that's a
61    // build bug; fail loud.
62    ModeCatalog::new(modes, current.as_str())
63        .expect("mode catalog must contain the current sandbox mode")
64}