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.
CONV-12 — Zeroization flux legal_lock¶
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_seqcomme mécanisme de monotonie. - Source B — Plan (HYP-IMPL-07, TASK-06, TASK-13) : crée une séquence nommée
audit_destruction_seqdans le schémavault_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 pasaudit_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 :
destructionJobIntervala 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
parentBatchIdobligatoire 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 tabledestruction_batches, pas dans l'audit log. - Source C — Plan (§4.4, MIN-11) : "Test batch de reprise :
parentBatchIdnon null" — testé au niveau de l'entity batch. - Impact : la traçabilité de filiation en audit externe est rompue si
parentBatchIdn'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.
DIV-05 — Exhaustivité du backfill had_legal_lock¶
- Source A — Spécification (§3, définition flux
legal_lock) : "un document appartient au fluxlegal_locksi unlegal_locklui 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_locketlegal_lock_until). Si un document a eu unlegal_lockactif dans le passé, qui a ensuite été retiré (non pas expiré mais retiré aveclegal_lock = falseetlegal_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.tssont 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).