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.jsonversionné etchronology.jsonordonné. - 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(projetProbatioVault-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)¶
- Requête authentifiée avec
proofIdsnon vide. - Contrôle plan/capability => Premium requis.
- Contrôle d’appartenance (RLS) de chaque
proofId. - Validation invariants RFC par preuve.
- Calcul taille totale chiffrée <= 1 GB.
- Génération
manifest+chronology+ URLs signées + guide + README. - Journalisation audit WORM.
- 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.
5.6 Flux nominal F3 — Cas mineur/représentant légal¶
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 } 5bis.3 Séquence d’export cas mineur / représentant légal (F3)¶
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
WhenPOST /exports/complaint-fileavec cesproofIds
Then réponse 200,proofCount=5,rejectedProofs=[]. -
ST-02 Rejet payload
Given utilisateur authentifié
When requête sansproofIds
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 avecproofCount=8et 2 entrées dansrejectedProofs. -
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é (horsintegrityHash)
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é parLEGAL_GUARDIANpour mineur
When appel export valide
Then 200 avecexportedBy.role=LEGAL_GUARDIANet é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¶
- Expression de besoin brute manquante dans le bloc “Besoin” (vide) : confirmer que le document complémentaire fait foi contractuelle.
- Référence épique manquante :
corrigée→ PD-185 (B2C-MINEURS), cf. §Références. Valeur par défaut de→ RÉSOLU : 15 min, envsignedUrlTtlEXPORT_SIGNED_URL_TTL_MIN, cf. §5.2.- Contexte de mesure performance absent (device/infra, charge, percentile déjà partiellement donné en P95).
Liste normative des valeurs→ RÉSOLU : enum 7 valeurs figée, cf. §5.1.documentTypeFormat contractuel de→ RÉSOLU : UUID v4, cf. §5.1.proofEnvelopeRefPolitique audit pour erreurs→ RÉSOLU : audit ALL (200 + 4xx/422), cf. CA-12.Borne maximale métier de cardinalité→ RÉSOLU : 500, cf. §5.1.proofIdsRègle 404 détaillée→ RÉSOLU : fail-fast (première erreur interrompt), cf. §6.- 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