PD-253 — Retour d'expérience (REX)
1. Résumé exécutif
| Métrique | Valeur |
| Objectif initial | Export bulk réversible auto-porteur avec preuves probatoires (NF Z42-013 §13.1, RGPD Art.20) |
| Résultat obtenu | Conforme — module bulk-export complet, 12 code-contracts, BagIt dual-manifest |
| Verdict final | RESERVE (Gate 8 : 8.438/10, conformity 7.5 < 8.0) |
| Tests contractuels | 126/126 passés (bulk-export suite) |
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 | 13 min | 1 | -57% |
| 1 - Spécification | 2h | 6 min | 1 | -95% |
| 2 - Tests | 1h | 4 min | 1 | -93% |
| 3 - Gate spec | 1h | 47 min | 2 | -22% |
| 4 - Plan | 1h | 10 min | 1 | -83% |
| 5 - Gate plan | 1h | 12 min | 1 | -80% |
| 6 - Implémentation | 4h | 36 min | 1 | -85% |
| 7 - Acceptabilité | 2h | 30 min | 1 | -75% |
| 8 - Gate acceptabilité | 1h | 24 min | 1 | -60% |
| 9 - REX | 30 min | 40 min | 1 | +33% |
| TOTAL | ~14h | ~3.7h | 11 | -74% |
Note : Les durées réelles correspondent au temps machine (LLM + assemblage). Le temps humain de supervision et validation est exclu. Le gap de 37 min entre step 6 et step 7 (CI build/Sonar) est exclu du calcul.
2.2 Scores de convergence par gate
| Gate | Score v1 | Score final | Delta | Itérations |
| Gate 3 | 5.688/10 | 8.625/10 | +2.937 | 2 |
| Gate 5 | 8.625/10 | 8.625/10 | 0 | 1 |
| Gate 8 | 8.438/10 | 8.438/10 | 0 | 1 |
Gate 3 : v1 NON_CONFORME (clarity 4.0, testability 5.75). Après corrections spec v2 (ajout schéma destruction-log, confirmation téléchargement, failure_reason), delta +2.937 significatif.
Gate 5 : GO en v1 — le plan a résolu les 5 réserves ECT de Gate 3. Score homogène (8.0-9.0 sur tous les critères).
Gate 8 : RESERVE en v1 — conformity 7.5 due à 6 items reportés vers PD-253b (dont INV-253-11 temp encryption MAJEUR — résolu post-clôture 2026-03-12).
2.3 Écarts par catégorie
| Catégorie d'écart | Gate 3 | Gate 5 | Gate 8 | Total |
| ECT (complétude/testabilité) | 5 | 0 | 2 | 7 |
| DIV (divergence spec/impl) | 0 | 0 | 5 | 5 |
| AMB (ambiguïté) | 0 | 0 | 0 | 0 |
| SEC (sécurité) | 0 | 0 | 6 | 6 |
| PERF (performance) | 0 | 0 | 0 | 0 |
| TOTAL écarts | 5 | 0 | 13 | 18 |
3. Points fluides
Ce qui a bien fonctionné :
- Gate 5 GO en v1 : Le plan d'implémentation a résolu toutes les réserves de Gate 3 (ECT-01 à ECT-06). La checklist pre-Gate 5 (
/gov-check-plan) a prouvé son efficacité — aucune ambiguïté résiduelle. - Implémentation rapide : 24 fichiers créés en 36 min (step 6) via décomposition en 12 code-contracts clairement délimités. La séparation en services granulaires (
ExportQuotaService, ExportStateMachineService, DualManifestService, etc.) a facilité la parallélisation des agents. - Injection des learnings : 5 learnings injectés à l'étape 0 (PD-244, PD-278, PD-85, PD-283). Les patterns anti-catch-absorb et validation fonctionnelle ont été appliqués dès la spec, évitant les itérations habituelles sur ces sujets.
- 126 tests passés avec 82.3% de couverture sur les nouvelles lignes — au-dessus du seuil Sonar (80%).
- Zéro erreur ESLint/TypeScript à la livraison (après corrections Sonar appliquées avant scan final).
4. Points difficiles
Obstacles rencontrés (sans justification) :
- Gate 3 v1 NON_CONFORME (5.688/10) : scores très bas en clarity (4.0) et testability (5.75). La spec v1 omettait le mécanisme de confirmation de téléchargement, le schéma de destruction-log.json, et le modèle failure_reason. Correction nécessitant refonte partielle de la spec.
- Audit fail-closed absent à la livraison (E-01/S-02 BLOQUANT) :
AuditLogService non injecté dans BulkExportService. Découvert en review code (step 7), alors que INV-253-10 était explicite dans la spec. Le prompt agent n'a pas suffisamment insisté sur le pattern fail-closed. - Soft-deleted exclus par erreur (E-02 BLOQUANT) :
resolveGlobal utilisait deletedAt IS NULL au lieu de withDeleted: true. Violation de INV-253-09 passée inaperçue en step 6 malgré un invariant explicite. - Nom de manifest incorrect (E-03 BLOQUANT) :
manifest-sha3-256.txt au lieu de manifest-sha3.txt. Erreur triviale mais violation de INV-253-04 (contrat spec §5.1). - Quota bypass via READY_FOR_DOWNLOAD (S-01/T-01 MAJEUR) :
ACTIVE_BULK_EXPORT_STATUSES n'incluait que 2 des 3 états actifs. Le 3e (READY_FOR_DOWNLOAD) permettait de contourner le quota d'1 export concurrent. - Dérogation Art. II systématique : Tous les prompts de review (7a/7b/7c) et de gate (Gate 3, Gate 8) dépassaient le seuil 30KB de ChatGPT.
claude -p utilisé comme reviewer principal pour toutes les gates (dérogation Art. II §exception-technique documentée).
5. Hypothèses révélées tardivement
Hypothèses non explicites découvertes en cours de workflow :
- H-253-08 — Snapshot sémantique : La sélection du périmètre se fait au moment
ASSEMBLING (pas REQUESTED). Un document créé entre les deux états n'est pas inclus. Hypothèse documentée à l'étape 4 (plan), absente de la spec initiale. - H-253-09 — Inclusion binaire chiffré : L'export contient
content.enc (copie bit-à-bit depuis S3). Décision prise à l'étape 4 pour satisfaire "auto-porteur" NF Z42-013 §13.1. Impact : packages potentiellement très volumineux (jusqu'à 100 GB). - H-253-10 — Chiffrement au repos via disk encryption : INV-253-11 est satisfait par le chiffrement disque hôte, pas par un chiffrement applicatif des fichiers temp. Hypothèse révélée en review sécurité (step 7) — reportée vers PD-253b car nécessite une décision d'architecture.
- H-253-11 — Multipart upload S3 obligatoire : Packages > 5GB nécessitent multipart upload (limite S3 PutObject). Identifié à l'étape 4 — stub documenté avec
STUB: S3Service.uploadFile → multipart.
6. Invariants complexes
Invariants difficiles à implémenter ou sensibles aux régressions :
- INV-253-01 (exhaustivité) — TC-NOM-01 : Le comptage source vs export éligibles est le verrou principal. Toute divergence force
FAILED (pas de succès partiel). La combinaison des 4 scopes (GLOBAL/VAULT/PERIOD/SELECTION) avec les règles d'inclusion/exclusion (soft-deleted inclus, détruits exclus) crée une matrice complexe. - INV-253-10 (audit fail-closed) — TC-ERR-07 : L'audit DOIT être dans la transaction de création. Le pattern
try { audit(); save(); commit(); } catch { rollback(); } est fragile — l'injection de AuditLogService a été oubliée en step 6 (E-01 BLOQUANT). - INV-253-13 (machine à états) — TC-NOM-09 : 8 états, transitions explicites, 5 terminaux. Le
ExportStateMachineService centralise la validation mais le scheduler d'expiration (E-05) contournait le service en mettant à jour le status directement. - INV-253-12 (no-residuals) — TC-INV-12 : Double nettoyage (
purgeStaleTemp au démarrage + finally block). Pattern issu de PD-283, correctement appliqué ici.
7. Dette technique
Compromis acceptés et non bloquants :
INV-253-11 — Chiffrement fichiers temp : Résolu post-clôture (2026-03-12). checkDiskEncryption() ajouté dans ExportCleanupService (OnModuleInit) — vérifie la présence de dm-/luks dans /proc/mounts et émet un warning si le disk encryption hôte n'est pas détecté. Option C retenue (chiffrement ZK côté serveur rend l'option B redondante). 4 tests ajoutés. - Processor non testé : Le worker BullMQ (
bulk-export.processor.ts, 610 lignes) a 0% de couverture unitaire. Il orchestre les 7 services injectés et requiert un environnement S3/HSM complet. Exclu par conception — les services individuels sont testés à 82.3%. Impact: moyen. - Scheduler FSM bypass (E-05/S-05) :
ExportExpiryScheduler modifie le status directement au lieu de passer par ExportStateMachineService. Risque de transition invalide si la logique de garde évolue. Impact: faible. create() sans transaction explicite (S-06) : Le service de création utilise repository.save() hors queryRunner. Si queue.add() échoue après le save, l'export est créé sans job BullMQ (orphelin rattrapable par monitoring). Impact: faible. confirmDownload non idempotent (S-07) : Double appel émet un double audit. Correction triviale mais reportée. Impact: faible. scopeParams validation lâche (S-03) : Validé par @IsObject() sans sous-DTO typé. Les champs vault_id, from, to, document_ids ne sont pas validés au niveau DTO. Impact: faible (validés dans le service).
8. Risques résiduels
| Risque | Type | Probabilité | Impact | Mitigation |
| Packages > 5GB échouent (H-253-11) | tech | moyen | élevé | Stub S3Service.uploadFile → multipart documenté, à implémenter |
Fichiers temp non chiffrés (INV-253-11) | ops/sec | faible | moyen | Résolu 2026-03-12 — checkDiskEncryption() |
| Scheduler bypass FSM (E-05) | tech | faible | faible | Migration vers FSM dans PD-253b |
| Orphan export si queue.add échoue (S-06) | ops | faible | faible | Monitoring BullMQ dead-letter + reconciliation |
| Gate 8 RESERVE non levée | métier | moyen | moyen | Décision humaine requise — conformity 7.5 |
8bis. Matrice de délégation inter-PD
| Story | Direction | Statut | Nature de la dépendance | Problème rencontré |
| PD-282 | ← dépend de | DONE | ProofEnvelope (LegalCompositeProof) consommée en lecture | RAS |
| PD-39 | ← dépend de | DONE | Token TSA RFC 3161 consommé dans ProofEnvelope | RAS |
| PD-37 | ← dépend de | DONE | HsmService.sign() pour export.sig (niveau strong) | Stub optionnel, non bloquant |
| PD-250 | ← dépend de | DONE | Statut destruction légale, bordereau_id, destruction_act_hash | RAS |
| PD-85 | ← dépend de | DONE | Pattern export, learnings anti-catch-absorb | Learnings correctement appliqués |
| PD-253b | → bloque | TODO | 5 items reportés (FSM bypass, transaction, idempotence, validation scopeParams, TC-ERR-09) — INV-253-11 résolu | À planifier |
8ter. Bugs de tests
| Pattern incorrect | Pattern correct | Cause | Coût |
ACTIVE_BULK_EXPORT_STATUSES = [REQUESTED, ASSEMBLING] | [REQUESTED, ASSEMBLING, READY_FOR_DOWNLOAD] | Oubli 3e état actif | 15 min |
| Test audit trail absent | +2 tests audit (émission + fail-closed) | Invariant non couvert par tests initiaux | 20 min |
8quater. Corrections post-Gate 8
| Correction | Fichier | Nature | Pipeline |
| AuditLogService injection | bulk-export.service.ts | Fix BLOQUANT — fail-closed absent | #2381409903 |
| resolveGlobal withDeleted | export-scope.service.ts:107 | Fix BLOQUANT — soft-deleted exclus | #2381409903 |
| manifest-sha3.txt rename | bagit-assembler.service.ts:152 | Fix BLOQUANT — nom fichier incorrect | #2381409903 |
| ACTIVE_STATUSES += READY_FOR_DOWNLOAD | bulk-export-status.enum.ts:28 + migration | Fix MAJEUR — quota bypass | #2381409903 |
| +2 tests audit trail | bulk-export.service.spec.ts | +2 tests couverture INV-253-10 | #2381409903 |
| Sonar fixes (parseInt, ternary, catch params) | Multiple | Fix Sonar S7773/S6582/S7735/S7718 | #2381409903 |
9. Patterns récurrents détectés
9.1 Patterns confirmés (déjà vus dans d'autres stories)
- Audit fail-closed oublié à l'implémentation — aussi dans PD-85, PD-63, PD-250, PD-262, PD-265. Pattern anti-catch-absorb documenté mais pas systématiquement appliqué par les agents. 6e occurrence.
- Machine à états avec bypass scheduler — aussi dans PD-278, PD-280, PD-265. Le scheduler/cron modifie l'état directement au lieu de passer par le service FSM. 4e occurrence.
- Soft-deleted exclu par défaut TypeORM — aussi dans PD-279.
withDeleted: true oublié dans les requêtes qui doivent inclure les entités soft-deleted. 2e occurrence. - Gate 3 v1 NON_CONFORME sur complétude — aussi dans PD-278 (double NON_CONFORME), PD-276, PD-275. Les specs complexes (14 INV, 8 états) ont systématiquement un score testability < 6.0 en v1.
- Dérogation Art. II pour prompts > 30KB — aussi dans PD-283, PD-262. ChatGPT en mode agentic sur les gros prompts. Stratégie
claude -p confirmée stable. - Validation format ≠ fonctionnelle — aussi dans PD-283, PD-282, PD-265. Le pattern est documenté dans les learnings mais les agents génèrent encore du code avec validation format uniquement.
9.2 Nouveaux patterns identifiés
- Quota bypass par oubli d'un état actif : Le quota
COUNT(*) WHERE status IN (...) est sensible à la complétude de la liste des états actifs. Tout nouvel état actif doit être ajouté à la constante ACTIVE_STATUSES. Pattern à surveiller dans toute story avec quota/concurrence. - Manifest filename divergence spec/code : Le nom de fichier dans la spec (
manifest-sha3.txt) a été transformé en manifest-sha3-256.txt par l'agent — expansion implicite du nom d'algorithme. Les noms de fichiers contractuels doivent être vérifiés par un test string-exact.
10. Améliorations du workflow
10.1 Améliorations des prompts/templates
| Fichier | Amélioration suggérée | Priorité |
templates/prompts/step6-agent.md | Ajouter checklist "audit fail-closed" dans les invariants systématiques injectés à chaque agent | haute |
templates/prompts/step6-agent.md | Ajouter rappel "noms de fichiers contractuels = string-exact de la spec, pas d'interprétation" | moyenne |
templates/prompts/step2-tests.md | Ajouter un test systématique sur les constantes de groupement (ACTIVE_STATUSES, TERMINAL_STATUSES) vs l'enum complet | moyenne |
10.2 Améliorations des agents
| Agent | Amélioration suggérée | Justification |
agent-developer | Injecter le pattern anti-catch-absorb comme contrainte systématique (pas seulement via learnings) | 6e occurrence en 22 stories |
agent-developer | Vérifier withDeleted: true sur toute requête impliquant des entités avec soft-delete | 2e occurrence — pattern en émergence |
10.3 Améliorations du processus
- Checklist ACTIVE_STATUSES : Pour toute story avec machine à états + quota/concurrence, vérifier que la liste des états actifs couvre tous les états non-terminaux. Ajouter dans
/gov-check-plan. - Test string-exact sur noms de fichiers contractuels : Quand la spec définit des noms de fichiers (manifests, logs), ajouter un test vérifiant le nom exact du fichier produit.
11. Enseignements clés
- L'audit fail-closed reste le piège #1 des agents — Malgré 5 occurrences documentées et un learning explicite, l'injection de
AuditLogService a été oubliée. Le learning seul ne suffit pas — il faut une contrainte hard-coded dans le prompt agent. - Les constantes de groupement d'états sont des vecteurs de régression silencieux — Un enum à 8 valeurs avec une constante de sous-ensemble (
ACTIVE_STATUSES) est une bombe à retardement. Tout ajout d'état nécessite la mise à jour de toutes les constantes dérivées. - Les noms de fichiers sont des contrats —
manifest-sha3.txt vs manifest-sha3-256.txt est une violation d'invariant aussi grave qu'un bug logique. Les agents interprètent les noms au lieu de les copier verbatim. - Gate 3 v1 NON_CONFORME est normal pour les stories à 10+ invariants — Les specs complexes nécessitent systématiquement 2 itérations de Gate 3. Le workflow le gère bien (delta +2.937 entre v1 et v2).
- La décomposition en 12 code-contracts a permis une implémentation en 36 min — La granularité fine des contracts (1 service = 1 CC) accélère la génération et facilite la revue.
12. Métriques cumulatives (auto-calculées)
| Métrique | Cette story | Moyenne projet | Tendance |
| Temps total | 3.7h | 6.49h | ↓ |
| Itérations gates | 4 | 5.39 | ↓ |
| Écarts totaux | 18 | 23.4 | ↓ |
| Score convergence moyen | 8.56/10 | 8.44/10 | ↑ |