Aller au contenu

PRD + FSD - audit-log (AUDIT) module

Module prefix: AUDIT · Plane: Control · Version scope: V1 · Country scope V1: CI only 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édent Legal-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 floor audit_retention_years ≥ 10)

Legal status discipline (memo v2) - toute obligation INCERTAIN - avocat requis bloque l'implémentation des stories qui en dépendent et impose une note [LEGAL-REVIEW] dans la spec/story. Toute obligation PRÉLIMINAIRE déclenche un avertissement + [LEGAL-REVIEW]. La conformité ne se déduit jamais d'un statut INCERTAIN. 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 (ligne Audit 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 de prd-v0.md § 5).
  • Schéma audit_entry figé 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_QUERY qui 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 de audit_entry durant 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_payload dè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 requis ou PRÉLIMINAIRE du 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_log lives in the platform DB; every row carries tenant_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_code is snapshotted on each row from the tenant at write time. This makes exports self-contained: an exported event for a CI tenant carries CI in cleartext, no join required.
  • Group / holding (V2): AUDIT does not know about groups today. V2 will add a group_id snapshot 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/Abidjan for any operator-side display (V2).
  • Filenames in exports are French: audit_<tenant_short_name>_<YYYY-MM-DD>_<YYYY-MM-DD>.jsonl and manifest.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_ip and user_agent are 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.