mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
Support forced workspace ids in requirements
Allow requirements.toml and cloud requirements to specify forced_chatgpt_workspace_id using the same single string or array shape as config.toml. Prefer the requirements value during config resolution, expose it through configRequirements/read, and regenerate app-server schema fixtures. Document requirements precedence and add targeted coverage for requirements parsing, config resolution, API mapping, and cloud requirements fixtures.
This commit is contained in:
@@ -6922,6 +6922,16 @@
|
||||
"object",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"forcedChatgptWorkspaceId": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/ForcedChatgptWorkspaceIds"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
||||
@@ -3539,6 +3539,16 @@
|
||||
"object",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"forcedChatgptWorkspaceId": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ForcedChatgptWorkspaceIds"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
||||
@@ -106,10 +106,34 @@
|
||||
"object",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"forcedChatgptWorkspaceId": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ForcedChatgptWorkspaceIds"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ForcedChatgptWorkspaceIds": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
],
|
||||
"description": "Workspace IDs that ChatGPT auth is allowed to use.\n\nThe config parser accepts the historical single-string form and the newer list form for deployments that allow multiple ChatGPT workspaces."
|
||||
},
|
||||
"NetworkDomainPermission": {
|
||||
"enum": [
|
||||
"allow",
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ForcedChatgptWorkspaceIds } from "../ForcedChatgptWorkspaceIds";
|
||||
import type { WebSearchMode } from "../WebSearchMode";
|
||||
import type { AskForApproval } from "./AskForApproval";
|
||||
import type { ResidencyRequirement } from "./ResidencyRequirement";
|
||||
import type { SandboxMode } from "./SandboxMode";
|
||||
|
||||
export type ConfigRequirements = {allowedApprovalPolicies: Array<AskForApproval> | null, allowedSandboxModes: Array<SandboxMode> | null, allowedWebSearchModes: Array<WebSearchMode> | null, featureRequirements: { [key in string]?: boolean } | null, enforceResidency: ResidencyRequirement | null};
|
||||
export type ConfigRequirements = {allowedApprovalPolicies: Array<AskForApproval> | null, allowedSandboxModes: Array<SandboxMode> | null, allowedWebSearchModes: Array<WebSearchMode> | null, featureRequirements: { [key in string]?: boolean } | null, enforceResidency: ResidencyRequirement | null, forcedChatgptWorkspaceId: ForcedChatgptWorkspaceIds | null};
|
||||
|
||||
@@ -873,6 +873,7 @@ pub struct ConfigRequirements {
|
||||
pub enforce_residency: Option<ResidencyRequirement>,
|
||||
#[experimental("configRequirements/read.network")]
|
||||
pub network: Option<NetworkRequirements>,
|
||||
pub forced_chatgpt_workspace_id: Option<ForcedChatgptWorkspaceIds>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
@@ -7700,6 +7701,7 @@ mod tests {
|
||||
feature_requirements: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
});
|
||||
|
||||
assert_eq!(reason, Some("askForApproval.granular"));
|
||||
|
||||
@@ -398,6 +398,7 @@ fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigR
|
||||
.enforce_residency
|
||||
.map(map_residency_requirement_to_api),
|
||||
network: requirements.network.map(map_network_requirements_to_api),
|
||||
forced_chatgpt_workspace_id: requirements.forced_chatgpt_workspace_id,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,6 +568,12 @@ mod tests {
|
||||
codex_core::config_loader::WebSearchModeRequirement::Cached,
|
||||
]),
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: Some(
|
||||
codex_protocol::config_types::ForcedChatgptWorkspaceIds::Multiple(vec![
|
||||
"workspace-a".to_string(),
|
||||
"workspace-b".to_string(),
|
||||
]),
|
||||
),
|
||||
feature_requirements: Some(codex_core::config_loader::FeatureRequirementsToml {
|
||||
entries: std::collections::BTreeMap::from([
|
||||
("apps".to_string(), false),
|
||||
@@ -642,6 +649,15 @@ mod tests {
|
||||
mapped.enforce_residency,
|
||||
Some(codex_app_server_protocol::ResidencyRequirement::Us),
|
||||
);
|
||||
assert_eq!(
|
||||
mapped.forced_chatgpt_workspace_id,
|
||||
Some(
|
||||
codex_protocol::config_types::ForcedChatgptWorkspaceIds::Multiple(vec![
|
||||
"workspace-a".to_string(),
|
||||
"workspace-b".to_string(),
|
||||
])
|
||||
),
|
||||
);
|
||||
assert_eq!(
|
||||
mapped.network,
|
||||
Some(NetworkRequirements {
|
||||
@@ -676,6 +692,7 @@ mod tests {
|
||||
allowed_sandbox_modes: None,
|
||||
allowed_web_search_modes: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
feature_requirements: None,
|
||||
mcp_servers: None,
|
||||
apps: None,
|
||||
@@ -733,6 +750,7 @@ mod tests {
|
||||
allowed_sandbox_modes: None,
|
||||
allowed_web_search_modes: Some(Vec::new()),
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
feature_requirements: None,
|
||||
mcp_servers: None,
|
||||
apps: None,
|
||||
|
||||
@@ -1166,6 +1166,7 @@ mod tests {
|
||||
allowed_sandbox_modes: None,
|
||||
allowed_web_search_modes: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
feature_requirements: None,
|
||||
mcp_servers: None,
|
||||
apps: None,
|
||||
@@ -1195,6 +1196,7 @@ mod tests {
|
||||
allowed_sandbox_modes: None,
|
||||
allowed_web_search_modes: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
feature_requirements: None,
|
||||
mcp_servers: None,
|
||||
apps: None,
|
||||
@@ -1224,6 +1226,7 @@ mod tests {
|
||||
allowed_sandbox_modes: None,
|
||||
allowed_web_search_modes: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
feature_requirements: None,
|
||||
mcp_servers: None,
|
||||
apps: None,
|
||||
@@ -1270,6 +1273,7 @@ mod tests {
|
||||
allowed_sandbox_modes: None,
|
||||
allowed_web_search_modes: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
feature_requirements: None,
|
||||
mcp_servers: None,
|
||||
apps: None,
|
||||
@@ -1352,6 +1356,7 @@ enabled = false
|
||||
allowed_sandbox_modes: None,
|
||||
allowed_web_search_modes: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
feature_requirements: None,
|
||||
mcp_servers: None,
|
||||
apps: None,
|
||||
@@ -1424,6 +1429,7 @@ enabled = false
|
||||
allowed_sandbox_modes: None,
|
||||
allowed_web_search_modes: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
feature_requirements: None,
|
||||
mcp_servers: None,
|
||||
apps: None,
|
||||
@@ -1494,6 +1500,7 @@ enabled = false
|
||||
allowed_sandbox_modes: None,
|
||||
allowed_web_search_modes: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
feature_requirements: None,
|
||||
mcp_servers: None,
|
||||
apps: None,
|
||||
@@ -1691,6 +1698,7 @@ enabled = false
|
||||
allowed_sandbox_modes: None,
|
||||
allowed_web_search_modes: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
feature_requirements: None,
|
||||
mcp_servers: None,
|
||||
apps: None,
|
||||
@@ -1726,6 +1734,7 @@ enabled = false
|
||||
allowed_sandbox_modes: None,
|
||||
allowed_web_search_modes: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
feature_requirements: None,
|
||||
mcp_servers: None,
|
||||
apps: None,
|
||||
@@ -1781,6 +1790,7 @@ enabled = false
|
||||
allowed_sandbox_modes: None,
|
||||
allowed_web_search_modes: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
feature_requirements: None,
|
||||
mcp_servers: None,
|
||||
apps: None,
|
||||
@@ -1831,6 +1841,7 @@ enabled = false
|
||||
allowed_sandbox_modes: None,
|
||||
allowed_web_search_modes: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
feature_requirements: None,
|
||||
mcp_servers: None,
|
||||
apps: None,
|
||||
@@ -1885,6 +1896,7 @@ enabled = false
|
||||
allowed_sandbox_modes: None,
|
||||
allowed_web_search_modes: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
feature_requirements: None,
|
||||
mcp_servers: None,
|
||||
apps: None,
|
||||
@@ -1940,6 +1952,7 @@ enabled = false
|
||||
allowed_sandbox_modes: None,
|
||||
allowed_web_search_modes: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
feature_requirements: None,
|
||||
mcp_servers: None,
|
||||
apps: None,
|
||||
@@ -1995,6 +2008,7 @@ enabled = false
|
||||
allowed_sandbox_modes: None,
|
||||
allowed_web_search_modes: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
feature_requirements: None,
|
||||
mcp_servers: None,
|
||||
apps: None,
|
||||
@@ -2083,6 +2097,7 @@ enabled = false
|
||||
allowed_sandbox_modes: None,
|
||||
allowed_web_search_modes: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
feature_requirements: None,
|
||||
mcp_servers: None,
|
||||
apps: None,
|
||||
@@ -2110,6 +2125,7 @@ enabled = false
|
||||
allowed_sandbox_modes: None,
|
||||
allowed_web_search_modes: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
feature_requirements: None,
|
||||
mcp_servers: None,
|
||||
apps: None,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use codex_protocol::config_types::ForcedChatgptWorkspaceIds;
|
||||
use codex_protocol::config_types::SandboxMode;
|
||||
use codex_protocol::config_types::WebSearchMode;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
@@ -505,6 +506,7 @@ pub struct ConfigRequirementsToml {
|
||||
#[serde(rename = "experimental_network")]
|
||||
pub network: Option<NetworkRequirementsToml>,
|
||||
pub guardian_policy_config: Option<String>,
|
||||
pub forced_chatgpt_workspace_id: Option<ForcedChatgptWorkspaceIds>,
|
||||
}
|
||||
|
||||
/// Value paired with the requirement source it came from, for better error
|
||||
@@ -542,6 +544,7 @@ pub struct ConfigRequirementsWithSources {
|
||||
pub enforce_residency: Option<Sourced<ResidencyRequirement>>,
|
||||
pub network: Option<Sourced<NetworkRequirementsToml>>,
|
||||
pub guardian_policy_config: Option<Sourced<String>>,
|
||||
pub forced_chatgpt_workspace_id: Option<Sourced<ForcedChatgptWorkspaceIds>>,
|
||||
}
|
||||
|
||||
impl ConfigRequirementsWithSources {
|
||||
@@ -574,6 +577,7 @@ impl ConfigRequirementsWithSources {
|
||||
enforce_residency: _,
|
||||
network: _,
|
||||
guardian_policy_config: _,
|
||||
forced_chatgpt_workspace_id: _,
|
||||
} = &other;
|
||||
|
||||
let mut other = other;
|
||||
@@ -599,6 +603,7 @@ impl ConfigRequirementsWithSources {
|
||||
enforce_residency,
|
||||
network,
|
||||
guardian_policy_config,
|
||||
forced_chatgpt_workspace_id,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -624,6 +629,7 @@ impl ConfigRequirementsWithSources {
|
||||
enforce_residency,
|
||||
network,
|
||||
guardian_policy_config,
|
||||
forced_chatgpt_workspace_id,
|
||||
} = self;
|
||||
ConfigRequirementsToml {
|
||||
allowed_approval_policies: allowed_approval_policies.map(|sourced| sourced.value),
|
||||
@@ -637,6 +643,7 @@ impl ConfigRequirementsWithSources {
|
||||
enforce_residency: enforce_residency.map(|sourced| sourced.value),
|
||||
network: network.map(|sourced| sourced.value),
|
||||
guardian_policy_config: guardian_policy_config.map(|sourced| sourced.value),
|
||||
forced_chatgpt_workspace_id: forced_chatgpt_workspace_id.map(|sourced| sourced.value),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -696,6 +703,11 @@ impl ConfigRequirementsToml {
|
||||
.guardian_policy_config
|
||||
.as_deref()
|
||||
.is_none_or(|value| value.trim().is_empty())
|
||||
&& self
|
||||
.forced_chatgpt_workspace_id
|
||||
.clone()
|
||||
.and_then(ForcedChatgptWorkspaceIds::normalized)
|
||||
.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -715,6 +727,7 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
|
||||
enforce_residency,
|
||||
network,
|
||||
guardian_policy_config: _guardian_policy_config,
|
||||
forced_chatgpt_workspace_id: _forced_chatgpt_workspace_id,
|
||||
} = toml;
|
||||
|
||||
let approval_policy = match allowed_approval_policies {
|
||||
@@ -968,6 +981,7 @@ mod tests {
|
||||
enforce_residency,
|
||||
network,
|
||||
guardian_policy_config,
|
||||
forced_chatgpt_workspace_id,
|
||||
} = toml;
|
||||
ConfigRequirementsWithSources {
|
||||
allowed_approval_policies: allowed_approval_policies
|
||||
@@ -988,6 +1002,8 @@ mod tests {
|
||||
network: network.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
guardian_policy_config: guardian_policy_config
|
||||
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
forced_chatgpt_workspace_id: forced_chatgpt_workspace_id
|
||||
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1013,6 +1029,10 @@ mod tests {
|
||||
let enforce_residency = ResidencyRequirement::Us;
|
||||
let enforce_source = source.clone();
|
||||
let guardian_policy_config = "Use the company-managed guardian policy.".to_string();
|
||||
let forced_chatgpt_workspace_id = ForcedChatgptWorkspaceIds::Multiple(vec![
|
||||
"workspace-a".to_string(),
|
||||
"workspace-b".to_string(),
|
||||
]);
|
||||
|
||||
// Intentionally constructed without `..Default::default()` so adding a new field to
|
||||
// `ConfigRequirementsToml` forces this test to be updated.
|
||||
@@ -1028,6 +1048,7 @@ mod tests {
|
||||
enforce_residency: Some(enforce_residency),
|
||||
network: None,
|
||||
guardian_policy_config: Some(guardian_policy_config.clone()),
|
||||
forced_chatgpt_workspace_id: Some(forced_chatgpt_workspace_id.clone()),
|
||||
};
|
||||
|
||||
target.merge_unset_fields(source.clone(), other);
|
||||
@@ -1058,6 +1079,10 @@ mod tests {
|
||||
enforce_residency: Some(Sourced::new(enforce_residency, enforce_source)),
|
||||
network: None,
|
||||
guardian_policy_config: Some(Sourced::new(guardian_policy_config, source)),
|
||||
forced_chatgpt_workspace_id: Some(Sourced::new(
|
||||
forced_chatgpt_workspace_id,
|
||||
RequirementSource::LegacyManagedConfigTomlFromMdm
|
||||
)),
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1094,6 +1119,7 @@ mod tests {
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
@@ -1138,6 +1164,7 @@ mod tests {
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
@@ -1193,6 +1220,28 @@ Use the cloud-managed guardian policy.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_forced_chatgpt_workspace_id() -> Result<()> {
|
||||
let single: ConfigRequirementsToml =
|
||||
from_str(r#"forced_chatgpt_workspace_id = "workspace-a""#)?;
|
||||
assert_eq!(
|
||||
single.forced_chatgpt_workspace_id,
|
||||
Some(ForcedChatgptWorkspaceIds::Single("workspace-a".to_string()))
|
||||
);
|
||||
|
||||
let multiple: ConfigRequirementsToml =
|
||||
from_str(r#"forced_chatgpt_workspace_id = ["workspace-a", "workspace-b"]"#)?;
|
||||
assert_eq!(
|
||||
multiple.forced_chatgpt_workspace_id,
|
||||
Some(ForcedChatgptWorkspaceIds::Multiple(vec![
|
||||
"workspace-a".to_string(),
|
||||
"workspace-b".to_string(),
|
||||
]))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blank_guardian_policy_config_is_empty() -> Result<()> {
|
||||
let requirements: ConfigRequirementsToml = from_str(
|
||||
|
||||
@@ -4477,6 +4477,39 @@ fn forced_chatgpt_workspace_id_accepts_string_and_list() -> std::io::Result<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requirements_forced_chatgpt_workspace_id_accepts_string_and_list() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let requirements_toml: crate::config_loader::ConfigRequirementsToml =
|
||||
toml::from_str(r#"forced_chatgpt_workspace_id = [" org_a ", "", "org_b"]"#)
|
||||
.expect("requirements workspace id list should parse");
|
||||
let config_layer_stack =
|
||||
ConfigLayerStack::new(Vec::new(), Default::default(), requirements_toml)
|
||||
.map_err(std::io::Error::other)?;
|
||||
|
||||
let config = Config::load_config_with_layer_stack(
|
||||
ConfigToml {
|
||||
forced_chatgpt_workspace_id: Some(ForcedChatgptWorkspaceIds::Single(
|
||||
"org_config".to_string(),
|
||||
)),
|
||||
..Default::default()
|
||||
},
|
||||
ConfigOverrides::default(),
|
||||
codex_home.abs(),
|
||||
config_layer_stack,
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
config.forced_chatgpt_workspace_id,
|
||||
Some(ForcedChatgptWorkspaceIds::Multiple(vec![
|
||||
"org_a".to_string(),
|
||||
"org_b".to_string()
|
||||
]))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_test_fixture() -> std::io::Result<PrecedenceTestFixture> {
|
||||
let toml = r#"
|
||||
model = "o3"
|
||||
@@ -5172,6 +5205,7 @@ fn test_requirements_web_search_mode_allowlist_does_not_warn_when_unset() -> any
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
};
|
||||
let requirement_source = crate::config_loader::RequirementSource::Unknown;
|
||||
let requirement_source_for_error = requirement_source.clone();
|
||||
@@ -5793,6 +5827,7 @@ async fn explicit_sandbox_mode_falls_back_when_disallowed_by_requirements() -> s
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
};
|
||||
|
||||
let config = ConfigBuilder::without_managed_config_for_tests()
|
||||
|
||||
@@ -1817,9 +1817,12 @@ impl Config {
|
||||
let include_apply_patch_tool_flag = features.enabled(Feature::ApplyPatchFreeform);
|
||||
let use_experimental_unified_exec_tool = features.enabled(Feature::UnifiedExec);
|
||||
|
||||
let forced_chatgpt_workspace_id = cfg
|
||||
.forced_chatgpt_workspace_id
|
||||
.and_then(ForcedChatgptWorkspaceIds::normalized);
|
||||
let forced_chatgpt_workspace_id =
|
||||
forced_chatgpt_workspace_id_from_requirements(config_layer_stack.requirements_toml())
|
||||
.or_else(|| {
|
||||
cfg.forced_chatgpt_workspace_id
|
||||
.and_then(ForcedChatgptWorkspaceIds::normalized)
|
||||
});
|
||||
|
||||
let forced_login_method = cfg.forced_login_method;
|
||||
|
||||
@@ -2312,6 +2315,15 @@ fn guardian_policy_config_from_requirements(
|
||||
})
|
||||
}
|
||||
|
||||
fn forced_chatgpt_workspace_id_from_requirements(
|
||||
requirements_toml: &ConfigRequirementsToml,
|
||||
) -> Option<ForcedChatgptWorkspaceIds> {
|
||||
requirements_toml
|
||||
.forced_chatgpt_workspace_id
|
||||
.clone()
|
||||
.and_then(ForcedChatgptWorkspaceIds::normalized)
|
||||
}
|
||||
|
||||
fn toml_uses_deprecated_instructions_file(value: &TomlValue) -> bool {
|
||||
let Some(table) = value.as_table() else {
|
||||
return false;
|
||||
|
||||
@@ -13,6 +13,7 @@ use codex_config::config_toml::ConfigToml;
|
||||
use codex_config::config_toml::ProjectConfig;
|
||||
use codex_git_utils::resolve_root_git_project_for_trust;
|
||||
use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use codex_protocol::config_types::ForcedChatgptWorkspaceIds;
|
||||
use codex_protocol::config_types::SandboxMode;
|
||||
use codex_protocol::config_types::TrustLevel;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
@@ -885,6 +886,7 @@ struct LegacyManagedConfigToml {
|
||||
approval_policy: Option<AskForApproval>,
|
||||
approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
sandbox_mode: Option<SandboxMode>,
|
||||
forced_chatgpt_workspace_id: Option<ForcedChatgptWorkspaceIds>,
|
||||
}
|
||||
|
||||
impl From<LegacyManagedConfigToml> for ConfigRequirementsToml {
|
||||
@@ -895,6 +897,7 @@ impl From<LegacyManagedConfigToml> for ConfigRequirementsToml {
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox_mode,
|
||||
forced_chatgpt_workspace_id,
|
||||
} = legacy;
|
||||
if let Some(approval_policy) = approval_policy {
|
||||
config_requirements_toml.allowed_approval_policies = Some(vec![approval_policy]);
|
||||
@@ -916,6 +919,7 @@ impl From<LegacyManagedConfigToml> for ConfigRequirementsToml {
|
||||
}
|
||||
config_requirements_toml.allowed_sandbox_modes = Some(allowed_modes);
|
||||
}
|
||||
config_requirements_toml.forced_chatgpt_workspace_id = forced_chatgpt_workspace_id;
|
||||
config_requirements_toml
|
||||
}
|
||||
}
|
||||
@@ -971,6 +975,7 @@ foo = "xyzzy"
|
||||
approval_policy: None,
|
||||
approvals_reviewer: None,
|
||||
sandbox_mode: Some(SandboxMode::WorkspaceWrite),
|
||||
forced_chatgpt_workspace_id: None,
|
||||
};
|
||||
|
||||
let requirements = ConfigRequirementsToml::from(legacy);
|
||||
@@ -990,6 +995,7 @@ foo = "xyzzy"
|
||||
approval_policy: None,
|
||||
approvals_reviewer: Some(ApprovalsReviewer::GuardianSubagent),
|
||||
sandbox_mode: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
};
|
||||
|
||||
let requirements = ConfigRequirementsToml::from(legacy);
|
||||
@@ -1009,6 +1015,7 @@ foo = "xyzzy"
|
||||
approval_policy: None,
|
||||
approvals_reviewer: Some(ApprovalsReviewer::User),
|
||||
sandbox_mode: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
};
|
||||
|
||||
let requirements = ConfigRequirementsToml::from(legacy);
|
||||
|
||||
@@ -637,6 +637,7 @@ allowed_approval_policies = ["on-request"]
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
}))
|
||||
}),
|
||||
)
|
||||
@@ -689,6 +690,7 @@ allowed_approval_policies = ["on-request"]
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
},
|
||||
);
|
||||
load_requirements_toml(&mut config_requirements_toml, &requirements_file).await?;
|
||||
@@ -730,6 +732,7 @@ async fn load_config_layers_includes_cloud_requirements() -> anyhow::Result<()>
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
};
|
||||
let expected = requirements.clone();
|
||||
let cloud_requirements = CloudRequirementsLoader::new(async move { Ok(Some(requirements)) });
|
||||
|
||||
@@ -612,6 +612,7 @@ mod tests {
|
||||
allowed_sandbox_modes: Some(vec![SandboxModeRequirement::ReadOnly]),
|
||||
allowed_web_search_modes: Some(vec![WebSearchModeRequirement::Cached]),
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
feature_requirements: Some(FeatureRequirementsToml {
|
||||
entries: BTreeMap::from([("guardian_approval".to_string(), true)]),
|
||||
}),
|
||||
@@ -805,6 +806,7 @@ approval_policy = "never"
|
||||
allowed_sandbox_modes: None,
|
||||
allowed_web_search_modes: Some(Vec::new()),
|
||||
guardian_policy_config: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
feature_requirements: None,
|
||||
mcp_servers: None,
|
||||
apps: None,
|
||||
|
||||
+5
-3
@@ -57,8 +57,8 @@ The generated JSON Schema for `config.toml` lives at `codex-rs/core/config.schem
|
||||
## Managed ChatGPT Workspace Restrictions
|
||||
|
||||
Managed deployments can restrict ChatGPT login to one or more workspace IDs with
|
||||
`forced_chatgpt_workspace_id`. Existing single-workspace configurations remain
|
||||
valid:
|
||||
`forced_chatgpt_workspace_id` in either `config.toml` or `requirements.toml`.
|
||||
Existing single-workspace configurations remain valid:
|
||||
|
||||
```toml
|
||||
forced_chatgpt_workspace_id = "workspace_id"
|
||||
@@ -73,7 +73,9 @@ forced_chatgpt_workspace_id = ["workspace_id_a", "workspace_id_b"]
|
||||
Codex trims whitespace from configured workspace IDs and ignores empty entries.
|
||||
An empty list remains an active allowlist and allows no ChatGPT workspace. If
|
||||
this setting is present, ChatGPT credentials whose token workspace ID is not in
|
||||
the configured allowlist are rejected and logged out.
|
||||
the configured allowlist are rejected and logged out. A value from
|
||||
`requirements.toml` takes precedence over ordinary config because requirements
|
||||
are managed policy.
|
||||
|
||||
## SQLite State DB
|
||||
|
||||
|
||||
Reference in New Issue
Block a user