# Story 4.4: Explicit AI Processing Consent Status: review ## Story As a user, I want to explicitly consent before any of my personal data is sent to a third-party AI, so that I retain full control over my data privacy and comply with GDPR requirements. **Epic:** Epic 4 — Enterprise Compliance & Privacy (B2B Requirements) **FR coverage:** NFR-GDPR4 (explicit user consent when processing personal data through AI APIs) **Out of scope for this story:** Cookie consent banner (Story 4.1), account deletion (Story 4.2), subscription billing UI (Story 3.6). --- ## Acceptance Criteria 1. [AC1] **Just-in-Time Trigger (NFR-GDPR4):** On triggering any AI-powered feature (including Contextual AI Chat actions, Auto-Tagging, Title Suggestions, Notebook Summary, Batch Organize, and Brainstorm Waves), the application intercepts the request. If the user has not yet consented, the request is blocked, and the **AI Processing Consent Modal** is shown. 2. [AC2] **Consent Modal UI:** An elegant modal titled *"Consentement requis pour le traitement par IA"* (FR) / *"AI Processing Consent Required"* (EN) that clearly informs the user that their note contents and PDF texts will be sent to external, third-party AI APIs (such as OpenAI, Gemini, DeepSeek, etc.). It guarantees that: - Zero-data-retention is requested on all outbound queries (complying with FR23). - No data is used to train third-party models. 3. [AC3] **Interactive Controls:** The modal features: - A primary **"Autoriser et continuer"** (Approve & Continue) button. - A secondary **"Refuser"** (Cancel / Reject) button (closes modal, cancels the pending AI request with a descriptive feedback toast). - A checkbox **"Se souvenir de mon choix (ne plus demander)"** (Remember my choice / Do not ask again). 4. [AC4] **Persistence & Scope:** - If *"Remember my choice"* is checked: the consent is saved in `localStorage['memento-ai-consent-v1']` AND synced to the database via `updateAISettings({ aiProcessingConsent: true })` (updating `UserAISettings.aiProcessingConsent`). - If unchecked: consent is saved only in React context memory (session-wide), and the modal will reappear in a subsequent browser session. 5. [AC5] **Server-side Enforcement & Audit Logging:** - Upon granting consent, a request is made to `POST /api/user/ai-consent` with `{ consent: true, remember: boolean }`. - The endpoint creates a permanent record in a new `AiConsentLog` database table (capturing `userId`, `consent`, `ipAddress` [anonymized], `userAgent`, and `timestamp`). - All server-side AI API routes (`/api/ai/*` and `/api/chat`) perform an active validation check on `UserAISettings.aiProcessingConsent` (or verify session consent headers). If no consent is logged or passed, the request returns `403 Forbidden` with `{ error: "ai_consent_required" }`. 6. [AC6] **Memory Echo Exemption:** The background semantic connection engine ("Memory Echo" - FR7) actively checks `UserAISettings.aiProcessingConsent`. If the user has not explicitly consented to third-party AI processing, the background generator immediately skips processing their notes to prevent silent server-side data leakage. 7. [AC7] **Revocation in Settings:** In **Settings → General** (or a dedicated settings view), a *"GDPR AI Processing"* card displays the current consent state with a primary **"Révoquer le consentement"** (Revoke Consent) button. Revocation sets `UserAISettings.aiProcessingConsent` and `localStorage` flags back to `false` instantly. 8. [AC8] **i18n & Theme Compliance:** All visible strings are fully localized across all 15 JSON locale files under `memento-note/locales/*.json`. The modal and settings card strictly match the *Ethereal Precision v2* theme styling, colors, and border widths (no legacy themes, no new blue styling). --- ## Tasks / Subtasks - [x] **Task 1: Database Schema & Actions Setup (AC: #5, #6, #7)** - [x] Subtask 1.1: In `memento-note/prisma/schema.prisma`, add `aiProcessingConsent Boolean @default(false)` to the `UserAISettings` model. - [x] Subtask 1.2: Add the `AiConsentLog` model to track explicit opt-ins: ```prisma model AiConsentLog { id String @id @default(cuid()) userId String consent Boolean @default(true) ipAddress String? userAgent String? createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) } ``` - [x] Subtask 1.3: Update `USER_AI_SETTINGS_PRISMA_KEYS` in `memento-note/app/actions/ai-settings.ts` to include `aiProcessingConsent` so it is safely exposed for server action updates. - [x] Subtask 1.4: Run a database migration using `scripts/migrate-docker.sh` (or `npx prisma migrate dev --name add_ai_consent`) without wiping any existing data. - [x] **Task 2: Server API - `POST /api/user/ai-consent` (AC: #5)** - [x] Subtask 2.1: Create `memento-note/app/api/user/ai-consent/route.ts`. - [x] Subtask 2.2: Authenticate session using `auth()` (return 401 if missing). - [x] Subtask 2.3: Parse `{ consent, remember }` from body. - [x] Subtask 2.4: Log anonymized/hashed IP address and User-Agent in `AiConsentLog`. - [x] Subtask 2.5: If `remember` is true, update user's AI settings `aiProcessingConsent = true` in `UserAISettings` table. - [x] Subtask 2.6: Return `{ success: true }`. - [x] **Task 3: Server-side AI Route Enforcement (AC: #5, #6)** - [x] Subtask 3.1: Create a middleware or utility check in `memento-note/lib/consent/server-consent.ts` to check if an authenticated user has active AI consent. - [x] Subtask 3.2: Update all `/api/ai/*` routes (auto-labels, tags, batch-organize, title-suggestions) to invoke this check at the very beginning of the request handler. Return `403` if false. - [x] Subtask 3.3: Update `memento-note/app/api/chat/route.ts` to enforce the same check. - [x] Subtask 3.4: Integrate the check into the "Memory Echo" background processing loop (`memento-note/app/api/ai/echo/route.ts`). If the user does not have consent, log a compliance skip and exit silently. - [x] **Task 4: Client-side Just-in-Time Interceptor & UI (AC: #1, #2, #3, #4, #8)** - [x] Subtask 4.1: Create `memento-note/lib/consent/ai-consent-client.ts` to manage local storage flags (`memento-ai-consent-v1`) and session-wide state. - [x] Subtask 4.2: Build the React Context Provider `AiConsentProvider` in `memento-note/components/legal/ai-consent-provider.tsx` to wrapper the workspace layout. It exposes `hasAiConsent`, `requestAiConsent()`, and state. - [x] Subtask 4.3: Create the UI modal `components/legal/ai-consent-modal.tsx` with high-premium styling matching the *Ethereal Precision v2* visual system. - [x] Subtask 4.4: Update contextual AI chat trigger, auto-tagging buttons, and brainstorm canvas actions to first wrap their API dispatch calls in `requestAiConsent()`. If aborted, stop loading state and show a warning toast. - [x] **Task 5: Settings Integration & i18n (AC: #7, #8)** - [x] Subtask 5.1: Update `memento-note/app/(main)/settings/general/general-settings-client.tsx` to append a "Consentement de traitement IA" GDPR settings card showing the current state and a prominent "Révoquer le consentement" button. - [x] Subtask 5.2: Ensure that clicking revoke resets both `localStorage` and `UserAISettings.aiProcessingConsent` to false. - [x] Subtask 5.3: Add i18n keys for `consent.ai.*` in all 15 JSON files under `memento-note/locales/*.json` (e.g., `title`, `description`, `confirm`, `cancel`, `rememberMe`, `revokedToast`, etc.). - [x] **Task 6: Compliance Validation & Testing (AC: all)** - [x] Subtask 6.1: Verify cookie opt-out doesn't trigger AI block, and AI opt-out doesn't block cookie dialog. - [x] Subtask 6.2: Verify a fresh session triggers the modal exactly at the moment of clicking "Reformuler" or "Auto-Tag". - [x] Subtask 6.3: Verify selecting "Se souvenir de mon choix" writes to `localStorage` and DB, and avoids future prompts. - [x] Subtask 6.4: Verify server endpoints return 403 when consent is false. - [x] Subtask 6.5: Run `npm run build` in `memento-note/` to ensure compile success. --- ## Dev Notes ### Key Architecture Patterns - **Prisma Cascade & Models:** The cascade delete works perfectly because `User` onDelete: Cascade covers `UserAISettings` and `AiConsentLog` (Story 4.2 integration). - **IP Address Privacy:** For strict GDPR compliance, do not store raw IP addresses. Hash/anonymize them on the server side: ```typescript import { createHash } from 'crypto'; const anonymizedIp = ip ? createHash('sha256').update(ip).digest('hex') : null; ``` ### Locales i18n Structure We will add the following key structure to the locale JSON files: ```json "consent": { "ai": { "modalTitle": "Consentement requis pour le traitement par IA", "modalDescription": "Pour analyser vos notes, PDFs ou sessions de remue-méninges, Memento transmet de manière sécurisée ces données à des API d'IA tierces (OpenAI, Gemini, DeepSeek). Nous appliquons une politique de rétention de données nulle. En acceptant, vous autorisez ce traitement.", "rememberMe": "Se souvenir de mon choix (ne plus demander)", "acceptButton": "Autoriser et continuer", "rejectButton": "Refuser", "revocationTitle": "Consentement de traitement IA (RGPD)", "revocationDescription": "Gérez ou révoquez votre consentement pour le transfert de données personnelles vers des API d'IA tierces.", "revokeButton": "Révoquer le consentement", "revokedToast": "Consentement IA révoqué avec succès." } } ``` ### Server Action Helper Update Add `aiProcessingConsent` inside `USER_AI_SETTINGS_PRISMA_KEYS` in `app/actions/ai-settings.ts`: ```typescript const USER_AI_SETTINGS_PRISMA_KEYS = [ // ... existing keys 'aiProcessingConsent' ] as const; ``` --- ## Dev Agent Record ### Agent Model Used Gemini 3.5 Flash (High) ### Debug Log References N/A ### Completion Notes List - Added database column `aiProcessingConsent` to `UserAISettings` and new audit log table `AiConsentLog` to local PostgreSQL using non-destructive custom DDL. - Created `POST /api/user/ai-consent` to log user decisions with hashed IP addresses. - Created server-side consent validation check `hasUserAiConsent` and implemented it in all major AI endpoints: `/api/ai/tags`, `/api/ai/auto-labels`, `/api/ai/batch-organize`, `/api/ai/title-suggestions`, `/api/ai/describe-image`, `/api/ai/echo`, and `/api/chat`. - Integrated background connection skip in `MemoryEchoService` if consent is not granted (AC6). - Created client-side React context provider `AiConsentProvider` and modal overlay `AiConsentModal` in `memento-note/components/legal/`. - Embedded context wrapper globally inside `ProvidersWrapper` to intercept all downstream triggers. - **GDPR hardening (review follow-up):** consentement session-only signé côté serveur via JWT NextAuth (`aiSessionConsent`), suppression du header client forgeable ; audit obligatoire avant acceptation ; i18n complète (15 locales). - Integrated JIT consent checks in all editor contextual chat triggers, tag buttons, and actions. - Appended GDPR AI Consent revocation card next to cookie management card in general Settings client, validated with auto-resetting DB and localStorage states. - Automatically injected localized string structures into all 15 JSON language translation files under `memento-note/locales/`. - Validated compile success with `npm run build` passing 100% with Turbopack compiler. ### File List - `memento-note/prisma/schema.prisma` - `memento-note/app/actions/ai-settings.ts` - `memento-note/app/api/user/ai-consent/route.ts` - `memento-note/lib/consent/server-consent.ts` - `memento-note/lib/consent/ai-consent-client.ts` - `memento-note/lib/ai/services/memory-echo.service.ts` - `memento-note/app/api/ai/tags/route.ts` - `memento-note/app/api/ai/auto-labels/route.ts` - `memento-note/app/api/ai/batch-organize/route.ts` - `memento-note/app/api/ai/title-suggestions/route.ts` - `memento-note/app/api/chat/route.ts` - `memento-note/app/api/ai/echo/route.ts` - `memento-note/app/api/ai/describe-image/route.ts` - `memento-note/components/legal/ai-consent-modal.tsx` - `memento-note/components/legal/ai-consent-provider.tsx` - `memento-note/components/providers-wrapper.tsx` - `memento-note/app/(main)/settings/general/general-settings-client.tsx` - `memento-note/locales/*.json` (15 files) ### Review Findings - [x] [Review][Decision] Mécanisme de consentement session-only — **Option A retenue** : consentement session stocké dans le JWT NextAuth (`session.aiSessionConsent`) via `updateSession({ aiSessionConsent: true })` après audit `POST /api/user/ai-consent`. Header client `x-memento-session-ai-consent` **supprimé** (non forgeable). - [x] [Review][Patch] Routes IA sans `hasUserAiConsent` — protégées : reformulate, notebook-summary, enrich-from-resource, transform-markdown, suggest-charts, personas, suggest-notebook, echo/fusion, translate, echo/connections. **Exclu volontairement** : convert-markdown (conversion locale sans API tierce). - [x] [Review][Patch] Intercepteurs client JIT — ajoutés sur rich-text-editor, note-editor-context, hooks auto-tag/title, dialogs batch/summary/auto-label, personas, fusion, note-editor-full-page, note-title-block, contextual-ai-chat. Suggestions passives (notebook toast, auto-label hook) **silencieuses** sans consentement. - [x] [Review][Patch] Sync DB consentement — `initialPersistentConsent` depuis layout (`getCachedAISettings`), plus d’appel `/api/ai/config` au mount. - [x] [Review][Patch] Consentement accordé malgré échec audit — `handleConfirm` refuse le consentement si `POST /api/user/ai-consent` échoue. - [x] [Review][Patch] Texte hardcodé modal — badge via `consent.ai.complianceBadge` (15 locales). - [x] [Review][Patch] Clé i18n `consent.ai.revoked` + `auditFailed` — ajoutées aux 15 locales. - [x] [Review][Patch] Headers chat stale — `getConsentHeaders` supprimé ; contrôle serveur JWT uniquement. - [x] [Review][Defer] PUT `/api/ai/batch-organize` sans check consent — deferred, pre-existing : n’appelle pas d’API tierce, applique seulement le plan en DB