capitalisation: intégrer 12 entrées depuis app-alexandrie et app-template-resto

- backend/risques/nestjs : guard multi-statut READ_METHODS avant statut
- backend/patterns/nestjs : fusionner lastSeenAt dans la réconciliation
- backend/risques/contracts : pas de process.env dans services/helpers
- backend/risques/nextjs : self-request Server Action + EXDEV atomic write
- backend/risques/prisma : champ enum-like stocké en String
- frontend/risques/general : Alert.prompt iOS-only
- frontend/risques/tests : 3 anti-patterns (helpers copiés, test indirect, test façade)
- workflow/risques/story-tracking : 2 entrées (hors périmètre, File List approximative)
- skill capitalisation-triage : nouveau format de rapport (tableaux par domaine)
- 95_a_capitaliser.md : purgé
This commit is contained in:
MaksTinyWorkshop
2026-03-31 14:47:42 +02:00
parent 80d9d0a48d
commit fc0bec0e2b
11 changed files with 325 additions and 89 deletions

View File

@@ -136,3 +136,32 @@ if (count !== null && count > QUOTA_MAX) {
- [ ] Mode dégradé permissif si `count === null` (Redis down)
- [ ] Clé nommée `{app}:quota:{action}:{userId}:{yyyy-mm-dd}` (date UTC)
- [ ] Anti-pattern évité : `incrBy` + `setEx` séparés (race condition si count === 1 concurrent)
---
<a id="pattern-fusionner-lastseenat-reconciliation"></a>
## Pattern : Fusionner `lastSeenAt` dans l'update de réconciliation — évite N requêtes DB par requête
- Objectif : éviter deux appels Prisma distincts (réconciliation + lastSeenAt) sur chaque requête authentifiée.
- Contexte : service de réconciliation d'état de session appelé à chaque request via guard ou middleware.
- Quand l'utiliser : dès qu'un `lastSeenAt` est mis à jour systématiquement et qu'un update conditionnel coexiste.
- Avantage : 1 requête DB par requête authentifiée au lieu de 2.
- Validé le : 30-03-2026
- Contexte technique : NestJS / Prisma — app-alexandrie
### Implémentation (exemple minimal)
```typescript
// ❌ 2 requêtes par requête authentifiée
private async reconcileSessionStatus(session) {
if (statusChanged) await prisma.session.update({ data: { status, graceEndsAt } });
}
await prisma.session.update({ data: { lastSeenAt: now } }); // 2ème update systématique
// ✅ 1 requête — lastSeenAt toujours inclus dans le même appel
private async reconcileSessionStatus(session, now = new Date()) {
await prisma.session.update({
data: { lastSeenAt: now, ...(statusChanged && { status, graceEndsAt }) }
});
}
```

View File

@@ -163,3 +163,27 @@ Tableau de correspondance :
- **Règle** : HTTP 403 = "tu n'as pas le droit d'effectuer cette action". HTTP 400 = "ta requête est mal formée".
- Contexte technique : NestJS / HTTP — 20-03-2026
---
<a id="risque-process-env-direct-service"></a>
## Feature flags / config : lecture directe de `process.env` dans les services ou helpers métier
### Risques
- Tests verts malgré une dépendance implicite à l'état global du process
- La validation Zod de l'env (ConfigService) existe mais est contournée au runtime via un helper non injecté
- Story conforme "pas de process.env direct en service métier" mais violation dans un helper utilisé par le service
### Symptômes
- `process.env.FEATURE_FLAG_X` dans un helper métier plutôt que dans un module ConfigService
- Tests passent mais comportement diverge selon l'env du process
### Bonnes pratiques / mitigations
- Ne jamais lire `process.env` directement dans les services ni les helpers métier.
- Injecter `ConfigService` (NestJS) et centraliser la lecture via une fonction pure recevant la config injectée.
- **Checklist review** : rechercher `process.env` dans `src/` hors `config/` ou `main.ts` — tout hit est suspect.
- Contexte technique : NestJS / ConfigService — 30-03-2026

View File

@@ -100,3 +100,36 @@ throw new HttpException(
- Interdire les méthodes "cachées" consommées hors contrat
- Tester au moins une implémentation par le contrat abstrait
- Contexte technique : TypeScript / provider strategy — 10-03-2026
---
<a id="risque-guard-multistatut-read-methods"></a>
## Guard multi-statut : ordre READ_METHODS avant statut et whitelist des writes critiques
### Risques
- Un guard qui vérifie le statut BLOCKED avant la méthode HTTP bloque aussi les GET, enfermant l'utilisateur sans moyen de sortir (ex : révoquer un appareil).
- Sans whitelist explicite des writes autorisés en état bloqué, le circuit de sortie est fermé.
### Symptômes
- L'utilisateur avec `sessionStatus === 'BLOCKED'` ne peut pas accéder à `GET /auth/devices`
- Tests passent mais l'ordre des conditions est inversé
### Bonnes pratiques / mitigations
```typescript
// ❌ DANGEREUX — BLOCKED bloque aussi les GET
if (user.sessionStatus === 'BLOCKED') throw new HttpException(...);
if (READ_METHODS.has(request.method)) return true; // jamais atteint pour BLOCKED
// ✅ CORRECT
if (READ_METHODS.has(request.method)) return true;
if (isDeviceManagementPath(request.path)) return true; // whitelist writes critiques
if (user.sessionStatus === 'BLOCKED') throw new HttpException(...);
```
- **Règle** : dans tout guard multi-statut, vérifier `READ_METHODS.has(request.method)` EN PREMIER, avant tout check de statut.
- **Règle** : définir une whitelist explicite des writes autorisés même en état bloqué.
- Contexte technique : NestJS / guard statut — app-alexandrie 30-03-2026

View File

@@ -73,3 +73,50 @@ return buildLocalizedPath(locale, "home");
- Retourner `null` (accès non bloqué) plutôt que de boucler
- Contexte technique : Next.js App Router / feature flags tenant — app-template-resto 17-03-2026
---
<a id="risque-server-action-self-request"></a>
## Server Action : self-request / loopback HTTP vers sa propre API
### Risques
- Latence réseau inutile (aller-retour HTTP interne)
- Fragilité sur `APP_URL` en environnement Docker (loopback non résolu)
- Double authentification (cookies reconstitués manuellement)
- Surface d'attaque SSRF
### Symptômes
- `fetch(process.env.APP_URL + '/api/...')` dans une Server Action
- Reconstitution manuelle de cookies de session dans l'appel
### Bonnes pratiques / mitigations
- Une Server Action peut importer et appeler directement des modules marqués `server-only` sans `fetch` interne — les deux s'exécutent côté serveur.
- **Règle** : si tu te retrouves à reconstituer des cookies de session pour faire un `fetch` vers ta propre API depuis une Server Action, c'est le signal que l'appel doit être direct.
- Contexte technique : Next.js App Router / Server Actions — app-template-resto 31-03-2026
---
<a id="risque-exdev-atomic-write"></a>
## Écriture atomique (tmp + rename) : EXDEV si tmp sur device différent
### Risques
- `rename()` lève `EXDEV` si le fichier temporaire et la destination sont sur deux devices différents (cas courant Docker bind-mount).
- L'erreur survient uniquement en production (Docker) — invisible en dev local.
### Symptômes
- `Error: EXDEV: cross-device link not permitted, rename '/tmp/...' → '/data/...'`
- Upload de fichier fonctionnel en dev, cassé en production Docker
### Bonnes pratiques / mitigations
- Créer le fichier temporaire dans **le même répertoire** que la destination finale, pas dans `os.tmpdir()`.
- S'assurer que le répertoire cible existe avant l'écriture du fichier temporaire.
- **Règle** : `os.tmpdir()` est interdit pour les fichiers qui seront renommés vers un volume Docker bind-mounté.
- Contexte technique : Node.js / Docker / fichiers media — app-template-resto 31-03-2026

View File

@@ -304,3 +304,26 @@ if (cursor) {
- **Règle** : ajouter un test unitaire "cursor invalide → 400" sur tout endpoint paginé par cursor
- Contexte technique : NestJS / pagination — app-alexandrie 24-03-2026
---
<a id="risque-enum-like-string-prisma"></a>
## Champ enum-like stocké en `String` Prisma — perte de contrainte DB et typage dégradé
### Risques
- Aucune contrainte en base sur les valeurs acceptées — insertion de valeurs invalides possible sans erreur DB.
- Cast manuel `as EnumType` dans le service masque l'absence de validation Prisma.
### Symptômes
- `as SomeEnum` dans un service ou repository sur un champ qui provient de la DB
- Getter `get model(): any` dans PrismaService pour contourner le typage
### Bonnes pratiques / mitigations
1. Tout champ à valeurs finies doit être déclaré avec un `enum` Prisma dès la création du modèle — jamais en `String`.
2. Si un modèle existant utilise `String`, créer une migration de conversion : `ALTER COLUMN ... TYPE "EnumType" USING ...::"EnumType"`.
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

View File

@@ -61,3 +61,26 @@ function processLinks(content: string) {
- **Règle** : les regex avec flag `/g` ou `/y` utilisées pour transformation de strings → toujours créer via une factory, jamais en singleton de module
- Contexte technique : TypeScript / React Native — app-alexandrie 24-03-2026
---
<a id="risque-alert-prompt-ios-only"></a>
## `Alert.prompt` iOS-only — fonctionnalité silencieusement cassée sur Android
### Risques
- `Alert.prompt` ne déclenche rien sur Android (retourne `undefined` silencieusement).
- Les tests unitaires passent (mock), mais le flux ne s'exécute jamais sur 50 % des devices en production.
### Symptômes
- Flux de saisie utilisateur qui fonctionne sur simulateur iOS mais est inactif sur Android
- Aucun message d'erreur côté dev ni côté utilisateur
### Bonnes pratiques / mitigations
1. Ne jamais utiliser `Alert.prompt` dans un projet Expo cross-platform.
2. Remplacer par une modale custom : `Modal` + `TextInput` React Native — portable, accessible, testable.
3. Wrapper le `TextInput` dans `KeyboardAvoidingView` avec `behavior={Platform.OS === 'ios' ? 'padding' : 'height'}`.
- Contexte technique : React Native / Expo cross-platform — app-alexandrie 31-03-2026

View File

@@ -44,3 +44,70 @@
- Pour un helper à fallback optionnel : tester explicitement le cas `fallbackToFr=false` (défaut) avec une valeur vide
- Contexte technique : TypeScript / Jest — app-template-resto 17-03-2026
---
<a id="risque-helpers-copies-tests"></a>
## Helpers copiés localement dans les tests (faux positif permanent)
### Risques
- La logique réellement exécutée en production peut diverger du helper copié dans le test sans casser aucun test.
- Un refactor du module source ne casse pas le test — la couverture est illusoire.
### Symptômes
- Fonctions utilitaires redéfinies dans `*.spec.ts` plutôt qu'importées depuis le module de production
- Tests verts malgré une régression dans le code source
### Bonnes pratiques / mitigations
- Les tests doivent importer le module réellement utilisé par l'écran/composant, jamais dupliquer la logique.
- Si la logique est partagée entre écran et test, l'extraire dans un utilitaire partagé (single source of truth).
- **Checklist review** : aucune fonction de production recopiée dans `*.spec.ts`.
- Contexte technique : TypeScript / Jest — 30-03-2026
---
<a id="risque-test-ecran-indirect"></a>
## Test d'écran indirect — logique UI validée via helper adjacent non relié au flux
### Risques
- Le test reste vert même si la logique de décision UI dans le screen diverge du helper testé.
- Un changement d'implémentation de l'écran ne casse aucun test.
### Symptômes
- `*.spec.ts` d'un écran qui n'importe pas le composant/écran mais seulement un helper utilitaire adjacent
- Couverture affichée OK mais comportement réel de l'écran non testé
### Bonnes pratiques / mitigations
- La logique de décision UI doit être soit testée via rendu composant (`@testing-library/react-native`), soit extraite dans un module dédié importé par le screen ET par le test (single source of truth).
- **Règle** : un test qui n'importe pas le composant écran ni son module de logique ne peut pas valider le comportement de l'écran.
- Contexte technique : React Native / Jest — 30-03-2026
---
<a id="risque-test-facade-flux-reel"></a>
## Test de façade — helpers adjacents validés à la place du flux réel
### Risques
- Une tâche d'intégration (conversion, persistance, atomicité, contrat UI) est clôturée alors que les tests ne valident que des helpers purs (chemins, labels, sanitization).
- Le pipeline réel, la persistance DB et les transitions UI ne sont jamais exercés.
### Symptômes
- Tests `*.spec.ts` créés uniquement sur des fonctions utilitaires pures pour une story qui promet un pipeline
- La tâche est cochée ✅ mais aucun test n'appelle le module de traitement de production
### Bonnes pratiques / mitigations
- Si une tâche promet conversion, statut DB, atomicité ou contrat UI/public : le test doit appeler le module de production correspondant ou un seam injecté unique.
- **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

View File

@@ -48,3 +48,49 @@
- Ne pas faire confiance au status `done` sans preuve dans le code
- Contexte technique : BMAD / agent Codex — app-template-resto 21-03-2026
---
<a id="risque-file-list-hors-perimetre"></a>
## Story validée avec changements hors périmètre non documentés
### Risques
- Le reviewer valide un faux scope ou rejette à tort une story correcte.
- Des changements source non tracés (working tree dirty) peuvent masquer une implémentation partielle ou des effets de bord.
### Symptômes
- Tâches `[x]` et File List propre, mais `git status` montre des fichiers modifiés hors story
- Aucun bloc "hors périmètre" dans le Dev Agent Record alors que des fichiers non liés ont été touchés
### Bonnes pratiques / mitigations
- Si des changements hors story sont présents dans le working tree, documenter un bloc **"hors périmètre"** explicite dans le Dev Agent Record (liste courte + justification).
- Ne marquer `done` qu'après clarification de périmètre.
- **Règle** : le reviewer croise systématiquement `git diff --name-only` avec la File List avant d'accepter.
- Contexte technique : BMAD / workflow agent — 30-03-2026
---
<a id="risque-file-list-approximative"></a>
## File List approximative — chemins faux ou fichiers source absents
### Risques
- Validation d'une implémentation avec traçabilité mensongère.
- Chemins fictifs dans la File List non détectés si le reviewer ne croise pas avec `git`.
### Symptômes
- File List courte ou générique alors que le diff réel touche de nombreux fichiers source
- Fichier listé avec un chemin qui n'existe pas dans le repo
### Bonnes pratiques / mitigations
- **Règle review** : comparer la File List du Dev Agent Record au `git diff --name-only` réel.
- Tout fichier source modifié absent de la File List → signaler en **MEDIUM**.
- Tout fichier listé avec un chemin inexistant en git → signaler en **HIGH**.
- Contexte technique : BMAD / workflow agent — app-template-resto 31-03-2026