capitalisation: intégration 33 propositions RL799_V2 (triage 2026-04-07)

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) <noreply@anthropic.com>
This commit is contained in:
MaksTinyWorkshop
2026-04-07 15:44:36 +02:00
parent 07f39ad433
commit 72758c1adc
15 changed files with 906 additions and 23 deletions

View File

@@ -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 | | `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 | | `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 | | `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 |

View File

@@ -5,8 +5,8 @@ bucket: patterns
tags: [contracts, zod, api, error-codes, monorepo] tags: [contracts, zod, api, error-codes, monorepo]
applies_to: [analysis, implementation, review, architecture] applies_to: [analysis, implementation, review, architecture]
severity: high severity: high
validated_on: 2026-03-20 validated_on: 2026-04-07
source_projects: [app-alexandrie] source_projects: [app-alexandrie, RL799_V2]
--- ---
# Backend — Patterns : Contracts # 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) - **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 - **200 + flag métier** = état métier normal que le client doit interpréter pour le rendu
---
<a id="pattern-coherence-result-repository"></a>
## 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

View File

@@ -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.
---
<a id="pattern-helper-auth-centralise-enrichissable"></a>
## 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.

View File

@@ -8,11 +8,11 @@ Avant toute proposition backend, identifie le fichier dont le nom et la descript
| Fichier | Domaine | Entrées clés | | 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 | | `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 | | `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é | | `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 | | `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 | | `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 | | `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 | | `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 |

View File

@@ -5,8 +5,8 @@ bucket: risques
tags: [auth, guards, request-user, sessions, admin] tags: [auth, guards, request-user, sessions, admin]
applies_to: [implementation, review, debug] applies_to: [implementation, review, debug]
severity: high severity: high
validated_on: 2026-03-24 validated_on: 2026-04-07
source_projects: [app-alexandrie] source_projects: [app-alexandrie, RL799_V2]
--- ---
# Backend — Risques & vigilance : Auth # 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 - **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 - Contexte technique : NestJS / Jest e2e — app-alexandrie 24-03-2026
---
<a id="risque-champ-metier-absent-jwt"></a>
## 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
---
<a id="risque-email-login-vs-contact-annuaire"></a>
## 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

View File

@@ -5,8 +5,8 @@ bucket: risques
tags: [contracts, zod, validation, error-codes, requestid] tags: [contracts, zod, validation, error-codes, requestid]
applies_to: [analysis, implementation, review, debug] applies_to: [analysis, implementation, review, debug]
severity: high severity: high
validated_on: 2026-03-24 validated_on: 2026-04-07
source_projects: [app-alexandrie] source_projects: [app-alexandrie, RL799_V2]
--- ---
# Backend — Risques & vigilance : Contracts # 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. - **Checklist review** : rechercher `process.env` dans `src/` hors `config/` ou `main.ts` — tout hit est suspect.
- Contexte technique : NestJS / ConfigService — 30-03-2026 - Contexte technique : NestJS / ConfigService — 30-03-2026
---
<a id="risque-statut-metier-non-propage-contrat"></a>
## 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

View File

@@ -70,3 +70,371 @@
- Précharger les données nécessaires avant boucle - Précharger les données nécessaires avant boucle
- Mesurer explicitement le coût d'un `upsert` unitaire dans les flux de sync - Mesurer explicitement le coût d'un `upsert` unitaire dans les flux de sync
- Contexte technique : Prisma / synchronisation provider — 10-03-2026 - Contexte technique : Prisma / synchronisation provider — 10-03-2026
---
<a id="risque-authorize-after-fetch"></a>
## 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
---
<a id="risque-valeur-sentinelle-dto-date"></a>
## 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
---
<a id="risque-endpoint-idempotent-sans-doublon"></a>
## 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
---
<a id="risque-fichier-orphelin-echec-persistance"></a>
## 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
---
<a id="risque-melange-date-locale-utc-seed"></a>
## 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
---
<a id="risque-champ-fantome-zod-non-persiste"></a>
## 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
---
<a id="risque-catch-vide-sans-logging"></a>
## 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
---
<a id="risque-params-route-non-valides"></a>
## 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
---
<a id="risque-cast-ts-brut-valeurs-db"></a>
## 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
---
<a id="risque-creation-ressource-temporelle-sans-chevauchement"></a>
## 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
---
<a id="risque-toctou-operations-conditionnelles"></a>
## 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
---
<a id="risque-biais-agregation-moyenne-ratio"></a>
## 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
---
<a id="risque-couplage-semantique-types-erreur"></a>
## 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
---
<a id="risque-service-http-aware"></a>
## 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
---
<a id="risque-count-sans-filtre-user"></a>
## 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
---
<a id="risque-env-top-level-module"></a>
## 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

View File

@@ -5,8 +5,8 @@ bucket: risques
tags: [prisma, transactions, tenant, schema, race-condition] tags: [prisma, transactions, tenant, schema, race-condition]
applies_to: [implementation, review, debug, architecture] applies_to: [implementation, review, debug, architecture]
severity: high severity: high
validated_on: 2026-03-23 validated_on: 2026-04-07
source_projects: [app-template-resto, app-alexandrie] source_projects: [app-template-resto, app-alexandrie, RL799_V2]
--- ---
# Backend — Risques & vigilance : Prisma # 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. 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 - Contexte technique : Prisma / PostgreSQL — app-alexandrie 31-03-2026
---
<a id="risque-migration-manuelle-hors-git"></a>
## 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/<nom>/migration.sql` est présent et tracké
- `git add prisma/migrations/<nom>/` 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
---
<a id="risque-relation-1-1-sans-unique"></a>
## 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

View File

@@ -5,8 +5,8 @@ bucket: patterns
tags: [tests, react-native, jest, styles, ui] tags: [tests, react-native, jest, styles, ui]
applies_to: [implementation, review] applies_to: [implementation, review]
severity: medium severity: medium
validated_on: 2026-03-19 validated_on: 2026-04-07
source_projects: [app-alexandrie] source_projects: [app-alexandrie, RL799_V2]
--- ---
# Frontend — Patterns : Tests # Frontend — Patterns : Tests
@@ -62,3 +62,31 @@ it('variante primary utilise colors.primary', () => {
1. `.spec.ts` (node) : tokens, valeurs, logique pure 1. `.spec.ts` (node) : tokens, valeurs, logique pure
2. `.spec.tsx` (config séparée avec renderer) : rendu visuel, interactions 2. `.spec.tsx` (config séparée avec renderer) : rendu visuel, interactions
---
<a id="pattern-niveaux-test-frontend-vue"></a>
## 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

View File

@@ -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 | | `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 | | `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 | | `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 | | `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 | | `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 | Sur-renders bundle non maîtrisé, useCallback inutile inline | | `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 | | `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 |

View File

@@ -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 - 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 - 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 - Contexte technique : Vue 3 / design system léger — RL799_V2, 02-04-2026
---
<a id="risque-aria-roles-sans-clavier"></a>
## 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
---
<a id="risque-duplication-logique-metier-monorepo"></a>
## 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
---
<a id="risque-event-listeners-globaux-modales"></a>
## 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
---
<a id="risque-boutons-imbriques"></a>
## Boutons imbriqués dans les listes interactives
### Risques
- Un `<button>` ou `<a>` contenant un autre élément interactif (bouton, lien) est du HTML invalide
- Casse l'accessibilité et produit un comportement imprévisible selon les navigateurs
### Symptômes
- `<button>` conteneur avec un `<button>` enfant (ex: étoile favori dans une carte cliquable)
- Comportement de clic imprévisible, événements qui ne remontent pas correctement
### Bonnes pratiques / mitigations
- Utiliser un `<div>` conteneur avec des boutons séparés côte à côte
- Si toute la ligne doit être cliquable, séparer la zone de clic principale (bouton content) de l'action secondaire (bouton étoile/action)
- **Signal review** : dans tout composant liste avec actions inline, vérifier qu'aucun élément interactif n'est imbriqué dans un autre
- Contexte technique : HTML / accessibilité — RL799_V2 06-04-2026
---
<a id="risque-fire-and-forget-sans-feedback"></a>
## Fire-and-forget sans feedback sur actions non-critiques
### Risques
- Une action asynchrone non-critique (cache IndexedDB, analytics, sync) lancée en fire-and-forget sans feedback masque les échecs
- L'utilisateur croit que l'action est faite (ex: document disponible hors-ligne) alors qu'elle a échoué
### Symptômes
- `.then(...).catch(() => {})` sur une action secondaire
- `catch { /* ignore */ }` sans log ni feedback visuel
### Bonnes pratiques / mitigations
- Même si l'action est non-bloquante, afficher un feedback discret en cas d'échec (toast, badge absent)
- L'utilisateur doit pouvoir distinguer "fait" de "échoué silencieusement"
- **Signal review** : tout `.catch(() => {})` ou `catch { /* ignore */ }` mérite au minimum un log ou un feedback visuel
- Contexte technique : frontend / actions async — RL799_V2 07-04-2026

View File

@@ -5,7 +5,7 @@ bucket: risques
tags: [navigation, expo-router, vue-router, vue, zustand, useeffect, deep-link, a11y] tags: [navigation, expo-router, vue-router, vue, zustand, useeffect, deep-link, a11y]
applies_to: [implementation, review, debug] applies_to: [implementation, review, debug]
severity: high severity: high
validated_on: 2026-04-02 validated_on: 2026-04-07
source_projects: [app-alexandrie, RL799_V2] source_projects: [app-alexandrie, RL799_V2]
--- ---
@@ -232,3 +232,25 @@ const routes = [
- Règle : un élément de navigation désactivé ne doit jamais être un lien - Règle : un élément de navigation désactivé ne doit jamais être un lien
- Utiliser un élément non interactif (`span`) ou un vrai contrôle désactivable (`button disabled`) selon le besoin - Utiliser un élément non interactif (`span`) ou un vrai contrôle désactivable (`button disabled`) selon le besoin
- Contexte technique : Vue 3 / Vue Router 4 / accessibilité — RL799_V2, 02-04-2026 - Contexte technique : Vue 3 / Vue Router 4 / accessibilité — RL799_V2, 02-04-2026
---
<a id="risque-etat-local-depuis-query-param"></a>
## État local initialisé depuis un query param de route sans synchronisation réactive
### Risques
- Un formulaire branché sur un query param de route peut soumettre un identifiant obsolète si la prop initiale est copiée une seule fois dans un état local
- Le bug est discret et passe facilement les tests textuels
### Symptômes
- Composant qui copie `route.query.id` dans un `ref()` au montage sans `watch`
- Navigation intra-page (même composant, query param différent) qui soumet l'ancien identifiant
### Bonnes pratiques / mitigations
- Quand un composant initialise un état local depuis une prop liée au router (ex: `route.query.*`), ajouter une synchronisation réactive explicite (`watch` sur la prop) ou utiliser directement la prop si possible
- Ajouter un test qui valide la synchro sur changement de query param (même composant réutilisé, navigation intra-page)
- Contexte technique : Vue 3 / Vue Router 4 — RL799_V2 02-04-2026

View File

@@ -55,3 +55,26 @@ const handleToggle = useCallback((id: string) => { ... }, []); // stable ✓
- Contexte technique : React — app-template-resto 22-03-2026 - Contexte technique : React — app-template-resto 22-03-2026
---
<a id="risque-fetch-sans-timeout"></a>
## Fetch sans timeout pour ressources lourdes
### Risques
- `fetch()` sans timeout pour charger des blobs/fichiers volumineux (PDF, images) laisse l'utilisateur attendre indéfiniment sur connexion dégradée
- Critique sur mobile avec connexion instable ; faux sentiment de freeze pour l'utilisateur
### Symptômes
- `fetch(url)` sans `AbortController` + timeout pour des blobs/fichiers volumineux
- Spinner infini sans message d'erreur
### Bonnes pratiques / mitigations
- Utiliser `AbortController` avec `setTimeout` (15s recommandé)
- Attraper `AbortError` et afficher un message explicite à l'utilisateur
- **Signal review** : tout `fetch` de blob/fichier sans `AbortController` est suspect
- Contexte technique : frontend / mobile — RL799_V2 06-04-2026

View File

@@ -5,8 +5,8 @@ bucket: risques
tags: [tests, jest, react-native, ts-jest, coverage, facade] tags: [tests, jest, react-native, ts-jest, coverage, facade]
applies_to: [analysis, implementation, review, debug] applies_to: [analysis, implementation, review, debug]
severity: high severity: high
validated_on: 2026-03-31 validated_on: 2026-04-07
source_projects: [app-alexandrie, app-template-resto] source_projects: [app-alexandrie, app-template-resto, RL799_V2]
--- ---
# Frontend — Risques & vigilance : Tests # Frontend — Risques & vigilance : Tests
@@ -122,3 +122,29 @@ source_projects: [app-alexandrie, app-template-resto]
- **Règle** : un test qui ne vérifie que des helpers adjacents ne peut pas clôturer une tâche d'intégration. - **Règle** : un test qui ne vérifie que des helpers adjacents ne peut pas clôturer une tâche d'intégration.
- Contexte technique : TypeScript / Jest — app-template-resto 31-03-2026 - Contexte technique : TypeScript / Jest — app-template-resto 31-03-2026
---
<a id="risque-tests-presence-textuelle-faux-gardefou"></a>
## Tests de présence textuelle = faux garde-fou de non-régression
### Risques
- Des tests basés uniquement sur `content.includes(...)` valident du texte statique tout en laissant passer des régressions réelles sur auth/API et comportements UI
- Les AC fonctionnels sont déclarés validés alors que le flux réel (autosave, submit, transitions d'état) n'est pas exercé
### Symptômes
- Tests qui lisent le fichier `.vue` et assertent uniquement des chaînes (`includes`) sans exécuter le composant ni ses interactions
- Story cochée malgré des régressions fonctionnelles invisibles aux tests verts
- Services API et guards d'accès validés par des assertions textuelles au lieu de tests comportementaux
### Bonnes pratiques / mitigations
- Exiger au moins un test comportemental par flux critique (montage composant + interaction + assertion d'effet)
- Reléguer les tests textuels au rôle de smoke structurel non bloquant
- Pour les services API et guards d'accès : exiger un test exécutant réellement la fonction (mock fetch/session/router) et validant statuts d'erreur + contrat d'appel
- Pour les templates/checklists critiques : ne pas se limiter à la présence de mots-clés, valider la structure attendue (sections obligatoires, champs non vides, format minimal)
- **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
- Contexte technique : Vue 3 / node:test — RL799_V2 02-04-2026

View File

@@ -5,8 +5,8 @@ bucket: risques
tags: [bmad, story, file-list, review, completion] tags: [bmad, story, file-list, review, completion]
applies_to: [analysis, implementation, review] applies_to: [analysis, implementation, review]
severity: high severity: high
validated_on: 2026-03-31 validated_on: 2026-04-07
source_projects: [app-alexandrie, app-template-resto] source_projects: [app-alexandrie, app-template-resto, RL799_V2]
--- ---
# Workflow — Risques & vigilance : Story tracking # Workflow — Risques & vigilance : Story tracking
@@ -105,3 +105,125 @@ source_projects: [app-alexandrie, app-template-resto]
- Tout fichier listé avec un chemin inexistant en git → signaler en **HIGH**. - Tout fichier listé avec un chemin inexistant en git → signaler en **HIGH**.
- Contexte technique : BMAD / workflow agent — app-template-resto 31-03-2026 - Contexte technique : BMAD / workflow agent — app-template-resto 31-03-2026
---
<a id="risque-story-review-non-alignee-git"></a>
## Story "review" non alignée avec la réalité du diff
### Risques
- La File List, les Completion Notes et les tâches `[x]` ne reflètent pas la réalité de `git diff` / `git status`
- Des régressions de tests passent inaperçues si le statut `review` est accordé sans croisement avec le diff
- Des fichiers créés dans un commit antérieur (ex: commit de refonte UI) sont listés dans la story sans traçabilité explicite
### Symptômes
- `git diff --name-only` montre des fichiers absents de la File List (ou inversement)
- Completion Notes contenant des déclarations contredites par le diff réel
- Tâches `[x]` "tests impactés passent" alors que des tests cassent sur des fichiers modifiés dans la story
- Fichiers listés dans la File List absents de `git diff HEAD~1..HEAD` pour le commit story
### Bonnes pratiques / mitigations
- **Avant passage en review** : comparer `File List` vs `git diff --name-only` + `git status --porcelain`
- Refuser le statut `review` si un fichier source de la story manque de la File List
- Exiger une section "preuves de validation" quand une tâche mentionne une validation UX/Dev
- Exiger un run de tests sur le périmètre touché, avec mention explicite des échecs non liés
- Si des fichiers sont répartis sur plusieurs commits, mentionner explicitement dans la File List le commit d'introduction de chaque fichier
- Contexte technique : BMAD / workflow agent — RL799_V2 02-04-2026
---
<a id="risque-quality-gate-declaratif"></a>
## Quality gate déclaratif non exécutable
### Risques
- Une story impose un gate process (PR template, checklist obligatoire) mais aucun mécanisme de contrôle vérifiable n'existe
- En review, cela crée un faux sentiment de contrôle et laisse passer des PR non conformes
### Symptômes
- Story qui déclare un quality gate via documentation/template sans CI check, script de validation, ni evidence review tracée
- AC marqués ✅ sur la base d'un template rempli mais sans preuve d'exécution
### Bonnes pratiques / mitigations
- La review doit vérifier au moins un mécanisme de contrôle actionnable (CI check, script de validation, required field policy, ou evidence review tracée)
- Sans ce mécanisme, classer les AC comme PARTIAL et repasser la story en `in-progress`
- Contexte technique : BMAD / workflow agent — RL799_V2 02-04-2026
---
<a id="risque-ac-hors-contrat-technique"></a>
## AC dépendante d'une capacité non supportée par le contrat technique
### Risques
- Un AC demande un comportement impossible avec le contrat courant (ex: multi-rôles alors que le JWT expose un rôle scalaire)
- Sans clarification explicite dans la story, la review crée des allers-retours et des faux écarts
### Symptômes
- AC mentionnant une capacité que le token, le schéma ou l'API ne supporte pas actuellement
- Dev qui implémente un workaround non documenté pour satisfaire l'AC
### Bonnes pratiques / mitigations
- Si un AC dépend d'une capacité non supportée par le contrat actuel (token, schéma, API), documenter explicitement la contrainte dans la story
- Marquer la capacité hors-scope tant que le contrat n'est pas migré
- Exiger une note Dev Notes + alignement Tasks/Tests sur cette contrainte
- Contexte technique : BMAD / workflow agent — RL799_V2 02-04-2026
---
<a id="risque-validation-taches-tests-textuels"></a>
## Tâches [x] validées par tests textuels au lieu de tests comportementaux
### Risques
- Une story passe en review avec des tâches `[x]` dont la preuve repose uniquement sur des assertions textuelles (`content.includes(...)`) sans exécuter le flux réel
- Les tests de présence textuelle valident des labels mais pas les cibles de navigation, ni les transitions d'état
### Symptômes
- Tests qui passent en vérifiant uniquement la présence de chaînes dans les fichiers source
- AC fonctionnels non réellement garantis (toast, mise à jour réactive, soumission) malgré un statut story en `review`
- Tests `content.includes(...)` valident des labels mais pas les `to`/`href` de navigation
### Bonnes pratiques / mitigations
- Toute tâche de test marquée `[x]` doit inclure au moins un test comportemental exécutable (pas seulement `content.includes(...)`)
- En cas de tests purement textuels, classer la tâche comme non validée et repasser la story en `in-progress`
- Pour toute AC mentionnant "clic" ou "navigation", imposer une vérification des cibles (`:to`, `href`, route name)
- **Règle** : un test qui ne vérifie que des helpers adjacents ou des chaînes statiques ne peut pas clôturer une tâche d'intégration
- Contexte technique : BMAD / workflow agent — RL799_V2 02-04-2026
---
<a id="risque-agents-autonomes-derive-contrats"></a>
## Agents autonomes et dérive des contrats partagés
### Risques
- Après une session multi-agents, les types/DTOs sont redéfinis localement dans chaque service au lieu d'utiliser le package shared
- La dérive est détectée uniquement lors d'une review globale de cohérence, pas pendant l'implémentation
### Symptômes
- Types définis localement dans `apps/frontend/src/services/` au lieu d'être importés depuis `packages/shared`
- Plusieurs services avec le même type défini indépendamment, avec des divergences subtiles
- Détection : `grep -r 'type.*=' apps/frontend/src/services/ | grep -v 'import'`
### Bonnes pratiques / mitigations
- Inclure dans le prompt d'agent une instruction explicite : "créer les DTOs dans `packages/shared` AVANT d'écrire le service frontend"
- Ajouter une review globale de cohérence après toute session multi-stories
- **Règle review** : tout type défini localement qui correspond à un DTO existant dans shared est une régression
- Contexte technique : BMAD / agents autonomes — RL799_V2 07-04-2026