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:cryptosupportesha3-256(Node.js >= 18 + OpenSSL 3) — vérifié - H-06 :
guide_plainte_france.pdfservi 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¶
- E-01 (BLOQUANT) : Rendre l'audit strictement fail-closed dans le catch — si audit KO, retourner 500 sans absorber l'erreur
- E-02 (MAJEUR) : Ajouter clamp
Math.min(configuredTtl, MAX_SIGNED_URL_TTL_MIN * 60)dans ExportService - E-03 (MAJEUR) : Ajouter test E2E controller assertant 403 + body vide + audit appelé
- E-04 (MAJEUR) : Ajouter tests TC-NOM-06/07 avec S3 mock (accès avant/après expiration)
- E-05 (MAJEUR) : Uniformiser message d'erreur 404 (même texte pour found vs accessible)