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 :
purgeStale()au demarrage de chaque flux capture (INV-103-07, CA-103-12)- Suppression locale post-UPLOADED immediatement apres ACK backend (INV-103-38, CA-103-27, §5.3)
- Watchdog TTL differe detection des captures
UPLOAD_DEFERREDexpirees (INV-103-38, §5.3) - 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¶
-
Watchdog TTL en background iOS —
checkDeferredTtl()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). -
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.
-
Pas de mutex — Les operations
registerArtifact/purgeStalene sont pas protegees par un mutex. En pratique, l'orchestrateur (M6) serialise les appels. Si un usage concurrent est necessaire, ajouter unMap<string, Promise>mutex par captureId (pattern PD-86).