docs(knowledge): capitalisation backend — intégration du triage local (mai-juin 2026)

Triage et intégration des propositions backend du buffer 95_a_capitaliser.md
(lot local RL799_V2 + app-alexandrie, mai-juin 2026), distinct de la capitalisation
remote antérieure (triage 2026-05-02).

~73 entrées intégrées sur knowledge/backend/, dont :
- patterns/auth.md : série "membrane d'auth fédérée BFF/OIDC" (9 patterns) + jose algo whitelist
- patterns/prisma.md : recette fusionnée "Migration String/Int → enum" (backfill + Cas A/B/C),
  row réactivable, endpoint replace atomique, updateMany conditionnel, etc.
- risques/general.md : 19 risques (epoch s vs ms, keepAliveTimeout=0, upsert+filtre liste,
  fail-safe catch-all, retrait asymétrique front/back, anti-énumération rate-limit, etc.)
- patterns/general, async, nestjs, contracts, tests + risques/auth, contracts, prisma, redis, stripe, tests
- compléments d'entrées existantes (authorize-after-fetch, P3014, cursor opaque, DI swc, Stripe v20...)
- README patterns/risques mis à jour

Doublons internes corrigés en relecture (suppression-champ .map() → general seul ;
e2e DB-based → tests.md seul). Doublons hors backend / entrées projet / rejets non intégrés.
Source 95_a_capitaliser.md non purgée à ce stade (purge en fin de capitalisation complète).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MaksTinyWorkshop
2026-06-25 11:25:02 +02:00
parent ef24d85d57
commit f1b783407a
18 changed files with 2896 additions and 24 deletions
+45 -1
View File
@@ -140,4 +140,48 @@ if (compensated === null) {
- **Règle** : toujours vérifier le retour du décrément de compensation et loguer explicitement si `null`. Documenter ce choix dans les Dev Notes de la story (comportement intentionnel vs bug).
- **Solution robuste** : encapsuler incrément + compensation conditionnelle dans le même pipeline `MULTI/EXEC` ou un script Lua (atomicité garantie), au prix d'une complexité plus élevée.
- Contexte technique : Redis / NestJS — app-alexandrie 01-04-2026
#### Compenser AUSSI quand la transaction DB échoue (pas seulement au dépassement)
Le même compteur doit être compensé quand l'écriture DB qui suit le quota échoue. Pattern récurrent : `consumeDailyQuota` (incrément Redis) est appelé **avant** une transaction DB. Si la transaction throw, le compteur du user est consommé sans avoir produit le side-effect attendu → quota fantôme. Le piège : la compensation `incrBy(-1)` existante n'est souvent déclenchée **que** sur dépassement de quota, pas sur exception de la transaction.
```typescript
// ❌ si $transaction throw, le compteur reste incrémenté → quota fantôme
await consumeDailyQuota({ ..., action: 'dm-message' });
const message = await prisma.$transaction(async (tx) => { /* INSERT, peut échouer */ });
// ✅ compensation systématique sur exception de la transaction
await consumeDailyQuota({ ... });
try {
const message = await prisma.$transaction(...);
} catch (err) {
await redis.incrBy(quotaKey, -1).catch(() => {});
throw err;
}
```
- **Trade-off** : garder l'ordre `quota → tx → compensation` (et non « tx puis quota ») garantit qu'on ne dépasse pas la limite sous charge concurrente (deux requêtes simultanées qui passeraient toutes deux le check). La compensation sur catch est donc **obligatoire**.
- **Règle** : tout flow `consumeDailyQuota` puis écriture DB doit compenser sur exception. Vérifier aussi les autres actions partageant ce flow (`comment`, `post`, `support_ticket`).
- Contexte technique : Redis / NestJS / Prisma — app-alexandrie 01-04-2026, complété 13-05-2026 (story 10.2)
---
<a id="risque-rate-limit-compteur-partage"></a>
## Rate-limit à compteur partagé entre endpoints jumeaux
### Risques
- Un helper de rate-limit dont la clé Redis omet un discriminant d'endpoint (`quota:${action}:${userId}:${jour}`) fait partager **un unique compteur** à plusieurs endpoints réutilisant le même `action` et le même discriminant (ex : l'IP).
- Les seuils respectifs des endpoints perdent tout sens : un excès sur l'un consomme le quota de l'autre. Ex : un excès de `login` bloque un `reset` de mot de passe légitime.
### Symptômes
- Un endpoint renvoie 429 alors que son propre seuil n'est pas atteint, à cause du trafic sur un endpoint jumeau.
- Le bug est invisible en test : chaque e2e exerce un seul endpoint à la fois, donc le compteur n'est jamais partagé pendant un test.
### Bonnes pratiques / mitigations
- **Règle de clé** : `clé = quota:<action>:<endpoint>:<identité>:<fenêtre>`. La clé DOIT inclure un discriminant d'endpoint, pas seulement l'identité de l'appelant.
- **Test de non-régression** : exercer DEUX endpoints jumeaux jusqu'au seuil dans le **même** test — un test mono-endpoint ne révèle jamais ce bug.
- Contexte technique : Redis / NestJS — app-alexandrie 22-05-2026