PRD + FSD - audit-log (AUDIT) module¶
Module prefix:
AUDIT· Plane: Control · Version scope: V1 · Country scope V1:CIonly Owner author: ANALYST · Source PRD:docs/prd/prd-v1.md(§§16.5, 17, 18, 27, 30) · Source vision:docs/prd/prd-vision-v2-v3.md· Source blueprint:docs/architecture/platform-saas-blueprint.md§5 Source legal audit:docs/legal/audit/memo-conformite-CI-v2-audit.md(audité le 30/04/2026 - disposition LEGAL REVIEW REQUIRED). Mémo source :docs/legal/memo-conformite-CI-v2.md(Section F5 pour AUDIT). L'audit précédentLegal-Compliance-audit.md(RE-RESEARCH NEEDED) est superseded. Sibling PRDs already binding contracts on AUDIT:tenant-admin-prd.md(NFR-09 best-effort-immediate, AC-09.1 retention flooraudit_retention_years ≥ 10)Legal status discipline (memo v2) - toute obligation
INCERTAIN - avocat requisbloque l'implémentation des stories qui en dépendent et impose une note[LEGAL-REVIEW]dans la spec/story. Toute obligationPRÉLIMINAIREdéclenche un avertissement +[LEGAL-REVIEW]. La conformité ne se déduit jamais d'un statutINCERTAIN. Items à surveiller pour AUDIT (memo F5) - conservation 10 ans des événements assurance (CIMA art. 37) + monnaie électronique (BCEAO arts. 7 + 26) =VÉRIFIÉ. Format minimal du journal (5 champs : id, horodatage, user, nature, résultat) =VÉRIFIÉ(CIMA art. 37). Durée de conservation des logs applicatifs serveur =PRÉLIMINAIRE(analogie fragile avec Décision ARTCI 2023-0877 GRH ; aucune délibération sectorielle assurance trouvée) → noter[LEGAL-REVIEW]partout où une durée applicative est figée.
V0 Envelope Scope (ajout 2026-05-21)¶
Ce bloc est strictement additif : il décrit comment le périmètre AUDIT de ce PRD V1 est tranché en sous-versions V0 (v0.0.0 à v0.1.0). Le contenu V1 existant reste la référence cible. Référence :
docs/prd/prd-v0.md§ 4 (ligneAudit log), § 5 (anchors transverses, notamment outbox D4 + D7) et § 8 (mapping stories).
Découpage par sous-version¶
| Sous-version | Périmètre AUDIT vs V1 PRD | Story IDs |
|---|---|---|
| v0.0.0 | Table audit_entry tenant-scopée, append-only, créée Day 1 sur la platform DB. Stubs d'émission câblés pour tous les événements V1 PRD §18 (TENANT, AUTH, ING, CP, OP, PAY, NOTIF). Pattern outbox via spring-modulith-events-jdbc (anchors D4 + D7 de prd-v0.md § 5). Pas de console AUDIT (UI reportée V1). Pas d'export, pas de recherche opérateur. |
AUDIT-001 |
| v0.0.1 | Émission réelle (plus seulement stubbée) sur deux chemins critiques : paiement (PAY_INITIATED, PAY_CONFIRMED, PAY_FAILED, PAY_RECONCILED) et notification (NOTIF_SENDING, NOTIF_SENT, NOTIF_FAILURE, NOTIF_SUPPRESSED_OPT_OUT). Le reste des familles d'événements continue de passer par les stubs jusqu'à ce que chaque module l'active. |
AUDIT-002 |
| v0.1.0 | Stubs des compliance reports (§A.3 AUDIT-US-04 + AUDIT-US-09 du V1 PRD) : endpoints + schémas de sortie posés, génération réelle minimale (export CSV par tenant + par plage), pas d'UI dédiée. Alerting santé d'ingest dégradée (AUDIT-US-09) en mode stub log-only. | AUDIT-003 |
Verrouillé pour V0¶
- Outbox pattern Day 1 : tout producteur d'audit écrit dans la même transaction que l'action métier via
spring-modulith-events-jdbc. Pas de fire-and-forget, pas d'écriture asynchrone non transactionnelle. C'est un anchor architectural irréversible (D4 + D7 deprd-v0.md§ 5). - Schéma
audit_entryfigé Day 1 :id,tenant_id,occurred_at,actor_type,actor_id,event_type,event_payload (jsonb),correlation_id,tenant_db_profile. Append-only par convention applicative en v0.0.0 (la contrainte DB-level tamper-evidence de l'AUDIT-US-05 V1 arrive avec V1). - Tenant scoping strict : toute lecture (même les stubs de compliance report en v0.1.0) passe par le resolver tenant. Pas d'accès cross-tenant en V0, sauf futur
PLATFORM_ADMIN_QUERYqui reste hors V0. - Toutes les familles d'événements V1 PRD §18 ont leur stub câblé dès v0.0.0, même si l'émission réelle n'arrive que progressivement. Cela garantit que l'activation par module (AUTH, ING, CP, OP, TENANT, …) ne nécessite pas de migration de schéma a posteriori.
Hors V0 (renvoyé à V1 ou plus tard)¶
- Pas de purge, pas de politique de rétention active en V0. Tous les événements sont conservés indéfiniment. La rétention 10 ans formelle (AUDIT-US-07, memo F5
VÉRIFIÉ) et le soft-delete / archival sont reportés à V1. Conséquence opérationnelle : prévoir la croissance non bornée deaudit_entrydurant la phase V0 (volumétrie attendue limitée : démo Basile + pilote NSIA, cf.prd-v0.md§ 9). - Console AUDIT opérateur / admin (AUDIT-US-02, AUDIT-US-03, AUDIT-US-08) : entièrement reportée à V1.
- Export CSV / JSON pour régulateur ou tenant offboarding (AUDIT-US-04) : stub seulement en v0.1.0 ; format pleinement défini en V1.
- Pseudonymisation PII des payloads (AUDIT-US-06) : reportée à V1. En V0 les payloads sont écrits tels quels, sous la responsabilité du producteur ; tout PRD producteur (NOTIF, CP, ING) doit minimiser ce qu'il met dans
event_payloaddès v0.0.0. - Contrainte DB-level append-only / tamper-evidence (AUDIT-US-05) : reportée à V1. En V0, append-only reste une convention applicative.
- Alerting santé (AUDIT-US-09) : stub log-only en v0.1.0 ; alerting actif en V1.
Aucun statut
INCERTAIN - avocat requisouPRÉLIMINAIREdu memo F5 n'est levé par le découpage V0. Les notes[LEGAL-REVIEW]sur les durées applicatives restent en vigueur partout où une durée est figée.
Part A - Product Requirements¶
A.1 Executive Summary¶
The audit-log module is the immutable trail of evidence that lets Papillon Collection Solution defend every business action it took on behalf of a brokerage cabinet - to a regulator, to a lawyer, to a disputing customer, or to a tenant during offboarding. It is consumed by every other module as a durable side-effect; it is read by platform admin only in V1 (no operator UI, no customer surface).
The narrow V1 problem statement:
When a regulator (CIMA/ARTCI), a lawyer, or a tenant asks "show me everything that happened in this renewal cycle / for this assuré / on this signed link", platform admin must be able to produce a reliable chronology of events from a single store, queryable by case, exportable in a verifiable format, retained for at least ten years, and provably not silently lost between business commit and audit persistence.
V1 deliberately keeps the surface small: no tenant viewer, no cryptographic chain, no per-tenant data-residency for the audit log itself, no granular per-field old/new diffing on operator edits. Those are V2 work, with split conditions captured in §A.6 + §C.5.
A.2 User Personas¶
| Persona | Side | Scope | V1 |
|---|---|---|---|
| Platform admin | [ADMIN] |
Papillon staff. Sole reader of the audit log in V1. Runs filtered queries, by-case lookups, exports for regulator/lawyer/dispute. Investigates ingest-failure alerts. | V1 |
| Producer modules | [BACKEND] |
TENANT, AUTH, ING, OP, CP, PAY, NOTIF (V1). Each emits audit entries as a guaranteed side-effect of their business writes, against a stable contract owned by AUDIT. | V1 |
| Compliance / legal | [ADMIN] |
Founder + retained lawyer. Consumers of exports (JSONL + manifest). No direct query UI in V1. Triggers ad-hoc exports through platform admin. | V1 |
| Regulator (CIMA / ARTCI) | external | Recipient of exports on demand. Never directly accesses the platform. | V1 |
| Tenant admin | [OPERATOR] |
Out of V1 scope. No self-service audit viewer. V2 will revisit (curated view per AC discussion below). | V2 |
| End customer (assuré) | [CUSTOMER] |
Out of V1 scope. ARTCI subject-access requests are handled manually by platform admin via export, not through a customer-facing surface. | V2 |
A.3 User Stories (with Given/When/Then ACs)¶
AUDIT-US-01 [V1] [BACKEND] - Guaranteed audit on committed business actions¶
As a producer module (TENANT/AUTH/ING/OP/CP/PAY/NOTIF) I want every committed business action to result in an audit entry that is guaranteed to be eventually persisted, even on JVM crash between business commit and audit write So that the audit log can be claimed as a complete proof base under V1 PRD §16.5.
ACs:
- AC-01.1 - Zero-loss on committed actions: GIVEN a producer commits a business write that belongs to the V1 audit catalog (§B.1), WHEN any combination of {JVM crash, network partition to AUDIT store, AUDIT store outage} occurs immediately after the commit, THEN the audit entry MUST be persisted no later than freshness_p95 + 60s after the producer has recovered. No committed business action may exist without its audit entry indefinitely.
- AC-01.2 - Producer latency budget: GIVEN any producer endpoint, WHEN it records an audit entry as part of its happy-path response, THEN the added p95 server-side latency attributable to audit recording MUST NOT exceed 20 ms measured at the producer's HTTP boundary.
- AC-01.3 - Audit failure does not roll back business: GIVEN AUDIT ingest is degraded, WHEN a producer cannot durably persist its audit entry within the p95 budget, THEN the producer MUST allow the business action to commit, MUST raise a high-severity operational alert (AUDIT_INGEST_DEGRADED), and MUST queue the entry for reconciliation. No business action is failed because of audit-side problems.
- AC-01.4 - Reconciliation job runs: GIVEN entries queued during a degradation window, WHEN AUDIT recovers, THEN a reconciliation job (interval ≤ 5 min) MUST drain the queue, persist the entries with their original business_committed_at timestamps, and emit AUDIT_RECONCILED with a count.
The mechanism (sync TX-write, transactional outbox, Spring application event with WAL, etc.) is left to the ARCHITECT - the PRD only pins the durability + latency + freshness contract.
AUDIT-US-02 [V1] [ADMIN] - Filtered query for incident investigation¶
As a platform admin I want to query the audit log by date range, tenant, agency, event type, and actor So that I can investigate an incident or pull a slice of activity in seconds.
ACs:
- AC-02.1 - Mandatory filters: GIVEN the platform-admin query API, WHEN no tenant_id is provided AND no actor_user_id is provided, THEN the query MUST be rejected (no unbounded cross-tenant scans). One of the two MUST be supplied.
- AC-02.2 - Cross-filter ANDing: WHEN multiple filters are supplied, THEN they combine with logical AND. Empty filter values are ignored.
- AC-02.3 - Pagination + ordering: GIVEN any query, THEN results are returned ordered by business_committed_at DESC, paginated at 200 rows max per page; a stable cursor is returned for the next page.
- AC-02.4 - Query latency: GIVEN an audit_log of up to 10 M rows, WHEN a filtered query supplies a tenant_id and a 30-day window, THEN p95 response time ≤ 2 s.
AUDIT-US-03 [V1] [ADMIN] - By-case lookup for §16.5 reconstruction¶
As a platform admin I want to retrieve every audit event tied to a single case, identified by customer, contract, OR signed-link So that I can hand a coherent chronology to a lawyer/regulator without joining JSONL exports by hand.
ACs:
- AC-03.1 - Three accepted case keys: WHEN I query by customer_id, contract_id, OR signed_link_id, THEN AUDIT returns every entry whose payload references that key, regardless of producer.
- AC-03.2 - Correlation chain: WHEN any returned entry carries a correlation_id, THEN the response MUST also include all other entries (across producers) sharing that same correlation_id, even if their case key differs (e.g. a NOTIF entry that fed into a CP session that fed into a PAY confirmation).
- AC-03.3 - Chronological grouping: WHEN results are returned, THEN they are grouped first by correlation_id, then ordered by business_committed_at within each group.
- AC-03.4 - Empty result is explicit: GIVEN no events match, WHEN I query, THEN the response is an empty array with HTTP 200 and a reason: "no_events_for_case_key" header - never a 404 (which would conflate "no events" with "key invalid").
AUDIT-US-04 [V1] [ADMIN] - Export for regulator / lawyer / tenant offboarding¶
As a platform admin I want to export a slice of the audit log as a verifiable file bundle So that I can hand it to a regulator, a lawyer, or a tenant during offboarding without anyone questioning its integrity.
ACs:
- AC-04.1 - Canonical format: WHEN I trigger an export, THEN AUDIT produces (a) a JSONL file with one event per line, UTF-8, sorted by business_committed_at ASC, and (b) a manifest.json containing {export_id, generated_at, requested_by, tenant_id, date_range, event_count, jsonl_sha256, schema_versions: [...]}.
- AC-04.2 - Integrity check: GIVEN an export bundle, WHEN any byte of the JSONL is altered, THEN re-computing sha-256 over the file MUST detect the alteration when compared against the manifest.
- AC-04.3 - Scope inputs: WHEN I trigger an export, THEN I MUST supply {tenant_id, date_range_start, date_range_end} and MAY supply {event_types[], correlation_id, case_key}. No export without a tenant_id.
- AC-04.4 - Performance budget: GIVEN an export of up to 100 000 events, WHEN I trigger it, THEN it MUST complete within 30 s and the bundle is delivered as a downloadable artifact (no in-memory render).
- AC-04.5 - Export is itself audited: WHEN an export completes, THEN AUDIT writes one self-event AUDIT_EXPORT_COMPLETED with {export_id, scope, event_count, jsonl_sha256, requested_by} (see AUDIT-US-08).
AUDIT-US-05 [V1] [BACKEND] - DB-level append-only tamper-evidence¶
As a Papillon I want the V1 audit log to be append-only at the database level So that no application bug, accidental script, or compromised application credential can rewrite history without producing a clear PostgreSQL error.
ACs:
- AC-05.1 - App role is INSERT-only: GIVEN the application's database role for audit_log, WHEN the role attempts UPDATE or DELETE, THEN PostgreSQL MUST reject the statement with permission denied.
- AC-05.2 - Read role is SELECT-only: GIVEN the platform-admin reader role, WHEN it attempts INSERT/UPDATE/DELETE on audit_log, THEN PostgreSQL MUST reject.
- AC-05.3 - DDL guard: WHEN a migration changes audit_log schema, THEN it MUST be reviewed in CR with the audit-schema label and signed off by the founder. Schema migrations are the only sanctioned path to alter the table; no application path exists.
- AC-05.4 - Honest scope of the claim: AUDIT does NOT defend against a malicious DBA or compromised infrastructure operator in V1. Tamper-evidence (hash chain / Merkle / WORM) is V2; this is documented in customer-facing positioning so the §16.5 claim is not over-stated.
AUDIT-US-06 [V1] [BACKEND] - Pseudonymized PII in audit payloads¶
As a Papillon I want every audit payload to store deterministic hashes of personal data instead of raw values, plus stable internal IDs So that the audit log honors law n°2013-450 minimization while still allowing case reconstruction.
ACs:
- AC-06.1 - Allowed PII columns: GIVEN any audit payload, WHEN it would carry personal data of an assuré (full name, phone, email, DOB, ID document number), THEN it MUST replace the raw value with hash_v1(value, salt_tenant) and MUST also carry the corresponding customer_id when known.
- AC-06.2 - Salt is per-tenant + server-secret: GIVEN the hashing key, THEN it MUST be a per-tenant salt held in platform-managed secret storage, never in source, never in any log line, never in any export. (V2 may add salt versioning to support deferred erasure - see OQ-02.)
- AC-06.3 - Operator PII is allowed at lower minimization: GIVEN audit entries about operators (login, logout, role change), THEN the actor_user_id (Keycloak sub) is stored in cleartext as a stable identifier; operator name/email are NOT stored in the payload (they are joinable from the AUTH module at read time when the operator still exists).
- AC-06.4 - IP and user-agent are PII but kept: GIVEN AUTH/CP/OP request events, THEN client_ip and user_agent are stored in cleartext (PII under ARTCI doctrine, but covered by 10-year retention rule and lawful-basis claim of CIMA §16.5 proof).
- AC-06.5 - Reconstruction works: GIVEN a candidate phone value, WHEN I hash it with salt_tenant and search the audit_log, THEN any matching event surfaces. Matching is exact, not fuzzy.
AUDIT-US-07 [V1] [BACKEND] - 10-year retention enforced¶
As a Papillon
I want audit entries to live at least 10 years from business_committed_at
So that CI fiscal retention (10y on FNE invoices and payment proofs) and CIMA proof reconstruction are uniformly satisfied across all event classes.
ACs:
- AC-07.1 - Floor is 10 years: GIVEN an audit entry, WHEN today < business_committed_at + 10 years, THEN it MUST NOT be deleted by any process.
- AC-07.2 - Tenant override only upward: GIVEN tenant-admin AC-09.1's audit_retention_years setting, WHEN the tenant sets it ≥ 10, THEN AUDIT honors the higher value; values < 10 are refused at write time by tenant-admin and never reach AUDIT.
- AC-07.3 - Retention applies even on tenant soft-delete: GIVEN a tenant has been soft-deleted (tenant-admin AC-05), WHEN the audit_log for that tenant is queried by platform admin, THEN it remains accessible until max(business_committed_at) + 10 years. Customer rows are purged; audit entries (pseudonymized) survive - see tenant-admin AC-05.3.
- AC-07.4 - No automatic 10y purge in V1: GIVEN an entry passes the 10-year mark, V1 does NOT automatically delete it; a manual purge tool gated on platform-admin + founder sign-off is V2 work. Storage is sized accordingly (§B.6).
AUDIT-US-08 [V1] [ADMIN] - Self-auditing platform admin actions¶
As a Papillon I want every platform-admin action that touches a tenant's audit data (queries, exports, schema migrations) to itself produce an audit entry So that the audit log defends the platform-admin role from accusations of silent data exfiltration.
ACs:
- AC-08.1 - Query is logged at session granularity: GIVEN a platform admin runs a filtered query (AUDIT-US-02) or by-case lookup (AUDIT-US-03), WHEN the response is delivered, THEN AUDIT writes one AUDIT_QUERY_EXECUTED event with {actor_user_id, filters, result_count, acting_on_tenant_id}. Repeated identical queries within 60 s are deduplicated to one entry to avoid noise.
- AC-08.2 - Export is logged at object granularity: GIVEN a platform admin triggers an export (AUDIT-US-04), WHEN the export completes, THEN AUDIT writes one AUDIT_EXPORT_COMPLETED event (see AC-04.5).
- AC-08.3 - Schema migrations are logged out-of-band: GIVEN a CR-approved migration runs against audit_log, WHEN it completes, THEN the deploy pipeline writes one AUDIT_SCHEMA_MIGRATED event with the migration ID and SHA. The application path cannot write this event; only the deploy pipeline can.
AUDIT-US-09 [V1] [ADMIN] - Health alerting on ingest degradation¶
As a Papillon (ops on call) I want to be paged when audit ingest is degraded, with enough context to act So that a silent audit outage cannot accumulate into a §16.5 proof gap.
ACs:
- AC-09.1 - High-severity alert: GIVEN AC-01.3 fires, THEN within 60 s a high-severity alert is delivered to the ops channel with {producer_module, error_class, queued_count, last_business_committed_at}.
- AC-09.2 - Backlog SLO: GIVEN AC-01.4 reconciliation, WHEN queued_count > 1000 OR oldest_queued_age > 30 min, THEN an additional escalation alert is raised.
- AC-09.3 - Recovery alert: WHEN reconciliation drains the queue to zero, THEN AUDIT emits AUDIT_RECONCILED and the alert is auto-resolved.
A.4 Non-Functional Requirements¶
| ID | NFR |
|---|---|
| NFR-01 | Durability: every committed business action in the V1 catalog (§B.1) MUST eventually have its audit entry persisted. Zero loss tolerated for committed actions. |
| NFR-02 | Producer latency: audit recording MUST add ≤ 20 ms p95 to producer endpoints, measured at the producer's HTTP boundary. |
| NFR-03 | Freshness: from business_committed_at, an entry MUST be queryable by platform admin within 5 s p95. |
| NFR-04 | Retention: ≥ 10 years from business_committed_at, uniform across all event classes. No automatic purge in V1. |
| NFR-05 | Query latency: filtered query (AUDIT-US-02) p95 ≤ 2 s on up to 10 M rows over a 30-day tenant-scoped window. |
| NFR-06 | Export performance: bundle of ≤ 100 000 events delivered within 30 s (AUDIT-US-04). |
| NFR-07 | Tamper-evidence (V1): DB-level append-only via role grants. No hash chain, no Merkle root, no WORM. V2 will revisit. |
| NFR-08 | Tenant isolation: every audit row carries tenant_id; queries enforce tenant scoping by default; only platform-admin self-events may carry acting_on_tenant_id distinct from the actor's home tenant. |
| NFR-09 | PII minimization: assuré PII never stored in raw form in payloads; pseudonymization via per-tenant salt. |
| NFR-10 | Observability: ingest queue depth, oldest queued age, reconciliation lag, query latency, export volume MUST be metrics surfaced to ops dashboards. |
| NFR-11 | Single source of truth: the audit_log table is the canonical store. JSONL exports are derivatives; if they disagree with the table, the table wins. |
A.5 MVP Scope (V1) vs V2 / V3¶
In scope V1¶
- Event catalog: V1 PRD §18 events + tenant-admin lifecycle + AUTH detail (signed-link issued/verified, operator login/logout, role change, password reset) + AUDIT self-events (query, export, schema migration). See §B.1.
- Mandatory fields: §18 minimum +
tenant_id,agency_id,country_code,correlation_id,client_ip,user_agent,event_schema_version. See §B.5. - Pseudonymized assuré PII (deterministic hash + per-tenant salt).
- Platform-admin filtered query API (AUDIT-US-02).
- Platform-admin by-case lookup (customer / contract / signed-link) (AUDIT-US-03).
- Platform-admin JSONL+manifest export (AUDIT-US-04).
- DB-level append-only (AUDIT-US-05).
- 10-year uniform retention floor (AUDIT-US-07).
- Self-auditing platform admin actions (AUDIT-US-08).
- Ingest-degradation alerting + reconciliation (AUDIT-US-09).
- Storage in the platform-shared DB regardless of tenant DB profile (V1 simplicity choice).
Out of scope V1 - explicit V2/V3 boundary¶
| Item | Version |
|---|---|
| Tenant-side audit viewer (curated subset visible to tenant admin in operator console) | V2 |
| Cryptographic tamper-evidence (hash chain, Merkle root, signed daily roots, MinIO object-lock WORM archive) | V2 |
| BYO-DB data residency for the audit log (audit rows in tenant DB; index in platform DB; or hybrid index+payload split) | V2 |
| Granular OP detail on operator edits (per-field old/new diffing on contract status, customer record, campaign cancellation) | V2 |
| Data-export and bulk-list-view auditing (e.g. operator clicked CSV export of customer list) | V2 |
Real-time pattern alerting (e.g. 5 failed AUTH attempts in 60 s on the same customer_id) - V1 captures the events; alerting on them is V2 |
V2 |
| Customer-side ARTCI subject-access portal (assuré reads what the platform recorded about them) | V2 |
| Automatic 10-year purge tool | V2 |
| Salt rotation + cryptographic-erasure ("forget" by burning the salt) | V2 |
| Cross-tenant analytics and group/holding consolidated view | V2/V3 |
| BILL fiscal events in the audit catalog (FNE invoice issued, FNE rejection, etc.) | OQ-01 - see §A.6 |
A.6 Open Questions¶
| ID | Question | Owner | V1 blocker |
|---|---|---|---|
| OQ-01 | Should BILL fiscal events (FNE invoice issued, FNE rejection, FNE retry, FNE archive) be in the V1 catalog? Not selected in interview but compliance-sensitive (CI fiscal retention is the same 10-year floor). | Founder + LEGAL_AUDITOR | No, but creates a fiscal audit gap if deferred |
| OQ-02 | Erasure-right policy: when an assuré exercises law n°2013-450 erasure, what does AUDIT do? V1 ships with refuse, retain pseudonymized 10y on legal-obligation basis but lawyer must confirm. | LEGAL_AUDITOR (re-research) | Soft blocker - V1 may ship pre-confirmation |
| OQ-03 | "Manual modification of a file" (V1 PRD §18) granularity: file-level only (in-V1 catalog) or per-field old/new (deferred to V2)? V1 ships with file-level. | Founder | No |
| OQ-04 | Alert routing for AUDIT_INGEST_DEGRADED: who is paged in V1 (single ops contact, on-call rotation, founder)? Implementation needs a concrete recipient. |
Founder | Yes - AC-09.1 needs an address |
| OQ-05 | Correlation-id origin: who generates correlation_id for a renewal cycle - NOTIF (on outbound notification), CP (on link creation), or upstream (on campaign validation)? Needs ARCHITECT alignment with OP/NOTIF/CP. |
ARCHITECT (cross-module) | Yes - affects AUDIT-US-03.2 |
| OQ-06 | Does platform-admin self-auditing extend to schema reads (DB-level SELECT outside the application) or only to application-mediated reads? V1 captures application-mediated only. | Founder | No |
A.7 Temporarily Validated¶
The legal audit Legal-Compliance-audit.md reports RE-RESEARCH NEEDED, and CRITICAL gap #2 ("audit-log retention period and integrity requirements: no duration is given; no integrity rule (append-only, signed, hashed) is specified; no list of mandatory fields") directly bears on this module. The following V1 assumptions are therefore made temporarily, pending re-researched primary sources:
| ID | Assumption | Audit gap reference |
|---|---|---|
| TV-01 | A 10-year retention floor across all event classes satisfies CIMA + CI fiscal proof obligations and ARTCI minimization. (Uniform rule pending lawyer pinning.) | Audit gap CRITICAL #2 + HIGH #11 (retention periods). |
| TV-02 | DB-level append-only (no UPDATE/DELETE grants on the application role) is acceptable tamper-evidence for V1 and consistent with §16.5 probative claim. | Audit gap CRITICAL #2 (integrity rule). |
| TV-03 | Refusing erasure on the audit log (under a legal-obligation lawful basis), retaining pseudonymized rows for 10 years, is compatible with law n°2013-450. | Audit gap CRITICAL #2 + HIGH #11 (lawful basis per feature). |
| TV-04 | Pseudonymization with deterministic hashing + per-tenant server-secret salt is sufficient to meet ARTCI minimization for assuré PII in audit payloads. | Audit gap HIGH #8 (lawful basis per V1 feature). |
| TV-05 | Storing audit data in the platform-shared DB even for BYO-DB tenants is an acceptable V1 trade-off and does not invalidate the BYO-DB sovereignty pitch (V2 will revisit). | Audit gap CRITICAL #1 (positioning). |
| TV-06 | Mandatory fields beyond §18 (tenant_id, agency_id, country_code, correlation_id, client_ip, user_agent, event_schema_version) are necessary and sufficient for §16.5 reconstruction. |
Audit gap CRITICAL #2 (mandatory field list). |
Part B - Functional Specifications¶
B.1 V1 Event Catalog (canonical)¶
Every event MUST be one of the types below. A type that is not in this catalog MUST NOT be written; the architect's API rejects unknown event_type values. Adding a type is a PRD-level change.
| Family | event_type | Producer | Trigger |
|---|---|---|---|
Naming convention (decided 2026-05-21, audit-catalog-rework): <MODULE>_<EVENT> UPPER_SNAKE. The module prefix must match the canonical AUDIT producer column. Adding an event_type is a PRD-level change. The catalog admits all events listed below. |
| Family | event_type | Producer | Trigger |
|---|---|---|---|
| Tenant | TENANT_ONBOARDED |
TENANT | Tenant transitions PENDING → ACTIVE (tenant-admin AC-02.3). |
TENANT_SUSPENDED |
TENANT | Tenant suspended (tenant-admin AC-04.2). | |
TENANT_REACTIVATED |
TENANT | Tenant reactivated (tenant-admin AC-04.3). | |
TENANT_SOFT_DELETED |
TENANT | Tenant transitions to DELETED (tenant-admin AC-05.1). | |
TENANT_PURGED |
TENANT | Customer + contract PII purge completed. | |
AGENCY_DISABLED |
TENANT | Agency status flipped to INACTIVE. | |
TENANT_SETTINGS_CHANGED |
TENANT | One per changed key (tenant-admin AC-09.2). | |
| Auth | AUTH_OPERATOR_LOGIN |
AUTH | Operator authenticates against Keycloak. |
AUTH_OPERATOR_LOGOUT |
AUTH | Operator session ends (explicit or idle timeout). | |
AUTH_OPERATOR_ROLE_CHANGED |
AUTH | Operator role granted/revoked. | |
AUTH_OPERATOR_PASSWORD_RESET |
AUTH | Password reset flow completed. | |
AUTH_OPERATOR_SESSION_KILLED |
AUTH | Forced session termination (e.g. TENANT_SUSPENDED cascade, manual revocation). Replaces legacy operator_session_force_killed. |
|
AUTH_OPERATOR_PERMANENTLY_LOCKED |
AUTH | Operator account permanently locked after failed-MFA cycles. Replaces legacy AUTH_PERMANENTLY_LOCKED. |
|
AUTH_LINK_ISSUED |
AUTH | Signed customer link generated (carries signed_link_id, customer_id, contract_id, expiry). Replaces legacy signed_link_issued. |
|
AUTH_LINK_CLICKED |
AUTH | Signed link physical click (channel marker + IP + UA captured). Precedes verification. Replaces legacy signed_link_clicked. |
|
AUTH_LINK_VERIFIED |
AUTH | Customer presents signed link and passes verification (V1 PRD §18 "authentication success"). | |
AUTH_LINK_REJECTED |
AUTH | Signed link presented but failed (signature mismatch, attempt-limit, malformed). One entry per attempt. | |
AUTH_LINK_REJECTED_EXPIRED |
AUTH | Click on a link past its 72h TTL. | |
AUTH_LINK_REJECTED_REVOKED |
AUTH | Click on a link that was revoked (re-issuance, all contracts paid, manual revocation). | |
AUTH_LINK_PERMANENTLY_REVOKED |
AUTH | Link transitions to permanently revoked after 3 lockout cycles (15 cumulative failures). Replaces legacy signed_link_permanently_revoked. |
|
AUTH_AGENCY_DISABLED_ACK |
AUTH | AUTH acknowledges AgencyDisabled (no scope mutation per R-OP-018). Replaces legacy agency_disabled_acknowledged. |
|
AUTH_CROSS_TENANT_DENIED |
AUTH | Operator attempts an action outside their agency_scope. Replaces legacy cross_tenant_access_denied. |
|
| Ingest | ING_UPLOAD_RECEIVED |
ING | New file uploaded (UI v0.0.3+) or picked up from S3 (cron v0.0.0+). |
ING_PARSE_FAILED |
ING | File parsing rejected before staging (invalid format, unreadable encoding). | |
ING_STAGED |
ING | File staged successfully (rows materialized; may carry STAGED_CLEAN or STAGED_WITH_ANOMALIES). |
|
ING_CLEAN |
ING | All anomalies resolved on a staged import. | |
ING_ROW_CORRECTED |
ING | Operator-corrected a row inline in the correction workshop. | |
ING_ABANDONED |
ING | Operator cancels a staged import before commit. | |
ING_COMMITTED |
ING | Staged import promoted to live (rows imported into contract/customer tables). Successor to legacy ING_FILE_IMPORTED. |
|
ING_STAGING_EXPIRED |
ING | Daily sweep purges a 7-day-old staged import not committed. | |
ING_CONTRACT_CREATED |
ING | A new contract row was created by import commit. | |
ING_CONTRACT_UPDATED |
ING | An existing contract row was updated by import commit. | |
ING_CUSTOMER_CREATED |
ING | A new customer row was created by import commit. | |
ING_CUSTOMER_UPDATED |
ING | An existing customer row was updated by import commit. | |
ING_DOB_DRIFT_DETECTED |
ING | DOB drift detected against previous import for the same customer. | |
ING_FILE_MANUAL_MODIFICATION |
OP/ING | Manual edit on an imported file (V1 PRD §18 "manual modification of a file"). File-level granularity in V1; per-field is V2. | |
| Operator | OP_CAMPAIGN_VALIDATED |
OP | Campaign validated for sending (V1 PRD §18 "campaign validation"). Replaces legacy CAMPAIGN_VALIDATE. |
OP_USER_CREATED |
OP | Cabinet admin creates a user (replaces legacy USER_CREATE). |
|
OP_USER_ROLE_CHANGED |
OP | Cabinet admin changes a user's role (replaces legacy USER_ROLE_CHANGE). |
|
OP_USER_DEACTIVATED |
OP | Cabinet admin deactivates a user (replaces legacy USER_DEACTIVATE). |
|
OP_PARAMETER_CHANGED |
OP | Practice parameter change (V1 PRD §18 "parameter change"; replaces legacy CABINET_PARAM_CHANGE). Distinct from TENANT_SETTINGS_CHANGED (which is platform-bounded). |
|
| Customer | CP_LINK_OPENED |
CP | Signed link opened (V1 PRD §18 "link opening"). Precedes AUTH_LINK_VERIFIED. |
CP_RECU_DOWNLOADED |
CP | Customer downloads a quittance/reçu PDF from the post-payment screen (replaces legacy customer_recu_downloaded). |
|
| Notif | NOTIF_SENDING |
NOTIF | Notification dispatch attempted (V1 PRD §18 "notification sending"). |
NOTIF_SENT |
NOTIF | Notification dispatch confirmed delivered by provider webhook. | |
NOTIF_FAILURE |
NOTIF | Notification dispatch failed (V1 PRD §18 "notification failure"). | |
NOTIF_SUPPRESSED_OPT_OUT |
NOTIF | Send refused because recipient previously opted out (STOP). | |
NOTIF_OPTED_OUT |
NOTIF | Recipient opt-out recorded (STOP inbound or cabinet-admin UI). | |
NOTIF_SUPPRESSION_REMOVED |
NOTIF | Suppression lifted (re-opt-in). | |
NOTIF_PAYMENT_RECEIPT_DISPATCHED |
NOTIF | Transactional payment-receipt push dispatched (NOTIF-US-11, template cp_payment_receipt). |
|
NOTIF_PAYMENT_RECEIPT_FAILED |
NOTIF | Transactional payment-receipt push failed. | |
NOTIF_CAP_EXCEEDED |
NOTIF | Per-tenant daily cap reached; subsequent sends blocked. | |
NOTIF_KILL_SWITCH_ENGAGED |
NOTIF | Kill-switch engaged (platform-admin or runaway-detector). | |
NOTIF_KILL_SWITCH_RELEASED |
NOTIF | Kill-switch released. | |
NOTIF_MANUAL_RETRY |
NOTIF | Operator triggers a manual retry on a failed send. | |
NOTIF_TRANSACTIONAL_OVER_CAP |
NOTIF | Transactional dispatch ignored cap (per policy) and is logged for review. | |
NOTIF_WEBHOOK_REJECTED |
NOTIF | Inbound provider webhook rejected (signature mismatch, malformed). | |
| Payment | PAY_INITIATED |
PAY | Payment initiation (V1 PRD §18 "payment initiation"). |
PAY_CONFIRMED |
PAY | Payment confirmation (V1 PRD §18 "payment confirmation"). | |
PAY_FAILED |
PAY | Payment failure (V1 PRD §18 "payment failure"). | |
PAY_RECONCILED |
PAY | Payment reconciliation completed (webhook + polling agreement, or manual reconciliation). | |
| Platform-Admin | PLATFORM_ADMIN_QUERY |
TENANT/AUDIT | Platform admin executes a cross-tenant query (TENANT-PRD §B.5). Carries SQL identifier executed. |
PLATFORM_ADMIN_TENANT_MUTATION |
TENANT | Platform admin performs a cross-tenant mutating action (suspend, reactivate, soft-delete, settings override). | |
| AUDIT self | AUDIT_QUERY_EXECUTED |
AUDIT | Platform-admin filtered query / by-case lookup (AC-08.1). |
AUDIT_EXPORT_COMPLETED |
AUDIT | Platform-admin export completed (AC-08.2). | |
AUDIT_INGEST_DEGRADED |
AUDIT | Ingest degradation detected (AC-09.1). | |
AUDIT_RECONCILED |
AUDIT | Reconciliation job drained the queue (AC-01.4). | |
AUDIT_SCHEMA_MIGRATED |
deploy | Migration executed against audit_log (AC-08.3). Written by the deploy pipeline, not the application. |
Rule R-CAT-001: any producer PRD that cites an event_type MUST use the exact name listed above. The AUDIT API rejects unknown event_type values at the boundary.
B.2 State Machine - Audit entry lifecycle¶
stateDiagram-v2
[*] --> PRODUCER_PENDING: producer commits business write + outbox row
PRODUCER_PENDING --> INGESTED: AUDIT consumer drains outbox + INSERTs row
PRODUCER_PENDING --> RECONCILE_QUEUE: AUDIT ingest degraded (AC-01.3)
RECONCILE_QUEUE --> INGESTED: reconciliation job drains queue (AC-01.4)
INGESTED --> [*]: terminal in V1 (no UPDATE, no DELETE, no REDACTED state)
Two notes:
- The PRD does NOT specify whether PRODUCER_PENDING is a transactional outbox row, a Spring application event, or another shape - the ARCHITECT picks the mechanism that meets NFR-01..03.
- A V2 state REDACTED (cryptographic erasure via salt-burn) is anticipated by AC-06.2 but explicitly out of V1.
B.3 Decision Table - required fields per event family¶
Y = required; N = not applicable; O = optional (carry if known).
| event family | tenant_id | agency_id | country_code | actor_user_id | customer_id | contract_id | signed_link_id | correlation_id | client_ip | user_agent | result |
|---|---|---|---|---|---|---|---|---|---|---|---|
| TENANT_* | Y | O | Y | Y (admin) | N | N | N | N | Y | Y | Y |
| AGENCY_DISABLED | Y | Y | Y | Y (admin) | N | N | N | N | Y | Y | Y |
| AUTH_OPERATOR_* | Y | O | Y | Y | N | N | N | O | Y | Y | Y |
| AUTH_LINK_ISSUED | Y | Y | Y | Y (operator) | Y | Y | Y | Y | N | N | Y |
| AUTH_LINK_VERIFIED | Y | Y | Y | N (system) | Y | Y | Y | Y | Y | Y | Y |
| AUTH_LINK_REJECTED | Y | Y | Y | N (system) | O | O | O | O | Y | Y | Y |
| ING_* | Y | Y | Y | Y (operator) | N | N | N | O | Y | Y | Y |
| OP_CAMPAIGN_VALIDATED | Y | Y | Y | Y | N | N | N | Y | Y | Y | Y |
| OP_PARAMETER_CHANGED | Y | O | Y | Y | N | N | N | N | Y | Y | Y |
| CP_LINK_OPENED | Y | Y | Y | N (system) | Y | Y | Y | Y | Y | Y | Y |
| NOTIF_* | Y | Y | Y | N (system) | Y | O | O | Y | N | N | Y |
| PAY_* | Y | Y | Y | N (system) | Y | Y | O | Y | O | O | Y |
| AUDIT_QUERY_EXECUTED | O | N | O | Y (admin) | N | N | N | N | Y | Y | Y |
| AUDIT_EXPORT_COMPLETED | Y | N | Y | Y (admin) | N | N | N | N | Y | Y | Y |
| AUDIT_SCHEMA_MIGRATED | N | N | N | N (deploy) | N | N | N | N | N | N | Y |
B.4 Functional Rules¶
FR-01 - Catalog gate
- IF event.event_type is not in §B.1, THEN reject the write at the AUDIT boundary with a structured error; do NOT silently drop. The producer's failure-handling path runs.
FR-02 - Mandatory fields
- IF any field marked Y in §B.3 for the event's family is missing or null at write time, THEN reject the write at the AUDIT boundary; the producer is responsible for failing closed.
FR-03 - Pseudonymization at write
- IF the payload would carry a raw assuré PII value (phone, name, email, DOB, ID document number), THEN AUDIT replaces it with hash_v1(value, salt[tenant_id]) before persisting. AUDIT MUST refuse to persist a raw PII string in any payload field; producers are responsible for hashing before send. Best-effort defensive scan at the AUDIT boundary catches bugs.
FR-04 - Tenant scoping on reads
- IF a query lacks both tenant_id and actor_user_id filters, THEN reject (AC-02.1).
- IF a query supplies tenant_id, THEN the result set is filtered to entries with that tenant_id OR with acting_on_tenant_id = tenant_id (covers self-events about platform admins acting on this tenant).
FR-05 - Correlation propagation
- IF an event carries correlation_id AND another event with the same correlation_id exists, THEN AUDIT-US-03.2 includes both in case-key responses regardless of differing case keys.
FR-06 - Reconciliation idempotence
- IF a reconciliation job replays an outbox entry that was already INGESTED (e.g. crash mid-way), THEN AUDIT detects via (producer_module, producer_outbox_id) uniqueness and skips silently. No duplicate audit row.
FR-07 - 10-year retention enforcement
- IF a job/process attempts DELETE on audit_log, THEN PostgreSQL rejects (AC-05.1). This is the only enforcement in V1; no time-window check is required because no purge tool exists in V1.
FR-08 - Export integrity
- IF an export bundle is produced, THEN manifest.json MUST carry jsonl_sha256 computed over the JSONL bytes. Re-computation MUST match. (AC-04.2)
FR-09 - Self-event dedup
- IF a platform admin runs the same query (same filters) twice within 60 s, THEN AUDIT writes ONE AUDIT_QUERY_EXECUTED event with an incremented query_count rather than two events. (AC-08.1)
FR-10 - Ingest-degradation classification
- IF a producer's audit recording exceeds the 20 ms p95 budget for ≥ 5 consecutive entries OR the queued-count rises above 100 within a 1-minute window, THEN producer-side health flips to DEGRADED and AUDIT_INGEST_DEGRADED is emitted.
B.5 Data Requirements¶
The canonical V1 entity is audit_log. All columns NOT NULL unless marked optional; the field list aligns with V1 PRD §27 ("Audit log") and §18 ("logs must include at least: event identifier, timestamp, initiating user or system, file concerned, result").
| Column | Type | Notes |
|---|---|---|
event_id |
UUID v7 | PK. Time-ordered for index locality. |
event_type |
TEXT | One of §B.1. |
event_schema_version |
SMALLINT | Per-event_type payload version. Starts at 1 for V1. |
business_committed_at |
TIMESTAMP WITH TIME ZONE | Producer's commit time. Source-of-truth timestamp for ordering and retention. |
audit_persisted_at |
TIMESTAMP WITH TIME ZONE | When AUDIT INSERTed. audit_persisted_at - business_committed_at is the freshness metric. |
tenant_id |
UUID | NOT NULL except for AUDIT_SCHEMA_MIGRATED. Indexed. |
acting_on_tenant_id |
UUID | Optional. Set when actor is platform admin acting on a tenant. Indexed. |
agency_id |
UUID | Optional per §B.3. |
country_code |
CHAR(2) | Snapshotted from tenant at event time (denormalized for export self-containment). |
producer_module |
TEXT | One of {TENANT, AUTH, ING, OP, CP, PAY, NOTIF, AUDIT, deploy}. |
producer_outbox_id |
TEXT | Optional in V1, populated when an outbox is used. Composite UNIQUE with producer_module for FR-06. |
actor_user_id |
TEXT | Keycloak sub for operator/admin actors; NULL for system actors. |
correlation_id |
UUID | Optional per §B.3. Indexed. |
customer_id |
UUID | Optional per §B.3. Indexed. |
contract_id |
UUID | Optional per §B.3. Indexed. |
signed_link_id |
UUID | Optional per §B.3. Indexed. |
result |
TEXT | One of SUCCESS / FAILURE / INFO. |
client_ip |
INET | Optional per §B.3. |
user_agent |
TEXT | Optional per §B.3. Truncated to 500 chars. |
payload |
JSONB | Event-specific structured payload. PII pseudonymized per FR-03. |
Indexes (V1):
- (tenant_id, business_committed_at DESC) - primary query path (AUDIT-US-02).
- (correlation_id) - by-case lookup.
- (customer_id) partial WHERE NOT NULL - by-case lookup.
- (contract_id) partial WHERE NOT NULL - by-case lookup.
- (signed_link_id) partial WHERE NOT NULL - by-case lookup.
- (actor_user_id, business_committed_at DESC) - operator/admin activity queries.
- (producer_module, producer_outbox_id) UNIQUE, partial WHERE producer_outbox_id NOT NULL - FR-06 idempotence.
Storage sizing (NFR-04 baseline): assume avg 1.5 KB per row including indexes; 2 M events/year/tenant on a moderate cabinet; 10-year horizon ⇒ ~30 GB per tenant per decade. Platform-DB sizing planning falls under the ARCHITECT.
B.6 Multi-Tenant Implications¶
- Shared DB / shared schema (default):
audit_loglives in the platform DB; every row carriestenant_id. Application enforces tenant scoping at the service boundary (FR-04). - BYO-DB: V1 stores audit rows in the platform DB regardless. The BYO-DB tenant's business data lives in their DB; their audit data lives on platform infrastructure. This is an explicit V1 trade-off (TV-05) documented in the BYO-DB onboarding contract; tenant-admin's TENANT-US-03 must surface this to the platform admin at provisioning. V2 will revisit (sovereignty options 2/3 of the round-1 interview).
- Country-profile:
country_codeis snapshotted on each row from the tenant at write time. This makes exports self-contained: an exported event for a CI tenant carriesCIin cleartext, no join required. - Group / holding (V2): AUDIT does not know about groups today. V2 will add a
group_idsnapshot column or compute group-level views via a join on tenant; either path is non-breaking because the V1 schema does not assume "one tenant per group".
B.7 Cross-Module Dependencies¶
AUDIT consumes (inputs):
- TENANT lifecycle events (per tenant-admin PRD AC-02.3, AC-04.2/3, AC-05.1, AC-05.2/3, AC-08.2, AC-09.2). Already binding contract.
- AUTH events (link issuance/verification/rejection, operator session lifecycle, role change, password reset).
- ING events (file import, import-error detection, manual file modification at file granularity in V1).
- OP events (campaign validation, parameter change).
- CP events (link opening).
- NOTIF events (notification sending, failure).
- PAY events (initiation, confirmation, failure).
- Deploy pipeline (AUDIT_SCHEMA_MIGRATED).
AUDIT exposes (outputs):
- Internal HTTP/RPC API for platform-admin queries (AUDIT-US-02, US-03).
- Export endpoint producing JSONL + manifest (AUDIT-US-04).
- Operational metrics (queue depth, freshness lag, query latency, export volume).
- High-severity alerts (AUDIT_INGEST_DEGRADED, AUDIT_RECONCILED).
AUDIT does NOT depend on: - BILL (V1 - see OQ-01). - Customer-portal user-facing surface. - Tenant-admin operator UI for read access (V2).
Contract version: every producer must align on the V1 catalog (§B.1) and field rules (§B.3). A producer adding an event_type requires an AUDIT PRD update first (FR-01).
Part C - Constraints & Boundaries¶
C.1 Regulatory Compliance¶
- CIMA Reg. 01-24 §16.5 proof obligation: AUDIT is the chosen artifact to satisfy "reconstruct the chronology of events of a renewal cycle (notification → authentication → payment) from the audit log" (V1 PRD §16.5). Article-level anchoring is missing from the legal memo (audit gap CRITICAL #2); the §B.1 catalog + §B.3 mandatory fields are designed to cover the reconstruction surface even before the lawyer pins the article.
- CI law n°2013-450 + ARTCI:
- Minimization is honored by pseudonymization (FR-03, AC-06).
- Lawful basis for keeping audit data 10 years across erasure requests: legal-obligation (CIMA proof + CI fiscal). TV-03; lawyer must confirm.
- Subject-access requests are handled by platform-admin export (AUDIT-US-04) in V1; no customer-facing portal until V2.
- DPO / correspondant à la protection des données: not addressed in this module; tenant-admin scope.
- CI fiscal / DGI-FNE: AUDIT does not itself emit FNE invoices (BILL does). BILL fiscal events are NOT in the V1 catalog (OQ-01); deferring may create an ARTCI/DGI audit gap if a fiscal dispute arises during V1 pilot. Recommend founder + lawyer revisit before launch.
- Cross-border data flow: AUDIT data is stored in CI-region infrastructure (deploy contract). No outbound transfer of audit data in V1.
C.2 Localization¶
- Audit data is internal in V1. No customer-facing French copy required.
- Timestamps stored in UTC; rendered in
Africa/Abidjanfor any operator-side display (V2). - Filenames in exports are French:
audit_<tenant_short_name>_<YYYY-MM-DD>_<YYYY-MM-DD>.jsonlandmanifest.json. Manifest fields are English (interoperability with regulator tooling). - Currency / phone / date-format: AUDIT does not transform - it stores values as the producer sent them (already normalized upstream).
C.3 Security¶
- Tenant isolation: NFR-08. Every audit row carries
tenant_id; query API rejects unscoped reads (AC-02.1, FR-04). - Append-only at DB level: AC-05. Application role has no UPDATE/DELETE grants. Deploy pipeline is the only path to alter the schema (AC-08.3).
- Pseudonymization secret management: per-tenant salts in platform-managed secret storage. Salt MUST NEVER appear in any log line, exception trace, error envelope, audit payload, or export.
- PII in cleartext exception:
client_ipanduser_agentare stored cleartext (AC-06.4) under TV-04 lawful basis. Re-evaluate if lawyer re-research changes the position. - Encryption in transit: all producer→AUDIT and admin→AUDIT calls run over TLS (platform-default).
- Encryption at rest: PostgreSQL volume-level encryption (deploy-default). No additional column-level encryption in V1; the pseudonymization layer is the V1 compensating control for assuré PII.
- Self-auditing: AUDIT-US-08 - every read/export by platform admin is logged.
- Honest scope: AUDIT does NOT defend against a malicious DBA in V1 (AC-05.4). Tamper-evidence is V2.
C.4 Connectivity¶
- AUDIT is a backend module with no customer-facing surface in V1 - V1 PRD §30 low-bandwidth budgets do NOT apply.
- AUDIT MUST tolerate transient store outages without losing data (NFR-01, AC-01.3, AC-01.4). Reconciliation queue lives producer-side; AUDIT recovery drains.
- Operator / platform-admin queries (V2 tenant-admin viewer notwithstanding) are online-first.
C.5 Out of Scope V1 (explicit boundaries)¶
- No tenant-side audit viewer; no operator-console "Journal d'audit" screen. Platform admin reads via internal tooling only in V1.
- No cryptographic tamper-evidence (hash chain, Merkle root, signed daily roots, MinIO WORM).
- No tenant-DB residency for audit data (BYO-DB tenants' audit data lives on platform DB - TV-05).
- No granular OP detail (per-field old/new on contract / customer / campaign edits).
- No data-export and bulk-list-view auditing (e.g. "operator clicked CSV export of customer list"). The V1 catalog (§B.1) does not include these.
- No real-time pattern alerting (e.g. "5 failed AUTH_LINK_REJECTED in 60 s on the same customer_id"). The events are captured; alerting on patterns is V2.
- No customer-facing ARTCI subject-access portal.
- No automatic 10-year purge tool. Storage grows monotonically in V1.
- No salt rotation / cryptographic erasure (V2 on confirmation of erasure-right policy).
- No cross-tenant analytics, no group/holding consolidated view (V2/V3).
- No BILL fiscal events (OQ-01 - pending founder+lawyer decision).
References¶
docs/prd/prd-v1.md§§16.5, 17, 18, 27, 30 - V1 logging, security, data model, connectivity.docs/prd/prd-vision-v2-v3.md- V2/V3 vision (multi-agency, group, analytics).docs/prd/tenant-admin-prd.md- TENANT producer contracts (NFR-09, AC-02.3, AC-04.2/3, AC-05, AC-08, AC-09).docs/prd/identity-prd.md- AUTH producer contracts (operator session lifecycle, signed-link issuance/verification).docs/architecture/platform-saas-blueprint.md§5 - module layout (control plane, AUDIT placement).docs/legal/audit/Legal-Compliance-audit.md- CRITICAL gaps #2, HIGH gap #11, the basis for §A.7 Temporarily Validated.