Aller au contenu

PD-280 — Plan d'implementation

1. Decoupe en composants

C1 — proof-verification-domain (enum + types + configuration)

Responsabilite : Extension du domaine CheckResult a 4 valeurs, introduction des types DTO de verification, configuration SLA.

Fichiers impactes : - src/modules/integrity/enums/check-result.enum.ts — ajout PENDING - src/modules/integrity/enums/verification-status.enum.tsnouveau : enum VerificationStatus { PENDING, DONE } - src/modules/integrity/enums/pending-reason.enum.tsnouveau : enum PendingReason { ANCHOR_BATCH_NOT_FINALIZED, TX_NOT_ENOUGH_CONFIRMATIONS } - src/modules/integrity/interfaces/proof-verification-result.interface.tsnouveau : interface ProofVerificationResult (reponse verification) - src/modules/integrity/config/proof-verification.config.tsnouveau : configuration SLA (pendingResolutionTtl, nextCheckAfterSeconds defaut) - src/modules/integrity/dto/proof-verification-response.dto.tsnouveau : DTO reponse API - src/modules/integrity/dto/proof-verification-error.dto.tsnouveau : DTO erreur metier

Decisions : - CheckResult est etendu sur place (pas de nouvel enum) car les 27 references existantes doivent toutes couvrir 4 valeurs. - VerificationStatus est un enum separe (niveau job, pas niveau maillon).

C2 — proof-verification-service (logique metier)

Responsabilite : Service de verification de preuve avec logique PENDING, reevaluation SLA lazy, mapping interne/API, controles de coherence.

Fichiers impactes : - src/modules/integrity/services/proof-verification.service.tsnouveau : service central de verification - src/modules/integrity/services/proof-verification-mapper.service.tsnouveau : mapping CheckResult.PENDING -> null en API

Decisions : - Service distinct de ArchiveChainVerifierService (PD-251) car le perimetre PD-280 est la verification de preuve (proof), pas la verification d'integrite periodique (archive). - La reevaluation SLA est lazy : declenchee uniquement a l'appel API, pas par worker background.

C3 — proof-verification-controller (endpoint API)

Responsabilite : Endpoint REST GET /proofs/:proofId/verification, validation d'entree, reponse formatee.

Fichiers impactes : - src/modules/integrity/controllers/proof-verification.controller.tsnouveau - src/modules/integrity/integrity.module.tsmodifie : enregistrement du controller, service, mapper et intercepteur dans le module existant IntegrityModule - Pipe de validation UUID v4 pour proofId (reutilisation ParseUUIDPipe existant)

C4 — proof-verification-guards (controles de coherence sortie)

Responsabilite : Intercepteur de sortie verifiant les invariants de coherence avant envoi au client.

Fichiers impactes : - src/modules/integrity/interceptors/verification-contract.interceptor.tsnouveau : controle INV-280-02, INV-280-03, valeur "PENDING" interdite en API, validation pendingReason dans l'enum autorisee

Decisions : - Intercepteur NestJS plutot que middleware pour acceder au corps de reponse. - En cas de violation, retourne 500 VERIFICATION_CONTRACT_BROKEN au lieu d'envoyer une reponse incoherente. - Validation runtime de pendingReason : si la valeur provient de la base de donnees et n'est pas dans l'enum PendingReason, l'intercepteur rejette avec 500 (protection contre desynchronisation code/DB).

C5 — proof-verification-migration (DDL)

Responsabilite : Migration TypeORM pour l'extension de l'enum PostgreSQL check_result, ajout de colonnes pour verification_request_id et pending_since.

Fichiers impactes : - src/database/migrations/XXXXXXXXX-PD-280-AddPendingToCheckResult.tsnouveau

Decisions architecturales : - Decision DDL-01 (enum) : L'enum PostgreSQL check_result passe de 3 a 4 valeurs via ALTER TYPE ... ADD VALUE 'PENDING' (et non varchar). Raison : type strict, validation DB-side, coherence avec les tables existantes. - ALTER TYPE ADD VALUE est non-transactionnel en PostgreSQL : la migration doit utiliser queryRunner.query() directement (pas de transaction wrapping). - Decision DDL-02 (verification_request_id) : Ajout d'une colonne verification_request_id UUID NOT NULL DEFAULT gen_random_uuid() sur la table integrity_verification_jobs (ou equivalent). Le UUID est genere a la creation du job de verification (processus externe, hors perimetre PD-280), pas par l'endpoint GET. L'endpoint GET lit simplement cette colonne. Le verificationRequestId est donc stable de PENDING a DONE sans aucune mutation par l'endpoint (conforme INV-280-09, lecture seule). Les rappels retournent le meme UUID (INV-280-06). - Decision DDL-03 (pending_since) : Ajout d'une colonne pending_since TIMESTAMPTZ DEFAULT NULL sur la table integrity_check_attempts pour chaque maillon. Remplie lors de la transition vers PENDING. Utilisee par le calcul SLA lazy pour determiner si elapsed > pendingResolutionTtl. - Down migration : recreation du type enum sans PENDING + cast controle (avec guard sur absence de lignes PENDING actives en base).

C6 — tla-alignment (artefacts formels)

Responsabilite : Alignement _AnchoredFacts_PROOF.tla et norm.yaml sur 4 etats.

Fichiers impactes (dans ProbatioVault-doc) : - docs/normes/pv-proof/formal/_AnchoredFacts_PROOF.tla — ajout PENDING dans RealStates et NormMapping - docs/normes/pv-proof/formal/norm.yaml — ajout PENDING: PENDING dans state_mapping

Decisions : - PV_Proof.tla n'est PAS modifie (il definit deja CheckResults a 4 valeurs depuis l'origine). - Le fichier _AnchoredFacts_PROOF.tla est genere par CI (extract-facts.py) mais la source de verite est norm.yaml. Modifier norm.yaml suffit pour la regeneration CI.

C7 — rfc-documentation (documentation RFC)

Responsabilite : Ajout de la section distinguant PENDING vs INDETERMINATE dans la RFC PV-PROOF.

Fichiers impactes (dans ProbatioVault-doc) : - RFC PV-PROOF : ajout section semantique PENDING vs INDETERMINATE

C8 — proof-verification-tests (tests)

Responsabilite : Tests unitaires, d'integration et contractuels couvrant les 30 scenarios TC-* (incluant TC-NOM-04).

Fichiers impactes : - src/modules/integrity/services/__tests__/proof-verification.service.spec.tsnouveau - src/modules/integrity/services/__tests__/proof-verification-mapper.service.spec.tsnouveau - src/modules/integrity/controllers/__tests__/proof-verification.controller.spec.tsnouveau - src/modules/integrity/interceptors/__tests__/verification-contract.interceptor.spec.tsnouveau


2. Flux techniques

2.1 Flux nominal — Verification avec maillons PENDING

Client                    Controller              Service                 Mapper              DB/Chain
  |-- GET /proofs/:id/verification -->|                  |                    |                   |
  |                       |-- validateUUID(id) -->|      |                    |                   |
  |                       |           |           |      |                    |                   |
  |                       |<-- ok ----|           |      |                    |                   |
  |                       |                       |      |                    |                   |
  |                       |-- verify(proofId) ---------->|                    |                   |
  |                       |                       |      |-- findProof(id) ---|                   |
  |                       |                       |      |<-- proof entity ---|                   |
  |                       |                       |      |                    |                   |
  |                       |                       |      |-- checkSlaExpiry() |                   |
  |                       |                       |      |   (lazy reeval)    |                   |
  |                       |                       |      |                    |                   |
  |                       |                       |      |-- verifyChainLinks |                   |
  |                       |                       |      |<-- ChainLinkResult |                   |
  |                       |                       |      |                    |                   |
  |                       |                       |      |-- toApiResponse(result) -------------->|
  |                       |                       |      |   PENDING -> null  |                   |
  |                       |                       |      |   compute status   |                   |
  |                       |                       |      |<-- VerificationResponse ---------------|
  |                       |                       |      |                    |                   |
  |                       |<-- ProofVerificationResult --|                    |                   |
  |                       |                       |      |                    |                   |
  |                       |-- [Interceptor: check coherence] -->|            |                   |
  |                       |<-- ok (or 500) ---------|         |              |                   |
  |                       |                       |           |              |                   |
  |<-- 200 { verificationRequestId, verificationStatus, linkResults, pending* } |               |

2.2 Flux de reevaluation SLA lazy (lecture seule — pas de mutation DB)

1. Client appelle GET /proofs/:proofId/verification
2. Service charge l'etat courant des maillons depuis la DB (SELECT, pas UPDATE)
3. Pour chaque maillon en etat interne PENDING :
   a. Calculer elapsed = now() - maillon.pending_since
   b. Si elapsed > pendingResolutionTtl :
      - **Projeter** le maillon comme INDETERMINATE dans la reponse courante
        (pas de mutation en base — l'etat DB reste PENDING)
      - Logger l'evenement de reclassification SLA pour observabilite
4. Construire la reponse avec les etats projetes
5. Si tous les maillons sont resolus (incluant projections) -> verificationStatus = DONE

IMPORTANT : Conformement a INV-280-09 et CA-12, cet endpoint est en LECTURE SEULE.
La reclassification en base est du ressort du processus de resolution externe (hors perimetre PD-280).
Le mode lazy se traduit par une PROJECTION a la lecture, pas une mutation.

SEMANTIQUE : La spec dit "reclassifie" — cela signifie que l'endpoint PRESENTE le maillon comme
INDETERMINATE dans sa reponse (projection), pas qu'il ecrit en base. L'etat en base reste PENDING
jusqu'a ce qu'un processus externe le mute. L'observabilite est assuree par un log applicatif
horodate emis a chaque projection SLA (evenement "sla_projection", pas "sla_mutation").
Cet evenement de log constitue la "preuve horodatee" exigee par les tests (§8).

2.3 Flux d'idempotence (rappel, PENDING ou DONE)

1. Client appelle GET /proofs/:proofId/verification
2. Service charge le job de verification existant (incluant verificationRequestId, genere a la creation du job)
3. Service charge l'etat courant des maillons
4. Construit la reponse avec le meme verificationRequestId (lu, pas genere)
5. Si tous resolus : verificationStatus = DONE
   Si au moins un en cours : verificationStatus = PENDING
6. Retourne la reponse avec le meme verificationRequestId dans les deux cas
7. Aucune mutation d'etat — le verificationRequestId est stable de PENDING a DONE car genere a la creation du job

NOTE : Le verificationRequestId est genere par le processus externe qui cree le job de verification
(ex: lors de la soumission de la preuve ou de la planification de la verification).
L'endpoint GET le lit simplement. Cela resout la tension lecture seule vs persistance UUID.

2.4 Diagrammes Mermaid

2.4.1 Graphe de dependances des composants

graph TD
    C1[C1 — proof-verification-domain<br/>enum + types + config]
    C2[C2 — proof-verification-service<br/>logique metier]
    C3[C3 — proof-verification-controller<br/>endpoint API]
    C4[C4 — proof-verification-guards<br/>intercepteur coherence]
    C5[C5 — proof-verification-migration<br/>DDL PostgreSQL]
    C6[C6 — tla-alignment<br/>artefacts formels]
    C7[C7 — rfc-documentation<br/>RFC PV-PROOF]
    C8[C8 — proof-verification-tests<br/>tests]

    C3 --> C2
    C3 --> C4
    C3 --> C1
    C2 --> C1
    C2 --> C5
    C4 --> C1
    C8 --> C1
    C8 --> C2
    C8 --> C3
    C8 --> C4
    C6 -.->|alignement formel| C1
    C7 -.->|documentation| C1

    style C1 fill:#e8f4fd,stroke:#2196f3
    style C2 fill:#e8f4fd,stroke:#2196f3
    style C3 fill:#fff3e0,stroke:#ff9800
    style C4 fill:#fce4ec,stroke:#e91e63
    style C5 fill:#f3e5f5,stroke:#9c27b0
    style C6 fill:#e8f5e9,stroke:#4caf50
    style C7 fill:#e8f5e9,stroke:#4caf50
    style C8 fill:#fff9c4,stroke:#fbc02d

2.4.2 Diagramme de sequence — Flux nominal (verification avec maillons PENDING)

sequenceDiagram
    participant Client
    participant Controller as C3 — Controller
    participant Interceptor as C4 — Intercepteur
    participant Service as C2 — Service
    participant Mapper as C2 — Mapper
    participant DB as PostgreSQL

    Client->>Controller: GET /proofs/:proofId/verification
    Controller->>Controller: ParseUUIDPipe(proofId)
    alt proofId invalide
        Controller-->>Client: 400 INVALID_PROOF_ID
    end

    Controller->>Service: verify(proofId)
    Service->>DB: SELECT proof + check_attempts + verification_job
    DB-->>Service: proof entity + maillons + job (verification_request_id)

    loop Pour chaque maillon PENDING
        Service->>Service: checkSlaExpiry(maillon)
        alt elapsed > pendingResolutionTtl
            Service->>Service: projeter comme INDETERMINATE (pas de mutation DB)
        end
    end

    Service->>Mapper: toApiResponse(internalResult)
    Note over Mapper: PENDING interne → null en API<br/>Derive verificationStatus (PENDING/DONE)<br/>Inclut/exclut pendingReason + nextCheckAfterSeconds
    Mapper-->>Service: ProofVerificationResponseDto

    Service-->>Controller: ProofVerificationResult
    Controller->>Interceptor: validation coherence sortie
    alt INV-280-02/03 viole
        Interceptor-->>Client: 500 VERIFICATION_CONTRACT_BROKEN
    end
    Interceptor-->>Controller: ok
    Controller-->>Client: 200 { verificationRequestId, verificationStatus, linkResults, pending* }

2.4.3 Diagramme de sequence — Reevaluation SLA lazy

sequenceDiagram
    participant Client
    participant Service as C2 — Service
    participant DB as PostgreSQL

    Client->>Service: GET /proofs/:proofId/verification
    Service->>DB: SELECT maillons WHERE proof_id = :proofId
    DB-->>Service: maillons (certains en etat PENDING)

    loop Pour chaque maillon PENDING
        Service->>Service: elapsed = now() - maillon.pending_since
        alt elapsed > pendingResolutionTtl
            Note over Service: Projection lecture seule :<br/>maillon presente comme INDETERMINATE<br/>Etat DB reste PENDING<br/>Log "sla_projection" emis
        else elapsed <= pendingResolutionTtl
            Note over Service: Maillon reste PENDING<br/>→ null en API
        end
    end

    Service-->>Client: Reponse avec etats projetes (pas de mutation DB)

3. Mapping invariants -> mecanismes

Invariant ID Exigence Mecanisme Composant Observable Risque
INV-280-01-checkresult-domain CheckResult contient exactement 4 valeurs Enum TypeScript compile + test exhaustif C1 Compilation echoue si 5e valeur ; test verifie cardinalite = 4 Faible : enum TypeScript strict
INV-280-02-status-coherence PENDING ssi au moins un linkResults[*] = null ; DONE ssi tous resolus Mapper : derive verificationStatus depuis linkResults ; intercepteur de sortie verifie biconditionnelle C2 + C4 Intercepteur rejette 500 si incoherence detectee Moyen : logique de derivation doit etre testee en profondeur
INV-280-03-pending-fields pendingReason + nextCheckAfterSeconds obligatoires si PENDING, interdits si DONE Mapper : inclut/exclut conditionnellement ; intercepteur verifie C2 + C4 500 si champs presents en DONE ou absents en PENDING Faible : test schema conditionnel
INV-280-04-finalization Si proof_finalized = TRUE, jamais PENDING/null Guard dans le service : si finalized, assert aucun maillon PENDING C2 500 VERIFICATION_CONTRACT_BROKEN si violation detectee Moyen : depend de l'integrite des donnees en base
INV-280-05-link-transitions Maillon : PENDING -> {OK,KO,INDETERMINATE} uniquement ; retour interdit Reclassification SLA lazy + verif chain links : seules transitions forward autorisees ; guard dans le mapper C2 Test TC-INV-05 : tentative de transition retour refusee Faible : endpoint lecture seule, mutations par processus externes
INV-280-06-job-transitions verificationRequestId : DONE terminal ; rappel retourne reponse identique Service : si DONE existant, retourne tel quel sans reouverture C2 Test TC-INV-06 : rappel idempotent, meme verificationRequestId Faible : lecture seule
INV-280-07-tla-bijection RealStates = CheckResults (4 valeurs) Mise a jour norm.yaml + _AnchoredFacts_PROOF.tla ; CI verifie ASSUME C6 Pipeline TLA+ vert (ASSUME satisfait) Faible : modification mecanique
INV-280-09-concurrent-idempotence Deux appels concurrents retournent le meme resultat Endpoint lecture seule, pas de lock applicatif C2 + C3 Test TC-NOM-10 : appels paralleles -> reponses identiques Faible : pas de mutation

4. Mapping criteres d'acceptation -> mecanismes

Critere ID Mecanisme(s) Composant Observable Risque
CA-01 Enum CheckResult a 4 valeurs compile + test domaine exhaustif C1, C8 Compilation + test TC-NOM-08 Faible
CA-02 DTO ProofVerificationResponseDto avec verificationRequestId, verificationStatus, linkResults ; mapper null pour PENDING C1, C2 Test TC-NOM-01 : payload JSON conforme Faible
CA-03 Service verification : batch PENDING_FINALITY -> maillon interne PENDING (pas INDETERMINATE) C2 Test TC-NOM-03 : assert PENDING et non INDETERMINATE Moyen
CA-04 Mapper derive DONE ssi tous linkResults non-null ; intercepteur valide biconditionnelle C2, C4 Tests TC-NOM-02, TC-ERR-03 Faible
CA-05 Mapper inclut/exclut pendingReason+nextCheckAfterSeconds selon status ; intercepteur valide C2, C4 Tests TC-NOM-01, TC-NOM-02, TC-ERR-04 Faible
CA-06 norm.yaml + _AnchoredFacts_PROOF.tla alignes sur 4 etats C6 Pipeline TLA+ vert : TC-NOM-07, TC-ERR-06 Faible
CA-07 Guard finalization dans service : assert aucun PENDING si proof_finalized=TRUE C2 Test TC-NOM-06 Moyen
CA-08 Suite de tests existante (PD-251 etc.) reste verte apres extension enum C8 Regression suite verte Moyen : impact potentiel sur les consommateurs de CheckResult
CA-09 Section ajoutee dans RFC PV-PROOF distinguant PENDING vs INDETERMINATE C7 Test TC-NR-03 : verification textuelle Faible
CA-10 Config pendingResolutionTtl [1h, 30j] ; clamp nextCheckAfterSeconds [1, 86400] C1, C2 Tests TC-NOM-05, TC-NEG-05, TC-NEG-09 Faible
CA-11 Validation UUID v4 en entree (ParseUUIDPipe) ; enums case-sensitive en sortie C3, C4 Tests TC-ERR-01, TC-NEG-02, TC-NEG-03, TC-NOM-09 Faible
CA-12 Endpoint lecture seule ; idempotence DONE ; concurrence identique C2, C3 Tests TC-NOM-10, TC-INV-06 Faible

5. Mapping tests (TC-*) -> mecanismes + observables

Test ID Reference spec Mecanisme(s) Point(s) d'observation Niveau de test vise
TC-NOM-01 INV-280-02, INV-280-03, CA-02, CA-05, CA-10, CA-11 Service verify + Mapper + Intercepteur Payload JSON : verificationRequestId UUID v4, status=PENDING, linkResults 4 cles, null pour PENDING, pendingReason present, nextCheckAfterSeconds dans [1,86400] Integration
TC-NOM-02 INV-280-02, INV-280-03, CA-02, CA-04, CA-05 Service verify + Mapper Payload : status=DONE, aucun null, aucun PENDING, pas de pending* Integration
TC-NOM-03 INV-280-05, CA-03 Service verify : evaluation maillon blockchain Maillon interne = PENDING (pas INDETERMINATE) quand PENDING_FINALITY Unitaire
TC-NOM-04 SCN-280-04, CA-03 Service : evaluation maillon blockchain inaccessible Maillon interne = INDETERMINATE (determination immediate, distinct de SLA lazy) Unitaire
TC-NOM-05 INV-280-05, CA-10, ERR-280-05 Service SLA lazy : projection PENDING -> INDETERMINATE a la lecture Etat maillon projete apres expiration SLA + absence erreur transport Integration
TC-NOM-06 INV-280-04, CA-07 Guard finalization dans service Aucun PENDING/null si proof_finalized=TRUE Unitaire
TC-NOM-07 INV-280-07, CA-06 norm.yaml + _AnchoredFacts_PROOF.tla Pipeline TLA+ ASSUME satisfait CI/Formel
TC-NOM-08 INV-280-01, CA-01 Enum CheckResult Cardinalite = 4, valeurs exactes Unitaire
TC-NOM-09 AMB-V2-01, CA-02, CA-11 Mapper : generation verificationRequestId UUID v4 present, stable en rappel DONE Integration
TC-NOM-10 INV-280-09, CA-12 Controller : 2 appels concurrents Reponses identiques, pas de mutation Integration
TC-ERR-01 ERR-280-01, CA-11 ParseUUIDPipe : rejet proofId invalide 400 INVALID_PROOF_ID Unitaire
TC-ERR-02 ERR-280-02 Service : proofId inexistant 404 PROOF_NOT_FOUND Unitaire
TC-ERR-03 ERR-280-03, INV-280-02, CA-04 Intercepteur coherence 500 VERIFICATION_CONTRACT_BROKEN (DONE + null) Unitaire
TC-ERR-04 ERR-280-04, INV-280-03, CA-05 Intercepteur coherence 500 VERIFICATION_CONTRACT_BROKEN (DONE + pending*) Unitaire
TC-ERR-06 ERR-280-06, INV-280-07, CA-06 CI TLA+ Build refuse (desalignement) CI/Formel
TC-NOM-04 SCN-280-04, CA-03 Service : evaluation maillon blockchain inaccessible Maillon interne = INDETERMINATE (determination immediate, distinct du SLA lazy) Unitaire
TC-INV-05 INV-280-05, CA-03, CA-10 Couche de persistance : guard transition retour (processus externe). Dans le perimetre PD-280 : le mapper verifie qu'aucun maillon terminal ne peut etre projete comme PENDING. Maillon terminal en base -> jamais expose comme null en API Unitaire
TC-INV-06 INV-280-06, CA-04, CA-12 Service : rappel DONE idempotent Reponse identique, verificationRequestId stable Integration
TC-NR-01 CA-08 Suite de tests PD-251 existante Tests verts apres extension enum Regression
TC-NR-02 CA-08 Schema linkResults 4 cles Compatibilite conservee Regression
TC-NR-03 CA-09 RFC PV-PROOF Presence textuelle section PENDING vs INDETERMINATE Documentaire
TC-NR-04 CA-06, CA-08 Pipeline TLA+ Build formel vert sur baseline + story CI/Formel
TC-NEG-01 CA-11 ParseUUIDPipe 400 pour UUID non v4 Unitaire
TC-NEG-02 CA-11 Intercepteur 500 pour cle linkResults hors contrat Unitaire
TC-NEG-03 CA-11 Intercepteur 500 pour enum minuscule Unitaire
TC-NEG-04 INV-280-02 Intercepteur 500 pour DONE avec linkResults contenant "PENDING" Unitaire
TC-NEG-05 CA-10 Mapper clamp Clamp nextCheckAfterSeconds en [1, 86400] Unitaire
TC-NEG-06 INV-280-05 Service/Mapper Transition OK->PENDING refusee Unitaire
TC-NEG-07 INV-280-06 Service DONE->PENDING retourne reponse DONE existante (pas erreur) Integration
TC-NEG-09 CA-10 Config validation Bornes pendingResolutionTtl [1h, 30j] Unitaire
TC-NEG-11 Perimetre Inspection architecture Aucun worker/cron SLA Documentaire

6. Gestion des erreurs

Code erreur Code HTTP Condition Mecanisme Observable
INVALID_PROOF_ID 400 proofId non UUID v4 ParseUUIDPipe avec exceptionFactory custom Corps JSON deterministe avec code erreur
PROOF_NOT_FOUND 404 proofId UUID v4 valide mais inexistant Service : query DB retourne null Corps JSON avec code erreur
VERIFICATION_CONTRACT_BROKEN 500 Incoherence detectee (DONE+null, DONE+pending*, enum minuscule, cle hors contrat, PENDING en linkResults) VerificationContractInterceptor Corps JSON avec code + requestId/proofId pour correlation

Strategie de traces : - Chaque erreur 500 VERIFICATION_CONTRACT_BROKEN produit un log error avec requestId et proofId pour correlation. - Pas de journal d'audit dedié (hors perimetre CA explicite) — les traces applicatives standard suffisent.


7. Impacts securite

Risque Mitigation Composant
Exposition de "PENDING" en API publique (INV-280-05) Intercepteur de sortie valide que linkResults[*] ne contient jamais la string "PENDING" ; 500 sinon C4
Fuite d'information sur l'etat interne des maillons Seul null est expose pour les maillons en cours ; pas de detail sur le type de pending C2
IDOR sur proofId ParseUUIDPipe + verification que la preuve appartient au tenant (reutilisation guard RLS existant PD-251, non reimplement dans PD-280 — pattern deja contractualise) C3
Timing attack sur l'etat de verification Endpoint lecture seule, pas de mutation, latence constante C2, C3
Injection dans proofId Validation UUID v4 stricte via regex contractuelle C3

8. Hypotheses techniques

ID Hypothese Impact si faux
HT-01 L'entity LegalCompositeProof (schema vault_secure) expose un champ proofId UUID v4 qui servira de cle d'entree pour la verification Si non, il faudra trouver ou creer la table/colonne de reference des preuves
HT-02 L'enum PostgreSQL check_result est utilise dans integrity_check_attempts et potentiellement dans d'autres tables de verification ; ALTER TYPE ADD VALUE est supporte sans downtime Si non, migration complexe avec recreation de type
HT-03 Le champ chain_blockchain dans IntegrityCheckAttempt a deja default: 'PENDING' en DB — ce qui confirme que PostgreSQL accepte deja la string 'PENDING' meme si l'enum TypeScript ne le contient pas Si l'enum PostgreSQL est stricte et ne contient que 3 valeurs, la migration ALTER TYPE est necessaire
HT-04 La table anchor_batches (schema vault_blockchain) contient un champ status avec au moins la valeur 'PENDING_FINALITY' pour detecter les maillons blockchain en attente Si non, le mecanisme de detection PENDING_FINALITY doit etre adapte
HT-05 verificationRequestId stockage RESOLU : Decision DDL-02 — colonne verification_request_id UUID NOT NULL DEFAULT gen_random_uuid() ajoutee a la migration C5. Genere a la creation du job de verification (processus externe), lu par l'endpoint GET (conforme INV-280-09 lecture seule). Stable de PENDING a DONE.
HT-06 pending_since par maillon RESOLU : Decision DDL-03 — colonne pending_since TIMESTAMPTZ ajoutee a la migration C5. Remplie lors de la transition vers PENDING.

9. Points de vigilance (risques, dette, pieges)

9.1 Extension de CheckResult — impact transversal

Risque : L'enum CheckResult est utilise dans PD-251 (integrity verification) par ArchiveChainVerifierService, ChainLinkResult, et tous les processors. Ajouter PENDING signifie que tous les pattern-match (switch/if) doivent couvrir 4 cas.

Mitigation : TypeScript en mode strict detecte les exhaustive checks manquants. Executer tsc --noEmit apres modification de l'enum pour identifier tous les sites a mettre a jour.

Action : Ajouter un test TC-NR-01 qui verifie que la suite existante reste verte.

9.2 Migration enum PostgreSQL

Risque : ALTER TYPE ... ADD VALUE n'est pas reversible dans une transaction PostgreSQL (pre-v12). La down migration necessite une recreation complete du type enum.

Mitigation : Down migration avec cast temporaire varchar + recreation enum 3 valeurs + cast retour. Guard : verifier qu'aucune ligne PENDING n'existe en base avant down migration.

9.3 Reevaluation SLA sans worker

Risque : Le mode lazy signifie que si personne n'appelle l'endpoint de verification, un maillon PENDING peut rester indefiniment sans reclassification.

Acceptation : Par perimetre de la spec — le worker background est explicitement hors perimetre (§2). La reclassification n'a lieu qu'a l'appel API.

9.4 Coherence avec PD-251

Risque : PD-251 utilise CheckResult pour le resultat de verification d'integrite periodique. PD-280 l'utilise pour la verification de preuve. Les deux modules partagent l'enum mais ont des semantiques distinctes.

Mitigation : L'enum est partage (correction de domaine), mais les services sont distincts. PD-251 ne doit jamais produire PENDING dans ses resultats finaux (il verifie dans un run batch complet).

9.5 Stubs inter-PD

  • // STUB: PD-281 — Zlint anchor enum : si PD-281 modifie les validateurs d'ancrage blockchain, la detection PENDING_FINALITY dans le maillon blockchain pourrait etre impactee.
  • // STUB: PD-??? — Worker SLA background : le worker de reevaluation en arriere-plan est hors perimetre actuel. Quand il sera implemente, il devra utiliser les memes regles de reclassification que le mode lazy.

10. Hors perimetre

Element Justification
Pattern asynchrone complet de verification (queue/BullMQ) Spec §2 : exclu explicitement
Worker/cron de reevaluation SLA en background Spec §2 : mode lazy uniquement dans ce perimetre
Changement de l'endpoint de creation de preuve Spec §2 : hors perimetre
Changement de la logique metier de finalisation de preuve Spec §2 : hors perimetre
Invariants constitutionnels transversaux (chiffrement at-rest, HSM/KMS) Spec §2 : non repetes, appliques par defaut
Politique de versionnement API pour ajout d'enum Spec §10.2 : non fournie, non testable
Comportement client legacy face a PENDING Spec §10.2 : non fournie
Down migration en production avec donnees PENDING actives Spec §10.2 : non tranchee

11. Mecanismes cross-module

Aucune modification d'autres modules. Le plan ne prevoit pas d'ajouter de guard, middleware ou intercepteur sur les routes d'un autre module. L'intercepteur VerificationContractInterceptor est enregistre uniquement sur le controller ProofVerificationController (pas en APP_GUARD global).

Interaction PD-251 : La dependance avec PD-251 est au niveau de l'enum partage CheckResult. Les services PD-251 continuent de ne produire que {OK, KO, INDETERMINATE} dans leurs resultats finaux — l'ajout de PENDING a l'enum n'affecte pas leur logique (les branches exhaustives seront a completer).


12. Perimetre de test

Niveau de test In scope Hors scope (justification)
Unitaire Tous les composants C1 a C5 : enum, service, mapper, intercepteur, controller, config
Integration Interactions service + DB (reevaluation SLA lazy), controller + service + intercepteur (flux complet), concurrence (TC-NOM-10)
E2E Flux complet GET /proofs/:id/verification avec DB reelle (pending nominal, done nominal, reclassification SLA)
CI/Formel Pipeline TLA+ (TC-NOM-07, TC-ERR-06, TC-NR-04)
Documentaire RFC PV-PROOF (TC-NR-03)
Regression Suite PD-251 complete + compilation exhaustive (TC-NR-01, TC-NR-02)

Tous les niveaux de test sont couverts, aucune exclusion.

Couverture attendue : exhaustive sur le perimetre in scope (C1 a C5 + C8). Chaque invariant (INV-280-), critere d'acceptation (CA-) et cas d'erreur (ERR-280-*) DOIT avoir au moins un test dedie avec oracle deterministe.


13. Contraintes techniques

ID Contrainte Detail
CT-01 Framework de test Jest (framework standard du projet ProbatioVault-backend, configure dans jest.config.ts)
CT-02 Compatibilite ESM/CJS Le runner Jest est configure en mode ts-jest avec module: commonjs. Les imports ESM-only doivent utiliser le pattern await import() dynamique ou jest.unstable_mockModule si mocking necessaire.
CT-03 Variables CI DATABASE_URL (PostgreSQL), CI=true (active SonarQube reporter). Configuration dans .gitlab-ci.yml template standard.
CT-04 Dependencies inter-PD PD-251 (DONE, enum CheckResult partage), PD-281 (TODO, Zlint anchor — STUB: // STUB: PD-281), Worker SLA background (TODO, story non creee — STUB: // STUB: PD-??? Worker SLA background)