Aller au contenu

PD-278 — Retour d'expérience (REX)

1. Résumé exécutif

Métrique Valeur
Objectif initial Ajouter l'état DIP (Dissemination Information Package) au cycle de vie documentaire NF Z42-013
Résultat obtenu Conforme — 13 composants livrés, 14 invariants implémentés, 99 tests unitaires passent
Verdict final RESERVE (8.13/10 Gate 8 v1) — reviews LLM ChatGPT en attente + audit synchrone à confirmer
Tests contractuels 99/99 passés (0 fail, 0 skip)

2. Métriques de convergence

2.1 Temps et itérations

Étape Durée estimée Durée réelle Itérations Écart
0 - Besoin 30 min ~30 min 1 0%
1 - Spécification 2h ~1.5h 1 -25%
2 - Tests 1h ~0.5h 1 -50%
3 - Gate spec 1h ~2h 3 +100%
4 - Plan 1h ~1.5h 1 +50%
5 - Gate plan 1h ~1.5h 2 +50%
6 - Implémentation 4h ~3h 1 (16 commits, 14 agents) -25%
7 - Acceptabilité 2h ~1h 1 -50%
8 - Gate acceptabilité 1h ~0.5h 1 -50%
9 - REX 30 min ~1h 1 +100%
TOTAL ~14h ~12.5h 12 -11%

Note : Toutes les étapes ont été réalisées le 2026-03-01 (single day). Les durées réelles sont estimées à partir des timestamps des commits et des documents produits.

2.2 Scores de convergence par gate

Gate Score v1 Score final Delta Itérations
Gate 3 (CONFORMITY_CHECK) 6.13/10 8.75/10 +2.62 3
Gate 5 (AMBIGUITY) 5.31/10 8.19/10 +2.88 2
Gate 8 (CLOSURE) 8.13/10 8.13/10 0 1

Observations : - Gate 3 : NON_CONFORME en v1 (completeness 5.75, testability 5.5, clarity 5.5) puis escalade en v2 (plateau 6.5) puis GO en v3 (8.75). La convergence a nécessité 3 itérations — un des plus longs parcours Gate 3 du projet. - Gate 5 : NON_CONFORME en v1 (coverage 4.25, risk_mitigation 4.75) avec amélioration significative en v2 (8.19, delta +2.88). La correction des 19 écarts (3 BLQ + 9 MAJ + 7 MIN) a débloqué la convergence. - Gate 8 : RESERVE en v1 (test_coverage 7.5 < 8.0). Seul critère sous le seuil. Verdict maintenu sans itération supplémentaire.

2.3 Écarts par catégorie

Catégorie d'écart Gate 3 Gate 5 Gate 8 Total
ECT (complétude/testabilité) 2 3 4 9
DIV (divergence spec/impl) 1 2 5 8
AMB (ambiguïté) 3 2 1 6
SEC (sécurité) 0 0 2 2
PERF (performance) 0 0 0 0
TOTAL écarts 6 7 12 25

Distribution : ECT dominant (36%), suivi de DIV (32%) et AMB (24%). Les écarts SEC (8%) sont apparus uniquement en Gate 8 (audit synchrone filter décorrélé, @Catch() global vs sélectif).

3. Points fluides

Ce qui a bien fonctionné : - Réutilisation de patterns PD-279 : Le service DIP (QueryRunner, SELECT FOR UPDATE ordonné, outbox transactionnel) a été construit sur le pattern RestitutionService livré juste avant, réduisant le temps d'implémentation. - Migration DDL : Le pattern ALTER TYPE ADD VALUE IF NOT EXISTS hors transaction (PD-250/PD-279) a été appliqué sans friction. - Rate-limit guard : Le pattern LegalRateLimitGuard (PD-81) avec dual Redis (per-min + daily) a été adapté sans modification structurelle. - Exception filter audit : Le pattern DepositAuditExceptionFilter (PD-60) a fourni une base fonctionnelle, bien que la divergence synchrone/async ait émergé en Gate 8. - Décomposition step 6 : 14 agents parallèles ont produit 16 commits proprement ordonnés. L'implémentation a été la phase la plus rapide proportionnellement. - Machine à états : Extension simple de DocumentStateMachineService avec 2 transitions sans conflit avec les transitions PD-250/PD-279 existantes. - 99 tests unitaires : Couverture élevée (>94% statements sur les fichiers core) en un seul cycle d'implémentation.

4. Points difficiles

Obstacles rencontrés (sans justification) : - Gate 3 — Convergence lente (3 itérations) : La v1 avait 3 critères sous 6.0 (completeness, testability, clarity). La v2 a stagné en escalade (delta 0.375, plateau à 6.5). Il a fallu 3 itérations pour atteindre GO (8.75). - Gate 5 — Score coverage exceptionnellement bas en v1 (4.25) : 19 écarts identifiés dont 3 bloquants (table attestation absente de C1, audit refus async vs spec synchrone, enum DB 5 valeurs vs spec 4). Le plan v2 a nécessité une refonte significative. - Audit synchrone exception filter (E-10) : La divergence entre spec §5.8 (synchrone via QueryRunner dédié), plan v2 (INSERT direct dans QueryRunner), et implémentation réelle (logAsync() dans un QueryRunner décorrélé) a traversé toutes les gates sans résolution. Identifiée comme MAJEUR en Gate 8. - Enum DB 5 valeurs vs spec 4 : L'état RESTITUTED (PD-279) dans l'enum PostgreSQL a créé une divergence récurrente entre spec/tests/plan qui a nécessité des corrections à chaque gate. - Reviews LLM ChatGPT en attente : Les 3 prompts ont été générés mais non soumis, bloquant la levée complète des réserves Gate 8.

5. Hypothèses révélées tardivement

Hypothèses non explicites découvertes en cours de workflow : - H-06 (geo_copy_count = copies) — découverte à l'étape 4 (plan). La colonne PD-279 existait avec DEFAULT 0, mais la couche stockage ne la met pas encore à jour. Garde potentiellement bloquante systématique. - H-09 (table dédiée attestations) — découverte à l'étape 5 (Gate 5 v1, DIV-02 BLOQUANT). La spec décrivait un schéma riche §5.13 sans spécifier le mécanisme de stockage. Le plan C1 initial omettait la DDL. - H-08 (retention_service BYPASSRLS) — contractualisée dans le plan mais non vérifiée dans l'infrastructure réelle. Impact : DIP→SEALED cross-owner impossible si le rôle n'existe pas.

6. Invariants complexes

Invariants difficiles à implémenter ou sensibles aux régressions : - INV-278-04 (auditabilité) — TC-INV-04, TC-ERR-03/04/05/13/14 : L'exigence de persistance synchrone des refus sécurité (spec §5.8) a créé une tension avec le pattern logAsync() existant. Le QueryRunner dédié du filter est décorrélé de l'écriture audit réelle. - INV-278-11 (package-atomicity) — TC-INV-11, TC-ERR-11 : Le ROLLBACK global sur N documents avec SELECT FOR UPDATE ordonné fonctionne en unit mock mais nécessite des tests d'intégration pour prouver l'atomicité ACID réelle. - INV-278-12 (concurrency-control) — TC-INV-12 : Les tests de concurrence sont mockés (2 appels séquentiels), pas parallèles réels. Le SELECT FOR UPDATE est présent mais non prouvé sous charge. - INV-278-01 (state-set) — TC-INV-01 : L'ambiguïté "exactement 4 états" vs "5 valeurs DB incluant RESTITUTED" a nécessité des corrections dans chaque gate.

7. Dette technique

Compromis acceptés et non bloquants : - signature_ref = null (STUB PD-37) — impact : moyen. L'attestation est créée sans signature cryptographique HSM. Tracker : PD-37. - Partitioning attestations : 2 partitions seulement (2025, 2026) — impact : moyen. Sans job de maintenance annuel, les INSERT échoueront au 2027-01-01. Tracker : ProbatioVault-infra. - Tests d'intégration DB absents (WORM triggers, RLS, concurrence) — impact : moyen. 4 catégories d'invariants non prouvées par les mocks. Tracker : TODO-03. - Vérification formelle TLA+ non exécutée (TC-FML-01) — impact : faible. Le modèle TLA+ a été modifié (norm.yaml) mais TLC n'a pas été lancé. Tracker : TODO-04. - dissemination.config.ts — 0% coverage directe — impact : faible. Validée implicitement via les tests du service. - N_MAX hardcodé dans DTO (@ArrayMaxSize(100)) vs configurable dans config — impact : faible. Incohérence si config modifiée sans adapter le DTO.

8. Risques résiduels

Risque Type Probabilité Impact Mitigation
Audit refus sécurité non garanti synchrone tech moyen élevé Vérifier comportement logAsync() ; si async, adapter filter pour INSERT SQL direct
geo_copy_count toujours à 0 bloque toute transition DIP ops moyen élevé Vérifier mécanisme PD-279 de comptage copies ; fallback config MIN_COPIES=0 temporaire
Partition attestations 2027 manquante ops élevé élevé Créer job de maintenance annuel ou ticket infra
retention_service rôle BYPASSRLS absent ops faible élevé Vérifier existence du rôle PostgreSQL avant déploiement
Reviews LLM non soumises retardent merge métier élevé faible Soumettre les 3 prompts ChatGPT

8bis. Matrice de délégation inter-PD

Story Direction Statut Nature de la dépendance Problème rencontré
PD-279 ← dépend de DONE Enum RESTITUTED, geo_copy_count, RestitutionService pattern Enum DB 5 valeurs vs spec 4 — récurrent dans toutes les gates
PD-250 ← dépend de DONE DocumentStateMachineService, EXPIRED terminal, WORM triggers RAS — non-régression confirmée
PD-81 ← dépend de DONE Pattern LegalRateLimitGuard Redis RAS — réutilisé sans modification
PD-60 ← dépend de DONE Pattern DepositAuditExceptionFilter Divergence sync/async non anticipée
PD-37 ← dépend de STUB HSM signature probante pour attestation signature_ref = null — stub documenté
PD-16 ← dépend de DONE Rôle retention_service BYPASSRLS Non vérifié en infrastructure réelle
PD-252 → bloque TODO Tests d'intégration PostgreSQL (triggers, RLS, concurrence) À planifier

8ter. Bugs de tests

Pattern incorrect Pattern correct Cause Coût
TC-INV-01 : |enum| = 4 TC-INV-01 : DIP ∈ enum_range RESTITUTED PD-279 dans l'enum DB ~30 min (corrections dans 3 gates)
TC-NOM : numérotation QA report décalée TC-NOM : numérotation spec de tests QA report vs spec utilisent des numéros différents ~15 min (traçabilité rompue)

8quater. Corrections post-Gate 8

Correction Fichier Nature Pipeline
Vérifier logAsync() synchrone vs async dans filter dissemination-audit-exception.filter.ts Fix architecture (E-10 MAJEUR)
Soumettre reviews LLM ChatGPT PD-278-review-{code,tests,security}.md Reviews manquantes (E-09 MAJEUR)

9. Patterns récurrents détectés

9.1 Patterns confirmés (déjà vus dans d'autres stories)

  • Machine à états explicite avec transitions autorisées/interdites — aussi dans PD-250, PD-279, PD-280. Pattern stabilisé.
  • Guard de sécurité fail-closed obligatoire — aussi dans PD-238, PD-240, PD-250, PD-279, PD-280. Le rate-limit Redis fail-closed 503 est systématique.
  • Stubs inter-PD acceptés en Gate 8 si documentés — aussi dans PD-63, PD-251, PD-279, PD-280. signature_ref = null (PD-37) accepté avec story destination.
  • Atomicité : transaction ACID sync + post-commit async — aussi dans PD-55, PD-264, PD-250, PD-251, PD-279. Outbox dans la même transaction, publication async.
  • Format non contractualisé dans spec v1 bloque en Gate 3 — aussi dans PD-32, PD-250, PD-264, PD-279, PD-280. Les 6 ZO de Gate 3 v1 étaient des formats non spécifiés (N_MAX, endpoints, motif).
  • Faux positifs LLM en reviews — aussi dans PD-251, PD-277, PD-281, PD-279, PD-280. Grilles inadaptées au périmètre.
  • Double NON_CONFORME Gate 3+5 v1 = complexité conceptuelle élevée — aussi dans PD-280. PD-278 est le 2ème cas avec double NON_CONFORME initial qui converge vers RESERVE/GO.

9.2 Nouveaux patterns identifiés

  • Audit synchrone exception filter décorrélé du QueryRunner — pattern nouveau : le filter NestJS crée un QueryRunner mais logAsync() utilise son propre mécanisme interne. La transaction du filter est vide. À surveiller pour toute story avec audit de refus sécurité.
  • Enum DB étendue par PD précédent crée divergence spec/tests récurrente — pattern nouveau : l'ajout de RESTITUTED (PD-279) a impacté PD-278 sur 3 gates successives. À anticiper pour toute story qui étend les enums documentaires.
  • N_MAX hardcodé dans DTO vs configurable dans config — pattern candidat : le décorateur @ArrayMaxSize(100) ne suit pas la config dissemination.config.ts. Incohérence potentielle si la config change.
  • Table partitionnée sans job de maintenance — pattern candidat : 2 partitions créées (2025, 2026) mais aucun mécanisme pour les années suivantes. Risque d'échec INSERT silencieux.

10. Améliorations du workflow

10.1 Améliorations des prompts/templates

Fichier Amélioration suggérée Priorité
templates/prompts/1 Specification.md Exiger la mention explicite des enum DB existantes quand la story ajoute une valeur enum — évite la divergence "exactement N" récurrente haute
templates/prompts/1 Specification.md Ajouter section "Mécanisme de stockage" pour les artefacts probatoires (attestation, bordereau) — évite l'omission de la DDL table dédiée haute
templates/prompts/4 Plan d'implémentation.md Exiger la vérification que chaque logAsync() dans un filter est dans la même transaction que le QueryRunner parent — évite la décorrélation haute
templates/prompts/7a Review Code.md Ajouter check : "Le QueryRunner du filter est-il utilisé par l'appel d'audit ?" moyenne
templates/prompts/2 Tests Validation.md Ajouter validation croisée : si spec dit "exactement N", vérifier que N correspond à la réalité DB actuelle moyenne

10.2 Améliorations des agents

Agent Amélioration suggérée Justification
config/agents step 1 Injecter le résultat de SELECT unnest(enum_range(NULL::document_status)) pour les stories qui étendent un enum Divergence enum 4 vs 5 a traversé 3 gates
config/agents step 4 Vérifier que chaque exception filter utilise le QueryRunner qu'il crée (pas un service externe avec sa propre connexion) DIV-07 Gate 8 : QueryRunner décorrélé
config/agents step 6 Pour les stories avec attestation/bordereau, vérifier existence de la table cible avant l'INSERT dans le service BLQ-01 Gate 5 : table attestation absente de C1

10.3 Améliorations du processus

  • Vérification DB préalable pour stories enum : Avant l'étape 1, interroger la base pour connaître l'état réel de l'enum PostgreSQL cible. Cela aurait évité 3 itérations de corrections sur "exactement 4 vs 5 valeurs".
  • Review croisée sync/async : Pour toute story avec audit de refus sécurité, ajouter une vérification explicite que la persistance d'audit utilise la même connexion/transaction que le filter qui la déclenche.
  • Checklist partitioning : Toute table partitionnée doit avoir un ticket de job de maintenance dans le même sprint que la story.

11. Enseignements clés

  1. Vérifier l'état réel du schéma DB avant de rédiger la spec — L'enum document_status contenait déjà 5 valeurs (incluant RESTITUTED PD-279), mais la spec a été rédigée avec "exactement 4". Ce décalage a coûté 3 itérations de Gate 3.

  2. Un QueryRunner créé dans un filter NestJS ne garantit pas que les appels internes l'utilisent — Le pattern queryRunner.startTransaction()service.logAsync()queryRunner.commitTransaction() est trompeur si logAsync() a sa propre connexion. La transaction est vide.

  3. Les stories qui ajoutent un état à une machine à états existante bénéficient directement des patterns de la story précédente — PD-278 a réutilisé les patterns PD-279 (RestitutionService) presque à l'identique. L'implémentation a été la phase la plus rapide.

  4. Les tables probatoires dédiées (attestations) doivent apparaître dans la spec, pas seulement dans le plan — L'omission de la table dissemination_attestations dans le plan v1 a été détectée comme BLOQUANT en Gate 5.

  5. Le double NON_CONFORME Gate 3+5 v1 est un indicateur fiable de complexité conceptuelle élevée — PD-278 et PD-280 partagent ce profil. La convergence nécessite des corrections structurelles, pas incrémentales.

12. Métriques cumulatives (auto-calculées)

Métrique Cette story Moyenne projet Tendance
Temps total 12.5h 5.6h ↑ (story medium/complex)
Itérations gates 6 5.3 ↑ (+13%)
Écarts totaux 25 21.7 ↑ (+15%)
Score convergence moyen 8.36/10 8.68 ↓ (-0.32)

Note : Le score moyen (8.36) est en dessous de la moyenne projet (8.68) principalement à cause des scores v1 très bas (Gate 3: 6.13, Gate 5: 5.31) qui ont nécessité des corrections significatives. Le score final Gate 3 (8.75) est dans la norme.


REX produit le 2026-03-01 — PD-278 NF Z42-013 état DIP Branche : feature/PD-278-nfz42013-dip-state Commits : 16 (aa0af7c → c92145b) Tests : 99/99 pass