"""Deterministic goal rewrite rules for controller-level recovery. Rules map goal substrings → safe fallback goals. Applied exactly once after a first-run failure. If no rule matches, recovery is skipped and the run terminates as failed. Rules (pattern → replacement): "deny bash" → "inspect project" (security block → harmless read) "deny rm" → "inspect project" "deny curl" → "inspect project" "forbidden" → "inspect project" "bad template" → "inspect project" (template logic error → harmless read) "retry execution" → "inspect project" (flaky step exhausted → harmless read) Goals with no matching rule (e.g. "test traversal", "test absolute path"): → no rewrite → recovery_skipped → final_status=failed """ from __future__ import annotations from typing import Optional # Each entry: (substring_to_match_in_lower_goal, replacement_goal) _RULES: list[tuple[str, str]] = [ ("deny bash", "inspect project"), ("deny rm", "inspect project"), ("deny curl", "inspect project"), ("forbidden", "inspect project"), ("bad template", "inspect project"), ("retry execution", "inspect project"), ] def try_rewrite(goal: str) -> Optional[str]: """Return a replacement goal if a rewrite rule matches, else None.""" lower = goal.lower() for pattern, replacement in _RULES: if pattern in lower: return replacement return None def explain(goal: str) -> str: """Return a human-readable reason for the rewrite, or 'no rule' if none.""" lower = goal.lower() for pattern, replacement in _RULES: if pattern in lower: return f"matched rule '{pattern}' → '{replacement}'" return "no rewrite rule for this goal"