# DPIA — Cohort progress sharing with enterprise managers

**Doc type:** Data Protection Impact Assessment (RGPD Article 35)
**Activity:** "Activity 3 — Cohort progress (enterprise managers)" from [`registre-des-traitements.md`](registre-des-traitements.md)
**Status:** v1 — production
**Last reviewed:** 2026-05-26
**Reviewer:** Controller (founder), self-DPO
**Next review:** 2027-05 or on material change (new sub-processor, new data category, new transfer)

## 1. Why a DPIA is required

Article 35 RGPD + CNIL guidance require a DPIA when processing is "likely to result in a high risk to the rights and freedoms of natural persons", specifically including:

- **Systematic monitoring** of data subjects → ✅ employer monitors learner progress over time
- **Innovative use of new technological solutions** combined with automated scoring → ✅ automated quiz/lab scoring, automated gating of certificate
- **Vulnerability of data subjects in the processing relationship** → ✅ employer/employee power asymmetry (CNIL guideline 248-2018)

Three criteria from the CNIL list of nine — DPIA is mandatory.

## 2. Description of the processing

### 2.1 Purpose
Allow enterprise customers (employers funding training for their staff) to monitor cohort progress, evidence completion to OPCO funders, and satisfy their training obligation under Code du Travail.

### 2.2 Personal data processed

| Field | Sensitivity | Source |
|---|---|---|
| Learner email | Personal — direct identifier | Learner sign-up |
| Learner name | Personal — direct identifier | Learner sign-up |
| Learner UUID | Pseudonymous identifier | Auto-generated |
| Module completion timestamps | Behavioural | Course interactions |
| Quiz scores (0–100) | Performance evaluation | Automated assessment |
| Lab scene completion | Behavioural | Course interactions |
| Final assessment score | Performance evaluation | Automated assessment |
| Time per module | Behavioural | Course interactions |
| Last activity timestamp | Behavioural | Course interactions |

No special-category data (Art. 9 RGPD). No criminal data (Art. 10).

### 2.3 Data flow

```
Learner browser  →  Netlify Function (auth)  →  Postgres `progress` (Supabase EU)
                                                       ↑
                             RLS scope ("customer" claim)
                                                       ↑
Enterprise manager browser  →  Netlify Function (cohort-list)  →  Postgres SELECT
```

The manager's session JWT carries a `customer` claim. RLS policy `progress_manager_scope` restricts SELECT to rows whose `learner_email` matches a `codes.redeemer_email` row scoped to that customer.

Service-role calls (Netlify Functions) bypass RLS; the application code enforces customer-scoping in SQL. RLS is defense-in-depth.

### 2.4 Recipients
- Internal Netlify Functions (no external transfer for storage)
- The customer's authorised managers (within their own scope)
- Stripe (financial linkage only, no progress data)

### 2.5 Retention
5 years post-completion. After 5 y, PII fields nulled, aggregate row retained for Qualiopi audit.

### 2.6 Transfers outside the EU
None for storage. See [`sub-processors.md`](sub-processors.md) for the residual CLOUD Act exposure on Supabase + Resend.

## 3. Necessity and proportionality

### 3.1 Necessity test
The employer has a *legal obligation* to evidence training under Code du Travail Art. L6321-1. Without progress data, the employer cannot:
- Discharge their training duty
- Claim OPCO refunding (which requires attestation of completion)
- Demonstrate Qualiopi compliance to their own auditor

→ Necessity confirmed.

### 3.2 Data minimisation
- We do **not** record video of the learner.
- We do **not** record keystroke timing.
- We do **not** record IP address geolocation beyond country (for VAT only).
- We do **not** record screen recordings.
- Quiz scores are integer 0–100 — sufficient to evidence pass/fail without revealing individual answer choices to the manager. Answer-level data is **not** exposed to managers, only score + pass/fail.
- We do **not** rank learners against each other in any manager view.

→ Minimisation confirmed.

### 3.3 Lawful basis
Article 6(1)(b) — performance of contract. The convention de formation signed by the customer explicitly references cohort progress sharing. Learners receive a privacy notice on first access (the `welcome.html` onboarding email and `/privacy.html` § 6 "Enterprise / Qualiopi learners") informing them their progress is shared with their employer's authorised managers.

→ Lawful basis confirmed.

## 4. Risk assessment

| Risk | Likelihood | Severity | Mitigation |
|---|---|---|---|
| Manager A sees learners from Customer B (cross-customer leak) | Low | High | RLS policy `progress_manager_scope` + application-level customer-scope check in every endpoint + Playwright regression coverage |
| Manager exports cohort CSV → file leaks to third party | Medium | Medium | Export endpoint logs `actor`, `customer`, `row_count`, `timestamp`. Customer-side handling falls under their own RGPD obligations (data-controller-to-controller transfer covered in convention). |
| Learner profiled by manager based on completion speed | Low | Medium | Privacy notice § 6 informs learner. Manager view shows status + score, not "how long it took"; time-per-module is dashboard-only for the learner |
| Score used as basis for HR decision (promotion / disciplinary) | Medium | High | Convention de formation includes a clause that the score may not be used as sole basis for an HR decision (Art. 22 RGPD — right not to be subject to automated decision-making). Customer accepts this in writing |
| Score row leaks via API misconfiguration | Low | Medium | All endpoints require session JWT + customer-scope match. No public-read endpoint exposes progress data. Periodic API-surface scan |
| Erasure request not honoured | Low | High | `gdpr-erase.mjs` covers `progress` table; quarterly sample audit verifies past requests were processed correctly |
| Sub-processor (Supabase / Resend) compelled by US authority under CLOUD Act | Low | Medium | Supabase data residency = Frankfurt; EU-residency contracts active. Resend account configured for EU residency. Documented in [`sub-processors.md`](sub-processors.md). Strict DINUM-grade customers (Defence / Interior / Justice) routed to Tier 2 OVH SecNumCloud instead — not yet active |
| Joint-controller ambiguity → CNIL complaint by learner | Low | Medium | Convention de formation includes a one-page joint-controller addendum naming win2linux as data controller of the platform; customer as joint controller of the cohort dataset; both share responsibility |

### Residual risk

After mitigation, the residual risk is **low to medium**. The single most important control is the customer-scope enforcement in both RLS and application code — multiple layers, tested via Playwright + unit tests on every commit.

The CLOUD Act risk on US-headquartered sub-processors (Supabase Inc., Resend Inc.) is the most-likely-to-be-questioned residual. Mitigation path documented: customers with strict DINUM Cloud-de-Confiance requirements move to Tier 2 (OVH SecNumCloud) which is not yet built but is on the doctrine gate #4 roadmap.

## 5. Stakeholder consultation

- **Learners**: informed via the public privacy policy at sign-up; given right to object under Art. 21 (objection is functionally identical to opt-out of the training programme — captured in the convention)
- **Customers**: see the convention de formation joint-controller addendum
- **DPO**: self-DPO (no external DPO consulted). Move to fractional DPO scheduled before first Qualiopi audit
- **CNIL**: this DPIA is filed internally; no prior consultation requested (residual risk below threshold per Art. 36)

## 6. Decision

**Proceed with processing.** Residual risk is low to medium after mitigation. The processing is necessary for the legitimate purpose, proportionate to that purpose, and supported by appropriate safeguards.

## 7. Implementation evidence

| Mitigation | Evidence |
|---|---|
| RLS policy in place | `db/migrations/0004_rls.sql` |
| Customer-scope enforcement in code | `netlify/functions/enterprise-progress-list.mjs`, `enterprise-codes-list.mjs`, `enterprise-export-csv.mjs` |
| Regression tests | `tests/enterprise-export-csv.test.js`, Playwright `cross-customer-access.spec.js` |
| Learner privacy notice | `/privacy.html` § 6 |
| Joint-controller addendum | `admin/convention-de-formation-template.md` (TODO before first signing) |
| RGPD erasure handling | `netlify/functions/gdpr-erase.mjs` |
| RGPD export handling | `netlify/functions/gdpr-export.mjs` |

## 8. Sign-off

- **Controller**: [signature placeholder — sign on PDF export before audit]
- **DPO**: Self-DPO, same as controller
- **Date**: 2026-05-26
- **Version**: 1.0

## Changelog

| Date | Version | Change |
|---|---|---|
| 2026-05-26 | 1.0 | Initial production DPIA covering Supabase EU cutover. |
