33 KiB
Backend — Risques & vigilance : Général
Extrait de la base de connaissance Lead_tech. Voir
knowledge/backend/risques/README.mdpour l'index complet.
Observabilité insuffisante (logs non structurés, pas de corrélation)
Risques
- MTTR très élevé : on devine
- Incapacité à mesurer l'impact utilisateur
Symptômes
- Logs "ça a crash" sans contexte
- Impossible de relier une requête à une erreur
- Latence qui dérive sans alerte
Bonnes pratiques / mitigations
- Logs structurés + requestId/traceId
- Métriques de base (latence, erreurs, throughput)
- Alertes simples sur 5xx/latence
Migrations risquées / non reproductibles
Risques
- Downtime
- Perte de données
- Incohérence entre environnements
Symptômes
- "Ça marche en local" mais pas en prod
- Migration qui échoue à mi-chemin
- Rollback impossible
Bonnes pratiques / mitigations
- Migrations versionnées + tests staging
- Stratégie expand/contract si besoin
- Plan de rollback/mitigation
Boucle upsert N+1 sur synchronisation provider
Risques
- Latence multipliée par le nombre d'items
- Charge DB inutile
- Timeouts ou contention sur gros volumes
Symptômes
- Une boucle applicative exécute un
upsertpar item - Temps de traitement qui explose avec le volume
- Logs SQL répétitifs et séquentiels
Bonnes pratiques / mitigations
- Batcher quand c'est possible
- Précharger les données nécessaires avant boucle
- Mesurer explicitement le coût d'un
upsertunitaire dans les flux de sync - Contexte technique : Prisma / synchronisation provider — 10-03-2026
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
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 denull - Le type DTO ment (
stringau lieu destring | null), et tout consommateur testantif (profile.field)a un comportement incorrect sur""
Symptômes
?? ''sur une date issue de?.toISOString()dans un repository Prisma- Type DTO avec
someDate: stringnon optionnel pour un modèle qui autorisenullen DB
Bonnes pratiques / mitigations
-
Si un champ de date peut être
nullen DB, le type DTO doit êtrestring | 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
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.createsansfindFirstpré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
created'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
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
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
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 aussinew 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 aussinew Date('...Z') -
Contexte technique : Node.js / seed — RL799_V2 03-04-2026
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/updatedu 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
datade l'appel Prisma -
Contexte technique : Zod / Prisma — RL799_V2 06-04-2026
Catch vide avalant les exceptions sans logging
Risques
- Les
catch {}oucatch { // 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 {sansconsole.errorni 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
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 } })sansisValidUuid(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
Cast TypeScript brut sur valeurs DB non contraintes
Risques
- Un cast
value as 'expected_a' | 'expected_b'sur un champ Prisma de typeStringsans 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 PrismaStringsans 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 typeString -
Contexte technique : TypeScript / Prisma — RL799_V2 07-04-2026
Création de ressource temporelle sans détection de chevauchement
Risques
- Un endpoint crée une ressource avec
startDate/endDatesans 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
TOCTOU sur opérations conditionnelles sans transaction
Risques
findUnique→ check status →updateen 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 unUPDATE ... 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
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
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
ApiErrorCodegénérique dans le package partagé et faire pointer les types domaine vers lui -
Contexte technique : backend / architecture — RL799_V2 07-04-2026
Service HTTP-aware — violation de la séparation des couches
Risques
- Un fichier
service.tsimporteResponse, construit les headers et status HTTP, et retourne un objetResponse - 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
Responsedans 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
Count sans filtre sur User — confusion membres / utilisateurs
Risques
prisma.user.count()sanswhereinclut 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()sanswherepour 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()sanswheredans un contexte de statistiques -
Contexte technique : Prisma / stats — RL799_V2 07-04-2026
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 premierimport- Les tests qui modifient
process.envaprès l'import ne voient pas l'effet - Config non-reloadable sans redémarrage
Symptômes
process.env.Xassigné à 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
Rate limiting — couverture de test insuffisante
Risques
- Tester uniquement l'objet
RateLimiteren isolation (.check(),.reset()) donne une fausse confiance. Les bugs de câblage (limiter non appelé, mauvais limiter sur le mauvais endpoint, format de réponse 429 incorrect) passent au travers.
Symptômes
- Tests unitaires verts mais réponse 429 absente ou mal formatée en intégration
- Header
retry-aftermanquant dans la réponse HTTP
Bonnes pratiques / mitigations
-
Pour chaque rate limiter intégré dans un endpoint, ajouter au minimum :
- Un test d'intégration qui dépasse le seuil via le handler HTTP et vérifie status 429 + body + header
retry-after - Un test d'expiration de fenêtre (mock
Date.nowet avancer le temps) - Vérifier que le logging sécurité est déclenché (au minimum visible dans la sortie test)
- Un test d'intégration qui dépasse le seuil via le handler HTTP et vérifie status 429 + body + header
-
Contexte technique : backend / rate limiting — RL799_V2 07-04-2026
Nommage des métriques agrégées dans les DTO
Risques
- Nommer un champ
xxxCountouxxxThisYearsans que le nom reflète exactement ce qui est compté - Confusion en maintenance, bugs d'affichage, labels UI incohérents
Symptômes
tenuesThisYearqui compte en réalité les présences du membre, pas le nombre total de tenues- Labels frontend incohérents avec le comportement réel du champ
Bonnes pratiques / mitigations
-
Le nom du champ DTO doit refléter exactement l'entité comptée ET le scope du comptage
-
Préférer
presencesThisYearàtenuesThisYearsi on compte les attendances d'un user, réservertenuesThisYearpour le COUNT de tenues elles-mêmes -
Signal review : tout champ
xxxCountouxxxThisYeardont le nom ne correspond pas à la requête sous-jacente -
Contexte technique : backend / DTO — RL799_V2 07-04-2026
.gitignore — .env* wildcard capture .env.example
Risques
- Utiliser
.env*dans.gitignoresans exception exclut aussi.env.example, qui est le fichier standard de documentation des variables d'environnement et qui DOIT être versionné
Symptômes
- Le fichier
.env.exampleexiste sur le disque mais n'apparaît pas dansgit status - Les développeurs ne savent pas quelles variables configurer
Bonnes pratiques / mitigations
-
Toujours ajouter
!.env.exampleaprès le wildcard.env*dans chaque.gitignore(racine ET sous-projets du monorepo) -
Vérifier avec
git check-ignore <path>que l'exception fonctionne -
Signal review :
.env*dans.gitignoresans!.env.example -
Contexte technique : git / configuration — RL799_V2 08-04-2026
Strip HTML regex — single-pass insuffisant
Risques
input.replace(/<[^>]*>/g, '')en un seul passage laisse des fragments exploitables sur des inputs malformés type<scr<script>ipt>alert(1)</script>
Symptômes
- Après strip, le résultat contient encore des fragments
>ou des reconstitutions partielles de tags
Bonnes pratiques / mitigations
-
Toujours (1) boucler le strip jusqu'à stabilisation (
while (prev !== result)) ET (2) supprimer les chevrons orphelins<>après la boucle -
Pour du plain text (pas de rich text), c'est suffisant. Pour du rich text, utiliser une lib dédiée (DOMPurify, sanitize-html)
-
Signal review :
replace(/<[^>]*>/g, '')sans boucle dans un code qui traite de l'input utilisateur -
Contexte technique : sécurité / sanitisation — RL799_V2 08-04-2026
Incohérence source de vérité — filtrage vs affichage sur des tables différentes
Risques
- Filtrer des entités sur un champ d'une table relationnelle (
profile.grade) tout en affichant le résultat depuis une autre table (directory.grade). Si les deux tables ne sont pas synchronisées en permanence, les résultats de filtrage et d'affichage divergent silencieusement.
Symptômes
- Un membre apparaît éligible mais avec un grade affiché incohérent, ou inversement un membre éligible est invisible parce que seule la table d'affichage a été mise à jour
Bonnes pratiques / mitigations
-
Toujours utiliser la même table comme source de vérité pour le filtrage ET l'affichage d'un même attribut
-
Si deux tables portent la même information, choisir celle qui fait autorité et aligner le code dessus
-
Signal review :
wheresurtableA.fieldavecselectsurtableB.fieldpour le même attribut -
Contexte technique : Prisma / relations — RL799_V2 08-04-2026
Check-then-create non atomique — catcher P2002
Risques
- Vérifier l'existence d'un enregistrement (
findFirst/findUnique) puis créer dans un second appel est une race condition classique - Deux requêtes concurrentes passent le check, l'une échoue sur la contrainte unique
Symptômes
- Erreur 500 générique au lieu d'un conflit 409, intermittent et difficile à reproduire
Bonnes pratiques / mitigations
-
Toujours catcher l'erreur Prisma
P2002(unique constraint violation) dans le bloc catch de la création et la transformer en réponse métier explicite (USER_ALREADY_EXISTS,CONFLICT, etc.) -
Le check préalable reste utile pour le cas nominal mais ne doit pas être la seule protection
-
Règle : tout
prisma.createsur une entité à contrainte unique doit avoir un catchP2002 -
Contexte technique : Prisma / concurrence — RL799_V2 08-04-2026
Double update non transactionnel sur la même entité
Risques
- Enchaîner deux
prisma.updateséparés sur la même entité (ex: update status puis update metadata) sans transaction laisse l'entité dans un état partiel si le second échoue
Symptômes
- L'entité est dans un état qui ne correspond à aucune transition définie
- Les flux aval (workflows, UI) se retrouvent bloqués
Bonnes pratiques / mitigations
-
Quand plusieurs champs d'une même entité doivent être modifiés ensemble pour représenter une transition d'état cohérente, les regrouper dans un seul appel
prisma.updateou les envelopper dans une$transaction -
Signal review : deux
prisma.updateséquentiels sur le mêmewhere: { id }sans$transaction -
Contexte technique : Prisma / atomicité — RL799_V2 08-04-2026
Fallback "legacy" qui bypass un nouveau workflow
Risques
- Laisser un fallback de compatibilité dans une transition d'état (ex: accepter
'draft'en plus de'pending_vm_approval'comme état source de la publication) crée un chemin de contournement du nouveau workflow - Un appel direct à la fonction interne bypass le contrôle métier
Symptômes
- Transition d'état possible depuis un état qui ne devrait plus être accepté
{ in: ['ancien', 'nouveau'] }dans un filtre Prisma sur une transition d'état
Bonnes pratiques / mitigations
-
Quand un nouveau workflow remplace un flux direct, retirer le fallback vers l'ancien état dans la transition atomique
-
Si la rétro-compatibilité est nécessaire, l'encapsuler dans un flag explicite ou une route dédiée, pas dans un
{ in: ['ancien', 'nouveau'] }silencieux -
Contexte technique : workflow / transitions d'état — RL799_V2 08-04-2026
Couverture incomplète des chemins d'écriture lors d'ajout de sécurité transverse
Risques
- Quand on ajoute une mesure de sécurité (chiffrement, sanitisation, validation, audit) sur un chemin d'écriture (ex: upload), les chemins alternatifs vers la même ressource (ex: create-from-text, import CSV, seed) sont oubliés, laissant une faille
Symptômes
- Un chemin d'écriture protégé, un autre non protégé, pour la même ressource
- Données non chiffrées ou non sanitisées créées par un chemin secondaire
Bonnes pratiques / mitigations
-
Avant de marquer une tâche sécurité comme terminée, lister TOUS les chemins d'écriture vers la ressource ciblée (grep
createDocument,writeFile,prisma.model.create) et vérifier que chacun est couvert -
Documenter la liste dans les Dev Notes de la story
-
Contexte technique : sécurité / transverse — RL799_V2 08-04-2026
Anonymisation RGPD — pièges courants
Risques
- Données personnelles dans les audit logs : stocker email ou nom dans les métadonnées d'un audit log d'anonymisation annule le droit à l'oubli (art. 17)
- Password en clair après anonymisation : remplacer le password par une string comme
'ANONYMIZED'est un signal exploitable en base - Dernier admin anonymisable : le système peut se retrouver sans administrateur
- Sessions non révoquées : le JWT existant reste valide jusqu'à expiration
Symptômes
- Email en clair dans les métadonnées d'audit après anonymisation
- Password
'ANONYMIZED'visible en base au lieu d'un hash bcrypt structurellement invalide - Aucun admin actif restant après anonymisation
- Utilisateur anonymisé qui peut encore accéder à l'application
Bonnes pratiques / mitigations
-
Utiliser un hash tronqué (ex:
sha256(email).slice(0,12)) pour la corrélation dans les audit logs, pas l'email brut -
Remplacer le password par un hash bcrypt structurellement invalide (ex:
$2b$10$INVALID...) -
Vérifier qu'au moins un admin actif reste après anonymisation
-
Révoquer tous les refresh tokens du user dans la même transaction
-
Contexte technique : RGPD / sécurité — RL799_V2 08-04-2026
Assertions de body d'erreur non mises à jour après migration de codes
Risques
- Lors d'une migration de codes d'erreur (ex: distinguer
UNAUTHORIZEDdeFORBIDDEN), les assertions de status HTTP sont mises à jour mais pas les assertions sur le body JSON
Symptômes
assert.equal(response.status, 401)passe, maisassert.equal(body.error.code, 'FORBIDDEN')échoue — le code réel estUNAUTHORIZED- Détecté seulement quand on relance la suite complète
Bonnes pratiques / mitigations
-
Lors de toute modification d'un code d'erreur dans un helper centralisé, rechercher TOUTES les assertions qui testent l'ancien code (
grep -rn 'FORBIDDEN' __tests__/) et les mettre à jour en cohérence -
Ne pas se fier au fait que "les tests passent" sans les exécuter réellement
-
Contexte technique : tests / migration de codes — RL799_V2 08-04-2026
Audit conditionnel sur un lookup DB secondaire
Risques
- Le handler utilise
requireRoleAccessqui ne retourne que{ email, role }mais pasuserId. Pour logger l'audit, un second lookup (getUserByEmail) est nécessaire et peut échouer silencieusement
Symptômes
- Une action sensible (création, promotion, suppression) réussit mais aucun log d'audit n'est écrit
- Le caller est pourtant authentifié
Bonnes pratiques / mitigations
-
Sur tout endpoint qui doit journaliser un audit, utiliser un helper qui retourne
{ userId, email, role }directement depuis le JWTsubclaim, plutôt qu'un helper + lookup DB -
Le userId est déjà dans le token — pas besoin d'aller le chercher en base
-
Contexte technique : audit / auth — RL799_V2 08-04-2026
Dérive du format d'erreur API entre services
Risques
- Chaque service recrée sa propre fonction
errorResponselocale au lieu de réutiliser un helper centralisé - Le format standard
{ error: { code, message, requestId } }n'est pas imposé par le typage
Symptômes
- Certaines routes API retournent
{ error: { code, message } }sansrequestId, d'autres incluent lerequestId - Les erreurs sans requestId sont impossibles à tracer en production
Bonnes pratiques / mitigations
-
Tout nouveau service doit inclure
requestId: crypto.randomUUID()dans ses réponses d'erreur -
Factoriser un helper
createApiErrorResponsepartagé danslib/pour éviter la divergence -
Signal review : réponse d'erreur sans
requestIddans un nouveau service -
Contexte technique : observabilité / API — RL799_V2 08-04-2026
Unicité applicative sans contrainte DB
Risques
- Un contrôle d'unicité uniquement applicatif sur des affectations "actives" reste vulnérable aux races concurrentes et peut créer deux titulaires actifs sur un même rôle
Symptômes
- Deux enregistrements actifs pour un slot censé être unique (ex: deux titulaires pour un rôle d'officier)
- Bug intermittent sous charge concurrente, invisible en dev
Bonnes pratiques / mitigations
-
Pour tout agrégat avec invariant "un seul actif" (ex: mandat d'officier par rôle), imposer une contrainte d'unicité au niveau base (index unique partiel ou stratégie équivalente) en plus des checks service
-
Les checks applicatifs seuls ne suffisent pas sous concurrence
-
Contexte technique : Prisma / contraintes — RL799_V2 08-04-2026
Docker Compose — migrations exécutées après redémarrage applicatif
Risques
- Fenêtre de non-compatibilité entre code déployé et schéma DB.
- Crash silencieux sur colonnes/contraintes nouvellement requises.
Symptômes
- Redéploiement "vert" puis erreurs runtime immédiates sur accès DB.
Bonnes pratiques / mitigations
-
Appliquer les migrations avant le redémarrage applicatif.
-
Séquence recommandée :
docker compose build->docker compose run --rm api prisma migrate deploy->docker compose up -d. -
Contexte technique : Docker Compose / déploiement — RL799_V2 08-04-2026
Scripts shell — sourcing global de .env
Risques
set -a; source .envexporte tous les secrets à tous les sous-processus.
Symptômes
- Secrets inutiles visibles dans l'environnement de commandes annexes (docker/curl/webhooks).
Bonnes pratiques / mitigations
-
Charger uniquement les variables nécessaires (
grep/cutou équivalent). -
Réserver
source .envaux scripts qui ont réellement besoin de tout le contexte. -
Contexte technique : shell / secrets env — RL799_V2 08-04-2026
Docker Compose — services auxiliaires sans healthcheck
Risques
- Faux positifs de disponibilité d'un service qui démarre mais n'est pas prêt.
Symptômes
- Service "up" mais non exploitable (port bloqué, DB locale corrompue, etc.).
Bonnes pratiques / mitigations
-
Ajouter un
healthcheckhomogène sur tous les services critiques et auxiliaires. -
Aligner la politique de readiness/liveness sur l'ensemble du compose.
-
Contexte technique : Docker Compose / observabilité service — RL799_V2 08-04-2026
Configuration fail-open d'un bind réseau en production
Risques
- Dashboard/service exposé publiquement si variable d'environnement manquante.
Symptômes
- Service censé rester localement accessible exposé sur
0.0.0.0.
Bonnes pratiques / mitigations
-
Forcer le bind sûr dans l'override de production (
127.0.0.1ou réseau privé explicite). -
Ne pas dépendre d'un
.envoptionnel pour une contrainte de sécurité. -
Contexte technique : Docker Compose / sécurité réseau — RL799_V2 08-04-2026
Logging de previews payload/HTML contenant des PII
Risques
- Fuite de données personnelles via logs applicatifs agrégés.
Symptômes
- Logs de debug contenant noms/emails/contenu message en clair.
Bonnes pratiques / mitigations
-
Ne jamais logger le body complet d'un message métier en prod.
-
Journaliser uniquement des métadonnées minimales (id, statut, taille, hash tronqué).
-
Contexte technique : logs / conformité — RL799_V2 15-04-2026
Base64 invalide — Buffer.from(..., 'base64') ne lève pas d'exception
Risques
- Configuration/signature invalide traitée comme erreur avaleuse (401 répétés sans cause claire).
Symptômes
- Échec systématique de vérification HMAC sans signal de configuration corrompue.
Bonnes pratiques / mitigations
-
Valider explicitement le format/base64 attendu avant dérivation HMAC.
-
Retourner un signal d'erreur opérationnelle explicite (config invalide) côté logs internes.
-
Contexte technique : crypto / intégration webhook — RL799_V2 15-04-2026
Dates localisées sans timeZone explicite
Risques
- Rendu de date divergent entre dev/CI/prod selon TZ machine.
Symptômes
- Même ISO affiché sur des jours différents selon environnement.
Bonnes pratiques / mitigations
-
Toujours passer
timeZonedanstoLocaleDateString/Intl.DateTimeFormatpour les sorties métier. -
Définir une timezone métier unique pour les communications utilisateur.
-
Contexte technique : dates / formatage serveur — RL799_V2 15-04-2026