PD-47 — Rapport de confrontation (Gate 5 — v2)¶
Ce rapport est produit par l'orchestrateur Claude avant la Gate 5 (review plan). Il confronte les documents produits pour identifier convergences, divergences et zones d'ombre. v2 : intègre les corrections Gate 5 v1 (3 BLOQUANTS, 9 MAJEURS, 4 MINEURS).
1. Sources confrontées¶
| Document | Étape | Version |
|---|---|---|
PD-47-specification.md | 1 (Spécification, post Gate 3 v2) | Avec corrections CORR E-01 à E-18 |
PD-47-tests.md | 2 (Tests, post Gate 3 v2) | Avec corrections CORR E-* alignées |
PD-47-plan.md | 4 (Plan v2) | Avec corrections B-01/02/03, M-01 à M-09, m-01 à m-04 |
PD-47-code-contracts.yaml | 4 (Code Contracts v2.0) | Avec corrections registry Gate 5 v1 |
Divergences v1 résolues¶
Les 3 divergences de la confrontation v1 ont été corrigées dans le plan v2 :
| Ancienne DIV | Résumé | Correction |
|---|---|---|
| DIV-01 (watchdog relance) | Mécanisme de relance non défini, indépendance violée | CORR B-02 : queue BullMQ backup-relaunch, consumer séparé |
| DIV-02 (wrapped DEK stockage) | Ambiguïté DB vs S3 metadata | CORR B-03 : DB source de vérité, S3 metadata copie redondante |
| DIV-03 (WAL conditionnalité) | TC-NOM-02 inconditionnel malgré H-TECH-01 | CORR m-04 : prérequis H-TECH-01 explicite, SKIP si non levée |
2. Convergences¶
Les 4 documents sont fortement alignés après les 16 corrections Gate 5 v1. Points de convergence :
-
C-01 — Machine d'états §5.7 : 6 états (SCHEDULED, RUNNING, SUCCESS, FAILED, EXPIRED, DELETED) et transitions autorisées/interdites identiques dans spec §5.7, tests TC-INV-07/TC-NEG-04/05, plan MOD-03 BackupStateService, contracts CC-47-02.
FAILED_FORMATexclu de l'enum status (spec [CORR E-02], tests TC-ERR-07, plan §9.2, contracts CC-47-02/CC-47-09). -
C-02 — Double chiffrement AES-256-GCM + SSE-KMS : spec INV-47-02, tests TC-INV-02/TC-NOM-01/02/03, plan MOD-04+MOD-05+MOD-15, contracts CC-47-03+CC-47-04+CC-47-15. Deux couches systématiquement exigées.
-
C-03 — Cleartext TTL 300s non configurable : spec INV-47-01 + §5.8, tests TC-INV-01, plan MOD-02 timer strict, contracts CC-47-01 forbidden + CC-47-10
cleartextTtlSeconds: 300. -
C-04 — Déduplication journal par backup_id seul : spec INV-47-05 [CORR E-04], tests TC-INV-05 + TC-NEG-07, plan MOD-07 [CORR M-03] (UPSERT ON CONFLICT DO NOTHING), contracts CC-47-06 + CC-47-13 (UNIQUE sur backup_id seul).
-
C-05 — Watchdog indépendant via BullMQ : spec INV-47-06 + ERR-47-05, tests TC-INV-06 + TC-NOM-05, plan MOD-08 [CORR B-02] (pas d'injection orchestrateur) + CC-47-07b (BackupRelaunchConsumer). Découplage via queue confirmé dans tous les documents.
-
C-06 — Hash SHA3-256 sur cleartext compressé AVANT chiffrement : spec §5.3 [CORR E-12] + INV-47-08, tests TC-INV-08 + TC-NOM-02/04, plan MOD-06 + §9.2 point 4, contracts CC-47-05 forbidden. Cohérence F1/F2/F3.
-
C-07 — Envelope encryption / DEK lifecycle : spec INV-47-09 + §10.4 [CORR E-18], tests TC-INV-09, plan MOD-04 (zéroïsation explicite) + [CORR B-03] (DB source de vérité, wrappedDek + iv + authTag dans BackupExecution), contracts CC-47-02 + CC-47-03.
-
C-08 — Fail-closed si Vault indisponible : plan [CORR M-05], contracts CC-47-03 forbidden. Aucun fallback HKDF. Cohérent avec INV-47-03/09.
-
C-09 — Vérification post-upload SHA3-256 : plan [CORR M-04] (download + decrypt + recalcul), contracts CC-47-04
verifyPostUpload()+ forbidden. Cohérent avec spec ERR-47-04 et INV-47-08. -
C-10 — try/finally garantissant événement journal : plan [CORR M-06], contracts CC-47-01 forbidden. Cohérent avec spec INV-47-05 (>= 1 événement par tentative).
-
C-11 — Réconciliation étendue SCHEDULED + RUNNING : plan [CORR M-07], contracts CC-47-08 forbidden + description. Extension documentée de spec §5.6.
-
C-12 — Circuit-breaker replanification max 3 : plan [CORR M-01], contracts CC-47-01 + CC-47-02 + CC-47-10. Décision d'implémentation explicite.
-
C-13 — Retry vs replanification : spec [CORR E-11], tests TC-ERR-03, plan §6.2/§9.2, contracts CC-47-01. Distinction clairement partagée (retries dans même tentative vs FAILED→SCHEDULED = nouvelle tentative).
-
C-14 — Restauration trimestrielle → incident conformité : spec ERR-47-08 + §5.8 [CORR E-16], tests TC-ERR-08 [CORR B-01], plan [CORR B-01], contracts CC-47-11.
-
C-15 — Rotation clé K_backup : spec §10.3 [CORR E-13], tests TC-INV-03, plan §7.3, contracts CC-47-03. Compatibilité déchiffrement avec versions précédentes confirmée.
3. Divergences¶
Les conflits ne doivent JAMAIS être lissés. Chaque divergence est rendue visible.
DIV-01 — Contrainte UNIQUE(backup_id) vs sémantique multi-événements du journal¶
- Spec (INV-47-05) : "au moins un événement append-only horodaté ; les doublons éventuels sont dédupliqués par backup_id."
- Plan (CORR M-03) :
ON CONFLICT (backup_id) DO NOTHINGavec index UNIQUE surbackup_idseul → un seul enregistrement possible par backup_id. - Contracts (CC-47-06) :
BackupJournalEventdéfinit un champeventTypeavec valeurs multiples (BACKUP_STARTED,BACKUP_COMPLETED,BACKUP_FAILED, etc.) et une méthodegetEvents(backupId)retournantBackupJournalEvent[](tableau).
La contrainte UNIQUE sur backup_id interdit de facto l'insertion de plusieurs événements de types différents pour le même backup. Le champ eventType (valeurs multiples) et le type de retour tableau de getEvents() sont sémantiquement incohérents avec cette contrainte (cardinalité effective : exactement 0 ou 1 par backup_id).
Cas limite try/finally : si l'étape 10 du flux F1 écrit un événement avec status SUCCESS, puis que l'étape 11 (transition état) échoue, le bloc finally tente d'écrire un événement FAILED avec le même backup_id → silencieusement ignoré (DO NOTHING). Le journal montre SUCCESS alors que l'état réel reste RUNNING. La réconciliation (MOD-09) corrigerait l'état, mais le journal resterait incohérent.
- Impact : Mineur. La spec est satisfaite (>= 1 événement). Mais l'architecture du journal est sur-spécifiée (eventType multiple, retour tableau) par rapport à la contrainte réelle (un seul événement par backup). Risque de confusion à l'implémentation.
DIV-02 — Indépendance watchdog : niveau service vs niveau processus¶
- Spec (INV-47-06) : "Le watchdog de contrôle d'exécution est indépendant de l'orchestrateur principal."
- Plan (MOD-08 + §9.1 point 3) : watchdog NestJS indépendant au niveau service (pas d'injection de BackupOrchestratorService). Mais watchdog et orchestrateur partagent le même processus NestJS. Si le processus crash, les deux sont indisponibles. Mitigation : cron Ansible fallback (CC-47-16).
- Tests (TC-INV-06) : "Si orchestrateur crashé (process kill), watchdog détecte absence backup." Mais le test utilise un mock (orchestrateur service down, processus NestJS actif). Le test ne valide pas le scénario processus NestJS down avec fallback Ansible.
L'interprétation de "indépendant" diffère : la spec vise probablement l'indépendance processus (le watchdog survit au crash de l'orchestrateur), le plan+tests ne valident que l'indépendance au niveau service.
- Impact : Mineur. Le fallback Ansible comble le gap, mais il n'est couvert par aucun test automatisé. Si le cron Ansible est mal configuré, INV-47-06 est violé en cas de crash du processus NestJS complet.
DIV-03 — Fréquence backup configurable (min 1h) vs cron fixe quotidien¶
- Spec (§5.8) :
backupFrequencyHoursconfigurable, min=1, max=24. L'heure du backup est aussi configurable. - Plan (MOD-12 ConfigService) : valide
backupFrequencyHoursavec rejet hors bornes [1, 24]. Mais le plan (§11) définitBACKUP_CRON_DAILY(défaut0 3 * * *) comme expression cron simple. - Plan (MOD-08 watchdog) :
WATCHDOG_CRONfixé à0 4 * * *(04:00 Europe/Paris), 1h après le backup par défaut.
Si l'opérateur configure backupFrequencyHours=4 (toutes les 4h), le cron unique 0 3 * * * ne déclenche qu'une seule exécution quotidienne. Le ConfigService valide la fréquence, mais rien ne lie cette valeur à la génération de l'expression cron. Le watchdog à 04:00 n'est pertinent que pour un backup à 03:00.
- Impact : Mineur. Le défaut 24h est cohérent. Mais un opérateur configurant une fréquence sub-quotidienne sans ajuster manuellement le cron obtiendrait un comportement silencieusement incorrect. Aucun garde-fou ni validation croisée cron/fréquence.
4. Zones d'ombre¶
ZO-01 — Cohérence données post-restauration (Q-47-03 non résolu)¶
- Spec (Q-47-03) : "Jeu minimal de tables/compteurs à comparer contractuellement" — non défini.
- Plan (§9.3 point 9) : "vérification minimale (count tables, dernière transaction) avec extensibilité".
- Tests (§9 non-testable) : classé Majeur.
CA-47-07 n'est que partiellement testable. Le plan fait un choix raisonnable mais non validé par la spec.
ZO-02 — Granularité journal WAL (Q-47-02 non résolu)¶
- Spec (§5.3) : "Journalisation append-only par segment ou lot contractuel" — les deux options ouvertes.
- Spec (Q-47-02) : non tranché.
- Plan (§9.3 point 10) : décide 1 événement par segment WAL.
Sous forte charge transactionnelle, un événement journal par segment WAL (~16 MB) peut générer un volume élevé d'insertions. L'impact performance n'est pas évalué. Le seuil de basculement vers du lotissement n'est pas défini.
ZO-03 — Gestion DST Europe/Paris (Q-47-01 non résolu)¶
- Spec (Q-47-01) : non tranché.
- Plan (§9.2 point 6) : "NestJS cron doit utiliser le timezone Europe/Paris natif".
- Tests (§9 non-testable) : classé Mineur.
Aucun test ne valide le comportement au changement d'heure (dernier dimanche de mars/octobre). Le watchdog 04:00 pourrait manquer le backup de 03:00 si le changement d'heure provoque un décalage.
ZO-04 — Policy lifecycle S3 sur versions non courantes (Q-47-04 non résolu)¶
- Spec (Q-47-04) : non tranché.
- Plan (§12) : hors périmètre.
- Tests (§9 non-testable) : classé Majeur.
Le bucket S3 a le versioning activé. La policy lifecycle 30 jours supprime les objets courants, mais le comportement sur les noncurrent versions n'est pas contractualisé. Risque de rétention illimitée (coût) ou suppression prématurée (perte forensique).
ZO-05 — SLO escalade d'alerte (Q-47-05 non résolu)¶
- Spec (Q-47-05) : non tranché.
- Plan (§12) : hors périmètre.
Les alertes critiques sont émises mais le délai de prise en charge n'est pas contractualisé. En production, un backup échoué non traité pendant 24h viole le RPO de facto.
ZO-06 — Levée H-TECH-01 : aucun plan de mitigation si échec¶
- Plan (§8 + §9.1 point 1) : "risque le plus élevé du plan. Action : tester pg_receivewal --create-slot avant implémentation."
- Tests (TC-NOM-02) : SKIP si H-TECH-01 non levée [CORR m-04].
- Plan (Périmètre de test) : F2 WAL E2E conditionné à levée.
Si les slots de réplication ne sont pas exposés par OVH, le RPO < 5 min (WAL) est inatteignable. Aucun mécanisme de dégradation gracieuse ni plan B n'est spécifié. La levée doit précéder l'implémentation mais aucune date ni procédure n'est définie.
ZO-07 — Mécanisme de déclenchement du test de restauration trimestriel¶
- Spec (§5.8) : périodicité 90 jours, non configurable [CORR E-16].
- Plan (CC-47-10) :
restorationPeriodicityDays: 90, mais CC-47-11 (BackupRestorationService) n'a ni cron ni mécanisme de déclenchement périodique.
Qui déclenche le test trimestriel ? Cron NestJS ? Opérationnel manuel ? Pipeline CI ? Non spécifié.
ZO-08 — TC-INV-09 : faisabilité du scan mémoire processus¶
- Tests (TC-INV-09) : "scan process memory map" pour vérifier l'absence de DEK en clair.
- Plan (§5.3 TC-INV-09) : reprend l'exigence.
Scanner la mémoire d'un processus Node.js pour chercher un pattern de 32 bytes est techniquement complexe et fragile. Le scan disque + DB est réaliste ; le scan mémoire ne l'est pas en test automatisé. Aucun mécanisme concret n'est proposé.
5. Recommandation¶
- Procéder — convergence confirmée sur les 9 invariants, la machine d'états, le chiffrement, le journal et le watchdog. Les 16 corrections Gate 5 v1 ont résolu tous les écarts bloquants et majeurs de la v1.
- Rework nécessaire
- Escalade
Réserves mineures (à clarifier à l'implémentation, non bloquantes) :
- DIV-01 (journal eventType vs UNIQUE) : clarifier si
eventTypeest conservé comme champ informatif sur l'événement unique ou supprimé. AdaptergetEvents()pour retournerBackupJournalEvent | nullsi cardinalité max = 1. - DIV-02 (watchdog process-level) : considérer un test d'intégration validant le fallback Ansible si faisable.
- DIV-03 (fréquence vs cron) : documenter la responsabilité opérateur ou ajouter une validation croisée dans ConfigService.
- ZO-06 (H-TECH-01) : la levée DOIT précéder l'implémentation F2. Non bloquant pour F1/F3/F4.