capitalisation: triage 95_a_capitaliser + création domaine infra

Triage des 27 propositions du buffer de capitalisation (skill
capitalisation-triage), avec vérification des doublons contre la base.

Intégré dans knowledge/ (23 entrées):
- backend: redis (compensation incrBy non-atomique), nestjs (injection
  cassée sous tsx watch; guard write mode dégradé), async (test rollback
  pipeline multi-fichiers), contracts (idempotence POST), auth (disclosure
  comptes soft-deleted), prisma (index partial soft-delete), llm-providers
  (nouveau: OAuth vs API key, prompt caching).
- frontend: tests (garde-fous parking Later), navigation (fichiers
  non-route sous src/app Expo Router), general (type client vs payload
  backend), state (fallback catch-all mapping DB→UI).
- workflow: story-tracking (statut BMAD vs narratif obsolète).
- product: general (nouveau: doc feature store sans UI).
- infra: NOUVEAU DOMAINE (traefik, tailscale, docker, docker-networking,
  reverse-proxy-paths, sidecar tailscale) + 00_INDEX.md.

Autres:
- 90_debug_et_postmortem.md: post-mortem réseau Docker partagé hors compose.
- Rejeté 3 doublons (types enum contracts, getter PrismaService, $transaction).
- Buffer 95_a_capitaliser.md purgé et restauré à son état initial.
- _projects.conf: MAJ statuts epics + ajout app-rl799.
This commit is contained in:
MaksTinyWorkshop
2026-06-25 10:31:22 +02:00
parent 1c876309f1
commit ef24d85d57
31 changed files with 1042 additions and 27 deletions
+3 -2
View File
@@ -9,12 +9,13 @@ Avant toute proposition backend, identifie le fichier dont le nom et la descript
| Fichier | Domaine | Entrées clés |
|---------|---------|--------------|
| `auth.md` | Auth, sessions, tokens, erreurs API, corrélation | Format erreur standardisé, middleware requestId, anti-énumération, token usage unique, autorisation interne, opérations atomiques |
| `contracts.md` | Contrats API, Zod, error codes, HTTP sémantique | Contracts-First/Zod-Infer/No-DTO, error codes comme contrat, HTTP 200 payload métier |
| `contracts.md` | Contrats API, Zod, error codes, HTTP sémantique | Contracts-First/Zod-Infer/No-DTO, error codes comme contrat, HTTP 200 payload métier, idempotence POST = retour ressource existante |
| `prisma.md` | Prisma, DB, migrations, pagination | Soft delete, pagination cursor, idempotency key, P2002 unique, Decimal sérialisation, migration manuelle P3014, filtrage métier dans service |
| `stripe.md` | Stripe, paiements, webhooks entrants, subscriptions | Provider-Strategy, metadata subscription_data, parsing webhook unique, restauration achats, Trial vs Paid |
| `nestjs.md` | NestJS, guards, Redis, quotas | Guard global APP_GUARD, RedisHealthService cache court, quota INCR+EXPIREAT atomique |
| `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, hooks fire-and-forget après création DB, fanout notification avec filtre grade, auto-purge fenêtre temporelle SQL |
| `async.md` | Jobs async, webhooks sortants, queues | Exécution asynchrone outbox light, webhooks sortants HMAC + retries idempotents, hooks fire-and-forget après création DB, fanout notification avec filtre grade, auto-purge fenêtre temporelle SQL, test de rollback pipeline multi-fichiers atomique |
| `general.md` | Architecture générale, helpers, RBAC | Helper auth centralisé enrichissable, ordre canonique des gates HTTP, délégation agrégat → endpoint agrégé, anti-énumération DELETE 204, lazy init memoizé, cap LRU par-user, convention dot-notation audit, whitelist explicite audit, singleton DB config, invalidation cache avant mutation, pipeline CI/CD GitHub Actions → VPS |
| `tests.md` | Tests d'intégration DB, isolation, atomicité | `cleanup.track()` LIFO, `globalSetup` purge, template database Postgres, helper `waitForX()` polling-borné, test d'atomicité transaction, convention `describe()` 2 niveaux, refactor itératif d'un fichier monolithe |
| `llm-providers.md` | Fournisseurs LLM, auth, coûts | OAuth consumer ≠ API key, prompt caching system prompt stable, budget cap fournisseur |
+27
View File
@@ -251,3 +251,30 @@ export const listRecentXxxForMember = async (
```
L'admin garde un endpoint distinct sans le filtre temporel pour l'accès historique complet.
---
<a id="pattern-test-rollback-pipeline-multi-fichiers"></a>
## Pattern : Test de rollback pour pipelines multi-fichiers atomiques
- Objectif : garantir qu'un pipeline qui écrit N fichiers avec rollback nettoie réellement l'état partiel quand le Nème échoue.
- Contexte : opération qui persiste plusieurs artefacts (ex : variantes/dérivés d'une image, fichiers d'un export) en gardant la liste des chemins déjà écrits pour pouvoir les supprimer en cas d'échec.
- Quand l'utiliser : tout pipeline « tout ou rien » sur N écritures de fichiers avec compensation manuelle (pas de transaction native).
- Quand l'éviter : écriture d'un seul fichier, ou stockage qui offre une vraie transactionnalité.
- Avantage :
- couvre le cas limite réel (échec en cours de pipeline) plutôt que le seul chemin nominal
- détecte une compensation incomplète (fichiers orphelins) ou excessive (suppression d'un fichier non écrit par ce pipeline)
- Limites / vigilance :
- le test doit être mis à jour quand N change (ajout d'un variant) pour couvrir le nouveau cas limite
- Validé le : 03-04-2026
- Contexte technique : Node.js / écriture fichiers — app-template-resto story 4.3
### Règle
Tout pipeline qui écrit N fichiers avec rollback (suppression des déjà-écrits si un échec survient) doit avoir un test unitaire couvrant le cas **« N-1 fichiers écrits + le Nème échoue »**. Ce test vérifie :
- que les N-1 fichiers déjà écrits sont **exactement** supprimés (ni plus, ni moins) ;
- que la phase de finalisation (`finalize()` ou équivalent) n'est **pas** appelée ;
- que l'erreur est bien propagée à l'appelant.
Quand le nombre d'artefacts change (ajout d'un variant), mettre à jour ce test pour couvrir le nouveau N.
+30 -2
View File
@@ -2,10 +2,10 @@
title: Backend — Patterns : Contracts
domain: backend
bucket: patterns
tags: [contracts, zod, api, error-codes, monorepo]
tags: [contracts, zod, api, error-codes, monorepo, idempotence, http-semantics]
applies_to: [analysis, implementation, review, architecture]
severity: high
validated_on: 2026-04-07
validated_on: 2026-06-25
source_projects: [app-alexandrie, RL799_V2]
---
@@ -150,6 +150,34 @@ return res.status(200).json({
---
<a id="pattern-idempotence-post-ressource-existante"></a>
## Pattern : Idempotence POST = retour de la ressource existante
- Objectif : garantir qu'un POST dont l'AC demande l'idempotence produit le **même résultat quel que soit le nombre d'appels**.
- Contexte : endpoint de création où une duplication est détectable (ressource déjà créée pour le même sujet, ex : demande d'export, déclenchement de job unique). Complément du pattern « HTTP 200 + payload métier » ci-dessus, appliqué au cas duplication.
- Quand l'utiliser : POST déclaré idempotent (retries client, double-tap mobile, rejeu réseau).
- Quand l'éviter : création stricte où une duplication est une vraie erreur métier que l'appelant doit traiter explicitement.
- Validé le : 13-04-2026
- Contexte technique : NestJS / contrat API — app-alexandrie story 9.2
### Règle
Un POST idempotent **retourne la ressource existante** (HTTP 200 / 201) quand une duplication est détectée, sans lever d'erreur. Un `409 CONFLICT` est un comportement de **blocage**, pas d'idempotence : il force l'appelant à gérer un cas d'erreur et casse le contrat « N appels = 1 résultat ».
```typescript
// ✅ IDEMPOTENT — retourne la ressource existante
if (existing) return this.serialize(existing);
// ❌ NON-IDEMPOTENT — lève une erreur de blocage
if (existing) throw new HttpException({ error: { code: 'ALREADY_EXISTS' } }, 409);
```
### Côté client
Dédupliquer par `id` avant d'insérer dans la liste/store local, pour éviter les doublons en cas de race condition (deux requêtes parallèles recevant la même ressource).
---
<a id="pattern-coherence-result-repository"></a>
## Pattern : Cohérence du pattern Result dans un repository
@@ -0,0 +1,59 @@
---
title: "Backend — Patterns : Fournisseurs LLM"
domain: backend
bucket: patterns
tags: [llm, anthropic, openai, api-key, oauth, prompt-caching, couts]
applies_to: [analysis, implementation, architecture]
severity: medium
validated_on: 2026-06-25
source_projects: [_Assistant_Cuisine]
---
# Backend — Patterns : Fournisseurs LLM
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/patterns/README.md` pour l'index complet.
---
<a id="pattern-oauth-consumer-vs-api-key"></a>
## OAuth abonnement consumer ≠ API key — ne pas mélanger les deux usages
Tous les fournisseurs LLM (Anthropic, OpenAI, Google) séparent strictement deux mondes :
| Type | Auth | Usage prévu | Coût |
|---|---|---|---|
| **Abonnement consumer** (Claude Pro/Max, ChatGPT Plus) | OAuth via app officielle | usage interactif personnel via clients officiels (desktop, web, plugin IDE) | forfait mensuel |
| **API platform** | API key | usage programmatique, multi-users, services tiers, prod | pay-per-token |
### Règle
- Ne **jamais** utiliser les credentials OAuth d'un abonnement consumer pour servir une application à plusieurs utilisateurs. Anthropic l'interdit explicitement (ToS) ; les autres le tolèrent moins ouvertement mais le principe est identique. Risque concret : bannissement du compte si détecté.
- Pour toute application applicative (chatbot, intégration produit), prendre une **API key séparée**, indépendante de l'abonnement consumer.
---
<a id="pattern-prompt-caching-system-prompt-stable"></a>
## Prompt caching obligatoire dès qu'un gros system prompt est stable
Tout système ayant un system prompt volumineux et stable (knowledge base, format de sortie, persona) DOIT activer le prompt caching côté fournisseur. La portion cachée est facturée à une fraction du tarif normal (ordre de grandeur : ~10 %), ce qui divise le coût input par 5 à 10 sur un usage où le system prompt domine.
```ts
// OpenAI : promptCacheKey via provider metadata
streamText({
model: openai("gpt-4.1-mini"),
system: bigStaticPrompt,
experimental_providerMetadata: {
openai: { promptCacheKey: "myapp-system-v1" },
},
});
// Anthropic : marquage explicite des blocs cacheables dans messages (cache_control)
```
### Conséquences sur les coûts
- Le **coût marginal** d'un nouvel utilisateur est quasi nul tant que le system prompt domine le volume de tokens.
- Pour un usage léger (quelques conversations/jour, prompt caching actif), prévoir un budget mensuel de l'ordre de quelques euros.
- Toujours poser un **budget cap** côté console fournisseur pour éviter toute dérive.
---