Aller au contenu

PD-85 — Export probatoire consolidé prêt au dépôt (backend)

1. Objectif

Contractualiser le comportement backend permettant de préparer un dossier probatoire exportable par l’app, sans déchiffrement serveur, via POST /exports/complaint-file, avec validation probatoire, manifest d’intégrité, chronologie consolidée, URLs signées de téléchargement, guide statique et README de vérification.

Le résultat attendu est un artefact auto-porteur et vérifiable indépendamment par un tiers (avocat, OPJ, expert), conforme aux invariants RFC PV-PROOF-001 v1.1.0 et aux contraintes d’accès (Premium, RLS, cas mineur/représentant légal).

2. Périmètre / Hors périmètre

Inclus

  • Endpoint POST /exports/complaint-file (export partiel explicite uniquement).
  • Validation pré-export des preuves demandées (INV-02, INV-03, INV-09, INV-11, INV-12).
  • Production de manifest.json versionné et chronology.json ordonné.
  • Retour des URLs signées de téléchargement des fichiers chiffrés.
  • Inclusion du guide statique guide_plainte_france.pdf.
  • Inclusion du README_VERIFICATION.txt.
  • Gestion des rejets partiels par preuve (rejectedProofs) sans bloquer les preuves valides.
  • Blocages contractuels : FREE (403), requête invalide (400), taille > limite (413), aucune preuve valide (422).
  • Cas B2C-mineurs : inclusion automatique des évidences additionnelles si applicables.

Exclu

  • Assemblage ZIP final, déchiffrement local, calcul hash final ZIP (PD-283).
  • Génération serveur d’un ZIP en clair (interdit, hors périmètre définitif).
  • Script automatisé de vérification tiers (V2).
  • Timeline PDF riche (V2).
  • Internationalisation du guide (V2).
  • Notification automatique à un tiers (V2).

3. Définitions

  • ProofEnvelope : enveloppe probatoire RFC PV-PROOF-001 v1.1.0.
  • Manifest : inventaire JSON signé logiquement via integrityHash.
  • Chronology : JSON ordonné d’événements probatoires.
  • Export partiel : export d’une liste explicite proofIds.
  • Rejet partiel : certaines preuves invalides, export maintenu pour les valides.
  • RLS : contrôle d’appartenance des preuves à l’utilisateur appelant.
  • B2C-Mineurs : contexte mineur/représentant légal (mandat, double validation, lifecycle ReKey).
  • Stack cible (obligatoire) : NestJS + TypeORM + PostgreSQL (projet ProbatioVault-backend).

4. Invariants (non négociables)

ID Règle Justification
INV-02 Chaque preuve exportée respecte FiveSectionsComplete. Intégrité structurelle RFC.
INV-03 Aucune preuve exportée n’a de ReKey actif. Cohérence et sécurité d’accès.
INV-09 Aucune preuve exportée n’expose de secret. Sécurité probatoire.
INV-11 envelopeSeal valide pour chaque preuve exportée. Authenticité cryptographique.
INV-12 Matériel de vérification offline suffisant pour chaque preuve exportée. Vérifiabilité tierce.
INV-85-01 Le backend ne déchiffre jamais les documents exportés. Zero-Knowledge.
INV-85-02 Chaque export possède un exportId unique (UUID v4). Traçabilité/non-répudiation.
INV-85-03 manifest.integrityHash = SHA3-256 du manifest canonicalisé (champ integrityHash exclu). Auto-vérification.
INV-85-04 Les URLs signées expirent en fenêtre bornée contractuelle (<= 30 min). Réduction exposition.
INV-85-05 Chaque export est journalisé en audit immuable (WORM). Conformité/auditabilité.
INV-85-06 L’export n’altère aucune ProofEnvelope existante. Immutabilité.
INV-85-07 Taille totale exportée <= MAX_BACKEND_EXPORT (1 GB). Limite technique.
INV-85-08-transitions Tout état de résultat d’export déclare explicitement ses transitions sortantes (autorisées/interdites). Élimination ambiguïtés machine à états.
INV-85-09-envelope-encryption Tout artefact cryptographique temporaire (clé, fragment, DEK, ReKey) est chiffré au repos (AES-256-GCM ou HSM envelope), aucun secret en clair en base. Invariant crypto constitutionnel.

5. Flux nominaux

5.1 Modèle de données contractuel (formats et validations)

Les formats sont définis uniquement ici et référencés par les autres sections.

Donnée Format/encodage Taille Charset / casse Validation Si invalide
proofIds[] UUID v4 texte 36 chars par item ASCII, case-insensitive regex UUID v4 400 (payload) ou 404 (non trouvé/non possédé)
exportId UUID v4 texte 36 chars ASCII, case-insensitive regex UUID v4 erreur interne contractuelle (réponse non conforme)
exportedBy.userId UUID v4 texte 36 chars ASCII, case-insensitive regex UUID v4 500 (incohérence interne)
exportedBy.role Enum 1 valeur ASCII upper snake case USER | LEGAL_GUARDIAN 500 si incohérence interne
proofCount entier non signé [1, 10_000]* n/a proofCount == proofs.length 500 si incohérence interne
proofs[].sha3_256 hex SHA3-256 64 chars [0-9a-f], case-sensitive (lowercase) regex ^[0-9a-f]{64}$ preuve rejetée (motif format hash invalide)
integrityHash hex SHA3-256 64 chars [0-9a-f], case-sensitive (lowercase) regex ^[0-9a-f]{64}$ réponse invalide, 500
exportedAt, sealedAt, expiresAt, events[].timestamp RFC 3339 UTC (Z) 20–30 chars ASCII parse date UTC stricte preuve rejetée ou 500 selon source
version SemVer max 20 chars ASCII regex semver 500 si incohérence interne
signedUrls[].url, guideUrl URL HTTPS max 2048 chars ASCII URL schéma https obligatoire entrée rejetée / 500
proofs[].documentType Enum 1 valeur ASCII upper snake case IMAGE | VIDEO | AUDIO | DOCUMENT | MESSAGE | SCREENSHOT | OTHER preuve rejetée si valeur hors enum
proofs[].proofEnvelopeRef UUID v4 texte 36 chars ASCII, case-insensitive regex UUID v4 (référence à l'enveloppe dans la table proof_envelopes) preuve rejetée si référence invalide
readmeVerification texte UTF-8 statique (asset serveur, non dynamique) max 200 KB UTF-8 non vide 500 si absent

* borne haute proofIds = 500 (garde-fou contractuel V1). Au-delà : 400 avec motif "too many proofs".

5.2 Bornes numériques et SLA temporels

Paramètre Défaut Min Max Unité Référence Hors borne
MAX_BACKEND_EXPORT 1 1 1 GB backend 413
EXPORT_RATE_LIMIT 10 1 100 exports/heure/utilisateur backend (env EXPORT_RATE_LIMIT_PER_HOUR) 429 Too Many Requests
signedUrlTtl 15 1 30 minutes backend (env EXPORT_SIGNED_URL_TTL_MIN) 400 si config invalide; 500 si runtime non conforme
Latence API (50 preuves) <=5 (P95) n/a 5 secondes contexte matériel non spécifié hors périmètre de validation perf tant que contexte absent
Latence API (100 preuves) <=10 (P95) n/a 10 secondes contexte matériel non spécifié hors périmètre de validation perf tant que contexte absent

Transitions temporelles identifiées : expiration URL signée uniquement.
Comportement à expiration : URL non utilisable, accès refusé (échec d’accès objet).

5.3 Machine d’états du résultat d’export

États : REQUEST_ACCEPTED_PARTIAL, REQUEST_ACCEPTED_FULL, REQUEST_REJECTED_ALL, REQUEST_REJECTED_POLICY, REQUEST_REJECTED_PAYLOAD, REQUEST_REJECTED_SIZE.

  • REQUEST_ACCEPTED_FULL -> * : INTERDITE (état terminal de réponse)
  • REQUEST_ACCEPTED_PARTIAL -> * : INTERDITE (état terminal de réponse)
  • REQUEST_REJECTED_ALL -> * : INTERDITE (état terminal, résolution manuelle côté client)
  • REQUEST_REJECTED_POLICY -> * : INTERDITE (état terminal)
  • REQUEST_REJECTED_PAYLOAD -> * : INTERDITE (état terminal)
  • REQUEST_REJECTED_SIZE -> * : INTERDITE (état terminal)

Aucune transition retour applicable (traitement synchrone, réponse terminale unique par requête).

5.4 Flux nominal F1 — Export Premium valide (complet)

  1. Requête authentifiée avec proofIds non vide.
  2. Contrôle plan/capability => Premium requis.
  3. Contrôle d’appartenance (RLS) de chaque proofId.
  4. Validation invariants RFC par preuve.
  5. Calcul taille totale chiffrée <= 1 GB.
  6. Génération manifest + chronology + URLs signées + guide + README.
  7. Journalisation audit WORM.
  8. Réponse 200 avec rejectedProofs=[].

5.5 Flux nominal F2 — Export partiel avec rejets

Identique F1, avec preuves invalides exclues du manifest.
Réponse 200 avec rejectedProofs renseigné et proofCount aligné sur preuves valides.

Ajout des évidences Mandate, Dual Validation, ReKey Lifecycle selon conditions d’existence; exportedBy.role obligatoire.

5.7 Atomicité multi-composant

Scope Synchrone/Async Garantie
Validation + génération réponse Synchrone Cohérence transactionnelle de la réponse
Audit WORM Synchrone (dans le flux de requête) Événement d’export tracé à succès
Aucune queue/worker spécifique PD-85 n/a Aucune garantie async additionnelle applicable

5.8 Migration DDL

Aucune modification de colonne existante identifiée dans PD-85.
Stratégie migration DDL : non applicable (pas de changement de schéma contractualisé).

5.9 Contraintes inter-modules

Élément Spécification
Modules externes impactés Capability/Plans, Documents/Proofs, B2C-Mineurs, Audit
Routes externes protégées Aucune route externe bloquée par guard PD-85 identifiée
Mécanisme cross-module Vérifications de lecture/éligibilité inter-modules avant export
Données externes requises plan/capability, ownership preuve, état mineur/mandat/dual-validation, lifecycle ReKey
FK/résolution cross-module Non spécifié dans le besoin (voir §10)
Scope guard Endpoint POST /exports/complaint-file uniquement
Exceptions rôles LEGAL_GUARDIAN autorisé selon règles B2C-mineurs

5bis. Diagrammes Mermaid

5bis.1 Machine d’états du résultat d’export (§5.3)

Tous les états sont terminaux (INV-85-08-transitions). Le résultat est déterminé en un seul passage synchrone selon les validations successives.

stateDiagram-v2
    [*] --> Validation_Payload
    state Validation_Payload <<choice>>
    Validation_Payload --> REQUEST_REJECTED_PAYLOAD : proofIds absent/vide/doublons/format invalide
    Validation_Payload --> Validation_Policy

    state Validation_Policy <<choice>>
    Validation_Policy --> REQUEST_REJECTED_POLICY : plan FREE / capability insuffisante
    Validation_Policy --> Validation_Size

    state Validation_Size <<choice>>
    Validation_Size --> REQUEST_REJECTED_SIZE : taille estimée > MAX_BACKEND_EXPORT (1 GB)\n[INV-85-07]
    Validation_Size --> Validation_Invariants

    state Validation_Invariants <<choice>>
    Validation_Invariants --> REQUEST_REJECTED_ALL : toutes preuves invalides\n(INV-02/INV-03/INV-09/INV-11/INV-12)
    Validation_Invariants --> REQUEST_ACCEPTED_PARTIAL : certaines preuves rejetées
    Validation_Invariants --> REQUEST_ACCEPTED_FULL : toutes preuves valides

    REQUEST_REJECTED_PAYLOAD --> [*]
    REQUEST_REJECTED_POLICY --> [*]
    REQUEST_REJECTED_SIZE --> [*]
    REQUEST_REJECTED_ALL --> [*]
    REQUEST_ACCEPTED_PARTIAL --> [*]
    REQUEST_ACCEPTED_FULL --> [*]

    note right of REQUEST_ACCEPTED_FULL
        État terminal — aucune transition sortante autorisée
        [INV-85-08-transitions]
    end note

5bis.2 Séquence d’export nominal (F1 — complet)

Flux multi-service couvrant les invariants cryptographiques (INV-85-01, INV-85-03, INV-85-04, INV-85-05, INV-85-06) et de sécurité (INV-09, INV-11, INV-12).

sequenceDiagram
    actor Client
    participant API as POST /exports/complaint-file
    participant Auth as Auth / RLS
    participant Cap as Capability / Plans
    participant Proof as Proof Service
    participant Crypto as Integrity (SHA3-256)
    participant Storage as Object Storage (S3)
    participant Audit as Audit WORM

    Client->>API: POST { proofIds }
    API->>Auth: Vérifier token + RLS ownership par proofId
    Auth-->>API: userId, role (USER | LEGAL_GUARDIAN)

    API->>Cap: Vérifier plan Premium
    Cap-->>API: OK / 403 FREE

    loop Pour chaque proofId
        API->>Proof: Charger ProofEnvelope (lecture seule, INV-85-06)
        Proof-->>API: envelope
        Note over API: Valider INV-02 (FiveSectionsComplete)
        Note over API: Valider INV-03 (pas de ReKey actif)
        Note over API: Valider INV-09 (aucun secret exposé)
        Note over API: Valider INV-11 (envelopeSeal valide)
        Note over API: Valider INV-12 (matériel vérification offline)
        Note over API: INV-85-01 : aucun déchiffrement
    end

    API->>API: Vérifier taille totale <= 1 GB (INV-85-07)
    API->>API: Générer exportId UUID v4 (INV-85-02)
    API->>API: Construire manifest.json + chronology.json
    API->>Crypto: SHA3-256 canonicalisé (JCS, champ integrityHash exclu)
    Crypto-->>API: integrityHash (INV-85-03)

    API->>Storage: Générer URLs signées (TTL <= 30 min, INV-85-04)
    Storage-->>API: signedUrls[] + guideUrl

    API->>Audit: Journaliser export (INV-85-05, WORM immuable)
    Audit-->>API: OK

    API-->>Client: 200 { exportId, manifest, chronology, signedUrls, guideUrl, readmeVerification }

Extension du flux nominal avec évidences additionnelles B2C-mineurs.

sequenceDiagram
    actor LG as Représentant légal
    participant API as POST /exports/complaint-file
    participant Auth as Auth / RLS
    participant B2C as Module B2C-Mineurs
    participant Proof as Proof Service
    participant Audit as Audit WORM

    LG->>API: POST { proofIds }
    API->>Auth: Vérifier token + RLS
    Auth-->>API: userId, role = LEGAL_GUARDIAN

    API->>B2C: Charger contexte mineur
    B2C-->>API: mandat, dual-validation, lifecycle ReKey

    loop Pour chaque proofId
        API->>Proof: Charger ProofEnvelope (INV-85-06, lecture seule)
        Proof-->>API: envelope + validations INV-02/03/09/11/12
    end

    Note over API: Inclure évidences additionnelles (Mandate, Dual Validation, ReKey Lifecycle)
    Note over API: exportedBy.role = LEGAL_GUARDIAN (CA-14)

    API->>API: Construire manifest enrichi + chronology
    API->>API: integrityHash SHA3-256 (INV-85-03)

    API->>Audit: Journaliser export LEGAL_GUARDIAN (INV-85-05)
    Audit-->>API: OK

    API-->>LG: 200 { manifest avec évidences B2C-mineurs, rejectedProofs }

6. Cas d’erreur

Code Condition Réponse attendue
400 proofIds absent/vide, format payload invalide, options incohérentes Erreur explicite, aucun export implicite
403 Plan FREE / capability insuffisante Message explicite de blocage Premium
400 proofIds contient des doublons Erreur explicite listant les doublons (déduplication interdite — fail-fast)
404 proofId introuvable ou non possédé (fail-fast : la première erreur 404 interrompt le traitement, les proofIds restants ne sont pas évalués) Erreur explicite corrélée au premier proofId fautif
413 Taille totale estimée > 1 GB Retour de la taille estimée + limite
422 Toutes les preuves rejetées par validations invariants Détail des rejets par preuve/motif
200 Succès total Manifest + chronology + URLs + guide + README, rejectedProofs=[]
200 Succès partiel Même payload, rejectedProofs non vide

7. Critères d’acceptation (testables)

ID Critère Observable
CA-01 Requête avec proofIds valide et Premium retourne 200 exportId, manifest, signedUrls présents
CA-02 proofIds absent ou vide retourne 400 code 400 + message validation
CA-03 Utilisateur FREE retourne 403 code 403 + motif plan
CA-04 Preuve non possédée retourne 404 code 404 corrélé proofId
CA-05 Dépassement 1 GB retourne 413 code 413 + taille estimée + limite 1 GB
CA-06 Toutes preuves invalides retourne 422 code 422 + rejectedProofs exhaustif
CA-07 Rejets partiels conservent les preuves valides 200 + proofCount = valides + rejectedProofs non vide
CA-08 integrityHash est vérifiable SHA3-256 (hors champ) recalcul externe égal à la valeur retournée
CA-09 chronology.events est triée chronologiquement ordre non décroissant des timestamps
CA-10 URLs signées expirent dans la fenêtre contractuelle accès valide avant expiration, refus après expiration
CA-11 Backend ne déchiffre aucun document absence d’appel/trace de déchiffrement dans audits techniques
CA-12 Export journalisé en WORM pour tout appel (succès ET erreurs 4xx/422) entrée audit immuable présente pour chaque réponse HTTP (200, 400, 403, 404, 413, 422)
CA-13 Cas mineur inclut évidences additionnelles applicables présence conditionnelle des éléments mandat/dual/ReKey
CA-14 exportedBy inclut identité + rôle champs présents et valides dans manifest

8. Scénarios de test (Given / When / Then)

  • ST-01 Nominal complet
    Given utilisateur Premium avec 5 preuves valides
    When POST /exports/complaint-file avec ces proofIds
    Then réponse 200, proofCount=5, rejectedProofs=[].

  • ST-02 Rejet payload
    Given utilisateur authentifié
    When requête sans proofIds
    Then réponse 400.

  • ST-03 Blocage FREE
    Given utilisateur FREE
    When appel endpoint export
    Then réponse 403.

  • ST-04 Succès partiel
    Given 10 preuves dont 2 invalides (INV-02/INV-03)
    When appel export
    Then 200 avec proofCount=8 et 2 entrées dans rejectedProofs.

  • ST-05 Rejet total
    Given 3 preuves toutes invalides
    When appel export
    Then 422 avec motifs par preuve.

  • ST-06 Limite taille
    Given sélection dépassant 1 GB
    When appel export
    Then 413 avec taille estimée et limite.

  • ST-07 Vérification hash manifest
    Given réponse 200
    When recalcul SHA3-256 canonicalisé (hors integrityHash)
    Then valeur égale à manifest.integrityHash.

  • ST-08 Expiration URL signée
    Given URL signée retournée
    When accès avant puis après expiration
    Then succès avant, refus après.

  • ST-09 Cas mineur via représentant légal
    Given export initié par LEGAL_GUARDIAN pour mineur
    When appel export valide
    Then 200 avec exportedBy.role=LEGAL_GUARDIAN et évidences additionnelles applicables.

9. Hypothèses explicites

ID Hypothèse Impact si faux
H-01 La canonicalisation pour integrityHash suit RFC 8785 (JCS). Hash non reproductible inter-systèmes, non-conformité probatoire.
H-02 Les hash sha3_256 sont normalisés en hex lowercase 64 chars. Risque d’ambiguïté de validation/casse.
H-03 La fenêtre TTL URL signée est configurable et bornée à 30 min max. Impossible de tester CA-10 de manière stable.
H-04 L’audit WORM couvre au minimum les succès d’export. Traçabilité potentiellement incomplète pour les échecs.
H-05 Les données B2C-mineurs requises sont accessibles au backend export. Impossible d’inclure les évidences additionnelles attendues.
H-06 Les performances P95 sont mesurées dans un environnement de référence à définir. Critères non-fonctionnels non vérifiables (hors périmètre de test).

10. Points à clarifier

  1. Expression de besoin brute manquante dans le bloc “Besoin” (vide) : confirmer que le document complémentaire fait foi contractuelle.
  2. Référence épique manquante : corrigée → PD-185 (B2C-MINEURS), cf. §Références.
  3. Valeur par défaut de signedUrlTtlRÉSOLU : 15 min, env EXPORT_SIGNED_URL_TTL_MIN, cf. §5.2.
  4. Contexte de mesure performance absent (device/infra, charge, percentile déjà partiellement donné en P95).
  5. Liste normative des valeurs documentTypeRÉSOLU : enum 7 valeurs figée, cf. §5.1.
  6. Format contractuel de proofEnvelopeRefRÉSOLU : UUID v4, cf. §5.1.
  7. Politique audit pour erreursRÉSOLU : audit ALL (200 + 4xx/422), cf. CA-12.
  8. Borne maximale métier de cardinalité proofIdsRÉSOLU : 500, cf. §5.1.
  9. Règle 404 détailléeRÉSOLU : fail-fast (première erreur interrompt), cf. §6.
  10. Résolution technique cross-module (FK/liaisons) pour données mineur/capability : délégué au plan d’implémentation (étape 4).

Références

  • Epic : PD-185 (B2C-MINEURS)
  • JIRA : PD-85
  • Repos concernés : ProbatioVault-backend (principal), ProbatioVault-app (consommateur PD-283)
  • Documents associés : RFC PV-PROOF-001 v1.1.0, RFC 3161, RFC 8785 (JCS), PD-84, PD-282, PD-283, Art. 1366 Code civil, Art. 226-1 et suivants Code pénal