# Audit prêt-pour-bêta — GUI V6 (HEAD `4b7c8db`, 2026-06-25) Périmètre : GUI V6 (`gui_v6/`) + chaîne prod (licence, télémétrie, diffusion) pour une mise en bêta testeur. Cible : un testeur installe l'EXE Windows → active sa licence → traite des PDF/images médicaux → ses stats remontent au portail. Audit en 3 axes (parcours/UX/PII, moteurs/OnnxTR/frozen, licence/télémétrie/diffusion). Findings vérifiés dans le code (lignes citées) ; les 2 plus lourds recontrôlés par Claude. **À FAIRE par Dom : valider / élaguer cette liste avant tout code (cocher / barrer).** --- ## P0 — Bloqueurs bêta (à corriger avant toute diffusion) ### P0-1 — Fuite PII silencieuse : la GUI ne fait pas fail-close si le NER obligatoire échoue - `gui_v6/engine_bridge.py:222-231` appelle `ensure_loaded()` sans vérifier l'état ; si CamemBERT-bio ne charge pas (`:173-175` → `UNAVAILABLE`, `camembert=None`), le traitement continue en **regex + gazetteers seuls** et le document sort compté « Réussi ». Le rescan résiduel ne couvre PAS les noms de personnes. - Le CLI de prod fait l'inverse : `scripts/anonymize_cli.py:184-198` fail-close (code 3, aucune sortie) si le modèle obligatoire manque. - **Impact** : modèle ONNX corrompu/absent (antivirus, ressource manquante) → PDF qui paraît traité, statut OK, mais noms patients potentiellement en clair. **Risque #1.** - **Fix** : dans `process_fn`, si `use_local_ner` et état `UNAVAILABLE` → lever une exception explicite (refus de traitement), le runner la remonte en échec. Aligne la GUI sur la garantie code-3 du CLI. ### P0-2 — Chaîne prod cassée : la GUI pointe `http://localhost`, jamais le portail réel - `Pseudonymisation_Gui_V6.py:57` → `AnonymisationApp()` sans client → `gui_v6/app.py:41` `LicenseClient("http://localhost")`. La télémétrie réutilise cette URL (`app.py:199`). Aucune env var / config / injection build ne fixe l'URL portail (grep exhaustif = 0). - **Impact** : sur un poste testeur, activation licence ET télémétrie POSTent sur `localhost` → rien n'écoute → **activation impossible, stats jamais remontées**. Toute la « chaîne prod » demandée est non fonctionnelle. Le portail tourne sur `app.aivanov.eu` (vérifié live, HTTPS 200, artefact servi). - **Fix** : injecter l'URL réelle (`DEFAULT_PORTAL_URL="https://app.aivanov.eu"` ou env var), `localhost` devient le secours dev. **Impose un rebuild** (l'EXE déjà publié est inutilisable pour l'activation). ### P0-3 — Gain −2 Go non réalisé : `optimum` réintroduit torch en dur - `requirements.txt:3` = `optimum[onnxruntime]>=2.0.0` ; optimum déclare `torch>=1.11` en dépendance **cœur** (vérifié). Le build installe requirements → torch dans le venv → PyInstaller le bundle malgré le retrait des hiddenimports (`4b7c8db`). EXE ~+2 Go. - `optimum` n'est importé que par `ner_manager_onnx.py` (legacy ONNX **non câblé** à la GUI), `Pseudonymisation_Gui_V5.py` (V5 legacy) et un script de finetune — **jamais par `gui_v6/`** (le NER GUI = `camembert_ner_manager` en onnxruntime brut). - **Fix** : retirer `optimum` (et hiddenimports `optimum*`) du build frozen GUI ; valider « torch-free » par la **taille EXE réelle** + grep de l'arbo PyInstaller, pas par le diff du spec. ### P0-4 — Build non reproductible : pas de précache des poids OnnxTR - `anonymisation_gui_v6_onefile.spec:50-67` **raise** `FileNotFoundError` si les poids `db_resnet50` + `crnn_vgg16_bn` absents du cache ; `scripts/build_windows_oneclick.ps1` ne télécharge jamais ces poids. Le build « marche » seulement grâce au cache résiduel de la machine 192.168.1.11. - **Fix** : étape pré-PyInstaller : `python -c "from onnxtr.models import ocr_predictor; ocr_predictor(det_arch='db_resnet50', reco_arch='crnn_vgg16_bn')"` + doc. ### P0-5 — Crash frozen ONNX probable : le hotfix CLI `6c6f653` non reporté au launcher GUI - Le launcher V6 ne pose pas `ANON_SKIP_LEGACY_ONNX_MANAGER=1` (lu à l'import du core, `anonymizer_core_refactored_onnx.py:211`) et ne pré-charge pas CamemBERT — alors que le hotfix CLI (`scripts/anonymize_cli.py:87`) le fait pour éviter « cannot load module more than once per process » en frozen Windows. - **Fix** : poser le flag tout en haut de `Pseudonymisation_Gui_V6.py` + pré-charger CamemBERT avant le 1er import du core. **À confirmer sur l'EXE** (non reproductible en dev). ### P0-6 — Contrôle de licence purement décoratif (décision Dom requise) - Enforcement nul : `tab_usage.py:205` active « Lancer » selon le nombre de documents seulement ; aucune gate sur le statut licence (lu pour affichage uniquement). - Pas de vérif locale : `license_client.py:208-213` lit `license.json` **sans vérifier signature ni `machine_id`** → fichier copiable sur N postes = N licences « actives ». - `machine_id` = MAC seule 12 chars (`machine_id.py:12-14`) = clonable, change en VM. - D-20.1 (fingerprint multi-composants) et D-20.4 (vérif locale machine_id) **non implémentés**. La bêta est une fenêtre idéale (aucune activation prod existante à migrer). - **Décision Dom** : niveau d'enforcement bêta (souple / dur / différé) — voir question. ### P0-7 — Pas de protection multi-instance (lock perdu vs V5) - `Pseudonymisation_Gui_V6.py:50-59` lance `mainloop()` sans file-lock `msvcrt.locking` (présent en V5). Double-clic testeur → 2 instances, 2 chargements ONNX, 2 écritures dans le même dossier sortie. - **Fix** : réintroduire le lock V5 + message « application déjà ouverte ». --- ## P1 — Forte friction testeur / écarts (à arbitrer pour la bêta) - **P1-1 — Dropzone décorative** (`tab_usage.py:103-115`) : zone « déposez vos fichiers » sans glisser-déposer réel. Confusion dès la 1re étape. → câbler DnD (tkinterdnd2) ou retirer la métaphore. - **P1-2 — 7 toggles « Données à détecter » non câblés** (`tab_config.py:351-357`) : les interrupteurs (Noms, DDN, Établissements…) n'ont ni variable ni callback → aucun effet. Promesse UI non tenue. → câbler au moteur ou rendre informatifs (lecture seule). - **P1-3 — Import/Export config désactivés « à venir »** (`tab_config.py:817-840`) alors que le Partage est mis en avant. Régression vs workflow email V5. → câbler ou retirer. - **P1-4 — `config_path` jamais transmis** (`app.py:155-162`, `tab_usage.py:53,176`) : le `dictionnaires.yml` externe éditable n'est pas garanti chargé en frozen (pas de copie au 1er lancement). Personnalisations établissement potentiellement ignorées. - **P1-5 — Erreurs/quarantaine sans pointeur** (`tab_usage.py:268,282`) : « échec » / « quarantaine » sans indiquer où est le doc non livré (`_*_failed/`). → afficher le chemin sortie + bouton « ouvrir le dossier ». - **P1-6 — Pas de validation d'inscriptibilité du dossier de sortie** (`processing_runner.py:214`) : dossier en lecture seule → tout le lot échoue par doc avec message cryptique. → test d'écriture en amont. - **P1-7 — Version app incohérente** : `gui_v6/__init__.py:11` = `6.0.0-g1` vs artefact/note bêta `2026.06.18.1203` (remonte aussi en télémétrie). → aligner sur le schéma de release. - **P1-8 — Runbook non exécutable tel quel** : republier impose un rebuild après P0-2 (le SHA de `note-beta-client.md` deviendra caduc). → ajouter étape « vérifier URL portail embarquée » au runbook. - **P1-9 — Clé de signature licence = clé dev auto-générée** (`app_aivanov/app/signing.py`) : KEY_ID « dev », paire RSA régénérée si volume recréé → licences invalidées. → provisionner une clé prod stable avant d'activer la vérif de signature côté client. - **P1-10 — Politiques VM (D-20.2) / dossiers partagés (D-20.3) non implémentées** (décision Dom, marquées « DECISION REQUIS » dans D-20). Acceptable de différer en bêta interne — à acter. - **P1-11 — Audit JSONL `original="docTR"`** (`anonymizer_core_refactored_onnx.py:5356`) : trace de provenance périmée (OCR = OnnxTR) visible dans le livrable d'audit. → `"OnnxTR"`. --- ## P2 — Polish (best-effort) - **P2-1 — Étapes de progression statiques** (`tab_usage.py:144-147`) : pastilles Extraction/Détection/Masquage/PDF décoratives ; pas de feedback intra-document. - **P2-2 — Cartes format de sortie décoratives** (`tab_usage.py:118-127`) : suggèrent un choix alors que la sortie est toujours PDF+TXT. → présenter comme « sorties produites ». - **P2-3 — Commentaires/docstrings docTR périmés** (`anonymizer_core_refactored_onnx.py:55,102`) : cosmétique, comportement OK. - **P2-4 — `os_info` non envoyé à l'activation** (`license_client.py:161-164`) : perte d'info diagnostique mineure. - **P2-5 — Contact / canal de retour vide** dans `docs/beta/note-beta-client.md:36`. - **P2-6 — `/api/v1/version` ne déclenche pas de download** (`tab_about.py:164-178`) : le testeur va manuellement sur le portail. Acceptable bêta. --- ## Verdicts positifs (ne pas re-traiter) - **Télémétrie RGPD : irréprochable.** Double filtrage liste-blanche (`usage_telemetry.py:72`, `_ALLOWED_DOC_KEYS`) + recalcul serveur Pydantic strict. Aucun nom/chemin/texte ne peut fuiter. Non bloquante (thread daemon, timeout 4 s, spool best-effort). App utilisable hors-ligne. - **Pipeline cœur fail-closed correct** : quarantaine moteur → échec, `SEUIL_RESCAN_RESIDUEL=0`, `redact_pdf_raster` pas dans un `except: pass`. La fuite P0-1 vient du **wiring GUI**, pas du cœur. - **engine_bridge / affichage moteurs** : logique « n'afficher que les moteurs embarqués » saine et cohérente avec OnnxTR (OCR) + CamemBERT-bio (NER onnxruntime). 0 référence docTR dans les chaînes affichées à l'utilisateur. - **Câblage `ONNXTR_CACHE_DIR` frozen** : chemins concordants, fail-closed si OnnxTR absent. - **Portail déployé et fonctionnel** : `app.aivanov.eu` HTTPS, artefact servi, download authentifié (licence active requise). Le serveur n'est pas le problème — le client n'y est pas connecté. --- ## Décompte : P0 = 7 · P1 = 11 · P2 = 6 ## Synthèse pour décision Le « complet pour prod bêta » est **plus que rebuild + ship** : la chaîne prod est aujourd'hui **non fonctionnelle bout-en-bout** (P0-2 localhost, P0-6 licence décorative), il y a un **risque PII #1** (P0-1 fail-open), et le **−2 Go n'est pas acquis** (P0-3 optimum). Rien de rédhibitoire : findings nets, fixes ciblés. C'est un vrai chantier de finition + câblage, pas une réécriture.