[FIX] Dispatch-Feasibility Preflight — Claim-Before-Validate Immunity#4087
Merged
Trecek merged 11 commits intoJun 12, 2026
Merged
Conversation
…ide effects The backend-compat check fired only inside run_skill, after side-effect tools (bootstrap_clone, claim_and_resolve_issue, create_and_publish_branch) could already execute. This moves the check to open_kitchen and dispatch_food_truck so the gate is closed before any external state mutates. Changes: - Extend LoadRecipeResult with post_prune_step_names so the server can inspect surviving step names without duplicating parse logic - Add _check_dispatch_feasibility in tools_execution.py: shared function evaluating post-prune run_skill steps against backend capabilities - Wire preflight into open_kitchen (both normal and deferred-recall paths) with gate.close() and ctx.disable_components on failure - Wire preflight into dispatch_food_truck before execute_dispatch - Close gate on recipe validation failure on both open_kitchen paths - Filter active_recipe_steps to post-prune names on both paths - Suppress post_prune_step_names in formatter registries Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move _check_dispatch_feasibility, _get_fix_required_hook_matchers, and filter_steps_by_post_prune into server/tools/_preflight.py to satisfy line-count and business-logic-in-handler arch constraints. Fix test issues: BackendCapabilities constructor, missing post_prune_step_names in mocks, MagicMock config_providers crash, missing pytest import, cascade map entry, and HOOK_REGISTRY monkeypatch targets. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ity check
The dispatch-feasibility preflight in dispatch_food_truck was called with
active_recipe_steps={}, making it a no-op. This commit mirrors the
open_kitchen pattern: after load_and_validate, load the Recipe via
recipes.find() + recipes.load() and build active_recipe_steps via
filter_steps_by_post_prune() before calling _check_dispatch_feasibility.
Also adds four missing tests:
- gate-closure after recipe validation failure (1a)
- full open_kitchen integration preflight blocks and closes gate (1b)
- real HOOK_REGISTRY test without monkeypatching (1e)
- behavioral fleet dispatch preflight: execute_dispatch not called on failure (1g)
And fixes the stale docstring path reference in test_tools_kitchen_preflight.py.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- TestPreflightGateClosure tests failed because open_kitchen hit the _require_orchestrator_exact headless guard and returned early without reaching the recipe validation or preflight paths. Added the same _require_orchestrator_exact patch (return_value=None) used by other open_kitchen tests (e.g. test_mcp_overrides.py). - test_tools_fleet_dispatch_preflight.py imports autoskillit.hook_registry but was missing from LAYER_CASCADE_CONSERVATIVE["hook_registry"] file-level entries, causing test_cascade_map_guard to flag it. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
_apply_triage_gate awaits ctx.recipes.apply_triage_gate() which is a MagicMock (not awaitable), causing a TypeError that returns early via the _kitchen_failure_envelope path — never reaching gate.disable(). Patch _apply_triage_gate as a passthrough coroutine to let both tests reach their target code paths (recipe validation and preflight). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tch text `applicable_guards` was a Python variable name leaking into the user-facing escape_hatch field of the dispatch feasibility error envelope. Replaced with plain English.
The hardcoded `recipe_name = ""` placeholder caused all recipe-scoped and step-scoped provider overrides in `_resolve_provider_profile` to be silently skipped, producing incorrect feasibility verdicts for recipes with configured provider overrides. Thread the actual recipe name from all three call sites.
…hen pattern Use None sentinel for _active_recipe_steps (matching open_kitchen's active_recipe_steps = None on load failure) and guard the preflight call with `_active_recipe_steps is not None`. Previously the empty dict default caused preflight to receive no steps and silently pass when recipe loading failed.
Module-level getsource included the import block where _check_dispatch_feasibility is imported, making both the presence assertion and the ordering assertion trivially true regardless of whether the function body actually calls it.
The `or 'fix-required'` branch was vacuously true for nearly any preflight failure, making the test unable to verify the dormancy hook specifically. Assert on 'dormancy_test_hook' alone.
…ertion tools_kitchen.py line numbers shifted by +2 after adding recipe_name kwarg to both _check_dispatch_feasibility calls; update _LEGACY_JSON_WRITES accordingly. The dormancy hook's script name does not appear in the preflight error envelope — only the hook's matcher does. Replace the vacuous `or 'fix-required'` assertion with a direct check on `unfixable_matchers` to verify the specific dormancy hook caused the failure.
71fdbd7 to
e283780
Compare
This was referenced Jun 30, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add a dispatch-feasibility preflight in the server layer that fires at
open_kitchenanddispatch_food_trucktime — validating that the backend can dispatch the recipe'srun_skillsteps before any irreversible side effects (clone, branch push, issue labeling) occur. On preflight failure, the gate is closed, structurally denying all downstream side-effect tools via_require_enabled(). This fixes the "claim-before-validate" anti-pattern identified in production runimpl-20260611-163753-197057where the backend-compat check fired only insiderun_skill, after claims and branch pushes had already happened.Individual Group Plans
Plan 1: Rectify — Dispatch-Feasibility Preflight — Claim-Before-Validate Immunity
The backend-compatibility gate (
_check_backend_compat) fires only insiderun_skill— after irreversible external side effects (clone, branch push, issue labeling) have already occurred. The incompatibility is statically knowable atopen_kitchentime from constants (HOOK_REGISTRY,backend.capabilities). The architectural weakness is thatopen_kitchenenables the gate and reveals tools before validating that the backend can dispatch the recipe's steps, and no existing test models the temporal ordering between gate-enable, side-effect tools, and the compat check.The immunity plan addresses three layers:
_check_dispatch_feasibilityfunction shared byopen_kitchenanddispatch_food_truck, evaluated against post-prune steps and the real backend, with gate-close on failure.HOOK_REGISTRY, and verify that side-effect tools are structurally denied after preflight failure.Plan 2: Implementation — dispatch_food_truck Preflight Fix and Missing Tests
Fix the
active_recipe_steps={}bug indispatch_food_truckthat renders the dispatch-feasibility preflight a no-op, and add four missing tests: gate-closure after validation failure (1a), fullopen_kitchenintegration preflight (1b), realHOOK_REGISTRYtest (1e), and behavioral fleet dispatch preflight (1g).The root cause is that
dispatch_food_truckcalls_check_dispatch_feasibilitywith an empty dict foractive_recipe_steps, unlikeopen_kitchenwhich properly loads theRecipeobject viarecipes.find()+recipes.load()and filters withfilter_steps_by_post_prune(). The fix mirrors theopen_kitchenpattern.Closes #4083
Implementation Plan
Plan files:
/home/talon/projects/autoskillit-runs/remediation-20260611-215136-833713/.autoskillit/temp/rectify/rectify_dispatch_feasibility_preflight_2026-06-11_215136.md/home/talon/projects/autoskillit-runs/remediation-20260611-215136-833713/.autoskillit/temp/make-plan/remediation_dispatch_feasibility_preflight_plan_2026-06-11_230600.md🤖 Generated with Claude Code via AutoSkillit
Token Usage Summary
* Step used a non-Anthropic provider; caching behavior may differ.
Token Efficiency
Model Usage Breakdown