Source code for ralph.policy.models._phase_definition

"""PhaseDefinition Pydantic model."""

from typing import Literal, cast

from pydantic import Field, model_validator

from ralph.policy.models._artifact_history_policy import ArtifactHistoryPolicy
from ralph.policy.models._artifact_proof_policy import ArtifactProofPolicy
from ralph.policy.models._frozen_policy_model import _FrozenPolicyModel
from ralph.policy.models._phase_commit_policy import PhaseCommitPolicy
from ralph.policy.models._phase_decision_route import PhaseDecisionRoute
from ralph.policy.models._phase_loop_policy import PhaseLoopPolicy
from ralph.policy.models._phase_parallelization import PhaseParallelization
from ralph.policy.models._phase_retry_policy import PhaseRetryPolicy
from ralph.policy.models._phase_transition import PhaseTransition
from ralph.policy.models._phase_verification_policy import PhaseVerificationPolicy
from ralph.policy.models._phase_workflow_fallback import PhaseWorkflowFallback
from ralph.policy.models._types import PhaseRole


[docs] class PhaseDefinition(_FrozenPolicyModel): """Definition of a single phase in the pipeline graph.""" drain: str = Field(..., description="Drain binding for this phase") transitions: PhaseTransition = Field(..., description="Transition routing rules") role: PhaseRole | None = Field( default=None, description="Phase role classifying behavior contract", ) skip_invocation: bool = Field( default=False, description=( "When True, the runtime routes directly without invoking an agent. " "Useful for pass-through or routing-only phases." ), ) retry_policy: PhaseRetryPolicy | None = Field( default=None, description="Per-phase retry policy override", ) loop_policy: PhaseLoopPolicy | None = Field( default=None, description="Loop bounds for analysis phases", ) decisions: dict[str, PhaseDecisionRoute] = Field( default_factory=dict, description="Analysis decision routing map", ) commit_policy: PhaseCommitPolicy | None = Field( default=None, description="Commit semantics for commit phases", ) verification: PhaseVerificationPolicy | None = Field( default=None, description="Verification gating policy", ) artifact_required: bool = Field( default=True, description=( "Whether this phase's output artifact is required for phase success when " "the phase's drain has an artifact contract. Defaults to True." ), ) terminal_outcome: Literal["success", "failure"] | None = Field( default=None, description="Explicit terminal outcome declaration", ) bypass_routes: dict[str, str] = Field( default_factory=dict, description="Named bypass routes (outcome -> target phase)", ) clean_outcome: str | None = Field( default=None, description=( "For role='review': the bypass_routes key that signals a clean review. " "The reducer looks up this key in bypass_routes to find the target phase. " "Required when role='review' and bypass_routes is non-empty." ), ) issues_outcome: str | None = Field( default=None, description=( "For role='review': the value to set as review_outcome when issues are found. " "Required when role='review'. Drives review_outcome propagation downstream." ), ) prompt_template: str | None = Field( default=None, description="File-backed .jinja prompt template for this phase", ) continuation_template: str | None = Field( default=None, description="Optional continuation .jinja prompt template for this phase", ) loopback_prompt_template: str | None = Field( default=None, description=( "Optional alternate .jinja prompt template for loopback retries that " "need structured upstream feedback." ), ) parallelization: PhaseParallelization | None = Field( default=None, description=( "Transition-scoped parallelization policy. When None, multi-work-unit plans " "must not fan out from this phase." ), ) artifact_history: ArtifactHistoryPolicy | None = Field( default=None, description=( "Optional artifact history policy. When set with enabled=True, the runtime " "archives the prior canonical artifact and Markdown handoff before overwrite. " "Phases sharing the same drain must agree on artifact_history.enabled." ), ) artifact_proof_policy: ArtifactProofPolicy | None = Field( default=None, description=( "Optional proof-validation policy for development_result artifacts. When set, " "the runtime validates plan and analysis proof entries before accepting the " "development_result artifact." ), ) workflow_fallback: PhaseWorkflowFallback | None = Field( default=None, description=( "Policy-declared fallback route when this phase's agent chain is exhausted. " "When set, routes to target instead of the global recovery.failed_route. " "Takes precedence over recovery.failed_route on chain exhaustion." ), ) clear_drains_on_fresh_entry: list[str] = Field( default_factory=list, description=( "Drain names to clear on every genuine fresh phase entry. " "On fresh entry (program start, cross-phase transition, or last-commit re-entry), " "Ralph Workflow deletes the primary artifact JSON and Markdown handoff for each " "listed drain. Empty list means no drain-based clearing on entry. " "Contrast with artifact_history.clear_on_fresh_entry which clears only the history." ), ) display_style: str | None = Field( default=None, description=( "Per-phase rich style override for phase banners. " "When set, this style string is used instead of the role-based default in " "phase_banner.phase_style. For example, set to 'theme.phase.planning' to give " "the planning phase a distinct color from other execution-role phases. " "Available theme keys include theme.phase.planning, theme.phase.development, " "theme.phase.development_analysis, theme.phase.commit, and theme.phase.failed." ), ) @model_validator(mode="before") @classmethod def _reject_legacy_fields(cls, data: object) -> object: if not isinstance(data, dict): return data d: dict[str, object] = cast("dict[str, object]", data) if d.get("embeds_analysis"): raise ValueError( "PhaseDefinition.embeds_analysis has been removed. " "Set role='analysis' instead. " "See docs/sphinx/policy-driven-overhaul-migration.md." ) if d.get("requires_commit"): raise ValueError( "PhaseDefinition.requires_commit has been removed. " "Set role='commit' instead. " "See docs/sphinx/policy-driven-overhaul-migration.md." ) return d