Files
cdsl-cad/backend/app/cad_agent/domain/state.py
T

111 lines
4.6 KiB
Python

"""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,
)