11"""Architectural structural tests for capability admission control.
22
33Verifies that:
4- - _compute_capability_feasibility is called inside load_and_validate
4+ - _compute_capability_feasibility is called inside load_and_validate with skip_resolutions
55- All four content-serving surfaces gate on dispatch_feasible
6+ (open_kitchen, get_recipe, load_recipe, dispatch_food_truck)
67- CAPABILITY_GATE_CALLABLES entries have a corresponding BACKEND_CAPABILITY_INGREDIENTS input
78- Every run_python step using a CAPABILITY_GATE_CALLABLES callable reads a capability ingredient
9+ - dispatch_food_truck injects _build_capability_overrides for backend capability signals
810"""
911
1012from __future__ import annotations
@@ -104,6 +106,73 @@ def test_load_recipe_gates_on_dispatch_feasible() -> None:
104106 pytest .fail ("load_recipe function not found in tools_recipe.py" )
105107
106108
109+ def test_dispatch_food_truck_gates_on_dispatch_feasible () -> None :
110+ """dispatch_food_truck must check dispatch_feasible before executing."""
111+ fleet_path = SRC_ROOT / "server" / "tools" / "tools_fleet_dispatch.py"
112+ src = fleet_path .read_text (encoding = "utf-8" )
113+ tree = ast .parse (src )
114+ for node in ast .walk (tree ):
115+ if isinstance (node , ast .AsyncFunctionDef ) and node .name == "dispatch_food_truck" :
116+ found = False
117+ for child in ast .walk (node ):
118+ if isinstance (child , ast .Constant ) and child .value == "dispatch_feasible" :
119+ found = True
120+ break
121+ assert found , (
122+ "dispatch_food_truck must reference 'dispatch_feasible' to gate "
123+ "capability-DOA fleet dispatches before subprocess launch."
124+ )
125+ return
126+ pytest .fail ("dispatch_food_truck function not found in tools_fleet_dispatch.py" )
127+
128+
129+ def test_compute_capability_feasibility_receives_skip_resolutions () -> None :
130+ """load_and_validate must pass skip_resolutions to _compute_capability_feasibility
131+ so the vacuous-gate detection has access to pruning context."""
132+ api_path = SRC_ROOT / "recipe" / "_api.py"
133+ tree = ast .parse (api_path .read_text (encoding = "utf-8" ))
134+ for node in ast .walk (tree ):
135+ if (
136+ isinstance (node , (ast .FunctionDef , ast .AsyncFunctionDef ))
137+ and node .name == "load_and_validate"
138+ ):
139+ for child in ast .walk (node ):
140+ if (
141+ isinstance (child , ast .Call )
142+ and isinstance (child .func , ast .Name )
143+ and child .func .id == "_compute_capability_feasibility"
144+ ):
145+ kw_names = {kw .arg for kw in child .keywords if kw .arg is not None }
146+ assert "skip_resolutions" in kw_names , (
147+ "load_and_validate must pass skip_resolutions kwarg "
148+ "to _compute_capability_feasibility for vacuous-gate detection."
149+ )
150+ return
151+ pytest .fail ("_compute_capability_feasibility call not found in load_and_validate" )
152+ pytest .fail ("load_and_validate function not found in _api.py" )
153+
154+
155+ def test_dispatch_food_truck_injects_capability_overrides () -> None :
156+ """dispatch_food_truck must reference _build_capability_overrides to inject
157+ backend capability signals into the load_and_validate call."""
158+ fleet_path = SRC_ROOT / "server" / "tools" / "tools_fleet_dispatch.py"
159+ src = fleet_path .read_text (encoding = "utf-8" )
160+ tree = ast .parse (src )
161+ for node in ast .walk (tree ):
162+ if isinstance (node , ast .AsyncFunctionDef ) and node .name == "dispatch_food_truck" :
163+ found = False
164+ for child in ast .walk (node ):
165+ if isinstance (child , ast .Name ) and child .id == "_build_capability_overrides" :
166+ found = True
167+ break
168+ assert found , (
169+ "dispatch_food_truck must reference _build_capability_overrides "
170+ "to inject backend capability signals into load_and_validate."
171+ )
172+ return
173+ pytest .fail ("dispatch_food_truck function not found in tools_fleet_dispatch.py" )
174+
175+
107176def test_capability_gate_callables_have_matching_ingredient () -> None :
108177 """Every CAPABILITY_GATE_CALLABLES callable must map to a BACKEND_CAPABILITY_INGREDIENTS input.
109178
@@ -129,3 +198,21 @@ def test_capability_gate_callables_have_matching_ingredient() -> None:
129198 assert len (CAPABILITY_GATE_CALLABLES ) > 0 , (
130199 "CAPABILITY_GATE_CALLABLES must declare at least one capability gate callable."
131200 )
201+
202+
203+ def test_capability_ingredient_to_skip_guard_keys_subset () -> None :
204+ """CAPABILITY_INGREDIENT_TO_SKIP_GUARD keys must be BACKEND_CAPABILITY_INGREDIENTS subset."""
205+ from autoskillit .core import (
206+ BACKEND_CAPABILITY_INGREDIENTS ,
207+ CAPABILITY_INGREDIENT_TO_SKIP_GUARD ,
208+ )
209+
210+ orphaned = set (CAPABILITY_INGREDIENT_TO_SKIP_GUARD ) - set (BACKEND_CAPABILITY_INGREDIENTS )
211+ assert not orphaned , (
212+ f"CAPABILITY_INGREDIENT_TO_SKIP_GUARD keys { orphaned } are not in "
213+ f"BACKEND_CAPABILITY_INGREDIENTS — vacuous-gate detection will silently "
214+ f"malfunction for these keys."
215+ )
216+ assert len (CAPABILITY_INGREDIENT_TO_SKIP_GUARD ) > 0 , (
217+ "CAPABILITY_INGREDIENT_TO_SKIP_GUARD must declare at least one mapping."
218+ )
0 commit comments