Explorar o código

主机:在 Root Delivery 防御性复核完整脚本

Root Manifest 保存和 deterministic preflight 复用正式内容合同、Direction 绑定和真实 Goal Coverage,阻断空正文、零 Element、零 Link 与闭包不一致的假成功。
SamLee hai 16 horas
pai
achega
6e6bb3885b

+ 102 - 8
script_build_host/src/script_build_host/application/root_delivery.py

@@ -6,7 +6,7 @@ from collections.abc import Mapping, Sequence
 from dataclasses import asdict
 from dataclasses import asdict
 from typing import Any
 from typing import Any
 
 
-from agent.orchestration import DecisionAction, ValidationVerdict
+from agent.orchestration import ArtifactRef, DecisionAction, ValidationVerdict
 
 
 from script_build_host.domain.artifacts import (
 from script_build_host.domain.artifacts import (
     ArtifactKind,
     ArtifactKind,
@@ -14,8 +14,14 @@ from script_build_host.domain.artifacts import (
     ArtifactVersion,
     ArtifactVersion,
     DirectionArtifact,
     DirectionArtifact,
 )
 )
-from script_build_host.domain.errors import ProtocolViolation
-from script_build_host.domain.goal_coverage import GoalPolicyError, validate_goal_coverage
+from script_build_host.domain.candidate_validation import (
+    artifact_realizes_goals,
+    complete_script_issues,
+    require_complete_script,
+    require_complete_sources,
+)
+from script_build_host.domain.errors import ProtocolViolation, ScriptBuildError
+from script_build_host.domain.goal_coverage import validate_goal_coverage
 from script_build_host.domain.phase_three_artifacts import (
 from script_build_host.domain.phase_three_artifacts import (
     RootDeliveryManifestV1,
     RootDeliveryManifestV1,
     canonicalize_legacy_projection,
     canonicalize_legacy_projection,
@@ -46,6 +52,7 @@ class RootDeliveryService:
 
 
     async def read_accepted_portfolio(self, *, context: Mapping[str, Any]) -> Mapping[str, Any]:
     async def read_accepted_portfolio(self, *, context: Mapping[str, Any]) -> Mapping[str, Any]:
         closure = await self._closure(context)
         closure = await self._closure(context)
+        evidence_refs = await self._closure_evidence_refs(closure)
         return {
         return {
             "direction_ref": _uri(closure.direction),
             "direction_ref": _uri(closure.direction),
             "candidate_portfolio_ref": _uri(closure.portfolio),
             "candidate_portfolio_ref": _uri(closure.portfolio),
@@ -53,8 +60,14 @@ class RootDeliveryService:
             "direction": closure.direction.artifact.content_payload(),
             "direction": closure.direction.artifact.content_payload(),
             "candidate_portfolio": closure.portfolio.artifact.content_payload(),
             "candidate_portfolio": closure.portfolio.artifact.content_payload(),
             "structured_script": closure.structured.artifact.content_payload(),
             "structured_script": closure.structured.artifact.content_payload(),
+            "validation_evidence_refs": [asdict(item) for item in evidence_refs],
         }
         }
 
 
+    async def validation_evidence_refs(
+        self, *, context: Mapping[str, Any]
+    ) -> tuple[ArtifactRef, ...]:
+        return await self._closure_evidence_refs(await self._closure(context))
+
     async def legacy_projection_dry_run(
     async def legacy_projection_dry_run(
         self, *, build_summary: str, context: Mapping[str, Any]
         self, *, build_summary: str, context: Mapping[str, Any]
     ) -> Mapping[str, Any]:
     ) -> Mapping[str, Any]:
@@ -97,6 +110,16 @@ class RootDeliveryService:
         direction = _direction(closure.direction)
         direction = _direction(closure.direction)
         portfolio = _portfolio(closure.portfolio)
         portfolio = _portfolio(closure.portfolio)
         structured = _structured(closure.structured)
         structured = _structured(closure.structured)
+        require_complete_script(
+            structured.paragraphs,
+            structured.elements,
+            structured.paragraph_element_links,
+        )
+        await self._validate_realized_goal_coverage(
+            script_build_id=binding.script_build_id,
+            direction=direction,
+            structured=structured,
+        )
         canonical = canonicalize_legacy_projection(
         canonical = canonicalize_legacy_projection(
             structured,
             structured,
             direction=direction.legacy_markdown,
             direction=direction.legacy_markdown,
@@ -170,14 +193,19 @@ class RootDeliveryService:
         direction = _direction(closure.direction)
         direction = _direction(closure.direction)
         portfolio = _portfolio(closure.portfolio)
         portfolio = _portfolio(closure.portfolio)
         structured = _structured(closure.structured)
         structured = _structured(closure.structured)
+        content_issues = complete_script_issues(
+            structured.paragraphs,
+            structured.elements,
+            structured.paragraph_element_links,
+        )
         goal_coverage_error: str | None = None
         goal_coverage_error: str | None = None
         try:
         try:
-            validate_goal_coverage(
-                direction_goal_ids=tuple(item.goal_id for item in direction.goals),
-                coverage=structured.goal_coverage,
-                adopted_source_refs=structured.source_artifact_refs,
+            await self._validate_realized_goal_coverage(
+                script_build_id=binding.script_build_id,
+                direction=direction,
+                structured=structured,
             )
             )
-        except GoalPolicyError as exc:
+        except ScriptBuildError as exc:
             goal_coverage_error = f"{exc.code}: {exc.summary}"
             goal_coverage_error = f"{exc.code}: {exc.summary}"
         goal_coverage_passed = goal_coverage_error is None
         goal_coverage_passed = goal_coverage_error is None
         direction_contract_passed = (
         direction_contract_passed = (
@@ -225,6 +253,19 @@ class RootDeliveryService:
                 "error": None,
                 "error": None,
                 "evidence_refs": [],
                 "evidence_refs": [],
             },
             },
+            {
+                "rule_id": "root-script-content",
+                "verdict": "failed" if content_issues else "passed",
+                "reason": (
+                    "; ".join(
+                        f"{item.path}: {item.message}" for item in content_issues[:8]
+                    )
+                    if content_issues
+                    else "Every active leaf Paragraph contains complete prose and linked content"
+                ),
+                "error": None,
+                "evidence_refs": [_uri(closure.structured)],
+            },
             {
             {
                 "rule_id": "root-goal-coverage",
                 "rule_id": "root-goal-coverage",
                 "verdict": "passed" if goal_coverage_passed else "failed",
                 "verdict": "passed" if goal_coverage_passed else "failed",
@@ -264,6 +305,59 @@ class RootDeliveryService:
             },
             },
         )
         )
 
 
+    async def _validate_realized_goal_coverage(
+        self,
+        *,
+        script_build_id: int,
+        direction: DirectionArtifact,
+        structured: StructuredScriptArtifactV1,
+    ) -> None:
+        realized: list[str] = []
+        sources: list[Any] = []
+        for ref in structured.source_artifact_refs:
+            version = await self._artifacts.get_by_id(
+                _artifact_id(ref), script_build_id=script_build_id
+            )
+            sources.append(version.artifact)
+            if artifact_realizes_goals(version.artifact):
+                realized.append(ref)
+        require_complete_sources(sources)
+        validate_goal_coverage(
+            direction_goal_ids=tuple(item.goal_id for item in direction.goals),
+            coverage=structured.goal_coverage,
+            adopted_source_refs=structured.source_artifact_refs,
+            realized_source_refs=realized,
+        )
+
+    async def _closure_evidence_refs(
+        self, closure: _RootClosure
+    ) -> tuple[ArtifactRef, ...]:
+        versions = [closure.direction, closure.portfolio, closure.structured]
+        structured = _structured(closure.structured)
+        binding_build_id = closure.structured.script_build_id
+        for ref in structured.source_artifact_refs:
+            versions.append(
+                await self._artifacts.get_by_id(
+                    _artifact_id(ref), script_build_id=binding_build_id
+                )
+            )
+        output: list[ArtifactRef] = []
+        seen: set[str] = set()
+        for version in versions:
+            uri = _uri(version)
+            if uri in seen:
+                continue
+            seen.add(uri)
+            output.append(
+                ArtifactRef(
+                    uri=uri,
+                    kind=version.artifact_type.value,
+                    version=str(version.artifact_version_id),
+                    digest=version.canonical_sha256,
+                )
+            )
+        return tuple(output)
+
     async def _closure(self, context: Mapping[str, Any]) -> _RootClosure:
     async def _closure(self, context: Mapping[str, Any]) -> _RootClosure:
         root_trace_id = _required(context, "root_trace_id")
         root_trace_id = _required(context, "root_trace_id")
         binding = await self._bindings.get_by_root(root_trace_id)
         binding = await self._bindings.get_by_root(root_trace_id)