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
ShareStateunion littérale : tous alignés sur{PENDING_ACTIVATION, ACTIVE, OTP_BLOCKED, REVOKED, EXPIRED}. - États terminaux
REVOKED/EXPIREDexplicitement immuables : A §5.5 matrice + transitions retour, B TC-NOM-16 / TC-NEG-04, C §F4 matriceStateActions, DALLOWED_ACTIONS.REVOKED = ALLOWED_ACTIONS.EXPIRED = ['view_events'].
2.2 Bornes TTL¶
- A §5.1 + §5.2 :
[15, 43200]min, défaut10080, presets{15,60,1440,10080,43200}. - B TC-NOM-01 (défaut 10080), TC-ERR-08 (hors bornes rejetées).
- C §2.1
TtlPickerpresets identiques, validation[15..43200]. - D
sharing-validationinvariants :TTL_MIN=15, TTL_MAX=43200, TTL_DEFAULT=10080, TTL_PRESETSordre fixe identique.
2.3 Pagination¶
- A INV-298-13 :
offset/limit,limit=20fixe MVP, tricreated_at desc. - B TC-NOM-05 (
offset=0,20+ tri desc), TC-NEG-02 (limit≠20interdit). - C §F3 :
useInfiniteQueryaveclimit=20fixe,offset=pages.length * 20. - D
sharing-api-client.SHARE_PAGE_SIZE = 20invariant figé,listSharesenvoielimit=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.ShareCtaretournenullsi!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-01beforeSendSentry. - D
sharing-masking.maskIpv4 = 'o1.o2.*.*';maskIpv6 = 4 hextets + :*:*:*:*;SHARING_PII_FIELDSexhaustif ;logShareEventZod 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 invariantstaleTime:0, gcTime:0sur clés['shares', *]et interdictionpersistQueryClient.
2.7 Offline interdit (INV-298-17)¶
- A INV-298-17, B TC-NOM-17 / TC-ERR-12, C §3 mapping
useNetInfoOnline, Dsharing-hooks.useNetInfoOnlineretournefalsesiNetInfo.isInternetReachable === falseet hooks mutateurs refusent.
2.8 Stack technique¶
- A §10.1 (React Native + Expo SDK 54 + TS,
react-native-safe-area-context,@react-native-community/datetimepickerdécidé PO 2026-04-22), C §1 et Dsharing-componentsinvariantenveloppés react-native-safe-area-context+TtlPickerpropose@react-native-community/datetimepicker. Interdiction Swift/natif : A §10.1, Dsharing-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, Dsharing-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_ACTIONSexhaustif 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), DRgpdNotice.onVisibilityChangegate submit ;DrmWarningModalutilisesharing.arb8.drm.*,useDrmWarningexpose{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/:idseul ;GET /shares/:id/eventsuniquement (TC-NOM-11, TC-ERR-06). - Source C (plan §F4 étape 2 + §2bis diagramme séquence) : introduit
useShareDetail(shareId) → GET /shares/:idcomme appel distinct, ainsi qu'un secondgetShare(shareId)de re-check anti-race avantPOST /shares/:id/revoke. - Source D (
sharing-api-client) : exposegetShareDetailet 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) :useRevokeShareinvariant « re-fetch le state avant POST (anti-race H-07) ; si terminal, abort avecShareErrorCode.ALREADY_TERMINAL». - Impact : ajoute un appel réseau supplémentaire non testé. Couverture tests incomplète pour cette branche (pas de TC
ALREADY_TERMINALcô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()surPOST /sharesetPOST /shares/:id/revoke. - Source D (
sharing-api-client) : invariant « createShare et revokeShare injectent un headerIdempotency-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
stateen 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
proofIdcôté API. - Source C (plan §F2) : «
GET /shares?proofId=...si endpoint PD-287 le supporte (HT-07) ; sinon filtre côté client à partir deGET /shares?offset=0&limit=20paginé jusqu'à couverture ». - Source D (
sharing-api-client.listShares) : signature non détaillée sur ce point, invariant n'interdit pas mais ne documente pasproofIdcomme 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é dansALLOWED_ACTIONS: pas d'auto-transition considérée. » - Source D (
sharing-hooks) :ALLOWED_ACTIONSne 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) : «
recipientEmailmalformé (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 « affichet('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) : «
nullou entier>=1; borne max backend à clarifier » ; spec §6 ERR-298-08 parle uniquement de TTL hors bornes, pas demaxViews. - Source B (tests) :
TC-ERR-08couvre TTL, aucun TC dédiémaxViews. §9 listemaxViewsborne max comme « NON TESTABLE — Majeur ». - Source C (plan HT-05) : « Borne max
maxViewsn'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) » +createShareforbidden « 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) : «
drmWarningSeenpar 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 compteau 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
eventTypebackend→UI non figé ». - Source C (plan §F5 + HT-04) : fallback
UNKNOWN_EVENTcôté UI. - Source D (
sharing-components.EventListItem) : rendmaskIp(raw); n'impose pas le champeventRecipientEmailvsrecipient_emailau 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
shareIdest 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 structureeventsdivergente, 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
shareStateentre 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.mdou 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 :
- Bloquant DIV-01 : l'invariant D
sharing-api-clientlisteGET /shares/:idcomme endpoint consommé, alors que la spec ne le contient pas. À aligner (soit ajouter l'endpoint à la spec, soit retirergetShareDetaildu contrat et dériver le détail de la liste). - 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.
- 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.