PD-287 — Rapport de confrontation (Étape 5 — Gate 5 Plan)¶
Ce rapport est produit par l'orchestrateur Claude avant la Gate 5 PMO. Il confronte la spécification, les tests, le plan d'implémentation et les code contracts pour identifier convergences, divergences et zones d'ombre.
1. Sources confrontées¶
PD-287-specification.md(étape 1, post Gate 3 GO) — 25 invariants, 35 données contractuelles, 7 flux nominaux, FSM à 5 états, 20 cas d'erreur, 31 critères d'acceptation, hypothèses H-287-01..08, points Q-287-01..07.PD-287-tests.md(étape 2, post Gate 3 GO) — matrice de couverture INV/CA, 19 tests nominaux, 20 tests d'erreur, 14 tests d'invariants, 5 non-régression, 14 adversariaux.PD-287-plan.md(étape 4) — 14 composants (C1..C14), 4 waves, 28 points de vigilance, ADR-287-01/02/03, hypothèses H-plan-01..09.PD-287-code-contracts.yaml(étape 4) — 11 modules, interfaces, invariants, forbidden patterns, dependency graph, cross_module_protections, feature flags, constants.
2. Convergences¶
- FSM : les 5 états
PENDING_ACTIVATION / ACTIVE / OTP_BLOCKED / REVOKED / EXPIREDet les transitions listées §5.5 spec sont reprises intégralement par le plan (ShareStateMachineC1) et par les contracts (invariant INV-287-06/07 modulesharing-api). Trigger PGprevent_forbidden_state_transitionajouté par le plan en défense en profondeur ; pas de contradiction. - Terminalité REVOKED/EXPIRED : spec INV-287-10, tests TC-INV-02 + TC-NEG-12, plan §3 mapping + trigger PG. Couverture cohérente.
- Anti-enumeration 404 générique destinataire : spec INV-287-12, tests TC-INV-06 + TC-ERR-05 + TC-NEG-02, plan
GenericErrorMapperC1 + jitter latence, contracts modulesharing-api(forbidden "différenciation de message"). Convergents sur le principe. - Audit append-only + hash-chain + ancrage Merkle : spec INV-287-13, tests TC-NOM-14 + TC-ERR-14 + TC-INV-14, plan C5 + C6 + ADR-287-03 (mono-instance via
pg_advisory_lock), contracts moduleaudit-journal(forbidden DELETE/UPDATE + anti-catch-absorb). Pipeline bout-en-bout cohérent. - Révocation non rétroactive : spec INV-287-23, tests TC-NOM-14, plan §3 (immuabilité DB via trigger), contracts module
audit-journal. Convergent. - Lock + idempotence + rate-limit + réconciliation + clearing : spec INV-287-20, tests TC-NOM-05/06/15/16/18 + TC-ERR-04/08/12/13/19/20 + TC-NEG-01/09/10/13, plan §2.1 + C7 + C1, contracts module
sharing-api. Couverture complète. - Atomicité DB + audit synchrone bloquant : spec INV-287-21, tests TC-INV-04/05 + TC-ERR-14, plan §2.1 (audit dans la même transaction SQL) + §3, contracts module
audit-journal(forbidden "append asynchrone post-response"). Convergent sur fail-closed. - Trust-store obligatoire fail-closed au bootstrap : spec INV-287-17, tests TC-INV-07 + TC-ERR-16 + TC-NEG-08, plan C9 + point de vigilance #25, contracts module
trust-store(forbidden "insecure"/"trust-all"/fallback CA system). Convergent. - Secrets temporaires crypto chiffrés au repos : spec INV-287-16, tests TC-INV-08 + TC-NEG-06, plan C4 + KMS envelope, contracts module
crypto-pre(forbidden DEK en clair, log envelope). Convergent. - PRE obligatoire, pas de fallback KMS direct : spec INV-287-02, tests TC-NOM-07 + TC-ERR-15 + TC-INV-09, plan §2.3 + ESLint rule custom, contracts module
crypto-pre(forbidden "fallback non-PRE"). Convergent. - Identité éphémère unique par lien : spec INV-287-05, tests TC-NOM-12, plan §2.1 étape 7 + contrainte UNIQUE DB, contracts module
crypto-pre. Convergent. - Purge proactive artefacts temporaires : spec INV-287-19, tests TC-NOM-17 + TC-NEG-04, plan C8 (
RetentionWorker.purgeOnBoot()+ hookonTerminal()), contracts moduleretention-worker. Convergent. - Rétention RGPD J+90 post-terminal + audit conservé : spec INV-287-15 + §5.11, tests TC-NOM-19 + TC-INV-12, plan C8, contracts module
retention-worker(invariant journal conservé). Convergent. - Seuil OTP cumulé 50 → REVOKED + notification : spec CA-287-31 + INV-287-20 + ERR-287-20, tests TC-NOM-18 + TC-ERR-20, plan §2.2 + C2 + C11, contracts modules
otp+notifications. Convergent. - Feature flag
export_zip_enableddefault false, 404 générique si désactivé : spec Q-287-06 + F-287-04, tests TC-NOM-08 sous condition, plan C10 + point #20, contracts feature_flags + moduleexport-zip. Convergent. - Guard cross-module
ShareProofGuardscopeProofsModule: spec §5.8, plan C12 + §2ter, contracts cross_module_protections (forbidden "guard global"). Convergent. - SLA révocation P95 ≤ 2000ms non configurable MVP : spec §5.3 + INV-287-08, plan constante
REVOCATION_P95_SLA_MS=2000, contracts modulesharing-api. Convergent. - Charge nominale contractuelle tests perf (1 lien actif, 10 RPS, staging mono-instance) : spec §5.2 + CA-287-08, tests TC-INV-08 + préconditions §3, plan TC-NOM-09b split perf (k6 N≥200), contracts module
tests. Convergent. - Anti-catch-absorb (learning 2026-03-08) : contracts module
audit-journal(forbidden.catch(() => logger.error(...))), plan §6 (règle ESLintaudit-must-throw). Propagation explicite du learning transverse. crypto.randomUUID()/crypto.randomInt()obligatoires (learning 2026-02-21) : contracts modulessharing-api+otp+recipient-session(forbiddenMath.random()). Propagation explicite.
3. Divergences¶
⚠️ Les conflits ne doivent JAMAIS être lissés. Chaque divergence est rendue visible.
- DIV-01 : Extension du vocabulaire d'événements audit hors enum contractuel
D-287-23. - Source A (spec) :
D-287-23définit strictement 6 types :LINK_CREATED|LINK_ACTIVATED|LINK_VIEWED|LINK_EXPORTED|LINK_REVOKED|LINK_EXPIRED. - Source B (plan §4 mapping CA-287-04 + point de vigilance #26 + §2.2) : introduit
LINK_OTP_BLOCKED,LINK_REAUTH_REQUESTED,LINK_REAUTH_CONFIRMED. Deux options évoquées (extension enum vs table dérivée) sans décision tranchée. - Source C (tests TC-NOM-03 / TC-NOM-05 / TC-ERR-07 / TC-ERR-09) : ne référencent pas de type d'événement nouveau — observables décrits via "transition état" et "row notification" plutôt que via événement enum.
-
Impact : l'extension étend la chaîne de hash et la sémantique probatoire. Si l'enum contractuel
D-287-23ne bouge pas, la chaîne intègre des événements hors contrat → risque de ré-ouverture Gate 3 sur la spec (ratification enum élargi) ou bien table séparée qui casse la continuité de la hash-chain unique (INV-287-13). Décision attendue Gate 5. -
DIV-02 : Format de livraison du document chiffré au destinataire.
- Source A (spec §5.4 F-287-03 + diagramme séquence §5bis) : parle d'
encrypted_doc_ref(référence/URL) dans la réponse SharingAPI → Recipient. - Source B (plan §2.3 étape 6 + ADR-287-01 + point de vigilance #2) : retient une livraison base64 inline en JSON jusqu'à 50 MB (constante
MAX_INLINE_DOCUMENT_BYTES=52428800) ; au-delà streaming binaireapplication/octet-stream. - Source C (tests TC-NOM-07 + CA-287-12 matrice) : listent les 6 champs attendus mais ne tranchent pas entre
encrypted_doc_refetencrypted_document_b64; golden files non spécifiés. -
Impact : divergence de contrat d'API. Un test TC-NOM-07 écrit contre
encrypted_doc_reféchouera contre une implémentationencrypted_document_b64. Risque de taille mémoire Node.js sur documents proches de 50 MB (buffer base64 = +33% + sérialisation JSON). Arbitrage PO/architecture requis. -
DIV-03 : Statut contractuel du chiffrement
recipient_email/client_ip/user_agentau repos. - Source A (spec D-287-03/28/29 + INV-287-16) :
INV-287-16classe uniquement les "artefacts crypto temporaires (DEK envelope, rekey, fragment, clé éphémère, capsule)". Email/IP/UA sont en §5.11 (rétention) sans exigence de chiffrement au repos. - Source B (plan point de vigilance #15 + §7) : ajoute
pgcrypto pgp_sym_encryptsur email/IP/UA etrecipient_email_sha256indexé. - Source C (tests TC-NEG-06) : cherche "secrets temporaires en clair" — périmètre ambigu (couvre-t-il email/IP/UA ?).
-
Impact : surcoût DB (chiffrement + index hashé) non budgété par spec. Risque d'over-engineering si PO n'exige pas ce renforcement. À l'inverse, position défensive cohérente avec RGPD et conforme à l'esprit de l'INV-287-15. Décision attendue : ratifier l'extension (bump INV-287-16 ou nouvel invariant) ou rétrograder en pattern recommandé non bloquant.
-
DIV-04 : Endpoints reauth OTP (
/reauth/request-otp,/reauth/confirm). - Source A (spec diagramme séquence §5bis) : ces endpoints sont présents dans le diagramme mais ne figurent ni dans §5.4 (flux nominaux), ni dans §6 (cas d'erreur), ni dans §5.2 (bornes) ni parmi les cas d'ERR-287-*.
- Source B (plan §1 + §2.3 + point de vigilance #3) : les formalise avec rate-limit partagé et anti-enum identiques à l'activation ; introduit événements audit
LINK_REAUTH_*(cf. DIV-01). - Source C (tests TC-ERR-09) : évoque "réauthentification OTP requise côté UX" sans test dédié d'endpoint. Aucun TC-NOM spécifique pour le flux reauth.
-
Impact : endpoints non couverts par un test intégration dédié ; quotas de resend
otp_resend_limit_per_hoursont partagés entre activation initiale et reauth (spec §5.2 n'était-elle pas scopée activation initiale ?). Risque de contournement quota via reauth si non explicite. Décision Gate 5 : ajouter TC-NOM dédié reauth + confirmer partage de compteur. -
DIV-05 : Testabilité de CA-287-27 (consentement RGPD affiché avant activation) vs stub front.
- Source A (spec CA-287-27 + H-287-08) : "Information RGPD destinataire affichée avant activation : capture UI + trace consentement info".
- Source B (plan §4 ligne CA-287-27 + H-plan-07 + hors périmètre) : "Page activation destinataire : bandeau RGPD obligatoire (front) + event audit
LINK_VIEWEDavec flagrgpd_info_shown=true" — front stubbé PD-287-FRONT. - Source C (tests TC-NOM-02) : "information RGPD affichée avant validation OTP et audit
LINK_ACTIVATEDprésent" — observable UI attendu. -
Impact : le backend seul ne peut pas observer "capture UI". Le plan propose un flag dans
LINK_VIEWED, mais la spec associe le critère àLINK_ACTIVATEDet à une observation UI. Si le front est stubbé PD-287-FRONT, TC-NOM-02 au niveau backend dégrade en "le flag est exposé dans la réponse API d'activation" — ce n'est pas ce que la spec décrit. Arbitrage : ré-écrire TC-NOM-02 en découpage backend (exposition de la contract API) + front (capture UI) ou rendre CA-287-27 explicite "hors périmètre MVP testable backend". -
DIV-06 : Périmètre audité par INV-287-01 (zero-knowledge bornée).
- Source A (spec INV-287-01) : "aucun endpoint, service, log ou dump mémoire listé dans le périmètre de la story" — périmètre non énuméré.
- Source B (plan point de vigilance #24) : énumère C1/C2/C3/C4/C5/C6/C7/C8/C10/C12 + logs applicatifs + DB replicas. Exclut systèmes externes (HSM, KMS, Blockchain client).
- Source C (tests TC-INV-11) : renvoie à "périmètre story" sans énumération ; plan propose protocole déterministe (GC×3 + heap snapshot + grep).
-
Impact : énumération faite unilatéralement par le plan. Si un reviewer Gate 5 (ou Gate 8) conteste le périmètre, le test peut être rejoué sur un scope différent. Décision attendue : ratifier l'énumération côté spec ou l'acter comme annexe test.
-
DIV-07 : Hypothèse "ownership preuve immuable durant la vie du lien".
- Source A (spec) : ne mentionne pas de transfert d'ownership. D-287-02
owner_user_idest présent mais sans règle de mutabilité. - Source B (plan H-plan + point de vigilance #22) : documente hypothèse ownership immuable durant la vie du lien + évolution tracée PD-287-EXT-3 (trigger PG REVOKED auto si transfert).
- Source C (tests) : aucun scénario de test portant sur un transfert d'ownership pendant la vie d'un lien.
-
Impact : zone grise assumée par le plan. Si l'écosystème ProbatioVault permet un transfert d'ownership ailleurs (epic autre que
sharing), les liens de PD-287 restent "orphelins" côté FSM. Pas bloquant MVP car documenté ; mais pas non plus testé défensivement. -
DIV-08 : 429 sur états FSM non "rate-limit" stricts (OTP_BLOCKED, REVOKED anti-abus).
- Source A (spec INV-287-12) : 429 "réservé aux conditions de rate-limit/anti-abus, sans fuite d'existence de ressource".
- Source A' (spec ERR-287-07 + ERR-287-20 + §5.4 F-287-02 + §5.5 table transitions) : impose néanmoins 429 +
share_state=OTP_BLOCKEDet 429 + transitionREVOKED. - Source B (plan point de vigilance #1 + #13) : reconnaît la tension, retient 429 + jitter latence + masquage header
X-OTP-Blocked-Until. Risque résiduel documenté. - Source C (tests TC-ERR-07 + TC-ERR-20) : valident la réponse 429 sans tester le fingerprinting par latence.
-
Impact : tension interne à la spec elle-même (déjà relevée Gate 3 écart #1). Le plan applique la règle littérale (429) mais à fine granularité, un test adversarial sophistiqué pourrait distinguer
OTP_BLOCKEDdeRATE_LIMITED rate_limit=open_requests_per_ip_per_minvia timing. Risque résiduel accepté mais à reconfronter Gate 8 avec métriques latence. -
DIV-09 : Check seuil OTP cumulé : avant vs après incrément.
- Source A (spec §5.2) :
otp_failed_attempts_total_per_link_max=50→ à 50, transition REVOKED. - Source B (plan point de vigilance #18) : "check
count >= 50avant incrément (la 50e tentative échouée déclenche REVOKED, stockage max 49 ou 50 selon transaction)". - Source C (tests TC-ERR-20) : "
otp_failed_attempts_total=49+ OTP invalide supplémentaire → 429 + REVOKED" — sémantique : la 50e tentative échouée est celle qui déclenche. -
Impact : ambiguïté "check avant incrément" vs test "49+1=50 déclenche". Si le plan check avant incrément, à 49 il rejette (incrément à 50 puis REVOKED) ; à 50 il rejette sans incrémenter. Cohérent avec tests mais formulation plan ambiguë. Préciser dans le code : "incrémenter d'abord dans une transaction atomique, puis tester
count >= 50et transitionner". -
DIV-10 : Rate-limit IP
open_requests_per_ip_per_min=30: couverture par tests. - Source A (spec §5.2) : paramètre défini comme protection anti-scan.
- Source B (plan §7 + point #4) : utilisé pour "mitiger DoS via token volé" en amont.
- Source C (tests) : aucun TC nominal ni adversarial spécifique pour ce quota IP (TC-NEG-09 parle de "rotation preuve/compte/IP" mais n'enforce pas le quota IP isolément).
- Impact : quota documenté mais non testé isolément. Ajouter TC-NEG dédié au rate-limit IP (scan token aléatoire sans quota IP → 429).
4. Zones d'ombre¶
- OTP TTL (
OTP_CODE_TTL_SECONDS=300s) : introduite par le plan (constante + H-plan-03) suite à écart Gate 3 #10. Aucun test ne vérifie explicitement qu'un OTP émis à t0 est rejeté à t0+301s. Ajouter un TC-NOM-OTP-TTL dédié. - Événement audit technique de purge RGPD : spec §5.11 dit "traçable par événement technique non destructif" ; plan C8 + contracts module
retention-worker(invariant "trace de purge"). Type d'événement non défini :LINK_PURGED? Hors enumD-287-23. Tombe sous DIV-01. - Clearing_success_cycles_required=2 : spec §5.2 définit le paramètre, plan C7 l'évoque, aucun test (TC-NOM-16 parle de "retour HEALTHY seulement après
clearing_success_cycles_requiredcycles" mais le mécanisme d'observation du statutHEALTHYn'est pas formalisé — y a-t-il un healthcheck exposé ? où est lu le statut ?). max_consultations_per_link: spec §5.2 + ERR non défini ; plan point #12 "feature dormante MVP". Aucun test MVP (acceptable). Mais CA-287-12 ("consultation fournit document + artefacts") ne précise pas qu'elle peut être plafonnée → pas d'ambiguïté si feature OFF par défaut.- Format
owner_key_ref: spec D-287 ne le définit pas dans §5.1 ; plan point #9 propose "HSM KeyHandle opaque ≤ 128 chars" comme extension. Non ratifié par spec → extension implicite. - Scope mémorisation warning "pas de DRM" : spec CA-287-26 dit "mémorisée" ; plan ADR-287-02 tranche "compte utilisateur serveur". Choix ferme côté plan, non tranché côté spec → à ratifier Gate 5.
- Définition formelle de "trust-store incomplet" : spec INV-287-17 parle de "trust-store obligatoire non optionnel" ; plan Point #25 définit "racine plateforme manquante OU CRL inaccessible OU (si OCSP requis, stapling KO)". Définition unilatérale côté plan, à ratifier.
- Worker
MerkleAnchorWorker: comportement post-crash avant ancrage : plan ADR-287-03 dit "reprise depuis le dernier batch ancré confirmé" ; tests TC-NOM-16 couvrent la réconciliation mais ne simulent pas explicitement un crash worker Merkle. Scénario d'injection de crash à ajouter. - Extension enum
D-287-23vs hash-chain : cf. DIV-01. Zone d'ombre : si la décision Gate 5 est "table dérivée" au lieu d'enum étendu, quel impact sur la hash-chain probatoire (double chaîne → rupture INV-287-13) ? - Golden files tests TC-NR-04 (stabilité format export offline signé) : tests non-régression sur le manifeste JCS signé, mais aucun golden file versionné côté tests. À matérialiser côté
agent-tests. - Observabilité SLO: spec §5.2 liste métriques attendues (
link_generation_latency_p95_ms,revocation_effective_latency_p95_ms) ; plan mentionne métrique Prometheusshare_revocation_latency_p95_msmais ne détaille pas le pipeline (scraping, alerting, dashboard). Hors scope story ? À confirmer.
5. Recommandation¶
- Procéder avec réserves majeures ratifiables — convergence globale forte sur invariants, flux et tests. 10 divergences identifiées, toutes anticipées par le plan via ADR ou Points de vigilance, mais 3 requièrent arbitrage formel avant Gate 5 GO :
- DIV-01 (enum
D-287-23étendu) : décision nécessaire entre (a) extension contractuelle via bump spec, (b) table dérivée hors hash-chain, ou © abandon des événements reauth/OTP_BLOCKED au profit de champs dans les événements existants. - DIV-02 (format livraison document chiffré) : trancher formellement ADR-287-01 (base64 inline 50 MB) vs
encrypted_doc_ref(URL présignée) → arbitrage architecture + impact test TC-NOM-07. - DIV-05 (testabilité CA-287-27) : découper ou redéfinir pour rendre le critère backend observable.
- Les divergences DIV-03 (chiffrement email/IP/UA), DIV-06 (périmètre INV-287-01), DIV-07 (ownership immuable), DIV-08 (429 FSM), DIV-09 (sémantique check 50), DIV-10 (rate-limit IP) peuvent être traitées par précision au sein du dossier Gate 5 sans re-ouvrir la spec.
- Zones d'ombre (OTP TTL, clearing healthcheck, golden files export, crash worker Merkle) → à couvrir par tests additionnels avant clôture, pas bloquant pour Gate 5.
Pas d'escalade humaine structurelle requise pour le moment — DIV-01/02/05 peuvent être tranchés dans le dossier de conformité Gate 5 par l'arbitre PMO.