Aller au contenu

PD-250 — Rapport de confrontation — Gate 5 (Step 5)

Date : 2026-02-25 Story : PD-250 — Job destruction définitive et bordereau Étape : 5 — Review plan d'implémentation Confrontation : Spécification v4 × Tests v4 × Plan d'implémentation × Code Contracts

Ce rapport est produit par l'orchestrateur Claude avant la gate PMO Step 5. Il confronte les documents produits pour identifier convergences, divergences et zones d'ombre.

1. Sources confrontées

Document Origine Version
Spécification PD-250-specification.md v4 (Step 1, validée Gate 3 avec réserves)
Tests & Validation PD-250-tests.md v4 (Step 2)
Plan d'implémentation PD-250-plan.md v1 (Step 4)
Code Contracts PD-250-code-contracts.yaml v1 (Step 4)
Review Plan (contexte) PD-250-plan-review.md Step 5, verdict ⛔ Rejeté

2. Convergences

CONV-01 — Éligibilité stricte triple condition

La spécification (INV-250-01, §5.1), les tests (TC-250-01/02/03), le plan (TASK-02, query SQL) et le code contract (destruction-eligibility, invariants) sont alignés sur la triple condition simultanée (DB retention + S3 Object Lock + legal_lock) avec clockSkewTolerance soustractive.

CONV-02 — Fail-closed probatoire

Les 4 documents convergent : aucune destruction physique sans bordereau signé et horodaté valide. Spec (INV-250-03, §5.4), Tests (TC-250-04/05/08b), Plan (TASK-03/TASK-12, point fail-closed), Code Contracts (destruction-bordereau + destruction-execution, forbidden: "Destruction S3 avant validation bordereau").

CONV-03 — Bordereau unique par batch et minimisation RGPD

Spec (INV-250-04/07, §3 champs autorisés), Tests (TC-250-08/11), Plan (TASK-03, payload restreint), Code Contracts (destruction-bordereau, forbidden: "Inclusion de nom, prénom...") sont cohérents. Les champs autorisés sont identiques dans les 4 sources.

CONV-04 — Idempotence et statut terminal DESTROYED

Spec (INV-250-08), Tests (TC-250-12/13), Plan (TASK-02/04, WHERE status != 'DESTROYED'), Code Contracts (destruction-execution, invariant idempotence) convergent sur le mécanisme.

CONV-05 — Transitions interdites et machine à états

Spec (INV-250-11, §10.5), Tests (TC-250-15), Plan (TASK-08, 5 transitions), Code Contracts (destruction-state-machine, forbidden: liste des 5 transitions) sont alignés. Le plan intègre correctement MAJ-28 (RECONCILIATION_FAILED → * INTERDITE).

CONV-06 — Contraintes BullMQ (nommage + API v5)

Spec (INV-250-13/14), Tests (TC-250-18/19), Plan (TASK-01/12/14, constantes pv-jobs-destruction/pv-jobs-prenotice), Code Contracts (tous modules processor, forbidden: noms avec :, API dépréciées). Convergence complète.

CONV-07 — SLA contractuels et comportements à expiration

Spec (INV-250-16, §10.3), Tests (TC-250-26/27/28), Plan (TASK-04/05/07, chronos SLA), Code Contracts (modules execution, reconciliation, alert). Les 3 SLA sont correctement repris avec leurs comportements contractuels.

CONV-08 — Conservation indéfinie des bordereaux

Spec (INV-250-06, §5.7), Tests (TC-250-10), Plan (TASK-03, retentionExpiry = null), Code Contracts (destruction-bordereau, invariant retentionExpiry). Convergence complète.

CONV-09 — Endpoint admin ADMIN-only

Spec (INV-250-15), Tests (TC-250-20/21), Plan (TASK-10, @Roles('ADMIN')), Code Contracts (destruction-admin-api, invariants). Convergence complète.

CONV-10 — Résolution des réserves Gate 3 (MAJ-25 à MAJ-28)

Le plan d'implémentation intègre explicitement les 4 réserves MAJEURES de la Gate 3 : - MAJ-25 (Async → await séquentiel) : Plan §4.4, TASK-04 boucle for...of. - MAJ-26 (destructionExecutionSla FAILED vs PARTIAL_FAILED) : Plan §3.1 étape 5, TASK-04 §3. - MAJ-27 (fail-closed zeroization) : Plan §4.4, TASK-09/14 (TC-250-17b). - MAJ-28 (RECONCILIATION_FAILED → * INTERDITE) : Plan TASK-08, 5 transitions.

CONV-11 — Résolution des réserves Gate 3 (MIN-11 à MIN-15)

Le plan intègre les 5 réserves MINEURES : - MIN-11 (parentBatchId en test) : TASK-14. - MIN-12 (restriction batch_result) : TASK-07/14. - MIN-13 (exclusion bordereaux de sélection) : TASK-02, filtre entity_type != 'BORDEREAU'. - MIN-14 (eIDAS contrôle opérationnel) : TASK-03 note. - MIN-15 (échantillonnage bornes) : TASK-14.

Spec (INV-250-12, §5.5), Tests (TC-250-17), Plan (TASK-09, TASK-04 branchement conditionnel), Code Contracts (destruction-zeroization, destruction-execution). La séquence zeroization → S3 → DB est cohérente.

CONV-13 — Audit transactionnel (même transaction que DESTROYED)

Spec (§10.4b, INV-250-05), Tests (TC-250-09/16), Plan (TASK-06, TASK-04 §6e), Code Contracts (destruction-audit, invariant "§10.4b"). Mécanisme ACID aligné.

CONV-14 — Réconciliation post-crash

Spec (ERR-250-05, §10.5), Tests (TC-250-07/13/28), Plan (TASK-05, §3.2), Code Contracts (destruction-reconciliation). Flux convergent : retry DB → DESTROYED ou RECONCILIATION_FAILED + escalade.

CONV-15 — Préavis de destruction

Spec (§5.8), Tests (TC-250-22), Plan (TASK-11, §3.3), Code Contracts (destruction-prenotice). Payload technique sans PII, cas N=0 documenté.

3. Divergences

Les conflits ne sont JAMAIS lissés. Chaque divergence est rendue visible.

DIV-01 — TC-250-16 : test unitaire mocké vs exigence de validation ACID réelle

  • Source A — Spécification (INV-250-05, §10.4b) : le mécanisme de non-perte est garanti par écriture transactionnelle ACID. TC-250-16 exige validation post-crash avec crash injecté "après suppression S3 mais avant le commit de la transaction DB".
  • Source B — Tests (TC-250-16) : décrit un scénario avec crash contrôlé et réconciliation, requérant une infrastructure PostgreSQL réelle pour démontrer l'atomicité ACID.
  • Source C — Plan (§6.2, mapping TC → fichiers) : TC-250-16 est mappé comme test unitaire dans destruction-audit.service.spec.ts.
  • Impact : un test unitaire avec mocks ne peut pas démontrer l'atomicité ACID transactionnelle ni la monotonie par séquence PostgreSQL. L'invariant INV-250-05 (mesurabilité de l'audit : non-perte, ordre causal, complétude) n'est pas démontrable avec le type de test prévu. Écart identifié comme BLOQUANT par la review du plan.

DIV-02 — Nom de la séquence PostgreSQL : audit_seq vs audit_destruction_seq

  • Source A — Spécification (INV-250-05) : contractualise explicitement le nom audit_seq comme mécanisme de monotonie.
  • Source B — Plan (HYP-IMPL-07, TASK-06, TASK-13) : crée une séquence nommée audit_destruction_seq dans le schéma vault_secure.
  • Impact : divergence de nommage entre le contrat spécifié et l'implémentation planifiée. Si des audits ou des vérifications externes recherchent audit_seq, ils ne trouveront pas audit_destruction_seq. La spécification étant contractuelle, le nommage doit être aligné.

DIV-03 — Ordonnancement du job de préavis : quotidien vs configurable

  • Source A — Spécification (§5.8) : "un job BullMQ dédié de préavis [...] exécuté quotidiennement".
  • Source B — Plan (TASK-11) : "Scheduling : quotidien (configurable via destructionJobInterval)".
  • Impact : destructionJobInterval a une plage [1, 168] heures. Si configuré à 168h (1 semaine), le job de préavis ne sera pas quotidien, en contradiction avec la spécification. Le préavis et la destruction partagent une variable de configuration qui ne convient qu'à la destruction.

DIV-04 — parentBatchId : entity batch vs audit log

  • Source A — Spécification (§5.9) : "La traçabilité de la filiation est assurée par un champ parentBatchId obligatoire dans l'audit log du nouveau batch".
  • Source B — Plan (TASK-13, DDL destruction_batches) : parent_batch_id UUID REFERENCES vault_secure.destruction_batches(id) — le champ est dans la table destruction_batches, pas dans l'audit log.
  • Source C — Plan (§4.4, MIN-11) : "Test batch de reprise : parentBatchId non null" — testé au niveau de l'entity batch.
  • Impact : la traçabilité de filiation en audit externe est rompue si parentBatchId n'est pas également présent dans les entries d'audit log. Un auditeur consultant uniquement les logs d'audit ne pourrait pas reconstituer la filiation inter-batch.
  • Source A — Spécification (§3, définition flux legal_lock) : "un document appartient au flux legal_lock si un legal_lock lui a été assigné à un moment quelconque de son cycle de vie".
  • Source B — Plan (TASK-09, TASK-13) : backfill UPDATE documents SET had_legal_lock = true WHERE legal_lock = true OR legal_lock_until IS NOT NULL.
  • Impact : le backfill repose sur l'état courant (legal_lock et legal_lock_until). Si un document a eu un legal_lock actif dans le passé, qui a ensuite été retiré (non pas expiré mais retiré avec legal_lock = false et legal_lock_until = NULL), le backfill ne le détectera pas. Risque de classification erronée entraînant l'absence de zeroization obligatoire (contournement INV-250-12). Ce risque dépend de l'existence d'un tel scénario dans le modèle métier.

DIV-06 — Chaînage systématique erreur unitaire → alerte

  • Source A — Spécification (INV-250-10) : "Toute erreur partielle est observable (audit + alerte) [...] Échec silencieux = non conforme."
  • Source B — Plan (TASK-07, TASK-04) : les alertes sont principalement traitées au niveau SLA global et batch (publishBatchResult). Le chaînage unitaire "erreur sur un document → alerte spécifique émise" n'est pas explicitement formalisé pour tous les cas d'erreur partielle (ERR-250-03, ERR-250-04).
  • Source C — Code Contracts (destruction-alert) : invariant "INV-250-10: Échec silencieux = non conforme" mais pas de mécanisme unitaire explicite.
  • Impact : une erreur unitaire sur un document pourrait être auditée (tracée en log) mais pas alertée (pas de notification opérationnelle émise), ce qui serait conforme à la lettre de l'audit mais pas à l'esprit de l'observabilité exigée par INV-250-10.

DIV-07 — Code Contracts : éléments du plan sans contrat dédié

  • Source A — Plan (§2.1, arborescence cible) : destruction.module.ts, destruction-batch.entity.ts, batch-status.enum.ts, destruction-audit-action.enum.ts sont planifiés.
  • Source B — Code Contracts : aucun contrat ne couvre destruction.module.ts, destruction-batch.entity.ts, ni les enums.
  • Impact : dans une exécution multi-agents, ces fichiers n'ont pas de frontière contractuelle explicite. Le risque est une implémentation sans garde-fou contractuel pour ces composants. Impact considéré mineur car ce sont des fichiers de structure/glue, pas de logique métier.

4. Zones d'ombre

ZO-01 — Tests d'intégration : stratégie non définie

Le plan (§6.1) mentionne des tests d'intégration nécessitant PostgreSQL et Redis mais n'identifie aucun TC spécifique comme test d'intégration. TC-250-16 (audit ACID) est l'exemple le plus flagrant, mais d'autres scénarios (TC-250-07 réconciliation, TC-250-13 crash post-S3) bénéficieraient également de tests d'intégration. Aucun document ne précise quels TCs nécessitent une infrastructure réelle vs des mocks.

ZO-02 — pdf-lib : conformité PDF/A non démontrée

Le plan (TASK-03, HYP-IMPL-05) choisit pdf-lib pour la génération PDF/A. Aucun document ne précise comment la conformité PDF/A (norme ISO 19005) sera vérifiée au-delà de la génération. pdf-lib ne garantit pas nativement le format PDF/A. La spécification exige PDF/A (§5.3) sans alternative.

ZO-03 — Cas N=0 préavis : ordre strict inter-jobs

La spécification (§5.8) indique que "le job de préavis s'exécute en premier dans la séquence" pour N=0. Le plan (TASK-11) documente le cas N=0 mais ne formalise pas le mécanisme garantissant l'ordre d'exécution entre les deux jobs BullMQ (préavis avant destruction). Aucun test ne vérifie cet ordonnancement. L'intervalle dépend du scheduling cron, ce qui est une garantie faible.

ZO-04 — Signature HSM : interface d'intégration

Le plan (TASK-03) mentionne "Appel au service HSM (PD-36) pour signature qualifiée eIDAS art. 26" mais le code contract destruction-bordereau ne définit pas l'interface attendue du service HSM. Si le module PD-36 ne fournit pas directement une méthode de signature de PDF, un adapter sera nécessaire (non planifié explicitement).

ZO-05 — Comportement du job si 0 documents éligibles

Le plan (§3.1, étape 2) indique "Si 0 documents → fin normale (pas de batch créé)". La spécification ne spécifie pas explicitement ce comportement. Il n'y a pas de test pour ce cas. Le comportement semble raisonnable mais n'est contractualisé nulle part.

ZO-06 — Concurrence entre jobs : double run simultané

Aucun document ne spécifie le comportement en cas de double exécution simultanée du job de destruction (deux instances BullMQ traitant en parallèle). Le SELECT FOR UPDATE (TASK-02, isStillEligible) protège au niveau document mais pas au niveau batch : deux jobs pourraient sélectionner les mêmes documents avant que l'un ne les marque DESTROYED.

ZO-07 — Migration ALTER TYPE et downtime

Le plan (§10, risque #1) identifie le risque de lock exclusif sur ALTER TYPE document_status. Aucun document ne contractualise la stratégie de migration (online DDL, fenêtre de maintenance, rollback). Le risque est identifié mais la mitigation ("hors heures, test en staging") n'est pas formalisée.

5. Recommandation

  • Procéder — convergence confirmée, aucun conflit bloquant
  • Rework nécessaire — divergences à résoudre avant de continuer
  • Escalade — décision humaine requise sur un point structurant

Justification :

Les convergences sont solides et couvrent l'essentiel du périmètre fonctionnel et technique (15 points de convergence confirmés). Les réserves Gate 3 (MAJ-25 à MAJ-28, MIN-11 à MIN-15) sont correctement intégrées dans le plan.

Cependant, 7 divergences sont identifiées : - DIV-01 (TC-250-16 unitaire vs ACID réel) est l'écart le plus critique : la testabilité de l'invariant INV-250-05 n'est pas démontrable avec le plan de test actuel. - DIV-03 (préavis quotidien vs configurable) et DIV-04 (parentBatchId entity vs audit log) sont des écarts de conformité directe avec la spécification. - DIV-05 (backfill had_legal_lock) présente un risque de sécurité probatoire si le scénario de retrait de legal_lock existe dans le modèle métier. - DIV-02, DIV-06, DIV-07 sont des écarts mineurs mais documentés.

Rework du plan recommandé avant passage en gate, ciblant prioritairement DIV-01 (stratégie de test d'intégration pour TC-250-16), DIV-03 (variable de configuration dédiée au préavis), DIV-04 (parentBatchId dans l'audit log) et DIV-05 (validation de l'exhaustivité du backfill).