Aller au contenu

PD-286 — Rapport de confrontation (Étape 5 — pré-Gate Plan)

Confrontation produite par l'orchestrateur Claude avant Gate 5 (AMBIGUITY). Compare la spécification, les tests, le plan d'implémentation et les code contracts. Objectif : rendre visibles les convergences, divergences et zones d'ombre, sans les lisser.

1. Sources confrontées

Source Document Étape d'origine
A PD-286-specification.md Étape 1 (Spécification, Gate 3 v2 = GO)
B PD-286-tests.md Étape 2 (Tests & Validation)
C PD-286-plan.md Étape 4 (Plan d'implémentation)
D PD-286-code-contracts.yaml Étape 4 (Code contracts)

2. Convergences

  • CONV-01 — Constantes de bornes (768 MB / 10 GB) : VOLUME_MAX_BYTES = 805_306_368 et MAX_TOTAL_EXPORT_BYTES = 10_737_418_240 sont identiques entre spec §3/§4 (INV-286-01/02), tests TC-ERR-01/TC-ERR-09, plan §3 et code contracts (constants:). Aucune divergence numérique.
  • CONV-02 — Stack technique : NestJS + TypeORM + PostgreSQL côté backend, React Native + Expo SDK 54 + TypeScript côté app. Spec §10.1, plan en-tête, code contracts en-tête concordent. Aucun composant Swift/SwiftUI requis.
  • CONV-03 — Algorithme cryptographique : SHA3-256 sur JCS (RFC 8785) du manifest partiel. Spec §3/INV-286-07, tests TC-NOM-04, plan §3 mapping INV-286-07, code contracts C3/C8. Lib partagée interne @probatiovault/jcs cohérente entre plan §9.2 et contracts C3/C8.
  • CONV-04 — Format integrityHash strict : regex /^[0-9a-f]{64}$/ case-sensitive imposée à la fois côté backend (validation contrat sortie) et côté app (Zod). Spec §5.1, tests TC-NEG-02, plan §5 (TC-NEG-02), code contracts C2/C8/C10. Plan/contracts interdisent explicitement toute normalisation toLowerCase.
  • CONV-05 — Machine à états et terminaux : 8 états identiques (REQUESTED, PLANNED_SINGLE, PLANNED_MULTI, DOWNLOADING, ASSEMBLING, COMPLETED, FAILED, EXPIRED). États terminaux COMPLETED/FAILED/EXPIRED sans transition sortante (INV-286-11). Spec §4, plan §2ter, code contracts C4 alignés. Trigger PG prevent_forbidden_export_transition ajouté en defense-in-depth (plan §3, contracts C4).
  • CONV-06 — Rétrocompatibilité legacy (INV-286-05) : si totalSize ≤ 805_306_368 ET toutes preuves ≤ VOLUME_MAX_BYTES, contrat legacy PD-85 sans volumes[]. Tests TC-NR-01, plan §2.1/§4 (CA-286-02), code contracts C2/C5 imposent l'omission stricte (@Expose conditionnel) et non null/undefined.
  • CONV-07 — Volume dédié exceptionnel : preuve unique > 805_306_368 et ≤ 10 GB → 1 volume dédié, jamais scindé. Spec INV-286-04, tests TC-NOM-06, plan §3 mapping INV-286-01/04, code contracts C1.
  • CONV-08 — .pvproof final UNIQUE et conteneur inchangé : pvproof.json interne enrichi de volumes_count + assembled_from[] (cardinalité = totalVolumes), sans modification du conteneur PD-85. Spec INV-286-08, tests TC-NOM-03, plan §3, code contracts C9.
  • CONV-09 — Audit WORM fail-closed : append synchrone dans la transaction, contient exportId, volumes_count, integrityHash[], statut final. Anti-catch-absorb (learning 2026-03-08) imposé. Spec INV-286-09, tests TC-NOM-05, plan §3/§6.4, code contracts C6.
  • CONV-10 — Vérification fonctionnelle hash AVANT assemblage : volume-verifier.verify() est appelé avant pvproof-assembler.appendVolume(), avec test d'ordre par spy. Spec INV-286-07, tests TC-NOM-04 + CA-286-05, plan §4 (CA-286-05), code contracts C7/C8.
  • CONV-11 — Téléchargement séquentiel exclusif : aucune parallélisation autorisée. Spec §2 (Hors périmètre), plan H-PD286-PLAN-06 + §10, code contracts C7 (forbidden Promise.all sur volumes[]).
  • CONV-12 — TTL bornes : SIGNED_URL_TTL et EXPORT_SESSION_TTL défaut 24h, bornes [1h, 72h], fail-closed au démarrage si hors bornes. Spec §5.2, plan §7.3, code contracts (constants: SIGNED_URL_TTL_HOURS_DEFAULT/EXPORT_SESSION_TTL_HOURS_DEFAULT).
  • CONV-13 — Worker EXPIRED : tick 30s, pg_advisory_lock mono-instance. Plan §1.1 C4 + §6.3 + §9.1, code contracts C4 + constant EXPIRED_WORKER_TICK_SECONDS.
  • CONV-14 — Aucun feature flag, aucune modification cross-module : périmètre strict src/modules/export/ (backend) + src/export/ (app). Plan §9.4, code contracts cross_module_protections: [] + feature_flags: [].
  • CONV-15 — Couverture des erreurs : TC-NOM-01/02/03/04/05/06, TC-ERR-01/03/04/05/06/07/08/09, TC-INV-03/10/11, TC-NR-01/02, TC-NEG-01..08 sont tous mappés dans plan §5 et exigés par code contracts C11. Couverture minimale 80%/80% explicite côté contracts.
  • CONV-16 — Anti-énumération : codes erreur uniformes, pas de différenciation "inexistant" vs "non autorisé". Plan §7.1 (réutilise pattern PD-85 + learning 2026-03-08), code contracts C5/C6 (forbidden).
  • CONV-17 — Refined types branded : volumeIndex/totalVolumes typés en branded types côté app (learning 2026-03-04). Code contracts C10 forbidden string brut. Plan implicite via H-PD286-PLAN-03 et schema strict.

3. Divergences

⚠️ Les conflits sont rendus visibles tels quels. Aucun lissage.

DIV-01 — Définition exacte de l'input du hash partiel

  • Source A (spec §3, INV-286-07) : integrityHash = SHA3-256(manifest partiel canonicalisé). Pas de mention d'exclusion d'un champ.
  • Source C (plan §3 mapping INV-286-07, §2.2) : recompute = SHA3-256(JCS(v.manifest \ {integrityHash})). Exclusion explicite du champ integrityHash avant hash.
  • Source D (code contracts C3, C8) : "exclure intégrityHash avant canonicalisation" en invariant ; "Inclusion du champ integrityHash dans l'input du hash" en forbidden.
  • Impact : la spec est ambiguë sur la présence ou non du champ integrityHash dans le manifest hashé. Le plan/contracts choisissent l'exclusion (logique : un hash ne peut s'auto-référencer), mais ce choix n'est pas écrit dans la spec. Un re-test cross-runtime divergent (Hermes vs Node) sur ce point invaliderait CA-286-05. Bloquant pour H-PD286-PLAN-01 : la suite de tests roundtrip CI doit valider ce choix avec le backend ET l'app sur la même définition.
  • Recommandation : amender la spec INV-286-07 et §3 (Définitions) pour expliciter manifest_partial \ {integrityHash}, ou inscrire ce choix au contrat. À défaut, risque de drift à long terme.

DIV-02 — Statut de Q-286-02 (taxonomie des codes d'erreur)

  • Source A (spec §10.2) : Q-286-02 ouvert — "Validation de la nomenclature (EXPORT_TOTAL_LIMIT_EXCEEDED, PROOF_TOO_LARGE, etc.)" en attente de décision PO.
  • Source B (tests §9) : "Taxonomie finale des codes d'erreur (Q-286-02) — Majeur — Nomenclature non figée contractuellement". Considérée comme non testable et bloquante.
  • Source C (plan §6.1, §6.2) : codes d'erreur instanciés tels quels (EXPORT_TOTAL_LIMIT_EXCEEDED, PROOF_TOO_LARGE, VOLUME_INTEGRITY_HASH_INVALID, etc.) sans mention de Q-286-02 ni d'attente PO.
  • Source D (code contracts C5, C6) : mêmes codes utilisés en invariants et exemples. Aucune marque "à valider".
  • Impact : si Q-286-02 reste ouvert, les tests verdict QA "Testable partiellement" reste valide ; si plan/contracts les figent, le test est passé du statut "non testable" à "testable" sans validation PO explicite. Risque de rework si le PO impose une autre nomenclature en post-implémentation. Tension avec CONSTITUTIONAL.md Article I (les ambiguïtés doivent être résolues avant Gate 5).
  • Recommandation : escalade PO requise sur Q-286-02 avant Gate 5, OU acter formellement la nomenclature dans la spec (amendement §6 + §10.2 avec verdict "résolu").

DIV-03 — Bumping du pvproof_format_version (compatibilité ascendante)

  • Source C (plan H-PD286-PLAN-04) : "Si gap : promotion vers gap doc (compatibilité ascendante) — pvproof_format_version bumpé à 2 dans pvproof.json, lecteurs PD-85 ignorent les champs inconnus". Mitigation conditionnelle au résultat de TC-NR-02.
  • Source C (plan §10 Hors périmètre) : "Bumping du pvproof_format_version à 2 — décision conditionnelle au résultat de TC-NR-02 (cf. H-PD286-PLAN-04). Si bumping nécessaire, story de suivi."
  • Source D (code contracts C9 forbidden) : "Modification du format conteneur (.pvproof) hors PD-85 — bumping format_version réservé à story de suivi".
  • Impact : tension interne plan/contracts. Si TC-NR-02 échoue (consommateur PD-85 intolérant aux nouveaux champs), le plan envisage un bump version, mais les contracts l'interdisent dans le périmètre PD-286. Le chemin de sortie n'est pas tracé : story de suivi à créer immédiatement ? blocage Gate 8 ? rollback ? Cette ambiguïté procédurale risque de bloquer la livraison si TC-NR-02 fail.
  • Recommandation : trancher le chemin de sortie. Option 1 : story PD-286bis pré-créée et dépendance déclarée. Option 2 : modifier C9 forbidden pour autoriser le bump conditionnel sous trigger documenté. À résoudre avant Gate 5.

DIV-04 — Ordre de tri volumes[] côté backend

  • Source A (spec §5.1, §5.4, §5bis) : aucune contrainte d'ordre dans la réponse. La séquentialité côté app est imposée mais l'ordre serveur est laissé libre.
  • Source C (plan §9.2 "Pièges connus") : "backend renvoie volumes[] triés par volumeIndex ascendant ; app re-trie défensivement". Décision explicite du plan.
  • Source D (code contracts C7) : "Tri local par volumeIndex ascendant (défensif, indépendant de l'ordre serveur)" — invariant côté app uniquement.
  • Source D (code contracts C1, C2, C5) : aucun invariant explicite imposant l'ordre côté backend.
  • Impact : le plan annonce un tri serveur déterministe mais aucun invariant code contracts ne l'enforce côté C1/C2/C5. Risque de régression silencieuse si une refacto change l'ordre serveur. Le tri défensif app couvre fonctionnellement, mais le contrat avec consommateurs tiers (audit externe, debug) devient flou.
  • Recommandation : ajouter un invariant explicite à C1 (ou C5) "volumes[] retourné trié par volumeIndex ascendant", avec test associé. Sinon, retirer la mention d'ordre serveur du plan §9.2.

DIV-05 — Tension INV-286-09 audit transactionnel vs spec §5.5 "aucune atomicité multi-composant"

  • Source A (spec §5.5) : "Atomicité DB + queue/append-only : Aucune atomicité multi-composant additionnelle applicable (pas de flux DB+queue nouveau introduit par ce besoin)."
  • Source A (spec INV-286-09) : "Audit WORM fail-closed inclut exportId, volumes_count, integrityHash[], statut final."
  • Source C (plan §3 mapping INV-286-09, §6.4) : "append synchrone dans la transaction Postgres ; rollback si append throw".
  • Source D (code contracts C4, C6) : "Transition persistée dans une transaction unique avec l'événement audit (atomicité C6)" et "append synchrone bloquant dans la transaction Postgres".
  • Impact : la spec dit "aucune atomicité multi-composant additionnelle" alors que plan/contracts imposent une atomicité state-transition + audit-event dans la même transaction Postgres. Lecture stricte : il y a bien un nouveau couplage transactionnel introduit par PD-286. Lecture indulgente : la spec parle d'atomicité multi-composant (DB + queue), pas intra-DB. Reste à clarifier.
  • Recommandation : amender la spec §5.5 pour expliciter "atomicité intra-Postgres state↔audit applicable, atomicité multi-composant non applicable". Ou inversement, retirer l'invariant transactionnel des contracts si la spec l'interdit (option moins probable car l'anti-catch-absorb learning l'impose).

DIV-06 — Lib @probatiovault/jcs : existante ou à publier ?

  • Source C (plan H-PD286-PLAN-01) : "Réutilise lib partagée (publication interne npm @probatiovault/jcs)". Verbe au futur/conditionnel ("publication") sans préciser si elle existe.
  • Source D (code contracts C3, C8) : "Lib JCS partagée interne @probatiovault/jcs (publication monorepo) — JAMAIS lib publique tierce non auditée" en invariant strict.
  • Impact : si @probatiovault/jcs n'existe pas encore, sa création est une dépendance amont non listée dans le découpage des waves. La wave 2 dépend de cette lib (C3, C8) mais aucun composant ne la produit. Risque de blocage step 6b si la lib est manquante.
  • Recommandation : ajouter un composant C0 "shared-jcs-lib" en wave 0 (foundations), OU vérifier que la lib existe déjà dans le monorepo et le mentionner explicitement dans le plan §1 et les contracts (avec son chemin/version).

DIV-07 — Cohérence diagramme spec §5bis vs matrice §4 transitions

  • Source A (spec §5bis diagramme) : ne montre pas explicitement PLANNED_SINGLE → FAILED ni PLANNED_MULTI → FAILED. Seule la transition * → EXPIRED (depuis non-terminaux) et DOWNLOADING/ASSEMBLING → FAILED sont dessinées.
  • Source A (spec §4 texte) : liste les transitions autorisées avec REQUESTED → FAILED et DOWNLOADING/ASSEMBLING → FAILED. Pas de PLANNED_* → FAILED dans le texte non plus.
  • Source C (plan §9.2) : "Le code C4 doit s'aligner sur le texte, pas sur le diagramme." Reconnaît l'écart implicitement.
  • Source D (code contracts C4) : "matrice de transitions exhaustive (cf. spec §4)" — s'aligne sur le texte.
  • Impact : si une erreur métier survient en PLANNED_* (ex: signed URL non générable côté backend après planification), il n'existe pas de transition légitime vers FAILED. Le code devra soit lever une exception non gérée, soit ajouter cette transition silencieusement. Risque de bug runtime non couvert par le test produit cartésien TC-INV-10 si la matrice de référence est incomplète.
  • Recommandation : amender spec §4 pour ajouter PLANNED_SINGLE → FAILED et PLANNED_MULTI → FAILED si le métier le permet (et amender §5bis), OU justifier explicitement dans le plan que toute erreur après PLANNED_* relève d'un crash interne (5xx, état inchangé).

DIV-08 — Validation manifest typage strict

  • Source A (spec §5.1) : manifest typé "JSON objet (RFC 8785 canonicalisable), non vide, UTF-8 JSON". Pas de schema strict imposé.
  • Source D (code contracts C2 forbidden) : "Type Record<string, unknown> pour manifest — typer JsonObject ou utiliser un type récursif explicite".
  • Source D (code contracts C8 forbidden) : "Modification de l'objet manifest reçu (mutation) — clone défensif via structuredClone".
  • Source B (tests TC-NEG-* ) : aucun test négatif sur la structure du manifest (présence de clés requises, profondeur, taille).
  • Impact : le plan/contracts imposent un typage strict mais ni spec ni tests ne définissent le schema attendu du manifest. Si le manifest reçu contient des champs inattendus ou manque des champs critiques, le hash recalcule correctement (JCS canonicalise tout JSON valide) mais l'assemblage .pvproof peut être corrompu.
  • Recommandation : la spec ou le plan doivent publier le schema canonique du manifest partiel (réutilisé par PD-85 ?) et un test TC-NEG dédié à la validation structurelle. Sinon, INV-286-07 protège l'intégrité bytes mais pas la sémantique.

DIV-09 — expires_at source d'horloge

  • Source A (spec §5.2) : ne précise pas qui détient l'horloge de référence pour expires_at.
  • Source C (plan H-PD286-PLAN-05, §9) : "horloge backend = source unique de vérité ; app vérifie via expires_at retourné par backend".
  • Source D (code contracts C4 forbidden) : "Calcul d'horloge basé sur Date.now() côté app pour décider de l'expiration backend".
  • Source D (code contracts C7 forbidden) : "Calcul local d'expiration via Date.now() — utiliser expires_at retourné par backend".
  • Impact : le contrat est correct mais non spécifié dans la spec ni testé en TC-ERR-05. Si l'app ignore expires_at et utilise Date.now(), faux positifs/négatifs EXPIRED. Test TC-ERR-05 actuel utilise "time-mocking" sans préciser quel temps.
  • Recommandation : amender spec §5.2 pour acter "source d'horloge = backend, app utilise expires_at opaque". Ajouter un test TC-NEG-09 : app reçoit un expires_at futur mais l'horloge locale est en avance → l'app ne doit PAS abort.

DIV-10 — ExportSession entity : nouvelle ou extension PD-85 ?

  • Source C (plan §1.1 C4) : crée entities/export-session.entity.ts + migration <ts>-pd286-export-multi-volumes.ts. Pas de précision sur l'existence préalable.
  • Source D (code contracts C4) : même fichier listé en files. Aucune précision.
  • Source A (spec) : ne décrit pas l'entité de persistance, ni n'évoque PD-85 sur ce point.
  • Impact : si PD-85 a déjà une entité d'export (ce qui est probable pour stocker exportId, signedUrls, etc.), créer une nouvelle ExportSession parallèle créerait une duplication. Si PD-85 utilise une autre représentation, la migration PD-286 doit en tenir compte. Risque de conflit migration/entité au step 6b.
  • Recommandation : auditer PD-85 (src/modules/export/entities/) avant le step 6b et préciser dans le plan : (a) entité existante étendue avec colonnes state, expires_at, volumes_count ; ou (b) nouvelle entité orthogonale avec FK vers l'existante. Amender code contracts C4 pour refléter le choix.

DIV-11 — Suite TC-INV-01/02/04/06/08/09 mentionnés en §5 tests sans définition

  • Source B (tests §5 Tests d'invariants) : la table cite TC-INV-01, TC-INV-02, TC-INV-04, TC-INV-06, TC-INV-08, TC-INV-09 en plus des TC-INV-03/10/11 définis en §3-4-5.
  • Source B (tests §3-4) : seuls TC-INV-03, TC-INV-10, TC-INV-11 sont explicitement définis dans les blocs Given/When/Then.
  • Source C (plan §5 mapping) : ne mappe que les TC-INV-* définis. Les autres sont absents.
  • Source D (code contracts C11 invariants) : "Tous les TC-* (...TC-INV-03/10/11...)" — n'inclut pas les autres.
  • Impact : les tests d'invariants TC-INV-01/02/04/06/08/09 apparaissent dans la matrice §5 du document tests sans définition. Soit ils sont à écrire (gap de spec test), soit ils sont des doublons des TC-NOM/TC-ERR existants (ambiguïté). Risque de couverture déclarée 100% mais réelle partielle.
  • Recommandation : trancher dans tests §3-5 : soit définir explicitement les blocs GWT manquants, soit retirer ces IDs de la matrice §5. À résoudre dans tests v2 avant Gate 5.

DIV-12 — crypto.timingSafeEqual non requis pour comparaison integrityHash

  • Source D (code contracts C8) : "comparaison stricte (===) avec integrityHash attendu" — pas de timing-safe.
  • Source D (code contracts C8 invariant) : "comparaison stricte (Buffer.timingSafeEqual non requis car non secret)".
  • Source A (spec) : ne se prononce pas.
  • Source C (plan §3 mapping INV-286-07) : "comparaison stricte (Buffer.timingSafeEqual non requis car non secret)". Concorde avec contracts.
  • Impact : non bloquant. Cohérent au sein de plan/contracts. À noter en zone d'ombre car la spec ne le justifie pas.
  • Recommandation : facultatif — amender INV-286-07 pour préciser "comparaison stricte non timing-safe acceptée car le hash n'est pas un secret".

4. Zones d'ombre

  • OMB-01 — Q-286-03 (rétention post-FAILED/EXPIRED) : durée non chiffrée. Plan retient "politique PD-85 par défaut" (§9.1), mais aucun document n'indique cette durée explicitement. Risque conformité RGPD non quantifié.
  • OMB-02 — Q-286-04 (TTL prod) : valeurs déployées non confirmées. Plan retient les défauts spec (24h/24h) mais sans validation prod. Le check "fail-closed au démarrage si hors bornes" couvre la dégradation, pas la dérive silencieuse en prod.
  • OMB-03 — Schema canonique du manifest partiel : ni la spec ni les tests ne publient le schema (clés requises, types, profondeur). Voir DIV-08.
  • OMB-04 — État des entités/migrations PD-85 : voir DIV-10. Audit nécessaire avant step 6b.
  • OMB-05 — Lib @probatiovault/jcs : existence et publication non vérifiées. Voir DIV-06.
  • OMB-06 — Comportement worker EXPIRED en cas d'export en DOWNLOADING côté app : si le backend transitionne vers EXPIRED pendant que l'app télécharge un volume valide, comment l'app est-elle notifiée ? Pull (watchdog expires_at local) ? Push (event WS) ? Erreur 410 sur le téléchargement suivant ? Plan §1.2 C7 mentionne "watchdog app sur expires_at" mais ne précise pas la réaction si aucun nouvel appel API n'est fait.
  • OMB-07 — Génération du manifestRootHash : tests ne mentionnent pas explicitement la vérification de manifestRootHash côté app. Plan calcule manifestRootHash = SHA3-256(JCS(...)) côté backend (§2.2) et le DTO l'expose (§5.1 spec), mais aucun TC-* ne valide qu'il est recalculé/vérifié côté app. Si non vérifié, INV-286-07 reste partiellement protégé (volumes individuels OK, ensemble racine non).
  • OMB-08 — Concurrence multi-export du même complaintId : si un user lance 2 exports simultanés sur le même dossier, comportement non spécifié. Quotas/rate limiting PD-85 sont déclarés "inchangés", mais le partitionnement déterministe pourrait produire le même contenu sur des exportId différents (acceptable) ou consommer 2× la temp storage (non acceptable). Hors périmètre déclaré, mais zone d'ombre opérationnelle.
  • OMB-09 — Trigger PG prevent_audit_mutation : code contracts C6 référence "réutilise pattern PD-287 audit-journal", mais ni spec ni plan ne confirment l'existence de ce pattern. Si PD-287 n'est pas mergé/déployé, dépendance amont non explicite.
  • OMB-10 — Métriques opérationnelles : plan §7.2 liste des logs structurés, mais aucune métrique Prometheus (compteur, histogramme) n'est définie pour observabilité prod (ex: latence partition, taux de hash mismatch, durée moyenne export multi-volumes). Acceptable pour MVP mais à documenter comme dette si ce n'est pas l'attendu.

5. Recommandation

  • Procéder — convergence confirmée, aucun conflit bloquant
  • Rework nécessaire — divergences à résoudre avant Gate 5
  • Escalade — décision humaine requise sur un point structurant

Conditions de levée du rework (priorisées)

Priorité Action Référence
P1 (bloquant Gate 5) Trancher Q-286-02 (taxonomie codes erreur) — escalade PO ou amendement spec DIV-02
P1 (bloquant Gate 5) Définir le chemin de sortie si TC-NR-02 fail (pvproof_format_version) DIV-03
P1 (bloquant Gate 5) Vérifier ou créer @probatiovault/jcs en wave 0 DIV-06, OMB-05
P1 (bloquant Gate 5) Auditer entités PD-85 et préciser stratégie ExportSession DIV-10, OMB-04
P2 (correctif spec/tests v2) Expliciter manifest \ {integrityHash} dans INV-286-07 DIV-01
P2 (correctif tests v2) Définir ou retirer TC-INV-01/02/04/06/08/09 DIV-11
P2 (correctif spec v2) Amender §4 transitions PLANNED_* → FAILED ou justifier l'absence DIV-07
P2 (correctif spec v2) Acter source d'horloge backend dans §5.2 + test TC-NEG-09 DIV-09
P3 (correctif plan v2) Ajouter invariant tri serveur volumes[] OU retirer du plan DIV-04
P3 (correctif plan v2) Publier schema canonique du manifest partiel DIV-08, OMB-03
P3 (correctif plan v2) Ajouter test recalcul manifestRootHash côté app OMB-07
P3 (correctif spec v2) Clarifier §5.5 sur atomicité intra-DB state↔audit DIV-05

Synthèse

  • Convergences fortes (17) sur les invariants critiques (constantes, hash, états, pvproof unique, audit fail-closed, séquentialité, anti-énumération, refined types).
  • Divergences (12) dont 4 P1 bloquantes pour Gate 5 (taxonomie erreur, sortie pvproof_version, lib JCS, entité ExportSession).
  • Zones d'ombre (10) dont 3 P2 à éclaircir avant step 6b (manifest schema, manifestRootHash app-side, comportement EXPIRED in-flight).

Le plan est techniquement solide et cohérent avec la spec sur 80% des invariants, mais 4 points P1 doivent être tranchés pour ne pas reporter de dette à Gate 8.