diff --git a/dag/extdeps/shell/exec.dag b/dag/extdeps/shell/exec.dag index 7bd49a2a26d..13c939bd262 100644 --- a/dag/extdeps/shell/exec.dag +++ b/dag/extdeps/shell/exec.dag @@ -114,6 +114,22 @@ data shell_exec_transport_ceiling_authority: ByteSize = host_exec_arg_max_strlen // ShellSpawnRefused the streams are absent. A sibling row rather than a field added to RunArgv, // because adding it there would hand every existing RunArgv caller empty streams on a spawn // failure where today its evaluation stops. + +// RunArgvStdin completes the family Run / RunArgv / RunArgvOutcome along the one axis they +// vary on: whether the child receives stdin. Run is argv-fixed [bash, -s] with the payload on +// stdin (the heredoc path); RunArgv takes a caller-named program and argument vector but no +// stdin; this row is the missing cell -- the SAME direct-exec shape with a stdin payload. The +// survey that located the gap (2026-10-01, before minting): the argv-plus-stdin shape already +// exists tree-wide only as TOOL-FIXED rows -- jq.Process.RunWithStdin (program fixed "jq"), +// ssh.Session.ExecPortableWordsWithStdin (program fixed "ssh"), git.Plumbing.HashObjectWriteStdin +// (program fixed "git") -- each a different service owning its own tool. None admits a +// caller-named program, so a runtime whose executable arrives as projected data (the bundled +// Codex native binary) had no typed route and fell back to the retained heredoc; that fallback +// is what this row retires. Same output triple and no-success-field discipline as RunArgv (a +// nonzero exit is DATA, decoded by the caller's policy), same transport grammar the seed's +// dispatch_shell already realizes generically (it reads declared argv children and pipes a +// declared stdin payload; no seed-side change). + service shell.Exec { operation Run { input { script: TransportScript } @@ -153,6 +169,25 @@ service shell.Exec { nonzero => Unit } } + operation RunArgvStdin { + input { program: NonEmptyStr, arguments: ProcessArgvExpansion, stdin_payload: String } + + output { + exit_code: Int from "exit_code" + stdout: String from "stdout" + stderr: String from "stderr" + } + + transport shell { + argv: [program, arguments] + stdin: stdin_payload + } + + exit { + 0 => Unit + nonzero => Unit + } + } operation RunArgvOutcome { input { program: NonEmptyStr, arguments: ProcessArgvExpansion } diff --git a/dag/gunbc/codex_app_server_press.dag b/dag/gunbc/codex_app_server_press.dag index e270b7196ca..63eeb211f78 100644 --- a/dag/gunbc/codex_app_server_press.dag +++ b/dag/gunbc/codex_app_server_press.dag @@ -1,7 +1,5 @@ module gunbc.codex_app_server_press -import std.dissolution { DissolutionCondition, dissolution_description, unbound_dissolution } - import std.types { NonEmptyStr, String, FilePath, List, Bool, Int, EpochSecs } import std.content_hash { ContentHash, content_hash_of_value } import gunbc.provider_interface_binding { @@ -72,9 +70,15 @@ import extdeps.llm.codex_app_server { codex_structured_error_code_quota_exceeded, codex_structured_error_code_authentication_required, } -import gunbc.retained_shell_script { retained_foreign } import extdeps.shell import extdeps.shell.exec +import extdeps.tools.env { env_path_resolved_program, env_prefixed_args } +import v2.std.orchestration { EnvSet } +import v2.std.compilers.cli_surface { + ProcessArgvExpansion, + process_argv_expansion, + cli_surface_of_literal_words, +} import extdeps.languages.json.emit { JsonValue, JsonObject, JsonString, JsonNumber, JsonBool, JsonArray, JsonNull, JsonKeyValue, @@ -415,11 +419,12 @@ type CodexAccountStandingSummary { // std.process.ProcessExit (ExitSuccess | ExitFailure with code and reason) models CLI/program exit // semantics with a typed reason string. ProcessExitObservation in gunbc.provider_wire_evidence // models shell.Exec transport (success bool + optional exit_code from the host). -// ProviderProcessExited carries only the parsed sentinel exit code while -// ProviderProcessObservationFailed means the mux marker or exit observation was absent — a -// press-parse outcome, not a host refusal reason. Converge when shell→intent typed stdio transport -// lands (docs/plans/shell-intent-emit-realization-design.md) and replaces sentinel reparsing — this -// coproduct deletes with that scaffold. +// ProviderProcessExited carries the transport's declared exit_code; ObservationFailed means the +// press never reached a process observation at all (interface binding unavailable, bundled +// executable absent) — a press-arm outcome, not a host refusal reason. The sentinel-parse +// alternative this coproduct once named is gone: the account trip runs as a typed argv through +// shell.Exec.RunArgvStdin, whose exit_code is a declared output, never parsed back out of a +// stream. type ProviderProcessExit = ProviderProcessExited { code: Int } @@ -515,49 +520,39 @@ fn binding_from_initialize_session( } } -data codex_press_stdio_sentinel_mux_dissolve_note: DissolutionCondition = unbound_dissolution(description: "Interim stdout/stderr/exit capture muxes shell.Exec.Run output through literal ---GUNBC_EXIT---/---GUNBC_STDOUT---/---GUNBC_STDERR--- sentinels (codex_press_account_trip_script → split_gunbc_stdio_capture). Wrapped in retained_foreign with dissolve-on v2.workflow.bash_emit. The sentinel concat itself dissolves when shell→intent typed stdio transport replaces foreign script muxing (docs/plans/shell-intent-emit-realization-design.md Phase 2 host-effect route for runtime-present press sites).") - -fn codex_press_account_trip_script( +// THE ACCOUNT TRIP IS A TYPED PROCESS, NOT A SHELL SCRIPT. The deleted shape concat-built one +// bash script (codex_press_account_trip_script) that exported CODEX_HOME, wrote the four request +// lines to temp files with printf, ran `codex app-server --stdio` with its streams redirected to +// files, and muxed exit+stdout+stderr back through literal ---GUNBC_ sentinels that +// split_gunbc_stdio_capture re-parsed out of the combined stream — wrapped in retained_foreign +// with a dissolve-on. The whole carrier is deleted with its site, per its own +// codex_press_stdio_sentinel_mux_dissolve_note, which named exactly this replacement and is +// deleted beside the concat it described. The child now spawns through shell.Exec.RunArgvStdin: +// env(1) carries the CODEX_HOME binding as an argv element (extdeps.tools.env, the one env(1) +// authority), the four request lines ride stdin as data, and stdout, stderr and the exit code +// arrive as separate declared outputs — no script body, no temp file, no sentinel, and no +// quoting for a path to escape. +fn codex_press_account_trip_request_lines() -> String { + join( + [ + codex_app_server_initialize_request_line() as String, + codex_app_server_initialized_notification_line() as String, + codex_app_server_account_read_request_line() as String, + codex_app_server_rate_limits_read_request_line() as String, + ], + "\n", + ) + "\n" +} + +fn codex_press_account_trip_invocation( executable: FilePath, codex_home: FilePath, -) -> String { - concat( - "export CODEX_HOME='", - concat( - codex_home as String, - concat( - "'; REQ=$(mktemp); OUT=$(mktemp); ERR=$(mktemp); ", - concat( - "printf '%s\\n' '", - concat( - codex_app_server_initialize_request_line() as String, - concat( - "' >\"$REQ\"; printf '%s\\n' '", - concat( - codex_app_server_initialized_notification_line() as String, - concat( - "' >>\"$REQ\"; printf '%s\\n' '", - concat( - codex_app_server_account_read_request_line() as String, - concat( - "' >>\"$REQ\"; printf '%s\\n' '", - concat( - codex_app_server_rate_limits_read_request_line() as String, - concat( - "' >>\"$REQ\"; cat \"$REQ\" | '", - concat( - executable as String, - "' app-server --stdio >\"$OUT\" 2>\"$ERR\"; EC=$?; echo \"---GUNBC_EXIT---$EC---\"; echo '---GUNBC_STDOUT---'; cat \"$OUT\"; echo '---GUNBC_STDERR---'; cat \"$ERR\"; rm -f \"$REQ\" \"$OUT\" \"$ERR\"; exit \"$EC\"", - ), - ), - ), - ), - ), - ), - ), - ), - ), - ), +) -> ProcessArgvExpansion { + process_argv_expansion( + surface: cli_surface_of_literal_words( + words: env_prefixed_args( + bindings: [EnvSet { name: "CODEX_HOME", value: codex_home as String }], + command_argv: [executable as String, "app-server", "--stdio"], ), ), ) @@ -1511,61 +1506,6 @@ fn parse_account_trip_session( } } -type GunbcStdioCapture { - stdout: String - stderr: String - exit_code: Int? -} - -fn parse_gunbc_exit_marker(combined: String) -> Int? { - let parts = split(s: combined, delimiter: "---GUNBC_EXIT---") - match parts.skip(n: 1).first() { - Absent => none - Present { value: after } => { - let code_parts = split(s: after, delimiter: "---") - match code_parts.first() { - Absent => none - Present { value: code_s } => parse_int(s: code_s.trim()) - } - } - } -} - -fn split_gunbc_stdio_capture(combined: String) -> GunbcStdioCapture { - let exit_code = parse_gunbc_exit_marker(combined: combined) - let parts = split(s: combined, delimiter: "---GUNBC_STDOUT---") - match parts.skip(n: 1).first() { - Absent => - GunbcStdioCapture { stdout: combined, stderr: "", exit_code: exit_code } - Present { value: after_marker } => { - let err_parts = split(s: after_marker, delimiter: "---GUNBC_STDERR---") - match err_parts.first() { - Absent => - GunbcStdioCapture { - stdout: after_marker.trim(), - stderr: "", - exit_code: exit_code, - } - Present { value: out } => - match err_parts.skip(n: 1).first() { - Absent => - GunbcStdioCapture { - stdout: out.trim(), - stderr: "", - exit_code: exit_code, - } - Present { value: err } => - GunbcStdioCapture { - stdout: out.trim(), - stderr: err.trim(), - exit_code: exit_code, - } - } - } - } - } -} - fn preflight_subject_from_bundle( bundle: CodexRuntimeBundle, binding: ProviderInterfaceBinding, @@ -1633,32 +1573,6 @@ fn parse_account_trip_stdout_for_tests(stdout: String) -> CodexAccountStandingEv ) } -fn process_receipt_from_capture( - capture: GunbcStdioCapture, - wire: ProviderWireEvidenceReceipt?, - run_success: Bool, -) -> ProviderProcessReceipt { - match capture.exit_code { - Present { value: code } => - ProviderProcessReceipt { - exit: ProviderProcessExited { code: code }, - wire: wire, - } - Absent => - if run_success { - ProviderProcessReceipt { - exit: ProviderProcessObservationFailed, - wire: wire, - } - } else { - ProviderProcessReceipt { - exit: ProviderProcessObservationFailed, - wire: wire, - } - } - } -} - fn observe_codex_account_preflight( bundle: CodexRuntimeBundle, probe_key: ProviderProbeKey, @@ -1685,30 +1599,28 @@ fn observe_codex_account_preflight( }, } } else { - let script = retained_foreign( - body: codex_press_account_trip_script( + let run = shell.Exec.RunArgvStdin( + program: env_path_resolved_program().invocation, + arguments: codex_press_account_trip_invocation( executable: projected.executable.path, codex_home: codex_home, ), - reason: "Codex app-server held-open account session; exit+stdout/stderr captured for typed NDJSON id correlation" as NonEmptyStr, + stdin_payload: codex_press_account_trip_request_lines(), ) - let run = shell.Exec.Run(script: script) - let capture = split_gunbc_stdio_capture(combined: run.stdout) let wire_capture = retain_provider_wire_capture( evidence_root: evidence_root, - stdout: capture.stdout, - stderr: capture.stderr, - success: run.success, - exit_code: capture.exit_code, + stdout: run.stdout, + stderr: run.stderr, + success: run.exit_code == 0, + exit_code: Present { value: run.exit_code }, ) - let process = process_receipt_from_capture( - capture: capture, + let process = ProviderProcessReceipt { + exit: ProviderProcessExited { code: run.exit_code }, wire: match wire_capture { ProviderWireCaptureOk { receipt: wire } => Present { value: wire } ProviderWireCaptureRefused { cause: _ } => none }, - run_success: run.success, - ) + } match wire_capture { ProviderWireCaptureRefused { cause: reason } => CodexPressUnavailable { @@ -1717,8 +1629,8 @@ fn observe_codex_account_preflight( } ProviderWireCaptureOk { receipt: wire } => { let evidence = parse_account_trip_session( - stdout: capture.stdout, - stderr: capture.stderr, + stdout: run.stdout, + stderr: run.stderr, evidence_root: evidence_root, mode: AccountTripDigestRetainPrivateSha512, ) @@ -1741,21 +1653,13 @@ fn observe_codex_account_preflight( wire: Present { value: wire }, }, } - match capture.exit_code { - Present { value: code } => - if code != 0 { - CodexPressUnavailable { - reason: "codex app-server process exited nonzero" as NonEmptyStr, - process: receipt.process, - } - } else { - outcome_from_standing_receipt(receipt: receipt) - } - Absent => - CodexPressUnavailable { - reason: "codex app-server process exit observation refused" as NonEmptyStr, - process: receipt.process, - } + if run.exit_code != 0 { + CodexPressUnavailable { + reason: "codex app-server process exited nonzero" as NonEmptyStr, + process: receipt.process, + } + } else { + outcome_from_standing_receipt(receipt: receipt) } } } diff --git a/dag/test/claim/codex_app_server_press_witness_test.dag b/dag/test/claim/codex_app_server_press_witness_test.dag index 348fdcf210b..4d0ca454833 100644 --- a/dag/test/claim/codex_app_server_press_witness_test.dag +++ b/dag/test/claim/codex_app_server_press_witness_test.dag @@ -1,6 +1,13 @@ module test.claim.codex_app_server_press_witness_test import std.types { Bool, String, NonEmptyStr, FilePath } +import v2.std.algebra { filter, list_flat_map } +import v2.std.compilers.cli_surface { + process_argv_expansion_surface, + cli_surface_arguments, + cli_argument_text, +} +import extdeps.tools.env { env_path_resolved_program } import std.content_hash { sha512_hex_digest, Sha512Digest, @@ -25,6 +32,8 @@ import gunbc.codex_app_server_press { AccountTripNativeCauseNumericCode, codex_press_method_itinerary, codex_press_account_trip_methods, + codex_press_account_trip_invocation, + codex_press_account_trip_request_lines, codex_press_outcome_wire_status, codex_press_outcome_operator_caption, parse_account_trip_stdout_for_tests, @@ -677,3 +686,97 @@ test fn structured_rpc_error_with_an_integer_numeric_code_is_a_numeric_code() -> _ => false } } + +// THE ACCOUNT-TRIP TRANSPORT SURFACE CARRIES NO SHELL CONTROL WORD. The account trip reaches the +// codex app-server through a transport; this claim projects that transport's surface as text and +// asserts it contains no shell programming word: no `export`, no command substitution `$(`, no +// `mktemp`, no `printf`, no `---GUNBC_` sentinel, no `rm -f`. The property is identity-stable +// across the shell-to-dag migration (docs/plans/shell-to-dag-residual-census-and-arc-completion.md +// 4): on the retained-heredoc side the surface WAS the script body `codex_press_account_trip_script` +// assembled, and that body was exactly the forbidden words -- the claim failed and was enrolled +// expected-red in v2.workflow.floor_expected_red while the script was the transport (receipt in +// that commit: FAIL here, the 24 pre-existing claims PASS). The migration landed: the trip runs as +// a typed argv through shell.Exec.RunArgvStdin -- program word (env(1)), env-prefixed argument +// words, and a stdin payload of request lines -- the projection below names that invocation, the +// claim passes, and the roster row removed itself by the roster's own stale-quarantine arm. Only +// the SURFACE PROJECTION changed with the transport; the property and the forbidden-word list did +// not. +data codex_account_trip_forbidden_shell_words: List = [ + "export ", "$(", "mktemp", "printf", "---GUNBC_", "rm -f", +] + +fn codex_account_trip_transport_surface_text() -> String { + let invocation = codex_press_account_trip_invocation( + executable: "/usr/local/bin/codex" as FilePath, + codex_home: "/home/witness/.codex" as FilePath, + ) + let argument_words: List = list_flat_map( + xs: cli_surface_arguments(surface: process_argv_expansion_surface(expansion: invocation)), + f: fn(argument) { [cli_argument_text(argument: argument)] }, + ) + join( + concat( + concat([env_path_resolved_program().invocation as String], argument_words), + [codex_press_account_trip_request_lines()], + ), + "\n", + ) +} + +test fn codex_account_trip_transport_surface_carries_no_shell_control_word() -> Bool { + let surface = codex_account_trip_transport_surface_text() + count( + filter( + xs: codex_account_trip_forbidden_shell_words, + predicate: fn(word) { string_contains(s: surface, pattern: word) }, + ), + ) == 0 +} + +// POSITIVE CONTROL: the typed invocation preserves what the script did, not just what it stopped +// doing. env(1) carries the CODEX_HOME binding as an argv element (never quoted into a script), +// the child is the projected executable invoked `app-server --stdio`, and the stdin payload is +// four request lines each newline-terminated -- initialize first, the account rate-limits read +// last, and no turn/start anywhere (the account trip itinerary has three steps without it, which +// press_account_trip_itinerary_is_three_steps_without_turn_start pins at the method grain). +test fn codex_account_trip_typed_invocation_preserves_trip_semantics() -> Bool { + let invocation = codex_press_account_trip_invocation( + executable: "/usr/local/bin/codex" as FilePath, + codex_home: "/home/witness/.codex" as FilePath, + ) + let words: List = list_flat_map( + xs: cli_surface_arguments(surface: process_argv_expansion_surface(expansion: invocation)), + f: fn(argument) { [cli_argument_text(argument: argument)] }, + ) + let stdin = codex_press_account_trip_request_lines() + count(words) == 4 + && match words.first() { + Present { value: binding } => binding == "CODEX_HOME=/home/witness/.codex" + Absent => false + } + && match words.skip(n: 1).first() { + Present { value: executable } => executable == "/usr/local/bin/codex" + Absent => false + } + && match words.skip(n: 2).first() { + Present { value: subcommand } => subcommand == "app-server" + Absent => false + } + && match words.last() { + Present { value: flag } => flag == "--stdio" + Absent => false + } + && count(split(s: stdin, delimiter: "\n")) == 5 + && string_contains( + s: stdin, + pattern: codex_app_server_method_wire_name(method: CodexAppServerInitialize) as String, + ) + && string_contains( + s: stdin, + pattern: codex_app_server_method_wire_name(method: CodexAppServerAccountRateLimitsRead) as String, + ) + && !string_contains( + s: stdin, + pattern: codex_app_server_method_wire_name(method: CodexAppServerTurnStart) as String, + ) +} diff --git a/src/v2/std/compilers/cli_surface.dag b/src/v2/std/compilers/cli_surface.dag index 8547b6f3dbe..a679e64a19e 100644 --- a/src/v2/std/compilers/cli_surface.dag +++ b/src/v2/std/compilers/cli_surface.dag @@ -130,9 +130,13 @@ type CliSurface sole_constructor { arguments: List } // be one import away from being routed around: not defeated, but satisfied by declaring an intent // nobody checked was true. // -// The admitted population is the two runner entry points that genuinely hold runtime words -- a -// filesystem path, a hostname -- and it is enumerable at TWO today, which is what makes the -// restriction honest rather than aspirational. Adding a third is an edit to this list in the +// The admitted population is the call sites that genuinely hold runtime words -- a filesystem +// path, a hostname -- the gunbc.command_runner runner entry points and, added 2026-10-02 +// (session/witty-wren-554), gunbc.codex_app_server_press.codex_press_account_trip_invocation, +// which holds the bundled Codex executable's projected path and the CODEX_HOME binding as argv +// words riding shell.Exec.RunArgvStdin. It is enumerable on the fingers of one hand today, which +// is what makes the restriction honest rather than aspirational. Adding another is an edit to +// this list in the // declaring module: a conspicuous, counted review event rather than an import written elsewhere. // This mechanism is verified to enforce at fn grain (a refused call names the caller and lists the // roster); it is NOT verified at operation grain, which is a separate filed defect and does not @@ -142,6 +146,10 @@ fn cli_surface_of_literal_words(words: List) -> CliSurface decl_ref(module_path: "gunbc.command_runner", decl_name: "run_shell_command"), decl_ref(module_path: "gunbc.command_runner", decl_name: "run_shell_command_observe"), decl_ref(module_path: "gunbc.command_runner", decl_name: "run_shell_command_observe_outcome"), + decl_ref( + module_path: "gunbc.codex_app_server_press", + decl_name: "codex_press_account_trip_invocation", + ), ] = CliSurface { arguments: fold_list( diff --git a/src/v2/workflow/floor_expected_red.dag b/src/v2/workflow/floor_expected_red.dag index f558cca1515..c83a7d73849 100644 --- a/src/v2/workflow/floor_expected_red.dag +++ b/src/v2/workflow/floor_expected_red.dag @@ -1160,7 +1160,7 @@ fn floor_expected_red_chunk_emitted_add_algebra_ref_ungrounded() -> List } fn floor_expected_red_chunks() -> List> { - Cons { head: floor_expected_red_chunk_00(), tail: Cons { head: floor_expected_red_chunk_loaded_carrier_body_receipt(), tail: Cons { head: floor_expected_red_chunk_02(), tail: Cons { head: floor_expected_red_chunk_03(), tail: Cons { head: floor_expected_red_chunk_04(), tail: Cons { head: floor_expected_red_chunk_05(), tail: Cons { head: floor_expected_red_chunk_06(), tail: Cons { head: floor_expected_red_chunk_07(), tail: Cons { head: floor_expected_red_chunk_08(), tail: Cons { head: floor_expected_red_chunk_09(), tail: Cons { head: floor_expected_red_chunk_10(), tail: Cons { head: floor_expected_red_chunk_11(), tail: Cons { head: floor_expected_red_chunk_12(), tail: Cons { head: floor_expected_red_chunk_13(), tail: Cons { head: floor_expected_red_chunk_14(), tail: Cons { head: floor_expected_red_chunk_15(), tail: Cons { head: floor_expected_red_chunk_16(), tail: Cons { head: floor_expected_red_chunk_17(), tail: Cons { head: floor_expected_red_chunk_18(), tail: Cons { head: floor_expected_red_chunk_19(), tail: Cons { head: floor_expected_red_chunk_20(), tail: Cons { head: floor_expected_red_chunk_21(), tail: Cons { head: floor_expected_red_chunk_24(), tail: Cons { head: floor_expected_red_chunk_interpreter_first_optional_divergence(), tail: Cons { head: floor_expected_red_chunk_disjoint_refinement_chains(), tail: Cons { head: floor_expected_red_chunk_live_tree_admission(), tail: Cons { head: floor_expected_red_chunk_emitted_add_algebra_ref_ungrounded(), tail: Empty {} } } } } } } } } } } } } } } } } } } } } } } } } } } } + Cons { head: floor_expected_red_chunk_00(), tail: Cons { head: floor_expected_red_chunk_loaded_carrier_body_receipt(), tail: Cons { head: floor_expected_red_chunk_02(), tail: Cons { head: floor_expected_red_chunk_03(), tail: Cons { head: floor_expected_red_chunk_04(), tail: Cons { head: floor_expected_red_chunk_05(), tail: Cons { head: floor_expected_red_chunk_06(), tail: Cons { head: floor_expected_red_chunk_07(), tail: Cons { head: floor_expected_red_chunk_08(), tail: Cons { head: floor_expected_red_chunk_09(), tail: Cons { head: floor_expected_red_chunk_10(), tail: Cons { head: floor_expected_red_chunk_11(), tail: Cons { head: floor_expected_red_chunk_12(), tail: Cons { head: floor_expected_red_chunk_13(), tail: Cons { head: floor_expected_red_chunk_14(), tail: Cons { head: floor_expected_red_chunk_15(), tail: Cons { head: floor_expected_red_chunk_16(), tail: Cons { head: floor_expected_red_chunk_17(), tail: Cons { head: floor_expected_red_chunk_18(), tail: Cons { head: floor_expected_red_chunk_19(), tail: Cons { head: floor_expected_red_chunk_20(), tail: Cons { head: floor_expected_red_chunk_21(), tail: Cons { head: floor_expected_red_chunk_24(), tail: Cons { head: floor_expected_red_chunk_interpreter_first_optional_divergence(), tail: Cons { head: floor_expected_red_chunk_disjoint_refinement_chains(), tail: Cons { head: floor_expected_red_chunk_live_tree_admission(), tail: Cons { head: floor_expected_red_chunk_emitted_add_algebra_ref_ungrounded(), tail: Empty {} } } } } } } } } } } } } } } } } } } } } } } } } } } } } // Mock-totality's 21 expected-red rows were stale-quarantined by the explicit-import closure in