"""Helper script for correction flows Robot Framework smoke tests. Exercises CorrectionService revert/append flows, impact analysis, dry-run reports, and cancellation without requiring persistence. """ from __future__ import annotations import sys from cleveragents.application.services.correction_service import CorrectionService from cleveragents.core.exceptions import ValidationError from cleveragents.domain.models.core.correction import CorrectionMode # --------------------------------------------------------------------------- # Shared fixtures # --------------------------------------------------------------------------- _PLAN_ID = "01ARZ3NDEKTSV4RRFFQ69G5FAV" _DECISION_D1 = "D1" _DECISION_D2 = "D2" _DECISION_D3 = "D3" def _simple_tree() -> dict[str, list[str]]: return {_DECISION_D1: [_DECISION_D2, _DECISION_D3]} def _deep_tree() -> dict[str, list[str]]: return { _DECISION_D1: [_DECISION_D2, _DECISION_D3], _DECISION_D2: ["D4", "D5"], _DECISION_D3: ["D6"], } # --------------------------------------------------------------------------- # Command handlers # --------------------------------------------------------------------------- def _revert_simple() -> None: svc = CorrectionService() req = svc.request_correction(_PLAN_ID, _DECISION_D1, CorrectionMode.REVERT) result = svc.execute_revert(req.correction_id, {_DECISION_D1: []}) assert result.status == "applied", f"Expected applied, got {result.status}" assert _DECISION_D1 in result.reverted_decisions print("revert-simple-ok") def _revert_deep_subtree() -> None: svc = CorrectionService() req = svc.request_correction(_PLAN_ID, _DECISION_D1, CorrectionMode.REVERT) result = svc.execute_revert(req.correction_id, _deep_tree()) assert len(result.reverted_decisions) == 6 print("revert-deep-subtree-ok") def _append_correction() -> None: svc = CorrectionService() req = svc.request_correction(_PLAN_ID, _DECISION_D1, CorrectionMode.APPEND) result = svc.execute_append(req.correction_id) assert result.status == "applied" assert result.spawned_child_plan_id is not None print("append-correction-ok") def _dry_run_report() -> None: svc = CorrectionService() req = svc.request_correction(_PLAN_ID, _DECISION_D1, CorrectionMode.REVERT) report = svc.generate_dry_run_report(req.correction_id, _simple_tree()) assert report.mode == "revert" assert len(report.decisions_to_invalidate) == 3 assert report.estimated_recompute_time_seconds > 0 print("dry-run-report-ok") def _impact_risk_levels() -> None: svc = CorrectionService() # Low risk: 2 nodes req1 = svc.request_correction(_PLAN_ID, _DECISION_D1, CorrectionMode.REVERT) impact1 = svc.analyze_impact(req1.correction_id, {_DECISION_D1: [_DECISION_D2]}) assert impact1.risk_level == "low", f"Expected low, got {impact1.risk_level}" # High risk: > 10 nodes (chain) tree: dict[str, list[str]] = {} current = "R" for i in range(14): child = f"R_c{i}" tree[current] = [child] current = child req2 = svc.request_correction(_PLAN_ID, "R", CorrectionMode.REVERT) impact2 = svc.analyze_impact(req2.correction_id, tree) assert impact2.risk_level == "high", f"Expected high, got {impact2.risk_level}" print("impact-risk-levels-ok") def _cancel_correction() -> None: svc = CorrectionService() req = svc.request_correction(_PLAN_ID, _DECISION_D1, CorrectionMode.REVERT) svc.cancel_correction(req.correction_id) updated = svc.get_correction(req.correction_id) assert updated.status == "cancelled" print("cancel-correction-ok") def _execute_cancelled_error() -> None: svc = CorrectionService() req = svc.request_correction(_PLAN_ID, _DECISION_D1, CorrectionMode.REVERT) svc.cancel_correction(req.correction_id) try: svc.execute_revert(req.correction_id, {}) print("FAIL: expected ValidationError") sys.exit(1) except ValidationError: print("execute-cancelled-error-ok") def _list_corrections_by_plan() -> None: svc = CorrectionService() svc.request_correction(_PLAN_ID, _DECISION_D1, CorrectionMode.REVERT) svc.request_correction(_PLAN_ID, _DECISION_D2, CorrectionMode.APPEND) svc.request_correction("OTHER_PLAN", _DECISION_D3, CorrectionMode.REVERT) result = svc.list_corrections(plan_id=_PLAN_ID) assert len(result) == 2, f"Expected 2, got {len(result)}" print("list-corrections-by-plan-ok") # --------------------------------------------------------------------------- # Dispatch # --------------------------------------------------------------------------- COMMANDS: dict[str, object] = { "revert-simple": _revert_simple, "revert-deep-subtree": _revert_deep_subtree, "append-correction": _append_correction, "dry-run-report": _dry_run_report, "impact-risk-levels": _impact_risk_levels, "cancel-correction": _cancel_correction, "execute-cancelled-error": _execute_cancelled_error, "list-corrections-by-plan": _list_corrections_by_plan, } def main() -> None: """Dispatch command from sys.argv.""" if len(sys.argv) < 2: raise SystemExit("Expected command argument") command = sys.argv[1] if command not in COMMANDS: raise SystemExit(f"Unknown command: {command}") func = COMMANDS[command] if callable(func): func() if __name__ == "__main__": main()