Aller au contenu

PD-103 — Agent Developer — Module M1 : capture-state-machine

1. Identite agent

  • Agent : agent-developer (Agent B — Claude)
  • Story : PD-103
  • Module : M1 — capture-state-machine
  • Wave : 1 (fondation, sans dependance inter-agents)
  • Date : 2026-04-03

2. Resume

Module M1 implemente la machine d'etats monotone a 8 etats pour la capture probatoire d'ecran. Le module fournit la validation structurelle des transitions, les gardes metier contractuelles, le terminal enforcement et la journalisation des transitions.

Fichiers crees : - src/capture/state-machine.ts — Implementation FSM - src/__tests__/capture/state-machine.test.ts — 90 tests (contractuels + non-regression)

Fichier existant reutilise : - src/capture/types.ts — Types, etats, constantes, table de transitions (T1 capture-types)

3. Artefacts livres

Fichier Role Lignes
src/capture/state-machine.ts FSM 8 etats, gardes, log, erreur typee ~220
src/__tests__/capture/state-machine.test.ts Tests contractuels + non-regression ~460

4. Architecture

4.1 Pattern utilise

Pattern identique a src/seal/state-machine.ts (PD-284) : - Table ALLOWED_TRANSITIONS exhaustive (importee de types.ts) - Fonction pure canTransition(from, to) sans effet de bord - Fonction pure isTerminalCaptureState(state) - Classe CaptureStateMachine avec etat mutable + log immutable - Erreur typee InvalidCaptureTransitionError avec from, to, code

4.2 Enrichissements par rapport au pattern existant

Enrichissement Justification
TransitionGuardContext Interface structuree pour les gardes metier (INV-103-08, INV-103-20..29, INV-103-38)
evaluateGuard(from, to, ctx) Fonction pure evaluant les gardes metier par paire (from, to)
canTransitionTo(nextState, ctx) Methode d'instance combinant verification structurelle + gardes
captureId dans le log Chaque entree du journal porte le CaptureId pour correlation (observabilite §8)
Timestamp ISO 8601 UTC Timestamps en format ISO au lieu de Date.now() pour alignement avec le contrat §5.1
Code d'erreur differencie TERMINAL_STATE vs INVALID_TRANSITION dans InvalidCaptureTransitionError.code

4.3 Gardes de transition implementees

Transition Garde Invariant
CAPTURED -> UPLOADING hashComputed + encryptionDone INV-103-20
CAPTURED -> CANCELLED userCancelled INV-103-21
UPLOADING -> UPLOADED s3UploadConfirmed + backendAck INV-103-22
UPLOADING -> UPLOAD_DEFERRED aucune INV-103-23
UPLOADING -> CANCELLED userCancelled INV-103-29
UPLOAD_DEFERRED -> UPLOADING jwtValid + freshPresignedUrl INV-103-24
UPLOAD_DEFERRED -> CANCELLED deferredTtlExpired INV-103-38
UPLOADED -> PENDING_SEAL aucune INV-103-25
PENDING_SEAL -> SEALED signatureStatus='SIGNED' + hsmSignatureRef != null INV-103-08/26
SEALED -> ANCHOR_CONFIRMED blockchainConfirmed INV-103-27

4.4 Exports publics

// Classes
export class CaptureStateMachine { ... }
export class InvalidCaptureTransitionError extends Error { ... }

// Interface
export interface TransitionGuardContext { ... }

// Fonctions pures
export function canTransition(from, to): boolean
export function isTerminalCaptureState(state): boolean
export function evaluateGuard(from, to, ctx): string | null

4.5 Diagramme

stateDiagram-v2
    [*] --> CAPTURED

    CAPTURED --> UPLOADING : hash+encrypt OK
    CAPTURED --> CANCELLED : user cancel

    UPLOADING --> UPLOADED : S3+ACK OK
    UPLOADING --> UPLOAD_DEFERRED : reseau KO
    UPLOADING --> CANCELLED : user cancel

    UPLOAD_DEFERRED --> UPLOADING : JWT+URL OK
    UPLOAD_DEFERRED --> CANCELLED : TTL expire

    UPLOADED --> PENDING_SEAL
    PENDING_SEAL --> SEALED : SIGNED+hsm_ref
    SEALED --> ANCHOR_CONFIRMED : blockchain OK

    ANCHOR_CONFIRMED --> [*]
    CANCELLED --> [*]

5. Couverture de tests

5.1 Metriques

Metrique Valeur Seuil
Statements 97.46% >= 80%
Branches 97.46% >= 80%
Functions 91.66% >= 80%
Lines 97.05% >= 80%
Tests 90
Succes 90/90 100%

5.2 Matrice de couverture TC-* -> fichiers de test

Test-ID Fichier de test Ligne Description
TC-INV-02 src/__tests__/capture/state-machine.test.ts ~41 CAPTURED -> UPLOADING refuse sans hash+chiffrement
TC-INV-10 src/__tests__/capture/state-machine.test.ts ~73 Etats terminaux stricts — toute transition rejetee
TC-NOM-12 src/__tests__/capture/state-machine.test.ts ~73 CANCELLED et ANCHOR_CONFIRMED terminaux
TC-ERR-02 src/__tests__/capture/state-machine.test.ts ~119 CAPTURED -> CANCELLED annulation explicite
TC-ERR-08 src/__tests__/capture/state-machine.test.ts ~148 Garde PENDING_SEAL -> SEALED cas positif et negatif
TC-NEG-13 src/__tests__/capture/state-machine.test.ts ~224 Transitions skip directes interdites (31 paires)

5.3 Tests additionnels (NON-CONTRACTUAL)

Test Description
Transitions autorisees exhaustives 10 paires valides + canTransition pour toute la table
Gardes INV-103-22 UPLOADING -> UPLOADED sans S3 / sans ACK
Gardes INV-103-24 UPLOAD_DEFERRED -> UPLOADING sans JWT / sans URL
Gardes INV-103-27 SEALED -> ANCHOR_CONFIRMED sans blockchain
Gardes INV-103-29 UPLOADING -> CANCELLED sans annulation explicite
Gardes INV-103-38 UPLOAD_DEFERRED -> CANCELLED sans TTL expire
Flux nominal complet CAPTURED -> ANCHOR_CONFIRMED (5 transitions)
Flux differe CAPTURED -> UPLOAD_DEFERRED -> UPLOADED (4 transitions)
canTransitionTo Verification sans mutation
Journal transitions Observabilite : timestamps, captureId, raisons
InvalidCaptureTransitionError Proprietes from/to/code
evaluateGuard isole Tests unitaires des gardes individuelles
Monotonicite Seul retour autorise = UPLOAD_DEFERRED -> UPLOADING

6. Decisions architecturales

6.1 Gardes comme fonction pure separee

  • Decision : evaluateGuard() est une fonction pure exportee, separee de la classe FSM.
  • Rationale : Permet de tester les gardes isolement sans instancier la machine. Les consumers (M6 orchestrator) peuvent pre-valider une transition sans effet de bord.
  • Alternatives considerees : Gardes inline dans transition() (moins testable), gardes comme methodes privees (non exportable).
  • Trade-offs : Legere duplication du mapping (from, to) dans evaluateGuard par rapport a la table ALLOWED_TRANSITIONS, mais meilleure lisibilite et testabilite.

6.2 Timestamp ISO 8601 au lieu de Date.now()

  • Decision : Les entrees du log utilisent new Date().toISOString() (ISO 8601 UTC) au lieu de Date.now() (millisecondes epoch).
  • Rationale : Alignement avec le format contractuel timestamp_device (RFC3339 UTC) et l'interface CaptureTransitionLog de types.ts. Facilite la correlation avec les logs backend.
  • Alternatives considerees : Date.now() (pattern PD-284), performance.now() (PD-284 learning).
  • Trade-offs : Microseconde de surcharge pour la serialisation ISO ; acceptable car les transitions sont evenementielles et non critiques en latence.

7. Hypotheses

ID Hypothese Impact si faux
HYP-M1-01 Les gardes sont evaluees cote mobile par l'orchestrateur (M6) qui fournit le TransitionGuardContext Si le contexte n'est pas correctement peuple par M6, les gardes seront trop restrictives
HYP-M1-02 La garde PENDING_SEAL -> SEALED (INV-103-08) est evaluee cote backend par le worker de scellement, pas cote mobile Cote mobile, cette transition sera declenchee par resync SSE/polling sans evaluation locale de la garde

8. Dependances

Dependance Direction Module
src/capture/types.ts Import T1 capture-types (prealable, livre)
M5 useCaptureStore Consommateur M5 importera CaptureStateMachine pour gerer l'etat persistant
M6 orchestrator Consommateur M6 instanciera CaptureStateMachine et fournira TransitionGuardContext

9. Invariants couverts

Invariant Couverture Mecanisme
INV-103-08-sealed-guard Tests positif + negatif (TC-ERR-08) Garde signatureStatus='SIGNED' + hsmSignatureRef
INV-103-20-transition-captured-uploading Tests positif + negatif (TC-INV-02) Garde hashComputed + encryptionDone
INV-103-21-transition-captured-cancelled Test positif + negatif (TC-ERR-02) Garde userCancelled
INV-103-22-transition-uploading-uploaded Tests positif + negatif Garde s3UploadConfirmed + backendAck
INV-103-23-transition-uploading-deferred Test positif (pas de garde) Transition structurelle
INV-103-24-transition-deferred-uploading Tests positif + negatif Garde jwtValid + freshPresignedUrl
INV-103-25-transition-uploaded-pending-seal Test positif (pas de garde) Transition structurelle
INV-103-26-transition-pending-seal-sealed Tests positif + negatif (TC-ERR-08) Delegue a INV-103-08
INV-103-27-transition-sealed-anchor Tests positif + negatif Garde blockchainConfirmed
INV-103-28-terminal-states Tests exhaustifs (TC-INV-10, TC-NOM-12) ALLOWED_TRANSITIONS[terminal] = []
INV-103-29-transition-uploading-cancelled Tests positif + negatif Garde userCancelled
INV-103-38-transition-deferred-cancelled Tests positif + negatif Garde deferredTtlExpired

10. Limites connues

  1. Pas de resync serveur : Contrairement a SealStateMachine (PD-284), pas de methode resync(). Le besoin n'est pas identifie dans la spec PD-103 pour le mobile. Si necessaire, a ajouter dans une iteration ulterieure.

  2. Gardes evaluees localement : La garde PENDING_SEAL -> SEALED (INV-103-08) est implementee mais sera probablement evaluee cote backend. Cote mobile, la transition sera declenchee par SSE/polling sans re-evaluation locale de la garde.

  3. Pas de persistence : La machine d'etats est en memoire. La persistence est deleguee a M5 (useCaptureStore) qui reconstituera l'etat au redemarrage.

11. Checklist qualite

  • 0 erreur TypeScript (strict: true)
  • Coverage > 80% (lignes, branches, fonctions)
  • Tous les tests TC-* contractuels implementes
  • Tests NON-CONTRACTUAL identifies comme tels
  • Aucune modification de fichier hors perimetre
  • Aucune deviation par rapport a la specification
  • Aucune fonctionnalite non prevue ajoutee
  • Pattern existant (PD-284) respecte et enrichi