feat: atomically journal and deliver authorized native operation outcomes
Some checks failed
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / pytest-smoke (push) Failing after 4s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0e747-8f27-7242-8df8-8bc44f88c929
This commit is contained in:
tegwick 2026-09-28 14:00:26 +02:00
parent 3c5cbfbafe
commit f0eff0ac92
13 changed files with 583 additions and 28 deletions

View file

@ -16,7 +16,7 @@ class AuditCoreSink:
"""No allow returns before durable remote custody acknowledges its record.
A lost receipt blocks execution, even if the archive already stored the
attempt. This is an authorization-attempt journal, not a mutation outbox.
attempt. Committed outcomes arrive separately from the transactional outbox.
"""
def __init__(self, *, base_url: str, token_file: Path, client: httpx.AsyncClient):
@ -34,30 +34,32 @@ class AuditCoreSink:
raise ValueError("operational audit custody is required")
async def append(self, record: dict) -> None:
correlation, outcome = record.get("correlation_id"), record.get("outcome")
if not isinstance(correlation, str) or not correlation or outcome not in {"authorized", "denied", "refused"}:
raise AccessFailure(503, "audit_unavailable")
await self._deliver({
"id": str(uuid4()), "type": "hub.access." + outcome, "source": "hub-core",
"subject": "hub-access:" + correlation, "tenant": "tenant:platform",
"correlation_id": correlation, "occurred_at": datetime.now(timezone.utc).isoformat(),
"data": record,
})
async def append_outcome(self, event: dict) -> None:
# The transaction supplies the immutable ID, timestamp and full envelope.
if (event.get("type") != "hub.operation.committed" or event.get("source") != "hub-core"
or event.get("tenant") != "tenant:platform"
or not isinstance(event.get("id"), str) or not event["id"]
or set(event) != {"id", "type", "source", "subject", "tenant", "correlation_id", "occurred_at", "data"}):
raise AccessFailure(503, "audit_unavailable")
await self._deliver(event)
async def _deliver(self, event: dict) -> None:
try:
async with asyncio.timeout(3):
# Probe each time, so a receiver's development fallback cannot
# be mistaken for admitted custody through a cached readiness.
await self.readiness()
token = self.token_file.read_text().strip()
if not token or not token.isascii() or any(c.isspace() for c in token):
raise ValueError("invalid sender credential")
correlation = record.get("correlation_id")
outcome = record.get("outcome")
if not isinstance(correlation, str) or not correlation or outcome not in {
"authorized", "denied", "refused",
}:
raise ValueError("invalid authorization audit record")
event = {
"id": str(uuid4()),
"type": "hub.access." + outcome,
"source": "hub-core",
"subject": "hub-access:" + correlation,
"tenant": "tenant:platform",
"correlation_id": correlation,
"occurred_at": datetime.now(timezone.utc).isoformat(),
"data": record,
}
raw = json.dumps(event, ensure_ascii=False, allow_nan=False).encode()
if len(raw) > 256 * 1024:
raise ValueError("audit envelope exceeds receiver limit")