PD-72 — Spécification canonique contractuelle du worker PRE de transfert coffre entreprise vers salarié¶
1. Objectif¶
La User Story PD-72 doit définir un workflow asynchrone contractuel, testable et traçable permettant le transfert probatoire d’un document RH scellé depuis le coffre entreprise vers le coffre personnel du salarié, via PRE (Proxy Re-Encryption), sans exposition du document en clair, avec preuve de remise, journalisation append-only et ancrage de preuve composite.
2. Périmètre / Hors périmètre¶
Inclus¶
- Orchestration d’un job BullMQ
pre-transferdéclenché à l’état documentSEALED. - Gestion de l’entité métier
DocumentTransferavec machine d’états explicite. - Appel orchestré aux modules existants : PRE engine (PD-41), HSM, TSA RFC3161, Merkle batch, anchoring, audit WORM, preuve composite.
- Émission de l’événement probatoire
TRANSFER_EMPLOYEE. - Génération de l’acte de remise signé et notification salarié (in-app + email neutre).
- Application de la policy RH
copy/transferavec action de révocation entreprise sitransfer. - Gestion des erreurs, retries, idempotence et anti-replay.
Exclu¶
- Implémentation interne des primitives cryptographiques (PRE/HSM/TSA/Merkle/blockchain).
- UX de consentement salarié (suppose
employee_consent = TRUEau moment nominal). - Définition du format interne du
proof_bundle.json(ProofEnvelope canonical existant). - Validation juridique externe de l’opposabilité probatoire (hors périmètre logiciel testable).
3. Définitions¶
- PRE : Proxy Re-Encryption, re-chiffrement sans déchiffrement du contenu.
- Zero-Knowledge (contexte story) : le worker ne déchiffre jamais le document ni
K_doc. - DocumentTransfer : agrégat de suivi d’un transfert entreprise → salarié.
- Policy
copy: transfert sans révocation d’accès entreprise. - Policy
transfer: transfert avec révocation d’accès entreprise après succès. - Événement
TRANSFER_EMPLOYEE: trace probatoire signée, horodatée, ancrée. - État terminal : état sans transition sortante autorisée.
- Replay : tentative de retraitement logique d’un même transfert déjà finalisé.
- Horodatage TSA : jeton RFC 3161 associé à l’événement probatoire.
4. Invariants (non négociables)¶
| ID | Règle | Justification |
|---|---|---|
| INV-01 | K_docEntreprise n’est jamais modifiée par PD-72. | Intégrité cryptographique source. |
| INV-02 | Le worker ne déchiffre jamais le document ni wrap_Entreprise. | Conformité Zero-Knowledge. |
| INV-03 | rk (ou artefact PRE temporaire équivalent) est détruit en mémoire immédiatement après re-chiffrement. | Réduction de surface d’exposition. |
| INV-04 | Aucun secret cryptographique temporaire n’est persisté en clair (enveloppe AES-256-GCM/HSM obligatoire). | Invariant envelope encryption (crypto). |
| INV-05 | Toute trace TRANSFER_EMPLOYEE est append-only, signée HSM, puis intégrée à un Merkle root ancré publiquement. | Non-répudiation et auditabilité. |
| INV-06 | Si transfer_policy = transfer, l’accès entreprise au contenu est révoqué après transfert validé. | Exigence RGPD métier. |
| INV-07 | Le workflow est idempotent : un même DocumentTransfer.id ne produit jamais double signature, double acte, double ancrage logique. | Robustesse en retry/replay. |
| INV-08 | Machine d’états fermée : toute transition non listée est explicitement interdite et journalisée. | Prévisibilité et testabilité. |
| INV-09 | Auteur et validateur de preuve/gate doivent être distincts. | Validation croisée (gouvernance). |
5. Flux nominaux¶
5.1 Modèle de données contractuel (DocumentTransfer)¶
Champs obligatoires :
id,document_id,status,source_vault_id,target_vault_id,transfer_policy,retry_count,created_at,updated_atrk_id,proof_id,error_code,completed_at: nullable selon état
Formats et contraintes (définis une seule fois ici, référencés ailleurs) :
| Donnée | Format / encodage | Taille | Jeu de caractères | Case | Regex | Invalide |
|---|---|---|---|---|---|---|
DocumentTransfer.id | UUID v4 texte | 36 chars | [0-9a-f-] | insensitive à l’entrée, normalisé lowercase | ^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ | rejet |
document_id | UUID v4 | 36 chars | idem | idem | idem | rejet |
source_vault_id | UUID v4 | 36 chars | idem | idem | idem | rejet |
target_vault_id | UUID v4 | 36 chars | idem | idem | idem | rejet |
status | enum | 1 valeur | ASCII uppercase + _ | sensitive | liste fermée | rejet |
transfer_policy | enum | 1 valeur | ASCII lowercase | sensitive | ^(copy|transfer)$ | rejet |
rk_id | identifiant opaque PRE | 1..128 chars | ASCII printable sans espace | sensitive | ^[!-~]{1,128}$ | rejet + alerte sécurité |
proof_id | UUID v4 | 36 chars | [0-9a-f-] | insensitive à l’entrée | regex UUID v4 | rejet |
error_code | code métier | 1..32 chars | [A-Z0-9_-] | sensitive | ^[A-Z0-9_-]{1,32}$ | clamp impossible, rejet |
retry_count | entier non signé | 0..3 | numérique | n/a | ^[0-3]$ | rejet |
hash_doc (événement) | SHA3-256 hex | 64 chars | [0-9a-f] | sensitive | ^[0-9a-f]{64}$ | rejet |
timestamp (événement) | RFC3339 UTC | 20..35 chars | UTF-8 | sensitive | validation RFC3339 | rejet |
hsm_signature | base64 | 44..4096 chars | base64 std | sensitive | ^[A-Za-z0-9+/=]+$ | rejet |
tx_id blockchain | identifiant opaque ancrage | 1..128 chars | ASCII printable sans espace | sensitive | ^[!-~]{1,128}$ | rejet |
5.2 Machine d’états et transitions¶
États : PENDING, READY_FOR_TRANSFER, TRANSFER_IN_PROGRESS, TRANSFERRED, PROOF_PENDING_ANCHOR, ANCHOR_CONFIRMED, FAILED, BLOCKED_WAITING_CONSENT.
Transitions sortantes explicites :
PENDING→READY_FOR_TRANSFER: garde = compte salarié actif ET clé publique certifiée ETemployee_consent = TRUE. Si compte absent →BLOCKED_WAITING_CONSENT(PRE-01). Si clé invalide →FAILED(PRE-02).READY_FOR_TRANSFER→TRANSFER_IN_PROGRESS: garde = vérification d'autorisation de l'émetteur. Mécanisme : le job BullMQ est déclenché par un événement interneDOCUMENT_SEALEDémis par le moduledocument-service(seul producteur autorisé). Le worker vérifie quesource_vault_iddu job correspond auvault_iddu document en base. Si mismatch → rejet + alerte sécurité. Aucune API externe ne peut déclencher directement le job.TRANSFER_IN_PROGRESS→TRANSFERRED(re-chiffrement + zeroization OK), ouFAILED.TRANSFERRED→PROOF_PENDING_ANCHOR.PROOF_PENDING_ANCHOR→ANCHOR_CONFIRMED(confirmation batch reçue avantANCHOR_TIMEOUT_HOURS, défaut 24h), ouFAILED(timeout dépassé ou erreur technique).FAILED→READY_FOR_TRANSFER: garde =retry_count < max_retries(défaut 3) ET erreur récupérable (PRE-03, PRE-04, timeout ancrage). Backoff exponentiel :delay = base_delay * 2^(retry_count)avecbase_delay = 30s. Erreurs non récupérables (PRE-02, PRE-07) : pas de retry,FAILEDterminal.BLOCKED_WAITING_CONSENT→READY_FOR_TRANSFERsi consentement devenu valide; sinon maintien.ANCHOR_CONFIRMED→* : INTERDITE (état terminal, résolution manuelle uniquement).FAILEDterminal (retry_count >= max_retriesou erreur non récupérable) →* : INTERDITE, escalade manuelle obligatoire.
Transitions retour (obligatoires) :
TRANSFERRED → TRANSFER_IN_PROGRESS: INTERDITE (immutabilité post re-chiffrement).PROOF_PENDING_ANCHOR → TRANSFERRED: INTERDITE (événement déjà émis).ANCHOR_CONFIRMED → PROOF_PENDING_ANCHOR: INTERDITE (finalité probatoire).BLOCKED_WAITING_CONSENT → PENDING: INTERDITE (repréparation non nécessaire).- Downgrade policy (
transferverscopy) aprèsTRANSFER_IN_PROGRESS: INTERDITE (verrou contractuel du transfert en cours).
Invariant états dédié : INV-08-transitions (couverture complète et interdictions explicites).
5.3 Flux nominal¶
- Réception événement
DOCUMENT_SEALEDéligible RH. Vérification d'autorisation : l'événement doit provenir d'un émetteur authentifié et lesource_vault_iddoit correspondre au propriétaire du document. - Création
DocumentTransferenPENDING. - Vérification préconditions (compte salarié actif, clé publique certifiée, consentement vrai). Si OK → transition
READY_FOR_TRANSFER. Si compte absent →BLOCKED_WAITING_CONSENT. Si clé invalide →FAILED. - Passage
TRANSFER_IN_PROGRESSet appel PRE re-encryption aveugle. - Zeroization immédiate des artefacts PRE temporaires.
- Passage
TRANSFERRED, création événementTRANSFER_EMPLOYEE, signature HSM. - Inclusion Merkle + horodatage TSA + planification ancrage batch.
- Passage
PROOF_PENDING_ANCHOR, génération acte de remise signé. - Notification salarié.
- Si
transfer_policy=transfer, révocation accès entreprise. - À confirmation batch:
ANCHOR_CONFIRMED+completed_at.
5.4 Atomicité multi-composant (DB + async)¶
| Scope | Synchrone/Async | Garantie |
|---|---|---|
Écritures DocumentTransfer | Synchrone transactionnel | ACID |
| Journal append-only | Async post-commit | idempotent, retry-safe |
| Merkle/TSA/ancrage | Async post-commit | rattrapage par réconciliation |
| Crash pré-commit | n/a | rollback, aucun artefact persistant |
| Crash post-commit | n/a | état DB source de vérité, reprise worker |
5bis. Diagrammes Mermaid¶
5bis.1 Diagramme d’états — DocumentTransfer (INV-08)¶
stateDiagram-v2
[*] --> PENDING : DOCUMENT_SEALED reçu
PENDING --> READY_FOR_TRANSFER : compte actif ∧ clé certifiée ∧ consent=TRUE
PENDING --> BLOCKED_WAITING_CONSENT : compte absent (PRE-01)
PENDING --> FAILED : clé invalide (PRE-02)
BLOCKED_WAITING_CONSENT --> READY_FOR_TRANSFER : consentement devenu valide
READY_FOR_TRANSFER --> TRANSFER_IN_PROGRESS : autorisation émetteur vérifiée
TRANSFER_IN_PROGRESS --> TRANSFERRED : re-chiffrement PRE + zeroization OK (INV-03)
TRANSFER_IN_PROGRESS --> FAILED : erreur technique
TRANSFERRED --> PROOF_PENDING_ANCHOR : événement signé HSM + Merkle (INV-05)
PROOF_PENDING_ANCHOR --> ANCHOR_CONFIRMED : confirmation batch < ANCHOR_TIMEOUT_HOURS
PROOF_PENDING_ANCHOR --> FAILED : timeout ou erreur technique
FAILED --> READY_FOR_TRANSFER : retry_count < max_retries ∧ erreur récupérable (INV-07)
ANCHOR_CONFIRMED --> [*]
note right of ANCHOR_CONFIRMED : État terminal (INV-08)\nAucune transition sortante
note right of FAILED : Terminal si retry_count ≥ 3\nou erreur non récupérable\n(PRE-02, PRE-07)
note left of TRANSFER_IN_PROGRESS : K_doc jamais déchiffrée (INV-02)\nrk détruit après usage (INV-03) 5bis.2 Diagramme de séquence — Flux nominal (§5.3)¶
sequenceDiagram
participant DS as DocumentService
participant W as Worker PRE-Transfer
participant DB as PostgreSQL
participant PRE as PRE Engine (PD-41)
participant HSM as HSM (PD-37)
participant TSA as TSA RFC3161 (PD-39)
participant MK as Merkle Batch (PD-55)
participant AUD as Audit WORM
participant NOT as Notification
DS->>W: événement DOCUMENT_SEALED
W->>DB: CREATE DocumentTransfer [PENDING]
Note over W: Vérification préconditions
W->>DB: UPDATE status → READY_FOR_TRANSFER
W->>DB: UPDATE status → TRANSFER_IN_PROGRESS
W->>PRE: re-chiffrement aveugle (wrap_Entreprise → wrap_Salarié)
PRE-->>W: wrap_Salarié + rk_id
Note over W: Zeroization rk immédiate (INV-03)
W->>DB: UPDATE status → TRANSFERRED
W->>HSM: signer événement TRANSFER_EMPLOYEE
HSM-->>W: hsm_signature (INV-05)
W->>AUD: journal append-only (INV-05)
W->>TSA: horodatage RFC3161
TSA-->>W: jeton TSA
W->>MK: inclusion Merkle + planification ancrage batch
W->>DB: UPDATE status → PROOF_PENDING_ANCHOR
W->>W: génération acte de remise signé
W->>NOT: notification salarié (contenu neutre)
alt transfer_policy = transfer
W->>DB: révocation accès entreprise (INV-06)
end
MK-->>W: confirmation batch (async)
W->>DB: UPDATE status → ANCHOR_CONFIRMED + completed_at 6. Cas d’erreur¶
| Code | Cas | Réponse contractuelle | État cible |
|---|---|---|---|
| PRE-01 | Salarié sans compte actif | rejet métier + notification RH, DocumentTransfer créé en PENDING puis transitionné vers BLOCKED_WAITING_CONSENT | BLOCKED_WAITING_CONSENT |
| PRE-02 | Clé publique invalide/non certifiée | rejet + alerte sécurité | FAILED |
| PRE-03 | Signature HSM indisponible | retry automatique jusqu’à 3 | FAILED puis READY_FOR_TRANSFER si retry restant |
| PRE-04 | TSA indisponible | retry automatique borné | FAILED puis reprise |
| PRE-05 | Batch blockchain différé | non bloquant | PROOF_PENDING_ANCHOR |
| PRE-06 | Replay détecté | rejet idempotent, aucun nouvel artefact | état inchangé |
| PRE-07 | Donnée au format invalide (§5.1) | rejet strict, journal sécurité | FAILED ou refus création |
| PRE-08 | Retry max atteint (retry_count=3) | escalade manuelle obligatoire | FAILED terminal |
7. Critères d’acceptation (testables)¶
| ID | Critère | Observable |
|---|---|---|
| CA-01 | Le salarié ouvre le document depuis son coffre après transfert réussi. | accès autorisé côté salarié, lecture valide. |
| CA-02 | En policy transfer, l’entreprise ne peut plus accéder au contenu après finalisation. | tentative accès entreprise refusée. |
| CA-03 | Un événement TRANSFER_EMPLOYEE signé est créé une seule fois. | unicité par DocumentTransfer.id, signature vérifiable. |
| CA-04 | La chaîne preuve Merkle→TSA→ancrage est complète. | proof_id résolu + tx_id présent en confirmation. |
| CA-05 | Relancer le même job ne crée aucune duplication métier. | compteurs artefacts inchangés, état stable. |
| CA-06 | rk/artefacts PRE temporaires ne sont jamais persistés en clair. | audit stockage: aucune valeur secrète en clair. |
| CA-07 | Performance transfert hors batch respecte SLA. | latence traitement P95 ≤ 3000 ms. |
| CA-08 | Toutes transitions interdites sont rejetées explicitement. | erreur métier + log transition interdite. |
| CA-09 | Après 3 retries échoués, aucune relance automatique n’est effectuée. | retry_count=3, état FAILED terminal. |
| CA-10 | Notification salarié est neutre (sans contenu sensible). | contenu notification sans payload documentaire. |
8. Scénarios de test (Given / When / Then)¶
- Flux nominal copy
- Given document
SEALED, consentement vrai, policycopy - When job
pre-transfers’exécute sans erreur -
Then état final
ANCHOR_CONFIRMED, accès salarié OK, accès entreprise conservé -
Flux nominal transfer
- Given document
SEALED, policytransfer - When transfert et preuve sont finalisés
-
Then révocation entreprise effective et vérifiable
-
Clé publique invalide
- Given clé publique salarié invalide
- When vérification préconditions s’exécute
-
Then transfert rejeté,
FAILED, alerte sécurité émise -
TSA indisponible récupérable
- Given TSA indisponible temporairement
- When job échoue puis retry
-
Then reprise réussie avant
retry_count=3 -
Batch ancrage différé
- Given preuve signée et Merkle inclus, ancrage non encore confirmé
- When fin du traitement nominal
-
Then état
PROOF_PENDING_ANCHORnon bloquant -
Replay
- Given un
DocumentTransfer.iddéjà finalisé - When même message est rejoué
-
Then aucun nouvel événement/signature/acte n’est créé
-
Transition interdite
- Given état
ANCHOR_CONFIRMED - When demande de transition vers un autre état
-
Then rejet explicite + log sécurité
-
Retry max atteint
- Given 3 échecs consécutifs
- When une nouvelle relance automatique est tentée
-
Then refus automatique, escalade manuelle requise
-
Validation formats
- Given
hash_dochors regex^[0-9a-f]{64}$ - When création événement probatoire
-
Then rejet strict avec code format invalide
-
Zeroization
- Given re-chiffrement terminé
- When contrôle post-traitement est exécuté
- Then aucun artefact PRE temporaire exploitable n’est récupérable
9. Hypothèses explicites¶
| ID | Hypothèse | Impact si faux |
|---|---|---|
| H-01 | Modules PD-41, PD-37, PD-39, PD-55 sont disponibles avec SLA contractuel. | Blocage du flux nominal et montée d’erreurs techniques. |
| H-02 | employee_consent est fourni par une source de vérité fiable. | Risque de blocage abusif ou transfert non autorisé. |
| H-03 | La vérification d’identité des coffres source/cible est déjà implémentée hors PD-72. | Risque de transfert vers mauvais coffre. |
| H-04 | Les IDs aléatoires suivent UUID v4. | Non-conformité format/interop si autre schéma. |
| H-05 | La conformité légale finale de l’“opposabilité” relève d’un audit externe. | Point juridique hors validation logicielle automatisée. |
10. Contraintes techniques et points à clarifier¶
10.1 Contraintes techniques (obligatoire)¶
- Projet cible :
ProbatioVault-backend - Stack contractuelle :
NestJS + TypeORM + PostgreSQL + BullMQ(aucune référence Swift/Spring autorisée). - Sécurité runtime : exécution worker en environnement isolé, accès strictement limité aux modules requis.
- Migration DDL colonne existante : Aucune applicable (PD-72 introduit une nouvelle entité; pas de type-change/nullable-change de colonne existante identifiée).
- Contraintes inter-modules :
- Aucune route HTTP cross-module à protéger spécifiquement dans PD-72 (flux asynchrone).
- Interactions modules obligatoires : PRE, HSM, TSA, Merkle, anchoring, audit, preuve.
- Échec contrat module externe => erreur explicite + état conforme §6.
- Learnings imposés :
- IDs aléatoires : UUID v4.
- Vérification signature HSM sans double-hash.
- Comparaison timing-safe pour tokens sécurité.
- Typage sémantique distinct des identifiants métier.
10.2 Bornes numériques contractualisées¶
| Paramètre | Défaut | Min | Max | Unité | Contexte | Percentile | Hors bornes |
|---|---|---|---|---|---|---|---|
retry_count | 0 | 0 | 3 | tentatives | worker | n/a | rejet incrément >3 |
max_retries | 3 | 1 | 3 | tentatives | worker | n/a | clamp interdit, rejet config |
| Latence transfert (hors batch) | 3000 | 500 | 3000 | ms | backend prod | P95 | alerte SLO + non conformité ENF |
| Débit cible mensuel | 10000 | 1000 | 50000 | transferts/mois | cluster prod | n/a | alerte capacité |
10.3 SLA temporels (transitions avec temps)¶
| SLA | Défaut | Min | Max | Configurabilité | Expiration / dépassement |
|---|---|---|---|---|---|
| Délai max traitement nominal (hors batch) | 3s | 0.5s | 3s | non | FAILED + retry |
| Fenêtre retry automatique | 3 tentatives | 1 | 3 | non | passage FAILED terminal si max atteint |
Délai attente ancrage (PROOF_PENDING_ANCHOR) | 24h | 1h | 72h | oui (env var ANCHOR_TIMEOUT_HOURS) | FAILED + alerte ops + escalade manuelle |
10.4 Points clarifiés (v2)¶
- SLA ancrage : défaut 24h, min 1h, max 72h, configurable via
ANCHOR_TIMEOUT_HOURS. Dépassement →FAILED+ alerte ops. - Format
hsm_signature: base64 standard (RFC 4648) de la signature DER ECDSA P-384. Regex :^[A-Za-z0-9+/=]+$, taille 44..4096 chars. - Format
tx_id: identifiant opaque dépendant du réseau d’ancrage, ASCII printable 1..128 chars. Le format exact est défini par le module anchoring (PD-55). - Notification RH
BLOCKED_WAITING_CONSENT: email neutre au service RH (1 seul envoi, pas de répétition automatique). Anti-spam : déduplication parDocumentTransfer.id. - Epic / chemin :
crypto-proof/PD-72-worker-pre-transferdans ProbatioVault-backend.
Références¶
- Epic : PD-189 — CRYPTO
- JIRA :
PD-72 - Repos concernés :
ProbatioVault-backend - Documents associés : expression de besoin PD-72, Architecture Technique RH v4.1, fiche brevet Legal PRE n°803, PD-41, PD-37, PD-39, PD-55, PD-38