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>
This commit is contained in:
2026-06-25 12:02:09 +02:00
parent 4b7c8db9a8
commit 6554a6d590
2 changed files with 322 additions and 0 deletions

View File

@@ -0,0 +1,161 @@
# 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.