|
@@ -132,6 +132,34 @@ def _contract(
|
|
|
return payload
|
|
return payload
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def _planner_input(payload: dict[str, object]) -> dict[str, object]:
|
|
|
|
|
+ input_refs = cast(list[dict[str, Any]], payload.get("input_decision_refs") or [])
|
|
|
|
|
+ comparison_refs = cast(
|
|
|
|
|
+ list[dict[str, Any]], payload.get("comparison_decision_refs") or []
|
|
|
|
|
+ )
|
|
|
|
|
+ base = cast(dict[str, Any] | None, payload.get("base_artifact_ref"))
|
|
|
|
|
+ return {
|
|
|
|
|
+ "task_kind": payload["task_kind"],
|
|
|
|
|
+ "scope_ref": payload["scope_ref"],
|
|
|
|
|
+ "intent_class": payload["intent_class"],
|
|
|
|
|
+ "objective": payload["objective"],
|
|
|
|
|
+ "goal_ids": payload["goal_ids"],
|
|
|
|
|
+ "criteria": payload["criteria"],
|
|
|
|
|
+ "input_decision_ids": [item["decision_id"] for item in input_refs],
|
|
|
|
|
+ "base_decision_id": next(
|
|
|
|
|
+ (
|
|
|
|
|
+ item["decision_id"]
|
|
|
|
|
+ for item in input_refs
|
|
|
|
|
+ if base is not None and item["artifact_ref"]["uri"] == base["uri"]
|
|
|
|
|
+ ),
|
|
|
|
|
+ None,
|
|
|
|
|
+ ),
|
|
|
|
|
+ "comparison_decision_ids": [item["decision_id"] for item in comparison_refs],
|
|
|
|
|
+ "supersedes_decision_ids": payload["supersedes_decision_ids"],
|
|
|
|
|
+ "gap_ref": payload["gap_ref"],
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def test_deep_control_uri_is_not_mistaken_for_base64_content() -> None:
|
|
def test_deep_control_uri_is_not_mistaken_for_base64_content() -> None:
|
|
|
scope = (
|
|
scope = (
|
|
|
"script-build://mission/900032/direction/main/portfolio/main/compose/main/"
|
|
"script-build://mission/900032/direction/main/portfolio/main/compose/main/"
|
|
@@ -305,6 +333,7 @@ class _PlaceholderThenReplacementExecutor:
|
|
|
refs = cast(dict[str, Any], context["task_spec"])["context_refs"]
|
|
refs = cast(dict[str, Any], context["task_spec"])["context_refs"]
|
|
|
kind = next(item.rsplit("/", 1)[-1] for item in refs if "/task-kinds/" in item)
|
|
kind = next(item.rsplit("/", 1)[-1] for item in refs if "/task-kinds/" in item)
|
|
|
artifact_kind = {
|
|
artifact_kind = {
|
|
|
|
|
+ "direction": "direction",
|
|
|
"compose": "structured_script",
|
|
"compose": "structured_script",
|
|
|
"structure": "structure",
|
|
"structure": "structure",
|
|
|
"paragraph": "paragraph",
|
|
"paragraph": "paragraph",
|
|
@@ -341,7 +370,7 @@ class _PlaceholderThenReplacementExecutor:
|
|
|
async def run_validator(self, context: dict[str, Any]) -> ValidatorRunResult:
|
|
async def run_validator(self, context: dict[str, Any]) -> ValidatorRunResult:
|
|
|
refs = cast(dict[str, Any], context["task_spec"])["context_refs"]
|
|
refs = cast(dict[str, Any], context["task_spec"])["context_refs"]
|
|
|
kind = next(item.rsplit("/", 1)[-1] for item in refs if "/task-kinds/" in item)
|
|
kind = next(item.rsplit("/", 1)[-1] for item in refs if "/task-kinds/" in item)
|
|
|
- placeholder = kind == "compose" and context["spec_version"] == 1
|
|
|
|
|
|
|
+ placeholder = kind == "compose" and context["spec_version"] == 2
|
|
|
verdict = ValidationVerdict.FAILED if placeholder else ValidationVerdict.PASSED
|
|
verdict = ValidationVerdict.FAILED if placeholder else ValidationVerdict.PASSED
|
|
|
criterion = cast(dict[str, Any], context["task_spec"])["acceptance_criteria"][0]
|
|
criterion = cast(dict[str, Any], context["task_spec"])["acceptance_criteria"][0]
|
|
|
await self.coordinator.submit_validation(
|
|
await self.coordinator.submit_validation(
|
|
@@ -386,7 +415,9 @@ class _PlaceholderThenReplacementExecutor:
|
|
|
return True
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
-async def _service(tmp_path: Path) -> tuple[PhaseTwoPlanningService, TaskCoordinator, str]:
|
|
|
|
|
|
|
+async def _service(
|
|
|
|
|
+ tmp_path: Path, *, active_direction: bool = True
|
|
|
|
|
+) -> tuple[PhaseTwoPlanningService, TaskCoordinator, str]:
|
|
|
root = "root-contracts"
|
|
root = "root-contracts"
|
|
|
coordinator = TaskCoordinator(
|
|
coordinator = TaskCoordinator(
|
|
|
FileSystemTaskStore(str(tmp_path / "ledger")),
|
|
FileSystemTaskStore(str(tmp_path / "ledger")),
|
|
@@ -403,28 +434,70 @@ async def _service(tmp_path: Path) -> tuple[PhaseTwoPlanningService, TaskCoordin
|
|
|
"context_refs": ["script-build://inputs/11"],
|
|
"context_refs": ["script-build://inputs/11"],
|
|
|
},
|
|
},
|
|
|
)
|
|
)
|
|
|
|
|
+ bindings = _Bindings(root)
|
|
|
service = PhaseTwoPlanningService(
|
|
service = PhaseTwoPlanningService(
|
|
|
coordinator=coordinator,
|
|
coordinator=coordinator,
|
|
|
- bindings=_Bindings(root),
|
|
|
|
|
|
|
+ bindings=bindings,
|
|
|
contracts=FileScriptTaskContractStore(tmp_path / "data"),
|
|
contracts=FileScriptTaskContractStore(tmp_path / "data"),
|
|
|
artifacts=cast(Any, SimpleNamespace()),
|
|
artifacts=cast(Any, SimpleNamespace()),
|
|
|
)
|
|
)
|
|
|
|
|
+ if active_direction:
|
|
|
|
|
+ await _activate_direction(service, coordinator, bindings, root)
|
|
|
return service, coordinator, root
|
|
return service, coordinator, root
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+async def _activate_direction(
|
|
|
|
|
+ service: PhaseTwoPlanningService,
|
|
|
|
|
+ coordinator: TaskCoordinator,
|
|
|
|
|
+ bindings: _Bindings,
|
|
|
|
|
+ root: str,
|
|
|
|
|
+) -> None:
|
|
|
|
|
+ coordinator.set_executor(_PlaceholderThenReplacementExecutor(coordinator))
|
|
|
|
|
+ planned = await service.plan_script_tasks(
|
|
|
|
|
+ contract_payloads=[_planner_input(_contract("direction"))],
|
|
|
|
|
+ parent_task_id=None,
|
|
|
|
|
+ context={"root_trace_id": root, "tool_call_id": "fixture:direction"},
|
|
|
|
|
+ )
|
|
|
|
|
+ direction_id = cast(str, planned["task_ids"][0])
|
|
|
|
|
+ cycle = (
|
|
|
|
|
+ await service.dispatch_script_tasks(
|
|
|
|
|
+ task_ids=(direction_id,),
|
|
|
|
|
+ context={"root_trace_id": root, "tool_call_id": "fixture:dispatch-direction"},
|
|
|
|
|
+ )
|
|
|
|
|
+ )[0]
|
|
|
|
|
+ accepted = await service.decide_script_task(
|
|
|
|
|
+ task_id=direction_id,
|
|
|
|
|
+ action=DecisionAction.ACCEPT.value,
|
|
|
|
|
+ reason="fixture Direction passed",
|
|
|
|
|
+ validation_id=cast(str, cycle["validation_id"]),
|
|
|
|
|
+ replacement_contract=None,
|
|
|
|
|
+ child_contracts=(),
|
|
|
|
|
+ context={"root_trace_id": root, "tool_call_id": "fixture:accept-direction"},
|
|
|
|
|
+ )
|
|
|
|
|
+ ledger = await coordinator.task_store.load(root)
|
|
|
|
|
+ decision = ledger.decisions[cast(str, accepted["decision_id"])]
|
|
|
|
|
+ attempt = ledger.attempts[cast(str, decision.attempt_id)]
|
|
|
|
|
+ assert attempt.submission is not None
|
|
|
|
|
+ direction_ref = attempt.submission.artifact_refs[0]
|
|
|
|
|
+ bindings.binding = replace(
|
|
|
|
|
+ bindings.binding,
|
|
|
|
|
+ active_direction_artifact_version_id=int(direction_ref.version),
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.asyncio
|
|
|
async def test_dispatch_explains_that_blocked_children_do_not_close_parent(
|
|
async def test_dispatch_explains_that_blocked_children_do_not_close_parent(
|
|
|
tmp_path: Path,
|
|
tmp_path: Path,
|
|
|
) -> None:
|
|
) -> None:
|
|
|
- service, coordinator, root = await _service(tmp_path)
|
|
|
|
|
|
|
+ service, coordinator, root = await _service(tmp_path, active_direction=False)
|
|
|
direction = await service.plan_script_tasks(
|
|
direction = await service.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("direction")],
|
|
|
|
|
|
|
+ contract_payloads=[_planner_input(_contract("direction"))],
|
|
|
parent_task_id=None,
|
|
parent_task_id=None,
|
|
|
context={"root_trace_id": root, "tool_call_id": "direction"},
|
|
context={"root_trace_id": root, "tool_call_id": "direction"},
|
|
|
)
|
|
)
|
|
|
direction_id = direction["task_ids"][0]
|
|
direction_id = direction["task_ids"][0]
|
|
|
retrieval = await service.plan_script_tasks(
|
|
retrieval = await service.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("decode-retrieval")],
|
|
|
|
|
|
|
+ contract_payloads=[_planner_input(_contract("decode-retrieval"))],
|
|
|
parent_task_id=direction_id,
|
|
parent_task_id=direction_id,
|
|
|
context={"root_trace_id": root, "tool_call_id": "retrieval"},
|
|
context={"root_trace_id": root, "tool_call_id": "retrieval"},
|
|
|
)
|
|
)
|
|
@@ -449,7 +522,9 @@ async def test_dispatch_explains_that_blocked_children_do_not_close_parent(
|
|
|
async def test_unique_portfolio_container_cannot_be_cancelled(tmp_path: Path) -> None:
|
|
async def test_unique_portfolio_container_cannot_be_cancelled(tmp_path: Path) -> None:
|
|
|
service, _coordinator, root = await _service(tmp_path)
|
|
service, _coordinator, root = await _service(tmp_path)
|
|
|
portfolio = await service.plan_script_tasks(
|
|
portfolio = await service.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("candidate-portfolio", execution_ready=False)],
|
|
|
|
|
|
|
+ contract_payloads=[
|
|
|
|
|
+ _planner_input(_contract("candidate-portfolio", execution_ready=False))
|
|
|
|
|
+ ],
|
|
|
parent_task_id=None,
|
|
parent_task_id=None,
|
|
|
context={"root_trace_id": root, "tool_call_id": "portfolio"},
|
|
context={"root_trace_id": root, "tool_call_id": "portfolio"},
|
|
|
)
|
|
)
|
|
@@ -577,18 +652,21 @@ async def test_real_operation_stop_converges_and_forbids_new_dispatch(tmp_path:
|
|
|
contracts=FileScriptTaskContractStore(tmp_path / "stop-contracts"),
|
|
contracts=FileScriptTaskContractStore(tmp_path / "stop-contracts"),
|
|
|
artifacts=cast(Any, SimpleNamespace()),
|
|
artifacts=cast(Any, SimpleNamespace()),
|
|
|
)
|
|
)
|
|
|
|
|
+ await _activate_direction(planning, coordinator, bindings, root)
|
|
|
portfolio = await planning.plan_script_tasks(
|
|
portfolio = await planning.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("candidate-portfolio", execution_ready=False)],
|
|
|
|
|
|
|
+ contract_payloads=[
|
|
|
|
|
+ _planner_input(_contract("candidate-portfolio", execution_ready=False))
|
|
|
|
|
+ ],
|
|
|
parent_task_id=None,
|
|
parent_task_id=None,
|
|
|
context={"root_trace_id": root, "tool_call_id": "stop:portfolio"},
|
|
context={"root_trace_id": root, "tool_call_id": "stop:portfolio"},
|
|
|
)
|
|
)
|
|
|
compose = await planning.plan_script_tasks(
|
|
compose = await planning.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("compose", execution_ready=False)],
|
|
|
|
|
|
|
+ contract_payloads=[_planner_input(_contract("compose", execution_ready=False))],
|
|
|
parent_task_id=portfolio["task_ids"][0],
|
|
parent_task_id=portfolio["task_ids"][0],
|
|
|
context={"root_trace_id": root, "tool_call_id": "stop:compose"},
|
|
context={"root_trace_id": root, "tool_call_id": "stop:compose"},
|
|
|
)
|
|
)
|
|
|
paragraph = await planning.plan_script_tasks(
|
|
paragraph = await planning.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("paragraph")],
|
|
|
|
|
|
|
+ contract_payloads=[_planner_input(_contract("paragraph"))],
|
|
|
parent_task_id=compose["task_ids"][0],
|
|
parent_task_id=compose["task_ids"][0],
|
|
|
context={"root_trace_id": root, "tool_call_id": "stop:paragraph"},
|
|
context={"root_trace_id": root, "tool_call_id": "stop:paragraph"},
|
|
|
)
|
|
)
|
|
@@ -665,7 +743,9 @@ async def test_contract_store_is_content_addressed_and_detects_tampering(tmp_pat
|
|
|
async def test_orphan_contract_reclamation_uses_only_ledger_references(tmp_path: Path) -> None:
|
|
async def test_orphan_contract_reclamation_uses_only_ledger_references(tmp_path: Path) -> None:
|
|
|
service, _, root = await _service(tmp_path)
|
|
service, _, root = await _service(tmp_path)
|
|
|
referenced = await service.plan_script_tasks(
|
|
referenced = await service.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("candidate-portfolio", execution_ready=False)],
|
|
|
|
|
|
|
+ contract_payloads=[
|
|
|
|
|
+ _planner_input(_contract("candidate-portfolio", execution_ready=False))
|
|
|
|
|
+ ],
|
|
|
parent_task_id=None,
|
|
parent_task_id=None,
|
|
|
context={"root_trace_id": root, "tool_call_id": "portfolio"},
|
|
context={"root_trace_id": root, "tool_call_id": "portfolio"},
|
|
|
)
|
|
)
|
|
@@ -775,7 +855,9 @@ def test_comparison_refs_are_kind_specific_and_limits_cannot_be_raised() -> None
|
|
|
async def test_planning_freezes_contract_and_creates_only_one_portfolio(tmp_path: Path) -> None:
|
|
async def test_planning_freezes_contract_and_creates_only_one_portfolio(tmp_path: Path) -> None:
|
|
|
service, coordinator, root = await _service(tmp_path)
|
|
service, coordinator, root = await _service(tmp_path)
|
|
|
result = await service.plan_script_tasks(
|
|
result = await service.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("candidate-portfolio", execution_ready=False)],
|
|
|
|
|
|
|
+ contract_payloads=[
|
|
|
|
|
+ _planner_input(_contract("candidate-portfolio", execution_ready=False))
|
|
|
|
|
+ ],
|
|
|
parent_task_id=None,
|
|
parent_task_id=None,
|
|
|
context={"root_trace_id": root, "tool_call_id": "plan-portfolio"},
|
|
context={"root_trace_id": root, "tool_call_id": "plan-portfolio"},
|
|
|
)
|
|
)
|
|
@@ -790,7 +872,9 @@ async def test_planning_freezes_contract_and_creates_only_one_portfolio(tmp_path
|
|
|
|
|
|
|
|
with pytest.raises(TaskContractError, match="one Portfolio"):
|
|
with pytest.raises(TaskContractError, match="one Portfolio"):
|
|
|
await service.plan_script_tasks(
|
|
await service.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("candidate-portfolio", execution_ready=False)],
|
|
|
|
|
|
|
+ contract_payloads=[
|
|
|
|
|
+ _planner_input(_contract("candidate-portfolio", execution_ready=False))
|
|
|
|
|
+ ],
|
|
|
parent_task_id=None,
|
|
parent_task_id=None,
|
|
|
context={"root_trace_id": root, "tool_call_id": "plan-portfolio-2"},
|
|
context={"root_trace_id": root, "tool_call_id": "plan-portfolio-2"},
|
|
|
)
|
|
)
|
|
@@ -799,101 +883,151 @@ async def test_planning_freezes_contract_and_creates_only_one_portfolio(tmp_path
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.asyncio
|
|
|
async def test_protected_phase_rejects_cross_phase_root_contracts(tmp_path: Path) -> None:
|
|
async def test_protected_phase_rejects_cross_phase_root_contracts(tmp_path: Path) -> None:
|
|
|
service, coordinator, root = await _service(tmp_path)
|
|
service, coordinator, root = await _service(tmp_path)
|
|
|
|
|
+ before = await coordinator.task_store.load(root)
|
|
|
|
|
+ existing_task_ids = set(before.tasks)
|
|
|
with pytest.raises(TaskContractError, match="PHASE_POLICY_VIOLATION"):
|
|
with pytest.raises(TaskContractError, match="PHASE_POLICY_VIOLATION"):
|
|
|
await service.plan_script_tasks(
|
|
await service.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("candidate-portfolio", execution_ready=False)],
|
|
|
|
|
|
|
+ contract_payloads=[
|
|
|
|
|
+ _planner_input(_contract("candidate-portfolio", execution_ready=False))
|
|
|
|
|
+ ],
|
|
|
parent_task_id=None,
|
|
parent_task_id=None,
|
|
|
context={"root_trace_id": root, "phase": 1},
|
|
context={"root_trace_id": root, "phase": 1},
|
|
|
)
|
|
)
|
|
|
ledger = await coordinator.task_store.load(root)
|
|
ledger = await coordinator.task_store.load(root)
|
|
|
- assert list(ledger.tasks) == [ledger.root_task_id]
|
|
|
|
|
|
|
+ assert set(ledger.tasks) == existing_task_ids
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.asyncio
|
|
|
async def test_invalid_contract_batch_creates_zero_tasks(tmp_path: Path) -> None:
|
|
async def test_invalid_contract_batch_creates_zero_tasks(tmp_path: Path) -> None:
|
|
|
service, coordinator, root = await _service(tmp_path)
|
|
service, coordinator, root = await _service(tmp_path)
|
|
|
- invalid = _contract("paragraph")
|
|
|
|
|
|
|
+ before = await coordinator.task_store.load(root)
|
|
|
|
|
+ existing_task_ids = set(before.tasks)
|
|
|
|
|
+ invalid = _planner_input(_contract("paragraph"))
|
|
|
invalid["output_schema"] = "structured-script/v1"
|
|
invalid["output_schema"] = "structured-script/v1"
|
|
|
- with pytest.raises(TaskContractError, match="output_schema"):
|
|
|
|
|
|
|
+ with pytest.raises(TaskContractError, match="Host-owned fields"):
|
|
|
await service.plan_script_tasks(
|
|
await service.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("candidate-portfolio"), invalid],
|
|
|
|
|
|
|
+ contract_payloads=[
|
|
|
|
|
+ _planner_input(_contract("candidate-portfolio")),
|
|
|
|
|
+ invalid,
|
|
|
|
|
+ ],
|
|
|
parent_task_id=None,
|
|
parent_task_id=None,
|
|
|
context={"root_trace_id": root},
|
|
context={"root_trace_id": root},
|
|
|
)
|
|
)
|
|
|
ledger = await coordinator.task_store.load(root)
|
|
ledger = await coordinator.task_store.load(root)
|
|
|
- assert list(ledger.tasks) == [ledger.root_task_id]
|
|
|
|
|
|
|
+ assert set(ledger.tasks) == existing_task_ids
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.asyncio
|
|
|
async def test_planning_rejects_unresolved_supersession_before_freeze(tmp_path: Path) -> None:
|
|
async def test_planning_rejects_unresolved_supersession_before_freeze(tmp_path: Path) -> None:
|
|
|
service, coordinator, root = await _service(tmp_path)
|
|
service, coordinator, root = await _service(tmp_path)
|
|
|
portfolio = await service.plan_script_tasks(
|
|
portfolio = await service.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("candidate-portfolio", execution_ready=False)],
|
|
|
|
|
|
|
+ contract_payloads=[
|
|
|
|
|
+ _planner_input(_contract("candidate-portfolio", execution_ready=False))
|
|
|
|
|
+ ],
|
|
|
parent_task_id=None,
|
|
parent_task_id=None,
|
|
|
context={"root_trace_id": root, "tool_call_id": "portfolio"},
|
|
context={"root_trace_id": root, "tool_call_id": "portfolio"},
|
|
|
)
|
|
)
|
|
|
compose = await service.plan_script_tasks(
|
|
compose = await service.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("compose", execution_ready=False)],
|
|
|
|
|
|
|
+ contract_payloads=[_planner_input(_contract("compose", execution_ready=False))],
|
|
|
parent_task_id=portfolio["task_ids"][0],
|
|
parent_task_id=portfolio["task_ids"][0],
|
|
|
context={"root_trace_id": root, "tool_call_id": "compose"},
|
|
context={"root_trace_id": root, "tool_call_id": "compose"},
|
|
|
)
|
|
)
|
|
|
replacement = _contract("paragraph")
|
|
replacement = _contract("paragraph")
|
|
|
replacement["supersedes_decision_ids"] = ["missing-accept"]
|
|
replacement["supersedes_decision_ids"] = ["missing-accept"]
|
|
|
|
|
+ before = await coordinator.task_store.load(root)
|
|
|
|
|
+ existing_task_ids = set(before.tasks)
|
|
|
|
|
|
|
|
- with pytest.raises(TaskContractError, match="import every superseded"):
|
|
|
|
|
|
|
+ with pytest.raises(TaskContractError, match="not an ACCEPT decision"):
|
|
|
await service.plan_script_tasks(
|
|
await service.plan_script_tasks(
|
|
|
- contract_payloads=[replacement],
|
|
|
|
|
|
|
+ contract_payloads=[_planner_input(replacement)],
|
|
|
parent_task_id=compose["task_ids"][0],
|
|
parent_task_id=compose["task_ids"][0],
|
|
|
context={"root_trace_id": root, "tool_call_id": "invalid-replacement"},
|
|
context={"root_trace_id": root, "tool_call_id": "invalid-replacement"},
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
ledger = await coordinator.task_store.load(root)
|
|
ledger = await coordinator.task_store.load(root)
|
|
|
- assert set(ledger.tasks) == {
|
|
|
|
|
- ledger.root_task_id,
|
|
|
|
|
- portfolio["task_ids"][0],
|
|
|
|
|
- compose["task_ids"][0],
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ assert set(ledger.tasks) == existing_task_ids
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.asyncio
|
|
|
async def test_open_compose_container_cannot_dispatch(tmp_path: Path) -> None:
|
|
async def test_open_compose_container_cannot_dispatch(tmp_path: Path) -> None:
|
|
|
service, coordinator, root = await _service(tmp_path)
|
|
service, coordinator, root = await _service(tmp_path)
|
|
|
portfolio = await service.plan_script_tasks(
|
|
portfolio = await service.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("candidate-portfolio", execution_ready=False)],
|
|
|
|
|
|
|
+ contract_payloads=[
|
|
|
|
|
+ _planner_input(_contract("candidate-portfolio", execution_ready=False))
|
|
|
|
|
+ ],
|
|
|
parent_task_id=None,
|
|
parent_task_id=None,
|
|
|
context={"root_trace_id": root, "tool_call_id": "p"},
|
|
context={"root_trace_id": root, "tool_call_id": "p"},
|
|
|
)
|
|
)
|
|
|
compose = await service.plan_script_tasks(
|
|
compose = await service.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("compose", execution_ready=False)],
|
|
|
|
|
|
|
+ contract_payloads=[_planner_input(_contract("compose", execution_ready=False))],
|
|
|
parent_task_id=portfolio["task_ids"][0],
|
|
parent_task_id=portfolio["task_ids"][0],
|
|
|
context={"root_trace_id": root, "tool_call_id": "c"},
|
|
context={"root_trace_id": root, "tool_call_id": "c"},
|
|
|
)
|
|
)
|
|
|
|
|
+ before = await coordinator.task_store.load(root)
|
|
|
|
|
+ operation_ids = set(before.operations)
|
|
|
with pytest.raises(TaskContractError, match="first accept child candidates"):
|
|
with pytest.raises(TaskContractError, match="first accept child candidates"):
|
|
|
await service.dispatch_script_tasks(
|
|
await service.dispatch_script_tasks(
|
|
|
task_ids=compose["task_ids"], context={"root_trace_id": root}
|
|
task_ids=compose["task_ids"], context={"root_trace_id": root}
|
|
|
)
|
|
)
|
|
|
ledger = await coordinator.task_store.load(root)
|
|
ledger = await coordinator.task_store.load(root)
|
|
|
- assert not ledger.operations
|
|
|
|
|
|
|
+ assert set(ledger.operations) == operation_ids
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.asyncio
|
|
|
-async def test_compose_placeholder_split_replacement_then_revised_v2_passes(
|
|
|
|
|
|
|
+async def test_compose_placeholder_split_replacement_then_revised_contract_passes(
|
|
|
tmp_path: Path,
|
|
tmp_path: Path,
|
|
|
) -> None:
|
|
) -> None:
|
|
|
service, coordinator, root = await _service(tmp_path)
|
|
service, coordinator, root = await _service(tmp_path)
|
|
|
coordinator.set_executor(_PlaceholderThenReplacementExecutor(coordinator))
|
|
coordinator.set_executor(_PlaceholderThenReplacementExecutor(coordinator))
|
|
|
portfolio = await service.plan_script_tasks(
|
|
portfolio = await service.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("candidate-portfolio", execution_ready=False)],
|
|
|
|
|
|
|
+ contract_payloads=[
|
|
|
|
|
+ _planner_input(_contract("candidate-portfolio", execution_ready=False))
|
|
|
|
|
+ ],
|
|
|
parent_task_id=None,
|
|
parent_task_id=None,
|
|
|
context={"root_trace_id": root, "tool_call_id": "portfolio"},
|
|
context={"root_trace_id": root, "tool_call_id": "portfolio"},
|
|
|
)
|
|
)
|
|
|
compose = await service.plan_script_tasks(
|
|
compose = await service.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("compose")],
|
|
|
|
|
|
|
+ contract_payloads=[_planner_input(_contract("compose", execution_ready=False))],
|
|
|
parent_task_id=portfolio["task_ids"][0],
|
|
parent_task_id=portfolio["task_ids"][0],
|
|
|
context={"root_trace_id": root, "tool_call_id": "compose-v1"},
|
|
context={"root_trace_id": root, "tool_call_id": "compose-v1"},
|
|
|
)
|
|
)
|
|
|
compose_id = compose["task_ids"][0]
|
|
compose_id = compose["task_ids"][0]
|
|
|
|
|
|
|
|
|
|
+ accepted_children: dict[str, str] = {}
|
|
|
|
|
+ for kind in ("structure", "paragraph", "element-set"):
|
|
|
|
|
+ planned = await service.plan_script_tasks(
|
|
|
|
|
+ contract_payloads=[_planner_input(_contract(kind))],
|
|
|
|
|
+ parent_task_id=compose_id,
|
|
|
|
|
+ context={"root_trace_id": root, "tool_call_id": f"plan:{kind}"},
|
|
|
|
|
+ )
|
|
|
|
|
+ child_id = cast(str, planned["task_ids"][0])
|
|
|
|
|
+ child_cycle = (
|
|
|
|
|
+ await service.dispatch_script_tasks(
|
|
|
|
|
+ task_ids=(child_id,),
|
|
|
|
|
+ context={"root_trace_id": root, "tool_call_id": f"dispatch:{kind}"},
|
|
|
|
|
+ )
|
|
|
|
|
+ )[0]
|
|
|
|
|
+ accepted = await service.decide_script_task(
|
|
|
|
|
+ task_id=child_id,
|
|
|
|
|
+ action=DecisionAction.ACCEPT.value,
|
|
|
|
|
+ reason=f"{kind} passed",
|
|
|
|
|
+ validation_id=cast(str, child_cycle["validation_id"]),
|
|
|
|
|
+ replacement_contract=None,
|
|
|
|
|
+ child_contracts=(),
|
|
|
|
|
+ context={"root_trace_id": root, "tool_call_id": f"accept:{kind}"},
|
|
|
|
|
+ )
|
|
|
|
|
+ accepted_children[kind] = cast(str, accepted["decision_id"])
|
|
|
|
|
+ await service.decide_script_task(
|
|
|
|
|
+ task_id=compose_id,
|
|
|
|
|
+ action=DecisionAction.REVISE.value,
|
|
|
|
|
+ reason="freeze the initial complete frontier",
|
|
|
|
|
+ validation_id=None,
|
|
|
|
|
+ replacement_contract=None,
|
|
|
|
|
+ child_contracts=(),
|
|
|
|
|
+ selected_decision_ids=tuple(accepted_children.values()),
|
|
|
|
|
+ context={"root_trace_id": root, "tool_call_id": "revise-compose-v1"},
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
first_cycle = (
|
|
first_cycle = (
|
|
|
await service.dispatch_script_tasks(
|
|
await service.dispatch_script_tasks(
|
|
|
task_ids=(compose_id,),
|
|
task_ids=(compose_id,),
|
|
@@ -913,7 +1047,14 @@ async def test_compose_placeholder_split_replacement_then_revised_v2_passes(
|
|
|
reason="replace only the unrealized opening scope",
|
|
reason="replace only the unrealized opening scope",
|
|
|
validation_id=first_validation.validation_id,
|
|
validation_id=first_validation.validation_id,
|
|
|
replacement_contract=None,
|
|
replacement_contract=None,
|
|
|
- child_contracts=(_contract("paragraph"),),
|
|
|
|
|
|
|
+ child_contracts=(
|
|
|
|
|
+ _planner_input(
|
|
|
|
|
+ _contract(
|
|
|
|
|
+ "paragraph",
|
|
|
|
|
+ scope="script-build://scopes/full/replacement",
|
|
|
|
|
+ )
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
context={"root_trace_id": root, "tool_call_id": "split-placeholder"},
|
|
context={"root_trace_id": root, "tool_call_id": "split-placeholder"},
|
|
|
)
|
|
)
|
|
|
replacement_id = split["payload"]["child_task_ids"][0]
|
|
replacement_id = split["payload"]["child_task_ids"][0]
|
|
@@ -936,33 +1077,18 @@ async def test_compose_placeholder_split_replacement_then_revised_v2_passes(
|
|
|
ledger = await coordinator.task_store.load(root)
|
|
ledger = await coordinator.task_store.load(root)
|
|
|
assert ledger.tasks[compose_id].status is TaskStatus.NEEDS_REPLAN
|
|
assert ledger.tasks[compose_id].status is TaskStatus.NEEDS_REPLAN
|
|
|
replacement_decision_id = replacement_accept["decision_id"]
|
|
replacement_decision_id = replacement_accept["decision_id"]
|
|
|
- replacement_attempt = ledger.attempts[
|
|
|
|
|
- cast(str, ledger.decisions[replacement_decision_id].attempt_id)
|
|
|
|
|
- ]
|
|
|
|
|
- assert replacement_attempt.submission is not None
|
|
|
|
|
- replacement_ref = replacement_attempt.submission.artifact_refs[0]
|
|
|
|
|
- frozen_replacement = {
|
|
|
|
|
- "decision_id": replacement_decision_id,
|
|
|
|
|
- "artifact_ref": {
|
|
|
|
|
- "uri": replacement_ref.uri,
|
|
|
|
|
- "kind": replacement_ref.kind,
|
|
|
|
|
- "version": replacement_ref.version,
|
|
|
|
|
- "digest": replacement_ref.digest,
|
|
|
|
|
- },
|
|
|
|
|
- "scope_ref": "script-build://scopes/full",
|
|
|
|
|
- "expected_task_kind": "paragraph",
|
|
|
|
|
- }
|
|
|
|
|
- compose_v2 = _contract("compose", execution_ready=False)
|
|
|
|
|
- compose_v2["candidate_closure_decision_refs"] = [frozen_replacement]
|
|
|
|
|
- compose_v2["adopted_decision_ids"] = [replacement_decision_id]
|
|
|
|
|
- compose_v2["compose_order"] = [replacement_decision_id]
|
|
|
|
|
await service.decide_script_task(
|
|
await service.decide_script_task(
|
|
|
task_id=compose_id,
|
|
task_id=compose_id,
|
|
|
action=DecisionAction.REVISE.value,
|
|
action=DecisionAction.REVISE.value,
|
|
|
reason="pin the accepted replacement and retry the complete render",
|
|
reason="pin the accepted replacement and retry the complete render",
|
|
|
validation_id=None,
|
|
validation_id=None,
|
|
|
- replacement_contract=compose_v2,
|
|
|
|
|
|
|
+ replacement_contract=None,
|
|
|
child_contracts=(),
|
|
child_contracts=(),
|
|
|
|
|
+ selected_decision_ids=(
|
|
|
|
|
+ accepted_children["structure"],
|
|
|
|
|
+ replacement_decision_id,
|
|
|
|
|
+ accepted_children["element-set"],
|
|
|
|
|
+ ),
|
|
|
context={"root_trace_id": root, "tool_call_id": "revise-compose-v2"},
|
|
context={"root_trace_id": root, "tool_call_id": "revise-compose-v2"},
|
|
|
)
|
|
)
|
|
|
second_cycle = (
|
|
second_cycle = (
|
|
@@ -984,12 +1110,13 @@ async def test_compose_placeholder_split_replacement_then_revised_v2_passes(
|
|
|
reloaded = await FileSystemTaskStore(str(tmp_path / "ledger")).load(root)
|
|
reloaded = await FileSystemTaskStore(str(tmp_path / "ledger")).load(root)
|
|
|
compose_task = reloaded.tasks[compose_id]
|
|
compose_task = reloaded.tasks[compose_id]
|
|
|
assert compose_task.status is TaskStatus.COMPLETED
|
|
assert compose_task.status is TaskStatus.COMPLETED
|
|
|
- assert compose_task.current_spec_version == 2
|
|
|
|
|
|
|
+ assert compose_task.current_spec_version == 3
|
|
|
assert len(compose_task.attempt_ids) == 2
|
|
assert len(compose_task.attempt_ids) == 2
|
|
|
assert [
|
|
assert [
|
|
|
reloaded.validations[validation_id].verdict for validation_id in compose_task.validation_ids
|
|
reloaded.validations[validation_id].verdict for validation_id in compose_task.validation_ids
|
|
|
] == [ValidationVerdict.FAILED, ValidationVerdict.PASSED]
|
|
] == [ValidationVerdict.FAILED, ValidationVerdict.PASSED]
|
|
|
assert [reloaded.decisions[item].action for item in compose_task.decision_ids] == [
|
|
assert [reloaded.decisions[item].action for item in compose_task.decision_ids] == [
|
|
|
|
|
+ DecisionAction.REVISE,
|
|
|
DecisionAction.SPLIT,
|
|
DecisionAction.SPLIT,
|
|
|
DecisionAction.REVISE,
|
|
DecisionAction.REVISE,
|
|
|
DecisionAction.ACCEPT,
|
|
DecisionAction.ACCEPT,
|
|
@@ -1014,12 +1141,14 @@ async def test_exploration_order_and_completion_order_do_not_choose_compose_orde
|
|
|
service, coordinator, root = await _service(tmp_path)
|
|
service, coordinator, root = await _service(tmp_path)
|
|
|
coordinator.set_executor(_PlaceholderThenReplacementExecutor(coordinator))
|
|
coordinator.set_executor(_PlaceholderThenReplacementExecutor(coordinator))
|
|
|
portfolio = await service.plan_script_tasks(
|
|
portfolio = await service.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("candidate-portfolio", execution_ready=False)],
|
|
|
|
|
|
|
+ contract_payloads=[
|
|
|
|
|
+ _planner_input(_contract("candidate-portfolio", execution_ready=False))
|
|
|
|
|
+ ],
|
|
|
parent_task_id=None,
|
|
parent_task_id=None,
|
|
|
context={"root_trace_id": root, "tool_call_id": "portfolio"},
|
|
context={"root_trace_id": root, "tool_call_id": "portfolio"},
|
|
|
)
|
|
)
|
|
|
compose = await service.plan_script_tasks(
|
|
compose = await service.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("compose", execution_ready=False)],
|
|
|
|
|
|
|
+ contract_payloads=[_planner_input(_contract("compose", execution_ready=False))],
|
|
|
parent_task_id=portfolio["task_ids"][0],
|
|
parent_task_id=portfolio["task_ids"][0],
|
|
|
context={"root_trace_id": root, "tool_call_id": "compose"},
|
|
context={"root_trace_id": root, "tool_call_id": "compose"},
|
|
|
)
|
|
)
|
|
@@ -1027,14 +1156,13 @@ async def test_exploration_order_and_completion_order_do_not_choose_compose_orde
|
|
|
task_by_kind: dict[str, str] = {}
|
|
task_by_kind: dict[str, str] = {}
|
|
|
for kind in creation_order:
|
|
for kind in creation_order:
|
|
|
planned = await service.plan_script_tasks(
|
|
planned = await service.plan_script_tasks(
|
|
|
- contract_payloads=[_contract(kind)],
|
|
|
|
|
|
|
+ contract_payloads=[_planner_input(_contract(kind))],
|
|
|
parent_task_id=compose_id,
|
|
parent_task_id=compose_id,
|
|
|
context={"root_trace_id": root, "tool_call_id": f"plan:{kind}"},
|
|
context={"root_trace_id": root, "tool_call_id": f"plan:{kind}"},
|
|
|
)
|
|
)
|
|
|
task_by_kind[kind] = planned["task_ids"][0]
|
|
task_by_kind[kind] = planned["task_ids"][0]
|
|
|
|
|
|
|
|
decision_by_kind: dict[str, str] = {}
|
|
decision_by_kind: dict[str, str] = {}
|
|
|
- ref_by_kind: dict[str, ArtifactRef] = {}
|
|
|
|
|
completion_order = tuple(reversed(creation_order))
|
|
completion_order = tuple(reversed(creation_order))
|
|
|
for kind in completion_order:
|
|
for kind in completion_order:
|
|
|
task_id = task_by_kind[kind]
|
|
task_id = task_by_kind[kind]
|
|
@@ -1055,47 +1183,25 @@ async def test_exploration_order_and_completion_order_do_not_choose_compose_orde
|
|
|
)
|
|
)
|
|
|
decision_id = accepted["decision_id"]
|
|
decision_id = accepted["decision_id"]
|
|
|
decision_by_kind[kind] = decision_id
|
|
decision_by_kind[kind] = decision_id
|
|
|
- ledger = await coordinator.task_store.load(root)
|
|
|
|
|
- attempt = ledger.attempts[cast(str, ledger.decisions[decision_id].attempt_id)]
|
|
|
|
|
- assert attempt.submission is not None
|
|
|
|
|
- ref_by_kind[kind] = attempt.submission.artifact_refs[0]
|
|
|
|
|
|
|
|
|
|
ledger = await coordinator.task_store.load(root)
|
|
ledger = await coordinator.task_store.load(root)
|
|
|
assert ledger.tasks[compose_id].status is TaskStatus.NEEDS_REPLAN
|
|
assert ledger.tasks[compose_id].status is TaskStatus.NEEDS_REPLAN
|
|
|
adoption_kinds = ("paragraph", "structure", "element-set")
|
|
adoption_kinds = ("paragraph", "structure", "element-set")
|
|
|
- closure = []
|
|
|
|
|
- for kind in adoption_kinds:
|
|
|
|
|
- ref = ref_by_kind[kind]
|
|
|
|
|
- closure.append(
|
|
|
|
|
- {
|
|
|
|
|
- "decision_id": decision_by_kind[kind],
|
|
|
|
|
- "artifact_ref": {
|
|
|
|
|
- "uri": ref.uri,
|
|
|
|
|
- "kind": ref.kind,
|
|
|
|
|
- "version": ref.version,
|
|
|
|
|
- "digest": ref.digest,
|
|
|
|
|
- },
|
|
|
|
|
- "scope_ref": "script-build://scopes/full",
|
|
|
|
|
- "expected_task_kind": kind,
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
- execution_ready = _contract("compose", execution_ready=False)
|
|
|
|
|
- execution_ready["candidate_closure_decision_refs"] = closure
|
|
|
|
|
- execution_ready["adopted_decision_ids"] = [decision_by_kind[kind] for kind in adoption_kinds]
|
|
|
|
|
- execution_ready["compose_order"] = [decision_by_kind[kind] for kind in adoption_kinds]
|
|
|
|
|
|
|
+ selected = tuple(decision_by_kind[kind] for kind in adoption_kinds)
|
|
|
await service.decide_script_task(
|
|
await service.decide_script_task(
|
|
|
task_id=compose_id,
|
|
task_id=compose_id,
|
|
|
action=DecisionAction.REVISE.value,
|
|
action=DecisionAction.REVISE.value,
|
|
|
reason="formal adoption is based on scope and intent, not timing",
|
|
reason="formal adoption is based on scope and intent, not timing",
|
|
|
validation_id=None,
|
|
validation_id=None,
|
|
|
- replacement_contract=execution_ready,
|
|
|
|
|
|
|
+ replacement_contract=None,
|
|
|
child_contracts=(),
|
|
child_contracts=(),
|
|
|
|
|
+ selected_decision_ids=selected,
|
|
|
context={"root_trace_id": root, "tool_call_id": "close-compose"},
|
|
context={"root_trace_id": root, "tool_call_id": "close-compose"},
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
reloaded = await FileSystemTaskStore(str(tmp_path / "ledger")).load(root)
|
|
reloaded = await FileSystemTaskStore(str(tmp_path / "ledger")).load(root)
|
|
|
frozen = await service.contract_for_task(root, reloaded.tasks[compose_id])
|
|
frozen = await service.contract_for_task(root, reloaded.tasks[compose_id])
|
|
|
- assert frozen.contract.compose_order == tuple(decision_by_kind[kind] for kind in adoption_kinds)
|
|
|
|
|
|
|
+ assert frozen.contract.compose_order == selected
|
|
|
assert [
|
|
assert [
|
|
|
next(kind for kind, task_id in task_by_kind.items() if task_id == child_id)
|
|
next(kind for kind, task_id in task_by_kind.items() if task_id == child_id)
|
|
|
for child_id in reloaded.tasks[compose_id].child_task_ids
|
|
for child_id in reloaded.tasks[compose_id].child_task_ids
|
|
@@ -1108,18 +1214,20 @@ async def test_phase_two_retrieval_counts_toward_task_and_depth_limits(tmp_path:
|
|
|
service, _, root = await _service(tmp_path)
|
|
service, _, root = await _service(tmp_path)
|
|
|
service.limits = PhaseTwoLimits(max_tasks=2)
|
|
service.limits = PhaseTwoLimits(max_tasks=2)
|
|
|
portfolio = await service.plan_script_tasks(
|
|
portfolio = await service.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("candidate-portfolio", execution_ready=False)],
|
|
|
|
|
|
|
+ contract_payloads=[
|
|
|
|
|
+ _planner_input(_contract("candidate-portfolio", execution_ready=False))
|
|
|
|
|
+ ],
|
|
|
parent_task_id=None,
|
|
parent_task_id=None,
|
|
|
context={"root_trace_id": root, "tool_call_id": "portfolio"},
|
|
context={"root_trace_id": root, "tool_call_id": "portfolio"},
|
|
|
)
|
|
)
|
|
|
compose = await service.plan_script_tasks(
|
|
compose = await service.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("compose", execution_ready=False)],
|
|
|
|
|
|
|
+ contract_payloads=[_planner_input(_contract("compose", execution_ready=False))],
|
|
|
parent_task_id=portfolio["task_ids"][0],
|
|
parent_task_id=portfolio["task_ids"][0],
|
|
|
context={"root_trace_id": root, "tool_call_id": "compose"},
|
|
context={"root_trace_id": root, "tool_call_id": "compose"},
|
|
|
)
|
|
)
|
|
|
with pytest.raises(TaskContractError, match="Task budget"):
|
|
with pytest.raises(TaskContractError, match="Task budget"):
|
|
|
await service.plan_script_tasks(
|
|
await service.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("decode-retrieval")],
|
|
|
|
|
|
|
+ contract_payloads=[_planner_input(_contract("decode-retrieval"))],
|
|
|
parent_task_id=compose["task_ids"][0],
|
|
parent_task_id=compose["task_ids"][0],
|
|
|
context={"root_trace_id": root, "tool_call_id": "retrieval"},
|
|
context={"root_trace_id": root, "tool_call_id": "retrieval"},
|
|
|
)
|
|
)
|
|
@@ -1127,7 +1235,11 @@ async def test_phase_two_retrieval_counts_toward_task_and_depth_limits(tmp_path:
|
|
|
service.limits = PhaseTwoLimits(max_depth=1)
|
|
service.limits = PhaseTwoLimits(max_depth=1)
|
|
|
with pytest.raises(TaskContractError, match="depth"):
|
|
with pytest.raises(TaskContractError, match="depth"):
|
|
|
await service.plan_script_tasks(
|
|
await service.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("structure", scope="script-build://scopes/full/local")],
|
|
|
|
|
|
|
+ contract_payloads=[
|
|
|
|
|
+ _planner_input(
|
|
|
|
|
+ _contract("structure", scope="script-build://scopes/full/local")
|
|
|
|
|
+ )
|
|
|
|
|
+ ],
|
|
|
parent_task_id=compose["task_ids"][0],
|
|
parent_task_id=compose["task_ids"][0],
|
|
|
context={"root_trace_id": root, "tool_call_id": "too-deep"},
|
|
context={"root_trace_id": root, "tool_call_id": "too-deep"},
|
|
|
)
|
|
)
|
|
@@ -1137,7 +1249,9 @@ async def test_phase_two_retrieval_counts_toward_task_and_depth_limits(tmp_path:
|
|
|
async def test_task_contract_cannot_be_silently_changed_in_task_spec(tmp_path: Path) -> None:
|
|
async def test_task_contract_cannot_be_silently_changed_in_task_spec(tmp_path: Path) -> None:
|
|
|
service, coordinator, root = await _service(tmp_path)
|
|
service, coordinator, root = await _service(tmp_path)
|
|
|
result = await service.plan_script_tasks(
|
|
result = await service.plan_script_tasks(
|
|
|
- contract_payloads=[_contract("candidate-portfolio", execution_ready=False)],
|
|
|
|
|
|
|
+ contract_payloads=[
|
|
|
|
|
+ _planner_input(_contract("candidate-portfolio", execution_ready=False))
|
|
|
|
|
+ ],
|
|
|
parent_task_id=None,
|
|
parent_task_id=None,
|
|
|
context={"root_trace_id": root},
|
|
context={"root_trace_id": root},
|
|
|
)
|
|
)
|