PD-286 — Retour d'expérience (REX)
1. Résumé exécutif
| Métrique | Valeur |
| Objectif initial | Export probatoire multi-volumes pour dossiers > 768 MB jusqu'à 10 GB, transparent côté utilisateur |
| Résultat obtenu | Conforme partiel — 11 modules livrés (C1..C10 + tests) ; chemin nominal multi-volume opérationnel ; cinq blocages identifiés en confrontation Gate 8 (audit FINAL non câblé, C6 non intégré, hashes non persistés, Sonar SKIP, service de bundling S3 hors-périmètre) |
| Verdict final | RESERVE (Gate 8 score 8.25/10, conformity = 6.0) |
| Tests contractuels | 380/380 backend (26 suites) + 289/289 app — total 669 tests passants |
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 | 16 min | 1 | -47% |
| 1 - Spécification | 2h | 22 min | 1 | -82% |
| 2 - Tests | 1h | 2 min | 1 | -97% |
| 3 - Gate spec | 1h | 31 min | 2 | -48% |
| 4 - Plan | 1h | 9 min | 1 | -85% |
| 5 - Gate plan | 1h | 12 min | 2 | -80% |
| 6 - Implémentation | 4h | 1h52 | 11 (waves) | -53% |
| 7 - Acceptabilité | 2h | 7 min | 1 | -94% |
| 8 - Gate acceptabilité | 1h | 12 min | 1 | -80% |
| 9 - REX | 30 min | 30 min | 1 | 0% |
| TOTAL | ~14h | ~4.2h | — | -70% |
2.2 Scores de convergence par gate
| Gate | Score v1 | Score final | Delta | Itérations |
| Gate 3 | 6.25/10 | 8.875/10 | +2.625 | 2 |
| Gate 5 | 7.25/10 | 8.875/10 | +1.625 | 2 |
| Gate 8 | 8.25/10 | 8.25/10 | 0 | 1 (RESERVE) |
2.3 Écarts par catégorie
| Catégorie d'écart | Gate 3 | Gate 5 | Gate 8 | Total |
| ECT (complétude/testabilité) | 2 | 1 | 1 | 4 |
| DIV (divergence spec/impl) | 5 | 8 | 11 | 24 |
| AMB (ambiguïté) | 6 | 4 | 8 | 18 |
| SEC (sécurité) | 0 | 0 | 2 | 2 |
| PERF (performance) | 0 | 0 | 0 | 0 |
| TOTAL écarts | 13 | 13 | 22 | 48 |
3. Points fluides
- Découpage en 4 waves (Foundations → Backend services → Intégration → Audit/Tests) bien adapté à la story cross-repo backend+app — parallélisation Wave 2 (5 modules en parallèle) sans collision.
- Réutilisation directe de l'infrastructure PD-85 (
IntegrityHashComputer, JsonCanonicalizeService, conteneur .pvproof, audit-journal) — aucune réécriture. - Branded types
ExportId, VolumeIndex, TotalVolumes appliqués dès le DTO C2 (learning 2026-03-04) — aucune inversion silencieuse détectée en review. crypto.randomUUID() utilisé exclusivement (learning 2026-02-21) — 0 alerte Sonar S2245. - Anti-catch-absorb (learning 2026-03-08) intégré dans C5 (try/throw + dataSource.transaction) et C6 (fail-closed sans
.catch()) dès la conception — 0 régression. - Validation de continuité
volumeIndex triplée (C1 runtime, C2 class-validator, C3 manifest root, C10 Zod, C7 tri défensif) — INV-286-06 protégé en defense-in-depth. - Trigger PG
prevent_forbidden_export_transition ajouté en complément de la state machine TS (C4) — sécurité multicouche pour INV-286-10/11. - Convergence Gate 3 et Gate 5 rapide (delta +2.625 et +1.625 en une seule itération de correction) — feedback gate exploitable directement par l'orchestrateur.
4. Points difficiles
- Gate 3 v1 NON_CONFORME (6.25/10) sur completeness=4.0 et testability=6.0 : la spec initiale ne couvrait pas les bornes, le diagramme d'état, le modèle de données contractuel ni les SLA temporels. Reprise complète des sections §4-§8 en v2.
- Gate 5 v1 NON_CONFORME (7.25/10) sur feasibility=5.0 : le plan référençait une lib partagée
@probatiovault/jcs jamais créée, et n'identifiait pas de service de bundling S3 comme dépendance amont (DIV-06, DIV-08). - Gate 8 RESERVE (8.25/10) sur conformity=6.0 : la confrontation a relevé cinq blocages — l'audit final WORM
status=COMPLETED n'est jamais émis (DIV-04, endpoint REST manquant), ExportAuditService (C6) livré mais non câblé en step 6c (DIV-03), integrityHashes non persistés sur ExportSession empêchant ExpiredExportWorker d'émettre appendFinal (DIV-05), Sonar SKIP au lieu de l'installation obligatoire (DIV-01), service de bundling S3 hors-périmètre sans story de dépendance identifiée (DIV-08). - Step 6c (intégration) tardive : C6 livré 41 minutes avant l'acceptabilité, sans temps pour câbler
ExportService → ExportAuditService ni faire migrer auditLogService.log() direct → ExportAuditService.appendStart/appendFinal(). - Endpoint REST
POST /exports/:id/finalize non identifié au plan — la spec §5.4 dit « App → Audit: append {exportId, status: COMPLETED} » mais ne tranche pas le canal (callback REST, websocket, polling worker). C7 passe à READY localement sans signaler le COMPLETED au backend.
5. Hypothèses révélées tardivement
- H-PD286-PLAN-01 (RFC 8785 cross-runtime Node ↔ Hermes) — découverte à l'étape 4 (plan) puis non vérifiée à l'étape 6 : la suite roundtrip CI 100 manifests
@probatiovault/jcs annoncée n'a pas été créée (la lib elle-même n'existe pas comme package npm interne). Risque INV-286-07 latent en production sur manifests Unicode/floats edge. - H-PD286-PLAN-04 (compatibilité ascendante
.pvproof) — découverte à l'étape 4, marquée à vérifier en TC-NR-02. C9 livre le test partiel (assembleur OK) mais le consommateur PD-85 e2e n'a pas été exécuté en CI. La tolérance des champs volumes_count + assembled_from[] par les lecteurs PD-85 reste empirique. integrityHashes doivent être persistés sur ExportSession — découverte à l'étape 6 (C6 §8.4) : sans colonne integrity_hashes JSONB, le worker EXPIRED ne peut pas émettre un appendFinal valide. Migration DB à compléter — non couvert par le plan §1.1 C4. - Service de bundling S3 par volume — découverte à l'étape 6 (C5 §4 décision D3) :
signedUrl pointe vers exports/{exportId}/v{volumeIndex}.bundle mais aucun service ne produit le bundle. Story dépendante non identifiée à l'étape 1 ni à l'étape 4. - Borne max sur
totalVolumes — émergée à l'étape 7 (review sécurité 7c) : la spec §5.1 dit « min 1, pas de borne max fixe contractuelle ». Un attaquant créant 13 preuves de 768 MB exact obtient totalSize = 9.984 GB ≤ MAX_TOTAL → 13 volumes dédiés acceptés.
6. Invariants complexes
- INV-286-07 (vérification fonctionnelle hash app) — TC-NOM-04, TC-ERR-04. Sensibilité cross-runtime (Node ↔ Hermes RFC 8785). Test d'ordre
verify() AVANT appendVolume() requis par spy (CA-286-05). - INV-286-09 (audit WORM fail-closed) — TC-NOM-05. Atomicité state-transition + audit-event dans la même transaction PostgreSQL ; rollback obligatoire si append throw (anti-catch-absorb). Endpoint REST de finalisation manquant en production → INV partiellement câblé.
- INV-286-10/11 (machine d'états) — TC-INV-10, TC-INV-11. Defense-in-depth code TS + trigger PG
prevent_forbidden_export_transition. Drift potentiel matrice C4 backend vs C7 app non testé en croisé. - INV-286-08 (
.pvproof unique conteneur inchangé) — TC-NOM-03, TC-NR-02. Sérialisation conditionnelle volumes_count + assembled_from[] uniquement si multi-volumes ; cardinalité assembled_from[].length === totalVolumes validée par C9 finalize(). - INV-286-05 (rétrocompatibilité legacy) — TC-NR-01. Sérialisation conditionnelle via champs
?: TypeScript (omission undefined plutôt que null) — choix contraint par le snapshot byte-stable PD-85.
7. Dette technique
- DIV-04 — Endpoint REST
POST /exports/:id/finalize absent : transition ASSEMBLING → COMPLETED côté backend jamais déclenchée nominal. Audit FINAL status=COMPLETED jamais émis. Impact: élevé (violation INV-286-09). - DIV-03 —
ExportAuditService (C6) livré mais non câblé : ExportService appelle directement auditLogService.log() ; les forbiddens C6 (signedUrl/manifest hors metadata, codes uniformes) non appliqués en production. Migration step 6c à compléter. Impact: élevé. - DIV-05 —
integrityHashes non persistés sur ExportSession : worker EXPIRED ne peut pas émettre appendFinal valide. Migration DB requise (integrity_hashes JSONB). Impact: moyen (concerne uniquement EXPIRED multi-volume). - DIV-06 — Lib
@probatiovault/jcs inexistante : suite roundtrip CI 100 manifests cross-runtime annoncée non créée. JsonCanonicalizeService PD-37 fait office de lib backend, pas d'équivalent app. Impact: moyen (faux négatifs INV-286-07 latents). - DIV-08 — Service de bundling S3 hors-périmètre :
signedUrl pointe vers des objets S3 inexistants tant qu'aucun service ne produit le bundle. Story dépendante non identifiée, non chiffrée, non planifiée. Impact: élevé (bloque le déploiement réel). - DIV-01 — Sonar QG SKIP :
sonar-scanner non installé, mitigation ESLint+TSC. Procédure §7 acceptabilité explicite la dérogation comme PLUS acceptée. Impact: moyen (procédural). - DIV-09 — TTL clamp silencieux :
EXPORT_SESSION_TTL_HOURS=100 est ramené à 72h au lieu de fail-closed au démarrage (spec §5.2). Impact: faible (misconfiguration non détectée). - DIV-10 — Borne max
totalVolumes absente : 13 volumes dédiés de 768 MB acceptés. Impact: faible (stockage temporaire borné par TTL + rate limiting existants). - TC-NR-02 (consommateur PD-85 e2e) : non exécuté en CI. Test marqué OK partiel. Impact: faible (mitigation
pvproof_format_version envisagée si fail). - Erreur TS résiduelle
complaint-file-response.dto.ts:134 : signalée par C3+C5 mais déclarée « hors scope » par C2. À reconfirmer par npx tsc --noEmit. Impact: faible.
8. Risques résiduels
| Risque | Type | Probabilité | Impact | Mitigation |
Audit FINAL status=COMPLETED jamais émis (DIV-04) | tech | élevée | élevé | Story de suivi : endpoint REST POST /exports/:id/finalize ou polling ExpiredExportWorker étendu aux COMPLETED |
| Bundling S3 absent → 404 download volume (DIV-08) | tech | élevée | élevé | Story amont à créer : service de bundling déterministe exports/{exportId}/v{N}.bundle |
| Faux négatifs INV-286-07 cross-runtime (DIV-06) | tech | faible | élevé | Suite CI roundtrip 100 manifests Node ↔ Hermes ; création package @probatiovault/jcs |
| Worker EXPIRED throw sur multi-volume (DIV-05) | tech | moyenne | moyen | Migration DB ajoutant integrity_hashes JSONB sur ExportSession |
volumes[] count abuse non borné (DIV-10) | sec | faible | moyen | Borne MAX_TOTAL_VOLUMES = 14 côté C5 ou contractualisation spec §5.1 |
| Drift matrice C4 backend ↔ C7 app (ZO-08) | tech | faible | moyen | Test croisé exportant ALLOWED_TRANSITIONS des deux côtés et comparant |
| Sonar QG non exécuté → règles sécurité non vérifiées (DIV-01) | proc | élevée | faible | brew install sonar-scanner + scan local obligatoire avant Gate 8 |
Migration 1745000000000-PD286-ExportMultiVolumes non testée (ZO-07) | tech | moyenne | moyen | Exécuter typeorm migration:run sur DB cible et joindre rapport à l'acceptabilité |
8bis. Matrice de délégation inter-PD
| Story | Direction | Statut | Nature de la dépendance | Problème rencontré |
| PD-85 | ← dépend de | DONE | Conteneur .pvproof, IntegrityHashComputer, audit-journal, controller export single-volume legacy | Rétrocompatibilité validée par snapshot byte-stable (TC-NR-01) ; TC-NR-02 e2e non exécuté |
| PD-37 | ← dépend de | DONE | JsonCanonicalizeService (RFC 8785) — réutilisé sur backend uniquement | Pas d'équivalent app → DIV-06 lib partagée manquante |
| PD-283 | ← dépend de | DONE | Tests state-machine (76 tests préservés) | Aucun écart |
| PD-101 | ← dépend de | DONE | Audit WORM fail-closed pattern | C6 hérite du pattern ; non câblé en step 6c (DIV-03) |
| PD-286bis (à créer) | → bloque | TODO | Endpoint REST finalisation POST /exports/:id/finalize | Identifié confrontation step 8 DIV-04 |
| PD-286ter (à créer) | → bloque | TODO | Service de bundling S3 par volume | Identifié confrontation step 8 DIV-08 |
| PD-286quater (optionnel) | → bloque | TODO | Migration DB integrity_hashes JSONB sur ExportSession | Identifié confrontation step 8 DIV-05 |
8ter. Bugs de tests
Aucun bug de pattern de test rencontré sur PD-286. Tests CI 669/669 PASS (380 backend + 289 app) en première exécution post-step 6c.
8quater. Corrections post-Gate 8
Aucune correction post-Gate 8 commitée à ce stade (REX produit immédiatement après le verdict RESERVE).
| Correction | Fichier | Nature | Pipeline |
| À planifier (DIV-04) | complaint-file.controller.ts | Ajout endpoint POST /exports/:id/finalize | story PD-286bis |
| À planifier (DIV-03) | export.service.ts | Migration auditLogService.log() → ExportAuditService.appendStart/appendFinal() | story PD-286bis (step 6c) |
| À planifier (DIV-05) | export-session.entity.ts + migration | Colonne integrity_hashes JSONB | story PD-286quater |
9. Patterns récurrents détectés
9.1 Patterns confirmés (déjà vus dans d'autres stories)
- Anti-catch-absorb appliqué dès la conception — confirmé sur PD-85, PD-63, PD-250, PD-262, PD-265, PD-283, PD-287, PD-286 (8 stories).
- Sonar QG SKIP
Docker indisponible — récurrent sur PD-302, PD-287, PD-286. La règle « brew install sonar-scanner obligatoire » introduite après PD-248 n'est pas systématiquement appliquée par l'orchestrateur. - Stub inter-PD non identifié comme dépendance amont — récurrent sur PD-251, PD-72, PD-287, PD-286 (service de bundling S3). La règle « stub inter-PD avec story destination obligatoire » est respectée mais ne couvre pas les dépendances amont non créées.
- Sérialisation conditionnelle
@Expose pour rétrocompatibilité legacy — récurrent sur PD-85 (réponse single-volume), PD-286 (réponse volumes[]). Pattern validé par snapshot byte-stable. - Branded types pour UUID sémantiquement distincts — récurrent sur PD-287, PD-286 (
ExportId, VolumeIndex, TotalVolumes). Aucun écart en review.
9.2 Nouveaux patterns identifiés
- Lib partagée référencée en plan sans création explicite en wave foundations (DIV-06) : le plan annonce
@probatiovault/jcs comme dépendance ; aucune wave ne la produit. Surveiller si récurrent sur les prochaines stories cross-repo. - Composant Wave 4 livré mais non câblé en step 6c (DIV-03) : C6 livré (48 tests) mais
ExportService continue d'appeler auditLogService.log() directement. La phase d'intégration (step 6c) doit valider que tous les composants Wave 4 sont effectivement intégrés au flux principal. - Endpoint REST de finalisation pour transitions terminales non identifié au plan (DIV-04) : la spec mentionne le canal (
App → Audit: append {status: COMPLETED}) sans préciser le moyen technique (REST, websocket, polling). Le plan ne tranche pas. À surveiller si récurrent sur les stories avec audit fail-closed. - Verdict scoring déterministe ≠ confrontation : Gate 8 score 8.25 RESERVE, mais confrontation recommande « rework nécessaire » avec 5 blocages. Le scoring déterministe est descendant des 4 critères (conformity 6.0, test_coverage 8.5, security 9.0, maintainability 9.5) ; le confrontation expose des divergences procédurales et structurelles que le scoring ne capture pas. Pattern à surveiller — pourrait justifier un critère « confrontation_blockers » dans le scoring.
10. Améliorations du workflow
10.1 Améliorations des prompts/templates
| Fichier | Amélioration suggérée | Priorité |
templates/prompts/4-plan.md | Section « Wave 0 — dépendances amont » obligatoire : lister les libs partagées, services externes, migrations DB nécessaires AVANT la wave foundations. Bloque si une dépendance externe est citée sans création explicite. | haute |
templates/prompts/3-spec-review.md | Ajouter check « le canal de communication des transitions terminales (REST/websocket/polling) est explicitement choisi » | moyenne |
templates/prompts/8-gate-closure.md | Ajouter une vérification que TOUS les composants livrés en step 6b sont effectivement consommés par le flux principal (grep cross-référencé service↔consommateur) | haute |
templates/outputs/PD-XX-rex.md | Ajouter une colonne « action de suivi » dans la matrice §8bis (story à créer + Jira ticket + estimation) | moyenne |
templates/prompts/7-acceptability.md | Forcer brew install sonar-scanner (non skip) — la dérogation docker=unavailable doit déclencher l'installation, pas le SKIP | haute |
10.2 Améliorations des agents
| Agent | Amélioration suggérée | Justification |
agents/agent-pmo.md (Gate 8) | Critère supplémentaire confrontation_blockers (0 = GO ; 1+ = RESERVE/NON_CONFORME) intégré au scoring | Sur PD-286, scoring=8.25 RESERVE alors que confrontation = 5 blocages élevés (DIV-03, DIV-04, DIV-05, DIV-01, DIV-08) |
agents/agent-architect.md (Step 4 plan) | Output obligatoire « Wave 0 dépendances amont externes » | DIV-06, DIV-08 sur PD-286 ; précédent PD-287 sur lib PRE |
agents/agent-developer.md (Step 6c intégration) | Vérification automatique que chaque service public livré en 6b est référencé par au moins un appelant du flux principal | DIV-03 sur PD-286 (C6 orphelin) |
10.3 Améliorations du processus
- Pre-Gate 5 — Phase 4 Sécurité-quotas : étendre
/gov-check-plan pour vérifier que tout invariant « min N, pas de borne max » a une mitigation produit (rate-limit, MAX_*) explicite. Sur PD-286, totalVolumes sans borne max a permis l'attaque DIV-10. - Step 6c — checklist de câblage : produire un rapport
wiring-check.md listant chaque service livré, sa cardinalité d'appelants, et un GO/NO-GO sur l'intégration au flux principal. Bloque si un service Wave 4 a 0 appelant en production code. - Acceptability Phase 1.5 — Sonar OBLIGATOIRE : si
sonar-scanner n'est pas installé, l'orchestrateur DOIT exécuter brew install sonar-scanner (non interactif via gov_ask_po confirm puis bash) avant de continuer. La dérogation ESLint+TSC n'est plus acceptée. - Confrontation step 8 — pondération scoring : si confrontation recommande « rework nécessaire » avec ≥3 divergences classées « élevé », forcer le verdict NON_CONFORME automatique, indépendamment du score moyen.
- Step 4 plan — output Wave 0 : section formelle « Dépendances amont » avec colonnes (nom, type lib/service/migration, statut existe/à créer, story bloquante).
11. Enseignements clés
- Une lib partagée référencée dans un plan DOIT être créée en wave 0 ou tracée comme dépendance bloquante —
@probatiovault/jcs cité en H-PD286-PLAN-01 puis jamais produit a transformé un test contractuel (suite roundtrip 100 manifests cross-runtime) en hypothèse non vérifiée. Avant Gate 5, l'orchestrateur DOIT vérifier l'existence de toute lib partagée citée par grep/npm ls. - Step 6c (intégration) est le seul moment où les services Wave 4 sont câblés au flux principal — un C6 livré avec 48 tests mais non appelé par
ExportService rend ses invariants caducs en production. Toute story doit produire un rapport « wiring-check » listant les références cross-services avant Gate 8. - Audit fail-closed nécessite un canal explicite de finalisation — la spec PD-286 a anticipé l'audit START via la requête, mais le canal de l'audit FINAL
status=COMPLETED (REST/websocket/polling) n'a jamais été tranché. Résultat : transition ASSEMBLING → COMPLETED jamais déclenchée nominalement. Pattern à surveiller pour toute story avec invariant audit fail-closed. sonar-scanner doit être installé une fois pour toutes — la dérogation docker=unavailable est toujours invoquée (PD-302, PD-287, PD-286). La règle introduite après PD-248 doit être promue de « recommandation » à « gate bloquant pré-Gate 8 » dans /gov-accept. - Une borne « min N, pas de max » dans le contrat est une vulnérabilité produit —
totalVolumes sans max permet 13 volumes dédiés acceptés. Toute borne contractuelle ouverte d'un côté doit avoir une mitigation produit explicite (rate-limit, MAX_*).
12. Métriques cumulatives (auto-calculées)
Section alimentée automatiquement par l'orchestrateur à partir de governance-metrics.yaml
| Métrique | Cette story | Moyenne projet | Tendance |
| Temps total | 4.2h | 5.83h | ↓ |
| Itérations gates | 5 | 5.5 | ↓ |
| Écarts totaux | 48 | 24.5 | ↑ |
| Score convergence moyen | 8.67/10 | 8.39/10 | ↑ |