//! Codex CLI validation rules (CDX-*, CDX-CFG-*, CDX-AG-*, CDX-APP-*)
//!
//! Validates:
//! - CDX-000: TOML Parse Error (HIGH) - invalid TOML syntax in config.toml
//! - CDX-001: Invalid approvalMode (HIGH) - must be "suggest", "auto-edit", or "full-auto"
//! - CDX-002: Invalid fullAutoErrorMode (HIGH) - must be "ask-user" or "ignore-and-continue"
//! - CDX-003: AGENTS.override.md in version control (MEDIUM) - should be in .gitignore
//! - CDX-004: Unknown config key (MEDIUM) - unrecognized key in .codex/config.toml
//! - CDX-005: project_doc_max_bytes exceeds limit (HIGH) - must be <= 65536
//! - CDX-006: Invalid project_doc_fallback_filenames (HIGH) - must be unique non-empty filenames
//! - CDX-CFG-001..027: Codex config schema/value checks (approval, sandbox, enums, unknown keys, etc.)
//! - CDX-AG-001..007: AGENTS.md quality and secret-safety checks for Codex
//! - CDX-APP-001..003: App and plugin configuration validation

use crate::{
    config::PerFileLintConfig,
    diagnostics::{Diagnostic, Fix},
    rules::{Validator, ValidatorMetadata},
    schemas::claude_md::find_generic_instructions,
    schemas::codex::{
        AGENTS_MD_MAX_SIZE, VALID_APPROVAL_MODES, VALID_FULL_AUTO_ERROR_MODES,
        VALID_MCP_OAUTH_STORES, VALID_MODEL_REASONING_SUMMARIES,
        VALID_SANDBOX_WORKSPACE_WRITE_MODES, parse_codex_toml,
    },
};
use rust_i18n::t;
use serde_json::Value;
use std::collections::{HashMap, HashSet};
use std::ffi::OsStr;
use std::path::Path;

use crate::rules::find_closest_value;

/// Find the byte span of a TOML string value for the given key.
/// Returns byte positions of the inner string (without quotes).
/// Returns None if the key is not found or appears more than once (uniqueness guard).
fn find_toml_string_value_span(
    content: &str,
    key: &str,
    current_value: &str,
) -> Option<(usize, usize)> {
    crate::span_utils::find_unique_toml_string_value(content, key, current_value)
}

const CODEX_MARKDOWN_RULE_IDS: &[&str] = &[
    "CDX-003",
    "CDX-AG-001",
    "CDX-AG-002",
    "CDX-AG-003",
    "CDX-AG-004",
    "CDX-AG-005",
    "CDX-AG-006",
    "CDX-AG-007",
];

const CODEX_CONFIG_RULE_IDS: &[&str] = &[
    "CDX-000",
    "CDX-001",
    "CDX-002",
    "CDX-004",
    "CDX-005",
    "CDX-006",
    "CDX-CFG-001",
    "CDX-CFG-002",
    "CDX-CFG-003",
    "CDX-CFG-004",
    "CDX-CFG-005",
    "CDX-CFG-006",
    "CDX-CFG-007",
    "CDX-CFG-008",
    "CDX-CFG-009",
    "CDX-CFG-010",
    "CDX-CFG-011",
    "CDX-CFG-012",
    "CDX-CFG-013",
    "CDX-CFG-014",
    "CDX-CFG-015",
    "CDX-CFG-016",
    "CDX-CFG-017",
    "CDX-CFG-018",
    "CDX-CFG-019",
    "CDX-CFG-020",
    "CDX-CFG-021",
    "CDX-CFG-022",
    "CDX-CFG-023",
    "CDX-CFG-024",
    "CDX-CFG-025",
    "CDX-CFG-026",
    "CDX-CFG-027",
    "CDX-CFG-028",
    "CDX-CFG-029",
    "CDX-CFG-030",
    "CDX-APP-001",
    "CDX-APP-002",
    "CDX-APP-003",
];

pub struct CodexValidator;
pub struct CodexConfigValidator;

const VALID_APPROVAL_POLICIES: &[&str] = &["untrusted", "on-request", "never", "on-failure"];
const VALID_SANDBOX_MODES: &[&str] = &["read-only", "workspace-write", "danger-full-access"];
const VALID_MODEL_VERBOSITY: &[&str] = &["low", "medium", "high"];
const VALID_PERSONALITIES: &[&str] = &["none", "friendly", "pragmatic"];
const VALID_SHELL_ENVIRONMENT_INHERIT: &[&str] = &["core", "all", "none"];
const VALID_CLI_AUTH_STORES: &[&str] = &["file", "keyring", "auto", "ephemeral"];
const VALID_DEFAULT_TOOLS_APPROVAL_MODES: &[&str] = &["auto", "prompt", "approve"];
const VALID_WEB_SEARCH_MODES: &[&str] = &["disabled", "cached", "indexed", "live"];

const KNOWN_APPROVAL_POLICY_SUB_FIELDS: &[&str] = &[
    "mcp_elicitations",
    "request_permissions",
    "rules",
    "sandbox_approval",
    "skill_approval",
];

const VALID_APPROVALS_REVIEWER: &[&str] = &["user", "auto_review", "guardian_subagent"];

const VALID_SERVICE_TIERS: &[&str] = &["fast", "flex"];

const KNOWN_PERMISSIONS_NETWORK_KEYS: &[&str] = &[
    "admin_url",
    "allowed_domains",
    "denied_domains",
    "enable_socks5",
    "enabled",
    "mitm",
    "mode",
    "proxy_url",
];

const VALID_WINDOWS_SANDBOX_VALUES: &[&str] = &["elevated", "unelevated"];

const KNOWN_FEATURE_KEYS: &[&str] = &[
    "apply_patch_freeform",
    "apply_patch_streaming_events",
    "apps",
    "apps_mcp_path_override",
    "auth_elicitation",
    "auto_compaction",
    "browser_use",
    "browser_use_external",
    "child_agents_md",
    "chronicle",
    "code_mode",
    "code_mode_only",
    "codex_git_commit",
    "codex_hooks",
    "collab",
    "collaboration_modes",
    "computer_use",
    "connectors",
    "current_time_reminder",
    "default_mode_request_user_input",
    "deferred_executor",
    "elevated_windows_sandbox",
    "enable_experimental_windows_sandbox",
    "enable_fanout",
    "enable_mcp_apps",
    "enable_request_compression",
    "exec_permission_approvals",
    "experimental_use_unified_exec_tool",
    "experimental_windows_sandbox",
    "external_migration",
    "fast_mode",
    "goals",
    "guardian_approval",
    "hooks",
    "image_detail_original",
    "image_generation",
    "imagegenext",
    "in_app_browser",
    "item_ids",
    "js_repl",
    "js_repl_tools_only",
    "local_thread_store_compression",
    "memories",
    "memory_tool",
    "mentions_v2",
    "multi_agent",
    "multi_agent_mode",
    "multi_agent_v2",
    "network_proxy",
    "non_prefixed_mcp_tool_names",
    "personality",
    "plugin_hooks",
    "plugin_sharing",
    "plugins",
    "prevent_idle_sleep",
    "realtime_conversation",
    "remote_compaction_v2",
    "remote_control",
    "remote_models",
    "remote_plugin",
    "request_permissions",
    "request_permissions_tool",
    "request_rule",
    "respect_system_proxy",
    "responses_websockets",
    "responses_websockets_v2",
    "rollout_budget",
    "runtime_metrics",
    "search_tool",
    "shell_snapshot",
    "shell_tool",
    "shell_zsh_fork",
    "skill_env_var_dependency_prompt",
    "skill_mcp_dependency_install",
    "sqlite",
    "standalone_web_search",
    "steer",
    "telepathy",
    "terminal_resize_reflow",
    "terminal_visualization_instructions",
    "token_budget",
    "tool_call_mcp_elicitation",
    "tool_search",
    "tool_search_always_defer_mcp_tools",
    "tool_suggest",
    "tui_app_server",
    "unavailable_dummy_tools",
    "undo",
    "unified_exec",
    "unified_exec_zsh_fork",
    "use_agent_identity",
    "use_legacy_landlock",
    "use_linux_sandbox_bwrap",
    "web_search",
    "web_search_cached",
    "web_search_request",
    "workspace_dependencies",
    "workspace_owner_usage_nudge",
    // Older-version tolerance: these shipped in earlier Codex releases but are
    // absent from the current rust-v0.138.0 schema.
    "apps_mcp_gateway",
    "experimental_use_freeform_apply_patch",
    "powershell_utf8",
    "responses_websocket_response_processed",
    "smart_approvals",
    "voice_transcription",
];

const KNOWN_TUI_KEYS: &[&str] = &[
    "alternate_screen",
    "animations",
    "model_availability_nux",
    "notification_method",
    "notifications",
    "show_tooltips",
    "status_line",
    "theme",
];

const KNOWN_SHELL_ENVIRONMENT_POLICY_KEYS: &[&str] = &[
    "exclude",
    "experimental_use_profile",
    "ignore_default_excludes",
    "include_only",
    "inherit",
    "set",
];

const KNOWN_MCP_SERVER_KEYS: &[&str] = &[
    "args",
    "bearer_token_env_var",
    "command",
    "cwd",
    "disabled_tools",
    "enabled",
    "enabled_tools",
    "env",
    "env_http_headers",
    "env_vars",
    "http_headers",
    "oauth",
    "oauth_resource",
    "required",
    "scopes",
    "startup_timeout_ms",
    "startup_timeout_sec",
    "tool_timeout_sec",
    "url",
];

const KNOWN_APPS_DEFAULT_KEYS: &[&str] = &[
    "approvals_reviewer",
    "enabled",
    "destructive_enabled",
    "open_world_enabled",
];
const KNOWN_APP_CONFIG_KEYS: &[&str] = &[
    "approvals_reviewer",
    "enabled",
    "destructive_enabled",
    "open_world_enabled",
    "default_tools_approval_mode",
    "default_tools_enabled",
    "tools",
];
const KNOWN_APP_TOOL_CONFIG_KEYS: &[&str] = &["enabled", "approval_mode"];

impl Validator for CodexValidator {
    fn metadata(&self) -> ValidatorMetadata {
        ValidatorMetadata {
            name: self.name(),
            rule_ids: CODEX_MARKDOWN_RULE_IDS,
        }
    }

    fn validate_per_file(
        &self,
        path: &Path,
        content: &str,
        config: &PerFileLintConfig<'_>,
    ) -> Vec<Diagnostic> {
        let mut diagnostics = Vec::new();

        // Determine whether this is a .md file (ClaudeMd) or a .toml file (CodexConfig)
        // using a direct filename check instead of the full detect_file_type() call.
        // This runs on every ClaudeMd file but the cost is negligible: a single
        // OsStr comparison before early return.
        let is_markdown = path
            .file_name()
            .and_then(|n| n.to_str())
            .is_some_and(|name| name.ends_with(".md"));

        if is_markdown {
            diagnostics.extend(validate_codex_markdown_rules(path, content, config));
            return diagnostics;
        }

        let ext = path.extension().and_then(OsStr::to_str).unwrap_or_default();
        let is_toml = ext.eq_ignore_ascii_case("toml");
        let is_config_file = is_toml
            || ext.eq_ignore_ascii_case("json")
            || ext.eq_ignore_ascii_case("yaml")
            || ext.eq_ignore_ascii_case("yml");

        if is_config_file {
            // Build key-to-line mappings once for all config-based rules (CDX-000..006 + CDX-CFG-*)
            let key_lines = build_key_line_map(content);

            // For Codex TOML config files, run legacy CDX-000..006 checks.
            // Skip TOML parsing entirely when all TOML-dependent rules are disabled.
            if is_toml {
                let cdx_001_enabled = config.is_rule_enabled("CDX-001");
                let cdx_002_enabled = config.is_rule_enabled("CDX-002");
                let cdx_004_enabled = config.is_rule_enabled("CDX-004");
                let cdx_005_enabled = config.is_rule_enabled("CDX-005");
                let cdx_006_enabled = config.is_rule_enabled("CDX-006");
                let legacy_enabled = config.is_rule_enabled("CDX-000")
                    || cdx_001_enabled
                    || cdx_002_enabled
                    || cdx_004_enabled
                    || cdx_005_enabled
                    || cdx_006_enabled;
                if legacy_enabled {
                    let parsed = parse_codex_toml(content);

                    // If TOML is broken, emit a diagnostic so users can fix invalid syntax
                    if config.is_rule_enabled("CDX-000")
                        && let Some(parse_error) = &parsed.parse_error
                    {
                        diagnostics.push(
                            Diagnostic::error(
                                path.to_path_buf(),
                                parse_error.line,
                                parse_error.column,
                                "CDX-000",
                                t!("rules.cdx_000.message", error = parse_error.message),
                            )
                            .with_suggestion(t!("rules.cdx_000.suggestion")),
                        );
                        return diagnostics;
                    }

                    // CDX-004: Unknown config keys (WARNING)
                    // Runs on unknown_keys which are populated whenever TOML parses successfully,
                    // even when schema extraction fails.
                    if cdx_004_enabled {
                        for unknown in &parsed.unknown_keys {
                            let mut diagnostic = Diagnostic::warning(
                                path.to_path_buf(),
                                unknown.line,
                                unknown.column,
                                "CDX-004",
                                t!("rules.cdx_004.message", key = unknown.key.as_str()),
                            )
                            .with_suggestion(t!("rules.cdx_004.suggestion"));

                            if let Some((start, end)) =
                                crate::rules::line_byte_range(content, unknown.line)
                            {
                                diagnostic = diagnostic.with_fix(Fix::delete(
                                    start,
                                    end,
                                    format!("Remove unknown config key '{}'", unknown.key),
                                    false,
                                ));
                            }

                            diagnostics.push(diagnostic);
                        }
                    }

                    let schema = match parsed.schema {
                        Some(s) => s,
                        None => return diagnostics,
                    };

                    // CDX-001: Invalid approvalMode (ERROR)
                    if cdx_001_enabled {
                        if parsed.approval_mode_wrong_type {
                            let line = key_lines.get("approvalMode").copied().unwrap_or(1);
                            diagnostics.push(
                                Diagnostic::error(
                                    path.to_path_buf(),
                                    line,
                                    0,
                                    "CDX-001",
                                    t!("rules.cdx_001.type_error"),
                                )
                                .with_suggestion(t!("rules.cdx_001.suggestion")),
                            );
                        } else if let Some(ref approval_value) = schema.approval_mode {
                            if !VALID_APPROVAL_MODES.contains(&approval_value.as_str()) {
                                let line = key_lines.get("approvalMode").copied().unwrap_or(1);
                                let mut diagnostic = Diagnostic::error(
                                    path.to_path_buf(),
                                    line,
                                    0,
                                    "CDX-001",
                                    t!("rules.cdx_001.message", value = approval_value.as_str()),
                                )
                                .with_suggestion(t!("rules.cdx_001.suggestion"));

                                // Unsafe auto-fix: replace with closest valid approval mode.
                                if let Some(suggested) =
                                    find_closest_value(approval_value, VALID_APPROVAL_MODES)
                                {
                                    if let Some((start, end)) = find_toml_string_value_span(
                                        content,
                                        "approvalMode",
                                        approval_value,
                                    ) {
                                        diagnostic = diagnostic.with_fix(Fix::replace(
                                            start,
                                            end,
                                            suggested,
                                            t!("rules.cdx_001.fix", fixed = suggested),
                                            false,
                                        ));
                                    }
                                }

                                diagnostics.push(diagnostic);
                            }
                        }
                    }

                    // CDX-002: Invalid fullAutoErrorMode (ERROR)
                    if cdx_002_enabled {
                        if parsed.full_auto_error_mode_wrong_type {
                            let line = key_lines.get("fullAutoErrorMode").copied().unwrap_or(1);
                            diagnostics.push(
                                Diagnostic::error(
                                    path.to_path_buf(),
                                    line,
                                    0,
                                    "CDX-002",
                                    t!("rules.cdx_002.type_error"),
                                )
                                .with_suggestion(t!("rules.cdx_002.suggestion")),
                            );
                        } else if let Some(ref error_mode_value) = schema.full_auto_error_mode {
                            if !VALID_FULL_AUTO_ERROR_MODES.contains(&error_mode_value.as_str()) {
                                let line = key_lines.get("fullAutoErrorMode").copied().unwrap_or(1);
                                let mut diagnostic = Diagnostic::error(
                                    path.to_path_buf(),
                                    line,
                                    0,
                                    "CDX-002",
                                    t!("rules.cdx_002.message", value = error_mode_value.as_str()),
                                )
                                .with_suggestion(t!("rules.cdx_002.suggestion"));

                                // Unsafe auto-fix: replace with closest valid error mode.
                                if let Some(suggested) = find_closest_value(
                                    error_mode_value,
                                    VALID_FULL_AUTO_ERROR_MODES,
                                ) {
                                    if let Some((start, end)) = find_toml_string_value_span(
                                        content,
                                        "fullAutoErrorMode",
                                        error_mode_value,
                                    ) {
                                        diagnostic = diagnostic.with_fix(Fix::replace(
                                            start,
                                            end,
                                            suggested,
                                            t!("rules.cdx_002.fix", fixed = suggested),
                                            false,
                                        ));
                                    }
                                }

                                diagnostics.push(diagnostic);
                            }
                        }
                    }

                    // CDX-005: project_doc_max_bytes exceeds limit (ERROR)
                    if cdx_005_enabled {
                        if parsed.project_doc_max_bytes_wrong_type {
                            let line = key_lines.get("project_doc_max_bytes").copied().unwrap_or(1);
                            diagnostics.push(
                                Diagnostic::error(
                                    path.to_path_buf(),
                                    line,
                                    0,
                                    "CDX-005",
                                    t!("rules.cdx_005.type_error"),
                                )
                                .with_suggestion(t!("rules.cdx_005.suggestion")),
                            );
                        } else if let Some(value) = schema.project_doc_max_bytes {
                            let line = key_lines.get("project_doc_max_bytes").copied().unwrap_or(1);
                            if value <= 0 {
                                diagnostics.push(
                                    Diagnostic::error(
                                        path.to_path_buf(),
                                        line,
                                        0,
                                        "CDX-005",
                                        t!("rules.cdx_005.type_error"),
                                    )
                                    .with_suggestion(t!("rules.cdx_005.suggestion")),
                                );
                            } else if value > 65536 {
                                diagnostics.push(
                                    Diagnostic::error(
                                        path.to_path_buf(),
                                        line,
                                        0,
                                        "CDX-005",
                                        t!("rules.cdx_005.message", value = &value.to_string()),
                                    )
                                    .with_suggestion(t!("rules.cdx_005.suggestion")),
                                );
                            }
                        }
                    }

                    // CDX-006: project_doc_fallback_filenames validation
                    if cdx_006_enabled {
                        let line = key_lines
                            .get("project_doc_fallback_filenames")
                            .copied()
                            .unwrap_or(1);

                        if parsed.project_doc_fallback_filenames_wrong_type {
                            diagnostics.push(
                                Diagnostic::error(
                                    path.to_path_buf(),
                                    line,
                                    0,
                                    "CDX-006",
                                    t!("rules.cdx_006.type_error"),
                                )
                                .with_suggestion(t!("rules.cdx_006.suggestion")),
                            );
                        } else {
                            for idx in &parsed.project_doc_fallback_filename_non_string_indices {
                                diagnostics.push(
                                    Diagnostic::error(
                                        path.to_path_buf(),
                                        line,
                                        0,
                                        "CDX-006",
                                        t!(
                                            "rules.cdx_006.non_string",
                                            index = &(idx + 1).to_string()
                                        ),
                                    )
                                    .with_suggestion(t!("rules.cdx_006.suggestion")),
                                );
                            }

                            for idx in &parsed.project_doc_fallback_filename_empty_indices {
                                diagnostics.push(
                                    Diagnostic::error(
                                        path.to_path_buf(),
                                        line,
                                        0,
                                        "CDX-006",
                                        t!("rules.cdx_006.empty", index = &(idx + 1).to_string()),
                                    )
                                    .with_suggestion(t!("rules.cdx_006.suggestion")),
                                );
                            }

                            if let Some(filenames) = &schema.project_doc_fallback_filenames {
                                // Single pass: detect duplicates and suspicious filenames together
                                let mut seen: HashSet<String> = HashSet::new();
                                let mut duplicates: HashSet<String> = HashSet::new();
                                let mut suspicious: HashSet<String> = HashSet::new();
                                for filename in filenames {
                                    let normalized = filename.trim();
                                    if normalized.is_empty() {
                                        continue;
                                    }
                                    if !seen.insert(normalized.to_string()) {
                                        duplicates.insert(normalized.to_string());
                                    }
                                    if is_suspicious_fallback_filename(normalized) {
                                        suspicious.insert(normalized.to_string());
                                    }
                                }

                                let mut dup_sorted: Vec<_> = duplicates.into_iter().collect();
                                dup_sorted.sort();
                                for filename in dup_sorted {
                                    diagnostics.push(
                                        Diagnostic::warning(
                                            path.to_path_buf(),
                                            line,
                                            0,
                                            "CDX-006",
                                            t!(
                                                "rules.cdx_006.duplicate",
                                                value = filename.as_str()
                                            ),
                                        )
                                        .with_suggestion(t!("rules.cdx_006.suggestion")),
                                    );
                                }

                                let mut sus_sorted: Vec<_> = suspicious.into_iter().collect();
                                sus_sorted.sort();
                                for filename in sus_sorted {
                                    diagnostics.push(
                                        Diagnostic::warning(
                                            path.to_path_buf(),
                                            line,
                                            0,
                                            "CDX-006",
                                            t!(
                                                "rules.cdx_006.suspicious",
                                                value = filename.as_str()
                                            ),
                                        )
                                        .with_suggestion(t!("rules.cdx_006.suggestion")),
                                    );
                                }
                            }
                        }
                    }
                }
            } // is_toml (legacy CDX-000..006)

            diagnostics.extend(validate_codex_config_rules(
                path, content, config, &key_lines,
            ));
        }

        diagnostics
    }
}

impl Validator for CodexConfigValidator {
    fn metadata(&self) -> ValidatorMetadata {
        ValidatorMetadata {
            name: self.name(),
            rule_ids: CODEX_CONFIG_RULE_IDS,
        }
    }

    fn validate_per_file(
        &self,
        path: &Path,
        content: &str,
        config: &PerFileLintConfig<'_>,
    ) -> Vec<Diagnostic> {
        let validator = CodexValidator;
        validator.validate(path, content, config)
    }
}

fn validate_codex_markdown_rules(
    path: &Path,
    content: &str,
    config: &PerFileLintConfig<'_>,
) -> Vec<Diagnostic> {
    let mut diagnostics = Vec::new();
    let filename = path
        .file_name()
        .and_then(|n| n.to_str())
        .unwrap_or_default();

    if config.is_rule_enabled("CDX-003") && filename == "AGENTS.override.md" {
        diagnostics.push(
            Diagnostic::warning(
                path.to_path_buf(),
                1,
                0,
                "CDX-003",
                t!("rules.cdx_003.message"),
            )
            .with_suggestion(t!("rules.cdx_003.suggestion")),
        );
    }

    if !matches!(
        filename,
        "AGENTS.md" | "AGENTS.local.md" | "AGENTS.override.md"
    ) {
        return diagnostics;
    }

    if config.is_rule_enabled("CDX-AG-001") && content.trim().is_empty() {
        diagnostics.push(
            Diagnostic::error(
                path.to_path_buf(),
                1,
                0,
                "CDX-AG-001",
                t!("rules.cdx_ag_001.message"),
            )
            .with_suggestion(t!("rules.cdx_ag_001.suggestion")),
        );
    }

    if config.is_rule_enabled("CDX-AG-002") {
        for (line_no, line) in content.lines().enumerate() {
            // Quick pre-check: only lowercase if the line might contain a marker.
            let might_have_marker = line.bytes().any(|b| {
                matches!(
                    b.to_ascii_lowercase(),
                    b'a' | b's' | b't' | b'p' | b'b' | b'g' | b'x'
                )
            });
            if !might_have_marker {
                continue;
            }
            let lower = line.to_ascii_lowercase();
            // Sensitive keywords only count when they appear in an assignment
            // context (e.g., "token=abc", "secret: xyz"), not as prose words
            // like "Token efficiency" or "save tokens". The keyword must be
            // directly followed by optional whitespace then = or :.
            let has_sensitive_key = ["api_key", "apikey", "secret", "token", "password", "bearer"]
                .iter()
                .any(|needle| has_keyword_assignment(&lower, needle));
            let contains_key_prefix = has_sk_token_prefix(line)
                || has_token_prefix(line, "ghp_")
                || has_token_prefix(line, "gho_")
                || has_token_prefix(line, "AKIA")
                || has_token_prefix(line, "xoxb-")
                || has_token_prefix(line, "xoxp-")
                || has_token_prefix(line, "glpat-")
                || has_token_prefix(line, "sk-ant-")
                || has_token_prefix(line, "sk-proj-");
            let has_interpolation = line.contains("${") || line.contains("$(");
            if (has_sensitive_key || contains_key_prefix) && !has_interpolation {
                diagnostics.push(
                    Diagnostic::error(
                        path.to_path_buf(),
                        line_no + 1,
                        0,
                        "CDX-AG-002",
                        t!("rules.cdx_ag_002.message"),
                    )
                    .with_suggestion(t!("rules.cdx_ag_002.suggestion")),
                );
                break;
            }
        }
    }

    if config.is_rule_enabled("CDX-AG-003") {
        let generic_count = find_generic_instructions(content).len();
        let trimmed = content.trim();
        let too_short = trimmed.len() < 120;
        let low_specificity =
            !trimmed.contains('`') && !trimmed.contains('/') && !trimmed.contains("--");
        if generic_count > 0 && too_short && low_specificity {
            diagnostics.push(
                Diagnostic::warning(
                    path.to_path_buf(),
                    1,
                    0,
                    "CDX-AG-003",
                    t!("rules.cdx_ag_003.message"),
                )
                .with_suggestion(t!("rules.cdx_ag_003.suggestion")),
            );
        }
    }

    // CDX-AG-004: AGENTS.md exceeds size limit
    if config.is_rule_enabled("CDX-AG-004") && content.len() > AGENTS_MD_MAX_SIZE {
        diagnostics.push(
            Diagnostic::warning(
                path.to_path_buf(),
                1,
                0,
                "CDX-AG-004",
                t!(
                    "rules.cdx_ag_004.message",
                    size = &content.len().to_string(),
                    limit = &AGENTS_MD_MAX_SIZE.to_string()
                ),
            )
            .with_suggestion(t!("rules.cdx_ag_004.suggestion")),
        );
    }

    // CDX-AG-005: AGENTS.md references missing file (simplified: check for broken markdown links)
    if config.is_rule_enabled("CDX-AG-005") {
        let fs = config.fs();
        for (line_no, line) in content.lines().enumerate() {
            // Match markdown-style file references like `path/to/file.ext`
            for cap in find_backtick_file_refs(line) {
                let reference = cap.trim();
                if reference.is_empty()
                    || reference.starts_with("http")
                    || reference.starts_with('$')
                    || reference.starts_with('<')
                    || !reference.contains('.')
                {
                    continue;
                }
                // Resolve relative to project root (parent of AGENTS.md's parent)
                let resolved = path
                    .parent()
                    .unwrap_or_else(|| Path::new("."))
                    .join(reference);
                if !fs.exists(&resolved) {
                    diagnostics.push(
                        Diagnostic::warning(
                            path.to_path_buf(),
                            line_no + 1,
                            0,
                            "CDX-AG-005",
                            t!("rules.cdx_ag_005.message", reference = reference),
                        )
                        .with_suggestion(t!("rules.cdx_ag_005.suggestion")),
                    );
                    break; // One diagnostic per file is enough
                }
            }
        }
    }

    // CDX-AG-006: AGENTS.md missing project context
    if config.is_rule_enabled("CDX-AG-006") {
        let trimmed = content.trim();
        let has_heading = trimmed.contains("# ");
        let has_command = trimmed.contains('`');
        let has_path = trimmed.contains('/') || trimmed.contains('\\');
        let long_enough = trimmed.len() >= 200;
        if !trimmed.is_empty() && !has_heading && !has_command && !has_path && !long_enough {
            diagnostics.push(
                Diagnostic::info(
                    path.to_path_buf(),
                    1,
                    0,
                    "CDX-AG-006",
                    t!("rules.cdx_ag_006.message"),
                )
                .with_suggestion(t!("rules.cdx_ag_006.suggestion")),
            );
        }
    }

    // CDX-AG-007: AGENTS.md contradicts config.toml (placeholder - requires cross-file analysis)
    // This rule is registered but validation requires project-level context;
    // actual cross-file checking is deferred to project-level validators.

    diagnostics
}

/// Extract file-like references inside backticks from a line.
fn find_backtick_file_refs(line: &str) -> Vec<&str> {
    let mut refs = Vec::new();
    let mut rest = line;
    while let Some(start) = rest.find('`') {
        let after = &rest[start + 1..];
        if let Some(end) = after.find('`') {
            let inner = &after[..end];
            if !inner.is_empty() && !inner.contains(' ') {
                refs.push(inner);
            }
            rest = &after[end + 1..];
        } else {
            break;
        }
    }
    refs
}

/// Check if `haystack` contains `word` at a word boundary followed (with optional whitespace)
/// by `=` or `:`. Used by CDX-AG-002 to distinguish secret assignments like `token=abc`
/// from prose like "Token efficiency".
fn has_keyword_assignment(haystack: &str, word: &str) -> bool {
    let bytes = haystack.as_bytes();
    let word_bytes = word.as_bytes();
    if word_bytes.len() > bytes.len() {
        return false;
    }
    let mut start = 0;
    while let Some(pos) = haystack[start..].find(word) {
        let abs = start + pos;
        let before_ok = abs == 0 || !bytes[abs - 1].is_ascii_alphanumeric();
        let after_idx = abs + word_bytes.len();
        let after_ok = after_idx >= bytes.len() || !bytes[after_idx].is_ascii_alphanumeric();
        if before_ok && after_ok {
            // Check if keyword is followed by optional whitespace then = or :
            let rest = &haystack[after_idx..];
            let trimmed = rest.trim_start();
            if trimmed.starts_with('=') || trimmed.starts_with(':') {
                return true;
            }
        }
        start = abs + 1;
    }
    false
}

/// Check if a line contains a known secret token prefix at a word boundary.
fn has_token_prefix(line: &str, prefix: &str) -> bool {
    line.match_indices(prefix).any(|(idx, _)| {
        let prev_is_alnum = idx > 0
            && line[..idx]
                .chars()
                .next_back()
                .is_some_and(|ch| ch.is_ascii_alphanumeric());
        let next_is_alnum = line[idx + prefix.len()..]
            .chars()
            .next()
            .is_some_and(|ch| ch.is_ascii_alphanumeric());
        !prev_is_alnum && next_is_alnum
    })
}

fn has_sk_token_prefix(line: &str) -> bool {
    line.match_indices("sk-").any(|(idx, _)| {
        let prev_is_alnum = idx > 0
            && line[..idx]
                .chars()
                .next_back()
                .is_some_and(|ch| ch.is_ascii_alphanumeric());
        let next_is_alnum = line[idx + 3..]
            .chars()
            .next()
            .is_some_and(|ch| ch.is_ascii_alphanumeric());
        !prev_is_alnum && next_is_alnum
    })
}

fn validate_codex_config_rules(
    path: &Path,
    content: &str,
    config: &PerFileLintConfig<'_>,
    key_lines: &HashMap<String, usize>,
) -> Vec<Diagnostic> {
    let mut diagnostics = Vec::new();
    let root = match parse_codex_config_value(path, content) {
        Ok(root) => root,
        Err(parse_error) => {
            if config.is_rule_enabled("CDX-000") {
                diagnostics.push(
                    Diagnostic::error(
                        path.to_path_buf(),
                        parse_error.line,
                        parse_error.column,
                        "CDX-000",
                        t!("rules.cdx_000.message", error = parse_error.message),
                    )
                    .with_suggestion(t!("rules.cdx_000.suggestion")),
                );
            }
            return diagnostics;
        }
    };

    let line_for = |key: &str| key_lines.get(key).copied().unwrap_or(1);

    if config.is_rule_enabled("CDX-CFG-001")
        && let Some(value) = value_at_path(&root, &["approval_policy"])
    {
        if let Some(policy) = value.as_str() {
            if !VALID_APPROVAL_POLICIES.contains(&policy) {
                diagnostics.push(
                    Diagnostic::error(
                        path.to_path_buf(),
                        line_for("approval_policy"),
                        0,
                        "CDX-CFG-001",
                        t!("rules.cdx_cfg_001.message", value = policy),
                    )
                    .with_suggestion(t!("rules.cdx_cfg_001.suggestion")),
                );
            }
        } else if !value.is_null() && !value.is_object() {
            diagnostics.push(
                Diagnostic::error(
                    path.to_path_buf(),
                    line_for("approval_policy"),
                    0,
                    "CDX-CFG-001",
                    t!("rules.cdx_cfg_001.type_error"),
                )
                .with_suggestion(t!("rules.cdx_cfg_001.suggestion")),
            );
        }
    }

    if config.is_rule_enabled("CDX-CFG-002")
        && let Some(value) = value_at_path(&root, &["sandbox_mode"])
    {
        if let Some(mode) = value.as_str() {
            if !VALID_SANDBOX_MODES.contains(&mode) {
                diagnostics.push(
                    Diagnostic::error(
                        path.to_path_buf(),
                        line_for("sandbox_mode"),
                        0,
                        "CDX-CFG-002",
                        t!("rules.cdx_cfg_002.message", value = mode),
                    )
                    .with_suggestion(t!("rules.cdx_cfg_002.suggestion")),
                );
            }
        } else if !value.is_null() {
            diagnostics.push(
                Diagnostic::error(
                    path.to_path_buf(),
                    line_for("sandbox_mode"),
                    0,
                    "CDX-CFG-002",
                    t!("rules.cdx_cfg_002.type_error"),
                )
                .with_suggestion(t!("rules.cdx_cfg_002.suggestion")),
            );
        }
    }

    // CDX-CFG-003: Codex rust-v0.138.0 replaced the fixed reasoning-effort
    // enum (none|minimal|low|medium|high|xhigh) with model-defined efforts -
    // the upstream schema now accepts any non-empty string the model
    // advertises (openai/codex#26444). agnix therefore only flags the shapes
    // that are still always wrong: non-string types and empty strings
    // (whitespace-only counts as empty - it is functionally no effort at
    // all). Unknown-but-non-empty values are NOT flagged - agnix cannot
    // know which efforts a given model advertises.
    if config.is_rule_enabled("CDX-CFG-003")
        && let Some(value) = value_at_path(&root, &["model_reasoning_effort"])
    {
        if let Some(effort) = value.as_str() {
            if effort.trim().is_empty() {
                diagnostics.push(
                    Diagnostic::error(
                        path.to_path_buf(),
                        line_for("model_reasoning_effort"),
                        0,
                        "CDX-CFG-003",
                        t!("rules.cdx_cfg_003.empty"),
                    )
                    .with_suggestion(t!("rules.cdx_cfg_003.suggestion")),
                );
            }
        } else if !value.is_null() {
            diagnostics.push(
                Diagnostic::error(
                    path.to_path_buf(),
                    line_for("model_reasoning_effort"),
                    0,
                    "CDX-CFG-003",
                    t!("rules.cdx_cfg_003.type_error"),
                )
                .with_suggestion(t!("rules.cdx_cfg_003.suggestion")),
            );
        }
    }

    if config.is_rule_enabled("CDX-CFG-004")
        && let Some(value) = value_at_path(&root, &["model_verbosity"])
    {
        if let Some(verbosity) = value.as_str() {
            if !VALID_MODEL_VERBOSITY.contains(&verbosity) {
                diagnostics.push(
                    Diagnostic::error(
                        path.to_path_buf(),
                        line_for("model_verbosity"),
                        0,
                        "CDX-CFG-004",
                        t!("rules.cdx_cfg_004.message", value = verbosity),
                    )
                    .with_suggestion(t!("rules.cdx_cfg_004.suggestion")),
                );
            }
        } else if !value.is_null() {
            diagnostics.push(
                Diagnostic::error(
                    path.to_path_buf(),
                    line_for("model_verbosity"),
                    0,
                    "CDX-CFG-004",
                    t!("rules.cdx_cfg_004.type_error"),
                )
                .with_suggestion(t!("rules.cdx_cfg_004.suggestion")),
            );
        }
    }

    if config.is_rule_enabled("CDX-CFG-005")
        && let Some(value) = value_at_path(&root, &["personality"])
    {
        if let Some(personality) = value.as_str() {
            if !VALID_PERSONALITIES.contains(&personality) {
                diagnostics.push(
                    Diagnostic::error(
                        path.to_path_buf(),
                        line_for("personality"),
                        0,
                        "CDX-CFG-005",
                        t!("rules.cdx_cfg_005.message", value = personality),
                    )
                    .with_suggestion(t!("rules.cdx_cfg_005.suggestion")),
                );
            }
        } else if !value.is_null() {
            diagnostics.push(
                Diagnostic::error(
                    path.to_path_buf(),
                    line_for("personality"),
                    0,
                    "CDX-CFG-005",
                    t!("rules.cdx_cfg_005.type_error"),
                )
                .with_suggestion(t!("rules.cdx_cfg_005.suggestion")),
            );
        }
    }

    if config.is_rule_enabled("CDX-CFG-006") {
        let is_toml = path
            .extension()
            .and_then(OsStr::to_str)
            .is_some_and(|ext| ext.eq_ignore_ascii_case("toml"));
        let skip_top_level = is_toml && config.is_rule_enabled("CDX-004");
        let cdx_cfg_028_active = config.is_rule_enabled("CDX-CFG-028");
        for path_key in collect_unknown_codex_keys(&root, cdx_cfg_028_active) {
            if skip_top_level && !path_key.contains('.') {
                continue;
            }
            let line = line_for(path_key.rsplit('.').next().unwrap_or(path_key.as_str()));
            diagnostics.push(
                Diagnostic::warning(
                    path.to_path_buf(),
                    line,
                    0,
                    "CDX-CFG-006",
                    t!("rules.cdx_cfg_006.message", key = path_key.as_str()),
                )
                .with_suggestion(t!("rules.cdx_cfg_006.suggestion")),
            );
        }
    }

    if config.is_rule_enabled("CDX-CFG-007")
        && let Some(value) = value_at_path(&root, &["sandbox_mode"])
        && value.as_str() == Some("danger-full-access")
    {
        let acknowledged = bool_at_path(&root, &["notice", "hide_full_access_warning"]);
        if acknowledged != Some(true) {
            diagnostics.push(
                Diagnostic::error(
                    path.to_path_buf(),
                    line_for("sandbox_mode"),
                    0,
                    "CDX-CFG-007",
                    t!("rules.cdx_cfg_007.message"),
                )
                .with_suggestion(t!("rules.cdx_cfg_007.suggestion")),
            );
        }
    }

    if config.is_rule_enabled("CDX-CFG-008") {
        let shell_inherit = value_at_path(&root, &["shell_environment_policy", "inherit"])
            .or_else(|| value_at_path(&root, &["shell_environment", "inherit"]));
        if let Some(value) = shell_inherit {
            if let Some(inherit) = value.as_str() {
                if !VALID_SHELL_ENVIRONMENT_INHERIT.contains(&inherit) {
                    diagnostics.push(
                        Diagnostic::error(
                            path.to_path_buf(),
                            line_for("inherit"),
                            0,
                            "CDX-CFG-008",
                            t!("rules.cdx_cfg_008.message", value = inherit),
                        )
                        .with_suggestion(t!("rules.cdx_cfg_008.suggestion")),
                    );
                }
            } else if !value.is_null() {
                diagnostics.push(
                    Diagnostic::error(
                        path.to_path_buf(),
                        line_for("inherit"),
                        0,
                        "CDX-CFG-008",
                        t!("rules.cdx_cfg_008.type_error"),
                    )
                    .with_suggestion(t!("rules.cdx_cfg_008.suggestion")),
                );
            }
        }
    }

    if config.is_rule_enabled("CDX-CFG-009")
        && let Some(mcp_servers) = value_at_path(&root, &["mcp_servers"])
    {
        if let Some(servers) = mcp_servers.as_object() {
            for (server_name, server) in servers {
                let Some(server_obj) = server.as_object() else {
                    diagnostics.push(
                        Diagnostic::error(
                            path.to_path_buf(),
                            line_for("mcp_servers"),
                            0,
                            "CDX-CFG-009",
                            t!(
                                "rules.cdx_cfg_009.invalid_server",
                                server = server_name.as_str()
                            ),
                        )
                        .with_suggestion(t!("rules.cdx_cfg_009.suggestion")),
                    );
                    continue;
                };

                let has_command = server_obj
                    .get("command")
                    .and_then(Value::as_str)
                    .is_some_and(|v| !v.trim().is_empty());
                let has_url = server_obj
                    .get("url")
                    .and_then(Value::as_str)
                    .is_some_and(|v| !v.trim().is_empty());
                if !has_command && !has_url {
                    diagnostics.push(
                        Diagnostic::error(
                            path.to_path_buf(),
                            line_for("mcp_servers"),
                            0,
                            "CDX-CFG-009",
                            t!(
                                "rules.cdx_cfg_009.missing_transport",
                                server = server_name.as_str()
                            ),
                        )
                        .with_suggestion(t!("rules.cdx_cfg_009.suggestion")),
                    );
                }
            }
        } else if !mcp_servers.is_null() {
            diagnostics.push(
                Diagnostic::error(
                    path.to_path_buf(),
                    line_for("mcp_servers"),
                    0,
                    "CDX-CFG-009",
                    t!("rules.cdx_cfg_009.type_error"),
                )
                .with_suggestion(t!("rules.cdx_cfg_009.suggestion")),
            );
        }
    }

    if config.is_rule_enabled("CDX-CFG-010") {
        let mut secret_paths = Vec::new();
        collect_hardcoded_secret_paths(&root, "", &mut secret_paths);
        secret_paths.sort();
        secret_paths.dedup();
        for secret_path in secret_paths {
            diagnostics.push(
                Diagnostic::error(
                    path.to_path_buf(),
                    line_for(secret_path.rsplit('.').next().unwrap_or("config")),
                    0,
                    "CDX-CFG-010",
                    t!("rules.cdx_cfg_010.message", key = secret_path.as_str()),
                )
                .with_suggestion(t!("rules.cdx_cfg_010.suggestion")),
            );
        }
    }

    if config.is_rule_enabled("CDX-CFG-011")
        && let Some(features) = value_at_path(&root, &["features"])
    {
        if let Some(features_obj) = features.as_object() {
            for feature_name in features_obj.keys() {
                if !KNOWN_FEATURE_KEYS.contains(&feature_name.as_str()) {
                    diagnostics.push(
                        Diagnostic::warning(
                            path.to_path_buf(),
                            line_for(feature_name.as_str()),
                            0,
                            "CDX-CFG-011",
                            t!("rules.cdx_cfg_011.message", key = feature_name.as_str()),
                        )
                        .with_suggestion(t!("rules.cdx_cfg_011.suggestion")),
                    );
                }
            }
        } else if !features.is_null() {
            diagnostics.push(
                Diagnostic::error(
                    path.to_path_buf(),
                    line_for("features"),
                    0,
                    "CDX-CFG-011",
                    t!("rules.cdx_cfg_011.type_error"),
                )
                .with_suggestion(t!("rules.cdx_cfg_011.suggestion")),
            );
        }
    }

    if config.is_rule_enabled("CDX-CFG-012")
        && let Some(value) = value_at_path(&root, &["cli_auth_credentials_store"])
    {
        if let Some(store) = value.as_str() {
            if !VALID_CLI_AUTH_STORES.contains(&store) {
                diagnostics.push(
                    Diagnostic::error(
                        path.to_path_buf(),
                        line_for("cli_auth_credentials_store"),
                        0,
                        "CDX-CFG-012",
                        t!("rules.cdx_cfg_012.message", value = store),
                    )
                    .with_suggestion(t!("rules.cdx_cfg_012.suggestion")),
                );
            }
        } else if !value.is_null() {
            diagnostics.push(
                Diagnostic::error(
                    path.to_path_buf(),
                    line_for("cli_auth_credentials_store"),
                    0,
                    "CDX-CFG-012",
                    t!("rules.cdx_cfg_012.type_error"),
                )
                .with_suggestion(t!("rules.cdx_cfg_012.suggestion")),
            );
        }
    }

    // CDX-CFG-013: Invalid sandbox_workspace_write mode
    if config.is_rule_enabled("CDX-CFG-013")
        && let Some(value) = value_at_path(&root, &["sandbox_workspace_write", "mode"])
    {
        if let Some(mode) = value.as_str() {
            if !VALID_SANDBOX_WORKSPACE_WRITE_MODES.contains(&mode) {
                diagnostics.push(
                    Diagnostic::error(
                        path.to_path_buf(),
                        line_for("mode"),
                        0,
                        "CDX-CFG-013",
                        t!("rules.cdx_cfg_013.message", value = mode),
                    )
                    .with_suggestion(t!("rules.cdx_cfg_013.suggestion")),
                );
            }
        } else if !value.is_null() {
            diagnostics.push(
                Diagnostic::error(
                    path.to_path_buf(),
                    line_for("mode"),
                    0,
                    "CDX-CFG-013",
                    t!("rules.cdx_cfg_013.type_error"),
                )
                .with_suggestion(t!("rules.cdx_cfg_013.suggestion")),
            );
        }
    }

    // CDX-CFG-014: Invalid model value (must be a string)
    if config.is_rule_enabled("CDX-CFG-014")
        && let Some(value) = value_at_path(&root, &["model"])
    {
        if !value.is_string() && !value.is_null() {
            diagnostics.push(
                Diagnostic::warning(
                    path.to_path_buf(),
                    line_for("model"),
                    0,
                    "CDX-CFG-014",
                    t!("rules.cdx_cfg_014.type_error"),
                )
                .with_suggestion(t!("rules.cdx_cfg_014.suggestion")),
            );
        }
    }

    // CDX-CFG-015: Invalid model_provider value (must be a string)
    if config.is_rule_enabled("CDX-CFG-015")
        && let Some(value) = value_at_path(&root, &["model_provider"])
    {
        if !value.is_string() && !value.is_null() {
            diagnostics.push(
                Diagnostic::error(
                    path.to_path_buf(),
                    line_for("model_provider"),
                    0,
                    "CDX-CFG-015",
                    t!("rules.cdx_cfg_015.type_error"),
                )
                .with_suggestion(t!("rules.cdx_cfg_015.suggestion")),
            );
        }
    }

    // CDX-CFG-016: Invalid model_reasoning_summary value
    if config.is_rule_enabled("CDX-CFG-016")
        && let Some(value) = value_at_path(&root, &["model_reasoning_summary"])
    {
        if let Some(summary) = value.as_str() {
            if !VALID_MODEL_REASONING_SUMMARIES.contains(&summary) {
                diagnostics.push(
                    Diagnostic::warning(
                        path.to_path_buf(),
                        line_for("model_reasoning_summary"),
                        0,
                        "CDX-CFG-016",
                        t!("rules.cdx_cfg_016.message", value = summary),
                    )
                    .with_suggestion(t!("rules.cdx_cfg_016.suggestion")),
                );
            }
        } else if !value.is_null() {
            diagnostics.push(
                Diagnostic::warning(
                    path.to_path_buf(),
                    line_for("model_reasoning_summary"),
                    0,
                    "CDX-CFG-016",
                    t!("rules.cdx_cfg_016.type_error"),
                )
                .with_suggestion(t!("rules.cdx_cfg_016.suggestion")),
            );
        }
    }

    // CDX-CFG-017: Invalid history configuration (must be an object)
    if config.is_rule_enabled("CDX-CFG-017")
        && let Some(value) = value_at_path(&root, &["history"])
    {
        if !value.is_object() && !value.is_null() {
            diagnostics.push(
                Diagnostic::warning(
                    path.to_path_buf(),
                    line_for("history"),
                    0,
                    "CDX-CFG-017",
                    t!("rules.cdx_cfg_017.type_error"),
                )
                .with_suggestion(t!("rules.cdx_cfg_017.suggestion")),
            );
        }
    }

    // CDX-CFG-018: Invalid tui configuration (must be an object)
    if config.is_rule_enabled("CDX-CFG-018")
        && let Some(value) = value_at_path(&root, &["tui"])
    {
        if !value.is_object() && !value.is_null() {
            diagnostics.push(
                Diagnostic::warning(
                    path.to_path_buf(),
                    line_for("tui"),
                    0,
                    "CDX-CFG-018",
                    t!("rules.cdx_cfg_018.type_error"),
                )
                .with_suggestion(t!("rules.cdx_cfg_018.suggestion")),
            );
        }
    }

    // CDX-CFG-019: Invalid file_opener value (must be a string)
    if config.is_rule_enabled("CDX-CFG-019")
        && let Some(value) = value_at_path(&root, &["file_opener"])
    {
        if !value.is_string() && !value.is_null() {
            diagnostics.push(
                Diagnostic::warning(
                    path.to_path_buf(),
                    line_for("file_opener"),
                    0,
                    "CDX-CFG-019",
                    t!("rules.cdx_cfg_019.type_error"),
                )
                .with_suggestion(t!("rules.cdx_cfg_019.suggestion")),
            );
        }
    }

    // CDX-CFG-020: Invalid MCP OAuth config (mcp_oauth_credentials_store must be valid)
    if config.is_rule_enabled("CDX-CFG-020")
        && let Some(value) = value_at_path(&root, &["mcp_oauth_credentials_store"])
    {
        if let Some(store) = value.as_str() {
            if !VALID_MCP_OAUTH_STORES.contains(&store) {
                diagnostics.push(
                    Diagnostic::error(
                        path.to_path_buf(),
                        line_for("mcp_oauth_credentials_store"),
                        0,
                        "CDX-CFG-020",
                        t!("rules.cdx_cfg_020.message", value = store),
                    )
                    .with_suggestion(t!("rules.cdx_cfg_020.suggestion")),
                );
            }
        } else if !value.is_null() {
            diagnostics.push(
                Diagnostic::error(
                    path.to_path_buf(),
                    line_for("mcp_oauth_credentials_store"),
                    0,
                    "CDX-CFG-020",
                    t!("rules.cdx_cfg_020.type_error"),
                )
                .with_suggestion(t!("rules.cdx_cfg_020.suggestion")),
            );
        }
    }

    // CDX-CFG-021: Invalid model_context_window (must be a positive integer)
    if config.is_rule_enabled("CDX-CFG-021")
        && let Some(value) = value_at_path(&root, &["model_context_window"])
    {
        if let Some(num) = value.as_i64() {
            if num <= 0 {
                diagnostics.push(
                    Diagnostic::warning(
                        path.to_path_buf(),
                        line_for("model_context_window"),
                        0,
                        "CDX-CFG-021",
                        t!("rules.cdx_cfg_021.message", value = &num.to_string()),
                    )
                    .with_suggestion(t!("rules.cdx_cfg_021.suggestion")),
                );
            }
        } else if !value.is_null() && !value.is_u64() {
            diagnostics.push(
                Diagnostic::warning(
                    path.to_path_buf(),
                    line_for("model_context_window"),
                    0,
                    "CDX-CFG-021",
                    t!("rules.cdx_cfg_021.type_error"),
                )
                .with_suggestion(t!("rules.cdx_cfg_021.suggestion")),
            );
        }
    }

    // CDX-CFG-022: Invalid model_auto_compact_token_limit (must be a positive integer)
    if config.is_rule_enabled("CDX-CFG-022")
        && let Some(value) = value_at_path(&root, &["model_auto_compact_token_limit"])
    {
        if let Some(num) = value.as_i64() {
            if num <= 0 {
                diagnostics.push(
                    Diagnostic::warning(
                        path.to_path_buf(),
                        line_for("model_auto_compact_token_limit"),
                        0,
                        "CDX-CFG-022",
                        t!("rules.cdx_cfg_022.message", value = &num.to_string()),
                    )
                    .with_suggestion(t!("rules.cdx_cfg_022.suggestion")),
                );
            }
        } else if !value.is_null() && !value.is_u64() {
            diagnostics.push(
                Diagnostic::warning(
                    path.to_path_buf(),
                    line_for("model_auto_compact_token_limit"),
                    0,
                    "CDX-CFG-022",
                    t!("rules.cdx_cfg_022.type_error"),
                )
                .with_suggestion(t!("rules.cdx_cfg_022.suggestion")),
            );
        }
    }

    // CDX-CFG-023: Invalid granular approval_policy sub-field name
    if config.is_rule_enabled("CDX-CFG-023")
        && let Some(value) = value_at_path(&root, &["approval_policy"])
    {
        if let Some(obj) = value.as_object() {
            for key in obj.keys() {
                if !KNOWN_APPROVAL_POLICY_SUB_FIELDS.contains(&key.as_str()) {
                    diagnostics.push(
                        Diagnostic::warning(
                            path.to_path_buf(),
                            line_for("approval_policy"),
                            0,
                            "CDX-CFG-023",
                            format!(
                                "Unknown approval_policy sub-field '{}'. Valid sub-fields: {}",
                                key,
                                KNOWN_APPROVAL_POLICY_SUB_FIELDS.join(", ")
                            ),
                        )
                        .with_suggestion(
                            "Remove or rename the unknown sub-field in approval_policy."
                                .to_string(),
                        ),
                    );
                }
            }
        }
    }

    // CDX-CFG-024: Invalid approvals_reviewer enum value
    if config.is_rule_enabled("CDX-CFG-024")
        && let Some(value) = value_at_path(&root, &["approvals_reviewer"])
    {
        if let Some(reviewer) = value.as_str() {
            if !VALID_APPROVALS_REVIEWER.contains(&reviewer) {
                diagnostics.push(
                    Diagnostic::warning(
                        path.to_path_buf(),
                        line_for("approvals_reviewer"),
                        0,
                        "CDX-CFG-024",
                        format!(
                            "Invalid approvals_reviewer value '{}'. Must be one of: {}",
                            reviewer,
                            VALID_APPROVALS_REVIEWER.join(", ")
                        ),
                    )
                    .with_suggestion(format!(
                        "Set approvals_reviewer to one of: {}.",
                        VALID_APPROVALS_REVIEWER.join(", ")
                    )),
                );
            }
        } else if !value.is_null() {
            diagnostics.push(
                Diagnostic::warning(
                    path.to_path_buf(),
                    line_for("approvals_reviewer"),
                    0,
                    "CDX-CFG-024",
                    format!(
                        "approvals_reviewer must be a string, not {}",
                        value_type_name(value)
                    ),
                )
                .with_suggestion(format!(
                    "Set approvals_reviewer to one of: {}.",
                    VALID_APPROVALS_REVIEWER.join(", ")
                )),
            );
        }
    }

    // CDX-CFG-025: Invalid service_tier enum value
    if config.is_rule_enabled("CDX-CFG-025")
        && let Some(value) = value_at_path(&root, &["service_tier"])
    {
        if let Some(tier) = value.as_str() {
            if !VALID_SERVICE_TIERS.contains(&tier) {
                diagnostics.push(
                    Diagnostic::warning(
                        path.to_path_buf(),
                        line_for("service_tier"),
                        0,
                        "CDX-CFG-025",
                        format!(
                            "Invalid service_tier value '{}'. Must be one of: {}",
                            tier,
                            VALID_SERVICE_TIERS.join(", ")
                        ),
                    )
                    .with_suggestion("Set service_tier to 'flex' or 'fast'."),
                );
            }
        } else if !value.is_null() {
            diagnostics.push(
                Diagnostic::warning(
                    path.to_path_buf(),
                    line_for("service_tier"),
                    0,
                    "CDX-CFG-025",
                    format!(
                        "service_tier must be a string, not {}",
                        value_type_name(value)
                    ),
                )
                .with_suggestion("Set service_tier to 'flex' or 'fast'."),
            );
        }
    }

    // CDX-CFG-030: Invalid web_search mode. Codex rust-v0.142.0 added the
    // `indexed` mode alongside the existing disabled/cached/live values.
    if config.is_rule_enabled("CDX-CFG-030")
        && let Some(value) = value_at_path(&root, &["web_search"])
    {
        if let Some(mode) = value.as_str() {
            if !VALID_WEB_SEARCH_MODES.contains(&mode) {
                diagnostics.push(
                    Diagnostic::warning(
                        path.to_path_buf(),
                        line_for("web_search"),
                        0,
                        "CDX-CFG-030",
                        format!(
                            "Invalid web_search value '{}'. Must be one of: {}",
                            mode,
                            VALID_WEB_SEARCH_MODES.join(", ")
                        ),
                    )
                    .with_suggestion(format!(
                        "Set web_search to one of: {}.",
                        VALID_WEB_SEARCH_MODES.join(", ")
                    )),
                );
            }
        } else if !value.is_null() {
            diagnostics.push(
                Diagnostic::warning(
                    path.to_path_buf(),
                    line_for("web_search"),
                    0,
                    "CDX-CFG-030",
                    format!(
                        "web_search must be a string, not {}",
                        value_type_name(value)
                    ),
                )
                .with_suggestion(format!(
                    "Set web_search to one of: {}.",
                    VALID_WEB_SEARCH_MODES.join(", ")
                )),
            );
        }
    }

    // CDX-CFG-026: Invalid permissions.network sub-field
    if config.is_rule_enabled("CDX-CFG-026")
        && let Some(value) = value_at_path(&root, &["permissions", "network"])
    {
        if let Some(obj) = value.as_object() {
            for key in obj.keys() {
                if !KNOWN_PERMISSIONS_NETWORK_KEYS.contains(&key.as_str()) {
                    diagnostics.push(
                        Diagnostic::info(
                            path.to_path_buf(),
                            line_for("network"),
                            0,
                            "CDX-CFG-026",
                            format!(
                                "Unknown permissions.network sub-field '{}'. Known sub-fields: {}",
                                key,
                                KNOWN_PERMISSIONS_NETWORK_KEYS.join(", ")
                            ),
                        )
                        .with_suggestion(
                            "Remove or rename the unknown sub-field in permissions.network."
                                .to_string(),
                        ),
                    );
                }
            }
        }
    }

    // CDX-CFG-027: Invalid windows.sandbox enum value
    if config.is_rule_enabled("CDX-CFG-027")
        && let Some(value) = value_at_path(&root, &["windows", "sandbox"])
    {
        if let Some(sandbox) = value.as_str() {
            if !VALID_WINDOWS_SANDBOX_VALUES.contains(&sandbox) {
                diagnostics.push(
                    Diagnostic::info(
                        path.to_path_buf(),
                        line_for("sandbox"),
                        0,
                        "CDX-CFG-027",
                        format!(
                            "Invalid windows.sandbox value '{}'. Must be one of: {}",
                            sandbox,
                            VALID_WINDOWS_SANDBOX_VALUES.join(", ")
                        ),
                    )
                    .with_suggestion(
                        "Set windows.sandbox to 'elevated' or 'unelevated'.".to_string(),
                    ),
                );
            }
        } else if !value.is_null() {
            diagnostics.push(
                Diagnostic::info(
                    path.to_path_buf(),
                    line_for("sandbox"),
                    0,
                    "CDX-CFG-027",
                    format!(
                        "windows.sandbox must be a string, not {}",
                        value_type_name(value)
                    ),
                )
                .with_suggestion("Set windows.sandbox to 'elevated' or 'unelevated'."),
            );
        }
    }

    // CDX-CFG-028: Reject unsupported `bearer_token` inline field in mcp_servers.
    // Upstream: openai/codex#19294 - Codex runtime rejects inline bearer_token and
    // requires users to configure `bearer_token_env_var` instead. The field was
    // removed from the generated config schema in rust-v0.125.0.
    if config.is_rule_enabled("CDX-CFG-028")
        && let Some(mcp_servers) = value_at_path(&root, &["mcp_servers"])
        && let Some(servers) = mcp_servers.as_object()
    {
        for (server_name, server_cfg) in servers {
            if let Some(server_obj) = server_cfg.as_object()
                && server_obj.contains_key("bearer_token")
            {
                let line = find_bearer_token_line(content, server_name).unwrap_or(1);
                diagnostics.push(
                    Diagnostic::error(
                        path.to_path_buf(),
                        line,
                        0,
                        "CDX-CFG-028",
                        t!("rules.cdx_cfg_028.message", server = server_name.as_str()),
                    )
                    .with_suggestion(t!("rules.cdx_cfg_028.suggestion")),
                );
            }
        }
    }

    // CDX-CFG-029: agents.max_threads cannot be set when multi_agent_v2 is
    // enabled. Upstream: openai/codex#19129 introduced the check;
    // openai/codex#19733 briefly removed it; openai/codex#19792 restored it
    // while moving the cap into `[features.multi_agent_v2].max_concurrent_threads_per_session`.
    // multi_agent_v2 uses the v2 agent lifecycle and rejects the legacy
    // `agents.max_threads` limit at config load with "agents.max_threads
    // cannot be set when multi_agent_v2 is enabled". Verified against the
    // rust-v0.128.0 source tag.
    if config.is_rule_enabled("CDX-CFG-029")
        && value_at_path(&root, &["agents", "max_threads"]).is_some()
        && is_multi_agent_v2_enabled(&root)
    {
        let line = key_lines
            .get("max_threads")
            .copied()
            .unwrap_or_else(|| key_lines.get("multi_agent_v2").copied().unwrap_or(1));
        diagnostics.push(
            Diagnostic::error(
                path.to_path_buf(),
                line,
                0,
                "CDX-CFG-029",
                t!("rules.cdx_cfg_029.message"),
            )
            .with_suggestion(t!("rules.cdx_cfg_029.suggestion")),
        );
    }

    if config.is_rule_enabled("CDX-APP-001")
        && let Some(apps) = value_at_path(&root, &["apps"])
    {
        if let Some(apps_obj) = apps.as_object() {
            for (app_name, app_value) in apps_obj {
                if app_name == "_default" {
                    if config.is_rule_enabled("CDX-CFG-024")
                        && let Some(default_obj) = app_value.as_object()
                        && let Some(reviewer) = default_obj.get("approvals_reviewer")
                    {
                        if let Some(reviewer_str) = reviewer.as_str() {
                            if !VALID_APPROVALS_REVIEWER.contains(&reviewer_str) {
                                diagnostics.push(
                                    Diagnostic::warning(
                                        path.to_path_buf(),
                                        line_for("approvals_reviewer"),
                                        0,
                                        "CDX-CFG-024",
                                        format!(
                                            "Invalid apps._default.approvals_reviewer value '{}'. Must be one of: {}",
                                            reviewer_str,
                                            VALID_APPROVALS_REVIEWER.join(", ")
                                        ),
                                    )
                                    .with_suggestion(format!(
                                        "Set apps._default.approvals_reviewer to one of: {}.",
                                        VALID_APPROVALS_REVIEWER.join(", ")
                                    )),
                                );
                            }
                        } else if !reviewer.is_null() {
                            diagnostics.push(
                                Diagnostic::warning(
                                    path.to_path_buf(),
                                    line_for("approvals_reviewer"),
                                    0,
                                    "CDX-CFG-024",
                                    format!(
                                        "apps._default.approvals_reviewer must be a string, not {}",
                                        value_type_name(reviewer)
                                    ),
                                )
                                .with_suggestion(format!(
                                    "Set apps._default.approvals_reviewer to one of: {}.",
                                    VALID_APPROVALS_REVIEWER.join(", ")
                                )),
                            );
                        }
                    }
                    continue;
                }
                let Some(app_obj) = app_value.as_object() else {
                    continue;
                };
                if let Some(mode) = app_obj.get("default_tools_approval_mode") {
                    if let Some(mode_str) = mode.as_str() {
                        if !VALID_DEFAULT_TOOLS_APPROVAL_MODES.contains(&mode_str) {
                            diagnostics.push(
                                Diagnostic::error(
                                    path.to_path_buf(),
                                    line_for("default_tools_approval_mode"),
                                    0,
                                    "CDX-APP-001",
                                    t!(
                                        "rules.cdx_app_001.message",
                                        app = app_name.as_str(),
                                        value = mode_str
                                    ),
                                )
                                .with_suggestion(t!("rules.cdx_app_001.suggestion")),
                            );
                        }
                    } else if !mode.is_null() {
                        diagnostics.push(
                            Diagnostic::error(
                                path.to_path_buf(),
                                line_for("default_tools_approval_mode"),
                                0,
                                "CDX-APP-001",
                                t!("rules.cdx_app_001.type_error", app = app_name.as_str()),
                            )
                            .with_suggestion(t!("rules.cdx_app_001.suggestion")),
                        );
                    }
                }
                if config.is_rule_enabled("CDX-CFG-024")
                    && let Some(reviewer) = app_obj.get("approvals_reviewer")
                {
                    if let Some(reviewer_str) = reviewer.as_str() {
                        if !VALID_APPROVALS_REVIEWER.contains(&reviewer_str) {
                            diagnostics.push(
                                Diagnostic::warning(
                                    path.to_path_buf(),
                                    line_for("approvals_reviewer"),
                                    0,
                                    "CDX-CFG-024",
                                    format!(
                                        "Invalid apps.{}.approvals_reviewer value '{}'. Must be one of: {}",
                                        app_name,
                                        reviewer_str,
                                        VALID_APPROVALS_REVIEWER.join(", ")
                                    ),
                                )
                                .with_suggestion(format!(
                                    "Set apps.{}.approvals_reviewer to one of: {}.",
                                    app_name,
                                    VALID_APPROVALS_REVIEWER.join(", ")
                                )),
                            );
                        }
                    } else if !reviewer.is_null() {
                        diagnostics.push(
                            Diagnostic::warning(
                                path.to_path_buf(),
                                line_for("approvals_reviewer"),
                                0,
                                "CDX-CFG-024",
                                format!(
                                    "apps.{}.approvals_reviewer must be a string, not {}",
                                    app_name,
                                    value_type_name(reviewer)
                                ),
                            )
                            .with_suggestion(format!(
                                "Set apps.{}.approvals_reviewer to one of: {}.",
                                app_name,
                                VALID_APPROVALS_REVIEWER.join(", ")
                            )),
                        );
                    }
                }
            }
        }
    }

    // CDX-APP-002: Invalid skills configuration (must be an object)
    if config.is_rule_enabled("CDX-APP-002")
        && let Some(value) = value_at_path(&root, &["skills"])
    {
        if !value.is_object() && !value.is_null() {
            diagnostics.push(
                Diagnostic::warning(
                    path.to_path_buf(),
                    line_for("skills"),
                    0,
                    "CDX-APP-002",
                    t!("rules.cdx_app_002.type_error"),
                )
                .with_suggestion(t!("rules.cdx_app_002.suggestion")),
            );
        }
    }

    // CDX-APP-003: Invalid profile configuration (must be a string)
    if config.is_rule_enabled("CDX-APP-003")
        && let Some(value) = value_at_path(&root, &["profile"])
    {
        if !value.is_string() && !value.is_null() {
            diagnostics.push(
                Diagnostic::warning(
                    path.to_path_buf(),
                    line_for("profile"),
                    0,
                    "CDX-APP-003",
                    t!("rules.cdx_app_003.type_error"),
                )
                .with_suggestion(t!("rules.cdx_app_003.suggestion")),
            );
        }
    }

    diagnostics
}

fn value_type_name(value: &Value) -> &'static str {
    match value {
        Value::Null => "null",
        Value::Bool(_) => "boolean",
        Value::Number(_) => "number",
        Value::String(_) => "string",
        Value::Array(_) => "array",
        Value::Object(_) => "object",
    }
}

struct CodexConfigParseError {
    line: usize,
    column: usize,
    message: String,
}

fn parse_codex_config_value(path: &Path, content: &str) -> Result<Value, CodexConfigParseError> {
    let extension = path
        .extension()
        .and_then(OsStr::to_str)
        .unwrap_or_default()
        .to_ascii_lowercase();

    match extension.as_str() {
        "toml" => {
            let parsed = parse_codex_toml(content);
            if let Some(parse_error) = parsed.parse_error {
                return Err(CodexConfigParseError {
                    line: parse_error.line.max(1),
                    column: parse_error.column,
                    message: parse_error.message,
                });
            }

            let table: toml::Table =
                toml::from_str(content).map_err(|error| CodexConfigParseError {
                    line: 1,
                    column: 0,
                    message: error.to_string(),
                })?;
            serde_json::to_value(table).map_err(|error| CodexConfigParseError {
                line: 1,
                column: 0,
                message: error.to_string(),
            })
        }
        "json" => serde_json::from_str::<Value>(content).map_err(|error| CodexConfigParseError {
            line: error.line().max(1),
            column: error.column(),
            message: error.to_string(),
        }),
        "yaml" | "yml" => {
            let yaml: serde_yaml::Value =
                serde_yaml::from_str(content).map_err(|error| CodexConfigParseError {
                    line: error.location().map_or(1, |loc| loc.line().max(1)),
                    column: error.location().map_or(0, |loc| loc.column()),
                    message: error.to_string(),
                })?;
            serde_json::to_value(yaml).map_err(|error| CodexConfigParseError {
                line: 1,
                column: 0,
                message: error.to_string(),
            })
        }
        _ => Err(CodexConfigParseError {
            line: 1,
            column: 0,
            message: "unsupported Codex config file extension".to_string(),
        }),
    }
}

fn value_at_path<'a>(root: &'a Value, path: &[&str]) -> Option<&'a Value> {
    let mut current = root;
    for segment in path {
        current = current.get(*segment)?;
    }
    Some(current)
}

fn bool_at_path(root: &Value, path: &[&str]) -> Option<bool> {
    value_at_path(root, path).and_then(Value::as_bool)
}

/// When `cdx_cfg_028_active` is true, `bearer_token` inside `mcp_servers.*`
/// is suppressed here because CDX-CFG-028 emits a more specific diagnostic.
/// When the user has disabled CDX-CFG-028, fall back to reporting it via the
/// generic unknown-key path so the field doesn't silently pass.
fn collect_unknown_codex_keys(root: &Value, cdx_cfg_028_active: bool) -> Vec<String> {
    let mut unknown = Vec::new();
    let Some(root_obj) = root.as_object() else {
        return unknown;
    };

    for key in root_obj.keys() {
        // Single source of truth lives in schemas/codex.rs so the TOML and
        // JSON/YAML backends can never drift on the top-level allow-list.
        if !crate::schemas::codex::is_known_top_level_key(key.as_str()) {
            unknown.push(key.clone());
        }
    }

    if let Some(features) = root_obj.get("features").and_then(Value::as_object) {
        for key in features.keys() {
            if !KNOWN_FEATURE_KEYS.contains(&key.as_str()) {
                unknown.push(format!("features.{key}"));
            }
        }
    }

    if let Some(tui) = root_obj.get("tui").and_then(Value::as_object) {
        for key in tui.keys() {
            if !KNOWN_TUI_KEYS.contains(&key.as_str()) {
                unknown.push(format!("tui.{key}"));
            }
        }
    }

    if let Some(shell) = root_obj
        .get("shell_environment_policy")
        .and_then(Value::as_object)
    {
        for key in shell.keys() {
            if !KNOWN_SHELL_ENVIRONMENT_POLICY_KEYS.contains(&key.as_str()) {
                unknown.push(format!("shell_environment_policy.{key}"));
            }
        }
    }

    if let Some(mcp_servers) = root_obj.get("mcp_servers").and_then(Value::as_object) {
        for (server_name, server_cfg) in mcp_servers {
            if let Some(server_obj) = server_cfg.as_object() {
                for key in server_obj.keys() {
                    // `bearer_token` is handled by CDX-CFG-028 with a more
                    // specific "use bearer_token_env_var" message - skip it
                    // here to avoid double-reporting the same line. If the
                    // user has disabled CDX-CFG-028 we fall through so the
                    // generic unknown-key path still flags it.
                    if key == "bearer_token" && cdx_cfg_028_active {
                        continue;
                    }
                    if !KNOWN_MCP_SERVER_KEYS.contains(&key.as_str()) {
                        unknown.push(format!("mcp_servers.{server_name}.{key}"));
                    }
                }
            }
        }
    }

    if let Some(apps) = root_obj.get("apps").and_then(Value::as_object) {
        for (app_name, app_cfg) in apps {
            if app_name == "_default" {
                if let Some(default_obj) = app_cfg.as_object() {
                    for key in default_obj.keys() {
                        if !KNOWN_APPS_DEFAULT_KEYS.contains(&key.as_str()) {
                            unknown.push(format!("apps._default.{key}"));
                        }
                    }
                }
                continue;
            }

            if let Some(app_obj) = app_cfg.as_object() {
                for key in app_obj.keys() {
                    if !KNOWN_APP_CONFIG_KEYS.contains(&key.as_str()) {
                        unknown.push(format!("apps.{app_name}.{key}"));
                    }
                }

                if let Some(tools_obj) = app_obj.get("tools").and_then(Value::as_object) {
                    for (tool_name, tool_cfg) in tools_obj {
                        if let Some(tool_obj) = tool_cfg.as_object() {
                            for key in tool_obj.keys() {
                                if !KNOWN_APP_TOOL_CONFIG_KEYS.contains(&key.as_str()) {
                                    unknown
                                        .push(format!("apps.{app_name}.tools.{tool_name}.{key}"));
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    unknown.sort();
    unknown.dedup();
    unknown
}

fn collect_hardcoded_secret_paths(value: &Value, path: &str, out: &mut Vec<String>) {
    match value {
        Value::Object(map) => {
            for (key, nested_value) in map {
                let next = if path.is_empty() {
                    key.to_string()
                } else {
                    format!("{path}.{key}")
                };

                if let Some(str_value) = nested_value.as_str()
                    && seems_hardcoded_secret(key, str_value)
                {
                    out.push(next.clone());
                }
                collect_hardcoded_secret_paths(nested_value, &next, out);
            }
        }
        Value::Array(values) => {
            for nested_value in values {
                collect_hardcoded_secret_paths(nested_value, path, out);
            }
        }
        _ => {}
    }
}

fn seems_hardcoded_secret(key: &str, value: &str) -> bool {
    let trimmed = value.trim();
    if trimmed.is_empty() {
        return false;
    }
    if trimmed.starts_with('$')
        || trimmed.starts_with("${")
        || trimmed.starts_with("env:")
        || trimmed.contains("${")
    {
        return false;
    }

    if trimmed.contains("sk-") && trimmed.len() >= 20 {
        return true;
    }

    let sensitive_key = [
        "api_key",
        "apikey",
        "secret",
        "token",
        "password",
        "bearer",
        "credential",
    ]
    .iter()
    .any(|needle| key.to_ascii_lowercase().contains(needle));
    if !sensitive_key {
        return false;
    }

    let has_letter = trimmed.chars().any(|c| c.is_ascii_alphabetic());
    let has_digit = trimmed.chars().any(|c| c.is_ascii_digit());
    trimmed.len() >= 12 && has_letter && has_digit
}

fn is_suspicious_fallback_filename(filename: &str) -> bool {
    filename.contains('/') || filename.contains('\\') || is_windows_absolute_path(filename)
}

fn is_windows_absolute_path(value: &str) -> bool {
    let bytes = value.as_bytes();
    bytes.len() >= 3
        && bytes[0].is_ascii_alphabetic()
        && bytes[1] == b':'
        && (bytes[2] == b'\\' || bytes[2] == b'/')
}

/// Return true when the `multi_agent_v2` feature is enabled in either shape
/// that the Codex config parser accepts:
///
/// - `[features] multi_agent_v2 = true` - flat boolean form (FeatureToml::Enabled)
/// - `[features.multi_agent_v2] enabled = true` - table form (FeatureToml::Config)
///
/// Returns false when the feature is missing, explicitly false, or set to any
/// non-boolean value (which Codex would reject earlier during TOML deserialize,
/// so we don't need to re-diagnose it here).
fn is_multi_agent_v2_enabled(root: &Value) -> bool {
    let Some(feature) = value_at_path(root, &["features", "multi_agent_v2"]) else {
        return false;
    };
    // Flat form: features.multi_agent_v2 = true
    if let Some(b) = feature.as_bool() {
        return b;
    }
    // Table form: [features.multi_agent_v2] enabled = true
    // The table may exist without `enabled`; upstream defaults that to false.
    if let Some(obj) = feature.as_object() {
        return obj.get("enabled").and_then(Value::as_bool).unwrap_or(false);
    }
    false
}

/// Find the 1-indexed line of `bearer_token =` under `[mcp_servers.<name>]`.
///
/// CDX-CFG-028 needs a line number that points at the offending line rather than
/// line 1. The flat `build_key_line_map` cannot distinguish between multiple
/// servers (multiple `bearer_token` keys collide), so this scanner walks the
/// content, finds the `[mcp_servers.<server_name>]` table header, and returns
/// the line of the first `bearer_token =` before the next `[` section header or
/// EOF. Returns `None` if the table header or the key can't be located.
fn find_bearer_token_line(content: &str, server_name: &str) -> Option<usize> {
    // Accept either `[mcp_servers.foo]` or `[mcp_servers."foo"]`.
    // Be strict about the boundary so `mcp_servers.foo_bar` doesn't match when
    // looking for `foo`.
    let header_plain = format!("[mcp_servers.{server_name}]");
    let header_quoted = format!("[mcp_servers.\"{server_name}\"]");
    let mut in_section = false;
    for (idx, line) in content.lines().enumerate() {
        let trimmed = line.trim_start();
        if trimmed.starts_with('[') {
            let trimmed_no_ws = trimmed.trim();
            in_section =
                trimmed_no_ws == header_plain.as_str() || trimmed_no_ws == header_quoted.as_str();
            continue;
        }
        if in_section {
            // Match bare `bearer_token = ...` or quoted `"bearer_token" = ...`.
            // Reject `bearer_token_env_var` and any other extension.
            let stripped = trimmed.trim_end();
            let rest = stripped
                .strip_prefix("bearer_token")
                .or_else(|| stripped.strip_prefix("\"bearer_token\""));
            if let Some(rest) = rest
                && rest.trim_start().starts_with('=')
            {
                return Some(idx + 1);
            }
        }
    }
    None
}

/// Build a map of TOML key names to their 1-indexed line numbers in a single pass.
///
/// Scans each line for a key followed by `=` (the TOML key-value separator).
/// Extracts keys by finding '=' positions; indexing is safe because find() returns
/// char-boundary positions in valid UTF-8. Handles both bare keys and simple quoted keys
/// (e.g., `"approvalMode"`), stripping quotes to normalize lookups. Prevents partial
/// matches by extracting only up to `=` (e.g., `approvalMode` will not match
/// `approvalModeExtra`).
///
/// Returns only the first occurrence of each key, which matches TOML semantics.
fn build_key_line_map(content: &str) -> HashMap<String, usize> {
    let mut map = HashMap::new();
    for (i, line) in content.lines().enumerate() {
        let trimmed = line.trim_start();
        // Extract the key portion: everything up to `=` or whitespace before `=`
        if let Some(eq_pos) = trimmed.find('=') {
            let key_part = trimmed[..eq_pos].trim_end();

            // Handle both bare keys and simple quoted keys.
            let key = if key_part.starts_with('"') && key_part.ends_with('"') && key_part.len() >= 2
            {
                key_part[1..key_part.len() - 1].to_string()
            } else {
                key_part.to_string()
            };

            // Only record the first occurrence (TOML spec: duplicate keys are errors)
            if !key.is_empty() && !map.contains_key(&key) {
                map.insert(key, i + 1);
            }
        }
    }
    map
}

/// Find the 1-indexed line number of a TOML key in the content.
///
/// Uses `strip_prefix` for UTF-8 safety and verifies the next non-whitespace
/// character is `=` to prevent partial key matches (e.g., `approvalMode`
/// does not match `approvalModeExtra`).
///
/// Production code uses `build_key_line_map` for single-pass efficiency;
/// this function is retained for targeted lookups in tests.
#[cfg(test)]
fn find_key_line(content: &str, key: &str) -> Option<usize> {
    for (i, line) in content.lines().enumerate() {
        let trimmed = line.trim_start();
        if let Some(after) = trimmed.strip_prefix(key) {
            if after.trim_start().starts_with('=') {
                return Some(i + 1);
            }
        }
    }
    None
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::config::LintConfig;
    use crate::diagnostics::DiagnosticLevel;

    fn validate_config(content: &str) -> Vec<Diagnostic> {
        let validator = CodexValidator;
        validator.validate(
            Path::new(".codex/config.toml"),
            content,
            &LintConfig::default(),
        )
    }

    fn validate_config_json(content: &str) -> Vec<Diagnostic> {
        let validator = CodexValidator;
        validator.validate(
            Path::new(".codex/config.json"),
            content,
            &LintConfig::default(),
        )
    }

    fn validate_config_with_config(content: &str, config: &LintConfig) -> Vec<Diagnostic> {
        let validator = CodexValidator;
        validator.validate(Path::new(".codex/config.toml"), content, config)
    }

    fn validate_config_at_path(path: &str, content: &str) -> Vec<Diagnostic> {
        let validator = CodexValidator;
        validator.validate(Path::new(path), content, &LintConfig::default())
    }

    fn validate_claude_md(path: &str, content: &str) -> Vec<Diagnostic> {
        let validator = CodexValidator;
        validator.validate(Path::new(path), content, &LintConfig::default())
    }

    // ===== CDX-001: Invalid approvalMode =====

    #[test]
    fn test_cdx_001_invalid_approval_mode() {
        let diagnostics = validate_config("approvalMode = \"yolo\"");
        let cdx_001: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-001").collect();
        assert_eq!(cdx_001.len(), 1);
        assert_eq!(cdx_001[0].level, DiagnosticLevel::Error);
        assert!(cdx_001[0].message.contains("yolo"));
    }

    #[test]
    fn test_cdx_001_valid_suggest() {
        let diagnostics = validate_config("approvalMode = \"suggest\"");
        let cdx_001: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-001").collect();
        assert!(cdx_001.is_empty());
    }

    #[test]
    fn test_cdx_001_valid_auto_edit() {
        let diagnostics = validate_config("approvalMode = \"auto-edit\"");
        let cdx_001: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-001").collect();
        assert!(cdx_001.is_empty());
    }

    #[test]
    fn test_cdx_001_valid_full_auto() {
        let diagnostics = validate_config("approvalMode = \"full-auto\"");
        let cdx_001: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-001").collect();
        assert!(cdx_001.is_empty());
    }

    #[test]
    fn test_cdx_001_all_valid_modes() {
        for mode in VALID_APPROVAL_MODES {
            let content = format!("approvalMode = \"{}\"", mode);
            let diagnostics = validate_config(&content);
            let cdx_001: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-001").collect();
            assert!(cdx_001.is_empty(), "Mode '{}' should be valid", mode);
        }
    }

    #[test]
    fn test_cdx_001_absent_approval_mode() {
        let diagnostics = validate_config("model = \"o4-mini\"");
        let cdx_001: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-001").collect();
        assert!(cdx_001.is_empty());
    }

    #[test]
    fn test_cdx_001_empty_string() {
        let diagnostics = validate_config("approvalMode = \"\"");
        let cdx_001: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-001").collect();
        assert_eq!(cdx_001.len(), 1);
    }

    #[test]
    fn test_cdx_001_case_sensitive() {
        let diagnostics = validate_config("approvalMode = \"Suggest\"");
        let cdx_001: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-001").collect();
        assert_eq!(cdx_001.len(), 1, "approvalMode should be case-sensitive");
    }

    #[test]
    fn test_cdx_001_autofix_case_insensitive() {
        // "Suggest" is a case-insensitive match to "suggest"
        let diagnostics = validate_config("approvalMode = \"Suggest\"");
        let cdx_001: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-001").collect();
        assert_eq!(cdx_001.len(), 1);
        assert!(
            cdx_001[0].has_fixes(),
            "CDX-001 should have auto-fix for case mismatch"
        );
        let fix = &cdx_001[0].fixes[0];
        assert!(!fix.safe, "CDX-001 fix should be unsafe");
        assert_eq!(fix.replacement, "suggest", "Fix should suggest 'suggest'");
    }

    #[test]
    fn test_cdx_001_no_autofix_when_duplicate() {
        // The regex pattern `approvalMode\s*=\s*"Suggest"` appears twice in this
        // valid TOML because a [section] table also uses the same key name.
        // The uniqueness guard should prevent autofix when there are multiple matches.
        let content = "approvalMode = \"Suggest\"\n\n[overrides]\napprovalMode = \"Suggest\"";
        let diagnostics = validate_config(content);
        let cdx_001: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-001").collect();
        assert_eq!(cdx_001.len(), 1);
        assert!(
            !cdx_001[0].has_fixes(),
            "CDX-001 should not have auto-fix when value pattern appears multiple times"
        );
    }

    #[test]
    fn test_cdx_001_no_autofix_nonsense() {
        let diagnostics = validate_config("approvalMode = \"yolo\"");
        let cdx_001: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-001").collect();
        assert_eq!(cdx_001.len(), 1);
        // "yolo" has no close match - should NOT get a fix
        assert!(
            !cdx_001[0].has_fixes(),
            "CDX-001 should not auto-fix nonsense values"
        );
    }

    #[test]
    fn test_cdx_001_autofix_targets_correct_bytes() {
        let content = "approvalMode = \"Suggest\"";
        let diagnostics = validate_config(content);
        let cdx_001: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-001").collect();
        assert_eq!(cdx_001.len(), 1);
        assert!(cdx_001[0].has_fixes());
        let fix = &cdx_001[0].fixes[0];
        let target = &content[fix.start_byte..fix.end_byte];
        assert_eq!(target, "Suggest", "Fix should target the inner value");
    }

    #[test]
    fn test_cdx_001_type_mismatch() {
        let diagnostics = validate_config("approvalMode = true");
        let cdx_001: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-001").collect();
        assert_eq!(cdx_001.len(), 1);
        assert!(cdx_001[0].message.contains("string"));
    }

    #[test]
    fn test_cdx_001_type_mismatch_number() {
        let diagnostics = validate_config("approvalMode = 42");
        let cdx_001: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-001").collect();
        assert_eq!(cdx_001.len(), 1);
    }

    #[test]
    fn test_cdx_001_type_mismatch_float() {
        let diagnostics = validate_config("approvalMode = 1.5");
        let cdx_001: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-001").collect();
        assert_eq!(cdx_001.len(), 1);
        assert!(
            cdx_001[0].message.contains("string"),
            "Expected type error message for float value"
        );
    }

    #[test]
    fn test_cdx_001_type_mismatch_array() {
        let diagnostics = validate_config("approvalMode = [\"suggest\"]");
        let cdx_001: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-001").collect();
        assert_eq!(cdx_001.len(), 1);
        assert!(
            cdx_001[0].message.contains("string"),
            "Expected type error message for array value"
        );
    }

    #[test]
    fn test_cdx_001_line_number() {
        let content = "model = \"o4-mini\"\napprovalMode = \"invalid\"";
        let diagnostics = validate_config(content);
        let cdx_001: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-001").collect();
        assert_eq!(cdx_001.len(), 1);
        assert_eq!(cdx_001[0].line, 2);
    }

    // ===== CDX-002: Invalid fullAutoErrorMode =====

    #[test]
    fn test_cdx_002_invalid_error_mode() {
        let diagnostics = validate_config("fullAutoErrorMode = \"crash\"");
        let cdx_002: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-002").collect();
        assert_eq!(cdx_002.len(), 1);
        assert_eq!(cdx_002[0].level, DiagnosticLevel::Error);
        assert!(cdx_002[0].message.contains("crash"));
    }

    #[test]
    fn test_cdx_002_valid_ask_user() {
        let diagnostics = validate_config("fullAutoErrorMode = \"ask-user\"");
        let cdx_002: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-002").collect();
        assert!(cdx_002.is_empty());
    }

    #[test]
    fn test_cdx_002_valid_ignore_and_continue() {
        let diagnostics = validate_config("fullAutoErrorMode = \"ignore-and-continue\"");
        let cdx_002: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-002").collect();
        assert!(cdx_002.is_empty());
    }

    #[test]
    fn test_cdx_002_all_valid_modes() {
        for mode in VALID_FULL_AUTO_ERROR_MODES {
            let content = format!("fullAutoErrorMode = \"{}\"", mode);
            let diagnostics = validate_config(&content);
            let cdx_002: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-002").collect();
            assert!(cdx_002.is_empty(), "Mode '{}' should be valid", mode);
        }
    }

    #[test]
    fn test_cdx_002_absent_error_mode() {
        let diagnostics = validate_config("model = \"o4-mini\"");
        let cdx_002: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-002").collect();
        assert!(cdx_002.is_empty());
    }

    #[test]
    fn test_cdx_002_empty_string() {
        let diagnostics = validate_config("fullAutoErrorMode = \"\"");
        let cdx_002: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-002").collect();
        assert_eq!(cdx_002.len(), 1);
    }

    #[test]
    fn test_cdx_002_autofix_case_insensitive() {
        // "Ask-User" is a case-insensitive match to "ask-user"
        let diagnostics = validate_config("fullAutoErrorMode = \"Ask-User\"");
        let cdx_002: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-002").collect();
        assert_eq!(cdx_002.len(), 1);
        assert!(
            cdx_002[0].has_fixes(),
            "CDX-002 should have auto-fix for case mismatch"
        );
        let fix = &cdx_002[0].fixes[0];
        assert!(!fix.safe, "CDX-002 fix should be unsafe");
        assert_eq!(fix.replacement, "ask-user", "Fix should suggest 'ask-user'");
    }

    #[test]
    fn test_cdx_002_no_autofix_nonsense() {
        let diagnostics = validate_config("fullAutoErrorMode = \"crash\"");
        let cdx_002: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-002").collect();
        assert_eq!(cdx_002.len(), 1);
        // "crash" has no close match - should NOT get a fix
        assert!(
            !cdx_002[0].has_fixes(),
            "CDX-002 should not auto-fix nonsense values"
        );
    }

    #[test]
    fn test_cdx_002_autofix_targets_correct_bytes() {
        let content = "fullAutoErrorMode = \"Ask-User\"";
        let diagnostics = validate_config(content);
        let cdx_002: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-002").collect();
        assert_eq!(cdx_002.len(), 1);
        assert!(cdx_002[0].has_fixes());
        let fix = &cdx_002[0].fixes[0];
        let target = &content[fix.start_byte..fix.end_byte];
        assert_eq!(target, "Ask-User", "Fix should target the inner value");
    }

    #[test]
    fn test_cdx_002_type_mismatch() {
        let diagnostics = validate_config("fullAutoErrorMode = false");
        let cdx_002: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-002").collect();
        assert_eq!(cdx_002.len(), 1);
        assert!(cdx_002[0].message.contains("string"));
    }

    #[test]
    fn test_cdx_002_case_sensitive() {
        let diagnostics = validate_config("fullAutoErrorMode = \"Ask-User\"");
        let cdx_002: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-002").collect();
        assert_eq!(
            cdx_002.len(),
            1,
            "fullAutoErrorMode should be case-sensitive"
        );
    }

    #[test]
    fn test_cdx_002_line_number() {
        let content = "model = \"o4-mini\"\nfullAutoErrorMode = \"crash\"";
        let diagnostics = validate_config(content);
        let cdx_002: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-002").collect();
        assert_eq!(cdx_002.len(), 1);
        assert_eq!(cdx_002[0].line, 2);
    }

    // ===== CDX-003: AGENTS.override.md in version control =====

    #[test]
    fn test_cdx_003_agents_override_md() {
        let diagnostics = validate_claude_md("AGENTS.override.md", "# Override");
        let cdx_003: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-003").collect();
        assert_eq!(cdx_003.len(), 1);
        assert_eq!(cdx_003[0].level, DiagnosticLevel::Warning);
        assert!(cdx_003[0].message.contains("AGENTS.override.md"));
    }

    #[test]
    fn test_cdx_003_not_triggered_on_claude_md() {
        let diagnostics = validate_claude_md("CLAUDE.md", "# My project");
        let cdx_003: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-003").collect();
        assert!(cdx_003.is_empty());
    }

    #[test]
    fn test_cdx_003_not_triggered_on_agents_md() {
        let diagnostics = validate_claude_md("AGENTS.md", "# My project");
        let cdx_003: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-003").collect();
        assert!(cdx_003.is_empty());
    }

    #[test]
    fn test_cdx_003_case_sensitive_extension() {
        // AGENTS.override.MD (wrong extension case) should NOT trigger CDX-003
        let diagnostics = validate_claude_md("AGENTS.override.MD", "# test");
        let cdx_003: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-003").collect();
        assert!(
            cdx_003.is_empty(),
            "CDX-003 should not fire for AGENTS.override.MD"
        );
    }

    #[test]
    fn test_cdx_003_not_triggered_on_config() {
        let diagnostics = validate_config("approvalMode = \"suggest\"");
        let cdx_003: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-003").collect();
        assert!(cdx_003.is_empty());
    }

    // ===== Config Integration =====

    #[test]
    fn test_config_disabled_codex_category() {
        let mut config = LintConfig::default();
        config.rules_mut().codex = false;

        let diagnostics = validate_config_with_config("approvalMode = \"invalid\"", &config);
        let cdx_rules: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule.starts_with("CDX-"))
            .collect();
        assert!(cdx_rules.is_empty());
    }

    #[test]
    fn test_config_disabled_specific_rule() {
        let mut config = LintConfig::default();
        config.rules_mut().disabled_rules = vec!["CDX-001".to_string()];

        let diagnostics = validate_config_with_config("approvalMode = \"invalid\"", &config);
        let cdx_001: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-001").collect();
        assert!(cdx_001.is_empty());
    }

    #[test]
    fn test_all_cdx_rules_can_be_disabled() {
        let rules = [
            "CDX-001", "CDX-002", "CDX-003", "CDX-004", "CDX-005", "CDX-006",
        ];

        for rule in rules {
            let mut config = LintConfig::default();
            config.rules_mut().disabled_rules = vec![rule.to_string()];

            let (content, path): (&str, &str) = match rule {
                "CDX-001" => ("approvalMode = \"invalid\"", ".codex/config.toml"),
                "CDX-002" => ("fullAutoErrorMode = \"crash\"", ".codex/config.toml"),
                "CDX-003" => ("# Override", "AGENTS.override.md"),
                "CDX-004" => ("totally_unknown_key = true", ".codex/config.toml"),
                "CDX-005" => ("project_doc_max_bytes = 999999", ".codex/config.toml"),
                "CDX-006" => (
                    "project_doc_fallback_filenames = [\"AGENTS.md\", \"AGENTS.md\"]",
                    ".codex/config.toml",
                ),
                _ => unreachable!(),
            };

            let validator = CodexValidator;
            let diagnostics = validator.validate(Path::new(path), content, &config);

            assert!(
                !diagnostics.iter().any(|d| d.rule == rule),
                "Rule {} should be disabled",
                rule
            );
        }
    }

    // ===== Valid Config =====

    #[test]
    fn test_valid_config_no_issues() {
        let content = r#"
model = "o4-mini"
approvalMode = "suggest"
fullAutoErrorMode = "ask-user"
notify = true
"#;
        let diagnostics = validate_config(content);
        assert!(
            diagnostics.is_empty(),
            "Expected no diagnostics, got: {:?}",
            diagnostics
        );
    }

    #[test]
    fn test_empty_config_no_issues() {
        let diagnostics = validate_config("");
        assert!(diagnostics.is_empty());
    }

    // ===== Multiple Issues =====

    #[test]
    fn test_multiple_issues() {
        let content = "approvalMode = \"yolo\"\nfullAutoErrorMode = \"crash\"";
        let diagnostics = validate_config(content);
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-001"));
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-002"));
    }

    #[test]
    fn test_cdx_002_empty_with_cdx_001_invalid() {
        // Both CDX-001 (invalid value) and CDX-002 (empty string) should fire together
        let content = "approvalMode = \"invalid\"\nfullAutoErrorMode = \"\"";
        let diagnostics = validate_config(content);
        let cdx_001: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-001").collect();
        let cdx_002: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-002").collect();
        assert_eq!(
            cdx_001.len(),
            1,
            "CDX-001 should fire for invalid approvalMode"
        );
        assert_eq!(
            cdx_002.len(),
            1,
            "CDX-002 should fire for empty fullAutoErrorMode"
        );
    }

    #[test]
    fn test_both_fields_wrong_type() {
        let content = "approvalMode = true\nfullAutoErrorMode = 123";
        let diagnostics = validate_config(content);
        let cdx_001: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-001").collect();
        let cdx_002: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-002").collect();
        assert_eq!(
            cdx_001.len(),
            1,
            "CDX-001 should fire for wrong-type approvalMode"
        );
        assert_eq!(
            cdx_002.len(),
            1,
            "CDX-002 should fire for wrong-type fullAutoErrorMode"
        );
        assert!(cdx_001[0].message.contains("string"));
        assert!(cdx_002[0].message.contains("string"));
    }

    // ===== CDX-004: Unknown config keys =====

    #[test]
    fn test_cdx_004_unknown_key() {
        let diagnostics = validate_config("totally_unknown_key = true");
        let cdx_004: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-004").collect();
        assert_eq!(cdx_004.len(), 1);
        assert_eq!(cdx_004[0].level, DiagnosticLevel::Warning);
        assert!(cdx_004[0].message.contains("totally_unknown_key"));
    }

    #[test]
    fn test_cdx_004_known_keys_no_warning() {
        let content = r#"
model = "o4-mini"
approvalMode = "suggest"
fullAutoErrorMode = "ask-user"
notify = true
project_doc_max_bytes = 32768
project_doc_fallback_filenames = ["AGENTS.md", "README.md"]
"#;
        let diagnostics = validate_config(content);
        let cdx_004: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-004").collect();
        assert!(cdx_004.is_empty(), "Known keys should not trigger CDX-004");
    }

    #[test]
    fn test_cdx_004_multiple_unknown_keys() {
        let content = "unknown_a = true\nunknown_b = false\nmodel = \"o4-mini\"";
        let diagnostics = validate_config(content);
        let cdx_004: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-004").collect();
        assert_eq!(cdx_004.len(), 2);
    }

    #[test]
    fn test_cdx_004_line_number() {
        let content = "model = \"o4-mini\"\nmy_custom_key = true";
        let diagnostics = validate_config(content);
        let cdx_004: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-004").collect();
        assert_eq!(cdx_004.len(), 1);
        assert_eq!(cdx_004[0].line, 2);
    }

    #[test]
    fn test_cdx_004_table_keys_not_flagged() {
        let content = r#"
model = "o4-mini"

[mcp_servers]
name = "test"
"#;
        let diagnostics = validate_config(content);
        let cdx_004: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-004").collect();
        assert!(
            cdx_004.is_empty(),
            "Known table keys should not trigger CDX-004"
        );
    }

    #[test]
    fn test_codex_0_133_0_new_keys_not_flagged() {
        // Config keys introduced in Codex rust-v0.133.0 (diffed from
        // codex-rs/core/config.schema.json between rust-v0.129.0 and
        // rust-v0.133.0). Regression guard against CDX-004 (unknown key) and
        // CDX-CFG-026 (unknown permissions.network sub-field) false-positives
        // on valid 0.133 configs. Covers every new allow-list entry:
        // top-level (apps_mcp_product_sku, desktop, include_collaboration_
        // mode_instructions, model_auto_compact_token_limit_scope), features
        // (mentions_v2, network_proxy, plugin_sharing), mcp_servers (oauth),
        // and permissions.network (mitm). The new [tui] keys (pet/pet_anchor)
        // are intentionally NOT added - TUI display tweaks are on the codex
        // changes_of_interest.irrelevant list (matching the v0.129 catch-up).
        let content = r#"
apps_mcp_product_sku = "codex-pro"
include_collaboration_mode_instructions = true
model_auto_compact_token_limit_scope = "body_after_prefix"

[desktop]
opaque = "value"

[features]
mentions_v2 = true
network_proxy = true
plugin_sharing = true

[mcp_servers.example]
command = "example-server"
oauth = true

[permissions.network]
mitm = true
"#;
        let diagnostics = validate_config(content);
        let cdx_004: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-004")
            .map(|d| d.message.as_str())
            .collect();
        assert!(
            cdx_004.is_empty(),
            "0.133 keys should not trigger CDX-004, got: {cdx_004:?}"
        );
        let cdx_cfg_026: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-026")
            .map(|d| d.message.as_str())
            .collect();
        assert!(
            cdx_cfg_026.is_empty(),
            "0.133 permissions.network `mitm` should not trigger CDX-CFG-026, got: {cdx_cfg_026:?}"
        );
    }

    #[test]
    fn test_cdx_004_has_suggestion() {
        let diagnostics = validate_config("bogus_setting = 42");
        let cdx_004: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-004").collect();
        assert_eq!(cdx_004.len(), 1);
        assert!(
            cdx_004[0].suggestion.is_some(),
            "CDX-004 should have a suggestion"
        );
    }

    // ===== CDX-005: project_doc_max_bytes exceeds limit =====

    #[test]
    fn test_cdx_005_exceeds_limit() {
        let diagnostics = validate_config("project_doc_max_bytes = 100000");
        let cdx_005: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-005").collect();
        assert_eq!(cdx_005.len(), 1);
        assert_eq!(cdx_005[0].level, DiagnosticLevel::Error);
        assert!(cdx_005[0].message.contains("100000"));
    }

    #[test]
    fn test_cdx_005_at_limit() {
        let diagnostics = validate_config("project_doc_max_bytes = 65536");
        let cdx_005: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-005").collect();
        assert!(cdx_005.is_empty(), "65536 is at the limit, should be valid");
    }

    #[test]
    fn test_cdx_005_below_limit() {
        let diagnostics = validate_config("project_doc_max_bytes = 32768");
        let cdx_005: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-005").collect();
        assert!(cdx_005.is_empty(), "32768 is below limit, should be valid");
    }

    #[test]
    fn test_cdx_005_just_over_limit() {
        let diagnostics = validate_config("project_doc_max_bytes = 65537");
        let cdx_005: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-005").collect();
        assert_eq!(cdx_005.len(), 1, "65537 exceeds 65536 limit");
    }

    #[test]
    fn test_cdx_005_wrong_type() {
        let diagnostics = validate_config("project_doc_max_bytes = \"not a number\"");
        let cdx_005: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-005").collect();
        assert_eq!(cdx_005.len(), 1);
        assert!(cdx_005[0].message.contains("integer"));
    }

    #[test]
    fn test_cdx_005_absent() {
        let diagnostics = validate_config("model = \"o4-mini\"");
        let cdx_005: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-005").collect();
        assert!(
            cdx_005.is_empty(),
            "Absent field should not trigger CDX-005"
        );
    }

    #[test]
    fn test_cdx_005_line_number() {
        let content = "model = \"o4-mini\"\nproject_doc_max_bytes = 999999";
        let diagnostics = validate_config(content);
        let cdx_005: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-005").collect();
        assert_eq!(cdx_005.len(), 1);
        assert_eq!(cdx_005[0].line, 2);
    }

    #[test]
    fn test_cdx_005_has_suggestion() {
        let diagnostics = validate_config("project_doc_max_bytes = 100000");
        let cdx_005: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-005").collect();
        assert_eq!(cdx_005.len(), 1);
        assert!(
            cdx_005[0].suggestion.is_some(),
            "CDX-005 should have a suggestion"
        );
    }

    #[test]
    fn test_cdx_005_negative_value() {
        // Negative values are invalid because project_doc_max_bytes must be a positive integer
        let diagnostics = validate_config("project_doc_max_bytes = -1");
        let cdx_005: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-005").collect();
        assert_eq!(cdx_005.len(), 1, "Negative values should trigger CDX-005");
        assert!(
            cdx_005[0].message.contains("positive integer"),
            "Error message should indicate positive integer requirement"
        );
    }

    #[test]
    fn test_cdx_005_zero_value() {
        // Zero is invalid because project_doc_max_bytes must be a positive integer
        let diagnostics = validate_config("project_doc_max_bytes = 0");
        let cdx_005: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-005").collect();
        assert_eq!(cdx_005.len(), 1, "Zero should trigger CDX-005");
        assert!(
            cdx_005[0].message.contains("positive integer"),
            "Error message should indicate positive integer requirement"
        );
    }

    #[test]
    fn test_cdx_004_and_cdx_005_combined() {
        // Both an unknown key and exceeding limit in the same file
        let content = "unknown_key = true\nproject_doc_max_bytes = 999999";
        let diagnostics = validate_config(content);
        let cdx_004: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-004").collect();
        let cdx_005: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-005").collect();
        assert_eq!(cdx_004.len(), 1, "CDX-004 should fire for unknown_key");
        assert_eq!(cdx_005.len(), 1, "CDX-005 should fire for exceeding limit");
    }

    // ===== CDX-006: project_doc_fallback_filenames validation =====

    #[test]
    fn test_cdx_006_valid_fallback_filenames() {
        let diagnostics =
            validate_config("project_doc_fallback_filenames = [\"AGENTS.md\", \"README.md\"]");
        let cdx_006: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-006").collect();
        assert!(
            cdx_006.is_empty(),
            "Valid fallback filenames should not trigger CDX-006"
        );
    }

    #[test]
    fn test_cdx_006_wrong_type() {
        let diagnostics = validate_config("project_doc_fallback_filenames = \"AGENTS.md\"");
        let cdx_006: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-006").collect();
        assert_eq!(cdx_006.len(), 1);
        assert_eq!(cdx_006[0].level, DiagnosticLevel::Error);
        assert!(cdx_006[0].message.contains("must be an array"));
    }

    #[test]
    fn test_cdx_006_non_string_entries() {
        let diagnostics = validate_config("project_doc_fallback_filenames = [\"AGENTS.md\", 42]");
        let cdx_006: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-006").collect();
        assert_eq!(cdx_006.len(), 1);
        assert_eq!(cdx_006[0].level, DiagnosticLevel::Error);
        assert!(cdx_006[0].message.contains("index 2"));
    }

    #[test]
    fn test_cdx_006_empty_entry() {
        let diagnostics = validate_config("project_doc_fallback_filenames = [\"\", \"AGENTS.md\"]");
        let cdx_006: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-006").collect();
        assert_eq!(cdx_006.len(), 1);
        assert_eq!(cdx_006[0].level, DiagnosticLevel::Error);
        assert!(cdx_006[0].message.contains("index 1"));
    }

    #[test]
    fn test_cdx_006_duplicate_entry_warns() {
        let diagnostics = validate_config(
            "project_doc_fallback_filenames = [\"AGENTS.md\", \"README.md\", \"AGENTS.md\"]",
        );
        let cdx_006: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-006").collect();
        assert_eq!(cdx_006.len(), 1);
        assert_eq!(cdx_006[0].level, DiagnosticLevel::Warning);
        assert!(cdx_006[0].message.contains("duplicate"));
    }

    #[test]
    fn test_cdx_006_suspicious_path_warns() {
        let diagnostics = validate_config(
            "project_doc_fallback_filenames = [\"AGENTS.md\", \"docs/AGENTS.md\", \"C:/tmp/a.md\"]",
        );
        let cdx_006: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-006").collect();
        assert_eq!(cdx_006.len(), 2);
        assert!(cdx_006.iter().all(|d| d.level == DiagnosticLevel::Warning));
        assert!(cdx_006.iter().any(|d| d.message.contains("docs/AGENTS.md")));
        assert!(cdx_006.iter().any(|d| d.message.contains("C:/tmp/a.md")));
    }

    #[test]
    fn test_cdx_006_line_number() {
        let content = "model = \"o4-mini\"\nproject_doc_fallback_filenames = [\"\"]";
        let diagnostics = validate_config(content);
        let cdx_006: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-006").collect();
        assert_eq!(cdx_006.len(), 1);
        assert_eq!(cdx_006[0].line, 2);
    }

    #[test]
    fn test_cdx_006_has_suggestion() {
        let diagnostics = validate_config("project_doc_fallback_filenames = [\"\"]");
        let cdx_006: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-006").collect();
        assert_eq!(cdx_006.len(), 1);
        assert!(
            cdx_006[0].suggestion.is_some(),
            "CDX-006 should have a suggestion"
        );
    }

    // ===== Fixture Integration =====

    #[test]
    fn test_valid_codex_fixture_no_diagnostics() {
        let fixture = include_str!("../../../../tests/fixtures/codex/.codex/config.toml");
        let diagnostics = validate_config(fixture);
        assert!(
            diagnostics.is_empty(),
            "Valid codex fixture should produce 0 diagnostics, got: {:?}",
            diagnostics
        );
    }

    // ===== find_key_line =====

    #[test]
    fn test_find_key_line() {
        let content =
            "model = \"o4-mini\"\napprovalMode = \"suggest\"\nfullAutoErrorMode = \"ask-user\"";
        assert_eq!(find_key_line(content, "model"), Some(1));
        assert_eq!(find_key_line(content, "approvalMode"), Some(2));
        assert_eq!(find_key_line(content, "fullAutoErrorMode"), Some(3));
        assert_eq!(find_key_line(content, "nonexistent"), None);
    }

    #[test]
    fn test_find_key_line_ignores_value_match() {
        // "approvalMode" appears as part of a string value, not as a key
        let content = "comment = \"the approvalMode field\"\napprovalMode = \"suggest\"";
        assert_eq!(find_key_line(content, "approvalMode"), Some(2));
    }

    #[test]
    fn test_find_key_line_at_start_of_content() {
        // Key on the very first line with no preceding content
        let content = "approvalMode = \"suggest\"";
        assert_eq!(find_key_line(content, "approvalMode"), Some(1));
    }

    #[test]
    fn test_find_key_line_with_leading_whitespace() {
        // Key with leading whitespace (indented)
        let content = "  approvalMode = \"suggest\"";
        assert_eq!(find_key_line(content, "approvalMode"), Some(1));
    }

    #[test]
    fn test_find_key_line_no_partial_match() {
        // "approvalMode" must not match "approvalModeExtra"
        let content = "approvalModeExtra = \"value\"\napprovalMode = \"suggest\"";
        assert_eq!(find_key_line(content, "approvalMode"), Some(2));
    }

    // ===== build_key_line_map =====

    #[test]
    fn test_build_key_line_map() {
        let content =
            "model = \"o4-mini\"\napprovalMode = \"suggest\"\nfullAutoErrorMode = \"ask-user\"";
        let map = build_key_line_map(content);
        assert_eq!(map.get("model"), Some(&1));
        assert_eq!(map.get("approvalMode"), Some(&2));
        assert_eq!(map.get("fullAutoErrorMode"), Some(&3));
        assert_eq!(map.get("nonexistent"), None);
    }

    #[test]
    fn test_build_key_line_map_no_partial_match() {
        let content = "approvalModeExtra = \"value\"\napprovalMode = \"suggest\"";
        let map = build_key_line_map(content);
        assert_eq!(map.get("approvalModeExtra"), Some(&1));
        assert_eq!(map.get("approvalMode"), Some(&2));
    }

    // ===== CDX-000 suggestion test =====

    #[test]
    fn test_cdx_000_has_suggestion() {
        let content = "this is not valid toml {{{}}}";
        let diagnostics = validate_config(content);

        let cdx_000: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-000").collect();
        assert_eq!(cdx_000.len(), 1);
        assert!(
            cdx_000[0].suggestion.is_some(),
            "CDX-000 should have a suggestion"
        );
        assert!(
            cdx_000[0]
                .suggestion
                .as_ref()
                .unwrap()
                .contains("TOML syntax"),
            "CDX-000 suggestion should mention TOML syntax"
        );
    }

    #[test]
    fn test_cdx_000_uses_localized_message() {
        let content = "this is not valid toml {{{}}}";
        let diagnostics = validate_config(content);

        let cdx_000: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-000").collect();
        assert_eq!(cdx_000.len(), 1);
        assert!(
            cdx_000[0]
                .message
                .contains("Failed to parse .codex/config.toml as TOML"),
            "CDX-000 message should use localized text, got: {}",
            cdx_000[0].message
        );
    }

    // ===== Autofix Tests =====

    #[test]
    fn test_cdx_004_has_fix() {
        let diagnostics = validate_config("totally_unknown_key = true");
        let cdx_004: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-004").collect();
        assert_eq!(cdx_004.len(), 1);
        assert!(cdx_004[0].has_fixes(), "CDX-004 should have fix");
        assert!(!cdx_004[0].fixes[0].safe, "CDX-004 fix should be unsafe");
        assert!(cdx_004[0].fixes[0].is_deletion());
    }

    #[test]
    fn test_cdx_004_fix_application() {
        let content = "model = \"o4-mini\"\ntotally_unknown_key = true\nnotify = true";
        let diagnostics = validate_config(content);
        let cdx_004: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-004").collect();
        assert_eq!(cdx_004.len(), 1);
        assert!(cdx_004[0].has_fixes());
        let fix = &cdx_004[0].fixes[0];
        let mut fixed = content.to_string();
        fixed.replace_range(fix.start_byte..fix.end_byte, &fix.replacement);
        assert!(!fixed.contains("totally_unknown_key"));
        assert!(fixed.contains("model"));
        assert!(fixed.contains("notify"));
    }

    // ===== CDX-CFG / CDX-AG / CDX-APP =====

    #[test]
    fn test_cdx_cfg_001_invalid_approval_policy() {
        let diagnostics = validate_config("approval_policy = \"always\"");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-001"));
    }

    #[test]
    fn test_cdx_cfg_002_invalid_sandbox_mode() {
        let diagnostics = validate_config("sandbox_mode = \"open\"");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-002"));
    }

    #[test]
    fn test_cdx_cfg_003_empty_reasoning_effort() {
        // The 0.138+ schema requires minLength 1 - an empty string is the
        // one string shape that is still always wrong.
        let diagnostics = validate_config("model_reasoning_effort = \"\"");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-003"));
    }

    #[test]
    fn test_cdx_cfg_003_whitespace_only_reasoning_effort() {
        // Whitespace-only is functionally empty - it must not bypass the
        // minLength-1 check the way a bare "" would.
        let diagnostics = validate_config("model_reasoning_effort = \"   \"");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-003"));
    }

    #[test]
    fn test_cdx_cfg_003_non_string_reasoning_effort() {
        let diagnostics = validate_config("model_reasoning_effort = 3");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-003"));
    }

    #[test]
    fn test_cdx_cfg_003_standard_reasoning_effort_not_flagged() {
        let diagnostics = validate_config("model_reasoning_effort = \"high\"");
        assert!(diagnostics.iter().all(|d| d.rule != "CDX-CFG-003"));
    }

    #[test]
    fn test_cdx_cfg_004_invalid_model_verbosity() {
        let diagnostics = validate_config("model_verbosity = \"verbose\"");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-004"));
    }

    #[test]
    fn test_cdx_cfg_005_invalid_personality() {
        let diagnostics = validate_config("personality = \"assistant\"");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-005"));
    }

    #[test]
    fn test_cdx_cfg_006_unknown_nested_key() {
        let diagnostics = validate_config("[features]\nunknown_flag = true");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-006"));
    }

    #[test]
    fn test_codex_v0_122_top_level_keys_accepted() {
        // 8 top-level keys present in upstream config-schema.json (verified
        // 2026-04-22) but missing from agnix's TOML allow-list before this
        // change. CDX-004 is the rule that fires for unknown TOML top-level
        // keys (CDX-CFG-006 explicitly skips top-level keys when CDX-004 is
        // enabled, so asserting against CDX-CFG-006 here would be vacuous).
        // Two of the keys (marketplaces, realtime) can also appear as TOML
        // tables; covered by the second config below.
        let toml_inline = r#"
experimental_realtime_start_instructions = "hi"
experimental_realtime_ws_startup_context = "ctx"
include_apps_instructions = true
include_environment_context = true
include_permissions_instructions = false
marketplaces = []
realtime = {}
tool_suggest = "off"
"#;
        let diagnostics = validate_config(toml_inline);
        let unknown: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-004").collect();
        assert!(
            unknown.is_empty(),
            "v0.122 top-level keys should not trigger CDX-004, got: {:?}",
            unknown
        );

        // Section variants: [realtime] and [[marketplaces]] tables
        let toml_tables = r#"
[realtime]
ws_url = "wss://example"

[[marketplaces]]
name = "official"
url = "https://example.com/marketplace"
"#;
        let diagnostics = validate_config(toml_tables);
        let unknown: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-004").collect();
        assert!(
            unknown.is_empty(),
            "v0.122 [realtime] and [[marketplaces]] tables should not trigger CDX-004, got: {:?}",
            unknown
        );
    }

    #[test]
    fn test_codex_v0_123_top_level_keys_accepted_toml() {
        // Codex CLI rust-v0.123.0 added 1 new top-level config key
        // (verified against config-schema.json on 2026-04-23):
        //   - experimental_thread_store_endpoint
        //
        // TOML path: CDX-004 fires for unknown top-level keys.
        let toml = r#"
experimental_thread_store_endpoint = "https://thread-store.example"
"#;
        let diagnostics = validate_config(toml);
        let unknown: Vec<_> = diagnostics.iter().filter(|d| d.rule == "CDX-004").collect();
        assert!(
            unknown.is_empty(),
            "experimental_thread_store_endpoint should not trigger CDX-004 (Codex v0.123+), got: {:?}",
            unknown
        );
    }

    #[test]
    fn test_codex_v0_123_top_level_keys_accepted_json() {
        // JSON/YAML path: CDX-CFG-006 fires for unknown top-level keys when
        // the file is JSON/YAML (NOT TOML, where CDX-004 takes over via
        // skip_top_level). The shared allow-list in
        // schemas/codex.rs::is_known_top_level_key must include
        // `experimental_thread_store_endpoint` for CDX-CFG-006 to accept it.
        // Both backends consult the same predicate, so TOML and JSON/YAML
        // can no longer disagree about it.
        let json = r#"{
  "experimental_thread_store_endpoint": "https://thread-store.example"
}"#;
        let diagnostics = validate_config_json(json);
        let unknown: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-006")
            .collect();
        assert!(
            unknown.is_empty(),
            "experimental_thread_store_endpoint should not trigger CDX-CFG-006 (Codex v0.123+), got: {:?}",
            unknown
        );
    }

    #[test]
    fn test_cdx_cfg_007_danger_full_access_without_ack() {
        let diagnostics = validate_config("sandbox_mode = \"danger-full-access\"");
        let cdx_007: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-007")
            .collect();
        assert_eq!(cdx_007.len(), 1);
        assert_eq!(cdx_007[0].level, DiagnosticLevel::Error);
    }

    #[test]
    fn test_cdx_cfg_008_invalid_shell_environment_inherit() {
        let diagnostics = validate_config("[shell_environment_policy]\ninherit = \"system\"");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-008"));
    }

    #[test]
    fn test_cdx_cfg_009_invalid_mcp_server_shape() {
        let diagnostics = validate_config("[mcp_servers.local]\nenabled = true");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-009"));
    }

    #[test]
    fn test_cdx_cfg_010_detects_hardcoded_secret() {
        let diagnostics = validate_config("api_key = \"sk-test-secret-value-123456\"");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-010"));
    }

    #[test]
    fn test_cdx_cfg_011_invalid_feature_flag_name() {
        let diagnostics = validate_config("[features]\nthis_flag_does_not_exist = true");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-011"));
    }

    #[test]
    fn test_codex_0_137_0_new_feature_flags_not_flagged() {
        // Feature flags present in upstream rust-v0.137.0
        // codex-rs/core/config.schema.json. Regression guard against
        // CDX-CFG-011 false positives on valid current configs.
        let diagnostics = validate_config(
            r#"[features]
imagegenext = true
local_thread_store_compression = true
non_prefixed_mcp_tool_names = true
standalone_web_search = true
unified_exec_zsh_fork = true
"#,
        );
        let cdx_cfg_011: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-011")
            .map(|d| d.message.as_str())
            .collect();
        assert!(
            cdx_cfg_011.is_empty(),
            "0.137 feature flags should not trigger CDX-CFG-011, got: {cdx_cfg_011:?}"
        );
    }

    #[test]
    fn test_codex_0_138_0_new_feature_flag_not_flagged() {
        // `terminal_visualization_instructions` was added to [features] in
        // upstream rust-v0.138.0 codex-rs/core/config.schema.json
        // (openai/codex#26013). Regression guard against CDX-CFG-011 /
        // CDX-CFG-006 false positives on valid current configs.
        let diagnostics = validate_config("[features]\nterminal_visualization_instructions = true");
        let unexpected: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-011" || d.rule == "CDX-CFG-006")
            .map(|d| d.message.as_str())
            .collect();
        assert!(
            unexpected.is_empty(),
            "0.138 feature flag should not be flagged, got: {unexpected:?}"
        );
    }

    #[test]
    fn test_codex_0_138_0_removed_websocket_flag_still_tolerated() {
        // `responses_websocket_response_processed` was removed from the
        // rust-v0.138.0 schema (openai/codex#26447) but shipped in earlier
        // releases - kept on the older-version tolerance list so configs
        // written for those versions don't start flagging.
        let diagnostics =
            validate_config("[features]\nresponses_websocket_response_processed = true");
        assert!(
            diagnostics
                .iter()
                .all(|d| d.rule != "CDX-CFG-011" && d.rule != "CDX-CFG-006"),
            "older-version feature flag must stay tolerated"
        );
    }

    #[test]
    fn test_codex_0_138_0_code_mode_table_not_flagged() {
        // rust-v0.138.0 changed `features.code_mode` from a plain bool to
        // bool-or-table ({ enabled, excluded_tool_namespaces },
        // openai/codex#26320). The key-name checks must accept the table
        // form without descending into its value.
        let diagnostics = validate_config(
            r#"[features.code_mode]
enabled = true
excluded_tool_namespaces = ["browser"]
"#,
        );
        let unexpected: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-011" || d.rule == "CDX-CFG-006")
            .map(|d| d.message.as_str())
            .collect();
        assert!(
            unexpected.is_empty(),
            "code_mode table form should not be flagged, got: {unexpected:?}"
        );
    }

    #[test]
    fn test_codex_0_142_0_config_surfaces_not_flagged() {
        // Codex CLI rust-v0.142.0 added `orchestrator`, structured
        // token/rollout/current-time feature configs, and the indexed
        // web-search mode. These are schema-facing surfaces from the release
        // notes/source diff, not app-server-only runtime state.
        let diagnostics = validate_config(
            r#"
web_search = "indexed"

[orchestrator.skills]
enabled = true

[orchestrator.mcp]
enabled = false

[features]
auto_compaction = true
deferred_executor = true
item_ids = true
multi_agent_mode = false
respect_system_proxy = true
use_agent_identity = true

[features.token_budget]
enabled = true
reminder_threshold_tokens = 2048
reminder_message_template = "Wrap up soon; {n_remaining} tokens remain."

[features.rollout_budget]
enabled = true
limit_tokens = 100000
reminder_at_remaining_tokens = [25000, 10000]
sampling_token_weight = 1.0
prefill_token_weight = 0.5

[features.current_time_reminder]
enabled = true
reminder_interval_model_requests = 4
clock_source = "external"
"#,
        );
        let unexpected: Vec<_> = diagnostics
            .iter()
            .filter(|d| {
                matches!(
                    d.rule.as_str(),
                    "CDX-004" | "CDX-CFG-006" | "CDX-CFG-011" | "CDX-CFG-030"
                )
            })
            .map(|d| d.message.as_str())
            .collect();
        assert!(
            unexpected.is_empty(),
            "0.142 config surfaces should not be flagged, got: {unexpected:?}"
        );
    }

    #[test]
    fn test_cdx_cfg_030_invalid_web_search_mode() {
        let diagnostics = validate_config("web_search = \"server_approved\"");
        let hits: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-030")
            .collect();
        assert_eq!(hits.len(), 1);
        assert!(hits[0].message.contains("indexed"));
    }

    #[test]
    fn test_cdx_cfg_030_web_search_must_be_string() {
        let diagnostics = validate_config("web_search = true");
        let hits: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-030")
            .collect();
        assert_eq!(hits.len(), 1);
        assert!(hits[0].message.to_lowercase().contains("string"));
    }

    #[test]
    fn test_cdx_cfg_030_can_be_disabled() {
        let mut config = LintConfig::default();
        config.rules_mut().disabled_rules = vec!["CDX-CFG-030".to_string()];
        let diagnostics = validate_config_with_config("web_search = \"server_approved\"", &config);
        assert!(diagnostics.iter().all(|d| d.rule != "CDX-CFG-030"));
    }

    #[test]
    fn test_codex_0_138_0_model_defined_reasoning_effort_not_flagged() {
        // rust-v0.138.0 replaced the fixed reasoning-effort enum with
        // model-defined efforts - any non-empty string the model advertises
        // is valid (openai/codex#26444), so values outside the old
        // none|minimal|low|medium|high|xhigh set must no longer flag.
        let diagnostics = validate_config("model_reasoning_effort = \"turbo\"");
        assert!(
            diagnostics.iter().all(|d| d.rule != "CDX-CFG-003"),
            "model-defined reasoning efforts must not trigger CDX-CFG-003"
        );
    }

    #[test]
    fn test_codex_0_140_0_new_top_level_key_not_flagged_toml() {
        // rust-v0.140.0 added `experimental_realtime_webrtc_call_base_url`
        // to the upstream config schema. The TOML path uses CDX-004 for
        // unknown top-level keys.
        let diagnostics = validate_config(
            "experimental_realtime_webrtc_call_base_url = \"https://realtime.example.com/call\"",
        );
        let unexpected: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-004" || d.rule == "CDX-CFG-006")
            .map(|d| d.message.as_str())
            .collect();
        assert!(
            unexpected.is_empty(),
            "0.140 realtime WebRTC key should not be flagged on TOML path, got: {unexpected:?}"
        );
    }

    #[test]
    fn test_codex_0_140_0_new_top_level_key_not_flagged_json() {
        // JSON/YAML path uses CDX-CFG-006 for unknown top-level keys.
        let diagnostics = validate_config_json(
            r#"{
  "experimental_realtime_webrtc_call_base_url": "https://realtime.example.com/call"
}"#,
        );
        let unexpected: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-006")
            .map(|d| d.message.as_str())
            .collect();
        assert!(
            unexpected.is_empty(),
            "0.140 realtime WebRTC key should not be flagged on JSON path, got: {unexpected:?}"
        );
    }

    #[test]
    fn test_cdx_cfg_012_invalid_cli_auth_store() {
        let diagnostics = validate_config("cli_auth_credentials_store = \"vault\"");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-012"));
    }

    #[test]
    fn test_cdx_ag_rules_on_agents_md() {
        let empty = validate_claude_md("AGENTS.md", "");
        assert!(empty.iter().any(|d| d.rule == "CDX-AG-001"));

        let secret = validate_claude_md("AGENTS.md", "api_key = sk-secret-value-123456");
        assert!(secret.iter().any(|d| d.rule == "CDX-AG-002"));

        let no_false_positive = validate_claude_md(
            "AGENTS.md",
            "Use task-runner and ask-for-help in local workflows.",
        );
        assert!(!no_false_positive.iter().any(|d| d.rule == "CDX-AG-002"));

        // Issue #659: prose containing "token" or "secret" should not trigger
        let prose_token = validate_claude_md(
            "AGENTS.md",
            "8. **Token efficiency** - Save tokens over decorations.",
        );
        assert!(
            !prose_token.iter().any(|d| d.rule == "CDX-AG-002"),
            "Prose word 'Token' should not trigger secret detection"
        );

        // But keyword + assignment should still trigger
        let real_secret = validate_claude_md("AGENTS.md", "token = sk-real-secret-123");
        assert!(
            real_secret.iter().any(|d| d.rule == "CDX-AG-002"),
            "Keyword in assignment context should still trigger"
        );

        let generic = validate_claude_md("AGENTS.md", "Be helpful and accurate.");
        assert!(generic.iter().any(|d| d.rule == "CDX-AG-003"));
    }

    #[test]
    fn test_cdx_app_001_invalid_default_tools_approval_mode() {
        let diagnostics = validate_config(
            "[apps.my_app]\nenabled = true\ndefault_tools_approval_mode = \"manual\"",
        );
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-APP-001"));
    }

    #[test]
    fn test_codex_0_137_0_app_approvals_reviewer_not_flagged() {
        let diagnostics = validate_config(
            r#"[apps.browser]
enabled = true
approvals_reviewer = "guardian_subagent"
"#,
        );
        let unexpected: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-006" || d.rule == "CDX-CFG-024")
            .map(|d| d.message.as_str())
            .collect();
        assert!(
            unexpected.is_empty(),
            "apps.*.approvals_reviewer should not trigger unknown-key or value diagnostics, got: {unexpected:?}"
        );
    }

    #[test]
    fn test_cdx_cfg_013_invalid_sandbox_workspace_write_mode() {
        let diagnostics = validate_config("[sandbox_workspace_write]\nmode = \"everything\"");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-013"));
    }

    #[test]
    fn test_cdx_cfg_014_invalid_model_type() {
        let diagnostics = validate_config("model = 123");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-014"));
    }

    #[test]
    fn test_cdx_cfg_014_valid_model_string_no_diagnostic() {
        let diagnostics = validate_config("model = \"o4-mini\"");
        assert!(diagnostics.iter().all(|d| d.rule != "CDX-CFG-014"));
    }

    #[test]
    fn test_cdx_cfg_015_invalid_model_provider_type() {
        let diagnostics = validate_config("model_provider = 42");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-015"));
    }

    #[test]
    fn test_cdx_cfg_016_invalid_model_reasoning_summary() {
        let diagnostics = validate_config("model_reasoning_summary = \"verbose\"");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-016"));
    }

    #[test]
    fn test_cdx_cfg_017_invalid_history_type() {
        let diagnostics = validate_config("history = \"not an object\"");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-017"));
    }

    #[test]
    fn test_cdx_cfg_018_invalid_tui_type() {
        let diagnostics = validate_config("tui = \"not an object\"");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-018"));
    }

    #[test]
    fn test_cdx_cfg_019_invalid_file_opener_type() {
        let diagnostics = validate_config("file_opener = 123");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-019"));
    }

    #[test]
    fn test_cdx_cfg_020_invalid_mcp_oauth_store() {
        let diagnostics = validate_config("mcp_oauth_credentials_store = \"vault\"");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-020"));
    }

    #[test]
    fn test_cdx_cfg_021_invalid_model_context_window() {
        let diagnostics = validate_config("model_context_window = -1");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-021"));
    }

    #[test]
    fn test_cdx_cfg_021_valid_model_context_window_no_diagnostic() {
        let diagnostics = validate_config("model_context_window = 128000");
        assert!(diagnostics.iter().all(|d| d.rule != "CDX-CFG-021"));
    }

    #[test]
    fn test_cdx_cfg_022_invalid_model_auto_compact_token_limit() {
        let diagnostics = validate_config("model_auto_compact_token_limit = -100");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-CFG-022"));
    }

    #[test]
    fn test_cdx_cfg_022_valid_limit_no_diagnostic() {
        let diagnostics = validate_config("model_auto_compact_token_limit = 4096");
        assert!(diagnostics.iter().all(|d| d.rule != "CDX-CFG-022"));
    }

    #[test]
    fn test_cdx_ag_004_agents_md_exceeds_size_limit() {
        let content = "# Project\n".to_string() + &"x".repeat(200_000);
        let diagnostics = validate_claude_md("AGENTS.md", &content);
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-AG-004"));
    }

    #[test]
    fn test_cdx_ag_005_missing_file_reference() {
        let diagnostics =
            validate_claude_md("AGENTS.md", "Check `nonexistent-file.ts` for details.");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-AG-005"));
    }

    #[test]
    fn test_cdx_ag_006_missing_project_context() {
        let diagnostics = validate_claude_md("AGENTS.md", "Be helpful and write good code.");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-AG-006"));
    }

    #[test]
    fn test_cdx_ag_006_has_context_no_diagnostic() {
        let diagnostics = validate_claude_md(
            "AGENTS.md",
            "# Project\nRun `npm test` to verify. Check src/ for source code.",
        );
        assert!(diagnostics.iter().all(|d| d.rule != "CDX-AG-006"));
    }

    #[test]
    fn test_cdx_ag_007_placeholder_no_diagnostics() {
        // CDX-AG-007 is a placeholder for cross-file analysis; it should not produce diagnostics
        let diagnostics = validate_claude_md(
            "AGENTS.md",
            "# Project\nRun `npm test` to verify. Check src/ for source code.",
        );
        assert!(diagnostics.iter().all(|d| d.rule != "CDX-AG-007"));
    }

    #[test]
    fn test_cdx_app_002_invalid_skills_type() {
        let diagnostics = validate_config("skills = \"not an object\"");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-APP-002"));
    }

    #[test]
    fn test_cdx_app_002_valid_skills_no_diagnostic() {
        let diagnostics = validate_config("[skills]");
        assert!(diagnostics.iter().all(|d| d.rule != "CDX-APP-002"));
    }

    #[test]
    fn test_cdx_app_003_invalid_profile_type() {
        let diagnostics = validate_config("profile = 123");
        assert!(diagnostics.iter().any(|d| d.rule == "CDX-APP-003"));
    }

    #[test]
    fn test_cdx_app_003_valid_profile_no_diagnostic() {
        let diagnostics = validate_config("profile = \"default\"");
        assert!(diagnostics.iter().all(|d| d.rule != "CDX-APP-003"));
    }

    // M10: Comprehensive negative test - valid config values don't trigger CDX-CFG rules
    #[test]
    fn test_cdx_cfg_negative_all_valid_values() {
        let content = r#"
model = "o4-mini"
approval_policy = "on-request"
sandbox_mode = "workspace-write"
model_reasoning_effort = "medium"
model_verbosity = "medium"
personality = "pragmatic"
model_reasoning_summary = "auto"
cli_auth_credentials_store = "auto"
mcp_oauth_credentials_store = "auto"
model_context_window = 128000
model_auto_compact_token_limit = 50000
profile = "default"
file_opener = "code"
approvals_reviewer = "user"
service_tier = "flex"

[sandbox_workspace_write]
mode = "allowlist"

[shell_environment_policy]
inherit = "core"

[history]
max_entries = 100

[tui]
theme = "dark"

[features]
web_search = true

[skills]
enabled = true

[mcp_servers.local]
command = "node"
args = ["server.js"]
url = "https://example.com"

[windows]
sandbox = "elevated"

[permissions.network]
enabled = true
mode = "allowlist"
"#;
        let diagnostics = validate_config(content);
        let cdx_cfg: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule.starts_with("CDX-CFG-"))
            .collect();
        assert!(
            cdx_cfg.is_empty(),
            "No CDX-CFG rules should fire for valid config, got: {:?}",
            cdx_cfg.iter().map(|d| &d.rule).collect::<Vec<_>>()
        );
    }

    // M11: CDX-AG-002 interpolation exclusion
    #[test]
    fn test_cdx_ag_002_interpolation_not_flagged() {
        let diagnostics = validate_claude_md("AGENTS.md", "api_key=${MY_SECRET}");
        assert!(diagnostics.iter().all(|d| d.rule != "CDX-AG-002"));
    }

    // M12: CDX-CFG-007 suppression with hide_full_access_warning
    #[test]
    fn test_cdx_cfg_007_suppressed_with_hide_warning() {
        let content = r#"
sandbox_mode = "danger-full-access"

[notice]
hide_full_access_warning = true
"#;
        let diagnostics = validate_config(content);
        assert!(diagnostics.iter().all(|d| d.rule != "CDX-CFG-007"));
    }

    // M13: CDX-CFG-010 env-var exclusion
    #[test]
    fn test_cdx_cfg_010_env_var_not_flagged() {
        let content = r#"api_key = "${MY_SECRET}""#;
        let diagnostics = validate_config(content);
        assert!(diagnostics.iter().all(|d| d.rule != "CDX-CFG-010"));
    }

    #[test]
    fn test_cdx_cfg_rules_parse_json_and_yaml_configs() {
        let json = r#"{"approval_policy":"always"}"#;
        let json_diags = validate_config_at_path(".codex/config.json", json);
        assert!(json_diags.iter().any(|d| d.rule == "CDX-CFG-001"));

        let yaml = "approval_policy: always\n";
        let yaml_diags = validate_config_at_path(".codex/config.yaml", yaml);
        assert!(yaml_diags.iter().any(|d| d.rule == "CDX-CFG-001"));
    }

    #[test]
    fn test_codex_0_128_0_new_json_yaml_keys_not_flagged() {
        // Regression guard for the JSON/YAML unknown-top-level-key path,
        // which now consults schemas/codex.rs::is_known_top_level_key (the
        // shared predicate over KNOWN_TOP_LEVEL_KEYS ∪ KNOWN_TABLE_KEYS).
        // Mirror of the TOML-side tests in schemas/codex.rs; the JSON/YAML
        // path treats top-level scalars and `[table]` keys uniformly.
        let json = r#"{
            "auto_review": {},
            "experimental_thread_store": {},
            "experimental_thread_config_endpoint": "https://example.com/threads",
            "hooks": {}
        }"#;
        let json_diags = validate_config_at_path(".codex/config.json", json);
        assert!(
            json_diags.iter().all(|d| d.rule != "CDX-004"),
            "0.128 JSON keys should not trigger CDX-004, got: {:?}",
            json_diags
                .iter()
                .filter(|d| d.rule == "CDX-004")
                .collect::<Vec<_>>()
        );

        let yaml = "auto_review: {}\nhooks: {}\nexperimental_thread_store: {}\n";
        let yaml_diags = validate_config_at_path(".codex/config.yaml", yaml);
        assert!(
            yaml_diags.iter().all(|d| d.rule != "CDX-004"),
            "0.128 YAML keys should not trigger CDX-004, got: {:?}",
            yaml_diags
                .iter()
                .filter(|d| d.rule == "CDX-004")
                .collect::<Vec<_>>()
        );
    }

    #[test]
    fn test_codex_unified_allowlist_fixes_json_toml_drift() {
        // Drift fix (issue #966): before unifying the top-level allow-list, the
        // JSON/YAML path flagged `debug` (a valid TOML `[debug]` table from the
        // v0.129 catch-up) because it lived only in the TOML schema list.
        // Both backends now share schemas/codex.rs::is_known_top_level_key, so
        // `debug` is accepted as JSON/YAML too.
        // (`include_apply_patch_tool` was dropped from the allow-list in #969 -
        // never in any audited schema as a top-level key or `[features]` sub-key.)
        let json = r#"{ "debug": {} }"#;
        let diags = validate_config_at_path(".codex/config.json", json);
        assert!(
            diags
                .iter()
                .all(|d| d.rule != "CDX-004" && d.rule != "CDX-CFG-006"),
            "unified keys should not be flagged on the JSON path, got: {:?}",
            diags
                .iter()
                .filter(|d| d.rule == "CDX-004" || d.rule == "CDX-CFG-006")
                .collect::<Vec<_>>()
        );

        // #969: the dropped `include_apply_patch_tool` must now be flagged as
        // unknown on the JSON backend too, mirroring the TOML-side check in
        // schemas/codex.rs::test_codex_stale_js_repl_keys_flagged - the two
        // backends stay in sync (#966), now agreeing on "unknown".
        let stale = validate_config_at_path(
            ".codex/config.json",
            r#"{ "include_apply_patch_tool": true }"#,
        );
        assert!(
            stale.iter().any(|d| d.rule == "CDX-CFG-006"),
            "dropped include_apply_patch_tool should be flagged (CDX-CFG-006) on the JSON path, got: {:?}",
            stale.iter().map(|d| d.rule.as_str()).collect::<Vec<_>>()
        );
    }

    #[test]
    fn test_cdx_000_reports_json_yaml_parse_errors() {
        let invalid_json = r#"{"approval_policy":"always""#;
        let json_diags = validate_config_at_path(".codex/config.json", invalid_json);
        assert!(json_diags.iter().any(|d| d.rule == "CDX-000"));

        let invalid_yaml = "approval_policy: [always\n";
        let yaml_diags = validate_config_at_path(".codex/config.yaml", invalid_yaml);
        assert!(yaml_diags.iter().any(|d| d.rule == "CDX-000"));
    }

    // ===== CDX-CFG-023: Invalid granular approval_policy sub-field name =====

    #[test]
    fn test_cdx_cfg_023_unknown_approval_policy_sub_field() {
        let content = "[approval_policy]\nunknown_field = \"never\"\nrules = \"on-request\"";
        let diagnostics = validate_config(content);
        let cdx_023: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-023")
            .collect();
        assert_eq!(cdx_023.len(), 1);
        assert_eq!(cdx_023[0].level, DiagnosticLevel::Warning);
        assert!(cdx_023[0].message.contains("unknown_field"));
    }

    #[test]
    fn test_cdx_cfg_023_valid_approval_policy_sub_fields_no_diagnostic() {
        let content = "[approval_policy]\nsandbox_approval = \"on-request\"\nrules = \"never\"\nmcp_elicitations = \"on-failure\"";
        let diagnostics = validate_config(content);
        let cdx_023: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-023")
            .collect();
        assert!(
            cdx_023.is_empty(),
            "Valid approval_policy sub-fields should not trigger CDX-CFG-023"
        );
    }

    #[test]
    fn test_cdx_cfg_023_string_approval_policy_no_diagnostic() {
        let diagnostics = validate_config("approval_policy = \"on-request\"");
        let cdx_023: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-023")
            .collect();
        assert!(cdx_023.is_empty());
    }

    #[test]
    fn test_cdx_cfg_023_object_approval_policy_no_type_error_from_001() {
        let content = "[approval_policy]\nsandbox_approval = \"on-request\"";
        let diagnostics = validate_config(content);
        let cdx_001: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-001")
            .collect();
        assert!(
            cdx_001.is_empty(),
            "CDX-CFG-001 should not fire a type error for object approval_policy"
        );
    }

    #[test]
    fn test_cdx_cfg_023_granular_all_valid_no_diagnostic() {
        let content = r#"
[approval_policy]
sandbox_approval = "on-request"
rules = "never"
mcp_elicitations = "on-failure"
request_permissions = "on-request"
skill_approval = "never"
"#;
        let diagnostics = validate_config(content);
        let cdx_cfg: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-023" || d.rule == "CDX-CFG-001")
            .collect();
        assert!(
            cdx_cfg.is_empty(),
            "Granular approval_policy with valid sub-fields should not trigger CDX-CFG-023 or CDX-CFG-001"
        );
    }

    // ===== CDX-CFG-024: Invalid approvals_reviewer enum value =====

    #[test]
    fn test_cdx_cfg_024_invalid_approvals_reviewer() {
        let diagnostics = validate_config("approvals_reviewer = \"auto\"");
        let cdx_024: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-024")
            .collect();
        assert_eq!(cdx_024.len(), 1);
        assert_eq!(cdx_024[0].level, DiagnosticLevel::Warning);
        assert!(cdx_024[0].message.contains("auto"));
    }

    #[test]
    fn test_cdx_cfg_024_valid_approvals_reviewer_user() {
        let diagnostics = validate_config("approvals_reviewer = \"user\"");
        let cdx_024: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-024")
            .collect();
        assert!(cdx_024.is_empty());
    }

    #[test]
    fn test_cdx_cfg_024_valid_approvals_reviewer_guardian() {
        let diagnostics = validate_config("approvals_reviewer = \"guardian_subagent\"");
        let cdx_024: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-024")
            .collect();
        assert!(cdx_024.is_empty());
    }

    #[test]
    fn test_cdx_cfg_024_valid_approvals_reviewer_auto_review() {
        let diagnostics = validate_config("approvals_reviewer = \"auto_review\"");
        let cdx_024: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-024")
            .collect();
        assert!(cdx_024.is_empty());
    }

    #[test]
    fn test_cdx_cfg_024_type_error() {
        let diagnostics = validate_config("approvals_reviewer = 42");
        let cdx_024: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-024")
            .collect();
        assert_eq!(cdx_024.len(), 1);
        assert!(cdx_024[0].message.contains("string"));
    }

    #[test]
    fn test_cdx_cfg_024_valid_app_approvals_reviewer_auto_review() {
        let diagnostics = validate_config(
            r#"[apps.browser]
approvals_reviewer = "auto_review"
"#,
        );
        let cdx_024: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-024")
            .collect();
        assert!(cdx_024.is_empty());
    }

    #[test]
    fn test_cdx_cfg_024_valid_app_default_approvals_reviewer_auto_review() {
        let diagnostics = validate_config(
            r#"[apps._default]
approvals_reviewer = "auto_review"
"#,
        );
        let unexpected: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-006" || d.rule == "CDX-CFG-024")
            .map(|d| d.message.as_str())
            .collect();
        assert!(
            unexpected.is_empty(),
            "apps._default.approvals_reviewer should not be flagged when valid, got: {unexpected:?}"
        );
    }

    #[test]
    fn test_cdx_cfg_024_invalid_app_approvals_reviewer() {
        let diagnostics = validate_config(
            r#"[apps.browser]
approvals_reviewer = "approve"
"#,
        );
        let cdx_024: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-024")
            .collect();
        assert_eq!(cdx_024.len(), 1);
        assert!(
            cdx_024[0]
                .message
                .contains("apps.browser.approvals_reviewer")
        );
        assert!(cdx_024[0].message.contains("approve"));
    }

    #[test]
    fn test_cdx_cfg_024_app_approvals_reviewer_type_error() {
        let diagnostics = validate_config(
            r#"[apps.browser]
approvals_reviewer = 42
"#,
        );
        let cdx_024: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-024")
            .collect();
        assert_eq!(cdx_024.len(), 1);
        assert!(cdx_024[0].message.contains("string"));
    }

    #[test]
    fn test_cdx_cfg_024_invalid_app_default_approvals_reviewer() {
        let diagnostics = validate_config(
            r#"[apps._default]
approvals_reviewer = "approve"
"#,
        );
        let cdx_024: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-024")
            .collect();
        assert_eq!(cdx_024.len(), 1);
        assert!(
            cdx_024[0]
                .message
                .contains("apps._default.approvals_reviewer")
        );
        assert!(cdx_024[0].message.contains("approve"));
    }

    #[test]
    fn test_cdx_cfg_024_app_default_approvals_reviewer_type_error() {
        let diagnostics = validate_config(
            r#"[apps._default]
approvals_reviewer = 42
"#,
        );
        let cdx_024: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-024")
            .collect();
        assert_eq!(cdx_024.len(), 1);
        assert!(cdx_024[0].message.contains("string"));
    }

    // ===== CDX-CFG-025: Invalid service_tier enum value =====

    #[test]
    fn test_cdx_cfg_025_invalid_service_tier() {
        let diagnostics = validate_config("service_tier = \"turbo\"");
        let cdx_025: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-025")
            .collect();
        assert_eq!(cdx_025.len(), 1);
        assert_eq!(cdx_025[0].level, DiagnosticLevel::Warning);
        assert!(cdx_025[0].message.contains("turbo"));
    }

    #[test]
    fn test_cdx_cfg_025_valid_service_tier_flex() {
        let diagnostics = validate_config("service_tier = \"flex\"");
        let cdx_025: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-025")
            .collect();
        assert!(cdx_025.is_empty());
    }

    #[test]
    fn test_cdx_cfg_025_valid_service_tier_fast() {
        let diagnostics = validate_config("service_tier = \"fast\"");
        let cdx_025: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-025")
            .collect();
        assert!(cdx_025.is_empty());
    }

    #[test]
    fn test_cdx_cfg_025_type_error() {
        let diagnostics = validate_config("service_tier = true");
        let cdx_025: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-025")
            .collect();
        assert_eq!(cdx_025.len(), 1);
        assert!(cdx_025[0].message.contains("string"));
    }

    // ===== CDX-CFG-026: Invalid permissions.network sub-field =====

    #[test]
    fn test_cdx_cfg_026_unknown_network_sub_field() {
        let content = "[permissions.network]\nunknown_option = true\nenabled = true";
        let diagnostics = validate_config(content);
        let cdx_026: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-026")
            .collect();
        assert_eq!(cdx_026.len(), 1);
        assert_eq!(cdx_026[0].level, DiagnosticLevel::Info);
        assert!(cdx_026[0].message.contains("unknown_option"));
    }

    #[test]
    fn test_cdx_cfg_026_valid_network_sub_fields_no_diagnostic() {
        let content = "[permissions.network]\nenabled = true\nmode = \"allowlist\"\nallowed_domains = [\"example.com\"]";
        let diagnostics = validate_config(content);
        let cdx_026: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-026")
            .collect();
        assert!(
            cdx_026.is_empty(),
            "Valid permissions.network sub-fields should not trigger CDX-CFG-026"
        );
    }

    #[test]
    fn test_cdx_cfg_026_no_permissions_network_no_diagnostic() {
        let diagnostics = validate_config("model = \"o4-mini\"");
        let cdx_026: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-026")
            .collect();
        assert!(cdx_026.is_empty());
    }

    // ===== CDX-CFG-027: Invalid windows.sandbox enum value =====

    #[test]
    fn test_cdx_cfg_027_invalid_windows_sandbox() {
        let content = "[windows]\nsandbox = \"permissive\"";
        let diagnostics = validate_config(content);
        let cdx_027: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-027")
            .collect();
        assert_eq!(cdx_027.len(), 1);
        assert_eq!(cdx_027[0].level, DiagnosticLevel::Info);
        assert!(cdx_027[0].message.contains("permissive"));
    }

    #[test]
    fn test_cdx_cfg_027_valid_windows_sandbox_elevated() {
        let content = "[windows]\nsandbox = \"elevated\"";
        let diagnostics = validate_config(content);
        let cdx_027: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-027")
            .collect();
        assert!(cdx_027.is_empty());
    }

    #[test]
    fn test_cdx_cfg_027_valid_windows_sandbox_unelevated() {
        let content = "[windows]\nsandbox = \"unelevated\"";
        let diagnostics = validate_config(content);
        let cdx_027: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-027")
            .collect();
        assert!(cdx_027.is_empty());
    }

    #[test]
    fn test_cdx_cfg_027_type_error() {
        let content = "[windows]\nsandbox = 123";
        let diagnostics = validate_config(content);
        let cdx_027: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-027")
            .collect();
        assert_eq!(cdx_027.len(), 1);
        assert!(cdx_027[0].message.contains("string"));
    }

    // ===== CDX-CFG-028: Unsupported inline MCP bearer_token field =====

    #[test]
    fn test_cdx_cfg_028_flags_inline_bearer_token() {
        let content = "[mcp_servers.myserver]\nurl = \"https://api.example.com\"\nbearer_token = \"sk-live-...\"";
        let diagnostics = validate_config(content);
        let cdx_028: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-028")
            .collect();
        assert_eq!(cdx_028.len(), 1);
        assert_eq!(cdx_028[0].level, DiagnosticLevel::Error);
        assert!(cdx_028[0].message.contains("myserver"));
        assert!(cdx_028[0].message.contains("bearer_token_env_var"));
    }

    #[test]
    fn test_cdx_cfg_028_bearer_token_env_var_is_fine() {
        let content = "[mcp_servers.myserver]\nurl = \"https://api.example.com\"\nbearer_token_env_var = \"MY_API_TOKEN\"";
        let diagnostics = validate_config(content);
        let cdx_028: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-028")
            .collect();
        assert!(
            cdx_028.is_empty(),
            "bearer_token_env_var is the documented replacement and must not flag"
        );
    }

    #[test]
    fn test_cdx_cfg_028_flags_each_server_independently() {
        let content = "\
[mcp_servers.s1]
url = \"https://a.example.com\"
bearer_token = \"t1\"

[mcp_servers.s2]
url = \"https://b.example.com\"
bearer_token = \"t2\"

[mcp_servers.s3]
url = \"https://c.example.com\"
bearer_token_env_var = \"S3_TOKEN\"
";
        let diagnostics = validate_config(content);
        let cdx_028: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-028")
            .collect();
        assert_eq!(cdx_028.len(), 2, "only s1 and s2 should flag, not s3");
        let names: Vec<String> = cdx_028.iter().map(|d| d.message.clone()).collect();
        assert!(names.iter().any(|m| m.contains("s1")));
        assert!(names.iter().any(|m| m.contains("s2")));
        assert!(!names.iter().any(|m| m.contains("'s3'")));
    }

    #[test]
    fn test_cdx_cfg_028_does_not_double_report_with_cdx_cfg_006() {
        // Guardrail: bearer_token is suppressed from the CDX-CFG-006 unknown-key
        // path (which DOES walk nested keys like mcp_servers.*.x) when
        // CDX-CFG-028 is active. Otherwise users would get two diagnostics for
        // the same line - generic "unknown key" warning + specific
        // "use bearer_token_env_var" error.
        let content =
            "[mcp_servers.myserver]\nurl = \"https://api.example.com\"\nbearer_token = \"secret\"";
        let diagnostics = validate_config(content);
        let cfg_006_for_bearer: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-006" && d.message.contains("bearer_token"))
            .collect();
        assert!(
            cfg_006_for_bearer.is_empty(),
            "bearer_token must not trigger CDX-CFG-006 when CDX-CFG-028 is handling it, got {:?}",
            cfg_006_for_bearer
        );
    }

    #[test]
    fn test_cdx_cfg_028_disabled_falls_through_to_cdx_cfg_006() {
        // Contract: if the user disables CDX-CFG-028, bearer_token must NOT be
        // silently accepted - it still falls through to the generic unknown-key
        // path (CDX-CFG-006) so the diagnosis surfaces one way or another.
        use crate::config::LintConfig;
        let mut config = LintConfig::default();
        config.rules_mut().disabled_rules = vec!["CDX-CFG-028".to_string()];
        let content =
            "[mcp_servers.myserver]\nurl = \"https://api.example.com\"\nbearer_token = \"secret\"";
        let validator = CodexConfigValidator;
        let diagnostics = validator.validate(std::path::Path::new("config.toml"), content, &config);
        let cfg_006_for_bearer: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-006" && d.message.contains("bearer_token"))
            .collect();
        assert_eq!(
            cfg_006_for_bearer.len(),
            1,
            "with CDX-CFG-028 disabled, bearer_token must fall through to the generic unknown-key path"
        );
    }

    #[test]
    fn test_cdx_cfg_028_line_points_at_bearer_token_not_line_1() {
        // Contract: the diagnostic line number points at the actual
        // `bearer_token =` line, not line 1 (which was the bug in the first
        // draft - line_for with a dotted path misses the flat key_lines map).
        let content = "\
# leading comment line 1
# another comment line 2

[mcp_servers.myserver]
url = \"https://api.example.com\"
bearer_token = \"secret\"
";
        let diagnostics = validate_config(content);
        let cdx_028: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-028")
            .collect();
        assert_eq!(cdx_028.len(), 1);
        assert_eq!(
            cdx_028[0].line, 6,
            "diagnostic must point at the bearer_token line, not line 1"
        );
    }

    #[test]
    fn test_cdx_cfg_028_matches_quoted_bearer_token_key() {
        // TOML allows quoted keys, and the config parser will normalize both
        // `bearer_token = ...` and `"bearer_token" = ...` to the same map key.
        // The line scanner must handle both forms so users don't get line 1
        // fallback on the quoted form.
        let content = "\
[mcp_servers.myserver]
url = \"https://api.example.com\"
\"bearer_token\" = \"secret\"
";
        let diagnostics = validate_config(content);
        let cdx_028: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-028")
            .collect();
        assert_eq!(cdx_028.len(), 1);
        assert_eq!(
            cdx_028[0].line, 3,
            "quoted bearer_token key must report its actual line, not line 1"
        );
    }

    #[test]
    fn test_cdx_cfg_028_distinct_lines_for_distinct_servers() {
        // Contract: with two servers both setting bearer_token, each diagnostic
        // must get its own line - not both reporting the same first-match line.
        let content = "\
[mcp_servers.s1]
url = \"https://a.example.com\"
bearer_token = \"t1\"

[mcp_servers.s2]
url = \"https://b.example.com\"
bearer_token = \"t2\"
";
        let diagnostics = validate_config(content);
        let cdx_028: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-028")
            .collect();
        assert_eq!(cdx_028.len(), 2);
        let s1 = cdx_028
            .iter()
            .find(|d| d.message.contains("s1"))
            .expect("s1 diag");
        let s2 = cdx_028
            .iter()
            .find(|d| d.message.contains("s2"))
            .expect("s2 diag");
        assert_eq!(s1.line, 3, "s1 bearer_token should be line 3");
        assert_eq!(s2.line, 7, "s2 bearer_token should be line 7");
    }

    // ===== CDX-CFG-029: agents.max_threads incompatible with multi_agent_v2 =====

    #[test]
    fn test_cdx_cfg_029_flat_form_flags_conflict() {
        // [features] multi_agent_v2 = true + [agents] max_threads = N
        let content = "\
[agents]
max_threads = 4

[features]
multi_agent_v2 = true
";
        let diagnostics = validate_config(content);
        let cdx_029: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-029")
            .collect();
        assert_eq!(cdx_029.len(), 1);
        assert_eq!(cdx_029[0].level, DiagnosticLevel::Error);
        assert!(
            cdx_029[0]
                .message
                .contains("'agents.max_threads' cannot be set when 'multi_agent_v2' is enabled")
        );
    }

    #[test]
    fn test_cdx_cfg_029_table_form_flags_conflict() {
        // [features.multi_agent_v2] enabled = true + [agents] max_threads = N
        let content = "\
[agents]
max_threads = 2

[features.multi_agent_v2]
enabled = true
";
        let diagnostics = validate_config(content);
        let cdx_029: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-029")
            .collect();
        assert_eq!(
            cdx_029.len(),
            1,
            "table form [features.multi_agent_v2] enabled=true must also trigger"
        );
    }

    #[test]
    fn test_cdx_cfg_029_table_form_without_enabled_does_not_flag() {
        // [features.multi_agent_v2] exists but `enabled` is not set - upstream
        // defaults that to false, so CDX-CFG-029 must not fire.
        let content = "\
[agents]
max_threads = 2

[features.multi_agent_v2]
usage_hint_enabled = true
";
        let diagnostics = validate_config(content);
        let cdx_029: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-029")
            .collect();
        assert!(
            cdx_029.is_empty(),
            "[features.multi_agent_v2] without explicit enabled=true must not flag, got {:?}",
            cdx_029
        );
    }

    #[test]
    fn test_cdx_cfg_029_table_form_enabled_false_does_not_flag() {
        let content = "\
[agents]
max_threads = 2

[features.multi_agent_v2]
enabled = false
";
        let diagnostics = validate_config(content);
        let cdx_029: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-029")
            .collect();
        assert!(cdx_029.is_empty());
    }

    #[test]
    fn test_cdx_cfg_029_max_threads_alone_is_fine() {
        let content = "[agents]\nmax_threads = 4\n";
        let diagnostics = validate_config(content);
        let cdx_029: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-029")
            .collect();
        assert!(cdx_029.is_empty());
    }

    #[test]
    fn test_cdx_cfg_029_multi_agent_v2_alone_is_fine() {
        // Only multi_agent_v2, no agents.max_threads - perfectly valid
        let content = "[features]\nmulti_agent_v2 = true\n";
        let diagnostics = validate_config(content);
        let cdx_029: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-029")
            .collect();
        assert!(cdx_029.is_empty());
    }

    #[test]
    fn test_cdx_cfg_029_can_be_disabled() {
        use crate::config::LintConfig;
        let mut config = LintConfig::default();
        config.rules_mut().disabled_rules = vec!["CDX-CFG-029".to_string()];
        let content = "\
[agents]
max_threads = 4

[features]
multi_agent_v2 = true
";
        let validator = CodexConfigValidator;
        let diagnostics = validator.validate(std::path::Path::new("config.toml"), content, &config);
        let cdx_029: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-029")
            .collect();
        assert!(cdx_029.is_empty());
    }

    #[test]
    fn test_cdx_cfg_028_can_be_disabled() {
        use crate::config::LintConfig;
        let mut config = LintConfig::default();
        config.rules_mut().disabled_rules = vec!["CDX-CFG-028".to_string()];
        let content = "[mcp_servers.myserver]\nurl = \"https://api.example.com\"\nbearer_token = \"sk-live-...\"";
        let validator = CodexConfigValidator;
        let diagnostics = validator.validate(std::path::Path::new("config.toml"), content, &config);
        let cdx_028: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CDX-CFG-028")
            .collect();
        assert!(cdx_028.is_empty());
    }
}
