|
@@ -0,0 +1,1063 @@
|
|
|
|
|
+from __future__ import annotations
|
|
|
|
|
+
|
|
|
|
|
+import asyncio
|
|
|
|
|
+import re
|
|
|
|
|
+from collections.abc import Mapping, Sequence
|
|
|
|
|
+from datetime import UTC, datetime
|
|
|
|
|
+from pathlib import Path
|
|
|
|
|
+from typing import Any, cast
|
|
|
|
|
+
|
|
|
|
|
+import pytest
|
|
|
|
|
+from agent import FileSystemArtifactStore, FileSystemTaskStore, FileSystemTraceStore
|
|
|
|
|
+from agent.orchestration import (
|
|
|
|
|
+ AgentRole,
|
|
|
|
|
+ ArtifactRef,
|
|
|
|
|
+ AttemptSubmission,
|
|
|
|
|
+ CriterionResult,
|
|
|
|
|
+ DecisionAction,
|
|
|
|
|
+ TaskCoordinator,
|
|
|
|
|
+ TaskStatus,
|
|
|
|
|
+ ValidationVerdict,
|
|
|
|
|
+)
|
|
|
|
|
+from agent.orchestration.protocols import ValidatorRunResult, WorkerRunResult
|
|
|
|
|
+from sqlalchemy import event, func, select
|
|
|
|
|
+
|
|
|
|
|
+from script_build_host.application.phase_two_boundary import ScriptPhaseTwoBoundaryVerifier
|
|
|
|
|
+from script_build_host.application.phase_two_candidates import PhaseTwoCandidateService
|
|
|
|
|
+from script_build_host.application.phase_two_inputs import (
|
|
|
|
|
+ AcceptedInputResolver,
|
|
|
|
|
+ ActiveFrontierResolver,
|
|
|
|
|
+ StoredTaskContractReader,
|
|
|
|
|
+)
|
|
|
|
|
+from script_build_host.application.phase_two_planning import (
|
|
|
|
|
+ PHASE_TWO_CANDIDATE_PORTFOLIO_READY,
|
|
|
|
|
+ PhaseTwoPlanningService,
|
|
|
|
|
+)
|
|
|
|
|
+from script_build_host.domain.artifacts import (
|
|
|
|
|
+ ArtifactKind,
|
|
|
|
|
+ DirectionGoal,
|
|
|
|
|
+ EvidenceRecordV1,
|
|
|
|
|
+ ScriptDirectionArtifactV1,
|
|
|
|
|
+)
|
|
|
|
|
+from script_build_host.domain.errors import PhaseTwoBoundaryNotReady
|
|
|
|
|
+from script_build_host.domain.phase_two_artifacts import (
|
|
|
|
|
+ CandidatePortfolioArtifactV1,
|
|
|
|
|
+ StructuredScriptArtifactV1,
|
|
|
|
|
+)
|
|
|
|
|
+from script_build_host.domain.records import BuildStatus, MissionBinding
|
|
|
|
|
+from script_build_host.domain.task_contracts import ScriptTaskKind
|
|
|
|
|
+from script_build_host.infrastructure.legacy_tables import (
|
|
|
|
|
+ script_build_element,
|
|
|
|
|
+ script_build_paragraph,
|
|
|
|
|
+ script_build_paragraph_element,
|
|
|
|
|
+ script_build_record,
|
|
|
|
|
+)
|
|
|
|
|
+from script_build_host.infrastructure.tables import (
|
|
|
|
|
+ artifact_version_table,
|
|
|
|
|
+ publication_table,
|
|
|
|
|
+)
|
|
|
|
|
+from script_build_host.infrastructure.task_contract_store import FileScriptTaskContractStore
|
|
|
|
|
+from script_build_host.repositories.legacy_state import SqlAlchemyLegacyBuildStateRepository
|
|
|
|
|
+from script_build_host.repositories.sqlalchemy import (
|
|
|
|
|
+ SqlAlchemyMissionBindingRepository,
|
|
|
|
|
+ SqlAlchemyScriptBusinessArtifactRepository,
|
|
|
|
|
+)
|
|
|
|
|
+from script_build_host.repositories.workspace import SqlAlchemyCandidateWorkspaceRepository
|
|
|
|
|
+
|
|
|
|
|
+ROOT = "phase-two-sql-root"
|
|
|
|
|
+SNAPSHOT_ID = 11
|
|
|
|
|
+FULL_SCOPE = "script-build://scopes/full"
|
|
|
|
|
+FULL_WRITE = "script-build://writes/full"
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _contract(
|
|
|
|
|
+ kind: ScriptTaskKind,
|
|
|
|
|
+ *,
|
|
|
|
|
+ scope: str = FULL_SCOPE,
|
|
|
|
|
+ write_scope: Sequence[str] = (FULL_WRITE,),
|
|
|
|
|
+ input_refs: Sequence[dict[str, Any]] = (),
|
|
|
|
|
+ base_ref: ArtifactRef | None = None,
|
|
|
|
|
+ supersedes: Sequence[str] = (),
|
|
|
|
|
+ closure: Sequence[dict[str, Any]] = (),
|
|
|
|
|
+ adopted: Sequence[str] = (),
|
|
|
|
|
+ held: Sequence[str] = (),
|
|
|
|
|
+ order: Sequence[str] = (),
|
|
|
|
|
+ comparison_refs: Sequence[dict[str, Any]] = (),
|
|
|
|
|
+ execution_ready: bool = True,
|
|
|
|
|
+) -> dict[str, Any]:
|
|
|
|
|
+ output_schema = {
|
|
|
|
|
+ ScriptTaskKind.DIRECTION: "script-direction/v1",
|
|
|
|
|
+ ScriptTaskKind.DECODE_RETRIEVAL: "evidence-record/v1",
|
|
|
|
|
+ ScriptTaskKind.STRUCTURE: "structure-artifact/v1",
|
|
|
|
|
+ ScriptTaskKind.PARAGRAPH: "paragraph-patch/v1" if base_ref else "paragraph-artifact/v1",
|
|
|
|
|
+ ScriptTaskKind.ELEMENT_SET: (
|
|
|
|
|
+ "element-set-patch/v1" if base_ref else "element-set-artifact/v1"
|
|
|
|
|
+ ),
|
|
|
|
|
+ ScriptTaskKind.COMPARE: "comparison-artifact/v1",
|
|
|
|
|
+ ScriptTaskKind.COMPOSE: "structured-script/v1",
|
|
|
|
|
+ ScriptTaskKind.CANDIDATE_PORTFOLIO: "candidate-portfolio/v1",
|
|
|
|
|
+ }[kind]
|
|
|
|
|
+ if kind in {ScriptTaskKind.COMPOSE, ScriptTaskKind.CANDIDATE_PORTFOLIO}:
|
|
|
|
|
+ intent = "compose" if kind is ScriptTaskKind.COMPOSE else "portfolio"
|
|
|
|
|
+ elif supersedes:
|
|
|
|
|
+ intent = "replace"
|
|
|
|
|
+ else:
|
|
|
|
|
+ intent = "explore"
|
|
|
|
|
+ payload: dict[str, Any] = {
|
|
|
|
|
+ "schema_version": "script-task-contract/v1",
|
|
|
|
|
+ "task_kind": kind.value,
|
|
|
|
|
+ "scope_ref": scope,
|
|
|
|
|
+ "intent_class": intent,
|
|
|
|
|
+ "objective": f"produce independently verifiable {kind.value} content",
|
|
|
|
|
+ "input_decision_refs": list(input_refs),
|
|
|
|
|
+ "base_artifact_ref": _ref_payload(base_ref) if base_ref else None,
|
|
|
|
|
+ "write_scope": list(write_scope),
|
|
|
|
|
+ "gap_ref": None,
|
|
|
|
|
+ "output_schema": output_schema,
|
|
|
|
|
+ "criteria": [
|
|
|
|
|
+ {
|
|
|
|
|
+ "criterion_id": "closed",
|
|
|
|
|
+ "description": "the frozen increment is concrete and independently verifiable",
|
|
|
|
|
+ "hard": True,
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ "budget": {
|
|
|
|
|
+ "max_attempts": 4,
|
|
|
|
|
+ "max_tokens": 32_000,
|
|
|
|
|
+ "max_seconds": 900,
|
|
|
|
|
+ "max_external_queries": 40,
|
|
|
|
|
+ "max_no_improvement": 3,
|
|
|
|
|
+ },
|
|
|
|
|
+ "supersedes_decision_ids": list(supersedes),
|
|
|
|
|
+ "candidate_closure_decision_refs": list(closure),
|
|
|
|
|
+ "adopted_decision_ids": list(adopted),
|
|
|
|
|
+ "held_or_rejected_decision_ids": list(held),
|
|
|
|
|
+ "compose_order": list(order),
|
|
|
|
|
+ "comparison_decision_refs": list(comparison_refs),
|
|
|
|
|
+ }
|
|
|
|
|
+ if not execution_ready and kind in {
|
|
|
|
|
+ ScriptTaskKind.COMPOSE,
|
|
|
|
|
+ ScriptTaskKind.CANDIDATE_PORTFOLIO,
|
|
|
|
|
+ }:
|
|
|
|
|
+ payload.update(
|
|
|
|
|
+ candidate_closure_decision_refs=[],
|
|
|
|
|
+ adopted_decision_ids=[],
|
|
|
|
|
+ held_or_rejected_decision_ids=[],
|
|
|
|
|
+ compose_order=[],
|
|
|
|
|
+ )
|
|
|
|
|
+ return payload
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _decision_ref(
|
|
|
|
|
+ decision_id: str,
|
|
|
|
|
+ ref: ArtifactRef,
|
|
|
|
|
+ *,
|
|
|
|
|
+ scope: str,
|
|
|
|
|
+ kind: ScriptTaskKind,
|
|
|
|
|
+) -> dict[str, Any]:
|
|
|
|
|
+ return {
|
|
|
|
|
+ "decision_id": decision_id,
|
|
|
|
|
+ "artifact_ref": _ref_payload(ref),
|
|
|
|
|
+ "scope_ref": scope,
|
|
|
|
|
+ "expected_task_kind": kind.value,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _ref_payload(ref: ArtifactRef) -> dict[str, Any]:
|
|
|
|
|
+ return {
|
|
|
|
|
+ "uri": ref.uri,
|
|
|
|
|
+ "kind": ref.kind,
|
|
|
|
|
+ "version": ref.version,
|
|
|
|
|
+ "digest": ref.digest,
|
|
|
|
|
+ "summary": ref.summary,
|
|
|
|
|
+ "metadata": ref.metadata,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _task_kind(context: Mapping[str, Any]) -> ScriptTaskKind:
|
|
|
|
|
+ refs = cast(dict[str, Any], context["task_spec"])["context_refs"]
|
|
|
|
|
+ values = [value.rsplit("/", 1)[-1] for value in refs if "/task-kinds/" in value]
|
|
|
|
|
+ assert len(values) == 1
|
|
|
|
|
+ return ScriptTaskKind(values[0])
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class _ScriptedExecutor:
|
|
|
|
|
+ """Drive real Coordinator stages through the same protected Host services."""
|
|
|
|
|
+
|
|
|
|
|
+ def __init__(
|
|
|
|
|
+ self,
|
|
|
|
|
+ *,
|
|
|
|
|
+ coordinator: TaskCoordinator,
|
|
|
|
|
+ candidates: PhaseTwoCandidateService,
|
|
|
|
|
+ artifacts: SqlAlchemyScriptBusinessArtifactRepository,
|
|
|
|
|
+ script_build_id: int,
|
|
|
|
|
+ ) -> None:
|
|
|
|
|
+ self.coordinator = coordinator
|
|
|
|
|
+ self.candidates = candidates
|
|
|
|
|
+ self.artifacts = artifacts
|
|
|
|
|
+ self.script_build_id = script_build_id
|
|
|
|
|
+
|
|
|
|
|
+ async def run_worker(self, context: dict[str, Any]) -> WorkerRunResult:
|
|
|
|
|
+ kind = _task_kind(context)
|
|
|
|
|
+ candidate_context = {
|
|
|
|
|
+ "root_trace_id": context["root_trace_id"],
|
|
|
|
|
+ "task_id": context["task_id"],
|
|
|
|
|
+ "attempt_id": context["attempt_id"],
|
|
|
|
|
+ "spec_version": context["spec_version"],
|
|
|
|
|
+ }
|
|
|
|
|
+ if kind is ScriptTaskKind.DECODE_RETRIEVAL:
|
|
|
|
|
+ await self.artifacts.freeze(
|
|
|
|
|
+ script_build_id=self.script_build_id,
|
|
|
|
|
+ task_id=context["task_id"],
|
|
|
|
|
+ attempt_id=context["attempt_id"],
|
|
|
|
|
+ spec_version=context["spec_version"],
|
|
|
|
|
+ artifact=EvidenceRecordV1(
|
|
|
|
|
+ evidence_id=f"evidence-{context['attempt_id']}",
|
|
|
|
|
+ source_type="decode",
|
|
|
|
|
+ tool_name="retrieve_decode",
|
|
|
|
|
+ query={"query": "opening tension", "top_k": 3},
|
|
|
|
|
+ source_refs=("decode-index://fixture/1",),
|
|
|
|
|
+ raw_artifact_ref=None,
|
|
|
|
|
+ summary="A concrete opening needs a visible tension and an observable detail.",
|
|
|
|
|
+ supports=("direction",),
|
|
|
|
|
+ confidence="high",
|
|
|
|
|
+ limitations=(),
|
|
|
|
|
+ content_sha256="",
|
|
|
|
|
+ created_at=datetime.now(UTC),
|
|
|
|
|
+ ),
|
|
|
|
|
+ )
|
|
|
|
|
+ elif kind is ScriptTaskKind.DIRECTION:
|
|
|
|
|
+ child_results = cast(list[dict[str, Any]], context["accepted_child_results"])
|
|
|
|
|
+ assert len(child_results) == 1
|
|
|
|
|
+ evidence_refs = child_results[0]["submission"]["evidence_refs"]
|
|
|
|
|
+ await self.artifacts.freeze(
|
|
|
|
|
+ script_build_id=self.script_build_id,
|
|
|
|
|
+ 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."
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ )
|
|
|
|
|
+ elif kind in {ScriptTaskKind.STRUCTURE, ScriptTaskKind.PARAGRAPH}:
|
|
|
|
|
+ workspace = await self.candidates.read_attempt_workspace(context=candidate_context)
|
|
|
|
|
+ if workspace["paragraphs"]:
|
|
|
|
|
+ paragraph_id = workspace["paragraphs"][0]["paragraph_id"]
|
|
|
|
|
+ else:
|
|
|
|
|
+ created = await self.candidates.create_script_paragraph(
|
|
|
|
|
+ payload={
|
|
|
|
|
+ "paragraph_index": 1,
|
|
|
|
|
+ "name": f"{kind.value} opening",
|
|
|
|
|
+ "content_range": {"scope": "opening"},
|
|
|
|
|
+ },
|
|
|
|
|
+ context=candidate_context,
|
|
|
|
|
+ )
|
|
|
|
|
+ paragraph_id = created["paragraph_id"]
|
|
|
|
|
+ await self.candidates.batch_update_script_paragraphs(
|
|
|
|
|
+ updates=(
|
|
|
|
|
+ {
|
|
|
|
|
+ "paragraph_id": paragraph_id,
|
|
|
|
|
+ "theme": "a specific reversal",
|
|
|
|
|
+ "form": "contrast",
|
|
|
|
|
+ "function": "hook",
|
|
|
|
|
+ "feeling": "curiosity",
|
|
|
|
|
+ "description": "The observable detail changes the initial interpretation.",
|
|
|
|
|
+ "full_description": (
|
|
|
|
|
+ "The opening states a familiar assumption, then overturns it with "
|
|
|
|
|
+ "one visible and source-grounded detail."
|
|
|
|
|
+ ),
|
|
|
|
|
+ },
|
|
|
|
|
+ ),
|
|
|
|
|
+ context=candidate_context,
|
|
|
|
|
+ )
|
|
|
|
|
+ elif kind is ScriptTaskKind.ELEMENT_SET:
|
|
|
|
|
+ created = await self.candidates.create_script_element(
|
|
|
|
|
+ payload={
|
|
|
|
|
+ "name": "observable reversal detail",
|
|
|
|
|
+ "dimension_primary": "实质",
|
|
|
|
|
+ "dimension_secondary": "narrative evidence",
|
|
|
|
|
+ "topic_support": {"scope": "opening"},
|
|
|
|
|
+ "weight_score": {"score": 0.9},
|
|
|
|
|
+ },
|
|
|
|
|
+ context=candidate_context,
|
|
|
|
|
+ )
|
|
|
|
|
+ workspace = await self.candidates.read_attempt_workspace(context=candidate_context)
|
|
|
|
|
+ if workspace["paragraphs"]:
|
|
|
|
|
+ await self.candidates.batch_link_paragraph_elements(
|
|
|
|
|
+ links=(
|
|
|
|
|
+ {
|
|
|
|
|
+ "paragraph_id": workspace["paragraphs"][0]["paragraph_id"],
|
|
|
|
|
+ "element_ids": [created["element_id"]],
|
|
|
|
|
+ },
|
|
|
|
|
+ ),
|
|
|
|
|
+ context=candidate_context,
|
|
|
|
|
+ )
|
|
|
|
|
+ elif kind is ScriptTaskKind.COMPARE:
|
|
|
|
|
+ pinned = await self.candidates.read_pinned_candidates(context=candidate_context)
|
|
|
|
|
+ candidate_refs = [str(item["artifact_ref"]["uri"]) for item in pinned]
|
|
|
|
|
+ await self.candidates.save_comparison_candidate(
|
|
|
|
|
+ payload={
|
|
|
|
|
+ "criterion_results": [
|
|
|
|
|
+ {
|
|
|
|
|
+ "criterion_id": "closed",
|
|
|
|
|
+ "candidate_results": [
|
|
|
|
|
+ {"artifact_ref": ref, "reason": "immutable candidate reviewed"}
|
|
|
|
|
+ for ref in candidate_refs
|
|
|
|
|
+ ],
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ "conflicts": ["the replacement has the more concrete opening"],
|
|
|
|
|
+ "recommendation": candidate_refs[-1],
|
|
|
|
|
+ },
|
|
|
|
|
+ context=candidate_context,
|
|
|
|
|
+ )
|
|
|
|
|
+ elif kind is ScriptTaskKind.COMPOSE:
|
|
|
|
|
+ await self.candidates.save_structured_script_candidate(
|
|
|
|
|
+ acceptance_notes=("all adopted increments are pinned by ACCEPT decisions",),
|
|
|
|
|
+ context=candidate_context,
|
|
|
|
|
+ )
|
|
|
|
|
+ elif kind is ScriptTaskKind.CANDIDATE_PORTFOLIO:
|
|
|
|
|
+ await self.candidates.save_candidate_portfolio(
|
|
|
|
|
+ payload={"unresolved_defects": []}, context=candidate_context
|
|
|
|
|
+ )
|
|
|
|
|
+ else: # pragma: no cover - this executor is intentionally phase-two bounded
|
|
|
|
|
+ raise AssertionError(f"unexpected worker kind: {kind.value}")
|
|
|
|
|
+
|
|
|
|
|
+ manifest = await self.candidates.resolve_attempt_manifest(context=candidate_context)
|
|
|
|
|
+ safe_ref = ArtifactRef(
|
|
|
|
|
+ manifest.artifact_ref.uri,
|
|
|
|
|
+ manifest.artifact_ref.kind,
|
|
|
|
|
+ manifest.artifact_ref.version,
|
|
|
|
|
+ manifest.artifact_ref.digest,
|
|
|
|
|
+ )
|
|
|
|
|
+ await self.coordinator.submit_attempt(
|
|
|
|
|
+ {
|
|
|
|
|
+ "role": AgentRole.WORKER.value,
|
|
|
|
|
+ "root_trace_id": context["root_trace_id"],
|
|
|
|
|
+ "task_id": context["task_id"],
|
|
|
|
|
+ "attempt_id": context["attempt_id"],
|
|
|
|
|
+ "spec_version": context["spec_version"],
|
|
|
|
|
+ "trace_id": context["worker_trace_id"],
|
|
|
|
|
+ "tool_call_id": f"submit:{context['attempt_id']}",
|
|
|
|
|
+ "operation_id": context["operation_id"],
|
|
|
|
|
+ "execution_epoch": context["execution_epoch"],
|
|
|
|
|
+ },
|
|
|
|
|
+ AttemptSubmission(
|
|
|
|
|
+ summary=f"kind={safe_ref.kind};status=frozen",
|
|
|
|
|
+ artifact_refs=[] if safe_ref.kind == ArtifactKind.EVIDENCE.value else [safe_ref],
|
|
|
|
|
+ evidence_refs=[safe_ref] if safe_ref.kind == ArtifactKind.EVIDENCE.value else [],
|
|
|
|
|
+ ),
|
|
|
|
|
+ )
|
|
|
|
|
+ return WorkerRunResult(context["worker_trace_id"], "completed")
|
|
|
|
|
+
|
|
|
|
|
+ async def run_validator(self, context: dict[str, Any]) -> ValidatorRunResult:
|
|
|
|
|
+ criteria = cast(dict[str, Any], context["task_spec"])["acceptance_criteria"]
|
|
|
|
|
+ results = [
|
|
|
|
|
+ CriterionResult(
|
|
|
|
|
+ criterion_id=item["criterion_id"],
|
|
|
|
|
+ verdict=ValidationVerdict.PASSED,
|
|
|
|
|
+ reason="the immutable SQL artifact is concrete and owner/digest closed",
|
|
|
|
|
+ )
|
|
|
|
|
+ for item in criteria
|
|
|
|
|
+ ]
|
|
|
|
|
+ await self.coordinator.submit_validation(
|
|
|
|
|
+ {
|
|
|
|
|
+ "role": AgentRole.VALIDATOR.value,
|
|
|
|
|
+ "root_trace_id": context["root_trace_id"],
|
|
|
|
|
+ "task_id": context["task_id"],
|
|
|
|
|
+ "attempt_id": context["attempt_id"],
|
|
|
|
|
+ "validation_id": context["validation_id"],
|
|
|
|
|
+ "snapshot_id": context["snapshot_id"],
|
|
|
|
|
+ "trace_id": context["validator_trace_id"],
|
|
|
|
|
+ "tool_call_id": f"validate:{context['validation_id']}",
|
|
|
|
|
+ "operation_id": context["operation_id"],
|
|
|
|
|
+ "execution_epoch": context["execution_epoch"],
|
|
|
|
|
+ },
|
|
|
|
|
+ ValidationVerdict.PASSED,
|
|
|
|
|
+ results,
|
|
|
|
|
+ "all hard criteria passed against the immutable snapshot",
|
|
|
|
|
+ (),
|
|
|
|
|
+ (),
|
|
|
|
|
+ (),
|
|
|
|
|
+ "accept",
|
|
|
|
|
+ )
|
|
|
|
|
+ return ValidatorRunResult(context["validator_trace_id"], "completed")
|
|
|
|
|
+
|
|
|
|
|
+ async def stop(self, trace_id: str) -> bool:
|
|
|
|
|
+ del trace_id
|
|
|
|
|
+ return True
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+async def _plan_one(
|
|
|
|
|
+ planning: PhaseTwoPlanningService,
|
|
|
|
|
+ payload: Mapping[str, Any],
|
|
|
|
|
+ *,
|
|
|
|
|
+ parent_task_id: str | None,
|
|
|
|
|
+ call_id: str,
|
|
|
|
|
+) -> str:
|
|
|
|
|
+ result = await planning.plan_script_tasks(
|
|
|
|
|
+ contract_payloads=(payload,),
|
|
|
|
|
+ parent_task_id=parent_task_id,
|
|
|
|
|
+ context={"root_trace_id": ROOT, "tool_call_id": call_id},
|
|
|
|
|
+ )
|
|
|
|
|
+ return cast(str, result["task_ids"][0])
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+async def _dispatch_accept(
|
|
|
|
|
+ planning: PhaseTwoPlanningService, coordinator: TaskCoordinator, task_id: str
|
|
|
|
|
+) -> tuple[str, ArtifactRef]:
|
|
|
|
|
+ cycles = await planning.dispatch_script_tasks(
|
|
|
|
|
+ task_ids=(task_id,),
|
|
|
|
|
+ context={"root_trace_id": ROOT, "tool_call_id": f"dispatch:{task_id}"},
|
|
|
|
|
+ )
|
|
|
|
|
+ assert cycles[0]["error"] is None
|
|
|
|
|
+ validation_id = cast(str, cycles[0]["validation_id"])
|
|
|
|
|
+ accepted = await planning.decide_script_task(
|
|
|
|
|
+ task_id=task_id,
|
|
|
|
|
+ action=DecisionAction.ACCEPT.value,
|
|
|
|
|
+ reason="independent validation passed",
|
|
|
|
|
+ validation_id=validation_id,
|
|
|
|
|
+ replacement_contract=None,
|
|
|
|
|
+ child_contracts=(),
|
|
|
|
|
+ context={"root_trace_id": ROOT, "tool_call_id": f"accept:{task_id}"},
|
|
|
|
|
+ )
|
|
|
|
|
+ decision_id = cast(str, accepted["decision_id"])
|
|
|
|
|
+ ledger = await coordinator.task_store.load(ROOT)
|
|
|
|
|
+ decision = ledger.decisions[decision_id]
|
|
|
|
|
+ attempt = ledger.attempts[cast(str, decision.attempt_id)]
|
|
|
|
|
+ assert attempt.submission is not None
|
|
|
|
|
+ refs = [*attempt.submission.artifact_refs, *attempt.submission.evidence_refs]
|
|
|
|
|
+ assert len(refs) == 1
|
|
|
|
|
+ return decision_id, refs[0]
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
|
+async def test_sql_phase_two_dynamic_replacement_compose_portfolio_and_boundary(
|
|
|
|
|
+ database: Any, tmp_path: Path
|
|
|
|
|
+) -> None:
|
|
|
|
|
+ engine, sessions = database
|
|
|
|
|
+ mutation_statements: list[str] = []
|
|
|
|
|
+
|
|
|
|
|
+ def observe_sql(
|
|
|
|
|
+ _connection: Any,
|
|
|
|
|
+ _cursor: Any,
|
|
|
|
|
+ statement: str,
|
|
|
|
|
+ _parameters: Any,
|
|
|
|
|
+ _context: Any,
|
|
|
|
|
+ _executemany: bool,
|
|
|
|
|
+ ) -> None:
|
|
|
|
|
+ if statement.lstrip().upper().startswith(("INSERT ", "UPDATE ", "DELETE ")):
|
|
|
|
|
+ mutation_statements.append(statement)
|
|
|
|
|
+
|
|
|
|
|
+ event.listen(engine.sync_engine, "before_cursor_execute", observe_sql)
|
|
|
|
|
+ build_states = SqlAlchemyLegacyBuildStateRepository(sessions)
|
|
|
|
|
+ script_build_id = await build_states.create(
|
|
|
|
|
+ execution_id=101,
|
|
|
|
|
+ topic_build_id=202,
|
|
|
|
|
+ topic_id=303,
|
|
|
|
|
+ agent_type="script-planner",
|
|
|
|
|
+ agent_config={},
|
|
|
|
|
+ data_source_url=None,
|
|
|
|
|
+ strategies_config={},
|
|
|
|
|
+ )
|
|
|
|
|
+ bindings = SqlAlchemyMissionBindingRepository(sessions)
|
|
|
|
|
+ await bindings.create(
|
|
|
|
|
+ script_build_id=script_build_id,
|
|
|
|
|
+ root_trace_id=ROOT,
|
|
|
|
|
+ input_snapshot_id=SNAPSHOT_ID,
|
|
|
|
|
+ engine_version="phase-two-test",
|
|
|
|
|
+ schema_version="v1",
|
|
|
|
|
+ )
|
|
|
|
|
+ task_path = tmp_path / "task-ledger"
|
|
|
|
|
+ artifact_path = tmp_path / "framework-artifacts"
|
|
|
|
|
+ contract_path = tmp_path / "contract-data"
|
|
|
|
|
+ task_store = FileSystemTaskStore(str(task_path))
|
|
|
|
|
+ framework_artifacts = FileSystemArtifactStore(str(artifact_path))
|
|
|
|
|
+ coordinator = TaskCoordinator(
|
|
|
|
|
+ task_store,
|
|
|
|
|
+ framework_artifacts,
|
|
|
|
|
+ FileSystemTraceStore(str(tmp_path / "traces")),
|
|
|
|
|
+ )
|
|
|
|
|
+ await coordinator.ensure_ledger(
|
|
|
|
|
+ ROOT,
|
|
|
|
|
+ {
|
|
|
|
|
+ "objective": "produce a governed phase-two candidate portfolio",
|
|
|
|
|
+ "acceptance_criteria": [
|
|
|
|
|
+ {"criterion_id": "portfolio", "description": "portfolio closed", "hard": True}
|
|
|
|
|
+ ],
|
|
|
|
|
+ "context_refs": [f"script-build://inputs/{SNAPSHOT_ID}"],
|
|
|
|
|
+ },
|
|
|
|
|
+ )
|
|
|
|
|
+ contract_store = FileScriptTaskContractStore(contract_path)
|
|
|
|
|
+ contract_reader = StoredTaskContractReader(contract_store)
|
|
|
|
|
+ artifacts = SqlAlchemyScriptBusinessArtifactRepository(sessions)
|
|
|
|
|
+ accepted_inputs = AcceptedInputResolver(
|
|
|
|
|
+ task_store=task_store,
|
|
|
|
|
+ artifact_store=framework_artifacts,
|
|
|
|
|
+ artifacts=artifacts,
|
|
|
|
|
+ contracts=contract_reader,
|
|
|
|
|
+ )
|
|
|
|
|
+ workspaces = SqlAlchemyCandidateWorkspaceRepository(sessions, artifacts)
|
|
|
|
|
+ candidates = PhaseTwoCandidateService(
|
|
|
|
|
+ bindings=bindings,
|
|
|
|
|
+ task_store=task_store,
|
|
|
|
|
+ framework_artifact_store=framework_artifacts,
|
|
|
|
|
+ artifacts=artifacts,
|
|
|
|
|
+ accepted_inputs=accepted_inputs,
|
|
|
|
|
+ active_frontier=ActiveFrontierResolver(),
|
|
|
|
|
+ workspaces=workspaces,
|
|
|
|
|
+ )
|
|
|
|
|
+ boundary = ScriptPhaseTwoBoundaryVerifier(
|
|
|
|
|
+ task_store=task_store,
|
|
|
|
|
+ artifact_store=framework_artifacts,
|
|
|
|
|
+ artifacts=artifacts,
|
|
|
|
|
+ bindings=bindings,
|
|
|
|
|
+ accepted_inputs=accepted_inputs,
|
|
|
|
|
+ )
|
|
|
|
|
+ planning = PhaseTwoPlanningService(
|
|
|
|
|
+ coordinator=coordinator,
|
|
|
|
|
+ bindings=bindings,
|
|
|
|
|
+ contracts=contract_store,
|
|
|
|
|
+ closure_gate=boundary,
|
|
|
|
|
+ )
|
|
|
|
|
+ coordinator.set_executor(
|
|
|
|
|
+ _ScriptedExecutor(
|
|
|
|
|
+ coordinator=coordinator,
|
|
|
|
|
+ candidates=candidates,
|
|
|
|
|
+ artifacts=artifacts,
|
|
|
|
|
+ script_build_id=script_build_id,
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ direction = await _plan_one(
|
|
|
|
|
+ planning, _contract(ScriptTaskKind.DIRECTION), parent_task_id=None, call_id="direction"
|
|
|
|
|
+ )
|
|
|
|
|
+ retrieval = await _plan_one(
|
|
|
|
|
+ planning,
|
|
|
|
|
+ _contract(ScriptTaskKind.DECODE_RETRIEVAL, write_scope=()),
|
|
|
|
|
+ parent_task_id=direction,
|
|
|
|
|
+ call_id="retrieval",
|
|
|
|
|
+ )
|
|
|
|
|
+ await _dispatch_accept(planning, coordinator, retrieval)
|
|
|
|
|
+ direction_decision, direction_ref = await _dispatch_accept(planning, coordinator, direction)
|
|
|
|
|
+
|
|
|
|
|
+ portfolio = await _plan_one(
|
|
|
|
|
+ planning,
|
|
|
|
|
+ _contract(
|
|
|
|
|
+ ScriptTaskKind.CANDIDATE_PORTFOLIO,
|
|
|
|
|
+ write_scope=("script-build://writes",),
|
|
|
|
|
+ execution_ready=False,
|
|
|
|
|
+ ),
|
|
|
|
|
+ parent_task_id=None,
|
|
|
|
|
+ call_id="portfolio",
|
|
|
|
|
+ )
|
|
|
|
|
+ compose = await _plan_one(
|
|
|
|
|
+ planning,
|
|
|
|
|
+ _contract(
|
|
|
|
|
+ ScriptTaskKind.COMPOSE,
|
|
|
|
|
+ write_scope=("script-build://writes",),
|
|
|
|
|
+ execution_ready=False,
|
|
|
|
|
+ ),
|
|
|
|
|
+ parent_task_id=portfolio,
|
|
|
|
|
+ call_id="compose",
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # Exploration order is intentionally Element -> Paragraph -> Structure.
|
|
|
|
|
+ old_element = await _plan_one(
|
|
|
|
|
+ planning,
|
|
|
|
|
+ _contract(
|
|
|
|
|
+ ScriptTaskKind.ELEMENT_SET,
|
|
|
|
|
+ scope="script-build://scopes/full/elements",
|
|
|
|
|
+ write_scope=("script-build://writes/elements",),
|
|
|
|
|
+ ),
|
|
|
|
|
+ parent_task_id=compose,
|
|
|
|
|
+ call_id="element-first",
|
|
|
|
|
+ )
|
|
|
|
|
+ old_element_decision, old_element_ref = await _dispatch_accept(
|
|
|
|
|
+ planning, coordinator, old_element
|
|
|
|
|
+ )
|
|
|
|
|
+ old_paragraph = await _plan_one(
|
|
|
|
|
+ planning,
|
|
|
|
|
+ _contract(
|
|
|
|
|
+ ScriptTaskKind.PARAGRAPH,
|
|
|
|
|
+ scope="script-build://scopes/full/opening",
|
|
|
|
|
+ write_scope=("script-build://writes/paragraphs/opening",),
|
|
|
|
|
+ ),
|
|
|
|
|
+ parent_task_id=compose,
|
|
|
|
|
+ call_id="paragraph-second",
|
|
|
|
|
+ )
|
|
|
|
|
+ old_paragraph_decision, old_paragraph_ref = await _dispatch_accept(
|
|
|
|
|
+ planning, coordinator, old_paragraph
|
|
|
|
|
+ )
|
|
|
|
|
+ structure = await _plan_one(
|
|
|
|
|
+ planning,
|
|
|
|
|
+ _contract(
|
|
|
|
|
+ ScriptTaskKind.STRUCTURE,
|
|
|
|
|
+ scope="script-build://scopes/full/opening",
|
|
|
|
|
+ write_scope=("script-build://writes/paragraphs/opening",),
|
|
|
|
|
+ input_refs=(
|
|
|
|
|
+ _decision_ref(
|
|
|
|
|
+ old_paragraph_decision,
|
|
|
|
|
+ old_paragraph_ref,
|
|
|
|
|
+ scope="script-build://scopes/full/opening",
|
|
|
|
|
+ kind=ScriptTaskKind.PARAGRAPH,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ base_ref=old_paragraph_ref,
|
|
|
|
|
+ ),
|
|
|
|
|
+ parent_task_id=compose,
|
|
|
|
|
+ call_id="structure-third",
|
|
|
|
|
+ )
|
|
|
|
|
+ structure_decision, structure_ref = await _dispatch_accept(planning, coordinator, structure)
|
|
|
|
|
+
|
|
|
|
|
+ paragraph_replacement = await _plan_one(
|
|
|
|
|
+ planning,
|
|
|
|
|
+ _contract(
|
|
|
|
|
+ ScriptTaskKind.PARAGRAPH,
|
|
|
|
|
+ scope="script-build://scopes/full/opening",
|
|
|
|
|
+ write_scope=("script-build://writes/paragraphs/opening",),
|
|
|
|
|
+ input_refs=(
|
|
|
|
|
+ _decision_ref(
|
|
|
|
|
+ structure_decision,
|
|
|
|
|
+ structure_ref,
|
|
|
|
|
+ scope="script-build://scopes/full/opening",
|
|
|
|
|
+ kind=ScriptTaskKind.STRUCTURE,
|
|
|
|
|
+ ),
|
|
|
|
|
+ _decision_ref(
|
|
|
|
|
+ old_paragraph_decision,
|
|
|
|
|
+ old_paragraph_ref,
|
|
|
|
|
+ scope="script-build://scopes/full/opening",
|
|
|
|
|
+ kind=ScriptTaskKind.PARAGRAPH,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ base_ref=structure_ref,
|
|
|
|
|
+ supersedes=(old_paragraph_decision,),
|
|
|
|
|
+ ),
|
|
|
|
|
+ parent_task_id=compose,
|
|
|
|
|
+ call_id="replace-paragraph",
|
|
|
|
|
+ )
|
|
|
|
|
+ paragraph_decision, paragraph_ref = await _dispatch_accept(
|
|
|
|
|
+ planning, coordinator, paragraph_replacement
|
|
|
|
|
+ )
|
|
|
|
|
+ element_replacement = await _plan_one(
|
|
|
|
|
+ planning,
|
|
|
|
|
+ _contract(
|
|
|
|
|
+ ScriptTaskKind.ELEMENT_SET,
|
|
|
|
|
+ scope="script-build://scopes/full/elements",
|
|
|
|
|
+ write_scope=("script-build://writes/elements",),
|
|
|
|
|
+ input_refs=(
|
|
|
|
|
+ _decision_ref(
|
|
|
|
|
+ old_element_decision,
|
|
|
|
|
+ old_element_ref,
|
|
|
|
|
+ scope="script-build://scopes/full/elements",
|
|
|
|
|
+ kind=ScriptTaskKind.ELEMENT_SET,
|
|
|
|
|
+ ),
|
|
|
|
|
+ _decision_ref(
|
|
|
|
|
+ paragraph_decision,
|
|
|
|
|
+ paragraph_ref,
|
|
|
|
|
+ scope="script-build://scopes/full/opening",
|
|
|
|
|
+ kind=ScriptTaskKind.PARAGRAPH,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ base_ref=paragraph_ref,
|
|
|
|
|
+ supersedes=(old_element_decision,),
|
|
|
|
|
+ ),
|
|
|
|
|
+ parent_task_id=compose,
|
|
|
|
|
+ call_id="replace-element",
|
|
|
|
|
+ )
|
|
|
|
|
+ element_decision, element_ref = await _dispatch_accept(
|
|
|
|
|
+ planning, coordinator, element_replacement
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ comparison = await _plan_one(
|
|
|
|
|
+ planning,
|
|
|
|
|
+ _contract(
|
|
|
|
|
+ ScriptTaskKind.COMPARE,
|
|
|
|
|
+ scope="script-build://scopes/full/opening",
|
|
|
|
|
+ write_scope=(),
|
|
|
|
|
+ comparison_refs=(
|
|
|
|
|
+ _decision_ref(
|
|
|
|
|
+ old_paragraph_decision,
|
|
|
|
|
+ old_paragraph_ref,
|
|
|
|
|
+ scope="script-build://scopes/full/opening",
|
|
|
|
|
+ kind=ScriptTaskKind.PARAGRAPH,
|
|
|
|
|
+ ),
|
|
|
|
|
+ _decision_ref(
|
|
|
|
|
+ paragraph_decision,
|
|
|
|
|
+ paragraph_ref,
|
|
|
|
|
+ scope="script-build://scopes/full/opening",
|
|
|
|
|
+ kind=ScriptTaskKind.PARAGRAPH,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ parent_task_id=compose,
|
|
|
|
|
+ call_id="compare-opening-candidates",
|
|
|
|
|
+ )
|
|
|
|
|
+ comparison_decision, comparison_ref = await _dispatch_accept(planning, coordinator, comparison)
|
|
|
|
|
+
|
|
|
|
|
+ with pytest.raises(PhaseTwoBoundaryNotReady, match="CandidatePortfolio"):
|
|
|
|
|
+ await planning.decide_script_task(
|
|
|
|
|
+ task_id=(await task_store.load(ROOT)).root_task_id,
|
|
|
|
|
+ action=DecisionAction.BLOCK.value,
|
|
|
|
|
+ reason=PHASE_TWO_CANDIDATE_PORTFOLIO_READY,
|
|
|
|
|
+ validation_id=None,
|
|
|
|
|
+ replacement_contract=None,
|
|
|
|
|
+ child_contracts=(),
|
|
|
|
|
+ context={"root_trace_id": ROOT, "tool_call_id": "early-boundary"},
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ local_refs = (
|
|
|
|
|
+ _decision_ref(
|
|
|
|
|
+ old_element_decision,
|
|
|
|
|
+ old_element_ref,
|
|
|
|
|
+ scope="script-build://scopes/full/elements",
|
|
|
|
|
+ kind=ScriptTaskKind.ELEMENT_SET,
|
|
|
|
|
+ ),
|
|
|
|
|
+ _decision_ref(
|
|
|
|
|
+ old_paragraph_decision,
|
|
|
|
|
+ old_paragraph_ref,
|
|
|
|
|
+ scope="script-build://scopes/full/opening",
|
|
|
|
|
+ kind=ScriptTaskKind.PARAGRAPH,
|
|
|
|
|
+ ),
|
|
|
|
|
+ _decision_ref(
|
|
|
|
|
+ structure_decision,
|
|
|
|
|
+ structure_ref,
|
|
|
|
|
+ scope="script-build://scopes/full/opening",
|
|
|
|
|
+ kind=ScriptTaskKind.STRUCTURE,
|
|
|
|
|
+ ),
|
|
|
|
|
+ _decision_ref(
|
|
|
|
|
+ paragraph_decision,
|
|
|
|
|
+ paragraph_ref,
|
|
|
|
|
+ scope="script-build://scopes/full/opening",
|
|
|
|
|
+ kind=ScriptTaskKind.PARAGRAPH,
|
|
|
|
|
+ ),
|
|
|
|
|
+ _decision_ref(
|
|
|
|
|
+ element_decision,
|
|
|
|
|
+ element_ref,
|
|
|
|
|
+ scope="script-build://scopes/full/elements",
|
|
|
|
|
+ kind=ScriptTaskKind.ELEMENT_SET,
|
|
|
|
|
+ ),
|
|
|
|
|
+ )
|
|
|
|
|
+ adopted = (structure_decision, paragraph_decision, element_decision)
|
|
|
|
|
+ held = (old_element_decision, old_paragraph_decision)
|
|
|
|
|
+ compose_contract = _contract(
|
|
|
|
|
+ ScriptTaskKind.COMPOSE,
|
|
|
|
|
+ write_scope=("script-build://writes",),
|
|
|
|
|
+ input_refs=(
|
|
|
|
|
+ _decision_ref(
|
|
|
|
|
+ direction_decision,
|
|
|
|
|
+ direction_ref,
|
|
|
|
|
+ scope=FULL_SCOPE,
|
|
|
|
|
+ kind=ScriptTaskKind.DIRECTION,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ closure=local_refs,
|
|
|
|
|
+ adopted=adopted,
|
|
|
|
|
+ held=held,
|
|
|
|
|
+ order=adopted,
|
|
|
|
|
+ comparison_refs=(
|
|
|
|
|
+ _decision_ref(
|
|
|
|
|
+ comparison_decision,
|
|
|
|
|
+ comparison_ref,
|
|
|
|
|
+ scope="script-build://scopes/full/opening",
|
|
|
|
|
+ kind=ScriptTaskKind.COMPARE,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ )
|
|
|
|
|
+ await planning.decide_script_task(
|
|
|
|
|
+ task_id=compose,
|
|
|
|
|
+ action=DecisionAction.REVISE.value,
|
|
|
|
|
+ reason="freeze the selected active frontier and formal compose order",
|
|
|
|
|
+ validation_id=None,
|
|
|
|
|
+ replacement_contract=compose_contract,
|
|
|
|
|
+ child_contracts=(),
|
|
|
|
|
+ context={"root_trace_id": ROOT, "tool_call_id": "close-compose"},
|
|
|
|
|
+ )
|
|
|
|
|
+ compose_decision, compose_ref = await _dispatch_accept(planning, coordinator, compose)
|
|
|
|
|
+
|
|
|
|
|
+ portfolio_contract = _contract(
|
|
|
|
|
+ ScriptTaskKind.CANDIDATE_PORTFOLIO,
|
|
|
|
|
+ write_scope=("script-build://writes",),
|
|
|
|
|
+ closure=(
|
|
|
|
|
+ _decision_ref(
|
|
|
|
|
+ compose_decision,
|
|
|
|
|
+ compose_ref,
|
|
|
|
|
+ scope=FULL_SCOPE,
|
|
|
|
|
+ kind=ScriptTaskKind.COMPOSE,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ adopted=(compose_decision,),
|
|
|
|
|
+ order=(compose_decision,),
|
|
|
|
|
+ )
|
|
|
|
|
+ await planning.decide_script_task(
|
|
|
|
|
+ task_id=portfolio,
|
|
|
|
|
+ action=DecisionAction.REVISE.value,
|
|
|
|
|
+ reason="freeze the uniquely adopted StructuredScript",
|
|
|
|
|
+ validation_id=None,
|
|
|
|
|
+ replacement_contract=portfolio_contract,
|
|
|
|
|
+ child_contracts=(),
|
|
|
|
|
+ context={"root_trace_id": ROOT, "tool_call_id": "close-portfolio"},
|
|
|
|
|
+ )
|
|
|
|
|
+ portfolio_decision, _ = await _dispatch_accept(planning, coordinator, portfolio)
|
|
|
|
|
+
|
|
|
|
|
+ ledger = await task_store.load(ROOT)
|
|
|
|
|
+ compose_attempt = ledger.attempts[cast(str, ledger.decisions[compose_decision].attempt_id)]
|
|
|
|
|
+ frontier = await candidates.read_active_frontier(
|
|
|
|
|
+ context={
|
|
|
|
|
+ "root_trace_id": ROOT,
|
|
|
|
|
+ "task_id": compose,
|
|
|
|
|
+ "attempt_id": compose_attempt.attempt_id,
|
|
|
|
|
+ "spec_version": compose_attempt.spec_version,
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
+ assert [item["decision_id"] for item in frontier["inputs"]] == list(adopted)
|
|
|
|
|
+ assert old_element_decision not in {item["decision_id"] for item in frontier["inputs"]}
|
|
|
|
|
+ assert old_paragraph_decision not in {item["decision_id"] for item in frontier["inputs"]}
|
|
|
|
|
+
|
|
|
|
|
+ await planning.decide_script_task(
|
|
|
|
|
+ task_id=ledger.root_task_id,
|
|
|
|
|
+ action=DecisionAction.BLOCK.value,
|
|
|
|
|
+ reason=PHASE_TWO_CANDIDATE_PORTFOLIO_READY,
|
|
|
|
|
+ validation_id=None,
|
|
|
|
|
+ replacement_contract=None,
|
|
|
|
|
+ child_contracts=(),
|
|
|
|
|
+ context={"root_trace_id": ROOT, "tool_call_id": "phase-two-boundary"},
|
|
|
|
|
+ )
|
|
|
|
|
+ await build_states.set_checkpoint(
|
|
|
|
|
+ script_build_id,
|
|
|
|
|
+ checkpoint_code=PHASE_TWO_CANDIDATE_PORTFOLIO_READY,
|
|
|
|
|
+ summary="one validated CandidatePortfolio is ready for phase three",
|
|
|
|
|
+ root_trace_id=ROOT,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ reloaded = await FileSystemTaskStore(str(task_path)).load(ROOT)
|
|
|
|
|
+ assert reloaded.tasks[reloaded.root_task_id].status is TaskStatus.BLOCKED
|
|
|
|
|
+ assert reloaded.tasks[reloaded.root_task_id].blocked_reason == (
|
|
|
|
|
+ PHASE_TWO_CANDIDATE_PORTFOLIO_READY
|
|
|
|
|
+ )
|
|
|
|
|
+ compose_children = [reloaded.tasks[item] for item in reloaded.tasks[compose].child_task_ids]
|
|
|
|
|
+ stored_reader = StoredTaskContractReader(FileScriptTaskContractStore(contract_path))
|
|
|
|
|
+ assert [
|
|
|
|
|
+ (
|
|
|
|
|
+ await stored_reader.read_for_task(
|
|
|
|
|
+ root_trace_id=ROOT,
|
|
|
|
|
+ task=item,
|
|
|
|
|
+ spec_version=item.current_spec_version,
|
|
|
|
|
+ )
|
|
|
|
|
+ ).task_kind
|
|
|
|
|
+ for item in compose_children[:3]
|
|
|
|
|
+ ] == [
|
|
|
|
|
+ ScriptTaskKind.ELEMENT_SET,
|
|
|
|
|
+ ScriptTaskKind.PARAGRAPH,
|
|
|
|
|
+ ScriptTaskKind.STRUCTURE,
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ compose_version = await artifacts.get_by_attempt(
|
|
|
|
|
+ script_build_id=script_build_id,
|
|
|
|
|
+ task_id=compose,
|
|
|
|
|
+ attempt_id=compose_attempt.attempt_id,
|
|
|
|
|
+ )
|
|
|
|
|
+ assert isinstance(compose_version.artifact, StructuredScriptArtifactV1)
|
|
|
|
|
+ assert len(compose_version.artifact.paragraphs) == 1
|
|
|
|
|
+ assert compose_version.artifact.elements
|
|
|
|
|
+ assert compose_version.artifact.paragraph_element_links
|
|
|
|
|
+ portfolio_attempt = reloaded.attempts[
|
|
|
|
|
+ cast(str, reloaded.decisions[portfolio_decision].attempt_id)
|
|
|
|
|
+ ]
|
|
|
|
|
+ portfolio_version = await artifacts.get_by_attempt(
|
|
|
|
|
+ script_build_id=script_build_id,
|
|
|
|
|
+ task_id=portfolio,
|
|
|
|
|
+ attempt_id=portfolio_attempt.attempt_id,
|
|
|
|
|
+ )
|
|
|
|
|
+ assert isinstance(portfolio_version.artifact, CandidatePortfolioArtifactV1)
|
|
|
|
|
+ assert portfolio_version.artifact.accepted_decision_ids == (compose_decision,)
|
|
|
|
|
+ assert portfolio_version.artifact.adopted_structured_script_ref == compose_ref.uri
|
|
|
|
|
+
|
|
|
|
|
+ async with sessions() as session:
|
|
|
|
|
+ build = (
|
|
|
|
|
+ (
|
|
|
|
|
+ await session.execute(
|
|
|
|
|
+ select(script_build_record).where(script_build_record.c.id == script_build_id)
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ .mappings()
|
|
|
|
|
+ .one()
|
|
|
|
|
+ )
|
|
|
|
|
+ branch_values = [
|
|
|
|
|
+ *(
|
|
|
|
|
+ await session.scalars(
|
|
|
|
|
+ select(script_build_paragraph.c.branch_id).where(
|
|
|
|
|
+ script_build_paragraph.c.script_build_id == script_build_id
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ *(
|
|
|
|
|
+ await session.scalars(
|
|
|
|
|
+ select(script_build_element.c.branch_id).where(
|
|
|
|
|
+ script_build_element.c.script_build_id == script_build_id
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ *(
|
|
|
|
|
+ await session.scalars(
|
|
|
|
|
+ select(script_build_paragraph_element.c.branch_id).where(
|
|
|
|
|
+ script_build_paragraph_element.c.script_build_id == script_build_id
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ ]
|
|
|
|
|
+ final_publications = await session.scalar(
|
|
|
|
|
+ select(func.count())
|
|
|
|
|
+ .select_from(publication_table)
|
|
|
|
|
+ .where(
|
|
|
|
|
+ publication_table.c.script_build_id == script_build_id,
|
|
|
|
|
+ publication_table.c.publication_type == "final",
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ phase_two_rows = (
|
|
|
|
|
+ (
|
|
|
|
|
+ await session.execute(
|
|
|
|
|
+ select(artifact_version_table).where(
|
|
|
|
|
+ artifact_version_table.c.script_build_id == script_build_id,
|
|
|
|
|
+ artifact_version_table.c.artifact_type.in_(
|
|
|
|
|
+ [
|
|
|
|
|
+ ArtifactKind.STRUCTURE.value,
|
|
|
|
|
+ ArtifactKind.PARAGRAPH.value,
|
|
|
|
|
+ ArtifactKind.ELEMENT_SET.value,
|
|
|
|
|
+ ArtifactKind.COMPARISON.value,
|
|
|
|
|
+ ArtifactKind.STRUCTURED_SCRIPT.value,
|
|
|
|
|
+ ArtifactKind.CANDIDATE_PORTFOLIO.value,
|
|
|
|
|
+ ]
|
|
|
|
|
+ ),
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ .mappings()
|
|
|
|
|
+ .all()
|
|
|
|
|
+ )
|
|
|
|
|
+ mutation_targets = {
|
|
|
|
|
+ match.group(1).strip('`"').lower()
|
|
|
|
|
+ for statement in mutation_statements
|
|
|
|
|
+ if (
|
|
|
|
|
+ match := re.match(
|
|
|
|
|
+ r"\s*(?:INSERT\s+INTO|UPDATE|DELETE\s+FROM)\s+([`\"\w]+)",
|
|
|
|
|
+ statement,
|
|
|
|
|
+ re.IGNORECASE,
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ assert mutation_targets <= {
|
|
|
|
|
+ "script_build_record",
|
|
|
|
|
+ "script_build_mission_binding",
|
|
|
|
|
+ "script_build_artifact_version",
|
|
|
|
|
+ "script_build_publication",
|
|
|
|
|
+ "script_build_paragraph",
|
|
|
|
|
+ "script_build_element",
|
|
|
|
|
+ "script_build_paragraph_element",
|
|
|
|
|
+ "script_build_task_plan_step",
|
|
|
|
|
+ }
|
|
|
|
|
+ assert not mutation_targets & {
|
|
|
|
|
+ "script_build_branch",
|
|
|
|
|
+ "script_build_round",
|
|
|
|
|
+ "script_build_multipath",
|
|
|
|
|
+ }
|
|
|
|
|
+ assert BuildStatus(str(build["status"])) is BuildStatus.PARTIAL
|
|
|
|
|
+ assert build["error_message"] == PHASE_TWO_CANDIDATE_PORTFOLIO_READY
|
|
|
|
|
+ assert build["reson_trace_id"] == ROOT and build["end_time"] is not None
|
|
|
|
|
+ assert branch_values and min(branch_values) > 0 and 0 not in branch_values
|
|
|
|
|
+ assert final_publications == 0
|
|
|
|
|
+ assert (
|
|
|
|
|
+ sum(
|
|
|
|
|
+ row["artifact_type"] == ArtifactKind.CANDIDATE_PORTFOLIO.value for row in phase_two_rows
|
|
|
|
|
+ )
|
|
|
|
|
+ == 1
|
|
|
|
|
+ )
|
|
|
|
|
+ for row in phase_two_rows:
|
|
|
|
|
+ if row["artifact_type"] in {
|
|
|
|
|
+ ArtifactKind.STRUCTURE.value,
|
|
|
|
|
+ ArtifactKind.PARAGRAPH.value,
|
|
|
|
|
+ ArtifactKind.ELEMENT_SET.value,
|
|
|
|
|
+ ArtifactKind.STRUCTURED_SCRIPT.value,
|
|
|
|
|
+ }:
|
|
|
|
|
+ assert row["legacy_branch_id"] == row["id"] > 0
|
|
|
|
|
+ else:
|
|
|
|
|
+ assert row["legacy_branch_id"] is None
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class _SingleBinding:
|
|
|
|
|
+ def __init__(self, root: str) -> None:
|
|
|
|
|
+ now = datetime.now(UTC)
|
|
|
|
|
+ self.value = MissionBinding(1, 1, root, 11, None, None, "test", "v1", now, now)
|
|
|
|
|
+
|
|
|
|
|
+ async def get_by_root(self, root_trace_id: str) -> MissionBinding:
|
|
|
|
|
+ assert root_trace_id == self.value.root_trace_id
|
|
|
|
|
+ return self.value
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class _BlockingExecutor:
|
|
|
|
|
+ def __init__(self) -> None:
|
|
|
|
|
+ self.started = asyncio.Event()
|
|
|
|
|
+ self.release = asyncio.Event()
|
|
|
|
|
+
|
|
|
|
|
+ async def run_worker(self, context: dict[str, Any]) -> WorkerRunResult:
|
|
|
|
|
+ self.started.set()
|
|
|
|
|
+ await self.release.wait()
|
|
|
|
|
+ return WorkerRunResult(context["worker_trace_id"], "failed", error="test release")
|
|
|
|
|
+
|
|
|
|
|
+ async def run_validator(self, context: dict[str, Any]) -> ValidatorRunResult:
|
|
|
|
|
+ raise AssertionError(f"unexpected validation: {context}")
|
|
|
|
|
+
|
|
|
|
|
+ async def stop(self, trace_id: str) -> bool:
|
|
|
|
|
+ del trace_id
|
|
|
|
|
+ self.release.set()
|
|
|
|
|
+ return True
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@pytest.mark.asyncio
|
|
|
|
|
+async def test_same_task_cannot_reserve_parallel_attempts(tmp_path: Path) -> None:
|
|
|
|
|
+ root = "parallel-attempt-root"
|
|
|
|
|
+ task_store = FileSystemTaskStore(str(tmp_path / "ledger"))
|
|
|
|
|
+ executor = _BlockingExecutor()
|
|
|
|
|
+ coordinator = TaskCoordinator(
|
|
|
|
|
+ task_store,
|
|
|
|
|
+ FileSystemArtifactStore(str(tmp_path / "artifacts")),
|
|
|
|
|
+ FileSystemTraceStore(str(tmp_path / "traces")),
|
|
|
|
|
+ executor=executor,
|
|
|
|
|
+ )
|
|
|
|
|
+ await coordinator.ensure_ledger(
|
|
|
|
|
+ root,
|
|
|
|
|
+ {
|
|
|
|
|
+ "objective": "parallel attempt guard",
|
|
|
|
|
+ "acceptance_criteria": [
|
|
|
|
|
+ {"criterion_id": "guard", "description": "guard", "hard": True}
|
|
|
|
|
+ ],
|
|
|
|
|
+ "context_refs": ["script-build://inputs/11"],
|
|
|
|
|
+ },
|
|
|
|
|
+ )
|
|
|
|
|
+ planning = PhaseTwoPlanningService(
|
|
|
|
|
+ coordinator=coordinator,
|
|
|
|
|
+ bindings=cast(Any, _SingleBinding(root)),
|
|
|
|
|
+ contracts=FileScriptTaskContractStore(tmp_path / "contracts"),
|
|
|
|
|
+ )
|
|
|
|
|
+ task = await planning.plan_script_tasks(
|
|
|
|
|
+ contract_payloads=(_contract(ScriptTaskKind.DIRECTION),),
|
|
|
|
|
+ parent_task_id=None,
|
|
|
|
|
+ context={"root_trace_id": root},
|
|
|
|
|
+ )
|
|
|
|
|
+ task_id = task["task_ids"][0]
|
|
|
|
|
+ first = asyncio.create_task(
|
|
|
|
|
+ planning.dispatch_script_tasks(task_ids=(task_id,), context={"root_trace_id": root})
|
|
|
|
|
+ )
|
|
|
|
|
+ await asyncio.wait_for(executor.started.wait(), timeout=2)
|
|
|
|
|
+ second = await planning.dispatch_script_tasks(
|
|
|
|
|
+ task_ids=(task_id,), context={"root_trace_id": root}
|
|
|
|
|
+ )
|
|
|
|
|
+ assert "Dispatch conflict" in cast(str, second[0]["error"])
|
|
|
|
|
+ executor.release.set()
|
|
|
|
|
+ await first
|
|
|
|
|
+ ledger = await task_store.load(root)
|
|
|
|
|
+ assert len(ledger.tasks[task_id].attempt_ids) == 1
|