@@ -132,115 +132,6 @@ const KNOWN_PERMISSIONS_NETWORK_KEYS: &[&str] = &[
132132
133133const VALID_WINDOWS_SANDBOX_VALUES : & [ & str ] = & [ "elevated" , "unelevated" ] ;
134134
135- // KNOWN_CONFIG_TOP_LEVEL_KEYS is the parallel allow-list used for .codex/config.{json,yaml}
136- // unknown-top-level-key detection. Keep it in sync with KNOWN_TOP_LEVEL_KEYS ∪ KNOWN_TABLE_KEYS
137- // in schemas/codex.rs (this set represents the flat union - the JSON/YAML backends don't
138- // distinguish scalar top-level keys from TOML `[section]` tables).
139- // Sourced from upstream codex-rs/core/config.schema.json; last reconciled @ rust-v0.133.0. Alphabetized.
140- const KNOWN_CONFIG_TOP_LEVEL_KEYS : & [ & str ] = & [
141- "agents" ,
142- "allow_login_shell" ,
143- "analytics" ,
144- "approval_policy" ,
145- "approvals_reviewer" ,
146- "apps" ,
147- "apps_mcp_product_sku" ,
148- "audio" ,
149- "auto_review" ,
150- "background_terminal_max_timeout" ,
151- "chatgpt_base_url" ,
152- "check_for_update_on_startup" ,
153- "cli_auth_credentials_store" ,
154- "commit_attribution" ,
155- "compact_prompt" ,
156- "default_permissions" ,
157- "desktop" ,
158- "developer_instructions" ,
159- "disable_paste_burst" ,
160- "experimental_compact_prompt_file" ,
161- "experimental_realtime_start_instructions" ,
162- "experimental_realtime_ws_backend_prompt" ,
163- "experimental_realtime_ws_base_url" ,
164- "experimental_realtime_ws_model" ,
165- "experimental_realtime_ws_startup_context" ,
166- "experimental_thread_config_endpoint" ,
167- "experimental_thread_store" ,
168- "experimental_thread_store_endpoint" ,
169- "experimental_use_freeform_apply_patch" ,
170- "experimental_use_unified_exec_tool" ,
171- "features" ,
172- "feedback" ,
173- "file_opener" ,
174- "forced_chatgpt_workspace_id" ,
175- "forced_login_method" ,
176- "ghost_snapshot" ,
177- "hide_agent_reasoning" ,
178- "history" ,
179- "hooks" ,
180- "include_apps_instructions" ,
181- "include_collaboration_mode_instructions" ,
182- "include_environment_context" ,
183- "include_permissions_instructions" ,
184- "instructions" ,
185- "js_repl_node_module_dirs" ,
186- "js_repl_node_path" ,
187- "log_dir" ,
188- "marketplaces" ,
189- "mcp_oauth_callback_port" ,
190- "mcp_oauth_callback_url" ,
191- "mcp_oauth_credentials_store" ,
192- "mcp_servers" ,
193- "memories" ,
194- "model" ,
195- "model_auto_compact_token_limit" ,
196- "model_auto_compact_token_limit_scope" ,
197- "model_catalog_json" ,
198- "model_context_window" ,
199- "model_instructions_file" ,
200- "model_provider" ,
201- "model_providers" ,
202- "model_reasoning_effort" ,
203- "model_reasoning_summary" ,
204- "model_supports_reasoning_summaries" ,
205- "model_verbosity" ,
206- "notice" ,
207- "notify" ,
208- "openai_base_url" ,
209- "oss_provider" ,
210- "otel" ,
211- "permissions" ,
212- "personality" ,
213- "plan_mode_reasoning_effort" ,
214- "plugins" ,
215- "profile" ,
216- "profiles" ,
217- "project_doc_fallback_filenames" ,
218- "project_doc_max_bytes" ,
219- "project_root_markers" ,
220- "projects" ,
221- "realtime" ,
222- "review_model" ,
223- "sandbox_mode" ,
224- "sandbox_workspace_write" ,
225- "service_tier" ,
226- "shell_environment_policy" ,
227- "show_raw_agent_reasoning" ,
228- "skills" ,
229- "sqlite_home" ,
230- "suppress_unstable_features_warning" ,
231- "tool_output_token_limit" ,
232- "tool_suggest" ,
233- "tools" ,
234- "tui" ,
235- "web_search" ,
236- "windows" ,
237- "windows_wsl_setup_acknowledged" ,
238- "zsh_path" ,
239- // Legacy compatibility in existing fixtures/tests.
240- "approvalMode" ,
241- "fullAutoErrorMode" ,
242- ] ;
243-
244135const KNOWN_FEATURE_KEYS : & [ & str ] = & [
245136 "apply_patch_freeform" ,
246137 "apps" ,
@@ -2062,7 +1953,9 @@ fn collect_unknown_codex_keys(root: &Value, cdx_cfg_028_active: bool) -> Vec<Str
20621953 } ;
20631954
20641955 for key in root_obj. keys ( ) {
2065- if !KNOWN_CONFIG_TOP_LEVEL_KEYS . contains ( & key. as_str ( ) ) {
1956+ // Single source of truth lives in schemas/codex.rs so the TOML and
1957+ // JSON/YAML backends can never drift on the top-level allow-list.
1958+ if !crate :: schemas:: codex:: is_known_top_level_key ( key. as_str ( ) ) {
20661959 unknown. push ( key. clone ( ) ) ;
20671960 }
20681961 }
@@ -3403,10 +3296,11 @@ experimental_thread_store_endpoint = "https://thread-store.example"
34033296 fn test_codex_v0_123_top_level_keys_accepted_json ( ) {
34043297 // JSON/YAML path: CDX-CFG-006 fires for unknown top-level keys when
34053298 // the file is JSON/YAML (NOT TOML, where CDX-004 takes over via
3406- // skip_top_level). The fix in rules/codex.rs::KNOWN_CONFIG_TOP_LEVEL_KEYS
3407- // must include `experimental_thread_store_endpoint` for CDX-CFG-006
3408- // to accept it. Without that arm of the fix, this test fails even if
3409- // the TOML schema allow-list (schemas/codex.rs) is updated.
3299+ // skip_top_level). The shared allow-list in
3300+ // schemas/codex.rs::is_known_top_level_key must include
3301+ // `experimental_thread_store_endpoint` for CDX-CFG-006 to accept it.
3302+ // Both backends consult the same predicate, so TOML and JSON/YAML
3303+ // can no longer disagree about it.
34103304 let json = r#"{
34113305 "experimental_thread_store_endpoint": "https://thread-store.example"
34123306}"# ;
@@ -3750,11 +3644,11 @@ hide_full_access_warning = true
37503644
37513645 #[ test]
37523646 fn test_codex_0_128_0_new_json_yaml_keys_not_flagged ( ) {
3753- // Regression guard for the JSON/YAML unknown-top-level-key path
3754- // (KNOWN_CONFIG_TOP_LEVEL_KEYS). Mirror of the TOML-side tests in
3755- // schemas/codex.rs; the two allow-lists are deliberately separate
3756- // because the JSON/YAML path treats top-level scalars and objects
3757- // uniformly while the TOML path splits scalar vs `[table]`.
3647+ // Regression guard for the JSON/YAML unknown-top-level-key path,
3648+ // which now consults schemas/codex.rs::is_known_top_level_key (the
3649+ // shared predicate over KNOWN_TOP_LEVEL_KEYS ∪ KNOWN_TABLE_KEYS).
3650+ // Mirror of the TOML-side tests in schemas/codex.rs; the JSON/YAML
3651+ // path treats top-level scalars and `[table]` keys uniformly .
37583652 let json = r#"{
37593653 "auto_review": {},
37603654 "experimental_thread_store": {},
@@ -3783,6 +3677,27 @@ hide_full_access_warning = true
37833677 ) ;
37843678 }
37853679
3680+ #[ test]
3681+ fn test_codex_unified_allowlist_fixes_json_toml_drift ( ) {
3682+ // Drift fix (issue #966): before unifying the top-level allow-list, the
3683+ // JSON/YAML path flagged `debug` (a valid TOML `[debug]` table from the
3684+ // v0.129 catch-up) because it lived only in the TOML schema list.
3685+ // Both backends now share schemas/codex.rs::is_known_top_level_key, so
3686+ // `debug` is accepted as JSON/YAML too.
3687+ let json = r#"{ "debug": {}, "include_apply_patch_tool": true }"# ;
3688+ let diags = validate_config_at_path ( ".codex/config.json" , json) ;
3689+ assert ! (
3690+ diags
3691+ . iter( )
3692+ . all( |d| d. rule != "CDX-004" && d. rule != "CDX-CFG-006" ) ,
3693+ "unified keys should not be flagged on the JSON path, got: {:?}" ,
3694+ diags
3695+ . iter( )
3696+ . filter( |d| d. rule == "CDX-004" || d. rule == "CDX-CFG-006" )
3697+ . collect:: <Vec <_>>( )
3698+ ) ;
3699+ }
3700+
37863701 #[ test]
37873702 fn test_cdx_000_reports_json_yaml_parse_errors ( ) {
37883703 let invalid_json = r#"{"approval_policy":"always""# ;
0 commit comments