feat: support managed permission profiles in requirements.toml (#23433)

## Why

Cloud-managed `requirements.toml` should be able to define the managed
permission profiles a client may select and constrain that selectable
set without requiring local user config to recreate the profile catalog.

This keeps requirements focused on restrictions. The selected default
remains a config or session choice, while requirements contribute the
managed profile bodies and `allowed_permissions` allowlist that the
config-loading boundary validates before a resolved runtime
`PermissionProfile` is installed.

## What changed

- Add `requirements.toml` support for a managed permission-profile
catalog plus its allowlist:

```toml
allowed_permissions = ["review", "build"]

[permissions.review]
extends = ":read-only"

[permissions.build]
extends = ":workspace"
```

- Merge requirements-defined profile bodies into the effective
permission catalog and reject profile ids that collide with
config-defined profiles.
- Validate that every `allowed_permissions` entry resolves to a built-in
or catalog profile before selection uses it.
- Preserve allowed configured named-profile selections. When a
configured named profile is disallowed, fall back to the first allowed
requirements profile with a startup warning.
- Keep built-in selections and the stock trust-based `:read-only` /
`:workspace` fallback path intact when no permission profile is
explicitly selected.
- Centralize the managed catalog and allowlist selection path in
`EffectivePermissionSelection` so the requirements boundary is visible
in config loading.
- Surface `allowedPermissions` through `configRequirements/read`, and
update the generated app-server schema fixtures plus the app-server
README.

## Validation

- `cargo test -p codex-config`
- `cargo test -p codex-core system_requirements_`
- `cargo test -p codex-core system_allowed_permissions_`
- `cargo test -p codex-app-server-protocol`
- `just write-app-server-schema`

## Related work

- Uses merged permission-profile inheritance support from #22270 and
#23705.
- Kept separate from the in-flight permission profile listing API in
#23412.
This commit is contained in:
viyatb-oai authored and GitHub committed 2026-05-20 17:33:01 -07:00
1 parent c9ff067e31
commit 713a5b1b00
14 files changed
+638 -35

No files matched your search

@@ -419,6 +419,7 @@ fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigR
.filter_map(map_sandbox_mode_requirement_to_api)
.collect()
}),
allowed_permissions: requirements.allowed_permissions,
allowed_web_search_modes: requirements.allowed_web_search_modes.map(|modes| {
let mut normalized = modes
.into_iter()
@@ -638,10 +639,21 @@ mod tests {
#[test]
fn requirements_api_includes_allow_managed_hooks_only() {
let mapped = map_requirements_toml_to_api(ConfigRequirementsToml {
allowed_permissions: Some(vec![
"managed-standard".to_string(),
"managed-build".to_string(),
]),
allow_managed_hooks_only: Some(true),
..ConfigRequirementsToml::default()
});
assert_eq!(
mapped.allowed_permissions,
Some(vec![
"managed-standard".to_string(),
"managed-build".to_string(),
])
);
assert_eq!(mapped.allow_managed_hooks_only, Some(true));
assert_eq!(mapped.hooks, None);
}