Files
rpa_vision_v3/docs/PLAN_ACTION_SUITE_2026-06-23.md
Dom 6907ecc82f docs: track design docs, plans, audits, coordination infrastructure, handoffs
- 21 docs/*.md: audits, design notes, deployment plans, checklists, memos
- Coordination: ROLES, runbooks (DGX reboot, Lea live), patches, registre, syntheses, systemd, QG template
- Handoffs: 6 Codex handoff documents + README + template
2026-07-02 13:29:58 +02:00

206 lines
20 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.
# Plan d'action — la suite (post-livraison DGX clinique)
- `Date`: 2026-06-23
- `Auteur`: Claude (mandat Dom)
- `Statut`: vivant — à mettre à jour au fil des validations
- `Portée`: **chapeaute** les plans existants (ne les remplace pas), dédoublonne et priorise les actions **encore ouvertes** après la livraison clinique du jour.
> Contexte : le DGX part **définitivement** en clinique aujourd'hui (`192.168.1.178`), puis travail **100 % à distance** (VPN Stormshield + scp). Le jour J lui-même est couvert par `MEMO_JOUR_J_LIVRAISON_DGX_CLINIQUE_2026-06-23.md` — non répété ici.
---
## 0. Deux clarifications de cadrage (lire avant le reste)
1. **Les gaps « pré-clinique » sont clos.** L'audit (`AUDIT_GAPS_APPLI_100PCT_2026-06-10`) et le postmortem (`POSTMORTEM_PANNE_SECTEUR_DGX_2026-06-20`) listaient des gaps durs (OVMF G2, IP statique G1, reconnexion Léa G4, reboot 11 services). Le `MEMO_JOUR_J` + `TABLEAU_ACTIONS_DOM_PRECLINIQUE_2026-06-21` confirment qu'ils sont **résolus** (watchdog OVMF LIVE testé, `.178` réservé DHCP, installateur autoportant). → **sortis de ce plan.**
2. **Accès distant = multi-VPN par site (correction Dom 23/06).** Pas « WireGuard caduc » : **WireGuard = notre VPN** (labo/éditeur, reste valide) ; **Stormshield = le VPN de la clinique** (côté client) ; d'autres clients auront **d'autres VPN**. ⇒ l'accès distant devient une **fonctionnalité paramétrée par fiche site** (`INSTALLATION_MULTI_SITE`), avec **SSH cert-only + RDP comme transport commun** au-dessus du VPN propre à chaque site. Le `PLAN_ACCES_DISTANT_SSH_CERT_DGX_2026-06-20` (WireGuard) n'est pas suspendu — il devient *un* profil d'accès parmi d'autres.
---
## Tableau de bord — sous-projets (labo d'abord, WIP ≤ 3 actifs)
> Chaque feature F# = **un sous-projet** (objectif, branche, prérequis, statut, done). Backlog priorisé ; **on n'ouvre que 2-3 sous-projets actifs à la fois** (réactivité = focus). **Merge prod = supervisé Dom.** Codex orchestre par sous-projet à son retour (28/06).
**Prérequis socle (avant parallélisme)** : merger `fix/dashboard-complete-installer` (HEAD `d686c3ac2`) sur `poc-dgx` (`1d6efdb1b`) → base git propre ; ménage code mort (Qwen) cadré.
| SP | Feature | Statut | Prérequis bloquant |
|----|---------|--------|--------------------|
| **SP-0** | Socle git (merge branche + base propre) | ✅ **fait** (23/06 — FF `1d6efdb1b``d686c3ac2`, local) | — *(parallélisme débloqué)* |
| **SP-1** | **F14/U-B Anchors** (compound) | ✅ **code FAIT, commit `2cabc6cb7` (br. `sp1/anchors-compound`), validé données réelles + persistance** | ré-import **en place** entravé par U-A (import = doublon) → SP-4 |
| SP-2 | F2 Rejeu intelligent (R1→R7) | 🟢 **débloqué** (Q-F2-1 ✅ ; point d'entrée tracé = agent_chat 5004 / SemanticMatcher) | gros chantier R1→R7, à cadrer |
| SP-3 | F8 Exécution native (durcir + sandbox) | 🔴 bloqué | Q-F8-1..4 + vérif sécurité `/execute/instruction` |
| SP-4 | F1/U-A Consolidation fragmentation (T3) | 🟢 **débloqué (Q-F1-1 ✅ signature trajectoire, source=DB Q-F9-1 ✅)** | — (active quand on veut) |
| SP-5 | F6/U-C Mutualisation/fédération | 🟠 décidé, à coder | dépend SP-1 (anchors) |
| SP-Q | F13 Ménage code mort | 🔵 en cours (Qwen, read-only) | — |
| — | F3 F4 F5 F7 F9 F10 F11 F12 | ⚪ backlog priorisé | — |
**Cadrage 3 sous-projets (23/06, 3 agents read-only) — interfaces communes & séquencement** :
- **3 interfaces partagées à poser UNE fois** : (1) **signature de trajectoire** (SP-4 = propriétaire) ; (2) **index embeddings/FAISS partagé** (`core/embeddings/shared_index.py`, consommé par SP-2-R3 **et** la sélection skill↔tâche) ; (3) **guard `machine_id` centralisé** dans `stream_processor.py` (SP-4 ∩ SP-2-R6, ~L3197/3284).
- **Phase 0** (fondation, série, petit) : signature de trajectoire + accès index partagé.
- **Phase 1** (parallèle, WIP=2, faible collision) : **SP-4** (propriétaire `stream_processor.py`) ∥ **compétences** (propriétaire `core/competences/`).
- **Phase 2** : **SP-2 rejeu** (le plus intriqué : touche `stream_processor` *et* FAISS) — après stabilisation Phase 0 + SP-4.
- ⚠️ **Endpoints compétences `verdict`/`promote` EXISTENT déjà** (`api/lea_competences.py`, blueprint `app.py:277`, test `tests/unit/test_lea_competence_verdict_api.py`) → chantier compétences = **auto-déclenchement (hook) + sélection intelligente** seulement.
- 1 **branche par sous-projet**, merge supervisé Dom.
**QG Qwen (23/06, 18:30) — GO avec 4 ajustements** (intégrés) :
1. **Marqueurs de propriété dans `api_stream.py`** (commentaires par range d'endpoints SP-4 vs compétences) — seul point de contact Phase 1, éviter conflits silencieux. Pas de refactor.
2. **Fallback R2/R3 obligatoire** (SP-2) : chaque nouveau chemin de résolution **retombe sur les coords figées** si la cascade intelligente échoue. Le rejeu *enrichit*, ne remplace pas. **Non-négociable démo.**
3. **`machine_id` guard intouché en Phase 1** : le lifting du silo (`stream_processor` ~L3197/3284) est **entièrement Phase 2 / SP-2-R6**. → lève la collision SP-4 ↔ SP-2.
4. **Dead import `ExternalDecisionClient`** (`api_stream.py:L7285`, module absent, inoffensif via try/except) → à nettoyer dans le **ménage code mort** (catégorie C), pas dans SP-4.
---
## Axe central — Rejeu intelligent des actions apprises 🎯
**C'est le cœur produit** (« Léa apprend, comprend, **rejoue en exploitant ce qu'elle a appris** » — pas du record-and-replay). Vérification **runtime du 2026-06-23** (à reconfirmer `fichier:ligne` avant toute modif — méthode projet) :
### État réel de la chaîne apprentissage → rejeu
| Maillon | État | Preuve (à reconfirmer) | Constat |
|---|---|---|---|
| Import auto Shadow → workflow rejouable | ❌ **débranché** | `finalize` `api_stream` ~2430-2466 : enqueue worker VLM, **pas** de conversion/import auto ; `ShadowLearningHook` jamais appelé | Ce qui est appris n'est **pas** rendu rejouable sans geste manuel |
| Rejeu consulte le fonds appris (TargetMemoryStore) | ❌ **débranché** | `build_replay_from_raw_events` (~1841-2200) ne consulte rien ; `TargetResolver.lookup()` (~3263) jamais appelé par `replay-session` | Le rejeu rejoue des **coords/anchors figés**, pas la cible apprise |
| Lecture FAISS / GlobalFAISSIndex au rejeu | ❌ **write-only** | `workflow_replay.py` accepte `faiss_manager` en param mais ne l'utilise pas ; aucun `.search()` au rejeu | Index **écrit, jamais lu** au rejeu (cohérent fédération dormante) |
| verify post-condition (état UI après action) | ⚠️ **absent** | `safety_checks` + pause supervisée OK si mode supervisé (`api_stream` ~4299-4367) ; **pas** de `verify_screen` post-action | Pas de boucle de feedback succès/échec |
| Templating `{{var.field.sub}}` au rejeu | ✅ **marche** | `_resolve_runtime_vars()` `replay_engine` ~2027-2041, appelé ~4293 | Données récupérées (T2A, extract) **réinjectées** en temps réel — **acquis, ne pas refaire** |
| Filtre `machine_id` (cross-session) | ✅ actif = **silo** | `stream_processor` ~3197-3200 et ~3285 | Apprentissage **siloté par poste** ; rejeu direct non filtré |
**Verdict** : le rejeu est aujourd'hui **« brut » (events → coords), pas « intelligent »**. L'apprentissage tourne (ShadowLearning, TargetMemory, FAISS) mais **en silos jamais wirés au rejeu**. Le templating des données récupérées, lui, fonctionne déjà.
### Chaîne cible (ce vers quoi on va)
```
Capture/Shadow → finalize → [R1] import auto en workflow rejouable
→ au rejeu, chaque action résolue par : cascade UI (OCR/template/YOLO/VLM)
+ [R2] fonds appris (TargetMemoryStore) + [R3] FAISS anchors (similarité)
→ [✅] réinjection des données récupérées (templating)
→ [R4] verify post-condition (échec = pause supervisée, pas stop = apprentissage)
→ [R5] le résultat du rejeu réécrit le fonds (boucle)
→ [R6] mutualisation : lever silo machine_id + brancher fédération
```
### Chantiers rejeu (ordonnés, du plus structurant au plus fin)
| ID | Chantier | Dépend de |
|---|---|---|
| **R1** | Brancher l'**import auto** d'une session apprise en workflow rejouable post-finalize | décision « provider Léa runtime » |
| **R2** | Faire **consulter le fonds appris au rejeu** : câbler `TargetResolver.lookup()` / `TargetMemoryStore` dans le chemin `replay-session` (résoudre par cible apprise, pas coords figées) | R1 |
| **R3** | **Lire FAISS au rejeu** : utiliser le `faiss_manager` déjà passé à `workflow_replay.py` comme fallback de résolution par similarité d'anchor | R2 |
| **R4** | **verify post-condition** : vérif état UI après action ; échec → pause supervisée (cf. *failure-is-learning*) | — |
| **R5** | **Boucle d'apprentissage** : succès/échec/correction humaine du rejeu réécrivent le fonds (TargetMemory + FAISS) | R2, R4 |
| **R6** | **Mutualisation** : lever le filtre `machine_id` + brancher la fédération (`GlobalFAISSIndex.search()` jamais appelé) | décisions produit (silo vs fédéré, PII) |
| **R7** | Bugs rejeu résiduels : reprise sur crash (R4 audit), OCR span/centre-de-ligne (R5 audit) | — |
⚠️ **Tout cet axe touche le chemin runtime de la démo.** Méthode imposée : **chirurgie itérative supervisée** — un maillon = un test ≤ 2 min = GO Dom, jamais de batch, démo `Urgence_aiva_demo` intacte à chaque étape. Reconfirmer le wiring au runtime **avant** chaque modif (imports lazy).
---
## Feature — Unification Léa ↔ VWB (anchors) 🔗
**Manque corrigé (signalé Dom 23/06).** Chantier dédié : `PLAN_CHANTIER_UNIFICATION_LEA_VWB_2026-06-17.md`. Cœur = **les anchors**, le pont entre ce que VWB capture au recording et ce que Léa **récupère au rejeu**. Symptôme T3 « Léa ne trouve pas le bloc-notes » = **fragmentation de l'apprentissage**, pas silo.
Cycle de vie cible de l'anchor : capture (VWB recording / Shadow) → persistance (`visual_anchors`) → **propagation au workflow****récupération au rejeu** (résolution visuelle).
| ID | Sous-chantier | État | Risque |
|---|---|---|---|
| **U-B** | **Anchors — propager `anchor_image_base64` aux substeps *compound*** (`b8b963059` n'a fait que les actions simples ; les compound, majoritaires côté Léa, restent `anchor_id=NULL` → « Ancre requise » sans image) **+ ré-importer** les workflows. `learned_workflow_bridge.py` `_convert_compound_substep` ~L279 | **prêt — fix ciblé bloquant** | faible/additif |
| **U-A** | **Consolidation fragmentation** : `workflow_id` = signature stable de trajectoire + create-or-update (fusion + agrégation d'observations) — débloque T3 | design décidé | moyen (touche build/persist démo) |
| **U-C** | **Fonds commun** = **F6** (décidé cross + intra) : lever filtre `machine_id` (`stream_processor` ~L3197/L3284) + brancher fédération anonymisée (`GlobalFAISSIndex.search()` jamais appelé) | décision prise, à coder | moyen-élevé |
| **U-D** | **Asymétrie grounding** : VWB recording = UI-DETR-1 ; replay Léa = cascade OCR/template/VLM → unifier le chemin de résolution | sujet ouvert post-démo | à trancher |
**Lien fort avec F2 (rejeu intelligent)** : la récupération des anchors au rejeu (U-B) est le **substrat de R2/R3** — sans anchors propagés/retrouvés, le rejeu ne peut pas résoudre par la vision et retombe sur des coords figées.
**⚠️ Implication de la décision F6 = cross-clinique** : un pack fédéré anonymisé **n'emporte ni coordonnées ni templates** (PII) → seule la **re-résolution visuelle par anchors/FAISS** permet de le rejouer ailleurs. Donc **F6 cross-clinique entraîne quasi-mécaniquement le principe « rejeu intelligent » (Q-F2-1)** : il devient un prérequis, pas une option. *(Décision induite Q-F14-1 au registre.)*
**Ordre interne** (du chantier) : confirmer provider Léa (Q-F2-2) → **U-B anchors** (gain visuel immédiat, faible risque) → U-A consolidation → U-C fédération.
**Prép SP-1 / U-B (vérif runtime 23/06, read-only)** :
- **Gap confirmé** : `learned_workflow_bridge.py:_convert_compound_substep` (L279-321) ne pose **jamais** `_anchor_image_base64` ; la branche action simple (L226-233) le fait. → substeps compound = `anchor_id NULL`.
- **Source dispo** : dans le JSON core, l'ancre du compound est à `target.context_hints.anchor_image_base64` (pas `target.anchor_image_base64`). `target` (parent) est **déjà passé** à `_convert_compound_substep`.
- **Fix** (additif, ~1 endroit) : dans la boucle compound (L169-187), poser `_anchor_image_base64` (même fallback que simple) sur le **1er substep cliquable** uniquement.
- **Impact DB** : **487/582 steps `anchor_id NULL`** (84 %) ; démo `Urgence_aiva_demo` = **8/18** manquants.
- ⚠️ **Caveat ré-import** : le ré-import lit la source via `_load_core_workflow(workflow_id, machine_id)` dans `data/training/workflows/{machine_id}/`**disponible par workflow** ; la source de la démo n'est pas trouvée par nom → **vérifier par core_workflow_id avant de compter sur le ré-import de la démo**.
- **Test** : ré-importer un workflow compound → un substep cliquable doit afficher son image via `GET /api/v3/anchor/<id>/thumbnail` + `StepNode.tsx`. **Risque** : code = additif (faible) ; **ré-import = étape sensible** (backup `workflows.db` + par workflow + revérifier le replay).
- **FAIT 23/06** : fix commité (`2cabc6cb7`, br. `sp1/anchors-compound`), TDD RED→GREEN, **validé sur données réelles** (source réelle : 2/2 clics compound désormais ancrés). Persistance confirmée : `import_learned_workflow` (L332) `pop("_anchor_image_base64")``save_anchor_image``VisualAnchor` + `step.anchor_id` (même chemin que les actions simples). Backup DB : `instance/workflows.db.bak-sp1-2026-06-23`.
- ⚠️ **Découverte** : `import_learned_workflow` **crée un nouveau workflow** (`generate_id`, L301) — **pas de mise à jour en place**. Donc rafraîchir les anchors d'un workflow **existant** (ex. démo) par ré-import = **doublon** → dépend de **U-A** (create-or-update). Le fix U-B est correct **en avant** (tout nouvel import aura les anchors compound) ; le rafraîchissement des workflows déjà en base est porté par U-A (SP-4, décision Q-F1-1).
---
## Feature — Exécution native agentique (computer-use zéro-shot) 🤖
**Manque recadré (signalé Dom 23/06).** Donner un **objectif en langage naturel** (« ouvre un navigateur et va sur YouTube ») et l'exécuter **sans workflow appris**, par planification + grounding visuel. Complément du rejeu appris (F2).
**⚠️ Constat runtime (vérif 23/06) : ce n'est PAS absent — les briques existent et sont wirées.** Le manque réel = **durcissement + sandbox + validation humaine**.
| ID | Brique | État runtime | Manque |
|---|---|---|---|
| F8.1 | **Boucle ORA** observe→reason(VLM)→act→verify→retry (`core/execution/observe_reason_act.py:run_instruction`) | ✅ **wired** via endpoint `/execute/instruction` (`api_v3/execute.py:2033`) | durcissement |
| F8.2 | **Planner NL→plan** (`agent_v0/server_v1/task_planner.py:understand()` gemma4, mode `_execute_free()`) | ⚠️ présent, **mode « free » peu mûr/peu testé** | maturation + tests |
| F8.3 | **Grounding cascade** OCR→**UI-TARS**→VLM (= F3, partagé) | ✅ wired (`input_handler.py:_grounding_ui_tars`) | — |
| F8.4 | **Sandbox Worker** (VM/Xvfb/VNC + kill-switch + **validation humaine**) | ❌ **absent** — exécution **directe sur l'host**, sans isolation ni pause | **= le vrai manque** (décision CUA P1) |
| F8.5 | **Boucle vers l'apprentissage** : un run natif réussi → capturé comme workflow appris (alimente F1) | ❌ absent | à câbler |
| F8.6 | **Replanification dynamique** si l'écran change radicalement (app crash…) | ❌ absent (ORA linéaire) | à ajouter |
**🔴 Sécurité — vérifié 23/06 (audit read-only)** : `POST /api/v3/execute/instruction` (VWB backend **5002**, `app.py:321` / `execute.py:2033`) lance la boucle ORA qui pilote **directement l'écran X11 de l'host** (pyautogui/xdotool, **pas de sandbox**, pas de pause humaine, pas de kill-switch) — cible de fait la VM Léa affichée. Auth = middleware Basic global (`DASHBOARD_PASSWORD`) **mais loopback exempté** ; sur le DGX clinique 5002 est **atteignable sur le LAN** (401 sans creds / 200 loopback), pas d'expo WAN. → Un acteur du LAN clinique avec les creds partagés (faibles, `Medecin2026!`) **ou tout process local (loopback)** peut déclencher une instruction agentique arbitraire sur l'host. **Mitigation à décider (prod)** : restreindre `/execute/instruction` à loopback / désactiver le mode « free » tant que F8.4 (sandbox) n'existe pas. Tant que F8.4 n'est pas en place, le mode « free » ne doit **PAS** être ouvert au-delà d'un environnement jetable — « JAMAIS l'hôte » + safety agent.
**Articulation avec le mode appris** : routage — pas de workflow appris pour le but → **mode natif** ; sinon **rejeu** (F2). Et le natif réussi **devient** appris (F8.5, la boucle se referme). Décisions → registre F8.
---
## H1 — Stabilisation clinique (jour J → quelques jours, à distance)
| # | Action | Source |
|---|---|---|
| 1 | **Aligner DGX↔local avant débranchement** : git 5 commits behind (`ec1fb81``d686c3ac2`) ; **backup `workflows.db` AVANT** reset | Qwen + tableau B |
| 2 | Ajouter **`RPA_SIGNING_KEY`** dans `.env.local` DGX (HMAC métadonnées FAISS, absent) | KO-1 Qwen |
| 3 | **Vérifier accès Stormshield** depuis laptop = point de non-retour avant départ | MEMO §5 |
| 4 | **Mot de passe** (pas PIN) compte VM `aivanov` → RDP + presse-papier | handoff |
| 5 | Confirmer **profil Stormshield laptop** avec PORQUET (atteint `.178` ?) | handoff |
| 6 | **Apprentissage e2e sur 1 poste TIM réel** avant de généraliser aux 5 | MEMO + audit |
## H2 — Consolidation & dette (semaines suivantes, scp via VPN)
| # | Chantier | Pourquoi |
|---|---|---|
| 7 | **Merger les branches** : `fix/dashboard-complete-installer` non mergée sur `poc-dgx` ; clarifier topologie `poc-dgx``main` | base git propre avant tout ménage |
| 8 | **Ménage code mort** (mission Qwen en cours) → exécution **par lots + QG** | post-stabilisation |
| 9 | **Test de charge multi-agents** (2-3 puis 5 Léa simultanées) | 1 TIM démo ≠ 5 TIM réels |
| 10 | Bugs non-rejeu résiduels de l'audit (watchdog `_retry_pending` A2, écran verrouillé non détecté…) — **re-vérifier lesquels sont encore ouverts** | audit gaps |
## H3 — Produit & fond (après stabilisation, sur décisions Dom)
| # | Chantier | Bloqué par |
|---|---|---|
| 11 | **Chantier B anchors VWB** (propager `anchor_image_base64` aux substeps compound) — fix ciblé, risque faible | rien, prêt |
| 12 | **CUA Sandbox Worker P1** (décision Dom 18/06 : VM/Xvfb/VNC + kill-switch, jamais l'hôte) | priorisation |
| 13 | **Source vérité workflows** (migration JSON→SQLAlchemy, sortir DETTE-015) | post-POC |
| 14 | **Shadow → Copilot → Autonomous** au runtime (aujourd'hui design) | R1-R6 + décisions |
---
## Décisions qui reviennent à Dom (registre dédié)
→ Suivi vivant dans **`DECISIONS_PRODUIT_EN_ATTENTE_2026-06-23.md`** (Dom remplit à tête reposée).
**Déjà tranché (23/06)** :
-**H3 en premier**, décisions avant exécution (séquencement préservé).
-**F6 = mutualisation cross-clinique ET intra-clinique** → fédération anonymisée **dans le périmètre** + lever le silo `machine_id` entre postes.
-**F11 = accès multi-VPN par site** (cf. §0.2).
**Encore ouvert** (bloque l'exécution des chantiers liés) :
1. **Principe « rejeu intelligent »** (R2/R3) — le rejeu *doit* consulter le fonds appris ? Change l'archi du replay.
2. **Provider Léa au runtime** (R1 + résolution).
3. **Critère de fusion des workflows** (create-or-update).
4. **Source de vérité workflows** (DB vs JSON) + **métrique produit** (24/79/37) — *reco Claude consignée : DB = vérité, JSON = échange ; métrique = rejouables validés*.
## Séquencement imposé
**Stabiliser (H1)****base git propre + merge (H2-7)****ménage code mort****axe rejeu R1→R7** (chirurgie supervisée) → **fond produit (H3)**.
On ne dégraisse pas, et on ne recâble pas le rejeu, sur une base mouvante.
---
## Plans sources (référence, ne pas dupliquer)
`MEMO_JOUR_J_LIVRAISON_DGX_CLINIQUE_2026-06-23` · `TABLEAU_ACTIONS_DOM_PRECLINIQUE_2026-06-21` · `PLAN_CHANTIER_UNIFICATION_LEA_VWB_2026-06-17` · `PLAN_ACCES_DISTANT_SSH_CERT_DGX_2026-06-20` (volet WG suspendu) · `PLAN_MIGRATION_WORKFLOWS_STORE_2026-06-09` · `AUDIT_GAPS_APPLI_100PCT_2026-06-10` · `CARTO_APPRENTISSAGE_FONDS_COMMUN_2026-06-16` · `CHECKLIST_DGX_PRE_CLINIQUE` · `INSTALLATION_MULTI_SITE`.