Aller au contenu

PD-56 — Rapport de confrontation (Gate 5 — Plan)

Ce rapport est produit par l'orchestrateur Claude avant la gate PMO 5. Il confronte les documents produits pour identifier convergences, divergences et zones d'ombre.

1. Sources confrontees

  • Specification : PD-56-specification.md (v3, etape 1)
  • Tests : PD-56-tests.md (v3, etape 2)
  • Plan : PD-56-plan.md (etape 4)
  • Code Contracts : PD-56-code-contracts.yaml (v1.0.0, etape 4)

2. Convergences

  • C-01 Machine d'etat : Les quatre documents s'accordent sur la machine a etats PENDING/AVAILABLE/CORRUPTED avec transitions identiques (spec §5.4, tests TC-INV-06/TC-ERR-09/TC-ERR-10, plan §3, contracts C1/C5). CORRUPTED est terminal strict dans tous les documents. AVAILABLE->PENDING est explicitement interdit partout.

  • C-02 Auto-verification obligatoire : INV-56-05 est coherent : la spec exige computeRoot == merkleRoot avant tout retour available, le plan l'implemente en etape 6 du flux F-01, les tests couvrent via TC-NOM-05/TC-ERR-08, les contracts C3 l'interdisent dans forbidden.

  • C-03 Anti-enumeration : Message uniforme ERR-56-01 pour eventId invalide ET inexistant — spec §6, plan §7, contracts C3/C8, tests TC-ERR-01/TC-ERR-02.

  • C-04 Anti-flood SECURITY_ALERT : INV-56-09 est coherent sur le mecanisme : premiere detection uniquement, pas de re-emission sur etat deja CORRUPTED. Spec §5.4, plan §3 (rowCount>0), contracts C5/C6, tests TC-NOM-11/TC-ERR-10/TC-NEG-14.

  • C-05 Codes contractuels : Les cinq codes ERR-56-01 a ERR-56-05 sont definis de maniere identique dans spec §6, plan §6, contracts C8, et couverts par les tests.

  • C-06 VARCHAR + CHECK constraint : Le plan (§9 point 3), les contracts C1/C2/C10, et la spec §5.6 convergent sur l'utilisation de varchar avec CHECK constraint au lieu d'ENUM PostgreSQL natif (REX PD-282).

  • C-07 Decoupage composants : Le plan decoupe en 12 composants (C1-C12) et les code contracts definissent un contrat pour chacun. Le mapping est bijectif. Les interfaces exposees sont coherentes entre plan et contracts.

  • C-08 Flux pending : Spec F-02/F-05, plan F-02, tests TC-NOM-02/TC-ERR-03 et contracts C9 sont coherents sur la distinction pending avec ETA (ERR-56-02 + payload) vs pending sans ETA (ERR-56-05).

  • C-09 Couverture tests : La matrice de couverture (tests §2) mappe chaque invariant INV-56-* et chaque critere CA-56-* a au moins un test. Le plan §5 confirme cette couverture en mappant chaque TC-* a un mecanisme et un observable.

  • C-10 Cas limite arbre unitaire : treeSize=1 avec merklePath=[] est valide dans spec §5.1, plan §9 point 7, contracts C7, tests TC-NOM-12/TC-NEG-13.

  • C-11 Tri lexicographique : hashPair(sorted(a,b)) pour la reconstruction de la racine est coherent dans spec §5.5 F-03, plan (reference MerkleProofVerifier existant), contracts C12, tests TC-NOM-04.

  • C-12 Finality gate : finalized_at IS NOT NULL obligatoire avant retour available — spec §5.4 condition PENDING->AVAILABLE, plan F-01 etape 7, contracts C3 forbidden, tests TC-NOM-06/CA-56-10.

3. Divergences

Les conflits ne doivent JAMAIS etre lisses. Chaque divergence est rendue visible.

  • DIV-01 : Persistance de l'etat AVAILABLE — semantique d'ecriture nominale
  • Spec (§5.4) : Definit une machine d'etat avec transition PENDING->AVAILABLE comme transition autorisee persistee. Le diagramme stateDiagram-v2 montre PENDING --> AVAILABLE comme transition normale.
  • Plan (§9bis, F-01 etape 8) : "Pas d'ecriture — l'etat proof_availability est deduit du contexte, pas persiste en nominal. Aucune ecriture en flux nominal (pas de persistance de transition PENDING->AVAILABLE)."
  • Contracts (C5) : L'interface transitionTo(leafHash, targetState) accepte tout ProofAvailabilityState y compris AVAILABLE, mais le plan ne l'appelle jamais avec AVAILABLE.
  • Impact : L'etat AVAILABLE n'est jamais ecrit en base. getCurrentState() retournera toujours PENDING pour les feuilles non corrompues. Fonctionnellement correct (le statut est re-derive a chaque appel), mais la colonne proof_availability_state ne refletera jamais la realite pour les preuves disponibles. Cela peut affecter des requetes analytiques futures (ex: SELECT COUNT(*) WHERE state = 'AVAILABLE' retournera toujours 0).

  • DIV-02 : Patron de concurrence pour la transition vers CORRUPTED

  • Plan §9 point 4 : "Utiliser SELECT ... FOR UPDATE dans une transaction. Pattern : BEGIN -> SELECT leaf FOR UPDATE -> CHECK state != 'CORRUPTED' -> UPDATE state = 'CORRUPTED' -> EMIT SECURITY_ALERT -> COMMIT."
  • Plan §3 (mapping INV-56-09) : "UPDATE ... WHERE state != 'CORRUPTED' RETURNING * : si rowCount > 0 -> premiere detection."
  • Contracts C5 forbidden : "Utiliser SELECT puis UPDATE separes sans lock (race condition)."
  • Impact : Deux patrons concurrents dans le meme plan. Le patron UPDATE ... WHERE ... RETURNING (§3) est un single-statement atomique suffisant (row-level lock implicite PostgreSQL). Le patron SELECT FOR UPDATE (§9 point 4) est un 2-phase pessimistic lock, plus verbeux mais aussi correct. Le contrat C5 interdit SELECT + UPDATE sans lock mais SELECT FOR UPDATE EST un lock. L'implementeur doit trancher entre les deux approches ; le plan ne donne pas de directive univoque.

  • DIV-03 : Hypothese H-56-06 de la spec vs realite PD-237

  • Spec (H-56-06) : "proofAvailabilityState est initialise et maintenu par le flux amont PD-237 avant invocation PD-56."
  • Plan (§1 C10, §9 point 6) : "PD-237 ne fournit PAS proof_availability_state -> migration PD-56 requise." La migration PD-56 ajoute la colonne avec default PENDING.
  • Contracts (C10) : Confirment la migration PD-56.
  • Impact : L'hypothese H-56-06 est factuellement fausse. Le plan corrige en ajoutant la migration. Pas de risque fonctionnel (le plan gere), mais l'hypothese de la spec devrait etre mise a jour pour eviter toute confusion.

  • DIV-04 : Nommage du parametre dans la state machine

  • Plan (§2 F-04) : ProofAvailabilityStateMachine.transitionTo(CORRUPTED, eventHash) — parametre nomme eventHash, targetState en premier.
  • Contracts (C5) : transitionTo(leafHash: string, targetState: ProofAvailabilityState) — parametre nomme leafHash, leafHash en premier, targetState en second.
  • Spec (§3) : "eventHash : hash canonique de l'evenement, utilise comme feuille Merkle" — eventHash et leafHash sont fonctionnellement identiques.
  • Impact : Confusion potentielle pour l'implementeur. Le nommage leafHash (contracts) est aligne avec le nom de colonne (leaf_hash), tandis que eventHash (plan) est aligne avec le vocabulaire spec. L'ordre des parametres dans l'appel (plan: transitionTo(CORRUPTED, eventHash)) ne correspond pas a la signature (contracts: transitionTo(leafHash, targetState)). Risque d'inversion d'arguments.

  • DIV-05 : Recalcul ETA sur fenetre suivante — mecanisme non detaille

  • Spec (§5.3) : "Si ETA depassee et preuve absente: rester pending, recalculer ETA sur fenetre suivante."
  • Tests (TC-NOM-07) : "estimatedAvailableAt est recalcule sur W2 en UTC." — Teste le recalcul.
  • Plan (§2 F-02) : "Lookup AnchorBatch candidat (le plus recent contenant eventId)." Pas de mention de recherche de fenetre suivante.
  • Contracts (C9) : Interface calculateETA(batch: {windowEnd, finalizedAt}): string | null — prend un seul batch en entree. Invariant mentionne "recalculer sur fenetre suivante" mais l'interface ne le permet pas (un seul batch, pas de notion de "suivant").
  • Impact : Le mecanisme de recalcul d'ETA n'est pas architecturalement resolu dans le plan. L'interface C9 ne peut pas calculer la fenetre suivante avec un seul batch en entree. L'orchestrateur C3 devrait probablement fournir le batch suivant, mais ce flux n'est pas documente.

4. Zones d'ombre

  • ZO-01 Resolution eventId -> eventHash (critique) : Le plan §9 point 1 identifie que AnchorBatchEvent (PD-55) n'expose pas de colonne eventHash visible. La spec H-56-01 suppose que cette resolution est deterministe. Le plan propose une jointure vers la table source metier mais ne contractualise pas la requete exacte. C'est le risque technique le plus critique : si la colonne n'existe pas et que la jointure est complexe, l'hypothese HT-56-01 du plan echoue. Point ouvert §10.2 Q1 de la spec.

  • ZO-02 Politique eventId dans batch FAILED : La spec §10.2 Q2 pose la question. Le plan F-02 note explicitement "le filtre sur batch FAILED est un point ouvert, le plan selectionne tout batch contenant l'eventId sans filtrage de statut". Aucun test ne couvre ce scenario specifique (eventId present dans un batch FAILED et absent de tout batch finalise). Risque : retour pending avec ETA sur un batch qui ne sera jamais finalise.

  • ZO-03 Protection timing attack : Le plan §7 mentionne constantTimeCompare() pour la comparaison de racine (reference MerkleProofVerifier PD-237). Ni la spec, ni les tests, ni les code contracts ne mentionnent cette protection. Aucun TC ne valide qu'une comparaison constant-time est effectivement utilisee.

  • ZO-04 Environnement benchmark CA-56-05 : Mentionne comme non fige dans spec §10.2 Q4, tests §9 ("regle non testable — Majeur"), plan §12 ("reserve sur l'opposabilite perf"). Consensus sur le probleme, mais aucune proposition de resolution.

  • ZO-05 Script off-chain TypeScript + execution standalone : Le plan et les contracts (C12) specifient un fichier .ts executable avec node scripts/verify-merkle-proof-offchain.ts. Un fichier TypeScript ne s'execute pas directement avec node sans transpilation prealable (ts-node ou build). Le mecanisme d'execution n'est pas precise.

  • ZO-06 MerkleProofVerifier.verifyProof() avec proof vide : Le plan §9 point 7 note qu'il faut verifier que le verifier existant (PD-237) retourne valid: true quand merklePath=[] et leaf == root. Cette verification n'est pas faite — c'est une hypothese implicite sur le code existant.

  • ZO-07 Requete pour trouver le batch candidat : Le plan F-02 dit "Lookup AnchorBatch candidat (le plus recent contenant eventId)" mais ne detaille pas la requete SQL ni la jointure anchor_batches JOIN anchor_batch_events. Le diagramme de sequence montre SELECT window_end FROM anchor_batches JOIN anchor_batch_events ON ... WHERE event_id = $1 mais la clause de jointure exacte et le tri ("le plus recent") ne sont pas contractualises.

  • ZO-08 Transaction scope du flux nominal : Le plan §3 (INV-56-02) mentionne "READ COMMITTED suffit car pas d'ecriture concurrente sur leaf" et "Lectures dans meme transaction". Mais le flux F-01 effectue 2-3 queries SQL sequentielles. La spec precise "meme snapshot transactionnel" (INV-56-02). Le plan ne specifie pas si ces queries sont dans une transaction explicite ou si READ COMMITTED sans transaction explicite suffit (risque de phantom read entre les queries).

5. Recommandation

  • Proceder — convergence confirmee, aucun conflit bloquant
  • Rework necessaire — divergences a resoudre avant de continuer
  • Escalade — decision humaine requise sur un point structurant

Justification : Le coeur contractuel est solide (convergences C-01 a C-12 couvrent les invariants, la machine d'etat, les codes d'erreur et la couverture de test). Cependant :

  1. DIV-02 (patron de concurrence contradictoire) doit etre tranche pour eviter une ambiguite d'implementation.
  2. DIV-05 (recalcul ETA) presente une incoherence entre l'interface C9 et l'exigence spec/tests : l'interface actuelle ne peut pas satisfaire TC-NOM-07.
  3. ZO-01 (resolution eventId -> eventHash) est critique : si non resolue avant implementation, le composant C4 (EventResolver) ne peut pas etre code.

Les autres divergences (DIV-01, DIV-03, DIV-04) sont mineures et resolubles sans rework structurel.