Aller au contenu

PD-280 — Alignement canonique de l'etat PENDING pour la verification de preuve (v3 corrigee)

0. Traceabilite des corrections (ecarts -> corrections)

Ecart Correction appliquee Localisation
ECT-01 DONE redefini strictement: tous les linkResults exposes API sont dans {OK, KO, INDETERMINATE}; null represente PENDING en API. §4 (INV-280-02), §5.1, §5.7, §7 (CA-04)
ECT-02 / DIV-01 / DIV-04 Suppression de INV-280-08 du perimetre PD-280; ajout note sur invariants constitutionnels transversaux appliques par defaut. §2, §4, §10
ECT-03 Scenarios manquants a completer cote tests: TC-INV-05 et TC-INV-06; retrait TC-INV-08. Document tests (apres ---SPLIT---)
ECT-04 nextCheckAfterSeconds est clamp en sortie dans [1,86400]; pas de rejet 500 pour depassement interne. §5.1, §5.2, §6, §7, tests TC-NEG-05
AMB-01 Frontiere interne/API explicitee (CheckResult.PENDING interne -> null API). §5.7
AMB-02 pendingReason et nextCheckAfterSeconds obligatoires si verificationStatus=PENDING; interdits si DONE. §4 (INV-280-03), §5.1, §7
AMB-03 Suppression de la notion non materialisee de "cycle"; reformulation "meme verification". §5.3, §9
DIV-02 Couvertures "Partielle" retirees dans la matrice de tests; couverture totale ou scenario ajoute. Document tests §2
DIV-03 CA-09 rendu testable textuellement sur contenu RFC (pas sur autorite d'approbation). §7 (CA-09), tests TC-NR-03
SEC-01 Mentions "journal d'audit" hors CA retirees de la spec et des tests. Tests TC-NOM-01, §8 observabilite
AMB-04 Hypothese de concurrence corrigee: endpoint de verification en lecture idempotente (pas de lock applicatif dans ce perimetre). §4 (INV-280-09), §9 (H-280-06)
DIV-05 Epic parent aligne dans les 2 documents: PD-217 — LEGAL & COMPLIANCE. §References + tests §1
MIN-01 Ajout test bornes min/max de pendingResolutionTtl. §8 (SCN-280-09) + tests TC-NEG-09
AMB-V2-01 Ajout de verificationRequestId (UUID serveur) comme identifiant technique de verification; reformulation de INV-280-06 sur cet identifiant. §3, §4 (INV-280-06), §5.1, §5.3, §8
AMB-V2-02 Reevaluation SLA specifiee en mode lazy sur appel API de verification; aucun worker background dans ce perimetre. §2, §5.2, §5.3, §8 (SCN-280-05)
AMB-V2-03 Ajout de INV-280-09 (idempotence concurrente sur proofId, endpoint lecture seule, resolution externe des maillons). §4 (INV-280-09), §5.3, §9 (H-280-06), §7 (CA-12)
ECT-V2-01 Oracle TC-INV-06 tranche: tentative DONE->PENDING => retour de la reponse DONE existante, identique au premier appel DONE (idempotence, pas rejet). §4 (INV-280-06), §5.3, Document tests TC-INV-06

1. Objectif

Definir contractuellement l'introduction de l'etat PENDING dans la verification de preuve, de maniere coherente entre: - le modele metier (CheckResult), - le contrat API de verification, - les invariants de finalisation, - et les artefacts formels TLA+.

La specification impose une distinction testable entre: - PENDING (transitoire, sous SLA), - INDETERMINATE (absence de conclusion potentiellement durable).

2. Perimetre / Hors perimetre

Inclus

  • Extension de CheckResult a 4 valeurs: OK | KO | INDETERMINATE | PENDING.
  • Introduction du statut global de job: verificationStatus: PENDING | DONE.
  • Introduction de verificationRequestId (UUID technique) pour identifier une verification.
  • Contrat API de verification avec linkResults nullable pour maillons en cours.
  • Regles de coherence entre verificationStatus, linkResults, pendingReason, nextCheckAfterSeconds.
  • Reevaluation SLA lazy declenchee lors de l'appel API de verification.
  • Alignement TLA+ (CheckResults, RealStates, ASSUME) et mapping norm.yaml.
  • Preservation de l invariant de finalisation: aucun maillon PENDING apres finalisation.

Exclu

  • Pattern asynchrone complet de verification (queue/BullMQ) - hors perimetre.
  • Worker/cron de reevaluation SLA - hors perimetre (mode lazy uniquement).
  • Changement de l endpoint de creation de preuve - hors perimetre.
  • Changement de la logique metier de finalisation de preuve - hors perimetre.
  • Toute regle non testable est hors perimetre de cette story.
  • Invariants constitutionnels transversaux (ex: chiffrement at-rest, HSM/KMS): non repetes ici, appliques par defaut.

3. Definitions

  • CheckResult: resultat de verification d un maillon (OK, KO, INDETERMINATE, PENDING) en representation interne.
  • verificationStatus: etat global du job de verification (PENDING, DONE) expose API.
  • verificationRequestId: UUID v4 technique, porte par la reponse, identifiant la verification materialisee cote serveur.
  • PENDING: verification en cours, issue attendue dans un delai borne.
  • INDETERMINATE: verification impossible a conclure, potentiellement durable.
  • linkResults: dictionnaire des resultats par maillon en API; null = maillon en cours.
  • proof_finalized: indicateur metier de finalisation de preuve.
  • SLA: borne temporelle contractuelle de resolution d un maillon PENDING.

4. Invariants (non negociables)

ID Regle Justification
INV-280-01-checkresult-domain CheckResult contient exactement OK, KO, INDETERMINATE, PENDING. Alignement metier + modele formel.
INV-280-02-status-coherence verificationStatus = PENDING ssi au moins un linkResults[*] = null en API; verificationStatus = DONE ssi tous les linkResults[*] sont dans {OK, KO, INDETERMINATE}. Coherence API testable sans ambiguite.
INV-280-03-pending-fields pendingReason et nextCheckAfterSeconds sont obligatoires si verificationStatus = PENDING, et interdits si verificationStatus = DONE. Contrat de reponse non ambigu.
INV-280-04-finalization Si proof_finalized = TRUE, alors tous les maillons sont dans {OK, KO, INDETERMINATE} (jamais PENDING interne, jamais null API). Preservation de l invariant ChainLinkExplicit.
INV-280-05-link-transitions Chaque maillon suit uniquement PENDING -> {OK\|KO\|INDETERMINATE}; toute transition inverse est interdite. Determinisme de machine a etats.
INV-280-06-job-transitions Pour un verificationRequestId donne: PENDING -> DONE autorise; DONE -> PENDING interdite. Si la verification est deja DONE, tout nouvel appel de verification pour le meme proofId retourne la reponse DONE existante a l identique (idempotence), sans reouverture. Elimine l ambiguite "meme verification" et impose un oracle deterministe.
INV-280-07-tla-bijection RealStates = CheckResults (egalite d ensembles a 4 valeurs). Suppression de l echec ASSUME.
INV-280-09-concurrent-idempotence La verification d une preuve est idempotente: deux appels concurrents au meme proofId retournent le meme resultat observable (pas de mutation due a l appel). L endpoint de verification est en lecture seule; la resolution des maillons est realisee par des processus externes et lue de maniere idempotente. Evite requirement de locking applicatif dans ce perimetre.

5. Modele de donnees et flux nominaux

5.1 Formats et contraintes de donnees (source unique de verite)

Donnee Format / encodage Taille Jeu de caracteres Case Regex / Regle Si invalide
proofId UUID v4 texte 36 caracteres ASCII [0-9a-f-] case-insensitive en entree, normalise en lowercase en sortie ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$ Rejet 400 Bad Request
verificationRequestId UUID v4 texte 36 caracteres ASCII [0-9a-f-] lowercase en sortie meme regex UUID v4 Rejet 500 (violation contrat serveur)
verificationStatus Enum texte 1 valeur ASCII uppercase + _ case-sensitive ^(PENDING|DONE)$ Rejet 500 (violation contrat serveur)
pendingReason Enum texte 1 valeur ASCII uppercase + _ case-sensitive ^(ANCHOR_BATCH_NOT_FINALIZED|TX_NOT_ENOUGH_CONFIRMATIONS)$ dans ce perimetre Rejet 500 (si present hors contrat ou valeur hors enum)
nextCheckAfterSeconds Entier decimal 1..86400 s (apres clamp) chiffres ASCII N/A entier strict Clamp serveur en sortie vers [1,86400]; 500 uniquement si champ present en DONE
linkResults (cles) Objet JSON 4 cles obligatoires ASCII uppercase + _ case-sensitive cles exactes: DOCUMENT_HASH, MERKLE_PROOF, TSA_TIMESTAMP, BLOCKCHAIN_ANCHOR Rejet 500
linkResults[*] (valeurs API) null ou enum texte 0 ou 1 valeur ASCII uppercase + _ case-sensitive null ou OK|KO|INDETERMINATE (jamais PENDING en API) Rejet 500
RealStates (TLA+) Ensemble symbolique 4 elements N/A case-sensitive {"OK","KO","INDETERMINATE","PENDING"} Echec verification formelle (build KO)

Regles de coherence supplementaires: - verificationRequestId est obligatoire sur toute reponse 200. - Si verificationStatus = DONE, alors aucune valeur linkResults[*] ne peut etre null. - Si verificationStatus = PENDING, au moins une valeur linkResults[*] est null. - Si verificationStatus = PENDING, pendingReason et nextCheckAfterSeconds sont obligatoires. - Si verificationStatus = DONE, pendingReason et nextCheckAfterSeconds sont absents.

5.2 SLA temporels (transitions avec temporalite)

SLA Defaut Min Max Configurabilite Comportement a expiration
Resolution d un maillon PENDING (pendingResolutionTtl) 72 h 1 h 30 j Configurable (parametre applicatif) Reevaluation lazy au moment d un appel API de verification: si TTL depasse, maillon reclassifie INDETERMINATE; le job peut passer DONE si tous les maillons sont resolus
nextCheckAfterSeconds conseille au client 60 s 1 s 86400 s Configurable (parametre applicatif) La valeur exposee est toujours clamp dans [1,86400]

Regle de perimetre: - Aucun worker/cron background n est requis ni autorise par cette story pour la reevaluation SLA.

5.3 Machine a etats et transitions retour

  • Etat job PENDING (pour un verificationRequestId)
  • PENDING -> DONE: AUTORISEE (condition: tous linkResults[*] exposes sont resolves dans {OK,KO,INDETERMINATE}).
  • PENDING -> PENDING: AUTORISEE (polling tant que condition de completion non atteinte).
  • Etat job DONE (terminal pour ce verificationRequestId)
  • DONE -> *: INTERDITE pour ce verificationRequestId.

  • Etat maillon PENDING (interne)

  • PENDING -> OK|KO|INDETERMINATE: AUTORISEE.
  • Etat maillon OK (terminal)
  • OK -> *: INTERDITE.
  • Etat maillon KO (terminal)
  • KO -> *: INTERDITE.
  • Etat maillon INDETERMINATE (terminal)
  • INDETERMINATE -> *: INTERDITE.

Regles idempotentes d appel: - L endpoint de verification est lecture seule (aucune mutation metier creee par l appel). - Si un resultat DONE existe deja pour le proofId dans ce perimetre, l endpoint retourne cette reponse DONE a l identique (y compris verificationRequestId), sans transition retour. - Deux appels concurrents pour le meme proofId doivent observer le meme resultat.

5.4 Strategie migration DDL (si colonne existante impactee)

Element Specification contractuelle
Type actuel et type cible Colonne check_result: soit varchar (inchangee), soit enum PostgreSQL 3 valeurs -> enum PostgreSQL 4 valeurs (ajout PENDING).
Strategie de backfill Aucun backfill obligatoire pour valeurs historiques (absence legitime de PENDING historique).
Impact triggers existants Les triggers existants restent valides s ils ne ferment pas le domaine a 3 valeurs; sinon mise a jour obligatoire pour accepter PENDING uniquement en phase non finalisee.
Impact workers/services dependants Tous les consommateurs qui pattern-match CheckResult doivent couvrir 4 valeurs; absence de branche PENDING = non conforme.
Down migration Si enum PostgreSQL: down migration exige recreation type sans PENDING + cast controle; perte potentielle des lignes PENDING interdite sans plan explicite.
Contraintes ajoutees Contrat metier: interdiction de persister PENDING quand proof_finalized = TRUE (validation applicative/test de non-regression).

5.5 Atomicite multi-composant

Aucune atomicite multi-composant additionnelle introduite par cette story (pas de nouveau flux DB + queue/append-only dans le perimetre).
Statut: Aucune contrainte additionnelle applicable dans ce perimetre.

5.6 Contraintes inter-modules

Interaction identifiee: dependance potentielle avec la logique de gel/integrite (PD-251) consommatrice de CheckResult.
Contrat: - Ce changement n autorise pas de contournement des guards d integrite existants. - Toute regle cross-module doit traiter explicitement PENDING comme non-final. - Aucune nouvelle route cross-module ajoutee dans ce perimetre.

5.7 Mapping interne/API (frontiere explicite)

  • Representation interne:
  • CheckResult peut prendre PENDING.
  • Representation API:
  • linkResults[*] = null represente un maillon interne PENDING.
  • verificationStatus expose PENDING ou DONE au niveau job.
  • Regle de conversion obligatoire:
  • Le code de mapping DTO convertit systematiquement CheckResult.PENDING -> null dans linkResults.
  • La valeur litterale "PENDING" ne doit jamais etre exposee dans linkResults[*] cote API publique.

5.8 Diagrammes Mermaid

5.8.1 Machine a etats — Maillon (CheckResult interne)

Chaque maillon suit une machine a etats monotone a 4 valeurs (INV-280-05). PENDING est le seul etat non-terminal. La transition PENDING -> INDETERMINATE peut etre declenchee par expiration SLA lazy (INV-280-04, ERR-280-05).

stateDiagram-v2
    [*] --> PENDING : creation maillon

    PENDING --> OK : verification reussie
    PENDING --> KO : verification echouee
    PENDING --> INDETERMINATE : echec durable\nou expiration SLA lazy\n(pendingResolutionTtl depasse)

    OK --> [*] : terminal (INV-280-05)
    KO --> [*] : terminal (INV-280-05)
    INDETERMINATE --> [*] : terminal (INV-280-05)

    note right of PENDING
        Si proof_finalized = TRUE,
        PENDING interdit (INV-280-04)
    end note

5.8.2 Machine a etats — Job de verification (verificationStatus)

Le job de verification est identifie par verificationRequestId (INV-280-06). L etat DONE est terminal et idempotent (INV-280-09).

stateDiagram-v2
    [*] --> PENDING : premier appel verification

    PENDING --> PENDING : polling\n(maillons non tous resolus)
    PENDING --> DONE : tous linkResults\ndans {OK, KO, INDETERMINATE}\n(INV-280-02)

    DONE --> [*] : terminal (INV-280-06)

    note right of DONE
        Tout rappel pour le meme proofId
        retourne la reponse DONE identique
        (idempotence, INV-280-06 / INV-280-09)
    end note

    note left of PENDING
        pendingReason + nextCheckAfterSeconds
        obligatoires (INV-280-03)
    end note

5.8.3 Sequence — Flux de verification de preuve (appel API)

Interactions entre le client, l endpoint de verification, la couche metier et la base de donnees. Couvre le flux nominal PENDING, la reevaluation SLA lazy et le mapping interne/API (§5.7).

sequenceDiagram
    participant Client
    participant API as Endpoint Verification
    participant Service as Service Metier
    participant DB as PostgreSQL

    Client->>API: GET /proofs/{proofId}/verify
    API->>API: Validation proofId UUID v4<br/>(ERR-280-01 si invalide)

    API->>DB: SELECT verification existante<br/>par proofId
    alt Verification DONE existante (INV-280-06)
        DB-->>API: verificationRequestId + resultat DONE
        API-->>Client: 200 { verificationStatus: DONE,<br/>linkResults: {OK|KO|INDETERMINATE},<br/>verificationRequestId }
    else Verification PENDING ou nouvelle
        DB-->>Service: maillons + timestamps

        loop Pour chaque maillon PENDING
            Service->>Service: Lecture etat maillon<br/>(resolution externe, INV-280-09)
            alt pendingResolutionTtl depasse
                Service->>Service: Reclassification lazy<br/>PENDING -> INDETERMINATE<br/>(ERR-280-05)
            end
        end

        Service->>Service: Calcul verificationStatus<br/>(INV-280-02)

        alt Tous maillons resolus
            Service->>DB: Persister DONE +<br/>verificationRequestId
            Service->>API: verificationStatus = DONE
        else Maillons encore PENDING
            Service->>API: verificationStatus = PENDING<br/>+ pendingReason<br/>+ nextCheckAfterSeconds<br/>(INV-280-03)
        end

        API->>API: Mapping CheckResult.PENDING -> null<br/>dans linkResults (§5.7)
        API->>API: Clamp nextCheckAfterSeconds<br/>[1,86400] (§5.2)
        API-->>Client: 200 { verificationRequestId,<br/>verificationStatus,<br/>linkResults, ... }
    end

6. Cas d erreur

ID Cas Reponse attendue
ERR-280-01 proofId invalide 400 Bad Request, code erreur metier INVALID_PROOF_ID
ERR-280-02 proofId inconnu 404 Not Found, code PROOF_NOT_FOUND
ERR-280-03 Reponse incoherente (DONE avec null) 500 Internal Server Error, code VERIFICATION_CONTRACT_BROKEN, trace technique obligatoire
ERR-280-04 Champs pendingReason/nextCheckAfterSeconds presents alors que DONE 500 Internal Server Error, code VERIFICATION_CONTRACT_BROKEN
ERR-280-05 Maillon depasse pendingResolutionTtl Reclassification lazy en INDETERMINATE au prochain appel API (pas d erreur transport)
ERR-280-06 TLA+ non aligne (RealStates != CheckResults) Echec pipeline de verification formelle (build refuse)
ERR-280-07 linkResults[*] expose "PENDING" en API 500 Internal Server Error, code VERIFICATION_CONTRACT_BROKEN

7. Criteres d acceptation (testables)

ID Critere Observable
CA-01 CheckResult contient OK, KO, INDETERMINATE, PENDING Enum compilee + tests de domaine
CA-02 L endpoint retourne verificationRequestId, verificationStatus et linkResults; maillon en cours = null Payload JSON conforme schema
CA-03 PENDING_FINALITY produit PENDING (pas INDETERMINATE) Test metier sur cas blockchain finalite
CA-04 verificationStatus = DONE ssi tous linkResults[*] exposes sont dans {OK,KO,INDETERMINATE} Tests de coherence bidirectionnels
CA-05 Si verificationStatus = PENDING, pendingReason + nextCheckAfterSeconds sont obligatoires; si DONE, ils sont absents Tests schema conditionnels
CA-06 RealStates = CheckResults a 4 valeurs, ASSUME satisfait Verification TLA+ passe
CA-07 Si proof_finalized = TRUE, aucun maillon PENDING interne et aucun null API Test invariant post-finalisation
CA-08 Non-regression verification existante Suite de tests existante verte
CA-09 La RFC PV-PROOF contient une section distinguant PENDING vs INDETERMINATE avec la semantique de cette spec Verification textuelle du document
CA-10 Bornes temporelles respectees (pendingResolutionTtl min/max, nextCheckAfterSeconds clamp [1,86400]) Tests de bornes + expiration
CA-11 Formats des donnees respectes (UUID, enums, cles linkResults) Validation d entree/sortie testee
CA-12 Deux appels concurrents au meme proofId retournent le meme resultat observable, et un DONE est retourne a l identique sur rappel Test de concurrence idempotente + test oracle DONE

8. Scenarios de test (Given / When / Then)

  • SCN-280-01 (pending nominal)
  • Given une preuve avec BLOCKCHAIN_ANCHOR non finalise
  • When le client appelle l endpoint de verification
  • Then verificationRequestId est present (UUID v4), verificationStatus = PENDING, linkResults.BLOCKCHAIN_ANCHOR = null, pendingReason present, nextCheckAfterSeconds dans [1,86400]

  • SCN-280-02 (done nominal)

  • Given une preuve avec tous maillons verifies
  • When le client appelle l endpoint
  • Then verificationStatus = DONE, aucun linkResults[*] = null, pendingReason absent, nextCheckAfterSeconds absent

  • SCN-280-03 (reclassification finality)

  • Given un batch en PENDING_FINALITY
  • When verification du maillon blockchain
  • Then resultat maillon interne = PENDING (jamais INDETERMINATE) et exposition API en null

  • SCN-280-04 (indeterminate durable)

  • Given blockchain inaccessible
  • When verification echoue sans perspective de resolution courte
  • Then resultat maillon = INDETERMINATE

  • SCN-280-05 (expiration SLA lazy)

  • Given un maillon est PENDING et son pendingResolutionTtl est depasse
  • When un appel API de verification est recu (declencheur lazy)
  • Then le maillon est reclassifie INDETERMINATE pendant le traitement de la reponse, sans worker background

  • SCN-280-06 (invariant finalisation)

  • Given proof_finalized = TRUE
  • When on lit les linkResults
  • Then aucune valeur PENDING ni null n est autorisee

  • SCN-280-07 (validation format proofId)

  • Given un proofId non UUID v4
  • When appel endpoint
  • Then 400 Bad Request avec code INVALID_PROOF_ID

  • SCN-280-08 (alignement TLA+)

  • Given CheckResults et RealStates
  • When la verification formelle est lancee
  • Then RealStates = CheckResults et ASSUME satisfait

  • SCN-280-09 (bornes pendingResolutionTtl)

  • Given pendingResolutionTtl configurable
  • When la configuration vaut min=1h, max=30j, puis valeurs hors bornes
  • Then les bornes contractuelles sont appliquees et testees (acceptation aux bornes, rejet/normalisation hors bornes selon mecanisme de config)

  • SCN-280-10 (concurrence idempotente)

  • Given deux appels concurrents au meme proofId
  • When les deux appels sont traites en parallele
  • Then les deux reponses sont identiques sur le resultat metier observable; aucune mutation due a l appel de verification

9. Hypotheses explicites

ID Hypothese Impact si faux
H-280-01 Le perimetre cible est ProbatioVault-backend (NestJS + TypeORM + PostgreSQL). Mauvaise stack en spec => ecart de conformite gate.
H-280-02 Les seules raisons pendingReason dans ce perimetre sont ANCHOR_BATCH_NOT_FINALIZED et TX_NOT_ENOUGH_CONFIRMATIONS. Si d autres raisons sont requises, extension de contrat API necessaire.
H-280-03 Pour un verificationRequestId, l etat job est monotone (DONE terminal). Si re-ouverture requise, nouveau modele d etat necessaire.
H-280-04 Les bornes SLA proposees (72h, [1h,30j], nextCheckAfterSeconds [1,86400]) sont acceptables metier. Si invalide, tests SLA et contrat polling a recalibrer.
H-280-05 Le schema de persistance peut accepter l ajout de valeur enum sans rupture irreversible. Si non, migration complexe et fenetre de maintenance requises.
H-280-06 L endpoint de verification est lecture seule et idempotent; la resolution effective des maillons est realisee par des processus externes. Si faux, un mecanisme explicite de locking/concurrency control devient necessaire.

10. Contraintes techniques et points a clarifier

10.1 Contraintes techniques (stack reelle)

  • Projet cible: ProbatioVault-backend.
  • Stack contractuelle: NestJS + TypeORM + PostgreSQL (interdiction de referencer une autre stack).
  • Artefacts impactes: code metier backend, contrats API, tests backend, fichiers TLA+ associes, norm.yaml.
  • Compatibilite backward: les consumers API doivent supporter la semantique PENDING (via null dans linkResults et verificationStatus).

10.2 Points a clarifier (donnees manquantes)

  1. Taxonomie definitive de pendingReason (liste exhaustive ou extensible) non fournie.
  2. Politique officielle de versionnement API pour l ajout de nouvelle valeur enum non fournie.
  3. Exigence explicite de comportement client legacy face a PENDING non fournie.
  4. Strategie de down migration acceptee en production (en cas de presence de donnees PENDING) non tranchee.

References

  • Epic parent : PD-217 — LEGAL & COMPLIANCE
  • JIRA : PD-280
  • Repos concernes : ProbatioVault-backend, ProbatioVault-doc
  • Documents associes : PV_Proof.tla, _AnchoredFacts_PROOF.tla, norm.yaml, RFC PV-PROOF, learnings PD-40, PD-86