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_eventsinto a partitioned table (RANGE onevent_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 ineai.audit_event_keys(primary key(tenant_id, sequence), uniqueevent_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 jobaudit_partitions(audit plane) calls it daily three months ahead. The function is not executable byeai_app.- Every partition gets the same forced RLS as the parent and
eai_apphas 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_stateissealedonce a signed seal covers the sequence (sealed_atis then the time of that seal),chainedbefore.
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.