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 configdissemination.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¶
-
Vérifier l'état réel du schéma DB avant de rédiger la spec — L'enum
document_statuscontenait 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. -
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 silogAsync()a sa propre connexion. La transaction est vide. -
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.
-
Les tables probatoires dédiées (attestations) doivent apparaître dans la spec, pas seulement dans le plan — L'omission de la table
dissemination_attestationsdans le plan v1 a été détectée comme BLOQUANT en Gate 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