fix(#108): restreint Code Mode Codex au namespace MCP IdeA et documente les tools au démarrage
Codex Code Mode pouvait appeler n'importe quel outil MCP directement, contournant la médiation d'approbation. On force [features.code_mode].direct_only_tool_namespaces = ["mcp__idea"] sur chaque surface qui écrit le config.toml Codex (permission projector, lifecycle, migration run-dir, assistant de ticket), et on ajoute initialize.instructions côté serveur MCP pour orienter Codex vers le bon outil idea_* dès la connexion, sans dépendre de la recherche sémantique différée. QA : domain 283/0, application 126/0 + agent_lifecycle 73/0 + change_agent_profile 19/0 + ticket_assistant 5/0, infrastructure 339/0 dont mcp_server 37/0, backend 68/0 (7 ignored). Les échecs web-server observés sur cargo test --workspace (Too many open files, cookies) sont une contamination de ressources inter-tests ; les deux tests concernés repassent isolément. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -3089,6 +3089,16 @@ impl PermissionProjector for FakeClaudeProjector {
|
||||
struct FakeCodexProjector;
|
||||
|
||||
impl FakeCodexProjector {
|
||||
const CODE_MODE_TOML: &'static str =
|
||||
"[features.code_mode]\ndirect_only_tool_namespaces = [\"mcp__idea\"]\n";
|
||||
|
||||
fn managed_tables() -> Vec<String> {
|
||||
vec![
|
||||
"sandbox_workspace_write".to_owned(),
|
||||
"features.code_mode".to_owned(),
|
||||
]
|
||||
}
|
||||
|
||||
fn modes(fallback: Posture) -> (&'static str, &'static str) {
|
||||
match fallback {
|
||||
Posture::Deny => ("read-only", "on-request"),
|
||||
@ -3121,9 +3131,12 @@ impl PermissionProjector for FakeCodexProjector {
|
||||
let network_access = matches!(network, Some(NetworkPolicy::Allow));
|
||||
let network_file = ProjectedFile::MergeToml {
|
||||
rel_path: ".codex/config.toml".to_owned(),
|
||||
managed_tables: vec!["sandbox_workspace_write".to_owned()],
|
||||
managed_tables: Self::managed_tables(),
|
||||
managed_keys: Vec::new(),
|
||||
contents: format!("[sandbox_workspace_write]\nnetwork_access = {network_access}\n"),
|
||||
contents: format!(
|
||||
"[sandbox_workspace_write]\nnetwork_access = {network_access}\n\n{}",
|
||||
Self::CODE_MODE_TOML
|
||||
),
|
||||
};
|
||||
let Some(eff) = eff else {
|
||||
return PermissionProjection {
|
||||
@ -3134,7 +3147,8 @@ impl PermissionProjector for FakeCodexProjector {
|
||||
};
|
||||
let (sandbox, approval) = Self::modes(eff.fallback());
|
||||
let contents = format!(
|
||||
"sandbox_mode = \"{sandbox}\"\napproval_policy = \"{approval}\"\n\n[sandbox_workspace_write]\nnetwork_access = {network_access}\n"
|
||||
"sandbox_mode = \"{sandbox}\"\napproval_policy = \"{approval}\"\n\n[sandbox_workspace_write]\nnetwork_access = {network_access}\n\n{}",
|
||||
Self::CODE_MODE_TOML
|
||||
);
|
||||
let mut args = vec![
|
||||
"--sandbox".to_owned(),
|
||||
@ -3149,7 +3163,7 @@ impl PermissionProjector for FakeCodexProjector {
|
||||
PermissionProjection {
|
||||
files: vec![ProjectedFile::MergeToml {
|
||||
rel_path: ".codex/config.toml".to_owned(),
|
||||
managed_tables: vec!["sandbox_workspace_write".to_owned()],
|
||||
managed_tables: Self::managed_tables(),
|
||||
managed_keys: vec!["sandbox_mode".to_owned(), "approval_policy".to_owned()],
|
||||
contents,
|
||||
}],
|
||||
@ -3665,7 +3679,7 @@ async fn codex_mergetoml_upserts_managed_keys_and_preserves_unmanaged() {
|
||||
// Pre-existing config with an unmanaged top-level key + an unmanaged table.
|
||||
fs.seed_read(
|
||||
&cfg_path,
|
||||
"user_key = \"keep-me\"\n[mcp_servers.idea]\ncommand = \"idea\"\n",
|
||||
"user_key = \"keep-me\"\n[mcp_servers.idea]\ncommand = \"idea\"\n\n[features.preview]\nenabled = true\n",
|
||||
);
|
||||
|
||||
// First projection.
|
||||
@ -3689,6 +3703,10 @@ async fn codex_mergetoml_upserts_managed_keys_and_preserves_unmanaged() {
|
||||
first.contains("[mcp_servers.idea]"),
|
||||
"unmanaged table preserved: {first}"
|
||||
);
|
||||
assert!(
|
||||
first.contains("[features.preview]\nenabled = true"),
|
||||
"unmanaged sibling features table preserved: {first}"
|
||||
);
|
||||
assert!(
|
||||
first.contains("sandbox_mode = \"workspace-write\""),
|
||||
"managed sandbox_mode upserted: {first}"
|
||||
@ -3697,6 +3715,10 @@ async fn codex_mergetoml_upserts_managed_keys_and_preserves_unmanaged() {
|
||||
first.contains("approval_policy = \"never\""),
|
||||
"managed approval_policy upserted: {first}"
|
||||
);
|
||||
assert!(
|
||||
first.contains("[features.code_mode]\ndirect_only_tool_namespaces = [\"mcp__idea\"]"),
|
||||
"managed Code Mode table upserted: {first}"
|
||||
);
|
||||
|
||||
// Second projection (relaunch): managed keys are replaced in place, not dup'd.
|
||||
sessions.remove(&sid(777));
|
||||
@ -3722,10 +3744,19 @@ async fn codex_mergetoml_upserts_managed_keys_and_preserves_unmanaged() {
|
||||
1,
|
||||
"idempotent: no duplicate approval_policy: {second}"
|
||||
);
|
||||
assert_eq!(
|
||||
second.matches("[features.code_mode]").count(),
|
||||
1,
|
||||
"idempotent: no duplicate code mode feature table: {second}"
|
||||
);
|
||||
assert!(
|
||||
second.contains("user_key = \"keep-me\""),
|
||||
"unmanaged key still preserved"
|
||||
);
|
||||
assert!(
|
||||
second.contains("[features.preview]\nenabled = true"),
|
||||
"unmanaged sibling features table still preserved: {second}"
|
||||
);
|
||||
}
|
||||
|
||||
// ---- (4) args/env fold into the spawned spec --------------------------------
|
||||
|
||||
@ -874,6 +874,11 @@ impl PermissionProjector for FakeClaudeProjector {
|
||||
/// `--sandbox`/`--ask-for-approval` args. Owns **no** `Replace` file.
|
||||
struct FakeCodexProjector;
|
||||
|
||||
impl FakeCodexProjector {
|
||||
const CODE_MODE_TOML: &'static str =
|
||||
"[features.code_mode]\ndirect_only_tool_namespaces = [\"mcp__idea\"]\n";
|
||||
}
|
||||
|
||||
impl PermissionProjector for FakeCodexProjector {
|
||||
fn key(&self) -> ProjectorKey {
|
||||
ProjectorKey::Codex
|
||||
@ -890,10 +895,12 @@ impl PermissionProjector for FakeCodexProjector {
|
||||
PermissionProjection {
|
||||
files: vec![ProjectedFile::MergeToml {
|
||||
rel_path: ".codex/config.toml".to_owned(),
|
||||
managed_tables: Vec::new(),
|
||||
managed_tables: vec!["features.code_mode".to_owned()],
|
||||
managed_keys: vec!["sandbox_mode".to_owned(), "approval_policy".to_owned()],
|
||||
contents: "sandbox_mode = \"workspace-write\"\napproval_policy = \"never\"\n"
|
||||
.to_owned(),
|
||||
contents: format!(
|
||||
"sandbox_mode = \"workspace-write\"\napproval_policy = \"never\"\n\n{}",
|
||||
Self::CODE_MODE_TOML
|
||||
),
|
||||
}],
|
||||
args: vec![
|
||||
"--sandbox".to_owned(),
|
||||
|
||||
Reference in New Issue
Block a user