jenkins 6a5e0d872b hermes(hux): HUX-11 foundation service core with threat and data models
Stdlib per-tenant service: trusted-header identity (router/relay/worker,
constant-time keys, slot pinned to the pod), fail-closed card flags with
capability negotiation, tenant-scoped store (atomic writes, revisions,
append-only ledgers, content-addressed blobs, manifest), audit outcome for
every request, and the /hux/v1 pipeline that maps errors to hux.error.v1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNPhwu2bsaRNg3DETSAZoM
2026-08-24 00:48:20 -03:00

41 lines
1.3 KiB
Python

"""Auditable outcome for every read and mutation.
One JSONL ledger per tenant per day. The record shape is
``common.schema.json#/$defs/audit_outcome``; it never carries request bodies,
only the action name, the resource id and the decision.
"""
from __future__ import annotations
from hux.identity import Identity
from hux.store import TenantStore, now_iso
OUTCOMES = ("allow", "deny", "not_found", "conflict", "flag_off")
def record(store: TenantStore, identity: Identity, action: str, resource: str, outcome: str, reason: str = "") -> dict:
"""Append an audit outcome and return it."""
if outcome not in OUTCOMES:
raise ValueError(f"unknown outcome {outcome!r}")
entry = {
"at": now_iso(),
"identity": identity.record(),
"action": action,
"resource": resource[:200],
"outcome": outcome,
}
if reason:
entry["reason"] = reason[:200]
store.append("audit", now_iso()[:10], entry)
return entry
def recent(store: TenantStore, limit: int = 200) -> list[dict]:
"""Newest audit rows across day ledgers, newest last."""
rows: list[dict] = []
for name in reversed(store.ledgers("audit")):
rows = store.read("audit", name) + rows
if len(rows) >= limit:
break
return rows[-limit:]