Aller au contenu

PD-85 — Acceptabilité

1. Références

  • Spécification : PD-85-specification.md
  • Tests contractuels : PD-85-tests.md
  • Plan d'implémentation : PD-85-plan.md
  • Commit / version évaluée : 1cc32b5 (feature/PD-85-export-dossier-plainte)
  • Date de la revue : 2026-03-08

2. Synthèse exécutive

L'implémentation PD-85 fournit un module d'export probatoire complet avec 22 fichiers source (~900 LOC), 68 tests (7 suites, 100% pass), et une architecture NestJS propre. Les invariants fondamentaux (Zero-Knowledge INV-85-01, UUID unique INV-85-02, SHA3-256 JCS INV-85-03, immutabilité INV-85-06, taille bornée INV-85-07) sont correctement implémentés.

Trois revues LLM (code, tests, sécurité) et les outils automatisés identifient 1 écart BLOQUANT (audit fail-closed non respecté dans le catch), 4 écarts MAJEURS (TTL non borné, preuve guards incomplète, tests S3/URL absents, énumération de ressources), et 6 écarts MINEURS.

Le scan SonarQube n'a pas pu être exécuté (serveur unreachable).

3. Résultats des tests contractuels

Reviews LLM (ChatGPT)

Review Verdict Réserves
Code ⚠️ RÉSERVES Audit fail-closed catch, TTL non borné, preuve guards
Tests ⚠️ RÉSERVES TC manquants (S3 URL, TTL), controller 0% coverage
Sécurité ⚠️ RÉSERVES Audit catch critique, énumération IDs, secret validator fragile

Reviews automatisées

Outil Résultat Détail
ESLint 0 erreurs (4 warnings pre-existants hors module)
Prettier Tous les fichiers conformes
TypeScript Compilation sans erreur
Tests 68/68 pass, 7 suites
Coverage ⚠️ Stmts 82.72%, Branch 74.15%, Funcs 67.64%, Lines 84.33% (seuil 85%)
SonarQube ❌ NON EXÉCUTÉ Serveur sonarqube.dev.probatiovault.com injoignable

Résultats tests par TC-ID

Test ID Statut Preuve d'exécution Commentaire
TC-NOM-01 PASS export.integration.spec.ts Nominal complet
TC-NOM-02 PASS export-service.spec.ts Rejet partiel
TC-NOM-03 PASS export-minor-evidence.spec.ts Évidences mineur
TC-NOM-04 PASS export-manifest-builder.spec.ts Chrono triée + exportedBy
TC-NOM-05 PASS export-manifest-builder.spec.ts Hash SHA3-256 JCS
TC-NOM-06 ABSENT URL signée avant expiration (intégration S3 absente)
TC-NOM-07 ABSENT URL signée après expiration (intégration S3 absente)
TC-ERR-01 PASS export-dto.spec.ts proofIds absent/vide
TC-ERR-02 PASS export-pipeline-guard.spec.ts FREE → 403
TC-ERR-03 PASS export.integration.spec.ts Proof not found → 404
TC-ERR-04 PASS export-service.spec.ts Taille > 1 GB → 413
TC-ERR-05 PASS export-service.spec.ts Toutes invalides → 422
TC-ERR-06 PASS export-dto.spec.ts UUID format invalide
TC-INV-02 PASS export-validators.spec.ts FiveSectionsComplete
TC-INV-03 PASS export-pipeline-guard.spec.ts ReKey actif
TC-INV-09 PASS export-validators.spec.ts Secret exposure
TC-INV-11 PASS export-validators.spec.ts EnvelopeSeal structural
TC-INV-12 PASS export-validators.spec.ts Offline material
TC-INV-8501 PASS export-validators.spec.ts Static scan zero-knowledge
TC-INV-8502 PASS export-service.spec.ts exportId UUID unique
TC-INV-8503 PASS export-manifest-builder.spec.ts SHA3-256 JCS recalculé
TC-INV-8504 ABSENT TTL borné [1,30] min non testé
TC-INV-8505 PASS export.integration.spec.ts Audit WORM présent
TC-INV-8506 PASS export.integration.spec.ts Lecture seule ProofEnvelope
TC-INV-8507 PASS export-service.spec.ts Taille > 1 GB
TC-INV-8508 PASS export-exceptions.spec.ts 6 états terminaux
TC-INV-8509 PASS (indirect) export-validators.spec.ts Via scan TC-INV-8501
TC-NR-01 PASS (partiel) export-service.spec.ts Stabilité schéma JSON
TC-NR-02 ABSENT Stabilité codes erreur
TC-NR-03 PASS export-manifest-builder.spec.ts proofCount == proofs.length
TC-NR-04 PASS export-manifest-builder.spec.ts Hash déterministe
TC-NR-05 ABSENT TTL URL non-régression
TC-NR-06 PASS export.integration.spec.ts Audit WORM
TC-NEG-01 PASS export-dto.spec.ts Doublons proofIds
TC-NEG-02 PASS export-dto.spec.ts Mix valides + invalides
TC-NEG-03 ABSENT URL non-HTTPS
TC-NEG-04 PASS export-manifest-builder.spec.ts Horodatage non UTC
TC-NEG-05 ABSENT URL expirée répétée
TC-NEG-06 PASS (indirect) export-validators.spec.ts Via scan INV-8501

Bilan : 30 PASS, 7 ABSENT, 3 PASS (indirect/partiel) sur 40 TC-*

4. Écarts identifiés

Classification des écarts

Niveau Définition
BLOQUANT Violation d'invariant, faille de sécurité, non-conformité majeure
MAJEUR Fonction incomplète ou non conforme sans rupture de sécurité
MINEUR Détail ou dette non critique

Détail des écarts

ID Description Référence Gravité Statut
E-01 Audit non fail-closed dans le catch : .catch(...) absorbe l'échec d'audit puis renvoie ExportException. Opération possible sans trace WORM INV-85-05, CC-85-04 forbidden BLOQUANT OUVERT
E-02 TTL URL signée non borné en code service : signedUrlTtlMin utilisé sans Math.min(config, 1800). Mauvaise config = URLs trop longues INV-85-04, CC-85-07 MAJEUR OUVERT
E-03 Preuve contractuelle guards incomplète : aucun test E2E pour POST /exports/complaint-file assertant 403 + body vide + audit appelé CC-85-06, axe obligatoire 7a MAJEUR OUVERT
E-04 TC-NOM-06/07 absents : tests intégration S3 mock pour validation accès URL avant/après expiration CA-10, INV-85-04 MAJEUR OUVERT
E-05 Énumération de ressources : messages d'erreur distincts ("Proof not found" vs "Proof not accessible") permettent de profiler l'existence d'IDs Sécurité, CC-85-04 MAJEUR OUVERT
E-06 Coverage sous seuil : Stmts 82.72%, Branch 74.15%, Funcs 67.64% (seuil 85%) CC-85-11 MINEUR OUVERT
E-07 Controller à 0% coverage complaint-file.controller.ts MINEUR OUVERT
E-08 TC-INV-8504 absent : TTL borné [1,30] min non testé INV-85-04 MINEUR OUVERT
E-09 Secret exposure validator fragile (substring match, contournable par encodage/obfuscation) INV-09 MINEUR (defense-in-depth, S3 presign = barrière primaire) OUVERT
E-10 DoS potentiel : checkOwnership N+1 queries (2 DB accès × 500 proofIds) Sécurité MINEUR OUVERT
E-11 SonarQube non exécuté (serveur injoignable) Phase 1.5 procédure MINEUR OUVERT

Stubs V1 documentés (NE PAS traiter comme écarts)

Stub Justification Story destination
extractProofHash() retourne '0'.repeat(64) en fallback HT-03 : sha3_256 pas toujours dans anchoringEvidenceRef Résolution en implémentation si données disponibles
resolveDocumentType() retourne toujours OTHER V1 : enrichissement différé depuis DocumentSecure metadata V2 enrichissement type document
Role hardcodé à USER dans controller V1 : détection LEGAL_GUARDIAN différée V2 rôle dynamique
estimateTotalSize utilise 1 MB par document HT-03 : encrypted_size non disponible en base Résolution quand champ ajouté

5. Hypothèses et TODO recensés

Hypothèses complémentaires

  • H-01 : JCS (json-canonicalize) conforme RFC 8785 — réutilisation de la lib existante (validée TSA)
  • H-02 : node:crypto supporte sha3-256 (Node.js >= 18 + OpenSSL 3) — vérifié
  • H-06 : guide_plainte_france.pdf servi en statique depuis le serveur (pas S3)

TODO résiduels (non bloquants)

  • Enrichir extractProofHash() quand données sha3_256 systématiquement disponibles
  • Enrichir resolveDocumentType() depuis DocumentSecure metadata
  • Détection dynamique du rôle LEGAL_GUARDIAN dans le controller
  • Optimiser checkOwnership avec requête set-based (IN clause)
  • Ajouter validation Joi pour EXPORT_SIGNED_URL_TTL_MIN dans config.schema.ts

6. Verdict d'acceptabilité (unique)

Verdict actuel : ⚠️ ACCEPTÉ AVEC RÉSERVES

Date : 2026-03-08

Motif synthétique : L'implémentation couvre les invariants fondamentaux (Zero-Knowledge, UUID, SHA3-256 JCS, immutabilité, taille bornée) et passe 68/68 tests. Cependant, 1 écart BLOQUANT (audit fail-closed dans catch) et 4 écarts MAJEURS (TTL non borné, preuve guards, tests S3 URL, énumération) doivent être corrigés avant soumission à Gate 8.

Conditions de levée des réserves

  1. E-01 (BLOQUANT) : Rendre l'audit strictement fail-closed dans le catch — si audit KO, retourner 500 sans absorber l'erreur
  2. E-02 (MAJEUR) : Ajouter clamp Math.min(configuredTtl, MAX_SIGNED_URL_TTL_MIN * 60) dans ExportService
  3. E-03 (MAJEUR) : Ajouter test E2E controller assertant 403 + body vide + audit appelé
  4. E-04 (MAJEUR) : Ajouter tests TC-NOM-06/07 avec S3 mock (accès avant/après expiration)
  5. E-05 (MAJEUR) : Uniformiser message d'erreur 404 (même texte pour found vs accessible)