From 43f90653c5c1f0f762cdfbb2becf828071cfec72 Mon Sep 17 00:00:00 2001 From: Yvette Carlisle Date: Fri, 5 Jun 2026 15:40:32 +0800 Subject: [PATCH] {"schema":"decodex/commit/1","summary":"Remove Codex CLI version validation","authority":"manual"} --- README.md | 3 - apps/decodex-app/README.md | 15 +- .../DecodexApp/DecodexServerBridge.swift | 1 - .../DecodexServerBridgeTests.swift | 3 +- apps/decodex/src/agent/app_server.rs | 321 +----------------- apps/decodex/src/agent/app_server/tests.rs | 283 ++------------- apps/decodex/src/cli.rs | 53 +-- apps/decodex/src/orchestrator/daemon.rs | 28 +- apps/decodex/src/orchestrator/entrypoints.rs | 10 +- apps/decodex/src/orchestrator/execution.rs | 31 +- apps/decodex/src/orchestrator/run_cycle.rs | 70 +--- .../tests/intake/candidate_selection.rs | 5 - .../tests/intake/prepare_issue_run.rs | 1 - .../tests/intake/run_and_prompting.rs | 12 +- .../tests/intake/workflow_reload.rs | 1 - .../tests/recovery/closeout/dispatch.rs | 6 +- .../tests/recovery/closeout/identity.rs | 3 - .../tests/recovery/runtime_reentry.rs | 46 +-- .../orchestrator/tests/retry/scheduling.rs | 5 - .../src/orchestrator/tests/runtime/failure.rs | 7 +- apps/decodex/src/orchestrator/types.rs | 5 - docs/index.md | 2 +- docs/reference/operator-control-plane.md | 2 +- docs/spec/app-server.md | 107 ++---- docs/spec/index.md | 3 +- docs/spec/runtime.md | 11 +- plugins/decodex/skills/manual-cli/SKILL.md | 3 - 27 files changed, 134 insertions(+), 903 deletions(-) diff --git a/README.md b/README.md index a0b6548..402e2a5 100644 --- a/README.md +++ b/README.md @@ -124,9 +124,6 @@ cargo run -p decodex --bin decodex -- serve --listen-address 127.0.0.1:8912 Project-scoped commands accept `--config ` after the subcommand when the operator wants to override registry-based project resolution for that command. -Use `--allow-unverified-codex` on `run`, `serve`, or `probe` only when deliberately -dogfooding a Codex build outside the locally verified app-server range; the default -guard remains fail-closed. `decodex status` prints the local runtime snapshot without refreshing live tracker, pull-request, or Codex account usage observers. Use `decodex status --live` when the operator needs fresh Linear/GitHub readback before acting; use the Accounts diff --git a/apps/decodex-app/README.md b/apps/decodex-app/README.md index c7cf76e..9edd84e 100644 --- a/apps/decodex-app/README.md +++ b/apps/decodex-app/README.md @@ -14,11 +14,9 @@ bundled Rust app helper so account UI stays on the same CLI-owned files even whe long-running local `decodex serve` is older than the app bundle. On launch the app also connects to an existing `decodex serve` on the default local endpoint when one is available; otherwise it starts the bundled Decodex binary as a normal scheduler with -`decodex serve --allow-unverified-codex --listen-address 127.0.0.1:8192`. The override -only downgrades unverified Codex app-server identity to a warning; other preflight -blockers remain fail-closed. The CLI owns the default scheduler interval, currently -15 seconds. App-started servers load the enabled project registry and own the same -operator listener as a manually started `decodex serve`. The helper owns account +`decodex serve --listen-address 127.0.0.1:8192`. The CLI owns the default scheduler +interval, currently 15 seconds. App-started servers load the enabled project registry +and own the same operator listener as a manually started `decodex serve`. The helper owns account operations and interactive login flows that need streamed command output: - list accounts without printing token material @@ -77,10 +75,9 @@ swift run --package-path apps/decodex-app DecodexApp Use hidden `decodex serve --dev` only when manually testing local account APIs, the app snapshot API, or dashboard routes while deliberately avoiding scheduler activity. The -normal app fallback is `decodex serve --allow-unverified-codex --listen-address -127.0.0.1:8192` so development Codex app-server builds can still run through the local -operator surface. Do not use `--dev` to validate project registration, Linear polling, -queue intake, or retained-lane execution; use ordinary `decodex serve` for those paths. +normal app fallback is `decodex serve --listen-address 127.0.0.1:8192`. Do not use +`--dev` to validate project registration, Linear polling, queue intake, or retained-lane +execution; use ordinary `decodex serve` for those paths. The staging script follows the local Rsnap-style signing path: it writes `target/decodex-app/Decodex.app`, signs the bundle with an Apple Development diff --git a/apps/decodex-app/Sources/DecodexApp/DecodexServerBridge.swift b/apps/decodex-app/Sources/DecodexApp/DecodexServerBridge.swift index f890ab6..ee29458 100644 --- a/apps/decodex-app/Sources/DecodexApp/DecodexServerBridge.swift +++ b/apps/decodex-app/Sources/DecodexApp/DecodexServerBridge.swift @@ -237,7 +237,6 @@ actor DecodexServerBridge { static func bundledServerArguments(listenAddress: String) -> [String] { [ "serve", - "--allow-unverified-codex", "--listen-address", listenAddress, ] } diff --git a/apps/decodex-app/Tests/DecodexAppTests/DecodexServerBridgeTests.swift b/apps/decodex-app/Tests/DecodexAppTests/DecodexServerBridgeTests.swift index c2e3db5..010a910 100644 --- a/apps/decodex-app/Tests/DecodexAppTests/DecodexServerBridgeTests.swift +++ b/apps/decodex-app/Tests/DecodexAppTests/DecodexServerBridgeTests.swift @@ -2,12 +2,11 @@ import XCTest final class DecodexServerBridgeTests: XCTestCase { - func testBundledServerArgumentsAllowUnverifiedCodex() { + func testBundledServerArgumentsStartServeOnListenAddress() { XCTAssertEqual( DecodexServerBridge.bundledServerArguments(listenAddress: "127.0.0.1:8192"), [ "serve", - "--allow-unverified-codex", "--listen-address", "127.0.0.1:8192", ] diff --git a/apps/decodex/src/agent/app_server.rs b/apps/decodex/src/agent/app_server.rs index 0acd7a2..878f0d7 100644 --- a/apps/decodex/src/agent/app_server.rs +++ b/apps/decodex/src/agent/app_server.rs @@ -92,17 +92,10 @@ const PREFLIGHT_CHECK_MODEL_PROVIDER: &str = "model_provider"; const PREFLIGHT_CHECK_SKILLS: &str = "skills"; const PREFLIGHT_CHECK_PLUGINS: &str = "plugins"; const PREFLIGHT_CHECK_MCP: &str = "mcp"; -const PREFLIGHT_CHECK_COMPATIBILITY: &str = "compatibility"; const PREFLIGHT_PLUGIN_MARKETPLACE_KIND: &str = "local"; -const APP_SERVER_COMPATIBILITY_SUPPORT_CLAIM: &str = "local_verified_codex_cli_exact_versions"; -const APP_SERVER_COMPATIBILITY_EVIDENCE: &str = - "initialize.userAgent plus successful app-server capability preflight"; -const APP_SERVER_COMPATIBILITY_CAPABILITY_SOURCE: &str = "bounded_app_server_preflight"; const APP_SERVER_SCHEMA_GENERATE_COMMAND: &str = "codex app-server generate-json-schema --experimental"; const APP_SERVER_SCHEMA_PROBE_OUT_DIR: &str = "target/decodex-app-server-schema-check"; -const APP_SERVER_SCHEMA_NOT_CHECKED_REASON: &str = - "normal dispatch uses exact-version allowlist; run decodex probe before expanding support"; const APP_SERVER_SCHEMA_REQUIRED_MARKERS: &[&str] = &[ "initialize", "config/read", @@ -127,19 +120,6 @@ const APP_SERVER_SCHEMA_REQUIRED_MARKERS: &[&str] = &[ ]; const APP_SERVER_SCHEMA_PROSE_KEYS: &[&str] = &["$comment", "comment", "description", "examples", "markdownDescription", "title"]; -const CODEX_CLI_VERSION_STABLE_0_136_0: &str = "0.136.0"; -const CODEX_CLI_VERSION_BETA_0_136_0_ALPHA_2: &str = "0.136.0-alpha.2"; -const CODEX_CLI_VERSION_DESKTOP_0_137_0_ALPHA_4: &str = "0.137.0-alpha.4"; -const SUPPORTED_CODEX_CLI_VERSION_MATCH_ORDER: &[&str] = &[ - CODEX_CLI_VERSION_DESKTOP_0_137_0_ALPHA_4, - CODEX_CLI_VERSION_BETA_0_136_0_ALPHA_2, - CODEX_CLI_VERSION_STABLE_0_136_0, -]; -const SUPPORTED_CODEX_CLI_VERSION_DISPLAY_ORDER: &[&str] = &[ - CODEX_CLI_VERSION_STABLE_0_136_0, - CODEX_CLI_VERSION_BETA_0_136_0_ALPHA_2, - CODEX_CLI_VERSION_DESKTOP_0_137_0_ALPHA_4, -]; const JSONRPC_METHOD_NOT_FOUND: i64 = -32_601; const CHILD_BUCKET_MODEL: &str = "Model"; const WAITING_REASON_MODEL_EXECUTION: &str = "model_execution"; @@ -189,6 +169,10 @@ impl AppServerCapabilityPreflightReport { &self.checks } + pub(crate) fn check_count(&self) -> usize { + self.checks.len() + } + fn push_ok( &mut self, name: &'static str, @@ -217,20 +201,6 @@ impl AppServerCapabilityPreflightReport { }); } - fn push_warning( - &mut self, - name: &'static str, - summary: impl Into, - details: BTreeMap, - ) { - self.checks.push(AppServerCapabilityPreflightCheck { - name, - status: AppServerCapabilityPreflightStatus::Warning, - summary: summary.into(), - details, - }); - } - fn has_blockers(&self) -> bool { self.checks.iter().any(|check| check.status == AppServerCapabilityPreflightStatus::Blocked) } @@ -245,51 +215,6 @@ impl AppServerCapabilityPreflightReport { if blockers.is_empty() { String::from("no blockers recorded") } else { blockers.join("; ") } } - - pub(crate) fn compatibility_status(&self) -> &'static str { - match self.compatibility_check().map(|check| check.status) { - Some(AppServerCapabilityPreflightStatus::Ok) => "supported", - Some(AppServerCapabilityPreflightStatus::Warning) => "unverified_allowed", - Some(AppServerCapabilityPreflightStatus::Blocked) => "unsupported", - None => "not_checked", - } - } - - pub(crate) fn compatibility_codex_cli_version(&self) -> Option<&str> { - self.compatibility_detail("codex_cli_version") - } - - pub(crate) fn compatibility_supported_versions(&self) -> Option<&str> { - self.compatibility_detail("supported_versions") - } - - pub(crate) fn compatibility_support_decision(&self) -> Option<&str> { - self.compatibility_detail("support_decision") - } - - pub(crate) fn compatibility_capability_evidence(&self) -> Option<&str> { - self.compatibility_detail("capability_evidence") - } - - pub(crate) fn compatibility_schema_evidence(&self) -> Option<&str> { - self.compatibility_detail("schema_evidence") - } - - pub(crate) fn compatibility_schema_cache(&self) -> Option<&str> { - self.compatibility_detail("schema_cache") - } - - pub(crate) fn compatibility_schema_marker_count(&self) -> Option<&str> { - self.compatibility_detail("schema_marker_count") - } - - fn compatibility_check(&self) -> Option<&AppServerCapabilityPreflightCheck> { - self.checks.iter().find(|check| check.name == PREFLIGHT_CHECK_COMPATIBILITY) - } - - fn compatibility_detail(&self, detail: &str) -> Option<&str> { - self.compatibility_check().and_then(|check| check.details.get(detail)).map(String::as_str) - } } #[derive(Clone, Debug, Eq, PartialEq)] @@ -513,7 +438,6 @@ pub(crate) struct AppServerRunRequest<'a> { pub(crate) max_turns: u32, pub(crate) timeout: Duration, pub(crate) process_env: AppServerProcessEnv, - pub(crate) allow_unverified_codex: bool, pub(crate) continuation_user_input: Option, pub(crate) activity_marker_path: Option, pub(crate) resume_thread_id: Option, @@ -522,7 +446,6 @@ pub(crate) struct AppServerRunRequest<'a> { pub(crate) dynamic_tool_handler: Option<&'a dyn DynamicToolHandler>, pub(crate) continuation_guard: Option<&'a dyn TurnContinuationGuard>, pub(crate) codex_account_provider: Option<&'a dyn CodexAccountProvider>, - pub(crate) compatibility_schema_evidence: Option, } pub(crate) struct AppServerThreadArchiveRequest<'a> { @@ -1080,18 +1003,8 @@ enum AppServerDynamicToolFailureKind { #[serde(rename_all = "snake_case")] enum AppServerCapabilityPreflightStatus { Ok, - Warning, Blocked, } -impl AppServerCapabilityPreflightStatus { - fn as_str(self) -> &'static str { - match self { - Self::Ok => "ok", - Self::Warning => "warning", - Self::Blocked => "blocked", - } - } -} #[derive(Clone, Debug, Eq, PartialEq)] enum AppServerCapabilityPreflightFailureKind { @@ -1199,13 +1112,12 @@ pub(crate) fn archive_app_server_thread_after_success( result } -pub(crate) fn probe_app_server( - listen: &str, - allow_unverified_codex: bool, -) -> crate::prelude::Result { +pub(crate) fn probe_app_server(listen: &str) -> crate::prelude::Result { let state_store = StateStore::open_in_memory()?; let probe_tool_handler = ProbeDynamicToolHandler; - let schema_evidence = probe_app_server_schema(&AppServerProcessEnv::default())?; + + probe_app_server_schema(&AppServerProcessEnv::default())?; + let result = execute_app_server_run( &AppServerRunRequest { project_id: String::from("probe"), @@ -1219,7 +1131,6 @@ pub(crate) fn probe_app_server( max_turns: 1, timeout: PROBE_TIMEOUT, process_env: AppServerProcessEnv::default(), - allow_unverified_codex, continuation_user_input: None, activity_marker_path: None, resume_thread_id: None, @@ -1228,7 +1139,6 @@ pub(crate) fn probe_app_server( dynamic_tool_handler: Some(&probe_tool_handler), continuation_guard: None, codex_account_provider: None, - compatibility_schema_evidence: Some(schema_evidence), }, &state_store, )?; @@ -1264,25 +1174,6 @@ fn preflight_check_blocker_summary(check: &AppServerCapabilityPreflightCheck) -> summary.push_str("; first_error="); summary.push_str(error); } - if check.name == PREFLIGHT_CHECK_COMPATIBILITY { - for detail_name in [ - "support_decision", - "parsed_version", - "codex_cli_version", - "user_agent", - "supported_versions", - "capability_evidence", - "schema_evidence", - "schema_cache", - ] { - if let Some(detail) = check.details.get(detail_name) { - summary.push(' '); - summary.push_str(detail_name); - summary.push('='); - summary.push_str(detail); - } - } - } summary } @@ -2380,14 +2271,8 @@ fn execute_app_server_run_inner( write_capability_preflight_marker_best_effort(request); - let capability_preflight = run_app_server_capability_preflight( - &mut client, - &mut recorder, - &request.cwd, - &initialize_response.user_agent, - request.allow_unverified_codex, - request.compatibility_schema_evidence.as_ref(), - )?; + let capability_preflight = + run_app_server_capability_preflight(&mut client, &mut recorder, &request.cwd)?; write_activity_marker_best_effort_for_request(request); @@ -2477,9 +2362,6 @@ fn run_app_server_capability_preflight( client: &mut AppServerClient, recorder: &mut RunRecorder<'_>, cwd: &str, - user_agent: &str, - allow_unverified_codex: bool, - compatibility_schema_evidence: Option<&AppServerSchemaProbeEvidence>, ) -> crate::prelude::Result { let mut report = AppServerCapabilityPreflightReport::new(); let config = preflight_request(recorder, &report, "config/read", || { @@ -2537,15 +2419,6 @@ fn run_app_server_capability_preflight( }, } - if !report.has_blockers() { - record_app_server_compatibility_guard( - &mut report, - user_agent, - allow_unverified_codex, - compatibility_schema_evidence, - ); - } - record_app_server_preflight_report(recorder, &report)?; if report.has_blockers() { @@ -3098,180 +2971,6 @@ fn command_output_excerpt(output: &[u8]) -> String { if excerpt.is_empty() { String::from("") } else { excerpt } } -fn record_app_server_compatibility_guard( - report: &mut AppServerCapabilityPreflightReport, - user_agent: &str, - allow_unverified_codex: bool, - schema_evidence: Option<&AppServerSchemaProbeEvidence>, -) { - let codex_cli_version = codex_cli_version_from_user_agent(user_agent); - let matched_supported_version = supported_codex_cli_version_from_user_agent(user_agent); - let mut details = BTreeMap::new(); - - details.insert(String::from("user_agent"), user_agent.to_owned()); - details.insert( - String::from("parsed_version"), - codex_cli_version - .as_deref() - .map(|version| format!("codex-cli {version}")) - .unwrap_or_else(|| String::from("unparsed")), - ); - details.insert(String::from("supported_versions"), supported_codex_cli_versions_display()); - details.insert(String::from("allow_unverified_codex"), allow_unverified_codex.to_string()); - details - .insert(String::from("support_claim"), APP_SERVER_COMPATIBILITY_SUPPORT_CLAIM.to_owned()); - details.insert( - String::from("support_decision"), - compatibility_support_decision( - codex_cli_version.as_deref(), - matched_supported_version, - allow_unverified_codex, - ) - .to_owned(), - ); - details.insert(String::from("evidence"), APP_SERVER_COMPATIBILITY_EVIDENCE.to_owned()); - details.insert(String::from("capability_evidence"), compatibility_capability_evidence(report)); - details.insert( - String::from("capability_evidence_source"), - APP_SERVER_COMPATIBILITY_CAPABILITY_SOURCE.to_owned(), - ); - - record_schema_evidence_details(&mut details, schema_evidence); - - if let Some(version) = codex_cli_version.as_deref() { - details.insert(String::from("codex_cli_version"), format!("codex-cli {version}")); - } - if let Some(version) = matched_supported_version { - details.insert(String::from("matched_supported_version"), format!("codex-cli {version}")); - report.push_ok( - PREFLIGHT_CHECK_COMPATIBILITY, - "app-server userAgent is within the locally verified Codex CLI capability range.", - details, - ); - } else if allow_unverified_codex { - details.insert(String::from("override"), String::from("allow_unverified_codex")); - report.push_warning( - PREFLIGHT_CHECK_COMPATIBILITY, - "app-server userAgent is outside the locally verified Codex CLI capability range; continuing because unverified Codex versions are explicitly allowed.", - details, - ); - } else { - report.push_blocked( - PREFLIGHT_CHECK_COMPATIBILITY, - "app-server userAgent is outside the locally verified Codex CLI capability range.", - details, - ); - } -} - -fn compatibility_support_decision( - parsed_version: Option<&str>, - matched_supported_version: Option<&str>, - allow_unverified_codex: bool, -) -> &'static str { - match (parsed_version, matched_supported_version, allow_unverified_codex) { - (_, Some(_), _) => "supported_exact_version", - (Some(_), None, true) => "unverified_allowed_by_override", - (None, None, true) => "unparsed_user_agent_allowed_by_override", - (Some(_), None, false) => "unsupported_unverified_version", - (None, None, false) => "unsupported_unparsed_user_agent", - } -} - -fn compatibility_capability_evidence(report: &AppServerCapabilityPreflightReport) -> String { - let checks = report - .checks - .iter() - .filter(|check| check.name != PREFLIGHT_CHECK_COMPATIBILITY) - .map(|check| format!("{}={}", check.name, check.status.as_str())) - .collect::>(); - - if checks.is_empty() { String::from("none") } else { checks.join(", ") } -} - -fn record_schema_evidence_details( - details: &mut BTreeMap, - schema_evidence: Option<&AppServerSchemaProbeEvidence>, -) { - if let Some(schema_evidence) = schema_evidence { - details.insert(String::from("schema_evidence"), String::from("checked")); - details - .insert(String::from("schema_command"), APP_SERVER_SCHEMA_GENERATE_COMMAND.to_owned()); - details.insert(String::from("schema_cache"), schema_evidence.cache_path.clone()); - details - .insert(String::from("schema_marker_count"), schema_evidence.marker_count.to_string()); - details.insert(String::from("schema_markers"), schema_evidence.required_markers.join(", ")); - } else { - details.insert(String::from("schema_evidence"), String::from("not_checked")); - details.insert( - String::from("schema_evidence_reason"), - APP_SERVER_SCHEMA_NOT_CHECKED_REASON.to_owned(), - ); - details.insert( - String::from("schema_required_markers"), - APP_SERVER_SCHEMA_REQUIRED_MARKERS.join(", "), - ); - } -} - -fn supported_codex_cli_version_from_user_agent(user_agent: &str) -> Option<&'static str> { - let codex_cli_version = codex_cli_version_from_user_agent(user_agent)?; - - SUPPORTED_CODEX_CLI_VERSION_MATCH_ORDER - .iter() - .copied() - .find(|version| codex_cli_version == *version) -} - -fn codex_cli_version_from_user_agent(user_agent: &str) -> Option { - let lower_user_agent = user_agent.to_ascii_lowercase(); - - if let Some(marker_start) = lower_user_agent.find("codex-cli") { - let marker_end = marker_start + "codex-cli".len(); - - return user_agent_version_token(&user_agent[marker_end..]); - } - if let Some(marker_start) = lower_user_agent.find("codex desktop/") { - let marker_end = marker_start + "codex desktop/".len(); - - return user_agent_version_token(&user_agent[marker_end..]); - } - - let first_token = user_agent.split_whitespace().next()?; - let (product, version) = first_token.rsplit_once('/')?; - - if !product.to_ascii_lowercase().contains("codex") { - return None; - } - - user_agent_version_token(version) -} - -fn user_agent_version_token(value: &str) -> Option { - let version = value - .trim_start_matches(|character: char| { - character.is_whitespace() || character == '/' || character == ':' - }) - .chars() - .take_while(|character| { - character.is_ascii_alphanumeric() - || *character == '.' - || *character == '-' - || *character == '_' - }) - .collect::(); - - version.chars().next().is_some_and(|character| character.is_ascii_digit()).then_some(version) -} - -fn supported_codex_cli_versions_display() -> String { - SUPPORTED_CODEX_CLI_VERSION_DISPLAY_ORDER - .iter() - .map(|version| format!("codex-cli {version}")) - .collect::>() - .join(", ") -} - fn record_app_server_preflight_report( recorder: &mut RunRecorder<'_>, report: &AppServerCapabilityPreflightReport, diff --git a/apps/decodex/src/agent/app_server/tests.rs b/apps/decodex/src/agent/app_server/tests.rs index 978aabb..fe574cb 100644 --- a/apps/decodex/src/agent/app_server/tests.rs +++ b/apps/decodex/src/agent/app_server/tests.rs @@ -16,11 +16,10 @@ use crate::{ app_server::{ APP_SERVER_SCHEMA_REQUIRED_MARKERS, AppServerCapabilityPreflightFailure, AppServerCapabilityPreflightReport, AppServerDynamicToolFailure, AppServerRunResult, - AppServerSchemaProbeEvidence, AppServerThreadArchiveRequest, AppServerTurnFailure, - CommandExecHealthCheck, CommandExecResponse, EffectiveThreadConfig, InitializeResponse, - ModelProviderCapabilitiesReadResponse, PREFLIGHT_CHECK_CONFIG, PREFLIGHT_CHECK_MODEL, - PluginListResponse, ProbeDynamicToolHandler, REQUEST_TIMEOUT, RequestWaitPhase, - RunRecorder, RuntimeConfigSummary, SUPPORTED_CODEX_CLI_VERSION_DISPLAY_ORDER, + AppServerThreadArchiveRequest, AppServerTurnFailure, CommandExecHealthCheck, + CommandExecResponse, EffectiveThreadConfig, InitializeResponse, + ModelProviderCapabilitiesReadResponse, PluginListResponse, ProbeDynamicToolHandler, + REQUEST_TIMEOUT, RequestWaitPhase, RunRecorder, RuntimeConfigSummary, SkillsListResponse, TurnContinuationGuard, UserInput, }, json_rpc::{ @@ -285,220 +284,6 @@ fn probe_result_shape_is_stable() { assert_eq!(result.turn_count, 1); } -#[test] -fn app_server_compatibility_guard_accepts_current_verified_codex_surfaces() { - let schema_evidence = AppServerSchemaProbeEvidence::checked( - String::from("target/decodex-app-server-schema-check"), - APP_SERVER_SCHEMA_REQUIRED_MARKERS, - ); - - for (user_agent, expected_codex_cli_version) in [ - ("codex-cli 0.136.0", "codex-cli 0.136.0"), - ("codex-cli 0.136.0-alpha.2", "codex-cli 0.136.0-alpha.2"), - ( - "Codex Desktop/0.136.0 (Mac OS 26.5.1; arm64) ghostty/1.3.2-main-_6246c288a (decodex; 0.1.0)", - "codex-cli 0.136.0", - ), - ( - "Codex Desktop/0.137.0-alpha.4 (Mac OS 26.5.1; arm64) ghostty/1.3.2-main-_6246c288a (decodex; 0.1.0)", - "codex-cli 0.137.0-alpha.4", - ), - ("decodex/0.136.0 (Mac OS 26.5.0; arm64) unknown (decodex; 0.1.0)", "codex-cli 0.136.0"), - ( - "decodex/0.136.0-alpha.2 (Mac OS 26.5.0; arm64) unknown (decodex; 0.1.0)", - "codex-cli 0.136.0-alpha.2", - ), - ( - "decodex/0.137.0-alpha.4 (Mac OS 26.5.1; arm64) unknown (decodex; 0.1.0)", - "codex-cli 0.137.0-alpha.4", - ), - ] { - let mut report = AppServerCapabilityPreflightReport::new(); - - report.push_ok( - PREFLIGHT_CHECK_CONFIG, - "config/read returned effective runtime configuration.", - BTreeMap::new(), - ); - - super::record_app_server_compatibility_guard( - &mut report, - user_agent, - false, - Some(&schema_evidence), - ); - - assert!(!report.has_blockers(), "{user_agent} should be supported"); - assert_eq!(report.compatibility_status(), "supported"); - assert_eq!(report.compatibility_support_decision(), Some("supported_exact_version")); - assert_eq!( - report.compatibility_codex_cli_version(), - Some(expected_codex_cli_version), - "{user_agent} should be the matched Codex CLI version" - ); - assert_eq!( - report.compatibility_supported_versions(), - Some("codex-cli 0.136.0, codex-cli 0.136.0-alpha.2, codex-cli 0.137.0-alpha.4") - ); - assert_eq!(report.compatibility_capability_evidence(), Some("config=ok")); - assert_eq!(report.compatibility_schema_evidence(), Some("checked")); - assert_eq!( - report.compatibility_schema_cache(), - Some("target/decodex-app-server-schema-check") - ); - } -} - -#[test] -fn app_server_compatibility_guard_rejects_unverified_codex_surfaces() { - for user_agent in [ - "codex-cli 0.137.0-alpha.0", - "codex-cli 0.137.0-alpha.5", - "codex-cli 0.136.1", - "other-app/0.136.0", - "openai/codex upstream-main-post-rust-v0.136.0", - ] { - let mut report = AppServerCapabilityPreflightReport::new(); - - report.push_ok( - PREFLIGHT_CHECK_CONFIG, - "config/read returned effective runtime configuration.", - BTreeMap::new(), - ); - - super::record_app_server_compatibility_guard(&mut report, user_agent, false, None); - - assert!(report.has_blockers(), "{user_agent} should be outside support"); - assert_eq!(report.compatibility_status(), "unsupported"); - assert!( - report - .compatibility_support_decision() - .is_some_and(|decision| decision.starts_with("unsupported_")), - "{user_agent} should record an unsupported decision" - ); - assert_eq!(report.compatibility_schema_evidence(), Some("not_checked")); - assert_eq!(report.checks()[1].name, "compatibility"); - assert_eq!(report.checks()[1].status, super::AppServerCapabilityPreflightStatus::Blocked); - assert!(report.checks()[1].summary.contains("outside")); - assert!(report.blocker_summary().contains("support_decision=unsupported_")); - } -} - -#[test] -fn app_server_compatibility_guard_allows_unverified_codex_when_requested() { - let schema_evidence = AppServerSchemaProbeEvidence::checked( - String::from("target/decodex-app-server-schema-check"), - APP_SERVER_SCHEMA_REQUIRED_MARKERS, - ); - let mut report = AppServerCapabilityPreflightReport::new(); - - report.push_ok( - PREFLIGHT_CHECK_CONFIG, - "config/read returned effective runtime configuration.", - BTreeMap::new(), - ); - - super::record_app_server_compatibility_guard( - &mut report, - "codex-cli 0.138.0-alpha.1", - true, - Some(&schema_evidence), - ); - - assert!(!report.has_blockers()); - assert_eq!(report.compatibility_status(), "unverified_allowed"); - assert_eq!(report.compatibility_support_decision(), Some("unverified_allowed_by_override")); - assert_eq!(report.compatibility_schema_evidence(), Some("checked")); - assert_eq!(report.compatibility_capability_evidence(), Some("config=ok")); - assert_eq!(report.checks()[1].name, "compatibility"); - assert_eq!(report.checks()[1].status, super::AppServerCapabilityPreflightStatus::Warning); - assert_eq!( - report.checks()[1].details.get("override").map(String::as_str), - Some("allow_unverified_codex") - ); -} - -#[test] -fn app_server_compatibility_guard_keeps_unverified_version_blocked_with_schema_evidence() { - let schema_evidence = AppServerSchemaProbeEvidence::checked( - String::from("target/decodex-app-server-schema-check"), - APP_SERVER_SCHEMA_REQUIRED_MARKERS, - ); - let mut report = AppServerCapabilityPreflightReport::new(); - - report.push_ok( - PREFLIGHT_CHECK_CONFIG, - "config/read returned effective runtime configuration.", - BTreeMap::new(), - ); - - super::record_app_server_compatibility_guard( - &mut report, - "codex-cli 0.137.0", - false, - Some(&schema_evidence), - ); - - assert!(report.has_blockers()); - assert_eq!(report.compatibility_status(), "unsupported"); - assert_eq!(report.compatibility_support_decision(), Some("unsupported_unverified_version")); - assert_eq!(report.compatibility_schema_evidence(), Some("checked")); - assert_eq!(report.compatibility_capability_evidence(), Some("config=ok")); -} - -#[test] -fn app_server_compatibility_guard_records_capability_and_schema_evidence_diagnostics() { - let schema_evidence = AppServerSchemaProbeEvidence::checked( - String::from("target/decodex-app-server-schema-check"), - APP_SERVER_SCHEMA_REQUIRED_MARKERS, - ); - let mut report = AppServerCapabilityPreflightReport::new(); - - report.push_ok( - PREFLIGHT_CHECK_CONFIG, - "config/read returned effective runtime configuration.", - BTreeMap::new(), - ); - report.push_ok( - PREFLIGHT_CHECK_MODEL, - "model/list returned an executable model selection.", - BTreeMap::new(), - ); - - super::record_app_server_compatibility_guard( - &mut report, - "codex-cli 0.136.0", - false, - Some(&schema_evidence), - ); - - let serialized = serde_json::to_value(&report).expect("report should serialize"); - let compatibility = &serialized["checks"][2]; - - assert_eq!(compatibility["name"], "compatibility"); - assert_eq!(compatibility["status"], "ok"); - assert_eq!(compatibility["details"]["user_agent"], "codex-cli 0.136.0"); - assert_eq!(compatibility["details"]["parsed_version"], "codex-cli 0.136.0"); - assert_eq!(compatibility["details"]["support_decision"], "supported_exact_version"); - assert_eq!(compatibility["details"]["capability_evidence"], "config=ok, model=ok"); - assert_eq!( - compatibility["details"]["capability_evidence_source"], - "bounded_app_server_preflight" - ); - assert_eq!(compatibility["details"]["schema_evidence"], "checked"); - assert_eq!(compatibility["details"]["schema_cache"], "target/decodex-app-server-schema-check"); - assert_eq!( - compatibility["details"]["schema_marker_count"], - super::APP_SERVER_SCHEMA_REQUIRED_MARKERS.len().to_string() - ); - assert!( - compatibility["details"]["schema_markers"] - .as_str() - .expect("schema markers should serialize") - .contains("dynamicTools") - ); -} - #[test] fn generated_schema_marker_validation_accepts_required_markers() { let temp_dir = TempDir::new().expect("temp dir should create"); @@ -578,30 +363,6 @@ fn generated_schema_marker_validation_rejects_prose_only_markers() { assert!(error.to_string().contains("marketplaceKinds")); } -#[test] -fn app_server_compatibility_versions_match_spec_table() { - let spec_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("..") - .join("..") - .join("docs") - .join("spec") - .join("app-server.md"); - let spec = fs::read_to_string(spec_path).expect("app-server spec should be readable"); - let documented_versions = spec - .lines() - .filter(|line| line.starts_with("| ") && line.contains("`codex-cli ")) - .filter_map(|line| line.split('`').find_map(|segment| segment.strip_prefix("codex-cli "))) - .map(str::to_owned) - .collect::>(); - let executable_versions = SUPPORTED_CODEX_CLI_VERSION_DISPLAY_ORDER - .iter() - .map(|version| (*version).to_owned()) - .collect::>(); - - assert_eq!(documented_versions, executable_versions); - assert!(spec.contains("Upstream `main` commits after `rust-v0.136.0` are outside")); -} - #[test] fn turn_start_request_uses_default_runtime_settings() { let request = super::build_turn_start_request("thread-1", "hello"); @@ -698,7 +459,6 @@ fn minimal_run_request<'a>() -> super::AppServerRunRequest<'a> { max_turns: 1, timeout: Duration::from_secs(30), process_env: AppServerProcessEnv::default(), - allow_unverified_codex: false, continuation_user_input: None, activity_marker_path: None, resume_thread_id: None, @@ -707,7 +467,6 @@ fn minimal_run_request<'a>() -> super::AppServerRunRequest<'a> { dynamic_tool_handler: None, continuation_guard: None, codex_account_provider: None, - compatibility_schema_evidence: None, } } @@ -2301,22 +2060,20 @@ fn live_app_server_resume_round_trip_updates_marker_and_state() { user_input: String::from( "Call `echo_resume` with `{\\\"text\\\":\\\"FIRST_OK\\\"}`. After the tool succeeds, reply with the exact text CONTINUE.", ), - max_turns: 3, - timeout: Duration::from_secs(30), - process_env: AppServerProcessEnv::default(), - allow_unverified_codex: false, - continuation_user_input: Some(String::from( + max_turns: 3, + timeout: Duration::from_secs(30), + process_env: AppServerProcessEnv::default(), + continuation_user_input: Some(String::from( "Call `echo_resume` with `{\\\"text\\\":\\\"SECOND_OK\\\"}`. After the tool succeeds, reply with the exact text DONE.", )), activity_marker_path: Some(marker_path.clone()), resume_thread_id: None, ephemeral_thread: false, command_exec_health_check: None, - dynamic_tool_handler: Some(&handler), - continuation_guard: Some(&guard), - codex_account_provider: None, - compatibility_schema_evidence: None, - }, + dynamic_tool_handler: Some(&handler), + continuation_guard: Some(&guard), + codex_account_provider: None, + }, &first_state_store, ) .expect("first live app-server run should succeed"); @@ -2350,20 +2107,18 @@ fn live_app_server_resume_round_trip_updates_marker_and_state() { user_input: String::from( "Call `echo_resume` with `{\\\"text\\\":\\\"SECOND_OK\\\"}`. After the tool succeeds, reply with the exact text DONE.", ), - max_turns: 1, - timeout: Duration::from_secs(30), - process_env: AppServerProcessEnv::default(), - allow_unverified_codex: false, + max_turns: 1, + timeout: Duration::from_secs(30), + process_env: AppServerProcessEnv::default(), continuation_user_input: None, activity_marker_path: Some(marker_path.clone()), resume_thread_id: Some(first_result.thread_id.clone()), ephemeral_thread: false, command_exec_health_check: None, - dynamic_tool_handler: Some(&handler), - continuation_guard: None, - codex_account_provider: None, - compatibility_schema_evidence: None, - }, + dynamic_tool_handler: Some(&handler), + continuation_guard: None, + codex_account_provider: None, + }, &resumed_state_store, ) .expect("resumed live app-server run should succeed"); diff --git a/apps/decodex/src/cli.rs b/apps/decodex/src/cli.rs index 487cb4e..c570b23 100644 --- a/apps/decodex/src/cli.rs +++ b/apps/decodex/src/cli.rs @@ -89,8 +89,6 @@ pub(crate) struct AttemptRequest { pub(crate) initial_issue_state: Option, #[serde(default)] pub(crate) lease_preacquired: bool, - #[serde(default)] - pub(crate) allow_unverified_codex: bool, pub(crate) issue_claim_fd: Option, pub(crate) dispatch_slot_fd: Option, pub(crate) dispatch_slot_index: Option, @@ -300,9 +298,6 @@ struct RunCommand { /// Explain current queued candidates without preparing or dispatching a lane. #[arg(long, requires = "dry_run", conflicts_with = "issue")] explain: bool, - /// Continue after warning when Codex app-server is outside the locally verified range. - #[arg(long)] - allow_unverified_codex: bool, } impl RunCommand { fn run(&self) -> Result<()> { @@ -322,7 +317,6 @@ impl RunCommand { preferred_attempt_number: None, preferred_retry_budget_base: None, preferred_workflow_snapshot: None, - allow_unverified_codex: self.allow_unverified_codex, }) } } @@ -337,9 +331,6 @@ struct ServeCommand { /// Start the local dev endpoint without polling or dispatching projects. #[arg(long, hide = true)] dev: bool, - /// Continue after warning when Codex app-server is outside the locally verified range. - #[arg(long)] - allow_unverified_codex: bool, } impl ServeCommand { fn run(&self) -> Result<()> { @@ -347,7 +338,6 @@ impl ServeCommand { config_path: self.project_config.as_path(), listen_address: &self.listen_address, dev: self.dev, - allow_unverified_codex: self.allow_unverified_codex, }) } } @@ -1220,24 +1210,14 @@ struct ProbeCommand { /// Override the expected app-server transport during probing. #[arg(value_name = "TRANSPORT", default_value = "stdio://")] transport: String, - /// Continue after warning when Codex app-server is outside the locally verified range. - #[arg(long)] - allow_unverified_codex: bool, } impl ProbeCommand { fn run(&self) -> Result<()> { - let report = agent::probe_app_server(&self.transport, self.allow_unverified_codex)?; + let report = agent::probe_app_server(&self.transport)?; println!( - "probe ok: compatibility={} support_decision={} codex_version={} supported_versions=\"{}\" capability_evidence=\"{}\" schema_evidence={} schema_cache={} schema_marker_count={} thread={} turn={} events={} output={}", - report.capability_preflight.compatibility_status(), - report.capability_preflight.compatibility_support_decision().unwrap_or("unknown"), - report.capability_preflight.compatibility_codex_cli_version().unwrap_or("unknown"), - report.capability_preflight.compatibility_supported_versions().unwrap_or("unknown"), - report.capability_preflight.compatibility_capability_evidence().unwrap_or("unknown"), - report.capability_preflight.compatibility_schema_evidence().unwrap_or("unknown"), - report.capability_preflight.compatibility_schema_cache().unwrap_or("none"), - report.capability_preflight.compatibility_schema_marker_count().unwrap_or("0"), + "probe ok: preflight_checks={} thread={} turn={} events={} output={}", + report.capability_preflight.check_count(), report.thread_id, report.turn_id, report.event_count, @@ -1284,7 +1264,6 @@ impl AttemptCommand { preferred_attempt_number: Some(request.attempt_number), preferred_retry_budget_base: Some(request.retry_budget_base), preferred_workflow_snapshot: Some(request.workflow_snapshot.as_str()), - allow_unverified_codex: request.allow_unverified_codex, }) } } @@ -1679,39 +1658,13 @@ mod tests { project_config: ProjectConfigArgs { config: Some(config) }, listen_address, dev, - allow_unverified_codex, }) if listen_address == "127.0.0.1:9000" && !dev - && !allow_unverified_codex && config == Path::new("./project.toml") )); } - #[test] - fn parses_runtime_unverified_codex_override() { - let cli = Cli::parse_from(["decodex", "run", "--allow-unverified-codex"]); - - assert!(matches!( - cli.command, - Command::Run(RunCommand { allow_unverified_codex: true, .. }) - )); - - let cli = Cli::parse_from(["decodex", "serve", "--allow-unverified-codex"]); - - assert!(matches!( - cli.command, - Command::Serve(ServeCommand { allow_unverified_codex: true, .. }) - )); - - let cli = Cli::parse_from(["decodex", "probe", "--allow-unverified-codex"]); - - assert!(matches!( - cli.command, - Command::Probe(ProbeCommand { allow_unverified_codex: true, .. }) - )); - } - #[test] fn parses_serve_dev() { let cli = Cli::parse_from(["decodex", "serve", "--dev"]); diff --git a/apps/decodex/src/orchestrator/daemon.rs b/apps/decodex/src/orchestrator/daemon.rs index dd2733e..9f2338a 100644 --- a/apps/decodex/src/orchestrator/daemon.rs +++ b/apps/decodex/src/orchestrator/daemon.rs @@ -14,7 +14,6 @@ struct DaemonTickRuntimeContext<'a, T, I> { worktree_manager: &'a WorktreeManager, review_state_inspector: &'a I, recoverable_worktree_skip_cache: Option<&'a mut RecoverableWorktreeSkipCache>, - allow_unverified_codex: bool, } fn load_daemon_tick_context( @@ -77,7 +76,6 @@ fn run_daemon_tick( retry_queue: &mut RetryQueue, recoverable_worktree_skip_cache: &mut RecoverableWorktreeSkipCache, context: &DaemonTickContext, - allow_unverified_codex: bool, ) -> Result<()> { let review_state_inspector = GhPullRequestReviewStateInspector { github_token_env_var: Some(context.config.github().token_env_var().to_owned()), @@ -96,7 +94,6 @@ fn run_daemon_tick( worktree_manager: &context.worktree_manager, review_state_inspector: &review_state_inspector, recoverable_worktree_skip_cache: Some(recoverable_worktree_skip_cache), - allow_unverified_codex, }, ) } @@ -575,7 +572,6 @@ where context.workflow, &summary, daemon_spawn_state.retry_budget_base, - context.allow_unverified_codex, )?; if let Err(error) = state::write_run_operation_marker_for_process( @@ -635,7 +631,6 @@ fn spawn_planned_daemon_child( workflow: &WorkflowDocument, summary: &RunSummary, retry_budget_base: i64, - allow_unverified_codex: bool, ) -> Result { let issue_claim_handoff = Some(state_store.clone_issue_claim_for_child(&summary.issue_id).inspect_err(|_error| { @@ -652,11 +647,10 @@ fn spawn_planned_daemon_child( preferred_issue_state: summary.issue_state.as_str(), preferred_initial_issue_state: Some(summary.initial_issue_state.as_str()), dispatch_mode: summary.dispatch_mode, - preferred_run_id: summary.run_id.as_str(), + preferred_run_id: summary.run_id.as_str(), preferred_attempt_number: summary.attempt_number, preferred_retry_budget_base: retry_budget_base, workflow, - allow_unverified_codex, issue_claim_handoff: issue_claim_handoff.as_ref(), dispatch_slot_handoff: dispatch_slot_handoff.as_ref(), dispatch_slot_index_handoff, @@ -785,12 +779,11 @@ fn spawn_run_once_child(request: SpawnRunOnceChildRequest<'_>) -> Result dispatch_slot_fd: None, dispatch_slot_index: request.dispatch_slot_index_handoff, dispatch_mode: request.dispatch_mode.into(), - run_id: String::from(request.preferred_run_id), - attempt_number: request.preferred_attempt_number, - retry_budget_base: request.preferred_retry_budget_base, - allow_unverified_codex: request.allow_unverified_codex, - workflow_snapshot: request.workflow.to_markdown()?, - }; + run_id: String::from(request.preferred_run_id), + attempt_number: request.preferred_attempt_number, + retry_budget_base: request.preferred_retry_budget_base, + workflow_snapshot: request.workflow.to_markdown()?, + }; let payload = serde_json::to_vec(&attempt_request)?; let mut command = Command::new(executable); @@ -874,11 +867,10 @@ where preferred_issue_claim_fd: None, preferred_dispatch_slot_fd: None, preferred_dispatch_slot_index: None, - dispatch_mode: entry.dispatch_mode, - preferred_run_identity: None, - preferred_retry_budget_base: None, - allow_unverified_codex: false, - })? + dispatch_mode: entry.dispatch_mode, + preferred_run_identity: None, + preferred_retry_budget_base: None, + })? else { if retry_entry_is_temporarily_blocked(tracker, project, workflow, state_store, &entry)? { diff --git a/apps/decodex/src/orchestrator/entrypoints.rs b/apps/decodex/src/orchestrator/entrypoints.rs index db60a71..de4e3b8 100644 --- a/apps/decodex/src/orchestrator/entrypoints.rs +++ b/apps/decodex/src/orchestrator/entrypoints.rs @@ -100,7 +100,6 @@ pub(crate) fn run_once(request: RunOnceRequest<'_>) -> Result<()> { preferred_run_identity, preferred_retry_budget_base: request.preferred_retry_budget_base, preferred_workflow_snapshot: request.preferred_workflow_snapshot, - allow_unverified_codex: request.allow_unverified_codex, }) { Ok(summary) => summary, Err(error) => { @@ -222,7 +221,6 @@ pub(crate) fn run_control_plane(request: ServeRequest<'_>) -> Result<()> { &state_store, &mut project_runtimes, &linear_scan_requests, - request.allow_unverified_codex, )?; publish_operator_snapshot(&operator_state_endpoint, &snapshot); @@ -786,14 +784,13 @@ fn run_control_plane_tick( project_runtimes: &mut HashMap, linear_scan_requests: &[OperatorLinearScanRequest], ) -> Result { - run_control_plane_tick_with_options(state_store, project_runtimes, linear_scan_requests, false) + run_control_plane_tick_with_options(state_store, project_runtimes, linear_scan_requests) } fn run_control_plane_tick_with_options( state_store: &StateStore, project_runtimes: &mut HashMap, linear_scan_requests: &[OperatorLinearScanRequest], - allow_unverified_codex: bool, ) -> Result { let registered_projects = state_store.list_projects()?; let now = Instant::now(); @@ -809,7 +806,6 @@ fn run_control_plane_tick_with_options( project_warnings, linear_scan_requests, now, - allow_unverified_codex, ) } else { control_plane_disabled_project_observer_tick(project, state_store, project_warnings) @@ -1095,7 +1091,6 @@ fn run_control_plane_project_tick( snapshot_warnings: &mut Vec<&'static str>, linear_scan_requests: &[OperatorLinearScanRequest], now: Instant, - allow_unverified_codex: bool, ) -> ControlPlaneProjectTick { if tracker_backoff_active(runtime, now) { snapshot_warnings.push(TRACKER_RATE_LIMIT_WARNING); @@ -1139,7 +1134,6 @@ fn run_control_plane_project_tick( runtime, &context, snapshot_warnings, - allow_unverified_codex, ), Err(error) => { tracing::warn!( @@ -1397,7 +1391,6 @@ fn control_plane_project_snapshot( runtime: &mut ProjectDaemonRuntime, context: &DaemonTickContext, snapshot_warnings: &mut Vec<&'static str>, - allow_unverified_codex: bool, ) -> ControlPlaneProjectTick { if let Err(error) = run_daemon_tick( project.config_path(), @@ -1406,7 +1399,6 @@ fn control_plane_project_snapshot( &mut runtime.retry_queue, &mut runtime.recoverable_worktree_skip_cache, context, - allow_unverified_codex, ) { if let Some(connector_backoff) = remember_tracker_backoff( runtime, diff --git a/apps/decodex/src/orchestrator/execution.rs b/apps/decodex/src/orchestrator/execution.rs index cadae12..0a7f6cb 100644 --- a/apps/decodex/src/orchestrator/execution.rs +++ b/apps/decodex/src/orchestrator/execution.rs @@ -143,7 +143,6 @@ fn execute_issue_run( workflow: &WorkflowDocument, state_store: &StateStore, issue_run: IssueRunPlan, - allow_unverified_codex: bool, ) -> Result where T: IssueTracker, @@ -167,16 +166,7 @@ where )?; let result = ensure_automation_activity_label(tracker, &issue_run.issue, project.service_id(), true) - .and_then(|_| { - execute_issue_run_inner( - tracker, - project, - workflow, - state_store, - &issue_run, - allow_unverified_codex, - ) - }); + .and_then(|_| execute_issue_run_inner(tracker, project, workflow, state_store, &issue_run)); state_store.clear_lease(&issue_run.issue.id)?; @@ -534,7 +524,6 @@ fn execute_issue_run_inner( workflow: &WorkflowDocument, state_store: &StateStore, issue_run: &IssueRunPlan, - allow_unverified_codex: bool, ) -> Result where T: IssueTracker, @@ -604,11 +593,10 @@ where issue_run, &review_context, ), - max_turns: workflow.frontmatter().execution().max_turns(), - timeout: ACTIVE_RUN_IDLE_TIMEOUT, - process_env: agent_git_credentials.process_env().clone(), - allow_unverified_codex, - continuation_user_input: Some(build_continuation_user_input( + max_turns: workflow.frontmatter().execution().max_turns(), + timeout: ACTIVE_RUN_IDLE_TIMEOUT, + process_env: agent_git_credentials.process_env().clone(), + continuation_user_input: Some(build_continuation_user_input( &issue_run.issue, workflow, issue_run.dispatch_mode, @@ -622,11 +610,10 @@ where command_exec_health_check: None, dynamic_tool_handler: Some(&decodex_tool_bridge), continuation_guard: Some(&continuation_guard), - codex_account_provider: codex_account_pool - .as_ref() - .map(|pool| pool as &dyn CodexAccountProvider), - compatibility_schema_evidence: None, - }, + codex_account_provider: codex_account_pool + .as_ref() + .map(|pool| pool as &dyn CodexAccountProvider), + }, state_store, ) .map_err(|error| { diff --git a/apps/decodex/src/orchestrator/run_cycle.rs b/apps/decodex/src/orchestrator/run_cycle.rs index bae03f6..3db4213 100644 --- a/apps/decodex/src/orchestrator/run_cycle.rs +++ b/apps/decodex/src/orchestrator/run_cycle.rs @@ -95,11 +95,10 @@ fn run_configured_cycle( preferred_issue_claim_fd: request.preferred_issue_claim_fd, preferred_dispatch_slot_fd: request.preferred_dispatch_slot_fd, preferred_dispatch_slot_index: request.preferred_dispatch_slot_index, - dispatch_mode: request.preferred_dispatch_mode.unwrap_or(IssueDispatchMode::Normal), - preferred_run_identity: request.preferred_run_identity, - preferred_retry_budget_base: request.preferred_retry_budget_base, - allow_unverified_codex: request.allow_unverified_codex, - }; + dispatch_mode: request.preferred_dispatch_mode.unwrap_or(IssueDispatchMode::Normal), + preferred_run_identity: request.preferred_run_identity, + preferred_retry_budget_base: request.preferred_retry_budget_base, + }; return match request.preferred_dispatch_mode { Some(_) => run_target_issue_once(target_context), @@ -107,14 +106,7 @@ fn run_configured_cycle( }; } - run_project_once( - &tracker, - &config, - &workflow, - request.state_store, - request.dry_run, - request.allow_unverified_codex, - ) + run_project_once(&tracker, &config, &workflow, request.state_store, request.dry_run) } fn load_configured_cycle_workflow( @@ -135,20 +127,11 @@ fn run_project_once( workflow: &WorkflowDocument, state_store: &StateStore, dry_run: bool, - allow_unverified_codex: bool, ) -> Result> where T: IssueTracker, { - run_project_once_with_exclusions( - tracker, - project, - workflow, - state_store, - dry_run, - &[], - allow_unverified_codex, - ) + run_project_once_with_exclusions(tracker, project, workflow, state_store, dry_run, &[]) } fn run_project_once_with_exclusions( @@ -158,7 +141,6 @@ fn run_project_once_with_exclusions( state_store: &StateStore, dry_run: bool, excluded_issue_ids: &[&str], - allow_unverified_codex: bool, ) -> Result> where T: IssueTracker, @@ -175,15 +157,7 @@ where return Ok(None); }; - complete_issue_run( - tracker, - project, - workflow, - state_store, - issue_run, - dry_run, - allow_unverified_codex, - ) + complete_issue_run(tracker, project, workflow, state_store, issue_run, dry_run) } fn reconcile_post_review_orchestration( @@ -1825,10 +1799,9 @@ where context.project, context.workflow, context.state_store, - issue_run, - context.dry_run, - context.allow_unverified_codex, - ) + issue_run, + context.dry_run, + ) } fn ensure_target_closeout_dispatch_is_unblocked( @@ -1959,7 +1932,6 @@ where dispatch_mode: IssueDispatchMode::Closeout, preferred_run_identity, preferred_retry_budget_base: context.preferred_retry_budget_base, - allow_unverified_codex: context.allow_unverified_codex, }) } @@ -2050,7 +2022,6 @@ fn target_issue_run_context_with_dispatch_mode<'a, T>( dispatch_mode, preferred_run_identity: context.preferred_run_identity, preferred_retry_budget_base: context.preferred_retry_budget_base, - allow_unverified_codex: context.allow_unverified_codex, } } @@ -2336,7 +2307,6 @@ fn complete_issue_run( state_store: &StateStore, issue_run: IssueRunPlan, dry_run: bool, - allow_unverified_codex: bool, ) -> Result> where T: IssueTracker, @@ -2345,14 +2315,7 @@ where return Ok(Some(run_summary_from_issue_run(project.service_id(), &issue_run))); } - let summary = execute_issue_run( - tracker, - project, - workflow, - state_store, - issue_run, - allow_unverified_codex, - )?; + let summary = execute_issue_run(tracker, project, workflow, state_store, issue_run)?; let review_state_inspector = GhPullRequestReviewStateInspector { github_token_env_var: Some(project.github().token_env_var().to_owned()), github_command_path: project.github().command_path().map(Path::to_path_buf), @@ -2365,13 +2328,12 @@ where state_store, &summary, &review_state_inspector, - |source_summary| run_retained_closeout_for_handoff_summary( - tracker, - project, - workflow, + |source_summary| run_retained_closeout_for_handoff_summary( + tracker, + project, + workflow, state_store, source_summary, - allow_unverified_codex, ), )? { return Ok(Some(retained_summary)); @@ -2386,7 +2348,6 @@ fn run_retained_closeout_for_handoff_summary( workflow: &WorkflowDocument, state_store: &StateStore, source_summary: &RunSummary, - allow_unverified_codex: bool, ) -> Result> where T: IssueTracker, @@ -2407,7 +2368,6 @@ where dispatch_mode: IssueDispatchMode::Closeout, preferred_run_identity: None, preferred_retry_budget_base: None, - allow_unverified_codex, }) } diff --git a/apps/decodex/src/orchestrator/tests/intake/candidate_selection.rs b/apps/decodex/src/orchestrator/tests/intake/candidate_selection.rs index 31c16e7..9d30208 100644 --- a/apps/decodex/src/orchestrator/tests/intake/candidate_selection.rs +++ b/apps/decodex/src/orchestrator/tests/intake/candidate_selection.rs @@ -289,7 +289,6 @@ fn plan_project_issue_run_prefers_post_review_repair_lane_over_normal_candidate( dispatch_mode: IssueDispatchMode::ReviewRepair, preferred_run_identity: None, preferred_retry_budget_base: None, - allow_unverified_codex: false, }) .expect("targeted review-repair planning should succeed") .expect("review-repair issue run should plan"); @@ -410,7 +409,6 @@ fn targeted_post_review_repair_skips_persisted_exhausted_retry_budget() { dispatch_mode: IssueDispatchMode::ReviewRepair, preferred_run_identity: None, preferred_retry_budget_base: None, - allow_unverified_codex: false, }) .expect("targeted review-repair planning should succeed"); @@ -565,7 +563,6 @@ fn plan_project_issue_run_prefers_post_review_closeout_lane_over_normal_candidat dispatch_mode: IssueDispatchMode::Closeout, preferred_run_identity: None, preferred_retry_budget_base: None, - allow_unverified_codex: false, }) .expect("targeted closeout planning should succeed") .expect("closeout issue run should plan"); @@ -688,7 +685,6 @@ fn plan_project_issue_run_allows_merged_closeout_after_retry_budget() { dispatch_mode: IssueDispatchMode::Closeout, preferred_run_identity: None, preferred_retry_budget_base: None, - allow_unverified_codex: false, }) .expect("targeted closeout planning should succeed") .expect("closeout issue run should plan"); @@ -1411,7 +1407,6 @@ fn non_dry_run_closeout_dispatch_errors_when_pr_state_read_fails() { dispatch_mode: IssueDispatchMode::Closeout, preferred_run_identity: None, preferred_retry_budget_base: None, - allow_unverified_codex: false, }) .expect_err("non-dry-run closeout dispatch should surface GH state read failures"); diff --git a/apps/decodex/src/orchestrator/tests/intake/prepare_issue_run.rs b/apps/decodex/src/orchestrator/tests/intake/prepare_issue_run.rs index e291685..123e5d0 100644 --- a/apps/decodex/src/orchestrator/tests/intake/prepare_issue_run.rs +++ b/apps/decodex/src/orchestrator/tests/intake/prepare_issue_run.rs @@ -624,7 +624,6 @@ fn run_target_issue_once_skips_reconciliation_for_preacquired_child_runs() { attempt_number: 1, }), preferred_retry_budget_base: None, - allow_unverified_codex: false, }) .expect("targeted child run should not error before refresh lookup"); diff --git a/apps/decodex/src/orchestrator/tests/intake/run_and_prompting.rs b/apps/decodex/src/orchestrator/tests/intake/run_and_prompting.rs index a5f48d8..0925fd1 100644 --- a/apps/decodex/src/orchestrator/tests/intake/run_and_prompting.rs +++ b/apps/decodex/src/orchestrator/tests/intake/run_and_prompting.rs @@ -90,7 +90,7 @@ fn dry_run_selects_one_issue_and_plans_worktree() { let (_temp_dir, config, workflow) = temp_project_layout(); let tracker = FakeTracker::new(vec![sample_issue("Todo", &[])]); let state_store = StateStore::open_in_memory().expect("state store should open"); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true) .expect("run once should succeed") .expect("one issue should be selected"); @@ -161,7 +161,6 @@ fn targeted_identifier_dispatch_accepts_status_ready_queued_issue() { dispatch_mode: IssueDispatchMode::Normal, preferred_run_identity: None, preferred_retry_budget_base: None, - allow_unverified_codex: false, }, ) .expect("targeted identifier run should succeed") @@ -195,7 +194,6 @@ fn targeted_inferred_dispatch_keeps_retry_for_active_issue() { dispatch_mode: IssueDispatchMode::Normal, preferred_run_identity: None, preferred_retry_budget_base: None, - allow_unverified_codex: false, }, ) .expect("targeted active identifier run should succeed") @@ -272,7 +270,6 @@ fn targeted_identifier_dispatch_accepts_status_visible_retained_closeout_lane() dispatch_mode: IssueDispatchMode::Normal, preferred_run_identity: None, preferred_retry_budget_base: None, - allow_unverified_codex: false, }, ) .expect("targeted retained closeout identifier run should succeed") @@ -364,7 +361,6 @@ fn targeted_identifier_dispatch_rejects_different_status_visible_closeout_lane() dispatch_mode: IssueDispatchMode::Normal, preferred_run_identity: None, preferred_retry_budget_base: None, - allow_unverified_codex: false, }, ) .expect_err("targeted closeout inference should reject a different visible lane"); @@ -405,7 +401,7 @@ fn dry_run_returns_none_when_intake_has_no_service_owned_candidate() { let tracker = FakeTracker::with_refresh_snapshots_and_project(vec![], vec![vec![]], false); let state_store = StateStore::open_in_memory().expect("state store should open"); let summary = - orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true, false) + orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true) .expect("dry run without queued issues should succeed"); assert!(summary.is_none(), "empty intake should simply produce no dry-run selection"); @@ -424,7 +420,7 @@ fn dry_run_returns_none_when_intake_has_no_service_owned_candidate() { let tracker = FakeTracker::new(vec![issue]); let state_store = StateStore::open_in_memory().expect("state store should open"); let summary = - orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true, false) + orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true) .expect("dry run should succeed"); assert!(summary.is_none(), "service-scoped queue labels should isolate intake"); @@ -486,7 +482,7 @@ fn dry_run_falls_back_to_normal_issue_when_retained_retry_loses_ownership() { ) .expect("worktree mapping should record"); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true) .expect("dry run should succeed") .expect("normal queued issue should be selected after retained retry is excluded"); diff --git a/apps/decodex/src/orchestrator/tests/intake/workflow_reload.rs b/apps/decodex/src/orchestrator/tests/intake/workflow_reload.rs index 8a437af..fbb94da 100644 --- a/apps/decodex/src/orchestrator/tests/intake/workflow_reload.rs +++ b/apps/decodex/src/orchestrator/tests/intake/workflow_reload.rs @@ -75,7 +75,6 @@ fn configured_cycle_workflow_snapshot_overrides_invalid_disk_workflow() { dispatch_mode: IssueDispatchMode::Normal, preferred_run_identity: None, preferred_retry_budget_base: None, - allow_unverified_codex: false, }) .expect("target issue dry run should succeed with the supplied snapshot"); diff --git a/apps/decodex/src/orchestrator/tests/recovery/closeout/dispatch.rs b/apps/decodex/src/orchestrator/tests/recovery/closeout/dispatch.rs index f2f5212..a0f8743 100644 --- a/apps/decodex/src/orchestrator/tests/recovery/closeout/dispatch.rs +++ b/apps/decodex/src/orchestrator/tests/recovery/closeout/dispatch.rs @@ -59,7 +59,7 @@ fn closeout_dispatch_completes_merged_lane_without_agent_turn() { .expect("run attempt should record"); let summary = - orchestrator::execute_issue_run(&tracker, &config, &workflow, &state_store, issue_run, false) + orchestrator::execute_issue_run(&tracker, &config, &workflow, &state_store, issue_run) .expect("deterministic closeout should complete"); assert_eq!(summary.dispatch_mode, IssueDispatchMode::Closeout); @@ -121,7 +121,6 @@ fn direct_closeout_dispatch_reuses_completed_handoff_run_identity_for_record_and dispatch_mode: IssueDispatchMode::Closeout, preferred_run_identity: None, preferred_retry_budget_base: None, - allow_unverified_codex: false, }) .expect("direct retained closeout should run") .expect("closeout summary should be printed"); @@ -202,7 +201,6 @@ fn same_run_closeout_reuses_matching_active_handoff_lease() { &fixture.workflow, &fixture.state_store, &source_summary, - false, ) .expect("same-run retained closeout should run") .expect("same-run retained closeout should produce a summary"); @@ -281,7 +279,7 @@ fn closeout_dispatch_validates_pr_before_marking_issue_done() { .expect("run attempt should record"); let error = - orchestrator::execute_issue_run(&tracker, &config, &workflow, &state_store, issue_run, false) + orchestrator::execute_issue_run(&tracker, &config, &workflow, &state_store, issue_run) .expect_err("unmerged PR should stop deterministic closeout"); assert!( diff --git a/apps/decodex/src/orchestrator/tests/recovery/closeout/identity.rs b/apps/decodex/src/orchestrator/tests/recovery/closeout/identity.rs index 9f9a1c4..c9bcf60 100644 --- a/apps/decodex/src/orchestrator/tests/recovery/closeout/identity.rs +++ b/apps/decodex/src/orchestrator/tests/recovery/closeout/identity.rs @@ -11,7 +11,6 @@ fn run_project_once_closeout_reuses_completed_handoff_run_identity_for_record_an &fixture.workflow, &fixture.state_store, true, - false, ) .expect("retained closeout dry-run planning should succeed"); let planned = @@ -27,7 +26,6 @@ fn run_project_once_closeout_reuses_completed_handoff_run_identity_for_record_an &fixture.workflow, &fixture.state_store, false, - false, ) .expect("retained closeout should run") .expect("closeout summary should be printed"); @@ -241,7 +239,6 @@ fn run_project_once_closeout_preserves_handoff_identity_after_fresh_activity_rec &fixture.workflow, &fixture.state_store, false, - false, ) .expect("retained closeout should run after recovery") .expect("closeout summary should be printed"); diff --git a/apps/decodex/src/orchestrator/tests/recovery/runtime_reentry.rs b/apps/decodex/src/orchestrator/tests/recovery/runtime_reentry.rs index 43877be..174e0e9 100644 --- a/apps/decodex/src/orchestrator/tests/recovery/runtime_reentry.rs +++ b/apps/decodex/src/orchestrator/tests/recovery/runtime_reentry.rs @@ -254,7 +254,7 @@ fn run_project_once_prefers_recovered_in_progress_worktree_after_empty_state_sta .ensure_worktree(&issue.identifier, false) .expect("recovered worktree should be created") .path; - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true) .expect("recovered dry run should succeed") .expect("active recovered issue should be selected"); @@ -329,7 +329,7 @@ fn run_project_once_recovers_retained_worktree_from_issue_identifier() { .ensure_worktree(&issue.identifier, false) .expect("recovered worktree should be created") .path; - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true) .expect("recovered dry run should succeed") .expect("active recovered issue should be selected"); @@ -373,7 +373,7 @@ fn run_project_once_recovers_ready_post_review_lane_before_landing() { &sample_review_handoff_marker(&worktree.branch_name, pr_url, &head_oid), ); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, false, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, false) .expect("recovered retained post-review lane should reconcile"); assert!( @@ -420,7 +420,7 @@ fn materialize_run_summary_worktree_creates_worktree_before_child_activity_marke vec![vec![issue.clone()], vec![issue.clone()]], ); let state_store = StateStore::open_in_memory().expect("state store should open"); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true) .expect("dry-run planning should succeed") .expect("brand-new lane should be selected"); @@ -538,7 +538,7 @@ fn materialize_daemon_spawn_state_uses_retained_retry_budget_marker() { state::write_run_retry_budget_attempt_count(&retained_worktree.path, "older-run", 4, 2) .expect("retry budget marker should write"); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true) .expect("dry-run planning should succeed") .expect("retained lane should still be selected"); let daemon_spawn_state = @@ -568,7 +568,7 @@ fn run_project_once_skips_recovered_worktree_with_fresh_activity_marker() { state::write_run_activity_marker(&worktree.path, "run-1", 1) .expect("activity marker should write"); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true) .expect("recovery should succeed"); assert!( @@ -630,7 +630,7 @@ fn run_project_once_retries_recovered_worktree_after_marker_process_is_killed() "kill-smoke child process should no longer be live after kill" ); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true) .expect("kill-smoke recovery should succeed") .expect("killed-process recovered lane should be selected for retry"); @@ -663,7 +663,7 @@ fn run_project_once_retries_recovered_worktree_from_previous_boot() { rewrite_run_activity_marker_host_boot_id(&worktree.path, "previous-boot"); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true) .expect("previous-boot recovery should succeed") .expect("previous-boot recovered lane should be selected for retry"); @@ -696,7 +696,7 @@ fn run_project_once_retries_recovered_worktree_from_reused_pid() { rewrite_run_activity_marker_process_start_identity(&worktree.path, "previous-process-start"); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true) .expect("same-boot PID-reuse recovery should succeed") .expect("same-boot PID-reuse recovered lane should be selected for retry"); @@ -740,7 +740,7 @@ fn run_project_once_clears_recovered_lease_when_marker_turns_stale() { .expect("fresh activity marker should write"); let initial_summary = - orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true, false) + orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true) .expect("initial recovery should succeed"); assert!( @@ -759,7 +759,7 @@ fn run_project_once_clears_recovered_lease_when_marker_turns_stale() { state::write_run_activity_marker_for_process(&worktree.path, "run-1", 1, u32::MAX) .expect("stale activity marker should write"); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true) .expect("stale recovery should succeed") .expect("stale recovered lease should no longer block retry planning"); @@ -790,7 +790,7 @@ fn run_project_once_skips_recovered_terminal_guarded_worktree_after_empty_state_ ) .expect("terminal guard marker should write"); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true) .expect("recovery should succeed"); assert!( @@ -813,7 +813,7 @@ fn run_project_once_clears_terminal_queued_lane_labels_without_dispatch() { let issue = sample_issue("Done", &[active_label.as_str()]); let tracker = FakeTracker::new(vec![issue.clone()]); let state_store = StateStore::open_in_memory().expect("state store should open"); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, false, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, false) .expect("terminal queued cleanup should succeed"); assert!(summary.is_none(), "terminal queued issues should not dispatch"); @@ -833,7 +833,7 @@ fn run_project_once_dry_run_keeps_terminal_queued_lane_labels() { let issue = sample_issue("Done", &[active_label.as_str()]); let tracker = FakeTracker::new(vec![issue.clone()]); let state_store = StateStore::open_in_memory().expect("state store should open"); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true) .expect("terminal queued dry run should succeed"); assert!(summary.is_none(), "terminal queued dry run should not dispatch"); @@ -856,7 +856,7 @@ fn run_project_once_preserves_terminal_recovered_worktree_without_prior_state_wh let worktree = worktree_manager .ensure_worktree(&issue.identifier, false) .expect("terminal retained worktree should be created"); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, false, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, false) .expect("reconciliation should finish cleanly"); assert!( @@ -918,7 +918,7 @@ fn run_project_once_clears_stale_completed_closeout_lease_but_keeps_worktree() { ) .expect("worktree mapping should record"); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, false, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, false) .expect("startup reconciliation should succeed"); assert!( @@ -995,7 +995,7 @@ fn run_project_once_preserves_fresh_completed_closeout_lease() { ) .expect("worktree mapping should record"); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, false, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, false) .expect("startup reconciliation should succeed"); assert!( @@ -1067,7 +1067,7 @@ fn run_project_once_preserves_completed_unmerged_closeout_worktree() { ) .expect("worktree mapping should record"); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, false, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, false) .expect("startup reconciliation should succeed"); assert!( @@ -1113,7 +1113,7 @@ fn run_project_once_skips_recovered_worktree_without_service_active_label() { .ensure_worktree(&issue.identifier, false) .expect("foreign retained worktree should exist"); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true) .expect("recovery should succeed"); assert!( @@ -1156,7 +1156,7 @@ fn run_project_once_recovers_worktree_when_identifier_lookup_labels_are_truncate .ensure_worktree(&listed_issue.identifier, false) .expect("recovered worktree should be created") .path; - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true) .expect("recovery should succeed") .expect("ambiguous label pagination should still recover the owned retained lane"); @@ -1217,7 +1217,7 @@ fn live_run_skips_issue_that_becomes_ineligible_after_worktree_prepare() { vec![vec![], vec![listed_issue.clone()], vec![sample_issue("In Progress", &[])]], ); let state_store = StateStore::open_in_memory().expect("state store should open"); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, false, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, false) .expect("run once should succeed"); assert!(summary.is_none()); @@ -1240,7 +1240,7 @@ fn live_run_clears_claimed_lease_when_refresh_fails_after_worktree_prepare() { let tracker = FakeTracker::with_refresh_error(vec![listed_issue.clone()], "transient refresh failure"); let state_store = StateStore::open_in_memory().expect("state store should open"); - let error = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, false, false) + let error = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, false) .expect_err("run once should propagate refresh failure"); assert!( @@ -1269,7 +1269,7 @@ fn run_project_once_ignores_fresh_marker_for_exited_process() { state::write_run_activity_marker_for_process(&worktree.path, "run-1", 1, exited_process_id) .expect("activity marker should write"); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, true) .expect("recovery should succeed") .expect("dead process marker should not block retry planning"); diff --git a/apps/decodex/src/orchestrator/tests/retry/scheduling.rs b/apps/decodex/src/orchestrator/tests/retry/scheduling.rs index dbfa820..0459d5a 100644 --- a/apps/decodex/src/orchestrator/tests/retry/scheduling.rs +++ b/apps/decodex/src/orchestrator/tests/retry/scheduling.rs @@ -98,7 +98,6 @@ fn retry_run_dry_run_enforces_active_ownership() { dispatch_mode: IssueDispatchMode::Retry, preferred_run_identity: None, preferred_retry_budget_base: None, - allow_unverified_codex: false, }) .expect("retry run should succeed"); @@ -131,7 +130,6 @@ fn targeted_run_dry_run_accepts_startable_issue_with_normal_dispatch() { dispatch_mode: IssueDispatchMode::Normal, preferred_run_identity: None, preferred_retry_budget_base: None, - allow_unverified_codex: false, }) .expect("targeted run should succeed"); @@ -168,7 +166,6 @@ fn retry_run_dry_run_rejects_terminal_guarded_issue_without_attention_label() { dispatch_mode: IssueDispatchMode::Retry, preferred_run_identity: None, preferred_retry_budget_base: None, - allow_unverified_codex: false, }) .expect("retry run should succeed"); @@ -1374,7 +1371,6 @@ fn daemon_tick_reconciles_ready_retained_review_lane_before_dry_run_planning() { review_state, )]), recoverable_worktree_skip_cache: None, - allow_unverified_codex: false, }, ); @@ -1493,7 +1489,6 @@ fn daemon_tick_clears_terminal_mapping_without_worktree_before_retained_land() { ), )]), recoverable_worktree_skip_cache: None, - allow_unverified_codex: false, }, ) .expect("daemon tick should not fail on stale terminal worktree state"); diff --git a/apps/decodex/src/orchestrator/tests/runtime/failure.rs b/apps/decodex/src/orchestrator/tests/runtime/failure.rs index 74ac9cb..1e4510d 100644 --- a/apps/decodex/src/orchestrator/tests/runtime/failure.rs +++ b/apps/decodex/src/orchestrator/tests/runtime/failure.rs @@ -927,7 +927,7 @@ fn live_run_without_candidate_does_not_require_github_token_authority() { let (_temp_dir, config, workflow) = temp_project_layout(); let tracker = FakeTracker::with_refresh_snapshots_and_project(vec![], vec![vec![]], true); let state_store = StateStore::open_in_memory().expect("state store should open"); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, false, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, false) .expect("empty backlog should not require github token authority"); assert!(summary.is_none()); @@ -1042,7 +1042,6 @@ fn execute_issue_run_clears_lease_when_active_label_setup_fails() { &workflow, &state_store, issue_run.clone(), - false, ) .expect_err("active-label setup failure should abort execution"); @@ -1088,7 +1087,7 @@ fn reconciliation_clears_stale_leases_and_terminal_worktrees() { ) .expect("worktree mapping should record"); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, false, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, false) .expect("reconciliation should succeed"); assert!(summary.is_none()); @@ -1134,7 +1133,7 @@ fn reconciliation_runs_without_project_validation() { .upsert_lease("pubfi", &issue.id, "run-1", "In Progress") .expect("lease should record"); - let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, false, false) + let summary = orchestrator::run_project_once(&tracker, &config, &workflow, &state_store, false) .expect("reconciliation should still succeed without any project validation"); assert!(summary.is_none(), "reconciliation-only startup should not dispatch a new lane here"); diff --git a/apps/decodex/src/orchestrator/types.rs b/apps/decodex/src/orchestrator/types.rs index a1214ac..f55d1f1 100644 --- a/apps/decodex/src/orchestrator/types.rs +++ b/apps/decodex/src/orchestrator/types.rs @@ -211,7 +211,6 @@ pub(crate) struct RunOnceRequest<'a> { pub(crate) preferred_attempt_number: Option, pub(crate) preferred_retry_budget_base: Option, pub(crate) preferred_workflow_snapshot: Option<&'a str>, - pub(crate) allow_unverified_codex: bool, } /// Multi-project local control-plane daemon request. @@ -219,7 +218,6 @@ pub(crate) struct ServeRequest<'a> { pub(crate) config_path: Option<&'a Path>, pub(crate) listen_address: &'a str, pub(crate) dev: bool, - pub(crate) allow_unverified_codex: bool, } /// Agent-readable runtime diagnosis request. @@ -360,7 +358,6 @@ struct RunCycleRequest<'a> { preferred_run_identity: Option>, preferred_retry_budget_base: Option, preferred_workflow_snapshot: Option<&'a str>, - allow_unverified_codex: bool, } struct SpawnRunOnceChildRequest<'a> { @@ -373,7 +370,6 @@ struct SpawnRunOnceChildRequest<'a> { preferred_attempt_number: i64, preferred_retry_budget_base: i64, workflow: &'a WorkflowDocument, - allow_unverified_codex: bool, issue_claim_handoff: Option<&'a File>, dispatch_slot_handoff: Option<&'a File>, dispatch_slot_index_handoff: Option, @@ -1433,7 +1429,6 @@ struct TargetIssueRunContext<'a, T> { dispatch_mode: IssueDispatchMode, preferred_run_identity: Option>, preferred_retry_budget_base: Option, - allow_unverified_codex: bool, } struct ConcurrencySnapshot { diff --git a/docs/index.md b/docs/index.md index a47bef8..4b12148 100644 --- a/docs/index.md +++ b/docs/index.md @@ -30,7 +30,7 @@ The split below is by question type, not by human-versus-agent audience. - Need runtime contracts, invariants, schemas, enums, state machines, or required behavior -> `docs/spec/` -- Need current Decodex/Codex app-server compatibility range or protocol support +- Need current Decodex/Codex app-server protocol support evidence -> `docs/spec/app-server.md` - Need Decodex operator lane-control capability support, including inspect, pause/resume, scan, interrupt, steer, retained retry/resume, manual attention, or diff --git a/docs/reference/operator-control-plane.md b/docs/reference/operator-control-plane.md index b105e43..26b4379 100644 --- a/docs/reference/operator-control-plane.md +++ b/docs/reference/operator-control-plane.md @@ -36,7 +36,7 @@ Decodex currently runs as a local, single-machine control plane: Decodex App is a native shell over the same local runtime and account-pool state. On launch it connects to an existing default local listener when one is reachable; if not, it starts the bundled `decodex` binary as -`decodex serve --allow-unverified-codex --listen-address 127.0.0.1:8192`. The app +`decodex serve --listen-address 127.0.0.1:8192`. The app fallback is a normal control-plane server: it loads the registered project registry, schedules only enabled projects, keeps active runtime DB-backed runs visible even when a project is disabled diff --git a/docs/spec/app-server.md b/docs/spec/app-server.md index 634392c..caaf7a5 100644 --- a/docs/spec/app-server.md +++ b/docs/spec/app-server.md @@ -35,11 +35,11 @@ codex app-server generate-json-schema --experimental --out target/decodex-app-se - `decodex` must treat the generated schema as more authoritative than stale handwritten assumptions. - `--experimental` is required when inspecting `dynamicTools` and related experimental fields in the generated bundle. -## Compatibility range +## Protocol support evidence -The current Decodex app-server support range is capability-gated rather than a broad -"latest Codex" promise. A Codex CLI or bundled Codex binary is inside the supported -range only when all of these are true: +The current Decodex app-server support contract is capability-gated rather than a broad +"latest Codex" promise. A Codex app-server surface is usable only when all of these +are true: - `codex app-server generate-json-schema --experimental` succeeds. - The generated schema contains the Decodex-owned request and notification contract in @@ -50,98 +50,33 @@ range only when all of these are true: - `decodex probe stdio://` completes the app-server capability preflight, standalone `command/exec` health check, and dynamic-tool round trip with `PROBE_OK`. -- The executable Decodex compatibility guard reports `compatibility=supported` for - the initialized app-server `userAgent` after the capability preflight succeeds. -Support has three distinct evidence layers: +Support evidence has two distinct layers: -- Exact-version support: the initialized app-server `userAgent` must parse to one of - the locally verified Codex CLI versions in the executable allowlist. This is the - only layer that can make the dispatch guard pass without the explicit dogfood - override described below. - Capability evidence: the bounded runtime preflight records the app-server methods - and inventories Decodex actually checked before the guard decision. This evidence - explains why the local runtime looked usable, but it does not authorize an - unlisted version. + and inventories Decodex actually checked before `thread/start` or `thread/resume`. + Any required capability failure is a pre-dispatch app-server preflight blocker + rather than a promptable agent turn. - Schema evidence: `decodex probe stdio://` regenerates the local schema cache and - records which required schema markers were checked. Retained dispatch records when - schema evidence was not checked in that dispatch path. Schema evidence is required - before expanding the allowlist, but it does not make an unsupported version pass by - itself. - -As of the 2026-06-03 self-compatibility pass, the verified local range is: - -| Codex surface | Version | Evidence | -| --- | --- | --- | -| `PATH` `codex` | `codex-cli 0.136.0` | Generated `--experimental` schema contains the required methods and fields; `decodex probe stdio://` returned `PROBE_OK`. | -| Codex Beta app bundled `codex` | `codex-cli 0.136.0-alpha.2` | Running `decodex probe stdio://` with the bundle resource directory first on `PATH` returned `PROBE_OK`. | -| Codex Desktop app-server | `codex-cli 0.137.0-alpha.4` | Generated `--experimental` schema contains the required schema bundle; `decodex probe stdio://` returned `PROBE_OK` with initialized `userAgent = "Codex Desktop/0.137.0-alpha.4 ..."`. | - -The same pass compared that range against upstream Codex: - -- GitHub release `rust-v0.136.0` is covered by the verified `PATH` `codex-cli 0.136.0` - probe above. -- Upstream `main` commits after `rust-v0.136.0` are outside the local support claim - until Radar review, schema regeneration, and `decodex probe stdio://` cover that - newer head or release. -- The checked-in upstream review queue generated on 2026-06-02 contained 40 queued - `openai/codex` subjects, including critical and high-priority app-server protocol, - plugin/tool metadata, sandbox/config, and release-packaging candidates. - Those queue entries are compatibility watch items, not adoption authorization. - -The previous 2026-05 local refresh covered `codex-cli 0.132.0-alpha.1` from `PATH` -and the Codex Beta app bundle's `codex-cli 0.131.0-alpha.9`. Treat those as historical -compatibility evidence, not the current upgrade target. - -`decodex probe stdio://` exposes the executable guard in its success line, including -`compatibility=supported`, `support_decision=supported_exact_version`, the observed -`codex_version`, the executable `supported_versions` list, `capability_evidence`, -`schema_evidence`, `schema_cache`, and `schema_marker_count`; the private preflight -report also records the full `schema_markers` list. During retained-lane dispatch, -the same compatibility check runs after the bounded capability preflight and before -`thread/start` or `thread/resume`; an app-server identity outside the locally -verified list is a pre-dispatch app-server preflight blocker rather than a promptable -agent turn. Unsupported newer versions must report a structured unsupported decision -such as `unsupported_unverified_version` or `unsupported_unparsed_user_agent`. -Operators may pass `--allow-unverified-codex` to `decodex run`, `decodex serve`, or -`decodex probe` when deliberately dogfooding a development Codex build. This changes -only the unsupported compatibility identity from a blocker to a warning with -`compatibility=unverified_allowed` and a support decision such as -`unverified_allowed_by_override`; all other capability preflight blockers remain -fail-closed. - -Current upstream Codex signals are beyond the local support claim whenever they are -newer than the latest locally probed version, or when checked-in Radar queue entries -flag app-server protocol, plugin metadata, dynamic tool, sandbox/config, GitHub/Linear -routing, or retained-lane lifecycle risk that has not yet been source-reviewed and -probed locally. In that case Decodex must not force an upgrade. It should keep running -the latest locally verified Codex surface, route the upstream change through Radar -review, regenerate the app-server schema, run `decodex probe stdio://`, and only then -promote the new Codex version or protocol shape into this compatibility range. -Latest upstream Codex remains unsupported until that promotion happens, even when a -local capability or schema check looks promising. - -To expand support for a new upstream app-server version: - -1. Install or select the target Codex binary locally without replacing the last known - verified runtime used by active lanes. + checks the required markers in this spec before completing the dynamic-tool round + trip. Normal retained dispatch does not regenerate the schema cache. + +`decodex probe stdio://` reports the probe result with `preflight_checks`, `thread`, +`turn`, `events`, and `output`. A passing probe must include `output=PROBE_OK`. + +To validate an upstream app-server protocol change: + +1. Install or select the target Codex binary locally without disrupting active lanes. 2. Run `codex app-server generate-json-schema --experimental --out - target/decodex-app-server-schema-check`. `decodex probe stdio://` uses the same - schema cache path, but an unlisted version must still fail the compatibility guard - until the allowlist is deliberately updated. + target/decodex-app-server-schema-check`. 3. Confirm the generated schema contains every required marker in this spec: `initialize`, `thread/start`, `thread/resume`, `turn/start`, `thread/archive`, `command/exec`, bounded preflight methods, `item/tool/call`, dynamic tool `namespace`, dynamic tool `deferLoading`, `inputText`, and `PluginListParams.marketplaceKinds`. -4. Update the executable allowlist locally and add or update compatibility tests for - the target exact version and nearby unsupported versions. -5. Run `decodex probe stdio://` and require `PROBE_OK`, `compatibility=supported`, - `support_decision=supported_exact_version`, `capability_evidence`, and - `schema_evidence=checked` for the target version. -6. Update this table in the same change as the executable allowlist and compatibility - tests. Do not document a new version as supported before the local guard and probe - output agree. +4. Run `decodex probe stdio://` and require `PROBE_OK`. +5. Update this spec or the runtime preflight only when the protocol shape or required + capability set changes. ## Implementation guidance diff --git a/docs/spec/index.md b/docs/spec/index.md index 087dd82..8a6965e 100644 --- a/docs/spec/index.md +++ b/docs/spec/index.md @@ -57,8 +57,7 @@ Then keep the body explicit: - [`runtime.md`](./runtime.md) defines the runtime state model, reconciliation rules, and tracker writeback boundaries. - [`app-server.md`](./app-server.md) defines the direct Codex `app-server` interaction - contract, current compatibility range, and protocol support evidence used by the - runtime. + contract and protocol support evidence used by the runtime. - [`lane-control.md`](./lane-control.md) defines the CLI/API-first operator lane-control capability matrix and the boundary between bottom-layer steer support and higher-level policy guardrails. diff --git a/docs/spec/runtime.md b/docs/spec/runtime.md index 0c21e31..8956f93 100644 --- a/docs/spec/runtime.md +++ b/docs/spec/runtime.md @@ -91,7 +91,7 @@ This boundary does not create a project-local runtime database contract. The run - Runtime policy decisions that depend on Codex behavior, such as idle timeout, stall thresholds, retry cutoffs, or liveness heuristics, must not be tuned from local Decodex observation alone. - For those decisions, use three inputs together: - the generated `codex app-server` schema for protocol shape and the current - compatibility range in [`app-server.md`](./app-server.md) + protocol support evidence in [`app-server.md`](./app-server.md) - live pilot telemetry for observed event cadence and failure modes - the relevant Codex or `app-server` implementation path for terminal semantics, waiting states, and progress signals - If those inputs disagree, treat the local implementation and generated schema as more authoritative than stale design assumptions. @@ -508,12 +508,9 @@ After a process restart, recent-run history, active lease ownership, retained po - When a queued retry becomes due, `decodex` must refresh that exact issue, redispatch it only if it is still active under retry policy, and otherwise release the queued claim. - Before a prepared lane starts `app-server`, `decodex` must refresh the selected issue once more and skip execution if the issue became terminal or otherwise ineligible. - After `app-server` initializes and before `thread/start` or `thread/resume`, `decodex` - must run the bounded app-server capability and compatibility preflight defined in + must run the bounded app-server capability preflight defined in [`app-server.md`](./app-server.md). Missing config/model/provider/skills/plugin/MCP - state, or an app-server identity outside the locally verified compatibility range, is - a pre-dispatch terminal blocker with an operator-readable error class, not a - promptable agent turn, unless the operator explicitly started `run`, `serve`, or - `probe` with `--allow-unverified-codex`. That override downgrades only the unverified - compatibility identity to a warning; capability failures still block dispatch. + state is a pre-dispatch terminal blocker with an operator-readable error class, not a + promptable agent turn. - If the local process crashed during a run, `decodex` must recover from the runtime database, current tracker cache or state, and retained worktree inspection. - If Linear shows a non-terminal state but no local lease exists, the issue may become eligible again after reconciliation or may be redispatched through the retained recovered worktree. diff --git a/plugins/decodex/skills/manual-cli/SKILL.md b/plugins/decodex/skills/manual-cli/SKILL.md index c5bf0fa..2402d04 100644 --- a/plugins/decodex/skills/manual-cli/SKILL.md +++ b/plugins/decodex/skills/manual-cli/SKILL.md @@ -136,9 +136,6 @@ Manual commit and landing are separate narrow workflows: - Use `run --dry-run` before live automation to validate project loading, issue discovery, eligibility, and worktree planning without tracker mutation. - Use `probe stdio://` before relying on the Codex app-server boundary. -- Use `--allow-unverified-codex` on `run`, `serve`, or `probe` only for deliberate - Codex development-version dogfooding. It turns an unsupported app-server identity - into a warning while keeping other preflight failures blocking. - Treat hidden `serve --dev` as isolated local-development infrastructure only. It serves dashboard, account, and app snapshot APIs, but it does not register projects, poll Linear, dispatch work, or accept `--config`. Decodex App's fallback server uses