""" Data models for quality validation and halting policies. Implements FR-9: QualityGate Validation Implements FR-10: Halting and Refinement Policy """ import uuid from abc import ABC, abstractmethod from dataclasses import dataclass, field from datetime import datetime from enum import Enum from typing import Any, Dict, List, Optional class GateType(Enum): """Type classification for quality gates.""" SCHEMA = "schema" PATTERN = "pattern" CUSTOM = "custom" class ValidationStatus(Enum): """Outcome status of a quality gate check.""" PASS = "pass" FAIL = "fail" WARNING = "warning" SKIPPED = "skipped" class HaltDecision(Enum): """Decision outcome from halting policy evaluation.""" CONTINUE = "continue" HALT_QUALITY_MET = "halted_quality_met" HALT_ITERATION_LIMIT = "halted_iteration_limit" HALT_BUDGET_EXHAUSTED = "halted_budget_exhausted" HALT_NO_IMPROVEMENT = "halted_no_improvement" @dataclass class ValidationDiagnostic: """ Single diagnostic message from a quality gate. Attributes: code: Machine-readable diagnostic code message: Human-readable description severity: Severity level (error, warning, info) """ code: str message: str severity: str = "error" def to_dict(self) -> Dict[str, Any]: """Convert to dictionary.""" return { "code": self.code, "message": self.message, "severity": self.severity, } @classmethod def from_dict(cls, data: Dict[str, Any]) -> "ValidationDiagnostic": """Create from dictionary.""" return cls( code=data["code"], message=data["message"], severity=data.get("severity", "error"), ) @dataclass class ValidationResult: """ Result of applying a quality gate to an artifact. Implements FR-9.3: Record pass/fail results and diagnostics. Attributes: id: Unique result identifier gate_id: ID of the quality gate that produced this result gate_type: Type of the quality gate artifact_id: ID of the validated artifact status: Pass/fail outcome score: Optional quality score (0.0-1.0) diagnostics: List of diagnostic messages validated_at: When validation occurred """ id: str gate_id: str gate_type: GateType artifact_id: str status: ValidationStatus score: Optional[float] = None diagnostics: List[ValidationDiagnostic] = field(default_factory=list) validated_at: datetime = field(default_factory=datetime.utcnow) @classmethod def create( cls, gate_id: str, gate_type: GateType, artifact_id: str, status: ValidationStatus, score: Optional[float] = None, diagnostics: Optional[List[ValidationDiagnostic]] = None, ) -> "ValidationResult": """Create a new ValidationResult.""" return cls( id=str(uuid.uuid4()), gate_id=gate_id, gate_type=gate_type, artifact_id=artifact_id, status=status, score=score, diagnostics=diagnostics or [], ) def to_dict(self) -> Dict[str, Any]: """Convert to dictionary for serialization.""" return { "id": self.id, "gate_id": self.gate_id, "gate_type": self.gate_type.value, "artifact_id": self.artifact_id, "status": self.status.value, "score": self.score, "diagnostics": [d.to_dict() for d in self.diagnostics], "validated_at": self.validated_at.isoformat(), } @classmethod def from_dict(cls, data: Dict[str, Any]) -> "ValidationResult": """Create from dictionary.""" return cls( id=data["id"], gate_id=data["gate_id"], gate_type=GateType(data["gate_type"]), artifact_id=data["artifact_id"], status=ValidationStatus(data["status"]), score=data.get("score"), diagnostics=[ ValidationDiagnostic.from_dict(d) for d in data.get("diagnostics", []) ], validated_at=datetime.fromisoformat(data["validated_at"]), ) class QualityGate(ABC): """ Abstract base class for quality gates. Implements FR-9.1/FR-9.2: Pluggable validation framework supporting multiple gates per artifact. """ def __init__(self, gate_id: str, name: str, gate_type: GateType): self.id = gate_id self.name = name self.gate_type = gate_type @abstractmethod def validate(self, content: str, artifact_id: str) -> ValidationResult: """ Validate content against this quality gate. Args: content: Content to validate artifact_id: ID of the artifact being validated Returns: ValidationResult with status and diagnostics """ pass @dataclass class QualityPolicy: """ Configuration for halting and refinement policy. Implements FR-10.1: Configurable QualityPolicies. Attributes: max_iterations: Maximum refinement iterations min_improvement: Minimum score improvement to continue fail_on_gate_failure: Whether any gate failure halts execution resource_budget: Maximum total runs allowed required_gate_ids: Gate IDs that must pass for quality to be met """ max_iterations: int = 3 min_improvement: float = 0.05 fail_on_gate_failure: bool = True resource_budget: int = 10 required_gate_ids: List[str] = field(default_factory=list) def to_dict(self) -> Dict[str, Any]: """Convert to dictionary.""" return { "max_iterations": self.max_iterations, "min_improvement": self.min_improvement, "fail_on_gate_failure": self.fail_on_gate_failure, "resource_budget": self.resource_budget, "required_gate_ids": self.required_gate_ids, } @classmethod def from_dict(cls, data: Dict[str, Any]) -> "QualityPolicy": """Create from dictionary.""" return cls( max_iterations=data.get("max_iterations", 3), min_improvement=data.get("min_improvement", 0.05), fail_on_gate_failure=data.get("fail_on_gate_failure", True), resource_budget=data.get("resource_budget", 10), required_gate_ids=data.get("required_gate_ids", []), ) @dataclass class HaltingRecord: """ Record of a halting decision. Implements FR-10.3: Record halting decisions in the RunManifest. Attributes: decision: The halting decision iteration: Current iteration number max_iterations: Maximum allowed iterations scores: Score history across iterations reason: Human-readable reason for decision recorded_at: When the decision was made """ decision: HaltDecision iteration: int max_iterations: int scores: List[float] = field(default_factory=list) reason: str = "" recorded_at: datetime = field(default_factory=datetime.utcnow) def to_dict(self) -> Dict[str, Any]: """Convert to dictionary for serialization.""" return { "decision": self.decision.value, "iteration": self.iteration, "max_iterations": self.max_iterations, "scores": self.scores, "reason": self.reason, "recorded_at": self.recorded_at.isoformat(), } @dataclass class RefinementResult: """ Result of a refinement loop execution. Attributes: iterations_run: Number of iterations executed final_results: Validation results from the last iteration halting_record: Record of the halting decision all_results: Validation results from all iterations run_ids: List of run IDs produced during refinement """ iterations_run: int final_results: List[ValidationResult] = field(default_factory=list) halting_record: Optional[HaltingRecord] = None all_results: List[List[ValidationResult]] = field(default_factory=list) run_ids: List[str] = field(default_factory=list) def to_dict(self) -> Dict[str, Any]: """Convert to dictionary.""" return { "iterations_run": self.iterations_run, "final_results": [r.to_dict() for r in self.final_results], "halting_record": self.halting_record.to_dict() if self.halting_record else None, "all_results": [ [r.to_dict() for r in iteration] for iteration in self.all_results ], "run_ids": self.run_ids, }