PD-86-GOV-PROMPT-STEP2
2/ Tests & Validation¶
Tu es Architecte QA senior / auditeur qualité indépendant, orienté testabilité, conformité et non-régression. À partir de la SPÉCIFICATION CANONIQUE CONTRACTUELLE ci-dessous, rédige les SCÉNARIOS DE TEST DE RÉFÉRENCE permettant de démontrer objectivement la conformité de toute implémentation.
Tu ne modifies PAS la spécification. Tu ne proposes AUCUNE implémentation.
Règles impératives¶
- Chaque invariant (INV-86-01 à INV-86-15) DOIT avoir au moins un scénario de test associé.
- Chaque critère d'acceptation (CA-86-01 à CA-86-12) DOIT être couvert par ≥1 test.
- Tout scénario doit être déterministe, reproductible et vérifiable.
- Les comportements ambigus doivent être signalés comme non testables.
- Aucune hypothèse implicite : tout prérequis doit être explicité.
- Les tests décrivent le quoi vérifier, jamais le comment coder.
- Les scénarios doivent être automatisables, même s'ils ne sont pas encore automatisés.
- Toute règle non testable → marquée explicitement NON TESTABLE avec justification.
Structure de sortie obligatoire¶
- Matrice de couverture : tableau INV/CA → scénario(s) de test
- Scénarios de test : ID (TC-86-XX), description, prérequis, étapes, résultat attendu, INV/CA couverts
- Tests de sécurité : scénarios spécifiques zero-knowledge, zeroization, fichiers temporaires
- Tests de performance : scénarios budget temps, warm-up, dégradation gracieuse
- Tests de gestion d'erreurs : MODEL_UNAVAILABLE, CORRUPTED_INPUT, UNSUPPORTED_FORMAT, TIMEOUT, INTERNAL_ERROR
- Tests UX/consentement : modal, confirmation mineur, préférence document, désactivation globale
- Signalement : invariants/CA potentiellement non testables ou ambigus
Références¶
- Spécification : PD-86-specification.md
- Epic : PD-185 — B2C-MINEURS
- Projet : ProbatioVault-app (iOS)
Contexte technique¶
- Domaine : app iOS (Swift/SwiftUI), traitement visuel local
- 4 entités locales : SensitiveDetectionSettings, DocumentSensitiveVerdict, DocumentSensitivePreference, ViewerGateDecision
- 4 catégories : VIOLENCE, NUDITE, SEXUEL, CHOQUANT
- Seuils par défaut : VIOLENCE 0.75, NUDITE 0.70, SEXUEL 0.70, CHOQUANT 0.75
- Vidéo : 12 frames, max(score)
- Cache chiffré, pas de fichiers temporaires
Voici la spécification :
# PD-86-specification.md
# 1. Metadata
- **Story ID** : PD-86
- **Epic** : PD-185 — B2C-MINEURS
- **Titre** : Détection de contenu sensible (IA locale)
- **Projet** : ProbatioVault-app (iOS)
- **Date** : 2026-02-24
- **Version** : 1.0.0
# 2. Périmètre
## 2.1 Couvert
- Détection locale de sensibilité visuelle à la **consultation** (viewer) pour :
- Images
- Vidéos (échantillonnage de frames)
- PDF (rendu de pages représentatives)
- Catégories détectées (whitelist contractuelle) :
- `VIOLENCE`
- `NUDITE`
- `SEXUEL`
- `CHOQUANT`
- Affichage d'un écran intermédiaire de consentement avant rendu du contenu quand un risque sensible est détecté.
- Gestion d'une préférence locale "ne plus me prévenir pour ce document".
- Cache local minimal des verdicts de classification, non ré-identifiant du contenu visuel.
## 2.2 Non couvert
- Aucune modération automatique (suppression, blocage, signalement).
- Aucune qualification légale/morale du contenu.
- Aucune analyse côté serveur / API externe.
- Aucune analyse à l'upload.
- Aucun contrôle parental (parental gate).
- Aucune classification de texte (OCR/NLP).
- Aucune persistance serveur des verdicts.
## 2.3 Clarifications contractuelles (Q1–Q10)
- **Q1 (modèle précis)** : Décision d'implémentation. Exigence contractuelle : modèle hors ligne avec benchmark documenté (précision/latence/taille) avant mise en production.
- **Q2 (ONNX vs CoreML)** : Tranché. Référence de livraison iOS : moteur natif iOS hors ligne ; le pipeline de conversion éventuel est hors périmètre de cette spec.
- **Q3 (agrégation vidéo)** : Tranché. Agrégation contractuelle = `max(score_categorie)` sur frames échantillonnées.
- **Q4 (nombre/stratégie frames)** : Tranché. 12 frames par vidéo, stratégie uniforme + inclusion obligatoire des 2 premières secondes.
- **Q5 (seuils)** : Tranché. Seuil par catégorie (configurable utilisateur) avec valeurs par défaut prudentes :
- `VIOLENCE: 0.75`
- `NUDITE: 0.70`
- `SEXUEL: 0.70`
- `CHOQUANT: 0.75`
- **Q6 (cache chiffré)** : Tranché. Cache local chiffré obligatoire.
- **Q7 (timeout inférence)** : Tranché. Comportement prudent obligatoire : modal "contenu non analysé, potentiellement sensible".
- **Q8 (thumbnails PDF)** : Décision d'implémentation. Exigence contractuelle : rendu local en mémoire uniquement, sans persistance disque.
- **Q9 (warm-up)** : Tranché. Warm-up déclenché à l'ouverture du coffre, puis lazy fallback au premier document si non prêt.
- **Q10 (désactivation utilisateur)** : Tranché. Désactivation globale autorisée (pas par catégorie dans PD-86).
# 3. Modèle de données
## 3.1 Entités locales
| Entité | Champs | Contraintes |
|---|---|---|
| `SensitiveDetectionSettings` | `enabled: Bool`, `thresholds: Map<Category, Float>`, `updatedAt: LocalTimestamp` | `threshold ∈ [0.50, 0.95]` |
| `DocumentSensitiveVerdict` | `documentId: String`, `modelVersion: String`, `isSensitive: Bool`, `detectedCategories: Category[]`, `roundedScores: Map<Category, Float(2d)>`, `analyzedAt: LocalTimestamp`, `analysisStatus: Enum` | Unicité `(documentId, modelVersion)` |
| `DocumentSensitivePreference` | `documentId: String`, `suppressWarning: Bool`, `updatedAt: LocalTimestamp` | Portée strictement locale |
| `ViewerGateDecision` (événement en mémoire) | `documentId`, `decision: SHOW_ANYWAY|KEEP_HIDDEN|CANCEL`, `requiresExplicitConfirmation: Bool` | Non persistant |
## 3.2 Types
- `Category` : `VIOLENCE | NUDITE | SEXUEL | CHOQUANT`
- `analysisStatus` : `SUCCESS | TIMEOUT | MODEL_UNAVAILABLE | UNSUPPORTED_FORMAT | CORRUPTED_INPUT | INTERNAL_ERROR`
- `isSensitive = true` si au moins un score catégorie >= seuil catégorie.
## 3.3 Relations
- `SensitiveDetectionSettings` (1) influence l'évaluation de `DocumentSensitiveVerdict` (N).
- `DocumentSensitivePreference` (0..1) par `documentId` surcharge l'affichage d'alerte pour ce document.
- Aucun lien serveur, aucune réplication cloud.
# 4. Règles métier
- **INV-86-01** : Toute classification est locale au device ; transmission réseau de contenu en clair interdite.
- **INV-86-02** : La détection n'est exécutée qu'au viewer ; jamais à l'import/upload.
- **INV-86-03** : Le fichier original n'est jamais modifié ; hash et scellement probatoire restent inchangés.
- **INV-86-04** : Seules les catégories whitelistées (`VIOLENCE`,`NUDITE`,`SEXUEL`,`CHOQUANT`) peuvent produire un flag sensible.
- **INV-86-05** : Si `isSensitive=true`, l'écran intermédiaire de consentement est obligatoire avant affichage.
- **INV-86-06** : Pour mineur, l'action "Afficher quand même" nécessite confirmation explicite additionnelle.
- **INV-86-07** : Le cache ne stocke jamais de média dérivé (images, frames, thumbnails), embeddings, OCR, texte extrait.
- **INV-86-08** : Le cache local des verdicts est chiffré.
- **INV-86-09** : En cas de timeout, indisponibilité modèle ou erreur critique d'analyse, politique prudente : avertir avant affichage.
- **INV-86-10** : Zeroization best-effort des buffers visuels en fin de traitement, y compris en cas d'échec/annulation.
- **INV-86-11** : Aucune écriture de fichier temporaire en clair durant le pipeline d'analyse.
- **INV-86-12** : Les logs ne contiennent ni pixel data, ni miniatures, ni métadonnées reconstituables du contenu.
- **INV-86-13** : Le résultat de classification est invalidé si `modelVersion` change.
- **INV-86-14** : Le paramètre utilisateur `enabled=false` désactive toute nouvelle classification, sans supprimer les preuves ni modifier les fichiers.
- **INV-86-15** : La préférence "ne plus me prévenir pour ce document" est réversible à tout moment par l'utilisateur.
## 4.1 Transitions d'état et transitions retour
| Domaine | Transition | Statut | Comportement retour |
|---|---|---|---|
| Détection globale | `ENABLED -> DISABLED` | Autorisée | Arrêt des nouvelles analyses ; aucun impact sur fichiers/probatoire |
| Détection globale | `DISABLED -> ENABLED` | Autorisée | Reprise immédiate des analyses au viewer |
| Préférence document | `WARN -> SUPPRESSED` | Autorisée | Plus d'écran intermédiaire pour ce document |
| Préférence document | `SUPPRESSED -> WARN` | Autorisée | Avertissement réactivé immédiatement |
| Verdict analyse | `UNANALYZED -> ANALYZED` | Autorisée | Verdict cacheable |
| Verdict analyse | `ANALYZED -> UNANALYZED` (changement modèle) | Autorisée | Invalidation cache, reclassification requise |
| Affichage | `HIDDEN -> SHOWN` | Autorisée après consentement | Affichage explicite contrôlé |
| Affichage | `SHOWN -> HIDDEN` | Autorisée | Retour immédiat au masquage |
## 4.2 SLA temporels
Aucune transition temporelle identifiée.
# 5. Interfaces
## 5.1 Contrat du module de détection locale
### Entrées
- `documentId: String`
- `documentType: IMAGE | VIDEO | PDF`
- `decryptedContentHandle: InMemoryHandle`
- `settingsSnapshot: SensitiveDetectionSettings`
- `context: { isMinor: Bool }`
### Sorties
- `SensitiveAnalysisResult` :
- `status: analysisStatus`
- `isSensitive: Bool`
- `detectedCategories: Category[]`
- `scores: Map<Category, Float>`
- `modelVersion: String`
- `durationMs: Int`
### Erreurs contractuelles
- `MODEL_UNAVAILABLE`
- `UNSUPPORTED_FORMAT`
- `CORRUPTED_INPUT`
- `TIMEOUT`
- `INTERNAL_ERROR`
## 5.2 Contrat du gate viewer
- Si `status=SUCCESS && isSensitive=true` -> modal obligatoire.
- Si `status in {TIMEOUT, MODEL_UNAVAILABLE, CORRUPTED_INPUT, INTERNAL_ERROR}` -> modal prudent obligatoire.
- Si préférence document `suppressWarning=true` -> modal omis uniquement pour ce document.
- Si utilisateur choisit `KEEP_HIDDEN` -> aucun rendu du contenu.
# 6. Critères d'acceptation
- **CA-86-01** : L'ouverture d'une image sensible détectée affiche un modal avant tout rendu du média.
- **CA-86-02** : L'ouverture d'une vidéo sensible détectée (agrégation `max`) affiche un modal avant tout rendu.
- **CA-86-03** : L'ouverture d'un PDF sensible détecté affiche un modal avant toute page.
- **CA-86-04** : Le système ne transmet aucun contenu visuel ni score à un endpoint distant.
- **CA-86-05** : Le hash probatoire du document reste identique avant/après classification.
- **CA-86-06** : Le cache local contient uniquement les champs autorisés (flag, catégories, scores arrondis, version modèle, timestamp, statut).
- **CA-86-07** : Aucun fichier temporaire média n'est présent sur disque après classification réussie ou échouée.
- **CA-86-08** : En cas de `TIMEOUT`, le modal "contenu potentiellement sensible/non analysé" est affiché.
- **CA-86-09** : En cas de modèle absent du bundle/runtime, le flux reste fonctionnel avec modal prudent.
- **CA-86-10** : La désactivation globale utilisateur empêche toute nouvelle classification tant qu'elle reste active.
- **CA-86-11** : La préférence "ne plus me prévenir" est appliquée par document et réversible.
- **CA-86-12** : À changement de `modelVersion`, un document précédemment classifié est reclassifié à la prochaine consultation.
# 7. Sécurité
- Architecture zero-knowledge stricte : traitement local uniquement, aucun appel d'analyse externe.
- Zeroization best-effort obligatoire des buffers mémoire de rendu/analyse en fin de flux (succès, erreur, timeout, annulation).
- Interdiction de persister des dérivés visuels (frames, thumbnails, previews non chiffrées).
- Interdiction de logs contenant données visuelles ou métadonnées reconstituables.
- Contrôle des mécanismes système pouvant générer des aperçus persistants non maîtrisés.
- Le cache de verdict doit être chiffré localement et limité au minimum nécessaire.
# 8. Performance
- Budget d'analyse cible par ouverture : `<= 700 ms` (hors décodage viewer incompressible).
- Exécution en arrière-plan ; blocage UI prolongé interdit.
- Warm-up du moteur au contexte "ouverture du coffre" (fallback lazy au premier document).
- Downscaling agressif autorisé et attendu pour l'analyse.
- Vidéo : échantillonnage 12 frames max (uniforme + début), sans décodage intégral.
- PDF : échantillonnage de pages représentatives, rendu mémoire uniquement.
# 9. Gestion d'erreurs
| Cas d'erreur | Code | Comportement utilisateur | Persistance |
|---|---|---|---|
| Modèle absent/inaccessible | `MODEL_UNAVAILABLE` | Modal prudent, possibilité d'afficher quand même | Statut erreur horodaté |
| Image/PDF/vidéo corrompu(e) | `CORRUPTED_INPUT` | Message non culpabilisant + modal prudent | Statut erreur horodaté |
| Format non supporté | `UNSUPPORTED_FORMAT` | Message clair "format non pris en charge", pas de crash | Statut erreur horodaté |
| Timeout inférence | `TIMEOUT` | Modal prudent "non analysé" | Statut erreur horodaté |
| Erreur interne inattendue | `INTERNAL_ERROR` | Dégradation contrôlée, contenu masqué par défaut | Statut erreur horodaté |
Règles communes :
- Aucune erreur ne doit provoquer de crash du viewer.
- Aucune erreur ne doit déclencher d'envoi réseau de contenu.
- Le mode dégradé conserve le contrôle utilisateur ("Afficher quand même" / "Garder masqué").
# 10. Contraintes techniques
- Domaine : iOS (Swift/SwiftUI), traitement visuel local hors ligne.
- Modèle compatible exécution offline sur device, versionné, et identifiable (`modelVersion`).
- Taille du composant modèle compatible distribution mobile (contrainte bundle), avec validation perf sur devices cibles.
- Compatibilité minimale iOS : décision d'implémentation (doit être explicitée avant développement).
- Stratégie DDL : **Non applicable** (aucune base serveur modifiée).
- Atomicité DB+queue : **Non applicable** (aucun flux serveur asynchrone).
📌 Artefact produit (à générer dans le Canvas) ➡️ PD-86-tests.md 📂 Chemin de destination : ProbatioVault-app/docs/epics/b2c-mineurs/PD-86-detection-contenu-sensible/PD-86-tests.md Structure : matrice de couverture, scénarios (TC-86-XX), tests sécurité, performance, erreurs, UX, signalement.