Aller au contenu

PD-103 — Agent Developer — Module capture-purge (M7)

1. Identification

Attribut Valeur
Story PD-103
Module M7 — capture-purge
Fichier source src/capture/purge-manager.ts
Fichier test src/__tests__/capture/purge-manager.test.ts
Agent Agent Developer (Claude)
Date 2026-04-03
Spec version v3

2. Responsabilite du module

Le module capture-purge (M7) gere la purge des artefacts temporaires sensibles du flux de capture probatoire :

  1. purgeStale() au demarrage de chaque flux capture (INV-103-07, CA-103-12)
  2. Suppression locale post-UPLOADED immediatement apres ACK backend (INV-103-38, CA-103-27, §5.3)
  3. Watchdog TTL differe detection des captures UPLOAD_DEFERRED expirees (INV-103-38, §5.3)
  4. Purge sur annulation nettoyage local sur CANCELLED (INV-103-29)

3. Implementation

3.1 Classe CapturePurgeManager

Gestionnaire singleton instancie par l'orchestrateur (M6). Suit le pattern etabli par src/export/temp-file-manager.ts (PD-283).

API publique :

Methode Responsabilite Invariant
registerArtifact(captureId, path) Enregistre un artefact ciphertext local
markDeferred(captureId) Marque un artefact comme differe (TTL commence)
purgeStale() Purge proactive au demarrage du flux INV-103-07
purgeAfterUpload(captureId) Suppression immediate post-UPLOADED INV-103-38, CA-103-27
purgeCancelled(captureId) Purge sur annulation explicite INV-103-29
checkDeferredTtl() Retourne les captures differees expirees INV-103-38
isDeferredExpired(captureId) Verifie si une capture differee a expire INV-103-38
getCiphertextPath(captureId) Acces au chemin ciphertext (reprise differee)
getDeferredRecord(captureId) Acces au record differe complet

3.2 Registre persistant (crash recovery)

  • Cle AsyncStorage : @probatiovault_capture_registry
  • Repertoire dedie : {documentDirectory}/ProbatioVault/capture-temp/
  • Persistance a chaque mutation du registre (best-effort)
  • Chargement au purgeStale() pour recoverer apres crash
  • Scan d'orphelins sur disque pour fichiers non enregistres

3.3 Strategie de purge

Situation Comportement
Demarrage flux (purgeStale) Charge registre persiste + supprime tous les fichiers + scan orphelins
Apres UPLOADED (purgeAfterUpload) Suppression immediate du ciphertext local + nettoyage registre
Annulation (purgeCancelled) Meme logique que purgeAfterUpload
TTL expire (checkDeferredTtl) Retourne la liste ; orchestrateur responsable de la transition FSM + appel purgeCancelled
Erreur suppression Best-effort : continue sans bloquer le flux principal

3.4 Design decisions

Le module ne mute PAS la FSM directement. checkDeferredTtl() est un check pur : il retourne les captures expirees et laisse l'orchestrateur (M6) gerer la transition UPLOAD_DEFERRED -> CANCELLED puis appeler purgeCancelled(). Cette separation respecte le principe de responsabilite unique.

4. Mapping invariants

Invariant Implementation Observable
INV-103-07 purgeStale() appele en premiere etape de M6 Trace horodatee avant capture
INV-103-29 purgeCancelled() appele apres abort S3 Fichier supprime + registre vide
INV-103-38 purgeAfterUpload() immediat post-UPLOADED ; checkDeferredTtl() pour TTL Absence artefact local apres ACK ; captures expirees detectees

5. Matrice de couverture tests

Test-ID Fichier Description
TC-NOM-04 src/__tests__/capture/purge-manager.test.ts purgeStale() au demarrage (5 cas)
TC-NOM-17 src/__tests__/capture/purge-manager.test.ts purge locale post-UPLOADED (3 cas)
TC-ERR-11 src/__tests__/capture/purge-manager.test.ts watchdog TTL differe (6 cas)
TC-INV-09 src/__tests__/capture/purge-manager.test.ts purge annulation (1 cas, partie M7)
TC-NR-12 src/__tests__/capture/purge-manager.test.ts non-regression purge locale (1 cas)
NON-CONTRACTUAL src/__tests__/capture/purge-manager.test.ts registre, persistence, constantes (5 cas)

Total : 21 tests, 21 passed.

6. Couverture

Metrique Valeur Seuil
Statements 92.85% >= 80%
Branches 93.54% >= 80%
Functions 89.47% >= 80%
Lines 94.62% >= 80%

Lignes non couvertes : constructeur CapturePurgeError (error class rarement instanciee directement), catch interne _persistRegistry et _scanOrphans (branches d'erreur best-effort).

7. Qualite

Critere Statut
TypeScript strict (noEmit) 0 erreur
Tests 21/21 passed
Coverage >= 80% Oui (tous criteres)
Aucune donnee sensible loggee Oui (pas de log de chemins/contenus sensibles)
Pattern existant respecte Oui (TempFileManager PD-283)
Zeroization Hors perimetre M7 (responsabilite M2)

8. Hypotheses

ID Hypothese Impact si faux
H-M7-01 expo-file-system/legacy est disponible dans le build cible Fallback : utiliser expo-file-system sans /legacy
H-M7-02 Le repertoire ProbatioVault/capture-temp/ est cree par l'initialisation storage existante Si absent : purgeStale et _scanOrphans retournent vide sans erreur (idempotent)
H-M7-03 L'orchestrateur (M6) appelle purgeStale() en premiere etape Si non respecte : artefacts residuels non purges au demarrage

9. Architectural decisions

architectural_decisions:
  - decision: "Registre AsyncStorage plutot que fichier JSON"
    rationale: "Pattern prouve par TempFileManager (PD-283), survit aux crashes, simple a tester"
    alternatives_considered:
      - "Fichier JSON dans capture-temp/"
      - "SQLite local"
    trade_offs: "AsyncStorage est limité en taille (~6MB) mais largement suffisant pour un registre de metadata"

  - decision: "checkDeferredTtl retourne les IDs sans muter la FSM"
    rationale: "Separation des responsabilites : M7 gere le filesystem, M1 gere les transitions d'etat"
    alternatives_considered:
      - "M7 appelle directement FSM.transition()"
    trade_offs: "Necessite coordination par l'orchestrateur (M6) mais evite couplage M7-M1"

10. Dependances

Module Direction Nature
M1 (capture-state-machine) M7 n'importe PAS M1 Independance stricte — pas de couplage FSM
M6 (capture-orchestrator) M6 instancie et appelle M7 M7 est consomme par M6
src/capture/types.ts Import types brandes CaptureId, CapturePurgeResult, constantes TTL
expo-file-system/legacy Runtime I/O Lecture/suppression fichiers
@react-native-async-storage/async-storage Persistence registre Crash recovery

11. Points de vigilance

  1. Watchdog TTL en background iOScheckDeferredTtl() ne s'executera pas si l'app est tuee par iOS. Mitigation : purgeStale() au prochain lancement detecte et purge les artefacts expires (point 9 du plan).

  2. Best-effort purge — Si un fichier est verrouille par un autre processus, la suppression echoue silencieusement. Le registre est quand meme nettoye pour eviter les boucles infinies.

  3. Pas de mutex — Les operations registerArtifact / purgeStale ne sont pas protegees par un mutex. En pratique, l'orchestrateur (M6) serialise les appels. Si un usage concurrent est necessaire, ajouter un Map<string, Promise> mutex par captureId (pattern PD-86).