File size: 6,461 Bytes
52a9af3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
use anyhow::Context;
use anyhow::Result;
use codex_config::CloudConfigBundleLoader;
use codex_config::test_support::CloudConfigBundleFixture;
use codex_core::CodexThreadSettingsOverrides;
use codex_features::Feature;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::ThreadSettingsOverrides;
use codex_utils_absolute_path::AbsolutePathBuf;
use core_test_support::responses::start_mock_server;
use core_test_support::skip_if_target_windows;
use core_test_support::submit_thread_settings;
use core_test_support::test_codex::test_codex;
use pretty_assertions::assert_eq;
use std::sync::Arc;
use std::sync::RwLock;
use tempfile::TempDir;

#[tokio::test]
async fn refreshed_cloud_bundle_updates_later_sessions() -> Result<()> {
    let server = start_mock_server().await;
    let home = Arc::new(TempDir::new()?);
    let initial_bundle = CloudConfigBundleFixture::enterprise_requirement(
        r#"allowed_approval_policies = ["never"]"#,
    )
    .add_enterprise_config(r#"developer_instructions = "initial managed instructions""#)
    .into_bundle();
    let latest = Arc::new(RwLock::new(initial_bundle));
    let getter_latest = Arc::clone(&latest);
    let loader = CloudConfigBundleLoader::from_getter(move || {
        let latest = Arc::clone(&getter_latest);
        async move { Ok(Some(latest.read().expect("bundle state lock").clone())) }
    });

    let mut initial_builder = test_codex()
        .with_home(Arc::clone(&home))
        .with_cloud_config_bundle(loader.clone());
    let initial = initial_builder.build_with_auto_env(&server).await?;
    assert_eq!(
        initial.session_configured.approval_policy,
        AskForApproval::Never
    );
    assert_eq!(
        initial
            .codex
            .config()
            .await
            .developer_instructions
            .as_deref(),
        Some("initial managed instructions")
    );

    *latest.write().expect("bundle state lock") = CloudConfigBundleFixture::enterprise_requirement(
        r#"allowed_approval_policies = ["on-request"]"#,
    )
    .add_enterprise_config(r#"developer_instructions = "refreshed managed instructions""#)
    .into_bundle();

    let mut refreshed_builder = test_codex()
        .with_home(home)
        .with_cloud_config_bundle(loader);
    let refreshed = refreshed_builder.build_with_auto_env(&server).await?;
    assert_eq!(
        refreshed.session_configured.approval_policy,
        AskForApproval::OnRequest
    );
    assert_eq!(
        refreshed
            .codex
            .config()
            .await
            .developer_instructions
            .as_deref(),
        Some("refreshed managed instructions")
    );

    Ok(())
}

#[tokio::test]
async fn managed_deny_read_requirements_follow_thread_permission_updates() -> Result<()> {
    skip_if_target_windows!(
        Ok(()),
        "Windows restricted-token sandbox cannot enforce deny-read policies"
    );

    let server = start_mock_server().await;
    let home = Arc::new(TempDir::new()?);
    let denied_root = home.path().join("managed-private");
    let nested_root = denied_root.join("nested");
    std::fs::create_dir_all(&nested_root)?;
    let nested_root = AbsolutePathBuf::from_absolute_path(nested_root)?;

    let mut builder = test_codex()
        .with_home(home)
        .with_cloud_config_bundle(
            CloudConfigBundleFixture::loader_with_enterprise_requirement(format!(
                "[permissions.filesystem]\ndeny_read = [{denied_root:?}]\n"
            )),
        )
        .with_config(|config| {
            config
                .permissions
                .set_permission_profile(PermissionProfile::workspace_write())
                .expect("safe workspace permissions should preserve managed denies");
        });
    let test = builder.build_with_auto_env(&server).await?;

    submit_thread_settings(
        &test.codex,
        ThreadSettingsOverrides {
            permission_profile: Some(PermissionProfile::read_only()),
            ..Default::default()
        },
    )
    .await?;
    let snapshot = test.codex.config_snapshot().await;
    assert!(
        !snapshot
            .permission_profile
            .file_system_sandbox_policy()
            .can_read_local_path_with_cwd(nested_root.as_path(), snapshot.cwd().as_path()),
        "a live permission change must preserve managed deny-read rules"
    );

    let conflicting_profile = PermissionProfile::workspace_write_with(
        std::slice::from_ref(&nested_root),
        NetworkSandboxPolicy::Restricted,
        /*exclude_tmpdir_env_var*/ false,
        /*exclude_slash_tmp*/ false,
    );
    let error = test
        .codex
        .preview_thread_settings_overrides(CodexThreadSettingsOverrides {
            permission_profile: Some(conflicting_profile),
            ..Default::default()
        })
        .await
        .err()
        .context("a concrete writable root must not override a managed deny")?;
    assert!(error.to_string().contains("permissions.filesystem"));

    Ok(())
}

#[tokio::test]
async fn managed_guardian_v1_requirements_disable_guardian_v2() -> Result<()> {
    let server = start_mock_server().await;

    for (requirements, guardian_v2_enabled) in [
        (r#"allowed_approvals_reviewers = ["auto_review"]"#, false),
        (
            r#"allowed_approvals_reviewers = ["guardian_subagent"]"#,
            false,
        ),
        (
            r#"allowed_approvals_reviewers = ["auto_review", "user"]"#,
            true,
        ),
        ("[features]\nguardian_approval = true\n", true),
        ("[features]\nauto_review = true\n", true),
    ] {
        let mut builder = test_codex()
            .with_pre_build_hook(|home| {
                std::fs::write(home.join("config.toml"), "[features]\nguardianv2 = true\n")
                    .expect("Guardian v2 configuration should be written");
            })
            .with_cloud_config_bundle(
                CloudConfigBundleFixture::loader_with_enterprise_requirement(requirements),
            );
        let test = builder.build_with_auto_env(&server).await?;

        assert!(test.config.features.enabled(Feature::GuardianApproval));
        assert_eq!(
            test.config.features.enabled(Feature::GuardianV2),
            guardian_v2_enabled,
            "{requirements}"
        );
    }

    Ok(())
}