"""Robot Framework helper for M6 E2E verification tests. Exercises the complete M6 success criteria sequence: 1. Porting action creation from YAML config 2. Plan use + execute via real CLI subprocess 3. Hierarchical decomposition: 4+ levels of subplans 4. Decision correction recomputes only affected subtree 5. Parallel execution scales to 10+ concurrent subplans 6. Realistic porting task completes autonomously (lifecycle) 7. Plan apply transitions to APPLIED state via real CLI subprocess 8. SubplanFailureHandler retry and stop-others logic CLI-facing tests (action-create-porting, plan-use-execute, plan-apply-lifecycle) invoke the real ``agents`` CLI via subprocess without mocking. Domain-level tests exercise real application code directly. Each subcommand prints a sentinel string on success and exits 0. On failure it prints a diagnostic to stderr and exits 1. Usage: python robot/helper_m6_e2e_verification.py """ from __future__ import annotations import os import re import sys from collections.abc import Callable from datetime import UTC, datetime from pathlib import Path from typing import NoReturn # Ensure src is importable when run from workspace root _SRC = str(Path(__file__).resolve().parents[1] / "src") if _SRC not in sys.path: sys.path.insert(0, _SRC) # Ensure robot/ is on the import path for helper_e2e_common. _ROBOT = str(Path(__file__).resolve().parent) if _ROBOT not in sys.path: sys.path.insert(0, _ROBOT) from helper_e2e_common import ( # noqa: E402 cleanup_workspace, is_expected_provider_unavailable, run_cli, setup_workspace, write_yaml, ) from helpers_common import reset_global_state # noqa: E402 from ulid import ULID # noqa: E402 from cleveragents.domain.models.core.correction import ( # noqa: E402 CorrectionDryRunReport, CorrectionImpact, CorrectionMode, CorrectionRequest, CorrectionStatus, ) from cleveragents.domain.models.core.decision import ( # noqa: E402 Decision, DecisionType, ) from cleveragents.domain.models.core.plan import ( # noqa: E402 ExecutionMode, NamespacedName, Plan, PlanIdentity, PlanPhase, ProcessingState, SubplanConfig, SubplanFailureHandler, SubplanMergeStrategy, SubplanStatus, ) # ------------------------------------------------------------------- # Valid ULID constants # ------------------------------------------------------------------- _ROOT_PLAN_ID = str(ULID()) _CHILD_PLAN_IDS = [str(ULID()) for _ in range(4)] _GRANDCHILD_PLAN_IDS = [str(ULID()) for _ in range(8)] _GREAT_GRANDCHILD_IDS = [str(ULID()) for _ in range(4)] _LEAF_IDS = [str(ULID()) for _ in range(4)] _ACTION_ULID = str(ULID()) _ROOT_DEC_ID = str(ULID()) _CHILD_DEC_IDS = [str(ULID()) for _ in range(4)] _ACTION_YAML = """\ name: local/port-to-typescript description: Autonomous Python to TypeScript porting action definition_of_done: All Python files converted to TypeScript with tests strategy_actor: openai/gpt-4 execution_actor: openai/gpt-4 """ # ------------------------------------------------------------------- # Helpers # ------------------------------------------------------------------- def _fail(msg: str) -> NoReturn: """Print failure message to stderr and exit with code 1.""" print(f"FAIL: {msg}", file=sys.stderr) raise SystemExit(1) def _extract_plan_id(output: str) -> str | None: """Extract a ULID plan_id from plain CLI output.""" match = re.search(r"\b([0-9A-Z]{26})\b", output) return match.group(1) if match else None def _make_plan( *, plan_id: str = _ROOT_PLAN_ID, parent_plan_id: str | None = None, root_plan_id: str | None = None, phase: PlanPhase = PlanPhase.STRATEGIZE, processing_state: ProcessingState = ProcessingState.QUEUED, subplan_config: SubplanConfig | None = None, subplan_statuses: list[SubplanStatus] | None = None, action_name: str = "local/port-to-typescript", description: str = "Port Python to TypeScript", ) -> Plan: """Create a Plan for testing with minimal required fields.""" return Plan( identity=PlanIdentity( plan_id=plan_id, parent_plan_id=parent_plan_id, root_plan_id=root_plan_id, ), namespaced_name=NamespacedName( server=None, namespace="local", name="port-to-typescript", ), description=description, action_name=action_name, phase=phase, processing_state=processing_state, subplan_config=subplan_config, subplan_statuses=subplan_statuses or [], ) # ------------------------------------------------------------------- # Subcommand: action-create-porting (CLI via subprocess) # ------------------------------------------------------------------- def action_create_porting() -> None: """Create a porting action from YAML config via real CLI subprocess. Writes a YAML file, invokes ``agents action create --config`` via subprocess, and verifies the action is persisted by querying the database. """ workspace = setup_workspace(prefix="m6_action_") yaml_path = write_yaml(_ACTION_YAML) try: r = run_cli("action", "create", "--config", yaml_path, workspace=workspace) if r.returncode != 0: _fail(f"action create: {r.stderr}") combined = r.stdout + r.stderr if "INTERNAL" in combined or "Traceback" in combined: _fail(f"action create crashed:\n{combined}") # Verify the output references the action name if "port-to-typescript" not in combined: _fail(f"action name not in output:\n{combined}") print("m6-action-create-porting-ok") finally: os.unlink(yaml_path) cleanup_workspace(workspace) # ------------------------------------------------------------------- # Subcommand: plan-use-execute (CLI via subprocess) # ------------------------------------------------------------------- def plan_use_execute() -> None: """Use and execute a porting plan via real CLI subprocess. Creates an action, then runs ``plan use`` and ``plan execute`` via subprocess. The plan is in strategize/queued so execute correctly rejects with a controlled "not ready" message. """ workspace = setup_workspace(prefix="m6_use_exec_") yaml_path = write_yaml(_ACTION_YAML) try: # Create the action first r1 = run_cli("action", "create", "--config", yaml_path, workspace=workspace) if r1.returncode != 0: _fail(f"action create: {r1.stderr}") # Plan use r2 = run_cli( "plan", "use", "local/port-to-typescript", "local/large-project", "--format", "plain", workspace=workspace, ) if r2.returncode != 0: _fail(f"plan use: {r2.stderr}") plan_id = _extract_plan_id(r2.stdout) if not plan_id: _fail(f"could not extract plan_id:\n{r2.stdout}") # Plan execute — plan is in strategize/queued, so this will # get a controlled rejection (not a crash). r3 = run_cli("plan", "execute", plan_id, workspace=workspace) combined = r3.stdout + r3.stderr if "Traceback" in combined: _fail(f"plan execute crashed:\n{combined}") if "INTERNAL" in combined and not is_expected_provider_unavailable(combined): _fail(f"plan execute crashed:\n{combined}") # Verify the output references the plan if plan_id not in combined: _fail(f"plan execute output missing plan_id {plan_id}") print("m6-plan-use-execute-ok") finally: os.unlink(yaml_path) cleanup_workspace(workspace) # ------------------------------------------------------------------- # Subcommand: hierarchical-decomposition # ------------------------------------------------------------------- def hierarchical_decomposition() -> None: """Verify hierarchical decomposition creates 4+ levels of subplans. Constructs a tree: root → L1 (4 children) → L2 (8 grandchildren) → L3 (4 great-grandchildren) → L4 (4 leaves). Verifies identity flags and root_plan_id propagation. """ # Level 0: root plan root = _make_plan(plan_id=_ROOT_PLAN_ID, root_plan_id=_ROOT_PLAN_ID) if not root.is_root_plan: _fail("root should be is_root_plan") if root.is_subplan: _fail("root should not be is_subplan") # Level 1: 4 children level1 = [] for cid in _CHILD_PLAN_IDS: p = _make_plan( plan_id=cid, parent_plan_id=_ROOT_PLAN_ID, root_plan_id=_ROOT_PLAN_ID, description=f"L1 subplan {cid[:8]}", ) if not p.is_subplan: _fail(f"L1 {cid} should be subplan") if p.identity.root_plan_id != _ROOT_PLAN_ID: _fail(f"L1 root_plan_id mismatch: {p.identity.root_plan_id}") level1.append(p) if len(level1) < 4: _fail(f"expected >= 4 L1 subplans, got {len(level1)}") # Level 2: 8 grandchildren (2 per L1) level2 = [] for i, gcid in enumerate(_GRANDCHILD_PLAN_IDS): parent = _CHILD_PLAN_IDS[i % len(_CHILD_PLAN_IDS)] p = _make_plan( plan_id=gcid, parent_plan_id=parent, root_plan_id=_ROOT_PLAN_ID, description=f"L2 subplan {gcid[:8]}", ) if p.identity.root_plan_id != _ROOT_PLAN_ID: _fail(f"L2 root mismatch: {p.identity.root_plan_id}") level2.append(p) if len(level2) < 4: _fail(f"expected >= 4 L2 subplans, got {len(level2)}") # Level 3: 4 great-grandchildren level3 = [] for i, ggid in enumerate(_GREAT_GRANDCHILD_IDS): parent = _GRANDCHILD_PLAN_IDS[i % len(_GRANDCHILD_PLAN_IDS)] p = _make_plan( plan_id=ggid, parent_plan_id=parent, root_plan_id=_ROOT_PLAN_ID, description=f"L3 subplan {ggid[:8]}", ) level3.append(p) # Level 4: 4 leaves level4 = [] for i, lid in enumerate(_LEAF_IDS): parent = _GREAT_GRANDCHILD_IDS[i % len(_GREAT_GRANDCHILD_IDS)] p = _make_plan( plan_id=lid, parent_plan_id=parent, root_plan_id=_ROOT_PLAN_ID, description=f"L4 leaf subplan {lid[:8]}", ) level4.append(p) total = 1 + len(level1) + len(level2) + len(level3) + len(level4) levels = 5 # L0, L1, L2, L3, L4 if levels < 5: _fail(f"expected >= 5 levels (4+ subplan levels), got {levels}") # Verify all non-root plans propagate root_plan_id for plan in [*level1, *level2, *level3, *level4]: if plan.identity.root_plan_id != _ROOT_PLAN_ID: _fail(f"root_plan_id not propagated: {plan.identity.plan_id}") if not plan.is_subplan: _fail(f"should be subplan: {plan.identity.plan_id}") if total < 21: _fail(f"expected >= 21 total plans, got {total}") print("m6-hierarchical-decomposition-ok") # ------------------------------------------------------------------- # Subcommand: correction-affected-subtree # ------------------------------------------------------------------- def correction_affected_subtree() -> None: """Verify decision correction recomputes only affected subtree. Creates a decision tree (root → 4 children), then simulates correction targeting one child. Verifies CorrectionImpact contains only the affected subtree, not the entire tree. """ # Root decision root = Decision( decision_id=_ROOT_DEC_ID, plan_id=_ROOT_PLAN_ID, parent_decision_id=None, sequence_number=0, decision_type=DecisionType.PROMPT_DEFINITION, question="How to port this project?", chosen_option="Module-by-module porting strategy", rationale="Systematic approach for large codebase", downstream_decision_ids=_CHILD_DEC_IDS, ) if root.parent_decision_id is not None: _fail("root decision should have no parent") if len(root.downstream_decision_ids) != len(_CHILD_DEC_IDS): _fail(f"root downstream count: {len(root.downstream_decision_ids)}") # 4 child decisions (one per module) children = [] for i, cid in enumerate(_CHILD_DEC_IDS): d = Decision( decision_id=cid, plan_id=_ROOT_PLAN_ID, parent_decision_id=_ROOT_DEC_ID, sequence_number=i + 1, decision_type=DecisionType.SUBPLAN_SPAWN, question=f"How to port module {i}?", chosen_option=f"Direct translation for module {i}", rationale=f"Module {i} has 1:1 Python→TS mapping", ) children.append(d) # Target child[1] for correction target_id = _CHILD_DEC_IDS[1] # Build CorrectionImpact scoped to just the target subtree impact = CorrectionImpact( affected_decisions=[target_id], affected_files=[f"src/module_1/file_{j}.py" for j in range(5)], affected_child_plans=[], risk_level="low", rollback_tier="phase", artifacts_to_archive=[], ) # Verify only 1 decision affected (not the root or other children) if len(impact.affected_decisions) != 1: _fail(f"expected 1 affected decision, got {len(impact.affected_decisions)}") if impact.affected_decisions[0] != target_id: _fail(f"wrong affected decision: {impact.affected_decisions[0]}") # Verify root is NOT affected if _ROOT_DEC_ID in impact.affected_decisions: _fail("root should not be in affected decisions") # Verify other children not affected for i, cid in enumerate(_CHILD_DEC_IDS): if i == 1: continue if cid in impact.affected_decisions: _fail(f"child {i} should not be affected") # Verify rollback_tier supports subtree-only if impact.rollback_tier not in ("full", "phase", "append_only"): _fail(f"unexpected rollback_tier: {impact.rollback_tier}") # Build dry-run report report = CorrectionDryRunReport( correction_id=str(ULID()), mode=CorrectionMode.REVERT, impact=impact, decisions_to_invalidate=[target_id], ) if len(report.decisions_to_invalidate) != 1: _fail( f"expected 1 decision to invalidate, " f"got {len(report.decisions_to_invalidate)}" ) # Verify CorrectionRequest request = CorrectionRequest( plan_id=_ROOT_PLAN_ID, target_decision_id=target_id, mode=CorrectionMode.REVERT, guidance="Re-evaluate module 1 porting strategy", dry_run=True, ) if request.status != CorrectionStatus.PENDING: _fail(f"initial status should be PENDING: {request.status}") print("m6-correction-affected-subtree-ok") # ------------------------------------------------------------------- # Subcommand: parallel-execution-scale # ------------------------------------------------------------------- def parallel_execution_scale() -> None: """Verify parallel execution scales to 10+ concurrent subplans. Constructs a SubplanConfig with PARALLEL mode and max_parallel=15, creates 15 SubplanStatuses, and verifies the plan model handles them correctly. """ config = SubplanConfig( execution_mode=ExecutionMode.PARALLEL, merge_strategy=SubplanMergeStrategy.GIT_THREE_WAY, max_parallel=15, fail_fast=False, retry_failed=True, max_retries=2, ) if config.execution_mode != ExecutionMode.PARALLEL: _fail(f"execution_mode mismatch: {config.execution_mode}") if config.max_parallel != 15: _fail(f"max_parallel mismatch: {config.max_parallel}") # Create 15 subplan statuses (simulating 15 concurrent subplans) statuses: list[SubplanStatus] = [] for i in range(15): st = SubplanStatus( subplan_id=str(ULID()), action_name="local/port-module", target_resources=[f"src/module_{i:02d}/"], status=ProcessingState.PROCESSING, ) statuses.append(st) if len(statuses) < 10: _fail(f"expected >= 10 concurrent subplans, got {len(statuses)}") # Create plan with parallel config plan = _make_plan( subplan_config=config, subplan_statuses=statuses, phase=PlanPhase.EXECUTE, processing_state=ProcessingState.PROCESSING, ) if not plan.has_subplans: _fail("plan should have subplans") if len(plan.subplan_statuses) != 15: _fail(f"expected 15 subplans, got {len(plan.subplan_statuses)}") if plan.subplan_config is None: _fail("subplan_config is None") if plan.subplan_config.max_parallel < 10: _fail(f"max_parallel should be >= 10: {plan.subplan_config.max_parallel}") # Verify CLI dict includes subplan info cli = plan.as_cli_dict() if "subplan_count" not in cli: _fail("as_cli_dict missing subplan_count") if cli["subplan_count"] != 15: _fail(f"subplan_count mismatch: {cli['subplan_count']}") print("m6-parallel-execution-scale-ok") # ------------------------------------------------------------------- # Subcommand: porting-task-autonomous # ------------------------------------------------------------------- def porting_task_autonomous() -> None: """Verify a realistic porting task completes autonomously. Simulates the full lifecycle: ACTION → STRATEGIZE → EXECUTE → APPLY with subplan decomposition and completion. """ # Phase 1: ACTION → STRATEGIZE (ACTION can always transition) plan_action = _make_plan( phase=PlanPhase.ACTION, processing_state=ProcessingState.QUEUED, ) if not plan_action.can_transition_to_next_phase: _fail("ACTION/QUEUED should allow transition") # Phase 2: STRATEGIZE with subplan config plan_strat = _make_plan( phase=PlanPhase.STRATEGIZE, processing_state=ProcessingState.COMPLETE, subplan_config=SubplanConfig( execution_mode=ExecutionMode.PARALLEL, max_parallel=10, ), ) if not plan_strat.can_transition_to_next_phase: _fail("STRATEGIZE/COMPLETE should allow transition") # Phase 3: EXECUTE with 10 subplans all COMPLETE exec_statuses = [] for i in range(10): exec_statuses.append( SubplanStatus( subplan_id=str(ULID()), action_name="local/port-module", target_resources=[f"src/module_{i}/"], status=ProcessingState.COMPLETE, started_at=datetime.now(tz=UTC), completed_at=datetime.now(tz=UTC), files_changed=i * 10 + 5, ) ) plan_exec = _make_plan( phase=PlanPhase.EXECUTE, processing_state=ProcessingState.COMPLETE, subplan_config=SubplanConfig( execution_mode=ExecutionMode.PARALLEL, max_parallel=10, ), subplan_statuses=exec_statuses, ) if not plan_exec.can_transition_to_next_phase: _fail("EXECUTE/COMPLETE should allow transition") if not plan_exec.has_subplans: _fail("execute plan should have subplans") # Verify all subplans completed for st in plan_exec.subplan_statuses: if st.status != ProcessingState.COMPLETE: _fail(f"subplan {st.subplan_id} not complete: {st.status}") # Phase 4: APPLY plan_apply = _make_plan( phase=PlanPhase.APPLY, processing_state=ProcessingState.APPLIED, subplan_config=plan_exec.subplan_config, subplan_statuses=exec_statuses, ) if not plan_apply.is_terminal: _fail("APPLIED should be terminal") # Verify total files changed across all subplans total_files = sum(st.files_changed for st in plan_apply.subplan_statuses) if total_files < 50: _fail(f"expected >= 50 files changed, got {total_files}") print("m6-porting-task-autonomous-ok") # ------------------------------------------------------------------- # Subcommand: plan-apply-lifecycle # ------------------------------------------------------------------- def plan_apply_lifecycle() -> None: """Verify ``agents plan apply`` via real CLI subprocess. Creates an action and plan via subprocess, then runs ``plan apply``. The plan is in strategize/queued (not execute/complete) so apply correctly rejects with a controlled error message. """ workspace = setup_workspace(prefix="m6_apply_") yaml_path = write_yaml(_ACTION_YAML) try: r1 = run_cli("action", "create", "--config", yaml_path, workspace=workspace) if r1.returncode != 0: _fail(f"action create: {r1.stderr}") r2 = run_cli( "plan", "use", "local/port-to-typescript", "--format", "plain", workspace=workspace, ) if r2.returncode != 0: _fail(f"plan use: {r2.stderr}") plan_id = _extract_plan_id(r2.stdout) if not plan_id: _fail(f"could not extract plan_id:\n{r2.stdout}") # apply on a strategize/queued plan — should get # a controlled rejection, not a crash. r3 = run_cli("plan", "apply", "--yes", plan_id, workspace=workspace) combined = r3.stdout + r3.stderr if "INTERNAL" in combined or "Traceback" in combined: _fail(f"apply crashed:\n{combined}") print("m6-plan-apply-lifecycle-ok") finally: os.unlink(yaml_path) cleanup_workspace(workspace) # ------------------------------------------------------------------- # Subcommand: failure-handler-logic # ------------------------------------------------------------------- def failure_handler_logic() -> None: """Verify SubplanFailureHandler retry and stop-others logic. Tests: - fail_fast=True stops others - SEQUENTIAL mode stops others - Retriable errors are retried - Non-retriable errors are not retried - Max retries exceeded stops retry """ handler = SubplanFailureHandler() # fail_fast stops others config_ff = SubplanConfig( execution_mode=ExecutionMode.PARALLEL, fail_fast=True, ) errored = SubplanStatus( subplan_id=str(ULID()), action_name="local/sub", status=ProcessingState.ERRORED, error="TimeoutError: timed out", ) if not handler.should_stop_others(config_ff, errored): _fail("fail_fast=True should stop others") # SEQUENTIAL stops others regardless of fail_fast config_seq = SubplanConfig( execution_mode=ExecutionMode.SEQUENTIAL, fail_fast=False, ) if not handler.should_stop_others(config_seq, errored): _fail("SEQUENTIAL should stop others") # Retriable error should retry config_retry = SubplanConfig(retry_failed=True, max_retries=2) retriable = SubplanStatus( subplan_id=str(ULID()), action_name="local/sub", status=ProcessingState.ERRORED, error="TimeoutError: timed out", attempt_number=1, ) if not handler.should_retry(config_retry, retriable): _fail("retriable error should be retried") # Non-retriable error should NOT retry non_retriable = SubplanStatus( subplan_id=str(ULID()), action_name="local/sub", status=ProcessingState.ERRORED, error="ConfigurationError: bad config", attempt_number=1, ) if handler.should_retry(config_retry, non_retriable): _fail("non-retriable error should not be retried") # Max retries exceeded exhausted = SubplanStatus( subplan_id=str(ULID()), action_name="local/sub", status=ProcessingState.ERRORED, error="TimeoutError: timed out", attempt_number=3, ) if handler.should_retry(config_retry, exhausted): _fail("max retries exceeded should not retry") print("m6-failure-handler-logic-ok") # ------------------------------------------------------------------- # Subcommand: subplan-config-modes # ------------------------------------------------------------------- def subplan_config_modes() -> None: """Verify SubplanConfig supports all execution modes and merge strategies.""" # All execution modes for mode in ExecutionMode: cfg = SubplanConfig(execution_mode=mode) if cfg.execution_mode != mode: _fail(f"execution_mode mismatch: {cfg.execution_mode} != {mode}") # All merge strategies for strat in SubplanMergeStrategy: cfg = SubplanConfig(merge_strategy=strat) if cfg.merge_strategy != strat: _fail(f"merge_strategy mismatch: {cfg.merge_strategy} != {strat}") # max_parallel bounds cfg_min = SubplanConfig(max_parallel=1) if cfg_min.max_parallel != 1: _fail(f"max_parallel=1 mismatch: {cfg_min.max_parallel}") cfg_max = SubplanConfig(max_parallel=50) if cfg_max.max_parallel != 50: _fail(f"max_parallel=50 mismatch: {cfg_max.max_parallel}") # Timeout cfg_timeout = SubplanConfig(timeout_per_subplan_seconds=600) if cfg_timeout.timeout_per_subplan_seconds != 600: _fail(f"timeout mismatch: {cfg_timeout.timeout_per_subplan_seconds}") # Default values default = SubplanConfig() if default.execution_mode != ExecutionMode.SEQUENTIAL: _fail(f"default mode: {default.execution_mode}") if default.merge_strategy != SubplanMergeStrategy.GIT_THREE_WAY: _fail(f"default merge: {default.merge_strategy}") if default.max_parallel != 5: _fail(f"default max_parallel: {default.max_parallel}") print("m6-subplan-config-modes-ok") # ------------------------------------------------------------------- # Subcommand: decision-tree-porting # ------------------------------------------------------------------- def decision_tree_porting() -> None: """Verify decision tree structure for a porting task. Creates a decision tree with PROMPT_DEFINITION root, SUBPLAN_PARALLEL_SPAWN decisions for module decomposition, and verifies parent-child relationships. """ root = Decision( decision_id=_ROOT_DEC_ID, plan_id=_ROOT_PLAN_ID, parent_decision_id=None, sequence_number=0, decision_type=DecisionType.PROMPT_DEFINITION, question="Port Python project to TypeScript", chosen_option="Hierarchical module-by-module porting", rationale="Break down into parallel workstreams", downstream_decision_ids=_CHILD_DEC_IDS, ) # PROMPT_DEFINITION must be root (no parent) if root.parent_decision_id is not None: _fail("PROMPT_DEFINITION should have no parent") # Spawn decisions for each module spawn_decisions = [] for i, cid in enumerate(_CHILD_DEC_IDS): d = Decision( decision_id=cid, plan_id=_ROOT_PLAN_ID, parent_decision_id=_ROOT_DEC_ID, sequence_number=i + 1, decision_type=DecisionType.SUBPLAN_PARALLEL_SPAWN, question=f"How to port module_{i:02d}?", chosen_option=f"Create subplan for module_{i:02d}", rationale=f"Module {i} is independent", downstream_plan_ids=[_CHILD_PLAN_IDS[i]], ) spawn_decisions.append(d) # Verify parent-child links for d in spawn_decisions: if d.parent_decision_id != _ROOT_DEC_ID: _fail(f"child {d.decision_id} parent mismatch") if d.decision_type != DecisionType.SUBPLAN_PARALLEL_SPAWN: _fail(f"child type should be SUBPLAN_PARALLEL_SPAWN: {d.decision_type}") if len(d.downstream_plan_ids) != 1: _fail(f"child should spawn 1 plan: {len(d.downstream_plan_ids)}") # Root should reference all children if len(root.downstream_decision_ids) != len(_CHILD_DEC_IDS): _fail( f"root downstream count: " f"{len(root.downstream_decision_ids)} != {len(_CHILD_DEC_IDS)}" ) # Verify superseded_by for correction flow corrected_root = root.with_superseded_by(str(ULID())) if corrected_root.superseded_by is None: _fail("superseded_by should be set") # Original should be untouched (frozen model) if root.superseded_by is not None: _fail("original root superseded_by should still be None") print("m6-decision-tree-porting-ok") # ------------------------------------------------------------------- # Dispatcher # ------------------------------------------------------------------- _COMMANDS: dict[str, Callable[[], None]] = { "action-create-porting": action_create_porting, "plan-use-execute": plan_use_execute, "hierarchical-decomposition": hierarchical_decomposition, "correction-affected-subtree": correction_affected_subtree, "parallel-execution-scale": parallel_execution_scale, "porting-task-autonomous": porting_task_autonomous, "plan-apply-lifecycle": plan_apply_lifecycle, "failure-handler-logic": failure_handler_logic, "subplan-config-modes": subplan_config_modes, "decision-tree-porting": decision_tree_porting, } def main() -> int: """Entry point called by Robot Framework ``Run Process``.""" if len(sys.argv) < 2: print( f"Usage: helper_m6_e2e_verification.py <{'|'.join(_COMMANDS)}>", ) return 1 command = sys.argv[1] handler = _COMMANDS.get(command) if handler is None: print(f"Unknown command: {command}") return 1 reset_global_state() handler() return 0 if __name__ == "__main__": sys.exit(main())