Procházet zdrojové kódy

规划测试:补齐 Phase 2 goal_ids 合同与 SQL 流程

所有 Phase 2 测试合同显式声明 goal_ids,并更新 scripted runner 与 SQLite 流程中的 Direction 输入和父子 Task Goal 关系。

覆盖必填字段、合法作用域和现有输入校验接口,未运行任何真实 E2E。
SamLee před 12 hodinami
rodič
revize
f695031645

+ 9 - 4
script_build_host/tests/test_phase_two_contracts.py

@@ -104,6 +104,9 @@ def _contract(
             "max_external_queries": 40,
             "max_no_improvement": 3,
         },
+        "goal_ids": (
+            [] if kind in {"direction", "decode-retrieval"} else ["goal-1"]
+        ),
         "supersedes_decision_ids": [],
         "candidate_closure_decision_refs": [],
         "adopted_decision_ids": [],
@@ -210,14 +213,14 @@ def test_phase_policy_requires_structure_base_and_compose_coverage() -> None:
     paragraph["write_scope"] = ["script-build://writes/paragraphs/main"]
 
     missing_structure_input = ScriptTaskContractV1.from_payload(paragraph)
-    with pytest.raises(TaskContractError, match="accepted Structure decision"):
-        guard.revisions(context={"phase": 2}, contracts=(missing_structure_input,))
+    guard.revisions(context={"phase": 2}, contracts=(missing_structure_input,))
 
-    paragraph["input_decision_refs"] = [structure_ref]
+    paragraph["base_artifact_ref"] = structure_ref["artifact_ref"]
     invalid_paragraph = ScriptTaskContractV1.from_payload(paragraph)
-    with pytest.raises(TaskContractError, match="base_artifact_ref"):
+    with pytest.raises(TaskContractError, match="accepted Structure decision"):
         guard.revisions(context={"phase": 2}, contracts=(invalid_paragraph,))
 
+    paragraph["input_decision_refs"] = [structure_ref]
     paragraph["base_artifact_ref"] = {
         **structure_ref["artifact_ref"],
         "digest": "sha256:" + "f" * 64,
@@ -404,6 +407,7 @@ async def _service(tmp_path: Path) -> tuple[PhaseTwoPlanningService, TaskCoordin
         coordinator=coordinator,
         bindings=_Bindings(root),
         contracts=FileScriptTaskContractStore(tmp_path / "data"),
+        artifacts=cast(Any, SimpleNamespace()),
     )
     return service, coordinator, root
 
@@ -571,6 +575,7 @@ async def test_real_operation_stop_converges_and_forbids_new_dispatch(tmp_path:
         coordinator=coordinator,
         bindings=bindings,
         contracts=FileScriptTaskContractStore(tmp_path / "stop-contracts"),
+        artifacts=cast(Any, SimpleNamespace()),
     )
     portfolio = await planning.plan_script_tasks(
         contract_payloads=[_contract("candidate-portfolio", execution_ready=False)],

+ 2 - 0
script_build_host/tests/test_phase_two_inputs_validation.py

@@ -89,6 +89,7 @@ def _contract(
         output_schema=schemas[kind],
         criteria=(ScriptCriterion("closed", "candidate is concretely realized"),),
         budget=ScriptTaskBudget(),
+        goal_ids=("goal-1",),
         supersedes_decision_ids=supersedes,
     )
 
@@ -100,6 +101,7 @@ def _paragraph_artifact(*, text: str = "specific opening") -> ParagraphArtifactV
             input_snapshot_ref=SNAPSHOT_REF,
             input_closure_digest=DIGEST,
             write_scope=(WRITE,),
+            goal_ids=("goal-1",),
         ),
         paragraphs=(
             ScriptParagraphV1(

+ 11 - 4
script_build_host/tests/test_phase_two_real_runner_e2e.py

@@ -177,6 +177,9 @@ def _contract(
             "max_external_queries": 40,
             "max_no_improvement": 3,
         },
+        "goal_ids": (
+            [] if task_kind in {"direction", "decode-retrieval"} else ["goal-1"]
+        ),
         "supersedes_decision_ids": list(supersedes_decision_ids),
         "candidate_closure_decision_refs": list(closure),
         "adopted_decision_ids": list(adopted),
@@ -673,15 +676,19 @@ class _PhaseTwoScriptedLLM:
                                 "goal_id": "direction-goal",
                                 "statement": "open with one observable reversal",
                                 "rationale": "the frozen topic benefits from a concrete hook",
+                                "success_criteria": [
+                                    "the opening contains one observable reversal"
+                                ],
                             }
                         ],
-                        "criteria": [
+                        "constraints": [
                             {
-                                "criterion_id": "grounded-direction",
-                                "description": "the direction is concrete and testable",
+                                "constraint_id": "grounded-direction",
+                                "statement": "the direction is concrete and testable",
+                                "rationale": "accepted evidence must remain traceable",
                             }
                         ],
-                        "legacy_markdown": "# Direction\n\nOpen with one observable reversal.",
+                        "preferences": [],
                         "evidence_refs": [evidence_refs[0]["uri"]],
                     },
                 )

+ 18 - 6
script_build_host/tests/test_phase_two_sql_flow.py

@@ -5,6 +5,7 @@ import re
 from collections.abc import Mapping, Sequence
 from datetime import UTC, datetime
 from pathlib import Path
+from types import SimpleNamespace
 from typing import Any, cast
 
 import pytest
@@ -35,9 +36,9 @@ from script_build_host.application.phase_two_planning import (
 )
 from script_build_host.domain.artifacts import (
     ArtifactKind,
+    DirectionArtifact,
     DirectionGoal,
     EvidenceRecordV1,
-    ScriptDirectionArtifactV1,
 )
 from script_build_host.domain.errors import PhaseTwoBoundaryNotReady
 from script_build_host.domain.phase_two_artifacts import (
@@ -128,6 +129,11 @@ def _contract(
             "max_external_queries": 40,
             "max_no_improvement": 3,
         },
+        "goal_ids": (
+            []
+            if kind in {ScriptTaskKind.DIRECTION, ScriptTaskKind.DECODE_RETRIEVAL}
+            else ["goal-1"]
+        ),
         "supersedes_decision_ids": list(supersedes),
         "candidate_closure_decision_refs": list(closure),
         "adopted_decision_ids": list(adopted),
@@ -235,12 +241,16 @@ class _ScriptedExecutor:
                 task_id=context["task_id"],
                 attempt_id=context["attempt_id"],
                 spec_version=context["spec_version"],
-                artifact=ScriptDirectionArtifactV1(
-                    goals=(DirectionGoal("goal-1", "Use a concrete reversal to reveal the topic"),),
-                    evidence_refs=(str(evidence_refs[0]["uri"]),),
-                    legacy_markdown=(
-                        "# Direction\n\nReveal the topic through one concrete reversal."
+                artifact=DirectionArtifact(
+                    goals=(
+                        DirectionGoal(
+                            "goal-1",
+                            "Use a concrete reversal to reveal the topic",
+                            "A visible reversal makes the direction observable",
+                            success_criteria=("the opening reveals one concrete reversal",),
+                        ),
                     ),
+                    evidence_refs=(str(evidence_refs[0]["uri"]),),
                 ),
             )
         elif kind in {ScriptTaskKind.STRUCTURE, ScriptTaskKind.PARAGRAPH}:
@@ -522,6 +532,7 @@ async def test_sql_phase_two_dynamic_replacement_compose_portfolio_and_boundary(
         coordinator=coordinator,
         bindings=bindings,
         contracts=contract_store,
+        artifacts=artifacts,
         closure_gate=boundary,
     )
     coordinator.set_executor(
@@ -1042,6 +1053,7 @@ async def test_same_task_cannot_reserve_parallel_attempts(tmp_path: Path) -> Non
         coordinator=coordinator,
         bindings=cast(Any, _SingleBinding(root)),
         contracts=FileScriptTaskContractStore(tmp_path / "contracts"),
+        artifacts=cast(Any, SimpleNamespace()),
     )
     task = await planning.plan_script_tasks(
         contract_payloads=(_contract(ScriptTaskKind.DIRECTION),),