Files
anonymisation/docs/superpowers/specs/2026-06-25-gui-v6-beta-prod-design.md
Domi31tls 6554a6d590 docs(beta): audit prêt-bêta GUI V6 + design de mise en prod bêta testeur
Audit prêt-pour-bêta de GUI V6 (3 axes : parcours/UX/PII, moteurs/OnnxTR/frozen,
licence/télémétrie/diffusion) → punch-list P0=7 / P1=11 / P2=6, findings vérifiés.

Design validé Dom : périmètre « A + chaîne prod », P0 + P1 honnêteté UI, toggles
« Données à détecter » câblés au moteur (gating par type + couplage rescan, pas de
plancher dur), licence souple + binding poste (D-20.4), −2 Go via excludes PyInstaller,
Linux-first TDD + un seul build Windows. Plus 3 exigences prod intégrées : diagnostics
logs auto-upload scrubbé (liste-blanche), mise à jour propre (installeur), modèles
embarqués hors-ligne. 6 chantiers (A-F), 2 repos. Diffusion = gate Dom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 16:43:04 +02:00

162 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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=<nom partagé avec P0-7>` 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.