|
|
@@ -88,22 +88,45 @@ class ScriptBuildValidationPolicy:
|
|
|
preset = "script_candidate_validator"
|
|
|
else:
|
|
|
raise ValueError(f"Unsupported script-build task kind: {kind!r}")
|
|
|
+ preflight_rules = [
|
|
|
+ "immutable-reference-present",
|
|
|
+ "sha256-digest-shape",
|
|
|
+ (
|
|
|
+ "root-delivery-artifact-shape"
|
|
|
+ if kind == ROOT_DELIVERY_KIND
|
|
|
+ else (
|
|
|
+ "phase-two-artifact-shape"
|
|
|
+ if kind in PHASE_TWO_KINDS
|
|
|
+ else "phase-one-artifact-shape"
|
|
|
+ )
|
|
|
+ ),
|
|
|
+ ]
|
|
|
+ if kind in PHASE_TWO_KINDS:
|
|
|
+ preflight_rules.extend(["business-artifact-owner", "realized-content"])
|
|
|
+ if kind in {"structure", "paragraph", "element-set", "compare"}:
|
|
|
+ preflight_rules.append("candidate-lineage")
|
|
|
+ if kind == "compare":
|
|
|
+ preflight_rules.append("comparison-fairness")
|
|
|
+ if kind == "compose":
|
|
|
+ preflight_rules.append("goal-coverage")
|
|
|
+ if kind == "candidate-portfolio":
|
|
|
+ preflight_rules.extend(["input-closure-digest", "goal-coverage"])
|
|
|
+ elif kind == ROOT_DELIVERY_KIND:
|
|
|
+ preflight_rules.extend(
|
|
|
+ [
|
|
|
+ "root-manifest-shape",
|
|
|
+ "root-blocking-defects-empty",
|
|
|
+ "root-input-closure",
|
|
|
+ "root-script-content",
|
|
|
+ "root-goal-coverage",
|
|
|
+ "root-direction-contract",
|
|
|
+ "root-legacy-projection",
|
|
|
+ ]
|
|
|
+ )
|
|
|
return ValidationPlan(
|
|
|
mode=ValidationMode.AGENT,
|
|
|
validator_preset=preset,
|
|
|
- preflight_rule_ids=(
|
|
|
- "immutable-reference-present",
|
|
|
- "sha256-digest-shape",
|
|
|
- (
|
|
|
- "root-delivery-artifact-shape"
|
|
|
- if kind == ROOT_DELIVERY_KIND
|
|
|
- else (
|
|
|
- "phase-two-artifact-shape"
|
|
|
- if kind in PHASE_TWO_KINDS
|
|
|
- else "phase-one-artifact-shape"
|
|
|
- )
|
|
|
- ),
|
|
|
- ),
|
|
|
+ preflight_rule_ids=tuple(preflight_rules),
|
|
|
max_evidence_queries=8,
|
|
|
max_evidence_items_per_query=25,
|
|
|
evidence_timeout_seconds=8.0,
|
|
|
@@ -215,9 +238,24 @@ def validate_phase_two_defects(
|
|
|
return defects
|
|
|
|
|
|
|
|
|
+class BusinessPreflight(Protocol):
|
|
|
+ async def deterministic_precheck(
|
|
|
+ self, *, context: Mapping[str, Any]
|
|
|
+ ) -> Sequence[Mapping[str, Any]]: ...
|
|
|
+
|
|
|
+
|
|
|
class ScriptBuildDeterministicValidator:
|
|
|
"""Optional deterministic validator for explicitly selected rule plans."""
|
|
|
|
|
|
+ def __init__(
|
|
|
+ self,
|
|
|
+ *,
|
|
|
+ candidates: BusinessPreflight | None = None,
|
|
|
+ root_delivery: BusinessPreflight | None = None,
|
|
|
+ ) -> None:
|
|
|
+ self._candidates = candidates
|
|
|
+ self._root_delivery = root_delivery
|
|
|
+
|
|
|
async def validate(
|
|
|
self,
|
|
|
context: ValidationContext,
|
|
|
@@ -225,7 +263,30 @@ class ScriptBuildDeterministicValidator:
|
|
|
) -> DeterministicValidationResult:
|
|
|
if plan.mode != ValidationMode.DETERMINISTIC:
|
|
|
raise ValueError("deterministic validator requires deterministic mode")
|
|
|
- available = {item.rule_id: item for item in precheck_snapshot(context)}
|
|
|
+ values = list(precheck_snapshot(context))
|
|
|
+ kind = task_kind(context.task.current_spec.context_refs)
|
|
|
+ protected_context = {
|
|
|
+ "root_trace_id": context.root_trace_id,
|
|
|
+ "task_id": context.task.task_id,
|
|
|
+ "attempt_id": context.attempt.attempt_id,
|
|
|
+ "snapshot_id": context.snapshot.snapshot_id,
|
|
|
+ "spec_version": context.task.current_spec_version,
|
|
|
+ }
|
|
|
+ if kind in PHASE_TWO_KINDS and self._candidates is not None:
|
|
|
+ values.extend(
|
|
|
+ _rule_result(item)
|
|
|
+ for item in await self._candidates.deterministic_precheck(
|
|
|
+ context=protected_context
|
|
|
+ )
|
|
|
+ )
|
|
|
+ elif kind == ROOT_DELIVERY_KIND and self._root_delivery is not None:
|
|
|
+ values.extend(
|
|
|
+ _rule_result(item)
|
|
|
+ for item in await self._root_delivery.deterministic_precheck(
|
|
|
+ context=protected_context
|
|
|
+ )
|
|
|
+ )
|
|
|
+ available = {item.rule_id: item for item in values}
|
|
|
selected = [
|
|
|
available.get(
|
|
|
rule_id,
|
|
|
@@ -248,6 +309,15 @@ class ScriptBuildDeterministicValidator:
|
|
|
return DeterministicValidationResult(verdict=verdict, rule_results=selected)
|
|
|
|
|
|
|
|
|
+def _rule_result(value: Mapping[str, Any]) -> DeterministicRuleResult:
|
|
|
+ return DeterministicRuleResult(
|
|
|
+ rule_id=str(value["rule_id"]),
|
|
|
+ verdict=ValidationVerdict(str(value["verdict"])),
|
|
|
+ reason=str(value.get("reason", "")),
|
|
|
+ error=(str(value["error"]) if value.get("error") is not None else None),
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
class EvidenceContentReader(Protocol):
|
|
|
async def read(
|
|
|
self,
|