PD-72 — Plan d'implementation¶
Navigation User Story
| Document | | | ---------- | -- | | [Expression de besoin](PD-72-besoin.md) | | | [Specification](PD-72-specification.md) | | | **Plan d'implementation** | *(ce document)* | | [Tests](PD-72-tests.md) | | [<- Retour a crypto-proof](../PD-189-epic.md) . [Index User Story](index.md)1. Decoupe en composants¶
Architecture cible¶
src/modules/document-transfer/
├── document-transfer.module.ts # Module NestJS, enregistrement BullMQ
├── entities/
│ └── document-transfer.entity.ts # Entite TypeORM DocumentTransfer
├── enums/
│ ├── transfer-status.enum.ts # PENDING, READY_FOR_TRANSFER, ...
│ ├── transfer-policy.enum.ts # copy | transfer
│ └── transfer-error-code.enum.ts # PRE-01..PRE-08
├── types/
│ └── branded-ids.ts # DocumentTransferId, VaultId, ProofId (branded)
├── interfaces/
│ ├── transfer-job.interface.ts # Payload job BullMQ
│ └── transfer-events.interface.ts # Contrat evenement TRANSFER_EMPLOYEE
├── services/
│ ├── document-transfer.service.ts # Service principal (creation, queries)
│ ├── transfer-state-machine.service.ts # Machine d'etats fermee (INV-08)
│ ├── transfer-orchestrator.service.ts # Orchestration flux nominal (etapes 1-11)
│ ├── transfer-preconditions.service.ts # Gardes preconditions (compte, cle, consentement)
│ ├── transfer-proof.service.ts # Emission evenement, acte de remise, Merkle/TSA
│ ├── transfer-revocation.service.ts # Revocation acces entreprise (policy transfer)
│ └── transfer-notification.service.ts # Notification salarie (in-app + email neutre)
├── processors/
│ └── pre-transfer.processor.ts # Worker BullMQ @Processor('pv:jobs:transfer')
├── listeners/
│ └── anchor-confirmation.listener.ts # Ecoute confirmation batch -> ANCHOR_CONFIRMED
├── constants/
│ └── transfer.constants.ts # MAX_RETRIES, BASE_DELAY, ANCHOR_TIMEOUT_HOURS
├── dto/
│ └── transfer-job.dto.ts # Validation payload job
├── migrations/
│ └── XXXXXX-create-document-transfer.ts # Migration DDL
└── __tests__/
├── transfer-state-machine.spec.ts # Tests unitaires machine d'etats
├── transfer-orchestrator.spec.ts # Tests unitaires orchestration
├── transfer-preconditions.spec.ts # Tests unitaires preconditions
├── transfer-proof.spec.ts # Tests unitaires proof/audit
├── transfer-revocation.spec.ts # Tests unitaires revocation
├── pre-transfer.processor.spec.ts # Tests unitaires processor
├── pre-transfer.integration.spec.ts # Tests integration (DB + queue)
└── fixtures/
└── transfer-fixtures.ts # Factories et fixtures
Composants et responsabilites¶
| Composant | Responsabilite |
|---|---|
DocumentTransferModule | Module NestJS, enregistrement queue BullMQ pv:jobs:transfer, export services, imports modules dependants |
DocumentTransfer (entity) | Entite TypeORM avec machine d'etats, UUIDs branded, contraintes DB |
TransferStateMachineService | Transitions autorisees, gardes, interdictions explicites (INV-08). Aucune logique metier. |
TransferOrchestratorService | Orchestration sequentielle du flux nominal (etapes 1-11 de la spec). Point d'entree unique du processor. |
TransferPreconditionsService | Verification compte actif, cle publique certifiee, consentement, autorisation emetteur (source_vault_id) |
TransferProofService | Emission TRANSFER_EMPLOYEE, signature HSM, inclusion Merkle, horodatage TSA, planification ancrage |
TransferRevocationService | Revocation acces entreprise si policy=transfer (INV-06) |
TransferNotificationService | Notification salarie in-app + email neutre (CA-10) |
PreTransferProcessor | Worker BullMQ, idempotence, retry backoff exponentiel, delegation a l'orchestrateur |
AnchorConfirmationListener | Ecoute confirmation batch blockchain, transition PROOF_PENDING_ANCHOR -> ANCHOR_CONFIRMED |
DocumentTransferService | CRUD DocumentTransfer, requetes, creation initiale a reception DOCUMENT_SEALED |
| Migration DDL | Creation table vault_secure.document_transfers avec contraintes, index, enum types |
2. Flux techniques¶
Flux N1 — Nominal (copy)¶
DocumentSealingService PreTransferProcessor TransferOrchestratorService
| | |
|-- DOCUMENT_SEALED event ------>| |
| (audit log source) | |
| |-- process(job) ----------->|
| | |
| | TransferPreconditionsService
| | |
| | |<-- checkAll() -->|
| | | compte actif? |
| | | cle certifiee? |
| | | consentement? |
| | | vault_id match? |
| | | |
| | TransferStateMachineService
| | |
| | |-- PENDING -> READY_FOR_TRANSFER
| | |-- READY_FOR_TRANSFER -> TRANSFER_IN_PROGRESS
| | |
| | PreService (PD-41)
| | |
| | |-- reEncrypt(wrap_Entreprise, rk)
| | |-- zeroize(rk) immediate
| | |
| | |-- TRANSFER_IN_PROGRESS -> TRANSFERRED
| | |
| | TransferProofService
| | |
| | |-- createEvent(TRANSFER_EMPLOYEE)
| | |-- signHSM(event)
| | |-- includeMerkle(event)
| | |-- timestampTSA(event)
| | |-- scheduleAnchor(batch)
| | |-- generateActeRemise()
| | |
| | |-- TRANSFERRED -> PROOF_PENDING_ANCHOR
| | |
| | TransferNotificationService
| | |
| | |-- notifySalarie(in-app + email neutre)
| | |
| |<-- OK (PROOF_PENDING_ANCHOR) ---|
Flux N2 — Confirmation ancrage (asynchrone)¶
AnchorBatchProcessor (PD-55) AnchorConfirmationListener TransferStateMachineService
| | |
|-- batch FINALIZED ----------->| |
| (event/callback) | |
| |-- findByProofId() -------->|
| | |
| |-- transition() ----------->|
| | PROOF_PENDING_ANCHOR |
| | -> ANCHOR_CONFIRMED |
| | |
| |-- setCompletedAt() ------->|
Flux E1 — Erreur recuperable (HSM/TSA)¶
PreTransferProcessor TransferOrchestratorService
| |
|-- process(job) -------------->|
| |-- ... erreur HSM/TSA ...
| |-- transition -> FAILED
| |-- retry_count < 3 ?
| | OUI: throw pour retry BullMQ
| | NON: FAILED terminal, escalade
|<-- throw (BullMQ retry) ------|
| |
|-- backoff: 30s * 2^count ---->|
|-- process(job) retry -------->|
Flux E2 — Replay idempotent¶
PreTransferProcessor TransferOrchestratorService
| |
|-- process(job) -------------->|
| |-- findByDocumentId()
| |-- status in terminal?
| | OUI: rejet PRE-06 (log replay + code retour)
| | NON: reprise etat courant
|<-- OK (idempotent) -----------|
2bis. Diagrammes Mermaid¶
Graphe de dependances inter-composants¶
graph TD
subgraph "PD-72 — DocumentTransferModule"
PROC[PreTransferProcessor]
ORCH[TransferOrchestratorService]
SM[TransferStateMachineService]
PREC[TransferPreconditionsService]
PROOF[TransferProofService]
REVOC[TransferRevocationService]
NOTIF[TransferNotificationService]
LISTEN[AnchorConfirmationListener]
SVC[DocumentTransferService]
ENTITY[(DocumentTransfer entity)]
end
subgraph "Modules externes"
PRE["PreService (PD-41)"]
AUDIT["AuditService (PD-31)"]
HSM["HsmService (PD-37)"]
TSA["TsaService (PD-39)"]
MERKLE["MerkleService (PD-54)"]
ANCHOR["AnchorService (PD-55)"]
NOTIF_EXT["NotificationService (PD-105)"]
DOC["DocumentSecure entity"]
end
PROC -->|"delegue process(job)"| ORCH
ORCH --> SM
ORCH --> PREC
ORCH --> PROOF
ORCH -->|"si policy=transfer"| REVOC
ORCH --> NOTIF
ORCH -->|"reEncrypt()"| PRE
ORCH --> SVC
SVC --> ENTITY
PREC --> DOC
PROOF --> AUDIT
PROOF --> HSM
PROOF --> TSA
PROOF --> MERKLE
PROOF --> ANCHOR
NOTIF --> NOTIF_EXT
LISTEN -->|"batch FINALIZED"| SM
LISTEN --> SVC
style PROC fill:#4a86c8,color:#fff
style ORCH fill:#4a86c8,color:#fff
style SM fill:#e8a735,color:#fff
style PROOF fill:#d64045,color:#fff
style ENTITY fill:#6c757d,color:#fff Diagramme de sequence — Flux nominal (copy)¶
sequenceDiagram
participant Seal as DocumentSealingService
participant Queue as BullMQ pv:jobs:transfer
participant Proc as PreTransferProcessor
participant Orch as TransferOrchestratorService
participant SM as TransferStateMachineService
participant Prec as TransferPreconditionsService
participant PRE as PreService (PD-41)
participant Proof as TransferProofService
participant HSM as HsmService (PD-37)
participant TSA as TsaService (PD-39)
participant Merkle as MerkleService (PD-54)
participant Anchor as AnchorService (PD-55)
participant Notif as TransferNotificationService
Seal->>Queue: DOCUMENT_SEALED event
Queue->>Proc: process(job)
Proc->>Orch: execute(job.data)
rect rgb(230, 245, 255)
Note over Orch,Prec: Phase 1 — Preconditions
Orch->>Prec: checkAll(payload)
Prec-->>Orch: OK (compte actif, cle certifiee, consentement, vault_id)
end
rect rgb(230, 255, 230)
Note over Orch,PRE: Phase 2 — Re-chiffrement PRE
Orch->>SM: transition(PENDING → READY_FOR_TRANSFER)
Orch->>SM: transition(READY_FOR_TRANSFER → TRANSFER_IN_PROGRESS)
Orch->>PRE: reEncrypt(wrap_Entreprise, rk)
PRE-->>Orch: wrap_Salarie
Note over Orch: zeroize(rk) dans finally
Orch->>SM: transition(TRANSFER_IN_PROGRESS → TRANSFERRED)
end
rect rgb(255, 240, 230)
Note over Orch,Anchor: Phase 3 — Preuve probatoire
Orch->>Proof: createTransferProof()
Proof->>HSM: sign(event_hash)
HSM-->>Proof: signature
Proof->>Merkle: include(event)
Merkle-->>Proof: merkle_proof
Proof->>TSA: timestamp(event)
TSA-->>Proof: tsa_token
Proof->>Anchor: schedule(batch)
Proof-->>Orch: proof_id
Orch->>SM: transition(TRANSFERRED → PROOF_PENDING_ANCHOR)
end
rect rgb(245, 230, 255)
Note over Orch,Notif: Phase 4 — Notification
Orch->>Notif: notifySalarie(transfer_ref)
Notif-->>Orch: OK
end
Orch-->>Proc: OK (PROOF_PENDING_ANCHOR) Diagramme de sequence — Confirmation ancrage (asynchrone)¶
sequenceDiagram
participant Batch as AnchorBatchProcessor (PD-55)
participant Listen as AnchorConfirmationListener
participant SM as TransferStateMachineService
participant SVC as DocumentTransferService
Batch->>Listen: batch FINALIZED (tx_id)
Listen->>SVC: findByProofId(proof_id)
SVC-->>Listen: DocumentTransfer (PROOF_PENDING_ANCHOR)
Listen->>SM: transition(PROOF_PENDING_ANCHOR → ANCHOR_CONFIRMED)
Listen->>SVC: setCompletedAt(now)
Note over Listen: Transfert termine Machine d'etats — Transitions autorisees¶
stateDiagram-v2
[*] --> PENDING: DOCUMENT_SEALED recu
PENDING --> READY_FOR_TRANSFER: Preconditions OK
PENDING --> BLOCKED_WAITING_CONSENT: Compte absent (PRE-01)
PENDING --> FAILED: Cle invalide (PRE-02)
BLOCKED_WAITING_CONSENT --> READY_FOR_TRANSFER: Consentement valide
READY_FOR_TRANSFER --> TRANSFER_IN_PROGRESS: Debut re-chiffrement
TRANSFER_IN_PROGRESS --> TRANSFERRED: Re-chiffrement OK
TRANSFER_IN_PROGRESS --> FAILED: Erreur (retry < 3)
TRANSFERRED --> PROOF_PENDING_ANCHOR: Preuve emise
PROOF_PENDING_ANCHOR --> ANCHOR_CONFIRMED: Batch finalise
PROOF_PENDING_ANCHOR --> FAILED: Timeout ancrage
FAILED --> PENDING: Retry (count < MAX)
ANCHOR_CONFIRMED --> [*] 3. Mapping invariants -> mecanismes¶
| Invariant ID | Exigence | Mecanisme | Composant | Observable | Risque |
|---|---|---|---|---|---|
| INV-01 | K_docEntreprise jamais modifiee | L'orchestrateur n'accede jamais a K_docEntreprise. Le PRE engine (PD-41) recoit wrap_Entreprise et produit wrap_Salarie sans toucher la cle source. Assertion : empreinte K_doc identique avant/apres. | TransferOrchestratorService, PreService (PD-41) | Hash K_doc avant == apres dans test TC-INV-01 | Faible — PD-41 deja valide |
| INV-02 | Worker ne dechiffre jamais | Aucun appel a decrypt() ou unwrap() dans le module document-transfer. Seul reEncrypt() aveugle est appele. Audit traces d'appels. | TransferOrchestratorService | Grep sur traces d'appels : absence de decrypt/unwrap (TC-INV-02) | Faible — architecture PRE by design |
| INV-03 | rk detruit immediatement apres re-chiffrement | Appel zeroize() sur buffer rk dans un bloc finally apres reEncrypt(). Buffer remplace par zeros. Variable mise a null. | TransferOrchestratorService | TC-INV-03 : buffer non recuperable post-traitement | Moyen — necessite pattern zeroize explicite |
| INV-04 | Aucun secret temporaire persiste en clair | rk jamais ecrit en DB/log/queue. Si persistance intermediaire necessaire : envelope AES-256-GCM via HSM. Scan stockage/logs/queues. | TransferOrchestratorService, PreTransferProcessor | TC-INV-04 : scan DB + logs + queue payload = 0 secret clair | Moyen — attention aux logs debug |
| INV-05 | Trace append-only, signee HSM, Merkle ancre | TransferProofService cree TRANSFER_EMPLOYEE via AuditService.logEvent() (append-only PD-31), signe HSM via HsmService.sign() (PD-37). Cote verification : utiliser crypto.verify(null, hash, key, sig) — JAMAIS createVerify() (learning PD-282), inclut Merkle (PD-54), horodatage TSA (PD-39), planifie ancrage (PD-55). | TransferProofService | TC-INV-05 : chaine complete event -> signature -> merkle -> tx_id | Moyen — dependance 4 modules externes |
| INV-06 | Revocation acces entreprise si policy=transfer | TransferRevocationService appele uniquement si transfer_policy === 'transfer' ET etat PROOF_PENDING_ANCHOR atteint. Revocation via modification droits acces coffre entreprise. | TransferRevocationService | TC-NOM-02 : acces entreprise refuse apres finalisation | Moyen — irreversibilite a tester |
| INV-07 | Idempotence (pas de double signature/acte/ancrage) | Garde idempotence par DocumentTransfer.id : si etat terminal, rejet explicite PRE-06 avec log securite + code retour observable. Contrainte UNIQUE sur (document_id, target_vault_id) en DB. | TransferOrchestratorService, DocumentTransfer entity | TC-ERR-05, TC-NR-01 : compteurs artefacts stables | Faible — contrainte DB + garde code |
| INV-08 | Machine d'etats fermee | TransferStateMachineService : map explicite des transitions autorisees. Toute transition non listee leve TransferForbiddenTransitionError + log securite. | TransferStateMachineService | TC-INV-08 : toute transition non listee rejetee + log | Faible — exhaustivite testable |
| INV-09 | Auteur != validateur | Metadata preuve : author_id (worker service account) != validator_id (HSM signer). Assertion dans TransferProofService. | TransferProofService | TC-INV-09 : author_id != validator_id dans dossier preuve | Faible — separation structurelle |
4. Mapping criteres d'acceptation -> mecanismes¶
| Critere ID | Mecanisme(s) | Composant | Observable | Risque |
|---|---|---|---|---|
| CA-01 | Re-chiffrement PRE aveugle produit wrap_Salarie dechiffrable par cle salarie. Test end-to-end : salarie dechiffre avec sa cle privee. | TransferOrchestratorService, PreService | Lecture valide cote salarie | Faible |
| CA-02 | TransferRevocationService supprime/revoque l'acces entreprise. Tentative acces post-revocation -> refus. | TransferRevocationService | Acces entreprise refuse (HTTP 403) | Moyen |
| CA-03 | Contrainte UNIQUE (document_id, target_vault_id) + garde idempotence dans orchestrateur. Un seul TRANSFER_EMPLOYEE par DocumentTransfer.id. | TransferOrchestratorService, entity DB | Unicite event + signature verifiable | Faible |
| CA-04 | TransferProofService enchaine : event -> HSM sign -> Merkle include -> TSA timestamp -> anchor schedule. proof_id resolu + tx_id a confirmation. | TransferProofService | proof_id + tx_id presents | Moyen |
| CA-05 | Garde idempotence : si DocumentTransfer existe en etat terminal, rejet explicite PRE-06 (etat inchange, log replay detecte, code retour observable). Compteurs artefacts identiques. | TransferOrchestratorService | Compteurs stables au replay | Faible |
| CA-06 | rk zeroize dans finally. Scan stockage : aucun secret clair. Payload job BullMQ ne contient jamais rk. | TransferOrchestratorService, PreTransferProcessor | Audit stockage = 0 secret clair | Moyen |
| CA-07 | Traitement synchrone (hors batch) <= 3s P95. Mesure latence dans processor avec metriques. | PreTransferProcessor | P95 <= 3000ms | Moyen — dependant SLA modules |
| CA-08 | TransferStateMachineService rejette toute transition non listee. Log securite emis. | TransferStateMachineService | Erreur metier + log transition interdite | Faible |
| CA-09 | retry_count >= MAX_RETRIES -> FAILED terminal. Processor ne re-throw pas. | PreTransferProcessor, TransferStateMachineService | retry_count=3, FAILED terminal, 0 relance | Faible |
| CA-10 | TransferNotificationService envoie message neutre : reference transfert, pas de contenu/cle/hash. Template valide par test. | TransferNotificationService | Contenu notification sans payload documentaire | Faible |
5. Mapping tests (TC-*) -> mecanismes + observables¶
| Test ID | Reference spec | Mecanisme(s) | Point(s) d'observation | Niveau de test vise |
|---|---|---|---|---|
| TC-NOM-01 | CA-01, CA-03, CA-04, INV-01..05 | Flux complet copy : PENDING -> ANCHOR_CONFIRMED | Etat final, acces salarie, event unique, proof_id + tx_id, acces entreprise conserve | Integration |
| TC-NOM-02 | CA-02, INV-06 | Flux transfer + revocation entreprise | Acces entreprise refuse apres ANCHOR_CONFIRMED | Integration |
| TC-NOM-03 | PRE-05 | Etat PROOF_PENDING_ANCHOR sans confirmation batch | Pas de rollback, acces salarie OK | Unit |
| TC-NOM-04 | CA-10 | Template notification neutre | Contenu sans document/cle/hash | Unit |
| TC-ERR-01 | PRE-01 | Precondition : compte absent -> BLOCKED_WAITING_CONSENT | Etat cible, 0 artefact probatoire | Unit |
| TC-ERR-02 | PRE-02 | Precondition : cle invalide -> FAILED + alerte | Etat FAILED, log securite | Unit |
| TC-ERR-03 | PRE-03, CA-09 | HSM persistant indisponible, 3 retries -> FAILED terminal | retry_count=3, 0 4e relance | Unit |
| TC-ERR-04 | PRE-04 | TSA indisponible temporaire, reprise avant retry 3 | Reprise reussie, 0 doublon event | Integration |
| TC-ERR-05 | PRE-06, INV-07, CA-03, CA-05 | Replay sur transfert finalise -> rejet idempotent PRE-06 (log + code retour explicite) | Etat inchange, 0 nouvel artefact | Unit |
| TC-ERR-06 | PRE-07 | Format invalide (hash_doc hors regex) -> rejet strict | Log securite, refus creation | Unit |
| TC-ERR-07 | PRE-08, CA-09 | retry_count=3, relance -> refus immediat | FAILED terminal, escalade tracee | Unit |
| TC-ERR-08 | SLA ancrage, ANCHOR_TIMEOUT_HOURS | Timeout ancrage -> FAILED + alerte ops | Transition FAILED, alerte ops | Unit |
| TC-ERR-09 | BLOCKED -> READY | Consentement valide -> reprise flux | Transition autorisee, notification RH | Unit |
| TC-ERR-10 | Autorisation emetteur | source_vault_id mismatch -> rejet + alerte | 0 DocumentTransfer cree, log securite | Unit |
| TC-ERR-11 | Backoff exponentiel | retry_count=1 -> delay >= 60s | Delay >= base_delay * 2^count | Unit |
| TC-INV-01 | INV-01 | Hash K_doc avant/apres job | Empreinte identique | Unit |
| TC-INV-02 | INV-02 | Audit traces : 0 decrypt/unwrap | Absence d'operation de dechiffrement | Unit |
| TC-INV-03 | INV-03 | Zeroize buffer rk post re-chiffrement | Buffer non recuperable | Unit |
| TC-INV-04 | INV-04 | Scan DB/logs/queue : 0 secret clair | Aucune valeur secrete en clair | Integration |
| TC-INV-05 | INV-05 | Chaine append-only -> HSM -> Merkle -> TSA -> anchor | Chaine complete | Integration |
| TC-INV-08 | INV-08 | Transitions non listees rejetees | Erreur + log pour chaque transition interdite | Unit |
| TC-INV-09 | INV-09 | author_id != validator_id | Metadata preuve | Unit |
| TC-NR-01 | INV-07 | Relance meme job -> 0 artefact supplementaire | Compteurs stables | Unit |
| TC-NR-02 | Interdictions retour | TRANSFERRED->TRANSFER_IN_PROGRESS etc. rejetes | Rejets explicites | Unit |
| TC-NR-03 | CA-07 | Latence P95 hors batch | P95 <= 3000ms | Perf |
| TC-NR-04 | Verrou policy | Downgrade transfer->copy apres TRANSFER_IN_PROGRESS | Rejet | Unit |
| TC-NR-05 | CA-09 | 0 relance auto au-dela de 3 | retry_count=3, FAILED terminal | Unit |
| TC-NEG-01 | Format UUID | UUID non v4 -> rejet strict | Erreur validation + log securite | Unit |
| TC-NEG-02 | Format rk_id | rk_id hors charset -> rejet + alerte | 0 traitement PRE lance | Unit |
| TC-NEG-03 | Transition interdite | ANCHOR_CONFIRMED -> tout -> rejet | Log transition interdite | Unit |
| TC-NEG-04 | Replay concurrent | N messages identiques simultanes | 1 seul resultat logique | Integration |
| TC-NEG-05 | Secret temporaire | Payload malveillant persistance clair -> rejet | Absence secret clair au repos | Unit/Sec |
6. Gestion des erreurs¶
| Code | Cas | Mecanisme technique | Composant responsable | Observable |
|---|---|---|---|---|
| PRE-01 | Salarie sans compte actif | TransferPreconditionsService.checkAccountActive() -> false -> transition BLOCKED_WAITING_CONSENT | TransferPreconditionsService, TransferStateMachineService | Etat BLOCKED, notification RH (1 email, dedup par transfer.id) |
| PRE-02 | Cle publique invalide/non certifiee | TransferPreconditionsService.checkPublicKey() -> validation echec -> transition FAILED (non recuperable) | TransferPreconditionsService | Etat FAILED, alerte securite, 0 retry |
| PRE-03 | HSM indisponible | TransferProofService.signHSM() throw -> catch dans orchestrateur -> transition FAILED -> retry si count < 3 | TransferProofService, TransferOrchestratorService | Etat FAILED, retry_count incremente, backoff exponentiel |
| PRE-04 | TSA indisponible | TransferProofService.timestampTSA() throw -> meme pattern que PRE-03 | TransferProofService, TransferOrchestratorService | Retry avec backoff |
| PRE-05 | Batch blockchain differe | Non bloquant : etat PROOF_PENDING_ANCHOR valide sans tx_id | TransferProofService | Etat PROOF_PENDING_ANCHOR, ancrage asynchrone |
| PRE-06 | Replay detecte | Garde idempotence : findByDocumentId() + check etat terminal -> rejet explicite PRE-06 (code + log securite) | TransferOrchestratorService | Etat inchange, 0 nouvel artefact |
| PRE-07 | Format invalide | Validation DTO (class-validator) + regex contractuels sur chaque champ (section 5.1 spec) | transfer-job.dto.ts, validators | Rejet strict, log securite |
| PRE-08 | Retry max atteint | Garde retry_count >= MAX_RETRIES dans processor -> ne pas re-throw -> FAILED terminal | PreTransferProcessor | FAILED terminal, escalade manuelle tracee |
| Timeout ancrage | ANCHOR_TIMEOUT_HOURS depasse | AnchorConfirmationListener ou cron : si created_at + timeout < now et etat PROOF_PENDING_ANCHOR -> FAILED | AnchorConfirmationListener | FAILED, alerte ops |
| Crash pre-commit | Rollback transaction | TypeORM transaction wrapping : aucun artefact persiste | TransferOrchestratorService | DB coherente, reprise worker |
| Crash post-commit | Etat DB = source de verite | Worker reprend avec etat courant de DocumentTransfer | PreTransferProcessor | Reprise idempotente |
Strategie de retry (backoff exponentiel)¶
// Dans PreTransferProcessor
const backoffDelay = TRANSFER_CONSTANTS.BASE_DELAY_MS * Math.pow(2, transfer.retry_count);
// BASE_DELAY_MS = 30_000 (30s)
// retry 0: 30s, retry 1: 60s, retry 2: 120s
Erreurs non recuperables (PRE-02, PRE-07) : FAILED terminal immediat, pas de retry.
7. Impacts securite¶
| Risque | Mitigation | Composant | Journalisation |
|---|---|---|---|
Exposition rk en memoire | Zeroization immediate dans finally (buffer.fill(0) + null). Pas de log du contenu rk. | TransferOrchestratorService | Log de zeroization (sans valeur) |
| Replay/double traitement | Garde idempotence par DocumentTransfer.id + UNIQUE constraint DB | TransferOrchestratorService, entity | Log PRE-06 |
| Injection via payload job | Validation DTO stricte (class-validator + regex contractuels). Rejet format invalide. | transfer-job.dto.ts | Log PRE-07 + alerte securite |
| source_vault_id forge | Verification autorisation : source_vault_id du job == vault_id du document en base | TransferPreconditionsService | Log TC-ERR-10 + alerte securite |
| Timing attack sur tokens | timingSafeEqual() pour toute comparaison de tokens/secrets (learning PD-238) | Services de verification | n/a |
| Double-hash signature HSM | Signature : HsmService.sign(hash) (appel PKCS#11 CKM_ECDSA). Verification : crypto.verify(null, hash, key, sig) — JAMAIS createVerify() (learning PD-282). Test roundtrip sign-verify OBLIGATOIRE (learning PD-282). | TransferProofService | n/a |
| Revocation incomplete (policy transfer) | Transaction atomique : revocation + log audit dans meme TX | TransferRevocationService | Audit WORM |
| Log sensible | rk, wrap_Entreprise, secrets temporaires JAMAIS loggues. Seuls les IDs de correlation. | Tous | Politique de log |
| Concurrent replay storm | Verrouillage pessimiste (SELECT FOR UPDATE) sur DocumentTransfer par document_id | TransferOrchestratorService | TC-NEG-04 |
IDs aleatoires¶
Tous les UUIDs generes via crypto.randomUUID() (learning PD-63). Typage branded :
type DocumentTransferId = string & { readonly __brand: 'DocumentTransferId' };
type VaultId = string & { readonly __brand: 'VaultId' };
type ProofId = string & { readonly __brand: 'ProofId' };
8. Hypotheses techniques¶
| ID | Hypothese | Impact si faux | Mitigation |
|---|---|---|---|
| H-01 | Modules PD-41 (PRE), PD-37 (HSM signature), PD-39 (TSA), PD-54 (Merkle), PD-55 (anchoring), PD-31 (audit) disponibles et fonctionnels. | Blocage flux nominal. | Interfaces mockees pour tests unitaires. Stubs documentes avec story destination. |
| H-02 | employee_consent est fourni par source fiable (module auth/user). | Transfert non autorise ou blocage abusif. | TransferPreconditionsService valide le flag ; si absent/faux -> BLOCKED_WAITING_CONSENT. |
| H-03 | La verification d'identite coffres source/cible est assuree par modules existants (vault, auth). | Transfert vers mauvais coffre. | Garde source_vault_id match dans TransferPreconditionsService. |
| H-04 | Les IDs suivent UUID v4 (crypto.randomUUID()). | Non-conformite format. | Validation regex sur tous les UUIDs entrants. |
| H-05 | Queue pv:jobs:transfer est deja declaree dans queue-names.ts (PD-21). | Blocage enregistrement processor. | Ajout conditionnel si absente. |
| H-06 | Le module notifications (PD-105) expose un service injectable pour email + in-app. | Notification impossible. | // STUB: PD-105 — injection conditionnelle, fallback log. |
| H-07 | Le consentement peut etre mis a jour apres blocage (event ou polling). | BLOCKED_WAITING_CONSENT permanent. | Listener/cron pour re-evaluer les transferts bloques. |
9. Points de vigilance (risques, dette, pieges)¶
-
Zeroization fiable : JavaScript n'offre pas de garantie formelle de zeroization memoire (GC, optimisations V8). Le pattern
buffer.fill(0)+nullest le meilleur effort. Pour une garantie HSM-grade, les secrets doivent rester dans le HSM (c'est le cas deK_doc). Lerkest le seul secret temporaire manipule cote Node.js. -
Couplage modules externes : L'orchestrateur depend de 6+ modules (PRE, HSM, TSA, Merkle, anchoring, audit). Chaque module est injecte via interface NestJS. Les tests unitaires mockent chaque dependance. Les tests d'integration valident les interactions reelles.
-
Machine d'etats exhaustive : La spec definit 8 etats et ~10 transitions autorisees. Le service doit couvrir les 8x8=64 combinaisons possibles (10 autorisees, 54 interdites). Les tests TC-INV-08 et TC-NR-02 couvrent les interdictions.
-
Timeout ancrage configurable :
ANCHOR_TIMEOUT_HOURS(defaut 24h, min 1h, max 72h) via variable d'environnement. Le cron/listener de timeout doit etre resilient aux redemarrages (verification basee surcreated_aten DB, pas sur timer en memoire). -
Notification anti-spam : Deduplication par
DocumentTransfer.idpour la notification RH (BLOCKED_WAITING_CONSENT). Un seul email par transfert bloque. -
Verrouillage pessimiste : Utiliser
SELECT FOR UPDATEpour proteger contre les replays concurrents (TC-NEG-04). Attention au deadlock si plusieurs transferts pour le meme document. -
Migration DDL : Nouvelle table
vault_secure.document_transfers. Aucune modification de table existante. Les enum types PostgreSQL (transfer_status,transfer_policy) sont crees dans la migration. -
ALTER TYPE ADD VALUE : Si les enums sont ajoutes a un type existant (peu probable ici — types nouveaux), respecter le pattern
commitTransaction()avant utilisation (learning PD-282/PD-279).
10. Perimetre de test¶
| Niveau de test | In scope | Hors scope (justification) |
|---|---|---|
| Unitaire | Tous les composants : state machine, orchestrateur, preconditions, proof, revocation, notification, processor, listener | -- |
| Integration | DB + queue BullMQ : flux complet PENDING->ANCHOR_CONFIRMED, replay concurrent (TC-NEG-04), scan secrets (TC-INV-04), chaine proof complete (TC-INV-05) | -- |
| E2E | Hors scope : necessite modules PD-41/PD-37/PD-39/PD-54/PD-55 deployes et fonctionnels. Les tests d'integration avec mocks valident les contrats d'interface. | Infrastructure multi-module non disponible en CI unitaire. Couvert par integration avec mocks des interfaces. |
| Performance | TC-NR-03 : benchmark P95 latence hors batch sur jeu de charge contractuel | -- |
| Securite | TC-INV-03/04, TC-NEG-02/05 : zeroization, scan secrets, injection payload | -- |
Tous les niveaux de test intra-module sont couverts. L'E2E cross-module est reporte aux tests d'integration avec mocks des interfaces PD-41/PD-37/PD-39/PD-54/PD-55.
Couverture minimale attendue : >= 80% sur le perimetre in scope.
Framework de test : Jest (configuré dans le projet ProbatioVault-backend existant, jest.config.ts). Runner CJS-compatible. Les modules ESM-only (si presents) sont transformes via ts-jest.
Ancre temporelle timeout ancrage : Le timeout ANCHOR_TIMEOUT_HOURS est calcule depuis le timestamp de transition vers PROOF_PENDING_ANCHOR (champ updated_at de DocumentTransfer), pas depuis created_at.
11. Hors perimetre¶
- Implementation interne des primitives cryptographiques (PRE/HSM/TSA/Merkle/blockchain) — modules existants.
- UX de consentement salarie — suppose
employee_consent = TRUEau moment nominal. - Format interne du
proof_bundle.json(ProofEnvelope canonical existant). - Validation juridique externe de l'opposabilite probatoire.
- Interface REST/HTTP pour declencher un transfert — le declenchement est exclusivement par evenement interne
DOCUMENT_SEALED. - Modification des routes d'autres modules : aucune modification d'autres modules. PD-72 est un flux asynchrone interne (worker BullMQ) sans route HTTP cross-module a proteger.
12. Mecanismes cross-module¶
Aucune modification d'autres modules. PD-72 consomme les services existants via injection de dependances NestJS :
| Module consomme | Service/Interface | Usage dans PD-72 |
|---|---|---|
| crypto/pre (PD-41) | PreService.reEncrypt() | Re-chiffrement aveugle |
| audit (PD-31) | AuditService.logEvent() | Journal append-only WORM |
| crypto/hsm (PD-37) | HsmService.sign() | Signature evenement probatoire |
| tsa (PD-39) | TsaService.timestamp() | Horodatage RFC 3161 |
| merkle (PD-54) | MerkleService.include() | Inclusion Merkle batch |
| anchor (PD-55) | AnchorService.schedule() | Planification ancrage blockchain |
| notifications (PD-105) | NotificationService.send() | Notification salarie. // STUB: PD-105 si non disponible |
| documents | DocumentSecure entity (read-only) | Verification document SEALED + vault_id |