123 lines
4.4 KiB
Python
123 lines
4.4 KiB
Python
"""Staging HTTP overlay for the exact Task lineage supersession capability."""
|
|
from __future__ import annotations
|
|
|
|
from http.server import ThreadingHTTPServer
|
|
from typing import Any, Callable, Mapping
|
|
from urllib.parse import urlparse
|
|
|
|
from aios_task_lineage_supersession import (
|
|
DECISION_SCHEMA,
|
|
TaskLineageSupersessionController,
|
|
TaskLineageSupersessionError,
|
|
)
|
|
|
|
SUPERSESSION_ROUTE = "/v1/control/task-lineage-supersession"
|
|
|
|
|
|
def _status(error: TaskLineageSupersessionError) -> int:
|
|
return error.http_status
|
|
|
|
|
|
class LineageSupersessionEndpoint:
|
|
"""Framework-neutral endpoint; authentication must precede all parsing/mutation."""
|
|
|
|
def __init__(
|
|
self,
|
|
db_path,
|
|
verifier,
|
|
authenticate_control: Callable[[Mapping[str, str]], Any],
|
|
*,
|
|
clock=None,
|
|
identity_digest=None,
|
|
) -> None:
|
|
self.authenticate_control = authenticate_control
|
|
self.controller = TaskLineageSupersessionController(
|
|
db_path, verifier, clock=clock, identity_digest=identity_digest
|
|
)
|
|
|
|
def post(self, headers: Mapping[str, str], body: Mapping[str, Any]) -> tuple[int, dict[str, Any]]:
|
|
self.authenticate_control(headers)
|
|
try:
|
|
result = self.controller.supersede(body)
|
|
except TaskLineageSupersessionError as exc:
|
|
return _status(exc), {"error": exc.code}
|
|
return (200 if result.get("idempotent") else 201), result
|
|
|
|
|
|
def build_runtime():
|
|
"""Bind to the already-active recovery API and its formal Owner verifier lazily."""
|
|
try:
|
|
from aios_agent_task_api import APIConfig
|
|
from aios_agent_task_api_p0_failed_lease_recovery import (
|
|
P0FailedLeaseRecoveryHandler,
|
|
P0FailedLeaseRecoveryTaskApplication,
|
|
)
|
|
from aios_agent_task_protocol import TaskProtocolError
|
|
from aios_owner_approval_verifier import (
|
|
GrantRootOwnerApprovalVerifier,
|
|
OwnerApprovalVerifierError,
|
|
)
|
|
except ImportError as exc:
|
|
raise RuntimeError("LINEAGE_SUPERSESSION_RUNTIME_OVERLAY_UNAVAILABLE") from exc
|
|
|
|
# The active entrypoint layers failed- and active-lease recovery on top of
|
|
# expired recovery. Inherit that current layer so adding lineage does not
|
|
# silently remove either already-deployed recovery route.
|
|
class Application(P0FailedLeaseRecoveryTaskApplication):
|
|
def __init__(self, config: Any) -> None:
|
|
super().__init__(config)
|
|
try:
|
|
verifier = GrantRootOwnerApprovalVerifier()
|
|
except OwnerApprovalVerifierError as exc:
|
|
raise TaskProtocolError(str(exc), 503) from exc
|
|
self.lineage_supersession = LineageSupersessionEndpoint(
|
|
config.db_path, verifier, self.authenticate_control
|
|
)
|
|
|
|
def health(self) -> dict[str, Any]:
|
|
value = dict(super().health())
|
|
value.update({
|
|
"task_lineage_supersession": True,
|
|
"task_lineage_supersession_schema": DECISION_SCHEMA,
|
|
"task_lineage_supersession_exact_scope": True,
|
|
"task_lineage_supersession_exact_cas": True,
|
|
"task_lineage_supersession_append_only": True,
|
|
"task_lineage_supersession_caller_payload": False,
|
|
})
|
|
return value
|
|
|
|
class Handler(P0FailedLeaseRecoveryHandler):
|
|
app: Application | None = None
|
|
|
|
def do_POST(self) -> None:
|
|
path = urlparse(self.path).path.rstrip("/") or "/"
|
|
if path != SUPERSESSION_ROUTE:
|
|
super().do_POST()
|
|
return
|
|
|
|
def action() -> None:
|
|
if self.app is None:
|
|
raise TaskProtocolError("SERVICE_UNAVAILABLE", 503)
|
|
status, result = self.app.lineage_supersession.post(
|
|
self._headers(), self._body()
|
|
)
|
|
if "error" in result:
|
|
raise TaskProtocolError(str(result["error"]), status)
|
|
self._json(result, status)
|
|
|
|
self._run(action)
|
|
|
|
return APIConfig, Application, Handler
|
|
|
|
|
|
def serve(config=None) -> None:
|
|
APIConfig, Application, Handler = build_runtime()
|
|
config = config or APIConfig.from_env()
|
|
application = Application(config)
|
|
Handler.app = application
|
|
ThreadingHTTPServer((config.host, config.port), Handler).serve_forever()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
serve()
|