"""Finite state for the single-stage Authoring CDSL protocol.""" from __future__ import annotations from dataclasses import dataclass, replace from enum import StrEnum from .errors import ErrorCode class TaskPhase(StrEnum): ANALYZING_REQUEST = "ANALYZING_REQUEST" AUTHORING_CDSL = "AUTHORING_CDSL" COMPILING_CDSL = "COMPILING_CDSL" BUILDING = "BUILDING" REPAIRING = "REPAIRING" PUBLISHING_BEST_EFFORT = "PUBLISHING_BEST_EFFORT" WAITING_FOR_USER = "WAITING_FOR_USER" COMPLETED = "COMPLETED" FAILED = "FAILED" CANCELLED = "CANCELLED" @dataclass(frozen=True, slots=True) class TaskState: task_id: str phase: TaskPhase version: int active_revision: str = "" repair_count: int = 0 last_error: ErrorCode | None = None retry_from_phase: TaskPhase | None = None requirements_path: str = "" authoring_path: str = "" runtime_cdsl_path: str = "" compile_audit_path: str = "" diagnostics_path: str = "" completion_path: str = "" clarification_path: str = "" _TRANSITIONS: dict[tuple[TaskPhase, str], TaskPhase] = { (TaskPhase.ANALYZING_REQUEST, "analysis_written"): TaskPhase.AUTHORING_CDSL, (TaskPhase.ANALYZING_REQUEST, "waiting_for_user"): TaskPhase.WAITING_FOR_USER, (TaskPhase.WAITING_FOR_USER, "clarification_received"): TaskPhase.ANALYZING_REQUEST, (TaskPhase.AUTHORING_CDSL, "authoring_written"): TaskPhase.COMPILING_CDSL, (TaskPhase.AUTHORING_CDSL, "repair_required"): TaskPhase.REPAIRING, (TaskPhase.COMPILING_CDSL, "compiled"): TaskPhase.BUILDING, (TaskPhase.COMPILING_CDSL, "repair_required"): TaskPhase.REPAIRING, (TaskPhase.BUILDING, "build_completed"): TaskPhase.PUBLISHING_BEST_EFFORT, (TaskPhase.BUILDING, "repair_required"): TaskPhase.REPAIRING, (TaskPhase.REPAIRING, "repair_started"): TaskPhase.AUTHORING_CDSL, (TaskPhase.PUBLISHING_BEST_EFFORT, "published"): TaskPhase.COMPLETED, (TaskPhase.PUBLISHING_BEST_EFFORT, "failed"): TaskPhase.FAILED, } for _phase in TaskPhase: if _phase not in {TaskPhase.COMPLETED, TaskPhase.FAILED, TaskPhase.CANCELLED}: _TRANSITIONS[(_phase, "failed")] = TaskPhase.FAILED _TRANSITIONS[(_phase, "cancelled")] = TaskPhase.CANCELLED if _phase in { TaskPhase.AUTHORING_CDSL, TaskPhase.COMPILING_CDSL, TaskPhase.BUILDING, TaskPhase.REPAIRING, }: _TRANSITIONS[(_phase, "publish_best_effort")] = TaskPhase.PUBLISHING_BEST_EFFORT def legal_transitions() -> dict[tuple[TaskPhase, str], TaskPhase]: return dict(_TRANSITIONS) def transition( state: TaskState, event: str, *, active_revision: str | None = None, repair_count: int | None = None, error: ErrorCode | None = None, requirements_path: str | None = None, authoring_path: str | None = None, runtime_cdsl_path: str | None = None, compile_audit_path: str | None = None, diagnostics_path: str | None = None, completion_path: str | None = None, clarification_path: str | None = None, ) -> TaskState: target = state.retry_from_phase if state.phase == TaskPhase.FAILED and event == "resume" else _TRANSITIONS.get((state.phase, event)) if target is None: raise ValueError(f"Illegal single-stage transition: {state.phase.value} --{event}--> ?") next_repairs = state.repair_count if repair_count is None else repair_count if not 0 <= next_repairs <= 2: raise ValueError("Single-stage repair count must be within [0, 2]") return replace( state, phase=target, version=state.version + 1, active_revision=state.active_revision if active_revision is None else active_revision, repair_count=next_repairs, last_error=error, retry_from_phase=state.phase if target == TaskPhase.FAILED and error in { ErrorCode.AUTHOR_TRANSPORT_UNAVAILABLE, ErrorCode.STORAGE_FAILURE, ErrorCode.RUNTIME_EXECUTION_FAILURE, } else None, requirements_path=state.requirements_path if requirements_path is None else requirements_path, authoring_path=state.authoring_path if authoring_path is None else authoring_path, runtime_cdsl_path=state.runtime_cdsl_path if runtime_cdsl_path is None else runtime_cdsl_path, compile_audit_path=state.compile_audit_path if compile_audit_path is None else compile_audit_path, diagnostics_path=state.diagnostics_path if diagnostics_path is None else diagnostics_path, completion_path=state.completion_path if completion_path is None else completion_path, clarification_path=state.clarification_path if clarification_path is None else clarification_path, )