Implement AUDIT-WP-0006 honest operational custody.
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

Postgres now reports custody_class=operational with a cited 30-day
recoverable window. Join ITC-CAP operations.audit at D4, publish the
interface card, and overlay user-engine tenants [*] from Git so an
ExternalSecret refresh cannot shrink it.
This commit is contained in:
tegwick 2026-08-16 00:24:33 +02:00
parent 0a3d05ff1c
commit ded432a63f
25 changed files with 832 additions and 94 deletions

View file

@ -38,6 +38,7 @@ from audit_core.interface import (
EventConflictError,
EventValidationError,
IdempotentAuditBackend,
custody_class_satisfies,
)
from audit_core.redaction import (
POLICY_REDACT,
@ -102,10 +103,13 @@ class IngestionApplication:
require_custody_class: str | None = None,
) -> None:
policy = backend.retention_policy
if require_custody_class and policy.custody_class != require_custody_class:
if require_custody_class and not custody_class_satisfies(
policy.custody_class, require_custody_class
):
# Production sets this. Without it, losing AUDIT_CORE_DATABASE_URL
# silently downgrades custody to the development store instead of
# failing to start.
# failing to start. ``operational`` and ``archive`` alias each
# other for one mixed-rollout deploy (AUDIT-WP-0006-T01).
raise ValueError(
f"backend custody_class={policy.custody_class!r} does not meet the "
f"required {require_custody_class!r}; refusing to start"
@ -330,11 +334,10 @@ class IngestionApplication:
return self._json(
start_response, HTTPStatus.SERVICE_UNAVAILABLE, {"status": "unavailable"}
)
policy = self.backend.retention_policy
return self._json(
start_response,
HTTPStatus.OK,
{"status": "ok", "custody_class": policy.custody_class, "durable": policy.durable},
self.backend.retention_policy.as_readiness(),
)
@staticmethod
@ -490,11 +493,15 @@ def build_backend() -> IdempotentAuditBackend:
else "AUDIT_CORE_DATABASE_URL" if url
else "brokered libpq environment")
log.info("custody backend: postgresql (%s)", source)
recoverable = os.environ.get("AUDIT_CORE_RECOVERABLE_DAYS")
return PostgresAuditBackend(
url or "",
credential_dir=credential_dir,
schema=os.environ.get("AUDIT_CORE_DATABASE_SCHEMA", "audit_core"),
retention_days=int(retention) if retention else None,
recoverable_days=(
int(recoverable) if recoverable else 30
),
max_size=int(os.environ.get("AUDIT_CORE_DB_POOL_MAX", "8")),
statement_timeout_ms=int(
os.environ.get("AUDIT_CORE_DB_STATEMENT_TIMEOUT_MS", "30000")