app-server: include filesystem entries in permission requests (#19086)

## Why

`item/permissions/requestApproval` sends a requested permission profile
to app-server clients. The core profile already stores filesystem
permissions as `entries`, but the v2 compatibility conversion used the
legacy `read`/`write` projection whenever possible and left `entries`
unset.

That made the request ambiguous for clients that consume the canonical
v2 shape: `permissions.fileSystem.entries` was missing even though
filesystem access was being requested. A client that rendered or echoed
grants from `entries` could treat the request as having no filesystem
permission entries, then return an empty or incomplete grant. The
app-server intersects responses with the original request, so omitted
filesystem permissions are denied.

## What Changed

- Populate `AdditionalFileSystemPermissions.entries` when converting
legacy read/write roots for request permission payloads, while
preserving `read` and `write` for compatibility.
- Mark `read` and `write` as transitional schema fields in the generated
app-server schema.
- Add regression coverage for the v2 conversion, the app-server
`item/permissions/requestApproval` round trip, and TUI app-server
approval conversion expectations.
- Refresh generated JSON and TypeScript schema fixtures.

## Verification

- `just fmt`
- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-app-server request_permissions_round_trip`
- `cargo test -p codex-tui
converts_request_permissions_into_granted_permissions`
- `cargo test -p codex-tui
resolves_permissions_and_user_input_through_app_server_request_id`
This commit is contained in:
Michael Bolin
2026-04-23 00:21:59 -07:00
committed by GitHub
Unverified
parent 993e3f407e
commit 8bc667b07b
14 changed files with 134 additions and 6 deletions
@@ -78,12 +78,32 @@ async fn request_permissions_round_trip() -> Result<()> {
assert_eq!(params.item_id, "call1");
assert!(params.cwd.as_path().is_absolute());
assert_eq!(params.reason, Some("Select a workspace root".to_string()));
let requested_writes = params
let requested_file_system = params
.permissions
.file_system
.and_then(|file_system| file_system.write)
.expect("request should include file system permissions");
let requested_writes = requested_file_system
.write
.clone()
.expect("request should include write permissions");
assert_eq!(requested_writes.len(), 2);
assert_eq!(
requested_file_system.entries,
Some(vec![
codex_app_server_protocol::FileSystemSandboxEntry {
path: codex_app_server_protocol::FileSystemPath::Path {
path: requested_writes[0].clone(),
},
access: codex_app_server_protocol::FileSystemAccessMode::Write,
},
codex_app_server_protocol::FileSystemSandboxEntry {
path: codex_app_server_protocol::FileSystemPath::Path {
path: requested_writes[1].clone(),
},
access: codex_app_server_protocol::FileSystemAccessMode::Write,
},
])
);
let resolved_request_id = request_id.clone();
mcp.send_response(