Aller au contenu

PD-55 — Worker ancrage blockchain périodique

1. Objectif

Spécifier, de manière contractuelle, le comportement d'un mécanisme automatique et périodique d'ancrage blockchain des événements probatoires internes ProbatioVault, afin de garantir : - l'intégrité cryptographique des lots d'événements, - la traçabilité bidirectionnelle entre événements internes et transaction publique, - la continuité temporelle de couverture probatoire, - la vérifiabilité externe sans coopération active de ProbatioVault.

La présente spécification est normative : toute exigence formulée avec "DOIT" est obligatoire.


2. Périmètre / Hors périmètre

Inclus

  • Agrégation périodique des événements probatoires non encore ancrés.
  • Construction d'un lot d'ancrage avec racine cryptographique unique (Merkle root).
  • Publication de la racine sur blockchain publique via le contrat d'ancrage disponible.
  • Enregistrement de la référence de transaction publique (tx_id) et du lien aux événements couverts.
  • Gestion des états d'ancrage des événements et du lot (cycle de vie traçable).
  • Détection et traitement des erreurs d'ancrage (sans perte d'événement).
  • Auditabilité externe des ancrages (preuves vérifiables hors système).

Exclu

  • Interface utilisateur de visualisation des ancrages.
  • Multi-chain simultané.
  • Fallback automatique vers une autre blockchain.
  • Changement de design cryptographique du Merkle persistence existant (PD-237).
  • Toute règle juridique interprétative non traduisible en vérification technique automatisable (hors périmètre).

3. Définitions

  • Événement probatoire : enregistrement métier produisant une trace devant être intégrée à une preuve composite.
  • Événement éligible : événement probatoire validé, non ancré, appartenant à la fenêtre d'agrégation active.
  • Types d'événements probatoires éligibles (liste fermée V1) : DOCUMENT_SIGNED, DOCUMENT_CERTIFIED, AUDIT_LOG_ENTRY.
  • Fenêtre d'ancrage : intervalle temporel de collecte des événements à agréger pour un cycle donné.
  • Cycle d'ancrage : exécution périodique complète : sélection → agrégation → publication → rattachement.
  • Lot d'ancrage : ensemble immuable d'événements couverts par une même racine.
  • Ordre canonique des événements d'un lot : tri strict par (created_at ASC, event_id ASC) ; en cas d'égalité de created_at, comparaison lexicographique ASCII de event_id.
  • Format temporel created_at : timestamp ISO 8601 UTC avec précision milliseconde (ex: 2026-02-21T10:30:00.123Z). Tous les horodatages du système DOIVENT être normalisés en UTC avant comparaison.
  • Canonicalisation JSON : sérialisation canonique normative selon RFC 8785 (JSON Canonicalization Scheme) avant calcul des hash utilisés pour l'arbre de Merkle.
  • Merkle root : empreinte racine représentant l'ensemble ordonné des événements d'un lot.
  • tx_id : identifiant de transaction blockchain de publication de la racine.
  • Finalité : état où la transaction est considérée confirmée lorsque le seuil de finalité est atteint, défini comme >= 30 confirmations ou >= 5 minutes après publication avec statut transaction valide.
  • Append-only : aucune suppression ni modification destructive d'un événement ou d'un lien d'ancrage déjà persisté.
  • Traçabilité bidirectionnelle : capacité de retrouver, depuis un événement, son lot/racine/transaction, et depuis une transaction, la liste des événements couverts.

4. Invariants (non négociables)

ID Règle Justification
INV-55-01 Le contenu métier brut ne DOIT jamais être publié on-chain ; seules des empreintes cryptographiques DOIVENT l'être. Protection des données, confidentialité probatoire.
INV-55-02 Un événement ne DOIT être rattaché qu'à au plus un lot d'ancrage finalisé. Idempotence et absence de double ancrage involontaire.
INV-55-03 Tout événement ancré DOIT être rattachable à un lot, une racine, un tx_id, un horodatage d'ancrage et un état de finalité. Traçabilité complète et auditabilité.
INV-55-04 Un lot d'ancrage finalisé DOIT être immutable (append-only) : ni modification de périmètre, ni réécriture de racine, ni réaffectation de tx_id. Intégrité de preuve et non-répudiation.
INV-55-05 Le calcul de racine d'un lot DOIT être déterministe pour un même ensemble d'événements ordonné selon l'ordre canonique (INV-55-13) et canonicalisé selon RFC 8785 avant hash. Reproductibilité et vérification indépendante.
INV-55-06 Aucun événement éligible ne DOIT être perdu en cas d'échec partiel du cycle ; il DOIT rester ré-ancrable. Robustesse opérationnelle et continuité probatoire.
INV-55-07 Les fenêtres temporelles successives ne DOIVENT laisser aucun "trou" de couverture pour les événements éligibles. Continuité temporelle exigée.
INV-55-08 La preuve externe DOIT être vérifiable sans accès privilégié aux systèmes internes (à partir d'artefacts exportables et données publiques). Auditabilité externe.
INV-55-09 Toute entrée externe au worker (identifiants, statuts transaction, références de lot) DOIT être validée strictement avant usage. Réduction des surfaces d'injection/corruption (learning PD-44).
INV-55-10 Toute transition d'état d'un lot ou d'un événement DOIT être journalisée de façon horodatée et corrélable. Exigence d'audit et forensics.
INV-55-11 Si aucune donnée éligible n'existe sur une fenêtre, le cycle DOIT produire une trace explicite "no-op" auditée. Continuité d'exploitation et preuve d'exécution périodique.
INV-55-12 Une stratégie de continuité en cas d'indisponibilité prolongée de la chaîne cible DOIT être documentée et testée au niveau procédure (même sans fallback automatique). Évite dépendance exclusive non maîtrisée.
INV-55-13 L'ordre canonique d'un lot DOIT être strictement appliqué avant construction Merkle : tri (created_at ASC, event_id ASC) avec comparaison lexicographique ASCII sur event_id comme tie-breaker. Supprime l'ambiguïté d'ordonnancement et garantit l'unicité du résultat de racine.

5. Flux nominaux

  1. Le cycle démarre périodiquement selon la cadence cible (10 minutes) et la politique de tolérance temporelle configurée.
  2. Les événements éligibles non ancrés sont sélectionnés dans la fenêtre.
  3. Un lot d'ancrage est constitué avec identifiant unique et périmètre figé.
  4. La racine Merkle du lot est produite de manière déterministe.
  5. La racine est publiée sur la blockchain cible via le contrat d'ancrage.
  6. Le tx_id retourné est enregistré avec le lot.
  7. Après atteinte de la finalité, le lot passe à l'état finalisé.
  8. Les événements du lot passent à l'état ancré, avec lien explicite vers lot/racine/tx_id.
  9. Les journaux d'audit du cycle (horodatages, volumes, statuts) sont persistés.

5bis. Diagrammes

5bis.1 Diagramme d'états — Cycle de vie d'un lot d'ancrage

Le lot d'ancrage traverse les états suivants. Toute transition est journalisée (INV-55-10). Un lot finalisé est immutable (INV-55-04).

stateDiagram-v2
    [*] --> CREATED : Cycle démarre,\névénements éligibles sélectionnés

    CREATED --> MERKLE_COMPUTED : Racine Merkle calculée\n(déterministe, INV-55-05/INV-55-13)
    CREATED --> FAILED : ERR-55-02 Échec calcul racine

    MERKLE_COMPUTED --> TX_SUBMITTED : Racine publiée on-chain\n(empreinte seule, INV-55-01)
    MERKLE_COMPUTED --> FAILED : ERR-55-03 Rejet transaction

    TX_SUBMITTED --> PENDING_FINALITY : tx_id enregistré,\nattente confirmations
    TX_SUBMITTED --> FAILED : ERR-55-04 tx_id invalide/absent

    PENDING_FINALITY --> FINALIZED : Finalité atteinte\n(≥30 confirmations OU ≥5min)
    PENDING_FINALITY --> ESCALATED : ERR-55-05 Timeout 1h\n→ alerte WARNING
    PENDING_FINALITY --> FAILED : ERR-55-03 Rejet détecté\naprès soumission

    ESCALATED --> FINALIZED : Finalité atteinte\naprès escalade
    ESCALATED --> FAILED : Timeout 2h → CRITICAL

    FINALIZED --> [*] : Lot immutable (INV-55-04),\névénements marqués ancrés (INV-55-02)

    FAILED --> [*] : Événements restent\nrééligibles (INV-55-06)

5bis.2 Diagramme d'états — Cycle de vie d'un événement probatoire

Un événement ne peut être rattaché qu'à un seul lot finalisé (INV-55-02). Toute tentative de double rattachement est rejetée (ERR-55-06).

stateDiagram-v2
    [*] --> ELIGIBLE : Événement validé,\nnon encore ancré

    ELIGIBLE --> ASSIGNED : Rattaché à un lot\n(périmètre figé)
    ELIGIBLE --> ELIGIBLE : Cycle no-op (INV-55-11),\nreste éligible

    ASSIGNED --> ANCHORED : Lot finalisé,\nlien lot/racine/tx_id (INV-55-03)
    ASSIGNED --> ELIGIBLE : Lot échoué (INV-55-06),\nretour éligible

    ANCHORED --> [*] : Traçabilité complète\n(INV-55-03, INV-55-08)

    note right of ANCHORED
        Immutable — append-only (INV-55-04)
        Double rattachement interdit (INV-55-02)
    end note

5bis.3 Diagramme de séquence — Cycle d'ancrage nominal

Ce diagramme illustre les interactions entre services lors d'un cycle complet réussi. La canonicalisation RFC 8785 (INV-55-05) et l'ordre canonique (INV-55-13) sont appliqués avant le calcul Merkle.

sequenceDiagram
    autonumber
    participant Scheduler as Scheduler<br/>(BullMQ)
    participant Worker as Anchor Worker
    participant DB as PostgreSQL<br/>(append-only)
    participant Merkle as Merkle Service<br/>(PD-237)
    participant Contract as Smart Contract<br/>(PD-53)
    participant Chain as Blockchain L2<br/>(PD-52)

    Scheduler->>Worker: Déclenchement cycle (cadence 10min ±2min)
    Worker->>DB: SELECT événements éligibles dans fenêtre<br/>(INV-55-07 continuité temporelle)

    alt Aucun événement éligible
        Worker->>DB: INSERT trace no-op auditée (INV-55-11)
    else Événements présents
        Worker->>DB: INSERT lot CREATED + rattachement événements<br/>(périmètre figé, max 10K par lot)
        Worker->>Worker: Tri canonique (created_at ASC, event_id ASC)<br/>(INV-55-13)
        Worker->>Merkle: Calcul racine Merkle<br/>(RFC 8785 + SHA-256, INV-55-05)
        Merkle-->>Worker: merkle_root (hex64)
        Worker->>DB: UPDATE lot → MERKLE_COMPUTED

        Worker->>Contract: anchorRoot(merkle_root)<br/>(empreinte seule, INV-55-01)
        Contract->>Chain: Transaction on-chain
        Chain-->>Contract: tx_id (0x + 64 hex)
        Contract-->>Worker: tx_id + block_number
        Worker->>DB: UPDATE lot → TX_SUBMITTED (tx_id, chain_id)

        loop Attente finalité (≥30 confirmations OU ≥5min)
            Worker->>Chain: Vérification confirmations
            Chain-->>Worker: Nombre de confirmations
        end

        Worker->>DB: UPDATE lot → FINALIZED<br/>(INV-55-04 immutable)
        Worker->>DB: UPDATE événements → ANCHORED<br/>(lien lot/racine/tx_id, INV-55-03)
        Worker->>DB: INSERT journal audit cycle<br/>(horodatages, volumes, statuts, INV-55-10)
    end

5bis.4 Diagramme de séquence — Vérification externe

La preuve est vérifiable sans accès aux systèmes internes (INV-55-08), à partir de l'artefact JSON exporté et des données publiques blockchain.

sequenceDiagram
    autonumber
    participant Auditor as Auditeur externe
    participant Artifact as Artefact JSON<br/>(§8.1)
    participant Chain as Blockchain publique
    participant Verify as Recalcul local

    Auditor->>Artifact: Lecture artefact exporté<br/>(lot_id, merkle_root, tx_id, events[])
    Auditor->>Chain: GET transaction par tx_id<br/>(données publiques)
    Chain-->>Auditor: Payload on-chain (merkle_root, block, status)

    Auditor->>Auditor: Comparer merkle_root artefact vs on-chain<br/>(doivent être identiques)

    loop Pour chaque événement
        Auditor->>Verify: Recalcul Merkle proof<br/>(event_hash + merkle_proof → racine)
        Verify-->>Auditor: Racine recalculée
        Auditor->>Auditor: Vérifier racine recalculée == merkle_root<br/>(INV-55-05, INV-55-13)
    end

    Note over Auditor: Vérification complète sans accès<br/>aux systèmes internes (INV-55-08)

6. Cas d'erreur

  • ERR-55-01 — Aucun événement éligible : cycle en no-op ; journal d'audit obligatoire ; aucun échec.
  • ERR-55-02 — Échec de calcul racine : lot non finalisé ; aucun événement marqué ancré ; ré-exécution possible sans perte.
  • ERR-55-03 — Rejet transaction on-chain : lot en échec ; événements conservés non ancrés ; cause traçable.
  • ERR-55-04 — tx_id invalide ou absent : lot non finalisable ; contrôle d'intégrité échoue ; alerte d'audit.
  • ERR-55-05 — Finalité non atteinte avant timeout contractuel : lot en état intermédiaire explicite ; pas de marquage ancré final ; durée maximale de l'état intermédiaire = 1 heure avant escalade opérationnelle obligatoire. Mécanisme d'escalade : le compteur démarre à la soumission on-chain (tx_submitted_at) ; à T+1h sans finalité, alerte automatique PagerDuty niveau WARNING avec contexte (lot_id, tx_id, chain_id, block_number, durée attente) ; si T+2h sans finalité, escalade CRITICAL avec notification équipe SRE.
  • ERR-55-06 — Tentative de double rattachement d'un événement : rejet explicite ; invariant INV-55-02 prioritaire.
  • ERR-55-07 — Indisponibilité blockchain prolongée : accumulation contrôlée des événements éligibles ; application de la stratégie de continuité documentée ; aucune suppression.
  • ERR-55-08 — Écart de fenêtre temporelle (trou détecté) : alerte critique et journal d'anomalie ; lot concerné non validé tant que non résolu.
  • ERR-55-09 — Échec de persistance d'un lien événement↔lot : transaction logique du cycle considérée non conforme ; pas de finalisation partielle silencieuse.
  • ERR-55-10 — Entrée non conforme (format/range/état) : rejet avant traitement ; journal d'audit de validation.

7. Critères d'acceptation (testables)

ID Critère Observable
CA-55-01 Un cycle périodique est déclenché selon la cadence définie avec une tolérance explicite de ±2 minutes (cycle entre 8 et 12 minutes). Horodatages successifs de cycles dans les logs d'audit.
CA-55-02 Un événement éligible apparaît dans exactement un lot finalisé. Requête de traçabilité : cardinalité = 1.
CA-55-03 Aucun contenu métier en clair n'est publié on-chain. Inspection payload transaction : empreinte seule.
CA-55-04 Pour un lot finalisé, les champs lot/racine/tx_id/horodatage/finalité sont tous présents. Contrôle de complétude des enregistrements.
CA-55-05 Deux recalculs de racine sur le même lot ordonné produisent la même valeur. Test de déterminisme (égalité stricte).
CA-55-06 En cas d'échec publication, les événements restent non ancrés et rééligibles. État post-erreur + réexécution réussie.
CA-55-07 En absence d'événement éligible, un enregistrement no-op est produit. Log d'audit no-op horodaté.
CA-55-08 Depuis un tx_id finalisé, on peut retrouver tous les événements couverts. Requête inverse transaction → événements.
CA-55-09 Toute tentative de double ancrage d'un même événement est rejetée. Erreur explicite + absence de second rattachement.
CA-55-10 Aucune fenêtre éligible n'est laissée non couverte sans alerte. Rapport de continuité temporelle sans trou silencieux.
CA-55-11 Les validations d'entrée rejettent toute valeur hors format attendu (tx_id, états, IDs). Cas de test négatifs avec rejet explicite.
CA-55-12 La stratégie de continuité chaîne est documentée et exécutable en test de procédure. Preuve documentaire + résultat d'exercice.

8. Modèle de données et formats

8.1 Format contractuel de l'artefact de preuve externe (JSON)

L'artefact exportable DOIT être sérialisé en UTF-8 et respecter le schéma minimal suivant :

{
  "version": "1.0",
  "lot_id": "uuid",
  "merkle_root": "hex64",
  "tx_id": "0x...",
  "chain_id": 137,
  "block_number": 12345678,
  "events": [
    {
      "event_id": "uuid",
      "event_hash": "hex64",
      "merkle_index": 0,
      "merkle_proof": ["hex64", "hex64"]
    }
  ]
}

8.2 Contraintes de format

  • version : chaîne, valeur normative initiale 1.0.
  • lot_id : UUID v4 canonical (8-4-4-4-12, hex lowercase).
  • merkle_root : hex64 lowercase sans préfixe 0x (64 caractères hex).
  • tx_id : hash de transaction EVM, format 0x + 64 hex lowercase (66 caractères).
  • chain_id : entier positif (EIP-155).
  • block_number : entier positif (>= 0).
  • events : tableau non vide pour un lot non no-op.
  • event_id : UUID canonical.
  • event_hash : hex64 lowercase sans préfixe 0x.
  • merkle_index : entier >= 0, position de la feuille dans l'ordre canonique du lot.
  • merkle_proof : tableau ordonné de siblings (hex64) du niveau feuille vers racine ; chaque élément représente le hash sibling au niveau correspondant.

8.3 Règles de vérification externe

La vérification externe DOIT pouvoir être exécutée avec : 1. l'artefact JSON exporté, 2. les données publiques blockchain (tx_id, chain_id, block_number), 3. le recalcul Merkle déterministe conforme à l'ordre canonique (INV-55-13) et à la canonicalisation RFC 8785 (INV-55-05).


9. Contraintes techniques

  • Taille maximale d'un lot : 10 000 événements.
  • Backlog maximal acceptable : 100 000 événements non ancrés.
  • SLA de résorption backlog : retour sous backlog nominal en 24 heures maximum après restauration de la chaîne cible.

9.1 Conduite au dépassement des seuils

Seuil Condition Action
Lot > 10 000 événements Fenêtre contient plus de 10K événements éligibles Découpage automatique en sous-lots de 10K max ; chaque sous-lot est ancré séparément avec traçabilité du lot parent.
Backlog > 100 000 événements Accumulation dépassant le seuil acceptable Alerte critique (PagerDuty niveau CRITICAL) ; passage en mode dégradé : priorisation des événements les plus anciens ; blocage de l'ingestion de nouveaux événements si backlog > 150K (circuit breaker).
SLA 24h dépassé Backlog non résorbé après 24h post-restauration chaîne Escalade opérationnelle obligatoire ; rapport d'incident ; intervention manuelle requise pour purge ou rattrapage accéléré.

10. Scénarios de test (Given / When / Then)

  1. Nominal avec événements
  2. Given un ensemble d'événements éligibles non ancrés dans la fenêtre
  3. When le cycle périodique s'exécute et la transaction atteint la finalité
  4. Then un lot finalisé unique est créé, chaque événement est marqué ancré, et tx_id est traçable

  5. Nominal sans événements (no-op)

  6. Given aucune donnée éligible dans la fenêtre
  7. When le cycle s'exécute
  8. Then aucun lot finalisé n'est créé et une trace no-op est journalisée

  9. Idempotence événement

  10. Given un événement déjà rattaché à un lot finalisé
  11. When un nouveau cycle tente de le reprendre
  12. Then le rattachement est refusé et aucun second ancrage n'est produit

  13. Échec publication blockchain

  14. Given un lot constitué et une publication rejetée par la chaîne
  15. When le cycle termine en erreur
  16. Then le lot n'est pas finalisé, les événements restent rééligibles, et l'erreur est auditée

  17. Finalité retardée

  18. Given une transaction soumise avec tx_id valide mais finalité non atteinte
  19. When le délai maximal de confirmation est dépassé
  20. Then le lot reste en état intermédiaire explicite sans marquage final des événements

  21. Déterminisme racine

  22. Given un lot figé et ordonné
  23. When la racine est recalculée deux fois
  24. Then la valeur de racine est strictement identique

  25. Traçabilité inverse

  26. Given un tx_id d'un lot finalisé
  27. When une vérification d'audit externe est demandée
  28. Then la liste des événements couverts est retrouvable et cohérente avec la racine

  29. Validation stricte des entrées

  30. Given des entrées malformées (ID, statut, tx_id)
  31. When elles sont soumises au worker
  32. Then elles sont rejetées avant traitement et consignées comme invalides

  33. Continuité temporelle

  34. Given plusieurs fenêtres successives avec flux continu d'événements
  35. When les cycles s'enchaînent sur une période prolongée
  36. Then aucun trou de couverture n'est détecté sans alerte explicite

  37. Indisponibilité chaîne prolongée

    • Given la chaîne cible indisponible sur une durée étendue
    • When les cycles continuent de s'exécuter
    • Then les événements s'accumulent sans perte et la procédure de continuité est déclenchée/auditée

11. Hypothèses explicites

ID Hypothèse Impact si faux
HYP-55-01 Les dépendances PD-52/PD-53/PD-237 sont opérationnelles et conformes à leurs contrats. Blocage fonctionnel : ancrage impossible ou non fiable.
HYP-55-02 Une horloge de référence cohérente est disponible pour dater fenêtres et cycles. Risque de trous/superpositions temporelles.
HYP-55-03 Le statut de finalité blockchain est observable de manière fiable. Impossibilité de conclure l'état final des lots.
HYP-55-04 Le schéma de persistance supporte les liens événement↔lot↔transaction en append-only. Traçabilité incomplète ou mutable.
HYP-55-06 La politique de rétention des journaux d'audit couvre les besoins de preuve externe. Auditabilité affaiblie.

12. Points à clarifier

  1. Chaîne cible normative de la V1 : réseau exact (ex. Polygon Amoy/Mainnet), niveau d'environnement, règles de bascule manuelle.
  2. Politique de lot vide : no-op uniquement ou ancrage "heartbeat" autorisé/interdit.
  3. Stratégie de continuité non-automatique : déclencheurs, rôles, délais, critères de reprise.
  4. Seuils d'alerte sécurité/ops : délais, taux d'échec, criticité.

Références

  • Epic : PD-187 (BLOCKCHAIN)
  • JIRA : PD-55
  • Repos concernés : ProbatioVault-backend
  • Documents associés :
  • PD-55 — Expression de besoin
  • PD-52 — Ethereum L2 setup (DONE)
  • PD-53 — Smart contract Merkle anchor (DONE)
  • PD-237 — Merkle persistence (DONE)
  • Learnings :
  • PD-44 (validation stricte des entrées / sécurité)
  • PD-40 (append-only, invariants numérotés, canonicalisation)
  • BATCH-RETRO (WORM/Object Lock : irréversibilité et traçabilité)