Aller au contenu

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

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

1. Sources confrontées

# Document Étape Version
A PD-277-specification.md 1 (Spec) v2
B PD-277-tests.md 2 (Tests) v2
C PD-277-plan.md 4 (Plan) v2
D PD-277-code-contracts.yaml 4 (Plan) v2 (header: "v1.0.0")
E PD-277-review-step5-v2.md 5 (Review Phase 1) v2
F PD-277-review-step5.md 5 (Review Phase 1) v1 (contexte)

2. Convergences

  • CONV-01 — Périmètre fonctionnel : Les 6 documents s'accordent sur le périmètre : anti-rejeu nonce sur reEncrypt() + PKI certificate binding sur generateReKey(), module legal-pre uniquement. Aucun document ne tente d'étendre le périmètre hors legal-pre.

  • CONV-02 — 8 invariants : Les invariants INV-277-01 à INV-277-08 sont repris de manière cohérente dans Spec (A §5), Tests (B §5), Plan (C §3) et Code contracts (D modules 1-7). Aucun invariant n'est contredit ou omis.

  • CONV-03 — Objectif 24/24 checks bloquants : Spec (A §1, §11 CA-277-07), Tests (B TC-NOM-04), Plan (C §2 F3) et Code contracts (D) convergent sur l'objectif de conformité Prolog 24/24.

  • CONV-04 — Atomicité SERIALIZABLE : Spec (A §7), Tests (B TC-NEG-02), Plan (C §1 C3, §2 F2) et Code contracts (D module 3) convergent sur la transaction SERIALIZABLE pour l'anti-rejeu nonce.

  • CONV-05 — Format nonce UUID v4 lowercase : Spec (A §4), Tests (B TC-ERR-02, TC-NEG-01), Plan (C §1 C3, §5 TC-ERR-02) et Code contracts (D module 3) convergent sur le format strict UUID v4 lowercase ASCII 36 caractères avec la même regex.

  • CONV-06 — Fail-closed systématique : Tous les documents convergent sur le principe fail-closed (INV-277-01). Spec (A §5, §10), Tests (B §4-5), Plan (C §3, §6) et Code contracts (D module 3 forbidden) appliquent ce principe sans exception.

  • CONV-07 — Aucun nouveau StatusEnum : Spec (A §2, INV-277-08), Tests (B TC-INV-08), Plan (C §3 INV-277-08) et Code contracts (D module 2 forbidden) s'accordent : aucun nouvel état métier.

  • CONV-08 — Corrections v1→v2 appliquées : Les constats v1 (review F) suivants sont adressés en v2 :

  • Frontière code contracts (2 modules → 1 pour legal-rekey-manager.service.ts) — CORRIGÉ (D module 3 pd277-rekey-manager-controls).
  • Module repository manquant → AJOUTÉ (D module 4b pd277-rekey-repository).
  • Fail-closed sur ReKeys hérités → AJOUTÉ (C §2 F2 étape 3b, D module 3 invariant hérité).
  • Validation certificats invalides/révoqués → ENRICHI (C §1 C4 : validation non expiré, non révoqué, compatible mandat).
  • Statut ACTIVE hérité PD-81 → CLARIFIÉ (C §2 F2 "hors scope PD-277").
  • Dépendances inter-PD → AJOUTÉ (C §11 tableau structuré avec statuts).

  • CONV-09 — Couverture tests ↔ invariants : La matrice de couverture (B §2) associe chaque invariant à au moins un test, et le plan (C §5) mappe chaque test à un mécanisme et un composant. Pas de trou de couverture identifié.

  • CONV-10 — 5 codes d'erreur : Les codes ERR-NONCE-MISSING, ERR-NONCE-FORMAT, PRE_NONCE_REPLAY_DETECTED, PRE_CERTIFICATE_BINDING_FAILED, ERR-PERSISTENCE-CONTROL sont identiques en Spec (A §10), Plan (C §6), Code contracts (D module 5) et Tests (B §4).

3. Divergences

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

  • DIV-01 : DDL certificats — DEFAULT '' non contractualisé dans la spec
  • Source A (Spec v2 §6) : DDL canonique = owner_certificate_id VARCHAR(255) NOT NULL et recipient_certificate_id VARCHAR(255) NOT NULLpas de DEFAULT.
  • Source C (Plan v2 §1 C1, §2 F4) : DDL = NOT NULL DEFAULT ''. Le plan affirme : "Ce choix de migration est contractualisé dans la spécification §6.1 (DDL canonique mis à jour en v2)."
  • Source D (Code contracts module pd277-migration) : Invariants mentionnent VARCHAR(255) NOT NULL sans préciser le DEFAULT.
  • Source E (Review v2 constat 1) : Classé BLOQUANT — "La spec impose NOT NULL sans DEFAULT, le plan implémente DEFAULT ''."
  • Impact : La spec v2 fournie ne contient PAS le DEFAULT '' que le plan prétend y avoir contractualisé. Les 3 artefacts (Spec, Plan, Code contracts) disent 3 choses différentes. La migration échouera si des LegalReKey pré-existants existent et que le DDL n'a pas de DEFAULT.

  • DIV-02 : Source de génération du nonce — contradiction interne à la spec

  • Source A (Spec v2 §4) : "Génération: crypto.randomUUID() côté serveur uniquement, jamais côté client."
  • Source A (Spec v2 §9 F2) : "Le système reçoit une demande reEncrypt contenant un nonce." — implique que le nonce est fourni par l'appelant.
  • Source C (Plan v2 §2 F2) : Le nonce arrive dans la requête (étape 1 : "Valider format nonce").
  • Source C (Plan v2 §6) : "Le client reçoit un HTTP 500 et peut retenter avec un nouveau nonce. [...] le client génère un nouveau nonce via crypto.randomUUID() et réessaie."
  • Source E (Review v2 constat 2) : Classé MAJEUR — "nonce fourni/renouvelé côté client, contraire à l'exigence server-only."
  • Impact : La spec se contredit elle-même (§4 "jamais côté client" vs §9 F2 "contenant un nonce"). Le plan adopte implicitement le modèle "nonce fourni par l'appelant" sans trancher l'ambiguïté. Si le nonce est généré côté serveur uniquement, toute l'architecture de F2 et le mécanisme de retry changent fondamentalement.

  • DIV-03 : Frontière API — endpoint controller non stabilisée

  • Source C (Plan v2 §2 F2) : "LegalPreController.reEncryptDocument() [nouveau endpoint ou méthode]" — alternative ouverte.
  • Source C (Plan v2 §10) : Hors périmètre = "aucune modification d'API, de contrôleur, ou de module tiers."
  • Source D (Code contracts) : Aucun module controller défini. Le fichier controller n'apparaît dans aucun files:.
  • Source E (Review v2 constat 3) : Classé MAJEUR — "Le plan ouvre deux options incompatibles et ne contractualise pas la frontière controller/orchestrator."
  • Impact : Si reEncryptWithNonce() est une nouvelle méthode dans le service, elle doit être exposée par un controller. Le plan décrit un point d'entrée controller (F2 étape 1) tout en excluant les modifications de controller du périmètre. Les agents d'implémentation n'ont pas de contrat sur le controller.

  • DIV-04 : Immuabilité certificats — couverture incomplète des chemins d'écriture

  • Source A (Spec v2 INV-277-05) : "Les identifiants de certificats liés à un LegalReKey sont immuables après création."
  • Source C (Plan v2 §1 C5) : Protection via updateStatus() + guard service/DTO.
  • Source D (Code contracts module pd277-rekey-repository) : Forbidden = "Méthode update() sans exclusion explicite des champs certificats." Mais ne mentionne pas save(), QueryBuilder.update(), ou accès direct.
  • Source E (Review v2 constat 4) : Classé MAJEUR — "pas de garantie explicite contre autres chemins d'écriture repository/query builder hors ce flux."
  • Impact : La protection couvre updateStatus() et les DTO mais pas les autres chemins d'accès TypeORM. Un développeur futur pourrait contourner involontairement la garde d'immuabilité via save() ou QueryBuilder.

  • DIV-05 : Référence de version dans les code contracts

  • Source D (Code contracts header) : "Source normative: PD-277-specification.md v2, PD-277-plan.md v1".
  • Source C (Plan) : Le plan est en version 2.
  • Source E (Review v2 constat 5) : Classé MINEUR — dette de traçabilité.
  • Impact : Les code contracts référencent le plan v1 alors qu'ils intègrent les corrections v2. Incohérence de traçabilité documentaire.

4. Zones d'ombre

  • ZO-01 — Modèle d'appel nonce non formalisé : La spec §4 dit "serveur uniquement" mais §9 F2 dit "contenant un nonce". Aucun document ne tranche explicitement : le nonce est-il (a) généré dans le service legal-pre avant l'appel à reEncrypt, (b) généré par l'appelant interne (un autre service backend), ou © fourni par le client HTTP ? Le modèle de retry (plan §6) suppose que l'appelant régénère un nonce, orientant vers (b) ou ©. Cette ambiguïté doit être résolue avant implémentation.

  • ZO-02 — Interface d'extraction certificats depuis TSP : Le plan (C4) mentionne "Extraire ownerCertificateId depuis tspResult.certificateChainRef" et "recipientCertificateId depuis tspResult (bob certificate)". Mais la spec ne définit pas ces champs dans TspVerificationResult. Le code contracts (D module 4) les définit comme optionnels. Aucun document ne spécifie le comportement si le TSP réel (non-stub) ne retourne pas ces champs — fallback ou fail-closed ?

  • ZO-03 — Coexistence reEncrypt() / reEncryptWithNonce() : Le plan crée reEncryptWithNonce() comme nouvelle méthode. Aucun document ne précise si l'ancienne reEncrypt() du LegalReKeyManagerService continue d'exister, est dépréciée, ou est supprimée. Les appelants existants de reEncrypt() ne sont pas identifiés.

  • ZO-04 — Backfill des ReKeys pré-existants : Le plan (V5) mentionne que les ReKeys pré-existants avec certificats vides ne peuvent pas utiliser reEncryptWithNonce() (fail-closed). Aucun document ne précise : combien de ReKeys pré-existants existent en production ? Quels cas d'usage métier sont affectés ? Quand la story de backfill sera-t-elle planifiée ?

  • ZO-05 — Simulation certificats invalides dans le stub TSP : Le plan (C4) mentionne : "En contexte stub, le stub retourne toujours des certificats valides sauf configuration de test explicite." Mais le code contracts du stub (D module 6) ne mentionne aucun mécanisme de configuration pour simuler des certificats invalides. Comment les tests TC-ERR-06 et TC-NEG-05 (certificat expiré/révoqué/non autorisé) sont-ils exécutés ?

  • ZO-06 — Détection ERR-PROLOG-FACTS-OUTDATED : Le plan (§10) exclut la "détection runtime de faits Prolog obsolètes" et renvoie au CI/CD. Mais le code d'erreur ERR-PROLOG-FACTS-OUTDATED est défini dans la spec (§10) et testé par TC-ERR-09. Aucun mécanisme de détection automatique n'est décrit — le constat est-il manuel ou CI/CD ?

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

Actions de rework requises :

  1. DIV-01 (BLOQUANT) : Aligner le DDL canonique dans TOUS les artefacts. Soit ajouter DEFAULT '' dans la spec v2 §6 et les code contracts, soit retirer le DEFAULT du plan et utiliser une migration en 2 étapes. Un seul DDL de référence doit exister.

  2. DIV-02 (MAJEUR) : Résoudre la contradiction interne de la spec §4 vs §9 sur la source du nonce. Formaliser explicitement qui génère le nonce et propager la décision dans plan, tests et code contracts.

  3. DIV-03 (MAJEUR) : Trancher la frontière controller. Soit ajouter un module controller dans les code contracts, soit documenter explicitement que le controller est modifié a minima et qu'un agent en est propriétaire.

  4. DIV-04 (MAJEUR) : Renforcer la garde d'immuabilité en ajoutant dans le code contracts des forbidden couvrant save() et QueryBuilder.update() sur les champs certificats, ou en documentant un mécanisme TypeORM (subscriber/listener).

  5. DIV-05 (MINEUR) : Corriger la référence de version dans l'en-tête des code contracts (plan.md v1plan.md v2).