|
@@ -7,31 +7,31 @@ framework's protected context and then checked against durable bindings.
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
-import asyncio
|
|
|
|
|
import re
|
|
import re
|
|
|
from collections.abc import Awaitable, Callable, Mapping, Sequence
|
|
from collections.abc import Awaitable, Callable, Mapping, Sequence
|
|
|
-from dataclasses import asdict, dataclass
|
|
|
|
|
-from datetime import UTC, datetime
|
|
|
|
|
|
|
+from dataclasses import asdict
|
|
|
|
|
+from datetime import datetime
|
|
|
from hashlib import sha256
|
|
from hashlib import sha256
|
|
|
from typing import Any, Protocol, cast
|
|
from typing import Any, Protocol, cast
|
|
|
from unicodedata import normalize
|
|
from unicodedata import normalize
|
|
|
-from uuid import uuid4
|
|
|
|
|
|
|
|
|
|
from agent.orchestration import (
|
|
from agent.orchestration import (
|
|
|
|
|
+ AgentRole,
|
|
|
ArtifactRef,
|
|
ArtifactRef,
|
|
|
AttemptSubmission,
|
|
AttemptSubmission,
|
|
|
CriterionResult,
|
|
CriterionResult,
|
|
|
EvidenceQuery,
|
|
EvidenceQuery,
|
|
|
ValidationVerdict,
|
|
ValidationVerdict,
|
|
|
)
|
|
)
|
|
|
|
|
+from agent.orchestration.validation_evidence import evidence_handle
|
|
|
|
|
|
|
|
from script_build_host.agents.validation import (
|
|
from script_build_host.agents.validation import (
|
|
|
- PHASE_TWO_KINDS,
|
|
|
|
|
RETRIEVAL_KINDS,
|
|
RETRIEVAL_KINDS,
|
|
|
ROOT_DELIVERY_KIND,
|
|
ROOT_DELIVERY_KIND,
|
|
|
precheck_business_artifact,
|
|
precheck_business_artifact,
|
|
|
precheck_snapshot,
|
|
precheck_snapshot,
|
|
|
task_kind,
|
|
task_kind,
|
|
|
|
|
+ validation_layer,
|
|
|
)
|
|
)
|
|
|
from script_build_host.domain.artifacts import (
|
|
from script_build_host.domain.artifacts import (
|
|
|
ArtifactKind,
|
|
ArtifactKind,
|
|
@@ -39,9 +39,11 @@ from script_build_host.domain.artifacts import (
|
|
|
DirectionConstraint,
|
|
DirectionConstraint,
|
|
|
DirectionGoal,
|
|
DirectionGoal,
|
|
|
DirectionPreference,
|
|
DirectionPreference,
|
|
|
- EvidenceRecordV1,
|
|
|
|
|
)
|
|
)
|
|
|
-from script_build_host.domain.errors import ArtifactNotFound, ProtocolViolation, ScriptBuildError
|
|
|
|
|
|
|
+from script_build_host.domain.errors import (
|
|
|
|
|
+ ProtocolViolation,
|
|
|
|
|
+ ValidationEvidenceUnauthorized,
|
|
|
|
|
+)
|
|
|
from script_build_host.domain.ports import (
|
|
from script_build_host.domain.ports import (
|
|
|
InputSnapshotRepository,
|
|
InputSnapshotRepository,
|
|
|
MissionBindingRepository,
|
|
MissionBindingRepository,
|
|
@@ -54,7 +56,7 @@ from script_build_host.domain.workbench import (
|
|
|
strategy_handle,
|
|
strategy_handle,
|
|
|
strategy_ref,
|
|
strategy_ref,
|
|
|
)
|
|
)
|
|
|
-from script_build_host.infrastructure.redaction import redact, redact_text
|
|
|
|
|
|
|
+from script_build_host.infrastructure.redaction import redact_text
|
|
|
|
|
|
|
|
from .contracts import RootDeliveryToolPort, ScriptCandidateToolPort, ScriptPlannerToolPort
|
|
from .contracts import RootDeliveryToolPort, ScriptCandidateToolPort, ScriptPlannerToolPort
|
|
|
|
|
|
|
@@ -66,26 +68,6 @@ _BUSINESS_ARTIFACT_KINDS = frozenset(item.value for item in ArtifactKind)
|
|
|
_DEFECT_CODE = re.compile(r"^[A-Z][A-Z0-9_]{0,63}$")
|
|
_DEFECT_CODE = re.compile(r"^[A-Z][A-Z0-9_]{0,63}$")
|
|
|
|
|
|
|
|
|
|
|
|
|
-@dataclass(frozen=True, slots=True)
|
|
|
|
|
-class RetrievalResult:
|
|
|
|
|
- source_refs: tuple[str, ...]
|
|
|
|
|
- summary: str
|
|
|
|
|
- supports: tuple[str, ...] = ()
|
|
|
|
|
- confidence: str = "unknown"
|
|
|
|
|
- limitations: tuple[str, ...] = ()
|
|
|
|
|
- raw_artifact_ref: str | None = None
|
|
|
|
|
- metadata: Mapping[str, Any] | None = None
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-class RetrievalAdapter(Protocol):
|
|
|
|
|
- async def retrieve(
|
|
|
|
|
- self,
|
|
|
|
|
- *,
|
|
|
|
|
- query: Mapping[str, Any],
|
|
|
|
|
- snapshot: Any,
|
|
|
|
|
- ) -> RetrievalResult: ...
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
class DispatchGuard(Protocol):
|
|
class DispatchGuard(Protocol):
|
|
|
async def ensure_dispatch_allowed(self, script_build_id: int) -> None: ...
|
|
async def ensure_dispatch_allowed(self, script_build_id: int) -> None: ...
|
|
|
|
|
|
|
@@ -108,7 +90,6 @@ class LegacyScriptToolGateway:
|
|
|
snapshots: InputSnapshotRepository,
|
|
snapshots: InputSnapshotRepository,
|
|
|
artifacts: ScriptBusinessArtifactRepository,
|
|
artifacts: ScriptBusinessArtifactRepository,
|
|
|
coordinator: Any,
|
|
coordinator: Any,
|
|
|
- retrieval_adapters: Mapping[str, RetrievalAdapter] | None = None,
|
|
|
|
|
dispatch_guard: DispatchGuard | None = None,
|
|
dispatch_guard: DispatchGuard | None = None,
|
|
|
planner_tools: ScriptPlannerToolPort | None = None,
|
|
planner_tools: ScriptPlannerToolPort | None = None,
|
|
|
candidate_tools: ScriptCandidateToolPort | None = None,
|
|
candidate_tools: ScriptCandidateToolPort | None = None,
|
|
@@ -120,14 +101,12 @@ class LegacyScriptToolGateway:
|
|
|
self.snapshots = snapshots
|
|
self.snapshots = snapshots
|
|
|
self.artifacts = artifacts
|
|
self.artifacts = artifacts
|
|
|
self.coordinator = coordinator
|
|
self.coordinator = coordinator
|
|
|
- self.retrieval_adapters = dict(retrieval_adapters or {})
|
|
|
|
|
self.dispatch_guard = dispatch_guard
|
|
self.dispatch_guard = dispatch_guard
|
|
|
self.planner_tools = planner_tools
|
|
self.planner_tools = planner_tools
|
|
|
self.candidate_tools = candidate_tools
|
|
self.candidate_tools = candidate_tools
|
|
|
self.budget_guard = budget_guard
|
|
self.budget_guard = budget_guard
|
|
|
self.root_tools = root_tools
|
|
self.root_tools = root_tools
|
|
|
self.workbench = workbench
|
|
self.workbench = workbench
|
|
|
- self._retrieval_locks: dict[tuple[str, str, str], asyncio.Lock] = {}
|
|
|
|
|
|
|
|
|
|
async def ensure_dispatch_allowed(self, context: Mapping[str, Any]) -> None:
|
|
async def ensure_dispatch_allowed(self, context: Mapping[str, Any]) -> None:
|
|
|
binding = await self.bindings.get_by_root(_required(context, "root_trace_id"))
|
|
binding = await self.bindings.get_by_root(_required(context, "root_trace_id"))
|
|
@@ -145,8 +124,7 @@ class LegacyScriptToolGateway:
|
|
|
async def plan_script_tasks(
|
|
async def plan_script_tasks(
|
|
|
self,
|
|
self,
|
|
|
*,
|
|
*,
|
|
|
- contracts: Sequence[Mapping[str, Any]],
|
|
|
|
|
- parent_task_id: str | None,
|
|
|
|
|
|
|
+ intents: Sequence[Mapping[str, Any]],
|
|
|
context: Mapping[str, Any],
|
|
context: Mapping[str, Any],
|
|
|
) -> Mapping[str, Any]:
|
|
) -> Mapping[str, Any]:
|
|
|
return cast(
|
|
return cast(
|
|
@@ -154,8 +132,7 @@ class LegacyScriptToolGateway:
|
|
|
await self._execute_fenced(
|
|
await self._execute_fenced(
|
|
|
context,
|
|
context,
|
|
|
lambda: self._planner().plan_script_tasks(
|
|
lambda: self._planner().plan_script_tasks(
|
|
|
- contract_payloads=contracts,
|
|
|
|
|
- parent_task_id=parent_task_id,
|
|
|
|
|
|
|
+ intents=intents,
|
|
|
context=context,
|
|
context=context,
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
@@ -168,9 +145,6 @@ class LegacyScriptToolGateway:
|
|
|
action: str,
|
|
action: str,
|
|
|
reason: str,
|
|
reason: str,
|
|
|
validation_id: str | None,
|
|
validation_id: str | None,
|
|
|
- replacement_contract: Mapping[str, Any] | None,
|
|
|
|
|
- child_contracts: Sequence[Mapping[str, Any]],
|
|
|
|
|
- selected_decision_ids: Sequence[str],
|
|
|
|
|
context: Mapping[str, Any],
|
|
context: Mapping[str, Any],
|
|
|
) -> Mapping[str, Any]:
|
|
) -> Mapping[str, Any]:
|
|
|
return cast(
|
|
return cast(
|
|
@@ -182,9 +156,6 @@ class LegacyScriptToolGateway:
|
|
|
action=action,
|
|
action=action,
|
|
|
reason=reason,
|
|
reason=reason,
|
|
|
validation_id=validation_id,
|
|
validation_id=validation_id,
|
|
|
- replacement_contract=replacement_contract,
|
|
|
|
|
- child_contracts=child_contracts,
|
|
|
|
|
- selected_decision_ids=selected_decision_ids,
|
|
|
|
|
context=context,
|
|
context=context,
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
@@ -235,6 +206,67 @@ class LegacyScriptToolGateway:
|
|
|
) -> Mapping[str, Any]:
|
|
) -> Mapping[str, Any]:
|
|
|
if self.workbench is None:
|
|
if self.workbench is None:
|
|
|
raise ProtocolViolation("Context Broker is not configured")
|
|
raise ProtocolViolation("Context Broker is not configured")
|
|
|
|
|
+ if str(context.get("role") or "") == AgentRole.VALIDATOR.value:
|
|
|
|
|
+ if section != "content":
|
|
|
|
|
+ raise ProtocolViolation(
|
|
|
|
|
+ "Validator evidence reads must use the complete content view"
|
|
|
|
|
+ )
|
|
|
|
|
+ root_trace_id = _required(context, "root_trace_id")
|
|
|
|
|
+ validation_id = _required(context, "validation_id")
|
|
|
|
|
+ ledger = await self.coordinator.task_store.load(root_trace_id)
|
|
|
|
|
+ validation = ledger.validations.get(validation_id)
|
|
|
|
|
+ if validation is None or validation.read_session is None:
|
|
|
|
|
+ raise ProtocolViolation("Validation evidence session is not initialized")
|
|
|
|
|
+ grant = next(
|
|
|
|
|
+ (
|
|
|
|
|
+ item
|
|
|
|
|
+ for item in validation.evidence_snapshot.grants
|
|
|
|
|
+ if item.handle == handle
|
|
|
|
|
+ ),
|
|
|
|
|
+ None,
|
|
|
|
|
+ )
|
|
|
|
|
+ if grant is None:
|
|
|
|
|
+ raise ProtocolViolation(
|
|
|
|
|
+ "context handle is outside this Validation evidence snapshot"
|
|
|
|
|
+ )
|
|
|
|
|
+ reserved = await self.coordinator.reserve_validation_read(
|
|
|
|
|
+ root_trace_id,
|
|
|
|
|
+ validation_id,
|
|
|
|
|
+ evidence_revision=validation.evidence_snapshot.revision,
|
|
|
|
|
+ handle=handle,
|
|
|
|
|
+ cursor=cursor,
|
|
|
|
|
+ )
|
|
|
|
|
+ if reserved["replayed"]:
|
|
|
|
|
+ return cast(Mapping[str, Any], reserved["result"])
|
|
|
|
|
+ try:
|
|
|
|
|
+ payload = await self.workbench.read_validation_evidence(
|
|
|
|
|
+ root_trace_id=root_trace_id,
|
|
|
|
|
+ validation_id=validation_id,
|
|
|
|
|
+ artifact_ref=grant.artifact_ref,
|
|
|
|
|
+ handle=handle,
|
|
|
|
|
+ section=section,
|
|
|
|
|
+ cursor=cursor,
|
|
|
|
|
+ )
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ await self.coordinator.cancel_validation_read(
|
|
|
|
|
+ root_trace_id,
|
|
|
|
|
+ validation_id,
|
|
|
|
|
+ evidence_revision=validation.evidence_snapshot.revision,
|
|
|
|
|
+ handle=handle,
|
|
|
|
|
+ cursor=cursor,
|
|
|
|
|
+ )
|
|
|
|
|
+ raise
|
|
|
|
|
+ await self.coordinator.complete_validation_read(
|
|
|
|
|
+ root_trace_id,
|
|
|
|
|
+ validation_id,
|
|
|
|
|
+ evidence_revision=validation.evidence_snapshot.revision,
|
|
|
|
|
+ handle=handle,
|
|
|
|
|
+ cursor=cursor,
|
|
|
|
|
+ next_cursor=cast(str | None, payload.get("next_cursor")),
|
|
|
|
|
+ exhausted=bool(payload.get("exhausted")),
|
|
|
|
|
+ result=payload,
|
|
|
|
|
+ )
|
|
|
|
|
+ return cast(Mapping[str, Any], payload)
|
|
|
return cast(
|
|
return cast(
|
|
|
Mapping[str, Any],
|
|
Mapping[str, Any],
|
|
|
await self.workbench.read_mission_context(
|
|
await self.workbench.read_mission_context(
|
|
@@ -352,121 +384,6 @@ class LegacyScriptToolGateway:
|
|
|
)
|
|
)
|
|
|
return values
|
|
return values
|
|
|
|
|
|
|
|
- async def retrieve(
|
|
|
|
|
- self,
|
|
|
|
|
- source_type: str,
|
|
|
|
|
- tool_name: str,
|
|
|
|
|
- query: Mapping[str, Any],
|
|
|
|
|
- context: Mapping[str, Any],
|
|
|
|
|
- ) -> dict[str, Any]:
|
|
|
|
|
- await self.ensure_dispatch_allowed(context)
|
|
|
|
|
- if self.budget_guard is not None:
|
|
|
|
|
- await self.budget_guard.ensure_tool_budget(
|
|
|
|
|
- root_trace_id=_required(context, "root_trace_id"),
|
|
|
|
|
- task_id=_required(context, "task_id"),
|
|
|
|
|
- entry="retrieval",
|
|
|
|
|
- )
|
|
|
|
|
- root_trace_id = _required(context, "root_trace_id")
|
|
|
|
|
- task_id = _required(context, "task_id")
|
|
|
|
|
- attempt_id = _required(context, "attempt_id")
|
|
|
|
|
- retrieval_key = (root_trace_id, task_id, attempt_id)
|
|
|
|
|
- lock = self._retrieval_locks.setdefault(retrieval_key, asyncio.Lock())
|
|
|
|
|
- async with lock:
|
|
|
|
|
- binding = await self.bindings.get_by_root(root_trace_id)
|
|
|
|
|
- try:
|
|
|
|
|
- existing = await self.artifacts.get_by_attempt(
|
|
|
|
|
- script_build_id=binding.script_build_id,
|
|
|
|
|
- task_id=task_id,
|
|
|
|
|
- attempt_id=attempt_id,
|
|
|
|
|
- )
|
|
|
|
|
- except ArtifactNotFound:
|
|
|
|
|
- pass
|
|
|
|
|
- else:
|
|
|
|
|
- artifact = existing.artifact
|
|
|
|
|
- frozen_query = redact(dict(query))
|
|
|
|
|
- if not isinstance(artifact, EvidenceRecordV1) or not isinstance(
|
|
|
|
|
- frozen_query, dict
|
|
|
|
|
- ):
|
|
|
|
|
- raise ProtocolViolation(
|
|
|
|
|
- "Retrieval Attempt already owns a non-Evidence artifact"
|
|
|
|
|
- )
|
|
|
|
|
- stored_query = dict(artifact.query)
|
|
|
|
|
- stored_query.pop("_retrieval_metadata", None)
|
|
|
|
|
- if (
|
|
|
|
|
- artifact.source_type != source_type
|
|
|
|
|
- or artifact.tool_name != tool_name
|
|
|
|
|
- or stored_query != frozen_query
|
|
|
|
|
- ):
|
|
|
|
|
- raise ProtocolViolation(
|
|
|
|
|
- "Retrieval Attempt is already bound to a different external query"
|
|
|
|
|
- )
|
|
|
|
|
- return _evidence_tool_payload(
|
|
|
|
|
- existing,
|
|
|
|
|
- root_trace_id=root_trace_id,
|
|
|
|
|
- task_id=task_id,
|
|
|
|
|
- attempt_id=attempt_id,
|
|
|
|
|
- resumed=True,
|
|
|
|
|
- )
|
|
|
|
|
- return await self._retrieve_once(source_type, tool_name, query, context)
|
|
|
|
|
-
|
|
|
|
|
- async def _retrieve_once(
|
|
|
|
|
- self,
|
|
|
|
|
- source_type: str,
|
|
|
|
|
- tool_name: str,
|
|
|
|
|
- query: Mapping[str, Any],
|
|
|
|
|
- context: Mapping[str, Any],
|
|
|
|
|
- ) -> dict[str, Any]:
|
|
|
|
|
- binding, snapshot = await self._scope(context)
|
|
|
|
|
- if source_type == "decode" and query.get("account_name"):
|
|
|
|
|
- frozen_accounts = {
|
|
|
|
|
- str(snapshot.account.get(key) or "")
|
|
|
|
|
- for key in ("account_name", "resolved_account_name")
|
|
|
|
|
- }
|
|
|
|
|
- if query["account_name"] not in frozen_accounts:
|
|
|
|
|
- raise ProtocolViolation("decode account_name is outside the frozen account scope")
|
|
|
|
|
- adapter = self.retrieval_adapters.get(source_type)
|
|
|
|
|
- if adapter is None:
|
|
|
|
|
- raise ProtocolViolation(f"retrieval adapter is not configured: {source_type}")
|
|
|
|
|
- result = await adapter.retrieve(query=dict(query), snapshot=snapshot)
|
|
|
|
|
- frozen_query = redact(dict(query))
|
|
|
|
|
- if not isinstance(frozen_query, dict):
|
|
|
|
|
- raise ProtocolViolation("redacted retrieval query must remain an object")
|
|
|
|
|
- if result.metadata:
|
|
|
|
|
- safe_metadata = redact(dict(result.metadata))
|
|
|
|
|
- if not isinstance(safe_metadata, dict):
|
|
|
|
|
- raise ProtocolViolation("redacted retrieval metadata must remain an object")
|
|
|
|
|
- frozen_query["_retrieval_metadata"] = safe_metadata
|
|
|
|
|
- record = EvidenceRecordV1(
|
|
|
|
|
- evidence_id=str(uuid4()),
|
|
|
|
|
- source_type=source_type,
|
|
|
|
|
- tool_name=tool_name,
|
|
|
|
|
- query=frozen_query,
|
|
|
|
|
- source_refs=_safe_text_tuple(result.source_refs),
|
|
|
|
|
- raw_artifact_ref=(
|
|
|
|
|
- redact_text(result.raw_artifact_ref) if result.raw_artifact_ref else None
|
|
|
|
|
- ),
|
|
|
|
|
- summary=redact_text(result.summary),
|
|
|
|
|
- supports=_safe_text_tuple(result.supports),
|
|
|
|
|
- confidence=redact_text(result.confidence),
|
|
|
|
|
- limitations=_safe_text_tuple(result.limitations),
|
|
|
|
|
- content_sha256="",
|
|
|
|
|
- created_at=datetime.now(UTC),
|
|
|
|
|
- )
|
|
|
|
|
- version, _ref = await self.artifacts.freeze(
|
|
|
|
|
- script_build_id=binding.script_build_id,
|
|
|
|
|
- task_id=_required(context, "task_id"),
|
|
|
|
|
- attempt_id=_required(context, "attempt_id"),
|
|
|
|
|
- spec_version=_required_int(context, "spec_version"),
|
|
|
|
|
- artifact=record,
|
|
|
|
|
- )
|
|
|
|
|
- return _evidence_tool_payload(
|
|
|
|
|
- version,
|
|
|
|
|
- root_trace_id=_required(context, "root_trace_id"),
|
|
|
|
|
- task_id=_required(context, "task_id"),
|
|
|
|
|
- attempt_id=_required(context, "attempt_id"),
|
|
|
|
|
- resumed=False,
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
async def view_frozen_images(
|
|
async def view_frozen_images(
|
|
|
self, image_handles: Sequence[str], context: Mapping[str, Any]
|
|
self, image_handles: Sequence[str], context: Mapping[str, Any]
|
|
|
) -> Sequence[Mapping[str, Any]]:
|
|
) -> Sequence[Mapping[str, Any]]:
|
|
@@ -607,18 +524,18 @@ class LegacyScriptToolGateway:
|
|
|
if task is None:
|
|
if task is None:
|
|
|
raise ProtocolViolation("validation task is outside the protected mission")
|
|
raise ProtocolViolation("validation task is outside the protected mission")
|
|
|
snapshot = await self.coordinator.artifact_store.get(root_trace_id, snapshot_id)
|
|
snapshot = await self.coordinator.artifact_store.get(root_trace_id, snapshot_id)
|
|
|
- allowed_values = [*snapshot.artifact_refs, *snapshot.evidence_refs]
|
|
|
|
|
- kind = task_kind(task.current_spec.context_refs)
|
|
|
|
|
- if kind in PHASE_TWO_KINDS:
|
|
|
|
|
- evidence_reader = getattr(self._candidates(), "validation_evidence_refs", None)
|
|
|
|
|
- if callable(evidence_reader):
|
|
|
|
|
- allowed_values.extend(await evidence_reader(context=context))
|
|
|
|
|
- elif kind == ROOT_DELIVERY_KIND:
|
|
|
|
|
- evidence_reader = getattr(self._root_tools(), "validation_evidence_refs", None)
|
|
|
|
|
- if callable(evidence_reader):
|
|
|
|
|
- allowed_values.extend(await evidence_reader(context=context))
|
|
|
|
|
- allowed_refs = {(ref.uri, ref.version, ref.digest, ref.kind): ref for ref in allowed_values}
|
|
|
|
|
- refs_by_handle = {_evidence_handle(ref): ref for ref in allowed_refs.values()}
|
|
|
|
|
|
|
+ validation_id = _required(context, "validation_id")
|
|
|
|
|
+ validation = ledger.validations.get(validation_id)
|
|
|
|
|
+ if validation is None:
|
|
|
|
|
+ raise ProtocolViolation("Validation is outside the protected mission")
|
|
|
|
|
+ refs_by_handle = {
|
|
|
|
|
+ item.handle: item.artifact_ref
|
|
|
|
|
+ for item in validation.evidence_snapshot.grants
|
|
|
|
|
+ }
|
|
|
|
|
+ allowed_refs = {
|
|
|
|
|
+ (ref.uri, ref.version, ref.digest, ref.kind): ref
|
|
|
|
|
+ for ref in refs_by_handle.values()
|
|
|
|
|
+ }
|
|
|
normalized_defects = _normalize_defects(
|
|
normalized_defects = _normalize_defects(
|
|
|
[_expand_validation_handles(item, refs_by_handle) for item in defects],
|
|
[_expand_validation_handles(item, refs_by_handle) for item in defects],
|
|
|
allowed_refs,
|
|
allowed_refs,
|
|
@@ -662,17 +579,6 @@ class LegacyScriptToolGateway:
|
|
|
if set(result_by_id) != expected_ids:
|
|
if set(result_by_id) != expected_ids:
|
|
|
raise ProtocolViolation("validation must report every Task criterion exactly once")
|
|
raise ProtocolViolation("validation must report every Task criterion exactly once")
|
|
|
overall = ValidationVerdict(verdict)
|
|
overall = ValidationVerdict(verdict)
|
|
|
- if overall is ValidationVerdict.PASSED and self.workbench is not None:
|
|
|
|
|
- try:
|
|
|
|
|
- await self.workbench.verify_context_exhausted(
|
|
|
|
|
- root_trace_id=root_trace_id,
|
|
|
|
|
- task_id=task_id,
|
|
|
|
|
- attempt_id=_required(context, "attempt_id"),
|
|
|
|
|
- )
|
|
|
|
|
- except Exception as exc:
|
|
|
|
|
- if getattr(exc, "code", None) != "CONTEXT_NOT_EXHAUSTED":
|
|
|
|
|
- raise
|
|
|
|
|
- raise ScriptBuildError("CONTEXT_NOT_EXHAUSTED", str(exc)) from exc
|
|
|
|
|
blocking = [item for item in normalized_defects if item["severity"] in {"hard", "critical"}]
|
|
blocking = [item for item in normalized_defects if item["severity"] in {"hard", "critical"}]
|
|
|
if overall is ValidationVerdict.PASSED and blocking:
|
|
if overall is ValidationVerdict.PASSED and blocking:
|
|
|
raise ProtocolViolation("passed validation cannot contain hard or critical defects")
|
|
raise ProtocolViolation("passed validation cannot contain hard or critical defects")
|
|
@@ -691,6 +597,7 @@ class LegacyScriptToolGateway:
|
|
|
attempt=attempt,
|
|
attempt=attempt,
|
|
|
snapshot=snapshot,
|
|
snapshot=snapshot,
|
|
|
prior_validation_count=max(0, len(task.validation_ids) - 1),
|
|
prior_validation_count=max(0, len(task.validation_ids) - 1),
|
|
|
|
|
+ context_documents=ledger.context_documents,
|
|
|
)
|
|
)
|
|
|
deterministic = precheck_snapshot(validation_context)
|
|
deterministic = precheck_snapshot(validation_context)
|
|
|
if overall is ValidationVerdict.PASSED and any(
|
|
if overall is ValidationVerdict.PASSED and any(
|
|
@@ -699,7 +606,10 @@ class LegacyScriptToolGateway:
|
|
|
raise ProtocolViolation("passed validation conflicts with deterministic precheck")
|
|
raise ProtocolViolation("passed validation conflicts with deterministic precheck")
|
|
|
if (
|
|
if (
|
|
|
overall is ValidationVerdict.PASSED
|
|
overall is ValidationVerdict.PASSED
|
|
|
- and task_kind(task.current_spec.context_refs) in PHASE_TWO_KINDS
|
|
|
|
|
|
|
+ and validation_layer(
|
|
|
|
|
+ task_kind(task.current_spec.context_refs) or ""
|
|
|
|
|
+ )
|
|
|
|
|
+ in {"local", "compare", "global", "governance"}
|
|
|
):
|
|
):
|
|
|
binding = await self.bindings.get_by_root(root_trace_id)
|
|
binding = await self.bindings.get_by_root(root_trace_id)
|
|
|
for ref in snapshot.artifact_refs:
|
|
for ref in snapshot.artifact_refs:
|
|
@@ -720,7 +630,10 @@ class LegacyScriptToolGateway:
|
|
|
)
|
|
)
|
|
|
if (
|
|
if (
|
|
|
overall is ValidationVerdict.PASSED
|
|
overall is ValidationVerdict.PASSED
|
|
|
- and task_kind(task.current_spec.context_refs) == ROOT_DELIVERY_KIND
|
|
|
|
|
|
|
+ and validation_layer(
|
|
|
|
|
+ task_kind(task.current_spec.context_refs) or ""
|
|
|
|
|
+ )
|
|
|
|
|
+ == "root-delivery"
|
|
|
):
|
|
):
|
|
|
root_rules = await self._root_tools().deterministic_precheck(context=context)
|
|
root_rules = await self._root_tools().deterministic_precheck(context=context)
|
|
|
if any(item.get("verdict") != "passed" for item in root_rules):
|
|
if any(item.get("verdict") != "passed" for item in root_rules):
|
|
@@ -894,9 +807,9 @@ class LegacyScriptToolGateway:
|
|
|
from agent.orchestration.validation_policy import ValidationContext
|
|
from agent.orchestration.validation_policy import ValidationContext
|
|
|
|
|
|
|
|
from script_build_host.agents.validation import (
|
|
from script_build_host.agents.validation import (
|
|
|
- PHASE_TWO_KINDS,
|
|
|
|
|
precheck_snapshot,
|
|
precheck_snapshot,
|
|
|
task_kind,
|
|
task_kind,
|
|
|
|
|
+ validation_layer,
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
root_trace_id = _required(context, "root_trace_id")
|
|
root_trace_id = _required(context, "root_trace_id")
|
|
@@ -917,9 +830,10 @@ class LegacyScriptToolGateway:
|
|
|
item.to_dict() for item in precheck_snapshot(validation_context)
|
|
item.to_dict() for item in precheck_snapshot(validation_context)
|
|
|
]
|
|
]
|
|
|
kind = task_kind(task.current_spec.context_refs)
|
|
kind = task_kind(task.current_spec.context_refs)
|
|
|
- if kind in PHASE_TWO_KINDS:
|
|
|
|
|
|
|
+ layer = validation_layer(kind or "")
|
|
|
|
|
+ if layer in {"local", "compare", "global", "governance"}:
|
|
|
rules.extend(await self._candidates().deterministic_precheck(context=context))
|
|
rules.extend(await self._candidates().deterministic_precheck(context=context))
|
|
|
- elif kind == "root-delivery":
|
|
|
|
|
|
|
+ elif layer == "root-delivery":
|
|
|
rules.extend(await self._root_tools().deterministic_precheck(context=context))
|
|
rules.extend(await self._root_tools().deterministic_precheck(context=context))
|
|
|
return {"rule_results": rules}
|
|
return {"rule_results": rules}
|
|
|
|
|
|
|
@@ -948,28 +862,6 @@ class LegacyScriptToolGateway:
|
|
|
return self.root_tools
|
|
return self.root_tools
|
|
|
|
|
|
|
|
|
|
|
|
|
-def _evidence_tool_payload(
|
|
|
|
|
- version: Any,
|
|
|
|
|
- *,
|
|
|
|
|
- root_trace_id: str,
|
|
|
|
|
- task_id: str,
|
|
|
|
|
- attempt_id: str,
|
|
|
|
|
- resumed: bool,
|
|
|
|
|
-) -> dict[str, Any]:
|
|
|
|
|
- artifact = version.artifact
|
|
|
|
|
- if not isinstance(artifact, EvidenceRecordV1):
|
|
|
|
|
- raise ProtocolViolation("Retrieval Attempt does not own Evidence")
|
|
|
|
|
- digest = str(version.canonical_sha256 or version.artifact_version_id)
|
|
|
|
|
- return {
|
|
|
|
|
- "evidence_handle": semantic_handle("evidence", root_trace_id, task_id, digest),
|
|
|
|
|
- "evidence": protect_model_value(
|
|
|
|
|
- _json_values(asdict(artifact)), namespace=attempt_id
|
|
|
|
|
- ),
|
|
|
|
|
- "source_count": len(artifact.source_refs),
|
|
|
|
|
- "resumed_from_frozen_evidence": resumed,
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
def _required(context: Mapping[str, Any], key: str) -> str:
|
|
def _required(context: Mapping[str, Any], key: str) -> str:
|
|
|
value = context.get(key)
|
|
value = context.get(key)
|
|
|
if not isinstance(value, str) or not value.strip():
|
|
if not isinstance(value, str) or not value.strip():
|
|
@@ -1110,7 +1002,7 @@ def _attempt_summary(ref: ArtifactRef, scope_ref: str) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
def _evidence_handle(ref: ArtifactRef) -> str:
|
|
def _evidence_handle(ref: ArtifactRef) -> str:
|
|
|
- return semantic_handle("src", ref.uri, ref.digest or ref.version or "")
|
|
|
|
|
|
|
+ return cast(str, evidence_handle(ref))
|
|
|
|
|
|
|
|
|
|
|
|
|
def _required_evidence_handle(
|
|
def _required_evidence_handle(
|
|
@@ -1119,7 +1011,9 @@ def _required_evidence_handle(
|
|
|
handle = str(value or "").strip()
|
|
handle = str(value or "").strip()
|
|
|
ref = refs_by_handle.get(handle)
|
|
ref = refs_by_handle.get(handle)
|
|
|
if ref is None:
|
|
if ref is None:
|
|
|
- raise ProtocolViolation("validation references an unknown evidence handle")
|
|
|
|
|
|
|
+ raise ValidationEvidenceUnauthorized(
|
|
|
|
|
+ "validation references an unknown evidence handle"
|
|
|
|
|
+ )
|
|
|
return ref
|
|
return ref
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1229,7 +1123,9 @@ def _normalize_evidence_refs(
|
|
|
key = (candidate.uri, candidate.version, candidate.digest, candidate.kind)
|
|
key = (candidate.uri, candidate.version, candidate.digest, candidate.kind)
|
|
|
frozen = allowed_refs.get(key)
|
|
frozen = allowed_refs.get(key)
|
|
|
if frozen is None:
|
|
if frozen is None:
|
|
|
- raise ProtocolViolation(f"{label} must come from the protected validation closure")
|
|
|
|
|
|
|
+ raise ValidationEvidenceUnauthorized(
|
|
|
|
|
+ f"{label} must come from the protected validation closure"
|
|
|
|
|
+ )
|
|
|
refs.append(frozen)
|
|
refs.append(frozen)
|
|
|
return _dedupe_refs(refs)
|
|
return _dedupe_refs(refs)
|
|
|
|
|
|
|
@@ -1256,8 +1152,6 @@ def _dedupe_refs(values: Any) -> tuple[ArtifactRef, ...]:
|
|
|
__all__ = [
|
|
__all__ = [
|
|
|
"DispatchGuard",
|
|
"DispatchGuard",
|
|
|
"LegacyScriptToolGateway",
|
|
"LegacyScriptToolGateway",
|
|
|
- "RetrievalAdapter",
|
|
|
|
|
- "RetrievalResult",
|
|
|
|
|
"ScriptCandidateToolPort",
|
|
"ScriptCandidateToolPort",
|
|
|
"ScriptPlannerToolPort",
|
|
"ScriptPlannerToolPort",
|
|
|
]
|
|
]
|