From 72758c1adc93e85a1763219275703128966c61eb Mon Sep 17 00:00:00 2001 From: MaksTinyWorkshop Date: Tue, 7 Apr 2026 15:44:36 +0200 Subject: [PATCH] =?UTF-8?q?capitalisation:=20int=C3=A9gration=2033=20propo?= =?UTF-8?q?sitions=20RL799=5FV2=20(triage=202026-04-07)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend: 21 entrées (general, prisma, contracts, auth, patterns) Frontend: 9 entrées (navigation, tests, general, performance, patterns) Workflow: 5 entrées (story-tracking) Nouveau fichier: backend/patterns/general.md 95_a_capitaliser.md purgé. Co-Authored-By: Claude Opus 4.6 (1M context) --- knowledge/backend/patterns/README.md | 1 + knowledge/backend/patterns/contracts.md | 24 +- knowledge/backend/patterns/general.md | 35 ++ knowledge/backend/risques/README.md | 8 +- knowledge/backend/risques/auth.md | 53 ++- knowledge/backend/risques/contracts.md | 28 +- knowledge/backend/risques/general.md | 368 +++++++++++++++++++ knowledge/backend/risques/prisma.md | 50 ++- knowledge/frontend/patterns/tests.md | 32 +- knowledge/frontend/risques/README.md | 8 +- knowledge/frontend/risques/general.md | 119 ++++++ knowledge/frontend/risques/navigation.md | 24 +- knowledge/frontend/risques/performance.md | 23 ++ knowledge/frontend/risques/tests.md | 30 +- knowledge/workflow/risques/story-tracking.md | 126 ++++++- 15 files changed, 906 insertions(+), 23 deletions(-) create mode 100644 knowledge/backend/patterns/general.md diff --git a/knowledge/backend/patterns/README.md b/knowledge/backend/patterns/README.md index b666a87..73c13e2 100644 --- a/knowledge/backend/patterns/README.md +++ b/knowledge/backend/patterns/README.md @@ -16,3 +16,4 @@ Avant toute proposition backend, identifie le fichier dont le nom et la descript | `multi-tenant.md` | Multi-tenant, isolation, feature flags | 403 vs 404, repository tenant-aware, tenantId dans updates, helper tenant partagé, feature flag tenant, EN enforcement | | `nextjs.md` | Next.js App Router, Server Actions, isolation | Runtime-only logique pure, server-only isolation, utilitaires purs sans server-only, réutiliser champ V1, validation URL externe | | `async.md` | Jobs async, webhooks sortants, queues | Exécution asynchrone outbox light, webhooks sortants HMAC + retries idempotents | +| `general.md` | Architecture générale, helpers, RBAC | Helper auth centralisé enrichissable | diff --git a/knowledge/backend/patterns/contracts.md b/knowledge/backend/patterns/contracts.md index d0bc548..5abab57 100644 --- a/knowledge/backend/patterns/contracts.md +++ b/knowledge/backend/patterns/contracts.md @@ -5,8 +5,8 @@ bucket: patterns tags: [contracts, zod, api, error-codes, monorepo] applies_to: [analysis, implementation, review, architecture] severity: high -validated_on: 2026-03-20 -source_projects: [app-alexandrie] +validated_on: 2026-04-07 +source_projects: [app-alexandrie, RL799_V2] --- # Backend — Patterns : Contracts @@ -147,3 +147,23 @@ return res.status(200).json({ - **4xx** = erreur technique ou de sécurité (401 non authentifié, 403 accès interdit, 404 introuvable) - **200 + flag métier** = état métier normal que le client doit interpréter pour le rendu + +--- + + +## Pattern : Cohérence du pattern Result dans un repository + +- Objectif : garantir une sémantique uniforme du retour d'erreur dans un même fichier repository. +- Contexte : repository utilisant le pattern `{ ok: true; data } | { ok: false }` pour certaines fonctions. +- Quand l'utiliser : dès qu'un repository a au moins une fonction utilisant le pattern Result. +- Risque si ignoré : retourner `null` sur erreur dans une fonction alors que les voisines retournent `{ ok: false }` crée une ambiguïté sémantique (null = pas trouvé vs null = erreur) et empêche le service d'adapter sa réponse HTTP (404 vs 500). +- Validé le : 03-04-2026 +- Contexte technique : TypeScript / Prisma — RL799_V2 story 6A.5 + +### Règle + +Quand un repository utilise le pattern `{ ok: true; data } | { ok: false }` pour certaines fonctions, **toutes** les fonctions du même fichier doivent utiliser le même pattern. + +### Signal review + +- `catch { return null }` dans un repository qui utilise `{ ok: false }` ailleurs diff --git a/knowledge/backend/patterns/general.md b/knowledge/backend/patterns/general.md new file mode 100644 index 0000000..b513205 --- /dev/null +++ b/knowledge/backend/patterns/general.md @@ -0,0 +1,35 @@ +--- +title: Backend — Patterns : Général +domain: backend +bucket: patterns +tags: [auth, helpers, architecture, rbac] +applies_to: [implementation, review, architecture] +severity: medium +validated_on: 2026-04-07 +source_projects: [RL799_V2] +--- + +# Backend — Patterns : Général + +> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/patterns/README.md` pour l'index complet. + +--- + + +## Pattern : Helper d'authentification centralisé enrichissable + +- Objectif : éviter la duplication de logique RBAC dans chaque service en centralisant un seul `requireRoleAccess` dans `lib/authHelpers.ts`. +- Contexte : chaque nouveau service réimplémentait `requireRoleAccess` localement avec des variations subtiles (certains retournaient `{ email }`, d'autres `{ email, role }`). +- Quand l'utiliser : dès qu'un endpoint nécessite une vérification de rôle ou d'authentification. +- Validé le : 07-04-2026 +- Contexte technique : backend / RBAC — RL799_V2 epics 7-8-9 + +### Règle + +- Un seul `requireRoleAccess` dans `lib/authHelpers.ts`, retournant toutes les infos du token utiles (email, role, sub). +- Quand un service a besoin d'une info supplémentaire : enrichir le helper centralisé, pas le copier. +- Le retour riche est rétrocompatible : les consommateurs existants utilisent ce dont ils ont besoin via destructuring. + +### Signal review + +- Tout service qui importe `verifyToken` directement ET fait son propre check RBAC est suspect de duplication. diff --git a/knowledge/backend/risques/README.md b/knowledge/backend/risques/README.md index b281676..bcea757 100644 --- a/knowledge/backend/risques/README.md +++ b/knowledge/backend/risques/README.md @@ -8,11 +8,11 @@ Avant toute proposition backend, identifie le fichier dont le nom et la descript | Fichier | Domaine | Entrées clés | |---------|---------|--------------| -| `auth.md` | Auth, sessions, guards, accès | AuthN/AuthZ dispersée, guard global manquant, null-check request.user, AdminRoleGuard sans @RequireAdminRole, GET sans contrôle accès, cookie après révocation, mock session sans expiresAt, buildApp partagé e2e | -| `contracts.md` | Contrats, validation, codes erreur | Contrats implicites, erreurs non standardisées, duplication constantes, schema orphelin, code erreur générique 409, ForbiddenException pour validation | -| `prisma.md` | Prisma, DB, transactions, migrations | @unique nullable, TOCTOU transaction, OR tenantId null, nextOrder race condition, tenantId sans FK, schema divergence spec, getter manquant, init module build, clearAllMocks imbriqué, cursor non validé | +| `auth.md` | Auth, sessions, guards, accès | AuthN/AuthZ dispersée, guard global manquant, null-check request.user, AdminRoleGuard sans @RequireAdminRole, GET sans contrôle accès, cookie après révocation, mock session sans expiresAt, buildApp partagé e2e, champ absent JWT, email login vs contact | +| `contracts.md` | Contrats, validation, codes erreur | Contrats implicites, erreurs non standardisées, duplication constantes, schema orphelin, code erreur générique 409, ForbiddenException pour validation, process.env direct, statut métier non propagé | +| `prisma.md` | Prisma, DB, transactions, migrations | @unique nullable, TOCTOU transaction, OR tenantId null, nextOrder race condition, tenantId sans FK, schema divergence spec, getter manquant, init module build, clearAllMocks imbriqué, cursor non validé, enum-like String, migration manuelle hors git, relation 1:1 sans unique | | `stripe.md` | Stripe, paiements, webhooks, subscriptions | billing_cycle_anchor vs current_period_end, list() sans has_more, concurrence trial→payant, non-idempotence, 200 pendant processing | | `nestjs.md` | NestJS, controllers, providers | TooManyRequestsException NestJS 11, controller corrompu insertions, repository dead layer, interface provider incomplète | | `redis.md` | Redis, cache, quotas, TTL | Thrash connexion sous charge, entitlements TTL > SLA, compteurs in-memory, TTL heure locale ±12h | | `nextjs.md` | Next.js, build, routing | Prisma init au chargement module, server-only dans repositories, redirect boucle infinie feature flags | -| `general.md` | Observabilité, migrations, performance | Observabilité insuffisante, migrations non reproductibles, upsert N+1 provider | +| `general.md` | Observabilité, migrations, performance, architecture | Observabilité insuffisante, migrations non reproductibles, upsert N+1, authorize-after-fetch, valeur sentinelle DTO, idempotence endpoint, fichier orphelin, mélange Date UTC/locale, champ fantôme Zod, catch vide, params non validés, cast TS brut, chevauchement temporel, TOCTOU, biais agrégation, couplage types erreur, service HTTP-aware, count sans filtre, env top-level | diff --git a/knowledge/backend/risques/auth.md b/knowledge/backend/risques/auth.md index 10ede0e..3b6de86 100644 --- a/knowledge/backend/risques/auth.md +++ b/knowledge/backend/risques/auth.md @@ -5,8 +5,8 @@ bucket: risques tags: [auth, guards, request-user, sessions, admin] applies_to: [implementation, review, debug] severity: high -validated_on: 2026-03-24 -source_projects: [app-alexandrie] +validated_on: 2026-04-07 +source_projects: [app-alexandrie, RL799_V2] --- # Backend — Risques & vigilance : Auth @@ -234,3 +234,52 @@ it('retourne 403 si subscription inactive', async () => { - **Règle** : ne jamais tenter de surcharger un mock partagé dans un `it` — créer un `buildApp` isolé avec `app.close()` en fin de test - Contexte technique : NestJS / Jest e2e — app-alexandrie 24-03-2026 + +--- + + +## Champ métier absent du JWT — découplage silencieux frontend/backend + +### Risques + +- Le frontend lit un champ dans `decodeJwtPayload(token)` qui n'est jamais émis par le service d'authentification +- Le comportement est silencieux — `undefined` est traité comme `''`, aucune erreur visible, l'UI se dégrade sans signal d'alerte + +### Symptômes + +- `decodeJwtPayload(token).field` retourne `undefined` pour tous les utilisateurs réels +- Filtres de grade/rôle côté UI entièrement inopérants (rank=0, aucune tab affichée) + +### Bonnes pratiques / mitigations + +- Toute donnée lue via `decodeJwtPayload` côté frontend doit être explicitement émise dans le payload JWT côté backend +- Lors de l'ajout d'un champ à `JwtPayload` (type TypeScript), vérifier immédiatement que le service d'authentification inclut ce champ à l'émission +- Ajouter un test d'intégration login → decode qui vérifie la présence des champs critiques dans le token retourné +- **Signal review** : un champ apparaît dans le type `JwtPayload` côté frontend sans modification correspondante dans `authService.ts` + +- Contexte technique : JWT / auth — RL799_V2 02-04-2026 + +--- + + +## Confusion email login / email de contact dans un endpoint annuaire + +### Risques + +- Le mapping de l'endpoint annuaire utilise `email: user.email` (email de login, toujours présent) alors que l'intention est d'exposer un email de contact optionnel +- Même un utilisateur à bas privilège peut récupérer les emails de login de tous les membres + +### Symptômes + +- `email: user.email` dans le mapping d'un endpoint de type "annuaire" ou "liste membres" +- Emails de connexion exposés à tous les utilisateurs authentifiés + +### Bonnes pratiques / mitigations + +- Dans tout endpoint annuaire ou profil public, distinguer explicitement : + - **email de login** : identifiant de compte, JAMAIS exposé à un tiers dans un endpoint annuaire + - **email de contact** : champ optionnel dans le profil ou la table directory, exposé uniquement s'il est renseigné +- Si le modèle ne dispose pas encore d'un champ email de contact distinct, renvoyer `undefined` pour le champ email dans l'annuaire +- **Signal review** : `email: user.email` dans le mapping d'un endpoint annuaire + +- Contexte technique : auth / annuaire — RL799_V2 02-04-2026 diff --git a/knowledge/backend/risques/contracts.md b/knowledge/backend/risques/contracts.md index 1df7d97..6075d59 100644 --- a/knowledge/backend/risques/contracts.md +++ b/knowledge/backend/risques/contracts.md @@ -5,8 +5,8 @@ bucket: risques tags: [contracts, zod, validation, error-codes, requestid] applies_to: [analysis, implementation, review, debug] severity: high -validated_on: 2026-03-24 -source_projects: [app-alexandrie] +validated_on: 2026-04-07 +source_projects: [app-alexandrie, RL799_V2] --- # Backend — Risques & vigilance : Contracts @@ -198,3 +198,27 @@ Tableau de correspondance : - **Checklist review** : rechercher `process.env` dans `src/` hors `config/` ou `main.ts` — tout hit est suspect. - Contexte technique : NestJS / ConfigService — 30-03-2026 + +--- + + +## Nouveau statut métier non propagé dans le contrat partagé + +### Risques + +- Un repository ou service crée une nouvelle valeur pour un champ enum-like (ex: `'absent_auto'` pour un champ `status: String`) sans la déclarer dans le type DTO ni le schema Zod du package shared +- Le champ Prisma étant un `String` sans contrainte DB, l'écriture passe, mais toute lecture avec validation Zod rejetterait la donnée + +### Symptômes + +- String literal non présent dans le type union ou le schema Zod du DTO correspondant +- `catch {}` vide dans les repositories qui avalent les erreurs de validation sans log + +### Bonnes pratiques / mitigations + +Quand un repository ou service crée une nouvelle valeur pour un champ enum-like : +1. Ajouter la valeur au type DTO dans `packages/shared` (ou équivalent) +2. Ajouter la valeur au schema de validation Zod correspondant +3. Vérifier que les endpoints de lecture qui parsent ces données acceptent la nouvelle valeur + +- Contexte technique : Zod / contrats partagés — RL799_V2 03-04-2026 diff --git a/knowledge/backend/risques/general.md b/knowledge/backend/risques/general.md index 74189b4..4e2db4a 100644 --- a/knowledge/backend/risques/general.md +++ b/knowledge/backend/risques/general.md @@ -70,3 +70,371 @@ - Précharger les données nécessaires avant boucle - Mesurer explicitement le coût d'un `upsert` unitaire dans les flux de sync - Contexte technique : Prisma / synchronisation provider — 10-03-2026 + +--- + + +## Anti-pattern : Authorize-after-fetch (contrôle d'accès après chargement) + +### Risques + +- Le contrôle d'accès effectué après chargement des relations métier volumineuses augmente le coût DB et la surface de fuite temporelle pour des requêtes finalement refusées + +### Symptômes + +- Handler qui charge un agrégat complet (avec includes/relations) puis vérifie l'accès +- Requêtes refusées après un temps de réponse anormalement long + +### Bonnes pratiques / mitigations + +- Pour les endpoints détail sensibles, filtrer l'accès dans la requête DB (`where` + scope grade/tenant) ou faire un pré-check minimal avant de charger les relations +- Les accès non autorisés ne doivent pas déclencher un fetch complet des données métier + +- Contexte technique : backend général — RL799_V2 02-04-2026 + +--- + + +## Valeur sentinelle `""` au lieu de `null` dans un DTO de date nullable + +### Risques + +- Un champ de date optionnel dans le modèle Prisma (`Date | null`) sérialisé en `""` dans le DTO au lieu de `null` +- Le type DTO ment (`string` au lieu de `string | null`), et tout consommateur testant `if (profile.field)` a un comportement incorrect sur `""` + +### Symptômes + +- `?? ''` sur une date issue de `?.toISOString()` dans un repository Prisma +- Type DTO avec `someDate: string` non optionnel pour un modèle qui autorise `null` en DB + +### Bonnes pratiques / mitigations + +- Si un champ de date peut être `null` en DB, le type DTO doit être `string | null` +- Le repository utilise `?? null` (pas `?? ''`) pour les dates nulles +- Le frontend utilise `??` (nullish coalescing) pas `||` pour les fallbacks d'affichage sur ces champs + +- Contexte technique : Prisma / DTO — RL799_V2 02-04-2026 + +--- + + +## Endpoint idempotent sans contrôle de doublon + +### Risques + +- Un endpoint crée un event/enregistrement censé être unique par contexte (ex: un vote par tenue, un PDF par planche) sans vérifier l'existence préalable +- Un double-clic ou retry réseau crée des doublons silencieux, ou des fichiers orphelins en cas d'artefact physique + +### Symptômes + +- `writeFile` + `prisma.create` sans `findFirst` préalable dans le même handler +- Plusieurs enregistrements identiques avec le même contexte en base +- Fichiers physiques (PDF, export) dupliqués sur disque + +### Bonnes pratiques / mitigations + +- Avant tout `create` d'un event contextuel, lire les events existants avec le même filtre de contexte +- Retourner 409 si l'event existe déjà, ou mettre à jour l'existant selon la sémantique métier +- Pour les artefacts physiques : retourner l'existant (200) au lieu de recréer (201), ou supprimer l'ancien avant d'en créer un nouveau + +- Contexte technique : backend général — RL799_V2 03-04-2026 + +--- + + +## Fichier orphelin sur échec de persistance en base + +### Risques + +- Un service écrit un fichier sur disque puis crée l'entrée correspondante en base ; si la création en base échoue, le fichier reste orphelin +- Un `catch {}` vide rend le debug impossible + +### Symptômes + +- Fichiers physiques présents sur disque sans enregistrement correspondant en base +- Erreurs de persistance invisibles dans les logs + +### Bonnes pratiques / mitigations + +```ts +const file = await writeFileToDisk(...); +try { + await createDbRecord(...); +} catch (err) { + await cleanupFile(file.path); + console.error('Erreur persistance:', err); + throw err; +} +``` + +- Si la création en base échoue, supprimer le fichier physique (cleanup) +- Toujours logger l'erreur dans le catch, même si on retourne une réponse d'erreur propre au client + +- Contexte technique : backend / fichiers — RL799_V2 03-04-2026 + +--- + + +## Mélange Date locale / Date UTC dans les données de seed + +### Risques + +- Des dates dynamiques construites via `new Date(year, month, day, hour)` (heure locale) cohabitent avec des dates fixes en UTC (`new Date('...T19:00:00Z')`) dans le même fichier +- L'incohérence est silencieuse et produit des décalages horaires non reproductibles entre environnements + +### Symptômes + +- `new Date(year, month, day, hour)` (local) dans un fichier qui utilise aussi `new Date('...Z')` (UTC) +- Données de seed dont les heures varient selon la timezone de l'environnement dev + +### Bonnes pratiques / mitigations + +- Les dates dynamiques dans un seed doivent être construites en UTC (`Date.UTC(...)`) si les dates fixes sont en UTC +- **Signal review** : `new Date(year, month, day, hour)` dans un fichier qui utilise aussi `new Date('...Z')` + +- Contexte technique : Node.js / seed — RL799_V2 03-04-2026 + +--- + + +## Champ fantôme accepté par validation Zod mais non persisté + +### Risques + +- Un champ optionnel ajouté dans un schéma Zod est accepté par la validation mais jamais exploité par la chaîne handler → repository → modèle Prisma +- L'utilisateur envoie une donnée sans erreur, mais elle est silencieusement perdue + +### Symptômes + +- Champ optionnel dans le schéma Zod absent du modèle Prisma +- Champ passé au service mais non inclus dans l'appel `create`/`update` du repository + +### Bonnes pratiques / mitigations + +- Avant d'ajouter un champ au schéma de validation, s'assurer que le modèle DB le supporte +- Si le modèle ne le supporte pas, ne pas accepter le champ du tout +- **Signal review** : champ dans le schéma Zod qui n'apparaît pas dans le `data` de l'appel Prisma + +- Contexte technique : Zod / Prisma — RL799_V2 06-04-2026 + +--- + + +## Catch vide avalant les exceptions sans logging + +### Risques + +- Les `catch {}` ou `catch { // ignore }` dans les services métier masquent les erreurs réelles +- En production, un disque plein ou un bug PDFKit retourne un message générique sans trace serveur + +### Symptômes + +- `catch {` sans `console.error` ni logger dans un service métier +- Bloc catch qui retourne un message d'erreur générique sans capturer `err` + +### Bonnes pratiques / mitigations + +- Toujours capturer et logger l'exception dans les catch des services métier +- Les catch de rollback secondaire (ex: unlink d'un fichier) peuvent être silencieux mais devraient au minimum émettre un `warn` +- **Signal review** : `catch {` sans variable dans un service métier (hors rollback secondaire) + +- Contexte technique : backend général — RL799_V2 06-04-2026 + +--- + + +## Paramètres de route non validés avant passage à Prisma + +### Risques + +- Un paramètre de route (`id`, `memberId`) passé directement à `prisma.findUnique({ where: { id } })` sans validation de format (UUID, slug) +- Des chaînes arbitraires atteignent la couche DB, polluant les logs et violant le principe de validation aux frontières + +### Symptômes + +- `const { id } = params; prisma.model.findUnique({ where: { id } })` sans `isValidUuid(id)` +- Erreurs Prisma non significatives en production sur des identifiants malformés + +### Bonnes pratiques / mitigations + +- Toujours valider les identifiants (UUID, slug, etc.) dès l'entrée du handler ou du service, avant tout appel DB +- Retourner 400 immédiatement si invalide + +- Contexte technique : backend / validation — RL799_V2 07-04-2026 + +--- + + +## Cast TypeScript brut sur valeurs DB non contraintes + +### Risques + +- Un cast `value as 'expected_a' | 'expected_b'` sur un champ Prisma de type `String` sans enum DB masque silencieusement toute valeur inattendue +- Le type-checking passe, mais une valeur inattendue en DB provoque des comportements incohérents en aval + +### Symptômes + +- `status as 'pending' | 'late'` sur un champ Prisma `String` sans enum DB +- Pas de fallback explicite pour les valeurs non attendues + +### Bonnes pratiques / mitigations + +- Utiliser des conditions explicites (`if (s === 'a') ... else if (s === 'b') ... else fallback`) ou un mapping validé +- Ne jamais faire confiance au type runtime via un simple cast TS +- **Signal review** : `as 'literal_a' | 'literal_b'` sur un champ Prisma de type `String` + +- Contexte technique : TypeScript / Prisma — RL799_V2 07-04-2026 + +--- + + +## Création de ressource temporelle sans détection de chevauchement + +### Risques + +- Un endpoint crée une ressource avec `startDate`/`endDate` sans vérifier les collisions avec les ressources existantes +- Un double-clic ou une erreur humaine génère des doublons avec les mêmes dates + +### Symptômes + +- Deux ressources avec des plages temporelles qui se chevauchent pour le même contexte (cotisations, abonnements, sessions) + +### Bonnes pratiques / mitigations + +- Avant insertion, query les ressources existantes dont les plages se chevauchent : `WHERE start < newEnd AND end > newStart` +- Retourner 409 Conflict si collision +- S'applique aux cotisations, abonnements, périodes de garde, sessions — tout ce qui a des bornes temporelles + +- Contexte technique : backend général — RL799_V2 07-04-2026 + +--- + + +## TOCTOU sur opérations conditionnelles sans transaction + +### Risques + +- `findUnique` → check status → `update` en séquence sans transaction crée une fenêtre TOCTOU (Time-of-Check Time-of-Use) +- Un traitement concurrent peut modifier l'état entre la vérification et la mise à jour + +### Symptômes + +- Données incohérentes sous charge concurrente (rare en MVP mais critique en prod) +- Double relance de cotisation, double changement de statut + +### Bonnes pratiques / mitigations + +- Wrapper dans `prisma.$transaction()` ou utiliser un `UPDATE ... WHERE status != 'paid'` atomique +- **Règle** : toute séquence read → validate → write sur la même entité doit être atomique + +- Contexte technique : Prisma / backend général — RL799_V2 07-04-2026 + +--- + + +## Calcul de moyenne vs ratio global — biais d'agrégation + +### Risques + +- Le ratio global (somme numérateurs / somme dénominateurs) donne un résultat biaisé vers les entités avec les dénominateurs les plus grands quand on veut calculer une "moyenne de taux" sur N entités + +### Symptômes + +- "% de présence moyenne par degré" calculé comme total_présents / total_inscrits au lieu d'une moyenne par tenue +- Tests verts car les données de test symétriques donnent le même résultat avec les deux méthodes + +### Bonnes pratiques / mitigations + +- Moyenne de présence par tenue → calculer le taux par tenue, puis la moyenne des taux — pas le ratio global +- Rédiger un test qui construit des données asymétriques pour détecter la différence (ex: une tenue avec 10 inscrits/8 présents, une avec 2 inscrits/2 présents → 83% global vs 90% moyenne correcte) + +- Contexte technique : backend / statistiques — RL799_V2 07-04-2026 + +--- + + +## Couplage sémantique sur les types d'erreur partagés + +### Risques + +- Un helper générique (ex: authHelpers) importe un type nommé d'après un domaine métier spécifique (ex: ConvocationErrorCode) +- Chaque nouveau domaine qui utilise l'auth helper "dépend" conceptuellement du module d'origine + +### Symptômes + +- Un helper générique utilisé par 12+ fichiers importe un type nommé d'après un seul domaine métier +- Couplage sémantique incohérent hérité de la première implémentation + +### Bonnes pratiques / mitigations + +- À la prochaine refacto touchant le helper, extraire un type `ApiErrorCode` générique dans le package partagé et faire pointer les types domaine vers lui + +- Contexte technique : backend / architecture — RL799_V2 07-04-2026 + +--- + + +## Service HTTP-aware — violation de la séparation des couches + +### Risques + +- Un fichier `service.ts` importe `Response`, construit les headers et status HTTP, et retourne un objet `Response` +- Le service devient intestable sans mock HTTP, la logique métier est couplée au transport, et la réutilisation (CLI, cron, WebSocket) est impossible + +### Symptômes + +- Le fichier route.ts ne fait que `(req) => handleX(req)` — pass-through d'une ligne +- Import de `Response` dans un fichier service + +### Bonnes pratiques / mitigations + +- Le service doit retourner `{ data: T } | { error: { code, message } }` et la route construit la Response +- Refactoriser quand un service est touché + +- Contexte technique : architecture en couches — RL799_V2 07-04-2026 + +--- + + +## Count sans filtre sur User — confusion membres / utilisateurs + +### Risques + +- `prisma.user.count()` sans `where` inclut les comptes techniques (admin, secretaire) dans les comptages métier "nombre de membres inscrits" +- Les stats métier sont faussées silencieusement + +### Symptômes + +- `prisma.user.count()` sans `where` pour un affichage de type "effectifs inscrits" +- Écart entre le comptage affiché et la réalité métier + +### Bonnes pratiques / mitigations + +- Toujours filtrer `{ where: { role: { not: 'admin' } } }` (ou équivalent) pour les comptages métier +- À terme, ajouter un champ `status` (active/inactive/suspended) pour distinguer les membres actifs +- **Signal review** : `prisma.user.count()` sans `where` dans un contexte de statistiques + +- Contexte technique : Prisma / stats — RL799_V2 07-04-2026 + +--- + + +## Variables d'environnement lues au top-level d'un module + +### Risques + +- `const FOO = process.env.FOO || 'default'` en dehors d'une fonction fige la valeur au premier `import` +- Les tests qui modifient `process.env` après l'import ne voient pas l'effet +- Config non-reloadable sans redémarrage + +### Symptômes + +- `process.env.X` assigné à une constante au top-level d'un module Node +- Tests qui doivent manipuler l'ordre d'import pour surcharger une variable d'environnement + +### Bonnes pratiques / mitigations + +- Encapsuler dans un getter : `const getFoo = () => process.env.FOO || 'default'` +- Permet la surcharge en test et le rechargement dynamique + +- Contexte technique : Node.js / tests — RL799_V2 07-04-2026 diff --git a/knowledge/backend/risques/prisma.md b/knowledge/backend/risques/prisma.md index 852801a..0bf2a02 100644 --- a/knowledge/backend/risques/prisma.md +++ b/knowledge/backend/risques/prisma.md @@ -5,8 +5,8 @@ bucket: risques tags: [prisma, transactions, tenant, schema, race-condition] applies_to: [implementation, review, debug, architecture] severity: high -validated_on: 2026-03-23 -source_projects: [app-template-resto, app-alexandrie] +validated_on: 2026-04-07 +source_projects: [app-template-resto, app-alexandrie, RL799_V2] --- # Backend — Risques & vigilance : Prisma @@ -338,3 +338,49 @@ if (cursor) { 3. **Signal review** : tout cast `as EnumType` sur une valeur issue de Prisma = dette à corriger immédiatement. - Contexte technique : Prisma / PostgreSQL — app-alexandrie 31-03-2026 + +--- + + +## Migration appliquée manuellement hors git + +### Risques + +- Une migration Prisma appliquée via DDL direct + `migrate resolve --applied` produit un fichier `migration.sql` qui peut rester untracked dans git +- Quiconque clone le repo et lance `prisma migrate deploy` n'a pas la migration + +### Symptômes + +- `??` (untracked) dans `git status` sur le dossier `prisma/migrations/` +- `prisma migrate status` rapporte un drift entre le schéma et l'état de la DB + +### Bonnes pratiques / mitigations + +Checklist minimale après `prisma migrate resolve --applied` : +- `git status` → vérifier que `prisma/migrations//migration.sql` est présent et tracké +- `git add prisma/migrations//` si untracked +- Valider que `prisma migrate status` rapporte la migration comme appliquée sans drift + +- Contexte technique : Prisma / migrations manuelles — RL799_V2 02-04-2026 + +--- + + +## Relation 1:1 métier sans contrainte `@unique` en DB + +### Risques + +- Un mapping métier 1:1 (ex: planche tracée → document généré) implémenté avec un simple index + `findFirst` crée un risque de backlinks non déterministes en cas d'incohérence de données + +### Symptômes + +- Champ de référence nullable seulement indexé, puis lecture via `findFirst` +- Le code suppose l'unicité mais la base ne l'impose pas + +### Bonnes pratiques / mitigations + +- Poser une contrainte `@unique` (nullable) sur la référence quand la relation métier est 1:1 +- Préférer `findUnique` / lecture déterministe +- **Signal review** : si le code suppose l'unicité, la base doit l'imposer explicitement + +- Contexte technique : Prisma / contraintes DB — RL799_V2 06-04-2026 diff --git a/knowledge/frontend/patterns/tests.md b/knowledge/frontend/patterns/tests.md index 1a9330f..93621c8 100644 --- a/knowledge/frontend/patterns/tests.md +++ b/knowledge/frontend/patterns/tests.md @@ -5,8 +5,8 @@ bucket: patterns tags: [tests, react-native, jest, styles, ui] applies_to: [implementation, review] severity: medium -validated_on: 2026-03-19 -source_projects: [app-alexandrie] +validated_on: 2026-04-07 +source_projects: [app-alexandrie, RL799_V2] --- # Frontend — Patterns : Tests @@ -62,3 +62,31 @@ it('variante primary utilise colors.primary', () => { 1. `.spec.ts` (node) : tokens, valeurs, logique pure 2. `.spec.tsx` (config séparée avec renderer) : rendu visuel, interactions + +--- + + +## Pattern : Niveaux de test frontend Vue + +### Synthèse + +- **Objectif** : clarifier quand utiliser chaque niveau de test frontend Vue (structurel, composant monté, E2E). +- **Contexte** : les tests frontend du projet sont du string-matching sur le source `.vue` (`readFileSync` + `includes`). Ce pattern est rapide mais ne valide pas le comportement réel. +- **Quand l'utiliser** : à chaque choix de stratégie de test sur un composant Vue. + +### Niveaux + +| Niveau | Outil | Quand l'utiliser | +|--------|-------|-----------------| +| Structurel (string-matching) | `node:test` + `readFileSync` | Smoke tests : vérifier qu'un composant contient les imports, props, slots attendus. Acceptable pour MVP/sprint rapide. | +| Composant monté | `@vue/test-utils` + `vitest` | Valider le comportement interactif (toggle, emit, slots conditionnels). Obligatoire dès qu'il y a de la logique UI. | +| E2E | Playwright | Parcours critiques multi-pages. | + +### Règle + +Si un test vérifie un *comportement* (ex: "le menu se ferme après clic"), il doit monter le composant, pas chercher une string dans le source. + +### Validation + +- Validé le : 03-04-2026 +- Contexte technique : Vue 3 / node:test — RL799_V2 story 6A.8 diff --git a/knowledge/frontend/risques/README.md b/knowledge/frontend/risques/README.md index e63f3a0..72a46cd 100644 --- a/knowledge/frontend/risques/README.md +++ b/knowledge/frontend/risques/README.md @@ -10,10 +10,10 @@ Avant toute proposition frontend, identifie le fichier dont le nom et la descrip |---------|---------|--------------| | `auth.md` | Auth, guards de rôle, entitlements, OAuth | Auth côté client, loading infini écran gated, bouton OAuth vide, guard rôle flash UX | | `state.md` | Zustand, state management, erreurs async, optimistic UI | Erreurs silencieuses, catch sans feedback, auto-reset état dégradé, fire-and-forget refresh, boolean UI hardcodé, flag isLoading unique, erreur sans rethrow, optimistic update sous-listes | -| `navigation.md` | Expo Router, deep link, useEffect fetch, contexte store | Store vide deep link/reload, guard incomplet états terminaux, collection sans clé contexte | +| `navigation.md` | Expo Router, Vue Router, deep link, useEffect fetch, contexte store | Store vide deep link/reload, guard incomplet états terminaux, collection sans clé contexte, double route racine, router-link disabled, état local query param | | `design-tokens.md` | Design tokens, spacing, Tailwind, StyleSheet RN | Double système espacement, dimensions via spacing, inline styles dashboard, classes Tailwind invalides | | `nextjs.md` | Next.js App Router, SSR, Server Actions, sécurité | useSearchParams sans Suspense, type ViewData dupliqué, composant React .ts, double validation segment, consent state ambigu, script inline XSS, window.location.reload, useTransition snapshot, window.confirm, import type server, img natif, useTransition global liste, formulaire defaultValue sans key | -| `tests.md` | Jest, ts-jest, tests React Native | Config node bloque .tsx, faux test négatif | -| `performance.md` | Re-renders, memoization, useCallback | Sur-renders bundle non maîtrisé, useCallback inutile inline | +| `tests.md` | Jest, ts-jest, tests React Native, Vue | Config node bloque .tsx, faux test négatif, helpers copiés, test écran indirect, test façade flux réel, tests présence textuelle | +| `performance.md` | Re-renders, memoization, useCallback, fetch | Sur-renders bundle non maîtrisé, useCallback inutile inline, fetch sans timeout | | `react-native.md` | React Native, fetch, ScrollView, TextInput | Focus ring TextInput, contentInset iOS-only, fetch sans response.ok | -| `general.md` | Accessibilité, regex, patterns transversaux | Accessibilité oubliée a11y, regex globale singleton lastIndex | +| `general.md` | Accessibilité, regex, patterns transversaux, monorepo | Accessibilité oubliée a11y, regex globale singleton lastIndex, Alert.prompt iOS-only, primitive UI couplée, migration partielle classes legacy, ARIA roles sans clavier, duplication logique métier monorepo, event listeners globaux modales, boutons imbriqués, fire-and-forget sans feedback | diff --git a/knowledge/frontend/risques/general.md b/knowledge/frontend/risques/general.md index 8d060e5..d7d0fb8 100644 --- a/knowledge/frontend/risques/general.md +++ b/knowledge/frontend/risques/general.md @@ -129,3 +129,122 @@ function processLinks(content: string) { - Si un reliquat legacy doit rester temporairement, documenter explicitement son périmètre et sa date de sortie attendue - En review, traiter toute nouvelle utilisation d'une classe legacy équivalente comme une régression de standardisation - Contexte technique : Vue 3 / design system léger — RL799_V2, 02-04-2026 + +--- + + +## ARIA roles sans comportement clavier associé + +### Risques + +- Poser `role="menu"` / `role="menuitem"` sur un composant sans implémenter le pattern clavier donne une fausse impression d'accessibilité +- Les rôles ARIA trompent les lecteurs d'écran et violent WCAG 2.1 (4.1.2 Name, Role, Value) + +### Symptômes + +- `role="menu"` sans fermeture via `Escape` +- Pas de navigation `ArrowUp` / `ArrowDown` ni de roving tabindex + +### Bonnes pratiques / mitigations + +Poser `role="menu"` / `role="menuitem"` implique obligatoirement : +- Fermeture via `Escape` +- Navigation via `ArrowUp` / `ArrowDown` +- Roving tabindex (`tabindex="0"` sur l'item actif, `-1` sur les autres) +- Focus automatique du premier item à l'ouverture + +**Règle** : ne jamais poser un `role` ARIA de widget interactif sans implémenter le pattern clavier correspondant (cf. WAI-ARIA Authoring Practices) + +- Contexte technique : Vue 3 / accessibilité — RL799_V2 03-04-2026 + +--- + + +## Duplication de logique métier dans les composants UI (monorepo) + +### Risques + +- Dans un monorepo avec un package partagé (`shared`), les fonctions utilitaires métier (ex: conversion grade → rang) sont redéfinies localement dans les composants ou pages frontend +- Ce type de duplication silencieuse provoque des divergences à terme + +### Symptômes + +- Fonction `switch/case` ou mapping identique à une fonction déjà exportée par `shared` +- Même signature et même logique dans plusieurs fichiers de couches différentes (composant, page, service) + +### Bonnes pratiques / mitigations + +- Les fonctions utilitaires métier ne doivent jamais être redéfinies localement dans les composants ou pages frontend +- Importer systématiquement depuis le package partagé (`@monrepo/shared` ou équivalent) plutôt que de copier-coller la logique +- **Signal review** : grep des fonctions utilitaires existantes dans shared avant de valider un nouveau switch/case + +- Contexte technique : Vue 3 / monorepo — RL799_V2 06-04-2026 + +--- + + +## Event listeners globaux pour interactions modales + +### Risques + +- `window.addEventListener('keydown')` pour capturer Escape dans une modale crée un listener global qui peut confliter avec d'autres modales +- Le listener fuit si le composant est mal démonté + +### Symptômes + +- `window.addEventListener('keydown', handler)` dans un composant modale +- Cleanup dans `onBeforeUnmount` mais risque de fuite si le démontage échoue + +### Bonnes pratiques / mitigations + +- Utiliser `@keydown.escape` directement sur l'élément dialog avec `tabindex="-1"` + focus automatique à l'ouverture +- Élimine le besoin de cleanup et scope l'interaction au composant +- **Signal review** : dans tout composant modale, vérifier que les listeners clavier sont sur l'élément, pas sur `window` + +- Contexte technique : Vue 3 / modales — RL799_V2 06-04-2026 + +--- + + +## Boutons imbriqués dans les listes interactives + +### Risques + +- Un `