# GUI V6 → prod bêta testeur — design **Date** : 2026-06-25 · **Branche** : `feature/q1-quarantine-mvp` · **HEAD de départ** : `4b7c8db` **Statut** : design validé Dom (cadrage + décisions ci-dessous), prêt pour plan d'implémentation. ## 1. Objectif Amener **GUI V6** (`gui_v6/`) à une **mise en prod bêta testeur** complète : un testeur non-technique installe l'EXE Windows → **active sa licence** → traite des PDF/images médicaux → ses **statistiques d'usage remontent au portail** `app.aivanov.eu`, et l'équipe peut **récupérer ses logs de diagnostic** (scrubbés) pour analyse. Le CLI est mis de côté (pas abandonné) ; il partage le même core, donc les fixes cœur lui profitent. ## 2. Périmètre **Dans le périmètre (décisions Dom) :** - App finie : correction des 7 P0 + des P1 qui trompent le testeur + UI honnête de bout en bout. - Rebuild Windows OnnxTR / **torch-free** (gain ~2 Go) + smoke. - Chaîne prod : activation licence bout-en-bout + télémétrie qui remonte réellement + diffusion testée. - **Diagnostics** : log fichier V6 + scrubbing PII (philosophie liste-blanche) + **auto-upload portail**. - **Mise à jour propre** : version réelle injectée au build, upgrade en place, app fermée, config préservée. - **Modèles embarqués dans l'EXE** : présents automatiquement à l'install, hors-ligne (déjà le cas, à fiabiliser). **Hors périmètre (différé, acté) :** - Enforcement licence **dur** (blocage), anti-VM (D-20.2/3), fingerprint multi-composants (D-20.1), vérification de signature RSA côté client + clé prod stable (P1-9). - **MAJ in-app auto-download** (l'app télécharge+lance l'installeur) — la MAJ bêta se fait par téléchargement manuel de l'installeur depuis le portail. - Onboarding profond : install validée sur site testeur, boucle de retour structurée. - DnD réel = **inclus** (voir D) ; tout le reste P1 non listé + P2 non listés = itération ultérieure. ## 3. Décisions clés (et pourquoi) | # | Décision | Justification | |---|----------|---------------| | D1 | **Licence souple + binding poste** : activation câblée, statut affiché, vérif locale `machine_id` (D-20.4), **pas de blocage** du traitement. | Valide la chaîne sans bloquer un testeur hors-ligne/mal activé. Durcissement post-bêta. | | D2 | **Toggles « Données à détecter » câblés pour de vrai**, défaut tous ON. | Un contrôle visible non fonctionnel **fausse le test humain**. | | D3 | **Pas de plancher dur** : décocher une catégorie relâche masquage **et** filet de sécurité pour cette catégorie ; l'utilisateur assume. | Sinon le toggle reste faussé (quarantaine systématique). `EMAIL/IBAN/IPP` (sans toggle) restent toujours masqués = plancher conservé. | | D4 | **−2 Go via `excludes` PyInstaller** ; validation par **taille EXE réelle**. | `optimum[onnxruntime]` tire `torch` en dép cœur ; legacy non câblé (import déjà sous try/except). | | D5 | **Linux-first TDD, un seul build Windows à la fin** ; P0-5 porte à l'identique le hotfix CLI `6c6f653`. | Minimise les cycles de build chers. | | D6 | **Rebuild = autonome** (build interne + smoke) ; **diffusion (republish portail) = gate Dom**. | Cadrage d'autonomie. | | D7 | **Logs auto-upload scrubbé** : log fichier V6 rotatif + diagnostics **structurés liste-blanche** (type d'exception, étape, code) + redaction ciblée des identifiants de doc connus, envoyés au portail (endpoint `/diagnostics`, auth licence, non bloquant). | Récupération auto pour analyse sans déranger le testeur ; RGPD garanti par la **même philosophie que la télémétrie** (whitelist), pas par un scrub a posteriori d'un log brut. | | D8 | **MAJ = installeur propre** : version réelle injectée au build, AppId fixe (upgrade en place), `CloseApplications`, config/licence préservées (hors dossier install). | Suffit en bêta ; MAJ in-app auto-download différée. | ## 4. Chantiers ### A. Sûreté & robustesse cœur - **P0-1 — Fail-close GUI sur NER indisponible.** `gui_v6/engine_bridge.py:make_process_fn` (210-233) : capturer l'état de `ensure_loaded()` ; si `use_local_ner` et état `UNAVAILABLE` (CamemBERT-bio non chargé, `:173-175`) → lever `EngineUnavailableError`. Le runner la remonte en **échec** (doc non livré). Réplique la garantie code-3 du CLI (`scripts/anonymize_cli.py:184-198`). *Test* : factory CamemBERT qui throw → 0 fichier en sortie + statut échec. - **P0-5 — Frozen ONNX.** `Pseudonymisation_Gui_V6.py:main` (50-58, entrée frozen confirmée `anonymisation_gui_v6_onefile.spec:145`) pose `os.environ.setdefault("ANON_SKIP_LEGACY_ONNX_MANAGER","1")` **avant** `from gui_v6.app import AnonymisationApp` (l.55), et pré-charge CamemBERT. *Test* : ordre d'init unitaire ; **confirmation au smoke EXE** (non reproductible en dev Linux). - **P0-7 — Lock multi-instance.** File-lock `msvcrt.locking` (Windows) au début de `main()`, message « application déjà ouverte ». Le nom du mutex est **partagé avec l'installeur** (D8/AppMutex). *Test* : unit mock. ### B. Chaîne prod — licence + télémétrie - **P0-2 — URL portail.** `DEFAULT_PORTAL_URL = "https://app.aivanov.eu"` injectée à l'entrypoint (override `ANON_PORTAL_URL`) ; `gui_v6/app.py:41` `localhost` devient secours dev. Télémétrie **et diagnostics** héritent de l'URL (`app.py:199`). *Test* : entrypoint construit le client sur l'URL prod. - **P0-6 — Licence souple + binding (D1).** Activation câblée bout-en-bout. Au démarrage : `local_status()` + **comparer `payload.machine_id` au `machine_id` local** ; si mismatch → statut « licence liée à un autre poste » **affiché**, traitement **non bloqué**. *Test* : `license.json` copié (machine_id ≠ local) → statut invalide, traitement fonctionne. ### C. Build torch-free reproductible + modèles embarqués - **P0-3 — −2 Go (D4).** `excludes=['torch','torchvision','optimum','optimum.onnxruntime','doctr']` aux 3 specs ; `optimum[onnxruntime]` en optionnel commenté dans `requirements.txt`. **Validation = taille EXE réelle + grep arbo PyInstaller pour `torch`**. - **P0-4 — Précache OnnxTR + modèles embarqués (#2).** Étape avant PyInstaller dans `scripts/build_windows_oneclick.ps1` : `ocr_predictor(det_arch='db_resnet50', reco_arch='crnn_vgg16_bn')` (télécharge les 2 poids). Confirme que le spec embarque bien CamemBERT-bio ONNX + poids OnnxTR (`anonymisation_gui_v6_onefile.spec` `datas`) → **modèles présents à l'install, hors-ligne, sans téléchargement** (aucun chemin de download runtime dans `gui_v6/` — vérifié). Doc build mise à jour. ### D. Honnêteté UI (anti-faussé) - **P1-2 — Toggles « Données à détecter » câblés (D2/D3).** 7 booléens (Noms→`NOM`, DDN→`DATE_NAISSANCE`, Établissements→`ETAB`, Adresses→`ADRESSE`, NIR→`NIR`, Téléphones→`TEL`, N° adhérent→`ADHERENT`), défaut tous ON, fil : `ConfigState` → `EngineSettings` → kwargs → `process_document` → `cfg` (core:2934/5678). Le moteur **gate le masquage par type** ; une catégorie décochée n'est plus masquée. **Couplage sécurité (D3)** : pour les catégories décochées du rescan résiduel (`CRITICAL_PII_KEYS`, core:610 — NIR, TEL, DATE_NAISSANCE), le **rescan est relâché** (pas de fausse quarantaine). `EMAIL/IBAN/IPP` restent toujours masqués. *Tests* : (a) décocher `ETAB` → établissement non masqué, autres masqués ; (b) décocher `NIR` → NIR en clair + pas de quarantaine ; (c) tous ON → non-régression. **Revue Qwen obligatoire (code sécurité).** - **P1-1 — Glisser-déposer réel** via `tkinterdnd2` (`tab_usage.py:103-115`) + boutons conservés. - **P1-3 — Import/Export config câblés** (`tab_config.py:817-840`), logique merge V5 (`scripts/merge_params.py`). - **P1-4 — `config_path` frozen** : résoudre `dictionnaires.yml` à côté de `sys.executable`, copie au 1er lancement, passé en `config_path`. - **P1-5 — Erreurs/quarantaine lisibles** : chemin sortie + mention `_*_failed/` + bouton « ouvrir le dossier ». - **P2-1 / P2-2 (bonus)** : étapes de progression et cartes format **honnêtes** (fonctionnelles ou visiblement non-cliquables). ### E. Diagnostics & logs (#1, D7) - **E1 — Log fichier V6.** Configurer un log loguru rotatif à chemin connu (`%LOCALAPPDATA%/Aivanov/Anonymisation/logs/anonymisation.log`) dans `Pseudonymisation_Gui_V6.py` **avant** tout import (combiné à l'ordre P0-5). Aujourd'hui V6 n'a **aucun** log fichier → en frozen fenêtré les diagnostics sont perdus. *Test* : le log est créé au chemin attendu. - **E2 — Diagnostics structurés liste-blanche (RGPD).** Un module `diagnostics` qui produit des **événements structurés** (type d'exception, étape moteur, code, durée, version, `run_id`, `machine_id`) — **jamais** de texte de document ni de contenu d'erreur libre. Les identifiants de documents (noms/chemins connus en entrée) sont **redactés** (→ ordinal/hash) partout. Même philosophie que `usage_telemetry._ALLOWED_*`. *Tests* : un run avec noms de fichiers PII → 0 nom/chemin/texte dans la charge utile diag. - **E3 — Upload auto.** Client diagnostic (réutilise l'auth licence/`machine_id` comme la télémétrie), `POST /api/v1/diagnostics/report`, **non bloquant** (thread daemon, spool best-effort, timeout court). Hérite de l'URL portail (P0-2). *Test* : échec réseau → traitement non figé, spool écrit. - **E4 — Portail (`app_aivanov`, repo séparé).** Endpoint `/api/v1/diagnostics/report` (Pydantic strict, auth licence/seat, recalcul/validation serveur), stockage, vue admin « Diagnostics par client ». Migration alembic. *Tests web.* **Revue Qwen RGPD.** ### F. Release, mise à jour & diffusion - **P1-7 + D8 — Version & installeur.** Injecter la vraie version release (schéma `2026.MM.DD.HHMM`, depuis `build_info`) dans `gui_v6/__init__.py` **et** dans `installer/Anonymisation.iss` (`AppVersion`, `VersionInfoVersion`) — aujourd'hui codée en dur `"1.0.0"` / `"6.0.0-g1"` (3 schémas incohérents). - **D8 — MAJ propre.** `Anonymisation.iss` : AppId fixe déjà présent (upgrade en place) ; ajouter `CloseApplications=yes` + `AppMutex=` pour fermer l'app avant remplacement de l'EXE. Licence/config en `%LOCALAPPDATA%/Aivanov/Anonymisation/` (hors `{app}`) → **préservées** (vérifié). *Test/checklist* : installer N+1 sur N préserve licence+config, version affichée correcte. - Rebuild Windows GUI V6 (192.168.1.11 via ssh-windows) ; **smoke** : `--self-test` ; 1 PDF natif ; 1 PDF scanné (`ocr_used=True`) ; **fail-close vérifié** ; **taille EXE mesurée** (−2 Go ?) ; cycle MAJ N→N+1 testé. - **Runbook** `docs/beta/runbook-portail-beta.md` finalisé (+ étape « vérifier URL portail embarquée ») ; **republish portail + MAJ SHA dans `note-beta-client.md` = gate Dom**. ## 5. Stratégie de tests & non-régression - TDD sur tout le testable Linux : nouveaux `tests/unit/test_gui_v6_*` + extension core (gating par type) + tests diagnostics/scrubbing. Non-régression suite existante (222+ tests) + gate `synthetic_regression`. - Revue **Qwen** sur les fixes sécurité (P0-1, P0-6, P1-2 gating+rescan) **et RGPD** (F2/F4 diagnostics). - Smoke EXE pour les concerns frozen-only (P0-4, P0-5, taille, cycle MAJ). ## 6. Critères d'acceptation (definition of done) 1. Aucune sortie produite si le NER obligatoire échoue (fail-close GUI = CLI). 2. Activation licence fonctionne contre `app.aivanov.eu` ; binding correct ; **non bloquant**. 3. Télémétrie d'un run réel **visible au portail** (RGPD : compteurs only). 4. Les 7 toggles modifient **réellement** la sortie ; tous ON = non-régression ; décoché = relâché. 5. Import/export, DnD, messages d'erreur : **fonctionnels** (zéro contrôle factice). 6. **Diagnostic** d'un run **visible au portail**, contenant **0 nom/chemin/texte de document** (RGPD vérifié). 7. **Modèles** présents dans l'EXE : OCR + NER fonctionnent **hors-ligne** post-install, sans téléchargement. 8. **MAJ N→N+1** : upgrade propre, licence + config préservées, app fermée pendant la MAJ, version correcte. 9. EXE rebuildé : self-test OK, OCR scanné OK, **taille −~2 Go confirmée**, 0 « cannot load module ». 10. Suite verte, 0 régression, revue Qwen GO (sécurité + RGPD). 11. Diffusion portail = **sur GO Dom** uniquement. ## 7. Risques & points différés - **R1** — P1-2 touche le cœur sécurité : risque de régression de masquage. Mitigation : TDD + gate `synthetic_regression` + revue Qwen ; défaut tous-ON identique à l'existant. - **R2** — Binding `machine_id` MAC-seule **faible** (contournable, fragile en VM). Accepté en souple ; durcissement (D-20.1) différé post-bêta — **signalé**. - **R3** — P0-5 (crash frozen) non confirmable en dev : **dépend du smoke EXE**. Mitigation : portage identique du hotfix CLI connu-bon. - **R4** — Sans signature vérifiée côté client (différé), le binding `machine_id` est éditable à la main : protège de la copie naïve, pas d'un utilisateur déterminé. Acceptable bêta. - **R5** — **RGPD diagnostics** : un log technique peut fuiter de la PII. Mitigation = événements **structurés liste-blanche** (pas de texte libre) + redaction des identifiants de doc + revue Qwen RGPD + défaut conservateur (dans le doute, on ne remonte pas la ligne). C'est le risque RGPD principal du lot. - **Différé** : MAJ in-app auto-download, enforcement dur, anti-VM, signature client, reste P1/P2.