Story NOTIF-006: Consent + cross-border disclosure copy contract (NOTIF-owned, consumed by CP)¶
Module: notifications
Slice: v0.0.1 NOTIF — consent copy block + cross-border disclosure asset (PRD V0 Envelope v0.0.1; design §2.1 + §1.4; NOTIF-US-08)
Side: [BACKEND] (content/contract supply — the rendering UI is CP-owned)
Version target: V0
Sub-version: v0.0.1
Priority: 10 (v0.0.1; after NOTIF-005)
Depends on: NOTIF-005 (opt-out link the copy references), NOTIF-001a (the customer_consented flag already carried at dispatch)
Can develop concurrently with: NOTIF-005
Merge order: After NOTIF-005
Estimated complexity: S
PRD User Stories: NOTIF-US-08 (AC-08.1, AC-08.2, AC-08.3); PRD V0 Envelope lock #2 + #3
Wireframe: N/A — copy contract only; the consent UI wireframe lives in the CP design (design §2.1)
⚠️ [ARCH-PENDING] — statut PRÉLIMINAIRE¶
Architecture is v0.0.0-only; v0.0.1 consent UI is a forward-compat checkpoint, owned on the rendering side by
customer-portal(CP). This NOTIF story delivers only the canonical FR copy block + the privacy/disclosure content asset that CP consumes. Reconcile the asset-delivery mechanism (shared content module vs. served endpoint) with the ARCHITECT v0.0.1 pass before dev-start.
Objective¶
v0.0.1 introduces an explicit consent checkbox on the Customer Portal landing page and a cross-border data-flow disclosure (ARTCI mitigations #2 and #3; loi n°2013-450 transparency duty, NOTIF-US-08). The rendering is CP-owned; NOTIF owns the canonical French copy so the wording does not drift between modules. This story delivers that copy as a versioned, single-source content asset (consent label, "learn more" link, privacy-notice page body, cross-border sub-processor disclosure) and the contract by which CP retrieves it.
Backend Scope¶
Entities¶
None. This story supplies content, not data rows. The customer_consented boolean is already persisted at dispatch (NOTIF-001a, v0.0.0).
Migrations¶
None.
Service Layer¶
- Ship the canonical FR copy as a versioned content asset (proposed: a
notif-contentresource bundle keyedconsent.*,privacy.*,disclosure.*, with acontent_versionstring e.g.papillon_privacy_fr_v1). CP imports the bundle (or fetches it from a NOTIF-served read-only endpoint — decide in ARCHITECT pass). - The privacy-notice page body (design §1.4) and the cross-border disclosure naming Meta Platforms Inc. + BSP + SMS aggregator (AC-08.2) are part of the same versioned bundle so they cannot drift from the SMS/WA body's
{privacy_url}.
API Endpoints¶
| Method | Path | Request | Response | Auth |
|---|---|---|---|---|
| GET | /api/notifications/content/privacy (proposed — or static bundle import) |
?lang=fr |
versioned FR content block | None (public content) |
Validation Rules¶
- One canonical
content_version; the SMS body{privacy_url}and the CP "En savoir plus" link MUST resolve to the same content (single source of truth — design §2.1 last line). - Disclosure text MUST name all three sub-processors (Meta, BSP, SMS aggregator), the legal basis (legitimate interest of the broker), and the right of objection (STOP / opt-out link).
Multi-Tenant Considerations¶
- v0.0.1 ships a platform-default FR page only (no tenant branding).
{tenant_name}is the only tenant-specific interpolation in the consent label. Tenant-override of the privacy page is V1 (PRD V0 §6.2 #3). No per-tenant content table in v0.0.1.
Audit & Logging¶
- No new audit event owned here. The
customer_consentedflag captured by CP is carried into NOTIF dispatch (NOTIF-001a) and already audited via the send events.
Frontend Scope¶
The consent checkbox + landing-page rendering is CP-owned (design §2.1). NOTIF supplies the strings below verbatim; CP renders them. Listed here so the copy contract is testable.
French micro-copy catalog (verbatim — locked, single source)¶
| Key | FR copy |
|---|---|
consent.checkbox.label |
J'ai lu et j'accepte que {tenant_name} et Papillon traitent mes données pour ce renouvellement. |
consent.checkbox.learn_more |
En savoir plus |
consent.checkbox.helper_off |
Cochez la case ci-dessus pour continuer. |
consent.error.save_failed |
Nous n'avons pas pu enregistrer votre choix. Réessayez. |
consent.error.retry_button |
Réessayer |
consent.offline.banner |
Hors ligne — votre choix sera sauvegardé dès la reconnexion. |
consent.collapsed.footer |
Vie privée · vos choix |
privacy.page.link_label |
Confidentialité et vos données |
Privacy-notice page body¶
Use the locked FR content of design §1.4 verbatim (sections: "Pourquoi vous recevez ce message", "Qui voit vos données", "Vos droits", "Durée de conservation", "Plus d'informations"). It names the broker (responsable), Papillon (sous-traitant technique), the SMS operator, and Meta Platforms / WhatsApp Cloud API (transfert hors UE / hors Côte d'Ivoire).
UI States (ALL REQUIRED — for the CP-rendered consent block; copy supplied here)¶
| State | Behavior | French Copy |
|---|---|---|
| Loading | Skeleton card, CTA disabled (≤ 600 ms) | — |
| Empty (initial) | Unticked checkbox, CTA disabled | "Cochez la case ci-dessus pour continuer." |
| Error | Save failed → warning banner, CTA not blocked, lazy retry | "Nous n'avons pas pu enregistrer votre choix. Réessayez." / "Réessayer" |
| Offline / low network | Store consent in sessionStorage, subtle caption, CTA enabled | "Hors ligne — votre choix sera sauvegardé dès la reconnexion." |
| Success | Checkbox ticked, CTA enabled, no banner | — |
Responsive Behavior¶
- Mobile-first (CUSTOMER), 360 / 768 / 1024 px; checkbox touch target ≥ 48 px (per design §2.1).
Connectivity Behavior (CUSTOMER stories only)¶
- The disclosure asset MUST NOT inflate the CP initial payload above the < 200 KB budget (AC-08.3) — server-side rendered or lazy-loaded. The privacy page itself targets < 50 KB (design §1.4).
Acceptance Criteria¶
AC-006.1: Canonical consent + disclosure copy is single-source
Given the SMS/WA body privacy_url and the CP "En savoir plus" link
When either is opened
Then both resolve to the same versioned FR content (content_version), with no drift
And the consent label reads exactly "J'ai lu et j'accepte que {tenant_name} et Papillon traitent mes données pour ce renouvellement."
AC-006.2: Disclosure names all sub-processors and legal basis
Given the cross-border disclosure content asset
When it is read
Then it explicitly names Meta Platforms Inc. (WhatsApp Cloud API, transfert hors UE), the BSP, and the SMS aggregator as sub-processors
And it states the legal basis (intérêt légitime du courtier) and the right of objection (STOP / lien d'opt-out)
AC-006.3: Disclosure asset respects the CP payload budget
Given the CP landing page < 200 KB initial-payload budget
When the disclosure asset is delivered
Then it is server-side rendered or lazy-loaded and does not push the initial payload above the budget
Compliance Rules¶
- ARTCI mitigation #2 (explicit consent capture) and #3 (privacy-notice link in every send) — PRD V0 Envelope "Verrouillé".
- NOTIF-US-08 / loi n°2013-450 transparency duty: cross-border disclosure. The legal-audit disposition for cross-border WhatsApp (gap #6) is RE-RESEARCH NEEDED → statut PRÉLIMINAIRE / INCERTAIN on the cross-border transfer itself. Emit
[LEGAL-REVIEW]. The disclosure copy may be drafted, but the underlying WA cross-border transfer remains under the ARTCI B5 + B10 production BLOQUANT and must be validated by counsel before public production.
Standards & Conventions¶
docs/standards/compliance-discipline.md(PRÉLIMINAIRE / INCERTAIN handling — this story carries both flags).docs/standards/connectivity-low-bandwidth.md(payload budgets).docs/standards/design-system.md(Checkbox/Card components CP reuses).docs/standards/api-guidelines.md(if served via endpoint, header-based versioning).
Testing Requirements¶
Unit Tests¶
- Content bundle exposes every key in the micro-copy catalog with exact FR strings.
content_versionis stable and equals the value embedded in the SMS{privacy_url}resolution.
Integration Tests¶
- CP consumes the bundle and renders the consent label with
{tenant_name}interpolated. - Privacy URL from an SMS body and the CP "En savoir plus" link reach identical content.
What QA Will Validate¶
- The disclosure page names Meta + BSP + SMS aggregator and the legal basis; the consent string matches byte-for-byte; CP initial payload stays under 200 KB.
Out of Scope¶
- The functional opt-out link endpoint (NOTIF-005).
- The consent checkbox component and its save API (CP-owned).
- Per-tenant privacy-page branding/override (V1).
- English version of the disclosure (V1 / marketing site).
Definition of Done¶
- [ ] Canonical FR content bundle shipped with all catalog keys + privacy page body + cross-border disclosure
- [ ]
content_versionsingle-sourced with the SMS/WA{privacy_url} - [ ] All acceptance criteria pass manually
- [ ] Unit + integration tests written and passing
- [ ] [CUSTOMER] disclosure delivery keeps CP initial payload < 200 KB
- [ ] French micro-copy matches the catalog exactly (byte-for-byte)
- [ ]
[LEGAL-REVIEW]acknowledged for the cross-border disclosure (gap #6 RE-RESEARCH; ARTCI B5+B10 BLOQUANT noted) - [ ] [ARCH-PENDING] asset-delivery mechanism reconciled with the ARCHITECT v0.0.1 pass before merge to a release branch