# Backend — Risques & vigilance : Contracts > Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/risques/README.md` pour l'index complet. --- ## Contrats API implicites (validation faible ou absente) ### Risques - Entrées non validées → erreurs bizarres / vulnérabilités - Changements qui cassent le front et les intégrations ### Symptômes - 500 sur erreurs utilisateur - Incohérences de format de réponse - "Ça marche en staging, pas en prod" (données réelles) ### Bonnes pratiques / mitigations - Schémas (OpenAPI/JSON Schema) + validation serveur - Formats de réponse cohérents - Versionner/éviter breaking changes --- ## Erreurs non standardisées (4xx/5xx incohérents) ### Risques - Front et automatisations impossibles à rendre robustes - Debug long (pas de codes internes, pas de corrélation) ### Symptômes - Clients qui "retry" sur des 4xx - Messages techniques exposés aux utilisateurs - Logs inexploitables ### Bonnes pratiques / mitigations - Mapping HTTP standard + format d'erreur stable - Codes internes d'erreurs applicatives - requestId/traceId partout --- ## Duplication silencieuse de constantes partagées (contracts) via fichier orphelin ### Risques - Deux sources de vérité qui divergent silencieusement (ex : topics officiels, enums métier, slugs) - Bug non détecté par TypeScript si la duplication est dans un fichier non importé (code mort) ### Symptômes - Incohérences entre API et client sur des listes/enums "censées être partagées" - "Ça marche chez moi" selon l'endroit où la constante est importée - Un fichier de config existe dans `apps/*` mais n'est jamais importé/greffé au runtime ### Bonnes pratiques / mitigations - Toute constante partagée vit dans `packages/contracts/src/` et est importée depuis là (jamais recopiée dans `apps/*`) - En review : repérer les fichiers "config/constants" ajoutés dans `apps/*` sur des domaines déjà couverts par `contracts` - (Optionnel) Outillage : intégrer une étape de détection de code mort / exports inutilisés au CI si ça devient récurrent --- ## Contracts : schema orphelin / type de retour désynchronisé ### Risques - Un `RequestSchema` défini dans `packages/contracts` mais jamais importé dans le controller ni le service mobile → dead code silencieux qui crée une fausse confiance - Un type de retour inline (`string` brut) à la place du type contracts → désynchronisation silencieuse entre contrat et implémentation ### Symptômes - `grep` du nom du schema ne trouve aucun `import` en dehors de sa définition - Service retourne `Promise<{ status: string }>` au lieu de `Promise` — le `status` n'est pas validé comme `CurationStatus` - Endpoints `POST /action` sans body ayant un schema `{ pathParam: string }` — le param vient du path, pas du body ### Bonnes pratiques / mitigations À chaque story qui ajoute des schemas dans `packages/contracts`, vérifier en review : 1. Chaque `RequestSchema` est utilisé dans un `ZodValidationPipe` (API) ou importé dans le service mobile. 2. Les `ResponseSchema` correspondent au type de retour typé du service (`Promise`, pas un type inline). 3. Les endpoints sans body (`POST /action`) définissent `z.object({})` ou omettent le body schema — ne jamais placer les path params dans le body schema. ```typescript // ❌ Anti-pattern — type inline, status non typé async showcaseThread(...): Promise<{ threadId: string; status: string }> { ... } // ✅ Pattern correct — type contracts importé import type { CurationResponse } from '@app-alexandrie/contracts'; async showcaseThread(...): Promise { ... } ``` - Contexte technique : NestJS / Zod / contracts-first — app-alexandrie 23-03-2026 --- ## Code d'erreur générique sur statut HTTP sémantique (409 CONFLICT) ### Risques - Utiliser `VALIDATION_ERROR` ou `INTERNAL_ERROR` sur un 409 rend les erreurs indistinguables côté client et monitoring - Les clients (mobile, monitoring, tests) ne peuvent pas brancher une logique conditionnelle sans un code sémantique ### Symptômes - Tous les conflits métier remontent le même code → impossible de distinguer "alias déjà résolu" de "handle déjà pris" - Tests forcés à matcher le message texte au lieu du code → fragiles ### Bonnes pratiques / mitigations Chaque scénario métier distinct doit avoir son propre code dans `error-code.ts` : ```typescript // ❌ Anti-pattern — code générique sur 409 throw new ConflictException({ error: { code: 'VALIDATION_ERROR', message: '...' } }); // ✅ Correct — code sémantique spécifique throw new ConflictException({ error: { code: 'ALIAS_ALREADY_RESOLVED', message: '...' } }); throw new ConflictException({ error: { code: 'HANDLE_ALREADY_TAKEN', message: '...' } }); ``` - **Règle** : 1 scénario métier distinct = 1 code d'erreur distinct - **Checklist review** : tout 409/422 doit avoir un code dans `error-code.ts`, jamais `VALIDATION_ERROR` ou `INTERNAL_ERROR` - Contexte technique : NestJS / error-code.ts — app-alexandrie 24-03-2026 --- ## `ForbiddenException` (403) utilisé pour des erreurs de validation ### Risques - Les clients qui filtrent par HTTP 400 manquent les erreurs de validation lancées en 403 - Sémantique API incorrecte → comportements clients imprévisibles ### Symptômes - `ForbiddenException` lancée pour des tags invalides, des formats incorrects, des liens HTTP - Clients API qui ignorent ces erreurs ou les traitent comme des refus d'accès ### Bonnes pratiques / mitigations Tableau de correspondance : | Cas | Exception correcte | Code HTTP | |---|---|---| | Tags invalides, contenu trop long, format incorrect | `BadRequestException` | 400 | | Accès refusé explicitement (accès forum, trial read-only) | `ForbiddenException` | 403 | | Quota dépassé | `HttpException(429)` via `HttpStatus.TOO_MANY_REQUESTS` | 429 | - **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 --- ## 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