PRD V0 - Pre-MVP Re-Scoping¶
Assisted renewal platform for brokers and insurers¶
Version 0.0.0 → 0.1.0 Sub-version Roadmap¶
Change Log¶
| Version | Date | Change |
|---|---|---|
| 0.0.0 | 2026-05-20 | Initial V0 envelope PRD: 7 sub-version contract, DB profile taxonomy, cross-module anchors, module scope deltas, compliance posture, personas. |
| 0.0.1 | 2026-05-21 | Founder review pass 1: resolved V0-OQ-01 (payment provider: first to accept partnership wins), resolved V0-OQ-03 (Keycloak shared realm + Organizations + per-org IdP), added § 5.8 Keycloak realm strategy locks, switched § 6.2 mitigation #3 to default platform-hosted privacy page, reframed § 2 multi-agency wording. Em-dash characters scrubbed (project writing convention). |
§ 1. Purpose of This Document¶
This document defines V0:
a re-scope of the V1 MVP into 7 demonstrable, independently-shippable sub-versions (v0.0.0 → v0.1.0).
V0 is NOT a throwaway prototype - it uses the same locked stack (Java 26 / Spring Boot 4.0.5 / React 19.2 / PostgreSQL / Keycloak) and applies additive, non-breaking cuts to V1's scope. Every feature in V0 must layer cleanly into V1 without rewrites.
V0 targets a bipolar market: - SME segment (S1–S3, SHARED DB profile): small brokerage firms deploying on V0's shared multi-tenant DB. - Enterprise segment (S6, BYO DB profile): lead prospect NSIA Insurance, deploying on their own PostgreSQL with application-level isolation.
V1 will later add mid-market (S4, SCHEMA profile) and large (S5, DATABASE profile) in V3+.
§ 2. V0 Purpose & Relationship to V1/V2/V3¶
V0 Objectives¶
- Ship a working, production-grade renewal platform in 12–16 weeks (first demo v0.0.0 at ~3–4 weeks) by slicing the V1 MVP into 7 sales-milestone sub-versions.
- Validate product-market fit via founder presentations and early customer feedback (demo target: Basile presentation; production go-live target: NSIA @ v0.0.4).
- Foundation for V1/V2: V0 implements critical architectural patterns (tenant-aware datasource resolver, Liquibase/Flyway multi-profile tooling, async tenant-context propagation, audit-log emission stubs) from v0.0.0 onwards so V1 inherits a production-ready platform shape, not scaffolding.
Relationship to V1¶
- V1 is the source of truth for what V0 cuts FROM. All cuts are documented deltas (kept / simplified / deferred).
- No feature in V0 contradicts V1 PRD unless explicitly noted as a V0 simplification and approved by founder.
- V1 PRD versions remain frozen for the duration of V0 development. V0 PRD annotations (§ 4) reference specific V1 sections.
- Additive rule: V1 features layer on top of V0 without rewrites. Example: V0 v0.0.1 has basic payment reconciliation; V1 adds advanced retry logic + manual fault resolution on top.
Relationship to V2/V3¶
- V2 remains a vision doc (prd-vision-v2-v3.md). No V2 features leak into V0 stories.
- DB profile timeline revised: V0 ships SHARED + BYO; V1 remains SHARED + BYO; SCHEMA + DATABASE deferred to V3 (unless customer need drives them earlier).
- Multi-agency in V0: a V0 tenant (cabinet) can have N agencies as sub-units starting at v0.0.2. Per-agency campaign routing is in scope. Multi-agency network consolidation across tenants (group-level reporting, shared admin) remains a V2 concept and stays out of V0.
- Regulatory posture: V0 accepts ARTCI risk (real WhatsApp + SMS before grants). V1 operationalizes ARTCI authorization paths.
§ 3. V0 Sub-Version Contract¶
The 7 sub-versions are sales milestones, each independently shippable + demonstrable. Each introduces new business rules, API endpoints, UI flows, and/or compliance posture.
| Sub-Version | Goal | Key Feature | Segment | Approx. Timeline |
|---|---|---|---|---|
| v0.0.0 | Skeleton: auth, tenant creation, S3-pickup ingestion (cron @02:00 per-tenant), campaign send via real WhatsApp + SMS, signed-link customer landing. No payment, identity verif, link expiration, or agencies. SHARED DB profile only. | Orchestrated renewal end-to-end (without payment). | S1–S3 | Week 3–4 |
| v0.0.1 | First payment provider live. Customer selects contracts to pay. Audit-log emission active (payment + link + login). Cabinet admin edits erroneous data before send. ARTCI mitigations: opt-out, consent, privacy notice, no-resend. Idempotency + webhook sig verify mandatory. | Official merchant payment + compliance audit trail. | S1–S3 | Week 5–7 |
| v0.0.2 | Cabinet admin imports agencies + agency users. Multi-agency campaign routing (per-agency queue, no per-operator assignment). Branch operators inline edit before send. Identity verification: phone last-4 only. Cabinet admin gets read-only status visibility (no actions). | Multi-agency campaigns + basic identity capture. | S1–S3 | Week 7–9 |
| v0.0.3 | UI ingestion upload added (S3 pickup remains permanent). Cabinet admin gains view + revoke + cancel actions after push (still no edit). | Self-service file upload + campaign control. | S1–S3 | Week 9–10 |
| v0.0.4 | BYO DB profile live - greenfield onboarding only. No A→D migration in V0 (deferred to V2). NSIA production go-live target. |
Enterprise single-tenant on customer's own DB. | S6 | Week 10–12 |
| v0.0.5 | SSO / IdP federation on top of Keycloak. | Enterprise identity integration. | S6 | Week 12–13 |
| v0.1.0 | Production-hardening: 72h signed-link expiration + observability hooks + rate-limit tuning + ARTCI-grant-driven adjustments (if grants land). V1 readiness gate. End of V0. | Production readiness. | S1–S6 | Week 13–16 |
Sub-Version → Module → Story Mapping appears in § 8.
§ 4. Per-Module Scope Changes vs V1 PRD¶
ING (ingestion)¶
| Aspect | V1 Scope | V0 Scope | Status | Notes |
|---|---|---|---|---|
| File format | CSV/XLSX; agency_id, customer_type + future-use columns |
Same | Frozen v0.0.0 | Column list fixed Day 1 (may be empty-valued). Future-proofs later multi-agency work. |
| S3 pickup | Per V1 PRD §11.1 | Cron @02:00 Africa/Abidjan per-tenant, not platform-wide |
Locked | MC-1. Stable UTC±0 timezone; per-tenant isolation. |
| UI upload | Deferred to V1 | Arrives v0.0.3 | Kept | Reduces v0.0.0 scope; cabinet admin uploads via web UI. |
| Validation rules | Full V1 set (V1 PRD §13 + §15) | Simplified: drop optional fields early, keep core contract/customer/amount/deadline | v0.0.0 only | v0.0.1+ adds back incrementally. |
| Error handling & retry | V1: detailed + re-upload | v0.0.0: cron idempotent, S3 pickup failure → audit + manual retry; v0.0.3: UI provides feedback loop | Simplified | Cron crash does NOT unlock stale S3 objects; must be re-uploaded (v0.0.3) or manually cleared. |
| Agency ingestion | N/A (V0 adds agencies) | v0.0.2: cabinet admin creates agencies via UI (no bulk import) | New | Agencies added post-tenant-creation, not via ingestion CSV. |
Implications for architecture: ING datasource must be tenant-scoped from v0.0.0. Flyway migrations for ingestion entities must be applyable to both SHARED and BYO profiles (D9).
CP (customer-portal)¶
| Aspect | V1 Scope | V0 Scope | Status | Notes |
|---|---|---|---|---|
| Customer authentication | Lightweight: signed-link + challenge (DOB/phone/RCCM) | v0.0.0: signed-link only (no challenge); v0.0.2: phone last-4 identity capture | Simplified | DOB/RCCM capture deferred to V1. v0.0.2 captures customer_type at payment, not full identity verification. |
| Link validity | 48–72h | v0.0.0–v0.0.5: no expiration; v0.1.0: 72h | Deferred | v0.1.0 hardens for production. Temporary posture: long-lived links acceptable for slow-3G resumability. |
| Link anti-replay | Signed-link spec (V1 PRD § 19) | v0.0.0: token + signature required; v0.1.0: adds attempt counter + rate-limiting | Simplified | Anti-replay (state-based) deferred to v0.1.0; signature-based auth only v0.0.0–v0.0.5. |
| Contract selection | Customer picks contracts + amounts | v0.0.0–v0.0.1: select all or none; v0.0.2+: per-contract toggle | Simplified | v0.0.0 ships with all-or-none; later sub-versions add granularity. |
| Payment flow | Full payment integration + reconciliation | v0.0.0: no payment; v0.0.1: payment live + webhook reconciliation; v0.0.2+: status polling + retry | Phased | Payment is a separate slice; CP integrates via PAY module APIs. |
| Mobile-first 360px | V1 requirement | Mandatory v0.0.0 onwards | Locked | MC-4. Non-negotiable for Slow 3G; verified via Slow-3G smoke test before story close. |
| Payload < 200 KB gzipped | V1 requirement | Mandatory v0.0.0 onwards | Locked | MC-4. Critical path: signed-link landing + list contracts only. Payment details lazy-load. |
| French-native UI | V1 requirement | Mandatory v0.0.0 onwards (French only) | Locked | MC-6. No i18n in V0; all strings French. |
| Form draft survival | V1 requirement (form state survives reload) | v0.0.0: session state on server (resumable via link reuse); v0.0.1+: form persists on server between retries | Simplified | No client-side localStorage in v0.0.0 (low-bandwidth context); server holds draft. |
Implications for architecture: CP must be aggressively code-split; landing page must load in <200KB gzipped on Slow 3G (critical path). Payment integration is loose (CP calls PAY APIs, does NOT orchestrate retry).
OP (operator-console)¶
| Aspect | V1 Scope | V0 Scope | Status | Notes |
|---|---|---|---|---|
| Campaign engine | Cohort selection + multi-step scheduling + A/B testing | v0.0.0–v0.0.1: simple send (all contracts matching deadline window, one batch); v0.0.2+: per-agency routing | Simplified | Campaign rules frozen v0.0.0: renewal_window_days from tenant settings, all matching contracts selected, one send per campaign creation. No scheduling, no segmentation. |
| Operator actions (pre-send) | Bulk edit, approve, flag exceptions | v0.0.0: read-only list (no edits); v0.0.1: inline edit before send (cabinet admin only); v0.0.2+: branch operators can edit | Phased | Approval workflow deferred; v0.0.0 operator only views. |
| Operator actions (post-send) | View status, cancel, revoke, resend | v0.0.0: none (fire-and-forget); v0.0.1: view status (read-only); v0.0.3: view + revoke + cancel (cabinet admin only) | Phased | Granular post-send control deferred; v0.0.0 has no feedback loop. |
| Multi-agency support | Not in V0 | v0.0.2: per-agency campaign routing; each branch operator sees their agency contracts only | New | Introduced v0.0.2; per-agency queue replaces global queue. |
| Offline sync minimum | V1 PRD § 20 (read-only cache of last push + local notes) | v0.0.0: online-first (polling only, no service worker); v0.0.1+: same | Deferred | Offline-sync not in V0. Operator-side remains online-first + polling (no WebSocket). |
| Desktop-first UI | V1 requirement | Mandatory v0.0.0 onwards | Locked | Operator console is desktop/tablet; high-bandwidth assumed. |
| French UI | V1 requirement | Mandatory v0.0.0 onwards (French only) | Locked | MC-6. |
| Campaign send via NOTIF | V1: NOTIF module sends SMS/WhatsApp | v0.0.0+: OP calls NOTIF APIs (Spring boundary); NOTIF is separate deployable | Locked | OP does NOT inline send; sends are async (domain events) with audit logging per send. |
Implications for architecture: OP couples loosely to NOTIF + PAY via domain events + API calls. No in-process messaging queue; outbox pattern (D4) ensures reliability.
PAY (payment)¶
| Aspect | V1 Scope | V0 Scope | Status | Notes |
|---|---|---|---|---|
| Payment provider | Multi-provider abstraction; providers configured per country | v0.0.0: none; v0.0.1: first provider goes live (Paymetrust / Flutterwave / Orange Business TBD); country-profile lookup from v0.0.1 | Phased | Provider integration is provider-specific. Abstraction in place v0.0.0 (interfaces); first concrete provider v0.0.1. |
| Merchant account ownership | Official merchant accounts only (not personal mobile-money) | Mandatory v0.0.0 onwards | Locked | No exceptions. Signed contract + regulatory compliance per provider. |
| Idempotency | Required (V1 PRD § 11.6) | Mandatory v0.0.1 onwards | Locked | MC-5. Customer may retry; system guarantees one charge. |
| Webhook signature verification | Required (V1 PRD § 11.6) | Mandatory v0.0.1 onwards | Locked | MC-5. Provider webhook handlers MUST verify signature before processing. |
| Payment status reconciliation | V1: webhook + status polling (§ 11.6) | v0.0.0: N/A; v0.0.1: webhook-driven primary, polling as fallback; webhook failure → manual reconciliation (audit logged) | Phased | v0.0.0 has no payment; v0.0.1 implements dual-path reconciliation. Reconciliation SOP documented in operations manual (out of PRD scope). |
| Payment attempt retry | V1: automatic retry + manual override | v0.0.0–v0.0.4: none (customer manually retries via link reuse); v0.1.0: minimal auto-retry + observability hooks | Deferred | v0.0.0–v0.0.4: simplicity over automation. v0.1.0 adds tuning before V1. |
Implications for architecture: PAY exposes /api/payments/... (D5); customer-side calls PAY APIs directly (not via OP relay). Webhook handlers are idempotent; audit-log writes via outbox (D7).
TENANT (tenant-admin)¶
| Aspect | V1 Scope | V0 Scope | Status | Notes |
|---|---|---|---|---|
| Tenant creation | TENANT PRD §A.3 (TENANT-US-01 + TENANT-US-02) | v0.0.0: platform admin creates tenant + cabinet admin user via Keycloak | Locked | Tenant directory + initial admin provisioning from v0.0.0. |
| Tenant profile columns | country_code, db_profile, notif_mode, etc. (§ 27 of V1 PRD) |
Same | Locked | country_code NOT NULL (default CI v0.0.0–v0.0.5, can be changed pre-launch). db_profile ∈ {SHARED, BYO}, default SHARED. notif_mode placeholder (used v0.1.0+ if ARTCI grants land). |
| DB profile selection | Not in V0 | v0.0.0: SHARED only (BYO connection string + decryption deferred); v0.0.4: BYO live for greenfield onboarding |
Phased | TENANT S0 (platform admin) uses datasource resolver interface; v0.0.4 provisioning enables BYO profile creation. |
| Agency management | Not in V0 | v0.0.0: none; v0.0.2: cabinet admin creates agencies + assigns users | New | Agencies are V0's organizational layer (sub-units within tenant, not separate tenants). |
| Cabinet admin delegation | Not in V0 | Deferred to V1 | Out of scope | V0: one cabinet admin per tenant (platform admin invites). V1 adds delegated admin. |
| Billing & invoicing (BILL module) | BILL PRD §A.3 | Deferred entirely to V1 (out of V0 scope) | Deferred | V0 ships with no billing UI or invoicing. Platforms → tenant invoicing via BILL in V1. |
Implications for architecture: TENANT S0–S1 in V0 (tenant creation + cabinet admin provisioning); multi-agency (S2) in v0.0.2. BILL is a pure V1 module; no placeholder in V0.
AUTH (identity)¶
| Aspect | V1 Scope | V0 Scope | Status | Notes |
|---|---|---|---|---|
| Operator auth (Keycloak) | V1 PRD §17 + AUTH PRD §A.3 | v0.0.0: Keycloak ROPC (Direct Grant) for cabinet + agency admins; shared papillon realm + Keycloak Organizations (one org per tenant) |
Locked | Keycloak >= 26.6 from v0.0.0. ROPC for simplicity. Operator JWT carries tenant_id claim emitted by a protocol mapper from the user's org id (or org attribute, decision in AUTH arch). See § 5.8 for full strategy. |
| Customer auth (signed-link) | V1 PRD § 19 (signed-link + challenge) | v0.0.0: signed-link token (SHA-256 stored, signature-based auth only); challenge deferred; v0.0.2: phone last-4 identity capture | Simplified | Auth tables on platform DB (D8 revised: AUTH-ADR-01). No HMAC; SHA-256 stored hash only. Attempt counter via Postgres + optimistic locking (AUTH-ADR-03). |
| Link signing service | Part of AUTH in V1 | v0.0.0: AUTH owns SignedLinkService (exposed to CP + OP via Spring boundary). Link lifetime = no expiration v0.0.0–v0.0.5; v0.1.0: 72h + attempt-limit |
Locked | ING / OP call SignedLinkService.createLink(...) at campaign send; returns URL-safe token. CP calls SignedLinkService.verifyLink(...) on landing. |
| SSO / IdP federation | Deferred to V1 | v0.0.5: per-Organization IdP brokering (OIDC / SAML) with domain-based discovery | New | v0.0.0 to v0.0.4: Keycloak ROPC only (no external IdP). v0.0.5: each enterprise tenant org gets its own IdP linked at the Organization level. NSIA user types [email protected], gets redirected to NSIA's IdP. IdP mappers auto-place federated users into org groups. |
| MFA (WhatsApp OTP) | V1 PRD (flag: LEGAL-REVIEW) | Deferred entirely; v0.0.0–v0.1.0: no OTP, no MFA | Deferred | Feature flag feature.whatsapp.operator-mfa.enabled=false from v0.0.0 (inherited from identity-arch.md). Requires ARTCI grant before enabling. |
Implications for architecture: AUTH module owns signed-link lifecycle + customer session state. Keycloak realm provisioning is part of TENANT S0. Platform DB hosts all AUTH tables (D1).
NOTIF (notifications)¶
| Aspect | V1 Scope | V0 Scope | Status | Notes |
|---|---|---|---|---|
| SMS delivery | V1 PRD §11.4 + NOTIF PRD §A.3 | v0.0.0: real SMS via official provider (country-specific: Orange Money SMS CI, etc.) | Locked | Real SMS from v0.0.0; no stub/mock in production. Provider API credentials in platform env. |
| WhatsApp delivery | V1 PRD §11.4 + NOTIF PRD §A.3 (real WA Cloud API) | v0.0.0: real WhatsApp Cloud API (Meta) via official business account | Locked | Real WhatsApp from v0.0.0 [FOUNDER-RISK-ACCEPTED]. ARTCI authorization (B10) deferred; dossier filed in parallel. Mandatory mitigations from v0.0.0: opt-out link, consent checkbox, privacy notice link, audit-log per send, no-resend after opt-out. |
| Provider selection by country | Per country_code + country_profile lookup | v0.0.0: country-profile lookup for SMS + WhatsApp providers | Locked | NOTIF reads tenant.country_code → country_profile → provider list. Multi-country support (SN, BJ, CM, GA) baked in from v0.0.0 (though only CI enabled in V0). |
| Email delivery | Deferred to V1 | Out of V0 scope | Out of scope | V0 is SMS/WhatsApp only. Email added in V1. |
| Consent & ARTCI compliance | V1 PRD §16 + NOTIF PRD §A.3 | Mandatory v0.0.0 onwards | Locked | [FOUNDER-RISK-ACCEPTED]. Every send includes opt-out link. Cabinet admin sees consent status per customer. Audit-log entry per send (ARTCI audit trail requirement). |
| Delivery retry & fallback | V1: SMS fallback if WhatsApp fails | v0.0.0: parallel sends (WhatsApp + SMS simultaneously); no cascading fallback | Simplified | v0.0.0 tries both; if one fails, audit-log notes failure (no auto-retry in v0.0.0). v0.0.1+: observability hooks added. |
Implications for architecture: NOTIF is a control-plane module (D5). OP calls NOTIF APIs (or publishes domain events) to send campaigns. NOTIF must support multiple providers per country_code (country-profile pattern). All sends are audit-logged (D7 outbox).
AUDIT (audit-log)¶
| Aspect | V1 Scope | V0 Scope | Status | Notes |
|---|---|---|---|---|
| Audit events | V1 PRD § 18 (full list) | v0.0.0: stubs in place for all V1 events (no UI); emission mandatory | Phased | Emission infra from v0.0.0. No audit console UI in V0 (deferred to V1). Audit entries persisted to tenant DB (outbox pattern). |
| Event scope | Operator actions + payment + notifications + identity | v0.0.0: OP sends, NOTIF sends, AUTH links, PAY payments (once live v0.0.1) | Phased | ING ingestion audited v0.0.0 (file received, validation passed/failed). NOTIF sends audited v0.0.0. AUTH links audited v0.0.0. PAY payments audited v0.0.1+. |
| Retention | V1: 7 years (CIMA + ARTCI requirement) | v0.0.0–v0.1.0: no purge (ship as-is); retention policy deferred to V1 | Deferred | V0 ships with all events retained. Soft-delete + archival policy added in V1. |
Implications for architecture: AUDIT module owns append-only audit_entry table (tenant-scoped). Domain event listeners emit audit entries via outbox (D4, D7). No query layer in V0; audit log is append-only.
§ 5. Cross-Cutting Day-1 Architectural Anchors¶
These patterns are implemented in every V0 sub-version from v0.0.0 onwards. They form the foundation for V1 and enable multi-profile (SHARED / BYO) support.
5.1 Tenant-Aware Datasource Resolver (D1, D2)¶
Pattern: Spring Boot exposes two logical datasources: platformDataSource (tenant directory, country profiles, billing master, auth tables) + tenantDataSource (business data: contracts, customers, campaigns, audit logs).
- Tenant resolution happens at request boundary (D3): Keycloak JWT
tenant_id(operator) or signed-link decode (customer). - Request-scoped context holds
currentTenant+currentAgency(v0.0.2+). - All business queries are tenant-scoped:
WHERE tenant_id = :tenant_id(no exceptions). - Platform queries use
@Qualifier(\"platformJdbcClient\")(D2); neverCrudRepository(which routes to@Primary). - Datasource resolver returns
SHAREDin v0.0.0; switches toBYOin v0.0.4 for eligible tenants.
Implementation: TenantDataSourceResolver, TenantContext, TenantFilter Spring components.
5.2 Liquibase/Flyway Multi-Profile Tooling (D9)¶
Folder structure:
db/
├── migration/
│ ├── platform/ ← applies once (shared DB)
│ │ ├── V001__init.sql
│ │ └── ...
│ └── tenant/ ← applies to SHARED tenant schema (v0.0.0)
│ │ AND to BYO-DB during provisioning (v0.0.4)
│ ├── V001__init.sql
│ └── ...
- Platform migrations run on app startup (v0.0.0).
- Tenant migrations run on app startup (SHARED DB) AND during BYO provisioning flow.
- Same migration set for both profiles (no dialect drift).
- Schema versioning is per-profile (platform vs tenant); never split.
Implementation: Dual-datasource Liquibase config in Spring Boot.
5.3 Async Tenant-Context Propagation¶
Use case: Campaign send (OP) → NOTIF delivery (async). Keycloak JWT is request-bound; async context must carry tenant_id.
- Domain events (D4) embed
tenantIdin payload. - Event listeners (e.g., NOTIF consumer) unwrap
tenantIdand set request context before executing business logic. - No thread-local
ThreadLocal<TenantContext>(not reliable across thread pools); all async listeners receive tenant_id in event payload.
Implementation: @TransactionalEventListener(AFTER_COMMIT) + domain event payload carries tenantId.
5.4 Signed-Link Signing Spec (AUTH)¶
Token format:
- Token = 32-byte SecureRandom → Base64url-encoded.
- Hash = SHA-256(token) stored in DB (never plaintext token).
- URL = /api/portail/{token}/contracts (token is the secret; signature computed client-side if needed).
Validation: - Customer presents token in URL. - Server computes SHA-256(token) and compares stored hash. - If match: token is valid. If no match: invalid. - Attempt counter (Postgres, optimistic locking): incremented on each presentation; after N failures (TBD: 5?), link is revoked.
Lifetime: - v0.0.0–v0.0.5: no expiration (supports Slow 3G resumability over multi-day outages). - v0.1.0: 72h TTL + attempt limit.
Anti-replay (v0.1.0+): token can be reused (resumable sessions); but webhook/payment idempotency prevents double-charge.
5.5 Payment Idempotency & Webhook Verification (PAY)¶
Idempotency (v0.0.1+): - Customer-facing payment API accepts idempotency key (UUID). - Key stored in Redis with 24h TTL. - Duplicate key → return cached response (same charge ID).
Webhook signature verification (v0.0.1+):
- Provider webhook includes HMAC signature header.
- Server verifies signature with provider's public key before processing.
- Invalid signature → reject with 400.
- Webhook handler is idempotent: idempotency key from provider payload (e.g., transaction_id).
5.6 Audit-Log Emission via Outbox Pattern (D4, D7)¶
Pattern (spring-modulith-events-jdbc):
- Business action (e.g., campaign send) publishes domain event.
- Event listener emits audit entry (INSERT to audit_entry).
- Audit-write failure MUST NOT roll back the business action.
Implementation:
- @DomainEvent published within transaction.
- @TransactionalEventListener(AFTER_COMMIT, phase = AFTER_COMMIT) fires after business action commits.
- Listener INSERT to audit_entry in same tenant-scoped context.
- If audit-write fails: log error, alert ops (Sentry), but do NOT roll back business action.
5.7 Compliance Audit Trail (ARTCI + CIMA)¶
Mandatory fields per V1 PRD § 18:
- audit_entry.event_type (e.g., OP_CAMPAIGN_VALIDATED, PAY_CONFIRMED, AUTH_LINK_ISSUED; full list in AUDIT PRD §B.1)
- audit_entry.actor_id (operator user ID or system)
- audit_entry.actor_type (USER, SYSTEM, WEBHOOK)
- audit_entry.subject_id (contract ID, customer ID, payment ID, etc.)
- audit_entry.subject_type (CONTRACT, CUSTOMER, PAYMENT, etc.)
- audit_entry.details (JSON: message count, delivery status per channel, error reason, etc.)
- audit_entry.occurred_at (timestamp)
- audit_entry.created_at (when logged)
Audit scope v0.0.0 onwards: - TENANT: tenant creation, cabinet admin provisioning, agency creation (v0.0.2). - ING: file received, validation pass/fail, contracts parsed. - OP: campaign creation, send initiated, cabinet admin view/edit/cancel/revoke. - NOTIF: send attempt per customer per channel, delivery status, opt-out recorded. - AUTH: signed-link created, customer login, challenge success/fail (v0.0.2+). - PAY: payment initiated, webhook received, reconciliation status (v0.0.1+).
5.8 Keycloak Realm Strategy (V0-OQ-03 resolution)¶
Strategy: shared realm + Keycloak Organizations + per-org IdP brokering. One realm (papillon) holds one Organization per tenant. Picked because Keycloak Organizations (GA in 26.0, matured in 26.6 Apr 2026, FGAP-for-Organizations preview May 2026) closes every historical gap that made realm-per-tenant the safe default, while removing the realm-count scaling cliff.
Concrete locks (binding for AUTH architecture + V1 carry-forward):
- Keycloak version: >= 26.6 at v0.0.0 launch. Track 26.7 for FGAP-for-Organizations GA.
- Single realm:
papillon. One Organization per tenant. Organization id is the durable identifier; not exposed to clients. - Organization attributes carry tenant metadata:
tenant_id(internal UUID),country_code(ISO 3166-1 alpha-2),group_id(nullable, reserved for V2). - Protocol mapper emits
tenant_idJWT claim (from org id ortenant_idattribute, decision in AUTH arch session). Spring side resolution (D3) unchanged. - Operator auth: ROPC (Direct Grant) v0.0.0 to v0.0.4. Each operator user is a member of exactly one Organization.
- v0.0.5: per-org IdP brokering. Each enterprise tenant org can be linked to its own OIDC/SAML IdP. Domain-based discovery (NSIA user types
[email protected]to trigger redirect to NSIA's IdP). IdP mappers (Hardcoded Group, Advanced Claim to Group) auto-place federated users into per-org isolated groups. - Tenant-admin self-service: until 26.7 ships GA with FGAP-for-Organizations, a thin custom admin UI in the OP module handles cabinet-admin delegation. Flagged as known temporary; expected to be replaced by the standard Admin Console once 26.7 lands.
- Customer-portal branding (broker logo above the fold): stays in React. Not a Keycloak theme concern (Keycloak only handles operator login).
- Hybrid carve-out path: any outlier tenant that requires a fundamentally different password policy, MFA flow, or token lifetime not expressible at the shared-realm baseline may be moved to a dedicated realm. Documented, planned for, not pre-paid for.
Trade-offs accepted: - Per-org password policy / MFA / token lifetimes are realm-level in 26.6. We mitigate via the carve-out path above. - Per-org Keycloak login branding is more limited than per-realm themes. Acceptable because customer branding lives in the customer portal, not in Keycloak. - Newer surface area than per-realm (~2 years of production maturity for Organizations). Mitigated by the v0.0.5 performance-testing gate (see § 10 TV).
§ 6. Compliance Posture & Regulatory Risk¶
6.1 Founder Risk Acceptance¶
Decision [FOUNDER-RISK-ACCEPTED] (2026-05-20): Real WhatsApp (Meta Cloud API) + SMS to Ivorian end-customers proceeds in v0.0.0 before ARTCI authorizations (B5 + B10) are granted. Founder explicitly accepted regulatory exposure on this date.
Dossier status: - ARTCI authorization request (B5: phone-number processing; B10: cross-border transfer to Meta/WhatsApp Cloud API) filed in parallel. - Expected grant timeline: before V1 GA (estimated 2026-Q3). - If grants are delayed: feature flags gate real sends; v0.1.0 (and onwards) can operate in "consent-only, no-send" mode pending ARTCI grant.
6.2 Mandatory Mitigations (v0.0.0 onwards)¶
Every NOTIF send must include:
- Opt-out link in message body (SMS + WhatsApp): "Répondez STOP pour vous désabonner" or similar.
- Consent checkbox on customer payment form (CP): "J'accepte de recevoir des relances par SMS/WhatsApp."
- Privacy notice link on customer portal: links to a default platform-hosted privacy page (FR), reflecting the platform's role as technical service provider, the categories of personal data processed, retention, subject rights (access/rectification/erasure), and contact for data requests. Tenant-specific overrides (broker-hosted privacy URL) are deferred to V1 to keep V0 customization overhead low.
- Audit-log entry per send (NOTIF module): captures customer phone, message text (or template ID), delivery status, timestamp, channel, operator, tenant.
- No-resend after opt-out: once customer replies STOP (or via cabinet admin UI v0.0.3+), NOTIF refuses subsequent sends to that phone number. Audit-log records opt-out.
Breach of any mitigation is a critical bug (fix before next deploy).
6.3 Inherited PRÉLIMINAIRE Flags¶
The operator-console PRD (v1) carries three PRÉLIMINAIRE compliance flags. V0 inherits them:
- TV-01: CIMA legal text on contract page (French official CIMA Reg. 01-24 text). Deferred to V1; v0.0.0 does not render it.
- TV-02: 72h signed-link validity + attempt-limit. Implemented in v0.1.0 (not v0.0.0–v0.0.5).
- TV-03: WhatsApp + ARTCI authorization. Covered by § 6.1 & 6.2 above.
Temporarily Validated (pending ARTCI confirmation): TV-03 mitigations are sufficient to mitigate regulatory risk during V0. To be confirmed by legal team post-V0.
6.4 Feature Flags (Regulatory Gates)¶
feature.whatsapp.operator-mfa.enabled=false(v0.0.0–v0.1.0): MFA via WhatsApp disabled pending ARTCI grant.feature.artci.sms-whatsapp.enabled=true(v0.0.0–v0.1.0): Real SMS/WhatsApp sending; can be flipped tofalseif ARTCI grant is delayed.feature.artci.phone-auth.granted=false(v0.0.0–v0.1.0): real-production ingestion of customer phone numbers gated pending ARTCI grant (mémo B5, Loi CI n°2013-450 art. 7 + Décision ARTCI n°2023-0877). Pré-production ingestion authorisée. BLOQUANT V0/V1 production. Seedocs/prd/ingestion-prd.mdline 9.
§ 7. Personas (V1 PRD Naming)¶
V0 supports the following personas. Each persona appears in one or more sub-versions.
| Persona | AUTH role (D-2) | Company role | V0 Earliest Appearance | Key Actions |
|---|---|---|---|---|
| Platform Admin | PLATFORM_ADMIN |
Company employee (Altarys/PAPILLON) | v0.0.0 (S0) | Create tenant + cabinet admin user in Keycloak; view tenant directory (platform admin console, TBD UI). |
| Cabinet Admin | CABINET_ADMIN (tenant-wide) |
Head of brokerage or agency network | v0.0.0 (S0) | Create campaigns, upload files (v0.0.3), view campaign status (v0.0.1), revoke/cancel (v0.0.3); manage agencies (v0.0.2). |
| Agency Admin | CABINET_ADMIN with agency_scope = [single] (see AUTH D-12) |
Head of a sub-unit (v0.0.2+) | v0.0.2 | Manage branch operators; assign contracts to operators (deferred to V1). NOT a distinct Keycloak role in V1. |
| Branch Operator | OPERATOR |
Field agent at an agency | v0.0.0 | View contracts before send (v0.0.0), inline edit before send (v0.0.2), resolve exceptions (deferred to V1). |
| End Customer (assuré) | none (signed-link only; no Keycloak user) | Individual buying insurance | v0.0.0 | Click signed-link, view contracts + amounts, select contracts (v0.0.2+), pay via merchant (v0.0.1+), receive receipt (v0.0.1+). |
§ 8. Sub-Version → Module → Story Mapping¶
This table maps each sub-version to the modules and their vertical slices (stories). Each story is tagged [V0.0.x].
| Sub-Version | ING | CP | OP | PAY | TENANT | AUTH | NOTIF | AUDIT |
|---|---|---|---|---|---|---|---|---|
| v0.0.0 | ING-001 (S3 cron setup) | CP-001 (signed-link landing + list) | OP-001 (campaign creation + send UI) | (none) | TENANT-001 (tenant create + cabinet admin provision) | AUTH-001 (Keycloak realm + ROPC) | NOTIF-001 (real SMS/WhatsApp) | AUDIT-001 (audit-log table + stubs) |
| v0.0.1 | ING-002 (S3 error handling) | CP-002 (payment UI) | OP-002 (campaign edit pre-send) | PAY-001 (first provider + webhook) | (none) | AUTH-002 (signed-link token + SHA-256) | NOTIF-002 (consent UI + opt-out) | AUDIT-002 (payment + send emission) |
| v0.0.2 | ING-003 (validation tweaks) | CP-003 (phone identity capture) | OP-003 (multi-agency routing) | PAY-002 (status polling) | TENANT-002 (agency CRUD + users) | AUTH-003 (challenge: phone last-4) | NOTIF-003 (per-agency queuing) | (none) |
| v0.0.3 | ING-004 (UI upload) | CP-004 (lazy-load payment details) | OP-004 (view + revoke + cancel) | (none) | (none) | (none) | (none) | (none) |
| v0.0.4 | (none) | (none) | (none) | (none) | TENANT-003 (BYO-DB provisioning) | AUTH-004 (BYO context routing) | (none) | (none) |
| v0.0.5 | (none) | (none) | (none) | (none) | (none) | AUTH-005 (Keycloak IdP federation) | (none) | (none) |
| v0.1.0 | (none) | CP-005 (72h link expiration + rate-limit) | OP-005 (observability instrumentation) | PAY-003 (retry + observability) | (none) | AUTH-006 (attempt-limit enforcement) | NOTIF-004 (rate-limit + retry) | AUDIT-003 (compliance report stubs) |
Note: Story count is ~18–20 total stories across 8 modules. Not every module has work in every sub-version; some modules (e.g., BILL, CAMP) are deferred entirely.
§ 9. Volumetry Estimate¶
Timeline¶
| Phase | Duration | Deliverable | Milestone |
|---|---|---|---|
| v0.0.0 | ~3–4 weeks | Skeleton (ingestion, campaign send, signed-link landing, real SMS/WhatsApp) | Demo-ready (Basile presentation target) |
| v0.0.1–v0.0.3 | ~5 weeks | Payment live, multi-agency, UI upload, cabinet control | Pilot-ready |
| v0.0.4–v0.0.5 | ~3 weeks | BYO profile live, Keycloak IdP | NSIA production go-live target |
| v0.1.0 | ~3 weeks | Hardening (72h link expiration, rate-limits, ARTCI adjustments) | V1 readiness gate |
| Total | ~14–16 weeks | 7 sub-versions, 18–20 stories | Production-grade renewal platform |
Team & Pipelining¶
- Pipelined execution: 2–3 developers working in parallel (one on v0.0.0, one on early v0.0.1 stories, third on infrastructure/tooling).
- AI workflow: each story uses the 7-personality ANALYST → ARCHITECT → DESIGNER → PRODUCT_OWNER → DEVELOPER → REVIEWER → FOUNDER flow (5–7 days per story typical). With pipelining, wall-clock time compresses.
- Infrastructure setup (Keycloak, Redis, MinIO, Postgres x2, docker-compose.yml) happens in parallel with TENANT S0 and AUTH S0 stories.
Resource Estimate¶
- Backend (Java/Spring): ~60% of effort (TENANT, ING, OP, PAY, AUTH, NOTIF, AUDIT modules).
- Frontend (React): ~30% (CP, OP operator console).
- DevOps/Infra: ~10% (docker-compose, Keycloak realm, Flyway migrations, multi-datasource config).
§ 10. Open Questions & Temporarily Validated Items¶
Open Questions Carried from V1 PRD¶
| ID | Question | Context | Owner | V0 Status |
|---|---|---|---|---|
| OQ-A1 | Retain tenant_provisioning_run indefinitely vs prune to audit at 90d? |
TENANT schema | Founder | Deferred to V1 (v0.0.0 does not prune). |
| OQ-A2 | BYO-DB: ship Flyway DDL pack to tenant DBA, or apply blind under contract clause? | TENANT provisioning (v0.0.4) | Founder + Legal | Deferred to TENANT PRD rework (v0.0.4 session). |
| OQ-A3 | papillon-platform-admin realm role vs separate papillon-admin realm? |
Keycloak design (AUTH) | Founder | Deferred to AUTH architecture. |
| OQ-X1 | Will AUTH consume TenantOnboarded synchronously or asynchronously? |
TENANT ↔ AUTH sync | ARCHITECT (AUTH) | Resolved: async (brief gap window documented in AUTH-arch). |
| OQ-X2 | Will BILL need a country_profile.fne_endpoint field, or hard-code the CI FNE URL? |
BILL module (V1) | ARCHITECT (BILL) | Deferred to V1 (not in V0 scope). |
New V0-Specific Open Questions¶
| ID | Question | Module | Owner | Resolution |
|---|---|---|---|---|
| V0-OQ-01 | Payment provider selection for v0.0.1: Paymetrust vs Flutterwave vs Orange Business? | PAY | Founder | Resolved (2026-05-21): the first provider to accept partnership wins; selection is procurement-driven, not technical. PAY abstraction is provider-agnostic so the choice is reversible. |
| V0-OQ-02 | Signed-link attempt-limit threshold: 3 vs 5 vs 10 attempts before revoke? | AUTH | ARCHITECT (AUTH) | v0.0.0: no limit; v0.1.0: TBD (recommend 5). |
| V0-OQ-03 | Keycloak realm strategy: one shared realm (multi-tenant) vs one per tenant? | AUTH + TENANT | ARCHITECT (AUTH) | Resolved (2026-05-21): shared papillon realm + Keycloak Organizations (one org per tenant) + per-org IdP brokering at v0.0.5. Keycloak >= 26.6 at v0.0.0; track 26.7 for FGAP-for-Organizations. tenant_id JWT claim emitted via protocol mapper. Hybrid carve-out to a dedicated realm allowed for any outlier tenant that needs a fundamentally different password/MFA policy. See § 5.8 for concrete locks. |
| V0-OQ-04 | ARTCI grant timeline: realistic date? Any interim gating strategy? | Compliance | Founder + Legal | Expected Q3 2026. Interim: feature flags + consent UI (§ 6.2). |
| V0-OQ-05 | When does BILL module enter V0 scope (if ever)? Or strictly V1? | BILL | Founder | Deferred entirely to V1. V0 has no billing UI or e-invoicing. |
Temporarily Validated (Pending Confirmation)¶
| Item | Status | Notes | Confirmation Gate |
|---|---|---|---|
| ARTCI risk mitigation sufficiency (TV-03) | PRÉLIMINAIRE | Opt-out + consent + audit trail + no-resend appear sufficient per memo B10. Requires post-V0 ARTCI confirmation. | Legal team post-v0.1.0. |
| SMS provider availability (Orange Money SMS CI) | PRÉLIMINAIRE | Assumed available for v0.0.0; procurement TBD. | Founder (provider selection V0-OQ-01). |
| WhatsApp Cloud API availability (for CI) | PRÉLIMINAIRE | Assumed available; official business account provisioning TBD. | Founder (WhatsApp onboarding). |
| Keycloak 26.6+ with Organizations: scaling under hundreds of orgs per realm | PRÉLIMINAIRE | Organizations feature is GA since 26.0, matured through 26.6 (Apr 2026). No public load-test data at hundreds-of-orgs scale. Validate during v0.0.5 hardening. | Performance testing v0.0.5. |
| Signed-link resumability without expiration | PRÉLIMINAIRE | Multi-day Slow-3G resumability requires no link expiration v0.0.0–v0.0.5. Validated v0.1.0 when 72h TTL added? | Usability testing + customer feedback. |
§ 11. CLAUDE.md Update Plan (Step 2 Preview)¶
Pending approval of this PRD, Step 2 will:
- Revise CLAUDE.md § "Multi-Tenant Model → V1 DB profiles":
- Update DB profile timeline: V0 ships SHARED + BYO; SCHEMA + DATABASE deferred to V3 (from prior "V2 = SCHEMA").
- Clarify V0 = sub-versions v0.0.0 → v0.1.0, not V1.
-
Lock the DB profile taxonomy from handoff (§ 3 above).
-
Add V0 sub-version entry to module ID table:
-
Extend existing module table to note which sub-versions each module participates in.
-
Update architectural decision D9 (Flyway multi-profile):
- Clarify that tenant migrations apply to both SHARED (v0.0.0) and BYO-DB (v0.0.4) profiles identically.
§ 12. What V0 Ships With vs V1 Ships With¶
Included in V0¶
✅ Tenant creation + cabinet admin provisioning (platform admin console TBD) ✅ Multi-agency support (cabinet → agencies + operators) ✅ File ingestion (S3 cron @02:00 per-tenant) ✅ Campaign creation + send (WhatsApp + SMS, real providers, ARTCI mitigations) ✅ Signed-link customer portal (no link expiration v0.0.0–v0.0.5) ✅ Official merchant payment (first provider live v0.0.1) ✅ Payment reconciliation (webhook + polling v0.0.1+) ✅ Audit-log emission (all V1 PRD § 18 events, stubs in place v0.0.0) ✅ Keycloak ROPC auth (operator side) ✅ Datasource resolver interface + dual-profile provisioning (Day 1) ✅ Liquibase/Flyway multi-profile tooling (Day 1) ✅ BYO-DB profile live (greenfield only, v0.0.4) ✅ Keycloak IdP federation (v0.0.5) ✅ 72h signed-link expiration + attempt-limit (v0.1.0)
Deferred to V1¶
❌ Platform admin console UI (tenant directory, audit log query, billing view) ❌ BILL module (billing + FNE e-invoicing for CI tenants) ❌ Audit-log query UI (admin-only reports) ❌ Cabinet admin delegation (multi-admin per tenant) ❌ Advanced campaign rules (segmentation, branching, A/B testing) ❌ Multi-agency network consolidation + reporting (V2 feature) ❌ Email delivery (SMS/WhatsApp only in V0) ❌ Form draft persistence (form state on server, not client-side) ❌ Offline operator sync (online-first polling, no service worker) ❌ SCHEMA profile (schema-per-tenant) ❌ DATABASE profile (DB-per-tenant on platform infra) ❌ A→D migration (SHARED → BYO migration deferred to V2) ❌ Advanced payment retry + manual reconciliation UI ❌ Customer identity verification (phone last-4 only; full identity deferred to V1)
§ 13. Success Criteria¶
V0 is successful when:
- ✅ All 18–20 stories reach
completedstatus (full test coverage, all ACs met). - ✅ Slow-3G smoke test passes on CP critical path (payload <200KB, usable on 3G, resumable via link reuse).
- ✅ ARTCI risk mitigations (opt-out, consent, audit, no-resend) are implemented and tested.
- ✅ Keycloak + Redis + MinIO + Postgres x2 + Docker Compose all run locally.
- ✅ Multi-profile architecture (SHARED + BYO) is non-breaking + tested.
- ✅ v0.1.0 goes to production (NSIA go-live) with no critical incidents in first 30 days.
- ✅ Founder can demo v0.0.0 to prospects within 3–4 weeks of development start.