Aller au contenu

PD-287 — Rapport de confrontation (Étape 8 — Gate 8 CLOSURE)

Ce rapport confronte les artefacts disponibles pour la Gate 8 (clôture d'acceptabilité) de PD-287. Il rend visibles les convergences et divergences factuelles, sans lisser les conflits ni proposer de remédiation. Les verdicts sont laissés au PMO.

1. Sources confrontées

Source Étape d'origine État
PD-287-specification.md Step 1 — Spécification Figée post-Gate 3 (GO)
PD-287-tests.md Step 2 — Tests contractuels Figée post-Gate 3 (GO)
PD-287-plan.md Step 4 — Plan d'implémentation Figé post-Gate 5 (GO)
PD-287-acceptability.md Step 7 — Acceptabilité Verdict NON_CONFORME (5 bloquants code + 2 critiques sécurité actionnables)

2. Convergences

  • CV-01 — Architecture cible : Spec §10.1, Plan §1 et Acceptabilité s'accordent sur la stack ProbatioVault-backend (NestJS + TypeORM + PostgreSQL), module SharingModule + workers dédiés.
  • CV-02 — FSM à 5 états : Spec §5.5 et Plan §3 (INV-287-06/07/10) convergent sur les 5 états PENDING_ACTIVATION / ACTIVE / OTP_BLOCKED / REVOKED / EXPIRED et sur la terminalité REVOKED/EXPIRED.
  • CV-03 — Zero-knowledge périmètre : Spec INV-287-01, Tests TC-INV-11/TC-NEG-06, Plan §3 et §5 convergent sur la borne du test (réseau + mémoire + logs/dumps).
  • CV-04 — Fail-closed journal : Spec INV-287-13/INV-287-21 + ERR-287-14, Tests TC-ERR-14, Plan §2.3 step 7 et §6 convergent sur la réponse 503 + rollback si l'append synchrone échoue.
  • CV-05 — Anti-enumeration 404 générique : Spec INV-287-12 + CA-287-18, Tests TC-INV-06/TC-ERR-05/TC-NEG-02, Plan §2ter (guard) + §6 (mapper) convergent sur l'enveloppe unique 404 pour tout cas d'invalidité côté destinataire.
  • CV-06 — Rétention RGPD J+90 : Spec INV-287-15 + §5.11 + CA-287-30, Tests TC-NOM-19/TC-INV-12, Plan §3 (C8 RetentionWorker) convergent sur le délai et la préservation du journal.
  • CV-07 — Stack d'idempotence et lock : Spec §5.6, Tests TC-NOM-15/TC-ERR-12/TC-ERR-13/TC-ERR-19, Plan §2.1 (étapes 3–4) et §2.7 convergent sur Redis lock TTL 600s + table share_idempotency_keys.
  • CV-08 — Feature-flag export ZIP : Spec §5.4 F-287-04 + Q-287-06, Tests TC-NOM-08 (conditionnel) + TC-NEG-07, Plan §2.4 + §5bis convergent sur le flag export_zip_enabled=false par défaut, 404 générique si désactivé.
  • CV-09 — Retrait INV-287-14 : Spec §4 (note de traçabilité), matrice de couverture Tests §2, Plan §4 (absence de ligne) convergent sur le retrait de INV-287-14 et la reclassification de l'export ZIP en nice-to-have.
  • CV-10 — WORM inchangé : Spec INV-287-03 + CA-287-15, Tests TC-NOM-13/TC-NR-02, Plan §3 (mapping INV-287-03) convergent : aucun chemin d'écriture sur le blob original.

3. Divergences

Les divergences ci-dessous ne sont pas lissées. Chaque ligne cite les sources exactes et leur contradiction.

3.1 Divergences implémentation ↔ plan/spec (issues de l'acceptabilité)

  • DIV-01 — Session non liée au share_link_id (critique sécurité #1, CVSS 9.1)
  • Source A — Plan §3 mapping INV-287-04/INV-287-05 : « session liée au share_link_id + IP+UA fingerprint faible » ; INV-287-05 exige compartimentation inter-partages.
  • Source B — Acceptabilité §Review Sécurité #1 : « Session non liée au shareLinkId : une session d'un lien A peut accéder au lien B ».
  • Impact : viole INV-287-05 (compartimentation) et INV-287-22 (guard inter-module). Bypass possible du FSM par réutilisation de session.

  • DIV-02 — reEncrypt() n'appelle pas verifyCertificateChain() (critique sécurité #2, CVSS 9.0)

  • Source A — Plan §3 mapping INV-287-17 + §8 H-plan-05 : TrustStoreService chargé au bootstrap, vérification chaîne certificat destinataire éphémère obligatoire avant toute opération PRE.
  • Source B — Acceptabilité §Review Sécurité #2 : « reEncrypt() ne fait pas verifyCertificateChain(), seulement isReady() ».
  • Impact : viole INV-287-17 (trust-store obligatoire, fail-closed). Un certificat forgé non rejeté permettrait une ré-encapsulation PRE hors gouvernance.

  • DIV-03 — Ownership preuve non vérifié à la création (bloquant code #1)

  • Source A — Plan §2.1 étape 5 : « Vérification ownership preuve via ProofsService.assertOwner(proof_id, userId) ».
  • Source B — Acceptabilité §Review Code #1 : « createShareLink ne vérifie pas proofIdownerUserId avant création ».
  • Impact : un propriétaire peut créer un lien de partage sur une preuve qu'il ne possède pas. Violation implicite de la spec (F-287-01 « Contrôles: ownership preuve »).

  • DIV-04 — ShareProofGuard non homogène vs anti-enum (bloquant code #2)

  • Source A — Plan §2ter + §3 mapping INV-287-12 + §6 mapping erreurs : GenericErrorMapper doit retourner 404 homogène avec message statique.
  • Source B — Acceptabilité §Review Code #2 : « ShareProofGuard relance les NotFoundException internes avec messages différents (INV-SEC-8) ».
  • Impact : viole INV-287-12. Fuite d'existence de ressources par différenciation de messages.

  • DIV-05 — Hash-chain race (bloquant code #3, haute sécurité #7)

  • Source A — Plan §3 mapping INV-287-13 + ADR-287-03 : séquenceur audit_sequence_number BIGSERIAL + worker mono-instance via pg_advisory_lock, lecture sous verrou.
  • Source B — Acceptabilité §Review Code #3 : « audit-journal.service.ts lit le dernier hash sans verrou — fork possible en concurrence ».
  • Impact : viole INV-287-13 (chaîne immuable) et INV-287-21 (atomicité). Deux ancres parallèles possibles → journal divergent.

  • DIV-06 — Consultation race sur maxConsultations (bloquant code #4)

  • Source A — Plan §9 point #12 : paramètre optionnel avec incrément sur LINK_VIEWED, spec §5.2 max_consultations_per_link.
  • Source B — Acceptabilité §Review Code #4 : « contrôle maxConsultations hors verrou, dépassement possible ».
  • Impact : si la feature est activée, dépassement du quota consultable en concurrence. Violation de la borne contractuelle §5.2.

  • DIV-07 — Idempotence purge (bloquant code #5)

  • Source A — Plan §2.1 étapes 3–4 + §6 : 409 IDEMPOTENCY_CONFLICT / IDEMPOTENCY_WINDOW_EXPIRED selon le cas, contrainte UNIQUE.
  • Source B — Acceptabilité §Review Code #5 : « clé expirée traitée comme absente sans nettoyage → unicité cassée ».
  • Impact : viole CA-287-23 et INV-287-20. Replay possible d'une clé expirée écrasant un enregistrement initial.

  • DIV-08 — Transactions atomiques non appliquées (majeurs code #6/#7/#8)

  • Source A — Plan §3 mapping INV-287-21 + ADR-287-03 : audit append dans la même transaction SQL que l'UPDATE métier ; rollback si l'append throw.
  • Source B — Acceptabilité §Review Code : createSession hors transaction avec audit ; purgeLink hors transaction atomique ; expireOrphanLinks update+audit hors transaction.
  • Impact : viole INV-287-21. Crash post-commit partiel possible → état DB sans entrée audit correspondante (fail-open silencieux).

  • DIV-09 — OTP blocage sans transition FSM (majeur code #9)

  • Source A — Plan §2.2 étape 3 (branche « 5e échec ») + Spec §5.5 PENDING_ACTIVATION → OTP_BLOCKED.
  • Source B — Acceptabilité §Review Code #9 : « OTP blocage ne transitionne pas shareLink.state → OTP_BLOCKED ».
  • Impact : viole INV-287-06/INV-287-07 (états fermés, transitions listées). Le share_state en base reste incohérent avec share_otp_state.

  • DIV-10 — TRUST_STORE_CONFIG provider non visible (majeur code #10)

  • Source A — Plan §1 C9 + §8 H-plan-05 : chargement au bootstrap, fail-closed explicite.
  • Source B — Acceptabilité §Review Code #10 : « Trust-store TRUST_STORE_CONFIG provider non visible dans les modules ».
  • Impact : viole INV-287-17. Si le provider n'est pas injecté, la vérification est absente ou utilise un stub.

  • DIV-11 — Export ZIP : archive.pipe() vers mauvais type de stream (majeur code #11)

  • Source A — Plan §2.4 : streaming ZIP vers destinataire, check taille pré-streaming.
  • Source B — Acceptabilité §Review Code #11 : « Export ZIP archive.pipe() vers mauvais type de stream ».
  • Impact : viole CA-287-13 (vérification offline signée). Export potentiellement corrompu ou tronqué.

  • DIV-12 — Tests unitaires/intégration absents

  • Source A — Plan §5 (matrice TC-NOM/TC-ERR/TC-INV) + §5bis (couverture 80 %/80 %) + §1 C14 « agent-tests ».
  • Source B — Acceptabilité §Prérequis acceptabilité : « Tests CI : pas de tests unitaires/intégration dans ce périmètre (code agents) » ; « Coverage : N/A (pas de tests) ».
  • Impact : viole INV-287-25 (toute règle doit être testable) et la couverture contractuelle annoncée. Aucun test TC-NOM/TC-ERR/TC-INV n'a été exécuté en CI.

  • DIV-13 — Sonar skipped

  • Source A — Spec implicite + CLAUDE.md .claude/rules/procedures.md (Phase 1.5 Sonar local est BLOQUANTE, derogation ESLint+tsc non acceptée, sonar-scanner doit être installé).
  • Source B — Acceptabilité §Analyse Sonar : « Quality Gate : SKIP (Docker unavailable, sonar-scanner non installé) ».
  • Impact : viole la procédure Gate 7 (phase 1.5). Aucun contrôle SonarQube effectué avant Gate 8.

  • DIV-14 — recipientSessionId exposé en audit events (haute sécurité #8)

  • Source A — Plan §7 « Session hijacking : recipient_session_id = UUIDv4 + cookie HttpOnly ».
  • Source B — Acceptabilité §Review Sécurité #8 : « recipientSessionId exposé en audit events → fuite bearer ».
  • Impact : viole INV-287-04 + l'hypothèse cookie HttpOnly; Secure. Un audit lu par un opérateur ou exporté devient porteur d'une capacité bearer.

  • DIV-15 — maybeExpireLink() fail-open (haute sécurité #5)

  • Source A — Plan INV-287-09 + §3 : worker + check inline guard rigide, fail-closed.
  • Source B — Acceptabilité §Review Sécurité #5 : « maybeExpireLink() fail-open (catch/warn) ».
  • Impact : viole INV-287-09. Un lien peut rester consommable post-TTL si l'expiration déclenche une exception silencieuse.

  • DIV-16 — PRE en mode STUB (critique sécurité #3, CVSS 9.8)

  • Source A — Plan §2 interdiction stub + ESLint « no-plaintext-key » + lint CI « stub cryptographique interdit ».
  • Source B — Acceptabilité §Review Sécurité #3 : « PRE en mode STUB (pas de crypto réelle) — attendu, documenté STUB: PD-287 ».
  • Impact : viole INV-287-02 (PRE obligatoire) et INV-287-18 (tests roundtrip complets). L'acceptabilité marque ce point « attendu » mais contredit l'interdiction explicite du plan.

3.2 Divergences plan ↔ spécification (extensions non validées)

  • DIV-17 — Extension de l'enum D-287-23
  • Source A — Spec §5.1 D-287-23 : enum fermée LINK_CREATED|LINK_ACTIVATED|LINK_VIEWED|LINK_EXPORTED|LINK_REVOKED|LINK_EXPIRED.
  • Source B — Plan §9 point #26 + §2.2 + §2.7 : ajout LINK_OTP_BLOCKED, LINK_REAUTH_REQUESTED, LINK_REAUTH_CONFIRMED présentés comme « correction spec proposée ».
  • Impact : extension non ratifiée par la spec figée post-Gate 3. Les tests ne couvrent pas ces événements (absents de §3/§5 Tests).

  • DIV-18 — Endpoints /reauth/* non listés par la spec

  • Source A — Spec §5.4 F-287-02 : ne liste pas d'endpoint reauth distinct, le diagramme de séquence §5bis les mentionne sous le nom /reauth/request-otp et /reauth/confirm.
  • Source B — Plan §2.3 + §1 C1 : formalise ces endpoints avec rate-limit et anti-enum.
  • Impact : le diagramme spec les montre, mais aucune entrée contractuelle §5.4 ou §6 ne les documente. Couverture test non explicite.

  • DIV-19 — OTP_CODE_TTL_SECONDS=300s

  • Source A — Spec §5.2 : pas de borne TTL OTP, seule la borne otp_block_duration_seconds existe.
  • Source B — Plan §2.2 + §9 point #10 + H-plan-03 : ajout constante OTP_CODE_TTL_SECONDS=300s non configurable MVP.
  • Impact : comportement différenciant non spécifié (OTP expiré avant 5 min → 404 générique). Non couvert par un TC dédié.

  • DIV-20 — Chiffrement pgp_sym_encrypt données destinataire

  • Source A — Spec INV-287-16 : secrets crypto temporaires chiffrés (DEK envelope, rekey, fragments). recipient_email, client_ip, user_agent ne sont pas listés comme secrets.
  • Source B — Plan §7 mitigation écart #15 : chiffrement pgp_sym_encrypt sur ces trois colonnes.
  • Impact : mesure de renforcement RGPD, mais non exigée par la spec. Les tests TC-NEG-06 scannent les secrets mais pas ces colonnes. Pas de conflit fonctionnel, divergence de périmètre crypto.

  • DIV-21 — ADR-287-01 livraison inline base64 jusqu'à 50 MB

  • Source A — Spec §5.4 F-287-03 et diagramme §5bis : fournit encrypted_doc_ref (référence), format de transport non contractualisé.
  • Source B — Plan ADR-287-01 : inline base64 jusqu'à 50 MB, sinon 413 ou stream binaire.
  • Impact : introduit une borne max_inline_document_bytes = 50 MB absente de la spec (§5.2). Risque : document probatoire > 50 MB non couvert.

  • DIV-22 — ADR-287-02 scope compte serveur pour le warning DRM

  • Source A — Spec CA-287-26 + INV-287-24 : « affiché une seule fois puis mémorisé » sans préciser le scope.
  • Source B — Plan ADR-287-02 : persistance serveur niveau compte utilisateur (user_preferences.no_drm_ack_at_utc).
  • Impact : choix d'implémentation documenté. Non testé explicitement pour la multi-device côté TC-NOM-11 (scope serveur validé, front hors périmètre).

3.3 Divergences tests ↔ acceptabilité

  • DIV-23 — Couverture contractuelle revendiquée vs absence de tests
  • Source A — Tests §2 matrice + §10 « Verdict QA : Testable intégralement = Oui ».
  • Source B — Acceptabilité §Prérequis : aucun test exécuté.
  • Impact : le verdict QA de la step 2 n'est pas factuel post-step 6. Les observables TC-* n'ont pas de preuve d'exécution.

  • DIV-24 — TC-INV-11 protocole mémoire non appliqué

  • Source A — Plan §9 point #11 : script scripts/security/memory-scan.sh, heap snapshot post-3×GC.
  • Source B — Acceptabilité : aucune trace d'exécution de TC-INV-11 ni de script memory-scan.
  • Impact : INV-287-01 non vérifié par le test contractuel.

4. Zones d'ombre

  • ZO-01 — Stubs PRE et Gate 8 : l'acceptabilité marque la PRE en STUB comme « attendu, documenté STUB: PD-287 ». Aucun des trois documents (spec, tests, plan) ne prévoit de dérogation STUB pour une primitive crypto obligatoire (INV-287-02/18). Le statut « attendu » est introduit uniquement par l'acceptabilité — sans traçabilité de la story de destination PD-XXX ni de la date cible de retrait du stub.
  • ZO-02 — Absence de tests CI : aucun des documents n'indique si l'absence de tests résulte d'un choix d'architecture agents (implémentation sans tests livrée intentionnellement) ou d'un oubli. Le plan §1 C14 « agent-tests » et §5bis (couverture 80 %/80 %) sont contredits sans explication.
  • ZO-03 — Sonar indisponible : l'acceptabilité mentionne « Docker indisponible » sans indiquer de plan d'installation local brew install sonar-scanner conformément à la procédure projet. Zone d'ombre sur la capacité à appliquer la règle « sonar-scanner DOIT être installé par l'orchestrateur ».
  • ZO-04 — Événements audit reauth/otp-blocked : si l'extension DIV-17 est acceptée, les champs prev_hash/event_hash pour ces nouveaux types doivent être couverts par TC-NOM-14. Aucune trace d'ajustement de la matrice de tests.
  • ZO-05 — Feature max_consultations_per_link : la spec définit la borne, le plan la traite comme feature dormante sans test obligatoire MVP, l'acceptabilité identifie une race sur la mécanique. Périmètre de livraison MVP ambigu (désactivée par défaut mais code livré non sûr).
  • ZO-06 — Performance P95 révocation : aucune des trois pièces (plan §5 Perf, tests TC-NOM-09b, acceptabilité) ne présente de mesure. INV-287-08 / CA-287-08 non vérifiés à ce stade.
  • ZO-07 — Trust-store OCSP : le plan §9 point #25 introduit la variable TRUST_STORE_REQUIRE_OCSP ; la spec ne la mentionne pas. L'acceptabilité ne trace pas sa valeur par défaut ni l'état de configuration staging.
  • ZO-08 — Notifications propriétaire (3 blocages, seuil 50) : le plan §1 C11 et les tests TC-NOM-05/TC-NOM-18 prévoient l'émission ; l'acceptabilité ne confirme ni n'infirme la livraison du dispatcher.
  • ZO-09 — Stub MailService / PD-287-MAIL-STUB : plan §5bis indique un stub mail tracé, aucune trace dans les revues code/sécurité sur sa présence effective.
  • ZO-10 — Coverage minimale 80 %/80 % : plan §5bis fixe le seuil ; l'acceptabilité coche « Coverage : N/A (pas de tests) ». Aucune décision explicite de dérogation ou de report.

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

Motif : l'acceptabilité step 7 rend un verdict NON_CONFORME (5 bloquants code + 2 critiques sécurité actionnables hors stub PRE documenté). Les divergences DIV-01 à DIV-16 documentent un écart direct entre l'implémentation livrée et les invariants/mécanismes du plan et de la spécification (INV-287-02/04/05/06/07/09/12/13/17/20/21/22, CA-287-18/23). À cela s'ajoutent les divergences plan↔spec DIV-17 à DIV-22 (extensions non ratifiées) et l'absence totale de tests CI et d'analyse Sonar (DIV-12/13/23/24). Le dossier est factuellement incompatible avec une clôture Gate 8 GO en l'état ; une décision PMO est requise sur la portée du rework et le statut de la dérogation STUB PRE (ZO-01).