diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index f2ac6ca00a..673dec6341 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -589,7 +589,7 @@ pub async fn run_main_with_transport_options( }); } if let Some(warning) = - codex_core::config::system_bwrap_warning(config.permissions.permission_profile().get()) + codex_core::config::system_bwrap_warning(config.permissions.permission_profile()) { config_warnings.push(ConfigWarningNotification { summary: warning, diff --git a/codex-rs/app-server/src/request_processors/command_exec_processor.rs b/codex-rs/app-server/src/request_processors/command_exec_processor.rs index d1781db5ff..0977702ee6 100644 --- a/codex-rs/app-server/src/request_processors/command_exec_processor.rs +++ b/codex-rs/app-server/src/request_processors/command_exec_processor.rs @@ -164,7 +164,7 @@ impl CommandExecRequestProcessor { let started_network_proxy = match self.config.permissions.network.as_ref() { Some(spec) => match spec .start_proxy( - self.config.permissions.permission_profile().get(), + self.config.permissions.permission_profile(), /*policy_decider*/ None, /*blocked_request_observer*/ None, managed_network_requirements_enabled, diff --git a/codex-rs/cli/src/debug_sandbox.rs b/codex-rs/cli/src/debug_sandbox.rs index 6952cb689e..83fce3b923 100644 --- a/codex-rs/cli/src/debug_sandbox.rs +++ b/codex-rs/cli/src/debug_sandbox.rs @@ -230,7 +230,7 @@ async fn run_command_under_sandbox( let network_proxy = match config.permissions.network.as_ref() { Some(spec) => Some( spec.start_proxy( - config.permissions.permission_profile().get(), + config.permissions.permission_profile(), /*policy_decider*/ None, /*blocked_request_observer*/ None, managed_network_requirements_enabled, diff --git a/codex-rs/codex-mcp/src/connection_manager.rs b/codex-rs/codex-mcp/src/connection_manager.rs index c5593a5598..e8dc36b9b2 100644 --- a/codex-rs/codex-mcp/src/connection_manager.rs +++ b/codex-rs/codex-mcp/src/connection_manager.rs @@ -80,6 +80,13 @@ impl McpConnectionManager { pub fn new_uninitialized( approval_policy: &Constrained, permission_profile: &Constrained, + ) -> Self { + Self::new_uninitialized_with_permission_profile(approval_policy, permission_profile.get()) + } + + pub fn new_uninitialized_with_permission_profile( + approval_policy: &Constrained, + permission_profile: &PermissionProfile, ) -> Self { Self { clients: HashMap::new(), @@ -87,7 +94,7 @@ impl McpConnectionManager { host_owned_codex_apps_enabled: false, elicitation_requests: ElicitationRequestManager::new( approval_policy.value(), - permission_profile.get().clone(), + permission_profile.clone(), /*reviewer*/ None, ), startup_cancellation_token: CancellationToken::new(), diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index f57ee7ede4..27b9ebc2f5 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -104,6 +104,18 @@ use std::path::Path; use std::time::Duration; use tempfile::TempDir; +fn active_permission_profile_state( + permission_profile: PermissionProfile, + profile_id: impl Into, +) -> PermissionProfileState { + PermissionProfileState::from_constrained_active_profile( + Constrained::allow_any(permission_profile), + Some(ActivePermissionProfile::new(profile_id)), + Vec::new(), + ) + .expect("active permission profile state should be valid") +} + fn stdio_mcp(command: &str) -> McpServerConfig { McpServerConfig { transport: McpServerTransportConfig::Stdio { @@ -7467,12 +7479,10 @@ async fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> { model_provider: fixture.openai_provider.clone(), permissions: Permissions { approval_policy: Constrained::allow_any(AskForApproval::Never), - constrained_permissions_profile: Constrained::allow_any( - PermissionProfile::read_only() - ), - active_permission_profile: Some(ActivePermissionProfile::new( + permission_profile_state: active_permission_profile_state( + PermissionProfile::read_only(), BUILT_IN_PERMISSION_PROFILE_READ_ONLY, - )), + ), workspace_roots: vec![fixture.cwd()], network: None, allow_login_shell: true, @@ -7921,10 +7931,10 @@ async fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> { model_provider: fixture.openai_custom_provider.clone(), permissions: Permissions { approval_policy: Constrained::allow_any(AskForApproval::UnlessTrusted), - constrained_permissions_profile: Constrained::allow_any(PermissionProfile::read_only()), - active_permission_profile: Some(ActivePermissionProfile::new( + permission_profile_state: active_permission_profile_state( + PermissionProfile::read_only(), BUILT_IN_PERMISSION_PROFILE_READ_ONLY, - )), + ), workspace_roots: vec![fixture.cwd()], network: None, allow_login_shell: true, @@ -8087,10 +8097,10 @@ async fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> { model_provider: fixture.openai_provider.clone(), permissions: Permissions { approval_policy: Constrained::allow_any(AskForApproval::OnFailure), - constrained_permissions_profile: Constrained::allow_any(PermissionProfile::read_only()), - active_permission_profile: Some(ActivePermissionProfile::new( + permission_profile_state: active_permission_profile_state( + PermissionProfile::read_only(), BUILT_IN_PERMISSION_PROFILE_READ_ONLY, - )), + ), workspace_roots: vec![fixture.cwd()], network: None, allow_login_shell: true, @@ -8238,10 +8248,10 @@ async fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> { model_provider: fixture.openai_provider.clone(), permissions: Permissions { approval_policy: Constrained::allow_any(AskForApproval::OnFailure), - constrained_permissions_profile: Constrained::allow_any(PermissionProfile::read_only()), - active_permission_profile: Some(ActivePermissionProfile::new( + permission_profile_state: active_permission_profile_state( + PermissionProfile::read_only(), BUILT_IN_PERMISSION_PROFILE_READ_ONLY, - )), + ), workspace_roots: vec![fixture.cwd()], network: None, allow_login_shell: true, diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index b132b17d24..099cda1926 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -134,6 +134,7 @@ mod managed_features; mod network_proxy_spec; mod otel; mod permissions; +mod resolved_permission_profile; #[cfg(test)] mod schema; pub use codex_config::ConfigLoadOptions; @@ -148,6 +149,7 @@ pub use managed_features::ManagedFeatures; pub use network_proxy_spec::NetworkProxySpec; pub use network_proxy_spec::StartedNetworkProxy; pub(crate) use permissions::resolve_permission_profile; +pub(crate) use resolved_permission_profile::PermissionProfileState; const DEFAULT_IGNORE_LARGE_UNTRACKED_DIRS: i64 = 200; const DEFAULT_IGNORE_LARGE_UNTRACKED_FILES: i64 = 10 * 1024 * 1024; @@ -247,15 +249,11 @@ pub(crate) async fn test_config() -> Config { pub struct Permissions { /// Approval policy for executing commands. pub approval_policy: Constrained, - /// Canonical constrained permissions profile before runtime workspace-root - /// materialization has been applied. - constrained_permissions_profile: Constrained, - /// Named or implicit built-in profile selected by config, rather than an - /// ad-hoc override. - active_permission_profile: Option, + /// Constrained permission profile plus its selected profile identity, if + /// the profile came from a built-in or named config profile. + permission_profile_state: PermissionProfileState, /// Thread-scoped runtime workspace roots. Symbolic `:workspace_roots` - /// entries in `constrained_permissions_profile` are materialized against - /// these roots. + /// entries in the permission profile are materialized against these roots. workspace_roots: Vec, /// Effective network configuration applied to all spawned processes. pub network: Option, @@ -286,8 +284,10 @@ impl Permissions { ) -> Self { Self { approval_policy, - constrained_permissions_profile: permission_profile, - active_permission_profile: None, + permission_profile_state: PermissionProfileState::from_constrained_legacy( + permission_profile, + ) + .expect("initial constrained permission profile should be valid"), workspace_roots: Vec::new(), network: None, allow_login_shell: true, @@ -297,21 +297,88 @@ impl Permissions { } } - /// Borrow the constrained canonical profile. This preserves the raw - /// symbolic `:workspace_roots` form for session/thread state. - pub fn permission_profile(&self) -> &Constrained { - &self.constrained_permissions_profile + pub(crate) fn permission_profile_state(&self) -> &PermissionProfileState { + &self.permission_profile_state } - /// Set the full constrained profile value and preserve the active profile - /// sidecar when the caller has already validated both together. - pub fn set_constrained_permission_profile_with_active_profile( + pub(crate) fn set_permission_profile_state( + &mut self, + permission_profile_state: PermissionProfileState, + ) { + self.permission_profile_state = permission_profile_state; + } + + /// Apply a permission profile snapshot emitted by core session state. + /// + /// This is a trusted-state bridge for consumers of `SessionConfigured`. + /// Config loading and app-server selection should resolve named profiles + /// through config instead of constructing this pair directly. + pub fn set_permission_profile_from_session_snapshot( + &mut self, + permission_profile: PermissionProfile, + active_permission_profile: Option, + ) -> ConstraintResult<()> { + self.set_permission_profile_from_session_snapshot_with_profile_workspace_roots( + permission_profile, + active_permission_profile, + Vec::new(), + ) + } + + pub fn set_permission_profile_from_session_snapshot_with_profile_workspace_roots( + &mut self, + permission_profile: PermissionProfile, + active_permission_profile: Option, + profile_workspace_roots: Vec, + ) -> ConstraintResult<()> { + self.permission_profile_state.set_active_permission_profile( + permission_profile, + active_permission_profile, + profile_workspace_roots, + ) + } + + /// Replace the current permission constraints with a trusted session + /// snapshot. This is only for clients that must mirror core session state + /// after their local config constraints reject the snapshot. + pub fn replace_permission_profile_from_session_snapshot( &mut self, permission_profile: Constrained, active_permission_profile: Option, - ) { - self.constrained_permissions_profile = permission_profile; - self.active_permission_profile = active_permission_profile; + ) -> ConstraintResult<()> { + self.replace_permission_profile_from_session_snapshot_with_profile_workspace_roots( + permission_profile, + active_permission_profile, + Vec::new(), + ) + } + + pub fn replace_permission_profile_from_session_snapshot_with_profile_workspace_roots( + &mut self, + permission_profile: Constrained, + active_permission_profile: Option, + profile_workspace_roots: Vec, + ) -> ConstraintResult<()> { + self.permission_profile_state = PermissionProfileState::from_constrained_active_profile( + permission_profile, + active_permission_profile, + profile_workspace_roots, + )?; + Ok(()) + } + + /// Borrow the canonical profile before runtime workspace-root + /// materialization has been applied. + pub fn permission_profile(&self) -> &PermissionProfile { + self.permission_profile_state.permission_profile() + } + + pub fn can_set_permission_profile( + &self, + permission_profile: &PermissionProfile, + ) -> ConstraintResult<()> { + self.permission_profile_state + .can_set_legacy_permission_profile(permission_profile) } pub fn set_workspace_roots(&mut self, workspace_roots: Vec) { @@ -323,8 +390,7 @@ impl Permissions { } fn materialized_permission_profile(&self) -> PermissionProfile { - self.constrained_permissions_profile - .get() + self.permission_profile() .clone() .materialize_project_roots_with_workspace_roots(&self.workspace_roots) } @@ -337,7 +403,7 @@ impl Permissions { /// Named profile selected by config, if the current profile has one. pub fn active_permission_profile(&self) -> Option { - self.active_permission_profile.clone() + self.permission_profile_state.active_permission_profile() } /// Effective filesystem sandbox policy derived from the canonical profile. @@ -348,9 +414,7 @@ impl Permissions { /// Effective network sandbox policy derived from the canonical profile. pub fn network_sandbox_policy(&self) -> NetworkSandboxPolicy { - self.constrained_permissions_profile - .get() - .network_sandbox_policy() + self.permission_profile().network_sandbox_policy() } /// Legacy compatibility projection derived from the canonical profile. @@ -380,8 +444,8 @@ impl Permissions { &file_system_sandbox_policy, network_sandbox_policy, ); - self.constrained_permissions_profile - .can_set(&permission_profile) + self.permission_profile_state + .can_set_legacy_permission_profile(&permission_profile) } /// Set permissions from a legacy sandbox policy and keep every permission @@ -421,9 +485,8 @@ impl Permissions { ], }; - self.constrained_permissions_profile - .set(permission_profile)?; - self.active_permission_profile = None; + self.permission_profile_state + .set_legacy_permission_profile(permission_profile)?; Ok(()) } @@ -432,23 +495,8 @@ impl Permissions { &mut self, permission_profile: PermissionProfile, ) -> ConstraintResult<()> { - self.set_permission_profile_with_active_profile( - permission_profile, - /*active_permission_profile*/ None, - ) - } - - /// Set permissions from the canonical profile and record the named source - /// profile, if one is known. - pub fn set_permission_profile_with_active_profile( - &mut self, - permission_profile: PermissionProfile, - active_permission_profile: Option, - ) -> ConstraintResult<()> { - self.constrained_permissions_profile - .set(permission_profile)?; - self.active_permission_profile = active_permission_profile; - Ok(()) + self.permission_profile_state + .set_legacy_permission_profile(permission_profile) } } @@ -3226,6 +3274,12 @@ impl Config { .value .set(effective_permission_profile) .map_err(std::io::Error::from)?; + let permission_profile_state = PermissionProfileState::from_constrained_active_profile( + constrained_permission_profile.value, + active_permission_profile, + Vec::new(), + ) + .map_err(std::io::Error::from)?; let otel = otel::resolve_config(cfg.otel.unwrap_or_default(), &mut startup_warnings); let config = Self { model, @@ -3241,8 +3295,7 @@ impl Config { startup_warnings, permissions: Permissions { approval_policy: constrained_approval_policy.value, - constrained_permissions_profile: constrained_permission_profile.value, - active_permission_profile, + permission_profile_state, workspace_roots, network, allow_login_shell, @@ -3529,7 +3582,7 @@ impl Config { pub fn managed_network_requirements_enabled(&self) -> bool { !matches!( - self.permissions.permission_profile().get(), + self.permissions.permission_profile(), PermissionProfile::Disabled ) && self .config_layer_stack diff --git a/codex-rs/core/src/config/resolved_permission_profile.rs b/codex-rs/core/src/config/resolved_permission_profile.rs new file mode 100644 index 0000000000..426c06e16e --- /dev/null +++ b/codex-rs/core/src/config/resolved_permission_profile.rs @@ -0,0 +1,201 @@ +use codex_config::Constrained; +use codex_config::ConstraintResult; +use codex_protocol::models::ActivePermissionProfile; +use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS; +use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_READ_ONLY; +use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE; +use codex_protocol::models::PermissionProfile; +use codex_utils_absolute_path::AbsolutePathBuf; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum BuiltInPermissionProfileId { + ReadOnly, + Workspace, + DangerFullAccess, +} + +impl BuiltInPermissionProfileId { + fn from_str(id: &str) -> Option { + match id { + BUILT_IN_PERMISSION_PROFILE_READ_ONLY => Some(Self::ReadOnly), + BUILT_IN_PERMISSION_PROFILE_WORKSPACE => Some(Self::Workspace), + BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS => Some(Self::DangerFullAccess), + _ => None, + } + } + + fn as_str(self) -> &'static str { + match self { + Self::ReadOnly => BUILT_IN_PERMISSION_PROFILE_READ_ONLY, + Self::Workspace => BUILT_IN_PERMISSION_PROFILE_WORKSPACE, + Self::DangerFullAccess => BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum ResolvedPermissionProfile { + Legacy(LegacyPermissionProfile), + BuiltIn(BuiltInPermissionProfile), + Named(NamedPermissionProfile), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct LegacyPermissionProfile { + permission_profile: PermissionProfile, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct BuiltInPermissionProfile { + id: BuiltInPermissionProfileId, + extends: Option, + permission_profile: PermissionProfile, + profile_workspace_roots: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct NamedPermissionProfile { + id: String, + extends: Option, + permission_profile: PermissionProfile, + profile_workspace_roots: Vec, +} + +impl ResolvedPermissionProfile { + pub(crate) fn from_active_profile( + permission_profile: PermissionProfile, + active_permission_profile: Option, + profile_workspace_roots: Vec, + ) -> Self { + let Some(active_permission_profile) = active_permission_profile else { + return Self::legacy(permission_profile); + }; + + let ActivePermissionProfile { id, extends } = active_permission_profile; + if let Some(built_in_id) = BuiltInPermissionProfileId::from_str(&id) { + Self::BuiltIn(BuiltInPermissionProfile { + id: built_in_id, + extends, + permission_profile, + profile_workspace_roots, + }) + } else { + Self::Named(NamedPermissionProfile { + id, + extends, + permission_profile, + profile_workspace_roots, + }) + } + } + + pub(crate) fn legacy(permission_profile: PermissionProfile) -> Self { + Self::Legacy(LegacyPermissionProfile { permission_profile }) + } + + pub(crate) fn permission_profile(&self) -> &PermissionProfile { + match self { + Self::Legacy(profile) => &profile.permission_profile, + Self::BuiltIn(profile) => &profile.permission_profile, + Self::Named(profile) => &profile.permission_profile, + } + } + + pub(crate) fn active_permission_profile(&self) -> Option { + match self { + Self::Legacy(_) => None, + Self::BuiltIn(profile) => Some(ActivePermissionProfile { + id: profile.id.as_str().to_string(), + extends: profile.extends.clone(), + }), + Self::Named(profile) => Some(ActivePermissionProfile { + id: profile.id.clone(), + extends: profile.extends.clone(), + }), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct PermissionProfileState { + resolved_permission_profile: Constrained, +} + +impl PermissionProfileState { + pub(crate) fn from_constrained_legacy( + constrained_permission_profile: Constrained, + ) -> ConstraintResult { + let resolved = + ResolvedPermissionProfile::legacy(constrained_permission_profile.get().clone()); + Self::from_constrained_resolved(constrained_permission_profile, resolved) + } + + pub(crate) fn from_constrained_active_profile( + constrained_permission_profile: Constrained, + active_permission_profile: Option, + profile_workspace_roots: Vec, + ) -> ConstraintResult { + let resolved = ResolvedPermissionProfile::from_active_profile( + constrained_permission_profile.get().clone(), + active_permission_profile, + profile_workspace_roots, + ); + Self::from_constrained_resolved(constrained_permission_profile, resolved) + } + + pub(crate) fn from_constrained_resolved( + constrained_permission_profile: Constrained, + resolved_permission_profile: ResolvedPermissionProfile, + ) -> ConstraintResult { + let permission_profile_constraint = constrained_permission_profile.clone(); + let resolved_permission_profile = Constrained::new( + resolved_permission_profile, + move |candidate: &ResolvedPermissionProfile| { + permission_profile_constraint.can_set(candidate.permission_profile()) + }, + )?; + Ok(Self { + resolved_permission_profile, + }) + } + + pub(crate) fn permission_profile(&self) -> &PermissionProfile { + self.resolved_permission_profile.get().permission_profile() + } + + pub(crate) fn active_permission_profile(&self) -> Option { + self.resolved_permission_profile + .get() + .active_permission_profile() + } + + pub(crate) fn can_set_legacy_permission_profile( + &self, + permission_profile: &PermissionProfile, + ) -> ConstraintResult<()> { + let candidate = ResolvedPermissionProfile::legacy(permission_profile.clone()); + self.resolved_permission_profile.can_set(&candidate) + } + + pub(crate) fn set_legacy_permission_profile( + &mut self, + permission_profile: PermissionProfile, + ) -> ConstraintResult<()> { + self.resolved_permission_profile + .set(ResolvedPermissionProfile::legacy(permission_profile)) + } + + pub(crate) fn set_active_permission_profile( + &mut self, + permission_profile: PermissionProfile, + active_permission_profile: Option, + profile_workspace_roots: Vec, + ) -> ConstraintResult<()> { + let candidate = ResolvedPermissionProfile::from_active_profile( + permission_profile, + active_permission_profile, + profile_workspace_roots, + ); + self.resolved_permission_profile.set(candidate) + } +} diff --git a/codex-rs/core/src/guardian/review_session.rs b/codex-rs/core/src/guardian/review_session.rs index 88b4159fdd..08789485e5 100644 --- a/codex-rs/core/src/guardian/review_session.rs +++ b/codex-rs/core/src/guardian/review_session.rs @@ -920,7 +920,7 @@ pub(crate) fn build_guardian_review_session_config( guardian_config.permissions.network = Some(NetworkProxySpec::from_config_and_constraints( live_network_config, network_constraints, - guardian_config.permissions.permission_profile().get(), + guardian_config.permissions.permission_profile(), )?); } for feature in [ diff --git a/codex-rs/core/src/guardian/tests.rs b/codex-rs/core/src/guardian/tests.rs index 18400fa230..742b3c8ae4 100644 --- a/codex-rs/core/src/guardian/tests.rs +++ b/codex-rs/core/src/guardian/tests.rs @@ -2163,7 +2163,7 @@ async fn guardian_review_session_config_preserves_parent_network_proxy() { }), ..Default::default() }), - parent_config.permissions.permission_profile().get(), + parent_config.permissions.permission_profile(), ) .expect("network proxy spec"); parent_config.permissions.network = Some(network.clone()); @@ -2191,9 +2191,7 @@ async fn guardian_review_session_config_preserves_parent_network_proxy() { ); assert_eq!( guardian_config.permissions.permission_profile(), - &Constrained::allow_only(PermissionProfile::from_legacy_sandbox_policy( - &SandboxPolicy::new_read_only_policy(), - )) + &PermissionProfile::from_legacy_sandbox_policy(&SandboxPolicy::new_read_only_policy()) ); } @@ -2230,7 +2228,7 @@ async fn guardian_review_session_config_uses_live_network_proxy_state() { NetworkProxySpec::from_config_and_constraints( parent_network, /*requirements*/ None, - parent_config.permissions.permission_profile().get(), + parent_config.permissions.permission_profile(), ) .expect("parent network proxy spec"), ); diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 384e09d354..e7b9277653 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -173,6 +173,7 @@ use crate::compact::collect_user_messages; use crate::config::Config; use crate::config::Constrained; use crate::config::ConstraintResult; +use crate::config::PermissionProfileState; use crate::config::StartedNetworkProxy; use crate::config::resolve_web_search_mode_for_turn; use crate::context_manager::ContextManager; @@ -617,8 +618,7 @@ impl Codex { compact_prompt: config.compact_prompt.clone(), approval_policy: config.permissions.approval_policy.clone(), approvals_reviewer: config.approvals_reviewer, - permission_profile: config.permissions.permission_profile().clone(), - active_permission_profile: config.permissions.active_permission_profile(), + permission_profile_state: config.permissions.permission_profile_state().clone(), windows_sandbox_level: WindowsSandboxLevel::from_config(&config), cwd: config.cwd.clone(), codex_home: config.codex_home.clone(), diff --git a/codex-rs/core/src/session/session.rs b/codex-rs/core/src/session/session.rs index c682c1021e..17ec91e7df 100644 --- a/codex-rs/core/src/session/session.rs +++ b/codex-rs/core/src/session/session.rs @@ -63,11 +63,10 @@ pub(crate) struct SessionConfiguration { /// When to escalate for approval for execution pub(super) approval_policy: Constrained, pub(super) approvals_reviewer: ApprovalsReviewer, - /// Canonical permission profile for the session. - pub(super) permission_profile: Constrained, - /// Named or implicit built-in permissions profile selected from config, if - /// any. - pub(super) active_permission_profile: Option, + /// Permission profile state for the session. Keep the constrained profile + /// and selected profile id in sync by using the methods below instead of + /// mutating the fields independently. + pub(super) permission_profile_state: PermissionProfileState, pub(super) windows_sandbox_level: WindowsSandboxLevel, /// Absolute working directory that should be treated as the *root* of the @@ -103,12 +102,32 @@ impl SessionConfiguration { &self.codex_home } + pub(super) fn permission_profile_state(&self) -> &PermissionProfileState { + &self.permission_profile_state + } + pub(super) fn permission_profile(&self) -> PermissionProfile { - self.permission_profile.get().clone() + self.permission_profile_state.permission_profile().clone() } pub(super) fn active_permission_profile(&self) -> Option { - self.active_permission_profile.clone() + self.permission_profile_state.active_permission_profile() + } + + pub(super) fn apply_permission_profile_to_permissions( + &self, + permissions: &mut crate::config::Permissions, + ) { + permissions.set_permission_profile_state(self.permission_profile_state.clone()); + } + + #[cfg(test)] + pub(super) fn set_permission_profile_for_tests( + &mut self, + permission_profile: PermissionProfile, + ) -> ConstraintResult<()> { + self.permission_profile_state + .set_legacy_permission_profile(permission_profile) } pub(super) fn sandbox_policy(&self) -> SandboxPolicy { @@ -117,7 +136,7 @@ impl SessionConfiguration { .unwrap_or_else(|_| { let file_system_sandbox_policy = self.file_system_sandbox_policy(); codex_sandboxing::compatibility_sandbox_policy_for_permission_profile( - self.permission_profile.get(), + self.permission_profile_state.permission_profile(), &file_system_sandbox_policy, self.network_sandbox_policy(), &self.cwd, @@ -126,11 +145,13 @@ impl SessionConfiguration { } pub(super) fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy { - self.permission_profile.get().file_system_sandbox_policy() + self.permission_profile().file_system_sandbox_policy() } pub(super) fn network_sandbox_policy(&self) -> NetworkSandboxPolicy { - self.permission_profile.get().network_sandbox_policy() + self.permission_profile_state + .permission_profile() + .network_sandbox_policy() } pub(super) fn thread_config_snapshot(&self) -> ThreadConfigSnapshot { @@ -227,16 +248,16 @@ impl SessionConfiguration { let active_permission_profile = updates.active_permission_profile.clone().or_else(|| { if permission_profile == self.permission_profile() { - self.active_permission_profile.clone() + self.active_permission_profile() } else { None } }); next_configuration.set_permission_profile_projection( permission_profile, + active_permission_profile, Some(¤t_file_system_sandbox_policy), )?; - next_configuration.active_permission_profile = active_permission_profile; } else if let Some(sandbox_policy) = updates.sandbox_policy.clone() { let file_system_sandbox_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries( @@ -245,14 +266,15 @@ impl SessionConfiguration { ¤t_file_system_sandbox_policy, ); let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy); - next_configuration.permission_profile.set( - PermissionProfile::from_runtime_permissions_with_enforcement( - SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy), - &file_system_sandbox_policy, - network_sandbox_policy, - ), - )?; - next_configuration.active_permission_profile = None; + next_configuration + .permission_profile_state + .set_legacy_permission_profile( + PermissionProfile::from_runtime_permissions_with_enforcement( + SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy), + &file_system_sandbox_policy, + network_sandbox_policy, + ), + )?; } else if cwd_changed && file_system_policy_matches_legacy && file_system_policy_has_rebindable_project_root_write @@ -266,13 +288,15 @@ impl SessionConfiguration { &next_configuration.cwd, ¤t_file_system_sandbox_policy, ); - next_configuration.permission_profile.set( - PermissionProfile::from_runtime_permissions_with_enforcement( - SandboxEnforcement::from_legacy_sandbox_policy(¤t_sandbox_policy), - &file_system_sandbox_policy, - current_network_sandbox_policy, - ), - )?; + next_configuration + .permission_profile_state + .set_legacy_permission_profile( + PermissionProfile::from_runtime_permissions_with_enforcement( + SandboxEnforcement::from_legacy_sandbox_policy(¤t_sandbox_policy), + &file_system_sandbox_policy, + current_network_sandbox_policy, + ), + )?; } if let Some(app_server_client_name) = updates.app_server_client_name.clone() { next_configuration.app_server_client_name = Some(app_server_client_name); @@ -286,6 +310,7 @@ impl SessionConfiguration { fn set_permission_profile_projection( &mut self, permission_profile: PermissionProfile, + active_permission_profile: Option, preserve_deny_reads_from: Option<&FileSystemSandboxPolicy>, ) -> ConstraintResult<()> { let enforcement = permission_profile.enforcement(); @@ -301,8 +326,11 @@ impl SessionConfiguration { &file_system_sandbox_policy, network_sandbox_policy, ); - self.permission_profile.set(effective_permission_profile)?; - Ok(()) + self.permission_profile_state.set_active_permission_profile( + effective_permission_profile, + active_permission_profile, + Vec::new(), + ) } } @@ -769,7 +797,7 @@ impl Session { let (network_proxy, session_network_proxy) = Self::start_managed_network_proxy( spec, current_exec_policy.as_ref(), - config.permissions.permission_profile().get(), + config.permissions.permission_profile(), network_policy_decider.as_ref().map(Arc::clone), blocked_request_observer.as_ref().map(Arc::clone), managed_network_requirements_configured, @@ -831,10 +859,12 @@ impl Session { // before any MCP-related events. It is reasonable to consider // changing this to use Option or OnceCell, though the current // setup is straightforward enough and performs well. - mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::new_uninitialized( - &config.permissions.approval_policy, - config.permissions.permission_profile(), - ))), + mcp_connection_manager: Arc::new(RwLock::new( + McpConnectionManager::new_uninitialized_with_permission_profile( + &config.permissions.approval_policy, + config.permissions.permission_profile(), + ), + )), mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()), unified_exec_manager: UnifiedExecProcessManager::new( config.background_terminal_max_timeout, @@ -939,7 +969,9 @@ impl Session { initial_messages, network_proxy: session_network_proxy.filter(|_| { Self::managed_network_proxy_active_for_permission_profile( - session_configuration.permission_profile.get(), + session_configuration + .permission_profile_state() + .permission_profile(), ) }), rollout_path, diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index 6f09cb6e2f..9b7b8ee4a0 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -868,8 +868,7 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow state.session_configuration.original_config_do_not_use = Arc::new(config); state .session_configuration - .permission_profile - .set(PermissionProfile::from_legacy_sandbox_policy( + .set_permission_profile_for_tests(PermissionProfile::from_legacy_sandbox_policy( &initial_policy, )) .expect("test setup should allow permission profile"); @@ -2887,8 +2886,7 @@ async fn set_rate_limits_retains_previous_credits() { compact_prompt: config.compact_prompt.clone(), approval_policy: config.permissions.approval_policy.clone(), approvals_reviewer: config.approvals_reviewer, - permission_profile: config.permissions.permission_profile().clone(), - active_permission_profile: config.permissions.active_permission_profile(), + permission_profile_state: config.permissions.permission_profile_state().clone(), windows_sandbox_level: WindowsSandboxLevel::from_config(&config), cwd: config.cwd.clone(), codex_home: config.codex_home.clone(), @@ -2991,8 +2989,7 @@ async fn set_rate_limits_updates_plan_type_when_present() { compact_prompt: config.compact_prompt.clone(), approval_policy: config.permissions.approval_policy.clone(), approvals_reviewer: config.approvals_reviewer, - permission_profile: config.permissions.permission_profile().clone(), - active_permission_profile: config.permissions.active_permission_profile(), + permission_profile_state: config.permissions.permission_profile_state().clone(), windows_sandbox_level: WindowsSandboxLevel::from_config(&config), cwd: config.cwd.clone(), codex_home: config.codex_home.clone(), @@ -3464,8 +3461,7 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati compact_prompt: config.compact_prompt.clone(), approval_policy: config.permissions.approval_policy.clone(), approvals_reviewer: config.approvals_reviewer, - permission_profile: config.permissions.permission_profile().clone(), - active_permission_profile: config.permissions.active_permission_profile(), + permission_profile_state: config.permissions.permission_profile_state().clone(), windows_sandbox_level: WindowsSandboxLevel::from_config(&config), cwd: config.cwd.clone(), codex_home: config.codex_home.clone(), @@ -3528,13 +3524,15 @@ async fn session_configuration_apply_preserves_profile_file_system_policy_on_cwd }, ]); let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy); - session_configuration.permission_profile = codex_config::Constrained::allow_any( - PermissionProfile::from_runtime_permissions_with_enforcement( - SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy), - &file_system_sandbox_policy, - network_sandbox_policy, - ), - ); + session_configuration + .set_permission_profile_for_tests( + PermissionProfile::from_runtime_permissions_with_enforcement( + SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy), + &file_system_sandbox_policy, + network_sandbox_policy, + ), + ) + .expect("set permission profile"); let updated = session_configuration .apply(&SessionSettingsUpdate { @@ -3569,13 +3567,15 @@ async fn session_configuration_apply_permission_profile_preserves_existing_deny_ ); existing_file_system_policy.glob_scan_max_depth = Some(2); existing_file_system_policy.entries.push(deny_entry.clone()); - session_configuration.permission_profile = codex_config::Constrained::allow_any( - PermissionProfile::from_runtime_permissions_with_enforcement( - SandboxEnforcement::from_legacy_sandbox_policy(&workspace_policy), - &existing_file_system_policy, - NetworkSandboxPolicy::Restricted, - ), - ); + session_configuration + .set_permission_profile_for_tests( + PermissionProfile::from_runtime_permissions_with_enforcement( + SandboxEnforcement::from_legacy_sandbox_policy(&workspace_policy), + &existing_file_system_policy, + NetworkSandboxPolicy::Restricted, + ), + ) + .expect("set permission profile"); let requested_file_system_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd( &workspace_policy, @@ -3750,13 +3750,15 @@ async fn session_configuration_apply_rederives_legacy_file_system_policy_on_cwd_ &sandbox_policy, &session_configuration.cwd, ); - session_configuration.permission_profile = codex_config::Constrained::allow_any( - PermissionProfile::from_runtime_permissions_with_enforcement( - SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy), - &file_system_sandbox_policy, - NetworkSandboxPolicy::from(&sandbox_policy), - ), - ); + session_configuration + .set_permission_profile_for_tests( + PermissionProfile::from_runtime_permissions_with_enforcement( + SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy), + &file_system_sandbox_policy, + NetworkSandboxPolicy::from(&sandbox_policy), + ), + ) + .expect("set permission profile"); let updated = session_configuration .apply(&SessionSettingsUpdate { @@ -3802,13 +3804,15 @@ async fn session_configuration_apply_preserves_absolute_cwd_write_root_on_cwd_up access: FileSystemAccessMode::Write, }, ]); - session_configuration.permission_profile = codex_config::Constrained::allow_any( - PermissionProfile::from_runtime_permissions_with_enforcement( - SandboxEnforcement::Managed, - &file_system_sandbox_policy, - NetworkSandboxPolicy::Restricted, - ), - ); + session_configuration + .set_permission_profile_for_tests( + PermissionProfile::from_runtime_permissions_with_enforcement( + SandboxEnforcement::Managed, + &file_system_sandbox_policy, + NetworkSandboxPolicy::Restricted, + ), + ) + .expect("set permission profile"); let updated = session_configuration .apply(&SessionSettingsUpdate { @@ -3997,8 +4001,7 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() { compact_prompt: config.compact_prompt.clone(), approval_policy: config.permissions.approval_policy.clone(), approvals_reviewer: config.approvals_reviewer, - permission_profile: config.permissions.permission_profile().clone(), - active_permission_profile: config.permissions.active_permission_profile(), + permission_profile_state: config.permissions.permission_profile_state().clone(), windows_sandbox_level: WindowsSandboxLevel::from_config(&config), cwd: config.cwd.clone(), codex_home: config.codex_home.clone(), @@ -4106,8 +4109,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) { compact_prompt: config.compact_prompt.clone(), approval_policy: config.permissions.approval_policy.clone(), approvals_reviewer: config.approvals_reviewer, - permission_profile: config.permissions.permission_profile().clone(), - active_permission_profile: config.permissions.active_permission_profile(), + permission_profile_state: config.permissions.permission_profile_state().clone(), windows_sandbox_level: WindowsSandboxLevel::from_config(&config), cwd: config.cwd.clone(), codex_home: config.codex_home.clone(), @@ -4151,10 +4153,12 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) { ); let services = SessionServices { - mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::new_uninitialized( - &config.permissions.approval_policy, - config.permissions.permission_profile(), - ))), + mcp_connection_manager: Arc::new(RwLock::new( + McpConnectionManager::new_uninitialized_with_permission_profile( + &config.permissions.approval_policy, + config.permissions.permission_profile(), + ), + )), mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()), unified_exec_manager: UnifiedExecProcessManager::new( config.background_terminal_max_timeout, @@ -4338,8 +4342,7 @@ async fn make_session_with_config_and_rx( compact_prompt: config.compact_prompt.clone(), approval_policy: config.permissions.approval_policy.clone(), approvals_reviewer: config.approvals_reviewer, - permission_profile: config.permissions.permission_profile().clone(), - active_permission_profile: config.permissions.active_permission_profile(), + permission_profile_state: config.permissions.permission_profile_state().clone(), windows_sandbox_level: WindowsSandboxLevel::from_config(&config), cwd: config.cwd.clone(), codex_home: config.codex_home.clone(), @@ -4441,8 +4444,7 @@ async fn make_session_with_history_source_and_agent_control_and_rx( compact_prompt: config.compact_prompt.clone(), approval_policy: config.permissions.approval_policy.clone(), approvals_reviewer: config.approvals_reviewer, - permission_profile: config.permissions.permission_profile().clone(), - active_permission_profile: config.permissions.active_permission_profile(), + permission_profile_state: config.permissions.permission_profile_state().clone(), windows_sandbox_level: WindowsSandboxLevel::from_config(&config), cwd: config.cwd.clone(), codex_home: config.codex_home.clone(), @@ -5958,8 +5960,7 @@ where compact_prompt: config.compact_prompt.clone(), approval_policy: config.permissions.approval_policy.clone(), approvals_reviewer: config.approvals_reviewer, - permission_profile: config.permissions.permission_profile().clone(), - active_permission_profile: config.permissions.active_permission_profile(), + permission_profile_state: config.permissions.permission_profile_state().clone(), windows_sandbox_level: WindowsSandboxLevel::from_config(&config), cwd: config.cwd.clone(), codex_home: config.codex_home.clone(), @@ -6003,10 +6004,12 @@ where ); let services = SessionServices { - mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::new_uninitialized( - &config.permissions.approval_policy, - config.permissions.permission_profile(), - ))), + mcp_connection_manager: Arc::new(RwLock::new( + McpConnectionManager::new_uninitialized_with_permission_profile( + &config.permissions.approval_policy, + config.permissions.permission_profile(), + ), + )), mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()), unified_exec_manager: UnifiedExecProcessManager::new( config.background_terminal_max_timeout, diff --git a/codex-rs/core/src/session/turn_context.rs b/codex-rs/core/src/session/turn_context.rs index 45dc06c776..eefa6bc9da 100644 --- a/codex-rs/core/src/session/turn_context.rs +++ b/codex-rs/core/src/session/turn_context.rs @@ -438,12 +438,8 @@ impl Session { per_turn_config.service_tier = session_configuration.service_tier.clone(); per_turn_config.personality = session_configuration.personality; per_turn_config.approvals_reviewer = session_configuration.approvals_reviewer; - per_turn_config - .permissions - .set_constrained_permission_profile_with_active_profile( - session_configuration.permission_profile.clone(), - session_configuration.active_permission_profile.clone(), - ); + session_configuration + .apply_permission_profile_to_permissions(&mut per_turn_config.permissions); let permission_profile = session_configuration.permission_profile(); let resolved_web_search_mode = resolve_web_search_mode_for_turn(&per_turn_config.web_search_mode, &permission_profile); diff --git a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs index 4fc6db14e4..c6a0007f13 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs @@ -3900,7 +3900,7 @@ async fn tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtr #[tokio::test] async fn build_agent_spawn_config_uses_turn_context_values() { fn pick_allowed_sandbox_policy( - constraint: &crate::config::Constrained, + permissions: &crate::config::Permissions, base: SandboxPolicy, cwd: &std::path::Path, ) -> SandboxPolicy { @@ -3915,16 +3915,9 @@ async fn build_agent_spawn_config_uses_turn_context_values() { if *candidate == base { return false; } - let file_system_sandbox_policy = - FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(candidate, cwd); - let network_sandbox_policy = NetworkSandboxPolicy::from(candidate); - let permission_profile = - PermissionProfile::from_runtime_permissions_with_enforcement( - SandboxEnforcement::from_legacy_sandbox_policy(candidate), - &file_system_sandbox_policy, - network_sandbox_policy, - ); - constraint.can_set(&permission_profile).is_ok() + permissions + .can_set_legacy_sandbox_policy(candidate, cwd) + .is_ok() }) .unwrap_or(base) } @@ -3948,7 +3941,7 @@ async fn build_agent_spawn_config_uses_turn_context_values() { #[allow(deprecated)] let turn_cwd = turn.cwd.clone(); let sandbox_policy = pick_allowed_sandbox_policy( - turn.config.permissions.permission_profile(), + &turn.config.permissions, turn.config.legacy_sandbox_policy(), turn_cwd.as_path(), ); diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 04839174f5..573b90320c 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -759,7 +759,7 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> { event_processor.print_config_summary(&config, &prompt_summary, &session_configured); if !json_mode && let Some(message) = - codex_core::config::system_bwrap_warning(config.permissions.permission_profile().get()) + codex_core::config::system_bwrap_warning(config.permissions.permission_profile()) { event_processor.process_warning(message); } diff --git a/codex-rs/tui/src/app/startup_prompts.rs b/codex-rs/tui/src/app/startup_prompts.rs index d4d6c3b8d1..16088a81d0 100644 --- a/codex-rs/tui/src/app/startup_prompts.rs +++ b/codex-rs/tui/src/app/startup_prompts.rs @@ -66,9 +66,9 @@ pub(super) fn emit_project_config_warnings(app_event_tx: &AppEventSender, config } pub(super) fn emit_system_bwrap_warning(app_event_tx: &AppEventSender, config: &Config) { - let Some(message) = crate::legacy_core::config::system_bwrap_warning( - config.permissions.permission_profile().get(), - ) else { + let Some(message) = + crate::legacy_core::config::system_bwrap_warning(config.permissions.permission_profile()) + else { return; }; diff --git a/codex-rs/tui/src/chatwidget/session_flow.rs b/codex-rs/tui/src/chatwidget/session_flow.rs index dcbabe15e5..16047b8df9 100644 --- a/codex-rs/tui/src/chatwidget/session_flow.rs +++ b/codex-rs/tui/src/chatwidget/session_flow.rs @@ -47,7 +47,7 @@ impl ChatWidget { let permission_sync = self .config .permissions - .set_permission_profile_with_active_profile( + .set_permission_profile_from_session_snapshot( session.permission_profile.clone(), session.active_permission_profile.clone(), ); @@ -55,10 +55,11 @@ impl ChatWidget { tracing::warn!(%err, "failed to sync permissions from SessionConfigured"); self.config .permissions - .set_constrained_permission_profile_with_active_profile( + .replace_permission_profile_from_session_snapshot( Constrained::allow_only(session.permission_profile.clone()), session.active_permission_profile.clone(), - ); + ) + .expect("allow-only snapshot should satisfy its own constraint"); } self.config.approvals_reviewer = session.approvals_reviewer; self.status_line_project_root_name_cache = None; diff --git a/codex-rs/tui/src/status/tests.rs b/codex-rs/tui/src/status/tests.rs index e69f2da3c8..65a390a007 100644 --- a/codex-rs/tui/src/status/tests.rs +++ b/codex-rs/tui/src/status/tests.rs @@ -306,7 +306,7 @@ async fn status_permissions_named_read_only_profile_shows_builtin_label() { .expect("set approval policy"); config .permissions - .set_permission_profile_with_active_profile( + .set_permission_profile_from_session_snapshot( PermissionProfile::read_only(), Some(ActivePermissionProfile::new( BUILT_IN_PERMISSION_PROFILE_READ_ONLY, @@ -335,7 +335,7 @@ async fn status_permissions_read_only_profile_shows_additional_writable_roots() .with_additional_writable_roots(config.cwd.as_path(), std::slice::from_ref(&extra_root)); config .permissions - .set_permission_profile_with_active_profile( + .set_permission_profile_from_session_snapshot( PermissionProfile::from_runtime_permissions( &file_system_policy, NetworkSandboxPolicy::Restricted, @@ -363,7 +363,7 @@ async fn status_permissions_named_workspace_profile_shows_builtin_label() { .expect("set approval policy"); config .permissions - .set_permission_profile_with_active_profile( + .set_permission_profile_from_session_snapshot( PermissionProfile::workspace_write(), Some(ActivePermissionProfile::new( BUILT_IN_PERMISSION_PROFILE_WORKSPACE, @@ -389,7 +389,7 @@ async fn status_permissions_workspace_auto_review_shows_reviewer_label() { .expect("set approval policy"); config .permissions - .set_permission_profile_with_active_profile( + .set_permission_profile_from_session_snapshot( PermissionProfile::workspace_write(), Some(ActivePermissionProfile::new( BUILT_IN_PERMISSION_PROFILE_WORKSPACE, @@ -415,7 +415,7 @@ async fn status_permissions_named_profile_shows_additional_writable_roots() { let extra_root = test_path_buf("/workspace/extra").abs(); config .permissions - .set_permission_profile_with_active_profile( + .set_permission_profile_from_session_snapshot( PermissionProfile::workspace_write_with( std::slice::from_ref(&extra_root), NetworkSandboxPolicy::Restricted, @@ -451,7 +451,7 @@ async fn status_permissions_workspace_roots_show_additional_directories() { .set_workspace_roots(config.workspace_roots.clone()); config .permissions - .set_permission_profile_with_active_profile( + .set_permission_profile_from_session_snapshot( PermissionProfile::workspace_write(), Some(ActivePermissionProfile::new(":workspace")), ) @@ -474,7 +474,7 @@ async fn status_permissions_broadened_workspace_profile_shows_builtin_label() { .expect("set approval policy"); config .permissions - .set_permission_profile_with_active_profile( + .set_permission_profile_from_session_snapshot( PermissionProfile::workspace_write_with( &[], NetworkSandboxPolicy::Enabled, @@ -499,7 +499,7 @@ async fn status_permissions_user_defined_profile_shows_name() { let mut config = test_config(&temp_home).await; config .permissions - .set_permission_profile_with_active_profile( + .set_permission_profile_from_session_snapshot( PermissionProfile::read_only(), Some(ActivePermissionProfile::new("locked")), ) @@ -519,7 +519,7 @@ async fn status_snapshot_shows_active_user_defined_profile() { set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs()); config .permissions - .set_permission_profile_with_active_profile( + .set_permission_profile_from_session_snapshot( PermissionProfile::read_only(), Some(ActivePermissionProfile::new("locked")), ) @@ -617,7 +617,7 @@ async fn status_snapshot_shows_auto_review_permissions() { config.approvals_reviewer = ApprovalsReviewer::AutoReview; config .permissions - .set_permission_profile_with_active_profile( + .set_permission_profile_from_session_snapshot( PermissionProfile::workspace_write(), Some(ActivePermissionProfile::new( BUILT_IN_PERMISSION_PROFILE_WORKSPACE,