Preserve legacy sandbox templates in TUI permission conversions (#60936)

## What changed

Add lossless conversions between legacy sandbox templates and `PermissionProfile` without probing workspace metadata, collapsing fixed roots into current-directory access, or deduplicating writable roots. Reject profiles whose restrictions cannot survive the round trip.

Use these conversions for sandbox inheritance in `create_thread` and for validating read-only temporary structured threads.

## Testing

Extend coverage for duplicate writable roots, existing `.git` metadata, unrepresentable restrictions, and child threads inheriting writable roots, network access, and temporary-directory exclusions.

GitOrigin-RevId: cb37acaf615b09f45680b4b0c4152470fbc04abb
This commit is contained in:
ioanbudea-OAI
2026-11-12 00:13:27 +00:00
committed by copyberry
parent 27294165dc
commit c93732887e
7 changed files with 240 additions and 48 deletions
@@ -2876,8 +2876,11 @@ fn mcp_server_status_serializes_absent_server_info_metadata_as_null() {
#[test]
fn sandbox_policy_round_trips_workspace_write_access() {
let directory = tempfile::tempdir().expect("tempdir");
let root = AbsolutePathBuf::from_absolute_path(directory.path()).expect("absolute root");
std::fs::create_dir(root.join(".git")).expect("git metadata");
let v2_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
writable_roots: vec![root.clone(), root.clone()],
network_access: true,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -2887,13 +2890,39 @@ fn sandbox_policy_round_trips_workspace_write_access() {
assert_eq!(
core_policy,
codex_protocol::protocol::SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
writable_roots: vec![root.clone(), root],
network_access: true,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
}
);
let profile = codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy_template(
&v2_policy.to_core(),
);
assert!(
!profile
.file_system_sandbox_policy()
.entries
.iter()
.any(|entry| {
entry.access == codex_protocol::permissions::FileSystemAccessMode::Read
&& matches!(
entry.path,
codex_protocol::permissions::FileSystemPath::Path { .. }
)
})
);
assert_eq!(
SandboxPolicy::from(
profile
.to_legacy_sandbox_policy_template()
.expect("portable legacy template")
),
v2_policy,
);
let back_to_v2 = SandboxPolicy::from(core_policy);
assert_eq!(back_to_v2, v2_policy);
}