Aller au contenu

PD-278 — Rapport de confrontation (Étape 5 — Gate AMBIGUITY)

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

1. Sources confrontées

Document Étape d'origine Version
PD-278-specification.md Étape 1 Correction finale v3
PD-278-tests.md Étape 2 Correction finale v3
PD-278-plan.md Étape 4 v2 (19 écarts corrigés)
PD-278-code-contracts.yaml Étape 4 v2

2. Convergences

  • Machine à états DIP : Les 4 documents convergent sur les transitions SEALED → DIP (gardes INV-278-02) et DIP → SEALED (explicite uniquement, INV-278-03). Transitions interdites identiques dans chaque document.
  • 14 invariants (INV-278-01 à INV-278-14) : Spec, tests et plan s'accordent sur la formulation, la justification et les observables de chaque invariant. Les code contracts reprennent fidèlement les invariants par module.
  • Atomicité package multi-documents : Spec §5.8, tests TC-ERR-11/TC-INV-11, plan C9 et code contracts dip-service convergent sur le pattern all-or-nothing via ROLLBACK global.
  • Audit transitions + refus sécurité synchrone : Spec §5.8 (transaction légère dédiée pour refus), tests TC-INV-04 (audit complet chaque cas), plan C11 (QueryRunner dédié synchrone), code contracts dip-exception-filter (persistance synchrone) — tous alignés.
  • Attestation de restitution : Spec §5.13 (schéma contractuel), tests TC-INV-05/TC-NOM-01, plan C1/C2/C9 (table vault_secure.dissemination_attestations), code contracts dip-attestation — 1 attestation par requête, reliée aux documents et à l'acteur.
  • WORM préservé en DIP : Spec INV-278-06, tests TC-NR-01/TC-INV-06/TC-NOM-06, plan trigger DB trg_motif_communication_worm, code contracts — alignés.
  • RLS préservée : Spec INV-278-07, tests TC-ERR-05/TC-NR-02/TC-INV-07, plan SET LOCAL app.current_user_id, code contracts — alignés.
  • Contrôle de concurrence : Spec §5.9 (tri lexicographique), tests TC-INV-12, plan SELECT FOR UPDATE ORDER BY id ASC, code contracts // LOCK-ORDER — alignés.
  • Ordre temporel : Spec INV-278-13 (returned_at = max(now_utc, disseminated_at)), tests TC-NOM-03/TC-NEG-07/TC-INV-13, plan GREATEST(NOW(), disseminated_at), code contracts — alignés.
  • Anti-contournement rétention : Spec INV-278-14, tests TC-ERR-14/TC-INV-13 cas B, plan C9 garde + retention_service BYPASSRLS, code contracts — alignés.
  • Rate-limit : Spec §5.6 (60 req/min, 1000 req/jour), tests TC-ERR-13, plan C8 (Redis INCR + EXPIRE), code contracts dip-rate-limit (fail-closed) — alignés.
  • Bornes numériques : Spec §5.6, tests TC-ERR-08A/08B, plan C4 (config Joi), code contracts dip-config — alignés sur MIN_COPIES=2, N_MAX=100, motif max 1024.
  • Contrat API : Spec §5.14, plan C10 (2 endpoints POST), code contracts dip-controller — convergents sur les routes et la sémantique.
  • Couverture tests : Tests matrice 100% des 14 invariants et 15 critères d'acceptation couverts. Plan confirme 49 tests contractuels. Aucun trou de couverture identifié.
  • Scénarios spec ST-01 à ST-11 : Tous 11 scénarios de la spec sont traçables dans les tests (TC-NOM-01, TC-ERR-07, TC-NOM-04, TC-NOM-03, TC-ERR-08A, TC-ERR-08B, TC-ERR-11, TC-ERR-12, TC-INV-12, TC-INV-13 cas B, TC-ERR-14 respectivement).
  • Patterns réutilisés : Plan et code contracts s'accordent sur la réutilisation de patterns établis : PD-250 (migration enum), PD-279 (RestitutionService/QueryRunner), PD-81 (LegalRateLimitGuard), PD-60 (DepositAuditExceptionFilter).
  • Interdictions Math.random() et subquery index : Code contracts et plan reprennent fidèlement les REX PD-63 et PD-55.

3. Divergences

Les conflits ne doivent JAMAIS être lissés. Chaque divergence est rendue visible.


  • DIV-01 (MAJEUR) : Cardinalité du set d'états — spec « exactement 4 » vs plan/DB « 5 incluant RESTITUTED »
  • Source Spec §4 INV-278-01 : « Le set des statuts implemente inclut exactement PENDING, SEALED, DIP, EXPIRED. »
  • Source Plan §3 INV-278-01, §4 CA-01, V-01 : « L'enum DB contient 5 valeurs (PENDING, SEALED, DIP, EXPIRED, RESTITUTED). PD-278 ajoute DIP. Le test vérifie DIP ∈ enum_range, pas la cardinalité exacte. »
  • Source Tests TC-INV-01 : « Ensemble exact = {PENDING, SEALED, DIP, EXPIRED} — Aucune valeur manquante ou additionnelle. »
  • Impact : Le test TC-INV-01 tel que rédigé dans le document tests (vérifiant l'ensemble exact = 4 valeurs) échouera si l'enum DB contient RESTITUTED (PD-279 DONE). Le plan a corrigé cette interprétation mais le document tests n'a pas été mis à jour. Incohérence tests vs plan sur le même test TC-INV-01.

  • DIV-02 (MAJEUR) : Codes d'erreur 404 et 503 absents de la spec
  • Source Spec §6 : Liste les codes E-400 à E-500 (11 codes). Aucun E-404-NOT-FOUND ni E-503-REDIS.
  • Source Plan §6 : Ajoute E-404-NOT-FOUND (document non trouvé après SELECT FOR UPDATE) et E-503-REDIS (Redis indisponible, fail-closed).
  • Impact : Le plan introduit 2 codes d'erreur non contractualisés dans la spec. Si la gate valide le plan sans mise à jour spec, ces codes ne sont pas traçables vers un contrat. Le cas 404 est particulièrement ambigu : le plan note que RLS pourrait filtrer un document existant, aboutissant à un 404 qui devrait être un 403.

  • DIV-03 (MINEUR) : Nommage du path parameter dans l'endpoint retour DIP→SEALED
  • Source Spec §5.14 : POST /api/v1/documents/{document_id}/dissemination-return
  • Source Plan §2.2, code contracts dip-controller : POST /api/v1/documents/{id}/dissemination-return
  • Impact : Divergence cosmétique (document_id vs id). Sans impact fonctionnel si le route pattern est identique, mais crée une ambiguïté de traçabilité dans la documentation API.

  • DIV-04 (MINEUR) : Scope du fuzz test TC-INV-09 — 4×4 vs 5×5
  • Source Tests TC-INV-09 : « Ensemble des transitions possibles sur 4 etats » → 4×4 = 16 combinaisons.
  • Source Plan §5.3 TC-INV-09 : « Fuzz 5×5 combinaisons (incluant RESTITUTED) » → 25 combinaisons.
  • Impact : Le document tests ne mentionne pas RESTITUTED dans le fuzz. Le plan étend le scope sans que le document tests ne le reflète. Les transitions impliquant RESTITUTED ne sont pas définies par PD-278 — le fuzz 5×5 du plan pourrait rejeter des transitions RESTITUTED valides (définies par PD-279) comme des erreurs.

  • DIV-05 (MINEUR) : Absence de TC-INV-14 dédié pour la non-bypass rétention
  • Source Spec §4 : INV-278-14 est un invariant distinct (« retention_due ne peut pas etre contournee via maintien indefini en DIP »).
  • Source Tests §2 matrice : INV-278-14 couvert par TC-INV-13 (cas B) + TC-ERR-14.
  • Source Plan §5.3 : Confirme mapping vers TC-INV-13 + TC-ERR-14.
  • Impact : Pas de test dédié TC-INV-14. La couverture de INV-278-14 est dispersée dans un test nommé d'après INV-278-13. Risque de traçabilité : un échec de TC-INV-13 ne distingue pas clairement lequel des deux invariants (13 ou 14) est violé.

4. Zones d'ombre

  • ZO-01 : Mécanisme d'enforcement du hard timeout SLA — La spec §5.5 contractualise un hard timeout (5000 ms) au-delà duquel la transition doit échouer atomiquement. Le plan mentionne slaHardTimeoutMs dans la config (C4) mais aucun mécanisme technique (PostgreSQL statement_timeout, Node.js AbortController, watchdog) n'est décrit dans C9 ni dans les code contracts pour l'enforcer. TC-NOM-05 l'observe mais ne décrit pas le mécanisme.

  • ZO-02 : Algorithme de calcul de hash_evidence — La spec §5.13 mentionne « empreinte intégrité » et le plan déclare le champ TEXT NOT NULL. Aucun document ne spécifie l'algorithme (SHA-256 ? HMAC ? Quels inputs : document_ids + actor_id + issued_at ?), ni si le calcul est fait applicativement ou via HSM. Le code contract dip-service ne mentionne pas le mécanisme de calcul.

  • ZO-03 : Schéma de la réponse API (201 Created / 200 OK) — La spec §5.14 définit les bodies d'entrée mais pas les bodies de réponse. Le plan mentionne DisseminationResponseDto (C5) sans décrire ses champs. Le test TC-NOM-01 vérifie attestation_id dans la réponse mais le contrat de réponse n'est formalisé nulle part.

  • ZO-04 : Authentification service-to-service pour retention_service — La spec autorise retention_service pour DIP→SEALED (INV-278-03, INV-278-14). Le plan décrit BYPASSRLS et le flux §2.4 comme « appel interne ». Aucun document ne précise le mécanisme d'authentification (JWT technique ? mTLS ? service account dans le même runtime ?). Le guard JwtAuthGuard est appliqué sur le controller — retention_service doit donc avoir un JWT valide.

  • ZO-05 : Comportement SELECT FOR UPDATE vs RLS — distinction 404 / 403 — Le plan §6 note que RLS peut filtrer des documents existants, rendant le SELECT vide. Le plan C9 étape 6 mentionne « E-404-NOT-FOUND si inexistant, E-403-RLS si RLS filtré ». Mais le mécanisme de distinction n'est pas décrit : un SELECT FOR UPDATE filtré par RLS retourne 0 lignes, indistinguable d'un document inexistant. Aucune requête secondaire (BYPASSRLS count check) n'est spécifiée.

  • ZO-06 : Monitoring dérive NTP — La spec §5.10 contractualise « derive max configurable, defaut 100 ms ». Aucun mécanisme de monitoring ou d'alerte n'est décrit dans le plan. Le GREATEST() gère le clock skew réactif mais pas la détection proactive.

  • ZO-07 : Trigger motif_communication — gap défense-en-profondeur NULL→value — Le trigger du plan autorise NULL → valeur (condition OLD.motif_communication IS NOT NULL). Un UPDATE ultérieur sur un document DIP dont le motif était NULL pourrait définir un motif après coup au niveau DB. L'API ne fournit pas cette route, mais la « dernière ligne de défense » DB ne couvre pas ce cas. La spec dit « immuable après création » — si création = la transition SEALED→DIP, un NULL initial devrait rester NULL.

  • ZO-08 : Partitioning de la table attestations — complexité vs scope — Le plan §13 (C1 step 17) propose un partitioning RANGE(issued_at) mais mentionne aussi une « alternative si partitioning complexe hors scope : table simple + dette technique avec story destination ». La décision n'est pas tranchée. En Gate 5, ce choix devrait être résolu car il impacte la migration DDL.

  • ZO-09 : Publication audit async — SLO 15 min (Q-04) — La spec Q-04 fixe un SLO de rattrapage <= 15 min. Le plan mentionne AuditLogService.logAsync() post-commit (pattern existant) mais aucun mécanisme de monitoring du SLO 15 min n'est planifié dans les composants C1–C13.

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 : DIV-01 (incohérence tests vs plan sur TC-INV-01 / cardinalité enum) et DIV-02 (codes 404/503 non contractualisés dans la spec) sont des écarts MAJEURS. DIV-01 aboutira à un test qui échoue en intégration (RESTITUTED déjà présent). DIV-02 introduit des comportements non traçables. Les zones d'ombre ZO-01 (hard timeout) et ZO-02 (hash_evidence) impactent l'implémentabilité de 2 invariants (§5.5 et INV-278-05/10).

Actions correctives suggérées : 1. Aligner la spec INV-278-01 sur la réalité DB 5 valeurs, ou reformuler en « DIP ∈ statuts implémentés ». 2. Aligner le document tests TC-INV-01 avec l'interprétation plan (DIP ∈ enum_range, pas cardinalité = 4). 3. Ajouter E-404-NOT-FOUND et E-503-REDIS dans la spec §6 ou les retirer du plan. 4. Trancher ZO-08 (partitioning oui/non) avant Gate 5.