Aller au contenu

PD-298 — Rapport de confrontation (Étape 5 — Gate Plan)

Confrontation des artefacts produits avant Gate 5 : spécification (step 1), tests (step 2), plan d'implémentation (step 4) et code contracts (step 4). Mode factuel : chaque divergence est citée avec sa source et rendue visible.

1. Sources confrontées

Source Document Étape Version/date
A PD-298-specification.md Step 1 2026-04-22
B PD-298-tests.md Step 2 2026-04-22
C PD-298-plan.md Step 4 2026-04-22
D PD-298-code-contracts.yaml Step 4 2026-04-22

Documents de référence en arrière-plan (non confrontés ici, cités pour contexte) : - PD-298-besoin.md (step 0), - PD-298-review-step3.md (gate spec, verdict GO v1 avec réserves A-01..A-10, H-01..H-07, S-01..S-11, C-01..C-07, NT-01..NT-07).

2. Convergences

2.1 Machine à états (5 états contractuels)

  • A §3 + §5.5, B §2 (INV-298-08), C §F4 table, D ShareState union littérale : tous alignés sur {PENDING_ACTIVATION, ACTIVE, OTP_BLOCKED, REVOKED, EXPIRED}.
  • États terminaux REVOKED/EXPIRED explicitement immuables : A §5.5 matrice + transitions retour, B TC-NOM-16 / TC-NEG-04, C §F4 matrice StateActions, D ALLOWED_ACTIONS.REVOKED = ALLOWED_ACTIONS.EXPIRED = ['view_events'].

2.2 Bornes TTL

  • A §5.1 + §5.2 : [15, 43200] min, défaut 10080, presets {15,60,1440,10080,43200}.
  • B TC-NOM-01 (défaut 10080), TC-ERR-08 (hors bornes rejetées).
  • C §2.1 TtlPicker presets identiques, validation [15..43200].
  • D sharing-validation invariants : TTL_MIN=15, TTL_MAX=43200, TTL_DEFAULT=10080, TTL_PRESETS ordre fixe identique.

2.3 Pagination

  • A INV-298-13 : offset/limit, limit=20 fixe MVP, tri created_at desc.
  • B TC-NOM-05 (offset=0,20 + tri desc), TC-NEG-02 (limit≠20 interdit).
  • C §F3 : useInfiniteQuery avec limit=20 fixe, offset=pages.length * 20.
  • D sharing-api-client.SHARE_PAGE_SIZE = 20 invariant figé, listShares envoie limit=SHARE_PAGE_SIZE.

2.4 Guard de propriété

  • A INV-298-12 + §5.8 garde UI locale.
  • B TC-NOM-07, TC-ERR-07.
  • C §F1 useOwnership, §Mécanismes cross-module « scope local ».
  • D sharing-guards.ShareCta retourne null si !isOwner ; scope local à SharingStack + ProofDetailScreen.

2.5 Masquage IP et scrubbing PII

  • A INV-298-09 + §5.1 format IPv4 x.x.*.* / IPv6 « 4 hextets + *:*:*:* ».
  • B TC-NOM-12, TC-NEG-07, INV-298-07 logs.
  • C §2.1 maskIp, §SEC-01 beforeSend Sentry.
  • D sharing-masking.maskIpv4 = 'o1.o2.*.*' ; maskIpv6 = 4 hextets + :*:*:*:* ; SHARING_PII_FIELDS exhaustif ; logShareEvent Zod strict refusant clés PII.

2.6 Fraîcheur réseau (no-cache INV-298-05)

  • A INV-298-05, B TC-NOM-13 / TC-NR-01, C §F2 cacheTime:0, staleTime:0, D invariant staleTime:0, gcTime:0 sur clés ['shares', *] et interdiction persistQueryClient.

2.7 Offline interdit (INV-298-17)

  • A INV-298-17, B TC-NOM-17 / TC-ERR-12, C §3 mapping useNetInfoOnline, D sharing-hooks.useNetInfoOnline retourne false si NetInfo.isInternetReachable === false et hooks mutateurs refusent.

2.8 Stack technique

  • A §10.1 (React Native + Expo SDK 54 + TS, react-native-safe-area-context, @react-native-community/datetimepicker décidé PO 2026-04-22), C §1 et D sharing-components invariant enveloppés react-native-safe-area-context + TtlPicker propose @react-native-community/datetimepicker. Interdiction Swift/natif : A §10.1, D sharing-components.forbidden.

2.9 i18n 100 %

  • A INV-298-10, B TC-NOM-14 / TC-NR-02, C ESLint react/no-literal-string + i18n-unused, D sharing-components.invariants « aucune chaîne literal », forbidden « chaîne literal visible (sauf testID) ».

2.10 Actions selon état (matrice)

  • A INV-298-11, B TC-NOM-08 / TC-NEG-04, C §F4 table 5×4, D ALLOWED_ACTIONS exhaustif immuable.

2.11 RGPD et DRM warning

  • A §F-298-06 + INV-298-03 / INV-298-06, B TC-NOM-03 / TC-NOM-04 / TC-NR-06 / TC-NR-07, C §F1 + §3 (INV-298-06 via measureInWindow), D RgpdNotice.onVisibilityChange gate submit ; DrmWarningModal utilise sharing.arb8.drm.*, useDrmWarning expose {seen, ack}.

3. Divergences

DIV-01 — Endpoint GET /shares/:id (détail) non présent dans la spec

  • Source A (spec §2 + §5.4 F-298-04) : liste explicite des endpoints consommés = POST /shares, POST /shares/:id/revoke, GET /shares/:id/events. L'endpoint détail individuel n'est pas listé ; §5.4 dit seulement « Le système affiche données du lien ».
  • Source B (tests) : aucun TC ne référence GET /shares/:id seul ; GET /shares/:id/events uniquement (TC-NOM-11, TC-ERR-06).
  • Source C (plan §F4 étape 2 + §2bis diagramme séquence) : introduit useShareDetail(shareId) → GET /shares/:id comme appel distinct, ainsi qu'un second getShare(shareId) de re-check anti-race avant POST /shares/:id/revoke.
  • Source D (sharing-api-client) : expose getShareDetail et invariant « Les endpoints consommés sont strictement : POST /shares, GET /shares, GET /shares/:id, POST /shares/:id/revoke, GET /shares/:id/events ».
  • Impact : si PD-287 n'expose pas GET /shares/:id, F4 et le re-check anti-race ne sont pas réalisables tels que planifiés. Aucune hypothèse HT-* ne couvre explicitement l'existence de cet endpoint (HT-07 couvre la matrice transitions, pas l'endpoint détail).

DIV-02 — Re-check anti-race avant POST /revoke non spécifié

  • Source A (spec §5.4 F-298-04) : séquence stricte « confirmation → POST /shares/:id/revoke ». Aucun re-fetch d'état intermédiaire.
  • Source B (tests) : TC-NOM-09 / TC-ERR-02 / TC-ERR-03 testent la confirmation et l'état obsolète après refresh, mais aucun test ne valide un re-check synchrone AVANT le POST.
  • Source C (plan §F4.4 + §2ter diagramme) : « avant POST, re-fetch état courant (anti-race H-07) ; si état devenu terminal, abort + toast ».
  • Source D (sharing-hooks) : useRevokeShare invariant « re-fetch le state avant POST (anti-race H-07) ; si terminal, abort avec ShareErrorCode.ALREADY_TERMINAL ».
  • Impact : ajoute un appel réseau supplémentaire non testé. Couverture tests incomplète pour cette branche (pas de TC ALREADY_TERMINAL côté client). Impact perf : +1 GET avant chaque POST revoke.

DIV-03 — Idempotency-Key introduit unilatéralement côté plan

  • Source A (spec) : aucune mention d'un header Idempotency-Key.
  • Source B (tests) : aucun test ne vérifie l'émission ou la forme de ce header.
  • Source C (plan §F1.5 + §1 C05 + HT-07) : génération idempotencyKey = uuidv4() sur POST /shares et POST /shares/:id/revoke.
  • Source D (sharing-api-client) : invariant « createShare et revokeShare injectent un header Idempotency-Key = uuidv4() (H-03) ».
  • Impact : si PD-287 n'accepte pas/ignore ce header, comportement neutre ; s'il l'exige ou le valide, divergence possible. Aucun test contractuel ne couvre cette exigence.

DIV-04 — Timeout réseau 30 s introduit par le plan

  • Source A (spec §5.2) : « Aucun objectif de performance chiffré (latence P95/P99, mémoire) n'est fourni pour cette story ; ces SLA de performance sont hors périmètre. »
  • Source B (tests) : TC-ERR-12 mentionne « timeout/offline » sans valeur chiffrée.
  • Source C (plan §F1.5) + Source D (sharing-api-client) : SHARE_REQUEST_TIMEOUT_MS = 30000.
  • Impact : valeur figée non validée dans la spec. Risque mineur mais divergence à tracer si PO/Product impose un SLA différent ultérieurement.

DIV-05 — Filtre d'état GET /shares?state= : contrat incertain

  • Source A (spec §2 + CA-298-06) : « filtre par état » mentionné sans contrat API. §5.4 F-298-03 : « Résultats triés par création décroissante, filtrables par état ».
  • Source B (tests TC-NOM-06) : « L'utilisateur applique successivement chaque filtre d'état » sans préciser si c'est client-side ou query param.
  • Source C (plan §F3) : « passe state en query param si supporté, sinon filtre post-réponse et refetch » (branchement conditionnel explicite, HT-08).
  • Source D (sharing-api-client) : invariant « listShares envoie offset, limit=SHARE_PAGE_SIZE et state (si filtre) ». Implique un filtre serveur.
  • Impact : divergence interne au plan lui-même (C branche vs D fige). Si PD-287 ne supporte pas state=, invariant D violé. Aucun TC ne capture la différence (client-side vs server-side).

DIV-06 — Liste par preuve : endpoint GET /shares?proofId= supposé

  • Source A (spec §5.4 F-298-02) : « Le système récupère les partages du propriétaire et affiche uniquement ceux liés à la preuve ». Contrat flou.
  • Source B (tests) : aucun TC dédié à F-298-02 ne couvre le filtrage par proofId côté API.
  • Source C (plan §F2) : « GET /shares?proofId=... si endpoint PD-287 le supporte (HT-07) ; sinon filtre côté client à partir de GET /shares?offset=0&limit=20 paginé jusqu'à couverture ».
  • Source D (sharing-api-client.listShares) : signature non détaillée sur ce point, invariant n'interdit pas mais ne documente pas proofId comme paramètre.
  • Impact : en cas de fallback client-side, pagination complète + filtre local = potentiel perf issue sur comptes avec beaucoup de partages. Ajoute un risque non couvert par la spec ni les tests.

DIV-07 — Self-loops state → state : INTERDITE présents dans le diagramme mais absents de la matrice

  • Source A (spec §5bis diagramme stateDiagram-v2) : contient PENDING_ACTIVATION --> PENDING_ACTIVATION : INTERDITE, ACTIVE --> ACTIVE : INTERDITE, OTP_BLOCKED --> OTP_BLOCKED : INTERDITE, REVOKED --> REVOKED : INTERDITE (terminal), EXPIRED --> EXPIRED : INTERDITE (terminal).
  • Source A (spec §5.5 matrice transitions) : la matrice 5×5 ne contient aucune colonne « état source = état cible » pour les 3 premiers états ; seuls les terminaux la mentionnent.
  • Source B (tests TC-NOM-16) : vérifie « transitions non listées refusées » sans expliciter les self-loops non-terminaux.
  • Source C (plan §V-04) : « Self-loops state → state : INTERDITE (diagramme) absents de la matrice §5.5. UI ne traite pas comme erreur (un refetch peut renvoyer le même état). Documenté dans ALLOWED_ACTIONS : pas d'auto-transition considérée. »
  • Source D (sharing-hooks) : ALLOWED_ACTIONS ne traite pas les self-loops.
  • Impact : contradiction interne spec (diagramme ≠ matrice). Le plan tranche pragmatiquement (refetch retournant le même état n'est pas une transition → pas une erreur) mais cette interprétation n'est ni confirmée par spec ni par tests. Risque : TC-NOM-16 ambigu.

DIV-08 — Normalisation email trim + toLowerCase non contractuelle dans la spec

  • Source A (spec §5.1) : recipientEmail « Email RFC 5322 simplifié (référence PD-287 D-287-03) » ; comparaison « case-insensitive ». Aucune règle explicite de normalisation côté client.
  • Source B (tests TC-NEG-03) : « recipientEmail malformé (espaces finaux, format invalide) » → soumission bloquée. Pas de test de normalisation.
  • Source C (plan §F1.4) : « normalisation email contractualisée (lowercase + trim, pas de dot-removal) ».
  • Source D (sharing-validation.normalizeEmail) : invariant « applique uniquement trim + toLowerCase (aucune dot-removal, aucune Unicode normalisation agressive) » + forbidden « Modifier silencieusement l'email ».
  • Impact : divergence potentielle avec backend si D-287-03 impose une autre normalisation (ex. préserver la casse du local-part). Risque RFC 5322 : la casse du local-part est techniquement significative (§2.3 RFC 5321 ne garantit pas l'égalité case-insensitive pour le local-part). Aucun TC ne couvre cette branche.

DIV-09 — Textes normatifs (ARB-7, ARB-8, RGPD) : tests « bloquants » vs plan « placeholder »

  • Source A (spec §4 INV-298-02 + INV-298-06) : « texte ARB-7 exact », « encart RGPD ».
  • Source B (tests §9) : classe ces comparaisons comme NON TESTABLE — Bloquant (ARB-7/ARB-8) et Majeur (texte RGPD exact) ; §10 liste « réserves bloquantes à lever avant validation finale ».
  • Source C (plan §1.2 + HT-02 + HT-03 + §8.1) : définit des clés i18n à valeurs placeholder (« À définir — PD-287 ARB-7 ») ; plan §8.1 précise « step 6b démarre sur placeholders mais Gate 8 NON_CONFORME » sans levée préalable.
  • Source D (sharing-components) : invariant « affiche t('sharing.arb7.revoke.body') » sans mécanisme de blocage si valeur = placeholder.
  • Impact : décalage de responsabilité. Tests réputent le sujet bloquant pour Gate 5 ; plan le traite comme non-bloquant pour Gate 5 mais bloquant pour Gate 8. Aucun gate explicite (escalade PO) n'est posé comme condition d'entrée en step 6b dans le plan.

DIV-10 — maxViews : validation côté client absente assumée par plan (HT-05)

  • Source A (spec §5.1) : « null ou entier >=1 ; borne max backend à clarifier » ; spec §6 ERR-298-08 parle uniquement de TTL hors bornes, pas de maxViews.
  • Source B (tests) : TC-ERR-08 couvre TTL, aucun TC dédié maxViews. §9 liste maxViews borne max comme « NON TESTABLE — Majeur ».
  • Source C (plan HT-05) : « Borne max maxViews n'est pas validée côté client ; l'erreur backend (400) est rendue telle quelle ».
  • Source D (sharing-validation.validateMaxViews) : invariant « accepte uniquement entier >=1 ou null ; la borne max n'est PAS vérifiée côté client (HT-05) » + createShare forbidden « Retourner l'erreur backend brute » → SharesApiError.code.
  • Impact : comportement documenté mais potentiellement dégradé UX (400 affiché via mapping générique). Aucun TC ne vérifie le mapping 400 → message i18n pour dépassement maxViews.

DIV-11 — Portée DRM warning : par compte (tests) vs par device × compte (plan V-02)

  • Source A (spec INV-298-03) : « À la première ouverture de création (par compte), l'avertissement DRM DOIT être affiché puis mémorisé localement. » Ambiguïté : « par compte » sans précision device.
  • Source B (tests TC-NOM-03 + Matrice INV-298-03) : « Comportement par compte utilisateur. »
  • Source C (plan §V-02 / S-05) : « drmWarningSeen par device → garantie faible » ; option future de flag côté backend.
  • Source D (sharing-drm-prefs) : clé AsyncStorage locale → implémentation per-device.
  • Impact : divergence sémantique réelle. Un même compte sur 2 devices verra 2 fois l'avertissement. Test TC-NOM-03 ne distingue pas device ; il passera sur un device unique, mais ne prouve pas l'invariant A par compte au sens strict (multi-device).

DIV-12 — GET /shares/:id/events payload : assumé aligné, contrat backend non vérifié

  • Source A (spec §5.1 + §5.4 F-298-05) : colonnes attendues eventAt, eventType, recipientEmail, ip, deviceType.
  • Source B (tests TC-NOM-11 + §9) : « NON TESTABLE — Majeur : Mapping exhaustif eventType backend→UI non figé ».
  • Source C (plan §F5 + HT-04) : fallback UNKNOWN_EVENT côté UI.
  • Source D (sharing-components.EventListItem) : rend maskIp(raw) ; n'impose pas le champ eventRecipientEmail vs recipient_email au nom du backend.
  • Impact : risque de champ absent/mal nommé en réponse backend → Zod rejet → écran inopérant. Aucun TC ne mocke un schéma backend spécifique autre que celui supposé. Levée HT-04 prévue Wave 2 (plan §8.1), sans blocage Gate 5.

DIV-13 — Sentry scrubbing : ajouté par plan, non-requis par spec/tests

  • Source A (spec INV-298-07) : « Les emails destinataires NE DOIVENT JAMAIS être loggés ; seul shareId est loggable. »
  • Source B (tests TC-NOM-15 / TC-NR-04) : « Absence d'email dans logs » — aucune exigence explicite Sentry/Datadog/analytics tiers.
  • Source C (plan §SEC-01) + D (sharing-telemetry) : configureSharingSentryScrub, beforeSend, beforeBreadcrumb.
  • Impact : élargissement utile de la couverture mais non contractualisé. Risque : si un analytics non scrubé (Datadog RUM, Amplitude…) est intégré, l'invariant INV-298-07 peut être violé sans échec de test. Plan §V documente « Analytics tierces non scrubées (S-01, §9) ».

DIV-14 — Tests de contrat vs Swagger PD-287 : planifiés après Gate 5

  • Source A (spec) : ne spécifie pas de vérification Swagger.
  • Source B (tests §8) : « Tests de contrat : Mock responses validées par Zod ; fixtures alignées sur Swagger PD-287 (à vérifier Wave 2, HT-06/07). »
  • Source C (plan §8.1) : levée HT-06/07/08 planifiée « Wave 2 fin » (donc post-Gate 5, pendant step 6b).
  • Impact : Gate 5 est soumis sans vérification d'aligement contractuel sur PD-287. Si PD-287 utilise cursor-based pagination, state= non supporté, ou structure events divergente, la boucle revient à step 4. Risque process documenté mais non mitigé avant Gate 5.

DIV-15 — Test TC-NOM-13 : spec « autre terminal » → plan « mock HTTP »

  • Source B (tests TC-NOM-13) : « un lien révoqué depuis un autre terminal » → « appel réseau frais ».
  • Source C (plan §V-06) : « TC-NOM-13 non-déterministe en CI (mutation multi-device). Remplacé par mock HTTP qui change shareState entre 2 appels GET successifs. »
  • Impact : le plan modifie unilatéralement la sémantique d'un test écrit en step 2 sans réécriture formelle de TC-NOM-13. Cohérence méthodologique : la décision d'adapter un test devrait passer par update de PD-298-tests.md ou note explicite dans les artefacts de tests.

DIV-16 — ProofShareListSection embarquée vs écran dédié

  • Source A (spec §2 + §5.4 F-298-02) : « section partages » d'une preuve (terme neutre).
  • Source C (plan §1 C08 + §F2) : « embarquée dans l'écran preuve existant » (pas d'écran dédié par-preuve).
  • Source D (sharing-screens) : invariant « ProofShareListSection est embarquée dans l'écran preuve existant (pas d'écran dédié par-preuve) ».
  • Impact : décision UX (embedded vs écran) qui n'est pas rediscutée avec le PO dans le plan ; découle implicitement du besoin mais mériterait confirmation (cf. Zones d'ombre §4).

4. Zones d'ombre

ZO-01 — Existence de GET /shares/:id côté PD-287

Aucun document ne confirme. Conditionne F4 (détail) et le re-check anti-race de DIV-02.

ZO-02 — Règle exacte de normalisation email backend D-287-03

Regex et règles (trim ? lowercase local-part ? Unicode NFC ? etc.) non fournies. Cf. DIV-08, HT-01.

ZO-03 — Textes normatifs ARB-7, ARB-8 et encart RGPD

Non fournis ; plan utilise placeholders. Plan §8.1 escalade PO avant Gate 8 mais aucune levée planifiée avant step 6b (cf. DIV-09).

ZO-04 — Borne max maxViews backend

Inconnue. Plan traite via erreur 400 mappée (DIV-10).

ZO-05 — Mapping eventType backend ↔ UI

HT-04 ; non figé ; Zod acceptera-t-il les 6 valeurs documentées ? UNKNOWN_EVENT prévu comme fallback côté UI (jamais renvoyé au backend, cf. D).

ZO-06 — Support backend des paramètres proofId, state sur GET /shares

HT-08 + DIV-05 + DIV-06. Plan prévoit branchements conditionnels mais D fige la signature.

ZO-07 — Support header Idempotency-Key côté PD-287

Non documenté. Cf. DIV-03.

ZO-08 — Réponse POST /shares/:id/revoke : 204 vs 409 vs payload

Plan suppose « 204 | 409 » (§2ter). Tests ne verrouillent ni l'un ni l'autre.

ZO-09 — IPv6 masking : canonical form via ipaddr.js

Règle contractuelle § 5.1 mentionne « 4 hextets + *:*:*:* » mais le choix de canonicalisation (zero-compression, lowercase) est laissé à ipaddr.js (HT-11). Cas tests IPv6 zero-compressed non couverts dans B (seulement mentionnés en TC-NOM-12 de manière générique).

ZO-10 — Déclenchement des 3 variantes de EmptyJournalMessage

Plan §F5 introduit 3 clés i18n (never_activated / no_recent_event / network_error). Spec §F-298-05 parle d'« un message contextuel explicite ». Tests ERR-298-06 exigent « message contextuel » sans préciser les 3 variantes. Algorithme de sélection non spécifié dans les artefacts.

ZO-11 — Self-loops (cf. DIV-07)

L'UI doit-elle rejeter un state identique reçu dans la réponse de mutation comme une transition interdite ? Ou l'ignorer comme un refetch normal ? Non tranché par spec/tests.

ZO-12 — Scope DRM warning multi-device (cf. DIV-11)

Intention réelle du PO ? « Par compte » signifie-t-il bien « par (compte × device) » en MVP ou « par compte » au sens strict (backend flag) ?

ZO-13 — ProofShareListSection confirmée par PO ?

Pas d'écran dédié « partages par preuve ». Cohérent avec l'esprit de la spec mais à valider PO avant step 6b pour éviter une refonte ultérieure (cf. DIV-16).

ZO-14 — ShareRequestTimeout = 30 s (cf. DIV-04)

Valeur non validée par spec ni PO. Sur 3G lente, 30 s peut sembler long ; aucun SLA.

ZO-15 — react-native-modal-datetime-picker fallback conditionnel

Plan HT-10 mentionne un fallback éventuel. Aucun critère objectif (« si nécessaire ») ne déclenche ce fallback. Risque de dépendance ajoutée non utilisée ou insuffisamment testée.


5. Synthèse par gravité

Gravité Divergences Zones d'ombre
Bloquant (empêche step 6b) DIV-01 (endpoint détail), DIV-09 (textes normatifs) ZO-01, ZO-02, ZO-03
Majeur (impact fonctionnel, peut cause rework step 6b) DIV-02, DIV-05, DIV-06, DIV-07, DIV-08, DIV-12, DIV-14, DIV-16 ZO-04, ZO-05, ZO-06, ZO-08, ZO-11, ZO-13
Mineur (impact UX/test marginaux) DIV-03, DIV-04, DIV-10, DIV-11, DIV-13, DIV-15 ZO-07, ZO-09, ZO-10, ZO-12, ZO-14, ZO-15

6. 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 :

  1. Bloquant DIV-01 : l'invariant D sharing-api-client liste GET /shares/:id comme endpoint consommé, alors que la spec ne le contient pas. À aligner (soit ajouter l'endpoint à la spec, soit retirer getShareDetail du contrat et dériver le détail de la liste).
  2. Bloquant DIV-09 : les tests §10 qualifient ARB-7/ARB-8/RGPD de « bloquant ». Le plan accepte de démarrer step 6b sur placeholders. Décision PO requise : soit ce compromis est acté explicitement et Gate 5 peut passer en GO avec réserve, soit l'escalade PO/Legal est exigée avant Gate 5.
  3. Majeurs DIV-02, DIV-05, DIV-06, DIV-07, DIV-08, DIV-12, DIV-14 : introduisent des suppositions sur PD-287 (endpoints, params, payload, self-loops, normalisation) qui, si fausses, imposent un retour step 4. Plan §8.1 prévoit leur levée en Wave 2 (après step 6b démarré). Cette séquence crée un risque de rework significatif. Proposition : verrouiller HT-06/07/08 + DIV-01 + DIV-08 avant l'ouverture de la Wave 2 (intégration API), en lisant le code backend PD-287 et/ou son Swagger (action documentable sans attendre un PO).

Aucune divergence n'est lissée ; le choix final entre GO/RESERVE/NON_CONFORME revient à la Gate 5.