Aller au contenu principal

ADR-0154 : Index d'audit partitionné par event_time et vue aux noms de la référence

Contexte​

La référence d'architecture (B13.2) demande une table d'index d'audit partitionnée par event_time, avec contraintes d'accès, rétention et archivage, et des colonnes nommées event_type, severity, identités, identifiants d'exécution, decision_reason, policy_revision, evidence_*, sanitized_metadata, result_status. eai.audit_events était une table simple (V010) dont les informations vivaient dans des colonnes jsonb chaînées (actor, resource, decision, context, evidence, attributes).

Décision​

  • V186 transforme eai.audit_events en table partitionnée (RANGE sur event_time, une partition par mois UTC, une partition DEFAULT pour ne jamais refuser un événement). Les lignes existantes sont copiées à l'identique (même id, même hash, mêmes horodatages) : la chaîne reste vérifiable.
  • Une table partitionnée ne peut pas porter UNIQUE (tenant_id, sequence) sans la clé de partition : les clés de chaîne vivent dans eai.audit_event_keys (clé primaire (tenant_id, sequence), event_id unique), alimentée par un déclencheur AFTER INSERT dans la même transaction. Une séquence ou un identifiant en double échoue toujours.
  • eai.audit_ensure_partitions(months_ahead, from_month) crée les partitions des mois suivants et y déplace, dans la même transaction, les lignes de ce mois trouvées dans DEFAULT. Le job audit_partitions du worker (plan audit) l'appelle chaque jour avec trois mois d'avance. La fonction n'est pas exécutable par eai_app.
  • Chaque partition reçoit la même RLS forcée que la table mère et eai_app n'y a ni UPDATE, ni DELETE, ni TRUNCATE.
  • Les événements écrits avant V150 n'ont pas d'event_time (leur hash a été calculé sans) : ils restent NULL, dans DEFAULT, pour ne pas casser la chaîne.
  • V187 ajoute la vue eai.audit_index (security_invoker) qui expose chaque événement sous les noms de la référence, calculés depuis les champs chaînés : rien n'est recopié, rien ne peut diverger. evidence_state vaut sealed dès qu'un sceau signé couvre la séquence (sealed_at est alors l'heure de ce sceau), chained avant.

Conséquences​

PostgreSQL n'est toujours pas le support WORM : la preuve reste l'export signé en stockage objet qualifié (ADR existants du lot G03). Les requêtes par tenant et séquence passent par l'index (tenant_id, sequence) de chaque partition. Le test de correspondance du modèle de données compte les tables partitionnées (relkind p) et ignore leurs partitions.

Preuves​

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.