|
|
@@ -1506,6 +1506,13 @@ class TaskCoordinator:
|
|
|
)
|
|
|
if preflight_result is not None:
|
|
|
validator_result = preflight_result
|
|
|
+ elif (
|
|
|
+ reused := await self._reuse_semantic_validation(
|
|
|
+ validation_context,
|
|
|
+ validation,
|
|
|
+ )
|
|
|
+ ) is not None:
|
|
|
+ validator_result = reused
|
|
|
elif validation.validation_plan.mode == ValidationMode.DETERMINISTIC:
|
|
|
validator_result = await asyncio.wait_for(
|
|
|
self._run_deterministic_validation(
|
|
|
@@ -1586,6 +1593,69 @@ class TaskCoordinator:
|
|
|
failure=validation.failure,
|
|
|
)
|
|
|
|
|
|
+ async def _reuse_semantic_validation(
|
|
|
+ self,
|
|
|
+ context: ValidationContext,
|
|
|
+ validation: ValidationReport,
|
|
|
+ ) -> Optional[ValidatorRunResult]:
|
|
|
+ """Reuse one completed semantic verdict for the same frozen Task snapshot."""
|
|
|
+
|
|
|
+ if validation.validation_plan.mode is not ValidationMode.AGENT:
|
|
|
+ return None
|
|
|
+ ledger = await self.task_store.load(context.root_trace_id)
|
|
|
+ for validation_id in reversed(context.task.validation_ids):
|
|
|
+ prior = ledger.validations.get(validation_id)
|
|
|
+ if (
|
|
|
+ prior is None
|
|
|
+ or prior.validation_id == validation.validation_id
|
|
|
+ or prior.status is not ValidationRunStatus.COMPLETED
|
|
|
+ or prior.verdict is None
|
|
|
+ or prior.spec_version != context.attempt.spec_version
|
|
|
+ or prior.validation_plan.mode is not ValidationMode.AGENT
|
|
|
+ or prior.summary.startswith("Deterministic preflight rejected")
|
|
|
+ ):
|
|
|
+ continue
|
|
|
+ prior_attempt = ledger.attempts.get(prior.attempt_id)
|
|
|
+ if prior_attempt is None or prior_attempt.snapshot_id is None:
|
|
|
+ continue
|
|
|
+ prior_snapshot = await self.artifact_store.get(
|
|
|
+ context.root_trace_id,
|
|
|
+ prior_attempt.snapshot_id,
|
|
|
+ )
|
|
|
+ if (
|
|
|
+ prior_snapshot.artifact_refs != context.snapshot.artifact_refs
|
|
|
+ or prior_snapshot.evidence_refs != context.snapshot.evidence_refs
|
|
|
+ ):
|
|
|
+ continue
|
|
|
+ await self.submit_validation(
|
|
|
+ {
|
|
|
+ "role": AgentRole.VALIDATOR.value,
|
|
|
+ "root_trace_id": context.root_trace_id,
|
|
|
+ "task_id": context.task.task_id,
|
|
|
+ "attempt_id": context.attempt.attempt_id,
|
|
|
+ "validation_id": validation.validation_id,
|
|
|
+ "snapshot_id": context.snapshot.snapshot_id,
|
|
|
+ "trace_id": validation.validator_trace_id,
|
|
|
+ "tool_call_id": f"reuse:{prior.validation_id}",
|
|
|
+ "operation_id": validation.operation_id,
|
|
|
+ "execution_epoch": validation.execution_epoch,
|
|
|
+ },
|
|
|
+ prior.verdict,
|
|
|
+ tuple(prior.criterion_results),
|
|
|
+ f"semantic_validation_reused_from={prior.validation_id};{prior.summary}"[:500],
|
|
|
+ tuple(prior.evidence_refs),
|
|
|
+ tuple(prior.unverified_claims),
|
|
|
+ tuple(prior.risks),
|
|
|
+ prior.recommendation,
|
|
|
+ )
|
|
|
+ return ValidatorRunResult(
|
|
|
+ trace_id=validation.validator_trace_id,
|
|
|
+ status="completed",
|
|
|
+ summary="semantic_validation_reused",
|
|
|
+ execution_stats=ExecutionStats(total_tokens=0, total_cost=0.0),
|
|
|
+ )
|
|
|
+ return None
|
|
|
+
|
|
|
async def _run_validation_preflight(
|
|
|
self,
|
|
|
context: ValidationContext,
|