PD-55 — Plan d'implémentation
| Champ | Valeur |
| Story ID | PD-55 |
| Titre | Worker ancrage blockchain périodique |
| Complexité | High |
| Fichiers estimés | 18-22 |
| Dépendances | PD-52 ✅, PD-53 ✅, PD-237 ✅, PD-21 ✅ |
Phase 0 — Go/No-Go
Vérification des dépendances
| Story | Statut | Composant vérifié | Action |
| PD-52 | ✅ DONE | TransactionService.sendAnchorTransaction() | Utiliser directement |
| PD-52 | ✅ DONE | ConfirmationTracker.track() | Utiliser pour finalité |
| PD-53 | ✅ DONE | Smart contract Merkle anchor | Déployé sur testnet |
| PD-237 | ✅ DONE | MerkleTreeService.persistTree() | Utiliser pour persistance |
| PD-21 | ✅ DONE | BaseProcessor (BullMQ) | Étendre pour le worker |
Vérification des prérequis
| Prérequis | Vérifié | Notes |
| Tables requises | ⚠️ | Créer anchor_batches, anchor_batch_events |
| Services dépendants | ✅ | MerkleTreeService, TransactionService, JobsService |
| Configuration BullMQ | ✅ | Module jobs existant avec Redis |
| Entités événements | ⚠️ | Table audit_log existante, à lier |
Go/No-Go : ✅ GO — Toutes les dépendances sont disponibles. Créer les nouvelles tables.
Architecture
Diagramme de flux
┌─────────────────────────────────────────────────────────────────────────┐
│ BullMQ Scheduler (10 min) │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ BlockchainAnchorProcessor │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────────────────┐ │
│ │ 1. Collect │→ │ 2. Build │→ │ 3. Anchor │ │
│ │ Events │ │ Batch │ │ On-chain │ │
│ └───────────────┘ └───────────────┘ └───────────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────────────────┐ │
│ │ ProofEvent │ │ MerkleTree │ │ TransactionService │ │
│ │ Service │ │ Service │ │ + ConfirmationTracker │ │
│ └───────────────┘ └───────────────┘ └───────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 4. Finalize & Link │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────────────────┐ │
│ │ AnchorBatch │→ │ Event Links │→ │ Audit Log │ │
│ │ Service │ │ (batch↔event) │ │ Journalisation │ │
│ └───────────────┘ └───────────────┘ └───────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
États du lot (AnchorBatch)
PENDING → BUILDING → SUBMITTED → CONFIRMED → FINALIZED
↘ ↘
FAILED PENDING_FINALITY
Contraintes techniques
Dépendances inter-PD
| Story | Statut | Nature de la dépendance |
| PD-52 | ✅ DONE | TransactionService, ConfirmationTracker |
| PD-53 | ✅ DONE | Smart contract ABI (si appel direct) |
| PD-237 | ✅ DONE | MerkleTreeService pour persistance arbres |
| PD-21 | ✅ DONE | BaseProcessor, JobsService |
Framework de test
- Runner : Jest (existant dans le projet)
- Tests d'intégration : Mocks Redis + DB in-memory (TypeORM SQLite)
- Variables CI :
DATABASE_URL, REDIS_URL, CI=true
Stratégie de test pour triggers DB (ECT-01) :
Le trigger d'immutabilité (FINALIZED) n'est pas testable avec SQLite in-memory (support limité des triggers). Stratégie retenue :
- Tests unitaires : Guard applicatif dans
AnchorBatchService.update() vérifiant batch.status !== 'FINALIZED' avant toute modification - Tests d'intégration CI : Utiliser
@testcontainers/postgresql pour exécuter T55-16 avec une vraie instance PostgreSQL - Double protection : Le trigger DB est la ligne de défense finale, le guard applicatif est la protection primaire
Configuration CI pour T55-16 :
services:
postgres:
image: postgres:15
env: { POSTGRES_DB: test, POSTGRES_USER: test, POSTGRES_PASSWORD: test }
Compatibilité ESM/CJS
- Aucune dépendance ESM-only identifiée
- Projet en CommonJS avec configuration Jest standard
Configuration par défaut (seuils spec)
| Paramètre | Valeur par défaut | Source |
ANCHOR_CYCLE_INTERVAL_MS | 600000 (10 min) | CA-55-01 |
ANCHOR_CYCLE_TOLERANCE_MS | 120000 (±2 min) | CA-55-01 |
FINALITY_CONFIRMATIONS | 30 | Définition "Finalité" §3 |
FINALITY_TIMEOUT_MS | 300000 (5 min) | Définition "Finalité" §3 |
ESCALADE_WARNING_MS | 3600000 (1h) | ERR-55-05 |
ESCALADE_CRITICAL_MS | 7200000 (2h) | ERR-55-05 |
BATCH_MAX_EVENTS | 10000 | §9 |
BACKLOG_MAX_EVENTS | 100000 | §9.1 |
CIRCUIT_BREAKER_THRESHOLD | 150000 | §9.1 |
Ces valeurs sont injectées via ConfigService (module @nestjs/config).
Code Contracts
CC-55-01 : Entité AnchorBatch
/**
* Lot d'ancrage blockchain.
*
* @invariant INV-55-02: Un événement ne peut être dans qu'un seul lot finalisé
* @invariant INV-55-04: Un lot finalisé est immutable
*
* CONTRAINTE D'IMMUTABILITÉ (ECT-01) :
* Une fois en status FINALIZED, toute tentative d'UPDATE sur les colonnes
* status, merkle_root, tx_id, finalized_at DOIT être rejetée.
* Implémentation : trigger DB ou contrainte CHECK sur status='FINALIZED'.
*
* NOTE : Cette immutabilité couvre également les relations anchor_batch_events :
* - Aucun DELETE/INSERT sur anchor_batch_events pour un batch FINALIZED
* - Le périmètre événementiel du lot est figé à la finalisation
*/
interface AnchorBatchContract {
// Identifiant unique (UUID v4)
batchId: string;
// Racine Merkle (hex64 lowercase, sans 0x)
merkleRoot: string;
// Référence vers MerkleTree (PD-237)
treeId: string | null;
// Transaction blockchain
txId: string | null;
chainId: number;
blockNumber: number | null;
// États
status: 'PENDING' | 'BUILDING' | 'SUBMITTED' | 'PENDING_FINALITY' | 'FINALIZED' | 'FAILED';
// Timestamps (ISO 8601 UTC ms)
windowStart: Date;
windowEnd: Date;
createdAt: Date;
submittedAt: Date | null;
finalizedAt: Date | null;
// Nombre d'événements
eventCount: number;
// Erreur si FAILED
errorReason: string | null;
}
CC-55-02 : Entité AnchorBatchEvent (relation)
/**
* Relation lot ↔ événement probatoire.
*
* @invariant INV-55-02: Contrainte unique sur (eventId, batchStatus='FINALIZED')
* @invariant INV-55-03: Traçabilité complète événement → lot → tx
*/
interface AnchorBatchEventContract {
// Clé primaire composite
batchId: string;
eventId: string;
// Type d'événement (liste fermée V1)
eventType: 'DOCUMENT_SIGNED' | 'DOCUMENT_CERTIFIED' | 'AUDIT_LOG_ENTRY';
// Hash de l'événement (hex64)
eventHash: string;
// Position dans le lot (ordre canonique)
leafIndex: number;
// Timestamp de l'événement (ISO 8601 UTC ms)
eventCreatedAt: Date;
}
CC-55-03 : Service AnchorBatchService
/**
* Gestion des lots d'ancrage.
*
* @invariant INV-55-06: Aucun événement perdu en cas d'échec
* @invariant INV-55-07: Continuité temporelle des fenêtres
* @invariant INV-55-10: Journalisation de chaque transition
*
* JOURNALISATION OBLIGATOIRE (ECT-05) :
* Chaque méthode modifiant l'état d'un lot DOIT appeler AuditService.log()
* avec un AuditEntry structuré :
* - action: 'ANCHOR_BATCH_CREATED' | 'ANCHOR_BATCH_FINALIZED' | 'ANCHOR_BATCH_FAILED'
* - entityType: 'anchor_batch'
* - entityId: batchId
* - payload: { status, eventCount, merkleRoot?, txId?, errorReason? }
*
* Injection : constructor(private readonly auditService: AuditService)
*/
interface AnchorBatchServiceContract {
/**
* Crée un nouveau lot avec les événements éligibles.
* @returns null si aucun événement (no-op tracé)
*/
createBatch(windowStart: Date, windowEnd: Date): Promise<AnchorBatch | null>;
/**
* Finalise un lot après confirmation blockchain.
* @throws si lot non en état PENDING_FINALITY
*/
finalizeBatch(batchId: string, txResult: TransactionResult): Promise<void>;
/**
* Marque un lot en échec (réversible pour retry).
*
* ATOMICITÉ (ECT-02) :
* Cette méthode DOIT s'exécuter dans une transaction DB unique :
* 1. UPDATE anchor_batches SET status='FAILED', error_reason=reason
* 2. DELETE FROM anchor_batch_events WHERE batch_id=batchId
* Les deux opérations sont atomiques : si l'une échoue, rollback complet.
* Les événements du lot FAILED retournent au pool des événements éligibles.
*/
failBatch(batchId: string, reason: string): Promise<void>;
/**
* Récupère les événements éligibles d'un lot.
*/
getBatchEvents(batchId: string): Promise<AnchorBatchEvent[]>;
/**
* Exporte l'artefact de preuve externe (JSON §8).
*/
exportProofArtifact(batchId: string): Promise<ProofArtifactDto>;
}
CC-55-04 : Service ProofEventService
/**
* Collecte des événements probatoires éligibles.
*
* @invariant INV-55-09: Validation stricte des entrées
* @invariant INV-55-13: Ordre canonique (created_at ASC, event_id ASC)
*
* JOURNALISATION OBLIGATOIRE (ECT-05) :
* La méthode markEventsAnchored() DOIT appeler AuditService.log()
* avec un AuditEntry structuré :
* - action: 'EVENTS_ANCHORED'
* - entityType: 'proof_event'
* - entityId: batchId
* - payload: { eventIds: string[], count: number }
*
* Injection : constructor(private readonly auditService: AuditService)
*/
interface ProofEventServiceContract {
/**
* Collecte les événements éligibles non ancrés dans la fenêtre.
* Tri canonique appliqué.
*
* @param windowStart Début de fenêtre (inclusif)
* @param windowEnd Fin de fenêtre (exclusif)
* @param limit Max 10000 événements (découpage si plus)
*/
collectEligibleEvents(
windowStart: Date,
windowEnd: Date,
limit?: number,
): Promise<ProofEventDto[]>;
/**
* Vérifie qu'un événement n'est pas déjà ancré.
*/
isEventAnchored(eventId: string): Promise<boolean>;
/**
* Marque les événements comme ancrés (lien vers batchId).
*/
markEventsAnchored(eventIds: string[], batchId: string): Promise<void>;
}
CC-55-05 : Processor BlockchainAnchorProcessor
/**
* Worker BullMQ d'ancrage périodique.
*
* @invariant INV-55-01: Seuls les hashes sont ancrés
* @invariant INV-55-05: Calcul déterministe (RFC 8785)
* @invariant INV-55-10: Journalisation de chaque transition
* @invariant INV-55-11: Trace no-op si aucun événement
*/
interface BlockchainAnchorProcessorContract {
/**
* Point d'entrée du job.
* Orchestration complète : collect → build → anchor → finalize.
*/
process(job: Job<AnchorJobData>): Promise<JobResult>;
/**
* Clé d'idempotence = windowStart ISO.
*/
getIdempotencyKey(job: Job<AnchorJobData>): string;
}
CC-55-06 : DTO ProofArtifactDto (§8 spec)
/**
* Artefact de preuve externe exportable.
* Format JSON UTF-8 conforme §8.1 de la spécification.
*
* @invariant INV-55-08: Vérifiable sans accès privilégié
*/
interface ProofArtifactDto {
version: '1.0';
lot_id: string; // UUID canonical
merkle_root: string; // hex64 lowercase sans 0x
tx_id: string; // 0x + 64 hex lowercase (66 chars)
chain_id: number; // EIP-155
block_number: number;
events: Array<{
event_id: string; // UUID canonical
event_hash: string; // hex64 lowercase
merkle_index: number; // position dans l'ordre canonique
merkle_proof: string[]; // siblings hex64
}>;
}
Diagrammes Mermaid
Graphe de dépendances des composants
graph TD
subgraph "BullMQ Scheduler"
CRON["⏱ Cron 10 min"]
end
subgraph "Processor"
BAP["BlockchainAnchorProcessor<br/>(CC-55-05)"]
end
subgraph "Services métier"
PES["ProofEventService<br/>(CC-55-04)"]
ABS["AnchorBatchService<br/>(CC-55-03)"]
AAS["AnchorAlertService<br/>(Phase 4 - tâche 14)"]
end
subgraph "Dépendances PD existantes"
MTS["MerkleTreeService<br/>(PD-237)"]
TXS["TransactionService<br/>(PD-52)"]
CFT["ConfirmationTracker<br/>(PD-52)"]
SC["Smart Contract<br/>(PD-53)"]
BULL["BaseProcessor / JobsService<br/>(PD-21)"]
end
subgraph "Entités"
AB["AnchorBatch<br/>(CC-55-01)"]
ABE["AnchorBatchEvent<br/>(CC-55-02)"]
PAD["ProofArtifactDto<br/>(CC-55-06)"]
end
subgraph "Infrastructure"
REDIS["Redis (BullMQ)"]
PG["PostgreSQL"]
AUDIT["AuditService"]
end
CRON -->|déclenche job| BAP
BAP -->|collecte événements| PES
BAP -->|crée/finalise lots| ABS
BAP -->|construit arbre| MTS
BAP -->|envoie tx| TXS
BAP -->|suit finalité| CFT
TXS -->|appelle| SC
BAP -->|étend| BULL
ABS -->|persiste| AB
ABS -->|persiste| ABE
ABS -->|exporte| PAD
ABS -->|journalise| AUDIT
PES -->|journalise| AUDIT
AAS -->|détecte gaps| ABS
AAS -->|alerte| AUDIT
CRON -.->|queue| REDIS
AB -.->|stocké| PG
ABE -.->|stocké| PG
Diagramme de séquence — Cycle d'ancrage nominal
sequenceDiagram
participant CRON as BullMQ Scheduler
participant BAP as BlockchainAnchorProcessor
participant PES as ProofEventService
participant ABS as AnchorBatchService
participant MTS as MerkleTreeService (PD-237)
participant TXS as TransactionService (PD-52)
participant CFT as ConfirmationTracker (PD-52)
participant DB as PostgreSQL
participant AUDIT as AuditService
CRON->>BAP: process(job: AnchorJobData)
activate BAP
Note over BAP: 1. Collect Events
BAP->>PES: collectEligibleEvents(windowStart, windowEnd)
PES->>DB: SELECT events WHERE NOT anchored AND created_at IN window
DB-->>PES: ProofEventDto[]
PES-->>BAP: events (triés canoniquement INV-55-13)
alt Aucun événement
BAP->>AUDIT: log(action: 'ANCHOR_NOOP', INV-55-11)
BAP-->>CRON: JobResult { status: 'NOOP' }
end
Note over BAP: 2. Build Batch
BAP->>ABS: createBatch(windowStart, windowEnd)
ABS->>DB: INSERT anchor_batches (PENDING)
ABS->>DB: INSERT anchor_batch_events
ABS->>AUDIT: log(action: 'ANCHOR_BATCH_CREATED')
ABS-->>BAP: AnchorBatch
Note over BAP: 3. Construct Merkle Tree
BAP->>MTS: persistTree(eventHashes)
MTS-->>BAP: { treeId, merkleRoot }
Note over BAP: 4. Anchor On-chain
BAP->>ABS: update batch (SUBMITTED)
BAP->>TXS: sendAnchorTransaction(merkleRoot)
TXS-->>BAP: txId
Note over BAP: 5. Wait for Finality
BAP->>CFT: track(txId, { confirmations: 30, timeout: 5min })
CFT-->>BAP: TransactionResult { confirmed: true, blockNumber }
Note over BAP: 6. Finalize
BAP->>ABS: finalizeBatch(batchId, txResult)
ABS->>DB: UPDATE anchor_batches SET status='FINALIZED'
ABS->>AUDIT: log(action: 'ANCHOR_BATCH_FINALIZED')
ABS-->>BAP: void
BAP->>PES: markEventsAnchored(eventIds, batchId)
PES->>AUDIT: log(action: 'EVENTS_ANCHORED')
BAP-->>CRON: JobResult { status: 'FINALIZED', batchId }
deactivate BAP
Diagramme de séquence — Échec et recovery
sequenceDiagram
participant BAP as BlockchainAnchorProcessor
participant ABS as AnchorBatchService
participant TXS as TransactionService (PD-52)
participant CFT as ConfirmationTracker (PD-52)
participant DB as PostgreSQL
participant AUDIT as AuditService
participant AAS as AnchorAlertService
Note over BAP: Transaction échoue ou timeout finalité
alt Erreur TransactionService
TXS--xBAP: Error (gas, nonce, network)
BAP->>ABS: failBatch(batchId, reason)
ABS->>DB: BEGIN TRANSACTION
ABS->>DB: UPDATE anchor_batches SET status='FAILED'
ABS->>DB: DELETE anchor_batch_events WHERE batch_id (ECT-02)
ABS->>DB: COMMIT
ABS->>AUDIT: log(action: 'ANCHOR_BATCH_FAILED')
Note over ABS: Événements retournent au pool éligible (INV-55-06)
end
alt Timeout ConfirmationTracker
CFT--xBAP: TimeoutError (>5 min)
BAP->>ABS: update batch (PENDING_FINALITY)
BAP->>AAS: escalade(batchId, elapsedTime)
alt T+1h sans finalité
AAS->>AUDIT: log(action: 'ESCALADE_WARNING', ERR-55-05)
end
alt T+2h sans finalité
AAS->>AUDIT: log(action: 'ESCALADE_CRITICAL', ERR-55-05)
end
end
alt Gap temporel détecté (INV-55-07)
AAS->>ABS: detectWindowGaps(since)
ABS->>DB: SELECT lots FINALIZED ORDER BY window_start
DB-->>ABS: gaps[]
AAS->>AUDIT: log(action: 'BATCH_BLOCKED_GAP', ERR-55-08)
Note over AAS: Lot bloqué jusqu'à résolution manuelle
end
Mapping INV/CA → Tâches
| Invariant/CA | Tâche couvrant | Test couvrant |
| INV-55-01 | CC-55-05 (hash only dans payload) | T55-15 |
| INV-55-02 | CC-55-02 (contrainte unique) | T55-08 |
| INV-55-03 | CC-55-02, CC-55-03 (traçabilité) | T55-02, T55-10 |
| INV-55-04 | CC-55-01 (status FINALIZED immutable) | T55-16 |
| INV-55-05 | CC-55-05 + MerkleTreeService (RFC 8785) | T55-03 |
| INV-55-06 | CC-55-03 (failBatch réversible) | T55-05, T55-13, T55-18 |
| INV-55-07 | CC-55-04 (collecte continue) | T55-09 |
| INV-55-08 | CC-55-06 (artefact exportable) | T55-10 |
| INV-55-09 | CC-55-04 (validation entrées) | T55-11 |
| INV-55-10 | CC-55-05 (journalisation) | T55-17 |
| INV-55-11 | CC-55-05 (trace no-op) | T55-04 |
| INV-55-12 | Runbook ops (hors code) | T55-14 |
| INV-55-13 | CC-55-04 (ordre canonique) | T55-03 |
| CA-55-01 | Config BullMQ (cron 10 min ±2) | T55-01 |
| CA-55-02 | CC-55-02 (cardinalité 1) | T55-02 |
| CA-55-03 | CC-55-05 (hash only) | T55-15 |
| CA-55-04 | CC-55-01 (champs complets) | T55-02 |
| CA-55-05 | MerkleTreeService (déterminisme) | T55-03 |
| CA-55-06 | CC-55-03 (failBatch) | T55-05 |
| CA-55-07 | CC-55-05 (trace no-op) | T55-04 |
| CA-55-08 | CC-55-06 (export proof) | T55-10 |
| CA-55-09 | CC-55-02 (rejet double) | T55-08 |
| CA-55-10 | CC-55-04 + alerting | T55-09 |
| CA-55-11 | CC-55-04 (validation) | T55-11 |
| CA-55-12 | Runbook ops | T55-14 |
Tâches d'implémentation
Phase 1 : Modèle de données (CC-55-01, CC-55-02)
| # | Tâche | Agent | Fichiers | Dépend de |
| 1 | Créer entité AnchorBatch | agent-developer | entities/anchor-batch.entity.ts | - |
| 2 | Créer entité AnchorBatchEvent | agent-developer | entities/anchor-batch-event.entity.ts | 1 |
| 3 | Créer migration | agent-developer | migrations/xxx-anchor-batch.ts | 1, 2 |
| 4 | Tests unitaires entités | agent-qa-unit-integration | entities/*.spec.ts | 1, 2 |
Phase 2 : Services métier (CC-55-03, CC-55-04)
| # | Tâche | Agent | Fichiers | Dépend de |
| 5 | Créer ProofEventService | agent-developer | services/proof-event.service.ts | 3 |
| 6 | Créer AnchorBatchService | agent-developer | services/anchor-batch.service.ts | 3, 5 |
| 7 | Créer DTOs (ProofArtifactDto) | agent-developer | dto/proof-artifact.dto.ts | - |
| 8 | Tests unitaires services | agent-qa-unit-integration | services/*.spec.ts | 5, 6 |
Phase 3 : Worker BullMQ (CC-55-05)
| # | Tâche | Agent | Fichiers | Dépend de |
| 9 | Créer BlockchainAnchorProcessor | agent-developer | processors/blockchain-anchor.processor.ts | 5, 6 |
| 10 | Configurer job répétable (10 min) | agent-developer | anchor.module.ts, constants/ | 9 |
| 11 | Tests unitaires processor | agent-qa-unit-integration | processors/*.spec.ts | 9 |
| 12 | Tests intégration (workflow complet) | agent-qa-unit-integration | __tests__/anchor.e2e.spec.ts | 9, 10 |
Phase 4 : Intégration & Alerting
| # | Tâche | Agent | Fichiers | Dépend de |
| 13 | Intégrer module anchor dans app | agent-developer | app.module.ts, anchor.module.ts | 10 |
| 14 | Configurer alertes PagerDuty | agent-sre | services/anchor-alert.service.ts | 6 |
| 15 | Tests de robustesse (timeout, reorg) | agent-qa-unit-integration | __tests__/anchor-robustness.spec.ts | 12 |
Détection des trous temporels (ECT-03)
La tâche 14 (alertes PagerDuty) DOIT implémenter une méthode detectWindowGaps() :
/**
* Détecte les fenêtres temporelles manquantes (INV-55-07).
* Appelé par le job de monitoring pour vérifier la continuité.
*
* @returns Liste des gaps détectés avec timestamps
*/
async detectWindowGaps(
since: Date,
windowSize: number = 600_000, // 10 min en ms
): Promise<Array<{ expectedStart: Date; expectedEnd: Date }>> {
// 1. Récupérer tous les lots FINALIZED depuis `since`
// 2. Vérifier la continuité des fenêtres (windowEnd[n] === windowStart[n+1])
// 3. Retourner les gaps détectés (fenêtres sans lot)
}
Alerte déclenchée si gaps.length > 0 → WARNING (gap < 1h) ou CRITICAL (gap >= 1h).
Traitement ERR-55-08 — Blocage lot si gap non résolu :
Conformément à ERR-55-08 ("lot concerné non validé tant que non résolu"), le processor DOIT :
- Avant chaque finalisation : appeler
detectWindowGaps(batch.windowStart, batch.windowEnd) - Si gap détecté dans la fenêtre du lot : bloquer la finalisation (état
BLOCKED_GAP) - Journaliser :
AuditService.log({ action: 'BATCH_BLOCKED_GAP', entityId: batchId, payload: { gaps } }) - Résolution : intervention manuelle requise pour valider que le gap est acceptable (événements vides) ou corriger
Le lot passe de BLOCKED_GAP → PENDING_FINALITY uniquement après validation manuelle via endpoint admin POST /admin/anchor/batches/:id/resolve-gap.
Phase 5 : Documentation & Export
| # | Tâche | Agent | Fichiers | Dépend de |
| 16 | Endpoint export proof artifact | agent-developer | anchor.controller.ts | 6, 7 |
| 17 | Tests endpoint | agent-qa-unit-integration | anchor.controller.spec.ts | 16 |
Estimation
| Phase | Tâches | Effort estimé |
| Phase 1 | 4 | 2-3h |
| Phase 2 | 4 | 4-5h |
| Phase 3 | 4 | 4-5h |
| Phase 4 | 3 | 2-3h |
| Phase 5 | 2 | 1-2h |
| Total | 17 | 13-18h |
Risques et mitigations
| Risque | Impact | Mitigation |
| Timeout ConfirmationTracker | Lot en PENDING_FINALITY trop longtemps | Mécanisme escalade ERR-55-05 (T+1h → WARNING, T+2h → CRITICAL) |
| Reorg blockchain | Perte de finalité | ConfirmationTracker existant détecte les reorgs |
| Backlog > 100K | Dégradation performance | Circuit breaker à 150K (§9.1 spec) |
| Double ancrage | Violation INV-55-02 | Contrainte unique DB + vérification pré-ancrage |
Notes d'implémentation
Ordre canonique (INV-55-13)
// Tri des événements avant construction Merkle
events.sort((a, b) => {
const dateCompare = a.createdAt.getTime() - b.createdAt.getTime();
if (dateCompare !== 0) return dateCompare;
return a.eventId.localeCompare(b.eventId, 'en', { sensitivity: 'base' });
});
Tous les timestamps doivent être en ISO 8601 UTC avec précision milliseconde :
const createdAt = event.createdAt.toISOString(); // "2026-02-21T10:30:00.123Z"
Canonicalisation RFC 8785
Utiliser la bibliothèque canonicalize (npm) déjà utilisée par PD-237 :
import canonicalize from 'canonicalize';
const canonicalJson = canonicalize(eventPayload);
const hash = createHash('sha256').update(canonicalJson!).digest('hex');
Références
- Spécification : PD-55-specification.md (v3)
- Tests : PD-55-tests.md
- Dépendances : PD-52, PD-53, PD-237, PD-21