Aller au contenu

PD-19 — Rapport de confrontation (Gate 5 — AMBIGUITY)

1. Sources confrontées

  • PD-19-plan.md (étape 4 — plan d'implémentation)
  • PD-19-code-contracts.yaml (étape 4 — contrats d'interface)
  • PD-19-specification.md v2 (étape 1 — spécification corrigée)
  • PD-19-tests.md v2 (étape 2 — scénarios de test)
  • PD-19-plan-review.md (Gate 5 Phase 1 — review ChatGPT)

2. Convergences

C-01 — Couverture structurelle 12/12 INV : Le code contracts YAML (invariant_coverage) mappe explicitement les 12 invariants (INV-01 à INV-12) vers des composants et des tests. La review ne conteste pas l'exhaustivité du mapping structurel. Convergence.

C-02 — Architecture middleware 3 couches : Plan et contracts définissent une chaîne à 3 middlewares (SecurityHeaders → CORS → GlobalRateLimit) avant les guards existants. La review reconnaît cette architecture (Point 8, Mineur). Convergence sur le design.

C-03 — Extension sans nouvelle dépendance : Le plan réutilise Helmet, ioredis, ConfigModule, Joi. La review ne conteste pas la faisabilité des dépendances. Convergence.

C-04 — Réserves Gate 3 identifiées : Le plan §6.4 et la Phase 6 citent explicitement DIV-01-v2 et DIV-02-v2 avec des actions de résolution. La review reconnaît leur mention (Points 5 et 6). Convergence sur l'identification.

C-05 — Redis atomique pour rate limiting : Le code contracts spécifie MULTI/INCR/PTTL/EXEC. Le plan §6.2 mentionne l'atomicité. Pas de contestation. Convergence.

C-06 — Fail-fast via Joi : Le plan Phase 1 définit un schéma Joi obligatoire avec required() et un validateSecurityConfig() qui Throws. Convergence avec ERR-05.

3. Divergences

DIV-01 — Point 1 review : credentials et exposedHeaders absents du plan

  • Source A (review, Point 1, Bloquant) : Le plan ne précise pas Access-Control-Allow-Credentials: true ni les en-têtes exposés X-Correlation-Id, Retry-After.
  • Source B (plan.md §3, Phase 3) : Le code snippet cors.config.ts (lignes 129-130) définit explicitement exposedHeaders: ['X-Correlation-Id', 'Retry-After'] et credentials: true.
  • Source C (code-contracts.yaml, cors_config) : Les propriétés exposedHeaders (lignes 113-118) et credentials (lignes 119-122) sont contractualisées avec valeurs exactes et invariant INV-03.
  • Constat : La review fait une lecture incomplète du plan. Le plan Phase 3 contient un code snippet TypeScript qui inclut explicitement exposedHeaders et credentials. Les code contracts les contractualisent formellement. Ce point Bloquant est un faux positif. Le résumé du prompt de review (qui abrège les documents) a probablement causé cette omission.

DIV-02 — Point 2 review : refus preflight non décrit

  • Source A (review, Point 2, Majeur) : Le plan ne décrit pas le comportement déterministe de refus des preflight pour origine non autorisée.
  • Source B (plan.md §3, Phase 3, item 2) : « Aucun indicateur CORS si origine non autorisée » — c'est le comportement de refus standard.
  • Source C (code-contracts.yaml, cors_middleware.use.behavior) : Ligne 201 « Si non autorisé: n'ajoute AUCUN header CORS (INV-04) ». Section preflight (203-206) : « Répond directement aux requêtes OPTIONS » avec « Statut 204 No Content ».
  • Constat : Le contrat de refus est implicitement défini par l'absence de headers CORS positifs, conformément à la RFC CORS. Le comportement preflight est contractualisé dans les code contracts. La review soulève un point légitime sur le manque de détail dans le plan narratif, mais le code contract comble ce manque. Gravité réelle : Mineur (le plan renvoie implicitement au contrat, et le contrat est complet).

DIV-03 — Point 3 review : HSTS et Cache-Control conditionnels non détaillés

  • Source A (review, Point 3, Majeur) : La logique conditionnelle HSTS et Cache-Control n'est pas détaillée dans le plan.
  • Source B (plan.md §3, Phase 2) : Le code snippet security-headers.config.ts (lignes 95-99) définit HSTS avec condition development/HTTPS et maxAge conditionnel. Le plan §5 table des environnements précise « 300s si HTTPS » pour dev.
  • Source C (code-contracts.yaml, security_headers_config) : strictTransportSecurity (lignes 37-51) définit les valeurs par env et la condition. cacheControl (lignes 52-65) définit no-store + routes sensibles.
  • Constat : Le plan contient les éléments dans le code snippet ET dans la table §5. Les code contracts sont exhaustifs. La review a raison que le plan narratif Phase 2 ne détaille pas la logique, mais le code snippet et les contracts le font. Gravité réelle : Mineur (complétude assurée par les contracts).

DIV-04 — Point 4 review : schéma de log et rétention non spécifiés

  • Source A (review, Point 4, Bloquant) : Le plan ne spécifie pas le schéma JSON de log ni la stratégie de rétention 90 jours.
  • Source B (plan.md §3, Phase 5) : « Format : JSON structuré avec timestamp UTC ISO-8601, correlation ID ».
  • Source C (code-contracts.yaml, audit_log_service.new_events) : Événements CORS_REJECTED et RATE_LIMIT_EXCEEDED avec champs détaillés (timestamp UTC ISO-8601, correlationId, environment, origin/ipHash, method, endpoint, reason/windowStart/count/limit). security_event_dto (lignes 355-379) contractualise l'interface.
  • Constat : Le schéma est partiellement dans le plan narratif et complètement dans les code contracts. La rétention 90 jours est dans la spécification (INV-09) mais ni le plan ni les contracts ne la traduisent en action technique (configuration logstash, rotation, etc.). La review a raison sur la rétention mais tort sur le schéma JSON. Gravité réelle : Majeur pour la rétention uniquement (paramètre opérationnel non traduit en tâche).

DIV-05 — Point 5 review : réserve DIV-01-v2 sans action test explicite

  • Source A (review, Point 5, Majeur) : Aucune action test explicite pour étendre TC-ERR-02 aux refus CORS et 429.
  • Source B (plan.md §3, Phase 6, item 4) : « Résoudre DIV-01-v2 : Ajouter assertions security headers sur 429 et refus CORS ».
  • Source C (code-contracts.yaml, test_assertions.security_headers) : Ligne 400 « Headers présents sur 200, 404, 405, 429, 500 » — couvre explicitement le 429. test_assertions.rate_limit ligne 428 « Security headers présents sur 429 ».
  • Constat : Le plan ET les contracts adressent explicitement DIV-01-v2. Le plan Phase 6 item 4 est une action concrète. Les test assertions couvrent le 429. La review fait une lecture partielle — l'action est mentionnée en Phase 6 et les assertions test contracts incluent le 429. Gravité réelle : Mineur (action planifiée et contractualisée, le plan pourrait juste être plus explicite sur le « comment »).

DIV-06 — Point 6 review : DIV-02-v2 non traduit en tâches concrètes

  • Source A (review, Point 6, Majeur) : La qualification « résolution IP proxy » n'est pas traduite en scénarios concrets.
  • Source B (plan.md §3, Phase 6, item 5) : « Résoudre DIV-02-v2 : Documenter résolution IP proxy comme test d'intégration ».
  • Source C (code-contracts.yaml, rate_limit_config.trustedProxies) : Définit le type string[] | 'loopback' | 'uniquelocal' mais pas les valeurs concrètes par env.
  • Constat : La review a partiellement raison. Le plan prévoit la résolution mais reste vague (« documenter comme test d'intégration » sans scénarios). Cependant, DIV-02-v2 est une réserve Gate 3 de nature opérationnelle — la définition des proxies de confiance dépend de l'infrastructure (reverse proxy, load balancer) et ne peut pas être entièrement spécifiée dans un plan fonctionnel. Le contrat laisse intentionnellement la valeur configurable. Gravité réelle : Mineur (réserve opérationnelle reconnue, non résoluble dans le plan seul).

DIV-07 — Point 7 review : double limitation sans stratégie d'arbitrage

  • Source A (review, Point 7, Majeur) : Le plan conserve GlobalRateLimitMiddleware et Specific Rate Limit Guards sans stratégie d'arbitrage.
  • Source B (plan.md §6.1) : « Les guards spécifiques restent actifs. Le middleware global s'ajoute en amont. Les seuils spécifiques (5/min login, 10/min registration) sont plus restrictifs que le global. »
  • Source C (plan.md §4, ordre d'exécution) : GlobalRateLimit est en position 3 (middleware), les guards sont en position 6 (guards). Les middlewares s'exécutent avant les guards dans NestJS.
  • Constat : La review soulève un point légitime. Le plan explique la coexistence (global en amont, spécifiques plus restrictifs) mais ne définit pas : (1) le format 429 des guards spécifiques est-il identique au contractuel ? (2) un utilisateur qui atteint le global mais pas le spécifique verra-t-il un 429 global avant d'atteindre le guard ? La réponse est oui par construction (middleware avant guard), ce qui est correct. Le format 429 des guards spécifiques est un artefact existant hors scope PD-19. Gravité réelle : Mineur (la coexistence est par design, les guards spécifiques sont pré-existants et hors scope).

DIV-08 — Point 8 review : mélange middlewares et guards dans la chaîne

  • Source A (review, Point 8, Mineur) : La section « Ordre middlewares » mélange middlewares et guards.
  • Source B (plan.md §4) : Le diagramme distingue clairement les positions 1-4 (middlewares) et 5-6 (guards) avec un séparateur.
  • Constat : Le diagramme est descriptif de la chaîne complète de traitement HTTP. Il distingue les middlewares (NestMiddleware) des guards (NestJS guards) par leur position et leur libellé. Dans NestJS, les middlewares s'exécutent avant les guards par design du framework. La review signale un risque d'ambiguïté qui est en fait clarifié par la structure du diagramme. Gravité réelle : Mineur (diagramme pédagogique, pas un contrat d'exécution NestJS).

DIV-09 — Point 9 review : fail-fast sans détail

  • Source A (review, Point 9, Majeur) : Le plan n'explicite pas les règles de validation fail-fast au-delà du schéma Joi.
  • Source B (plan.md §3, Phase 1) : Schéma Joi avec CORS_ORIGINS: required(), RATE_LIMIT_GLOBAL_MAX: min(1).default(100), etc. + validateSecurityConfig() qui Throws si invalide.
  • Source C (spécification ERR-05) : « Le service ne doit pas démarrer en mode permissif implicite. Comportement attendu unique : échec explicite de démarrage (fail-fast). »
  • Constat : Le plan définit un schéma Joi avec required() et un appel validateSecurityConfig() qui lève une exception. C'est exactement le pattern NestJS standard pour le fail-fast (ConfigModule + Joi validation at bootstrap). Le plan ne détaille pas chaque champ validé car les code contracts (security_headers_config, cors_config, rate_limit_config) le font. La review a raison que le plan narratif manque de détail, mais le mécanisme est standard et les contracts comblent le manque. Gravité réelle : Mineur.

DIV-10 — Point 10 review : headers sur erreurs hors middlewares

  • Source A (review, Point 10, Majeur) : Le plan ne décrit pas de stratégie pour garantir les headers sur 404/405/500 et erreurs hors middlewares applicatifs.
  • Source B (plan.md §3, Phase 2, item 2) : « S'exécuter sur TOUTE réponse y compris erreurs (INV-11) ». §4 : « SecurityHeadersMiddleware applique les headers via res.on('finish') pour garantir leur présence même sur les erreurs. »
  • Source C (code-contracts.yaml, security_headers_middleware) : Ligne 176 « Utilise res.on('finish') pour garantir présence sur erreurs (INV-11) ». Test assertions ligne 400 « Headers présents sur 200, 404, 405, 429, 500 ».
  • Constat : Le plan ET les contracts adressent explicitement ce point via res.on('finish'). Ce pattern Node.js/Express intercepte l'événement finish sur l'objet Response, qui se déclenche quand la réponse est envoyée au client, quel que soit le code de statut. C'est la stratégie technique pour couvrir les erreurs (404 NestJS, 405 method not allowed, 500 internal). La review fait une lecture incomplète. Gravité réelle : Non applicable — le point est entièrement couvert.

4. Zones d'ombre

ZO-01 — Rétention 90 jours non traduite : INV-09 exige 90 jours de rétention des journaux. Ni le plan ni les contracts ne définissent le mécanisme technique (rotation, archivage, logstash/ELK). C'est un paramètre opérationnel qui pourrait relever de l'infra plutôt que du code applicatif.

ZO-02 — Format 429 des guards spécifiques : Les guards pré-existants (LoginRateLimitGuard, etc.) ont-ils le même format 429 que le contractuel (INV-07) ? Le plan ne l'adresse pas car ces guards sont hors scope PD-19, mais une incohérence de format pourrait créer de la confusion pour les clients API.

5. Synthèse des écarts

ID Source Nature Gravité review Gravité réelle
DIV-01 Review vs Plan+Contracts Faux positif (credentials/exposedHeaders dans le code snippet) Bloquant Écarté
DIV-02 Review vs Contracts Plan narratif incomplet mais contracts complets (refus preflight) Majeur Mineur
DIV-03 Review vs Plan+Contracts Plan narratif incomplet mais code snippet + contracts complets (HSTS/Cache) Majeur Mineur
DIV-04 Review vs Plan+Contracts Schéma log dans contracts, rétention 90j non traduite Bloquant Majeur
DIV-05 Review vs Plan+Contracts Action planifiée et assertions contractualisées (DIV-01-v2) Majeur Mineur
DIV-06 Review vs Plan+Contracts Réserve opérationnelle reconnue (DIV-02-v2, dépend infra) Majeur Mineur
DIV-07 Review vs Plan Coexistence par design, guards pré-existants hors scope Majeur Mineur
DIV-08 Review vs Plan Diagramme pédagogique clair Mineur Mineur
DIV-09 Review vs Plan+Contracts Mécanisme standard Joi, détails dans contracts Majeur Mineur
DIV-10 Review vs Plan+Contracts Faux positif (res.on('finish') explicite dans plan et contracts) Majeur Écarté

Bloquants légitimes : 0 (les 2 Bloquants sont des faux positifs ou réduits à Majeur) Écarts Majeurs réels : 1 (DIV-04 rétention 90j) Écarts Mineurs : 7 (DIV-02, DIV-03, DIV-05, DIV-06, DIV-07, DIV-08, DIV-09) Écarts écartés : 2 (DIV-01 faux positif, DIV-10 faux positif)

6. Recommandation

  • Procéder — convergence confirmée, aucun conflit bloquant
  • Rework nécessaire — divergences à résoudre avant de continuer
  • Escalade — décision humaine requise sur un point structurant

Justification : Le plan et les code contracts forment un ensemble cohérent et complet. Les 2 Bloquants de la review sont des faux positifs causés par la lecture de résumés plutôt que des documents complets. Le seul Majeur réel (rétention 90j) est un paramètre opérationnel qui relève de l'infrastructure (logstash/ELK/rotation) et non du code applicatif — il peut être documenté comme prérequis infra sans bloquer l'implémentation. Les 7 Mineurs concernent le niveau de détail du plan narratif, systématiquement comblé par les code contracts.


Document produit par le workflow de gouvernance IA — Gate 5 Phase 2 (Confrontation Claude) Date : 2026-02-09