Skip to main content

ADR-0154: Audit index partitioned by event_time and view under the reference names

Context​

The architecture reference (B13.2) asks for an audit index table partitioned by event_time, with access constraints, retention and archiving, and columns named event_type, severity, identities, execution ids, decision_reason, policy_revision, evidence_*, sanitized_metadata, result_status. eai.audit_events was a plain table (V010) whose information lived in chained jsonb columns (actor, resource, decision, context, evidence, attributes).

Decision​

  • V186 turns eai.audit_events into a partitioned table (RANGE on event_time, one partition per UTC month, a DEFAULT partition so that no event is ever refused). Existing rows are copied unchanged (same id, same hash, same times): the chain stays verifiable.
  • A partitioned table cannot hold UNIQUE (tenant_id, sequence) without the partition key: the chain keys live in eai.audit_event_keys (primary key (tenant_id, sequence), unique event_id), filled by an AFTER INSERT trigger in the same transaction. A duplicate sequence or event id still fails.
  • eai.audit_ensure_partitions(months_ahead, from_month) creates the coming monthly partitions and moves, in the same transaction, the rows of that month found in DEFAULT. The worker job audit_partitions (audit plane) calls it daily three months ahead. The function is not executable by eai_app.
  • Every partition gets the same forced RLS as the parent and eai_app has no UPDATE, DELETE or TRUNCATE on it.
  • Events written before V150 have no event_time (their hash was computed without it): they stay NULL, in DEFAULT, so the chain is not broken.
  • V187 adds the view eai.audit_index (security_invoker) exposing every event under the reference names, computed from the chained fields: nothing is copied, nothing can diverge. evidence_state is sealed once a signed seal covers the sequence (sealed_at is then the time of that seal), chained before.

Consequences​

PostgreSQL is still not the WORM medium: the proof stays the signed export in qualified object storage (existing ADRs of lot G03). Per tenant and sequence queries use the (tenant_id, sequence) index of each partition. The data model mapping test counts partitioned tables (relkind p) and ignores their partitions.

Evidence​

services/control-plane/tests/test_audit_partitions_index.py, services/control-plane/tests/test_core_audit.py, services/control-plane/tests/test_evidence_gateway.py, services/control-plane/tests/test_core_rls_forced.py.