PD-286 — Rapport de confrontation (Étape 8 — Gate CLOSURE)¶
Confrontation pré-Gate 8 sur l'acceptabilité finale de l'export probatoire multi-volumes. Sources : spécification, tests, plan, acceptabilité + 10 livrables agents (C1..C10).
1. Sources confrontées¶
| # | Document | Étape | Rôle |
|---|---|---|---|
| S | PD-286-specification.md | 1 | Contrat fonctionnel + invariants INV-286-01..11 |
| T | PD-286-tests.md | 2 | 26 scénarios + matrice couverture |
| P | PD-286-plan.md | 4 | Plan d'implémentation + mapping invariants→mécanismes |
| A | PD-286-acceptability.md | 7 | Verdict acceptabilité (auto + LLM) |
| C1 | livrable export-partitioner | 6b | 39 tests, 100 % branch coverage |
| C2 | livrable export-multi-dto | 6b | DTO + validators class-validator |
| C3 | livrable export-manifest-multi | 6b | 41 tests, manifest partiel + root |
| C4 | livrable export-state-machine-backend | 6b | Entity + state machine + worker EXPIRED |
| C5 | livrable export-controller-multi | 6b | Controller + service + routing policy |
| C6 | livrable export-audit-multi | 6b | Façade audit, 48 tests |
| C7 | livrable app-export-orchestrator-multi | 6b | Orchestrator app, 11 tests PD-286 |
| C8 | livrable app-volume-verifier | 6b | 14 tests, recompute SHA3-256(JCS) |
| C9 | livrable app-pvproof-assembler-multi | 6b | 36 tests, .pvproof unique |
| C10 | livrable app-export-api-client | 6b | Zod, 51 tests |
2. Convergences¶
2.1 Bornes contractuelles partagées¶
VOLUME_MAX_BYTES = 805_306_368(768 MiB) etMAX_TOTAL_EXPORT_BYTES = 10_737_418_240(10 GiB) cohérents partout (S §4 INV-286-01/02 ; P §1.1 ; C1 §2.2 ; C3 §2.2 ; C5 §2 ; C10 §3.2).- TTL bornes
[1h, 72h]défaut 24h pourSIGNED_URL_TTLetEXPORT_SESSION_TTL(S §5.2 ; P §9.1 ; C4 §6 worker).
2.2 Algorithme cryptographique¶
- Hash partiel :
integrityHash = SHA3-256(JCS_RFC8785(manifest \ {integrityHash}))cohérent côté backend C3 et côté app C8 (vector regression test C3 + recomputation C8). Réutilisation deIntegrityHashComputerPD-85 confirmée par C3. - Hash racine :
manifestRootHash = SHA3-256(JCS({exportId, totalVolumes, volumes:[{volumeIndex, integrityHash, estimatedBytes}]}))(P §3 ; C3 §2.2 ; C10 schemas valide la regex). - Lib JCS unique côté backend :
JsonCanonicalizeService(PD-37) — aucun import npmcanonicalizeinterdit (vérifié C3 §4 forbiddens, C8 §3 forbiddens).
2.3 Matrice d'états et transitions¶
- Spec §4 (transitions autorisées + interdictions terminales) reprise byte-identical par C4 backend (
ALLOWED_TRANSITIONS) et C7 app (ExportStateMulti). Trigger PGprevent_forbidden_export_transitionajoute defense-in-depth côté DB (C4 migration). - Terminaux
COMPLETED/FAILED/EXPIREDsans transition sortante (INV-286-11) — appliqué parisTerminal()C4 +prepareRetry()C7 + test "EXPIRED is terminal".
2.4 Rétrocompatibilité PD-85 (INV-286-05)¶
- Branche legacy single-volume conservée (P §2.1 ; C5
executeLegacySingle; C9 shapeLegacybyte-identical ; C10 routerparseExportApiResponseacceptesingle-volume). - Sérialisation conditionnelle via champs
?:TypeScript (omissionundefinedplutôt quenull) — décision D1 C2. - Tests PD-85 préservés : 158 tests non-régression backend (A), 19/19 schemas + 14/14 api-client legacy (C10), 4/4 pvproof-assembler PD-85 (C9), 76 tests state-machine PD-283 (C7).
2.5 Invariants INV-286-06 (continuité volumeIndex)¶
- Validation triplée : C1 partitioner (assertion runtime), C2 DTO (
IsValidVolumesShape), C3 manifest root (validateVolumes), C10 Zod (superRefine), C7 tri défensif (sort ASC).
2.6 Anti-patterns appliqués¶
crypto.randomUUID()exclusivement (C5) — learning 2026-02-21.- Anti-catch-absorb (C5 try/throw + dataSource.transaction ; C6 fail-closed sans
.catch()) — learning 2026-03-08. - Branded types
ExportId,ExportSessionUserId,VolumeIndex,TotalVolumes(C4, C10) — learning 2026-03-04. - Anti-énumération codes erreur uniformes (C6
ExportAuditRejectionReasonenum fermée + UPPER_SNAKE) — learning 2026-03-08.
3. Divergences¶
⚠️ Conflits rendus visibles, sans lissage.
DIV-01 — Sonar QG SKIP non conforme à la procédure¶
- Source A (acceptability §Phase 1.5) : « Quality Gate : SKIP (Docker indisponible, sonar-scanner non installé — capability probe: docker=unavailable). Mitigation : ESLint + TSC couvrent les règles Sonar critiques. »
- Source B (procedures.md, étape 7) : « Phase 1.5 (Sonar local) est BLOQUANTE. Si Sonar QG ERROR -> STOP avant reviews LLM. La derogation ESLint+tsc comme substitution n'est PLUS acceptee. Si sonar-scanner n'est pas installe, l'orchestrateur DOIT l'installer avant de continuer. »
- Impact : violation de la procédure Article V boucle d'acceptabilité ; le verdict A « ACCEPTABLE avec réserves » est techniquement non valide tant que Sonar n'a pas été exécuté localement (
brew install sonar-scannern'est pas mentionné).
DIV-02 — TypeScript : « 0 error PD-286 » contredit par C2/C3/C5¶
- Source A (acceptability §Phase 1) : « TypeScript | OK | 0 error PD-286 (1 error pré-existante
merkle/v2hors scope). » - Source B (C2 §11 + C3 §7 + C5 §5) : trois livrables convergent pour identifier deux erreurs résiduelles :
complaint-file-response.dto.ts:134(attribuée explicitement à C2 PD-286) etmerkle-proof-v2.controller.ts:16. C2 dit « unique erreur résiduelle hors périmètre C2 », mais C3 et C5 la situent danscomplaint-file-response.dto.tsqui appartient à C2. - Impact : si l'erreur TS dans le DTO C2 est réelle, l'acceptability sous-déclare l'état. À investiguer avant Gate 8 — soit l'erreur a été corrigée depuis (à reconfirmer par
npx tsc --noEmit | grep complaint-file-response), soit l'acceptability est inexacte.
DIV-03 — Audit : C5 n'utilise pas ExportAuditService (C6 livré)¶
- Source A (C5 §4 décision D2) : « C6 (export-audit-multi) est en Wave 4 et n'est pas encore livré [...] j'utilise directement
auditLogService.log()(PD-37) [...]. C6 pourra ultérieurement consolider enExportAuditServicesans changer la sémantique. » - Source B (C6 §1) : « Statut : implémenté + testé (48 tests). »
- Source C (C6 §8.2) : « L'implémentation actuelle de
ExportService[...] appelle directementauditLogService.log()avec unmetadataad hoc. Cette pratique est désormais à proscrire [...]. Diff attendu en step 6c. » - Impact : C6 livré mais non câblé ; les invariants protégés par C6 (validation
signedUrl/manifestinterdits dans metadata, énumération fermée desreasonCode, regex hash lowercase) ne sont pas appliqués sur le chemin de production. Le metadata de C5 contient potentiellement des champs non normalisés. Migration step 6c non réalisée.
DIV-04 — Endpoint REST de finalisation COMPLETED manquant¶
- Source A (spec §5.4 step 5 + diagramme séquence) : « App → Audit: append {exportId, status: COMPLETED} ».
- Source B (plan §2.2) : « ExportAudit (côté serveur via callback) : appendEnd(exportId, status=COMPLETED) ».
- Source C (C6 §8.5) : « Aujourd'hui aucun endpoint REST n'expose
appendFinalcôté backend [...] story enfant à arbitrer avec le PMO. » - Source D (C7 livrable) : aucune mention d'appel REST
POST /exports/:id/finalize. L'orchestrator app passe àREADYlocalement sans signaler le COMPLETED au backend. - Impact : transition
ASSEMBLING → COMPLETEDcôté backend jamais déclenchée en production. Seul EXPIRED est géré (parExpiredExportWorker). L'audit WORM finalstatus=COMPLETED(CA-286-07, INV-286-09) n'est pas émis. CA-286-07 implémentable en théorie, non implémenté en pratique sur le chemin nominal.
DIV-05 — integrityHashes non persistés côté ExportSession¶
- Source A (C6 §8.4 open question) : « Aujourd'hui les
integrityHashesne sont pas persistés surExportSession— ils sont seulement émis dans le payload audit START. Pour permettre au worker EXPIRED de réémettreintegrityHashesau moment du FINAL, il faut [...] ajouter une colonneintegrity_hashes JSONB. » - Source B (C4 entity) : pas de colonne
integrity_hashesdansExportSession. - Source C (C6 §8.3) :
appendFinalrequiertintegrityHashes(cardinalité = totalVolumes), sinon le builder lèvecardinality must equal totalVolumes. - Impact :
ExpiredExportWorker(C4) ne peut pas émettre unappendFinalvalide pour les sessions EXPIRED multi-volumes. Soit l'audit FINAL est skippé (violation INV-286-09), soit le worker throw et la transition échoue. Non couvert par les tests d'intégration ; détectable uniquement en runtime sur multi-volume EXPIRED.
DIV-06 — Lib JCS partagée @probatiovault/jcs inexistante¶
- Source A (plan H-PD286-PLAN-01) : « Suite CI roundtrip dédiée : génère 100 manifests aléatoires (incl. accents, emojis, floats edge) côté backend, calcule hash, transmet à un test app jest qui recalcule et compare. Bloque le merge si divergence. Réutilise lib partagée (publication interne npm
@probatiovault/jcs). » - Source B (C8 §6.3 dette) : «
@probatiovault/jcsn'existe pas comme package npm interne. Le canonicalizer PD-54 fait office de lib interne mais est typéEvent. » - Source C (C3 §6 + C8 §6.1) : vector regression interne à backend uniquement ; aucune suite roundtrip backend↔app. C8 reporte la responsabilité « à la suite intégration plus large ».
- Impact : la mitigation contractuelle de l'hypothèse cross-runtime H-PD286-PLAN-01 (RFC 8785 byte-identical Node.js ↔ React Native + Hermes) n'est pas en place. Risque INV-286-07 : faux négatifs d'intégrité sur manifests Unicode/floats edge. Non détectable par les tests unitaires actuels.
DIV-07 — TC-NR-02 (consommateur PD-85) non couvert E2E¶
- Source A (plan §5) : « TC-NR-02 (régression conteneur PD-85) | OK | Vérifier consommateur PD-85 valide encore ». Test marqué Integration.
- Source B (C9 §4.2) : « TC-NR-02 (régression conteneur PD-85) | OK partiel | [...] vérification consommateur PD-85 e2e relève de C11 (suite tests d'intégration) ».
- Source C (acceptability) : aucune mention TC-NR-02 dans la liste des tests passants.
- Impact : H-PD286-PLAN-04 (« le conteneur
.pvprooftolère l'ajout de champsvolumes_countetassembled_from[]») non vérifiée par un test passant en CI. Risque casse silencieuse des consommateurs externes du.pvproofPD-85.
DIV-08 — Service de bundling S3 hors-périmètre, jamais référencé¶
- Source A (C5 §4 décision D3 + H-C5-03) : «
ExportVolumeDto.signedUrlcontient une seule URL — implique un objet bundle par volume côté storage. La création effective du bundle [...] est hors-périmètre du module export (probable story future). C5 sait juste signer la clé prévue : convention déterministeexports/{exportId}/v{volumeIndex}.bundle. » - Source B (spec, plan) : aucune mention d'un service de bundling, ni d'une dépendance externe pour produire les blobs S3 par volume.
- Source C (acceptability) : pas de mention.
- Impact : en production, les
signedUrlretournées par C5 pointent vers des objets S3 inexistants tant qu'aucun service ne produit le bundle. Toute tentative de download volume → 404 →state FAILEDcôté app. Story dépendante non identifiée, non chiffrée, non planifiée.
DIV-09 — Bornes TTL : clamp vs fail-closed¶
- Source A (spec §5.2) : « Si non-respect bornes config: démarrage service refusé (fail-closed). »
- Source B (C5 H-C5-05) : «
EXPORT_SESSION_TTL_HOURSenv var, si fournie hors bornes [1, 72] [...] est clampée silencieusement par C5. La spec §5.2 demande "démarrage service refusé (fail-closed)". Différence assumée : C5 clamp, C4 worker clamp aussi. À trancher en revue. » - Impact : violation contractuelle volontaire reportée à la revue. Une valeur
EXPORT_SESSION_TTL_HOURS=100(hors borne 72) ne fait pas crasher le service ; elle est silencieusement ramenée à 72h. Conséquence opérationnelle : misconfiguration non détectée.
DIV-10 — Volume count abuse non mitigé contractuellement¶
- Source A (acceptability §Review Sécurité 7c MAJEUR) : « L'endpoint
POST /exports/complaint-filene limite pas le nombre de preuves atomiques > VOLUME_MAX_BYTES — un attaquant pourrait demander 13 volumes dédiés de 768 MB chacun. Mitigation : MAX_TOTAL_EXPORT_BYTES (10 GB) + MAX_PROOF_IDS (500) + rate limiting existant. » - Source B (spec §4 invariants) : pas de borne contractuelle sur
totalVolumes(cf. §5.1 « min1, pas de borne max fixe contractuelle »). - Source C (C2 §6 V2) : « Pas de borne max sur
totalVolumes(cf. plan §9 vigilance). C2 ne pose pas de borne arbitraire pour rester aligné avec le contrat. » - Impact : la mitigation citée par l'acceptability (MAX_TOTAL_EXPORT_BYTES) borne le total bytes mais pas le nombre de volumes — un attaquant créant 14 preuves de 766 MB obtient
totalSize ≈ 10.5 GB > MAX_TOTAL→ rejet 413 OK ; mais 13 preuves de 768 MB exact →totalSize = 9.984 GB ≤ MAX_TOTAL→ accepté → 13 volumes dédiés émis. Mitigation incomplète.
DIV-11 — Couverture totale tests backend annoncée vs détaillée¶
- Source A (acceptability §Prérequis) : « Tests CI : 380/380 PASS (26 suites,
npx jest --testPathPattern=export) [...] 222 tests PD-286 + 158 tests PD-85 non-régression. » - Source B (somme livrables backend) : C1 (39) + C3 (41) + C6 (48) = 128 tests PD-286 nouveaux explicitement déclarés. C4, C5 ne donnent pas de chiffre net. Écart de ~94 tests à attribuer entre C2/C4/C5 et tests d'intégration.
- Source C (livrables app) : 289 tests app (C7 §4 « 289 / 289 (13 suites) ») non comptabilisés dans les 380/380 backend.
- Impact : décompte difficile à reconstituer ; pas de divergence factuelle bloquante mais traçabilité dégradée. À reconfirmer par exécution
npx jest --testPathPattern=export --listTests.
4. Zones d'ombre¶
ZO-01 — Tests E2E réels en staging¶
L'acceptability cite « C11 en staging » comme couverture des tests d'intégration E2E (TC-NOM-01 500MB, TC-NOM-02 2GB, TC-ERR-04 corruption, TC-ERR-05 TTL). Aucun rapport d'exécution de ces tests E2E n'est fourni dans les documents. Statut « ACCEPTABLE avec réserves » s'appuie sur les tests unitaires/intégration locaux uniquement.
ZO-02 — Performance / SLA temporels en conditions réelles¶
Spec §5.2 : « Contexte perf de référence: réseau mobile 4G standard, appareil classe iPhone 12 équivalent. » Aucun benchmark mesuré sur un export 2 GB en 4G n'est rapporté. H-PD286-PLAN-06 (« téléchargement séquentiel acceptable côté UX ») non validé.
ZO-03 — Q-286-02 (taxonomie codes erreur) non résolue¶
Spec §10.2 Q-286-02 : « Validation de la nomenclature (EXPORT_TOTAL_LIMIT_EXCEEDED, PROOF_TOO_LARGE, etc.). » C5 §2 utilise effectivement ces codes mais aucun arbitrage produit n'est documenté. Risque divergence avec consommateurs aval (front, télémétrie).
ZO-04 — Q-286-03 (rétention post-FAILED/EXPIRED) non chiffrée¶
Spec §10.2 Q-286-03 + plan §9.1 : « politique post-FAILED/EXPIRED non chiffrée par produit. Plan retient la politique PD-85 existante par défaut. » Aucune confirmation produit/RGPD dans l'acceptability.
ZO-05 — Q-286-04 (TTL prod) non confirmées¶
Spec §10.2 Q-286-04 : « Confirmation des bornes contractuelles proposées (24h défaut, 1h min, 72h max). » Plan retient les défauts spec mais aucune validation prod déployement.
ZO-06 — Stratégie de signed URL revocation absente¶
Acceptability §Review Sécurité 7c MAJEUR : « Les signed URLs ont un TTL configurable mais pas de revocation mechanism — risque si session compromise pendant le téléchargement multi-volumes. Mitigation : TTL borné [1, 30] min (INV-85-04), S3 bucket policy. » Conflit borne : C4 TTL est [1h, 72h] (cf. spec §5.2 et constants C4 §6), pas [1, 30] min. La mitigation citée s'appuie sur INV-85-04 PD-85, pas sur la borne PD-286 — divergence de référence à clarifier.
ZO-07 — Cohérence migration 1745000000000-PD286-ExportMultiVolumes.ts¶
C4 §2 livre la migration mais aucune mention dans l'acceptability d'une exécution npm run typeorm migration:run ou d'un test sur DB cible. La table export_sessions, le type ENUM et les triggers n'ont peut-être jamais été appliqués en environnement de test.
ZO-08 — Drift potentiel matrice C4 backend vs C7 app¶
Plan §2ter et C4 §9 imposent une matrice identique entre backend et app pour les transitions communes. Aucun test croisé n'est rapporté pour vérifier que ExportStateMachine.listAllowedTransitions() (C4) === ExportStateMulti.ALLOWED_TRANSITIONS (C7). Drift latent.
5. Recommandation¶
- Procéder — convergence confirmée, aucun conflit bloquant
- Rework nécessaire — divergences à résoudre avant de continuer
- Escalade — décision humaine requise sur un point structurant
Justification du rework (5 blocages identifiés, par ordre de criticité)¶
- DIV-04 (CA-286-07 non couvert nominal) — l'audit final WORM
status=COMPLETEDn'est jamais émis sur le chemin nominal multi-volume. Violation directe de l'invariant INV-286-09 (Art. III tracabilité). Bloque l'acceptation Gate 8 sans correctif (endpointPOST /exports/:id/finalizeou équivalent). - DIV-03 (C6 livré mais non câblé) — l'audit C5 contourne
ExportAuditService. Les forbiddens du contract C6 (signedUrl/manifest dans metadata, codes uniformes) ne sont pas appliqués en production. Migration step 6c indispensable. - DIV-05 (
integrityHashesnon persistés) —ExpiredExportWorkerne peut pas émettre unappendFinalvalide pour les sessions EXPIRED multi-volume. Migration DB à compléter. - DIV-01 (Sonar SKIP) — non-conformité Article V (acceptabilité).
brew install sonar-scannerpuis exécution effective avant Gate 8. - DIV-08 (service de bundling S3 absent) — sans bundling, les
signedUrlpointent vers du néant en prod ; toute story de production de bundle doit être identifiée et chiffrée avant que PD-286 puisse être considéré comme déployable.
Points secondaires à clarifier avant Gate 8¶
- DIV-02 : reconfirmer l'état TypeScript sur
complaint-file-response.dto.ts:134. - DIV-06 : créer le package
@probatiovault/jcsou documenter explicitement la dette + suite roundtrip CI. - DIV-07 : exécuter TC-NR-02 contre un consommateur PD-85 réel.
- DIV-09 : trancher clamp vs fail-closed sur les bornes TTL.
- DIV-10 : ajouter une borne max sur
totalVolumesou un MAX_DEDICATED_VOLUMES côté C5. - ZO-01, ZO-02, ZO-07 : exécuter et rapporter les tests E2E + benchmark perf + migration DB sur environnement de test.