Skip to content

Commit 888d3f1

Browse files
committed
test: add bounded remediation loop assertion for research.yaml
1 parent 760ce61 commit 888d3f1

2 files changed

Lines changed: 80 additions & 10 deletions

File tree

tests/recipe/test_research_audit_impl.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,85 @@ def test_check_audit_retry_loop_on_failure_escalates(self, recipe) -> None:
236236
assert step.on_failure == "escalate_stop"
237237

238238

239+
class TestResearchRemediationLoop:
240+
"""Tests for the audit_impl → remediate → plan_phase retry cycle in research.yaml."""
241+
242+
@pytest.fixture(scope="class")
243+
def recipe(self):
244+
return load_recipe(builtin_recipes_dir() / "research.yaml")
245+
246+
def test_audit_impl_nogo_routes_to_remediate(self, recipe) -> None:
247+
"""research.yaml audit_impl default (NO GO) route must target remediate."""
248+
step = recipe.steps["audit_impl"]
249+
conditions = step.on_result.conditions
250+
default_cond = next((c for c in conditions if c.when is None), None)
251+
assert default_cond is not None, "audit_impl must have a default route"
252+
assert default_cond.route == "remediate", (
253+
f"audit_impl default route must be 'remediate', got '{default_cond.route}'"
254+
)
255+
256+
def test_audit_impl_error_routes_to_escalate_stop(self, recipe) -> None:
257+
step = recipe.steps["audit_impl"]
258+
conditions = step.on_result.conditions
259+
error_cond = next((c for c in conditions if c.when and "result.error" in c.when), None)
260+
assert error_cond is not None
261+
assert error_cond.route == "escalate_stop"
262+
263+
def test_has_remediate_step(self, recipe) -> None:
264+
assert "remediate" in recipe.steps
265+
assert recipe.steps["remediate"].action == "route"
266+
267+
def test_remediate_carries_remediation_path(self, recipe) -> None:
268+
step = recipe.steps["remediate"]
269+
assert "context.remediation_path" in step.with_args["remediation_path"]
270+
271+
def test_remediate_routes_to_check_audit_retry_loop(self, recipe) -> None:
272+
step = recipe.steps["remediate"]
273+
assert step.on_success == "check_audit_retry_loop"
274+
275+
def test_has_check_audit_retry_loop_step(self, recipe) -> None:
276+
step = recipe.steps["check_audit_retry_loop"]
277+
assert step.tool == "run_python"
278+
assert step.with_args["callable"] == "autoskillit.smoke_utils.check_loop_iteration"
279+
280+
def test_check_audit_retry_loop_max_iterations(self, recipe) -> None:
281+
step = recipe.steps["check_audit_retry_loop"]
282+
assert step.with_args["max_iterations"] == "2"
283+
284+
def test_check_audit_retry_loop_max_exceeded_routes_to_escalate_stop(self, recipe) -> None:
285+
step = recipe.steps["check_audit_retry_loop"]
286+
conditions = step.on_result.conditions
287+
max_cond = next(
288+
(c for c in conditions if c.when and "max_exceeded" in c.when and "== true" in c.when),
289+
None,
290+
)
291+
assert max_cond is not None
292+
assert max_cond.route == "escalate_stop"
293+
294+
def test_check_audit_retry_loop_default_routes_to_pre_remediation_cleanup(
295+
self, recipe
296+
) -> None:
297+
step = recipe.steps["check_audit_retry_loop"]
298+
conditions = step.on_result.conditions
299+
default_cond = next((c for c in conditions if c.when is None), None)
300+
assert default_cond is not None
301+
assert default_cond.route == "pre_remediation_cleanup"
302+
303+
def test_check_audit_retry_loop_on_failure_escalates(self, recipe) -> None:
304+
step = recipe.steps["check_audit_retry_loop"]
305+
assert step.on_failure == "escalate_stop"
306+
307+
def test_has_pre_remediation_cleanup_step(self, recipe) -> None:
308+
step = recipe.steps["pre_remediation_cleanup"]
309+
assert step.tool == "run_cmd"
310+
assert "worktree remove" in step.with_args["cmd"]
311+
312+
def test_pre_remediation_cleanup_routes_to_plan_phase(self, recipe) -> None:
313+
step = recipe.steps["pre_remediation_cleanup"]
314+
assert step.on_success == "plan_phase"
315+
assert step.on_failure == "plan_phase"
316+
317+
239318
class TestResearchCampaignAuditIngredient:
240319
"""Tests for audit ingredient in research-campaign.yaml."""
241320

tests/recipe/test_research_context_tracking.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -111,16 +111,7 @@ def test_research_recipe_passes_semantic_rules(recipe):
111111
errors = validate_recipe_structure(recipe)
112112
assert not errors, f"Structural validation errors: {errors}"
113113
findings = run_semantic_rules(recipe)
114-
_KNOWN_NON_CONFORMING = {"audit-impl-remediation-route"}
115-
fired_rules = {f.rule for f in findings if f.severity == Severity.ERROR}
116-
for rule_name in _KNOWN_NON_CONFORMING:
117-
assert rule_name in fired_rules, (
118-
f"Exclusion for '{rule_name}' is stale — rule no longer fires. "
119-
f"Remove from _KNOWN_NON_CONFORMING."
120-
)
121-
error_findings = [
122-
f for f in findings if f.severity == Severity.ERROR and f.rule not in _KNOWN_NON_CONFORMING
123-
]
114+
error_findings = [f for f in findings if f.severity == Severity.ERROR]
124115
assert not error_findings, (
125116
f"Semantic rule errors: {[f'{f.rule}: {f.message}' for f in error_findings]}"
126117
)

0 commit comments

Comments
 (0)