174 lines
6.0 KiB
Python
174 lines
6.0 KiB
Python
"""Dependency-light candidate API facade.
|
|
|
|
The production transport is intentionally external. This module exposes the
|
|
application boundary without opening listeners or changing production routes.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Mapping
|
|
|
|
from .evidence import EvidenceChain
|
|
from .orchestrator import AutonomousReleaseOrchestrator
|
|
from .release import ReleaseController
|
|
from .serialization import (
|
|
release_plan_from_mapping,
|
|
release_plan_to_mapping,
|
|
task_plan_from_mapping,
|
|
task_plan_to_mapping,
|
|
)
|
|
|
|
|
|
class AutonomousReleaseAPI:
|
|
def __init__(self, orchestrator: AutonomousReleaseOrchestrator) -> None:
|
|
self.orchestrator = orchestrator
|
|
self.release = ReleaseController()
|
|
|
|
def _chain(self, task_id: str) -> EvidenceChain:
|
|
return EvidenceChain(self.orchestrator.workflow.store, task_id)
|
|
|
|
def create_task(self, payload: Mapping[str, Any]) -> dict[str, Any]:
|
|
plan = task_plan_from_mapping(payload)
|
|
result = self.orchestrator.create(plan)
|
|
self.orchestrator.workflow.next_steps(plan)
|
|
return {
|
|
"task_id": result.task_id,
|
|
"plan_digest": result.plan_digest,
|
|
"production_side_effects": result.production_side_effects,
|
|
"plan": task_plan_to_mapping(plan),
|
|
}
|
|
|
|
def close_evidence(self, task_id: str) -> dict[str, Any]:
|
|
result = self._chain(task_id).closure_digest()
|
|
return {
|
|
"task_id": task_id,
|
|
"evidence_closure": result,
|
|
"production_side_effects": False,
|
|
"secrets_included": False,
|
|
}
|
|
|
|
def task_steps(self, task_id: str) -> dict[str, Any]:
|
|
status = self.task_status(task_id)
|
|
return {
|
|
"task_id": task_id,
|
|
"steps": status["steps"],
|
|
"production_side_effects": False,
|
|
"secrets_included": False,
|
|
}
|
|
|
|
def claim(self, task_id: str, payload: Mapping[str, Any]) -> dict[str, Any]:
|
|
step_id = str(payload["step_id"])
|
|
worker_id = str(payload["worker_id"])
|
|
session_id = str(payload["session_id"])
|
|
workflow = self.orchestrator.workflow
|
|
workflow.claim(task_id, step_id, worker_id, session_id)
|
|
workflow.start(task_id, step_id, worker_id, session_id)
|
|
self._chain(task_id).append(
|
|
"STEP_CLAIMED",
|
|
task_id,
|
|
{
|
|
"step_id": step_id,
|
|
"worker_id": worker_id,
|
|
"session_id": session_id,
|
|
"secrets_included": False,
|
|
},
|
|
)
|
|
return {
|
|
"task_id": task_id,
|
|
"step_id": step_id,
|
|
"state": "RUNNING",
|
|
"production_side_effects": False,
|
|
"secrets_included": False,
|
|
}
|
|
|
|
def heartbeat(self, task_id: str, payload: Mapping[str, Any]) -> dict[str, Any]:
|
|
step_id = str(payload["step_id"])
|
|
worker_id = str(payload["worker_id"])
|
|
session_id = str(payload["session_id"])
|
|
self.orchestrator.workflow.heartbeat(task_id, step_id, worker_id, session_id)
|
|
return {
|
|
"task_id": task_id,
|
|
"step_id": step_id,
|
|
"state": "RUNNING",
|
|
"production_side_effects": False,
|
|
"secrets_included": False,
|
|
}
|
|
|
|
def result(self, task_id: str, payload: Mapping[str, Any]) -> dict[str, Any]:
|
|
step_id = str(payload["step_id"])
|
|
result_digest = str(payload["result_digest"])
|
|
self.orchestrator.workflow.complete(task_id, step_id, result_digest=result_digest)
|
|
self._chain(task_id).append(
|
|
"STEP_RESULT",
|
|
task_id,
|
|
{
|
|
"step_id": step_id,
|
|
"result_digest": result_digest,
|
|
"secrets_included": False,
|
|
},
|
|
)
|
|
return {
|
|
"task_id": task_id,
|
|
"step_id": step_id,
|
|
"state": "COMPLETED",
|
|
"production_side_effects": False,
|
|
"secrets_included": False,
|
|
}
|
|
|
|
def evidence(self, task_id: str) -> dict[str, Any]:
|
|
chain = self._chain(task_id)
|
|
return {
|
|
"task_id": task_id,
|
|
"events": chain.export(),
|
|
"verified": chain.verify(),
|
|
"production_side_effects": False,
|
|
"secrets_included": False,
|
|
}
|
|
|
|
def create_release_plan(self, payload: Mapping[str, Any]) -> dict[str, Any]:
|
|
plan = release_plan_from_mapping(payload)
|
|
self.release = ReleaseController()
|
|
self.release.attach_plan(plan)
|
|
self.release.mark_built()
|
|
return {
|
|
"plan": release_plan_to_mapping(plan),
|
|
"release_state": self.release.status().state.value,
|
|
"production_side_effects": False,
|
|
"secrets_included": False,
|
|
}
|
|
|
|
def verify_release(self, payload: Mapping[str, Any]) -> dict[str, Any]:
|
|
self.release.verify(
|
|
artifact_ok=payload.get("artifact_ok") is True,
|
|
evidence_ok=payload.get("evidence_ok") is True,
|
|
)
|
|
self.release.request_owner_approval()
|
|
return {
|
|
"release_state": self.release.status().state.value,
|
|
"production_release": "BLOCKED_EXTERNAL_OWNER_SIGNATURE",
|
|
"production_side_effects": False,
|
|
"secrets_included": False,
|
|
}
|
|
|
|
def task_status(self, task_id: str) -> dict[str, Any]:
|
|
store = self.orchestrator.workflow.store
|
|
state, revision = store.get_task_state(task_id)
|
|
steps = store.list_steps(task_id)
|
|
return {
|
|
"task_id": task_id,
|
|
"state": state.value,
|
|
"revision": revision,
|
|
"steps": {
|
|
step_id: {
|
|
"state": item.state.value,
|
|
"attempt": item.attempt,
|
|
"max_attempts": item.max_attempts,
|
|
"worker_id": item.worker_id,
|
|
"session_id": item.session_id,
|
|
}
|
|
for step_id, item in steps.items()
|
|
},
|
|
"production_side_effects": False,
|
|
"secrets_included": False,
|
|
}
|