Aller au contenu

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. Une borne « min N, pas de max » dans le contrat est une vulnérabilité produittotalVolumes 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