Aller au contenu

PD-47 — Décomposition en tâches agents

1. Matrice des dépendances

ID Agent Description Produit Consomme Dépend de Niveau
T1 agent-developer Entities, Types partagés, Migration TypeORM backup-execution.entity.ts, backup-journal-event.entity.ts, types/index.ts, migration [] 0
T2 agent-developer Services utilitaires (Validation + Config + Hash) backup-validation.service.ts, backup-config.service.ts, backup-hash.service.ts [] 0
T3 agent-developer Terraform KMS + IAM + S3 kms-backup.tf, iam-backup.tf, storage-aws-core.tf (modification) [] 0
T4 agent-developer Ansible backup provisioning postgresql_backup/ role complet [] 0
T5 agent-developer BackupStateService (machine d'états §5.7) backup-state.service.ts Entities (T1) [T1] 1
T6 agent-developer BackupEncryptionService (AES-256-GCM + Vault Transit) backup-encryption.service.ts Types (T1), Config (T2) [T1, T2] 1
T7 agent-developer BackupJournalService (append-only + dédup) backup-journal.service.ts Entities (T1), Validation (T2) [T1, T2] 1
T8 agent-developer BackupS3Service (upload SSE-KMS + vérification SHA3-256) backup-s3.service.ts Types (T1), Config (T2), Encryption (T6), Hash (T2) [T1, T2, T6] 1
T9 agent-developer BackupOrchestratorService (flux F1/F3, retry, circuit-breaker) backup-orchestrator.service.ts State (T5), Encryption (T6), S3 (T8), Hash (T2), Journal (T7), Validation (T2), Config (T2) [T5, T6, T7, T8] 2
T10 agent-developer BackupWatchdogService + BackupRelaunchConsumer backup-watchdog.service.ts, backup-relaunch.consumer.ts S3 (T8), Config (T2), AlertService (existant) [T8] 2
T11 agent-developer BackupReconciliationService + BackupRestorationService backup-reconciliation.service.ts, backup-restoration.service.ts State (T5), S3 (T8), Journal (T7), Encryption (T6), Hash (T2) [T5, T6, T7, T8] 2
T12 agent-developer BackupModule (wiring DI, cron, BullMQ) backup.module.ts, index.ts Tous services (T5-T11) [T9, T10, T11] 3
T13 agent-developer Tests unitaires + intégration (TC-NOM/ERR/INV/NEG/NR) src/backup/__tests__/**/*.spec.ts Tous services + module (T1-T12) [T12] 3

2. Bloc parallelization

parallelization:
  strategy: by_level
  levels:
    - level: 0
      tasks: [1, 2, 3, 4]
      agents: [agent-developer, agent-developer, agent-developer, agent-developer]
      branches:
        - "feature/PD-47-l0-entities-migration"
        - "feature/PD-47-l0-utility-services"
        - "feature/PD-47-l0-terraform-infra"
        - "feature/PD-47-l0-ansible-infra"
      estimated_time: "2h"
    - level: 1
      tasks: [5, 6, 7, 8]
      agents: [agent-developer, agent-developer, agent-developer, agent-developer]
      branches:
        - "feature/PD-47-l1-state-service"
        - "feature/PD-47-l1-encryption-service"
        - "feature/PD-47-l1-journal-service"
        - "feature/PD-47-l1-s3-service"
      estimated_time: "2h"
    - level: 2
      tasks: [9, 10, 11]
      agents: [agent-developer, agent-developer, agent-developer]
      branches:
        - "feature/PD-47-l2-orchestrator"
        - "feature/PD-47-l2-watchdog-relaunch"
        - "feature/PD-47-l2-reconciliation-restoration"
      estimated_time: "3h"
    - level: 3
      tasks: [12, 13]
      agents: [agent-developer, agent-developer]
      branches:
        - "feature/PD-47-l3-module-wiring"
        - "feature/PD-47-l3-tests"
      estimated_time: "4h"
  total_sequential_time: "25.5h"
  total_parallel_time: "11h"
  speedup_factor: 2.32
  git_strategy: branch_per_level

3. Détail de chaque tâche

Tâche 1 — Entities, Types partagés et Migration TypeORM

  • Agent : agent-developer
  • Niveau : 0
  • Dépend de : []
  • Contracts : CC-47-02 (entity), CC-47-06 (entity), CC-47-13
  • Projet : backend
  • Fichiers :
  • src/backup/entities/backup-execution.entity.ts
  • src/backup/entities/backup-journal-event.entity.ts
  • src/backup/types/index.ts
  • src/migrations/XXXXXXXXXX-create-backup-tables.ts
  • Description : Produire les deux entities TypeORM (BackupExecution, BackupJournalEvent) avec tous les champs définis dans CC-47-02 et CC-47-06, les enums partagés (BackupStatus, BackupType), les DTOs et interfaces (EncryptedPayload, ValidationResult, BackupExecutionResult, WatchdogResult, ReconciliationResult, RestorationResult, BackupConfig, S3BackupMetadata, S3UploadResult, BackupJournalEventDto), et la migration TypeORM complète.

Migration : créer les tables backup_executions et backup_journal_events avec : - Contrainte CHECK sur status enum - Index UNIQUE sur backup_id seul dans journal (pas backup_id + event_type) [CORR M-03] - Colonnes wrapped_dek, iv, auth_tag dans backup_executions [CORR B-03] - RLS policy INSERT-only sur backup_journal_events - Ne JAMAIS utiliser ALTER TYPE ADD VALUE dans la même transaction que WHERE (cf. PD-282)

Decision trace : Les types partagés sont extraits dans src/backup/types/index.ts plutôt que co-localisés dans chaque service. Cela permet à tous les agents de niveaux suivants d'importer depuis un point unique, réduisant les risques d'incompatibilité d'interface au merge.


Tâche 2 — Services utilitaires (Validation + Config + Hash)

  • Agent : agent-developer
  • Niveau : 0
  • Dépend de : []
  • Contracts : CC-47-09, CC-47-10, CC-47-05
  • Projet : backend
  • Fichiers :
  • src/backup/services/backup-validation.service.ts
  • src/backup/services/backup-config.service.ts
  • src/backup/services/backup-hash.service.ts
  • Description : Trois services purement fonctionnels sans dépendance sur les entities ou autres services PD-47 :

BackupValidationService (CC-47-09) : validation regex/type/bornes pour tous les champs §5.1 (backup_id, hash_sha3_256, s3_object_key_backup, s3_object_key_wal, size_bytes, timestamp). Rejet avec code FAILED_FORMAT (hors enum status). Valider size_bytes <= Number.MAX_SAFE_INTEGER même si regex OK (cf. R-05 spec review).

BackupConfigService (CC-47-10) : chargement config depuis env/Vault. Distinction stricte rejet vs clamp selon §5.8 colonne "Hors bornes" [CORR M-02]. Validation croisée delay_initial < 100 && factor <= 1 → warning (cf. R-08). Les constantes non configurables (cleartextTtlSeconds=300, restorationPeriodicityDays=90) sont en dur.

BackupHashService (CC-47-05) : calcul SHA3-256 via crypto.createHash('sha3-256'). Hash TOUJOURS sur le clair compressé AVANT chiffrement (cohérence F1/F2/F3, cf. R-02 spec review). Ne JAMAIS utiliser SHA-256 à la place de SHA3-256.

Decision trace : Regroupement de 3 services petits et indépendants en une seule tâche pour optimiser le ratio overhead/production. Alternative : 3 tâches séparées (rejetée — speedup marginal, overhead de merge accru).


Tâche 3 — Terraform KMS + IAM + S3

  • Agent : agent-developer
  • Niveau : 0
  • Dépend de : []
  • Contract : CC-47-15
  • Projet : infra
  • Fichiers :
  • terraform/kms-backup.tf (nouveau)
  • terraform/iam-backup.tf (nouveau)
  • terraform/storage-aws-core.tf (modification)
  • Description : Provisionner l'infrastructure AWS pour le backup :

kms-backup.tf : clé KMS symétrique dédiée backup (aws_kms_key.backup_key), rotation automatique activée, alias alias/pv-backup. Séparée de la clé maître existante.

iam-backup.tf : user IAM backup_user isolé du backend user. Policy : s3:PutObject, s3:GetObject, s3:ListBucket, s3:HeadObject uniquement sur le bucket backups. Policy kms:Encrypt, kms:Decrypt, kms:GenerateDataKey uniquement sur la clé backup. Aucun accès cross-bucket. Access key stockée dans Vault (kv/data/ci/aws-backup).

storage-aws-core.tf (modification) : upgrade SSE du bucket backups de AES256 vers aws:kms avec SSEKMSKeyId pointant vers la clé backup. Ne PAS modifier le block_public_acls existant. Ne PAS modifier le lifecycle 30 jours existant. Ajouter bucket policy HTTPS-only si absente.

Decision trace : User IAM dédié plutôt que rôle IAM. Alternative considérée : IAM role avec assume-role (rejetée — le backend tourne sur VPS OVH, pas sur EC2/ECS, donc pas de metadata service pour STS).


Tâche 4 — Ansible Backup Provisioning

  • Agent : agent-developer
  • Niveau : 0
  • Dépend de : []
  • Contract : CC-47-16
  • Projet : infra
  • Fichiers :
  • ansible/roles/postgresql_backup/tasks/main.yml
  • ansible/roles/postgresql_backup/templates/pg-receivewal.service.j2
  • ansible/roles/postgresql_backup/templates/watchdog-check.sh.j2
  • ansible/roles/postgresql_backup/defaults/main.yml
  • ansible/roles/postgresql_backup/handlers/main.yml
  • Description : Nouveau rôle Ansible postgresql_backup :

tasks/main.yml : installation postgresql-client-15 (pg_dump, pg_basebackup, pg_receivewal), déploiement service systemd, déploiement cron watchdog fallback.

pg-receivewal.service.j2 : unit systemd pour pg_receivewal --slot=pv_backup --directory=/var/wal-archive/. Restart=always, After=network-online.target. Connection string récupérée depuis Vault (pas en clair dans le template).

watchdog-check.sh.j2 : script shell fallback indépendant du process NestJS. Vérifie aws s3 ls s3://{{ bucket }}/YYYY/MM/ pour la présence du backup du jour. Si absent → alerte via webhook/email. Ce cron Ansible est la défense en profondeur pour INV-47-06.

defaults/main.yml : variables par défaut (slot name, wal directory, bucket name, watchdog cron expression). Aucun secret en clair — références Vault uniquement.


Tâche 5 — BackupStateService

  • Agent : agent-developer
  • Niveau : 1
  • Dépend de : [T1]
  • Contract : CC-47-02 (service)
  • Projet : backend
  • Fichiers :
  • src/backup/services/backup-state.service.ts
  • Description : Machine d'états stricte §5.7. Table de transitions autorisées en constante :
SCHEDULED → RUNNING
RUNNING → SUCCESS
RUNNING → FAILED
SUCCESS → EXPIRED
EXPIRED → DELETED
FAILED → SCHEDULED (si replanificationCount < max)

Toute transition hors table → throw BackupStateTransitionError + journalisation du refus (from, to, backupId, timestamp).

Méthode create() : crée une BackupExecution en état SCHEDULED. Méthode transition() : valide la transition, met à jour le status, incrémente replanificationCount si FAILED→SCHEDULED, sauvegarde via TypeORM repository.

FAILED_FORMAT n'est PAS un état — ne doit JAMAIS apparaître dans BackupStatus.

Circuit-breaker : transition FAILED→SCHEDULED refusée si replanificationCount >= maxReplanificationCycles (DI-01).


Tâche 6 — BackupEncryptionService

  • Agent : agent-developer
  • Niveau : 1
  • Dépend de : [T1, T2]
  • Contract : CC-47-03
  • Projet : backend
  • Fichiers :
  • src/backup/services/backup-encryption.service.ts
  • Description : Envelope encryption AES-256-GCM avec Vault Transit :

encrypt() : 1. Générer DEK : crypto.randomBytes(32) 2. Générer IV : crypto.randomBytes(12) 3. Chiffrer : crypto.createCipheriv('aes-256-gcm', dek, iv) 4. Wrap DEK via Vault Transit (transit/encrypt/pv-backup) 5. Zéroïser DEK : dekBuffer.fill(0) immédiatement après usage 6. Retourner {ciphertext, iv, authTag, wrappedDek, kBackupVersion}

decrypt() : 1. Unwrap DEK via Vault Transit (transit/decrypt/pv-backup, version spécifiée) 2. Déchiffrer : crypto.createDecipheriv('aes-256-gcm', dek, iv) 3. Vérifier authTag 4. Zéroïser DEK

Fail-closed [CORR M-05] : si Vault Transit est indisponible, throw BACKUP_ENCRYPTION_FAILED. Aucun fallback HKDF local.

Ne JAMAIS réutiliser une DEK entre artefacts. Ne JAMAIS réutiliser un IV avec la même clé. Ne JAMAIS utiliser crypto.createCipher (deprecated). Ne JAMAIS utiliser createVerify() — utiliser crypto.verify(null, ...) si signature (cf. PD-282).


Tâche 7 — BackupJournalService

  • Agent : agent-developer
  • Niveau : 1
  • Dépend de : [T1, T2]
  • Contract : CC-47-06 (service)
  • Projet : backend
  • Fichiers :
  • src/backup/services/backup-journal.service.ts
  • Description : Journal append-only conforme INV-47-05 :

writeEvent() : 1. Valider tous les champs via BackupValidationService (§5.1) 2. INSERT avec ON CONFLICT (backup_id) DO NOTHING [CORR M-03] 3. Aucun UPDATE, aucun DELETE (garanti par RLS au niveau DB)

getEvents() : lecture seule par backup_id.

Le service est consommé par l'orchestrateur (flux F1/F3), le watchdog (indirectement via journalisation d'alerte), la réconciliation, et la restauration.

La déduplication est sur backup_id seul (pas backup_id + event_type), alignée sur la spec INV-47-05.


Tâche 8 — BackupS3Service

  • Agent : agent-developer
  • Niveau : 1
  • Dépend de : [T1, T2, T6]
  • Contract : CC-47-04
  • Projet : backend
  • Fichiers :
  • src/backup/services/backup-s3.service.ts
  • Description : Interface S3 avec SSE-KMS et vérification d'intégrité :

upload() : PutObject avec ServerSideEncryption: 'aws:kms' + SSEKMSKeyId. Metadata S3 : wrapped_dek, iv, auth_tag, k_backup_version en copie redondante [CORR B-03]. Multipart pour artefacts > 100 MB. Credentials du backup IAM user dédié (pas du backend user).

verifyPostUpload() [CORR M-04] : download l'objet, déchiffre via BackupEncryptionService.decrypt(), recalcule SHA3-256 via BackupHashService.computeSHA3_256(), compare avec expectedHash. Ne JAMAIS se fier à l'ETag S3 (MD5/multipart, non lié à SHA3-256).

checkObjectExists() : HeadObject pour le watchdog.

generateBackupKey() : génère la clé S3 conforme aux patterns §5.1 (YYYY/MM/backup_YYYYMMDD.enc pour pg_dump/basebackup, wal/{WAL_SEGMENT_NAME}.enc pour WAL).

Decision trace : injection de BackupEncryptionService dans S3Service pour verifyPostUpload. Alternative : déplacer la vérification dans l'orchestrateur (rejetée — le contrat CC-47-04 attribue cette responsabilité à S3Service).


Tâche 9 — BackupOrchestratorService

  • Agent : agent-developer
  • Niveau : 2
  • Dépend de : [T5, T6, T7, T8]
  • Contract : CC-47-01
  • Projet : backend
  • Fichiers :
  • src/backup/services/backup-orchestrator.service.ts
  • Description : Service le plus complexe — orchestre les flux F1 et F3 :

runDailyBackup() (flux F1, cron 03:00 Europe/Paris) : Séquence complète en 12 étapes (cf. plan §2.1). Wrappé dans try/finally garantissant au minimum un événement journal [CORR M-06]. Timer cleartext strict : Date.now() - exportEndTs < 300_000 (INV-47-01).

runWeeklyBasebackup() (flux F3, cron dimanche 02:00) : Identique à F1 sauf commande (pg_basebackup -D /tmp/basebackup -Ft -z), type (basebackup), et cron.

replanify() : transition FAILED→SCHEDULED avec vérification du circuit-breaker max_replanification_cycles [CORR M-01]. Si compteur atteint → refus + escalade critique.

Gestion des erreurs : - Échec export (ERR-47-01) : retry max 3, backoff exponentiel delay = min(delay_initial × factor^attempt, timeout_upload) - Échec chiffrement (ERR-47-02) : abort immédiat, purge, alerte - Échec upload (ERR-47-03) : retry dans la même tentative (pas de FAILED→SCHEDULED), puis FAILED - Hash invalide post-upload (ERR-47-04) : objet marqué invalide

child_process.spawn pour pg_dump/pg_basebackup. Ne JAMAIS utiliser Math.random()crypto.randomUUID() pour tout identifiant.


Tâche 10 — BackupWatchdogService + BackupRelaunchConsumer

  • Agent : agent-developer
  • Niveau : 2
  • Dépend de : [T8]
  • Contracts : CC-47-07, CC-47-07b
  • Projet : backend
  • Fichiers :
  • src/backup/services/backup-watchdog.service.ts
  • src/backup/consumers/backup-relaunch.consumer.ts
  • Description : BackupWatchdogService (CC-47-07) : Cron 04:00 Europe/Paris. Vérifie la présence de backup_YYYYMMDD.enc en S3 via BackupS3Service.checkObjectExists(). Si absent :
  • Émettre alerte via AlertService (existant)
  • Envoyer job dans queue BullMQ backup-relaunch [CORR B-02]
  • Attendre le résultat du job (timeout configurable)
  • Si relance échoue → escalade critique obligatoire

Indépendance totale : ne JAMAIS injecter BackupOrchestratorService. Ne JAMAIS lire backup_executions. Lire S3 directement. Le watchdog est un producteur de queue, pas un appelant direct.

BackupRelaunchConsumer (CC-47-07b) : Consommateur BullMQ pour la queue backup-relaunch. Injecte BackupOrchestratorService, appelle runDailyBackup(). Si la relance échoue, émet escalade critique via AlertService. Ce consumer est le SEUL pont entre watchdog et orchestrateur (découplage CC-07).


Tâche 11 — BackupReconciliationService + BackupRestorationService

  • Agent : agent-developer
  • Niveau : 2
  • Dépend de : [T5, T6, T7, T8]
  • Contracts : CC-47-08, CC-47-11
  • Projet : backend
  • Fichiers :
  • src/backup/services/backup-reconciliation.service.ts
  • src/backup/services/backup-restoration.service.ts
  • Description : BackupReconciliationService (CC-47-08) : Implémente OnApplicationBootstrap. Au démarrage :
  • Chercher BackupExecution en état SCHEDULED ou RUNNING [CORR M-07] (pas uniquement RUNNING)
  • Pour RUNNING : vérifier présence objet S3. Si absent → transition vers FAILED + journal
  • Pour SCHEDULED : vérifier timestamp dépassé. Si timeout → transition vers FAILED + journal
  • Journaliser chaque réconciliation (observable — trace/log/ événement d'audit) [CORR E-09]
  • Ne JAMAIS créer un état SUCCESS si l'objet S3 est absent

BackupRestorationService (CC-47-11) : Flux F4 en 10 étapes (cf. plan §2.4) : 1. Sélection du backup logique le plus récent avant targetTimestamp 2. Download + decrypt 3. Vérification hash SHA3-256 AVANT pg_restore (INV-47-08) 4. pg_restore en staging 5. Download + decrypt WAL segments intermédiaires 6. Rejouage WAL (recovery_target_time) 7. Validation cohérence schéma/données (jeu contractuel minimal) 8. Enregistrement preuve 9. Si échec → ouverture incident conformité [CORR B-01], clôture bloquée sans plan d'action


Tâche 12 — BackupModule (wiring DI)

  • Agent : agent-developer
  • Niveau : 3
  • Dépend de : [T9, T10, T11]
  • Contract : CC-47-12
  • Projet : backend
  • Fichiers :
  • src/backup/backup.module.ts
  • src/backup/index.ts
  • Description : Module NestJS @Module avec :
  • imports : TypeOrmModule.forFeature([BackupExecution, BackupJournalEvent]), ScheduleModule, BullModule.registerQueue({ name: 'backup-relaunch' }) [CORR B-02]
  • providers : tous les services CC-47-01 à CC-47-11 + BackupRelaunchConsumer
  • exports : services nécessaires aux consommateurs externes (si applicable)

Le fichier index.ts réexporte les types publics du module.

Enregistrement dans AppModule : ajouter BackupModule aux imports de app.module.ts (modification d'un fichier existant hors périmètre backup — à coordonner avec l'orchestrateur).


Tâche 13 — Tests unitaires + intégration

  • Agent : agent-developer
  • Niveau : 3
  • Dépend de : [T12]
  • Contract : CC-47-14
  • Projet : backend
  • Fichiers :
  • src/backup/__tests__/backup-validation.service.spec.ts
  • src/backup/__tests__/backup-config.service.spec.ts
  • src/backup/__tests__/backup-hash.service.spec.ts
  • src/backup/__tests__/backup-state.service.spec.ts
  • src/backup/__tests__/backup-encryption.service.spec.ts
  • src/backup/__tests__/backup-journal.service.spec.ts
  • src/backup/__tests__/backup-s3.service.spec.ts
  • src/backup/__tests__/backup-orchestrator.service.spec.ts
  • src/backup/__tests__/backup-watchdog.service.spec.ts
  • src/backup/__tests__/backup-reconciliation.service.spec.ts
  • src/backup/__tests__/backup-restoration.service.spec.ts
  • src/backup/__tests__/backup-integration.spec.ts
  • Description : Test runner : Jest [CORR M-08].

Tests unitaires : chaque service reçoit ses propres tests.

Couverture contractuelle : - TC-NOM-01 à TC-NOM-08 (flux nominaux) - TC-ERR-01 à TC-ERR-08 (cas d'erreur) - TC-INV-01 à TC-INV-09 (invariants — GIVEN/WHEN/THEN formalisés dans le plan §5.3 suite à R-03) - TC-NEG-01 à TC-NEG-08 (négatifs/adversariaux) - TC-NR-01 à TC-NR-05 (non-régression)

Tests critiques : - Roundtrip encrypt-decrypt obligatoire (JAMAIS tester encrypt et decrypt isolément — cf. PD-282) - State machine exhaustive : 6×6 = 36 combinaisons de transitions - Journal append-only : tentative UPDATE → rejet RLS - Déduplication backup_id : insertion doublon → DO NOTHING - Timer cleartext < 300s - Circuit-breaker replanification (max cycles atteint)

Prérequis : - TC-NOM-02 (WAL) : conditionné à H-TECH-01. Si slot de réplication OVH non confirmé → SKIP avec justification [CORR m-04] - Ne JAMAIS mocker la validation §5.1 dans les tests d'intégration - Ne JAMAIS utiliser Math.random()crypto.randomUUID()

Mocks : - S3 : localstack ou mock @aws-sdk/client-s3 - Vault Transit : mock HTTP des endpoints encrypt/decrypt - pg_dump/pg_basebackup : mock child_process - BullMQ : @golevelup/ts-jest mock queue - AlertService : mock existant

4. Résumé d'exécution

Métrique Valeur
Total tâches 13
Niveaux de parallélisation 4
Temps séquentiel estimé 25.5h
Temps parallèle estimé 11h
Speedup factor 2.32x
Stratégie Git branch_per_level
Projets impactés backend (T1-T2, T5-T13), infra (T3-T4)
Tâches backend 11
Tâches infra 2
Contracts couverts CC-47-01 à CC-47-16 (16/16)

Détail par niveau

Niveau Tâches Durée parallèle Branches
0 T1, T2, T3, T4 2h 4 branches (2 backend + 2 infra)
1 T5, T6, T7, T8 2h 4 branches backend
2 T9, T10, T11 3h 3 branches backend
3 T12, T13 4h 2 branches backend

Risques d'intégration au merge

Risque Probabilité Mitigation
Types incompatibles entre services Faible Types centralisés dans T1, contracts font référence
Conflit import paths après merge L0 Faible Chaque tâche produit des fichiers distincts
Conflit app.module.ts (T12) Moyen T12 modifie un fichier partagé — merge manuel si conflit
Tests échouent après merge complet Moyen Exécution Jest post-merge avant acceptabilité