Aller au contenu

PD-275 — Rapport de confrontation (Étape 5 — Gate Plan, v2)

Ce rapport est produit par l'orchestrateur Claude avant la gate PMO 5 (itération 2). Il confronte la spécification, les tests, le plan d'implémentation (post-corrections v2) et les code contracts pour identifier convergences, divergences et zones d'ombre.

1. Sources confrontées

  • Spécification : PD-275-specification.md (étape 1, inchangée)
  • Tests : PD-275-tests.md (étape 2, inchangés)
  • Plan d'implémentation : PD-275-plan.md (étape 4, avec corrections v2 : suppression C8, BLK-01 pré-check signer, MAJ-02 double protection rôles)
  • Code Contracts : PD-275-code-contracts.yaml (étape 4, inchangés)

2. Convergences

  • INV → mécanismes : Les 12 invariants (INV-275-01 à INV-275-12) sont tous mappés vers des mécanismes concrets dans le plan (§3) avec composants identifiés et observables. La couverture est exhaustive.
  • CA → mécanismes : Les 11 critères d'acceptation (CA-01 à CA-08 + CA-02b/04b/04c/05b) sont tous mappés dans le plan (§4) avec des composants et observables précis.
  • TC → mécanismes : Le plan (§5) mappe chaque test (TC-NOM, TC-ERR, TC-INV, TC-NR, TC-NEG) vers des mécanismes et des points d'observation, avec le niveau de test visé (unit/integration/E2E).
  • Codes d'erreur : Les 9 codes ERR-* de la spec sont tous repris dans le plan (§6, composant C10) avec condition, effet et observable. Concordance exacte.
  • Fail-closed : Les trois documents s'accordent sur le deny-by-default systématique pour finalizeBatch(), submitBatch() et revokeSigner().
  • Verrou pessimiste : Le SELECT ... FOR UPDATE est contractualisé dans la spec (INV-275-11), testé (TC-NOM-10, TC-NOM-11), implémenté dans le plan (C4 findByAddressForUpdate) et interdit en contournement dans les code contracts (forbidden: lecture sans verrou pessimiste).
  • Anti-usurpation revokedBy : Concordance spec (INV-275-10) → plan (C7 validation DTO + extraction request.user.sub) → tests (TC-ERR-09) → code contracts (forbidden: Accepter revokedBy depuis le body).
  • État terminal REVOKED : Concordance spec (INV-275-06) → plan (pas de méthode de réactivation) → tests (TC-INV-06, TC-NEG-06) → code contracts (forbidden: transition REVOKED -> *).
  • Migration réversible : Concordance spec (CA-08) → plan (C1 migration up/down) → tests (TC-NOM-09) → code contracts (INV migration réversible).
  • Bornes confirmation_count : Les bornes [0, 2147483647] sont identiques entre spec (§10.2), plan (C1 CHECK constraint, C5 validation), tests (TC-ERR-01, TC-NEG-01, TC-NEG-02).
  • Hypothèses : Le plan reprend les hypothèses de la spec (H-01 à H-07) et les traduit en hypothèses techniques (HT-01 à HT-07) avec des mitigations concrètes.
  • Hors périmètre : Concordance exacte entre spec (§2 Exclu) et plan (§10).
  • Stack technique : Le plan est conforme à la stack contractuelle spec (NestJS + TypeORM + PostgreSQL + TypeScript). Aucune mention de stack non conforme.
  • Schéma signer_registry : Concordance spec (§3, §5) → plan (C1 DDL, C2 entity) → code contracts (module signer-registry-entity).
  • Suppression C8 (v2) : La correction v2 supprime SignerActiveGuard (C8), résolvant la divergence DIV-03 de la confrontation v1. Le contrôle signer est désormais exclusivement dans le service layer avec SELECT ... FOR UPDATE transactionnel, conforme à INV-275-11. Le flux BullMQ worker est correctement adressé.
  • Pré-check signer (BLK-01) : La correction v2 déplace le contrôle signer AVANT createBatch() dans FT4, garantissant CA-05 ("aucun batch créé" si signer REVOKED/inconnu). Conforme à l'exigence spec.
  • Double protection rôles (MAJ-02) : Le service revokeSigner() vérifie les rôles en plus du controller @Roles(), couvrant les appels service-to-service. Conforme à INV-275-09.
  • Autorisation revoke : Concordance spec (INV-275-09, Flux F3 étape 2) → plan (C7 @Roles + C4 assertion interne) → tests (TC-ERR-08) → code contracts (INV-275-09 dans signer-controller).

3. Divergences

⚠️ Les conflits ne doivent JAMAIS être lissés. Chaque divergence est rendue visible.

  • DIV-01 : Nom de l'état batch pré-finalisation
  • Source A (Plan §2 FT2, §3, §6) : utilise PENDING_FINALITY comme état pré-finalisation.
  • Source B (Spec §5 Modèle d'états) : utilise NON_FINALIZED comme état pré-finalisation.
  • Source C (Tests TC-INV-07B) : reprend NON_FINALIZED de la spec.
  • Impact : Divergence terminologique. Le plan s'appuie sur l'état réel du code existant (PD-55) tandis que la spec utilise une abstraction. La correspondance doit être explicitement documentée (NON_FINALIZED = PENDING_FINALITY dans le code) sinon les tests TC-INV-07B risquent de vérifier un état inexistant.

  • DIV-02 : Signature de revokeSigner() — incohérence interne au plan

  • Source A (Plan C4 — MAJ-02) : revokeSigner(address: string, actorIdentity: string, actorRoles: string[], manager: EntityManager) — 4 paramètres, incluant actorRoles pour la double protection.
  • Source B (Plan C7 — FT3 flux) : SignerRegistryService.revokeSigner(address, actorIdentity, manager) — 3 paramètres, actorRoles absent de l'appel.
  • Impact : La correction v2 (MAJ-02) a ajouté actorRoles dans la définition du service C4 mais le flux technique FT3 n'a pas été mis à jour pour refléter ce 4ème paramètre. Ambiguïté contractuelle : l'agent implémentant C7 (controller) ne sait pas qu'il doit transmettre actorRoles au service.

  • DIV-03 : Anti-usurpation revokedBy — couverture des vecteurs d'injection

  • Source A (Spec §6, ERR-REVOKEDBY-SPOOFING) : "presence d'un champ revokedBy fourni par appelant (payload/query/header metier) → requete invalide/refusee".
  • Source B (Plan C7, FT3, §7.2) : La validation anti-spoofing couvre uniquement le body DTO ("si le body contient un champ revokedBy → ERR-REVOKEDBY-SPOOFING"). Les vecteurs query param et header métier ne sont pas explicitement traités dans le flux technique.
  • Impact : La spec contractualise le rejet sur trois vecteurs (payload, query, header). Le plan ne couvre explicitement que le body. Conformité anti-usurpation potentiellement incomplète.

  • DIV-04 : INV-275-09 absent des code contracts du service signer-registry-service

  • Source A (Plan C4 — MAJ-02) : Le service vérifie actorRoles et throws ERR-REVOKE-UNAUTHORIZED — double protection controller + service.
  • Source B (Code Contracts signer-registry-service) : Les invariants listés sont INV-275-04, INV-275-05, INV-275-06, INV-275-11, INV-275-12. INV-275-09 (revoke-authorization) est absent.
  • Source C (Code Contracts signer-controller) : INV-275-09 est présent.
  • Impact : Si le service est censé vérifier les rôles (MAJ-02), l'invariant INV-275-09 doit aussi figurer dans les code contracts du service. L'agent implémentant le service (agent-signer) n'a pas cette exigence dans son contrat.

  • DIV-05 : Double vérification signer dans le flux de soumission (FT4)

  • Source A (Spec Flux F4) : Décrit une fenêtre de sérialisation unique — "Le systeme ouvre une transaction DB et verrouille la ligne signer ciblee via SELECT ... FOR UPDATE" → décision → soumission dans la même transaction.
  • Source B (Plan FT4 — BLK-01) : Introduit deux vérifications distinctes : (1) pré-check avec transaction dédiée AVANT createBatch(), (2) re-vérification dans la transaction submitBatch() APRÈS createBatch()/buildBatch().
  • Impact : Le plan crée deux fenêtres de sérialisation au lieu d'une seule. Entre les deux, createBatch() et buildBatch() s'exécutent hors verrou signer. Si revokeSigner() intervient entre le pré-check et le re-check, un batch sera créé puis immédiatement rejeté au submit — ce qui satisfait CA-05 ("aucun batch créé" ne veut-il dire aucun batch persisté en DB ou aucun batch entamé ?). La sémantique spec F4 semble exiger une fenêtre unique, le plan en introduit deux pour des raisons pratiques (le pré-check évite le travail inutile de createBatch/buildBatch).

  • DIV-06 : Duplication de tests entre sections nominales et erreurs

  • Source A (Tests §3-4) : TC-ERR-02 ≡ TC-NOM-02, TC-ERR-04 ≡ TC-NOM-07, TC-ERR-05 ≡ TC-NOM-06.
  • Source B (Plan §5) : Reconnaît les duplications mais mappe des niveaux de test différents (TC-ERR → Unit, TC-NOM → Unit + Integration).
  • Impact : Mineur. Redondance de maintenance sans valeur ajoutée fonctionnelle si les Given/When/Then sont identiques.

  • DIV-07 : HTTP status ERR-SIGNER-REVOKED dans le contexte BullMQ worker

  • Source A (Plan §6) : ERR-SIGNER-REVOKED → HTTP 409.
  • Source B (Plan FT4) : submitBatch() est appelé depuis un BullMQ worker, pas un endpoint HTTP. L'erreur est une exception interne (batch → FAILED).
  • Impact : Mineur. Le code HTTP est trompeur pour une erreur interne. N'affecte pas le comportement mais peut induire en erreur lors de l'implémentation.

4. Zones d'ombre

  • ZO-01 : Seed initial du registre signer — aucun composant ni contrat. Le plan (§9.1) identifie le risque critique du seed initial et recommande un script seed-signers.ts ou une insertion SQL. Mais aucun composant (C1-C11) ne couvre cette responsabilité, et les code contracts ne mentionnent aucun fichier de seed. Ce risque est identifié mais non contractualisé.

  • ZO-02 : Rôle SIGNER_ADMIN dans Keycloak — hors périmètre code mais bloquant. Le plan (HT-02, §7.1) mentionne que SIGNER_ADMIN est un nouveau rôle à provisionner dans Keycloak. La spec (INV-275-09) l'exige. Aucun composant ni contrat ne couvre cette provision.

  • ZO-03 : Normalisation EIP-55 des adresses — recommandée mais non contractualisée. Le plan (§9.4, HT-07) recommande la normalisation EIP-55 systématique. La spec (Q-03) l'identifie comme question ouverte. Ni les invariants de la spec ni les code contracts ne contractualisent cette normalisation. Risque de collision d'adresses en casse différente.

  • ZO-04 : ConfirmationTracker — interface d'appel non contractualisée. Le plan (FT1, FT4) montre ConfirmationTrackerFinalityGuardService.updateConfirmationCount(). Mais l'interface d'appel n'est contractualisée ni dans le plan ni dans les code contracts. Aucun composant ne couvre la modification du tracker existant pour appeler updateConfirmationCount().

  • ZO-05 : Identité service account pour revokedBy — non traitée. La spec (§3, H-06) définit actor identity comme "JWT sub ou identite de service account". Le plan (C7, FT3) fixe actorIdentity = request.user.sub. Le cas d'un service account appelant revokeSigner() sans JWT HTTP (appel interne) n'est pas documenté dans le plan. Si un service account appelle le service directement (pas via le controller), la source de actorIdentity n'est pas contractualisée.

  • ZO-06 : Audit events — placement transactionnel asymétrique. FT3 place SIGNER_REVOKED dans la transaction (atomicité avec la mutation, conforme INV-275-05). FT2 place ANCHOR_BATCH_FINALIZED après le commit. Un crash entre commit et logging de ANCHOR_BATCH_FINALIZED perd la trace d'audit de finalisation. La spec ne contractualise pas l'atomicité audit pour la finalisation (seulement pour la révocation), donc techniquement conforme, mais potentiellement incohérent.

  • ZO-07 : Questions ouvertes Q-01 à Q-05 non résolues entre spec et plan. Les 5 questions ouvertes de la spec sont transmises comme hypothèses techniques dans le plan. Le verdict QA des tests confirme que la testabilité est partielle. Ces réserves persistent inchangées.

  • ZO-08 : Responsabilité de production des tests d'intégration. Les code contracts assignent owner_agent: agent-test au module tests-pd275. Mais les fichiers .spec.ts sont aussi listés dans les modules individuels (ex: signer-registry.service.spec.ts dans signer-registry-service avec owner_agent: agent-signer). Double assignation sans clarification : qui produit les tests unitaires du service — agent-signer ou agent-test ?

  • ZO-09 : Sémantique de "aucun batch créé" (CA-05) vs pré-check FT4. CA-05 exige "Aucun batch n'est cree" si signer REVOKED/inconnu. Le plan (BLK-01) ajoute un pré-check qui bloque AVANT createBatch(), satisfaisant cette exigence. Mais si le pré-check passe et revokeSigner() intervient entre le pré-check et submitBatch(), le re-check dans submitBatch() rejetera — mais createBatch() aura déjà été exécuté. La question est : createBatch() persiste-t-il un batch en DB ? Si oui, CA-05 est partiellement violé dans la fenêtre de course.

5. 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 : Les convergences sont solides (12/12 invariants mappés, 11/11 CA mappés, couverture tests complète). La correction v2 a résolu les divergences v1 (suppression C8, pré-check signer BLK-01). Cependant :

  1. DIV-02 (MAJEUR) : Incohérence interne — la signature revokeSigner() dans C4 (4 params avec actorRoles) ne correspond pas à l'appel dans FT3 (3 params). L'agent implémentant C7 n'a pas l'information contractuelle pour transmettre actorRoles.
  2. DIV-03 (MAJEUR) : La couverture anti-spoofing revokedBy du plan ne traite que le body, alors que la spec exige query et header aussi.
  3. DIV-04 (MAJEUR) : INV-275-09 manquant dans les code contracts du service — contrat incomplet pour la double protection MAJ-02.
  4. DIV-05 (MAJEUR) : La double fenêtre de vérification signer (pré-check + re-check) diverge de la sémantique de sérialisation unique de la spec F4, avec impact potentiel sur l'interprétation de TC-NOM-10.
  5. DIV-01 (MINEUR) : Réconciliation terminologique PENDING_FINALITY vs NON_FINALIZED nécessaire.