Skip to content

Commit 7f8402c

Browse files
Trecekclaude
andcommitted
test: add capability admission control tests and structural guards
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>
1 parent 1eb0f01 commit 7f8402c

2 files changed

Lines changed: 106 additions & 0 deletions

File tree

tests/recipe/test_bundled_recipes_dispatch_ready.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,3 +303,41 @@ def test_dispatch_feasible_defaults_true_for_recipes_without_capability_gates()
303303
)
304304
assert result.get("dispatch_feasible") is True
305305
assert "infeasible_steps" not in result
306+
307+
308+
def _discover_capability_gate_recipes() -> list[str]:
309+
"""Return recipe names that contain a gate_backend_write step."""
310+
gate_recipes = []
311+
for name in _RECIPE_STEMS:
312+
recipe_path = _RECIPES_DIR / f"{name}.yaml"
313+
if not recipe_path.exists():
314+
continue
315+
recipe = load_recipe(recipe_path)
316+
for step in recipe.steps.values():
317+
if step.tool == "run_python" and step.with_args.get("callable", "").endswith(
318+
"gate_backend_write"
319+
):
320+
gate_recipes.append(name)
321+
break
322+
return gate_recipes
323+
324+
325+
_CAPABILITY_GATE_RECIPES = _discover_capability_gate_recipes()
326+
327+
328+
@pytest.mark.parametrize("recipe_name", _CAPABILITY_GATE_RECIPES)
329+
def test_codex_capability_gate_recipes(recipe_name: str) -> None:
330+
"""Every recipe with a gate_backend_write step must report
331+
dispatch_feasible=False when loaded with codex overrides."""
332+
result = load_and_validate(
333+
recipe_name,
334+
project_dir=_PROJECT_ROOT,
335+
ingredient_overrides={"backend_supports_git_write": "false"},
336+
backend_name="codex",
337+
)
338+
assert result.get("valid") is True
339+
assert result.get("dispatch_feasible") is False, (
340+
f"Recipe '{recipe_name}' with gate_backend_write step did not report "
341+
f"dispatch_feasible=False under codex overrides"
342+
)
343+
assert "gate_backend_write" in result.get("infeasible_steps", [])

tests/server/test_capability_admission_e2e.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,71 @@ def test_claude_code_backend_produces_feasible_recipe() -> None:
5353
assert result.get("valid") is True
5454
assert result.get("dispatch_feasible") is True
5555
assert "infeasible_steps" not in result
56+
57+
58+
def test_capability_override_parity() -> None:
59+
"""Server and fleet capability override functions must produce identical output.
60+
61+
_backend_capability_overrides (IL-3 server) and _build_capability_overrides
62+
(IL-2 fleet) are parallel implementations — IL-2 cannot import IL-3. This
63+
test asserts value-level parity for every capability state.
64+
"""
65+
from unittest.mock import MagicMock
66+
67+
from autoskillit.fleet._api import _build_capability_overrides
68+
from autoskillit.server.tools._auto_overrides import _backend_capability_overrides
69+
70+
for writable in (True, False):
71+
backend = MagicMock()
72+
backend.capabilities.git_metadata_writable = writable
73+
assert _backend_capability_overrides(backend) == _build_capability_overrides(backend), (
74+
f"Parity violation for git_metadata_writable={writable}"
75+
)
76+
77+
assert _backend_capability_overrides(None) == _build_capability_overrides(None), (
78+
"Parity violation for backend=None"
79+
)
80+
81+
82+
@pytest.mark.anyio
83+
async def test_open_kitchen_refuses_doa_codex_pipeline() -> None:
84+
"""open_kitchen must return success=False with kitchen='dispatch_infeasible'
85+
when load_and_validate reports dispatch_feasible=False."""
86+
import json
87+
from unittest.mock import AsyncMock, patch
88+
89+
from autoskillit.server.tools.tools_kitchen import open_kitchen
90+
from tests.server.conftest import _make_mock_ctx
91+
92+
tool_ctx = _make_mock_ctx()
93+
tool_ctx.gate.enabled = True
94+
tool_ctx.recipe_name = "implementation"
95+
tool_ctx.kitchen_id = "test-kitchen"
96+
tool_ctx.backend.name = "codex"
97+
tool_ctx.recipes.load_and_validate.return_value = {
98+
"content": "name: implementation\nsteps:\n build:\n cmd: task build\n",
99+
"valid": True,
100+
"errors": [],
101+
"requires_packs": [],
102+
"requires_features": [],
103+
"content_hash": "abc123",
104+
"composite_hash": "def456",
105+
"recipe_version": "1.0",
106+
"suggestions": [],
107+
"post_prune_step_names": ["build"],
108+
"dispatch_feasible": False,
109+
"infeasible_steps": ["gate_backend_write"],
110+
}
111+
tool_ctx.recipes.find.return_value = None
112+
113+
fastmcp_ctx = AsyncMock()
114+
115+
with patch("autoskillit.server._get_ctx", return_value=tool_ctx):
116+
result = await open_kitchen(name="implementation", ctx=fastmcp_ctx)
117+
118+
parsed = json.loads(result)
119+
assert parsed["success"] is False
120+
assert parsed["kitchen"] == "dispatch_infeasible"
121+
assert "gate_backend_write" in parsed["infeasible_steps"]
122+
tool_ctx.gate.disable.assert_called_once()
123+
fastmcp_ctx.disable_components.assert_called_once_with(tags={"kitchen"})

0 commit comments

Comments
 (0)