Persist active permission profiles in turn context (#39145)

## What changed

- Add the optional `active_permission_profile` field to `TurnContextItem` to record the built-in or named profile that produced the effective permission profile.
- Populate the field when serializing a turn context while preserving compatibility with records where it is absent.

## Testing

- Add coverage verifying that turn-context serialization stores the active permission profile.

GitOrigin-RevId: d69142ab1b26595ab406235f89818ef41370e963
This commit is contained in:
Shijie Rao authored and copyberry committed 2026-08-18 05:46:08 +00:00
1 parent de7bbb0481
commit 230791fd1f
14 files changed
+48

No files matched your search

@@ -703,6 +703,7 @@ mod tests {
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: codex_protocol::protocol::SandboxPolicy::new_read_only_policy(), sandbox_policy: codex_protocol::protocol::SandboxPolicy::new_read_only_policy(),
permission_profile: None, permission_profile: None,
active_permission_profile: None,
network: None, network: None,
file_system_sandbox_policy: None, file_system_sandbox_policy: None,
model: (*model).to_string(), model: (*model).to_string(),
@@ -108,6 +108,7 @@ mod persisted_resume_approval_policy_tests {
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(), sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None, permission_profile: None,
active_permission_profile: None,
network: None, network: None,
file_system_sandbox_policy: None, file_system_sandbox_policy: None,
model: "gpt-5".to_string(), model: "gpt-5".to_string(),
@@ -309,6 +309,7 @@ fn reference_context_item() -> TurnContextItem {
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(), sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None, permission_profile: None,
active_permission_profile: None,
network: None, network: None,
file_system_sandbox_policy: None, file_system_sandbox_policy: None,
model: "gpt-test".to_string(), model: "gpt-test".to_string(),
@@ -231,6 +231,7 @@ async fn record_initial_history_resumed_bare_turn_context_does_not_hydrate_previ
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: turn_context.sandbox_policy(), sandbox_policy: turn_context.sandbox_policy(),
permission_profile: None, permission_profile: None,
active_permission_profile: None,
network: None, network: None,
file_system_sandbox_policy: None, file_system_sandbox_policy: None,
model: previous_model.to_string(), model: previous_model.to_string(),
@@ -278,6 +279,7 @@ async fn record_initial_history_resumed_hydrates_previous_turn_settings_from_lif
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: turn_context.sandbox_policy(), sandbox_policy: turn_context.sandbox_policy(),
permission_profile: None, permission_profile: None,
active_permission_profile: None,
network: None, network: None,
file_system_sandbox_policy: None, file_system_sandbox_policy: None,
model: previous_model.to_string(), model: previous_model.to_string(),
@@ -1341,6 +1343,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: turn_context.sandbox_policy(), sandbox_policy: turn_context.sandbox_policy(),
permission_profile: None, permission_profile: None,
active_permission_profile: None,
network: None, network: None,
file_system_sandbox_policy: None, file_system_sandbox_policy: None,
model: previous_model.to_string(), model: previous_model.to_string(),
@@ -1430,6 +1433,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: turn_context.sandbox_policy(), sandbox_policy: turn_context.sandbox_policy(),
permission_profile: None, permission_profile: None,
active_permission_profile: None,
network: None, network: None,
file_system_sandbox_policy: None, file_system_sandbox_policy: None,
model: previous_model.to_string(), model: previous_model.to_string(),
@@ -1462,6 +1466,7 @@ async fn record_initial_history_resumed_aborted_turn_without_id_clears_active_tu
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: turn_context.sandbox_policy(), sandbox_policy: turn_context.sandbox_policy(),
permission_profile: None, permission_profile: None,
active_permission_profile: None,
network: None, network: None,
file_system_sandbox_policy: None, file_system_sandbox_policy: None,
model: previous_model.to_string(), model: previous_model.to_string(),
@@ -1592,6 +1597,7 @@ async fn record_initial_history_resumed_unmatched_abort_preserves_active_turn_fo
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: turn_context.sandbox_policy(), sandbox_policy: turn_context.sandbox_policy(),
permission_profile: None, permission_profile: None,
active_permission_profile: None,
network: None, network: None,
file_system_sandbox_policy: None, file_system_sandbox_policy: None,
model: current_model.to_string(), model: current_model.to_string(),
@@ -1719,6 +1725,7 @@ async fn record_initial_history_resumed_trailing_incomplete_turn_compaction_clea
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: turn_context.sandbox_policy(), sandbox_policy: turn_context.sandbox_policy(),
permission_profile: None, permission_profile: None,
active_permission_profile: None,
network: None, network: None,
file_system_sandbox_policy: None, file_system_sandbox_policy: None,
model: previous_model.to_string(), model: previous_model.to_string(),
@@ -1889,6 +1896,7 @@ async fn record_initial_history_resumed_replaced_incomplete_compacted_turn_clear
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: turn_context.sandbox_policy(), sandbox_policy: turn_context.sandbox_policy(),
permission_profile: None, permission_profile: None,
active_permission_profile: None,
network: None, network: None,
file_system_sandbox_policy: None, file_system_sandbox_policy: None,
model: previous_model.to_string(), model: previous_model.to_string(),
+22
View File
@@ -3497,6 +3497,7 @@ async fn record_initial_history_forked_hydrates_previous_turn_settings() {
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: turn_context.sandbox_policy(), sandbox_policy: turn_context.sandbox_policy(),
permission_profile: None, permission_profile: None,
active_permission_profile: None,
network: None, network: None,
file_system_sandbox_policy: None, file_system_sandbox_policy: None,
model: previous_model.to_string(), model: previous_model.to_string(),
@@ -9549,6 +9550,27 @@ async fn turn_context_item_omits_legacy_equivalent_file_system_sandbox_policy()
); );
} }
#[tokio::test]
async fn turn_context_item_stores_active_permission_profile() {
let (_session, mut turn_context) = make_session_and_context().await;
let active_permission_profile = ActivePermissionProfile::read_only();
let config = Arc::make_mut(&mut turn_context.config);
config
.permissions
.set_permission_profile_from_session_snapshot(PermissionProfileSnapshot::active(
PermissionProfile::read_only(),
active_permission_profile.clone(),
))
.expect("test setup should allow updating permission profile");
assert_eq!(
turn_context
.to_turn_context_item()
.active_permission_profile,
Some(active_permission_profile)
);
}
#[tokio::test] #[tokio::test]
async fn turn_context_item_stores_split_file_system_sandbox_policy_when_different() { async fn turn_context_item_stores_split_file_system_sandbox_policy_when_different() {
let (_session, mut turn_context) = make_session_and_context().await; let (_session, mut turn_context) = make_session_and_context().await;
@@ -478,6 +478,7 @@ impl TurnContext {
approvals_reviewer: Some(self.config.approvals_reviewer), approvals_reviewer: Some(self.config.approvals_reviewer),
sandbox_policy: self.sandbox_policy(), sandbox_policy: self.sandbox_policy(),
permission_profile: Some(self.permission_profile()), permission_profile: Some(self.permission_profile()),
active_permission_profile: self.config.permissions.active_permission_profile(),
network: self.turn_context_network_item(), network: self.turn_context_network_item(),
file_system_sandbox_policy: self.non_legacy_file_system_sandbox_policy(), file_system_sandbox_policy: self.non_legacy_file_system_sandbox_policy(),
model: self.model_info.slug.clone(), model: self.model_info.slug.clone(),
@@ -38,6 +38,7 @@ fn resume_history(
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: config.legacy_sandbox_policy(), sandbox_policy: config.legacy_sandbox_policy(),
permission_profile: None, permission_profile: None,
active_permission_profile: None,
network: None, network: None,
file_system_sandbox_policy: None, file_system_sandbox_policy: None,
model: previous_model.to_string(), model: previous_model.to_string(),
+5
View File
@@ -3039,6 +3039,10 @@ pub struct TurnContextItem {
pub sandbox_policy: SandboxPolicy, pub sandbox_policy: SandboxPolicy,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub permission_profile: Option<PermissionProfile>, pub permission_profile: Option<PermissionProfile>,
/// Built-in or named profile that produced `permission_profile`, when known.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub active_permission_profile: Option<ActivePermissionProfile>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub network: Option<TurnContextNetworkItem>, pub network: Option<TurnContextNetworkItem>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
@@ -5783,6 +5787,7 @@ mod tests {
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess, sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None, permission_profile: None,
active_permission_profile: None,
network: Some(TurnContextNetworkItem { network: Some(TurnContextNetworkItem {
allowed_domains: vec!["api.example.com".to_string()], allowed_domains: vec!["api.example.com".to_string()],
denied_domains: vec!["blocked.example.com".to_string()], denied_domains: vec!["blocked.example.com".to_string()],
+1
View File
@@ -1704,6 +1704,7 @@ async fn resume_candidate_matches_cwd_reads_latest_turn_context() -> std::io::Re
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(), sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None, permission_profile: None,
active_permission_profile: None,
network: None, network: None,
file_system_sandbox_policy: None, file_system_sandbox_policy: None,
model: "test-model".to_string(), model: "test-model".to_string(),
+4
View File
@@ -434,6 +434,7 @@ mod tests {
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess, sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None, permission_profile: None,
active_permission_profile: None,
network: None, network: None,
file_system_sandbox_policy: None, file_system_sandbox_policy: None,
model: "gpt-5".to_string(), model: "gpt-5".to_string(),
@@ -480,6 +481,7 @@ mod tests {
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess, sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: Some(permission_profile.clone()), permission_profile: Some(permission_profile.clone()),
active_permission_profile: None,
network: None, network: None,
file_system_sandbox_policy: None, file_system_sandbox_policy: None,
model: "gpt-5".to_string(), model: "gpt-5".to_string(),
@@ -522,6 +524,7 @@ mod tests {
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(), sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None, permission_profile: None,
active_permission_profile: None,
network: None, network: None,
file_system_sandbox_policy: None, file_system_sandbox_policy: None,
model: "gpt-5".to_string(), model: "gpt-5".to_string(),
@@ -561,6 +564,7 @@ mod tests {
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(), sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None, permission_profile: None,
active_permission_profile: None,
network: None, network: None,
file_system_sandbox_policy: None, file_system_sandbox_policy: None,
model: "gpt-5".to_string(), model: "gpt-5".to_string(),
+1
View File
@@ -926,6 +926,7 @@ mod tests {
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess, sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None, permission_profile: None,
active_permission_profile: None,
network: None, network: None,
file_system_sandbox_policy: None, file_system_sandbox_policy: None,
model: model.to_string(), model: model.to_string(),
@@ -603,6 +603,7 @@ fn turn_context(root: &Path, turn_id: &str) -> RolloutItem {
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(), sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None, permission_profile: None,
active_permission_profile: None,
network: None, network: None,
file_system_sandbox_policy: None, file_system_sandbox_policy: None,
model: "test-model".to_string(), model: "test-model".to_string(),
@@ -1398,6 +1398,7 @@ async fn migration_compacts_subagent_prefix_and_does_not_project_it() {
approvals_reviewer: None, approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(), sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None, permission_profile: None,
active_permission_profile: None,
network: None, network: None,
file_system_sandbox_policy: None, file_system_sandbox_policy: None,
model: "test-model".to_string(), model: "test-model".to_string(),