973 lines
32 KiB
Python
973 lines
32 KiB
Python
"""AIOS-native binding layer for the unattended resume controller.
|
|
|
|
This module intentionally contains no HTTP client, Unix-socket client, database,
|
|
systemd integration, scheduler loop, secret handling, or privileged identity
|
|
mutation.
|
|
|
|
It converts existing AIOS authorities into the generic callbacks defined by
|
|
``autonomous_resume_integration``. Field code supplies the current authoritative
|
|
implementations for:
|
|
|
|
- durable Owner authorization/grant projection;
|
|
- canonical governed-worker activation/status;
|
|
- governed task admission;
|
|
- task/claim/TaskLease/TaskPrincipal/WriterLease/CAS ownership;
|
|
- bounded source-lane execution and integration;
|
|
- Gate0;
|
|
- ReleaseReady/deployability;
|
|
- PromotionController;
|
|
- final production acceptance;
|
|
- mission resume lease/checkpoint/effect reconciliation.
|
|
|
|
The adapter is deterministic and fail-closed. It never fabricates any credential,
|
|
principal, lease, fence, task, commit, receipt, approval, or deployment result.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from enum import Enum
|
|
import hashlib
|
|
import json
|
|
from typing import Mapping, Protocol, Sequence
|
|
|
|
from .autonomous_resume_controller import (
|
|
EffectState,
|
|
MissionCheckpoint,
|
|
MissionObservation,
|
|
MissionPhase,
|
|
ResumeLease,
|
|
StepResult,
|
|
StepStatus,
|
|
)
|
|
from .autonomous_resume_integration import (
|
|
AuthorityCallbacks,
|
|
CallableAutonomousExecutionPort,
|
|
PhaseCallbacks,
|
|
)
|
|
|
|
|
|
def _canonical(value) -> bytes:
|
|
return json.dumps(
|
|
value,
|
|
sort_keys=True,
|
|
separators=(",", ":"),
|
|
ensure_ascii=False,
|
|
allow_nan=False,
|
|
).encode("utf-8")
|
|
|
|
|
|
def _digest(value) -> str:
|
|
return hashlib.sha256(_canonical(value)).hexdigest()
|
|
|
|
|
|
class WorkerState(str, Enum):
|
|
ACTIVE = "ACTIVE"
|
|
STARTING = "STARTING"
|
|
INACTIVE = "INACTIVE"
|
|
BLOCKED = "BLOCKED"
|
|
UNKNOWN = "UNKNOWN"
|
|
|
|
|
|
class WorkState(str, Enum):
|
|
PENDING = "PENDING"
|
|
RUNNING = "RUNNING"
|
|
COMPLETE = "COMPLETE"
|
|
BLOCKED = "BLOCKED"
|
|
UNKNOWN = "UNKNOWN"
|
|
|
|
|
|
class GateState(str, Enum):
|
|
PASS = "PASS"
|
|
HOLD = "HOLD"
|
|
UNKNOWN = "UNKNOWN"
|
|
|
|
|
|
class ReadyState(str, Enum):
|
|
READY = "READY"
|
|
NOT_READY = "NOT_READY"
|
|
UNKNOWN = "UNKNOWN"
|
|
|
|
|
|
class PromotionState(str, Enum):
|
|
IN_PROGRESS = "IN_PROGRESS"
|
|
VERIFIED = "VERIFIED"
|
|
ROLLED_BACK = "ROLLED_BACK"
|
|
BLOCKED = "BLOCKED"
|
|
UNKNOWN = "UNKNOWN"
|
|
|
|
|
|
class AcceptanceState(str, Enum):
|
|
PASS = "PASS"
|
|
FAIL_ROLLED_BACK = "FAIL_ROLLED_BACK"
|
|
BLOCKED = "BLOCKED"
|
|
UNKNOWN = "UNKNOWN"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DurableGrantView:
|
|
grant_id: str
|
|
active: bool
|
|
repository: str
|
|
allowed_target_prefixes: tuple[str, ...]
|
|
max_task_risk: str
|
|
expires_at: float | None = None
|
|
owner_action_required: bool = False
|
|
evidence_refs: tuple[str, ...] = ()
|
|
|
|
def permits(self, *, repository: str, branch: str, risk: str) -> bool:
|
|
if not self.active:
|
|
return False
|
|
if self.repository != repository:
|
|
return False
|
|
if not any(branch.startswith(prefix) for prefix in self.allowed_target_prefixes):
|
|
return False
|
|
rank = {"low": 0, "medium": 1, "engineering": 2, "high": 3}
|
|
if risk not in rank or self.max_task_risk not in rank:
|
|
return False
|
|
return rank[risk] <= rank[self.max_task_risk]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TaskLaneSpec:
|
|
lane: str
|
|
task_id: str
|
|
repository: str
|
|
target_branch: str
|
|
risk: str
|
|
description: str
|
|
write_set: tuple[str, ...]
|
|
|
|
def __post_init__(self) -> None:
|
|
if not self.lane or not self.task_id or not self.repository:
|
|
raise ValueError("lane_task_repository_required")
|
|
if not self.target_branch.startswith("agent/"):
|
|
raise ValueError("target_branch_must_use_agent_prefix")
|
|
if not self.write_set:
|
|
raise ValueError("write_set_required")
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TaskAdmissionRequest:
|
|
task_id: str
|
|
idempotency_key: str
|
|
repository: str
|
|
base_oid: str
|
|
target_branch: str
|
|
expected_branch_oid: str
|
|
risk: str
|
|
description: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TaskAdmissionReceipt:
|
|
task_id: str
|
|
generation: int
|
|
target_branch: str
|
|
persisted: bool
|
|
claimable: bool
|
|
receipt_ref: str
|
|
|
|
def __post_init__(self) -> None:
|
|
if self.generation < 1:
|
|
raise ValueError("task_generation_must_be_positive")
|
|
if not self.receipt_ref:
|
|
raise ValueError("task_admission_receipt_required")
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class WorkerActivationReceipt:
|
|
state: WorkerState
|
|
receipt_ref: str | None = None
|
|
retry_after_seconds: int | None = None
|
|
owner_action_required: bool = False
|
|
code: str = "WORKER_STATE"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class LaneExecutionView:
|
|
lane: str
|
|
state: WorkState
|
|
condition_fingerprint: str
|
|
receipt_refs: tuple[str, ...] = ()
|
|
retry_after_seconds: int | None = None
|
|
code: str = "LANE_STATE"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class IntegrationView:
|
|
state: WorkState
|
|
condition_fingerprint: str
|
|
receipt_refs: tuple[str, ...] = ()
|
|
retry_after_seconds: int | None = None
|
|
code: str = "INTEGRATION_STATE"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class GateView:
|
|
state: GateState
|
|
condition_fingerprint: str
|
|
receipt_refs: tuple[str, ...] = ()
|
|
retry_after_seconds: int | None = None
|
|
code: str = "GATE_STATE"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ReleaseReadyView:
|
|
state: ReadyState
|
|
condition_fingerprint: str
|
|
material_digest: str | None
|
|
receipt_refs: tuple[str, ...] = ()
|
|
retry_after_seconds: int | None = None
|
|
code: str = "READY_STATE"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PromotionView:
|
|
state: PromotionState
|
|
condition_fingerprint: str
|
|
material_digest: str | None
|
|
receipt_refs: tuple[str, ...] = ()
|
|
retry_after_seconds: int | None = None
|
|
code: str = "PROMOTION_STATE"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class AcceptanceView:
|
|
state: AcceptanceState
|
|
condition_fingerprint: str
|
|
receipt_refs: tuple[str, ...] = ()
|
|
code: str = "ACCEPTANCE_STATE"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class MissionEffectView:
|
|
effect_state: EffectState
|
|
condition_fingerprint: str
|
|
requires_reconcile: bool
|
|
receipt_refs: tuple[str, ...] = ()
|
|
|
|
|
|
class AiosMissionAuthorities(Protocol):
|
|
"""Existing AIOS authority surface consumed by this adapter."""
|
|
|
|
# Live truth.
|
|
def read_head_oid(self, repository: str) -> str: ...
|
|
def read_material_digest(self, mission_id: str) -> str: ...
|
|
def read_generation(self, mission_id: str) -> int: ...
|
|
def read_effect_view(self, mission_id: str) -> MissionEffectView: ...
|
|
def owner_action_resolved(self, mission_id: str) -> bool: ...
|
|
|
|
# Durable mission state / one-winner resume authority.
|
|
def load_checkpoint(self, mission_id: str) -> MissionCheckpoint | None: ...
|
|
def acquire_resume_lease(
|
|
self,
|
|
mission_id: str,
|
|
generation: int,
|
|
idempotency_key: str,
|
|
lease_seconds: int,
|
|
) -> ResumeLease | None: ...
|
|
def persist_checkpoint(
|
|
self,
|
|
checkpoint: MissionCheckpoint,
|
|
lease: ResumeLease,
|
|
) -> None: ...
|
|
def persist_owner_action_required(
|
|
self,
|
|
payload: Mapping[str, object],
|
|
observation: MissionObservation,
|
|
checkpoint: MissionCheckpoint,
|
|
lease: ResumeLease,
|
|
) -> str: ...
|
|
def release_resume_lease(
|
|
self,
|
|
lease: ResumeLease,
|
|
outcome_code: str,
|
|
) -> None: ...
|
|
def reconcile_effects(
|
|
self,
|
|
observation: MissionObservation,
|
|
checkpoint: MissionCheckpoint,
|
|
lease: ResumeLease,
|
|
) -> StepResult: ...
|
|
|
|
# Admission.
|
|
def read_durable_grant(self, mission_id: str) -> DurableGrantView: ...
|
|
def ensure_canonical_worker(
|
|
self,
|
|
mission_id: str,
|
|
generation: int,
|
|
idempotency_key: str,
|
|
) -> WorkerActivationReceipt: ...
|
|
def create_or_resume_task(
|
|
self,
|
|
request: TaskAdmissionRequest,
|
|
) -> TaskAdmissionReceipt: ...
|
|
def read_task_admission(
|
|
self,
|
|
lane: TaskLaneSpec,
|
|
) -> TaskAdmissionReceipt | None: ...
|
|
|
|
# Phase execution.
|
|
def read_lane_execution(
|
|
self,
|
|
lane: TaskLaneSpec,
|
|
task: TaskAdmissionReceipt,
|
|
) -> LaneExecutionView: ...
|
|
def drive_integration(
|
|
self,
|
|
lanes: Sequence[TaskLaneSpec],
|
|
tasks: Sequence[TaskAdmissionReceipt],
|
|
) -> IntegrationView: ...
|
|
def run_gate0(self, mission_id: str) -> GateView: ...
|
|
def build_release_ready(self, mission_id: str) -> ReleaseReadyView: ...
|
|
def run_promotion(self, mission_id: str) -> PromotionView: ...
|
|
def run_final_acceptance(self, mission_id: str) -> AcceptanceView: ...
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class AiosUnattendedMissionConfig:
|
|
mission_id: str
|
|
repository: str
|
|
lanes: tuple[TaskLaneSpec, ...]
|
|
admission_risk: str = "medium"
|
|
|
|
def __post_init__(self) -> None:
|
|
if not self.mission_id or not self.repository:
|
|
raise ValueError("mission_and_repository_required")
|
|
if not self.lanes:
|
|
raise ValueError("at_least_one_lane_required")
|
|
names = [lane.lane for lane in self.lanes]
|
|
task_ids = [lane.task_id for lane in self.lanes]
|
|
branches = [lane.target_branch for lane in self.lanes]
|
|
if len(names) != len(set(names)):
|
|
raise ValueError("duplicate_lane")
|
|
if len(task_ids) != len(set(task_ids)):
|
|
raise ValueError("duplicate_task_id")
|
|
if len(branches) != len(set(branches)):
|
|
raise ValueError("duplicate_target_branch")
|
|
owners: dict[str, str] = {}
|
|
for lane in self.lanes:
|
|
if lane.repository != self.repository:
|
|
raise ValueError("lane_repository_mismatch")
|
|
for resource in lane.write_set:
|
|
if resource in owners:
|
|
raise ValueError(
|
|
f"overlapping_lane_resource:{resource}:"
|
|
f"{owners[resource]}:{lane.lane}"
|
|
)
|
|
owners[resource] = lane.lane
|
|
|
|
|
|
class AiosUnattendedCallbackFactory:
|
|
"""Construct generic controller callbacks from current AIOS authorities."""
|
|
|
|
def __init__(
|
|
self,
|
|
authority: AiosMissionAuthorities,
|
|
config: AiosUnattendedMissionConfig,
|
|
) -> None:
|
|
self.authority = authority
|
|
self.config = config
|
|
self._admissions: dict[str, TaskAdmissionReceipt] = {}
|
|
|
|
def build_port(self) -> CallableAutonomousExecutionPort:
|
|
return CallableAutonomousExecutionPort(
|
|
AuthorityCallbacks(
|
|
read_observation=self.read_observation,
|
|
load_checkpoint=self.authority.load_checkpoint,
|
|
acquire_resume_lease=self.authority.acquire_resume_lease,
|
|
persist_checkpoint=self.authority.persist_checkpoint,
|
|
persist_owner_action_required=self.authority.persist_owner_action_required,
|
|
release_resume_lease=self.authority.release_resume_lease,
|
|
reconcile=self.authority.reconcile_effects,
|
|
),
|
|
PhaseCallbacks(
|
|
admission=self.admission,
|
|
source_execution=self.source_execution,
|
|
gate0=self.gate0,
|
|
release_ready=self.release_ready,
|
|
promotion=self.promotion,
|
|
final_acceptance=self.final_acceptance,
|
|
),
|
|
)
|
|
|
|
def read_observation(self, mission_id: str) -> MissionObservation:
|
|
if mission_id != self.config.mission_id:
|
|
raise ValueError("mission_id_mismatch")
|
|
generation = self.authority.read_generation(mission_id)
|
|
head_oid = self.authority.read_head_oid(self.config.repository)
|
|
material = self.authority.read_material_digest(mission_id)
|
|
effect = self.authority.read_effect_view(mission_id)
|
|
fingerprint = _digest({
|
|
"schema": "aios.unattended-condition.v1",
|
|
"mission_id": mission_id,
|
|
"generation": generation,
|
|
"head_oid": head_oid,
|
|
"material_digest": material,
|
|
"effect_state": effect.effect_state.value,
|
|
"effect_condition": effect.condition_fingerprint,
|
|
"requires_reconcile": effect.requires_reconcile,
|
|
})
|
|
return MissionObservation(
|
|
mission_id=mission_id,
|
|
generation=generation,
|
|
head_oid=head_oid,
|
|
material_digest=material,
|
|
condition_fingerprint=fingerprint,
|
|
effect_state=effect.effect_state,
|
|
requires_reconcile=effect.requires_reconcile,
|
|
owner_action_resolved=self.authority.owner_action_resolved(mission_id),
|
|
evidence_refs=effect.receipt_refs,
|
|
)
|
|
|
|
def admission(
|
|
self,
|
|
observation: MissionObservation,
|
|
checkpoint: MissionCheckpoint,
|
|
lease: ResumeLease,
|
|
) -> StepResult:
|
|
grant = self.authority.read_durable_grant(observation.mission_id)
|
|
grant_refs = tuple(grant.evidence_refs)
|
|
|
|
if not grant.active:
|
|
return StepResult(
|
|
StepStatus.BLOCKED_OWNER if grant.owner_action_required
|
|
else StepStatus.BLOCKED_DETERMINISTIC,
|
|
"DURABLE_GRANT_NOT_ACTIVE",
|
|
self._phase_fingerprint(
|
|
"admission", observation, {"grant_id": grant.grant_id, "active": False}
|
|
),
|
|
receipt_refs=grant_refs,
|
|
owner_action_payload=(
|
|
{
|
|
"boundary": "DURABLE_GRANT_NOT_ACTIVE",
|
|
"grant_id": grant.grant_id,
|
|
}
|
|
if grant.owner_action_required else None
|
|
),
|
|
)
|
|
|
|
for lane in self.config.lanes:
|
|
if not grant.permits(
|
|
repository=lane.repository,
|
|
branch=lane.target_branch,
|
|
risk=lane.risk,
|
|
):
|
|
return StepResult(
|
|
StepStatus.BLOCKED_OWNER if grant.owner_action_required
|
|
else StepStatus.BLOCKED_DETERMINISTIC,
|
|
"DURABLE_GRANT_SCOPE_MISMATCH",
|
|
self._phase_fingerprint(
|
|
"admission",
|
|
observation,
|
|
{
|
|
"grant_id": grant.grant_id,
|
|
"lane": lane.lane,
|
|
"branch": lane.target_branch,
|
|
"risk": lane.risk,
|
|
},
|
|
),
|
|
receipt_refs=grant_refs,
|
|
owner_action_payload=(
|
|
{
|
|
"boundary": "DURABLE_GRANT_SCOPE_MISMATCH",
|
|
"grant_id": grant.grant_id,
|
|
"lane": lane.lane,
|
|
}
|
|
if grant.owner_action_required else None
|
|
),
|
|
)
|
|
|
|
worker = self.authority.ensure_canonical_worker(
|
|
observation.mission_id,
|
|
observation.generation,
|
|
self._idempotency(
|
|
"worker",
|
|
observation.mission_id,
|
|
observation.generation,
|
|
),
|
|
)
|
|
worker_refs = self._refs(grant_refs, (worker.receipt_ref or "",))
|
|
|
|
if worker.state is WorkerState.UNKNOWN:
|
|
return StepResult(
|
|
StepStatus.RECONCILE,
|
|
worker.code,
|
|
self._phase_fingerprint(
|
|
"worker", observation, {"state": worker.state.value}
|
|
),
|
|
receipt_refs=worker_refs,
|
|
)
|
|
if worker.state is WorkerState.BLOCKED:
|
|
return StepResult(
|
|
StepStatus.BLOCKED_OWNER if worker.owner_action_required
|
|
else StepStatus.BLOCKED_DETERMINISTIC,
|
|
worker.code,
|
|
self._phase_fingerprint(
|
|
"worker", observation, {"state": worker.state.value}
|
|
),
|
|
receipt_refs=worker_refs,
|
|
owner_action_payload=(
|
|
{"boundary": worker.code}
|
|
if worker.owner_action_required else None
|
|
),
|
|
)
|
|
if worker.state in {WorkerState.STARTING, WorkerState.INACTIVE}:
|
|
return StepResult(
|
|
StepStatus.IN_PROGRESS,
|
|
worker.code,
|
|
self._phase_fingerprint(
|
|
"worker", observation, {"state": worker.state.value}
|
|
),
|
|
receipt_refs=worker_refs,
|
|
retry_after_seconds=worker.retry_after_seconds,
|
|
)
|
|
if worker.state is WorkerState.ACTIVE and not worker.receipt_ref:
|
|
return StepResult(
|
|
StepStatus.RECONCILE,
|
|
"WORKER_ACTIVE_WITHOUT_RECEIPT",
|
|
self._phase_fingerprint(
|
|
"worker", observation, {"state": worker.state.value}
|
|
),
|
|
receipt_refs=worker_refs,
|
|
)
|
|
|
|
task_refs: list[str] = list(worker_refs)
|
|
admissions: dict[str, TaskAdmissionReceipt] = {}
|
|
for lane in self.config.lanes:
|
|
request = TaskAdmissionRequest(
|
|
task_id=lane.task_id,
|
|
idempotency_key=self._idempotency(
|
|
"task",
|
|
observation.generation,
|
|
lane.task_id,
|
|
observation.head_oid,
|
|
),
|
|
repository=lane.repository,
|
|
base_oid=observation.head_oid,
|
|
target_branch=lane.target_branch,
|
|
expected_branch_oid=observation.head_oid,
|
|
risk=lane.risk,
|
|
description=lane.description,
|
|
)
|
|
receipt = self.authority.create_or_resume_task(request)
|
|
task_refs.append(receipt.receipt_ref)
|
|
receipt_error = self._admission_receipt_error(
|
|
lane,
|
|
receipt,
|
|
observation=observation,
|
|
)
|
|
if receipt_error is not None:
|
|
return StepResult(
|
|
StepStatus.RECONCILE,
|
|
receipt_error,
|
|
self._phase_fingerprint(
|
|
"task",
|
|
observation,
|
|
{"lane": lane.lane, "task_id": lane.task_id},
|
|
),
|
|
receipt_refs=tuple(task_refs),
|
|
)
|
|
if not receipt.persisted:
|
|
return StepResult(
|
|
StepStatus.RECONCILE,
|
|
"TASK_PERSISTENCE_UNKNOWN",
|
|
self._phase_fingerprint(
|
|
"task",
|
|
observation,
|
|
{"lane": lane.lane, "task_id": lane.task_id},
|
|
),
|
|
receipt_refs=tuple(task_refs),
|
|
)
|
|
if not receipt.claimable:
|
|
return StepResult(
|
|
StepStatus.IN_PROGRESS,
|
|
"TASK_NOT_CLAIMABLE_YET",
|
|
self._phase_fingerprint(
|
|
"task",
|
|
observation,
|
|
{"lane": lane.lane, "task_id": lane.task_id},
|
|
),
|
|
receipt_refs=tuple(task_refs),
|
|
)
|
|
admissions[lane.lane] = receipt
|
|
|
|
self._admissions = admissions
|
|
return StepResult(
|
|
StepStatus.SUCCEEDED,
|
|
"GOVERNED_TASKS_ADMITTED",
|
|
self._phase_fingerprint(
|
|
"admission",
|
|
observation,
|
|
{"tasks": sorted(r.task_id for r in admissions.values())},
|
|
),
|
|
receipt_refs=tuple(task_refs),
|
|
next_phase=MissionPhase.S2_EXECUTION,
|
|
)
|
|
|
|
def source_execution(
|
|
self,
|
|
observation: MissionObservation,
|
|
checkpoint: MissionCheckpoint,
|
|
lease: ResumeLease,
|
|
) -> StepResult:
|
|
recovery_refs: list[str] = []
|
|
for lane in self.config.lanes:
|
|
if lane.lane in self._admissions:
|
|
continue
|
|
receipt = self.authority.read_task_admission(lane)
|
|
if receipt is None:
|
|
return StepResult(
|
|
StepStatus.RECONCILE,
|
|
"TASK_ADMISSION_NOT_RESOLVED",
|
|
self._phase_fingerprint(
|
|
"source",
|
|
observation,
|
|
{"lane": lane.lane, "task_id": lane.task_id},
|
|
),
|
|
receipt_refs=tuple(recovery_refs),
|
|
)
|
|
recovery_refs.append(receipt.receipt_ref)
|
|
receipt_error = self._admission_receipt_error(
|
|
lane,
|
|
receipt,
|
|
observation=observation,
|
|
)
|
|
if receipt_error is not None:
|
|
return StepResult(
|
|
StepStatus.RECONCILE,
|
|
receipt_error,
|
|
self._phase_fingerprint(
|
|
"source",
|
|
observation,
|
|
{"lane": lane.lane, "task_id": lane.task_id},
|
|
),
|
|
receipt_refs=tuple(recovery_refs),
|
|
)
|
|
if not receipt.persisted:
|
|
return StepResult(
|
|
StepStatus.RECONCILE,
|
|
"TASK_PERSISTENCE_UNKNOWN",
|
|
self._phase_fingerprint(
|
|
"source",
|
|
observation,
|
|
{"lane": lane.lane, "task_id": lane.task_id},
|
|
),
|
|
receipt_refs=tuple(recovery_refs),
|
|
)
|
|
if not receipt.claimable:
|
|
return StepResult(
|
|
StepStatus.IN_PROGRESS,
|
|
"TASK_NOT_CLAIMABLE_YET",
|
|
self._phase_fingerprint(
|
|
"source",
|
|
observation,
|
|
{"lane": lane.lane, "task_id": lane.task_id},
|
|
),
|
|
receipt_refs=tuple(recovery_refs),
|
|
)
|
|
self._admissions[lane.lane] = receipt
|
|
|
|
views: list[LaneExecutionView] = []
|
|
refs: list[str] = list(recovery_refs)
|
|
for lane in self.config.lanes:
|
|
view = self.authority.read_lane_execution(
|
|
lane,
|
|
self._admissions[lane.lane],
|
|
)
|
|
if view.lane != lane.lane:
|
|
return StepResult(
|
|
StepStatus.RECONCILE,
|
|
"LANE_IDENTITY_MISMATCH",
|
|
self._phase_fingerprint(
|
|
"source",
|
|
observation,
|
|
{"expected": lane.lane, "actual": view.lane},
|
|
),
|
|
receipt_refs=tuple(refs),
|
|
)
|
|
if view.state is WorkState.COMPLETE and not view.receipt_refs:
|
|
return StepResult(
|
|
StepStatus.RECONCILE,
|
|
"LANE_COMPLETE_WITHOUT_RECEIPT",
|
|
view.condition_fingerprint,
|
|
receipt_refs=tuple(refs),
|
|
)
|
|
views.append(view)
|
|
refs.extend(view.receipt_refs)
|
|
|
|
unknown = [v for v in views if v.state is WorkState.UNKNOWN]
|
|
if unknown:
|
|
return StepResult(
|
|
StepStatus.RECONCILE,
|
|
unknown[0].code,
|
|
self._phase_fingerprint(
|
|
"source",
|
|
observation,
|
|
{"states": [(v.lane, v.state.value) for v in views]},
|
|
),
|
|
receipt_refs=tuple(refs),
|
|
)
|
|
|
|
blocked = [v for v in views if v.state is WorkState.BLOCKED]
|
|
if blocked:
|
|
return StepResult(
|
|
StepStatus.BLOCKED_DETERMINISTIC,
|
|
blocked[0].code,
|
|
self._phase_fingerprint(
|
|
"source",
|
|
observation,
|
|
{"states": [(v.lane, v.state.value) for v in views]},
|
|
),
|
|
receipt_refs=tuple(refs),
|
|
)
|
|
|
|
pending = [
|
|
v for v in views
|
|
if v.state in {WorkState.PENDING, WorkState.RUNNING}
|
|
]
|
|
if pending:
|
|
retry_values = [
|
|
v.retry_after_seconds
|
|
for v in pending
|
|
if v.retry_after_seconds is not None
|
|
]
|
|
return StepResult(
|
|
StepStatus.IN_PROGRESS,
|
|
"SOURCE_LANES_IN_PROGRESS",
|
|
self._phase_fingerprint(
|
|
"source",
|
|
observation,
|
|
{"states": [(v.lane, v.state.value) for v in views]},
|
|
),
|
|
receipt_refs=tuple(refs),
|
|
retry_after_seconds=min(retry_values) if retry_values else None,
|
|
)
|
|
|
|
integration = self.authority.drive_integration(
|
|
self.config.lanes,
|
|
tuple(self._admissions[lane.lane] for lane in self.config.lanes),
|
|
)
|
|
refs.extend(integration.receipt_refs)
|
|
if integration.state is WorkState.UNKNOWN:
|
|
return StepResult(
|
|
StepStatus.RECONCILE,
|
|
integration.code,
|
|
integration.condition_fingerprint,
|
|
receipt_refs=tuple(refs),
|
|
)
|
|
if integration.state is WorkState.BLOCKED:
|
|
return StepResult(
|
|
StepStatus.BLOCKED_DETERMINISTIC,
|
|
integration.code,
|
|
integration.condition_fingerprint,
|
|
receipt_refs=tuple(refs),
|
|
)
|
|
if integration.state in {WorkState.PENDING, WorkState.RUNNING}:
|
|
return StepResult(
|
|
StepStatus.IN_PROGRESS,
|
|
integration.code,
|
|
integration.condition_fingerprint,
|
|
receipt_refs=tuple(refs),
|
|
retry_after_seconds=integration.retry_after_seconds,
|
|
)
|
|
if integration.state is WorkState.COMPLETE and not integration.receipt_refs:
|
|
return StepResult(
|
|
StepStatus.RECONCILE,
|
|
"INTEGRATION_COMPLETE_WITHOUT_RECEIPT",
|
|
integration.condition_fingerprint,
|
|
receipt_refs=tuple(refs),
|
|
)
|
|
return StepResult(
|
|
StepStatus.SUCCEEDED,
|
|
"SOURCE_INTEGRATION_COMPLETE",
|
|
integration.condition_fingerprint,
|
|
receipt_refs=tuple(refs),
|
|
next_phase=MissionPhase.GATE0,
|
|
)
|
|
|
|
def gate0(
|
|
self,
|
|
observation: MissionObservation,
|
|
checkpoint: MissionCheckpoint,
|
|
lease: ResumeLease,
|
|
) -> StepResult:
|
|
view = self.authority.run_gate0(observation.mission_id)
|
|
if view.state is GateState.UNKNOWN:
|
|
status = StepStatus.RECONCILE
|
|
elif view.state is GateState.HOLD:
|
|
status = StepStatus.BLOCKED_DETERMINISTIC
|
|
else:
|
|
if not view.receipt_refs:
|
|
return StepResult(
|
|
StepStatus.RECONCILE,
|
|
"GATE0_PASS_WITHOUT_RECEIPT",
|
|
view.condition_fingerprint,
|
|
)
|
|
status = StepStatus.SUCCEEDED
|
|
return StepResult(
|
|
status,
|
|
view.code,
|
|
view.condition_fingerprint,
|
|
receipt_refs=view.receipt_refs,
|
|
retry_after_seconds=view.retry_after_seconds,
|
|
next_phase=(
|
|
MissionPhase.RELEASE_READY
|
|
if status is StepStatus.SUCCEEDED else None
|
|
),
|
|
)
|
|
|
|
def release_ready(
|
|
self,
|
|
observation: MissionObservation,
|
|
checkpoint: MissionCheckpoint,
|
|
lease: ResumeLease,
|
|
) -> StepResult:
|
|
view = self.authority.build_release_ready(observation.mission_id)
|
|
if view.state is ReadyState.UNKNOWN:
|
|
status = StepStatus.RECONCILE
|
|
elif view.state is ReadyState.NOT_READY:
|
|
status = StepStatus.BLOCKED_DETERMINISTIC
|
|
else:
|
|
if not view.material_digest:
|
|
return StepResult(
|
|
StepStatus.RECONCILE,
|
|
"READY_WITHOUT_MATERIAL_DIGEST",
|
|
view.condition_fingerprint,
|
|
receipt_refs=view.receipt_refs,
|
|
)
|
|
if not view.receipt_refs:
|
|
return StepResult(
|
|
StepStatus.RECONCILE,
|
|
"READY_WITHOUT_RECEIPT",
|
|
view.condition_fingerprint,
|
|
)
|
|
status = StepStatus.SUCCEEDED
|
|
return StepResult(
|
|
status,
|
|
view.code,
|
|
view.condition_fingerprint,
|
|
receipt_refs=view.receipt_refs,
|
|
retry_after_seconds=view.retry_after_seconds,
|
|
next_phase=(
|
|
MissionPhase.PROMOTION
|
|
if status is StepStatus.SUCCEEDED else None
|
|
),
|
|
material_digest=view.material_digest,
|
|
)
|
|
|
|
def promotion(
|
|
self,
|
|
observation: MissionObservation,
|
|
checkpoint: MissionCheckpoint,
|
|
lease: ResumeLease,
|
|
) -> StepResult:
|
|
view = self.authority.run_promotion(observation.mission_id)
|
|
if view.state is PromotionState.UNKNOWN:
|
|
status = StepStatus.RECONCILE
|
|
elif view.state is PromotionState.IN_PROGRESS:
|
|
status = StepStatus.IN_PROGRESS
|
|
elif view.state is PromotionState.VERIFIED:
|
|
if not view.receipt_refs:
|
|
return StepResult(
|
|
StepStatus.RECONCILE,
|
|
"PROMOTION_VERIFIED_WITHOUT_RECEIPT",
|
|
view.condition_fingerprint,
|
|
material_digest=view.material_digest,
|
|
)
|
|
status = StepStatus.SUCCEEDED
|
|
elif view.state is PromotionState.ROLLED_BACK:
|
|
status = StepStatus.BLOCKED_DETERMINISTIC
|
|
else:
|
|
status = StepStatus.BLOCKED_DETERMINISTIC
|
|
return StepResult(
|
|
status,
|
|
view.code,
|
|
view.condition_fingerprint,
|
|
receipt_refs=view.receipt_refs,
|
|
retry_after_seconds=view.retry_after_seconds,
|
|
next_phase=(
|
|
MissionPhase.FINAL_ACCEPTANCE
|
|
if status is StepStatus.SUCCEEDED else None
|
|
),
|
|
material_digest=view.material_digest,
|
|
)
|
|
|
|
def final_acceptance(
|
|
self,
|
|
observation: MissionObservation,
|
|
checkpoint: MissionCheckpoint,
|
|
lease: ResumeLease,
|
|
) -> StepResult:
|
|
view = self.authority.run_final_acceptance(observation.mission_id)
|
|
if view.state is AcceptanceState.UNKNOWN:
|
|
status = StepStatus.RECONCILE
|
|
elif view.state is AcceptanceState.PASS:
|
|
if not view.receipt_refs:
|
|
return StepResult(
|
|
StepStatus.RECONCILE,
|
|
"FINAL_ACCEPTANCE_PASS_WITHOUT_RECEIPT",
|
|
view.condition_fingerprint,
|
|
)
|
|
status = StepStatus.SUCCEEDED
|
|
else:
|
|
status = StepStatus.BLOCKED_DETERMINISTIC
|
|
return StepResult(
|
|
status,
|
|
view.code,
|
|
view.condition_fingerprint,
|
|
receipt_refs=view.receipt_refs,
|
|
next_phase=(
|
|
MissionPhase.COMPLETE
|
|
if status is StepStatus.SUCCEEDED else None
|
|
),
|
|
)
|
|
|
|
@staticmethod
|
|
def _admission_receipt_error(
|
|
lane: TaskLaneSpec,
|
|
receipt: TaskAdmissionReceipt,
|
|
*,
|
|
observation: MissionObservation,
|
|
) -> str | None:
|
|
if receipt.task_id != lane.task_id:
|
|
return "TASK_IDENTITY_MISMATCH"
|
|
if receipt.target_branch != lane.target_branch:
|
|
return "TASK_BRANCH_MISMATCH"
|
|
if receipt.generation != observation.generation:
|
|
return "TASK_GENERATION_MISMATCH"
|
|
return None
|
|
|
|
@staticmethod
|
|
def _refs(*groups: Sequence[str]) -> tuple[str, ...]:
|
|
seen: set[str] = set()
|
|
values: list[str] = []
|
|
for group in groups:
|
|
for item in group:
|
|
if item and item not in seen:
|
|
seen.add(item)
|
|
values.append(item)
|
|
return tuple(values)
|
|
|
|
@staticmethod
|
|
def _idempotency(*parts: object) -> str:
|
|
return "aios:auto:" + _digest(
|
|
{"schema": "aios.unattended-idempotency.v1", "parts": list(parts)}
|
|
)
|
|
|
|
@staticmethod
|
|
def _phase_fingerprint(
|
|
phase: str,
|
|
observation: MissionObservation,
|
|
details: Mapping[str, object],
|
|
) -> str:
|
|
return _digest({
|
|
"schema": "aios.unattended-phase-condition.v1",
|
|
"phase": phase,
|
|
"mission_id": observation.mission_id,
|
|
"generation": observation.generation,
|
|
"head_oid": observation.head_oid,
|
|
"material_digest": observation.material_digest,
|
|
"details": details,
|
|
})
|