PD-250 — Rapport de confrontation (Étape 8 — Gate CLOSURE)
Ce rapport est produit par l'orchestrateur Claude avant la gate PMO 8. Il confronte les documents produits pour identifier convergences, divergences et zones d'ombre.
1. Sources confrontées
| # | Document | Étape d'origine | Version |
| A | PD-250-specification.md | Étape 1 | v4 |
| B | PD-250-tests.md | Étape 2 | v4 |
| C | PD-250-plan.md | Étape 4 | v1 (post Gate 5 GO) |
| D | PD-250-acceptability.md | Étape 7 | v1 |
| E | PD-250-acceptability-review.md | Étape 7 (post-correction) | v1 |
| F | PD-250-code-contracts.yaml | Étape 4 | v1 |
| G | Implémentation réelle | Étape 6 | commits 14dfb48 + 9a44207 |
2. Convergences
2.1 Couverture fonctionnelle complète (Spec ↔ Tests ↔ Plan)
- 16/16 invariants (INV-250-01 à INV-250-16) sont couverts par au moins un scénario de test (B §1) et au moins une tâche du plan (C §4.1).
- 16/16 critères d'acceptation (CA-250-01 à CA-250-16) sont couverts par au moins un scénario de test (B §1) et au moins une tâche du plan (C §4.2).
- 8/8 cas d'erreur (ERR-250-01 à ERR-250-08) sont couverts par au moins un scénario de test (B §1) et au moins une tâche du plan (C §4.3).
- La matrice de couverture (B §1) est complète et cohérente avec les mappings du plan (C §4).
2.2 Architecture et stack contractuelle
- Spec (A §10.1) ↔ Plan (C §12) ↔ Implémentation (G) : La stack NestJS + TypeORM + PostgreSQL + BullMQ v5 + S3 (OVH Object Storage) est contractuellement respectée dans les trois documents.
- Plan (C §2.1) ↔ Implémentation (G) : L'arborescence
src/modules/destruction/ correspond exactement à la cible du plan. Les 14 tâches documentées sont implémentées. - Dépendances inter-PD (C §12) : Les 9 dépendances (PD-21, PD-63, PD-81, PD-39, PD-37, PD-36, PD-55, PD-240, PD-264) sont cohérentes entre plan et implémentation.
2.3 Résultats quantitatifs acceptabilité
- 475/475 tests du module destruction passent (D Phase 1).
- Coverage : 92.79% global, 98.98% services, 99.37% processors (D Phase 1).
- ESLint, Prettier, TypeScript : 0 erreur, 0 warning (D Phase 1).
- Les 28 scénarios de test (TC-250-01 à TC-250-28) sont mappés à des fichiers
.spec.ts réels (C §6.2) et passent.
2.4 Paramètres numériques contractuels
- Spec (A §10.2) ↔ Plan (C §9) ↔ Implémentation (G) : Les 10 paramètres numériques et 3 SLA temporels sont convergents. Bornes min/max identiques dans les trois documents. Rejet strict (pas de clamp) confirmé par TC-250-23.
2.5 Machine à états des documents
- Spec (A §10.5) ↔ Plan (C TASK-08) ↔ Tests (B TC-250-15) ↔ Implémentation (G) : Les transitions autorisées (
PENDING → SEALED → EXPIRED → DESTROYED, EXPIRED → RECONCILIATION_FAILED) et les 5 transitions interdites (INV-250-11 + MAJ-28 incluant RECONCILIATION_FAILED → *) sont convergentes sur les 4 sources.
2.6 Résolutions des réserves Gate 5
- MAJ-25 (séquentiel await) : convergent — pattern
for...of séquentiel dans plan (C TASK-04) et implémentation. - MAJ-26 (SLA destructionExecutionSla) : convergent — deux comportements distincts (FAILED avant 1er doc, PARTIAL_FAILED en cours) documentés dans plan (C TASK-04) et testés (TC-250-26, TC-250-27).
- MAJ-27 (fail-closed zeroization) : convergent — test TC-250-17b implémenté, plan (C TASK-14).
- MAJ-28 (
RECONCILIATION_FAILED → * interdit) : convergent — TC-250-15 étendu à 5 transitions. - MIN-11 (parentBatchId obligatoire) : convergent entre plan (C TASK-12) et implémentation.
- MIN-12 (guard EventEmitter2 ADMIN/SYSTEM) : convergent entre plan (C TASK-07) et implémentation.
- MIN-13 (filtre
entity_type != BORDEREAU) : voir DIV-01 — convergence entre plan et code contract, divergence avec l'implémentation. - MIN-14 (frontière eIDAS) : convergent — contrôle opérationnel, hors tests automatisés.
- MIN-15 (échantillonnage bornes) : convergent — TC-250-23 teste 3 paramètres sur bornes min ET max.
2.7 Corrections post-acceptabilité confirmées
- R-01 (MAJEUR) — Alignement colonnes migration/entity : RÉSOLU (E §E-01). Colonnes
pdf_s3_key, pdf_hash, signature_key_label alignées entre migration et entity. - R-03 (MAJEUR) —
allowUnknown: false : RÉSOLU (E §E-03). Config Joi conforme au code contract.
2.8 Fail-closed systématique
- Spec (A §5.4) ↔ Plan (C §8.2) ↔ Implémentation (G) : Les 5 scénarios fail-closed (signature invalide, TSA indisponible, bordereau non persisté, zeroization échouée, chaîne audit indisponible) sont convergents entre spec, plan et implémentation.
2.9 Flux de préavis
- Spec (A §5.8) ↔ Plan (C TASK-11) ↔ Tests (B TC-250-22) ↔ Implémentation (G) : Job BullMQ dédié, quotidien, événement bus interne avec payload technique (pas de PII), cas N=0 inclus. Convergent.
2.10 Sécurité et RGPD
- Spec (A INV-250-07) ↔ Tests (B TC-250-11) ↔ Plan (C TASK-03) ↔ Implémentation (G) : Le bordereau ne contient que les champs autorisés (§3). Aucune donnée personnelle directe. Convergent.
- Spec (A §5.10) ↔ Plan (C TASK-07) : Métriques batch avec compteurs agrégés uniquement, pas d'identifiants individuels. Convergent.
2.11 Réconciliation post-crash
- Spec (A §5.9, ERR-250-05) ↔ Plan (C TASK-05) ↔ Tests (B TC-250-07, TC-250-13, TC-250-28) : La stratégie de réconciliation (retry DB ≤
reconciliationDbRetryCount, SLA borné, état terminal DESTROYED ou RECONCILIATION_FAILED, escalade obligatoire) est convergente sur les 3 sources.
2.12 Idempotence et reprise
- Spec (A INV-250-08, §5.9) ↔ Plan (C TASK-02, TASK-04, TASK-12) ↔ Tests (B TC-250-12, TC-250-13) : L'idempotence repose sur le statut terminal
DESTROYED (pas de TTL de rejeu). La reprise crée un nouveau batch avec parentBatchId. Convergent.
2.13 Audit transactionnel
- Spec (A §10.4b) ↔ Plan (C TASK-06) ↔ Tests (B TC-250-16) : L'audit log est inséré dans la même transaction ACID que la finalisation
DESTROYED. Monotonie par séquence PostgreSQL audit_seq. Convergent.
3. Divergences
⚠️ Les conflits ne doivent JAMAIS être lissés. Chaque divergence est rendue visible.
DIV-01 — Filtre entity_type != 'BORDEREAU' absent de l'implémentation
- Source C (Plan TASK-02) : Exige explicitement
AND d.entity_type != 'BORDEREAU' dans la requête SQL (résolution MIN-13 Gate 5). - Source F (Code contracts) : Forbidden —
Sélection de documents de type BORDEREAU. - Source G (Implémentation) :
eligibility.service.ts ne contient PAS cette clause. La protection repose implicitement sur retentionExpiry = null (les bordereaux n'ayant pas de date d'expiration ne passeront jamais la condition retention_until < NOW()). - Source E (Review E-11) : Confirme — « Le filtre défensif explicite
entity_type != 'BORDEREAU' n'est pas présent. » - Impact : Defense-in-depth manquante. Si un bordereau avait un
retention_until non-null par bug de création, il pourrait être sélectionné pour destruction. - Gravité : MINEUR (defense-in-depth, protection implicite existante via
retentionExpiry = null)
DIV-02 — Test 403 + audit d'accès refusé : preuve contractuelle incomplète
- Source A (Spec INV-250-15 / CA-250-15) : « Requête non autorisée → rejet 403 + trace d'audit. »
- Source B (Tests TC-250-20) : « Appel par utilisateur non-ADMIN → 403, aucun contenu bordereau, trace d'audit d'accès refusé. »
- Source D (Acceptabilité R-02) : MAJEUR — « pas d'audit explicite du refus 403 ».
- Source G (Implémentation) :
DestructionAccessDeniedFilter créé et câblé au contrôleur. Le mécanisme existe dans le code. - Source G (Test) :
bordereau.controller.spec.ts — le test non-ADMIN ne vérifie PAS le code HTTP 403, ne vérifie PAS l'appel audit DOCUMENT_DESTROY_ACCESS_DENIED. Assertion triviale (expect(controllerWithDenyGuard).toBeDefined()). - Source E (Review E-02, E-05) : PARTIELLEMENT RÉSOLU / NON RÉSOLU — « le test non-ADMIN ne démontre pas explicitement le 403 + audit refus. »
- Impact : Le mécanisme d'audit 403 existe dans le code, mais sa preuve de bon fonctionnement par les tests est insuffisante. INV-250-15 n'est pas prouvé contractuellement par les tests.
- Gravité : MAJEUR (INV-250-15 non prouvé par les tests)
DIV-03 — trackedDefault absent de la validation Joi
- Source F (Code contracts) : Forbidden —
Joi.number().min().max() sans .default(trackedDefault(...)). - Source C (Plan TASK-01) : « Schema Joi avec
trackedDefault() pour chaque paramètre (pattern PD-22 existant). » - Source G (Implémentation) :
destruction.config.ts utilise .default(500), .default(24), etc. — valeurs numériques directes, PAS trackedDefault(...). - Source E (Review E-12) : PARTIELLEMENT RÉSOLU —
allowUnknown: false corrigé mais trackedDefault(...) non implémenté. - Impact : Les defaults ne sont pas traçables au démarrage (pas de log indiquant quels paramètres utilisent leur valeur par défaut vs une valeur configurée). Non-conformité au code contract.
- Gravité : MINEUR (observabilité réduite, pas de risque fonctionnel)
DIV-04 — Matrice combinatoire 9 cas WORM non implémentée en test
- Source B (Tests TC-250-01) : « 9 documents couvrant toutes les combinaisons booléennes des 3 conditions d'éligibilité. »
- Source G (Test réel) :
eligibility.service.spec.ts passe, mais ne couvre pas explicitement les 9 combinaisons booléennes (3 conditions × 2 états + 1 cas nominal complet). - Source E (Review E-07) : NON RÉSOLU.
- Impact : Conditions d'éligibilité testées individuellement et en combinaisons partielles, pas exhaustivement. Certaines combinaisons limites pourraient ne pas être couvertes.
- Gravité : MINEUR (couverture partielle existante, risque de régression limité)
DIV-05 — Absence de test e2e sécurité endpoint admin
- Source B (Tests TC-250-20) : Décrit comme test d'accès HTTP (unitaire + intégration implicite).
- Source D (Acceptabilité T-02) : MAJEUR — « Absence de test e2e sécurité pour l'endpoint admin bordereaux. »
- Source G (Implémentation) : Tests unitaires uniquement. Aucun
*.e2e-spec.ts pour /admin/bordereaux. - Source E (Review E-06) : NON RÉSOLU.
- Impact : L'intégration complète (guard + filter + audit + HTTP pipeline) n'est validée que par tests unitaires avec mocks. Bug d'intégration possible (mauvais ordre de guards, filter non déclenché en runtime).
- Gravité : MINEUR (structure testée unitairement, risque d'intégration non couvert)
DIV-06 — Section architectural_decisions absente du code contracts
- Source D (Acceptabilité R-04) : MINEUR — section manquante.
- Source F (Code contracts) : Absente du fichier
PD-250-code-contracts.yaml. - Source E (Review E-04) : NON RÉSOLU.
- Impact : Décisions architecturales (choix
pdf-lib, pattern séquentiel, schéma réconciliation) non formalisées dans les code contracts. Traçabilité réduite. - Gravité : MINEUR (documentation, pas de risque fonctionnel)
DIV-07 — 4 tests globaux en échec hors périmètre PD-250
- Source D (Acceptabilité Phase 1) : « 6448/6516 global (4 échecs pré-existants hors périmètre) : deposit.controller, delete-account-rate-limit, reauth-token-blacklist, session-invalidation. »
- Source D (Acceptabilité) : Confirmé identique sur
main. - Impact : Pas d'impact PD-250 (pré-existant sur
main). Les tests du module destruction (475/475) sont 100% verts. Violation formelle de l'Article IV si considéré strictement. - Gravité : MINEUR (hors périmètre PD-250, pré-existant confirmé sur
main)
DIV-08 — Quality Gate Sonar non exécutée (Phase 1.5 BLOQUANTE)
- Source D (Acceptabilité Phase 1.5) : « Quality Gate : NONE (Docker indisponible localement, sonar-scanner non installé). Sera vérifié post-merge via pipeline CI/CD GitLab. »
- Source CLAUDE.md : « Phase 1.5 (Sonar local) est BLOQUANTE. Si Sonar QG ERROR → STOP avant reviews LLM. »
- Source D (Acceptabilité) : Les reviews LLM (Phase 2) ont été effectuées sans validation Sonar préalable.
- Impact : La phase bloquante du processus de gouvernance n'a pas été exécutée. Pas de preuve d'absence de code smells, security hotspots, ou bugs détectés par Sonar. Les reviews LLM n'auraient pas dû démarrer sans Sonar selon le processus.
- Gravité : MAJEUR (violation du processus de gouvernance défini dans CLAUDE.md)
DIV-09 — Assertions faibles dans certains tests
- Source D (Acceptabilité T-04) : MINEUR — « Assertions trop faibles sur certains tests (DTO validation, query called). »
- Source E (Review E-08) : NON RÉSOLU — « Des assertions faibles/no-op subsistent. »
- Impact : Certains tests passent sans prouver le comportement attendu (assertions structurelles sans validation runtime).
- Gravité : MINEUR (couverture formelle sans profondeur, risque de faux positifs dans les tests)
DIV-10 — Test contractuel BullMQ v5 trop permissif
- Source D (Acceptabilité T-05) : MINEUR — « Test contractuel BullMQ v5 trop permissif (scan inclut tests/commentaires). »
- Source B (Tests TC-250-19) : « Scan des appels API BullMQ → absence d'occurrences
getRepeatableJobs et removeRepeatableByKey. » - Source E (Review E-09) : NON RÉSOLU — scan étendu aux tests/commentaires pour la présence API v5.
- Impact : Le scan statique vérifiant la présence de
getJobSchedulers/removeJobScheduler inclut les fichiers de test et commentaires, ce qui pourrait masquer une absence dans le code source réel. - Gravité : MINEUR (INV-250-14 est couvert fonctionnellement mais avec précision réduite du scan)
4. Zones d'ombre
ZO-01 — Comportement de selectPreNotice avec clockSkewTolerance
- Source A (Spec §5.1) :
clockSkewTolerance s'applique aux 3 contrôles d'éligibilité et ne s'applique PAS à la validation TSA. - Source A (Spec §5.8) : Ne précise pas explicitement si
clockSkewTolerance doit s'appliquer au calcul de la fenêtre de préavis (J+N). - Source G (Implémentation) : Applique
clockSkewTolerance dans selectPreNotice. - Source B (Tests) : Aucun test ne couvre l'interaction
clockSkewTolerance × preNoticeDays. - Impact : Pas de divergence fonctionnelle constatée, mais ambiguïté contractuelle sur le périmètre exact de
clockSkewTolerance.
ZO-02 — Mécanisme de déclenchement de reprise parentBatchId
- Source A (Spec §5.9) : La reprise crée un nouveau batch avec
parentBatchId pour les batches PARTIAL_FAILED et FAILED. - Source C (Plan TASK-12) : Prévoit
parentBatchId si reprise. - Ambiguïté : Le mécanisme de déclenchement (automatique au prochain run du job planifié ? Reprise manuelle explicite ?) n'est pas clairement contractualisé. Le processor retraite naturellement les documents non-
DESTROYED au prochain run, mais le lien parentBatchId avec le batch d'origine (identification automatique) n'est pas documenté dans le flux automatique.
ZO-03 — Frontière stubs / production pour TSA et S3
- Source D (Acceptabilité D-01, D-02) et Source E (Review E-13, E-14) : Les stubs TSA (
TODO(PD-250)) et S3 sont toujours présents. Tests en mode fail-closed. - Source A (Spec HYP-250-07) : Le TSP doit être qualifié eIDAS.
- Ambiguïté : La frontière entre « stub acceptable en gate » et « stub à remplacer avant mise en production » n'est pas formalisée contractuellement. Aucun contrôle automatisé ne peut vérifier la qualification eIDAS.
ZO-04 — Paramètre DESTRUCTION_PRE_NOTICE_JOB_INTERVAL non dans la spec
- Source A (Spec §10.2) : 10 paramètres numériques listés, 3 SLA = 13 paramètres.
- Source G (Implémentation) : La config Joi inclut un paramètre supplémentaire
DESTRUCTION_PRE_NOTICE_JOB_INTERVAL (14 paramètres total). - Impact : Ce paramètre additionnel (intervalle dédié du job de préavis) n'est pas dans la table §10.2 de la spec. Il semble issu d'une décision d'implémentation non répercutée dans la spec.
ZO-05 — Comportement du SLA destructionExecutionSla pour le dernier document
- Source A (Spec §5.5) et Source C (Plan TASK-04) : Le SLA
destructionExecutionSla borne le délai entre validation bordereau et début de destruction. Si dépassé AVANT 1er doc → FAILED, si dépassé EN COURS → PARTIAL_FAILED (MAJ-26). - Ambiguïté : Le comportement si le SLA est dépassé pendant le traitement du dernier document (tous les précédents déjà DESTROYED) n'est pas explicitement spécifié. Le batch devrait-il être SUCCESS (tous détruits) ou PARTIAL_FAILED (SLA dépassé) ?
5. Recommandation
Rework recommandé (MAJEUR)
| Priorité | DIV | Action corrective |
| MAJEUR | DIV-02 | Renforcer TC-250-20 : le test non-ADMIN doit vérifier (1) qu'une ForbiddenException est levée ou que le code HTTP 403 est retourné, (2) que mockAuditService.logBatchEvent est appelé avec DOCUMENT_DESTROY_ACCESS_DENIED. L'assertion expect(controllerWithDenyGuard).toBeDefined() est insuffisante pour prouver INV-250-15. |
| MAJEUR | DIV-08 | Exécuter le scan Sonar (SonarCloud si Docker local indisponible) OU formaliser une dérogation explicite avec justification (ESLint strict + TypeScript strict + 0 erreur comme couverture de substitution). La dérogation doit être tracée dans l'acceptabilité. |
Réserves acceptables (MINEUR — post-merge)
| DIV | Justification d'acceptabilité |
| DIV-01 | Protection implicite par retentionExpiry = null sur les bordereaux. Defense-in-depth à ajouter post-merge. |
| DIV-03 | trackedDefault est un pattern de traçabilité/observabilité. Les defaults sont documentés. Harmonisation post-merge. |
| DIV-04 | Couverture partielle existante (tests individuels par condition). Enrichissement post-merge. |
| DIV-05 | Tests unitaires couvrent la structure. Test e2e sécurité à ajouter dans une passe de hardening. |
| DIV-06 | Documentation uniquement, pas de risque fonctionnel. Compléter post-merge. |
| DIV-07 | Pré-existant confirmé sur main. Hors périmètre PD-250. |
| DIV-09 | Assertions partielles — tests passent mais profondeur à améliorer. Post-merge. |
| DIV-10 | INV-250-14 couvert fonctionnellement. Précision du scan à améliorer post-merge. |