feat(legal): registre des sous-traitants et inventaire reel des cookies

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018BAUeCFpDkRD6tU5wGsc1C
This commit is contained in:
David 2026-09-07 21:40:58 +02:00
parent 94a681601f
commit 5504f9c1ce

View File

@ -0,0 +1,113 @@
/**
* Registre des traitements — source unique.
*
* ⚠️ Contenu juridique. Ce fichier liste les destinataires des données et ce
* que le site dépose sur l'appareil du visiteur. La politique de
* confidentialité publique, la politique de cookies et la page de conformité
* de l'administration le lisent toutes les trois : un sous-traitant ajouté ici
* apparaît partout, et il devient impossible de mettre à jour l'un en oubliant
* les autres. Il tient lieu de registre de l'article 30 côté produit.
*
* Seuls les faits structurels vivent ici — noms, nature, encadrement du
* transfert. Les descriptions sont dans les fichiers de messages, sinon la
* moitié de la page resterait en français pour un lecteur anglophone.
*
* Les durées de conservation, elles, vivent côté serveur
* (`domain/services/data-retention`) : c'est le serveur qui les applique.
*/
/** Fondement d'un transfert hors Union européenne (chap. V du RGPD). */
export type TransferBasis =
| 'eu' // Aucune sortie de l'Union.
| 'adequacy' // Décision d'adéquation de la Commission.
| 'scc'; // Clauses contractuelles types.
export interface Subprocessor {
/** Clé de traduction sous `marketing.privacy.processors`. */
key: string;
/** Nom commercial, identique dans toutes les langues. */
name: string;
transferBasis: TransferBasis;
/** Vrai si le prestataire n'intervient que sur une fonctionnalité précise. */
conditional: boolean;
}
/**
* Chaque entrée correspond à un service réellement branché, vérifié dans le
* code — pas une liste d'intentions.
*/
export const SUBPROCESSORS: readonly Subprocessor[] = [
{ key: 'stripe', name: 'Stripe', transferBasis: 'scc', conditional: false },
{ key: 'openai', name: 'OpenAI', transferBasis: 'scc', conditional: true },
{ key: 'sentry', name: 'Sentry', transferBasis: 'scc', conditional: false },
{ key: 'pappers', name: 'Pappers', transferBasis: 'eu', conditional: true },
{ key: 'oauth', name: 'Google, Microsoft', transferBasis: 'scc', conditional: true },
];
export interface StoredItem {
/** Nom technique, tel qu'il apparaît dans le navigateur. */
name: string;
/** Cookie déposé par le serveur, ou valeur écrite par la page. */
kind: 'cookie' | 'localStorage';
}
/**
* Ce que le site dépose réellement.
*
* L'inventaire précédent annonçait Google Analytics (`_ga`, `_gid`, `_gat`),
* Facebook Pixel (`_fbp`), LinkedIn (`li_fat_id`) et HubSpot (`hubspotutk`).
* Aucun de ces scripts n'est chargé par l'application : décrire des traceurs
* qui n'existent pas est aussi inexact que d'en dissimuler, et rendait sans
* objet le consentement demandé pour eux.
*/
export const ESSENTIAL_STORAGE: readonly StoredItem[] = [
{ name: 'accessToken', kind: 'cookie' },
{ name: 'refreshToken', kind: 'cookie' },
{ name: 'xpeditis_session', kind: 'cookie' },
{ name: 'cookieConsent', kind: 'localStorage' },
];
export const FUNCTIONAL_STORAGE: readonly StoredItem[] = [{ name: 'NEXT_LOCALE', kind: 'cookie' }];
/**
* Responsable du traitement (art. 13.1.a).
*
* Les champs à `null` sont obligatoires et manquants. La page publique ne
* publie que ce qui est renseigné, et la console d'administration les signale :
* mieux vaut une mention absente, visible et corrigeable, qu'une mention
* inventée.
*/
export interface ControllerIdentity {
tradingName: string;
legalName: string | null;
registrationNumber: string | null;
address: string | null;
privacyContact: string;
/** Délégué à la protection des données, si un DPO a été désigné. */
dpo: string | null;
}
export const CONTROLLER: ControllerIdentity = {
tradingName: 'Xpeditis',
legalName: null,
registrationNumber: null,
address: null,
privacyContact: 'privacy@xpeditis.com',
dpo: null,
};
/** Autorité de contrôle compétente, à mentionner au titre de l'art. 13.2.d. */
export const SUPERVISORY_AUTHORITY = {
name: 'Commission nationale de l’informatique et des libertés (CNIL)',
url: 'https://www.cnil.fr/fr/plaintes',
} as const;
/** Mentions obligatoires encore absentes de l'identité du responsable. */
export function missingControllerFields(controller: ControllerIdentity = CONTROLLER): string[] {
const required: [keyof ControllerIdentity, string][] = [
['legalName', 'legalName'],
['registrationNumber', 'registrationNumber'],
['address', 'address'],
];
return required.filter(([field]) => !controller[field]).map(([, key]) => key);
}