From 6554a6d590add522de8eade833fb25622b90d38d Mon Sep 17 00:00:00 2001 From: Domi31tls Date: Thu, 25 Jun 2026 12:02:09 +0200 Subject: [PATCH] =?UTF-8?q?docs(beta):=20audit=20pr=C3=AAt-b=C3=AAta=20GUI?= =?UTF-8?q?=20V6=20+=20design=20de=20mise=20en=20prod=20b=C3=AAta=20testeu?= =?UTF-8?q?r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../beta/2026-06-25_audit-pret-beta-gui-v6.md | 161 ++++++++++++++++++ .../2026-06-25-gui-v6-beta-prod-design.md | 161 ++++++++++++++++++ 2 files changed, 322 insertions(+) create mode 100644 docs/beta/2026-06-25_audit-pret-beta-gui-v6.md create mode 100644 docs/superpowers/specs/2026-06-25-gui-v6-beta-prod-design.md diff --git a/docs/beta/2026-06-25_audit-pret-beta-gui-v6.md b/docs/beta/2026-06-25_audit-pret-beta-gui-v6.md new file mode 100644 index 0000000..e3b7f84 --- /dev/null +++ b/docs/beta/2026-06-25_audit-pret-beta-gui-v6.md @@ -0,0 +1,161 @@ +# 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. diff --git a/docs/superpowers/specs/2026-06-25-gui-v6-beta-prod-design.md b/docs/superpowers/specs/2026-06-25-gui-v6-beta-prod-design.md new file mode 100644 index 0000000..d8f9108 --- /dev/null +++ b/docs/superpowers/specs/2026-06-25-gui-v6-beta-prod-design.md @@ -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=` 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.