Aller au contenu

Plan d'implémentation — PD-63

Story : PD-63 — GET /documents/:id/download Date : 2026-02-20 Auteur : Claude (Orchestrateur) Version : v2 — Corrections Gate 5 v1


1. Analyse de l'existant

1.1 Base PD-46 (DONE)

L'endpoint GET /documents/:id/download est déjà implémenté par PD-46 :

Composant Fichier Statut
Controller download.controller.ts ✅ Existant
Service download.service.ts ✅ Existant
Guard (owner) document-access.guard.ts ✅ Existant (owner only)
S3 Presign s3-presign.service.ts ✅ Existant
Audit audit-download.service.ts ✅ Existant (non-bloquant)
Errors download.errors.ts ✅ Existant

1.2 Deltas PD-63 vs PD-46

PD-63 ajoute/modifie les éléments suivants :

Delta Description Impact
Δ1 Audit fail-closed (INV-63-05) Modifier download.service.ts
Δ2 ERR-63-11 (audit indisponible) Ajouter code erreur
Δ3 Flux Partage actif (H-63-07) Compléter document-access.guard.ts
Δ4 Flux B2B co-détention (H-63-07) Compléter document-access.guard.ts
Δ5 Statuts document (§3.1) Ajouter vérification statut
Δ6 Champs modèle 410/423 (5 champs) Migration + Entity update
Δ7 Schéma audit (CA-63-07) Enrichir événements audit
Δ8 Mapping ERR-46→ERR-63 Renommer/mapper codes
Δ9 Intercepteur audit global Journaliser rejets 401/403/404 précoces

2. Découpage en composants

2.1 Module Download (modifications)

src/modules/documents/download/
├── download.controller.ts      # Pas de modification
├── download.service.ts         # Δ1: fail-closed
├── download.errors.ts          # Δ2, Δ8: nouveaux codes
├── download.constants.ts       # Pas de modification
├── download.dto.ts             # Pas de modification
├── download.module.ts          # Pas de modification
├── interceptors/
│   └── audit-reject.interceptor.ts  # Δ9: journaliser rejets précoces
└── guards/
    └── document-access.guard.ts # Δ3, Δ4, Δ5: partage + B2B + statuts

2.2 Module Audit (modifications)

src/modules/documents/audit/
└── audit-download.service.ts   # Δ7: schéma enrichi

2.3 Entity DocumentSecure (modifications)

src/modules/documents/entities/
└── document-secure.entity.ts   # Δ6: 5 champs (deleted_at, deletion_reason, legal_lock, legal_lock_reason, legal_lock_until)

2.4 Migration (nouveau)

src/database/migrations/
└── YYYYMMDDHHMMSS-add-document-status-fields.ts  # Δ6 (5 champs)

2.5 Diagrammes Mermaid

Graphe de dépendances des composants

graph TD
    subgraph "Module Download"
        CTRL["download.controller.ts"]
        SVC["download.service.ts"]
        ERR["download.errors.ts"]
        GUARD["document-access.guard.ts"]
        INTCPT["audit-reject.interceptor.ts"]
    end

    subgraph "Module Audit"
        AUDIT["audit-download.service.ts"]
    end

    subgraph "Module Documents"
        ENTITY["document-secure.entity.ts"]
        MIG["migration: add-document-status-fields"]
    end

    subgraph "Externe"
        S3["s3-presign.service.ts"]
        JWT["OidcJwtAuthGuard"]
        DB[(PostgreSQL)]
    end

    CTRL -->|"@UseGuards"| JWT
    CTRL -->|"@UseGuards"| GUARD
    CTRL -->|"@UseInterceptors"| INTCPT
    CTRL --> SVC
    SVC --> S3
    SVC -->|"fail-closed Δ1"| AUDIT
    GUARD -->|"check owner/partage/B2B Δ3-Δ5"| ENTITY
    GUARD --> DB
    INTCPT -->|"log rejets 401/403/404 Δ9"| AUDIT
    SVC --> ERR
    GUARD --> ERR
    ENTITY -->|"5 champs Δ6"| MIG
    MIG --> DB

Diagramme de séquence — Téléchargement nominal (owner)

sequenceDiagram
    actor Client
    participant CTRL as download.controller
    participant JWT as OidcJwtAuthGuard
    participant GUARD as document-access.guard
    participant SVC as download.service
    participant AUDIT as audit-download.service
    participant S3 as s3-presign.service

    Client->>CTRL: GET /documents/:id/download
    CTRL->>JWT: canActivate()
    JWT-->>CTRL: ✓ JWT valide

    CTRL->>GUARD: canActivate()
    GUARD->>GUARD: check owner (PD-46)
    GUARD->>GUARD: check statut document (Δ5)
    GUARD-->>CTRL: ✓ accès autorisé

    CTRL->>SVC: getDownloadUrl(documentId, userId)
    SVC->>AUDIT: emit(DOWNLOAD_REQUESTED)
    alt Audit OK
        AUDIT-->>SVC: ✓
        SVC->>S3: getSignedUrl(GetObjectCommand, TTL=300s)
        S3-->>SVC: presignedUrl
        SVC-->>CTRL: { url, expiresIn: 300 }
        CTRL-->>Client: 200 { url, expiresIn }
    else Audit KO (fail-closed Δ1)
        AUDIT-->>SVC: ✗ erreur
        SVC-->>CTRL: ERR-63-11 (503)
        CTRL-->>Client: 503 Service Unavailable
    end

Diagramme de séquence — Rejet précoce (intercepteur Δ9)

sequenceDiagram
    actor Client
    participant INTCPT as audit-reject.interceptor
    participant JWT as OidcJwtAuthGuard
    participant AUDIT as audit-download.service

    Client->>INTCPT: GET /documents/:id/download
    INTCPT->>JWT: canActivate()
    JWT-->>INTCPT: ✗ 401 Unauthorized

    INTCPT->>AUDIT: emit(DOWNLOAD_REJECTED, { reason: "401", documentId })
    AUDIT-->>INTCPT: ✓
    INTCPT-->>Client: 401 Unauthorized

3. Mapping Invariants → Mécanismes

Invariant Mécanisme Composant Observable
INV-63-01 Backend ne lit pas le contenu S3PresignService GetObjectCommand sans read
INV-63-02 Aucune clé exposée DownloadResponseDto Inspection payload
INV-63-03 TTL 5 min DOWNLOAD_TTL_SECONDS URL expire après 300s
INV-63-04 Auth requise OidcJwtAuthGuard 401 sans JWT
INV-63-05 Audit fail-closed + rejets précoces DownloadService + AuditRejectInterceptor Δ1 + Δ9 : throw si audit échoue, log 401/403/404
INV-63-06 Révocation bloque DocumentAccessGuard Δ3/Δ4 : check révocation
INV-63-07 Multi-cloud ConfigService Même code OVH/AWS
INV-63-08 Droits explicites DocumentAccessGuard Δ3/Δ4 : check explicite
INV-63-09 Pas d'altération WORM Aucune opération PUT Vérifiable avant/après
INV-63-10 Erreurs neutres DownloadError Messages redacted

4. Mapping Critères → Tests

CA Mécanisme Test(s)
CA-63-01 DownloadService.getDownloadUrl() TC-INT-01
CA-63-02 OidcJwtAuthGuard TC-INT-04
CA-63-03 DocumentAccessGuard TC-ERR-02, TC-INT-06
CA-63-04 DocumentAccessGuard (révocation) TC-ERR-03, TC-INT-05
CA-63-05 TTL constant TC-INT-15, TC-E2E-05
CA-63-06 Inspection payload/logs TC-SEC-01, TC-SEC-16
CA-63-07 AuditDownloadService + AuditRejectInterceptor TC-NOM-06, TC-INT-14, ST-63-12
CA-63-08 Pas d'opération PUT TC-INT-13
CA-63-09 DocumentAccessGuard (3 flux) TC-NOM-02/03/04
CA-63-10 Config multi-provider TC-INT-11/12

4b. Mapping ERR → Mécanismes/Tâches

Code erreur Composant Tâche(s) Test(s)
ERR-63-01 (400) Validation DTO (existant) - TC-ERR-01
ERR-63-02 (401) OidcJwtAuthGuard (existant) Tâche 15 (intercepteur) TC-INT-04
ERR-63-03 (403) DocumentAccessGuard Tâches 6-8 TC-ERR-02, TC-INT-06
ERR-63-04 (403) DocumentAccessGuard (révocation) Tâches 6-8 TC-ERR-03, TC-INT-05
ERR-63-05 (404) Query DB (existant) Tâche 15 (intercepteur) TC-INT-06
ERR-63-06 (410) DocumentAccessGuard (statut) Tâche 8 TC-INT-07
ERR-63-07 (423) DocumentAccessGuard (legal_lock) Tâche 8 TC-INT-08
ERR-63-08 (429) Rate limiter global (optionnel) Hors périmètre -
ERR-63-09 (500) Exception filter NestJS (existant) - TC-INT-10
ERR-63-10 (503) S3PresignService (existant) - TC-INT-09
ERR-63-11 (503) DownloadService (fail-closed) Tâche 12 TC-INT-14

5. Plan de tâches

Phase 0 — Vérifications préalables (go/no-go)

# Tâche Agent Critère go/no-go Action si no-go
0a Vérifier table document_shares agent-developer Table existe Créer ticket PD-XXX
0b Vérifier claims JWT tenant_id/roles agent-developer Claims présents Ticket config Keycloak
0c Vérifier RLS vault_secure.documents agent-developer RLS active Documenter migration requise
0d Vérifier schéma audit enrichi agent-developer Service compatible Adapter AuditService

Règle : Si un critère échoue, escalader avant Phase 1. Ne pas bloquer l'implémentation sans décision explicite.

Phase 1 — Migration et Entity (Δ6)

# Tâche Agent Fichiers Dépend de
1 Créer migration add-document-status-fields (5 champs) agent-developer migrations/*.ts 0a-0d
2 Ajouter champs Entity DocumentSecure agent-developer document-secure.entity.ts 1
3 Tests unitaires Entity agent-qa-unit-integration document-secure.entity.spec.ts 2

Champs migration : - deleted_at TIMESTAMP NULL - deletion_reason VARCHAR(50) NULL - legal_lock BOOLEAN DEFAULT false - legal_lock_reason VARCHAR(50) NULL - legal_lock_until TIMESTAMP NULL

Phase 2 — Codes d'erreur (Δ2, Δ8)

# Tâche Agent Fichiers Dépend de
4 Ajouter ERR-63-11 + mapping ERR-46→63 agent-developer download.errors.ts -
5 Tests codes d'erreur agent-qa-unit-integration download.errors.spec.ts 4

Phase 3 — Guard enrichi (Δ3, Δ4, Δ5)

# Tâche Agent Fichiers Dépend de
6 Implémenter check partage actif agent-developer document-access.guard.ts 2
7 Implémenter check B2B co-détention agent-developer document-access.guard.ts 6
8 Implémenter check statut document agent-developer document-access.guard.ts 7
9 Tests unitaires guard agent-qa-unit-integration document-access.guard.spec.ts 8

Note tâche 8 : Pour statut ARCHIVED, vérifier accessibilité S3 via HEAD Object. Si non accessible (Glacier non restauré), retourner ERR-63-10 (503).

Phase 4 — Audit fail-closed (Δ1, Δ7)

# Tâche Agent Fichiers Dépend de
10 Enrichir schéma événement audit agent-developer audit-download.service.ts -
11 Implémenter fail-closed dans service agent-developer download.service.ts 10
12 Tests fail-closed agent-qa-unit-integration download.service.spec.ts 11

Phase 5 — Intercepteur audit global (Δ9)

# Tâche Agent Fichiers Dépend de
13 Créer AuditRejectInterceptor agent-developer audit-reject.interceptor.ts 10
14 Enregistrer intercepteur sur controller agent-developer download.controller.ts 13
15 Tests intercepteur (401/403/404) agent-qa-unit-integration audit-reject.interceptor.spec.ts 14

Phase 6 — Tests d'intégration

# Tâche Agent Fichiers Dépend de
16 Tests intégration endpoint agent-qa-unit-integration download.controller.e2e-spec.ts 9, 12, 15

Total : 16 tâches (+ 4 vérifications Phase 0)


6. Code Contracts

Voir fichier séparé PD-63-code-contracts.yaml.


7. Contraintes techniques

7.1 Dépendances inter-PD

Story Statut Nature
PD-46 DONE Base de l'endpoint — réutilisation complète
PD-37 DONE HSM audit signature — H-63-03
PD-31 DONE Pattern audit — réutilisation
PD-60 DONE Endpoint miroir (upload)

7.2 Framework de test

  • Runner : Jest (existant)
  • Tests d'intégration : Avec services réels (PostgreSQL, Redis)
  • Mocks : S3 via mock-aws-sdk
  • Environnement CI : DATABASE_URL, REDIS_URL, S3_* vars

7.3 Compatibilité ESM/CJS

  • Projet en CommonJS (pas d'ESM)
  • Pas de dépendance ESM-only identifiée

8. Hypothèses d'implémentation

ID Hypothèse Impact si faux Vérification (Phase 0)
H-IMPL-01 Table document_shares existe pour partage actif Créer table/entity si absente Tâche 0a
H-IMPL-02 Claims JWT tenant_id et roles disponibles (B2B) Configurer Keycloak si absent Tâche 0b
H-IMPL-03 RLS active sur vault_secure.documents Vérifier migration RLS Tâche 0c
H-IMPL-04 Service audit supporte schéma enrichi Adapter AuditService si nécessaire Tâche 0d

9. Points de vigilance

# Point Risque Mitigation
1 Audit fail-closed peut bloquer téléchargements Service audit indisponible → 503 pour tous Infrastructure : Haute disponibilité service audit (réplication, health checks). Applicatif : Fail-closed strict, pas de retry applicatif — conformité INV-63-05.
2 Partage/B2B = TODO dans guard actuel Régression possible Tests exhaustifs (Phase 3)
3 Champs modèle (5 champs) = migration Downtime potentiel Migration non-bloquante (ajout colonnes NULL)
4 Timing attack (TC-SEC-12) Oracle d'énumération Latence constante
5 Rejets précoces non journalisés Non-conformité traçabilité Intercepteur audit global (Phase 5)

10. Estimation

Phase Tâches Complexité
0 — Vérifications 4 Faible
1 — Migration/Entity 3 Faible
2 — Codes d'erreur 2 Faible
3 — Guard enrichi 4 Moyenne
4 — Audit fail-closed 3 Moyenne
5 — Intercepteur audit 3 Moyenne
6 — Tests intégration 1 Moyenne
Total 20 Moyenne

Généré par : Claude (Orchestrateur) Date : 2026-02-20 Version : v2 — Corrections Gate 5 v1

Corrections appliquées (Gate 5 v1)

Écart Correction
ECT-01 Ajout Phase 5 — Intercepteur audit global (Δ9) pour journaliser rejets 401/403/404
ECT-02 Migration complétée : 5 champs (pas 2)
AMB-01 Note tâche 8 : ARCHIVED → HEAD Object → 503
ECT-03 Ajout section 4b : Mapping ERR → Mécanismes/Tâches
SEC-01 Point de vigilance 1 reformulé : distinction infra vs appli
AMB-02 Ajout Phase 0 — Vérifications préalables (go/no-go)