You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Investigation: Codex Pipeline DOA Bypass via _is_vacuous_gate Reachability Blind Spot
Date: 2026-06-30 Scope: Why Codex+implementation pipeline passes dispatch_infeasible admission control and runs 21 minutes of irreversible side effects before dying at gate_backend_write, despite PR #4094 and #4130 adding capability admission control. Mode: Deep Analysis (2 exploration batches + adversarial challenge + post-report validation; 9 subagents)
Summary
The Codex+implementation pipeline bypasses admission control because _is_vacuous_gate (_recipe_composition.py:178-213, introduced by PR #4130 on June 27) incorrectly declares gate_backend_write as "vacuous" — concluding that since all guarded steps were pruned, the gate has nothing left to guard and should not trigger dispatch_infeasible. But _is_vacuous_gate only checks whether surviving steps use the capability ingredient in their with_args; it does not check whether the gate step itself remains reachable in the post-prune flow graph. After _prune_skipped_steps prunes implement, route-repair redirects create_impl_worktree.on_success from implement to gate_backend_write, making the gate directly reachable. The pipeline is dispatched, runs 11 side-effecting steps (~21 minutes), then gate_backend_write fires and routes to release_issue_failure — exactly the DOA outcome that admission control was designed to prevent.
This is the third oscillation of a pendulum pattern: PR #4094 blocks DOA pipelines → PR #4130 unblocks (vacuous-gate exemption goes too far) → today's failure. The structural cause is that admission control operates on heuristic inference (pattern-matching callables against registries) rather than author-declared intent (the purpose_critical step annotation proposed in PR #4094's REQ-SCHEMA-001, which was never implemented).
Root Cause
Primary (architectural):_is_vacuous_gate at _recipe_composition.py:178-213 has a reachability blind spot. It determines vacuity by checking two conditions: (1) at least one pre-prune step with a matching skip_when_false guard was pruned (pruned_for_capability), and (2) no surviving step (other than the gate itself) references the capability ingredient in with_args (live_uses_capability). Both conditions are met for Codex+implementation. But the function never checks whether the gate step is reachable from any surviving step via routing fields (on_success, on_failure, on_result, etc.). After route-repair, create_impl_worktree.on_success points directly to gate_backend_write. The gate is reachable, will execute, and will deterministically fail — but _is_vacuous_gate returns True. [SUPPORTED]
Proximate (why the reachability check is missing): PR #4130 was solving a real false-positive problem — #4094's admission control blocked all Codex+implementation runs even though the pruned recipe's gate was intended to be a defense-in-depth terminal. The vacuous-gate concept is architecturally correct (a gate CAN be genuinely unreachable), but the implementation checks the wrong predicate. It asks "does any surviving step consume the capability?" when it should also ask "is the gate step itself reachable from the entry point?" [SUPPORTED]
Why prior fixes did not address this: The fix chain follows a pendulum pattern:
Today (June 30) — pipeline passes admission and wastes 21 minutes before failing
The purpose_critical per-step annotation proposed in PR #4094's design (REQ-SCHEMA-001) was never implemented. The system uses inference-based heuristics layered on inference-based heuristics, each layer correcting the prior while introducing new failure surface. [SUPPORTED]
src/autoskillit/server/tools/tools_kitchen.py:844-847 — open_kitchen feasibility check: passes because dispatch_feasible=True [SUPPORTED]
src/autoskillit/recipes/implementation.yaml:298-365 — create_impl_worktree (no guard) → implement (guarded, line 328) → gate_backend_write (no guard) topology [SUPPORTED]
src/autoskillit/recipes/remediation.yaml:335-405 — Same topology (create_impl_worktree at 335, implement at 347, gate_backend_write at 396-405) [SUPPORTED]
src/autoskillit/recipes/implementation-groups.yaml:261-323 — Same topology [SUPPORTED]
src/autoskillit/recipe/_analysis_bfs.py:17,58 — bfs_reachable() and _build_step_graph() — existing reachability machinery not used by _is_vacuous_gate [SUPPORTED]
_session_overrides.update(_backend_capability_overrides(...)) injects "false" (line 659); _promote_capability_keys(_config_layer, _session_overrides) copies it into config-authoritative layer so it wins the merge (line 661); merged at line 662
tests/recipe/test_bundled_recipes_dispatch_ready.py:268-282 — function named test_codex_implementation_dispatch_infeasible but asserts dispatch_feasible is True (name-assertion mismatch from pendulum) [SUPPORTED]
tests/recipe/test_bundled_recipes_dispatch_ready.py:329-350 — test_codex_capability_gate_recipes uses disjunctive assertion accepting EITHER True OR False — cannot catch a new class of regression [SUPPORTED]
No end-to-end test simulates a full Codex+implementation pipeline (open_kitchen → execute steps → verify side-effect prevention). All tests are layer-local. [SUPPORTED]
Similar Patterns
_check_dispatch_feasibility (_preflight.py:34-114) solves the analogous problem for run_skill steps — iterates steps, checks per-step provider profile, skips steps routed to a capable backend. Same pattern needed for run_python capability gates. [SUPPORTED]
The create_impl_worktree step executes at the orchestrator/host level (unsandboxed run_cmd), not inside a Codex session. All host-side git operations succeed on Codex — the irreducible gap is only implement committing inside the sandbox. [SUPPORTED]
Design Intent Findings
_is_vacuous_gate (PR [FIX] Dispatch Feasibility Immunity — Capability-Aware Admission Control #4130, commit 1112c0f3f, 2026-06-27): Introduced to fix false-positive blocking from PR [FIX] Rectify: Capability Admission Control at Recipe Load — Refuse DOA Pipelines #4094. Commit message: "A gate step (gate_backend_write) that guards already-pruned operations survives pruning and blocks the entire recipe. ... Three independent subsystems — step pruning, capability feasibility, and per-step backend override — operate without cross-communication." The function's purpose is to detect genuinely unreachable gates and exempt them from infeasibility. The purpose is correct; the reachability predicate is incomplete. [SUPPORTED]
_compute_capability_feasibility (PR [FIX] Rectify: Capability Admission Control at Recipe Load — Refuse DOA Pipelines #4094, commit cfb6ecc51, 2026-06-12): Introduced as the "definitive" admission control after 84+ downstream fixes. PR body: "84 [FIX] commits over ~3 weeks have patched individual links in the capability injection chain without closing the architectural gap." REQ-SCHEMA-001 proposed a purpose_critical step annotation; it was never implemented — the heuristic approach was substituted. [SUPPORTED]
Prior investigations directly on this topic:investigation_codex_gate_backend_write_recurrence_2026-06-12_083200.md (June 12, deep analysis, 9 subagents), investigation_codex_dispatch_infeasible_2026-06-12_151200.md (June 12, deep analysis). Both identified the root cause correctly but proposed fixes that were either not implemented as designed (purpose_critical) or introduced new gaps (vacuous-gate).
This is a recurring pattern — consider running /rectify for architectural immunity after resolving the immediate issue.
External Research
Codex CLI workspace-write sandbox protects .git/ as read-only by design (Linux landlock/bwrap). Headless codex exec cannot use --sandbox danger-full-access with approval_policy=never. This constraint is structural and unfixable from AutoSkillit's side. Source: openai.com/codex/concepts/sandboxing, openai/codex PR #19852. [SUPPORTED]
Aider performs all git operations host-side (LLM never touches .git/). AutoSkillit's static capability declaration is the most robust multi-backend pattern but is consumed only mid-pipeline, not at admission with correct reachability. [SUPPORTED]
Scope Boundary
Investigated:_is_vacuous_gate logic and its reachability blind spot; all three affected recipes (implementation, remediation, implementation-groups); _prune_skipped_steps route-repair mechanism; _compute_capability_feasibility full logic; all five admission-control enforcement surfaces (normal open_kitchen, deferred open_kitchen, get_recipe, load_recipe, dispatch_food_truck); 16-commit fix chain; installed vs. repo version parity; existing BFS reachability machinery; adversarial challenge of root cause (7 counterhypotheses tested, all refuted).
Not yet explored: Whether load_recipe soft enforcement (sets flag but doesn't early-return at tools_recipe.py:240-245) is a latent exploit path; whether purpose_critical annotation is still the correct long-term fix or whether reachability alone is sufficient; exact GitHub fail-label issue count attributable to this family; whether research/merge-prs recipes silently degrade on Codex (no gate step, so no DOA signal).
Recommendations
Single recommendation: Add post-prune reachability check to _is_vacuous_gate.
_is_vacuous_gate (_recipe_composition.py:178-213) must verify that the gate step is NOT reachable from any surviving step before declaring it vacuous. The check should use the existing _collect_all_route_targets helper (same file, line 25) which already covers all routing fields (on_success, on_failure, on_context_limit, on_rate_limit, on_exhausted, on_result conditions).
After the for cap_key in gate_input_keys loop completes (line 212 is inside the loop body), at the same indentation level as return True (line 213):
Scan post_prune_steps for any step (other than the gate itself) whose route targets include gate_step_name
If found, return False — the gate is reachable and therefore not vacuous
This leverages existing infrastructure (_collect_all_route_targets at line 25) and adds a single structural check to close the reachability gap. No schema changes, no new registries, no heuristic layers.
Add an end-to-end test that verifies Codex+implementation is rejected pre-claim (not just at load_and_validate level, but at open_kitchen/dispatch_food_truck level)
Harden load_recipe enforcement at tools_recipe.py:240-245 from soft (flag-only) to hard (early-return refusal)
Add purpose_critical annotation (REQ-SCHEMA-001) — killed as immediate fix: correct long-term but scope is too large for the acute issue; reachability check is sufficient and composable with purpose_critical if added later
Add skip_when_false to create_impl_worktree — killed: create_impl_worktree correctly executes at host level (unsandboxed); blocking it blocks the worktree for non-gate uses
Hand-key backend_supports_git_write check in _check_dispatch_feasibility — killed: same rot class as prior fixes; does not generalize to next capability
Breakage Analysis
Recommendation: Add post-prune reachability check to _is_vacuous_gate
Breakage surface: Tests pinning dispatch_feasible=True for Codex+implementation: test_backend_ingredient_injection.py:554-564, test_bundled_recipes_dispatch_ready.py:268-282, test_capability_admission_e2e.py (vacuous-gate test). All must be updated to assert dispatch_feasible=False and infeasible_steps=["gate_backend_write"].
Prior reverts: None found on admission control or vacuous-gate logic (git log --grep="revert" -i)
Downstream contract violations: None — gate_backend_write step is retained as defense-in-depth; skip_when_false pruning unchanged; research/merge-prs recipes unaffected (no gate_backend_write step). Fleet dispatch gains earlier refusal. ingredients_only=true discovery flow is unaffected (infeasibility is on the recipe, not the ingredients).
Implementation sites easy to miss: The deferred-recall open_kitchen path (tools_kitchen.py:731-734) is a second enforcement site; load_recipe (tools_recipe.py:240-245) needs hard enforcement upgrade; fleet dispatch_food_truck (tools_fleet_dispatch.py:340-351) already checks dispatch_feasible.
Risk level: LOW — concentrated change in one function (_is_vacuous_gate), test inversions, and one soft→hard enforcement upgrade in load_recipe.
Confidence Levels
Finding
Confidence
_is_vacuous_gate missing reachability check is the root cause
SUPPORTED
Route-repair makes gate_backend_write reachable via create_impl_worktree
SUPPORTED
All three gate recipes (implementation, remediation, implementation-groups) affected
SUPPORTED
Existing BFS machinery can be reused for the fix
SUPPORTED
No version skew — installed 0.10.831 == repo HEAD
SUPPORTED
PR #4130 introduced the regression; PR #4094 was correct but over-blocked
SUPPORTED
Pendulum pattern across the fix chain
SUPPORTED
purpose_critical annotation (REQ-SCHEMA-001) never implemented
SUPPORTED
Tests pin the bug as correct behavior
SUPPORTED
Adversarial challenge: 7 counterhypotheses tested, all refuted
SUPPORTED
load_recipe soft enforcement is a latent gap
NEEDS-EVIDENCE (structural observation; no observed exploit)
Research-family recipe silent degradation on Codex
NEEDS-EVIDENCE (no gate step = no DOA signal; no incident observed)
Investigation: Codex Pipeline DOA Bypass via _is_vacuous_gate Reachability Blind Spot
Date: 2026-06-30
Scope: Why Codex+implementation pipeline passes dispatch_infeasible admission control and runs 21 minutes of irreversible side effects before dying at gate_backend_write, despite PR #4094 and #4130 adding capability admission control.
Mode: Deep Analysis (2 exploration batches + adversarial challenge + post-report validation; 9 subagents)
Summary
The Codex+implementation pipeline bypasses admission control because
_is_vacuous_gate(_recipe_composition.py:178-213, introduced by PR #4130 on June 27) incorrectly declaresgate_backend_writeas "vacuous" — concluding that since all guarded steps were pruned, the gate has nothing left to guard and should not triggerdispatch_infeasible. But_is_vacuous_gateonly checks whether surviving steps use the capability ingredient in theirwith_args; it does not check whether the gate step itself remains reachable in the post-prune flow graph. After_prune_skipped_stepsprunesimplement, route-repair redirectscreate_impl_worktree.on_successfromimplementtogate_backend_write, making the gate directly reachable. The pipeline is dispatched, runs 11 side-effecting steps (~21 minutes), thengate_backend_writefires and routes torelease_issue_failure— exactly the DOA outcome that admission control was designed to prevent.This is the third oscillation of a pendulum pattern: PR #4094 blocks DOA pipelines → PR #4130 unblocks (vacuous-gate exemption goes too far) → today's failure. The structural cause is that admission control operates on heuristic inference (pattern-matching callables against registries) rather than author-declared intent (the
purpose_criticalstep annotation proposed in PR #4094's REQ-SCHEMA-001, which was never implemented).Root Cause
Primary (architectural):
_is_vacuous_gateat_recipe_composition.py:178-213has a reachability blind spot. It determines vacuity by checking two conditions: (1) at least one pre-prune step with a matchingskip_when_falseguard was pruned (pruned_for_capability), and (2) no surviving step (other than the gate itself) references the capability ingredient inwith_args(live_uses_capability). Both conditions are met for Codex+implementation. But the function never checks whether the gate step is reachable from any surviving step via routing fields (on_success,on_failure,on_result, etc.). After route-repair,create_impl_worktree.on_successpoints directly togate_backend_write. The gate is reachable, will execute, and will deterministically fail — but_is_vacuous_gatereturnsTrue. [SUPPORTED]Proximate (why the reachability check is missing): PR #4130 was solving a real false-positive problem — #4094's admission control blocked all Codex+implementation runs even though the pruned recipe's gate was intended to be a defense-in-depth terminal. The vacuous-gate concept is architecturally correct (a gate CAN be genuinely unreachable), but the implementation checks the wrong predicate. It asks "does any surviving step consume the capability?" when it should also ask "is the gate step itself reachable from the entry point?" [SUPPORTED]
Why prior fixes did not address this: The fix chain follows a pendulum pattern:
The
purpose_criticalper-step annotation proposed in PR #4094's design (REQ-SCHEMA-001) was never implemented. The system uses inference-based heuristics layered on inference-based heuristics, each layer correcting the prior while introducing new failure surface. [SUPPORTED]Affected Components
src/autoskillit/recipe/_recipe_composition.py:178-213—_is_vacuous_gate(): missing reachability check [SUPPORTED]src/autoskillit/recipe/_recipe_composition.py:109-175—_compute_capability_feasibility(): calls_is_vacuous_gateand trusts its result [SUPPORTED]src/autoskillit/recipe/_recipe_composition.py:401-524—_prune_skipped_steps()(function); route-repair logic at lines 460-520 redirectscreate_impl_worktree.on_successtogate_backend_write[SUPPORTED]src/autoskillit/recipe/_api.py:405-412—load_and_validate(): calls_compute_capability_feasibility, propagatesdispatch_feasible=True[SUPPORTED]src/autoskillit/server/tools/tools_kitchen.py:844-847—open_kitchenfeasibility check: passes becausedispatch_feasible=True[SUPPORTED]src/autoskillit/recipes/implementation.yaml:298-365—create_impl_worktree(no guard) →implement(guarded, line 328) →gate_backend_write(no guard) topology [SUPPORTED]src/autoskillit/recipes/remediation.yaml:335-405— Same topology (create_impl_worktreeat 335,implementat 347,gate_backend_writeat 396-405) [SUPPORTED]src/autoskillit/recipes/implementation-groups.yaml:261-323— Same topology [SUPPORTED]src/autoskillit/recipe/_analysis_bfs.py:17,58—bfs_reachable()and_build_step_graph()— existing reachability machinery not used by_is_vacuous_gate[SUPPORTED]src/autoskillit/core/types/_type_constants.py:95-118—BACKEND_CAPABILITY_INGREDIENTS,CAPABILITY_GATE_CALLABLES,CAPABILITY_INGREDIENT_TO_SKIP_GUARDregistries [SUPPORTED]Data Flow
AUTOSKILLIT_FEATURES__CODEX_BACKEND=true AUTOSKILLIT_AGENT_BACKEND__BACKEND=codex autoskillit orderget_backend("codex")→CodexBackend→capabilities.git_metadata_writable=False(codex.py:594)open_kitchen(name="implementation")→_backend_capability_overrides()→{"backend_supports_git_write": "false"}(_auto_overrides.py:27-28)_session_overrides.update(_backend_capability_overrides(...))injects"false"(line 659);_promote_capability_keys(_config_layer, _session_overrides)copies it into config-authoritative layer so it wins the merge (line 661); merged at line 662load_and_validate("implementation", ingredient_overrides={"backend_supports_git_write": "false"})→_prune_skipped_steps():implement(line 328:skip_when_false: inputs.backend_supports_git_write)create_impl_worktree.on_successredirected fromimplement→gate_backend_write(lines 464-465, 484-485)retry_worktree,fix,merge_gate_fix,rebase_conflict_fix,resolve_review,resolve_pre_review_conflicts— same guard_compute_capability_feasibility()evaluates survivinggate_backend_write:tool=run_python✓, callable inCAPABILITY_GATE_CALLABLES✓,all_falsy=True✓,on_exhausted="escalate"(schema default) ✓_is_vacuous_gate():pruned_for_capability=True(implement pruned),live_uses_capability=False(no surviving step hasbackend_supports_git_writein with_args) → returnsTrueinfeasible→dispatch_feasible=Trueopen_kitchenreturnssuccess=True→ kitchen gate opens → orchestrator starts executingbootstrap_clone(~3s),claim_and_resolve_issue(~3s),create_and_publish_branch(~3s),make-plan(~13min),review-approach(~5min),dry-walkthrough(~8min),create_impl_worktree(~1s)gate_backend_write(backend_supports_git_write="false")→{"backend_capable": "false"}→ routes torelease_issue_failurefail, clone registered as error, worktree abandoned. ~21 minutes wasted.Test Gap Analysis
Tests pin the bug as correct behavior — the same pattern as the June 12 investigation:
tests/server/test_backend_ingredient_injection.py:554-564— asserts Codex + implementationopen_kitchenreturnssuccess=Trueanddispatch_feasible=True(changed by PR [FIX] Dispatch Feasibility Immunity — Capability-Aware Admission Control #4130 fromsuccess=False, kitchen=dispatch_infeasible) [SUPPORTED]tests/recipe/test_bundled_recipes_dispatch_ready.py:268-282— function namedtest_codex_implementation_dispatch_infeasiblebut assertsdispatch_feasible is True(name-assertion mismatch from pendulum) [SUPPORTED]tests/recipe/test_bundled_recipes_dispatch_ready.py:329-350—test_codex_capability_gate_recipesuses disjunctive assertion accepting EITHERTrueORFalse— cannot catch a new class of regression [SUPPORTED]Similar Patterns
_check_dispatch_feasibility(_preflight.py:34-114) solves the analogous problem forrun_skillsteps — iterates steps, checks per-step provider profile, skips steps routed to a capable backend. Same pattern needed forrun_pythoncapability gates. [SUPPORTED]failure-verdict-bypass-reachable(Rectify: Failure-Verdict Bypass Immunity via Static Reachability Rule #3630) already performs terminal-classified reachability usingbfs_reachable()and_build_step_graph(). The machinery for the correct check exists. [SUPPORTED]create_impl_worktreestep executes at the orchestrator/host level (unsandboxedrun_cmd), not inside a Codex session. All host-side git operations succeed on Codex — the irreducible gap is onlyimplementcommitting inside the sandbox. [SUPPORTED]Design Intent Findings
_is_vacuous_gate(PR [FIX] Dispatch Feasibility Immunity — Capability-Aware Admission Control #4130, commit1112c0f3f, 2026-06-27): Introduced to fix false-positive blocking from PR [FIX] Rectify: Capability Admission Control at Recipe Load — Refuse DOA Pipelines #4094. Commit message: "A gate step (gate_backend_write) that guards already-pruned operations survives pruning and blocks the entire recipe. ... Three independent subsystems — step pruning, capability feasibility, and per-step backend override — operate without cross-communication." The function's purpose is to detect genuinely unreachable gates and exempt them from infeasibility. The purpose is correct; the reachability predicate is incomplete. [SUPPORTED]_compute_capability_feasibility(PR [FIX] Rectify: Capability Admission Control at Recipe Load — Refuse DOA Pipelines #4094, commitcfb6ecc51, 2026-06-12): Introduced as the "definitive" admission control after 84+ downstream fixes. PR body: "84[FIX]commits over ~3 weeks have patched individual links in the capability injection chain without closing the architectural gap." REQ-SCHEMA-001 proposed apurpose_criticalstep annotation; it was never implemented — the heuristic approach was substituted. [SUPPORTED]gate_backend_writestep (PR Implementation Plan: Backend-Conditional Routing — gate_backend_write Step #3960, commit207deda91, 2026-06-09): Converts DOA pipeline ending from false-success to honest-failure. Never intended to prevent wasted work — only to make the waste end honestly. [SUPPORTED]BackendCapabilities.git_metadata_writable(PR [fix] Codex Pipeline Falseno_changesClassification — Part A (Cascade-Critical Bugs 1–3) #3844, commit5a971233d, 2026-06-06): Documents the Codex sandbox constraint. Static declaration, no runtime probe. Correct. [SUPPORTED]Historical Context
This is the third oscillation of a confirmed pendulum pattern:
The pendulum:
Fix chain (all verified by commit message + diff):
#3844 → #3880 → #3892 → #3902 → #3960 → #3973 → #3980 → #3981 → #3995 → #4068 → #4071 → #4075 → #4086 → #4087 → #4094 → #4130
Prior investigations directly on this topic:
investigation_codex_gate_backend_write_recurrence_2026-06-12_083200.md(June 12, deep analysis, 9 subagents),investigation_codex_dispatch_infeasible_2026-06-12_151200.md(June 12, deep analysis). Both identified the root cause correctly but proposed fixes that were either not implemented as designed (purpose_critical) or introduced new gaps (vacuous-gate).This is a recurring pattern — consider running
/rectifyfor architectural immunity after resolving the immediate issue.External Research
workspace-writesandbox protects.git/as read-only by design (Linux landlock/bwrap). Headlesscodex execcannot use--sandbox danger-full-accesswithapproval_policy=never. This constraint is structural and unfixable from AutoSkillit's side. Source: openai.com/codex/concepts/sandboxing, openai/codex PR #19852. [SUPPORTED].git/). AutoSkillit's static capability declaration is the most robust multi-backend pattern but is consumed only mid-pipeline, not at admission with correct reachability. [SUPPORTED]Scope Boundary
Investigated:
_is_vacuous_gatelogic and its reachability blind spot; all three affected recipes (implementation, remediation, implementation-groups);_prune_skipped_stepsroute-repair mechanism;_compute_capability_feasibilityfull logic; all five admission-control enforcement surfaces (normal open_kitchen, deferred open_kitchen, get_recipe, load_recipe, dispatch_food_truck); 16-commit fix chain; installed vs. repo version parity; existing BFS reachability machinery; adversarial challenge of root cause (7 counterhypotheses tested, all refuted).Not yet explored: Whether
load_recipesoft enforcement (sets flag but doesn't early-return attools_recipe.py:240-245) is a latent exploit path; whetherpurpose_criticalannotation is still the correct long-term fix or whether reachability alone is sufficient; exact GitHubfail-label issue count attributable to this family; whether research/merge-prs recipes silently degrade on Codex (no gate step, so no DOA signal).Recommendations
Single recommendation: Add post-prune reachability check to
_is_vacuous_gate._is_vacuous_gate(_recipe_composition.py:178-213) must verify that the gate step is NOT reachable from any surviving step before declaring it vacuous. The check should use the existing_collect_all_route_targetshelper (same file, line 25) which already covers all routing fields (on_success,on_failure,on_context_limit,on_rate_limit,on_exhausted,on_resultconditions).After the
for cap_key in gate_input_keysloop completes (line 212 is inside the loop body), at the same indentation level asreturn True(line 213):post_prune_stepsfor any step (other than the gate itself) whose route targets includegate_step_nameFalse— the gate is reachable and therefore not vacuousThis leverages existing infrastructure (
_collect_all_route_targetsat line 25) and adds a single structural check to close the reachability gap. No schema changes, no new registries, no heuristic layers.Complementary actions:
test_codex_implementation_dispatch_infeasibleto assertdispatch_feasible=False(reversing PR [FIX] Dispatch Feasibility Immunity — Capability-Aware Admission Control #4130's flip back to [FIX] Rectify: Capability Admission Control at Recipe Load — Refuse DOA Pipelines #4094's correct contract)load_recipeenforcement attools_recipe.py:240-245from soft (flag-only) to hard (early-return refusal)Killed alternatives:
_is_vacuous_gateentirely — killed: re-introduces the false-positive from [FIX] Rectify: Capability Admission Control at Recipe Load — Refuse DOA Pipelines #4094 for any future recipe with genuinely unreachable gatespurpose_criticalannotation (REQ-SCHEMA-001) — killed as immediate fix: correct long-term but scope is too large for the acute issue; reachability check is sufficient and composable with purpose_critical if added laterskip_when_falsetocreate_impl_worktree— killed:create_impl_worktreecorrectly executes at host level (unsandboxed); blocking it blocks the worktree for non-gate usesbackend_supports_git_writecheck in_check_dispatch_feasibility— killed: same rot class as prior fixes; does not generalize to next capabilityBreakage Analysis
_is_vacuous_gatedispatch_feasible=Truefor Codex+implementation:test_backend_ingredient_injection.py:554-564,test_bundled_recipes_dispatch_ready.py:268-282,test_capability_admission_e2e.py(vacuous-gate test). All must be updated to assertdispatch_feasible=Falseandinfeasible_steps=["gate_backend_write"].git log --grep="revert" -i)gate_backend_writestep is retained as defense-in-depth;skip_when_falsepruning unchanged; research/merge-prs recipes unaffected (nogate_backend_writestep). Fleet dispatch gains earlier refusal.ingredients_only=truediscovery flow is unaffected (infeasibility is on the recipe, not the ingredients).open_kitchenpath (tools_kitchen.py:731-734) is a second enforcement site;load_recipe(tools_recipe.py:240-245) needs hard enforcement upgrade; fleetdispatch_food_truck(tools_fleet_dispatch.py:340-351) already checksdispatch_feasible._is_vacuous_gate), test inversions, and one soft→hard enforcement upgrade inload_recipe.Confidence Levels
_is_vacuous_gatemissing reachability check is the root causegate_backend_writereachable viacreate_impl_worktreepurpose_criticalannotation (REQ-SCHEMA-001) never implementedload_recipesoft enforcement is a latent gap