Implement approval engine production readiness

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a05e2e-805b-7042-a750-71f473bceea2
This commit is contained in:
tegwick 2026-09-02 00:52:04 +02:00
parent ebce5abb27
commit 2bd2d19a98
30 changed files with 1679 additions and 53 deletions

100
approval_engine/audit.py Normal file
View file

@ -0,0 +1,100 @@
"""Asynchronous delivery of the transactional outbox to audit-core."""
from __future__ import annotations
import json
import threading
from pathlib import Path
from typing import Any, Callable
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
from .store import Engine
class AuditDeliveryError(RuntimeError):
"""Bounded delivery failure; details deliberately exclude response bodies."""
def audit_envelope(payload: dict[str, Any]) -> dict[str, Any]:
details = dict(payload.get("details") or {})
return {
"id": payload["event_id"],
"type": payload["action"],
"source": payload["source"],
"subject": payload["resource"],
"tenant": payload["tenant"],
"correlation_id": details.get("approval_id") or payload["event_id"],
"occurred_at": payload["observed_at"],
"data": {
"schema_version": payload["schema_version"],
"scope": payload["scope"],
"actor": payload.get("actor"),
"resource": payload["resource"],
"outcome": payload["outcome"],
"reason": payload.get("reason"),
"details": details,
},
}
class AuditCoreSink:
def __init__(
self,
base_url: str,
token_file: str | Path,
*,
timeout_seconds: float = 5,
opener: Callable[..., Any] = urlopen,
) -> None:
self.url = base_url.rstrip("/") + "/v1/events"
self.token_file = Path(token_file)
self.timeout_seconds = timeout_seconds
self.opener = opener
def __call__(self, payload: dict[str, Any]) -> None:
token = self.token_file.read_text(encoding="utf-8").strip()
if not token or any(ch.isspace() for ch in token):
raise AuditDeliveryError("audit credential is unavailable")
body = json.dumps(audit_envelope(payload), sort_keys=True).encode("utf-8")
request = Request(
self.url,
data=body,
method="POST",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"Idempotency-Key": payload["event_id"],
},
)
try:
response = self.opener(request, timeout=self.timeout_seconds)
status = int(response.getcode())
response.close()
except (HTTPError, URLError, OSError) as exc:
raise AuditDeliveryError(type(exc).__name__) from exc
if status not in {200, 202}:
raise AuditDeliveryError(f"audit ingest returned status {status}")
class OutboxWorker:
def __init__(
self,
engine: Engine,
sink: Callable[[dict[str, Any]], None],
*,
heartbeat_interval_seconds: int = 300,
) -> None:
self.engine = engine
self.sink = sink
self.heartbeat_interval_seconds = heartbeat_interval_seconds
def run_once(self) -> dict[str, int]:
if self.engine.heartbeat_due(self.heartbeat_interval_seconds):
self.engine.emit_heartbeat()
return self.engine.drain(self.sink)
def run_forever(self, stop: threading.Event, poll_seconds: float = 5) -> None:
while not stop.is_set():
self.run_once()
stop.wait(poll_seconds)