Story AUDIT-002: Real audit emission on the payment and notification hot paths¶
Module: audit-log Slice: AUDIT-002 (Architecture §11) Side: [BACKEND] Version target: V0.0.1 Priority: 5 Depends on: AUDIT-001a, AUDIT-001b (outbox + builder + catalog), AUDIT-001c (PiiHasher), PAY-001 (payment confirm/fail boundaries), NOTIF-002 (send/fail boundaries) Can develop concurrently with: None — it validates two other modules' emission Merge order: After AUDIT-001b, AUDIT-001c, PAY-001, NOTIF-002 Estimated complexity: S (AUDIT-side; the real producer work lives in PAY-001 + NOTIF-002) PRD User Stories: N/A — Technical Story (V0 sequencing of real emission; verifies AUDIT-US-01 + AUDIT-US-06 on two producers) Wireframe: N/A — backend only
Objective¶
Flip the two highest-value producers — payment and notification — from stubbed emission to real AuditEventBuilder calls, and prove from the AUDIT side that the rows land with all mandatory fields and with phone numbers hashed (never raw). This is the first real exercise of the full write path end-to-end on live business events. No new AUDIT-side production code is needed beyond test fixtures; the producer changes are owned by PAY-001 and NOTIF-002.
Backend Scope¶
Entities¶
None new. Asserts rows in audit_log (schema from AUDIT-001a).
Migrations¶
None.
Service Layer¶
- AUDIT-side: no new production beans. Test fixtures + helpers only.
- Producer-side (owned by PAY-001 / NOTIF-002, listed here for traceability):
- PAY emits via
AuditEventBuilderat itsconfirm()/fail()/initiate()/reconcile()boundaries:PAY_INITIATED,PAY_CONFIRMED,PAY_FAILED,PAY_RECONCILED. - NOTIF emits at its
send()/fail()boundaries:NOTIF_SENDING,NOTIF_SENT,NOTIF_FAILURE,NOTIF_SUPPRESSED_OPT_OUT. NOTIF consumesPiiHasherto putpayload.phone_hash(never the raw+225…number).
API Endpoints¶
None.
Validation Rules¶
- FR-02 (mandatory fields): every
PAY_*andNOTIF_*row must carry the fields markedYfor its family in PRD §B.3 (PAY_: tenant_id, agency_id, country_code, customer_id, contract_id, correlation_id, result; NOTIF_: tenant_id, agency_id, country_code, customer_id, correlation_id, result). A missing mandatory field is rejected at the AUDIT boundary. - FR-03 / AC-06.1: a NOTIF payload must never contain a string matching a raw Ivorian phone (
\+225\d{10}); it carriesphone_hashinstead.
Multi-Tenant Considerations¶
- Each emitted row carries the snapshotted
tenant_id+agency_idfrom the producer's context. A synthetic two-tenant run must show rows correctly partitioned. - NOTIF phone hashes use the per-tenant salt — the same recipient number produces different
phone_hashvalues across tenants.
Audit & Logging¶
- Real emission now active for:
PAY_INITIATED,PAY_CONFIRMED,PAY_FAILED,PAY_RECONCILED,NOTIF_SENDING,NOTIF_SENT,NOTIF_FAILURE,NOTIF_SUPPRESSED_OPT_OUT(PRD §B.1). All other families remain on stubs until their own module activates.
Frontend Scope¶
N/A — backend only.
Acceptance Criteria¶
(Derived from the Architecture §11 AUDIT-002 acceptance bullets; anchored on FR-02 + AC-06.1.)
AC-002.1: Hot-path rows land with full mandatory fields
Given a synthetic run of 100 payments and 100 SMS sends across two tenants
When the audit drain completes
Then the audit_log row count equals the expected count
And every PAY_* and NOTIF_* row carries all fields marked Y for its family in PRD §B.3
AC-002.2: No raw phone ever persisted
Given the NOTIF_SENDING / NOTIF_FAILURE / NOTIF_SUPPRESSED_OPT_OUT rows from that run
When their payloads are scanned
Then no payload field matches the raw-phone regex \+225\d{10}
And each carries a phone_hash produced by PiiHasher
AC-002.3: Per-tenant hash isolation on the hot path
Given the same recipient phone number sent under tenant A and tenant B
When both NOTIF rows are written
Then their phone_hash values differ (different per-tenant salts)
Compliance Rules¶
- CIMA Reg. 01-24 §16.5 — payment initiation/confirmation/failure and notification sending/failure are explicitly named §18 events; real emission here is what makes the renewal-cycle chronology (notification → authentication → payment) reconstructable.
- Law n°2013-450 minimisation — NOTIF must hash the recipient phone (AC-06.1, FR-03).
[LEGAL-REVIEW]TV-04 lawful-basis note still applies.
Standards & Conventions¶
docs/standards/java-spring-guidelines.md— producer emission via the AUDIT named-interface; domain-event naming.docs/standards/critical-rules.md— rule 2 (XOF integer minor units — PAY payload amounts), rule 3 (catalog).docs/standards/multi-datasource-patterns.md— platform-DB audit storage for both DB profiles.
Testing Requirements¶
Unit Tests¶
- (Producer-side, in PAY/NOTIF stories) builder calls populate the correct event_type + fields.
Integration Tests¶
- AC-002.1: synthetic 100-payment + 100-SMS run; assert exact row counts and full §B.3 mandatory fields per family.
- AC-002.2: regex defensive check — no
\+225\d{10}in any NOTIF payload field;phone_hashpresent. - AC-002.3: same number under two tenants → different
phone_hash. - Idempotence carry-over: a redelivered PAY/NOTIF outbox event produces no duplicate row (FR-06, exercised via the real path).
What QA Will Validate¶
- PAY and NOTIF rows appear for every committed business action on those paths.
- No raw phone leaks into any audit payload.
- Counts and fields match expectations under a burst run.
Out of Scope¶
- Real emission for TENANT/AUTH/ING/OP/CP (each activates in its own module story; stubs remain here).
- Compliance-report export + ingest metrics (AUDIT-003).
- Defensive PII scan at the AUDIT boundary (V1, AUDIT-008) — V0 relies on code review of the activated producers (Architecture §0.3).
- Query / by-case / export APIs (V1).
Definition of Done¶
- [ ] PAY emits PAY_INITIATED/CONFIRMED/FAILED/RECONCILED via
AuditEventBuilder(verified from AUDIT side) - [ ] NOTIF emits NOTIF_SENDING/SENT/FAILURE/SUPPRESSED_OPT_OUT with
phone_hash(no raw phone) - [ ] AC-002.1 mandatory-field completeness verified on a burst run
- [ ] AC-002.2 no raw-phone regex match in any payload
- [ ] AC-002.3 per-tenant hash isolation verified
- [ ] Tenant isolation verified (
tenant_id+agency_idcorrect; platform DB for both V1 profiles) - [ ] Audit entries present for every required PAY/NOTIF event (no silent failure on a store outage — entries queue and drain)