PD-86 — Specification Review
Story : PD-86 — Détection de contenu sensible (IA locale) Epic : PD-185 — B2C-MINEURS Projet : ProbatioVault-app (iOS) Date de revue : 2026-02-24 Documents audités : PD-86-specification.md (v1.0.0), PD-86-tests.md
Learnings contextuels injectés
| Source | Pattern |
| PD-84 (B2C Freemium) | Transitions inverses = angle mort systématique des specs ChatGPT |
| PD-248 (Screenshot Protection) | Stratégie robustesse non documentée → RESERVE Gate 5 |
| PD-242 (Recovery Envelope) | Pattern try/finally obligatoire pour zeroization |
| PD-105 (Push Notifications) | Whitelist > Blacklist pour catégories ; PQueue/mutex pour atomicité concurrente |
| PD-107 (Biometric Auth) | Invariants zero-knowledge explicites dès le départ = GO Gate 3 |
| PD-106 (MFA Settings) | Contradictions spec → 4 itérations Gate 3 quand scope mineurs non défini |
| PD-174 (Auto-lock) | Data loss risk : distinguer "désactiver" vs "effacer" ; navigation guard > boolean flag |
Constats
C-01 — "Pages représentatives" PDF non définies
- Type : Ambiguïté
- Référence : Spec §8 Performance ("échantillonnage de pages représentatives") ; Spec §2.1 ("rendu de pages représentatives") ; Spec §Q8
- Description : Le terme "pages représentatives" n'est accompagné d'aucune règle contractuelle de sélection. Contrairement aux vidéos où la stratégie est explicite (12 frames, uniforme + 2 premières secondes), les PDF n'ont ni nombre de pages cible, ni stratégie de sélection (première/médiane/dernière, toutes les N pages, etc.).
- Impact : Deux implémentations conformes à la spec pourraient analyser des pages complètement différentes d'un même PDF, produisant des verdicts divergents. Rend TC-86-25 et PERF-86-05 non vérifiables objectivement. Les tests eux-mêmes signalent ce problème (ST-86-04).
- Gravité : Bloquant
C-02 — Interaction suppressWarning=true × statut d'erreur non spécifiée
- Type : Ambiguïté
- Référence : Spec §5.2 Contrat gate viewer ; INV-86-09 ; Spec §9 Gestion d'erreurs
- Description : Le contrat gate viewer stipule : si
suppressWarning=true → modal omis pour ce document. Il stipule aussi : si status in {TIMEOUT, MODEL_UNAVAILABLE, CORRUPTED_INPUT, INTERNAL_ERROR} → modal prudent obligatoire. La spec ne résout pas le conflit quand un document a suppressWarning=true ET que l'analyse échoue (ex: nouveau timeout après changement modelVersion, ou reclassification suite à invalidation cache). Quelle règle prime ? Aucun test ne couvre cette combinaison. - Impact : Deux comportements contradictoires possibles : (a) suppression du modal car préférence utilisateur, (b) modal prudent car erreur d'analyse. L'absence de priorité explicite est une ambiguïté fonctionnelle bloquante.
- Gravité : Bloquant
C-03 — suppressWarning=true autorise l'omission du modal pour un mineur
- Type : Risque sécu/conformité
- Référence : Spec §5.2 ("Si préférence document suppressWarning=true → modal omis uniquement pour ce document") ; INV-86-06 ; TC-86-13
- Description : Le contrat gate viewer autorise l'omission du modal quand
suppressWarning=true sans condition sur isMinor. Or INV-86-06 exige une confirmation additionnelle pour les mineurs. Si un mineur active "ne plus me prévenir" sur un document sensible, la spec ne précise pas si INV-86-06 prime sur suppressWarning. Le flux §5.2 tel qu'écrit permettrait au mineur de contourner la protection par document. Aucun test ne couvre la combinaison isMinor=true + suppressWarning=true. - Impact : Un mineur pourrait désactiver la protection document par document, neutralisant l'invariant INV-86-06. Pour une feature B2C-MINEURS (Epic PD-185), c'est un risque de conformité protection des mineurs.
- Gravité : Bloquant
C-04 — Invalidation cache absente lors du changement de seuils utilisateur
- Type : Hypothèse dangereuse
- Référence : Spec §4.2 ("Aucune transition temporelle identifiée") ; Spec §3.1 (
DocumentSensitiveVerdict) ; INV-86-13 - Description : Le verdict de classification n'a pas de TTL ni d'invalidation liée aux seuils. INV-86-13 invalide le cache uniquement si
modelVersion change. Si l'utilisateur abaisse ses seuils (ex: NUDITE de 0.70 à 0.55), les documents déjà classifiés avec des scores entre 0.55 et 0.70 ne seront pas reclassifiés (le modelVersion n'a pas changé). Ces documents passeront sans avertissement malgré des scores supérieurs au nouveau seuil. - Impact : L'utilisateur renforce sa protection (abaisse un seuil), mais les documents déjà analysés restent avec l'ancien verdict. Le résultat est l'inverse de l'intention utilisateur. Aucun test ne couvre ce scénario de modification de seuils post-classification.
- Gravité : Bloquant
C-05 — Budget 700 ms non borné par device cible ni percentile
- Type : Non testable
- Référence : Spec §8 Performance ("Budget d'analyse cible par ouverture : <= 700 ms") ; Spec §10 ("validation perf sur devices cibles") ; TC-86-23 ; PERF-86-01
- Description : Le budget de 700 ms est énoncé sans matrice de devices de référence. La spec mentionne "devices cibles" (§10) sans les lister. Un iPhone SE 2 et un iPhone 15 Pro ont des performances d'inférence radicalement différentes. Le qualificatif "cible" rend le seuil non contractuel. Aucun percentile (P50, P95, P99) n'est défini. Les tests signalent déjà ce problème (ST-86-03).
- Impact : Impossible de statuer GO/NON_CONFORME sur la performance sans device de référence et percentile contractuels. Le test PERF-86-01 est non déterministe en l'état.
- Gravité : Majeur
C-06 — Compatibilité iOS minimale non spécifiée
- Type : Ambiguïté
- Référence : Spec §10 Contraintes techniques ("Compatibilité minimale iOS : décision d'implémentation")
- Description : La version iOS minimale est déléguée à la phase d'implémentation. Or cette décision impacte directement la disponibilité des API CoreML/Vision, les capacités de chiffrement (Data Protection levels), et les contraintes de performance d'inférence. L'absence de cette borne rend indéterminable la faisabilité de plusieurs invariants (INV-86-08 chiffrement cache, INV-86-10 zeroization).
- Impact : Un choix tardif de version minimale (ex: iOS 14 vs iOS 17) pourrait invalider des pans entiers de l'architecture technique ou rendre
MODEL_UNAVAILABLE le cas nominal sur des devices anciens. - Gravité : Majeur
C-07 — Source de isMinor non contractualisée
- Type : Ambiguïté
- Référence : Spec §5.1 Entrées (
context: { isMinor: Bool }) ; INV-86-06 ; TC-86-13 - Description : L'invariant INV-86-06 exige une confirmation additionnelle pour mineur. Le contrat reçoit
isMinor: Bool en entrée, mais la spécification ne définit ni la source de cette information (profil utilisateur, âge déclaré, offre B2C-MINEURS, flag serveur), ni sa fiabilité, ni le comportement si l'information est absente ou indéterminée. Le périmètre §2.2 exclut le contrôle parental, mais ne clarifie pas comment le flag mineur est obtenu. - Impact : Si
isMinor est un toggle local falsifiable, la protection INV-86-06 est contournable. TC-86-13 présuppose context.isMinor=true sans vérifier la fiabilité de cette valeur. Learning PD-106 : scope mineur/reauthentification non défini = 4 itérations Gate 3. - Gravité : Majeur
C-08 — Seuils configurables par mineur sans garde-fou
- Type : Hypothèse dangereuse
- Référence : Spec §3.1 (
threshold ∈ [0.50, 0.95]) ; Spec §Q10 ; INV-86-06 - Description : L'utilisateur peut configurer ses seuils entre 0.50 et 0.95. La spec ne distingue pas les bornes autorisées selon le contexte mineur/majeur. Un mineur pourrait monter tous les seuils à 0.95, rendant la détection quasi inopérante (seuls les contenus les plus explicites seraient flaggés). Q10 traite la désactivation globale (autorisée) mais pas la manipulation des seuils par un mineur.
- Impact : La combinaison "mineur" + "seuils à 0.95" contourne de facto la détection sans la désactiver formellement. Pour une feature B2C-MINEURS, l'absence de seuils plancher pour les mineurs est un risque de conformité.
- Gravité : Majeur
C-09 — Absence de borne temporelle pour le timeout d'inférence
- Type : Ambiguïté
- Référence : Spec §Q7 ; Spec §9 Gestion d'erreurs (TIMEOUT) ; INV-86-09
- Description : Le comportement en cas de timeout est bien spécifié (modal prudent), mais la durée de timeout elle-même n'est pas contractualisée. Aucun seuil en millisecondes ne distingue "analyse lente" de "timeout". Le budget de 700 ms (§8) est une cible performance, pas un seuil de timeout.
- Impact : Sans seuil de timeout défini, TC-86-08 requiert une "condition de timeout reproductible" mais ne peut être déterministe. Deux implémentations pourraient avoir des timeouts à 1s ou 30s, avec des expériences utilisateur radicalement différentes.
- Gravité : Majeur
- Type : Contradiction
- Référence : Spec §5.2 Contrat gate viewer vs §9 Gestion d'erreurs
- Description : Le contrat gate viewer (§5.2) liste les statuts déclenchant un "modal prudent obligatoire" :
TIMEOUT, MODEL_UNAVAILABLE, CORRUPTED_INPUT, INTERNAL_ERROR. Le statut UNSUPPORTED_FORMAT est absent de cette liste. La table de gestion d'erreurs (§9) dit pour UNSUPPORTED_FORMAT : "Message clair 'format non pris en charge', pas de crash". Question non résolue : si le format n'est pas supporté pour l'analyse ML mais que le viewer iOS sait afficher le contenu, le contenu est-il affiché sans consentement ? Ou masqué ? - Impact : Un format d'image exotique que le viewer gère mais que le modèle ML ne gère pas pourrait être affiché sans aucune analyse ni consentement, contournant l'ensemble du système de protection.
- Gravité : Majeur
C-11 — Absence de test : désactivation globale + document déjà classifié sensible
- Type : Incohérence Spec↔Tests
- Référence : INV-86-14 ; TC-86-10 ; Spec §4.1 (ENABLED → DISABLED)
- Description : INV-86-14 stipule que
enabled=false empêche "toute nouvelle classification, sans supprimer les preuves". TC-86-10 vérifie l'absence de nouvelle classification. Mais aucun test ne vérifie le comportement à l'ouverture d'un document déjà classifié sensible quand la détection est désactivée. Le verdict en cache indique isSensitive=true : le modal s'affiche-t-il encore ? Le verdict est-il ignoré ? - Impact : Comportement indéterminé pour un scénario courant (désactiver la détection après avoir classifié des documents). L'utilisateur pourrait s'attendre à ce que la désactivation supprime les modals, ou au contraire les conserve. Learning PD-174 : distinguer "désactiver" vs "effacer" est critique.
- Gravité : Majeur
C-12 — Race condition : reclassification concurrente non spécifiée
- Type : Hypothèse dangereuse
- Référence : INV-86-13 ; Spec §5.1 (contrat module) ; Spec §3.1 (unicité documentId, modelVersion)
- Description : La spec ne traite pas la concurrence : un utilisateur ouvre le même document dans deux contextes simultanés (split view iPad), ou ouvre un document pendant qu'une reclassification (changement modelVersion) est en cours pour ce même document. Le contrat du module est décrit pour un appel unique. Aucune mention d'idempotence, de lock, ou de gestion de race condition sur le cache de verdict.
- Impact : Risque de double classification simultanée, corruption du cache de verdict, ou comportement non déterministe du gate viewer (verdict V1 vs V2 pendant la transition). Learning PD-105 : "PQueue ou mutex pour opérations atomiques concurrentes — évite race conditions".
- Gravité : Majeur
C-13 — Absence de test pour les bornes de seuils (0.50 et 0.95)
- Type : Incohérence Spec↔Tests
- Référence : Spec §3.1 (
threshold ∈ [0.50, 0.95]) ; aucun TC dédié - Description : Les seuils sont contractuellement bornés entre 0.50 et 0.95. Aucun scénario de test ne vérifie : (a) le rejet d'un seuil hors bornes (ex: 0.49 ou 0.96), (b) le comportement fonctionnel aux bornes exactes, © la validation à la saisie utilisateur.
- Impact : Un seuil invalide (0.00 ou 1.00) pourrait être accepté silencieusement, désactivant de facto la détection ou la rendant systématique.
- Gravité : Majeur
C-14 — InMemoryHandle non défini ; pas de gestion OOM
- Type : Ambiguïté
- Référence : Spec §5.1 Entrées (
decryptedContentHandle: InMemoryHandle) - Description : Le type
InMemoryHandle n'est défini nulle part dans la spécification. Aucune contrainte sur la taille maximale du contenu en mémoire. Pour une vidéo HD (plusieurs centaines de Mo en brut déchiffré), le chargement en mémoire pourrait provoquer un MemoryWarning iOS et un kill OOM. La spec ne prévoit pas de cas d'erreur OUT_OF_MEMORY dans l'enum analysisStatus, ni de stratégie de streaming partiel. - Impact : Crash potentiel non documenté pour les fichiers volumineux. L'enum d'erreurs est incomplète pour ce cas.
- Gravité : Mineur
- Type : Incohérence Spec↔Tests
- Référence : Matrice §1.1 (INV-86-01 couvert par TC-86-04, TC-86-18, TC-86-19, TC-86-20) ; TC-86-18
- Description : TC-86-18 est listé comme couvrant INV-86-01 ("transmission réseau interdite"). Le lien est ténu : la vérification réseau est un sous-résultat périphérique du test, dont l'objet principal est
UNSUPPORTED_FORMAT. De même TC-86-19 et TC-86-20 couvrent INV-86-01 uniquement via leur clause "pas d'envoi réseau". Cette couverture indirecte fragmente la vérification de l'invariant zero-knowledge. - Impact : Si ces tests sont modifiés pour se concentrer sur leur objet principal (gestion d'erreurs), la couverture de INV-86-01 se dégrade silencieusement. TC-86-04 reste le seul test dédié.
- Gravité : Mineur
C-16 — INV-86-10 (zeroization best-effort) sans critère d'acceptation
- Type : Non testable
- Référence : INV-86-10 ; TC-86-16 ; ST-86-01
- Description : Les tests eux-mêmes signalent (ST-86-01) que la zeroization best-effort est "partiellement non testable en black-box stricte". L'invariant est qualifié "best-effort", ce qui est honnête. Cependant, aucun critère d'acceptation (CA-86-XX) ne correspond à cet invariant. C'est le seul invariant sécurité sans CA associé.
- Impact : Sans CA, le critère de réussite est indéterminé. "Best-effort" sans définition du minimum acceptable rend la validation formelle impossible.
- Gravité : Mineur
C-17 — INV-86-12 (logs) : seuil de "reconstituabilité" non défini
- Type : Ambiguïté
- Référence : INV-86-12 ; TC-86-15 ; ST-86-02
- Description : Les tests signalent (ST-86-02) l'ambiguïté du terme "reconstituable". L'invariant interdit "ni pixel data, ni miniatures, ni métadonnées reconstituables du contenu". TC-86-15 évoque "règles de détection de fuite" sans les définir. Sans whitelist/blacklist de champs loggables, le test est subjectif.
- Impact : Un auditeur pourrait considérer un
documentId en log comme "reconstituable" (par corrélation avec le coffre), un autre le jugerait acceptable. Verdict dépendant de l'interpréteur. - Gravité : Mineur
C-18 — Scores arrondis Float(2d) : moment de l'arrondi vs décision
- Type : Hypothèse dangereuse
- Référence : Spec §3.1 (
roundedScores) ; Spec §3.2 (formule isSensitive) - Description : Les scores sont arrondis à 2 décimales dans le verdict persisté. La spec ne précise pas si la décision
isSensitive est prise AVANT ou APRÈS arrondi. Pour un score brut de 0.6999 avec seuil 0.70 : avant arrondi = non sensible, après arrondi (0.70) = sensible. L'incohérence potentielle entre le flag et les scores stockés rendrait un audit post-hoc contradictoire. - Impact : Résultats différents selon l'implémentation. Lors d'un audit, les scores arrondis pourraient ne pas justifier le verdict persisté.
- Gravité : Mineur
C-19 — Mécanismes système iOS d'aperçu non listés (exigence ouverte)
- Type : Non testable
- Référence : Spec §7 Sécurité ("Contrôle des mécanismes système pouvant générer des aperçus persistants non maîtrisés")
- Description : Cette exigence fait référence implicitement aux mécanismes iOS (app switcher screenshots, Spotlight indexation, iOS thumbnails cache, QuickLook). Aucune liste contractuelle de mécanismes à contrôler n'est fournie. Aucun CA ni test ne couvre cette exigence. iOS peut introduire de nouveaux mécanismes entre versions.
- Impact : Exigence non vérifiable par une équipe tierce. La liste est potentiellement non finie. Learning PD-248 : "stratégie robustesse non documentée → RESERVE".
- Gravité : Mineur
C-20 — ViewerGateDecision.requiresExplicitConfirmation : règle de calcul absente
- Type : Ambiguïté
- Référence : Spec §3.1 (
ViewerGateDecision) ; INV-86-06 - Description : Le champ
requiresExplicitConfirmation: Bool est défini dans l'entité ViewerGateDecision (événement mémoire) mais la spécification ne précise pas quelle logique détermine sa valeur. On devine qu'il dépend de context.isMinor et de isSensitive, mais le mapping entrées → valeur n'est pas explicité. - Impact : Champ structurel sans règle de calcul explicite. L'implémentation devra deviner l'intention.
- Gravité : Mineur
Synthèse
| Gravité | Nombre | Références |
| Bloquant | 4 | C-01, C-02, C-03, C-04 |
| Majeur | 9 | C-05, C-06, C-07, C-08, C-09, C-10, C-11, C-12, C-13 |
| Mineur | 7 | C-14, C-15, C-16, C-17, C-18, C-19, C-20 |
| Total | 20 | |
Points bloquants à résoudre
- C-01 — Définir contractuellement la stratégie d'échantillonnage PDF (nombre de pages, sélection).
- C-02 — Définir la priorité entre
suppressWarning et erreur d'analyse (INV-86-09 vs préférence utilisateur). - C-03 — Définir si
suppressWarning est autorisé/honoré pour un mineur (isMinor=true) — INV-86-06 vs §5.2. - C-04 — Spécifier le comportement d'invalidation du cache quand les seuils utilisateur changent (pas seulement
modelVersion).