|
|
@@ -89,12 +89,14 @@ class ValidationPlan:
|
|
|
mode: ValidationMode = ValidationMode.AGENT
|
|
|
validator_preset: Optional[str] = "validator"
|
|
|
rule_ids: Tuple[str, ...] = ()
|
|
|
+ preflight_rule_ids: Tuple[str, ...] = ()
|
|
|
max_evidence_queries: int = 10
|
|
|
max_evidence_items_per_query: int = 50
|
|
|
evidence_timeout_seconds: float = 10.0
|
|
|
|
|
|
def __post_init__(self) -> None:
|
|
|
object.__setattr__(self, "rule_ids", tuple(self.rule_ids))
|
|
|
+ object.__setattr__(self, "preflight_rule_ids", tuple(self.preflight_rule_ids))
|
|
|
if self.mode == ValidationMode.AGENT and self.rule_ids:
|
|
|
raise ValueError("Agent validation plans cannot contain deterministic rules")
|
|
|
if (
|
|
|
@@ -109,6 +111,10 @@ class ValidationPlan:
|
|
|
raise ValueError("Validation rule_ids must be unique")
|
|
|
if any(not item.strip() for item in self.rule_ids):
|
|
|
raise ValueError("Validation rule_ids cannot be empty")
|
|
|
+ if len(set(self.preflight_rule_ids)) != len(self.preflight_rule_ids):
|
|
|
+ raise ValueError("Validation preflight_rule_ids must be unique")
|
|
|
+ if any(not item.strip() for item in self.preflight_rule_ids):
|
|
|
+ raise ValueError("Validation preflight_rule_ids cannot be empty")
|
|
|
if (
|
|
|
isinstance(self.max_evidence_queries, bool)
|
|
|
or not isinstance(self.max_evidence_queries, int)
|
|
|
@@ -147,10 +153,9 @@ class ValidationPlan:
|
|
|
"validator" if mode == ValidationMode.AGENT else None,
|
|
|
),
|
|
|
rule_ids=tuple(data.get("rule_ids", [])),
|
|
|
+ preflight_rule_ids=tuple(data.get("preflight_rule_ids", [])),
|
|
|
max_evidence_queries=int(data.get("max_evidence_queries", 10)),
|
|
|
- max_evidence_items_per_query=int(
|
|
|
- data.get("max_evidence_items_per_query", 50)
|
|
|
- ),
|
|
|
+ max_evidence_items_per_query=int(data.get("max_evidence_items_per_query", 50)),
|
|
|
evidence_timeout_seconds=float(data.get("evidence_timeout_seconds", 10.0)),
|
|
|
)
|
|
|
|
|
|
@@ -206,13 +211,9 @@ class ExecutionStats:
|
|
|
def __post_init__(self) -> None:
|
|
|
if self.primary_model is not None:
|
|
|
if not isinstance(self.primary_model, str) or not self.primary_model.strip():
|
|
|
- raise ValueError(
|
|
|
- "ExecutionStats.primary_model must be a non-blank string"
|
|
|
- )
|
|
|
+ raise ValueError("ExecutionStats.primary_model must be a non-blank string")
|
|
|
if self.total_tokens is not None and (
|
|
|
- isinstance(self.total_tokens, bool)
|
|
|
- or not isinstance(self.total_tokens, int)
|
|
|
- or self.total_tokens < 0
|
|
|
+ isinstance(self.total_tokens, bool) or not isinstance(self.total_tokens, int) or self.total_tokens < 0
|
|
|
):
|
|
|
raise ValueError("ExecutionStats.total_tokens must be a non-negative integer")
|
|
|
if self.total_cost is not None and (
|
|
|
@@ -222,9 +223,7 @@ class ExecutionStats:
|
|
|
or self.total_cost < 0
|
|
|
):
|
|
|
raise ValueError("ExecutionStats.total_cost must be finite and non-negative")
|
|
|
- if self.failure_code is not None and not isinstance(
|
|
|
- self.failure_code, FailureCode
|
|
|
- ):
|
|
|
+ if self.failure_code is not None and not isinstance(self.failure_code, FailureCode):
|
|
|
try:
|
|
|
object.__setattr__(self, "failure_code", FailureCode(self.failure_code))
|
|
|
except (TypeError, ValueError) as exc:
|
|
|
@@ -285,11 +284,7 @@ class TaskSpec:
|
|
|
created_at: str = field(default_factory=utc_now)
|
|
|
|
|
|
def __post_init__(self) -> None:
|
|
|
- if (
|
|
|
- isinstance(self.version, bool)
|
|
|
- or not isinstance(self.version, int)
|
|
|
- or self.version <= 0
|
|
|
- ):
|
|
|
+ if isinstance(self.version, bool) or not isinstance(self.version, int) or self.version <= 0:
|
|
|
raise ValueError("TaskSpec.version must be a positive integer")
|
|
|
if not isinstance(self.objective, str) or not self.objective.strip():
|
|
|
raise ValueError("TaskSpec.objective must be a non-empty string")
|
|
|
@@ -303,9 +298,7 @@ class TaskSpec:
|
|
|
if not acceptance_criteria:
|
|
|
raise ValueError("TaskSpec.acceptance_criteria must contain at least one criterion")
|
|
|
if any(not isinstance(item, AcceptanceCriterion) for item in acceptance_criteria):
|
|
|
- raise ValueError(
|
|
|
- "TaskSpec.acceptance_criteria must contain AcceptanceCriterion values"
|
|
|
- )
|
|
|
+ raise ValueError("TaskSpec.acceptance_criteria must contain AcceptanceCriterion values")
|
|
|
criterion_ids = [item.criterion_id for item in acceptance_criteria]
|
|
|
if len(set(criterion_ids)) != len(criterion_ids):
|
|
|
raise ValueError("TaskSpec acceptance criterion IDs must be unique")
|
|
|
@@ -323,9 +316,7 @@ class TaskSpec:
|
|
|
if isinstance(criteria_data, (str, bytes, dict)) or criteria_data is None:
|
|
|
raise ValueError("TaskSpec.acceptance_criteria must be a collection")
|
|
|
try:
|
|
|
- acceptance_criteria = tuple(
|
|
|
- AcceptanceCriterion.from_dict(item) for item in criteria_data
|
|
|
- )
|
|
|
+ acceptance_criteria = tuple(AcceptanceCriterion.from_dict(item) for item in criteria_data)
|
|
|
except TypeError as exc:
|
|
|
raise ValueError("TaskSpec.acceptance_criteria must be a collection") from exc
|
|
|
return cls(
|
|
|
@@ -482,9 +473,7 @@ class TaskAttempt:
|
|
|
if self.accepted_child_decision_ids is not None:
|
|
|
values = tuple(self.accepted_child_decision_ids)
|
|
|
if any(not isinstance(item, str) or not item for item in values):
|
|
|
- raise ValueError(
|
|
|
- "TaskAttempt.accepted_child_decision_ids must contain non-empty strings"
|
|
|
- )
|
|
|
+ raise ValueError("TaskAttempt.accepted_child_decision_ids must contain non-empty strings")
|
|
|
object.__setattr__(self, "accepted_child_decision_ids", values)
|
|
|
|
|
|
@classmethod
|
|
|
@@ -509,9 +498,7 @@ class TaskAttempt:
|
|
|
snapshot_id=data.get("snapshot_id"),
|
|
|
submission=AttemptSubmission.from_dict(submission) if submission else None,
|
|
|
execution_stats=(
|
|
|
- ExecutionStats.from_dict(data["execution_stats"])
|
|
|
- if data.get("execution_stats") is not None
|
|
|
- else None
|
|
|
+ ExecutionStats.from_dict(data["execution_stats"]) if data.get("execution_stats") is not None else None
|
|
|
),
|
|
|
error=data.get("error"),
|
|
|
started_at=data.get("started_at"),
|
|
|
@@ -585,8 +572,7 @@ class ValidationReport:
|
|
|
validation_plan=ValidationPlan.from_dict(data.get("validation_plan", {})),
|
|
|
evidence_queries_used=int(data.get("evidence_queries_used", 0)),
|
|
|
evidence_query_results={
|
|
|
- str(key): dict(value)
|
|
|
- for key, value in data.get("evidence_query_results", {}).items()
|
|
|
+ str(key): dict(value) for key, value in data.get("evidence_query_results", {}).items()
|
|
|
},
|
|
|
status=ValidationRunStatus(data.get("status", ValidationRunStatus.PENDING.value)),
|
|
|
operation_id=data.get("operation_id"),
|
|
|
@@ -599,9 +585,7 @@ class ValidationReport:
|
|
|
risks=list(data.get("risks", [])),
|
|
|
recommendation=data.get("recommendation", ""),
|
|
|
execution_stats=(
|
|
|
- ExecutionStats.from_dict(data["execution_stats"])
|
|
|
- if data.get("execution_stats") is not None
|
|
|
- else None
|
|
|
+ ExecutionStats.from_dict(data["execution_stats"]) if data.get("execution_stats") is not None else None
|
|
|
),
|
|
|
error=data.get("error"),
|
|
|
started_at=data.get("started_at"),
|
|
|
@@ -853,12 +837,37 @@ def json_values(value: Any) -> Any:
|
|
|
|
|
|
|
|
|
__all__ = [
|
|
|
- "CompletionPolicy", "AgentRole", "TaskStatus", "AttemptStatus",
|
|
|
- "ValidationRunStatus", "ValidationVerdict", "ValidationMode", "ValidationPlan", "DecisionAction",
|
|
|
- "OperationKind", "OperationStatus", "FailureCode", "ExecutionStats",
|
|
|
- "AcceptanceCriterion", "TaskSpec", "TaskRecord", "TaskAttempt",
|
|
|
- "ArtifactRef", "ArtifactSnapshot", "AttemptSubmission", "CriterionResult",
|
|
|
- "ValidationReport", "PlannerDecision", "BackgroundOperation", "CommandRecord",
|
|
|
- "EventDraft", "OrchestrationEvent", "EventPage", "TaskLedger", "TaskCycleResult",
|
|
|
- "json_values", "new_id", "utc_now",
|
|
|
+ "CompletionPolicy",
|
|
|
+ "AgentRole",
|
|
|
+ "TaskStatus",
|
|
|
+ "AttemptStatus",
|
|
|
+ "ValidationRunStatus",
|
|
|
+ "ValidationVerdict",
|
|
|
+ "ValidationMode",
|
|
|
+ "ValidationPlan",
|
|
|
+ "DecisionAction",
|
|
|
+ "OperationKind",
|
|
|
+ "OperationStatus",
|
|
|
+ "FailureCode",
|
|
|
+ "ExecutionStats",
|
|
|
+ "AcceptanceCriterion",
|
|
|
+ "TaskSpec",
|
|
|
+ "TaskRecord",
|
|
|
+ "TaskAttempt",
|
|
|
+ "ArtifactRef",
|
|
|
+ "ArtifactSnapshot",
|
|
|
+ "AttemptSubmission",
|
|
|
+ "CriterionResult",
|
|
|
+ "ValidationReport",
|
|
|
+ "PlannerDecision",
|
|
|
+ "BackgroundOperation",
|
|
|
+ "CommandRecord",
|
|
|
+ "EventDraft",
|
|
|
+ "OrchestrationEvent",
|
|
|
+ "EventPage",
|
|
|
+ "TaskLedger",
|
|
|
+ "TaskCycleResult",
|
|
|
+ "json_values",
|
|
|
+ "new_id",
|
|
|
+ "utc_now",
|
|
|
]
|