|
@@ -132,6 +132,39 @@ class PhaseThreeContinuationService:
|
|
|
"status"
|
|
"status"
|
|
|
) in {TaskStatus.NEEDS_REPLAN.value, TaskStatus.PENDING.value}:
|
|
) in {TaskStatus.NEEDS_REPLAN.value, TaskStatus.PENDING.value}:
|
|
|
reentry = await self._phase_three_reentry_ready(binding.root_trace_id)
|
|
reentry = await self._phase_three_reentry_ready(binding.root_trace_id)
|
|
|
|
|
+ at_phase_two_boundary = (
|
|
|
|
|
+ completion.get("status") == TaskStatus.BLOCKED.value
|
|
|
|
|
+ and completion.get("blocked_reason")
|
|
|
|
|
+ == PHASE_TWO_CANDIDATE_PORTFOLIO_READY
|
|
|
|
|
+ )
|
|
|
|
|
+ if status is BuildStatus.RUNNING and at_phase_two_boundary:
|
|
|
|
|
+ if self.phase_two_boundary_verifier is None:
|
|
|
|
|
+ raise PhaseThreeBoundaryNotReady(
|
|
|
|
|
+ "phase-two closure verifier is unavailable"
|
|
|
|
|
+ )
|
|
|
|
|
+ await self.phase_two_boundary_verifier.verify_checkpoint(
|
|
|
|
|
+ script_build_id=script_build_id,
|
|
|
|
|
+ root_trace_id=binding.root_trace_id,
|
|
|
|
|
+ input_snapshot_id=str(binding.input_snapshot_id),
|
|
|
|
|
+ )
|
|
|
|
|
+ ledger = await self.coordinator.task_store.load(binding.root_trace_id)
|
|
|
|
|
+ if any(
|
|
|
|
|
+ item.status
|
|
|
|
|
+ in {
|
|
|
|
|
+ OperationStatus.PENDING,
|
|
|
|
|
+ OperationStatus.RUNNING,
|
|
|
|
|
+ OperationStatus.STOP_REQUESTED,
|
|
|
|
|
+ }
|
|
|
|
|
+ for item in ledger.operations.values()
|
|
|
|
|
+ ):
|
|
|
|
|
+ raise PhaseThreeBoundaryNotReady("phase two still has active operations")
|
|
|
|
|
+ lease = self.owner_lease_factory(script_build_id)
|
|
|
|
|
+ token = await lease.acquire(script_build_id)
|
|
|
|
|
+ try:
|
|
|
|
|
+ await self._set_phase_two_checkpoint(token)
|
|
|
|
|
+ finally:
|
|
|
|
|
+ await lease.release()
|
|
|
|
|
+ status = BuildStatus.PARTIAL
|
|
|
if status is not BuildStatus.PARTIAL and not reentry:
|
|
if status is not BuildStatus.PARTIAL and not reentry:
|
|
|
raise PhaseThreeBoundaryNotReady("phase-three advance requires a partial build")
|
|
raise PhaseThreeBoundaryNotReady("phase-three advance requires a partial build")
|
|
|
if not reentry and (
|
|
if not reentry and (
|
|
@@ -228,7 +261,7 @@ class PhaseThreeContinuationService:
|
|
|
]
|
|
]
|
|
|
if active_operations:
|
|
if active_operations:
|
|
|
raise PhaseThreeBoundaryNotReady("phase two still has active operations")
|
|
raise PhaseThreeBoundaryNotReady("phase two still has active operations")
|
|
|
- closure = await self._accepted_root_closure(script_build_id, ledger)
|
|
|
|
|
|
|
+ closure = await self._accepted_root_closure(script_build_id, binding.root_trace_id, ledger)
|
|
|
await self._verify_owner(owner_token)
|
|
await self._verify_owner(owner_token)
|
|
|
snapshot, binding = await self._ensure_snapshot(snapshot, binding, owner_token)
|
|
snapshot, binding = await self._ensure_snapshot(snapshot, binding, owner_token)
|
|
|
root_contract = await self._execute_fenced(
|
|
root_contract = await self._execute_fenced(
|
|
@@ -378,8 +411,25 @@ class PhaseThreeContinuationService:
|
|
|
root_trace_id=owner_token.root_trace_id,
|
|
root_trace_id=owner_token.root_trace_id,
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
+ async def _set_phase_two_checkpoint(self, owner_token: MissionOwnerToken) -> None:
|
|
|
|
|
+ summary = "Phase two accepted one closed candidate portfolio."
|
|
|
|
|
+ if self.fenced_command_gate is not None:
|
|
|
|
|
+ await self.fenced_command_gate.set_checkpoint(
|
|
|
|
|
+ owner_token,
|
|
|
|
|
+ checkpoint_code=PHASE_TWO_CANDIDATE_PORTFOLIO_READY,
|
|
|
|
|
+ summary=summary,
|
|
|
|
|
+ )
|
|
|
|
|
+ return
|
|
|
|
|
+ await self._verify_owner(owner_token)
|
|
|
|
|
+ await self.legacy_state.set_checkpoint(
|
|
|
|
|
+ owner_token.script_build_id,
|
|
|
|
|
+ checkpoint_code=PHASE_TWO_CANDIDATE_PORTFOLIO_READY,
|
|
|
|
|
+ summary=summary,
|
|
|
|
|
+ root_trace_id=owner_token.root_trace_id,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
async def _accepted_root_closure(
|
|
async def _accepted_root_closure(
|
|
|
- self, script_build_id: int, ledger: Any
|
|
|
|
|
|
|
+ self, script_build_id: int, root_trace_id: str, ledger: Any
|
|
|
) -> _AcceptedRootClosure:
|
|
) -> _AcceptedRootClosure:
|
|
|
root = ledger.tasks[ledger.root_task_id]
|
|
root = ledger.tasks[ledger.root_task_id]
|
|
|
if len(root.child_task_ids) != 2:
|
|
if len(root.child_task_ids) != 2:
|
|
@@ -435,10 +485,18 @@ class PhaseThreeContinuationService:
|
|
|
version.artifact, ScriptDirectionArtifactV1
|
|
version.artifact, ScriptDirectionArtifactV1
|
|
|
):
|
|
):
|
|
|
raise PhaseThreeBoundaryNotReady("accepted Direction has the wrong Artifact type")
|
|
raise PhaseThreeBoundaryNotReady("accepted Direction has the wrong Artifact type")
|
|
|
|
|
+ contract_refs = [
|
|
|
|
|
+ item
|
|
|
|
|
+ for item in child.current_spec.context_refs
|
|
|
|
|
+ if item.startswith("script-build://task-contracts/sha256/")
|
|
|
|
|
+ ]
|
|
|
|
|
+ if len(contract_refs) != 1:
|
|
|
|
|
+ raise PhaseThreeBoundaryNotReady("Root child does not pin one Task contract")
|
|
|
|
|
+ child_contract = await self.contracts.read(root_trace_id, contract_refs[0])
|
|
|
accepted[kind] = AcceptedDecisionRef(
|
|
accepted[kind] = AcceptedDecisionRef(
|
|
|
decision_id=decisions[0].decision_id,
|
|
decision_id=decisions[0].decision_id,
|
|
|
artifact_ref=artifact_ref,
|
|
artifact_ref=artifact_ref,
|
|
|
- scope_ref="script-build://scope/root",
|
|
|
|
|
|
|
+ scope_ref=child_contract.contract.scope_ref,
|
|
|
expected_task_kind=ScriptTaskKind(kind),
|
|
expected_task_kind=ScriptTaskKind(kind),
|
|
|
)
|
|
)
|
|
|
if set(accepted) != {
|
|
if set(accepted) != {
|
|
@@ -467,7 +525,7 @@ class PhaseThreeContinuationService:
|
|
|
) -> Any:
|
|
) -> Any:
|
|
|
contract = ScriptTaskContractV1(
|
|
contract = ScriptTaskContractV1(
|
|
|
task_kind=ScriptTaskKind.ROOT_DELIVERY,
|
|
task_kind=ScriptTaskKind.ROOT_DELIVERY,
|
|
|
- scope_ref="script-build://scope/root",
|
|
|
|
|
|
|
+ scope_ref=closure.direction.scope_ref,
|
|
|
intent_class=ScriptIntentClass.DELIVER,
|
|
intent_class=ScriptIntentClass.DELIVER,
|
|
|
objective=root.current_spec.objective,
|
|
objective=root.current_spec.objective,
|
|
|
input_decision_refs=(closure.direction, closure.portfolio),
|
|
input_decision_refs=(closure.direction, closure.portfolio),
|