---
from: claude (Agent B)
to: dom
date: 2026-06-05T19:30:00+02:00
topic: planB-d13-complet
status: open
priority: high
scope: PLANIFICATION uniquement — lecture seule, aucun code modifié
references:
  - plan: docs/coordination/inbox/for-dom/2026-06-05_claude_plan-v11-5-parallele.md (commit 57aa0f0)
  - decision: docs/coordination/decisions/2026-06-02_dom_d13-partial-scope.md
  - module: admin_mode.py
  - gui: Pseudonymisation_Gui_V5.py (2893 lignes, v5.4)
---

# Plan B — D-13 complet : protection des réglages avancés en mode non-admin (GUI v6)

> **Périmètre.** Sous-plan du chantier v11.5 (cap D-17). Définit les **règles**
> admin / non-admin à appliquer dans la GUI v6 (customtkinter). Ne contient aucun
> code ; l'implémentation attend le GO bêta (D-16) et se fait dans `gui_v6/` +
> extension `admin_mode.py`. Les **écrans** des sections « avancé » sont co-conçus
> avec l'Agent A (voir § Zone de contact A↔B).

## 0. Rappel du modèle de menace D-13

Le mode admin n'est **pas** un contrôle d'accès cryptographique : c'est un
« verrou anti-distrait » (cf. docstring `admin_mode.py`). Activation par
`ANON_ADMIN=1` ou fichier `.admin`. Deux objectifs distincts, à ne pas confondre :

1. **Anti-leak RGPD** (critique) — empêcher l'envoi de données hors poste.
   Déjà couvert par D-11 : VLM/Ollama **caché** en non-admin, et de toute façon
   `VlmManager=None` quand le module est neutralisé.
2. **Anti-dégradation qualité** (important, périmètre v11.5) — empêcher le
   bêta-testeur / utilisateur final de **casser la détection** en éditant des
   stopwords, profils techniques, regex, ou d'**écraser des fichiers de config
   de référence**. C'est l'objet de ce plan.

Conséquence : pour les réglages qui ne provoquent **pas** de fuite externe mais
peuvent **dégrader le masquage**, la bonne politique par défaut est
**griser/désactiver (visible mais verrouillé)** plutôt que **cacher**, pour rester
pédagogique. Exception : ce qui touche au leak externe se **cache**.

---

## 1. Inventaire exhaustif des réglages exposés

Source : balayage de `Pseudonymisation_Gui_V5.py`, `config/`, `config_defaults.py`,
`profile_defaults.py`.

### 1.A — Réglages UI (widgets actuels v5)

| # | Réglage | Widget v5 | Variable / méthode | Écrit dans |
|---|---|---|---|---|
| R1 | **Analyse visuelle VLM (Ollama)** | `Checkbutton` (l.769) | `self.use_vlm` / `_on_vlm_toggle` | aucun (runtime) |
| R2 | **Profil : « Désactiver le VLM »** | `Checkbutton` (l.1088) | `profile_force_disable_vlm_var` | `profiles.yml` |
| R3 | **Whitelist — phrases à NE PAS anonymiser** | `Listbox` + ajout/suppr (l.919) | `_wl_listbox` | `dictionnaires.yml` → `whitelist_phrases` |
| R4 | **Blacklist — mots à TOUJOURS masquer** | `Listbox` (l.928) | `_bl_listbox` | `dictionnaires.yml` → `blacklist.force_mask_terms` |
| R5 | **Stop-words additionnels** (ne jamais traiter comme nom) | `Listbox` (l.939) | `_sw_listbox` | `dictionnaires.yml` → `additional_stopwords` |
| R6 | **Profil actif** (sélection) | `Combobox` (l.1032) | `processing_profile_label_var` | lecture seule |
| R7 | **Profil : description** | `Entry` (l.1064) | `profile_description_var` | `profiles.yml` |
| R8 | **Profil : « Masque manuel obligatoire »** | `Checkbutton` (l.1077) | `profile_require_manual_mask_var` | `profiles.yml` |
| R9 | **Profil : masque PDF mémorisé** | `Combobox` (l.1109) | `manual_mask_template_var` | `profiles.yml` |
| R10 | **Créer / Renommer / Supprimer / Définir par défaut un profil** | Boutons (l.2040-2147) | `_create/_rename/_delete/_set_default_…profile` | `profiles.yml` |
| R11 | **Sauvegarder le profil courant** | Bouton (l.2147) | `_save_selected_processing_profile` | `profiles.yml` |
| R12 | **Masque manuel : template actif** (anonymisation) | `Combobox` (l.881) | `manual_mask_template_var` | runtime |
| R13 | **Éditeur de masques PDF** (designer) | Bouton (l.2306) | `_open_manual_mask_designer` | `config/mask_templates/` |
| R14 | **Sauvegarder les paramètres** (WL/BL/SW → YAML) | Bouton (l.2629) | `_save_params` → `_save_param_listboxes` | **`dictionnaires.yml` (écriture)** |
| R15 | **Exporter les paramètres** (→ JSON) | Bouton (l.2539) | `_export_params` | JSON sur disque (Bureau) |
| R16 | **Importer des paramètres** (JSON → listes) | Bouton (l.2596) | `_import_params` | listes en mémoire |
| R17 | **Dossier / fichier source** | sélecteur | `dir_var` / `_single_file` | runtime |

### 1.B — Réglages présents dans le **schéma de config** mais PAS exposés en UI v5

Importants à connaître car la GUI v6 pourrait vouloir les exposer (profils
techniques). Aujourd'hui chargés silencieusement (en-tête v5 : « Pas d'onglet
Avancé (NER + YAML chargés silencieusement) »).

| # | Réglage | Fichier / clé | Statut v5 | Sensibilité |
|---|---|---|---|---|
| S1 | **`regex_overrides`** (patterns + placeholders custom) | `dictionnaires.yml` → `regex_overrides[]` | non exposé UI | **technique sensible** (une mauvaise regex casse la détection ou plante) |
| S2 | **`blacklist.force_mask_regex`** | `dictionnaires.yml` | non exposé UI | technique sensible |
| S3 | **`whitelist.org_gpe_keep` / `sections_titres` / `noms_maj_excepts`** | `dictionnaires.yml` → `whitelist.*` | non exposé UI | technique sensible (peut désactiver le masquage d'établissements) |
| S4 | **`kv_labels_preserve`** | `dictionnaires.yml` | non exposé UI | technique sensible |
| S5 | **`flags.regex_engine` / `case_insensitive` / `unicode_word_boundaries`** | `dictionnaires.yml` → `flags` | non exposé UI | technique sensible |
| S6 | **`additional_villes_blacklist` / `additional_dpi_labels` / `additional_companion_blacklist`** | `dictionnaires.yml` | non exposé UI | modérée (qualité) |
| S7 | **`dictionaries_overlay`** par profil (surcharge YAML embarquée) | `profiles.yml` → `dictionaries_overlay` | partiellement (via BL profil) | **technique sensible** |
| S8 | **Choix du moteur NER** (GLiNER / CamemBERT-bio / EDS-Pseudo / ONNX) | aucun fichier UI ; chargé via `_auto_load_ner()` (l.473), managers l.437-439 | **non exposé** (silencieux) | **technique sensible** (désactiver un moteur dégrade le recall F1=0.963) |
| S9 | **Seuils NER** (`NerThresholds`) | `ner_manager_onnx.py` | non exposé UI | technique sensible |
| S10 | **Chemins config** (`cfg_path`, `profiles_path`, `MODELS_DIR`) | `DEFAULT_CFG`, `DEFAULT_PROFILES_CFG` | non exposé UI (pas de file picker) | sensible (réécriture d'un autre fichier) |

> Note : le **choix du moteur NER** (reporté à v11.5 selon D-13) n'a **aucune UI
> aujourd'hui**. L'exposer en v6 est une **création** d'écran, donc à protéger
> dès l'origine. Recommandation forte : **réservé admin**, et même en admin,
> exposer en lecture/diagnostic plutôt qu'en désactivation libre, pour ne pas
> permettre de couper un moteur et faire chuter le recall sans le vouloir.

### 1.C — Fichiers de config sensibles (cibles d'écriture)

| Fichier | Rôle | Écriture en v5 par | Politique non-admin |
|---|---|---|---|
| `config/dictionnaires.yml` | surcharge locale active (WL/BL/SW/regex) | R14 `_save_param_listboxes` | **bloquer l'écriture** |
| `config/dictionnaires.default.yml` | **source de vérité** | jamais (ne doit jamais l'être) | **bloquer (admin compris)** |
| `config/profiles.yml` | profils locaux | R10/R11 | **bloquer écriture** (lecture/sélection OK) |
| `config/profiles.default.yml` | source de vérité profils | jamais | **bloquer (admin compris)** |
| `config/admin_rules.yml` | règles d'admin candidates | (gouvernance) | **bloquer** |
| `config/mask_templates/`, `config/mask_templates` GUI | masques PDF | R13 designer | autorisé (non sensible PII) |
| Export JSON (Bureau) | échange par email | R15 | **autorisé** (sortie, pas d'écrasement config) |

---

## 2. Matrice admin / non-admin

Légende : **V** = visible, **É** = éditable, **S** = sauvegardable (peut écrire un fichier).
`—` = non applicable. `(cacher)` = absent de l'UI. `(grisé)` = visible mais désactivé.

| # | Réglage | non-admin V | non-admin É | non-admin S | admin V | admin É | admin S | Mode UI non-admin |
|---|---|:--:|:--:|:--:|:--:|:--:|:--:|---|
| R1 | VLM Ollama (case) | ❌ | ❌ | — | ✅ | ✅ | — | **cacher** (leak RGPD) |
| R2 | Profil « Désactiver VLM » | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | **cacher** (lié VLM) |
| R3 | Whitelist phrases | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | **grisé** (lecture) |
| R4 | Blacklist force-mask | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | **grisé** (lecture) |
| R5 | Stop-words additionnels | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | **grisé** (lecture) |
| R6 | Profil actif (sélection) | ✅ | ✅ | — | ✅ | ✅ | — | **actif** (choisir un profil pré-validé est sûr) |
| R7 | Profil : description | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | **grisé** |
| R8 | Profil : masque manuel obligatoire | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | **grisé** |
| R9 | Profil : masque PDF mémorisé | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | **grisé** |
| R10 | Créer/Renommer/Suppr/Défaut profil | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | **cacher** (écrit profiles.yml) |
| R11 | Sauvegarder le profil | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | **cacher** |
| R12 | Masque manuel actif (anonymisation) | ✅ | ✅ | — | ✅ | ✅ | — | **actif** (sécurité, ajoute du masquage) |
| R13 | Éditeur masques PDF | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | **actif** (n'augmente jamais le leak) |
| R14 | Sauvegarder paramètres → YAML | ❌ | — | ❌ | ✅ | — | ✅ | **cacher** (écrit dictionnaires.yml) |
| R15 | Exporter paramètres (JSON) | ✅ | — | ✅ | ✅ | — | ✅ | **actif** (sortie d'échange, pas d'écrasement config) |
| R16 | Importer paramètres (JSON) | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | **actif en mémoire**, mais **R14 bloqué** → l'import reste sans effet persistant en non-admin (voir § 3.3) |
| R17 | Dossier / fichier source | ✅ | ✅ | — | ✅ | ✅ | — | **actif** (cœur métier) |
| S1 | `regex_overrides` (si exposé v6) | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | **cacher** (profil technique) |
| S2 | `force_mask_regex` | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | **cacher** |
| S3 | `whitelist.*` techniques | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | **cacher** |
| S4 | `kv_labels_preserve` | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | **cacher** |
| S5 | `flags.*` (regex_engine…) | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | **cacher** |
| S6 | `additional_villes/dpi/companion` | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | **grisé** (qualité, lecture) |
| S7 | `dictionaries_overlay` profil | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | **cacher** |
| S8 | Choix moteur NER | ✅ (diag) | ❌ | ❌ | ✅ | ✅* | ❌* | **grisé/diagnostic** ; *même admin : lecture conseillée (voir § 3.4) |
| S9 | Seuils NER | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | **cacher** |
| S10 | Chemins config (file pickers) | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | **cacher** (ne pas exposer en v6 hors admin) |

**Principe de lecture de la matrice** :
- **Cacher** = réglage *leak-sensible* (VLM) ou *technique avancé* (regex/profil
  technique/moteur/seuils/chemins). Inutile et risqué de le montrer au bêta.
- **Griser** = réglage *qualité* visible à titre pédagogique (le bêta voit ce que
  l'admin a configuré) mais non modifiable / non sauvegardable.
- **Actif** = réglage qui *ne peut qu'augmenter la sécurité* (ajouter du masquage :
  masque manuel R12/R13) ou *cœur métier* (R6 sélection de profil validé, R17
  source), ou *sortie sans écrasement* (R15 export).

---

## 3. Règles UI et règles de sauvegarde

### 3.1 — Cacher vs désactiver/griser (décision par catégorie)

| Catégorie | Politique non-admin | Justification |
|---|---|---|
| **Leak externe** (VLM, force_disable_vlm) | **Cacher** | Ne doit pas exister dans l'UI bêta (D-11). |
| **Technique avancé** (regex_overrides, force_mask_regex, whitelist.*, flags, kv_labels, dictionaries_overlay, seuils NER, chemins config) | **Cacher** | Bruit pour le bêta + casse silencieuse de la détection. Regrouper dans un onglet/section « Profils techniques » entièrement masquée hors admin. |
| **Qualité éditable** (WL/BL/SW, descriptions, flags profil, villes/dpi/companion) | **Griser (read-only)** | Pédagogique : le bêta voit la config sans pouvoir la dégrader. |
| **Gestion de profils** (CRUD, sauvegarde profil) | **Cacher** | Écrit `profiles.yml`. |
| **Sécurité additive** (masque manuel actif, designer, sélection profil, source) | **Actif** | Ne réduit jamais le masquage. |
| **Échange** (export JSON) | **Actif** ; **import** actif en mémoire mais sans persistance (R14 bloqué) | Sortie pour email, pas d'écrasement de config. |

### 3.2 — Implémentation UI (customtkinter v6)

1. **Cacher** : ne pas instancier le widget/onglet quand `not is_admin()`.
   L'onglet/section « Profils techniques » et la section VLM ne sont **pas
   créés** hors admin (pas seulement `grid_remove`, pour éviter toute
   réactivation accidentelle).
2. **Griser** : créer le widget puis `configure(state="disabled")`. Pour les
   `Listbox`/listes éditables : désactiver les boutons +Ajouter / Supprimer et
   passer la liste en lecture seule ; afficher un bandeau discret
   « Réglages avancés en lecture seule — mode admin requis pour modifier ».
3. **Helper centralisé** (extension `admin_mode.py`) :
   - `admin_only_visible(widget)` → ne crée/affiche que si admin.
   - `admin_only_editable(widget)` → `state="normal"` si admin sinon `"disabled"`.
   - `guard_save(feature) ` → wrappe l'écriture (voir 3.3).
   Cela centralise la logique au lieu de la disperser dans la GUI (frontière
   Agent B : `admin_mode.py` + sections « avancé » de `gui_v6/`).
4. **Titre fenêtre** : conserver le tag `[⚙ MODE ADMIN]` (déjà en v5, l.383-384).
   En v6, ajouter une **bannière** persistante en mode admin (rouge/orange).

### 3.3 — Règles de sauvegarde (blocage de l'écriture des fichiers sensibles)

Le point dur de D-13 complet : **l'UI masquée ne suffit pas**. Il faut un garde
au niveau de l'**écriture** pour qu'aucun chemin (raccourci clavier, import +
save, futur bouton) ne puisse écraser un fichier sensible hors admin.

1. **Garde à la source** : toute méthode qui écrit un fichier de config sensible
   doit appeler `admin_required(...)` (déjà fourni par `admin_mode.py`) **avant**
   l'écriture. Cibles : `_save_param_listboxes` (R14 → `dictionnaires.yml`),
   `_save_selected_processing_profile`, `_create/_rename/_delete/_set_default…`
   (R10/R11 → `profiles.yml`), et toute future écriture de `regex_overrides`,
   `dictionaries_overlay`, `flags`, seuils.
2. **Liste blanche d'écriture** : définir dans `admin_mode.py` un ensemble
   `SENSITIVE_CONFIG_FILES = {dictionnaires.yml, dictionnaires.default.yml,
   profiles.yml, profiles.default.yml, admin_rules.yml, hospital_stopwords.yml,
   medical_terms_whitelist.yml}` + une fonction `assert_writable(path)` qui lève
   si `path` est sensible et `not is_admin()`. Appelée par tous les `write_text`
   de config. Filet de sécurité indépendant de l'UI.
3. **`*.default.yml` jamais réécrits** — même en admin. `assert_writable` refuse
   l'écriture des `*.default.yml` quel que soit le mode (sources de vérité).
4. **Import JSON (R16)** : autorisé à charger en mémoire (pas de fuite), mais le
   bouton **Sauvegarder (R14) étant caché/bloqué en non-admin**, l'import reste
   sans effet persistant. À documenter dans l'UI : « Import chargé. Sauvegarde
   réservée au mode admin. » Évite de laisser croire que la config est modifiée.
5. **Export JSON (R15)** : autorisé en non-admin — c'est une **sortie** vers le
   Bureau pour échange par email, pas un écrasement de config. (Cohérent avec le
   workflow « export → merge → renvoi YAML » des préférences projet.)

### 3.4 — Cas particulier : choix du moteur NER (S8)

Reporté à v11.5 par D-13 mais **sans UI existante**. Recommandation :
- Hors admin : **non exposé** (ou bandeau diagnostic en lecture seule listant les
  moteurs chargés : EDS-Pseudo / GLiNER / CamemBERT-bio / ONNX + état).
- En admin : exposer en **diagnostic** (voir/recharger) ; **déconseiller** une
  case « désactiver moteur X » librement, car couper un moteur fait chuter le
  recall (multi-signal F1=0.963). Si Dom veut le toggle, l'assortir d'un
  avertissement explicite « peut réduire la détection ». À trancher par Dom.

---

## 4. Tests attendus (matrice admin / non-admin)

Tests pilotables sans GUI réelle en testant les **helpers** + les **gardes
d'écriture** ; tests GUI en smoke (`gui_v6`). Cible : `tests/unit/test_d13_admin_*`.

### 4.A — `admin_mode` (logique)
- `is_admin()` : `ANON_ADMIN ∈ {1,true,yes,on}` → True ; vide/0 → False ;
  fichier `.admin` présent → True ; `force_refresh` re-évalue le cache.
- `admin_required("x")` : lève `RuntimeError` hors admin, ne lève pas en admin.
- `assert_writable(path)` (nouveau) :
  - fichier sensible + non-admin → lève ;
  - fichier sensible + admin → OK **sauf** `*.default.yml` → lève (toujours) ;
  - fichier non sensible (export JSON, mask_templates) → OK dans les deux modes.

### 4.B — Matrice par réglage (paramétrée admin ∈ {False, True})
Pour chaque réglage R1–R17 / S1–S10, asserter la cible de la matrice § 2 :

| Assertion | non-admin attendu | admin attendu |
|---|---|---|
| widget créé (visible) | selon col. « non-admin V » | « admin V » |
| widget `state` | `disabled` si grisé, absent si caché | `normal` |
| la sauvegarde écrit le fichier | **non** (lève / no-op) pour R10/R11/R14/S* | **oui** |
| `dictionnaires.yml` / `profiles.yml` non modifiés après tentative non-admin | hash fichier inchangé | modifié après save admin |

### 4.C — Non-régression (garde-fou n°1 du plan maître)
- `tests/unit` (98 passed) **restent verts** — D-13 ne touche pas le moteur.
- Audit `evaluate_quality.py` ≥ 98.5 ; leak score 100/100 inchangé.
- Smoke v6 : lancement non-admin → aucune section technique/VLM présente ;
  lancement admin (`ANON_ADMIN=1`) → sections présentes + bannière admin.

### 4.D — Test « anti-contournement »
- Simuler import JSON (R16) puis tentative de save (R14) en non-admin →
  `dictionnaires.yml` **inchangé**.
- Vérifier qu'aucun `write_text` sur un fichier sensible n'est atteignable hors
  `assert_writable` (revue : grep des `write_text` sur `config/` dans `gui_v6/`).

---

## 5. Impacts GUI v5 vs GUI v6

### GUI v5 (`Pseudonymisation_Gui_V5.py`) — **laisser tel quel**
- D-13 **partiel** est déjà livré et acté (VLM caché, titre admin). Conforme au
  gel bêta (D-16) : on ne re-patche pas 2893 lignes tkinter.
- **Aucune modification v5** dans ce chantier. (Si un hotfix MVP devenait
  nécessaire, il resterait hors périmètre v11.5.)

### GUI v6 (`Pseudonymisation_Gui_V6.py` / `gui_v6/`) — **lieu d'implémentation**
- D-13 **complet** s'implémente nativement à la construction de chaque écran v6,
  via les helpers `admin_mode` (§ 3.2). Pas de rétro-fit : la visibilité/édition
  est décidée **au moment de créer le widget**.
- Frontières (plan maître § 3) : Agent B possède `admin_mode.py` (extension :
  `assert_writable`, `SENSITIVE_CONFIG_FILES`, helpers UI) et les **règles** des
  sections « avancé » ; Agent A possède les écrans `gui_v6/`.
- Structure cible v6 (proposition) : un onglet **« Profils techniques »** + une
  section **VLM** entièrement **conditionnés à `is_admin()`** (non instanciés
  hors admin) ; la section **« Paramètres avancés »** (WL/BL/SW) **toujours
  visible** mais **read-only** hors admin.

---

## 6. Zone de contact Agent A ↔ Agent B (contrat à figer avant code)

Les écrans « Paramètres avancés » et « Profils techniques » de la GUI v6 sont
**co-conçus** : **B fournit les règles, A fournit les écrans**. Contrat proposé :

**Ce que B (ce plan) fournit à A :**
1. La **matrice § 2** (visible/éditable/sauvegardable par réglage et par mode).
2. Les **helpers** `admin_mode` (signatures) que A appellera :
   - `is_admin() -> bool`
   - `admin_only_visible(parent, build_fn)` — n'appelle `build_fn` que si admin.
   - `admin_only_editable(widget)` — applique `state`.
   - `assert_writable(path)` — à appeler avant toute écriture config.
3. La **liste des écritures à garder** (R10/R11/R14, futurs S1/S7/S5…).
4. La **convention de regroupement** : tout réglage « technique avancé »
   (S1–S5, S7, S9, S10, R2) dans **un seul** conteneur masquable d'un bloc.

**Ce que A fournit à B :**
1. Les conteneurs/onglets v6 nommés (où s'accrochent les sections « avancé »).
2. L'emplacement de la **bannière mode admin** (cohérence avec la bannière
   licence réservée à l'Agent C).
3. Le point d'appel unique des écritures de config (pour y placer
   `assert_writable`) afin d'éviter des `write_text` dispersés.

**À trancher par Dom :**
- S8 (toggle moteur NER) en admin : **diagnostic seul** (recommandé) ou toggle
  avec avertissement ?
- Import JSON (R16) en non-admin : garder l'import-en-mémoire (proposé) ou le
  cacher aussi ?

---

## 7. Synthèse des recommandations

1. **Deux politiques** : *cacher* le leak-sensible (VLM) et le technique avancé
   (regex/profils techniques/moteur/seuils/chemins) ; *griser* le qualité
   (WL/BL/SW) ; *laisser actif* l'additif-sécurité (masques) et l'export.
2. **Garde d'écriture indépendante de l'UI** (`assert_writable` +
   `SENSITIVE_CONFIG_FILES`) : filet de sécurité contre tout contournement.
   `*.default.yml` jamais réécrits, même en admin.
3. **GUI v5 inchangée** ; tout dans `gui_v6/` + extension `admin_mode.py`.
4. **Tests** : matrice paramétrée admin/non-admin + anti-contournement + 98 tests
   moteur verts (garde-fou non négociable).
5. **Contrat A↔B** figé avant tout code (helpers + matrice + points d'écriture).
6. **Attente GO bêta (D-16)** avant tout codage — ce document est de la
   planification pure.

— Claude (Agent B)
