PD-278 — Rapport de confrontation (Etape 8)¶
Ce rapport est produit par l'orchestrateur Claude avant la gate PMO etape 8. Il confronte les documents produits pour identifier convergences, divergences et zones d'ombre. Date : 2026-03-01
1. Sources confrontees¶
| Document | Etape d'origine | Role |
|---|---|---|
Specification (PD-278-specification.md) | Etape 3 | Contrat fonctionnel de reference |
Tests contractuels (PD-278-tests.md) | Etape 3 | Scenarios de validation |
Plan d'implementation v2 (PD-278-plan.md) | Etape 5 | Architecture et mecanismes techniques |
Code contracts (PD-278-code-contracts.yaml) | Etape 5 | Invariants, forbidden, interfaces |
Acceptabilite (PD-278-acceptability.md) | Etape 7 | Revue code/tests/couverture |
| Agent report: dip-migration | Etape 6 | Migration DDL |
| Agent report: dip-entity-extension | Etape 6 | Extension entite DocumentSecure |
| Agent report: dip-attestation | Etape 6 | Entite DisseminationAttestation |
| Agent report: dip-state-machine | Etape 6 | Machine a etats |
| Agent report: dip-config | Etape 6 | Configuration Joi |
| Agent report: dip-dto | Etape 6 | DTOs et codes d'erreur |
| Agent report: dip-audit-types | Etape 6 | Types audit |
| Agent report: dip-journal-types | Etape 6 | Types journal |
| Agent report: dip-rate-limit | Etape 6 | Guard rate-limit Redis |
| Agent report: dip-service | Etape 6 | Service core metier |
| Agent report: dip-controller | Etape 6 | Controller REST |
| Agent report: dip-exception-filter | Etape 6 | Filter audit refus securite |
| Agent report: dip-tla-formal | Etape 6 | Modele formel TLA+ |
| Agent report: dip-tests | Etape 6 | 99 tests unitaires |
| Code source implemente | Etape 6 | Fichiers .ts sur la branche feature/PD-278-nfz42013-dip-state |
2. Convergences¶
CONV-01 : Machine a etats et transitions DIP¶
Tous les documents s'accordent sur les transitions autorisees et interdites : - SEALED -> DIP : autorisee avec gardes (spec §5.2, plan §2.1, state-machine report, service report, tests TC-NOM-01) - DIP -> SEALED : autorisee, explicite uniquement (spec §5.2, plan §2.2, INV-278-03, tests TC-NOM-03/04) - DIP -> EXPIRED : interdite (spec §5.2, plan §3, state-machine report §2) - EXPIRED -> * : terminal strict (spec §5.2 INV-278-08, plan §3, state-machine report §2) - Pas de timeout implicite (spec INV-278-03, plan V-06, tests TC-NOM-04, acceptabilite §4.3 TC-INV-03)
CONV-02 : Atomicite transactionnelle ACID¶
Convergence totale sur le pattern QueryRunner + SELECT FOR UPDATE ordonne : - Spec §5.8 et §5.9 : atomicite multi-composant + verrouillage ordonne - Plan §2.1 : QueryRunner explicite, tri lexicographique ASC - Service report §3 : for (const row of rows) sequentiel, ROLLBACK global - Code source : ORDER BY d.id ASC FOR UPDATE (dissemination.service.ts:124) - Tests : TC-INV-11, TC-INV-12 couverts (acceptabilite §4.3)
CONV-03 : Attestation 1-par-requete¶
Spec §5.13 INV-278-05, plan §3, service report §3, attestation entity report, tests TC-INV-05 : tous alignes sur exactement 1 attestation par requete SEALED->DIP, inseree dans la meme transaction.
CONV-04 : Rate-limit fail-closed¶
Spec INV-278-02, plan C8, rate-limit report, code source, tests TC-ERR-13 : Redis indisponible -> 503 E-503-REDIS, pas de bypass. Convergence totale.
CONV-05 : WORM preserve en DIP¶
Spec INV-278-06, plan §3, entity-extension report §3 (aucune colonne existante modifiee), migration report (trigger trg_motif_communication_worm), tests TC-NR-01, TC-INV-06. Convergence.
CONV-06 : Ordre temporel garanti¶
Spec INV-278-13, plan §3, service report §3 : GREATEST(NOW(), disseminated_at) en SQL. Tests TC-NOM-03, TC-NEG-07. Convergence.
CONV-07 : Anti-contournement retention¶
Spec INV-278-14, plan §2.4, service report : garde retention_due=false sur SEALED->DIP, role retention_service pour cloture DIP->SEALED. Tests TC-ERR-14, TC-INV-13 cas B. Convergence.
CONV-08 : Resultats d'execution¶
Acceptabilite §3 : 99/99 tests PASS, ESLint 0 erreur, Prettier OK, TypeScript 0 erreur. QA report §6 : 99 passed, 0 failed. Convergence confirmee.
CONV-09 : Gardes sequentielles par document¶
Spec INV-278-02, plan C9, service report §3 : gardes etat, copies, retention evaluees sequentiellement par document. Forbidden Promise.all respecte. Code source (dissemination.service.ts:142) : for (const row of rows). Convergence.
CONV-10 : Roles autorises¶
Spec §5.3/§5.4 : F1={PA, SA, auditor}, F2={PA, SA, auditor, retention_service}. Plan §2.1/§2.2, controller report §2, code contracts dip-controller. Convergence.
CONV-11 : Package mono/multi et dissemination_package_id¶
Spec §5.1 : NULL en mono, UUID v4 serveur en multi. Plan C9, service report, DTO report, tests TC-NOM-07. Convergence.
CONV-12 : motif_communication immutable apres creation¶
Spec §5.1 (trigger DB), plan C1 step 9, migration report §3, tests TC-NOM-06. Convergence.
3. Divergences¶
Les conflits ne sont JAMAIS lisses. Chaque divergence est rendue visible.
DIV-01 : Codes d'erreur E-404-NOT-FOUND et E-503-REDIS absents de la specification¶
- Specification (§6) : Liste 15 codes d'erreur. Ne mentionne ni
E-404-NOT-FOUNDniE-503-REDIS. - Plan d'implementation (§6) : Ajoute
E-404-NOT-FOUND(document non trouve, HTTP 404) etE-503-REDIS(Redis indisponible, HTTP 503). - Code implemente (
dissemination-error.dto.ts:36-37, 79-80) : Les deux codes sont implementes avec le commentaire-- impl plan SS6(pas-- spec SS6). - Impact : Le contrat API expose 17 codes d'erreur alors que la specification en contractualise 15. Un consommateur se basant uniquement sur la spec ne connaitrait pas les reponses 404 et 503.
DIV-02 : Persistance audit refus securite — logAsync vs INSERT direct vs spec §5.8¶
- Specification (§5.8) : "Persistance audit refus securite : Synchrone (transaction legere dediee)". Garantie : "Aucune tentative refusee sans trace".
- Plan C11 (§13 pseudo-code) :
queryRunner.query('INSERT INTO vault_secure.audit_log ...')— INSERT SQL direct dans le QueryRunner dedie. - Agent report dip-exception-filter (§2.2, §8 DA-01) : Utilise
AuditLogService.logAsync()awaited dans le QueryRunner. Justification : "INSERT SQL direct dans audit_log impossible car la table requiert entry_canonical, entry_hash, hsm_signature (NOT NULL) generes par AuditSignatureService". - Code implemente (
dissemination-audit-exception.filter.ts:111) :await this.auditLogService.logAsync({...})dans unqueryRunner.startTransaction()/commitTransaction(). - Acceptabilite (E-10, MAJEUR) : "le contrat §5.8 exige persistance synchrone via QueryRunner dedie. L'implementation actuelle delegue a logAsync() qui peut etre asynchrone. A confirmer si logAsync() ecrit de maniere synchrone dans la base ou s'il enqueue."
- Impact : Le mecanisme diverge sur 3 niveaux : (1) la spec exige "synchrone", (2) le plan pseudo-code montre un INSERT direct, (3) l'implementation utilise
logAsync()qui — selon l'agent report — tente une persistance synchrone avec fallback BullMQ si HSM indisponible. De plus, lequeryRunnercree dans le filter (lignes 107-123) n'est PAS passe alogAsync(), donc la transaction du filter est decorrellee de l'ecriture audit : lecommitTransaction()ligne 123 commite une transaction vide, tandis quelogAsync()utilise son propre mecanisme interne.
DIV-03 : Cardinalite enum — spec dit 4 etats, realite est 5¶
- Specification (§4 INV-278-01) : "Le set des statuts implemente inclut exactement PENDING, SEALED, DIP, EXPIRED."
- Plan (V-01, §4 CA-01) : "L'enum DB contient 5 valeurs (PENDING, SEALED, DIP, EXPIRED, RESTITUTED). PD-278 ajoute DIP. Le test verifie DIP ∈ enum_range, pas |enum| = 4."
- Code contracts (
dip-entity-extension) : "INV-278-01: DocumentStatus enum contient DIP (DIP ∈ enum, pas verification de cardinalite exacte)". - State-machine report (§1.1) : SEALED targets incluent EXPIRED, RESTITUTED, DIP.
- Tests : TC-INV-01 verifie
DIP ∈ enum, pas la cardinalite = 4. - Impact : La specification dit "exactement" 4 etats. L'implementation en contient 5. Le plan documente explicitement cet ecart (V-01) et adapte le test, mais la spec n'a pas ete corrigee. Un auditeur lisant la spec seule constaterait une violation d'INV-278-01.
DIV-04 : Timestamp migration — plan vs implementation¶
- Plan (§13 C1) :
src/database/migrations/1741400000000-PD278-AddDipState.ts - Migration agent report :
src/database/migrations/1741500000000-PD278-AddDipState.ts - Fichier reel sur la branche :
1741500000000-PD278-AddDipState.ts - Impact : Ecart mineur de nommage. Le plan reference un timestamp qui ne correspond pas au fichier reel. La tracabilite plan->fichier est rompue pour ce composant.
DIV-05 : N_MAX hardcode dans le DTO vs configurable dans la config¶
- Specification (Q-01) : "
N_MAXfixe a 100 pour PD-278." - Config (
dissemination.config.ts) :maxPackageSizeconfigurable viaDIP_MAX_PACKAGE_SIZE, default 100, max 100. - DTO (
create-dissemination.dto.ts:37) :@ArrayMaxSize(100)— valeur en dur, pas de reference a la config. - Agent DTO report (H-DTO-02) : "La valeur N_MAX = 100 est figee pour PD-278. @ArrayMaxSize(100) est en dur. Si N_MAX devient configurable dynamiquement, il faudra un custom validator."
- Impact : La config permet de reduire
maxPackageSize(ex: a 50) sans que le DTO ne le reflette — le DTO continuera a accepter 100. Incoherence potentielle entre validation DTO et validation service si la config est modifiee.
DIV-06 : Numerotation TC dans le rapport QA vs specification des tests¶
- Specification tests : TC-NOM-04 = "No timeout implicite" ; TC-NOM-05 = "SLA latence P95" ; TC-NOM-06 = "motif_communication" ; TC-NOM-07 = "package_id mono/multi".
- QA report (§3.1) : TC-NOM-04 = "motif_communication persist" ; TC-NOM-06 = "package_id=NULL mono, UUID multi" ; TC-NOM-07 = "returned_at >= disseminated_at (GREATEST)". TC-NOM-05 absent.
- Acceptabilite (§4.1) : TC-NOM-04 = ABSENT (timeout test) ; TC-NOM-05 = ABSENT (SLA) ; TC-NOM-06 = PASS (motif) ; TC-NOM-07 = PASS (package_id).
- Impact : Le QA report utilise une numerotation differente de la spec tests pour les memes tests. La tracabilite TC-ID est rompue entre le rapport QA et les scenarios contractuels. L'acceptabilite corrige implicitement ce decalage mais le QA report seul est trompeur.
DIV-07 : QueryRunner du filter decorelle de l'audit effectif¶
- Plan C11 (§13) : Le pseudo-code montre le QueryRunner dedie effectuant directement l'INSERT audit, puis commitant.
- Code implemente (
dissemination-audit-exception.filter.ts:107-123) : Le filter cree un QueryRunner, demarre une transaction, appellethis.auditLogService.logAsync({...}), puis commite et release le QueryRunner. MaislogAsync()n'utilise PAS le QueryRunner passe — il a sa propre logique interne d'ecriture. LestartTransaction()/commitTransaction()du filter encapsule un appel qui n'est pas lie a cette transaction. - Impact : Le pattern transactionnel est illusoire : le
commitTransaction()commite une transaction vide. SilogAsync()echoue, lerollbackTransaction()ne defait rien puisque l'ecriture n'etait pas dans la transaction du filter. La garantie "persistance AVANT retour HTTP" depend entierement du comportement interne delogAsync(), pas du QueryRunner du filter.
DIV-08 : Schema attestation — colonnes supplementaires vs spec §5.13¶
- Specification (§5.13) : Schema minimal :
attestation_id,request_id,document_ids,actor_id,issued_at,motif_communication,hash_evidence,signature_ref. - Migration (agent report) et entity : Ajoutent
id(PK UUID auto-generated),dissemination_package_id,created_atau-dela du schema spec. - Impact : Ecart mineur. Les colonnes supplementaires sont des necessites techniques (PK pour TypeORM, correlation package, audit temporel). Pas de violation fonctionnelle mais le schema reel est un sur-ensemble du contrat.
DIV-09 : @Catch() global vs @Catch(HttpException) dans le filter¶
- Plan C11 (§13 pseudo-code) :
@Catch(UnauthorizedException, ForbiddenException, HttpException)— catch selectif. - Code implemente (
dissemination-audit-exception.filter.ts:35) :@Catch()— catch global de toutes les exceptions. - Agent report dip-exception-filter : "Non-HttpException: retour 500 sans audit" — gere dans le code (lignes 61-64).
- Impact : Le filter capture TOUTES les exceptions, pas seulement les HttpException. Les exceptions non-HTTP (erreurs TypeORM, Redis, etc.) sont transformees en 500 generique sans audit. Cela peut masquer des stack traces en production si le filter intercepte des erreurs inattendues.
DIV-10 : E-409-STATE — audit ou pas ?¶
- Specification (§4 INV-278-04) : "chaque tentative refusee pour 401/403/429/409-retention_due persiste un evenement audit securite". La liste est limitative —
E-409-STATEn'est pas dans la liste. - Plan (§6) : E-409-STATE — "Audit refus : Non (erreur metier, pas securite)".
- Filter : Confirme — 409-STATE n'est pas audite (seul 409-RETENTION-DUE l'est).
- Impact : Convergence entre plan et implementation. Mais la spec pourrait etre interpretee comme "409-retention_due" etant un sous-ensemble de 409, ou comme une liste limitative. L'implementation choisit la lecture limitative, coherente avec le plan.
4. Zones d'ombre¶
ZO-01 : Reviews LLM ChatGPT non soumises¶
L'acceptabilite (E-09, MAJEUR) indique que les 3 prompts de review (code, tests, securite) ont ete generes mais pas soumis a ChatGPT. Les fichiers PD-278-review-code.md, PD-278-review-tests.md, PD-278-review-security.md existent sur la branche mais leur contenu n'est pas referencee dans l'acceptabilite comme verdicts ChatGPT recus. Aucun document ne clarifie si ces reviews sont bloquantes pour la gate 8.
ZO-02 : ValidationPipe whitelist:true — configuration reelle non verifiee¶
Le DTO report (H-DTO-01) conditionne le rejet des champs serveur (dissemination_package_id, disseminated_at, dissemination_returned_at) a la configuration whitelist: true et forbidNonWhitelisted: true du ValidationPipe global. Aucun document ne confirme la configuration actuelle du ValidationPipe dans le projet. Si whitelist n'est pas actif, un client pourrait envoyer dissemination_package_id sans etre rejete au niveau DTO — la spec exigerait alors un 400 E-400-READONLY-FIELD que seul le service pourrait generer.
ZO-03 : Tests d'integration DB — planification non tracee¶
L'acceptabilite (E-01 a E-04, MINEURS) et les tests identifient 4 categories necessitant des tests d'integration PostgreSQL reels : - WORM triggers (TC-INV-06, TC-NR-01) - RLS (TC-INV-07, TC-NR-02) - Concurrence reelle (TC-INV-12) - Chiffrement au repos (TC-INV-10, TC-NEG-05)
L'acceptabilite mentionne "A planifier dans un sprint suivant" (TODO-03) mais aucun ticket JIRA n'est reference. La couverture de ces invariants repose uniquement sur la presence des mecanismes dans le code, pas sur leur validation effective.
ZO-04 : TLA+ TLC — verification formelle non executee¶
TC-FML-01 (CA-10) n'a pas ete execute. Le TLA+ report confirme que norm.yaml a ete modifie mais que _AnchoredFacts_NF.tla doit etre regenere par CI. Aucun document ne confirme que la regeneration a eu lieu ni que TLC a ete lance sur la configuration mise a jour.
ZO-05 : Partitioning attestations — job de maintenance absent¶
La migration cree 2 partitions (2025, 2026). La spec §5.13 exige une conservation de 10 ans. Le plan (§14) mentionne un "job de maintenance annuel" pour creer la partition N+1 et l'archivage cold storage. L'acceptabilite (TODO-02) confirme "Erreur INSERT si partition manquante". Aucun ticket n'est reference pour ce job. A partir du 2027-01-01, les INSERT dans dissemination_attestations echoueront si la partition 2027 n'est pas creee.
ZO-06 : retention_service — existence du role BYPASSRLS non verifiee¶
Le plan (H-08) mentionne : "retention_service possede un role technique avec BYPASSRLS (REX PD-16)". L'acceptabilite (H-08) suppose : "retention_service role IAM avec BYPASSRLS". Aucun document ne confirme que ce role existe reellement dans l'infrastructure actuelle ni que le BYPASSRLS est configure au niveau PostgreSQL.
ZO-07 : geo_copy_count — valeur toujours 0 ?¶
Le plan (H-06, V-08) et l'entity report (§6) confirment que geo_copy_count existe avec DEFAULT 0. La spec exige copies >= MIN_COPIES (defaut 2). Si la couche stockage (PD-279) ne met pas a jour geo_copy_count, la garde bloquera systematiquement toutes les transitions SEALED->DIP avec E-422-GUARD-COPIES. Aucun document ne confirme que le mecanisme de comptage est operationnel.
ZO-08 : Comportement exact de AuditLogService.logAsync()¶
Le filter utilise auditLogService.logAsync(). L'agent report dip-exception-filter (§8 DA-01) decrit : "sync attempt + queue fallback". Mais aucun document ne documente le comportement exact de logAsync() : est-ce un await qui ecrit directement en base ? est-ce un enqueue BullMQ ? quelle est la garantie de persistance ? Cette zone d'ombre est directement liee a DIV-02 et DIV-07.
ZO-09 : Down migration — enum DIP non supprimable¶
La spec §5.7 mentionne "Down migration : Retrait de DIP uniquement si aucun enregistrement actif en DIP". Le plan (V-10), la migration report et l'acceptabilite (TC-NR-06) confirment la garde COUNT(*) > 0. Cependant, la migration report precise : "L'enum DIP reste dans le type PostgreSQL (DROP VALUE n'existe pas)". Le down retirerait les colonnes et la table attestations, mais la valeur DIP resterait dans l'enum PostgreSQL. La spec ne mentionne pas cette limitation.
ZO-10 : Coverage du fichier dissemination.config.ts¶
L'acceptabilite (E-08, MINEUR) note que dissemination.config.ts a 0% de coverage directe (pas de fichier .spec.ts dedie). L'agent config report (§ matrice de couverture) propose 7 cas de tests (TC-CONFIG-01 a TC-CONFIG-07) mais aucun n'a ete implemente. La validation de la config est testee implicitement via les tests du service.
5. Recommandation¶
- Proceder — convergence confirmee, aucun conflit bloquant
- Rework necessaire — divergences a resoudre avant de continuer
- Escalade — decision humaine requise sur un point structurant
Justification¶
Deux divergences necessitent une resolution avant la gate :
-
DIV-02 + DIV-07 (MAJEUR) : Le mecanisme d'audit synchrone des refus securite (spec §5.8) ne fonctionne pas comme contractualise. Le QueryRunner du filter est decorelle de l'ecriture audit reelle. La question n'est pas de savoir si
logAsync()ecrit — c'est de savoir si la garantie "persistance AVANT retour HTTP" est tenue. Ce point est deja signale comme E-10 MAJEUR dans l'acceptabilite. -
DIV-01 (MINEUR mais contractuel) : Les codes E-404 et E-503, absents de la spec, sont exposes par l'API. Si la spec est le contrat de reference, ces codes doivent y etre ajoutes ou documentes comme extensions implementees.
Les autres divergences (DIV-03 a DIV-10) sont documentees par le plan ou l'acceptabilite et ne bloquent pas la gate, mais meritent un tracking explicite.
Les zones d'ombre ZO-01 (reviews LLM), ZO-07 (geo_copy_count operationnel) et ZO-08 (comportement logAsync) sont les plus impactantes et devraient etre clarifiees.