Skip to content

[FIX] Rectify: Capability Admission Control at Recipe Load — Refuse DOA Pipelines#4094

Merged
Trecek merged 11 commits into
developfrom
capability-admission-control-at-recipe-load-refuse-doa-pipel/4090
Jun 12, 2026
Merged

[FIX] Rectify: Capability Admission Control at Recipe Load — Refuse DOA Pipelines#4094
Trecek merged 11 commits into
developfrom
capability-admission-control-at-recipe-load-refuse-doa-pipel/4090

Conversation

@Trecek

@Trecek Trecek commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Codex-backend implementation pipelines repeatedly die at gate_backend_write (step 12) after executing 11 irreversible side-effect steps (clone, claim, branch publish, implement). 84 [FIX] commits over ~3 weeks have patched individual links in the capability injection chain without closing the architectural gap: there is no admission control that refuses a dead-on-arrival (DOA) pipeline at recipe load time. The gate_backend_write step is a mid-pipeline runtime firewall — by the time it fires, the damage (wasted time, orphaned branches, claimed issues) is done.

The immunity approach: extend load_and_validate to compute a dispatch_feasible signal based on whether capability-gated steps that route to terminal failure are reachable in the post-prune step graph. All four content-serving surfaces (open_kitchen normal, open_kitchen deferred-recall, get_recipe, load_recipe) gate on this signal. The pipeline is refused before any tool is revealed.

Requirements

REQ-SCHEMA-001: RecipeStep must support a purpose-critical annotation parsed from recipe YAML, plumbed through recipe/schema.py, _PARSE_STEP_HANDLED_FIELDS, and _parse_step in recipe/io.py so the import-time handled-fields assertion passes.

REQ-SCHEMA-002: All bundled implementation-family recipes (implementation.yaml, remediation.yaml, implementation-groups.yaml) must declare their implement-family steps as purpose-critical; every other bundled-recipe step invoking a git_metadata_write-class skill (merge-prs.yaml implement/retry_worktree, research-implement.yaml implement_phase) must carry an explicit purpose-critical or degraded-ok classification — no unclassified git-write step may remain. User-authored recipes without annotations are fail-open (admitted vacuously); this default must be documented.

REQ-SCHEMA-003: LoadRecipeResult must gain dispatch_feasible: bool and infeasible_steps: list[str] fields, mirrored in OpenKitchenResult; structural valid semantics must remain unchanged.

REQ-ADMIT-001: load_and_validate must classify a recipe as infeasible when any purpose-critical step is pruned by skip_when_false, keyed on backend_name rather than the presence of capability ingredient overrides.

REQ-ADMIT-002: The infeasibility result must be machine-readable, naming the backend, the missing capability, and the pruned purpose-critical steps.

REQ-ADMIT-003: The infeasibility result must include the ingredients_table so ingredients_only=True discovery flows continue to function (the existing _recipe_validation_error_response builder does not carry it — a new refusal response builder is required).

REQ-ADMIT-004: The refusal's user_visible_message must name the backend, the missing capability, and the pruned purpose-critical steps, and must include an actionable escape hatch (e.g., switch the backend to claude-code), following the _preflight.py:95-110 hook-class precedent.

REQ-SURF-001: Both open_kitchen paths (normal and deferred-recall) must fail closed on an infeasible recipe — calling both gate.disable() and disable_components(tags={"kitchen"}), returning a structured refusal envelope — before any side-effect tool is callable.

REQ-SURF-002: get_recipe must gate on dispatch_feasible in addition to its existing valid gate and refuse to serve an infeasible recipe's content.

REQ-SURF-003: load_recipe must fail closed on infeasibility instead of returning recipe content with only a validation_failed flag.

REQ-SURF-004: The dispatch-feasibility preflight consumed by dispatch_food_truck must refuse infeasible recipes before any issue claim or branch publication.

REQ-SURF-005: The gate_backend_write recipe step must remain in place as defense-in-depth.

REQ-TEST-001: An arch test must cross-reference SKILL_CAPABILITY_REGISTRY to require that every bundled-recipe step invoking a git_metadata_write-class skill carries an explicit purpose-critical or degraded-ok classification.

REQ-TEST-002: An integration test must assert that every recipe-content-serving surface refuses an infeasible recipe.

REQ-TEST-003: The tests currently pinning Codex+implementation admission (test_backend_ingredient_injection.py, test_bundled_recipes_dispatch_ready.py, test_recipe_backend_composition_matrix.py) must assert the precise refusal instead.

Closes #4090

Implementation Plan

Plan file: /home/talon/projects/autoskillit-runs/remediation-20260612-092948-265956/.autoskillit/temp/rectify/rectify_capability_admission_control_2026-06-12_093500.md

🤖 Generated with Claude Code via AutoSkillit

Token Usage Summary

Step Model count uncached output cache_read peak_ctx turns cache_write time
resolve_review* sonnet 13 4.0k 215.4k 26.8M 137.3k 899 984.9k 1h 41m
dispatch:44d3705c-3f62-459b-af5b-9678f2bbcb64* sonnet 1 114 3.6k 1.2M 94.6k 28 94.9k 23m 9s
plan* opus[1m] 13 14.7k 280.6k 19.5M 141.0k 602 1.1M 3h 1m
verify* sonnet 13 6.8k 174.8k 7.0M 74.1k 390 598.5k 1h 24m
implement* MiniMax-M3 13 38.7M 181.4k 0 0 1033 0 1h 52m
audit_impl* sonnet 13 8.5k 124.2k 2.9M 66.0k 209 429.5k 1h 5m
prepare_pr* MiniMax-M3 12 2.7M 29.5k 0 0 169 0 12m 25s
compose_pr* MiniMax-M3 12 2.3M 18.4k 0 0 150 0 10m 15s
review_pr* sonnet 17 2.6k 445.8k 16.6M 101.1k 758 967.4k 1h 50m
dispatch:0145c757-e69c-4878-929a-ba4acb07e3d1* sonnet 1 322 13.7k 3.0M 86.3k 84 62.0k 42m 39s
fix* sonnet 6 980 43.2k 6.6M 98.2k 318 293.1k 32m 54s
dispatch:341f411a-3d76-4fcf-88a3-15a64c6d7bd4* sonnet 1 354 10.0k 3.4M 86.4k 89 62.0k 1h 18m
dispatch:7e27c189-e656-4ec2-b528-b128157c59a8* sonnet 1 490 17.2k 5.0M 98.3k 122 73.9k 1h 26m
dispatch:36a96fbc-6c25-4316-a068-66f069a326ac* sonnet 1 386 14.1k 3.8M 89.9k 102 65.5k 1h 14m
dispatch:540912dc-04a8-4bda-b6dd-1ba4a533b68f* sonnet 1 418 12.6k 4.1M 89.9k 106 65.5k 1h 27m
dispatch:726982d8-8d44-4345-91ee-7c36d41bf8c0* sonnet 1 330 11.6k 3.1M 84.9k 82 60.5k 1h 10m
dispatch:c51e35aa-e1dc-42fd-91a6-8cadbf463f94* sonnet 1 418 12.6k 4.1M 90.7k 107 66.3k 1h 36m
dispatch:136d06d2-7313-45ac-b80d-f17a96a36af8* sonnet 1 322 10.0k 3.0M 83.9k 80 59.5k 1h 8m
dispatch:04f878e3-4291-4f0d-bae2-8cc73470181d* sonnet 1 468 164 4.1M 81.4k 58 201.6k 1h 0m
dispatch:037f42f2-ac88-494d-b62b-2764614ba87c* sonnet 1 330 9.7k 3.0M 84.4k 84 140.6k 1h 48m
diagnose_ci* MiniMax-M3 1 277.2k 2.0k 0 0 16 0 1m 6s
resolve_ci* sonnet 1 158 6.4k 908.4k 56.9k 49 36.3k 4m 1s
dispatch:028ce8d5-c194-48e4-96b2-5f2598e02391* sonnet 1 418 18.1k 4.2M 94.3k 105 69.9k 1h 15m
Total 44.1M 1.7M 122.1M 141.0k 5.5M 26h 28m

* Step used a non-Anthropic provider; caching behavior may differ.

Token Efficiency

Step LoC Changed cache_read/LoC cache_write/LoC output/LoC
resolve_review 199 134448.3 4949.2 1082.5
dispatch:44d3705c-3f62-459b-af5b-9678f2bbcb64 0
plan 0
verify 0
implement 2352 0.0 0.0 77.1
audit_impl 0
prepare_pr 0
compose_pr 0
review_pr 0
dispatch:0145c757-e69c-4878-929a-ba4acb07e3d1 360 8363.5 172.2 38.2
fix 36 182321.1 8141.6 1199.6
dispatch:341f411a-3d76-4fcf-88a3-15a64c6d7bd4 446 7548.5 139.0 22.5
dispatch:7e27c189-e656-4ec2-b528-b128157c59a8 235 21444.0 314.5 73.2
dispatch:36a96fbc-6c25-4316-a068-66f069a326ac 0
dispatch:540912dc-04a8-4bda-b6dd-1ba4a533b68f 0
dispatch:726982d8-8d44-4345-91ee-7c36d41bf8c0 0
dispatch:c51e35aa-e1dc-42fd-91a6-8cadbf463f94 844 4875.8 78.5 14.9
dispatch:136d06d2-7313-45ac-b80d-f17a96a36af8 0
dispatch:04f878e3-4291-4f0d-bae2-8cc73470181d 0
dispatch:037f42f2-ac88-494d-b62b-2764614ba87c 0
diagnose_ci 0
resolve_ci 7 129768.6 5190.9 914.9
dispatch:028ce8d5-c194-48e4-96b2-5f2598e02391 0
Total 4479 27262.4 1218.4 369.6

Model Usage Breakdown

Model steps uncached output cache_read cache_write time
sonnet 18 27.5k 1.1M 102.6M 4.3M 21h 11m
opus[1m] 1 14.7k 280.6k 19.5M 1.1M 3h 1m
MiniMax-M3 4 44.0M 231.4k 0 0 2h 16m

Trecek and others added 10 commits June 12, 2026 12:12
…ALLABLES

Move BACKEND_CAPABILITY_INGREDIENTS to core/types/_type_constants.py
(IL-0) so it is importable by the recipe layer (IL-2) without violating
the IL-006 contract that forbids recipe → config imports. Add the new
CAPABILITY_GATE_CALLABLES frozenset alongside it. Update config/ to
re-export from core, preserving backward compatibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add _compute_capability_feasibility() that inspects the post-prune
step graph for DOA patterns from capability-gated run_python steps.
A step is a capability gate when its with_args["callable"] final
component matches CAPABILITY_GATE_CALLABLES and the capability
ingredient it reads resolves to a falsy value with no recovery route.

The result flows into the LoadRecipeResult/OpenKitchenResult dicts as
dispatch_feasible (bool) and infeasible_steps (list[str]) and is also
registered in the formatter suppression sets.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Refuse DOA pipelines before any tool is revealed:
- open_kitchen (both normal and deferred-recall): full gate disable +
  ctx.disable_components({kitchen}) + dispatch_infeasible envelope
- get_recipe resource: lightweight JSON error envelope
- load_recipe tool: dispatch_infeasible flag + user_visible_message

After admission control, correctly-admitted pipelines never reach
gate_backend_write with backend_capable="false". The step remains as
defense-in-depth but should never fire for a correctly-admitted
pipeline.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- test_codex_implementation_dispatch_infeasible: codex + implementation
  recipe reports dispatch_feasible=False with gate_backend_write infeasible
- test_claude_implementation_dispatch_feasible: claude-code backend
  reports dispatch_feasible=True
- test_dispatch_feasible_defaults_true: recipes without capability gates
  default to feasible
- E2E chain test verifies backend → load_and_validate → dispatch_feasible
- AST-level tests guard all four content-serving surfaces and the
  load_and_validate call site
- CAPABILITY_GATE_CALLABLES ↔ smoke_utils export sync test

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add server/test_capability_admission_e2e.py to recipe cascade entry
- Update pyright suppression allowlist line number (276→279) in _api.py
- Bump tools_kitchen.py line limit 1200→1250 for infeasible response helper
- Update _type_constants symbol count 91→93 for new capability constants
- Sync _LEGACY_JSON_WRITES line numbers after tools_kitchen.py insertion

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add three missing tests identified by the audit-impl pass:
1. test_capability_override_parity - asserts _backend_capability_overrides
   (IL-3 server) and _build_capability_overrides (IL-2 fleet) produce
   identical output for every capability state
2. test_open_kitchen_refuses_doa_codex_pipeline - exercises the
   open_kitchen dispatch-infeasible refusal path through
   _dispatch_infeasible_response
3. test_codex_capability_gate_recipes - parametrized contract test
   asserting every bundled recipe containing a gate_backend_write step
   yields dispatch_feasible=False when loaded with codex overrides

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The test loaded a recipe with capability gates but claimed to test
recipes without them — rename and fix docstring to match reality.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
pathlib is stdlib since Python 3.4 — importorskip is a no-op guard.
Also add gate.enable.assert_not_called() to verify gate stays disabled.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
File-level substring checks would pass even if the target function
lacked the gate. Use AST walking scoped to get_recipe and load_recipe.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The field was missing from the list-field copy set, allowing cached
results to share the same list object across callers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Trecek Trecek force-pushed the capability-admission-control-at-recipe-load-refuse-doa-pipel/4090 branch from 7f8402c to 23d64da Compare June 12, 2026 19:14
…behavior

- test_codex_open_kitchen_returns_success: expect dispatch_infeasible (codex
  lacks git_metadata_writable, so gate_backend_write steps are infeasible)
- test_open_kitchen_refuses_doa_codex_pipeline: set gate_infrastructure_ready=True
  to enter deferred-recall path without calling _open_kitchen_handler (which
  calls gate.enable()), matching the test's assert_not_called expectation
- test_current_json_write_sites_match_allowlist: update shifted line numbers
  for tools_kitchen.py json.dumps sites (171→201, 190→220, 224→254, 950→997,
  1010→1057)
- test_no_src_module_exceeds_line_limit: bump tools_kitchen.py exemption from
  1250 to 1275 for capability admission control dispatch_feasible gating

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant