Aller au contenu

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

  1. 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.
  2. 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.
  3. Les noms de fichiers sont des contratsmanifest-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.
  4. 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).
  5. 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