diff --git a/docs/AUDIT_GAPS_APPLI_100PCT_2026-06-10.md b/docs/AUDIT_GAPS_APPLI_100PCT_2026-06-10.md new file mode 100644 index 000000000..c130e93a0 --- /dev/null +++ b/docs/AUDIT_GAPS_APPLI_100PCT_2026-06-10.md @@ -0,0 +1,174 @@ +# Audit — Ce qui manque pour une appli 100% fonctionnelle + +- **Date** : 2026-06-10 +- **Demandeur** : Dom +- **Auteur** : Claude (audit read-only par 4 sous-agents d'exploration + contre-vérifications manuelles) +- **Périmètre** : agent client Windows (Léa), chaîne d'apprentissage, capacité de replay, maturité produit/fleet +- **Statut findings** : les fichier:ligne proviennent d'agents d'exploration. Les 3 contradictions majeures ont été re-vérifiées à la main (voir annexe). **Avant d'engager du code sur un item, revalider le point au cas par cas** (méthode habituelle). +- **Avis croisé Qwen** : reçu 2026-06-10 23:30 (`inbox_claude/2026-06-10_2330_qwen-to-claude_AVIS-GAPS-APPLI-100PCT.md`) — intégré en **Addendum (§7)**. + +--- + +## 1. Diagnostic central + +L'appli aujourd'hui, honnêtement qualifiée : **un record-and-replay supervisé robuste, avec une couche sémantique réelle au grounding, mais dont la boucle d'apprentissage n'est pas fermée et dont les filets de sécurité sont écrits mais débranchés.** + +Trois promesses produit non tenues dans le code qui tourne : + +1. **La boucle d'apprentissage est ouverte** — Shadow observe et construit des workflows, mais ils n'arrivent jamais dans VWB (import jamais déclenché). +2. **L'exécution ne se vérifie pas elle-même** — verify, healing, recovery : tout existe, tout est désactivé ou non branché. +3. **Pas de généralisation** — un workflow appris ne survit pas à un changement de poste/résolution ; FAISS est construit au training mais jamais consulté au replay. + +Point structurel transverse : **deux chemins d'exécution aux capacités différentes** : +- `visual_workflow_builder/backend/api_v3/execute.py` (exécution locale VWB, Legacy + ORA) +- `agent_v0/server_v1/replay_engine.py` → agent Windows Léa (chemin POC) + +Certains manques n'existent que sur l'un des deux. `t2a_decision` et le templating profond `{{var.field.sub}}` sont **implémentés sur le chemin Léa** (replay_engine.py:1922 et :2017) mais absents du chemin local. Cette asymétrie a piégé jusqu'aux agents d'audit eux-mêmes — c'est un coût de maintenance et un risque d'erreur permanent. + +--- + +## 2. Axe agent client Windows (Léa) — ~80% fonctionnel, 1 bug critique + +### Ce qui marche (vérifié wired) +- Capture complète : clics/double-clics, clavier+buffer texte, scroll, multi-écrans, DPI awareness, floutage sensible, dédup pHash. +- Résilience réseau : buffer SQLite persistant, retry 3×, backoff 1→30s, heartbeat 5s. +- Purge captures après ACK serveur (`PURGE_AFTER_ACK=1` défaut). +- Enroll Bearer token + machine_id ; détection dialogues système UAC/CredUI/SmartScreen **fail-closed** (pause supervisée). +- Rétention logs 180 j (conforme Règlement IA art. 12). + +### Gaps + +| # | Gap | Sévérité | Preuve | Type | +|---|-----|----------|--------|------| +| A1 | **Timeout HTTP client 5s** : étape serveur > 5s (extract_text, t2a) → client coupe, action déjà sortie de la queue → **perdue silencieusement**. Incident documenté 8 mai (4 actions perdues, pause step 18). | 🔴 BLOQUANT POC | `agent_v1/core/executor.py:1786` ; `docs/REPLAY_BLOCAGE_NOTES_MEDICALES_2026-05-08.md` | Bug | +| A2 | **Watchdog `_retry_pending` absent** côté serveur : actions perdues jamais republiées. | 🔴 BLOQUANT POC | `network/streamer.py:99-105` (constat) | Non implémenté | +| A3 | Écran verrouillé non détecté : agent capture noir, tape dans le vide. | 🟠 Important | — | Non implémenté | +| A4 | RecoveryEngine client : code complet, jamais appelé. | 🟡 Nice-to-have | `agent_v1/core/recovery.py` | Écrit non branché | +| A5 | Long-polling HTTP fragile par construction (vs SSE/WebSocket). | 🟡 Post-POC | `main.py:287-366` | Architecture | + +Note : la suspicion « appel Ollama de commentaire d'action orphelin côté client » **ne se confirme pas** côté agent_v1 (`OLLAMA_HOST` défini dans config mais aucun appel client). Le commentaire d'action est côté serveur. + +--- + +## 3. Axe chaîne d'apprentissage — marche jusqu'au dernier mètre, puis s'arrête + +### Ce qui marche (vérifié wired) +- Chaîne Shadow complète : streaming → LiveSessionManager → `_worker_queue.txt` → `run_worker.py` → ScreenAnalyzer → CLIP → FAISS indexation → GraphBuilder DBSCAN → workflow JSON dans `data/training/workflows/{machine_id}/`. +- Apprentissage post-replay : ReplayLearner (JSONL `data/learning/replay_results/`) + TargetMemoryStore (SQLite `data/learning/target_memory.db`), consultés avant résolution, alimentés après succès/échec. + +### Gaps + +| # | Gap | Sévérité | Preuve | Type | +|---|-----|----------|--------|------| +| L1 | **Workflows Shadow jamais importés dans VWB** : `import_core_workflow()` existe (`visual_workflow_builder/backend/api/workflows.py:622`) mais aucun appel automatique post-finalize. La boucle d'apprentissage produit des fichiers que personne ne voit. | 🔴 BLOQUANT promesse produit | grep `import_core_workflow` depuis server_v1 = 0 hit | Écrit non branché | +| L2 | ShadowLearningHook jamais importé (état avril 2026 inchangé). | 🟠 Important | `core/grounding/shadow_learning_hook.py` | Écrit non branché | +| L3 | **FAISS construit mais jamais interrogé au replay** — la mémoire sémantique ne sert pas à la résolution. | 🟠 Important | `core/embedding/faiss_manager.py` | Écrit non branché | +| L4 | Pas de généralisation cross-résolution/cross-poste : workflows cloisonnés par machine_id, ancres dépendantes du poste source. | 🟠 Important | `core/models/workflow_graph.py` | Non implémenté | +| L5 | **Copilot : inexistant** (aucun moteur de suggestion). **Autonomous : AutonomousPlanner isolé du replay engine.** Le cycle Shadow→Copilot→Autonomous est aujourd'hui Shadow→(rien)→exécution déclenchée manuellement. | 🔴 BLOQUANT promesse produit | `agent_chat/autonomous_planner.py`, `agent_v0/server_v1/execution_plan_runner.py` | Squelettes | +| L6 | Recapture anchor VWB : pas de revalidation/régénération PNG post-modification (bug connu mai 2026). | 🟠 Important | `visual_workflow_builder/backend/services/anchor_image_service.py` | Non implémenté | + +--- + +## 4. Axe replay/exécution — ça clique bien, mais ça ne sait pas si ça a marché + +### Ce qui marche (vérifié wired) +- Cascade de résolution active : template matching → CLIP → OCR/UI-TARS → VLM. +- DialogHandler branché (détection popups pré-step). +- Pause supervisée avec choix utilisateur (skip/static/coords, timeout 120s). +- Chemin Léa : `t2a_decision` (replay_engine.py:1922, handlers :2045+), templating profond `{{var.field.sub}}` (`path.split('.')` :2017), extract_text. + +### Gaps + +| # | Gap | Sévérité | Preuve | Type | +|---|-----|----------|--------|------| +| R1 | **`verify_level='none'` en dur** : aucune vérification post-action que le clic a produit l'effet attendu (seul pHash d'attente d'écran). Contraire au principe « vérif avant + après chaque clic ». | 🟠 Important (🔴 avant clinique) | `execute.py:1545` | Branché désactivé | +| R2 | **VLM pre-check `if False:` en dur** : pas de validation que l'élément trouvé = l'élément attendu. | 🟠 Important | `core/execution/observe_reason_act.py:1707` | Branché désactivé | +| R3 | Healing engine implémenté, jamais appelé. | 🟠 Important | `core/healing/healing_engine.py:21-150` | Écrit non branché | +| R4 | **Aucune reprise après crash** : crash au step N → redémarrage à 0, pas de checkpoint. | 🔴 BLOQUANT clinique | `execute.py:1732` (thread daemon sans checkpoint) | Non implémenté | +| R5 | **OCR-DIRECT « centre de ligne »** : substring d'une ligne docTR → coordonnées du centre de la ligne entière. Sur une barre d'onglets, Imagerie/Notes/Synthèse ≈ mêmes coords. Latent et sournois. | 🟠 Important | `agent_v0/server_v1/resolve_engine.py:1447-1527` | Bug | +| R6 | Timeout VWB→serveur 30s vs étapes longues (t2a/Ollama lent) → 504. | 🟠 Important | `server_client.py:207` | Bug config | +| R7 | Reporting d'exécution pauvre : méthode de grounding utilisée et raison d'échec non tracées. | 🟠 Important | ExecutionStep DB | Incomplet | +| R8 | Popup détecté mais gestion échouée → continue (log seul), pas de pause. | 🟡 Nice-to-have | `execute.py:283` | Incomplet | +| R9 | 3 systèmes de grounding morts (code zombie) : `fast_detector`, `smart_matcher`, `template_matcher` standalone. | 🟡 Ménage | `core/grounding/` | Poids mort | +| R10 | TitleVerifier aveugle en VM (crop EasyOCR 45px illisible). | 🟡 Connu | `core/grounding/title_verifier.py:34-67` | Limitation | + +--- + +## 5. Axe maturité produit / fleet — OK pour 5 TIM supervisés, pas au-delà + +### Ce qui marche (vérifié) +- Fleet : enroll/uninstall/revoke SQLite (`agent_registry.py`), `_guard_agent_registry_access`, last_seen heartbeat. +- Sessions concurrentes thread-safe ; uploads images rate-limités sans sérialisation. +- Healthcheck multi-composants + timer systemd (API, dashboard, worker heartbeat, disque). +- Export ZIP workflows ; dashboard HTTP Basic fail-closed. + +### Gaps + +| # | Gap | Sévérité | Preuve | Type | +|---|-----|----------|--------|------| +| P1 | **DETTE-006/010 — grounding Qwen3-VL instable** (`smart_resize` non déterministe, config checkpoint factor 28 vs 32). LE risque technique du calendrier POC. | 🔴 BLOQUANT POC | `docs/MIGRATION_VLM_PLAN_2026-05-09.md`, DETTE_TECHNIQUE.md | En cours | +| P2 | **1 seul replay simultané** (verrou global `_replay_lock`). Acceptable POC séquentiel, bloquant au-delà. | 🟠 Important post-POC | `api_stream.py` | Limitation | +| P3 | Opérabilité non-dev : pas d'onglet « état systèmes », pas de monitoring GPU/Ollama, erreurs JSON brut. Acceptable seulement avec Dom en SSH derrière. | 🟠 Important | `web_dashboard/app.py` | Incomplet | +| P4 | Export ZIP sans **restore** en masse ; backup exporte les JSON, pas la DB (DETTE-015 : symlink tient pour le POC). | 🟠 Important | `core/system/backup_exporter.py:58-160` | Incomplet | +| P5 | Multi-users/RBAC : 1 user statique. Accepté POC (lié DETTE-016). | 🟡 Post-POC | `web_dashboard/app.py:67` | Accepté | +| P6 | Pas de rotation des logs serveurs (`logs/*.log`) — artifact_retention ne couvre que les données. | 🟡 Nice-to-have | `core/system/artifact_retention.py` | Incomplet | +| P7 | DETTE-013 : tests unit non exécutables sans `RPA_API_TOKEN` (sys.exit à l'import). | 🟠 Important dev | `agent_v0/server_v1/api_stream.py:135` | Bug | +| P8 | Ménage pré-POC (~9-10 j-h, `MENAGE_PRE_POC_2026-05-29.md`) non engagé ; ~21 modules core/ orphelins documentés. | 🟡 Planifié | docs/POC/ | Dette | + +--- + +## 6. Priorisation proposée + +### Horizon 1 — avant le M2 live (jours) : fiabiliser la chaîne qui existe +1. **A1** Timeout client 5s → 30s (1 constante) + **A2** watchdog `_retry_pending` serveur — le duo qui a tué la session du 8 mai. +2. **P1** Trancher DETTE-006/010 (calibration grounding Qwen3-VL sur DGX) avant le bench J+6. +3. **A3** Détection écran verrouillé. + +### Horizon 2 — avant la clinique (semaines) : les filets de sécurité +4. **R1/R2** Réactiver verify (au moins post-condition légère) + pre-check. +5. **R5** Fix OCR centre-de-ligne (span du substring, pas centre de ligne). +6. **R4** Reprise sur crash (checkpoint step) + **R7** tracer la méthode de résolution. +7. **R6** Timeout VWB 30s → adapté aux étapes longues (ou polling asynchrone). + +### Horizon 3 — la promesse produit (post-POC) : fermer la boucle +8. **L1** Pont auto Shadow→VWB (`import_core_workflow` post-finalize) — LA pièce qui transforme l'outil en produit apprenant. +9. **L3** FAISS consulté au replay + **L4** début de généralisation cross-poste. +10. **L5** Copilot (moteur de suggestion) puis branchement AutonomousPlanner. +11. Unifier les deux chemins d'exécution (execute.py local vs replay_engine.py Léa). +12. **P2** Replays parallèles, **P3** opérabilité TIM, **P4** restore, RBAC. + +--- + +## 7. Addendum — Avis croisé Qwen (historien/QG, 2026-06-10 23:30) + +### Convergences avec l'audit code +- **DETTE-006/010 = les deux vrais risques démo** (« si le grounding dérive, les clics ratent. Démo morte. ») — aligné avec P1/Horizon 1. +- **Monitoring/alerting productif absent** (P3) : « si un worker crashe à 3h du matin sur un TIM, personne ne le saura ». +- Écarts doc vs réalité confirmés par son registre : ContinuousLearner/Shadow hook orphelins (L2), cascade YOLO et OmniParser neutralisées (DETTE-004), ~1900 lignes de code mort jamais câblé (autonomous_planner, seeclick…) — cohérent avec L5/R9. + +### Apports nouveaux de Qwen (absents de l'audit code) +1. **Multi-TIM jamais testé > 1 agent simultané** : le Fleet existe, mais routage session, isolation mémoire et contention GPU sous charge réelle sont **inconnus**. Mon audit avait noté le verrou replay global (P2) ; Qwen élargit : c'est toute la concurrence multi-agents qui n'a aucune preuve d'exécution. +2. **Le pipeline complet record → replay → compétence n'a jamais tourné en conditions réelles** : M2 live n'a pas encore eu lieu. « Le premier vrai test sera devant le client » si on ne fait pas M2 avant. +3. **Incidents récurrents de son registre** : worker zombie 5 jours (résolu par watchdog N3), tunnel Ollama instable (stabilisé systemd), UI-TARS 500 non détecté (toujours 0 test dédié), OOM VRAM GB10 (fixé). +4. **DETTE-015 jugée fragile** : le symlink a déjà cassé une fois (P0-1) ; peut resurgir si cwd change. + +### Point de tension à arbitrer en M2 (pas tranché) +Qwen affirme : « si le serveur redémarre, les agents Windows tombent — pas de reconnexion automatique ». Mon audit client a trouvé buffer SQLite persistant + retry + backoff + health-check 30s (`streamer.py`). Les deux peuvent être vrais : le **transport** se reconnecte, mais la **session/replay en cours** ne reprend probablement pas après un restart serveur. À vérifier explicitement pendant M2 (test : restart serveur en cours de session). + +### Verdict Qwen +- **1 TIM en démo contrôlée : prêt** (sous réserve DETTE-006/010). +- **5 TIM réels en clinique : pas prêt** — le gap n'est pas dans le code métier (OCR, VLM, grounding) mais dans l'**infra multi-utilisateur** : sessions, isolation, monitoring, résilience. + +Ce verdict est compatible avec la priorisation §6 et la renforce : l'Horizon 2 doit inclure un **test de charge multi-agents** (2-3 agents simultanés minimum) avant la clinique, en plus des filets de sécurité. + +--- + +## Annexe — Contradictions inter-agents résolues (contre-vérifiées à la main) + +| Affirmation agent d'audit | Verdict après vérification | +|---|---| +| « t2a_decision non implémenté » | **FAUX sur le chemin Léa** : implémenté `agent_v0/server_v1/replay_engine.py:1922` + handlers :2045+. Vrai uniquement pour le chemin local execute.py. | +| « `{{var.field.sub}}` ne marche pas » | **FAUX sur le chemin Léa** : `path.split('.')` replay_engine.py:2017. Vrai uniquement chemin local. | +| « Le chemin replay vers Léa est démis / Léa n'existe plus » | **FAUX** : pont `learned_workflow_bridge.py` côté VWB + polling `/replay/next` côté client actifs. Les deux chemins coexistent — c'est l'asymétrie connue. | + +Leçon : tout audit ou modification doit d'abord identifier **sur quel chemin d'exécution** il porte. diff --git a/docs/CARTO_APPRENTISSAGE_FONDS_COMMUN_2026-06-16.md b/docs/CARTO_APPRENTISSAGE_FONDS_COMMUN_2026-06-16.md new file mode 100644 index 000000000..6543267b8 --- /dev/null +++ b/docs/CARTO_APPRENTISSAGE_FONDS_COMMUN_2026-06-16.md @@ -0,0 +1,104 @@ +# Cartographie — Chaîne d'apprentissage & mise en commun des connaissances (2026-06-16) + +> Question Dom : *« Comment le savoir appris sur chaque poste est-il mutualisé dans un fonds commun unique partagé par tous les postes, et exploité vers l'autonomie ? »* +> Méthode : graphify (graphe code 58k nodes) + 3 agents Explore + vérif code directe. Cross-checks Codex (pipeline server_v1) & Qwen (fédération/FAISS) **en cours** — ce doc sera enrichi. +> ⚠️ Verdicts = état runtime constaté ce jour (`poc-dgx` @ `2b1743c20`), pas la doc d'intention. + +## TL;DR (réponse directe) + +Le « fonds commun → autonomie » est **partiellement construit, mais les maillons clés sont soit silotés par machine, soit dormants** : + +1. ✅ **Ce qui EST déjà commun** : les **compétences YAML** (`core/competences/`) et les **embeddings** (CLIP→FAISS) — partagés serveur, tous postes. +2. ❌ **Ce qui est SILOTÉ par machine (codé en dur)** : le **stockage** workflows/sessions (`{machine_id}/`) et surtout le **cross-session learning** qui **refuse de matcher entre machines** (`if workflow_machine != machine_id: continue`). C'est l'anti-pattern direct vs la vision. +3. 🌙 **Ce qui est DORMANT** : la **fédération** (`core/federation` : LearningPack, GlobalFAISSIndex) — le vrai mécanisme de fonds commun — est **bien conçue, globale et anonymisée** (Qwen confirmé : zéro machine_id, clé `pack_source_hash`), mais **doublement inerte au runtime** : (a) alimentée **seulement** par l'endpoint import **manuel** (jamais auto-déclenché) ; (b) son **`search()` n'est JAMAIS appelé** (`faiss_global.py:199` défini, zéro consommateur actif) → **index write-only, jamais consulté** → contribue **zéro** au comportement/à l'autonomie aujourd'hui. +4. 🚧 **Ce qui manque pour l'autonomie** : la **couche graphe** (WorkflowGraph) **EST construite en live** (`finalize_session` → `GraphBuilder`, import lazy `stream_processor.py:3017-3022`, DBSCAN) — **correction d'un faux "orphelin"** — **mais le graphe est siloté par machine** (persisté sous `workflows/{machine_id}/`) et le merge cross-session est machine-filtré. La progression **Shadow→Copilot→Autonomous** reste **du design, pas du runtime** (Shadow observe+log). + +> ⚠️ **Caveat méthodo** : ce code utilise massivement des **imports lazy dans les handlers/méthodes**. Les verdicts "orphelin" basés sur grep d'imports top-level sont **non fiables** (federation ET GraphBuilder étaient ainsi faussement classés orphelins, puis confirmés WIRED). Tout "orphelin" ci-dessous non vérifié par lazy-import est à recontrôler. + +→ Aujourd'hui, par défaut, **chaque poste est un silo cognitif** : il n'apprend pas des autres, sauf pour les compétences YAML et les embeddings centralisés. + +## Tableau de synthèse (WIRED ? · COMMUN/SILOTÉ) + +### A. Capture → construction → stockage (agent Explore #1) +| Composant | fichier:ligne | WIRED | COMMUN/SILOTÉ | +|---|---|---|---| +| TraceStreamer (tag machine_id) | `agent_v1/network/streamer.py:91` | ✅ (main.py:227) | tague machine_id sur chaque POST | +| register/stream/finalize | `server_v1/api_stream.py:1748/1801/2336` | ✅ endpoints | session taguée machine_id | +| `_persist_workflow` | `server_v1/stream_processor.py:4417` (appelé 3066) | ✅ | **SILOTÉ** : écrit `data/training/workflows/{machine_id}/` + tag `_machine_id` | +| Store disque sessions | `data/training/live_sessions/{machine_id}/` | ✅ | **SILOTÉ** (arbo 1:1 par machine) | +| `_run_cross_session_learning` / `_find_best_cross_session_match` | `stream_processor.py:3149 / 3273` | ✅ (via finalize) | **SILOTÉ CODÉ** : `if workflow_machine != machine_id: continue` (L3284-3286) → un poste n'apprend jamais d'un autre | +| Listing `list_workflows` | `api_stream.py:2799` + `stream_processor.py:4518` | ✅ | **BIMODAL** : `machine_id=None` → tous ; sinon filtré | +| Client `list_workflows()` | `lea_ui/server_client.py:228` | ✅ (smart_tray:802) | **COMMUN** : n'envoie PAS machine_id → reçoit tous | +| Dashboard list_sessions | `web_dashboard/app.py:2289` | ✅ | filtre disque par machine_id (optionnel) | +| Replay ciblage | `api_stream.py:3064` + `replay_engine.py:1559` | ✅ | machine_id = **ROUTE** l'exécution vers le bon poste (légitime) | + +### B. Apprentissage / cognition / compétences (agent Explore #2) +| Module | fichier:ligne | WIRED | COMMUN/SILOTÉ | +|---|---|---|---| +| `target_memory_store` (mémoire cibles, phase 1) | `core/learning/target_memory_store.py:77` | ✅ hot-path `resolve_engine.py:1869` | **SILOTÉ par machine** (`data/learning/target_memory.db` local, sauf `RPA_LEARNING_DIR` partagé) | +| `continuous_learner` | `core/learning/continuous_learner.py` | ✅ (`stream_processor.py:3145`) | **SILOTÉ par session** | +| `replay_learner` | `server_v1/replay_learner.py:90` | ✅ (`api_stream.py:2436`) | **SILOTÉ par session** | +| `learning_manager` (états workflow VWB) | `core/learning/learning_manager.py:37` | ✅ singleton VWB | **COMMUN** | +| **Compétences YAML** (catalog/replay/persist/verdicts/promotions) | `core/competences/*` | ✅ endpoints `api_stream` + dashboard | ✅ **COMMUN** (tous lisent `data/competences/`) — **c'est le vrai fonds commun qui marche** | +| `observe_reason_act` (ORALoop) | `core/execution/observe_reason_act.py:145` | ✅ (VWB `api_v3/execute.py`) | siloté par exécution | +| `feedback_processor`, `versioned_store`, `core/cognition/*`, `core/knowledge`, `core/coaching`, `core/healing`, `core/supervision` | — | ⚠️ **ORPHELINS** (tests seuls) | — | + +### C. Graphe / embeddings / autonomie (agent Explore #3) +| Composant | fichier:ligne | WIRED | COMMUN/SILOTÉ | +|---|---|---|---| +| ScreenState (perception) | `core/pipeline/workflow_pipeline.py` | ✅ serveur | COMMUN | +| StateEmbedding + Builder (fusion 512d) | `core/models/state_embedding.py:44` / `core/embedding/state_embedding_builder.py:25` | ✅ `stream_processor` startup | **COMMUN** (vecteurs `data/training/embeddings/*.npy` centralisés) | +| CLIP (OpenCLIP ViT-B-32) | `core/embedding/clip_embedder.py` | ✅ | COMMUN | +| FAISSManager (similarité) | `core/embedding/faiss_manager.py:40` | ✅ `stream_processor` | **COMMUN serveur** mais **index per-session en mémoire** (pas un index global persistant) | +| **WorkflowGraph builder (couche 4)** | `core/graph/graph_builder.py:148` | ✅ **WIRED** (import lazy `stream_processor.py:3017`, instancié `:3022` dans `finalize_session`, DBSCAN) — *corrigé : Agent #3 l'avait dit orphelin à tort* | **SILOTÉ** : graphe construit par session, persisté `workflows/{machine_id}/` ; merge cross-session machine-filtré | +| WorkflowNode/Edge (modèles couche 4) | `core/models/workflow_graph.py:384` | ✅ utilisés par GraphBuilder | siloté (idem) | +| **Shadow** observer | `core/workflow/shadow_observer.py:25` | ⚠️ partiel (`api_stream:2700` observe + LOG seulement) | pas d'apprentissage collectif | +| **Copilot / Autonomous** | `core/learning/learning_engine.py` | ❌ **design papier**, pas runtime | — | +| `audit_trail.execution_mode` (shadow/assisted/autonomous) | `server_v1/audit_trail.py:50` | ✅ enregistré | mais **pas exploité** pour décider | + +### D. Fédération = LE fonds commun par design (vérif directe + Qwen en cours) +| Composant | fichier:ligne | WIRED | COMMUN/SILOTÉ | +|---|---|---|---| +| `LearningPackExporter` | `core/federation/learning_pack.py` | 🌙 **endpoint manuel** `GET /api/v1/traces/stream/learning-pack/export` (`api_stream.py:6278`, import lazy L6292) | conçu commun | +| `LearningPack` + `GlobalFAISSIndex` | `core/federation/learning_pack.py:294` / `faiss_global.py:51` | 🌙 **endpoint manuel** `POST .../learning-pack/import` (`api_stream.py:6323`, L6334-6353 `GlobalFAISSIndex()` ré-instancié) | conçu commun | +| Déclenchement automatique | — | ❌ **AUCUN** : rien dans le flux capture→learn n'appelle export/import | → fonds commun **dormant** | + +**Verdict fédération (Claude + Qwen)** : +- ✅ **Bien architecturé** (Qwen) : `LearningPack` anonymise (machine_id blacklisté `_SENSITIVE_METADATA_KEYS:64`, `source_hash` SHA-256), `GlobalFAISSIndex` est **global** (clé `pack_source_hash`+`workflow_skeleton_id`+`node_name`+`app_name`, **aucun machine_id**), persistant (`.faiss`+`.meta.json`, `save/load` L245/277). Export prend **tous** les workflows (`processor._workflows.values()` L6305) sans filtre machine. +- ❌ **Mais doublement inerte** : (a) **pas d'auto-déclenchement** (rien dans capture→learn n'appelle export/import) ; (b) **`search()` jamais appelé** — `_global_faiss_index` n'est référencé QU'aux lignes `api_stream.py:6351-6372` (instanciation + `add_pack` à l'import). Aucun chemin de résolution/apprentissage/replay ne **lit** l'index global. → **write-only, jamais consulté** : contribue 0 au runtime. + +Conclusion : le fonds commun fédéré est **codé correctement mais débranché du runtime** — c'est exactement le maillon à activer pour la vision. + +## Où `machine_id` cloisonne vs route +- **ROUTE (légitime)** : ciblage d'un replay/d'une session vers le bon poste (`replay_engine.py:1559`, `start_replay`). +- **CLOISONNE (à corriger vs vision)** : (1) dossiers de stockage `{machine_id}/` ; (2) **filtre dur du cross-session learning** (`stream_processor.py:3284`) ; (3) `target_memory.db` local par machine. + machine_id **instable** (nouvel ID à chaque relance) qui fragmente même au sein d'un poste. + +## Gap vs vision « fonds commun → autonomie » (constats, pas décisions) +Pour réaliser la vision, il manque le câblage de : +1. **Dé-siloter le savoir workflows/sessions** : retirer le filtre machine_id du cross-session learning + stockage commun (ou index commun), en gardant machine_id pour le seul routing. +2. **Activer la fédération en continu** (auto-export/import ou store partagé) au lieu du manuel dormant — c'est l'endroit conçu pour ça. +3. **Câbler la couche graphe (4)** en live (aujourd'hui orpheline) pour un knowledge graph commun. +4. **Implémenter Shadow→Copilot→Autonomous** (aujourd'hui observe+log / design) consommant ce fonds commun. +5. **Stabiliser machine_id** (persisté) pour ne pas fragmenter. + +## Ce qui marche DÉJÀ comme fonds commun (à capitaliser) +- **Compétences YAML** (`core/competences/`) : micro-workflows réutilisables, états supervisés, **lus par tous les postes**. C'est le modèle commun qui fonctionne → piste à étendre. +- **Embeddings centralisés** (`data/training/embeddings/`) : matière première commune déjà là. + +## E. Pipeline server_v1 (cross-check Codex — intégré) +- Pipeline **WIRED** : capture → `_worker_queue.txt` → `run_worker.py` → `StreamProcessor.reprocess_session()` → workflow JSON → replay → apprentissage. `api_stream.py` importe+instancie `ReplayLearner`/`StreamProcessor`/`StreamWorker` (`:32/40/41`, `:562-563`, startup `:1626-1629`). +- **`ReplayLearner` = commun mais faiblement effectif** : `ActionOutcome` sans `machine_id`, stockage global `data/learning/replay_results/` ; MAIS `query_similar()` ne lit que le cache mémoire `_recent` (`:273-304`) et `build_replay_from_raw_events()` crée une **nouvelle instance** (`stream_processor.py:2379-2382`) → **l'historique JSONL global n'est pas exploité** après restart/hors instance globale. +- **Risque ambiguïté** : la queue worker ne porte que `session_id`, pas `machine_id` (`api_stream.py:734-760`) → résolution disque ambiguë si 2 machines ont le même `session_id`. +- `machine_id` **route** légitimement : `/replay`, `/replay/next` refusent les actions d'une autre machine (`:3978-3982`, `:4033-4054`), `_find_active_agent_session` filtre `machine_id`/`bg_` (`replay_engine.py:1559-1588`). + +## Pistes de correction CONVERGENTES (constats Codex+Claude — NON décidées, mapping seulement) +1. **Cantonner `machine_id` au routing/fleet/replay-target/audit** — pas au stockage ni au matching du savoir. +2. **Dé-siloter les workflows appris** : sortir du stockage logique `workflows/{machine_id}/` (ou neutraliser ce marqueur en lecture/matching). +3. **Retirer/rendre optionnel le filtre machine** dans `_run_cross_session_learning()` / `_find_best_cross_session_match()` (`stream_processor.py:3193-3203`, `3283-3286`) → apprendre sur tous les workflows compatibles. +4. **Brancher le fonds commun fédéré** : (a) alimenter le `GlobalFAISSIndex` en continu (auto export/import ou store partagé) ; (b) **appeler son `search()`** dans le hot-path résolution/apprentissage (aujourd'hui jamais lu). +5. **Rendre `ReplayLearner` durable** : charger/interroger l'historique JSONL global, réutiliser l'instance globale (pas une neuve par session). +6. **Stabiliser `machine_id`** (persisté) pour ne pas fragmenter intra-poste. +7. **Étendre le modèle "compétences communes"** (`core/competences/`, déjà commun + supervisé) comme colonne vertébrale du fonds commun. + +--- +*Sources : graphify-out (58k nodes) ; agents Explore #1/#2/#3 (⚠ verdicts "orphelin" sur imports top-level corrigés par lazy-import) ; cross-checks **Codex** (server_v1, msg 16:43) & **Qwen** (federation/FAISS, msg 16:50) intégrés ; vérifs directes `api_stream.py:6271-6372`, `faiss_global.py:199`, `stream_processor.py:3017-3022`.* diff --git a/docs/CHECKLIST_DGX_PRE_CLINIQUE.md b/docs/CHECKLIST_DGX_PRE_CLINIQUE.md new file mode 100644 index 000000000..6fa16d1bb --- /dev/null +++ b/docs/CHECKLIST_DGX_PRE_CLINIQUE.md @@ -0,0 +1,145 @@ +# CHECKLIST DGX — Contrôle avant installation clinique + +- `Auteur`: Qwen +- `Date`: 2026-06-19 +- `Version`: v1 — à vérifier point par point avant déploiement site clinique + +--- + +## 1. SERVICES — Tous démarrent au reboot + +| # | Service | Port | Statut attendu | Check | +|---|---|---|---|---| +| 1.1 | rpa-streaming | 5005 | `health=200` | `curl http://127.0.0.1:5005/health` | +| 1.2 | rpa-vision-v3-dashboard | 5001 | `401 sans creds, 200 avec creds` | `curl -u lea: http://127.0.0.1:5001/api/system/status` | +| 1.3 | rpa-vision-v3-vwb-backend | 5002 | `401 LAN, 200 loopback` | `curl http://127.0.0.1:5002/health` puis `curl http://192.168.x.x:5002/health` | +| 1.4 | rpa-agent-chat | 5004 | `200` | `curl http://127.0.0.1:5004/api/status` | +| 1.5 | rpa-vision-v3-api | 8000 | `fermé LAN` | `curl http://192.168.x.x:8000` → timeout/refused | +| 1.6 | rpa-vision-v3-vwb-frontend | 3002 | `200` | `curl http://127.0.0.1:3002` | +| 1.7 | rpa-vision-v3-stream-worker | 5099 | `running` | `systemctl status rpa-vision-v3-stream-worker` | +| 1.8 | rpa-vision-v3-worker | — | `running` | `systemctl status rpa-vision-v3-worker` | +| 1.9 | rpa-firewall | — | `active (exited)` | `systemctl status rpa-firewall` | +| 1.10 | Dashboard systemd | 5001 | **service system ACTIF** (pas fallback user) | ✅ **VALIDÉ reboot 20/06** — system service active, fallback user masked | + +**Check reboot** : `systemctl list-units --type=service | grep rpa` → tous `active running` ou `active exited` + +--- + +## 2. RÉSEAU — Ports sensibles fermés LAN + +| # | Port | Risque | Statut attendu | Check | +|---|---|---|---|---| +| 2.1 | 5900 (VNC GNOME) | Remote desktop | **LAN fermé, loopback OK** | `nmap 192.168.x.x -p 5900` → filtered/closed | +| 2.2 | 5902 (VNC VM Windows) | Remote desktop VM | **LAN fermé, tunnel SSH only** | `nmap 192.168.x.x -p 5902` → filtered/closed | +| 2.3 | 3389 (RDP/xrdp) | Remote desktop | **LAN fermé** | `nmap 192.168.x.x -p 3389` → filtered/closed | +| 2.4 | 22220 (SSH VM Windows) | Shell VM | **LAN fermé** | `nmap 192.168.x.x -p 22220` → filtered/closed | +| 2.5 | 8000 (API upload) | API non protégé | **LAN fermé** | `nmap 192.168.x.x -p 8000` → filtered/closed | +| 2.6 | 11434 (Ollama) | Modèles IA | **LAN fermé** | `nmap 192.168.x.x -p 11434` → filtered/closed | +| 2.7 | 5002 (VWB backend) | Données workflows | **LAN : auth requise (401)** | `curl http://192.168.x.x:5002/api/workflows/` → 401 | +| 2.8 | 5004 (Agent chat) | Chat interface | **À arbitrer** — ouvert ou fermé ? | Décision Dom | +| 2.9 | 3002 (VWB frontend) | Interface web | **À arbitre** — ouvert ou fermé ? | Décision Dom | + +--- + +## 3. SÉCURITÉ — Authentification + accès + +| # | Item | Statut attendu | Check | +|---|---|---|---| +| 3.1 | Dashboard Basic Auth | `401 sans creds` | `curl http://192.168.x.x:5001/api/system/status` → 401 | +| 3.2 | VWB Basic Auth | `401 LAN, 200 loopback` | Vérifié ✅ (commit cf81ce4c7) | +| 3.3 | Streaming Bearer Auth | `401 sans token` | `curl http://127.0.0.1:5005/api/v1/...` → 401 | +| 3.4 | SSH clé uniquement | Pas de password login | `grep PasswordAuthentication /etc/ssh/sshd_config` → no | +| 3.5 | Firewall persistant reboot | Ports fermés après reboot | ✅ **VALIDÉ reboot 20/06** — ports sensibles filtrés, services ouverts OK | +| 3.6 | RPA_SIGNING_KEY défini | FAISS metadata valide | ⚠️ **À FIXER** — HMAC mismatch, Option A en attente | + +--- + +## 4. VM WINDOWS — Autostart + stabilité + +| # | Item | Statut attendu | Check | +|---|---|---|---| +| 4.1 | VM boot auto au reboot DGX | Service systemd user `aivanov` | ✅ **VALIDÉ reboot 20/06** — `win11-arm-lea.service` auto-démarre, linger=yes | +| 4.2 | VM accessible VNC | Tunnel SSH `localhost:5902` | Vérifié ✅ | +| 4.3 | VM ne pas libvirt en parallèle | Pas de conflit disk.qcow2 owner | ⚠️ **À DOCUMENTER** — ne pas lancer libvirt VM | +| 4.4 | disk.qcow2 owner = aivanov | Pas libvirt-qemu | `ls -la disk.qcow2` → aivanov:aivanov | +| 4.5 | swtpm lancé par script | Pas manuel | Script standalone gère swtpm ✅ | +| 4.6 | Léa config.txt pointe DGX | Pas cloud URL | `cat config.txt` → DGX IP | + +--- + +## 5. DONNÉES — Persistence + integrity + +| # | Item | Risque | Statut attendu | Check | +|---|---|---|---|---| +| 5.1 | workflows.db | 24 workflows live | `curl -u lea: http://127.0.0.1:5001/api/workflows | jq '.total'` → 24 | +| 5.2 | FAISS index | 13666 vectors | `curl ... /api/knowledge-base/stats | jq '.vectors_indexed'` → 13666 | +| 5.3 | FAISS metadata HMAC | Test endpoint 200 | ⚠️ **À FIXER** — Option A (resigner) | +| 5.4 | Sessions training | Non trackées git → safe au reset | `ls data/training/sessions/` | +| 5.5 | Git aligné | HEAD = dernier commit P0 | `git log -1` → cf81ce4c7 | +| 5.6 | workflows.db préservé au git reset | Backup avant reset | ⚠️ **Procédure à respecter** | + +--- + +## 6. STABILITÉ — Test reboot (✅ exécuté en réel le 2026-06-20) + +| # | Item | Check | Résultat | Verdict | +|---|---|---|---|---| +| 6.1 | Reboot DGX | Coupure secteur 02:07 | 9 services reviennent | ✅ PASS | +| 6.2 | VM Windows auto-start | `win11-arm-lea.service` | VM auto-démarre | ✅ PASS | +| 6.3 | Firewall persisté | Ports après reboot | Sensibles filtrés, services ouverts | ✅ PASS | +| 6.4 | Dashboard systemd | Après reboot | System service actif, user fallback masked | ✅ PASS | +| 6.5 | Worker healthy | Après reboot | PID 2267 actif, last_cycle continu | ✅ PASS | +| 6.6 | **IP DHCP dérive** | `.45` → `.46` | IP statique `.45` appliquée (Dom) | ⚠️ **G1 — IP statique obligatoire clinique** | +| 6.7 | **OVMF corruption VM** | Coupure brutale | OVMF corrompu, récupération manuelle (Codex) | ⚠️ **G2 — auto-réparation OVMF à implémenter** | +| 6.8 | **Léa guest reconnecte** | config.txt | CONFIGURE_ME, pas DGX | ⚠️ **G4 — config.txt à renseigner** | + +--- + +## 7. PRÉ-REQUIS DSI (envoyés à Nicolas PORQUET) + +| # | Item | Statut | Check | +|---|---|---|---| +| 7.1 | Proxy HTTPS | À installer clinique | Architecture validée | +| 7.2 | Docker | À installer | — | +| 7.3 | VLAN isolation | À configurer | — | +| 7.4 | SSH clé uniquement | ✅ Configuré DGX | `PasswordAuthentication no` | +| 7.5 | 100% on-premise | ✅ Aucune cloud call | Vérifier config Léa | +| 7.6 | Pas de secrets exposés | ✅ .env.local permissions | `ls -la .env.local` → 600 | + +--- + +## ⚠️ ITEMS À FIXER AVANT CLINIQUE + +1. **Dashboard fallback user** → ✅ **FIXÉ 20/06** (mask persistant, system service actif) +2. **Auto-start VM** → ✅ **VALIDÉ 20/06** (reboot réel prouvé) +3. **FAISS Option A** → ✅ **FIXÉ 19/06** (metadata resigné, 13666 vectors, test success=true) +4. **Git DGX aligné** : DGX sur ec1fb81, cible cf81ce4c7 → aligner avec backup workflows.db +5. **Test reboot** → ✅ **exécuté en réel 20/06** (5 PASS, 3 gaps identifiés) +6. **G1 Dérive IP DHCP** : IP statique labo `.45` OK ; clinique = Ethernet `.178` obligatoire +7. **G2 Auto-réparation OVMF** : snapshot sain au boot + restauration auto si TianoCode loop → **À IMPLÉMENTER** +8. **G4 Léa reprise auto** : config.txt persistant DGX + token + auto-login → **À RENSEIGNER** + +--- + +## Commandes smoke rapide (à lancer sur DGX) + +```bash +# Services +systemctl list-units --type=service | grep rpa + +# Health endpoints +curl -s http://127.0.0.1:5002/health +curl -s http://127.0.0.1:5005/health +curl -s -u lea:v_zhmqOpGYcR-t7xJFKZyW-LjpvBuOOKss0ZleyH4jQ http://127.0.0.1:5001/api/system/status | jq '{workflows_count,status}' +curl -s -H "Authorization: Bearer o3_LHqV_7_Gc6OVPHndhsBbvG6HJ5PCgl8yIBhGUIz8" http://127.0.0.1:5005/api/v1/traces/stream/processing/status | jq '{status,processing_ready}' + +# Firewall LAN +nmap 192.168.1.45 -p 5900,5902,3389,22220,8000,11434 + +# VM +virsh -c qemu:///system list # doit être VIDE (standalone, pas libvirt) +ps aux | grep qemu-system-aarch64 | grep win11 + +# Git +cd ~/ai/rpa_vision_v3 && git log -1 --oneline +``` diff --git a/docs/DECISIONS_PRODUIT_EN_ATTENTE_2026-06-23.md b/docs/DECISIONS_PRODUIT_EN_ATTENTE_2026-06-23.md new file mode 100644 index 000000000..b42b08b8f --- /dev/null +++ b/docs/DECISIONS_PRODUIT_EN_ATTENTE_2026-06-23.md @@ -0,0 +1,87 @@ +# Décisions produit en attente — à trancher à tête reposée + +- `Date`: 2026-06-23 +- `Auteur`: Claude (questions) / Dom (réponses) +- `Usage`: Dom remplit la ligne **Réponse Dom** quand il a la tête au calme. Chaque décision débloque un ou plusieurs chantiers du plan `PLAN_ACTION_SUITE_2026-06-23.md`. Segmentation par **fonctionnalité (F#)**. + +> Contexte : H3 (cœur produit) traité **en premier**, décisions d'abord puis exécution séquencée (validé Dom 23/06). On ne touche pas l'archi rejeu pendant la livraison clinique du jour. + +--- + +## ✅ Déjà tranché (23/06) + +| Réf | Décision | Réponse Dom | +|---|---|---| +| **PROC-1** | « H3 en premier » = trancher les décisions produit d'abord, exécution séquencée ensuite | ✅ OK | +| **F6-1** | Niveau de mutualisation du fonds appris | ✅ **Cross-clinique ET intra-clinique** → la fédération anonymisée (cross) **est dans le périmètre**, + lever le silo `machine_id` entre postes d'une même clinique | +| **F11-1** | Accès distant | ✅ **Multi-VPN par site** (WireGuard=nôtre, Stormshield=clinique, autres à venir) ; SSH cert + RDP = transport commun | +| **F9-1** | Source de vérité workflows | ✅ **DB SQLAlchemy = vérité, JSON = échange** ; métrique produit = rejouables validés ; migration JSON→DB séquencée post-clinique (23/06) | +| **F1-1** | Critère de fusion workflows | ✅ **Signature de trajectoire** (hash de la séquence d'actions/écrans) → débloque U-A create-or-update (23/06) | +| **F2-1 / F14-1** | Principe rejeu intelligent | ✅ **Oui, prérequis** — le rejeu consulte le fonds appris (TargetMemory + FAISS anchors), pas de coords figées ; cohérent F6 cross-clinique (23/06) | + +--- + +## ⏳ À trancher + +> ✅ **Tranchés 23/06** (voir §Déjà tranché) : Q-F9-1 (DB), Q-F1-1 (signature trajectoire), Q-F2-1 + Q-F14-1 (rejeu intelligent = prérequis). **Restent** : Q-F2-2 (point d'entrée Léa — Claude trace en read-only), et les items **parkés** : Q-F8-* (natif/sécu mis de côté momentanément), Q-F11-2 (VPN). + +### F2 — Rejeu intelligent (le plus urgent) + +**Q-F2-1 — Principe « rejeu intelligent ».** Valides-tu que le rejeu **doit consulter le fonds appris** (TargetMemoryStore + FAISS anchors) au lieu de rejouer des coordonnées figées ? *(Vérif runtime 23/06 : aujourd'hui le rejeu est « brut », il ne consulte rien.)* C'est la décision qui change l'archi du replay direct (chantiers R2/R3). +> ⚠️ **Quasi-entraînée par ta décision F6 = cross-clinique** : un pack fédéré anonymisé n'a ni coords ni templates → la re-résolution visuelle (anchors/FAISS) devient le seul moyen de rejouer ailleurs. Voir Q-F14-1. +> **Réponse Dom :** _____ + +**Q-F2-2 — Provider Léa au runtime.** Quel modèle/route sert la **résolution** pendant le rejeu ? (impacte l'import auto R1 + la cascade de résolution). Options connues : Qwen3-VL-4B/vLLM (grounder prod), gemma4 (cerveau/lecteur), autre. +> **Tracé Claude 23/06** (read-only) : point d'entrée runtime = **agent_chat (5004)** → `SemanticMatcher.find_workflow()` (`agent_chat/app.py:906`) sur `/api/chat`. Sélection sur **fichiers JSON** (`data/workflows/`, `data/training/workflows/`, `…/live_sessions/workflows/`), **pas la DB** → ⚠️ **gap avec Q-F9-1 (DB=vérité)** : la sélection runtime devra migrer sur la DB. **Pas de filtre machine_id** à la sélection. `api_stream` (5005) = présent mais pas le chemin chat actif. Modèle de résolution = Qwen3-VL-4B grounder + gemma4 (bench 13/06). + +### F1 / F9 — Apprentissage & workflows + +**Q-F1-1 — Critère de fusion des workflows** *(tu as dit « on verra à tête reposée »)*. Quand deux sessions apprises sont-elles « le même » workflow (→ create-or-update plutôt que doublon) ? Par signature d'écran de départ ? nom ? séquence d'actions ? application cible ? +> **Réponse Dom :** _____ + +**Q-F9-1 — Source de vérité workflows + métrique produit.** → **Ma reco ci-dessous (§Reco F9-1)**, à valider ou amender. +> **Réponse Dom :** _____ + +### F14 — Unification Léa ↔ VWB (anchors) + +**Q-F14-1 — Re-exécutabilité des packs fédérés (décision induite).** Un pack cross-clinique anonymisé n'emporte **ni coordonnées ni templates** (PII) → re-résolution **visuelle** obligatoire au rejeu (anchors/FAISS). ⇒ acte-t-on que **le « rejeu intelligent » (Q-F2-1) devient un prérequis** (pas une option) dès lors que F6 = cross-clinique ? +> **Réponse Dom :** _____ + +*(Le sous-chantier U-B « propagation des anchors aux substeps compound + ré-import » ne demande aucune décision produit — c'est un fix prêt, à exécuter post-stabilisation sous GO. Idem U-D « asymétrie grounding UI-DETR-1 vs cascade » = à trancher plus tard, sujet ouvert post-démo.)* + +### F8 — Exécution native agentique (zéro-shot) + +*Constat 23/06 : briques présentes et wirées (boucle ORA `/execute/instruction`, planner NL→plan gemma4), mais mode « free » peu mûr, **sans sandbox ni validation humaine**, exécution directe sur l'host.* + +**Q-F8-1 — Périmètre du mode natif.** Desktop/navigateur **généraliste** (« ouvre YouTube ») ou **borné aux apps métier** (Easily…) ? (impacte le risque et le choix moteur) +> **Réponse Dom :** _____ + +**Q-F8-2 — Surface d'exécution / sandbox (sécurité).** Acte-t-on que le mode free ne tourne **QUE dans un sandbox** (VM/Xvfb + kill-switch + pause humaine), **jamais l'host** — prérequis avant tout élargissement ? (= la décision CUA P1) +> **Réponse Dom :** _____ + +**Q-F8-3 — Moteur agentique.** Garder ORA + gemma4/Qwen3-VL (100 % local) ou évaluer un agent computer-use dédié (UI-TARS mode agent, Qwen3-VL agent…) ? → un mini-état-de-l'art web peut éclairer (les modèles évoluent vite). +> **Réponse Dom :** _____ + +**Q-F8-4 — Niveau d'autonomie.** Validation humaine step-by-step au début (Copilot) puis desserrage progressif (cohérent F7 Shadow→Copilot→Autonomous + safety) ? +> **Réponse Dom :** _____ + +### F11 — Accès distant multi-VPN + +**Q-F11-2 — Abstraction « fiche site → accès ».** Quels **types de site/VPN** anticiper au-delà de Stormshield + WireGuard ? Faut-il coder dès maintenant une abstraction générique « fiche site → méthode d'accès », ou gérer les 2 cas connus d'abord et généraliser plus tard ? +> **Réponse Dom :** _____ + +--- + +## 💡 Reco Claude — Q-F9-1 (source de vérité workflows) + +**Recommandation : la DB (SQLAlchemy/SQLite `workflows.db`) = source de vérité unique ; le JSON = format d'échange/export uniquement (packs fédération, portabilité, import VWB), PAS un 2ᵉ magasin.** + +**Pourquoi la DB :** +- Les workflows sont **relationnels** (workflow ↔ steps ↔ substeps ↔ anchors ↔ session/machine) : intégrité référentielle, requêtes, versionnement. +- Le **create-or-update à la fusion** (Q-F1-1) = un upsert atomique trivial en SQL, pénible et risqué sur des fichiers JSON. +- **Concurrence** : 5 Léa qui apprennent/s'enrôlent en parallèle (test de charge) → JSON + chemins relatifs au cwd = races + fragilité du symlink (DETTE-015 déjà constatée). +- **F6 fédération** a justement besoin de DB-comme-vérité + JSON-comme-échange : un `LearningPack` = sérialisation d'une requête DB → pack JSON ; l'import = désérialisation → DB. JSON-comme-vérité **entre en conflit** avec ça. + +**Nuance de timing :** la **migration** JSON→DB est un refactor persistant → **séquencée post-clinique** (après stabilisation + merge), comme le prévoit déjà `PLAN_MIGRATION_WORKFLOWS_STORE`. D'ici là le symlink reste, dette connue, **on n'y touche pas pendant la livraison**. + +**Métrique produit (24/79/37) :** la seule défendable face à un client/DSI = **les workflows rejouables validés** (≈ les **24** VWB aujourd'hui, ce que Léa exécute vraiment de bout en bout). Les **79** (catalogue agent-chat) et **37** (KB/FAISS) = compteurs **internes**, à ne pas exposer comme « N workflows ». Une fois la DB source de vérité : métrique = `count(workflows WHERE statut = rejouable_validé)`. diff --git a/docs/DESIGN_ANONYMISATION_TOKENS_TYPES_2026-06-28.md b/docs/DESIGN_ANONYMISATION_TOKENS_TYPES_2026-06-28.md new file mode 100644 index 000000000..b841fe022 --- /dev/null +++ b/docs/DESIGN_ANONYMISATION_TOKENS_TYPES_2026-06-28.md @@ -0,0 +1,44 @@ +# Design — Anonymisation par tokens typés (= apprentissage des variables) + +- `Date`: 2026-06-28 +- `Auteur`: Claude (idée Dom) +- `Statut`: design actif +- `Origine`: PII patient en clair confirmée en production clinique (titres de fenêtre + **contenu médical des `text_input`**). Idée Dom : remplacer la PII par des **tokens typés** plutôt que flouter. + +## Principe directeur (Dom 28/06) + +**Léa apprend l'INTERFACE, pas la DONNÉE.** Ce qui compte pour l'apprentissage, c'est **où sont les champs, leur type, l'enchaînement** — **pas leur contenu**. Après apprentissage, Léa devra : +- **saisir des données** dans les bons champs (contenu fourni au runtime par un agent / une extraction), +- **lire les écrans pour extraire des données** que l'« agent » traitera. + +→ Le contenu capturé (texte saisi, valeurs OCR, nom patient dans un titre) est une **variable**, pas une constante à mémoriser. On le remplace par un **token typé** : on **anonymise** ET on **apprend la carte des variables** d'un seul geste. Flouter détruit l'info ; tokeniser la préserve **structurellement** sans la valeur. + +Conforme au principe d'anonymisation déjà acté (`feedback_anonymisation_stricte` : remplacer uniquement les identités, ne jamais réécrire le clinique) — ici on va plus loin : **le contenu d'un champ saisi n'est même pas nécessaire**, on garde le champ, pas la valeur. + +## Modèle d'anonymisation par type de capture + +| Capture | Ce qu'on garde (interface) | Ce qu'on retire (donnée) | Comment | +|---|---|---|---| +| **`text_input`** (texte tapé) | le **fait** qu'un champ texte a été saisi (+ éventuellement nb de caractères) | **tout le contenu** (diagnostics, notes médicales = données de santé) | **`[SAISIE]`** (option **b**, décision Dom). Pas de NER nécessaire — on ne stocke simplement pas le contenu. **Résout la fuite la plus grave.** | +| **`active_window_title`** | l'**app/écran** (« GXD5 Pacs », « Expert Santé », « Firefox ») = contexte d'apprentissage | l'**identité patient** (nom, IPP, âge) | tokens typés `[NOM_1]`/`[IPP_1]`/`[AGE_1]` via **couche 1 (regex+structurel)** + **couche 2 (NER)** pour les noms libres | +| **OCR / lecture d'écran** | les **zones/champs** d'où extraire | les **valeurs** extraites (PII) | token typé de la zone (« extraire un `[NOM]` ici »), valeur réinjectée au runtime, non persistée | + +## Architecture (2 couches + intégration) + +- **Couche 1 — regex + structurel (FAIT, `agent_v0/server_v1/pii_sanitizer.py`)** : tokens typés cohérents, **sans modèle**, déployable partout. Capte IPP/NIR/TEL/EMAIL/AGE + noms format clinique (`NOM (NAISSANCE) Prénom`, `[Nom Prénom]` PACS) + blacklist logiciels. Couvre 5/7 patients du jour, **tous les IPP**. 5 tests verts. +- **Couche 2 — NER CamemBERT-bio (à vendorer)** : moteur `CamembertNerManager` du projet `~/ai/anonymisation` (ONNX **CPU**, ~9-12 ms/titre, labels typés PER/IPP/AGE/DATE/HOPITAL…). Pour les **noms libres** que la couche 1 rate (« Prénom NOM — Firefox »). Modèle **421 Mo → côté DGX** (postes cliniques trop légers — contrainte Dom). Lazy, optionnel. +- **Intégration** : une fonction `sanitize_event(event, mapping)` au **point de persistance serveur** : `text_input` → `[SAISIE]` ; titres → `anonymize_text` (couche 1 + 2) ; cohérence par **mapping de session** (même entité → même token). + +## Placement & déploiement + +- **Côté serveur (DGX)** : les events y remontent déjà ; le client reste léger. (Cible privacy-by-design = supprimer le contenu **au plus près du poste** avant stream — évolution, quand le client pourra être modifié.) +- **Déploiement GATED** : ne pas redémarrer le serveur DGX pendant des sessions live. +- **Ne casse pas l'apprentissage** : `workflow_trajectory_signature` tokenise déjà la PII pour le discriminant ; les tokens typés **renforcent** la carte des variables. + +## Décisions Dom +- ✅ **Option (b)** pour `text_input` : placeholder `[SAISIE]`, on ne garde pas le contenu (28/06). +- ⏳ **Donnée déjà capturée** (9 patients, 6 IPP, contenu médical) : assainir a posteriori vs **purger** — avec Amina (reco Claude : purger les sessions du jour une fois le fix en place). + +## Connexe +- **Config-remontée** des specs postes (CPU/RAM/GPU/OS) pour cibler + fournir des prérequis (`screen_metadata` en remonte déjà une partie). +- Réutilise : `~/ai/anonymisation` (`camembert_ner_manager.py`, gazetteers INSEE, blacklists, regex, PLACEHOLDERS). diff --git a/docs/DESIGN_OVMF_AUTOREPAIR_VM_2026-06-20.md b/docs/DESIGN_OVMF_AUTOREPAIR_VM_2026-06-20.md new file mode 100644 index 000000000..b09b0da39 --- /dev/null +++ b/docs/DESIGN_OVMF_AUTOREPAIR_VM_2026-06-20.md @@ -0,0 +1,59 @@ +# Design — Auto-réparation OVMF de la VM Léa (gap G2, reprise non-assistée) + +- `Auteur`: Claude (infra) +- `Date`: 2026-06-20 ~03:15 CEST +- `Statut`: **PROPOSITION / design read-only** — à appliquer après revue Dom (garde-fou : aucun changement service prod sans Dom). +- `Référence`: post-mortem `docs/POSTMORTEM_PANNE_SECTEUR_DGX_2026-06-20.md` (gap G2). + +## 1. Problème + +Une coupure brutale corrompt `OVMF_VARS.fd` (NVRAM UEFI) → la VM boucle dans TianoCore/Windows Boot Manager. Le 2026-06-20, blocage 02:07→02:18 jusqu'à intervention manuelle de Codex (restore OVMF connu-bon + TPM frais). En clinique sans technicien, ce blocage serait **permanent**. + +## 2. Pourquoi le service ne s'auto-répare pas aujourd'hui + +`~/.config/systemd/user/win11-arm-lea.service` : +- `Restart=on-failure` — **inopérant** : en boucle TianoCore, QEMU **ne sort pas** (process « running » à 99 % CPU). Aucun échec → aucun restart. +- `ExecStartPre` efface `tpm2-00.permall` (TPM frais à chaque boot) **mais pas `OVMF_VARS.fd`** → un OVMF corrompu **survit aux restarts** → boucle permanente. + +## 3. Détecteur fiable + +Le **guest agent QEMU** (`windows-11-arm-lea-agent.sock`) ne répond **que** si Windows a réellement booté. En boucle firmware, il ne répond jamais. Signature d'échec = *pas de réponse guest-agent après N min* **+** *CPU QEMU élevé*. (Plus robuste qu'une analyse framebuffer ; v1 suffisante.) + +## 4. Design proposé (2 briques + garde-fous) + +### Brique A — Snapshot « known-good » après boot sain +Watchdog compagnon (lancé en `ExecStartPost=... &` ou service apparié `vm-health-watchdog.service`) : +1. Fenêtre de boot (0→~6 min), poll guest-agent toutes les 30 s (`guest-ping` via socket agent). +2. **Guest-agent répond** → boot sain : copie atomique `OVMF_VARS.fd` → `OVMF_VARS.fd.known-good`, écrit sentinel `boot-ok`, log horodaté. C'est le point de restauration. + +### Brique B — Détection boucle + restauration auto +Si à T+6 min le guest-agent **ne répond toujours pas** ET CPU QEMU > 80 % (signature boucle) : +1. Écrit sentinel `boot-failed`. +2. Archive l'OVMF suspect : `OVMF_VARS.fd` → `OVMF_VARS.fd.failed-` (convention déjà utilisée par Codex). +3. Restaure `OVMF_VARS.fd.known-good` → `OVMF_VARS.fd`. +4. Arrête le QEMU en boucle firmware. **Sûr** : aucun OS n'a booté (guest-agent jamais monté) → pas de risque de corruption Windows (≠ règle « jamais kill un Windows booté », qui ne s'applique pas ici). +5. systemd relance (`Restart=on-failure` se déclenche enfin) avec le bon OVMF. + +### Garde-fous (anti-mauvais comportement) +- **Pas de known-good** (1er boot, jamais eu de boot sain) → ne PAS restaurer ; log + alerte, comportement actuel conservé. +- **Compteur d'essais** : max 2 auto-restaurations consécutives (sentinel compteur). Au-delà → stop + alerte (évite la boucle restore→échec→restore si le known-good est lui aussi mauvais). +- **Faux positifs** : Windows peut booter lentement → fenêtre 6–8 min + double critère (guest-agent ET CPU). Réglable. +- **TPM** : on garde l'effacement `tpm2-00.permall` existant (évite le hang TPM) ; l'auto-réparation OVMF est complémentaire. +- **Idempotence** : nettoyage des sentinels en début de cycle. + +## 5. Points d'intégration (à valider Dom avant écriture) + +- `win11-arm-lea.service` : ajouter `ExecStartPre` de garde (si `boot-failed` + known-good → restaurer avant lancement) et `ExecStartPost` qui lance le watchdog. +- Nouveau script `vm-health-watchdog.sh` (briques A+B) dans `~/quickemu-win11-arm-lea/`. +- Optionnel : `vm-health-watchdog.service` (PartOf=win11-arm-lea.service) plutôt qu'un `&`, pour un cycle de vie propre. + +## 6. Plan de test (sans risque, sur la VM labo) + +1. Boot sain → vérifier création `OVMF_VARS.fd.known-good` + sentinel `boot-ok`. +2. Simuler corruption (copier l'`OVMF_VARS.fd.failed-powercut-20260620-021854` archivé sur le live) → vérifier détection à T+6 min, archivage, restauration, restart, boot sain. +3. Vérifier le compteur d'essais (corrompre aussi le known-good) → stop + alerte, pas de boucle infinie. +4. Mesurer le temps total de reprise auto (cible < 10 min sans intervention). + +## 7. Décision attendue (Dom) + +GO/NO-GO sur l'écriture + le réglage de la fenêtre (6 vs 8 min) et du mécanisme d'alerte (log seul ? message coordination ? mail ?). Application supervisée, un changement / un test, après ton réveil. diff --git a/docs/INSTALLATION_MULTI_SITE.md b/docs/INSTALLATION_MULTI_SITE.md new file mode 100644 index 000000000..1d5f2f6db --- /dev/null +++ b/docs/INSTALLATION_MULTI_SITE.md @@ -0,0 +1,540 @@ +# Guide d'installation Lea - POC, MVP, production et multi-site + +- Date: 2026-06-19 +- Statut: version initiale exploitable, a durcir avant production +- Scope: installations Lea/RPA Vision V3 sur DGX + postes ou VM Windows +- Source: etat POC DGX, runbooks coordination, installeur Windows, systemd, dashboard/VWB/worker + +## Objectif + +Ce document sert de base d'installation reproductible pour plusieurs phases: + +1. **POC**: installation controlee sur un site pilote, avec assistance technique forte. +2. **MVP**: installation repetable avec artefacts figes, checklist, rollback et preuves. +3. **Production**: installation industrialisee, secrets par site, supervision, sauvegardes, signature de l'installeur, support. +4. **Multi-etablissement**: meme produit, mais variables reseau, comptes, tokens, politiques de securite et donnees separees par etablissement. + +Le principe directeur: une installation Lea ne doit pas dependre de la memoire d'un agent ou d'une session de debug. Elle doit etre executable par une checklist, testable, rollbackable et auditable. + +## Architecture cible d'une installation + +### Composants cote serveur + +| Composant | Role | Port POC | Exposition attendue | +|---|---|---:|---| +| Dashboard Flask | supervision, verite produit, fleet, workflow status | 5001 | LAN/VPN autorise avec auth | +| VWB backend | workflows, anchors, base SQLite VWB | 5002 | local ou LAN controle selon packaging | +| Agent Chat | chat Lea, bulles, feedback temps reel | 5004 | accessible depuis poste/VM Windows | +| Streaming/Fleet | ingestion agent Windows, sessions, API agent | 5005 | accessible depuis poste/VM Windows | +| Upload/API core | API interne upload/traitement | 8000 | loopback par defaut | +| Worker | compilation/apprentissage/replay | n/a | service systemd | +| Ollama/VLM | inference locale | 11434 | local DGX, pas expose sauf decision explicite | +| VM/VNC Windows | console Windows DGX si utilisee | 5902 | tunnel SSH/VPN uniquement | + +### Composants cote Windows + +| Composant | Role | Artefact | +|---|---|---| +| Lea agent | capture, tray, chat, apprentissage, streaming | `deploy/lea_package` ou installeur Inno | +| `config.txt` | URL serveur, token API, machine id, label utilisateur | genere par dashboard ou installeur | +| Installeur Inno | installation MVP/prod, Python embedded, silent install | `deploy/installer/Lea.iss` | +| VM Windows | poste de test ou execution clinique | VMware, Hyper-V, QEMU standalone ou poste physique | + +## Niveaux d'installation + +| Niveau | Usage | Acceptable | Interdit | +|---|---|---|---| +| POC labo | tests rapides, validation technique | scripts manuels documentes, tunnel VNC, debug coordonne | secrets reutilises sans trace, actions non consignees | +| POC site | installation pilote chez client | artefact fige, runbook, rollback, preuves | activer reseau/site sans fenetre et rollback | +| MVP | repetition sur plusieurs postes | installeur signe ou controle, config par poste, smoke automatique | ZIP manuel non versionne comme seul chemin | +| Production | support multi-etablissement | CI release, signature, supervision, backups, rotation secrets | `DASHBOARD_AUTH_DISABLED`, tokens partages, ports remote ouverts | + +## Fiche site obligatoire + +Chaque etablissement doit avoir une fiche separee avant installation. + +```text +SITE_ID= +Nom etablissement= +Contact technique= +Contact metier= +Fenetre installation= +Fenetre rollback= + +DGX_HOSTNAME= +DGX_IP_LAB= +DGX_IP_SITE= +DGX_PREFIX= +DGX_GATEWAY= +DGX_DNS_1= +DGX_DNS_2= +IPv6=off/on +VLAN=non/oui + id +Interface Ethernet cible= + +Dashboard URL= +Streaming URL agent= +Agent Chat URL= +VWB URL= + +Windows cible= +Type Windows=poste physique / VMware / Hyper-V / QEMU DGX +Nom machine Windows= +Utilisateur Windows= +Mode acces distant=VNC / RDP / console hyperviseur / aucun + +RPA_API_TOKEN= +DASHBOARD_USER= +DASHBOARD_PASSWORD= +ENCRYPTION_PASSWORD= +SECRET_KEY= + +Politique retention= +Chemin backup= +Responsable validation GO= +``` + +Regle: aucun token ou mot de passe d'un site ne doit etre reutilise sur un autre site. + +## Pre-requis avant installation + +### Pre-requis DGX ou serveur Linux + +- OS Linux valide pour le deploiement. +- Acces admin local ou fenetre avec administrateur. +- Python 3.10 a 3.12. +- GPU NVIDIA si inference VLM locale attendue. +- Ollama installe si le mode VLM local l'utilise. +- Repo disponible sur la branche cible, par exemple `poc-dgx` pour le POC. +- Ports internes/externes arbitres avant installation. +- Disque libre suffisant pour sessions, captures, backups et modeles. +- Politique backup validee avant toute migration ou reset. + +### Pre-requis Windows + +- Windows 10/11. +- Droits suffisants pour installer Lea. +- Acces reseau au DGX sur les ports agent requis. +- Pour MVP/prod, privilegier l'installeur Inno avec Python embedded. +- Pour POC, le ZIP `deploy/Lea_v.zip` reste acceptable si documente. + +### Pre-requis securite + +- `DASHBOARD_AUTH_DISABLED` interdit hors dev local. +- `RPA_AUTH_DISABLED` interdit hors dev local. +- `RPA_API_TOKEN` obligatoire. +- `DASHBOARD_PASSWORD` obligatoire. +- Remote desktop/VNC/RDP ouverts uniquement en tunnel/VPN, jamais en LAN large par defaut. +- Les secrets sont stockes hors git. +- Le poste Windows doit afficher clairement l'etat d'enregistrement/apprentissage. + +## Procedure POC actuelle + +Cette section decrit l'etat connu du POC DGX. Elle n'est pas encore le chemin production final. + +### 1. DGX - recuperer le code + +```bash +git clone rpa_vision_v3 +cd rpa_vision_v3 +git checkout poc-dgx +``` + +Si le DGX contient deja des donnees runtime, ne jamais faire de reset destructif sans backup des chemins suivants: + +```text +visual_workflow_builder/backend/instance/workflows.db +visual_workflow_builder/backend/data/ +data/training/ +data/runtime/ +data/workflows +graphify-out/ si l'historique graphe doit etre preserve +``` + +### 2. DGX - environnement Python + +```bash +python3 -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +``` + +### 3. DGX - configuration runtime + +Creer `.env.local` depuis le modele: + +```bash +cp deploy/systemd/rpa_vision_v3.env.example .env.local +``` + +Valeurs obligatoires a remplacer: + +```text +ENCRYPTION_PASSWORD=CHANGE_ME +SECRET_KEY=CHANGE_ME +RPA_API_TOKEN=CHANGE_ME +DASHBOARD_USER=lea +DASHBOARD_PASSWORD=CHANGE_ME +ENVIRONMENT=production +RPA_PROCESSING_WORKER=external +RPA_API_HOST=127.0.0.1 +RPA_DASHBOARD_HOST=0.0.0.0 +RPA_VLM_MODEL=gemma4:26b +RPA_GROUNDING_ENGINE=qwen3vl_vllm +VLLM_MODEL=Qwen/Qwen3-VL-4B-Instruct +``` + +Pour un site, documenter explicitement quels services restent en loopback et quels services sont accessibles depuis la VM/poste Windows. En POC DGX, le dashboard est expose au LAN sur `0.0.0.0:5001` avec auth. En clinique durcie, preferer un reverse proxy HTTPS ou un bind loopback derriere proxy/VPN. + +### 4. DGX - services + +Services systemd attendus ou equivalents: + +```text +rpa-agent-chat.service +rpa-firewall.service +rpa-streaming.service +rpa-vision-v3-api.service +rpa-vision-v3-dashboard.service ou fallback rpa-vision-v3-dashboard-user +rpa-vision-v3-worker.service +rpa-vision-v3-stream-worker.service +rpa-vision-v3-vwb-backend.service +rpa-vision-v3-vwb-frontend.service +rpa-vllm-grounder.service si active +rpa-vision.target +``` + +Commandes de controle: + +```bash +systemctl status rpa-agent-chat.service +systemctl status rpa-firewall.service +systemctl status rpa-streaming.service +systemctl status rpa-vision-v3-dashboard.service +systemctl status rpa-vision-v3-worker.service +systemctl status rpa-vision-v3-stream-worker.service +systemctl status rpa-vision-v3-vwb-backend.service +systemctl status rpa-vision-v3-vwb-frontend.service +systemctl list-units 'rpa*' +``` + +En POC dev, `svc.sh` reste utilisable: + +```bash +./svc.sh status +./svc.sh start +./svc.sh restart api +./svc.sh stop +``` + +### 5. DGX - modele local + +Verifier Ollama et les modeles attendus. Le POC DGX distingue: + +- cerveau/lecteur Ollama: par exemple `gemma4:26b` selon `.env.local`; +- grounder VLM via vLLM: `Qwen/Qwen3-VL-4B-Instruct` avec `think=false`. + +```bash +curl http://localhost:11434/api/tags +ollama list +# Si le modele site manque: +ollama pull +``` + +Ne pas exposer `11434` au LAN sans decision explicite. + +### 6. DGX - donnees VWB/apprentissage + +Verifier: + +```bash +test -f visual_workflow_builder/backend/instance/workflows.db +find visual_workflow_builder/backend/data -maxdepth 3 -type f | head +find data/training -maxdepth 3 -type f | head +``` + +Endpoints utiles: + +```bash +curl -s http://127.0.0.1:5002/health +curl -s http://127.0.0.1:5005/health +curl -s http://127.0.0.1:5001/api/system/status +``` + +Selon l'auth dashboard, `5001` peut repondre `401`; c'est attendu si l'auth est active. + +## Installation Windows + +### Chemin POC ZIP + +Construire le package: + +```bash +./deploy/build_package.sh --clean +./deploy/build_package.sh +``` + +Copier `deploy/Lea_v.zip` sur la machine Windows, puis: + +1. Dezipper. +2. Modifier `config.txt`. +3. Lancer `install.bat`. +4. Lancer `Lea.bat`. + +`config.txt` minimum: + +```text +RPA_SERVER_URL=http://:5005/api/v1 +RPA_API_TOKEN= +RPA_MACHINE_ID= +RPA_USER_LABEL= +RPA_BLUR_SENSITIVE=false +RPA_LOG_RETENTION_DAYS=180 +``` + +### Chemin MVP/prod installeur + +Le chemin cible est l'installeur Inno Setup: + +```bash +./deploy/installer/build_installer.sh +``` + +Sortie attendue: + +```text +deploy/releases/Lea-Setup-v1.0.1.exe +``` + +Installation silencieuse type: + +```cmd +Lea-Setup-v1.0.1.exe /VERYSILENT /CONFIG=C:\temp\enroll.txt /DIR="C:\Lea" /LOG="C:\temp\lea-install.log" +``` + +`enroll.txt`: + +```text +USER_NAME=Prenom Nom +USER_EMAIL=prenom.nom@example.local +USER_ID=EMP-00123 +SERVER_URL=http://:5005/api/v1 +API_TOKEN= +``` + +Production: l'installeur doit etre signe pour eviter les alertes SmartScreen et faciliter le deploiement IT. + +## VM Windows DGX - etat POC et regles + +Etat confirme le 2026-06-19: + +- Windows 11 ARM DGX fonctionne via QEMU standalone. +- Acces utilisateur via VNC tunnel `localhost:5902`. +- Le runtime actif utilise `disk.qcow2` cote utilisateur `aivanov`. +- La definition libvirt `win11-arm-lea` peut apparaitre arretee pendant que le standalone tourne. + +Regles: + +- Ne pas demarrer la VM libvirt `win11-arm-lea` pendant que QEMU standalone utilise deja `disk.qcow2`. +- Ne pas lancer deux VM sur le meme disque. +- En standalone, `disk.qcow2` doit etre accessible a l'utilisateur qui lance QEMU, actuellement `aivanov`. +- Si retour libvirt, prevoir de remettre l'ownership attendu par libvirt, par exemple `libvirt-qemu:libvirt-qemu`, apres arret complet du standalone. +- Le VNC doit rester en loopback/tunnel, par exemple `127.0.0.1:5902`, pas en exposition LAN large. + +Loose ends a traiter avant MVP: + +1. Persister proprement le VNC loopback dans le script original ou dans un wrapper. +2. Decider si le VNC a un password pose automatiquement via monitor, ou pas de password car tunnel obligatoire. +3. Choisir un seul runtime officiel pour la VM DGX: standalone documente ou libvirt corrige. +4. Documenter le rollback disk owner standalone <-> libvirt. +5. Decider et implementer l'auto-start au reboot DGX si la VM Windows doit etre disponible apres redemarrage: + - script persiste, pas `/tmp/vmvnc.sh`; + - service systemd `User=aivanov` ou user service avec `loginctl enable-linger`; + - `After=network-online.target`; + - garde-fou contre un demarrage libvirt parallele; + - choix VNC: sans password car loopback+tunnel, ou wrapper qui pose le password via monitor. + +## Reseau site + +Chaque site doit avoir un plan reseau valide avant installation. + +Exemple clinique prepare: + +```text +DGX_IP_SITE=192.168.1.178 +PREFIX=/24 +GATEWAY=192.168.1.243 +DNS_1=192.168.1.9 +DNS_2=192.168.1.8 +IPv6=off +VLAN=non +Interface=Ethernet uniquement +``` + +Regles: + +- Ne pas activer le profil Ethernet site pendant les tests labo sans GO. +- Prevoir un acces local ou une console avant toute bascule reseau. +- Noter le rollback exact avant modification IP. +- Valider depuis Windows: dashboard, chat, streaming. + +## Smoke tests d'acceptation + +### Serveur + +| Test | Attendu | +|---|---| +| `systemctl status rpa-streaming` | actif | +| `systemctl status rpa-vision-v3-dashboard` | actif ou fallback documente | +| `curl :5005/health` | 200/healthy | +| dashboard `:5001` | login/401 ou UI, pas 500 | +| `/api/system/status` | coherent, pas de faux vert | +| `/api/workflows` | workflows VWB visibles | +| worker status | `healthy` ou `idle` non degrade si aucun job | +| ports remote VNC/RDP | fermes au LAN, tunnel only | + +### Windows + +| Test | Attendu | +|---|---| +| `config.txt` | valeurs site/poste remplacees, aucun `CONFIGURE_ME` | +| lancement Lea | tray visible | +| chat Lea | connexion au DGX | +| capture/apprentissage | demarre avec consentement utilisateur | +| stop apprentissage | session ecrite cote DGX | +| dashboard fleet | machine visible | +| streaming | session recue sur `5005` | + +### VWB/apprentissage + +| Test | Attendu | +|---|---| +| `workflows.db` | present, backup effectue | +| workflows dashboard | liste chargee | +| anchors | images visibles | +| replay supervise | action ou demande de confirmation, pas de clic non controle | +| worker | session traitee puis retour sain | + +## Criteres GO / NO-GO + +GO installation site si: + +- artefacts versionnes et identifies; +- fiche site complete; +- secrets generes pour le site; +- DGX accessible et services verts; +- Windows connecte au DGX; +- dashboard/VWB/worker coherents; +- ports remote non exposes hors tunnel/VPN; +- rollback documente; +- preuves archivees. + +NO-GO si: + +- un secret `CHANGE_ME` ou `CONFIGURE_ME` reste en place; +- dashboard auth desactive hors dev; +- `RPA_AUTH_DISABLED=true` hors dev; +- VWB/workflows absents sans decision; +- Windows ne rejoint pas le streaming; +- VM ou poste controle le mauvais DGX; +- deux runtimes VM utilisent le meme disque; +- ports VNC/RDP exposes au LAN sans validation; +- pas de backup `workflows.db` avant reset/deploy. + +## Sauvegarde et rollback + +Avant chaque installation ou upgrade: + +```bash +STAMP=$(date +%Y%m%dT%H%M%S) +mkdir -p .codex_backups/install-$STAMP +cp -a visual_workflow_builder/backend/instance/workflows.db .codex_backups/install-$STAMP/ 2>/dev/null || true +cp -a visual_workflow_builder/backend/data .codex_backups/install-$STAMP/vwb-data 2>/dev/null || true +cp -a data/training .codex_backups/install-$STAMP/training 2>/dev/null || true +cp -a .env.local .codex_backups/install-$STAMP/env.local 2>/dev/null || true +``` + +Rollback code: + +```bash +git fetch origin +git checkout +systemctl restart rpa-streaming rpa-vision-v3-dashboard rpa-vision-v3-worker rpa-agent-chat +``` + +Rollback donnees: restaurer uniquement les chemins sauvegardes, jamais faire `git clean -xfd` sur le DGX POC avec donnees runtime non trackees. + +## Journal de preuve d'installation + +Pour chaque installation, creer un dossier de preuve: + +```text +installations/// + site-sheet.md + versions.txt + services.txt + ports.txt + dashboard-smoke.txt + windows-agent-smoke.txt + vwb-smoke.txt + backups.txt + incidents.md + verdict.md +``` + +`versions.txt` doit contenir: + +```bash +git rev-parse HEAD +git status -sb +python --version +pip freeze | sort +systemctl --version +ollama list +``` + +## Industrialisation requise avant production + +| Sujet | Etat POC | Attendu MVP/prod | +|---|---|---| +| Installeur Windows | Inno disponible | release signee, tests install/desinstall | +| Enrollment | config/token manuel ou dashboard | token par poste, expiration/revocation | +| Secrets | `.env.local` manuel | coffre ou procedure secrete auditee | +| Services | systemd partiel selon DGX | target unique, healthcheck, recover | +| Supervision | dashboard + logs | alerting, retention, export incident | +| Backups | manuels | job planifie, test de restauration | +| VM Windows DGX | standalone VNC manuel fonctionnel | runtime officiel choisi, persiste et auto-start arbitre | +| Multi-site | variables en coordination | fiche site versionnee, aucun secret partage | +| Documentation utilisateur | `LISEZMOI.txt` | guide utilisateur + admin par site | +| Support | agents war-room | procedure support N1/N2/N3 | + +## Check-list courte jour d'installation + +1. Valider fiche site et fenetre rollback. +2. Verifier artefact serveur et installeur Windows. +3. Generer secrets site/poste. +4. Sauvegarder donnees DGX existantes. +5. Installer ou mettre a jour serveur. +6. Configurer reseau sans perdre l'acces de rollback. +7. Demarrer services. +8. Installer Lea sur Windows. +9. Lancer smoke serveur. +10. Lancer smoke Windows. +11. Tester dashboard, VWB, chat, streaming, worker. +12. Archiver preuves. +13. Donner verdict GO/NO-GO. +14. Si GO, remettre les acces temporaires en mode securise. + +## Documents sources + +- `README.md` +- `deploy/systemd/rpa_vision_v3.env.example` +- `deploy/lea_package/LISEZMOI.txt` +- `deploy/lea_package/config.txt` +- `deploy/build_package.sh` +- `deploy/installer/README.md` +- `deploy/installer/Lea.iss` +- `docs/coordination/RUNBOOK-DGX-POST-REBOOT-CHECK.md` +- `docs/coordination/RUNBOOK-LEA-LIVE-DRAFT.md` +- `docs/coordination/active/2026-06-19_1418_postaction-windows-dgx-fonctionne.md` +- `docs/coordination/inbox_codex/2026-06-19_1420_claude-to-codex_ACK-STOP-DIAG-VM-LOOSE-ENDS.md` diff --git a/docs/MEMO_JOUR_J_LIVRAISON_DGX_CLINIQUE_2026-06-23.md b/docs/MEMO_JOUR_J_LIVRAISON_DGX_CLINIQUE_2026-06-23.md new file mode 100644 index 000000000..4b8d61716 --- /dev/null +++ b/docs/MEMO_JOUR_J_LIVRAISON_DGX_CLINIQUE_2026-06-23.md @@ -0,0 +1,34 @@ +# Mémo Jour J — Livraison définitive du DGX à la clinique (2026-06-23) + +> Contexte : le DGX part **définitivement** à la clinique. Après, travail **100% à distance** depuis le labo. Tout doit marcher avant de débrancher. + +## ✅ Validé la veille (2026-06-22) — rien à refaire +- **Installateur Léa 1-clic autoportant** (python-embed, **sans Python système**, raccourci Bureau + démarrage auto) — testé sur VM Win11, enrôlement confirmé (Qwen). +- **Apprentissage RPA** : OK. +- **Reboot complet du DGX** : 11 services + grounder (modèle ~60 s) + VM reviennent **seuls**. +- **Auto-réparation OVMF de la VM** (watchdog user `vm-ovmf-watchdog.service`) **LIVE** : après une coupure secteur, la VM se **répare et reboote seule** (~3-5 min). Testé (détection boucle CPU 99% → restauration known-good → relance). +- **IP DGX clinique `192.168.1.178`** : réservation DHCP côté clinique pour la MAC Ethernet `10:b6:76:f0:2f:f4`. Tous les services `enabled` (auto-start au boot). +- **Dashboard** bascule faite : `RPA_PUBLIC_URL=http://192.168.1.178:5005` → les Léa générées pointent direct `.178`. + +## 🏥 Sur site (jour J) +1. Brancher le DGX sur l'**Ethernet clinique** + allumer. **Attendre ~3-5 min** (boot + grounder + VM). +2. Vérifier `.178` : depuis un poste clinique, ouvrir **http://192.168.1.178:5001** (login `lea`). Si le dashboard répond → DGX up + bonne IP. +3. Grounder vision : prêt ~3 min après le boot — **ne pas tester la vision de Léa avant**. +4. **Installer Léa sur les postes TIM** : sur chaque poste → `http://192.168.1.178:5001` → Fleet → enrôler → télécharger le ZIP → **clic droit Extraire** → double-clic **`Installer-Lea.bat`** → vérifier : pas d'erreur Python, Léa dans le systray, apparaît dans la fleet. **Tester l'apprentissage sur 1 poste avant de généraliser.** +5. 🔴 **AVANT DE PARTIR (point de non-retour)** : depuis ton laptop via le **VPN Stormshield**, ouvrir **http://192.168.1.178:5001**. Si le dashboard s'affiche → **ton accès distant est bon, tu peux partir**. + +## 🏠 Après le départ — travail à distance (labo) +- Accès DGX : **VPN Stormshield → `http://192.168.1.178:5001`** (dashboard) + `ssh aivanov@192.168.1.178` (cert). +- ⚠️ Déploiement de code/correctifs : **scp/rsync par-dessus le VPN** — PAS `git pull` (la Gitea maison n'est pas exposée sur internet). +- Coupure secteur clinique → la VM s'auto-répare (watchdog OVMF). Accès VM : RDP (presse-papier) ou VNC. + +## Identifiants +- Voir ta note `DGX SSH aivanov Dom31.txt` (Bureau VM / clé USB) et `~/ai/rpa_vision_v3/.env.local` sur le DGX. (Non recopiés ici : ce fichier est dans le dépôt.) +- VM Windows : compte `aivanov`. + +## Reste / dette (post-installation — « on a encore du boulot ») +- **Compte VM `aivanov`** : définir un **mot de passe** (pas juste un PIN) pour que le RDP + presse-papier marchent. +- Code dev↔DGX : aligné (diff fait — seuls des tests + `config.py`/`index.html` mineurs diffèrent, non bloquant). +- Mot de passe VNC perdu à chaque reboot VM (cosmétique — RDP = voie normale). Persistance VNC à câbler si besoin. +- `hostname` Léa remonte `N/A` (python-embed) — cosmétique. +- Profil **Stormshield laptop** (2ᵉ accès) à confirmer avec PORQUET (atteint `.178` ?). diff --git a/docs/NOTE_DSI_DEPLOIEMENT_GPO_LEA_2026-06-28.md b/docs/NOTE_DSI_DEPLOIEMENT_GPO_LEA_2026-06-28.md new file mode 100644 index 000000000..e1e1b112f --- /dev/null +++ b/docs/NOTE_DSI_DEPLOIEMENT_GPO_LEA_2026-06-28.md @@ -0,0 +1,40 @@ +# Note DSI — Déploiement / mise à jour de Léa par GPO (AD clinique) + +- Date : 2026-06-28 +- Pour : Nicolas PORQUET (DSI Hôpital privé Wallerstein) +- De : équipe Léa (Dom) +- Objet : utiliser l'AD/GPO de la clinique comme **canal de déploiement** des mises à jour de l'agent Léa sur les postes pilotes, en remplacement d'un updater applicatif maison. + +## 1. Contexte technique de Léa (côté poste) +- Léa s'installe **par utilisateur** (`%LOCALAPPDATA%\Lea\`), **sans droits administrateur ni UAC**, sans Python système (Python embarqué). +- Le programme d'installation est un **EXE (Inno Setup)** supportant le mode **silencieux** (`/SILENT` / `/VERYSILENT`). **Pas de package MSI** à ce jour. +- L'app tourne en contexte utilisateur (`pythonw.exe`), démarrage par raccourci/observation. + +## 2. Besoin +Pousser une **mise à jour** (la prochaine, et idéalement les suivantes) sur **un sous-ensemble de postes** (les postes pilotes Léa), **sans intervention manuelle poste par poste**. + +## 3. Options GPO envisagées (à valider avec vous) +| Option | Mécanisme | Adapté ? | +|---|---|---| +| **Logon script (utilisateur)** | script au login qui lance l'installeur en silencieux ou copie les fichiers à jour dans `%LOCALAPPDATA%\Lea\` | ✅ **fit naturel** (per-user, sans admin) | +| **GPP — Files / Scheduled Task** | déploiement de fichiers ou tâche planifiée de mise à jour | ✅ alternative | +| GPO **Software Installation** | déploiement **MSI** assigné machine/utilisateur | ❌ nécessite un **MSI** (non disponible) | + +- **Idempotence** : le script vérifiera un **marqueur de version** pour ne PAS réinstaller à chaque ouverture de session. +- **Payload** : les fichiers de mise à jour seront déposés sur un **partage atteignable** par les postes (SYSVOL ou share réseau dédié) — à définir avec vous. +- **Rollback** : en cas de version défaillante, repush de la version précédente par la même voie. + +## 4. Questions / ce que nous vous demandons +1. **Topologie exacte des postes pilotes** : PC physiques joints à l'AD ? hôtes **RDP** ? applications **Citrix** publiées ? (cela décide GPO **machine** vs **utilisateur**). +2. Pouvez-vous créer une **OU dédiée** ou un **groupe de sécurité** ciblant les postes/utilisateurs Léa ? +3. Un **partage réseau** (ou SYSVOL) pour héberger le payload de mise à jour ? +4. Vos **contraintes de sécurité** : les **logon/startup scripts** sont-ils autorisés par votre politique ? Y a-t-il **AppLocker / SRP / WDAC** ou une exigence de **signature de code** sur les exécutables ? (le cas échéant on fournit l'empreinte SHA256 / on discute signature). +5. Validez-vous le principe **rollback = repush version précédente** ? + +## 5. Ce que nous fournissons +- L'**installeur silencieux** (EXE) + son empreinte SHA256. +- Un **script de logon** type (lancement silencieux + contrôle de version/marqueur). +- La **liste des postes** pilotes concernés. + +## 6. Bénéfice +Canal de déploiement **standard, piloté par la DSI, traçable**, sans updater applicatif maison (donc sans risque de « briquer » les postes par un mécanisme de mise à jour custom). Compatible mises à jour ultérieures. diff --git a/docs/PLAN_ACCES_DISTANT_SSH_CERT_DGX_2026-06-20.md b/docs/PLAN_ACCES_DISTANT_SSH_CERT_DGX_2026-06-20.md new file mode 100644 index 000000000..2c96eb27c --- /dev/null +++ b/docs/PLAN_ACCES_DISTANT_SSH_CERT_DGX_2026-06-20.md @@ -0,0 +1,138 @@ +# PLAN — Accès distant DGX consolidé par SSH-certificat (labo + déplacement) + +- `Auteur`: Claude (infra), co-construit avec Codex (réseau/box) + Qwen (client Windows) +- `Date`: 2026-06-20 +- `Statut`: **PLAN / proposition** — décisions + application supervisées par Dom (rien de modifiant sans GO). +- `Réf`: [[project_remote_access_consolidation_20260620]], `reference_dgx_static_ip`. + +## 1. Objectif + +Accès distant **robuste et unifié** au DGX, authentifié par **certificat SSH (CA OpenSSH)**, dans 2 contextes — **labo** (LAN/WiFi) et **déplacement depuis le PC Windows de Dom** (internet) — vers 3 cibles : +1. **Terminal DGX** (SSH) ; +2. **VM Windows** (VNC `5902` over SSH) ; +3. **Bureau Ubuntu du DGX** (GNOME, via VNC — « éventuel »). + +Contrainte forte : **on-premise / no-cloud** (pas de Tailscale-cloud ni Cloudflare). + +## 2. État actuel (relevé read-only) + +| Élément | Constat | Conséquence | +|---|---|---| +| SSH DGX | Aucune CA (`trustedusercakeys none`) ; auth par clé OK | terrain vierge propre pour une CA | +| sshd | ⚠️ `PasswordAuthentication yes` (la checklist DSI annonce "no") | à corriger dans le durcissement | +| VPN/overlay | Aucun installé | à mettre en place | +| Bureau Ubuntu | Pas de VNC `5900` actif (DGX headless) | cible 3 à créer | +| IPv4 box | **`82.64.97.95` routable** (Free, pas CGNAT) | **port-forward UDP possible → WireGuard direct viable** | +| Ingress | Box forwarde déjà 80/443 → NPM ; `lea.labs…` → `82.64.97.95` | DDNS de fait + infra réutilisable | +| IPv6 | DGX a une IPv6 globale `2a01:e0a:28:ad60:…` | option SSH IPv6 direct (secondaire) | + +## 3. Architecture cible — 2 couches découplées + +**Couche AUTH (identique partout) = CA OpenSSH.** On ne gère plus des `authorized_keys` poste par poste : une CA signe les clés. Un seul point de confiance, révocation centralisée, plus d'avertissement host-key. + +**Couche TRANSPORT (selon contexte) :** +- Labo : SSH direct `192.168.1.45` (LAN). +- Déplacement : **WireGuard self-hosted** → une fois le tunnel monté, on est « sur le LAN » et **tout passe par SSH/tunnels** (terminal + VNC VM + VNC bureau) — *exactement le même geste qu'au labo*. + +Bénéfice : **un seul mécanisme d'accès (SSH + tunnels) pour les 3 cibles**, que l'on soit au labo ou en déplacement. Le transport ne change que la « route ». + +## 4. Approches transport comparées + +| | A. WireGuard self-hosted ✅ reco | B. Mesh self-hosted (Netbird/Headscale) | C. SSH-over-443 (NPM/wstunnel) | +|---|---|---|---| +| NAT/CGNAT | OK (IPv4 routable + fwd UDP dispo) | OK même CGNAT (relais) | OK (443 quasi toujours ouvert) | +| No-cloud | ✅ total | ✅ si self-hosted (serveur ctrl à tenir) | ✅ (réutilise NPM) | +| Complexité | Faible/moyenne | Plus élevée (mgmt+signal+relay) | Moyenne (wrap TLS) | +| Réseaux hôtels restrictifs (UDP bloqué) | ⚠️ → fallback nécessaire | OK | ✅ idéal | +| Pertinence ici | **Forte** (on a IPv4 routable) | Surdimensionné maintenant | **Bon comme fallback** | + +**Recommandation : A (WireGuard) en primaire + C (WireGuard-over-wstunnel sur 443) en fallback** pour les réseaux qui bloquent l'UDP (hôtels, certains 4G). B (mesh) seulement si une clinique se révèle CGNAT/durcie. + +## 5. Couche AUTH — détail CA OpenSSH + +Bonnes pratiques (Red Hat / Teleport / OpenSSH cookbook) : **2 CA séparées** (privilege separation), clés CA **hors DGX**. + +1. **Générer les CA** (sur le poste Dom, stockage sûr, JAMAIS sur le DGX) : + - `ssh-keygen -t ed25519 -f user_ca -C "rpa-user-ca"` + - `ssh-keygen -t ed25519 -f host_ca -C "rpa-host-ca"` +2. **Certifier l'hôte DGX** (supprime l'avertissement host-key, survit aux rotations) : + - `ssh-keygen -s host_ca -I dgx-zgx2ff4 -h -n 192.168.1.45,dgx.lab, -V +52w /etc/ssh/ssh_host_ed25519_key.pub` + - sshd : `HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub` + - client `known_hosts` : `@cert-authority 192.168.1.45,*.lab ` +3. **Certifier l'utilisateur Dom** (laptop + poste labo) : + - `ssh-keygen -s user_ca -I dom-laptop -n aivanov -V +52w id_ed25519.pub` (principal = compte cible) + - sshd : `TrustedUserCAKeys /etc/ssh/user_ca.pub` (+ option `AuthorizedPrincipalsFile`) +4. **Durcir sshd** : `PasswordAuthentication no`, `KbdInteractiveAuthentication no`, `PermitRootLogin prohibit-password`. +5. **Révocation** : KRL (`ssh-keygen -k`) + `RevokedKeys` dans sshd — révoquer un poste perdu sans toucher les autres. + +Validité : **certs user 1 an** (simple, + KRL) ou **courts** (plus sûr, nécessite un re-signing régulier). Reco équipe réduite : **1 an + KRL**. + +## 6. Couche TRANSPORT — détail + +**WireGuard (primaire)** : +- Serveur `wg0` **sur le DGX** (miroir du futur déploiement clinique) : ex. `10.10.0.1/24`, `ListenPort 51820`. +- Box : **port-forward UDP 51820 → DGX**. Nom dédié `vpn.<…>.laurinebazin.design` → `82.64.97.95` (DDNS). +- Laptop = peer (`10.10.0.2/24`), `Endpoint vpn.…:51820`, `AllowedIPs` couvrant `10.10.0.0/24` + `192.168.1.45/32` (accès DGX + ses services). +- Connecté → `ssh dom@10.10.0.1` (cert) ; `-L 5902` VM ; `-L 5900` bureau. + +**Fallback 443 (réseaux UDP-bloqués)** : `wstunnel` encapsule WireGuard dans du WebSocket/`wss` sur **443** via le NPM (`wstunnel … wss://vpn.…:443`). Tout sort en 443 → passe les proxys/hôtels. Réf : Hetzner/eduVPN. + +**IPv6 direct (secondaire/dépannage)** : quand le réseau client a l'IPv6, `ssh` direct vers l'IPv6 globale du DGX (épingler `mngtmpaddr`, firewall allow, cert). Pratique mais non garanti (réseaux IPv4-only). + +## 7. Accès aux 3 cibles (même geste partout) + +| Cible | Mécanisme | Commande type | +|---|---|---| +| Terminal DGX | SSH cert | `ssh dom@` | +| VM Windows | VNC over SSH | `ssh -N -L 5902:127.0.0.1:5902 dom@` → VNC `localhost:5902` | +| Bureau Ubuntu DGX | VNC over SSH | **DGX = X11/Xorg (confirmé Qwen)** → `x11vnc -localhost -rfbauth ~/.vnc/passwd` sur la session `:1` (ou TigerVNC), **bind loopback + password** → `ssh -N -L 5900:127.0.0.1:5900 …` → VNC `localhost:5900`. ⚠️ l'ancien x11vnc était en `nohup` non-persisté (mort à la panne) et sans password — à recréer en **service systemd user sécurisé** (P0). | + +`` = `192.168.1.45` au labo, `10.10.0.1` (wg) en déplacement. **La seule variable est l'hôte.** + +**Presse-papier / copier-coller (demande Dom)** : +- Bureau Ubuntu (x11vnc/TigerVNC) : **texte natif** ✅, rien à faire. +- VM Windows (VNC QEMU) : **à activer**. QEMU 8.2.2 supporte le clipboard vdagent et le bus `virtio-serial` est déjà présent → ajouter `-chardev qemu-vdagent,id=vdagent,clipboard=on` + `-device virtserialport,chardev=vdagent,name=com.redhat.spice.0` + installer **SPICE guest tools (vdagent)** dans Windows = **copier-coller texte bidirectionnel** (garde le viewer VNC). Le tunnel SSH ne gêne pas (in-band). +- Transfert **fichiers** (option) : SPICE (client virt-viewer) ou RDP natif (selon édition Windows — Home ARM = pas de serveur RDP). +- ⚠️ Sous-chantier séparé : modif `vm_launch.sh` + install guest + **redémarrage VM gracieux** (jamais kill) → GO Dom. + +## 8. Décisions attendues (Dom) + +1. **Endpoint WireGuard** : sur le **DGX** (reco, mirroir clinique) ou sur le **poste Dom** (gateway always-on) ? +2. **Fallback 443 (wstunnel)** : oui/non (utile si tu te connectes depuis hôtels/4G restrictifs) ? +3. **Validité cert user** : **1 an + KRL** (reco) ou courts ? +4. **Bureau Ubuntu DGX** : on le met en place maintenant ou « éventuel » plus tard ? (+ Wayland vs X11 — input Qwen). +5. **Garde des clés CA** : sur le poste Dom (reco) — confirmer le lieu de stockage. + +**Convergence équipe (Qwen + Claude)** sur les 5 options recommandées : WG sur DGX ✅, fallback 443 ✅, certs 1 an + KRL ✅, bureau Ubuntu maintenant (X11, P0 sécurité) ✅, clés CA hors DGX ✅. Codex (box/transport) à compléter. + +**À confirmer par Dom (poste Windows — Qwen n'y a pas accès)** : +- Version OpenSSH du laptop (`ssh -V`) — natif Win10 1803+/11, support cert OK normalement. +- Client VNC Windows : **RealVNC Viewer** ou **TigerVNC viewer** (reco ; Remmina = Linux only). +- A-t-il déjà un accès SSH depuis Windows vers le DGX (aujourd'hui : non testé) ? + +**Note credentials** : seul `RPA_API_TOKEN` (config.txt Léa = `.env.local` DGX) est partagé entre Léa et le DGX ; le plan SSH-cert n'y touche pas → pas de conflit, mais si un renouvellement de token est fait par ailleurs, le répercuter aux 2 endroits. + +## 9. Mise en œuvre — chirurgie itérative supervisée (un pas = un test) + +| Phase | Contenu | Owner | Réversible | +|---|---|---|---| +| 0 | Backups (`sshd_config`, known state), inventaire exposition | Claude/Codex | — | +| 1 | CA générées (off-DGX) + host cert + `@cert-authority` client → **test : plus d'avertissement host-key** | Claude | oui | +| 2 | User cert + `TrustedUserCAKeys` → **test : login par cert** *en gardant clé/MDP en secours* | Claude | oui | +| 3 | Durcissement (`PasswordAuthentication no`, KRL) → **test** ; retrait ancienne clé | Claude (GO Dom) | oui (backup) | +| 4 | WireGuard DGX + fwd UDP box + peer laptop → **test reach déplacement (terminal+VM+bureau)** | Codex+Claude | oui | +| 5 (opt) | Fallback wstunnel/443 | Codex | oui | +| 6 (opt) | Bureau Ubuntu (TigerVNC/grd headless) | Qwen | oui | +| 7 | Doc + **portage clinique** (Ethernet `.178`, même CA, même WG) | tous | — | + +## 10. Sécurité & garde-fous + +- Clé privée **CA hors DGX** (compromission DGX ≠ compromission CA). +- `PasswordAuthentication no` corrige l'écart actuel (DSI annonçait "no"). +- WG = port unique UDP exposé (vs SSH brut exposé). SSH reste **non exposé en direct sur internet** (accès via WG), sauf option IPv6/fallback maîtrisée. +- Révocation par KRL (poste volé) sans réémettre les autres certs. +- Rollback : `sshd_config.bak` + redémarrage sshd ; WG = `wg-quick down`. + +## 11. Portage clinique + +Même CA + même WireGuard rejouables sur le DGX clinique (Ethernet statique `.178`). L'accès distant de Dom (support/maintenance à distance) devient un livrable DSI propre (un port UDP, auth cert, révocable) — argument sécurité pour Nicolas PORQUET. diff --git a/docs/PLAN_ACTION_SUITE_2026-06-23.md b/docs/PLAN_ACTION_SUITE_2026-06-23.md new file mode 100644 index 000000000..59db18fb4 --- /dev/null +++ b/docs/PLAN_ACTION_SUITE_2026-06-23.md @@ -0,0 +1,205 @@ +# 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//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`. diff --git a/docs/PLAN_CHANTIER_UNIFICATION_LEA_VWB_2026-06-17.md b/docs/PLAN_CHANTIER_UNIFICATION_LEA_VWB_2026-06-17.md new file mode 100644 index 000000000..113e7082f --- /dev/null +++ b/docs/PLAN_CHANTIER_UNIFICATION_LEA_VWB_2026-06-17.md @@ -0,0 +1,92 @@ +# Plan de chantier — Unification Léa + VWB (préparé le 2026-06-17 au soir) + +> Préparé par analyse multi-agents (3 agents read-only) + graphify, après le check UI post-reboot DGX. +> **Rien n'a été modifié.** Document de cadrage pour la reprise. +> Méthode imposée : chirurgie itérative supervisée (1 modif = 1 test = validation Dom). Pas de batch. + +## 0. Diagnostic corrigé (important — deux hypothèses du soir invalidées) + +Le check UI a fait remonter 3 symptômes : **T3** (Léa « ne trouve pas » le bloc-notes), **T5/anchors** (images d'ancres absentes au VWB), et le sujet de fond **fonds commun**. L'analyse de code rectifie le diagnostic « à chaud » : + +| Hypothèse du soir | Verdict après analyse code | +|---|---| +| T3 = silo machine_id (Léa-VM ne voit pas le savoir du .11) | **FAUX au niveau sélection.** Le `SemanticMatcher` ne filtre par aucune machine ; Léa tourne sur le DGX qui héberge les dossiers des deux postes → elle *voit* déjà tous les workflows. | +| T3 = filtre `is_production_ready` | **FAUX.** Aucun composant runtime (matcher, exécuteur, chat) ne lit `is_production_ready`/`learning_state`. | +| **Vraie cause T3** | **Fragmentation de l'apprentissage.** 100 répétitions → ~20 workflows distincts nommés d'après les apps vues (« Bloc-notes, Explorateur et Terminal (2)(3)… »), aucun nommé franchement « ouvrir le bloc-notes » → le matching sémantique se dilue sur 20 quasi-doublons. (Le silo existe, mais au niveau *renforcement cross-machine*, pas sélection.) | + +--- + +## Chantier A — Consolidation de l'apprentissage (débloque T3) — PRIORITÉ HAUTE + +### Cause racine +Chaque session de streaming **crée** un workflow neuf, **sans jamais chercher ni fusionner** un existant. +- Nommage + suffixe `(2)(3)` : `agent_v0/server_v1/stream_processor.py:4335-4344` (`_generate_workflow_name`) — collision de nom → variante numérotée au lieu de renforcement. +- Persistance directe sans dédup : `stream_processor.py:4417-4445` (`_persist_workflow`), build `:2966-3112` (`_build_workflow_from_session`). +- 1 observation / node : build **toujours séquentiel** (`graph_builder.py:345` `clusters={i:[i]}`), donc `observation_count = 1` (`graph_builder.py:909`). DBSCAN d'agrégation volontairement désactivé. +- **Aucune fonction de merge/dédup de workflows** dans tout `core/`. Le `VariantManager` (`core/variants/variant_manager.py:266`, seul code qui fait `observation_count += 1`) **n'est jamais appelé**. +- `is_production_ready` calculé dans `core/training/quality_validator.py:114,238-246` (seuil `min_observations_per_node=3`) — toujours False car 1 obs/node, **mais sans effet runtime** (label informatif uniquement). + +### Leviers (effort / risque / impact) +- **A. Découpler « exécutable » de « production_ready »** — faible / faible / moyen — quick-win sémantique. +- **B. Fusion/dédup create-or-update à la persistance** — élevé / moyen / **fort** — cause racine. +- **C. Rebrancher l'agrégation des observations** (`VariantManager` ou `_run_cross_session_learning` qui réécrit `observation_count`) — moyen / moyen / fort. +- **D. Seuil `min_observations` configurable/contextuel** — faible / moyen / faible — cosmétique seul. +- **E. `workflow_id` = signature stable de la trajectoire** (hash de la séquence d'actions) au lieu des apps vues — moyen / moyen / fort — supprime le `(2)(3)` à la racine, rend B trivial. + +**Reco** : **E + B** (signature stable + create-or-update avec agrégation d'observations), D en complément, A en quick-win si on veut juste « rendre exécutable » vite. + +### Composants +`stream_processor.py` (L2966-3112, L4335-4344, L4417-4445, cross-session L3149-3268, list L4518) · `graph_builder.py` (L345, L909, L384-456) · `quality_validator.py` (L114, L238) · `semantic_matcher.py` (sélection) · `variant_manager.py` (L266, à rebrancher) · `replay_learner.py:358` (consolidate = hints seulement, leurre). + +--- + +## Chantier B — Affichage anchors VWB (débloque T5) — FIX PRÉCIS, FAIBLE RISQUE + +### Cause racine +Le commit `b8b963059` n'a corrigé **que la moitié** : l'import lit `target.context_hints.anchor_image_base64` **uniquement pour les actions simples**. +- `services/learned_workflow_bridge.py` : branche action simple `else` L226-233 (lit le base64, ajouté par le commit) ; **les actions *compound*** (majoritaires dans les workflows Léa) passent par `_convert_compound_substep` L279 qui **ne lit jamais le base64** → substeps `anchor_id=NULL` → frontend affiche « Ancre requise » sans image (`frontend_v4/.../StepNode.tsx:113-119`). +- Aggravant état DGX : les workflows en base datent d'avril, **100% des steps `anchor_id=NULL`** → ré-import nécessaire après fix. + +### Chaîne (pour mémoire) +import `api_v3/learned_workflows.py:249` → convert `learned_workflow_bridge.py:72` → `save_anchor_image` → table `visual_anchors` (`db/models.py:163`) → API `GET /api/v3/anchor//thumbnail` (`api_v3/capture.py:356`) → React `StepNode.tsx` (`api.ts:136`). Pas de mismatch URL/chemin. + +### Fix +Propager `anchor_image_base64` aux substeps compound (passer `target` dans la boucle compound L169-187 / `_convert_compound_substep`, poser l'ancre sur le 1er substep cliquable — éviter de dupliquer sur N substeps). Risque faible/additif. **Puis ré-importer** les workflows cibles. + +--- + +## Chantier C — Fonds commun / mutualisation cross-poste — STRATÉGIQUE, DÉCISION PRODUIT D'ABORD + +### État +- Fédération `core/federation/` **entièrement débranchée au runtime** : `GlobalFAISSIndex.search()` (`faiss_global.py:199`) **jamais appelé** ; endpoints export/import (`api_stream.py:6277-6372`) sans aucun déclencheur (ni cron, ni frontend). +- **Anonymisation = le maillon le plus mûr et prêt** : `learning_pack.py` exclut machine_id/hostname/patient/nip/ipp (`_clean_metadata` L410, `_SENSITIVE_METADATA_KEYS` L61-66), hash SHA-256 (L388), export = embeddings 512d + signatures (pas de pixels/OCR brut). +- Silo réel = `stream_processor._run_cross_session_learning` L3197/L3284 (`workflow_machine != machine_id → continue`) : bloque le renforcement cross-machine. +- Identité : `machine_id` workflows = `hostname_os` (ex `DESKTOP-58D5CAC_windows`, `agent_v1/config.py:34-37`) ≠ token enrôlement `cbd8f9f0…` (`agent_registry.py:107-111`, sécurité parc). À ne pas confondre. + +### Options +- **Intra-clinique brut** (lever filtre cross-session L3197/L3284, charger toutes machines) — simple, **non anonymisé** → acceptable seulement intra-site. +- **Cross-clinique anonymisé** (brancher `search()` global + déclencheur export/import + index global peuplé) — la vraie « fédération », effort moyen-élevé, **seul canal sûr PII**. + +--- + +## Décisions produit à trancher AVANT de coder (pour Dom) + +1. **Quel point d'entrée Léa au runtime ?** Deux providers concurrents : `agent_chat/app.py:678,906` (port 5004, SemanticMatcher sur `data/training/workflows/`) vs chat serveur `api_stream.py:6623` (`_list_available_workflows` qui liste des **sessions live**, pas les workflows entraînés). Les logs DGX du soir montrent le 5004 + SemanticMatcher → **probablement 5004**, à confirmer formellement avant de coder A. +2. **Critère de « même parcours »** pour la fusion (levier E/B) : signature de trajectoire ? nom de base ? (workflows de 7 à 89 nodes pour « le même » parcours → alignement non trivial). +3. **Source de vérité workflows** (DETTE-015) : DB VWB SQLite (5002) vs JSON `data/training/workflows/` — la route `/api/workflows` de Léa fusionne les deux avec une dédup fragile. Trancher la source canonique. +4. **Niveau de mutualisation** : intra-clinique brut (rapide, non anonymisé) vs fonds commun cross-clinique anonymisé (fédération). Implications réglementaires opposées. +5. **Re-exécutabilité des packs fédérés** : l'export n'emporte que des squelettes anonymisés (pas les templates/coordonnées) → un chemin de re-résolution visuelle est nécessaire (cohérent avec le contrat 100% vision). + +--- + +## Ordre recommandé pour la reprise + +1. **Confirmer le provider runtime de Léa** (Q1) — 10 min, read-only, débloque tout le reste. +2. **Chantier B (anchors)** — fix ciblé compound + ré-import. Faible risque, gain visuel immédiat (T5). +3. **Chantier A (consolidation)** — E + B, chirurgie itérative. Débloque T3 (le bloc-notes « connu »). +4. **Chantier C (fonds commun)** — décision produit (Q4) puis implémentation anonymisée. Le plus stratégique pour la proposition de valeur, mais le moins urgent pour une démo. + +## Garde-fous +- Tout changement au build/persist (Chantier A) touche le chemin qui alimente la démo Urgence → chirurgie itérative, 1 test par modif. +- Champ de mines `core/` : vérifier le wiring runtime réel avant de rebrancher (`VariantManager`), pas seulement la présence du code. +- Mutualisation cross-site = uniquement via `LearningPack` anonymisé, jamais recopie de JSON bruts (PII : OCR, titres fenêtres patients). diff --git a/docs/PLAN_DEPLOY_NAVIGATION_WIRING_2026-07-01.md b/docs/PLAN_DEPLOY_NAVIGATION_WIRING_2026-07-01.md new file mode 100644 index 000000000..bc19ea311 --- /dev/null +++ b/docs/PLAN_DEPLOY_NAVIGATION_WIRING_2026-07-01.md @@ -0,0 +1,98 @@ +--- +name: plan-deploiement-navigation-2026-07-01 +description: Plan déploiement + vérification pour wiring navigate — diff, smoke, rollback. Commit = GO Dom supervisé. v2 corrigée après revue croisée Claude. +type: project +--- + +# Plan de déploiement — Wiring navigate (brique navigation serveur) + +- Date : 2026-07-01 23:50 → **v2 2026-07-02 11:00** (corrections après revue croisée Claude) +- Branche : `feat/push-log-dgx` +- Commit = **GO Dom supervisé** (serveur DGX clinique live) + +## Fichiers modifiés (3 hotspots + 4 modules navigation + 6 fichiers tests) + +| Fichier | Changement | Lignes | +|---------|-----------|--------| +| `agent_v0/server_v1/api_stream.py` | +1 import `from core.navigation import _handle_navigate_action` + +1 dispatch `elif type_ == "navigate"` (asyncio.wait_for, timeout=180s) | +10 | +| `agent_v0/server_v1/replay_engine.py` | +1 `"navigate"` dans `_ALLOWED_ACTION_TYPES` + +1 `"navigate"` dans `_SERVER_SIDE_ACTION_TYPES` | +2 | +| `core/navigation/__init__.py` | Nouveau : handler `_handle_navigate_action` + exports `__all__` | +115 | +| `core/navigation/visual_verifier.py` | Nouveau : OCR-ancré verify_before/after, fuzzy match | +408 | +| `core/navigation/grounding.py` | Nouveau : OCR-anchor-first + VLM fallback + coords cache | +375 | +| `core/navigation/visual_login.py` | Nouveau : DPI urgences login, verify + resolve | +227 | +| `core/navigation/action_resolver.py` | Nouveau : coords normalisés, OCR adapters | +205 | + +**Tests ajoutés (6 fichiers, 131 tests) :** + +| Fichier | Tests | Rôle | +|---------|-------|------| +| `tests/unit/test_visual_verifier.py` | 48 | OCR-ancré, fuzzy match, verify_before/after | +| `tests/unit/test_grounding.py` | 39 | OCR-anchor, VLM fallback, coords cache | +| `tests/unit/test_visual_login.py` | 17 | DPI urgences login, verify + resolve | +| `tests/unit/test_action_resolver.py` | 14 | coords normalisés, OCR adapters | +| `tests/unit/test_navigate_wiring.py` | 7 | Boot non-régression (import, allowed types, handler callable) | +| `tests/unit/test_navigate_handler_e2e.py` | 6 | E2e mocké (nominal, OCR miss, no screenshot, never-fail) | + +## Smoke commands post-commit (à exécuter sur DGX après deploy) + +```bash +# 1. Boot serveur streaming — pas d'ImportError +RPA_AUTH_DISABLED=true python -c "from agent_v0.server_v1 import api_stream; print('api_stream OK')" + +# 2. Types autorisés — navigate présent +RPA_AUTH_DISABLED=true python -c "from agent_v0.server_v1.replay_engine import _ALLOWED_ACTION_TYPES, _SERVER_SIDE_ACTION_TYPES; print('navigate in ALLOWED:', 'navigate' in _ALLOWED_ACTION_TYPES); print('navigate in SERVER_SIDE:', 'navigate' in _SERVER_SIDE_ACTION_TYPES)" + +# 3. Handler callable +RPA_AUTH_DISABLED=true python -c "from core.navigation import _handle_navigate_action; print('handler callable:', callable(_handle_navigate_action))" + +# 4. Tests non-regression (navigation + wiring) +RPA_AUTH_DISABLED=true python -m pytest tests/unit/ -k "navigat or visual_verifier or grounding or visual_login or action_resolver or wiring or e2e" -v --tb=short + +# 5. Service rpa-streaming actif + health endpoint +systemctl is-active rpa-streaming.service +curl -s http://localhost:5005/health | python3 -m json.tool +``` + +## Critères de rollback (si smoke échoue) + +| Critère | Action | +|---------|--------| +| `ImportError` sur `api_stream` | Rollback git — `git revert --no-edit ..HEAD` | +| `"navigate"` absent des `_ALLOWED`/`_SERVER_SIDE` | Rollback — `git revert --no-edit ..HEAD` | +| Handler non callable | Rollback — `git revert --no-edit ..HEAD` | +| Tests wiring/e2e FAIL | Ne pas deploy — investiguer avant | +| Serveur 5005 ne boote pas | `systemctl restart rpa-streaming` + vérifier logs | + +## Données à préserver sur DGX (ne pas écraser) + +- `visual_workflow_builder/backend/instance/workflows.db` — **TRACKÉ par git** (modifié dans working tree). `git reset --hard` l'écraserait → **INTERDIT**. Backup obligatoire avant deploy. +- `.env.local` — creds clinique (DASHBOARD_PASSWORD, RPA_VLM_MODEL) + +## Procédure de rollback rapide + +```bash +# Étape 0 : NOTER SHA_PRE_DEPLOY avant le merge +cd /home/aivanov/ai/rpa_vision_v3 +SHA_PRE_DEPLOY=$(git rev-parse HEAD) # ← noter ce SHA avant git pull/merge + +# Backup workflows.db (tracké par git — ne jamais reset --hard) +cp visual_workflow_builder/backend/instance/workflows.db /tmp/workflows.db.backup + +# Rollback : revert vers SHA pré-deploy +git revert --no-edit ..HEAD # annule tout ce qui est arrivé APRÈS le SHA noté (pas HEAD~1 — core/navigation/__init__.py est nouveau, checkout échouerait) +systemctl restart rpa-streaming rpa-vision-v3-api + +# Restaurer workflows.db runtime si besoin +cp /tmp/workflows.db.backup visual_workflow_builder/backend/instance/workflows.db +``` + +> **⚠️ INTERDICTION** : `git reset --hard` est **INTERDIT** sur DGX — `workflows.db` est tracké par git et modifié dans le working tree. Un reset hard écraserait les données runtime. + +## Statut + +- Build+TDD : **FAIT** (131 tests verts, 0 régression) +- Plan déploiement : **FAIT v2** (4 corrections revue croisée Claude appliquées : service name, health URL, DGX path, rollback command + workflows.db tracking) +- Commit : **EN ATTENTE GO Dom** (demain matin) +- Deploy DGX : **EN ATTENTE GO Dom** (supervisé, serveur clinique live) + +— Qwen diff --git a/docs/PLAN_MENAGE_CODE_MORT_2026-06-23.md b/docs/PLAN_MENAGE_CODE_MORT_2026-06-23.md new file mode 100644 index 000000000..3f28989e6 --- /dev/null +++ b/docs/PLAN_MENAGE_CODE_MORT_2026-06-23.md @@ -0,0 +1,263 @@ +# Plan de ménage code mort — 2026-06-23 + +- `Auteur`: Qwen (audit read-only) +- `Date`: 2026-06-23 +- `Statut`: plan — **aucune exécution sans GO Dom** +- `Méthode`: existing-first (graphify-out/ + grep imports), vérification wiring runtime + +--- + +## Synthèse + +| Zone | Fichiers Python | A (WIRED) | B (ORPHELIN) | C (MORT) | Lignes C estimées | +|------|----------------|-----------|-------------|----------|-------------------| +| core/ | 226 | ~70 (31%) | ~75 (33%) | ~22 (10%) | ~800 | +| agent_v0/server_v1/ | 26 | 21 | 1 | 4 | ~510 | +| agent_v0/agent_v1/ | 44 | 36 | 2 | 6 | ~290 | +| server/ | 8 | 4 | 2 | 2 | ~300 | +| scripts/ | ~40 | 3 | 0 | ~37 | ~2500 | +| deploy/ | 10+ | 3 | 5 | 2 | ~100 | +| root | 4 | 1 | 2 | 1 | ~80 | +| **Total** | ~360 | ~138 | ~84 | ~74 | **~4580** | + +**~20% du codebase est MORT confirmé (catégorie C), ~23% est ORPHELIN/projection (B).** +Les zones les plus chargées : `core/analytics/` (13/17 orphelins), `core/cognition/` (4/5 morts), `core/extraction/` (4/5 morts), `scripts/` (37/40 morts). + +--- + +## NE PAS TOUCHER (runtime démo + systemd DGX + installateur) + +- `agent_v0/server_v1/api_stream.py` (7747 lignes) — serveur principal runtime +- `agent_v0/server_v1/stream_processor.py` (6085 lignes) — orchestrateur central +- `agent_v0/server_v1/resolve_engine.py` — résolution anchors OCR/VLM +- `agent_v0/server_v1/replay_engine.py` — replay actions +- `agent_v0/agent_v1/core/executor.py` — exécuteur agent Windows +- `visual_workflow_builder/backend/app.py` — VWB backend +- `web_dashboard/app.py` — dashboard +- `server/api_upload.py` — upload API (loopback DGX) +- Tous les services systemd DGX (rpa-vision-v3-*.service, ollama, rpa-vllm-grounder) +- `deploy/installer/` (Lea.iss, config_template.txt, build_installer.sh) +- `deploy/lea_package/` (config.txt, requirements_agent.txt) +- Chemin démo `Urgence_aiva_demo` end-to-end + +--- + +## Inventaire par zone — catégorie C (MORT confirmé) + +### core/ — MORT (22 fichiers, ~800 lignes) + +| Fichier | Lignes | Preuve mort | Doublon de ? | +|---------|--------|-------------|-------------| +| `cognition/precondition.py` | ~50 | Zero imports | — | +| `cognition/scene_expected.py` | ~50 | Zero imports | — | +| `cognition/trace.py` | ~80 | Zero imports | — | +| `cognition/vram_orchestrator.py` | ~100 | Zero imports (sauf __init__ test) | gpu/device_policy.py | +| `extraction/data_store.py` | ~80 | Zero imports | — | +| `extraction/extraction_engine.py` | ~120 | Zero imports (sauf __init__ try/except) | field_extractor.py | +| `extraction/iteration_controller.py` | ~60 | Zero imports | — | +| `extraction/schema.py` | ~40 | Zero imports | — | +| `execution/spatial_index.py` | ~60 | Zero imports | — | +| `execution/target_memory.py` | ~80 | Zero imports | learning/target_memory_store.py | +| `execution/workflow_runner.py` | ~200 | Zero imports (sauf __init__.py) | dag_executor.py | +| `interfaces/action_executor_interface.py` | ~40 | Tests only | — | +| `interfaces/error_handler_interface.py` | ~30 | Tests only | — | +| `interfaces/target_resolver_interface.py` | ~30 | Tests only | — | +| `graph/simple_state.py` | ~40 | Zero imports | — | +| `grounding/server.py` | ~500 | **SUPPRIMÉ** (n'existe plus) | — | +| `detection/seeclick_adapter.py` | ~300 | Zero runtime imports, __init__ fallback mort | — | +| `supervision/circuit_breaker.py` | ~40 | Zero imports, doublon system/circuit_breaker | system/circuit_breaker | +| `supervision/supervisor.py` | ~200 | Zero imports, docstrings mentionnent modules jamais wired | — | +| `gpu/clip_manager.py` | ~60 | Zero imports | embedding/clip_embedder | +| `gpu/ollama_manager.py` | ~80 | Zero imports | detection/ollama_client | +| `auth/manage_vault.py` | ~40 | Zero imports | auth/credential_vault | + +### agent_v0/ — MORT (10 fichiers, ~800 lignes) + +| Fichier | Lignes | Preuve mort | Doublon de ? | +|---------|--------|-------------|-------------| +| `server_v1/vm_controller.py` | 143 | Importé uniquement par visual_wait (mort) | — | +| `server_v1/visual_wait.py` | 54 | Importé uniquement par vm_controller (mort) | — | +| `server_v1/workflow_replay.py` | 256 | Zero imports, projection jamais intégrée | replay_engine.py | +| `agent_v1/window_info.py` | 55 | Zero imports | window_info_crossplatform.py | +| `agent_v1/tools/test_lea_pause_flow.py` | ~60 | Script debug standalone | — | +| `agent_v1/tools/test_lea_toast.py` | ~80 | Script debug standalone | — | +| `agent_v1/ui/_test_paused_toast.py` | ~40 | Script debug standalone | — | +| `agent_v1/monitoring/__init__.py` | 0 | Stub vide jamais développé | — | +| `agent_v0/config.py` | 58 | Zero imports | agent_v1/config.py | +| `agent_v0/setup_v1.sh` | 30 | Vestige, requirements.txt absent | — | + +### server/ — MORT (2 fichiers, ~300 lignes) + +| Fichier | Lignes | Preuve mort | Doublon de ? | +|---------|--------|-------------|-------------| +| `server/api_upload_dev_8001.py` | ~150 | Pas dans services.conf, dev-only | api_upload.py | +| `server/api_upload_dev_8002.py` | ~150 | Pas dans services.conf, dev-only | api_upload.py | + +### scripts/ — MORT (~37 fichiers, ~2500 lignes) + +| Fichier pattern | Count | Preuve mort | Notes | +|----------------|-------|-------------|-------| +| `demo_*_*_vwb_*_*.py` (dated) | ~12 | Scripts debug Jan 2026, jamais appelés par runtime | Vestiges développement VWB propriétés | +| `diagnostic_*_*.py` (dated) | ~6 | Scripts debug Jan 2026 | Diagnostic palette/catalogue | +| `test_*_vwb_*_*.py` (dated) | ~10 | Scripts test standalone Jan 2026 | Tests ad-hoc palette/propriétés | +| `implementer_*.py`, `implementation_*.py` | ~2 | Scripts debug Jan 2026 | Implémentation propriétés | +| `creer_sauvegarde_vwb_*.py` | 1 | Script debug | Sauvegarde ad-hoc | +| `analyse_cas_undefined_*.py` | 1 | Script debug | Analyse ad-hoc | +| `start_vwb_backend_*.py` (4 variants) | 4 | Doublons de `start_vwb_backend.py` | Versions ultra_stable, thread_safe, final, catalogue_complet | +| `start_system_complet_*.sh` | 1 | Script dated | Remplacé par systemd | +| `start_vwb_complete_*.sh` (2) | 2 | Scripts dated | Remplacé par systemd | + +**scripts/ WIRED (3)** : `record_and_build.py` (A), `bench_t2a_dryrun.py` (A), `backup_vwb_and_audit.sh` (A) + +### deploy/ — MORT (2 fichiers) + +| Fichier | Preuve mort | Doublon de ? | +|---------|-------------|-------------| +| `deploy/configs/config_dev_windows.txt` | Config dev, pas utilisé par installateur | config_template.txt | +| `agent_v0/setup_v1.sh` | Vestige, requirements.txt absent | — | + +### root — MORT (1 fichier) + +| Fichier | Preuve mort | +|---------|-------------| +| `mcp_rpa_vision.py` | MCP server jamais importé/callé par runtime ou systemd | + +--- + +## Inventaire par zone — catégorie B (ORPHELIN/projection) + +### core/ — ORPHELIN (~45+30 borderline, principalement analytics/ et healing/strategies/) + +| Zone | Fichiers | Preuve orphelin | Projection plausible ? | +|------|---------|-----------------|----------------------| +| `analytics/` (13 orphelins) | metrics_collector, anomaly_detector, insight_generator, performance_analyzer, success_rate_calculator, archive_storage, timeseries_store, query_engine, report_generator, analytics_api, dashboard_manager, realtime_analytics, resource_collector | Test-only ou zero imports | ✅ Analytics = fonctionnalité produit future (dashboard insights) | +| `healing/strategies/` (5) | base_strategy, format_transformation, semantic_variants, spatial_fallback, timing_adaptation | Test-only via `test_self_healing.py` | ✅ Self-healing = produit prévu | +| `cognition/working_memory.py` (1) | Intra-core lazy import seulement | ✅ Cognition = produit futur | +| `detection/owl_detector.py` (1) | Importé par agent_chat/autonomous_planner seulement | ✅ OWL = grounder alternatif | +| `detection/roi_optimizer.py`, `spatial_analyzer.py` | Test-only | ✅ Optimisation ROI future | +| `grounding/smart_resize.py` | Test-only, jamais importé runtime | ✅ Resize intelligent utile | +| `gpu/vram_monitor.py` | Zero imports | ✅ GPU monitoring utile | +| `monitoring/automation_scheduler.py` | __init__ try/except | ✅ Automation scheduling | +| `security/flask_security.py`, `input_validator.py`, `ip_allowlist.py`, `rate_limiter.py`, `audit_log.py` | __init__ exports mais jamais importés directement | ✅ Sécurité = P0 produit | +| `precision/` (5 fichiers) | Test-only | ✅ Metrics/precision utile | +| `system/artifact_retention.py` | __init__ export, jamais utilisé runtime | ✅ Rétention utile | + +### agent_v0/ — ORPHELIN + +| Fichier | Preuve | Projection ? | +|---------|--------|-------------| +| `server_v1/session_worker.py` | Commentaires seulement, pas importé | ✅ Background session processing | +| `agent_v1/core/anchor_catalog.py` + `anchor_relative.py` | Importé par tests seulement | ✅ Anchor resolution future | +| `deploy_windows.py` | Script packaging manuel | ✅ Packaging Windows | +| `deploy/windows_client/` (23 fichiers) | Copie déployée sur Windows, pas importée depuis repo Linux | ✅ = package agent Windows | + +### server/ — ORPHELIN + +| Fichier | Preuve | Projection ? | +|---------|--------|-------------| +| `server/processing_pipeline.py` | Importé par api_upload seulement | ✅ Pipeline processing | +| `server/processing_queue.py` | Importé par api_upload seulement | ✅ Queue processing | +| `server/storage_encrypted.py` | Importé par api_upload seulement | ✅ Encrypted storage | +| `server/worker_daemon.py` | Importé par api_upload seulement | ✅ Worker daemon | + +### root — ORPHELIN + +| Fichier | Preuve | Projection ? | +|---------|--------|-------------| +| `monitoring_server.py` | Optional dans services.conf (5003), pas déployé DGX | ✅ Monitoring utile | +| `run_gui.py` | Script standalone GUI launcher | ✅ GUI dev tool | +| `cli.py` | CLI entry point, importé par core modules | ✅ CLI utile | + +### deploy/ — ORPHELIN + +| Fichier | Preuve | Projection ? | +|---------|--------|-------------| +| `deploy/dgx/vm_launch.sh` + `vm_stop.sh` | Scripts QEMU VM DGX | ✅ VM management | +| `deploy/configs/config_pc_fixe_lan.txt` + `config_tim_pauline.txt` + `config_vm_lan.txt` | Configs déployées manuellement | ✅ Déploiement multi-site | +| `deploy/windows-rdp-launcher/` | Scripts RDP | ✅ Remote access | +| `deploy/hyperv_glpi_ubuntu24/` | Script HyperV | ✅ VM creation | + +--- + +## Quick wins sûrs (C évident, zéro risque) + +| # | Fichier | Lignes | Risque | Action | +|---|---------|--------|--------|--------| +| 1 | `core/interfaces/` (3 fichiers) | ~100 | Zéro | Supprimer — jamais importé runtime | +| 2 | `core/cognition/` (4 morts: precondition, scene_expected, trace, vram_orchestrator) | ~280 | Zéro | Supprimer — zero imports | +| 3 | `core/extraction/` (4 morts: data_store, engine, controller, schema) | ~300 | Zéro | Supprimer — field_extractor seul wired | +| 4 | `core/supervision/` (2: circuit_breaker doublon, supervisor jamais wired) | ~240 | Zéro | Supprimer — doublons | +| 5 | `core/detection/seeclick_adapter.py` | ~300 | Zéro | Supprimer — fallback mort | +| 6 | `server_v1/vm_controller.py` + `visual_wait.py` | ~200 | Zéro | Supprimer pair — jamais importé | +| 7 | `agent_v1/window_info.py` | 55 | Zéro | Supprimer doublon | +| 8 | `agent_v0/config.py` | 58 | Zéro | Supprimer doublon | +| 9 | `scripts/ dated Jan 2026` (~37) | ~2500 | Zéro | Supprimer — vestiges dev | +| 10 | `server/api_upload_dev_8001.py` + `api_upload_dev_8002.py` | ~300 | Faible | Supprimer dev-only | + +**Total quick wins** : ~10 actions, ~4133 lignes supprimées, zéro risque runtime + +--- + +## Zones à risque (à instruire avant GO) + +| Zone | Risque | Pourquoi | +|------|--------|----------| +| `core/analytics/` (13 orphelins) | Moyen | Projection produit plausible — supprimer = perdre design analytics | +| `core/healing/strategies/` (5) | Moyen | Self-healing = produit prévu — test-only mais intention future | +| `core/grounding/infigui_server.py` + `infigui_worker.py` | Moyen | Server jamais lancé mais worker existe — dépendance circulaire mort | +| `core/security/` (5 orphelins) | Moyen | Sécurité P0 produit — supprimer = perdre auth/rate-limiting design | +| `core/gpu/` (3 morts: vram_monitor, clip_manager, ollama_manager) | Faible | GPU monitoring plausible mais doublon vérifié | + +--- + +## Proposition de réorganisation + +**Pas de réorganisation proposée à ce stade.** Le ménage C suffira à dégraisser ~4500 lignes. Les B (projections) resteront en place avec documentation `# PROJECTION: ` pour les distinguer du code wired. + +**Après ménage C**, réorganisation possible : +- `scripts/` dated → archiver dans `scripts/archive/` ou supprimer +- `core/interfaces/` mort → supprimer (remplacé par protocols Python si besoin) +- `deploy/configs/` → consolider dans `deploy/installer/config_template.txt` + +--- + +## Plan d'exécution par lots (chirurgie itérative supervisée) + +**Chaque lot = petit, testable (≤ 2 min), QG entre chaque, GO Dom requis.** + +| Lot | Action | Lignes | Test QG | Risque | +|-----|--------|--------|---------|--------| +| **L1** | Supprimer `core/interfaces/` (3 fichiers) | ~100 | `pytest tests/ -k "not e2e"` | Zéro | +| **L2** | Supprimer `core/cognition/` morts (4 fichiers) | ~280 | pytest | Zéro | +| **L3** | Supprimer `core/extraction/` morts (4 fichiers) | ~300 | pytest | Zéro | +| **L4** | Supprimer `core/supervision/` (2 fichiers) | ~240 | pytest | Zéro | +| **L5** | Supprimer `core/detection/seeclick_adapter.py` | ~300 | pytest + vérifier __init__.py fallback | Zéro | +| **L6** | Supprimer `server_v1/vm_controller.py` + `visual_wait.py` | ~200 | pytest | Zéro | +| **L7** | Supprimer `agent_v1/window_info.py` + `agent_v0/config.py` + `setup_v1.sh` | ~140 | pytest | Zéro | +| **L8** | Nettoyer __init__.py exports morts dans core/ | ~50 lignes | pytest + import check | Faible | +| **L9** | Supprimer `scripts/` dated Jan 2026 (~37 fichiers) | ~2500 | pytest (aucun script dans test path) | Zéro | +| **L10** | Supprimer `server/api_upload_dev_8001/8002.py` | ~300 | pytest + vérifier services.conf | Faible | +| **L11** | Supprimer `core/gpu/` morts (3 fichiers) | ~200 | pytest | Faible | +| **L12** | Supprimer `core/auth/manage_vault.py` + `execution/workflow_runner.py` | ~240 | pytest | Faible | + +**Total** : 12 lots, ~4580 lignes, risque zéro à faible. Chaque lot précédé de GO Dom + QG post-lot. + +--- + +## Liste NE PAS TOUCHER + +- Runtime démo `Urgence_aiva_demo` (VWB → backend → agent_v1 → Léa → Easily) +- Services systemd DGX (10 services actifs) +- Installateur clinique (`deploy/installer/`, `deploy/lea_package/`) +- `api_stream.py` (7747 lignes, serveur principal) +- `stream_processor.py` (6085 lignes, orchestrateur) +- `resolve_engine.py` (résolution anchors) +- `replay_engine.py` (replay actions) +- `learned_workflow_bridge.py` (pont VWB ↔ core) +- `core/competences/` (verdicts, promotions, persist, catalog — wired) +- `core/federation/` (GlobalFAISSIndex, LearningPack — wired) +- `core/embedding/` (clip, faiss, state, fusion — wired) +- `core/execution/` (observe_reason_act, target_resolver, input_handler — wired) +- `core/healing/healing_engine.py` + `execution_integration.py` — wired +- `core/analytics/analytics_system.py` + `screen_change_detector.py` — wired +- `core/workflow/` (semantic_matcher, variable_manager, execution_plan, shadow_observer — wired) diff --git a/docs/PLAN_MIGRATION_WORKFLOWS_STORE_2026-06-09.md b/docs/PLAN_MIGRATION_WORKFLOWS_STORE_2026-06-09.md new file mode 100644 index 000000000..c4f587f7f --- /dev/null +++ b/docs/PLAN_MIGRATION_WORKFLOWS_STORE_2026-06-09.md @@ -0,0 +1,46 @@ +# Plan de migration — persistance workflows (JSON → SQLAlchemy) + +- `Date`: 2026-06-09 +- `Auteur`: Claude (proposition, validation Dom requise) +- `Statut`: PLANIFIÉ — **post-POC, aucun refactor engagé** +- `Réf dette`: DETTE-015 (docs/DETTE_TECHNIQUE.md) +- `Priorité`: P2 (fragilité réelle, contournée proprement pour le POC par symlink) + +## Constat + +Trois stockages de workflows coexistent, sans source de vérité unique : + +| Store | Emplacement | Utilisé par | État | +|-------|-------------|-------------|------| +| Fichiers JSON | `visual_workflow_builder/backend/data/workflows/*.json` | route API VWB `/api/workflows/` (`api/workflows.py:53`, **relatif au cwd**) | **source réelle** (42 fichiers) | +| DB SQLAlchemy | `…/backend/instance/workflows.db`, table `workflows` + Alembic | composants SQLAlchemy (`db.models`) | propre mais **pas lue par la route** (23 lignes) | +| JSON legacy | `data/training/workflows/` | dashboard `web_dashboard` (`app.py:187-189`) | vide partout | + +### Problèmes +1. **Pas de source unique** → divergences (les 42 JSON ≠ 23 lignes DB). +2. **Résolution par cwd** → le bug P0-1 du 2026-06-09 (dev cwd=backend OK ; DGX cwd=racine = 0 workflows). +3. Pas d'écriture atomique ni de validation de schéma côté JSON. +4. Confusion : 3 emplacements, dont un legacy mort. + +## Cible + +**Unifier sur la DB SQLAlchemy déjà présente** (infra à moitié en place : table + Alembic). La route API lit/écrit la DB ; le store fichier JSON et le legacy sont retirés. Bénéfices : source unique, transactions, intégrité, requêtes, fin de la dépendance au cwd. + +> Note : un store fichier n'est pas disqualifiant en soi ; c'est la **coexistence non synchronisée** + la dépendance au cwd qui posent problème. On choisit SQLAlchemy car l'infra existe déjà (vs fiabiliser le JSON, qui laisserait le double store). + +## Plan (TDD, post-POC, validation Dom entre étapes) + +1. **Audit d'usage** : recenser tous les appels à `WorkflowDatabase` (lecture **et** écriture) — API, moteur d'exécution, agent, frontend VWB. Cartographier le contrat (méthodes `list/load/save/delete`). +2. **Repository SQLAlchemy** : implémenter un `WorkflowRepository` exposant le **même contrat** que `WorkflowDatabase`, adossé à la table `workflows` (réutiliser `db.models`). Tests unitaires CRUD. +3. **Script de migration** : importer les 42 JSON → table `workflows` (idempotent, backup DB avant). Vérifier parité (42 JSON ↔ N lignes, diff de contenu). +4. **Bascule de la route** derrière un flag (`RPA_WORKFLOWS_BACKEND=sqlalchemy|json`, défaut `json`) → tests d'équivalence API (mêmes réponses qu'avant). +5. **Bascule par défaut** sur SQLAlchemy une fois la parité prouvée ; retrait du symlink (DETTE-015). +6. **Nettoyage** : retirer le store legacy `data/training/workflows` (dashboard) ou le rebrancher sur le repository ; supprimer `WorkflowDatabase` quand plus aucun appelant. + +## Préconditions / risques +- **Ne pas engager avant la fin du POC** (refactor de persistance = risque pour la démo). +- Touche frontend VWB + agent + moteur d'exécution → bascule progressive sous flag obligatoire. +- Le symlink (DETTE-015) reste le contournement stable jusqu'à la migration. + +## Effort estimé +~1,5–2,5 j en TDD (audit + repository + migration + tests d'équivalence + bascule). diff --git a/docs/PLAN_REMISE_AU_CARRE_APPRENTISSAGE_2026-06-27.md b/docs/PLAN_REMISE_AU_CARRE_APPRENTISSAGE_2026-06-27.md new file mode 100644 index 000000000..1c7946665 --- /dev/null +++ b/docs/PLAN_REMISE_AU_CARRE_APPRENTISSAGE_2026-06-27.md @@ -0,0 +1,103 @@ +# Plan de remise au carré — chaîne d'apprentissage & rejeu de Léa + +- `Date`: 2026-06-27 +- `Auteur`: Claude (mandat Dom) +- `Statut`: actif — formalise l'analyse du 27/06 ; **n'invente rien**, chapeaute `PLAN_ACTION_SUITE_2026-06-23` (axe « Rejeu intelligent ») et `CARTO_APPRENTISSAGE_FONDS_COMMUN_2026-06-16`. +- `Contrainte cardinale (Dom 27/06)`: **câbler la chaîne ET rendre Léa correcte AVANT la dernière manip manuelle de Dom** (= avant la perte de l'accès hands-on / on-site). Cette contrainte est la **barre d'acceptation** de ce plan. *(À préciser : quoi exactement, et quelle date.)* + +--- + +## 1. Pourquoi ça ne marche pas (vérifié le 27/06) + +**En une phrase : la chaîne a été diagnostiquée (16/06), décidée (23/06) et planifiée (R1→R7), puis la livraison clinique a absorbé les 2 semaines — le câblage end-to-end n'a jamais démarré. Le blocage est d'EXÉCUTION, pas de décision.** + +Faits vérifiés (grep/fichier, ce jour) — la chaîne n'est pas câblée au runtime : + +| Maillon | Preuve vérifiée 27/06 | Effet | +|---|---|---| +| Worker d'enrichissement | `data/training/_worker_queue.txt` = **0 octet, mtime 11/06** | enrichissement **à l'arrêt depuis 16 j** | +| Import auto session→workflow | `ShadowLearningHook()` instancié **uniquement dans son propre fichier**, jamais dans `finalize`/runtime | l'appris **n'est pas** rendu rejouable sans geste manuel | +| Lecture du fonds au rejeu | `GlobalFAISSIndex.search()` → **grep = 0 appel** ; `TargetResolver.lookup()` jamais appelé par `replay-session` | rejeu = **coords figées**, pas cible apprise | +| Dé-silo | filtre `machine_id` à **5 points** de `stream_processor.py` (3199, 3285, 4499, 4530, 5062) | apprentissage **siloté par poste** | + +Causes racines (au-delà du « c'est débranché ») : +1. **Les 2 semaines = infra de survie** (portage DGX ARM, installateur EXE, réseau/firewall/VPN, reprise panne secteur + reboot, watchdog OVMF, enrôlement, streaming, push-log). Indispensable pour livrer — mais **0 h sur le câblage de la boucle**. +2. **Découpage par composant, jamais par boucle.** Hook, FAISS, TargetMemory bâtis et validés isolément (sessions/agents différents). **Personne n'a possédé ni testé la boucle entière sur une vraie session** → trous accumulés à chaque soudure, invisibles. +3. **Le raccourci démo est devenu permanent.** Le rejeu `Urgence_aiva` marchait en coords figées + templating `{{var}}` (seule pièce câblée). « Ça marchait » → le chemin intelligent n'a **jamais été allumé**. + +⚠️ **À confirmer en amont (audit runtime Qwen, NON vérifié indépendamment)** : « **11/15 postes heartbeat-only, 0 % résolution vision/OCR/anchors** ». Si avéré, le premier problème n'est pas l'apprentissage mais que **les postes ne font pas encore le geste du POC** (pas de vraie capture, cascade vision non exercée). → **à prouver, chiffré, AVANT tout recâblage** (Phase 0). + +--- + +## 1bis. CARTE DE CÂBLAGE VÉRIFIÉE (28/06 — 3 agents read-only, sourcé code) + +> Cette section **corrige** le §1 sur deux points (diagnostic affiné, moins grave qu'annoncé). + +**Deux corrections vérifiées (mes affirmations antérieures étaient fausses) :** +1. **« sessions → squelettes sans action » = FAUX.** Les actions (clics/saisies) sont attachées au workflow sur les **edges** (`WorkflowEdge.action`, `graph_builder.py:1457`), pas sur `node.variants` (qui = variantes *visuelles* d'écran, champ non peuplé au runtime). 48/71 workflows auto-appris portent leurs actions ; 23 sont vides (sessions trop courtes). +2. **« rejeu = coords figées » = FAUX.** Léa **résout chaque cible par la vue** à chaque rejeu (cascade OCR→template→YOLO→VLM sur anchors, `resolve_engine.py:1804`). Coords = fallback ultime seulement. Conforme 100 % vision. + +**Ce qui MARCHE** : capture→workflow avec actions ; worker traite ; 36/71 atteignent `AUTO_CANDIDATE` ; rejeu visuel (VWB-DB 226 `click_anchor` + JSON auto-appris non vides) ; **R2 à moitié branché** (`TargetMemoryStore` consulté en tête de `_resolve_target_sync`, `resolve_engine.py:1862`). + +**Les 4 vrais trous (sourcés) :** + +| # | Trou | Preuve | Type | +|---|---|---|---| +| **1 (P0)** | **11/15 postes n'enregistrent rien** | démarrage capture 100 % manuel (clic TIM « Apprenez-moi », `smart_tray.py:349` / `chat_window.py:1716`) ; heartbeats auto (`main.py:378`). Risque : dialogue consentement `Tk()` (`smart_tray.py:54`) invisible en RDP/Citrix `pythonw` | **amont / UX (à confirmer bug vs usage par logs client)** | +| 2 | **Apprentissage incrémental débranché** | `LearningManager` non instancié serveur ; mute `WorkflowStats` mémoire non re-persisté ; `record_observation` (`learning_manager.py:54`) **0 appelant**. Seul `GraphBuilder` écrit `learning_state`, fige sur OBSERVATION si qualité faible (`graph_builder.py:400`) | promotion jamais déclenchée | +| 3 | **2 mondes disjoints** : JSON auto-appris ≠ DB VWB rejouable | stores/loaders/matchers séparés ; une session apprise ne devient pas un workflow DB rejouable | = **R1** (pont JSON→DB) | +| 4 | **Fonds commun jamais lu au rejeu** | `GlobalFAISSIndex.search()` = 0 appel (seul `add_pack` écrit) | = **R3** (FAISS au rejeu) | + +**Points d'insertion confirmés** : R1 = worker `_process_session` après `_persist_workflow` (réutiliser `import_learned_workflow`/`learned_workflow_bridge`, idempotence par `workflow_trajectory_signature` existante). R2/R3 = `resolve_engine.py:1862-1878` (élargir `memory_lookup` + insérer `GlobalFAISSIndex.search()`). + +**Priorité (contrainte « Léa correcte avant dernière manip ») : #1 (amont) d'abord** — si 11/15 postes ne capturent pas, l'aval est sans objet. Test décisif = grep logs client `"Session … en cours"` vs `"Session finalisée"` (Qwen). + +--- + +## 2. État des décisions (rappel — la plupart sont déjà prises) + +Tranchées le 23/06 (`DECISIONS_PRODUIT_EN_ATTENTE_2026-06-23.md`) → **on exécute, on ne re-décide pas** : +- **F2-1/F14-1** : rejeu intelligent = **OUI, prérequis** (consulter le fonds appris, pas de coords figées). +- **F1-1** : critère de fusion = **signature de trajectoire** (create-or-update). +- **F9-1** : **DB = vérité**, JSON = échange ; métrique = workflows rejouables validés. +- **F6-1** : mutualisation **cross + intra-clinique** (fédération anonymisée dans le périmètre + lever silo `machine_id`). + +**Reste ouvert (1 seule, vraie décision) :** +- **Q-F2-2 — Provider Léa au runtime** : quel modèle/route sert la **résolution** au rejeu. ⚠️ Gap tracé 23/06 : le point d'entrée actif = **agent_chat 5004 → `SemanticMatcher.find_workflow()` sur fichiers JSON**, pas la DB → **contredit F9-1**. Se résout **en chemin** à la Phase 2 (où la résolution est recâblée). Reco modèle : Qwen3-VL-4B grounder + gemma4 (bench 13/06). + +**Décisions potentiellement induites par la Phase 0** : si « 0 % vision » confirmé, une décision « comment forcer/garantir la capture vision réelle sur poste » surgira (priorité absolue, avant R1). + +--- + +## 3. Plan d'exécution (séquencé, chirurgie supervisée) + +> Les chantiers R1→R7 détaillés sont dans `PLAN_ACTION_SUITE_2026-06-23` (§ Axe central) — **non dupliqués ici**. Ce plan ajoute la **Phase 0 de mesure** (nouvelle) et l'**ordre/critères**. + +**Phase 0 — MESURER (avant tout recâblage).** Établir la vérité terrain : par poste, nb de **vraies sessions**, la **cascade vision est-elle déclenchée** (compteur de résolutions par méthode), captures reçues, état queue. **C'est ce que push-log + une télémétrie vision apportent** (lien direct avec briques 1-4 livrées). → *Mission Qwen (accès runtime DGX).* **Critère de sortie : on sait, chiffré, ce que font les 15 postes.** + +**Phase 1 — RECONNECTER L'AMONT (R1).** Import auto session→workflow post-`finalize` + **relancer le worker** (queue morte 11/06). *Critère : une session TIM réelle devient un workflow rejouable sans geste manuel.* + +**Phase 2 — RECONNECTER L'AVAL (R2+R3) + résoudre Q-F2-2.** Câbler `TargetResolver.lookup()` + lecture FAISS au rejeu, **fallback obligatoire sur coords figées** (non négociable Qwen — enrichir, pas casser) ; aligner le point d'entrée résolution sur la DB (F9-1). *Critère : Léa résout par cible apprise, retombe sur coords si échec.* + +**Phase 3 — BOUCLE + DÉ-SILO (R4/R5/R6).** verify post-condition (échec → pause supervisée), réécriture du fonds, lever silo `machine_id` + brancher fédération (`GlobalFAISSIndex.search()`). + +--- + +## 4. Gouvernance (corrige la cause racine #2) + +- **Un seul propriétaire de la boucle entière** (pas un découpage par composant). +- **Critère d'acceptation = un test end-to-end sur une session réelle**, pas une validation par brique. +- Chirurgie itérative supervisée : un maillon = un test ≤ 2 min = GO Dom ; démo `Urgence_aiva` intacte à chaque étape ; reconfirmer le wiring runtime avant chaque modif (imports lazy = verdicts « orphelin » non fiables). +- **Merge prod supervisé Dom.** + +## 5. Lien avec la « dernière manip manuelle » (deadline) + +La contrainte de Dom fait de ce plan un **chemin critique** : tant que Léa n'est pas correcte (au moins Phase 0 + Phase 1-2 sur 1 poste pilote), **la dernière manip manuelle ne doit pas avoir lieu** — sinon plus d'accès hands-on pour réparer. → **Définir avec Dom : quelle est cette manip, et sa date butoir**, pour caler le séquencement. + +## 6. Première action concrète + +**Phase 0 confiée à Qwen** (chiffres runtime). Doc + page décisions = ce fichier. Reste : GO Dom sur le séquencement + définition de la deadline « dernière manip ». + +--- + +*Plans sources (ne pas dupliquer) : `PLAN_ACTION_SUITE_2026-06-23`, `CARTO_APPRENTISSAGE_FONDS_COMMUN_2026-06-16`, `DECISIONS_PRODUIT_EN_ATTENTE_2026-06-23`, `PLAN_CHANTIER_UNIFICATION_LEA_VWB_2026-06-17`.* diff --git a/docs/POSTMORTEM_PANNE_SECTEUR_DGX_2026-06-20.md b/docs/POSTMORTEM_PANNE_SECTEUR_DGX_2026-06-20.md new file mode 100644 index 000000000..84f83814b --- /dev/null +++ b/docs/POSTMORTEM_PANNE_SECTEUR_DGX_2026-06-20.md @@ -0,0 +1,74 @@ +# Post-mortem — Panne secteur DGX 2026-06-20 (test reboot non planifié) + +- `Auteur`: Claude (infra) +- `Date`: 2026-06-20 ~03:00 CEST +- `Scope`: La coupure électrique réelle de 02:07 traitée comme **exécution non planifiée du test de stabilité reboot** (`CHECKLIST_DGX_PRE_CLINIQUE.md` §6). Objectif : mesurer ce qui se rétablit **seul** vs ce qui exige une **intervention** — c'est le critère clé pour une clinique sans personnel technique sur place. +- `Méthode`: diagnostic read-only multi-agents (Claude infra, Qwen appli/guest, Codex consolidation). Aucun reset Git, aucun changement réseau hors IP statique arbitrée par Dom. + +--- + +## 1. Timeline + +| Heure | Événement | +|---|---| +| 02:07 | Coupure secteur. DGX reboot. Poste Dom (Linux) reboot. Laptop Windows `.11` (sur batterie) jamais coupé. | +| 02:07:42 | `win11-arm-lea.service` (user) auto-démarre la VM. | +| ~02:09 | Watcher coordination (systemd user) revenu seul. Services rpa système remontés. | +| 02:07→02:18 | VM bloquée en boucle TianoCore (QEMU 99% CPU, guest agent/SSH absents). | +| 02:18 | Codex diagnostique : `OVMF_VARS.fd` corrompu par coupure brutale. | +| 02:21 | Codex restaure OVMF connu-bon (18/06) + TPM frais → VM boot prouvé (écran verrouillage Windows). | +| ~02:28 | Claude : DGX revenu en DHCP sur `.46` (au lieu de `.45`) → IP statique `.45` appliquée (décision Dom). | +| ~02:35 | Accès VM Dom rétabli (tunnel + VNC, mot de passe OK). | +| ~02:55 | Crash-loop `dashboard-user` éteint (Qwen). Revue infra/appli consolidée. | + +--- + +## 2. Ce qui s'est rétabli SEUL (✅ socle solide) + +| Domaine | Constat | Réf checklist | +|---|---|---| +| Boot DGX + services rpa | Tous `active+enabled` (dashboard, streaming, agent-chat, vwb back/front, api, worker, stream-worker, **vllm-grounder**, firewall) | §1 PASS | +| Firewall | Réappliqué : 5900/5902/3389/22220/8000/11434 **filtrés LAN**, seuls 5001/5002/5004/5005 ouverts | §2 PASS (fort) | +| Auth | Dashboard 401, VWB 401 (basic auth), streaming Bearer | §3 majoritaire PASS | +| Auto-start VM | `win11-arm-lea.service` a bien démarré la VM (linger=yes) | §4.1 — prouvé (était « à implémenter ») | +| Coordination | Watcher couche-1 (systemd user) revenu seul | — | + +→ **Le socle infra/services/sécurité survit à une coupure brutale sans intervention.** + +## 3. Ce qui a EXIGÉ une intervention (⚠️ gaps reprise non-assistée) + +| # | Problème | Cause | Correctif (qui) | Risque clinique | +|---|---|---|---|---| +| G1 | **DGX IP a dérivé `.45`→`.46`** | bail DHCP après reboot | IP statique `.45` (Claude/Dom) | **Élevé** — casse tous clients/tunnels pointant `.45`. DHCP non fiable. | +| G2 | **VM bloquée TianoCore** | `OVMF_VARS.fd` corrompu (coupure brutale) | restore OVMF connu-bon + TPM frais (Codex) | **Élevé** — sans agent, VM morte jusqu'à intervention manuelle. | +| G3 | **`dashboard-user` crash-loop** (244 restarts) | fallback user clash port 5001 (service système le sert déjà) | stop + mask session (Qwen) | Moyen — bruit/ressources ; `disabled` mais relancé. | +| G4 | **Léa guest non reconnectée** | `config.txt` = `CONFIGURE_ME` + login Windows requis | à renseigner `.45`+token (Qwen) | **Élevé** — VM redémarre mais Léa ne reprend pas le travail seule. | +| G5 | **Mot de passe VNC** | `-vnc password=on` sans `set_password` dans les scripts | rétabli de fait (tunnel) | Faible/Moyen — fragile si relaunch sans repose mdp. | + +--- + +## 4. Recommandations de durcissement — reprise CLINIQUE non-assistée + +> Toutes **modifiantes → à valider par Dom** (mises en file, non appliquées cette nuit). + +1. **BIOS DGX = « Power On » au retour AC** (physique, Dom) — sinon une coupure laisse le DGX éteint. +2. **IP statique** : fait au labo (`.45`) ; cible clinique = Ethernet statique `.178` (DHCP = point faible démontré par G1). +3. **Auto-réparation OVMF** dans `win11-arm-lea.service` : au boot Windows réussi, snapshot `OVMF_VARS.fd` sain ; `ExecStartPre` : si boucle TianoCore détectée (CPU 99% + guest agent absent N s), restaurer le snapshot sain automatiquement. → neutralise G2 sans agent. +4. **`rpa-vision-v3-dashboard-user`** : `mask` **persistant** (pas seulement session) — G3. +5. **Léa reprise auto** (G4) : `config.txt` persistant vers IP DGX + token ; auto-login Windows + Léa auto-start (`pythonw`) + reconnexion fleet sans geste humain. +6. **Mot de passe VNC** (G5) : poser le mot de passe au lancement via le monitor (script), ou documenter la procédure de repose. + +--- + +## 5. Propositions de MAJ pour `CHECKLIST_DGX_PRE_CLINIQUE.md` (Qwen, propriétaire) + +- §4.1 « auto-start VM » : passer **À IMPLÉMENTER → VALIDÉ** (prouvé par la panne, 02:07:42). +- §1.10 / Items à fixer #1 : dashboard service **système actif** confirmé ; le fallback user est l'orphelin → masquer. +- §6 « Test reboot » : **exécuté en réel le 2026-06-20** → renseigner les résultats (col. Résultat) à partir des sections 2 et 3 ci-dessus. +- Ajouter une ligne **G1 dérive IP DHCP** et **G2 corruption OVMF** comme items de durcissement explicites. + +--- + +## 6. Verdict test + +Le **socle technique tient** (services, firewall, auth, auto-start VM). Les **deux points durs** pour une clinique sans technicien sur place sont **G1 (dérive IP DHCP)** et **G2 (corruption OVMF VM non auto-réparée)** : tous deux ont nécessité un agent cette nuit. La cible clinique doit les **automatiser** (IP statique Ethernet + auto-réparation OVMF). G4 (Léa ne reprend pas seule) est le troisième chantier reprise. diff --git a/docs/README_PROPOSED_2026-07-02.md b/docs/README_PROPOSED_2026-07-02.md new file mode 100644 index 000000000..2770b225c --- /dev/null +++ b/docs/README_PROPOSED_2026-07-02.md @@ -0,0 +1,272 @@ +# RPA Vision V3 — Automatisation basée sur la compréhension visuelle des interfaces + +> ⚠️ **Projet en phase POC clinique** — déployé mais **non production-ready**. +> Voir [`docs/STATUS.md`](STATUS.md) pour l'état réel par module. Certaines +> briques tournent en clinique de bout en bout, d'autres sont **codées mais +> gated/dormantes** (activation supervisée par Dom uniquement). + +*Dernière mise à jour : 2 juillet 2026 (brouillon proposé — remplace le README daté du 14 avril 2026)* + +> **Note de relecture** : ce brouillon reflète la trajectoire mai→juillet 2026 +> (livraison DGX Spark, flotte clinique Wallerstein, PII, extraction, grounder +> Qwen3-VL, installeur EXE, MAJ silencieuse). L'ancien README/STATUS s'arrêtait +> au « premier replay Notepad du 13 avril 2026 » et ne reflétait plus la réalité. + +## Intention + +Automatiser des workflows métier hospitaliers par **compréhension sémantique +de l'écran** plutôt que par coordonnées de clic fixes. Le système observe le +TIM, reconstruit un graphe d'états de l'interface, et cherche à **rejouer +intelligemment** la procédure en reconnaissant visuellement les éléments cibles +— y compris quand l'UI change légèrement. Objectif produit : Léa **apprend** un +parcours et le **généralise**, ce n'est **pas** du record-and-replay. + +Terrain cible : postes hospitaliers hétérogènes (**vrais logiciels métier en +mode web, RDP et Citrix**), TIM sur **2 écrans** → capture de la **fenêtre +active**. C'est cette hétérogénéité qui justifie l'approche « 100 % vision ». + +Contraintes fortes : +- **100 % vision** : résolution de l'UI par la vue, pas par sélecteurs DOM/API. +- **100 % local** : inférence sur GPU local (Ollama / vLLM). Aucun appel cloud + dans le pipeline par défaut (passeport souverain santé). + +> Historique : la maquette « Easily Assure » a servi de banc de test jusqu'à +> mi-2026 ; elle est **abandonnée comme cible** (recadrage juin 2026). Ne plus +> raisonner « Easily ». + +## Architecture en couches + +``` +RawSession (couche 0) — capture événements + screenshots (Agent V1 Windows) + ↓ +ScreenState (couche 1) — états d'écran à plusieurs niveaux d'abstraction + ↓ +UIElement (couche 2) — détection sémantique (OCR / template / VLM) + ↓ +State Embedding (couche 3) — fusion multi-modale + index FAISS + ↓ +Workflow Graph (couche 4) — nœuds, transitions, résolution de cibles par la vue +``` + +### Topologie runtime réelle (POC clinique) + +``` +[Léa — client Windows sur poste TIM] ~15-19 postes enrôlés + │ (HTTPS/Bearer, sortant uniquement) + ▼ +[DGX Spark — 192.168.1.178 — serveur clinique unique] + ├─ Streaming server (5005) — sessions live, pipeline core, replay + ├─ Dashboard Fleet (5001) — enrôlement + build installeur + supervision + ├─ Agent Chat (5004) + ├─ VWB backend/frontend (5002/3002) — admin + └─ Grounder VLM (Qwen-VL, vLLM/Ollama) +``` + +Le serveur **n'initie jamais** de connexion vers les postes. Accès distant Dom +via VPN clinique (Stormshield) + SSH cert ; déploiement code par scp/rsync +(le DGX ne fait pas `git pull`). + +## État des fonctionnalités (synthèse) + +Le détail par module est dans [`docs/STATUS.md`](STATUS.md). +Légende : **opérationnel** (utilisé en clinique, sans régression connue) / +**alpha** (fonctionnel sur cas de référence, peu généralisé) / +**gated** (codé + testé mais désactivé par défaut, activation supervisée) / +**débranché** (code présent mais jamais appelé au runtime) / +**en cours**. + +**Opérationnel (tourne en clinique)** +- Capture Windows (Agent V1) + streaming vers le DGX (JPEG + downscale, PII-safe). +- Client Léa autonome : **installeur EXE Windows** (python-embed 3.12.8, sans + Python système, install per-user sans UAC), enrôlement via dashboard Fleet, + démarrage auto (raccourci Bureau optionnel à l'install). Version client + **1.0.2** (upgrade-safe). +- Résilience client RDP/Citrix : watchdog re-affiche le tray si Léa disparaît. +- Streaming server FastAPI (5005). **Sessions live en mémoire** + (`live_session_manager.py`) ; **persistances SQLite/JSONL** en place à côté : + registre d'agents enrôlés, store de logs clients par `machine_id`, mémoire de + résolutions (`replay_memory`), DB workflows VWB. +- **Assainissement PII** couche 1 (regex + structurel, `pii_sanitizer.py`) : + câblé au chokepoint `stream_event`, floute aussi les `focus_*`, + `text_input` → token `[SAISIE]`. Déployé sur le DGX. +- Enrôlement flotte : dashboard construit à la volée le ZIP Léa complet + autoportant avec config (URL serveur / token / machine_id). +- Grounding visuel : résolution **par la vue** au replay — stratégie **VLM-first** + sur les éléments texte, ordre pré-compilé **OCR → template → VLM** (YOLO présent + dans le code mais sans appelant au runtime). Les coords figées ne sont + qu'ultime fallback. + Grounder **Qwen3-VL** câblé (`RPA_GROUNDING_ENGINE=qwen3vl_vllm`) — activé en + override runtime sur le DGX (défaut du repo = legacy Qwen2.5-VL). + +**Alpha** +- Construction de workflow graph depuis une session ; matching heuristique. +- Replay E2E supervisé multi-étapes (bien au-delà du 1er succès Notepad d'avril). +- Mode apprentissage : pause + demande d'aide humaine quand la résolution échoue + (l'échec est un signal d'apprentissage, pas un stop en erreur). +- Embeddings CLIP + index FAISS (construit ; **lecture au replay débranchée**). +- **Extraction dossier patient** (`core/extraction/`) : orchestrateur + OCR(valeurs) → VLM(rôles, ancré sur ids OCR = 0 hallucination) → qualité, + persistance en DB VWB. Handler `extract_dossier` dispatché côté serveur. + *Lecture écran→JSON prouvée sur vrai DPI urgences ; à confirmer en usage courant.* +- **Navigation visuelle** (`core/navigation/`, nouveau) : login visuel, grounding + OCR-first, `verify_before`/`verify_after` (vision = validateur). Handler + `navigate` dispatché côté serveur. *Naissant — à éprouver.* +- Web Dashboard (5001), Agent Chat (5004), module auth (Fernet + TOTP), + federation (LearningPack, export/import). +- Visual Workflow Builder (VWB) : catalogue d'actions, import d'anchors Léa, + tests de compétence supervisés, Basic auth LAN. *Les bugs DB runtime + historiques ont été largement corrigés ; durcissement encore en cours.* + +**Gated (codé + testé, OFF par défaut — activation supervisée)** +- **MAJ silencieuse client** (canary par poste, DETTE-022) : résolveur canary + serveur + orchestrateur client implémentés et testés. `RPA_AUTO_UPDATE_ENABLED` + OFF ; swap atomique + rollback **implémentés** (`Lea.bat`, marqueur + `UPDATE_READY`) mais **jamais exercés en prod** — revue humaine obligatoire + avant activation. +- **Import auto de l'appris → DB VWB rejouable** (R1) : `RPA_R1_AUTO_IMPORT` OFF. +- **Log shipper client** (remontée auto des logs vers le serveur) : gated OFF. +- PII couche 2 (NER CamemBERT-bio, ONNX CPU côté DGX) : à embarquer, dormant. + +**Débranché / en cours** +- Chaîne apprentissage **non bouclée end-to-end** : R1 (pont JSON appris ↔ DB VWB) + et R3 (lecture FAISS au replay) manquants ; apprentissage incrémental débranché ; + savoir siloté par `machine_id`. +- Self-healing / recovery global ; analytics / reporting. + +## Limitations connues + +- Le replay est validé sur un nombre restreint d'applications ; robustesse + grounding encore un sujet ouvert (dette DETTE-006/010). +- Chaîne apprentissage : capture→workflow marche, mais le **bouclage + observation→rejeu généralisé n'est pas câblé** (R1/R3). FAISS construit mais + jamais lu au replay. +- **Combien de postes exercent réellement le geste complet est non vérifié** + (Phase 0 de mesure en attente). Postes possiblement muets sous RDP/Citrix. +- PII : couche 1 déployée ; couche 2 (NER) dormante. Historique de données + capturées en clair — décision purge/assainissement en attente. +- Asymétrie connue : VWB direct utilise un détecteur d'UI au recording que le + replay sur Léa n'utilise pas (sujet ouvert post-POC, à ne pas « fixer » là). +- 🔴 `POST /api/v3/execute/instruction` pilote l'écran X11 de l'hôte **sans + sandbox/kill-switch** ; atteignable sur LAN clinique. Chantier sandbox (F8.4) + identifié comme prioritaire. + +## Démarrage + +### Prérequis + +- Python 3.10 à 3.12 +- Serveur : GPU NVIDIA local. Cible clinique = **DGX Spark** (GB10, mémoire + unifiée, ARM64, headless). Alternative dev = x86 RTX 5070. +- LLM local : **Ollama** (`:11434`) et/ou **vLLM** (grounder Qwen3-VL). +- Windows 10/11 pour le client Agent V1. + +### Installation (serveur / dev Linux) + +```bash +python3 -m venv .venv # venv unique du repo (svc.sh + unités systemd utilisent .venv) +source .venv/bin/activate # (le DGX historique utilise venv_v3 ; réfs venv_v3 du Makefile = périmées en local) +pip install -r requirements.txt + +# Ollama local + modèle VLM +ollama serve & +ollama pull gemma4:latest # modèle VLM par défaut du repo (core/config.py, .env.example) +# Le grounder Qwen3-VL est servi par vLLM, pas par un pull Ollama — voir docs/STATUS.md + +cp .env.example .env # ajuster RPA_VLM_MODEL, VLM_ENDPOINT, ports +``` + +### Lancer les services + +Services pilotés par `svc.sh` (source de vérité : `services.conf`). + +```bash +./svc.sh status +./svc.sh start boot # groupe nominal (variantes : full, vwb, ou un service seul) +./svc.sh start streaming # streaming server seul (5005) +./svc.sh stop boot +``` + +| Port | Service | +|---|---| +| 8000 | API Server (upload / traitement core) | +| 5001 | Web Dashboard / Fleet (enrôlement, supervision) | +| 5002 | VWB Backend (Flask) | +| 5003 | Monitoring | +| 5004 | Agent Chat | +| 5005 | Streaming Server (Agent V1 → pipeline core) — canal principal Léa | +| 5006 | Session Cleaner | +| 5099 | Worker VLM (analyse sessions → workflows JSON) — lancé avec le groupe boot | +| 3002 | VWB Frontend (Vite/React) | + +### Client Windows (Agent V1) — déploiement clinique + +Le client capture souris/clavier/écran et envoie au serveur (sortant uniquement). +Déploiement recommandé via le **dashboard Fleet** (enrôlement + ZIP autoportant), +puis `Installer-Lea.bat` sur le poste (installeur EXE Python-embedded). + +```bash +# Build du ZIP complet autoportant (python-embed + source à jour) +./deploy/build_package_full.sh +# Build de l'installeur EXE (Inno Setup, per-user) +./deploy/installer/build_installer.sh +``` + +## Arborescence du dépôt + +``` +rpa_vision_v3/ +├── agent_v0/ +│ ├── agent_v1/ # Client Windows (capture, tray, MAJ, PII-safe logs) +│ └── server_v1/ # FastAPI streaming + pipeline (replay, resolve, PII, extraction) +├── core/ +│ ├── detection/ # Cascade OCR / template / YOLO / VLM +│ ├── embedding/ # CLIP + FAISS +│ ├── graph/ # Workflow graphs +│ ├── extraction/ # Lecture écran→JSON (OCR→VLM→qualité) [nouveau] +│ ├── navigation/ # Login visuel, grounding, verify [nouveau] +│ ├── execution/, learning/, auth/, federation/, gpu/ +├── server/ # API upload / traitement core (8000) +├── visual_workflow_builder/ # VWB (Flask + React Vite) +├── web_dashboard/ # Dashboard + Fleet +├── tools/ # Outils CLI (session cleaner 5006, POC lecture écran) +├── agent_chat/ # Interface conversationnelle + planner +├── deploy/ # Build ZIP, installeur EXE, systemd, dgx/ +├── data/ # Sessions, embeddings, FAISS, apprentissage +├── docs/ # Documentation technique (voir STATUS.md) +├── tests/ # pytest (unit, integration, e2e) +├── services.conf, svc.sh, run.sh +``` + +## Tests + +```bash +source .venv/bin/activate +pytest -m "not slow" -q +pytest tests/integration/ -q +``` + +Quelques tests legacy sont connus comme cassés — voir `docs/` et la mémoire projet. + +## Documentation + +- [`docs/STATUS.md`](STATUS.md) — état réel par module (⚠ à réaligner sur juillet 2026) +- [`docs/PLAN_ACTION_SUITE_2026-06-23.md`](PLAN_ACTION_SUITE_2026-06-23.md) — plan consolidé post-livraison clinique +- [`docs/PLAN_REMISE_AU_CARRE_APPRENTISSAGE_2026-06-27.md`](PLAN_REMISE_AU_CARRE_APPRENTISSAGE_2026-06-27.md) — pourquoi la chaîne apprentissage n'est pas câblée + plan +- [`docs/DESIGN_ANONYMISATION_TOKENS_TYPES_2026-06-28.md`](DESIGN_ANONYMISATION_TOKENS_TYPES_2026-06-28.md) — PII par tokens typés +- [`docs/DESIGN_MAJ_SILENCIEUSE_CANARY_2026-07-01.md`](DESIGN_MAJ_SILENCIEUSE_CANARY_2026-07-01.md) — MAJ silencieuse canary (gated) +- [`docs/POC/PORTAGE_DGX_SPARK_2026-05-28.md`](POC/PORTAGE_DGX_SPARK_2026-05-28.md) — portage DGX Spark +- [`docs/EXECUTION_LOOP_FLAGS.md`](EXECUTION_LOOP_FLAGS.md), [`docs/CONFORMITE_AI_ACT.md`](CONFORMITE_AI_ACT.md) + +## Concepts clés + +- **RPA 100 % vision** : l'agent localise un élément par ce qu'il voit + (label + contexte visuel), pas par `x,y`. La vision est **source de vérité** : + les coords servent en exécution, mais l'écran est re-résolu si divergence. +- **Léa apprend, comprend, généralise** — pas record-and-replay. +- **Apprentissage progressif** : shadow → assisté → autonome, supervisé. +- **LLM 100 % local** : Ollama / vLLM sur GPU local. Aucun appel cloud dans le + pipeline par défaut. + +## Licence + +Propriétaire — tous droits réservés. diff --git a/docs/RESEAU_CLINIQUE_FLUX_LEA_DGX_2026-06-24.md b/docs/RESEAU_CLINIQUE_FLUX_LEA_DGX_2026-06-24.md new file mode 100644 index 000000000..cd00b047d --- /dev/null +++ b/docs/RESEAU_CLINIQUE_FLUX_LEA_DGX_2026-06-24.md @@ -0,0 +1,43 @@ +# Flux réseau Léa ↔ DGX — demande DSI (mise en service clinique) + +- `Date` : 2026-06-24 +- `Objet` : autoriser les postes TIM (client **Léa**) à dialoguer avec le serveur **DGX**, en environnement **VLAN segmenté**. +- `Interlocuteur` : DSI clinique. + +--- + +## 1. Contexte + +- Le **serveur DGX** est raccordé au VLAN `192.168.1.0/24`, adresse **`192.168.1.178`** (réservation DHCP / IP statique, MAC `10:B6:76:F0:2F:F4`, gateway `192.168.1.243`). +- Les **postes TIM** exécutent le client **Léa** (agent léger). Léa se connecte **en sortie** vers le DGX : **le poste appelle le serveur**, le serveur n'initie jamais de connexion vers le poste. +- Le réseau étant **segmenté en VLAN** : si les postes sont sur un VLAN différent de celui du DGX, le **routage inter-VLAN** doit autoriser les flux ci-dessous. +- Déploiement **100 % local** : **aucun flux internet** n'est requis pour Léa (pas de cloud, pas de NAT entrant). + +## 2. Flux à autoriser — poste TIM → DGX `192.168.1.178` (TCP) + +| Port | Service | Usage | Auth applicative | +|------|---------|-------|------------------| +| **5005** | Streaming Léa | **Canal principal** : enrôlement, remontée des captures, réception des étapes | Jeton Bearer | +| **5001** | Dashboard | Enrôler un poste + télécharger l'installeur (navigateur) | Basic (compte `lea`) | +| **5004** | Agent-chat | Orchestration Léa | Jeton | +| *(3002 / 5002)* | Visual Workflow Builder | *Optionnel — poste admin uniquement, non requis sur chaque poste TIM* | Basic (compte `lea`) | + +- **Sens** : connexions **sortantes du poste** vers le DGX. Le trafic retour passe par les connexions établies → si le pare-feu DSI est **stateful**, **aucune règle entrante spécifique** n'est à créer. +- Uniquement **TCP unicast** vers `192.168.1.178`. Pas de multicast/broadcast. + +## 3. Sécurité + +- Tous les services exposés sont **authentifiés** (jeton Bearer ou Basic) → l'ouverture n'expose **aucun service anonyme**. +- **Aucune exposition internet** : pas de NAT entrant, pas de cloud, traitement 100 % local sur le DGX. +- Côté DGX, un **pare-feu local** (`rpa-firewall`) restreint déjà 5004/5005 à une **liste blanche** de sous-réseaux ; il sera **complété avec le sous-réseau du VLAN des postes**. + +## 4. Demandes à la DSI + +1. **Communiquer le sous-réseau du VLAN des postes TIM** (ex. `192.168.X.0/24`) → pour compléter le pare-feu du DGX. +2. **Autoriser le routage / les ACL inter-VLAN** pour les flux du §2, du VLAN postes vers `192.168.1.178`. +3. **Alternative la plus simple** (si acceptable côté sécurité) : raccorder les postes TIM **sur le même VLAN que le DGX** (`192.168.1.0/24`) → plus aucun réglage inter-VLAN ; le pare-feu DGX les autorise déjà. + +## 5. Côté éditeur (déjà en place) + +- DGX : 12 services actifs, pare-feu local opérationnel et persistant (survit au reboot). +- Ajout du sous-réseau postes au pare-feu DGX = **une seule règle**, **persistée** et **réversible**, applicable **immédiatement en live** dès que le sous-réseau est connu. diff --git a/docs/TABLEAU_ACTIONS_DOM_PRECLINIQUE_2026-06-21.md b/docs/TABLEAU_ACTIONS_DOM_PRECLINIQUE_2026-06-21.md new file mode 100644 index 000000000..c2d4f6d61 --- /dev/null +++ b/docs/TABLEAU_ACTIONS_DOM_PRECLINIQUE_2026-06-21.md @@ -0,0 +1,82 @@ +# Tableau d'actions Dom — finalisation phase pré-clinique + +- `Date`: 2026-06-21 (vivant, mis à jour par Claude + Qwen au fil des validations) +- `Objectif`: **à la clinique, Dom ne fait que brancher le DGX + installer Léa.** Tout le reste validé avant. + +--- + +## A. Validé automatiquement — AUCUNE action Dom ✅ + +| Item | Preuve | +|---|---| +| 9 services RPA actifs **+ enabled** (reboot-persistant) | `systemctl is-enabled` = enabled ×9 | +| Dashboard online, **24 workflows**, KB OK | `/api/system/status`, `/api/knowledge-base/stats` | +| Worker apprentissage armé (idle) | `processing status: running/armed` | +| VWB backend 5002 + frontend 3002 | HTTP 200 | +| Agent-chat 5004 | `/api/status` OK | +| Grounder vLLM | service enabled+active (8000/8001) | +| VM Win11 auto-start | `win11-arm-lea` enabled + Linger=yes | +| WireGuard serveur DGX | `wg-quick@wg0` enabled+active | +| **SSH cert-only** (no password, CA) | prouvé depuis `.40` | +| RDP VM (chaîne tunnel→guest) | prouvé depuis `.40` (LOGON nego OK) | +| Firewall persistant (ports sensibles loopback) | `rpa-firewall` enabled, scan LAN | +| Fleet / enrôlement | ✅ `/api/v1/agents/fleet` → **7 machines enrôlées** (corrige Qwen « 0 ») | +| Grounder vLLM **Qwen3-VL-4B** | ✅ enabled+active (8000/8001) = grounder de prod (corrige Qwen « dégradé ») | +| Données : FAISS 13666, anchors 468, 24 wf | ✅ (Qwen, re-vérifié) | + +## B. Actions DOM — à faire (avec mon guidage) 👤 + +| # | Action | État | +|---|---|---| +| 1 | **Box** : port-forward UDP 51820 → 192.168.1.45 (Freebox) | ✅ fait — reste **activer WireGuard** sur le laptop | +| 2 | **Laptop `.11`** : installer bundle SSH (cert) + `DGX-Lea.conf` + lanceur RDP | à faire | +| 3 | **GO** sur les correctifs prod confirmés (tableau B′ ci-dessous) | à faire | +| 4 | Valider `config.txt` Léa (`.45`+token) avant déploiement VM | à faire | +| 5 | GO design **auto-réparation OVMF** | à faire | +| 6 | (Jour clinique) IP statique Ethernet `.178` + exclusion DHCP `.45` labo | clinique | +| 7 | (Hardening) BIOS DGX **Power-On au retour secteur** | clinique | + +### B′. Correctifs prod confirmés (cross-validés) — à exécuter sous GO Dom 🔧 +| Item | Détail | Owner | +|---|---|---| +| **RPA_SIGNING_KEY** absent `.env.local` | HMAC métadonnées FAISS (« Option A ») | Claude/Qwen + GO | +| **Git DGX pas aligné** (`ec1fb81`→`cf81ce4c7`) | active auth VWB LAN ; **backup `workflows.db` AVANT reset** | Claude + GO | +| **config.txt Léa non déployée** (VM) | transférer après validation `.45`+token | Qwen + GO | +| **Guest SSH VM (22220)** ne répond pas | sshd guest down (forward OK) ; Léa gérable via RDP sinon | Qwen/Dom | +| **workflows_count** 24/79/37 | expliqué : 24=VWB visuels, 79=catalogue agent-chat, 37=KB/FAISS → **Dom choisit la métrique produit** | clarifié | + +## C. En cours de validation — Claude + Qwen (preuve + re-vérif croisée) 🔄 + +| Item | Owner | État | +|---|---|---| +| WireGuard bout-en-bout (handshake depuis l'extérieur) | Claude | bloqué sur B-1 (box) | +| RDP depuis `.11` | Dom+Claude | bloqué sur B-2 | +| **Chaîne d'apprentissage e2e** (capture→upload→grounding→workflow→replay) | Qwen | à prouver (worker armé, 0 session) | +| **Léa enrôlement fleet** | Claude | ✅ système OK (`/api/v1/agents/fleet`, 2 machines) MAIS `last_seen` pré-panne → **relancer Léa dans la VM** pour re-check | +| **Léa sur VM Win11** | Dom (RDP)+Qwen | à lancer (enrôlée, pas connectée depuis reboot) | +| **Léa sur laptop `.11`** | Dom | bloqué sur B-2 (install) | +| **Léa sur serveur `.40` (Linux)** | Qwen | ✅ faisable (`agent_v1` cross-platform : config Linux, window_info X11/xdotool, orchestrateur Léa-first agent-chat Linux) → à tester | +| Fleet endpoint | Claude | ✅ résolu : `/api/v1/agents/fleet` (Bearer) ou proxy `/api/fleet/fleet` | +| Perf : agent-chat CLIP sur CPU (pas GPU) | Claude/Qwen | à noter (vérifier si voulu) | + +## D. Jour clinique — le minimum (objectif atteint si A+B+C verts) + +1. **Brancher le DGX** (réseau clinique Ethernet `.178`). +2. **Installer Léa** sur les postes. +→ tout le reste déjà validé et figé. + +--- + +## E. Portage clinique — bascule WiFi `.45` → Ethernet `.178` (point Dom) + +**Principe** : sur WireGuard, le DGX est toujours `10.10.0.1`, **indépendant du réseau physique**. `ssh dgx-vpn` + RDP (`DGX_HOST=10.10.0.1`) marchent à l'identique labo et clinique. Le changement d'IP est **invisible** pour l'accès distant. + +| Ce qui change à la clinique | Action | +|---|---| +| Routeur clinique forward **UDP 51820 → 192.168.1.178** | Dom + DSI (équivalent du forward Freebox vers `.45`) | +| **Endpoint** config WireGuard = IP publique clinique | Claude régénère `DGX-Lea.conf` quand IP connue | +| DGX bascule sur profil Ethernet `.178` | déjà pré-configuré (`Connexion filaire 3`) | +| Host cert SSH | ✅ déjà : principals incluent `192.168.1.178` | +| CA, SSH cert-only, serveur WG, forward RDP, lanceurs | ✅ config locale DGX → rejouée telle quelle | + +**Ne PAS toucher la carte Ethernet `.178` au labo** (consigne Dom). Au labo = WiFi only. diff --git a/docs/VEILLE_OCR_SPACE_ENGINE3_2026-07-02.md b/docs/VEILLE_OCR_SPACE_ENGINE3_2026-07-02.md new file mode 100644 index 000000000..927d3bedd --- /dev/null +++ b/docs/VEILLE_OCR_SPACE_ENGINE3_2026-07-02.md @@ -0,0 +1,83 @@ +# Veille — OCR.space Engine 3 vs notre brique OCR locale (2026-07-02) + +> Origine : lien trouvé par Dom (https://ocr.space/ocrapi#ocrengine3), analysé par agent de +> recherche web (Claude) le 2026-07-02. Verdict court : **sans suite pour la prod** (cloud / +> on-prem Windows propriétaire, bbox Engine 3 moins précises), mais **3 idées à retenir**, +> dont un bench PP-OCRv5 à faire. + +## 1. Ce qu'est OCR.space + +**API OCR cloud** (`https://api.ocr.space/parse/image`), JSON, éditée par a9t9 Software — +même éditeur que **UI.Vision RPA** (d'où la proximité ressentie avec notre besoin). +Plans : Free (25 000 req/mois, 1 MB/image), PRO (300 000/mois, 5 MB), PRO PDF (100+ MB). + +### Les 3 moteurs + +| | Engine 1 | Engine 2 | Engine 3 | +|---|---|---|---| +| Positionnement | le plus rapide, langues asiatiques | « meilleur all-round », auto-détect langue | le plus récent, « précision la plus élevée » | +| Langues | ~25 dont français | latines + chinois | **200+, auto-détection** | +| Spécifique | — | orientations mixtes | **tables → Markdown, manuscrit, cases à cocher** | +| Overlay bbox | précis, rapide | précis, rapide | dispo mais **« not as precise as Engine 1/2 »**, appel **2-3× plus lent** | +| Quotas free | 25 000/mois | idem | **2 500/mois** (compute-intensive) | +| PDF searchable | oui | oui | **non** | + +Doc officielle : *« Engine 3 prioritizes OCR accuracy and Markdown output over spatial +precision »* → moteur type « document VLM » orienté texte/structure, **pas** grounding +spatial. **L'inverse de notre besoin RPA** (bbox mot fiables pour cliquer). + +### Format de sortie +`isOverlayRequired=true` → `TextOverlay.Lines[].Words[]` avec `WordText/Left/Top/Width/Height`. += équivalent de ce que **docTR nous donne déjà** (hiérarchie mots+bbox), gratuit, Apache 2.0. +Paramètres notables : `detectOrientation` (auto-rotation), `scale` (upscale interne images +basse résolution — cas d'école screenshots 96 DPI), `isTable`, `OCREngine=1|2|3` (même JSON +quel que soit le moteur). + +## 2. On-premise ? + +**« OCR.space Local » existe** (section `#local`) : 100 % offline, mêmes API. MAIS : +- **Windows Server 2022+** (DGX = ARM Linux → VM Windows dédiée rien que pour l'OCR) +- Prix opaque (contact sales ; avis tiers : ~999 $/mois entreprise, non confirmé) +- Boîte noire propriétaire installée par leur support via RDP +- **Engine 3 en local non garanti** (blog on-prem ne mentionne que l'Engine 2) + +Sources : https://ocr.space/ocrapi (#local), https://forum.ocr.space/t/how-about-pricing-and-order-ocr-space/28246, +https://www.koncile.ai/en/ressources/ocr-space-test-review + +## 3. Verdict (contrainte 100 % local) + +**Rien pour la prod.** Cloud exclu (données patient) ; version locale = Windows Server +propriétaire à prix opaque ; le moteur intéressant (Engine 3) a des bbox explicitement +moins précises et plus lentes — rédhibitoire pour du grounding de clic. + +**À retenir (transposable chez nous) :** +1. **Upscale ×2 des crops basse résolution avant OCR** (leur param `scale`) — gain connu, + quasi gratuit, mesurable immédiatement. +2. **Schéma overlay unifié multi-moteurs** (Lines→Words {text, bbox, confidence}) en sortie + de toute la cascade → moteurs interchangeables sans toucher l'aval. La seule vraie bonne + idée d'architecture à copier. +3. **Leçon Engine 3** : les moteurs « haute précision texte » sacrifient la précision + spatiale → valide notre split OCR (valeurs+bbox) / VLM (rôles). Ne pas chercher un + moteur unique qui fait les deux. + +## 4. Alternatives locales — état de l'art screenshots/UI (sources < 12 mois) + +- **PaddleOCR 3.x / PP-OCRv5** — piste n°1. Apache 2.0, 106 langues dont **français**, + **`return_word_box=True` = bbox au niveau mot** (post-merge ponctuation/accents à prévoir). + **Moteur OCR retenu par OmniParser (Microsoft) pour les GUI agents** = validation directe + sur notre cas. Sources : paddleocr.ai (PP-OCRv5 multi-languages), arxiv 2408.00203 + (OmniParser), huggingface.co/blog/baidu/ppocrv5 +- **Qwen3-VL (déjà déployé vLLM)** — text spotting coords normalisées [0,1000] ; utilisable + en validateur/fallback OCR sans nouveau composant ; moins déterministe qu'un OCR classique. +- **Surya / Surya 2** — très bon mais **licence OpenRAIL-M à seuil de revenus** → risque + licence produit commercial. À écarter ou budgéter. +- Repères 2026 : PP-OCRv5 = meilleure précision/débit généraliste ; docTR/EasyOCR corrects + sur texte numérique mais dépassés en multilingue ; GOT-OCR2/dots.ocr = document, pas UI. + +## 5. Actions proposées (non lancées — décision Dom) + +1. **Bench PP-OCRv5 (`lang=fr`, `return_word_box=True`) vs docTR/EasyOCR** sur nos vraies + captures DPI (jeu du POC `tools/poc_lecture_ecran.py`, 13 champs GEMSA/CCMU) : précision + petites polices, bbox mot, latence GPU DGX. +2. **Upscale ×2 systématique** des crops avant OCR — à mesurer sur le même bench. +3. **Schéma overlay unifié** en sortie de cascade (docTR/EasyOCR/PP-OCRv5/Qwen3-VL). diff --git a/docs/coordination/ROLES.md b/docs/coordination/ROLES.md new file mode 100644 index 000000000..db1a14e8d --- /dev/null +++ b/docs/coordination/ROLES.md @@ -0,0 +1,81 @@ +# Rôles — Coordination multi-agents rpa_vision_v3 + +- `Date`: 2026-06-08 +- `Auteur`: Qwen +- `Statut`: actif + +--- + +## Équipe + +| Agent | Rôle | Inbox | Sortie attendue | +|---|---|---|---| +| **Dom** | Propriétaire du projet, décideur final, filtre produit | Direct | GO/NOGO, arbitrages, validation démo | +| **Codex** | Coordinateur, orchestration, arbitrages techniques, synthèse | `inbox_codex/` | Missions, QG consolidés, plans, décisions | +| **Claude** | Implémentation, patches, benchmarks, plans techniques | `inbox_claude/` | Commits, RESULTAT, plans, analyses | +| **Qwen** | Quality Gate (QG), audit, historien, garde-fou, propositions | `inbox_qwen/` | Verdicts GO/NOGO, audits, synthèses, docs | +| **Gemini** | Consultation ponctuelle, recherche externe | `inbox_gemini/` | Recherche, analyse comparative | + +--- + +## Règles de coordination + +### 1. Inbox routing +- 1 question = 1 fichier dans l'inbox du destinataire +- Format : `YYYY-MM-DD_HHMM_auteur-to-destinataire_SUJET.md` +- Réponse courte et actionnable, pas de rapport long sauf demande explicite + +### 1bis. Orchestration Codex +- Codex est l'orchestrateur actif du projet. +- Tant que Claude et Qwen ont des loops de coordination actifs, Codex doit leur donner en continu une prochaine tâche actionnable. +- Exception unique : Codex attend explicitement un feu vert de Dom ou un QG bloquant avant d'autoriser l'étape suivante. +- Si une exécution est bloquée, Codex doit quand même distribuer du travail non destructif utile : préparation, QG, audit read-only, rollback, plan de tests, registre décisions. +- Claude ne doit pas rester sans mission d'exécution/preuves ou préparation technique. +- Qwen ne doit pas rester sans mission de QG, audit, contradiction check ou registre. + +### 2. Statuts +| Statut | Signification | +|---|---| +| `open` | En attente de réponse | +| `ACK` | Lu et pris en compte | +| `RESULTAT` | Livraison d'un résultat | +| `GO` / `NO-GO` | Verdict qualité | +| `DRAFT` | Brouillon, pas encore prêt pour revue | +| `archived` | Archivé (ne pas supprimer) | + +### 3. Règle QG +- Pas d'action structurelle sans QG (Quality Gate) explicite +- Pas de promotion de competence sans QG + GO Dom +- Qwen signale à Dom toute contradiction entre agents + +### 4. Règles de sécurité +- Pas de secret en clair dans les messages de coordination +- Pas de replay autonome sans Dom devant Windows +- Pas de code mort supprimé sans GO Dom explicite +- Pas d'activation modèle/grounder sans bench + QG + GO Dom + +### 5. Capitalisation +- Chaque avancée importante doit être capitalisée dans `active/` ou `registre/` +- Les inboxes sont un flux, pas une mémoire +- `syntheses/` = rapports d'inventaires et benchmarks +- `registre/` = décisions formelles + +### 6. Sessions +- Qwen reste dans la session longue (historien, garde le fil complet) +- Codex et Claude ont chacun leur session propre +- Si Qwen voit des contradictions entre les instructions de Codex/Claude, il signale à Dom avant d'agir + +--- + +## Flux de travail typique + +``` +Codex → mission → Claude/ Qwen +Claude → implémentation → commit → RESULTAT → Codex/Qwen +Qwen → QG → GO/NO-GO → Codex +Codex → synthèse → Dom → GO/NOGO +``` + +--- + +*Document vivant — mettre à jour quand les rôles évoluent.* diff --git a/docs/coordination/RUNBOOK-DGX-POST-REBOOT-CHECK.md b/docs/coordination/RUNBOOK-DGX-POST-REBOOT-CHECK.md new file mode 100644 index 000000000..ace87a01b --- /dev/null +++ b/docs/coordination/RUNBOOK-DGX-POST-REBOOT-CHECK.md @@ -0,0 +1,233 @@ +# RUNBOOK DGX - Check post-deploiement et post-reboot POC + +- Date: 2026-06-17 +- Responsable coordination: Codex +- Scope: POC DGX uniquement +- Cible labo actuelle: DGX `192.168.1.45` +- Cible clinique preparee: Ethernet `192.168.1.178/24`, gateway `192.168.1.243`, DNS `192.168.1.9` puis `192.168.1.8`, IPv6 inactive, profil inactif tant que Dom/Codex ne donnent pas le GO. + +## Objectif + +Valider qu'apres deploiement puis redemarrage du DGX, toute la chaine POC reste fonctionnelle sans dependance `localhost` cote utilisateur: + +- dashboard; +- VWB; +- import workflow; +- anchors visibles; +- agent-chat / "Discuter avec Lea"; +- streaming/fleet; +- worker/replay; +- installateur Lea autonome; +- configuration reseau DGX sans bascule clinique accidentelle. + +## Regle de conduite + +- Dom execute uniquement les gestes UI de la section "Check Dom". +- Les agents techniques collectent les preuves systeme et reseau. +- Toute divergence est notee avec: etape, symptome, impact, rollback propose. +- Pas de changement reseau clinique actif pendant le check labo: le Wi-Fi reste le lien de test. + +## 0. Prerequis avant reboot + +| Point | Attendu | Statut | +|---|---|---| +| Branche DGX | `poc-dgx` | A confirmer par Claude | +| Commit DGX | `9605cc9d9` ou commit ulterieur explicitement valide | A confirmer par Claude | +| Commits POC presents | `667575c3a`, `9605cc9d9` | A confirmer par Claude/Qwen | +| Deploy propre | pas de fichier parasite embarque dans le runtime POC | A confirmer | +| Donnees runtime | backup `backend/instance/workflows.db` avant reset/deploy, restore apres deploy, hash/size verifies | A confirmer par Claude | +| Donnees non trackees | `data/workflows` (symlink) + donnees VWB sous `visual_workflow_builder/backend/data/` non supprimes | A confirmer | +| VWB build | bundle reconstruit depuis `poc-dgx` | A confirmer | +| Installateur Lea | version `1.0.1`, Python embedded obligatoire | A confirmer | +| Profil Ethernet clinique | prepare si besoin, `autoconnect off`, non active | A confirmer par Qwen | +| Enrolement VM Lea | VM/poste pointe vers DGX labo `192.168.1.45` pour le test, pas vers l'URL cloud | A confirmer | + +NO-GO reboot si le deploiement n'est pas fige, si le DGX ne pointe pas sur le bon commit, ou si `workflows.db`/anchors/workflows ne sont pas verifies apres deploy. + +## 1. Baseline avant reboot + +Un agent technique releve l'etat avant redemarrage: + +| Famille | Controle | Attendu | +|---|---|---| +| Services principaux | dashboard, VWB backend, streaming, agent-chat, worker/fleet actifs | actifs | +| Ports utilisateur | `5001`, `5004`, `5005` disponibles depuis le LAN autorise | OK | +| Ports internes | `5002`, `8000`, `11434` selon architecture DGX | OK ou documente | +| Dashboard | acces par `http://192.168.1.45:5001` | login ou 401 attendu, pas page blanche | +| Streaming | health `:5005` | 200/healthy | +| Auth streaming | route protegee sans token | 401 attendu | +| Agent chat | status/health `:5004` | 200/online | +| VWB | frontend charge depuis IP DGX | OK | +| Bundle VWB | aucune URL active `localhost`/`127.0.0.1` | OK | +| VM Windows Lea | Lea pointe vers DGX `192.168.1.45` pour `5004/5005` | OK | + +Si un point est deja rouge avant reboot, on corrige avant de redemarrer. Le reboot ne doit pas servir a masquer une panne. + +## 2. Reboot DGX + +1. Claude annonce le debut du reboot dans la coordination. +2. Codex garde la coordination ouverte. +3. Attendre le retour reseau du DGX sur le Wi-Fi labo. +4. Ne pas activer le profil Ethernet clinique pendant ce cycle. +5. Claude poste l'heure de retour et le premier etat des services. + +NO-GO immediat si le DGX ne revient pas sur le reseau labo ou si le dashboard reste inaccessible plus de 10 minutes apres retour reseau. + +## 3. Check technique post-reboot + +### 3.1 Services + +Noms constates/attendus a verifier sur le DGX. Ne pas supposer qu'un service existe: si une unite est absente, le noter et verifier si sa fonction est couverte par un autre service. + +- `rpa-streaming` +- `rpa-agent-chat` +- `rpa-vision-v3-dashboard` +- `rpa-vision-v3-vwb-frontend` +- `rpa-vision-v3-vwb-backend` +- `rpa-vision-v3-worker` +- `rpa-vision-v3-stream-worker` si installe, sinon documenter l'absence +- `rpa-vision-v3-api` +- `rpa-grounding` ou service grounder equivalent si present; sinon documenter le fallback +- `rpa-vision-v3-healthcheck.timer` si actif dans le deploiement final; sinon ne pas bloquer mais documenter + +Attendu: + +- services POC critiques actifs apres reboot; +- services POC critiques enabled si necessaires au demarrage automatique; +- pas de boucle restart; +- logs recents sans traceback bloquant; +- healthcheck timer actif ou decision documentee si non utilise; +- VWB frontend servi par le vrai frontend POC (`frontend_v4`), pas l'ancien `frontend/`. +- Ne jamais utiliser `git clean -xfd` sur DGX pendant la sequence POC: cela detruirait les donnees runtime non trackees. + +### 3.2 Reseau et ports + +| Port | Usage | Attendu labo | +|---|---|---| +| `5001` | dashboard / VWB integre | accessible via `192.168.1.45` | +| `5004` | agent-chat / ChatWindow Lea | accessible depuis VM Windows autorisee | +| `5005` | streaming/fleet/replay | accessible depuis VM Windows autorisee | +| `5002` | VWB backend si separe | conforme au deploiement DGX | +| `8000` | upload/API core si actif | conforme au deploiement DGX | +| `11434` | Ollama local DGX | local/interne sauf decision contraire | + +Attendu: + +- aucun appel navigateur produit vers `localhost` ou `127.0.0.1`; +- `5004/5005` repondent depuis la VM Windows; +- le dashboard charge toutes ses ressources depuis l'IP DGX; +- la carte Ethernet clinique reste inactive ou non connectee pendant le check labo. + +### 3.3 Endpoints et fonctions serveur + +| Controle | Attendu | +|---|---| +| Dashboard `5001` | page login/auth visible, pas 500 | +| Streaming `5005/health` | healthy | +| Streaming route protegee sans token | 401 | +| Fleet machines | VM Lea visible ou reenrolement documente | +| Enrolement VM Lea | `config.txt`/runtime pointe vers DGX, pas vers URL cloud | +| Replay next | repond correctement pour machine enrollee | +| Agent chat `5004` | online et session chat possible | +| VWB catalog/workflows | liste chargee | +| Anchors API | upload/thumbnail/original accessibles depuis IP DGX | +| Worker | pas de backlog bloquant, pas de crash loop | +| Modele/grounding | service disponible ou fallback documente | + +## 4. Check Dom UI post-reboot + +Dom ne fait que ces gestes: + +| Etape | Geste Dom | GO | NO-GO | +|---|---|---|---| +| T1 Dashboard | Ouvrir l'URL DGX fournie | page login/dashboard visible | page blanche, site inaccessible, erreur 500 | +| T2 Chat Lea | Dans la VM, ouvrir "Discuter avec Lea", envoyer "Bonjour" | reponse en quelques secondes | pas connectee, erreur serveur, fermeture fenetre | +| T3 Bulles action | Demander une action simple | bulles/progression visibles | aucune progression visible pendant une action lancee | +| T4 Import VWB | Importer un workflow JSON Lea | succes, workflow visible | erreur, rien ne se passe | +| T5 Anchors | Ouvrir une etape avec ancre | image d'ancre/crop visible | image absente, plein ecran au lieu de crop | +| T6 Replay supervise | Lancer un replay safe/supervise | execution ou demande de confirmation | clic hors cible, blocage, absence de reaction | +| T7 Reconnexion apres reboot | Fermer/rouvrir Lea sur VM | reconnecte DGX sans reconfig manuelle | demande Python/outils externes ou config manuelle non prevue | + +## 5. Criteres GO/NO-GO + +GO livraison labo si: + +- DGX revient apres reboot; +- services critiques actifs; +- dashboard accessible via IP DGX; +- VWB charge sans `localhost`; +- chat Lea fonctionne depuis VM; +- import workflow OK; +- anchors visibles; +- replay supervise OK ou comportement d'arret/confirmation conforme; +- installateur Lea autonome confirme; +- profil Ethernet clinique non active par erreur. + +NO-GO livraison si un des points suivants echoue: + +- DGX ne revient pas proprement apres reboot; +- dashboard/VWB inaccessible; +- `5004` ou `5005` indisponible depuis la VM; +- "Discuter avec Lea" ne fonctionne pas; +- installateur demande Python ou un outil externe; +- VWB appelle encore `localhost` cote navigateur; +- import/anchors/replay cassent; +- configuration Ethernet clinique activee accidentellement pendant les tests labo. + +## 6. Preuves a archiver + +Claude fournit: + +- commit DGX courant; +- preuve backup/restore `workflows.db` si reset/deploy effectue; +- confirmation que `data/workflows` et les vrais chemins `visual_workflow_builder/backend/data/anchors`, `visual_workflow_builder/backend/data/anchor_images`, `visual_workflow_builder/backend/instance/workflows.db` sont presents apres deploy/reboot; +- liste services actifs/enabled; +- ports et endpoints avec resultats; +- resultat grep bundle no-localhost; +- resultat test VM Lea chat; +- resultat import/anchors/replay; +- chemin de l'artefact installateur Lea 1.0.1; +- incidents et rollback si besoin. + +Qwen fournit: + +- controle croise commits/fichiers parasites; +- matrice GO/NO-GO post-reboot; +- verification profil Ethernet clinique inactif; +- etat VM Windows 11 ARM DGX ou decision fallback VMware clinique. + +Codex consolide: + +- verdict final pret/pas pret; +- liste des anomalies restantes; +- decision de passage au check site ou retour correction. + +## 7. Rollback minimal + +Si VWB regresse: + +- revenir au dernier commit POC valide connu; +- redeployer le bundle precedent; +- redemarrer uniquement dashboard/VWB backend; +- refaire T1, T4, T5. + +Si agent-chat regresse: + +- verifier service `rpa-agent-chat`; +- verifier env `5004`/feedback bus; +- redemarrer agent-chat puis streaming si necessaire; +- refaire T2/T3. + +Si streaming/fleet regresse: + +- verifier service `rpa-streaming`; +- verifier token/auth; +- verifier reenrolement VM; +- refaire T2/T6. + +Si reseau clinique s'active par erreur: + +- revenir au profil Wi-Fi labo; +- desactiver le profil Ethernet clinique; +- confirmer retour dashboard `192.168.1.45`; +- ne reprendre les tests qu'apres stabilisation. diff --git a/docs/coordination/RUNBOOK-LEA-LIVE-DRAFT.md b/docs/coordination/RUNBOOK-LEA-LIVE-DRAFT.md new file mode 100644 index 000000000..70b263e85 --- /dev/null +++ b/docs/coordination/RUNBOOK-LEA-LIVE-DRAFT.md @@ -0,0 +1,97 @@ +# Runbook — Test Lea live grandeur nature + +- `Date`: 2026-06-08 +- `Auteur`: Qwen (draft) +- `Statut`: DRAFT — en attente preflight vert + GO Dom +- **Interdit** : ❌ Replay autonome + +--- + +## Prérequis + +| # | Vérification | Commande / Action | Critère GO | +|---|---|---|---| +| 1 | Windows cible visible | `ping DESKTOP-58D5CAC_windows` | ✅ Répond | +| 2 | httpx OK côté Windows | `python -c "import httpx; print('OK')"` dans venv Windows | ✅ Import OK | +| 3 | Agent V1 en cours | Vérifier processus Windows agent V1 | ✅ Running | +| 4 | DGX Ollama accessible | `curl http://localhost:11434/api/tags` | ✅ Répond | +| 5 | Workflows acquis retrouvés | Dashboard → workflows chargés | ✅ ≥ 1 workflow visible | +| 6 | Dom présent physiquement | Devant le poste Windows | ✅ Confirmé | + +--- + +## Exécution + +### Étape 1 : Préflight (NON destructif) + +```bash +# Sur le poste dev Linux +curl -X POST http://localhost:5005/api/v1/traces/stream/replay/preflight \ + -H "Content-Type: application/json" \ + -d '{"machine_id": "DESKTOP-58D5CAC_windows"}' +``` + +**Attendu** : `workflow_known: true`, aucun blocage G1-G6. + +### Étape 2 : Capture Shadow supervisée + +1. Ouvrir le dashboard Lea +2. Démarrer un apprentissage supervisé +3. Scénario safe : Notepad → Enregistrer → Explorateur → Easily Assure +4. **Ne pas** déclencher de replay autonome +5. Observer et collecter les preuves en temps réel + +### Étape 3 : Collecte des preuves + +| Preuve | Où la trouver | Critère | +|---|---|---| +| `live_events.jsonl` | `data/training/live_events.jsonl` | Non vide, timestamps cohérents | +| `learn_*.json` | `agent_chat/state/learn_*.json` | Présent, state non vide | +| Shadow understanding | `agent_chat/state/` | Compréhension non vide | +| Screenshots avant/après | Captures manuelles | Chaque action documentée | +| Rapports preflight | Sortie curl étape 1 | `workflow_known: true` | + +### Étape 4 : Arrêt propre + +1. Stopper l'apprentissage via le dashboard +2. Vérifier que les fichiers de state sont cohérents +3. Ne pas lancer de replay automatique + +--- + +## Critères GO/NOGO + +### GO (tous doivent être verts) + +| ID | Critère | +|---|---| +| G1 | Préflight `workflow_known: true` | +| G2 | Windows cible joignable | +| G3 | `httpx` import OK côté Windows | +| G4 | DGX Ollama accessible (`localhost:11434`) | +| G5 | Workflows acquis retrouvés (≥ 1) | +| G6 | Dom confirme présent physiquement | + +### NOGO (un seul suffit) + +| ID | Critère | +|---|---| +| N1 | Préflight KO | +| N2 | Windows injoignable | +| N3 | `httpx` absent | +| N4 | DGX Ollama inaccessible | +| N5 | Dom absent devant Windows | +| N6 | Replay autonome demandé | + +--- + +## Rollback + +1. Arrêter proprement l'apprentissage +2. Ne pas supprimer les fichiers de state +3. Documenter le point d'arrêt +4. Rapporter à Codex/Claude + +--- + +*Ce runbook est un draft. Il sera validé après preflight vert et GO Dom.* diff --git a/docs/coordination/patches/2026-06-18_api_workflows_fix.patch b/docs/coordination/patches/2026-06-18_api_workflows_fix.patch new file mode 100644 index 000000000..93c1ea1c9 --- /dev/null +++ b/docs/coordination/patches/2026-06-18_api_workflows_fix.patch @@ -0,0 +1,208 @@ +diff --git a/tests/unit/test_dashboard_routes.py b/tests/unit/test_dashboard_routes.py +index 3f8f0528c..69cc1b2fb 100644 +--- a/tests/unit/test_dashboard_routes.py ++++ b/tests/unit/test_dashboard_routes.py +@@ -212,6 +212,58 @@ class TestDashboardRoutes: + data = resp.get_json() + assert 'workflows' in data + ++ def test_workflows_list_reads_vwb_db(self, client, monkeypatch, tmp_path): ++ """Régression red-gate : /api/workflows reflète la base VWB v3, pas 0. ++ ++ Avant correctif l'endpoint globait un store JSON vide et renvoyait ++ toujours total:0. On construit une DB VWB minimale (schéma canonique ++ workflows + steps) et on vérifie que l'endpoint expose le compte réel. ++ """ ++ import sqlite3 ++ from pathlib import Path ++ ++ db_path = tmp_path / "instance" / "workflows.db" ++ db_path.parent.mkdir(parents=True, exist_ok=True) ++ conn = sqlite3.connect(str(db_path)) ++ conn.execute( ++ "CREATE TABLE workflows (id VARCHAR(64) PRIMARY KEY, name VARCHAR(255), " ++ "description TEXT, created_at DATETIME, updated_at DATETIME, " ++ "is_active BOOLEAN, source VARCHAR(64), review_status VARCHAR(32))" ++ ) ++ conn.execute( ++ "CREATE TABLE steps (id VARCHAR(64) PRIMARY KEY, workflow_id VARCHAR(64), " ++ "action_type VARCHAR(64))" ++ ) ++ conn.execute( ++ "INSERT INTO workflows VALUES (?,?,?,?,?,?,?,?)", ++ ("wf_aiva", "Urgence_aiva_demo", "demo", "2026-06-01", "2026-06-18", ++ 1, "manual", ""), ++ ) ++ conn.execute( ++ "INSERT INTO workflows VALUES (?,?,?,?,?,?,?,?)", ++ ("wf_learned", "Learned_flow", "", "2026-06-02", "2026-06-17", ++ 1, "learned_import", "pending"), ++ ) ++ # 3 steps pour wf_aiva → nodes_count attendu = 3 ++ for i in range(3): ++ conn.execute( ++ "INSERT INTO steps VALUES (?,?,?)", (f"s{i}", "wf_aiva", "click") ++ ) ++ conn.commit() ++ conn.close() ++ ++ monkeypatch.setattr(dashboard_app, "VWB_DB_PATH", Path(db_path)) ++ ++ resp = client.get('/api/workflows') ++ assert resp.status_code == 200 ++ data = resp.get_json() ++ assert data['total'] == 2, f"attendu 2 workflows, obtenu {data['total']}" ++ names = {w['name'] for w in data['workflows']} ++ assert 'Urgence_aiva_demo' in names ++ aiva = next(w for w in data['workflows'] if w['name'] == 'Urgence_aiva_demo') ++ assert aiva['nodes_count'] == 3 ++ assert aiva['source'] == 'manual' ++ + def test_sessions_list(self, client): + """L'API sessions retourne la liste.""" + resp = client.get('/api/agent/sessions') +diff --git a/web_dashboard/app.py b/web_dashboard/app.py +index 7ee00c811..aec1edaf9 100644 +--- a/web_dashboard/app.py ++++ b/web_dashboard/app.py +@@ -189,6 +189,20 @@ SESSIONS_PATH = DATA_PATH / "sessions" + WORKFLOWS_PATH = DATA_PATH / "workflows" + LOGS_PATH = BASE_PATH / "logs" + ++# Source canonique des workflows (décision produit D3) : la base VWB v3 ++# (SQLAlchemy/SQLite) que Léa lit déjà au runtime. Chemin absolu robuste (PAS la ++# DB fantôme vide à la racine du repo `instance/workflows.db`, schéma obsolète, ++# ni l'ancien store JSON `data/training/workflows/` créé vide sur DGX). ++# Surchargeable via RPA_VWB_DB_PATH pour les déploiements atypiques. ++def _resolve_vwb_db_path() -> Path: ++ override = os.getenv("RPA_VWB_DB_PATH", "").strip() ++ if override: ++ return Path(override).expanduser() ++ return BASE_PATH / "visual_workflow_builder" / "backend" / "instance" / "workflows.db" ++ ++ ++VWB_DB_PATH = _resolve_vwb_db_path() ++ + # StorageManager + storage = StorageManager(base_path=str(DATA_PATH)) + +@@ -261,7 +275,9 @@ def system_status(): + """Statut du système.""" + try: + sessions_count = len(list(SESSIONS_PATH.glob('*'))) if SESSIONS_PATH.exists() else 0 +- workflows_count = len(list(WORKFLOWS_PATH.glob('*.json'))) if WORKFLOWS_PATH.exists() else 0 ++ # Source canonique D3 : base VWB v3 (même comptage que /api/workflows), ++ # pas l'ancien store JSON `data/training/workflows/` créé vide sur DGX. ++ workflows_count = len(_load_workflows_from_vwb_db()) + + dependencies_ok = True + try: +@@ -785,36 +801,83 @@ def rename_session_workflow(session_id): + # API Workflows + # ============================================================================= + ++def _load_workflows_from_vwb_db() -> list: ++ """Charge les workflows depuis la base VWB v3 (source canonique D3). ++ ++ Lit directement le SQLite que Léa interroge au runtime (cf. ++ `agent_chat/app.py` → `GET /api/v3/session/state`). On compte les `steps` ++ par workflow pour `nodes_count` (pas de notion d'`edges` en DAG linéaire : ++ `edges_count` = max(steps-1, 0)). Robuste à l'absence de la DB ou des ++ colonnes `source`/`review_status` (DB ancienne) : retourne [] sans planter. ++ """ ++ import sqlite3 ++ ++ if not VWB_DB_PATH.exists(): ++ return [] ++ ++ workflows = [] ++ conn = sqlite3.connect(str(VWB_DB_PATH)) ++ try: ++ conn.row_factory = sqlite3.Row ++ # Colonnes disponibles (la DB fantôme/ancienne n'a pas source/review_status) ++ cols = {row[1] for row in conn.execute("PRAGMA table_info(workflows)")} ++ has_source = 'source' in cols ++ has_review = 'review_status' in cols ++ ++ select_cols = ['id', 'name', 'description', 'created_at', 'updated_at'] ++ if has_source: ++ select_cols.append('source') ++ if has_review: ++ select_cols.append('review_status') ++ ++ # Nombre de steps par workflow (= nodes du DAG) ++ step_counts = { ++ row[0]: row[1] ++ for row in conn.execute( ++ "SELECT workflow_id, COUNT(*) FROM steps GROUP BY workflow_id" ++ ) ++ } ++ ++ rows = conn.execute( ++ f"SELECT {', '.join(select_cols)} FROM workflows ORDER BY updated_at DESC" ++ ).fetchall() ++ ++ for row in rows: ++ wf_id = row['id'] ++ nodes_count = step_counts.get(wf_id, 0) ++ workflows.append({ ++ 'workflow_id': wf_id, ++ 'name': row['name'] or wf_id, ++ 'description': row['description'] or '', ++ 'nodes_count': nodes_count, ++ 'edges_count': max(nodes_count - 1, 0), ++ 'learning_state': 'OBSERVATION', ++ 'created_at': str(row['created_at'] or ''), ++ 'updated_at': str(row['updated_at'] or ''), ++ 'execution_count': 0, ++ 'source': row['source'] if has_source else 'manual', ++ 'review_status': row['review_status'] if has_review else '', ++ 'file_path': f"vwb_db://{wf_id}", ++ }) ++ finally: ++ conn.close() ++ ++ return workflows ++ ++ + @app.route('/api/workflows') + def list_workflows(): +- """Liste tous les workflows.""" ++ """Liste tous les workflows depuis la base VWB v3 (source canonique D3). ++ ++ Avant ce correctif, l'endpoint globait `data/training/workflows/*.json` ++ (ancien store JSON, créé vide sur DGX) et renvoyait toujours `total: 0`, ++ rendant la surface « ce que Léa sait » faussement vide. On lit désormais la ++ même base SQLite que Léa au runtime. ++ """ + try: +- workflows = [] + hide_unnamed = request.args.get('hide_unnamed', 'true').lower() == 'true' + +- if not WORKFLOWS_PATH.exists(): +- WORKFLOWS_PATH.mkdir(parents=True, exist_ok=True) +- return jsonify({'workflows': [], 'total': 0, 'hidden_unnamed': 0}) +- +- for wf_file in WORKFLOWS_PATH.glob('*.json'): +- try: +- with open(wf_file, 'r') as f: +- wf_data = json.load(f) +- +- workflows.append({ +- 'workflow_id': wf_data.get('workflow_id', wf_file.stem), +- 'name': wf_data.get('name', wf_file.stem), +- 'description': wf_data.get('description', ''), +- 'nodes_count': len(wf_data.get('nodes', [])), +- 'edges_count': len(wf_data.get('edges', [])), +- 'learning_state': wf_data.get('learning_state', 'OBSERVATION'), +- 'created_at': wf_data.get('created_at', ''), +- 'updated_at': wf_data.get('updated_at', ''), +- 'execution_count': wf_data.get('execution_count', 0), +- 'file_path': str(wf_file) +- }) +- except Exception as e: +- print(f"Erreur lecture workflow {wf_file}: {e}") ++ workflows = _load_workflows_from_vwb_db() + + # Filtrer les workflows "Unnamed" si demandé + if hide_unnamed: diff --git a/docs/coordination/patches/2026-06-18_worker_idle_vs_degraded.diff b/docs/coordination/patches/2026-06-18_worker_idle_vs_degraded.diff new file mode 100644 index 000000000..9c4bed69e --- /dev/null +++ b/docs/coordination/patches/2026-06-18_worker_idle_vs_degraded.diff @@ -0,0 +1,310 @@ +diff --git a/agent_v0/server_v1/api_stream.py b/agent_v0/server_v1/api_stream.py +index 547aeb299..aa620853b 100644 +--- a/agent_v0/server_v1/api_stream.py ++++ b/agent_v0/server_v1/api_stream.py +@@ -835,15 +835,56 @@ def _get_worker_queue_status() -> Dict[str, Any]: + components_ready = bool(components) and all(bool(v) for v in components.values()) + health_status = (health or {}).get("status") + running = bool(health) and not health_stale and health_status != "stopped" ++ ++ # Distinction VEILLE (armé, lazy) vs DÉGRADÉ (vrai échec). ++ # ++ # Les composants lourds (ScreenAnalyzer/CLIP/FAISS/StateEmbedding) sont ++ # chargés en lazy par run_worker : le processor n'est instancié qu'au ++ # premier _process_session (cf. run_worker._get_processor / _process_session). ++ # Un worker neuf qui n'a jamais reçu de session écrit donc status="healthy" ++ # avec tous les composants à false — c'est l'état NORMAL « en veille », pas ++ # une panne. L'étiqueter "degraded" fait lire une panne là où il n'y en a pas. ++ # ++ # Signal retenu pour « init jamais tentée » : TOUS les composants à false ET ++ # sessions_processed == 0 ET sessions_failed == 0. Justification : run_worker ++ # n'appelle _get_processor() (donc l'init lazy) que dans _process_session, qui ++ # incrémente toujours exactement un compteur (processed / failed / skipped). ++ # Tant que processed == 0 ET failed == 0, aucune session n'a déclenché une ++ # init suivie d'un traitement — le worker est armé en attente. Un simple skip ++ # (dossier/shots absents) passe quand même par _get_processor() : les ++ # composants se chargent, donc tous-à-false devient faux et on n'entre pas ici. ++ # run_worker._health_components() écrit toujours les 4 clés (jamais un dict ++ # vide), d'où le test sur les VALEURS et non sur la présence des clés. ++ # Si run_worker a lui-même forcé status="degraded" (VLM + ScreenAnalyzer ++ # absent, cf. run_worker._write_health), c'est un VRAI échec : on le conserve. ++ stats = (health or {}).get("stats") or {} ++ init_attempted = bool(stats.get("sessions_processed", 0)) or bool( ++ stats.get("sessions_failed", 0) ++ ) ++ components_all_false = bool(components) and not any( ++ bool(v) for v in components.values() ++ ) ++ armed = ( ++ running ++ and not components_ready ++ and health_status == "healthy" ++ and components_all_false # aucun composant lourd encore chargé ++ and not init_attempted ++ ) ++ + status = health_status or "unknown" + if not running: + status = "stale" if health else "unknown" ++ elif armed: ++ # En veille : worker sain, composants chargés à la 1re session. ++ status = "idle" + elif not components_ready: + status = "degraded" + + return { + "running": running, + "status": status, ++ "armed": armed, + "queue_length": len(queue), + "queue": queue, + "replay_lock_active": REPLAY_LOCK_FILE.exists(), +@@ -858,11 +899,29 @@ def _get_worker_queue_status() -> Dict[str, Any]: + "components": components, + "components_ready": components_ready, + "processing_ready": running and not REPLAY_LOCK_FILE.exists() and components_ready, +- "stats": (health or {}).get("stats") or {}, ++ "status_hint": _worker_status_hint(status, armed), ++ "stats": stats, + "note": "Le worker VLM tourne dans un process séparé (agent_v0.server_v1.run_worker).", + } + + ++def _worker_status_hint(status: str, armed: bool) -> str: ++ """Message humain pour le statut worker (consommé par le dashboard).""" ++ if armed or status == "idle": ++ return "En veille — composants chargés à la 1re session." ++ if status == "degraded": ++ return "Worker apprentissage dégradé — init des composants en échec." ++ if status == "stale": ++ return "Health file périmé (> 180s) — worker peut-être arrêté." ++ if status == "stopped": ++ return "Worker arrêté." ++ if status == "busy": ++ return "Traitement d'une session en cours." ++ if status == "healthy": ++ return "Worker prêt — composants chargés." ++ return "État worker inconnu." ++ ++ + # ========================================================================= + # Compteur d'analyses en cours par session (pour attendre avant finalize) + # ========================================================================= +diff --git a/tests/integration/test_stream_processor.py b/tests/integration/test_stream_processor.py +index 660187901..344e614cb 100644 +--- a/tests/integration/test_stream_processor.py ++++ b/tests/integration/test_stream_processor.py +@@ -1289,3 +1289,158 @@ class TestAPIEndpoints: + assert len(workflows) == 1 + assert workflows[0]["workflow_id"] == "wf_api_001" + assert workflows[0]["nodes"] == 2 ++ ++ ++class TestWorkerStatusTruthfulness: ++ """Truthfulness du statut worker exposé par _get_worker_queue_status. ++ ++ Distingue VEILLE (armé, lazy : worker neuf qui n'a jamais traité de ++ session, composants chargés à la 1re session) de DÉGRADÉ (init tentée ++ et en échec). Un worker en veille ne doit JAMAIS être étiqueté 'degraded'. ++ """ ++ ++ # Même contrainte que TestAPIEndpoints : api_stream fail-closed à l'import ++ # si RPA_API_TOKEN absent. ++ _TEST_API_TOKEN = "test_token_for_worker_status_0123456789abcdef" ++ ++ @pytest.fixture(autouse=True) ++ def _ensure_api_token(self, monkeypatch): ++ monkeypatch.setenv("RPA_API_TOKEN", self._TEST_API_TOKEN) ++ ++ @pytest.fixture ++ def status_env(self, tmp_path, monkeypatch): ++ """Isole les fichiers worker (health/queue/lock) sur tmp_path.""" ++ from agent_v0.server_v1 import api_stream ++ ++ health_file = tmp_path / "_worker_health.json" ++ queue_file = tmp_path / "_worker_queue.txt" ++ lock_file = tmp_path / "_replay_active.lock" ++ monkeypatch.setattr(api_stream, "WORKER_HEALTH_FILE", health_file) ++ monkeypatch.setattr(api_stream, "WORKER_QUEUE_FILE", queue_file) ++ monkeypatch.setattr(api_stream, "REPLAY_LOCK_FILE", lock_file) ++ return api_stream, health_file ++ ++ @staticmethod ++ def _write_health(health_file, **overrides): ++ """Écrit un health file frais (mtime récent => non stale).""" ++ payload = { ++ "pid": 1234, ++ "started_at": "2026-06-18T10:00:00", ++ "last_cycle": "2026-06-18T10:00:30", ++ "current_session": None, ++ "queue_length": 0, ++ "components": { ++ "screen_analyzer": False, ++ "clip_embedder": False, ++ "faiss_manager": False, ++ "state_embedding_builder": False, ++ }, ++ "stats": { ++ "sessions_processed": 0, ++ "sessions_failed": 0, ++ "sessions_skipped": 0, ++ "total_screenshots_analyzed": 0, ++ }, ++ "status": "healthy", ++ } ++ payload.update(overrides) ++ health_file.write_text(json.dumps(payload), encoding="utf-8") ++ ++ def test_fresh_worker_is_idle_not_degraded(self, status_env): ++ """Worker neuf : healthy, 0 session, tous composants false ++ => statut 'idle' (en veille / armé), PAS 'degraded'.""" ++ api_stream, health_file = status_env ++ self._write_health(health_file) # défaut = état neuf ++ ++ status = api_stream._get_worker_queue_status() ++ ++ assert status["running"] is True ++ assert status["status"] == "idle", status ++ assert status["armed"] is True ++ assert status["components_ready"] is False ++ # processing_ready reste False tant que les composants ne sont pas chargés ++ assert status["processing_ready"] is False ++ assert "veille" in status["status_hint"].lower() ++ ++ def test_worker_init_failed_is_degraded(self, status_env): ++ """Init tentée et en échec : run_worker force status='degraded' ++ (VLM + ScreenAnalyzer absent) => on conserve 'degraded'.""" ++ api_stream, health_file = status_env ++ self._write_health( ++ health_file, ++ status="degraded", # forcé par run_worker._write_health ++ components={ ++ "screen_analyzer": False, ++ "clip_embedder": True, ++ "faiss_manager": True, ++ "state_embedding_builder": False, ++ }, ++ stats={ ++ "sessions_processed": 0, ++ "sessions_failed": 1, # une session a tenté l'init et échoué ++ "sessions_skipped": 0, ++ "total_screenshots_analyzed": 0, ++ }, ++ ) ++ ++ status = api_stream._get_worker_queue_status() ++ ++ assert status["running"] is True ++ assert status["status"] == "degraded", status ++ assert status["armed"] is False ++ assert status["processing_ready"] is False ++ assert "dégradé" in status["status_hint"].lower() ++ ++ def test_worker_partial_components_after_attempt_is_degraded(self, status_env): ++ """Composants partiels après tentative de traitement (sessions_failed>0), ++ sans status forcé par le worker => 'degraded' (pas 'idle').""" ++ api_stream, health_file = status_env ++ self._write_health( ++ health_file, ++ status="healthy", ++ components={ ++ "screen_analyzer": True, ++ "clip_embedder": True, ++ "faiss_manager": False, # un composant manquant ++ "state_embedding_builder": True, ++ }, ++ stats={ ++ "sessions_processed": 0, ++ "sessions_failed": 2, ++ "sessions_skipped": 0, ++ "total_screenshots_analyzed": 0, ++ }, ++ ) ++ ++ status = api_stream._get_worker_queue_status() ++ ++ assert status["status"] == "degraded", status ++ assert status["armed"] is False ++ ++ def test_worker_ready_after_processing_is_healthy(self, status_env): ++ """Worker ayant traité au moins une session, tous composants chargés ++ => 'healthy' et processing_ready=True.""" ++ api_stream, health_file = status_env ++ self._write_health( ++ health_file, ++ status="healthy", ++ components={ ++ "screen_analyzer": True, ++ "clip_embedder": True, ++ "faiss_manager": True, ++ "state_embedding_builder": True, ++ }, ++ stats={ ++ "sessions_processed": 3, ++ "sessions_failed": 0, ++ "sessions_skipped": 0, ++ "total_screenshots_analyzed": 42, ++ }, ++ ) ++ ++ status = api_stream._get_worker_queue_status() ++ ++ assert status["status"] == "healthy", status ++ assert status["armed"] is False ++ assert status["components_ready"] is True ++ assert status["processing_ready"] is True +diff --git a/web_dashboard/templates/index.html b/web_dashboard/templates/index.html +index c96cc8bf4..aeb0e7fa8 100644 +--- a/web_dashboard/templates/index.html ++++ b/web_dashboard/templates/index.html +@@ -2838,13 +2838,23 @@ + ]); + + const processingReady = processing && processing.processing_ready === true; +- const processingDegraded = processing && !processing.error && !processingReady; ++ // « En veille » (armé/lazy) ≠ « dégradé » : un worker neuf sans ++ // session a tous ses composants à false par design (chargement à la ++ // 1re session), ce n'est PAS une panne. Seul status==='degraded' ++ // (init tentée et en échec) est une vraie alerte. ++ const processingArmed = processing && (processing.armed === true || processing.status === 'idle'); ++ const processingDegraded = processing && !processing.error && processing.status === 'degraded'; ++ const statusHint = (processing && processing.status_hint) || ''; + statusEl.innerHTML = processingDegraded + ? '⚠️' +- : ''; ++ : processingArmed ++ ? '⏸️' ++ : ''; + statusEl.title = processingDegraded +- ? 'Streaming en ligne, worker apprentissage dégradé' +- : 'Serveur streaming en ligne'; ++ ? `Streaming en ligne, worker apprentissage dégradé${statusHint ? ' — ' + statusHint : ''}` ++ : processingArmed ++ ? `Streaming en ligne, worker en veille${statusHint ? ' — ' + statusHint : ''}` ++ : 'Serveur streaming en ligne'; + + document.getElementById('streamActiveSessions').textContent = data.active_sessions || 0; + document.getElementById('streamTotalEvents').textContent = data.total_events || 0; +@@ -2862,14 +2872,20 @@ + if (data.server_version) rows.push({label: 'Version serveur', value: data.server_version}); + if (processing && !processing.error) { + const status = processing.status || 'unknown'; ++ const workerIcon = processingReady ? '✅' : (processingArmed ? '⏸️' : '⚠️'); + rows.push({ + label: 'Worker apprentissage', +- value: processingReady ? `✅ ${status}` : `⚠️ ${status}` ++ value: `${workerIcon} ${status}` + }); + rows.push({ + label: 'Composants intelligence', +- value: processing.components_ready ? 'prêts' : 'non prêts' ++ value: processing.components_ready ++ ? 'prêts' ++ : (processingArmed ? 'en veille (chargés à la 1re session)' : 'non prêts') + }); ++ if (statusHint) { ++ rows.push({label: 'Détail worker', value: statusHint}); ++ } + if (processing.queue_length !== undefined) { + rows.push({label: 'Queue apprentissage', value: processing.queue_length}); + } diff --git a/docs/coordination/patches/2026-06-19_vwb_basic_auth.patch b/docs/coordination/patches/2026-06-19_vwb_basic_auth.patch new file mode 100644 index 000000000..408aefc07 --- /dev/null +++ b/docs/coordination/patches/2026-06-19_vwb_basic_auth.patch @@ -0,0 +1,318 @@ +diff --git a/visual_workflow_builder/backend/app.py b/visual_workflow_builder/backend/app.py +index 7bdae57b0..c3a285cc0 100644 +--- a/visual_workflow_builder/backend/app.py ++++ b/visual_workflow_builder/backend/app.py +@@ -28,6 +28,109 @@ load_dotenv() # fallback .env dans cwd (n'écrase pas les vars déjà définies + # Initialize Flask app + app = Flask(__name__) + ++# ============================================================ ++# HTTP Basic Auth LAN (cohérent avec le dashboard 5001) ++# ============================================================ ++# Le VWB (backend 5002) était exposé au LAN SANS authentification. On ajoute ++# un middleware before_request qui exige un header Authorization: Basic ++# pour toute requête NON-loopback (LAN), avec les MÊMES credentials que le ++# dashboard : DASHBOARD_USER / DASHBOARD_PASSWORD (dans .env.local). ++# ++# GARDE-FOU CRITIQUE — exemption loopback : ++# Le dashboard (agent_chat/app.py `_fetch_vwb_workflows`) et les healthchecks ++# appellent ce backend en boucle locale (http://localhost:5002 → 127.0.0.1). ++# Exiger l'auth en loopback CASSERAIT l'intégration dashboard↔VWB. On exempte ++# donc 127.0.0.1 / ::1 (et ::ffff:127.0.0.1) de toute auth. ++# ++# Différence assumée avec le dashboard (fail-closed) : ici on NE crashe PAS si ++# DASHBOARD_PASSWORD est absent. On log un warning et on laisse passer le LAN ++# (mode POC dev/dégradé). En clinique, DASHBOARD_PASSWORD est défini dans ++# .env.local (chargé ci-dessus, lignes 24-26) → l'auth LAN est effective. ++import base64 as _base64 ++import hmac as _hmac ++ ++_VWB_AUTH_USER = os.getenv("DASHBOARD_USER", "lea").strip() ++_VWB_AUTH_PASSWORD = os.getenv("DASHBOARD_PASSWORD", "").strip() ++# Désactivation explicite (dev/tests, parité avec le dashboard). ++_VWB_AUTH_DISABLED = os.getenv("DASHBOARD_AUTH_DISABLED", "").lower() in ( ++ "1", "true", "yes", ++) ++ ++# Adresses considérées comme loopback (server-to-server, jamais challengées). ++_VWB_LOOPBACK_ADDRS = {"127.0.0.1", "::1", "::ffff:127.0.0.1"} ++ ++# Paths publics (pas d'auth) — healthchecks systemd / NPM / smokes. ++_VWB_PUBLIC_PATHS = {"/health", "/api/health"} ++ ++if not _VWB_AUTH_PASSWORD and not _VWB_AUTH_DISABLED: ++ logging.getLogger("vwb.auth").warning( ++ "[SECURITE] DASHBOARD_PASSWORD non defini : l'auth Basic LAN du VWB " ++ "(5002) est INACTIVE (le LAN passe sans credentials). Definir " ++ "DASHBOARD_PASSWORD dans .env.local pour l'activer (cible clinique)." ++ ) ++ ++ ++def _vwb_auth_ok(header_value: str) -> bool: ++ """Valide le header Authorization Basic. Comparaison constant-time. ++ ++ Logique identique au dashboard (`web_dashboard/app.py::_dashboard_auth_ok`). ++ """ ++ if not header_value or not header_value.lower().startswith("basic "): ++ return False ++ try: ++ decoded = _base64.b64decode(header_value[6:].strip()).decode("utf-8") ++ except (ValueError, UnicodeDecodeError): ++ return False ++ if ":" not in decoded: ++ return False ++ user, _, password = decoded.partition(":") ++ user_ok = _hmac.compare_digest(user, _VWB_AUTH_USER) ++ pwd_ok = _hmac.compare_digest(password, _VWB_AUTH_PASSWORD) ++ return user_ok and pwd_ok ++ ++ ++@app.before_request ++def _vwb_basic_auth_middleware(): ++ """Middleware d'auth HTTP Basic LAN sur le backend VWB (port 5002). ++ ++ - Bypass total si DASHBOARD_AUTH_DISABLED=true (dev/tests). ++ - Bypass total si DASHBOARD_PASSWORD absent (mode POC degrade, warning emis ++ au demarrage) — on ne casse pas le service faute de secret. ++ - Loopback (127.0.0.1 / ::1) : JAMAIS challenge (proxy dashboard, healthcheck). ++ - Preflight CORS (OPTIONS) : laisse passer (le navigateur n'envoie pas ++ l'en-tete Authorization au preflight). ++ - Paths publics (_VWB_PUBLIC_PATHS) : healthchecks externes. ++ - Sinon (requete LAN) : header Authorization: Basic obligatoire, sinon 401. ++ """ ++ from flask import request, Response ++ ++ # Dev / tests / mode degrade sans secret : bypass total ++ if _VWB_AUTH_DISABLED or not _VWB_AUTH_PASSWORD: ++ return None ++ ++ # Preflight CORS : pas d'auth (le navigateur n'envoie pas les credentials) ++ if request.method == "OPTIONS": ++ return None ++ ++ # Exemption loopback (server-to-server : dashboard, healthcheck) ++ if (request.remote_addr or "") in _VWB_LOOPBACK_ADDRS: ++ return None ++ ++ # Paths publics (healthchecks externes) ++ if (request.path or "/") in _VWB_PUBLIC_PATHS: ++ return None ++ ++ if _vwb_auth_ok(request.headers.get("Authorization", "")): ++ return None ++ ++ # Pas authentifie — challenge 401 avec WWW-Authenticate ++ return Response( ++ '{"error": "authentication required"}', ++ status=401, ++ mimetype="application/json", ++ headers={"WWW-Authenticate": 'Basic realm="RPA Vision V3 VWB"'}, ++ ) ++ + # ============================================================ + # Logging — fichier rotatif + console (idempotent) + # ============================================================ +diff --git a/visual_workflow_builder/backend/instance/workflows.db b/visual_workflow_builder/backend/instance/workflows.db +index db6eabd62..b7e181cbe 100644 +Binary files a/visual_workflow_builder/backend/instance/workflows.db and b/visual_workflow_builder/backend/instance/workflows.db differ +diff --git a/visual_workflow_builder/backend/tests/test_vwb_basic_auth.py b/visual_workflow_builder/backend/tests/test_vwb_basic_auth.py +new file mode 100644 +index 000000000..f4bff4d9d +--- /dev/null ++++ b/visual_workflow_builder/backend/tests/test_vwb_basic_auth.py +@@ -0,0 +1,195 @@ ++""" ++Tests de l'auth HTTP Basic LAN du backend VWB (port 5002). ++ ++Le VWB etait expose au LAN SANS authentification. Le middleware ++`_vwb_basic_auth_middleware` ajoute un challenge 401 sur toute requete ++NON-loopback, avec les MEMES credentials que le dashboard ++(DASHBOARD_USER / DASHBOARD_PASSWORD). ++ ++Controles cles : ++- Loopback (127.0.0.1) sans credentials -> 200 (proxy dashboard / healthcheck). ++- LAN (REMOTE_ADDR non loopback) sans credentials -> 401 + WWW-Authenticate. ++- LAN avec mauvais mot de passe -> 401. ++- LAN avec bons credentials -> passage (pas de 401). ++- /health public meme en LAN. ++- DASHBOARD_AUTH_DISABLED=true -> bypass total. ++- DASHBOARD_PASSWORD absent -> auth inactive (mode POC degrade, pas de crash). ++""" ++from __future__ import annotations ++ ++import base64 ++import importlib ++import os ++import sys ++from pathlib import Path ++ ++import pytest ++ ++# Le backend VWB s'importe en tant que module top-level `app` ++# (cf. tests/conftest.py : `from app import app, db`). On ajoute le repertoire ++# backend au path pour pouvoir le recharger avec les variables d'env voulues. ++_BACKEND_DIR = Path(__file__).resolve().parent.parent ++if str(_BACKEND_DIR) not in sys.path: ++ sys.path.insert(0, str(_BACKEND_DIR)) ++ ++# Adresse LAN simulee (non loopback) ++_LAN_ADDR = "192.168.1.50" ++_LAN_ENV = {"REMOTE_ADDR": _LAN_ADDR} ++ ++ ++def _basic_auth_header(user: str, password: str) -> str: ++ token = base64.b64encode(f"{user}:{password}".encode()).decode() ++ return f"Basic {token}" ++ ++ ++def _reload_app(): ++ """Recharge le module `app` pour relire les constantes d'auth depuis l'env.""" ++ if "app" in sys.modules: ++ return importlib.reload(sys.modules["app"]) ++ return importlib.import_module("app") ++ ++ ++@pytest.fixture ++def auth_enabled_client(monkeypatch): ++ """Client VWB avec auth LAN active (DASHBOARD_USER/PASSWORD definis).""" ++ monkeypatch.setenv("DASHBOARD_USER", "lea") ++ monkeypatch.setenv("DASHBOARD_PASSWORD", "secret-test-pwd") ++ monkeypatch.delenv("DASHBOARD_AUTH_DISABLED", raising=False) ++ mod = _reload_app() ++ mod.app.config["TESTING"] = True ++ mod.app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///:memory:" ++ with mod.app.test_client() as c: ++ with mod.app.app_context(): ++ mod.db.create_all() ++ yield c ++ mod.db.drop_all() ++ ++ ++@pytest.fixture ++def auth_disabled_client(monkeypatch): ++ """Client VWB avec auth desactivee (DASHBOARD_AUTH_DISABLED=true).""" ++ monkeypatch.setenv("DASHBOARD_AUTH_DISABLED", "true") ++ monkeypatch.setenv("DASHBOARD_PASSWORD", "secret-test-pwd") ++ mod = _reload_app() ++ mod.app.config["TESTING"] = True ++ mod.app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///:memory:" ++ with mod.app.test_client() as c: ++ with mod.app.app_context(): ++ mod.db.create_all() ++ yield c ++ mod.db.drop_all() ++ ++ ++@pytest.fixture ++def no_password_client(monkeypatch): ++ """Client VWB sans DASHBOARD_PASSWORD (mode POC degrade : auth inactive).""" ++ monkeypatch.delenv("DASHBOARD_PASSWORD", raising=False) ++ monkeypatch.delenv("DASHBOARD_AUTH_DISABLED", raising=False) ++ mod = _reload_app() ++ mod.app.config["TESTING"] = True ++ mod.app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///:memory:" ++ with mod.app.test_client() as c: ++ with mod.app.app_context(): ++ mod.db.create_all() ++ yield c ++ mod.db.drop_all() ++ ++ ++class TestVwbBasicAuth: ++ """Auth HTTP Basic LAN sur le backend VWB (5002).""" ++ ++ def test_loopback_no_creds_passes(self, auth_enabled_client): ++ """Requete loopback (127.0.0.1) sans creds -> PAS de 401. ++ ++ Garde-fou critique : le dashboard proxifie en loopback. La requete ++ ne doit jamais etre challengee (200, ou autre code applicatif != 401). ++ """ ++ resp = auth_enabled_client.get("/api/v3/session/state") ++ assert resp.status_code != 401, ( ++ f"Loopback ne doit jamais etre challenge (got {resp.status_code})" ++ ) ++ ++ def test_lan_no_creds_returns_401(self, auth_enabled_client): ++ """Requete LAN (non loopback) sans creds -> 401 + WWW-Authenticate.""" ++ resp = auth_enabled_client.get( ++ "/api/v3/session/state", environ_base=_LAN_ENV ++ ) ++ assert resp.status_code == 401 ++ assert "WWW-Authenticate" in resp.headers ++ assert "Basic" in resp.headers["WWW-Authenticate"] ++ ++ def test_lan_wrong_password_returns_401(self, auth_enabled_client): ++ """Requete LAN avec mauvais mot de passe -> 401.""" ++ resp = auth_enabled_client.get( ++ "/api/v3/session/state", ++ environ_base=_LAN_ENV, ++ headers={"Authorization": _basic_auth_header("lea", "wrong")}, ++ ) ++ assert resp.status_code == 401 ++ ++ def test_lan_wrong_user_returns_401(self, auth_enabled_client): ++ """Requete LAN avec mauvais utilisateur -> 401.""" ++ resp = auth_enabled_client.get( ++ "/api/v3/session/state", ++ environ_base=_LAN_ENV, ++ headers={"Authorization": _basic_auth_header("intruder", "secret-test-pwd")}, ++ ) ++ assert resp.status_code == 401 ++ ++ def test_lan_valid_credentials_pass(self, auth_enabled_client): ++ """Requete LAN avec bons creds -> PAS de 401 (auth franchie).""" ++ resp = auth_enabled_client.get( ++ "/api/v3/session/state", ++ environ_base=_LAN_ENV, ++ headers={"Authorization": _basic_auth_header("lea", "secret-test-pwd")}, ++ ) ++ assert resp.status_code != 401, ( ++ f"Bons creds doivent franchir l'auth (got {resp.status_code})" ++ ) ++ ++ def test_lan_malformed_header_returns_401(self, auth_enabled_client): ++ """Requete LAN avec header mal forme (Bearer) -> 401.""" ++ resp = auth_enabled_client.get( ++ "/api/v3/session/state", ++ environ_base=_LAN_ENV, ++ headers={"Authorization": "Bearer tototoken"}, ++ ) ++ assert resp.status_code == 401 ++ ++ def test_lan_health_is_public(self, auth_enabled_client): ++ """/health reste public meme en LAN (healthcheck externe).""" ++ resp = auth_enabled_client.get("/health", environ_base=_LAN_ENV) ++ assert resp.status_code == 200 ++ ++ def test_lan_options_preflight_not_blocked(self, auth_enabled_client): ++ """Preflight CORS (OPTIONS) en LAN -> pas de 401 (CORS preserve).""" ++ resp = auth_enabled_client.open( ++ "/api/v3/session/state", method="OPTIONS", environ_base=_LAN_ENV ++ ) ++ assert resp.status_code != 401 ++ ++ def test_auth_disabled_bypass_lan(self, auth_disabled_client): ++ """DASHBOARD_AUTH_DISABLED=true -> LAN passe sans creds.""" ++ resp = auth_disabled_client.get( ++ "/api/v3/session/state", environ_base=_LAN_ENV ++ ) ++ assert resp.status_code != 401 ++ ++ def test_no_password_degraded_lan_passes(self, no_password_client): ++ """DASHBOARD_PASSWORD absent -> mode POC degrade : LAN passe (pas de crash).""" ++ resp = no_password_client.get( ++ "/api/v3/session/state", environ_base=_LAN_ENV ++ ) ++ assert resp.status_code != 401 ++ ++ ++@pytest.fixture(autouse=True) ++def _restore_module(monkeypatch): ++ """Restaure le module `app` en mode auth desactivee apres chaque test, ++ pour ne pas contaminer les autres tests VWB (qui importent `app`).""" ++ yield ++ monkeypatch.setenv("DASHBOARD_AUTH_DISABLED", "true") ++ monkeypatch.delenv("DASHBOARD_PASSWORD", raising=False) ++ monkeypatch.delenv("DASHBOARD_USER", raising=False) ++ if "app" in sys.modules: ++ importlib.reload(sys.modules["app"]) diff --git a/docs/coordination/registre/2026-06-08_decisions.md b/docs/coordination/registre/2026-06-08_decisions.md new file mode 100644 index 000000000..76afac79e --- /dev/null +++ b/docs/coordination/registre/2026-06-08_decisions.md @@ -0,0 +1,83 @@ +# Registre des décisions — 2026-06-08 + +- `Date`: 2026-06-08 +- `Auteur`: Qwen (compilation) +- `Statut`: actif, source de vérité jusqu'à contre-ordre Dom + +--- + +## Décisions tranchées + +### D-01 : DGX = Option A (court terme) +- **Cible** : `/home/aivanov/ai/rpa_vision_v3`, user `aivanov` +- **Tranché par** : Dom (15:15) +- **Référence** : `active/2026-06-08_1515_decisions-dom-go-operationnels.md` +- **Branch** : `poc-dgx` poussée sur Gitea (`6d34b3cb6`) +- **Option B** (`/opt/rpa_vision_v3`, user `rpa`) : reportée post-POC + +### D-02 : WP-A dashboard fail-closed — GO +- **Ce que ça fait** : Dashboard refuse de démarrer si `DASHBOARD_PASSWORD` absent ET auth non désactivée. Mot de passe par défaut supprimé. +- **Commit** : `549ea0631` +- **Tests** : 11/11 passés +- **Tranché par** : Dom (15:15) + QG Qwen (15:48) + +### D-03 : WP-B verrou enrôlement — GO +- **Ce que ça fait** : `RPA_FLEET_ENROLL_LOCKED=true` bloque enrôlement nouveau `machine_id`. Ferme contournement "poste révoqué + nouveau ID + token global". +- **Commit** : `f18de016d` +- **Tests** : 6/6 passés +- **Tranché par** : Dom (15:15) + QG Qwen (15:48) + +### D-04 : P1.g GPU merge — GO +- **Ce que ça fait** : `resolve_device(auto/cuda/cpu)` avec garde-fou VRAM (`min_free_gb=2`, `max_total_gb=6`), fallback CPU. +- **Commit** : `0e215da84` +- **Tests** : 15/15 passés, smoke OK (`auto → cuda`, `cpu → cpu`) +- **Tranché par** : Dom (15:15) + QG Qwen (15:25) +- **Rollback** : `RPA_VISION_DEVICE=cpu`, `RPA_EASYOCR_GPU=0` + +### D-05 : Grounder candidat = Qwen3-VL-4B-Instruct via vLLM +- **Bench** : 87.5% accuracy, 1.1s latence, 1 dangereux/16, Apache-2.0 +- **Tranché par** : Claude (15:45) + QG Qwen (16:10) +- **Note** : Pas d'activation runtime sans GO Dom + bench sur écrans réels + +### D-06 : Grounding gemma4:26b supervisé +- **Bench** : 69% accuracy, **0 dangereux**, 14.4s OCR +- **Tranché par** : Claude (15:45) + QG Qwen (16:10) +- **Usage** : Grounding supervisé (pas autonome) + +### D-07 : UI-TARS gate vision +- **Ce que ça fait** : `has_vision_capability()` via `/api/show` — skip propre si modèle aveugle + `logger.warning` +- **Commit** : `d00fe7b00` +- **Tranché par** : Claude (12:25) + QG Qwen (12:28) +- **Contexte** : UI-TARS cassé sur DGX (aveugle, mmproj non chargé) → 500 silencieux corrigé + +### D-08 : Trained artifacts V2 — GO avec réserves +- **Paquet** : ~306 Mo, 6107 fichiers (anchors ajoutés, screen_states retirés) +- **Anti-secret** : CLEAN (0 token, 0 identité patient, 0 machine_id réel) +- **Rewrite** : 2 colonnes (`image_path` + `thumbnail_path`) `/home/dom/` → `/home/aivanov/` +- **Revue visuelle** : Dom confirme captures 100% fictives (mockup `urgence.labs`) +- **Tranché par** : Claude (16:08) + Codex (16:06) + QG Qwen (16:10, 16:50) +- **Transfert** : Bloqué en attente GO Dom effectif + +### D-09 : Lea live — GO quand preflight vert +- **Condition** : Dom devant Windows + preflight G1-G6 verts +- **Scope** : Capture Shadow supervisée, Notepad/Explorateur/Easily lecture seule +- **Interdit** : ❌ Replay autonome +- **Tranché par** : Dom (15:15) + +### D-10 : Code mort — NO-GO suppression +- **Ce qui est interdit** : Suppression, déplacement, archive, marquage `DEPRECATED` massif +- **Autorisé** : Inventaire read-only, documentation +- **Tranché par** : Dom (16:37) + Codex (16:37) + Claude (16:35) +- **Référence** : `active/2026-06-08_1637_decision-piste2-no-go-code-mort.md` + +--- + +## Décisions en attente + +| Sujet | En attente de | Impact | +|---|---|---| +| Unification `.service` files | Conception Codex | Migration DGX propre | +| Transfert trained artifacts DGX | GO Dom effectif + tar créé | Clone DGX complet | +| Node.js sur DGX | Dom/Claude | VWB frontend 3002 | +| Benchmark GPU P1.g | Claude (en cours) | Activation large P1.g | +| Scan anti-code-mort | Attente (read-only) | Détection automatique "UI-TARS bis" | diff --git a/docs/coordination/registre/2026-06-09_decisions.md b/docs/coordination/registre/2026-06-09_decisions.md new file mode 100644 index 000000000..6ff69717a --- /dev/null +++ b/docs/coordination/registre/2026-06-09_decisions.md @@ -0,0 +1,145 @@ +# Registre des decisions — 2026-06-09 + +- `Date`: 2026-06-09 +- `Auteur`: Codex +- `Statut`: actif + +--- + +## Decisions tranchees + +### D-2026-06-09-01 : Codex orchestre activement Claude et Qwen + +- **Tranche par** : Dom, 2026-06-09 +- **Decision** : Codex est l'orchestrateur actif du projet. Tant que Claude et Qwen ont des loops de coordination actifs, Codex doit leur fournir une prochaine tache actionnable. +- **Exception** : Codex peut ne pas distribuer une execution seulement s'il attend explicitement un feu vert de Dom ou un QG bloquant. +- **Implication** : en cas de blocage sur execution, Codex distribue quand meme des taches non destructives : preparation, audit read-only, QG, rollback, plan de tests, registre decisions. +- **Persistance** : regle ajoutee a `docs/coordination/ROLES.md`. + +### D-2026-06-09-02 : Dashboard DGX — GO partiel, produit incomplet + +- **Tranche par** : QG Qwen + Codex +- **Refs** : + - `docs/coordination/inbox_codex/2026-06-09_1120_claude-to-codex_RESULTAT-DASHBOARD-DGX-E2E.md` + - `docs/coordination/inbox_codex/2026-06-09_1130_qwen-to-codex-claude-dom_QG-DASHBOARD-DGX-E2E-GO-PARTIEL.md` +- **Decision** : securite dashboard DGX validee ; dashboard produit non valide tant que P0 workflows non servis et ZIP agent absent ne sont pas corriges. +- **Suite autorisee** : preparation Lea live read-only ; preparation corrections P0 sans execution. +- **Bloquant multi-TIM** : WP-C token par poste. + +### D-2026-06-09-03 : Branching stable = `poc-prod` + +- **Tranche par** : Dom, relaye par Claude a 14:12 CEST. +- **Ref** : `docs/coordination/inbox_codex/2026-06-09_1412_claude-to-codex-qwen_DECISIONS-DOM-poc-prod-m4-clone-p0-go.md` +- **Decision** : branche stable POC/NVP/prod = `poc-prod`. +- **Schema retenu** : `main` racine, `dev` integration quotidienne, `poc-prod` stable protegee promue depuis `dev` apres QG. +- **Conservation** : `poc-dgx` conserve comme snapshot DGX. +- **Reste ouvert** : chantier blobs ~15 G, sort de `master`, droits de push/protection sur `poc-prod`. + +### D-2026-06-09-04 : Docker — rester clone + venv + systemd pour le POC + +- **Tranche par** : Dom, relaye par Claude a 14:12 CEST. +- **Refs** : + - `docs/coordination/inbox_codex/2026-06-09_1410_claude-to-codex_AUDIT-DOCKER-VS-CLONE-SYSTEMD.md` + - `docs/coordination/inbox_codex/2026-06-09_1412_claude-to-codex-qwen_DECISIONS-DOM-poc-prod-m4-clone-p0-go.md` +- **Decision court terme** : ne pas dockeriser pour le POC ; conserver clone + venv + systemd. +- **Decision moyen terme** : Docker hybride progressif post-POC, avec Ollama et agent RPA hors conteneur. + +### D-2026-06-09-05 : GO execution P0-1/P0-2 dashboard DGX + +- **Tranche par** : Dom, relaye par Claude a 14:12 CEST. +- **Ref** : `docs/coordination/inbox_codex/2026-06-09_1412_claude-to-codex-qwen_DECISIONS-DOM-poc-prod-m4-clone-p0-go.md` +- **P0-1** : appliquer `DATABASE_URL` absolu pour VWB, restart service, verifier `/api/workflows/` = 23. +- **P0-2** : build `deploy/Lea_v1.0.0.zip` sur DGX pour usage POC interne. +- **Garde-fou** : paquet agent non distribuable multi-TIM tant que WP-C token par poste n'est pas implemente et QG. + +### D-2026-06-09-06 : M2 cible dev Linux, Ollama via tunnel DGX + +- **Tranche par** : QG Qwen corrige a 15:12 CEST. +- **Refs** : + - `docs/coordination/inbox_codex/2026-06-09_1420_claude-to-codex_RESULTAT-M2-VERIF-SERVEUR-DEV-LINUX-READONLY.md` + - `docs/coordination/inbox_codex/2026-06-09_1425_claude-to-codex_RESULTAT-M2-OLLAMA-ENV-READONLY.md` + - `docs/coordination/inbox_codex/2026-06-09_1512_qwen-to-codex-claude-dom_QG-M2-CORRECTION-OLLAMA-TUNNEL-GO.md` +- **Decision** : M2 cible le serveur dev Linux `192.168.1.40`, pas le DGX directement. +- **Ollama** : acces via tunnel SSH local `127.0.0.1:11434` vers DGX ; Qwen indique tunnel relance et modeles disponibles. +- **Garde-fou** : execution live Shadow uniquement avec Dom devant Windows + GO Codex. Tunnel non persistant = P1 a stabiliser avant demo multi-machine. + +### D-2026-06-09-07 : WP-C peut commencer apres P0 dashboard + +- **Tranche par** : QG Qwen a 15:05 et 15:06 CEST. +- **Refs** : + - `docs/coordination/inbox_codex/2026-06-09_1505_qwen-to-codex-claude-dom_QG-PREP-P0-DASHBOARD-WPC-GO.md` + - `docs/coordination/inbox_codex/2026-06-09_1506_qwen-to-codex-claude-dom_QG-POST-P0-BRANCHES-DOCKER.md` +- **Decision QG** : WP-C token par poste peut commencer en parallele M2 apres P0 dashboard. +- **Garde-fou** : WP-C reste bloquant multi-TIM ; tests P0 requis : build ZIP sans token global + enroll poste unique. + +### D-2026-06-09-08 : P0 dashboard corrige par symlink workflow store + +- **Tranche par** : Dom, relaye et execute par Claude a 20:25 CEST. +- **Ref** : `docs/coordination/inbox_codex/2026-06-09_2025_claude-to-codex-qwen_RESULTAT-P0-DASHBOARD-CORRECTIONS.md` +- **Rectificatif** : le diagnostic `DATABASE_URL` etait incomplet/errone ; la route VWB `/api/workflows/` lit les JSON via `WorkflowDatabase("data/workflows")`, pas SQLAlchemy. +- **Cause racine** : divergence de `WorkingDirectory` dev vs DGX, donc chemin relatif `data/workflows` resolu au mauvais endroit sur DGX. +- **Correction POC** : symlink `data/workflows` -> `visual_workflow_builder/backend/data/workflows`. +- **Verification** : VWB 5002 `/api/workflows/` = 39 workflows. +- **Rollback** : `rm data/workflows && mkdir data/workflows`. +- **Note** : `DATABASE_URL` laisse en place, neutre pour cette route, utile aux composants SQLAlchemy. + +### D-2026-06-09-09 : DETTE-015 double store workflows + +- **Ref** : `docs/coordination/inbox_codex/2026-06-09_2035_claude-to-codex-qwen_INFO-DETTE-015-store-workflows.md` +- **Decision** : enregistrer la dette double store workflows en P2 post-POC. +- **Docs** : + - `docs/DETTE_TECHNIQUE.md` + - `docs/PLAN_MIGRATION_WORKFLOWS_STORE_2026-06-09.md` +- **Cible post-POC** : unifier sur la DB SQLAlchemy existante sous flag, TDD, sans refactor avant la demo. + +### D-2026-06-09-10 : Qwen indisponible, Codex orchestre en mode degrade + +- **Tranche par** : Dom, 2026-06-09. +- **Decision** : Qwen est considere temporairement indisponible. Codex ne lui route plus de nouvelles taches tant que la panne n'est pas levee. +- **Implication** : Codex assure l'interim de coordination et demande a Claude des self-checks/read-only/handoffs, sans executer les actions qui exigent un QG formel si elles sont structurelles ou risquées. +- **Garde-fou** : pas de code WP-C, pas de live M2, pas de multi-TIM sans GO explicite Dom/Codex et, si possible, QG a posteriori quand Qwen revient. + +### D-2026-06-09-11 : Qwen revenu, reprise QG normale + +- **Tranche par** : Dom/Codex, 2026-06-09 20:47 CEST. +- **Refs** : + - `docs/coordination/inbox_codex/2026-06-09_2025_qwen-to-codex-claude-dom_QG-TUNNEL-PERSISTANT-M2-READINESS-GO.md` + - `docs/coordination/inbox_codex/2026-06-09_2026_qwen-to-codex-claude-dom_QG-WPC-CARTO-GO.md` + - `docs/coordination/inbox_codex/2026-06-09_2027_qwen-to-codex-claude-dom_ACK-MESSAGES-FIN-JOURNEE.md` +- **Decision** : Qwen est revenu ; Codex reprend le mode normal avec QG Qwen. +- **Action** : Qwen doit rendre le QG post-P0 dashboard sur le resultat Claude 20:25 ; Claude peut avancer WP-C Patch 1 local/TDD uniquement. + +### D-2026-06-09-12 : Dashboard post-P0 — GO final + +- **Tranche par** : QG Qwen a 20:50 CEST. +- **Refs** : + - `docs/coordination/inbox_codex/2026-06-09_2050_qwen-to-codex-claude-dom_QG-POST-P0-DASHBOARD-GO.md` + - `docs/coordination/inbox_codex/2026-06-09_2050_qwen-to-codex-claude-dom_ACK-POST-P0.md` +- **Decision** : P0-1 et P0-2 resolus ; dashboard produit debloque pour M2. +- **Verification Qwen** : VWB 5002 `/api/workflows/` = 39, symlink OK, tunnel Ollama actif. +- **Garde-fou** : ZIP agent POC interne uniquement ; multi-TIM reste NOGO avant WP-C. +- **Point P1** : verifier et retirer `DATABASE_URL` si present dans env systemd/service pour eviter dette inutile avant GO M2. + +### D-2026-06-09-13 : WP-C Patch 1 GO, Patch 2 autorise local/TDD + +- **Tranche par** : QG Qwen a 21:10 CEST + Codex. +- **Refs** : + - `docs/coordination/inbox_codex/2026-06-09_2058_claude-to-codex_RESULTAT-WPC-PATCH1-MIGRATION-TDD.md` + - `docs/coordination/inbox_codex/2026-06-09_2104_claude-to-codex_INFO-WPC-PATCH1-COMMITE.md` + - `docs/coordination/inbox_codex/2026-06-09_2110_qwen-to-codex-claude-dom_QG-WPC-PATCH1-GO.md` +- **Patch 1** : GO, commit local `f7f692641`, non pousse, non deploye DGX. +- **DATABASE_URL** : garder ; P1 post-POC, pas de cleanup avant M2. +- **Patch 2 autorise** : generation token a l'enroll, local/TDD uniquement, pas de branchement auth, pas de ZIP, pas de deploiement DGX. + +--- + +## Decisions en attente + +| Sujet | En attente de | Impact | +|---|---|---| +| WP-C Patch 2 local/TDD | Resultat Claude + QG Qwen | Generation token enroll seulement | +| Branching details : blobs/master/protection | QG Qwen + GO Dom | Nettoyage et protection stable | +| Execution M2 Lea live | Dom devant Windows + GO Codex | Capture Shadow supervisee | +| Push/deploiement WP-C Patch 1+ | GO Dom/Codex separe | Integration distante | + +— Codex diff --git a/docs/coordination/registre/2026-06-10_decisions.md b/docs/coordination/registre/2026-06-10_decisions.md new file mode 100644 index 000000000..4e51636ab --- /dev/null +++ b/docs/coordination/registre/2026-06-10_decisions.md @@ -0,0 +1,62 @@ +# Registre des decisions — 2026-06-10 + +- `Date`: 2026-06-10 +- `Auteur`: Codex +- `Statut`: actif + +--- + +## Decisions tranchees + +### D-2026-06-10-01 : Plan de continuite si contexte Codex bas/reset + +- **Tranche par** : Dom, relaye par Codex a 09:18 CEST. +- **Contexte** : Dom signale qu'il reste tres peu de contexte Codex et qu'un reset est prevu vers 14:12. +- **Decision** : Codex doit poser un plan de relais pour permettre a Claude et Qwen d'avancer sans dependance forte a son contexte. +- **Claude** : peut avancer WP-C Patch 2 local/TDD seulement et produire preuves/resultat. +- **Qwen** : garde le fil, rend QG, signale contradictions, tient active/registre si necessaire. +- **Dom** : reste arbitre pour live, push, deploiement, branches et extension de scope. +- **Refs** : + - `docs/handoffs/2026-06-10_handoff_codex_context-low-reset-1412.md` + - `docs/coordination/active/2026-06-10_0918_continuite-codex-context-reset.md` + - `docs/coordination/inbox_claude/2026-06-10_0918_codex-to-claude_RELANCE-WPC-PATCH2-CONTINUITE.md` + - `docs/coordination/inbox_qwen/2026-06-10_0918_codex-to-qwen_QG-CONTINUITE-CONTEXT-RESET.md` + +### D-2026-06-10-02 : WP-C Patch 1-3 GO, arret avant Patch 4 runtime + +- **Tranche par** : QG Qwen + Codex, 2026-06-10. +- **Refs** : + - `docs/coordination/inbox_codex/2026-06-10_1425_qwen-to-codex-claude-dom_QG-WPC-PATCH2-GO.md` + - `docs/coordination/inbox_codex/2026-06-10_1435_qwen-to-codex-claude-dom_QG-WPC-PATCH3-GO.md` + - `docs/coordination/active/2026-06-10_1437_wpc-patch1-3-go-stop-patch4.md` +- **Decision** : WP-C Patch 1, Patch 2 et Patch 3 sont GO, locaux, non pousses, non deployes. +- **Etat commits** : + - Patch 1 : `f7f692641`. + - Patch 2 : `9fb2c7bfe`. + - Patch 3 : `b20d17882`. +- **Garde-fou** : Patch 4 est le premier impact runtime (`api_stream.py::_verify_token` derriere flag `RPA_FLEET_PER_AGENT_TOKEN`) et reste interdit sans decision Dom explicite. +- **Autorise** : preparation read-only, plan TDD, plan rollback, audit risques. +- **Interdit** : Patch 4 code, push, deploiement, ZIP/package, M2 live, multi-TIM, secret en clair. + +### D-2026-06-10-03 : WP-C arrete pour POC, dette auth globale acceptee + +- **Tranche par** : Dom, relaye/QG par Qwen a 14:50 CEST. +- **Refs** : + - `docs/coordination/inbox_codex/2026-06-10_1450_qwen-to-codex-claude-dom_DECISION-WPC-ABANDON-DETTE.md` + - `docs/coordination/active/2026-06-10_1440_anti-doublon-wpc-verdict.md` + - `docs/DETTE_TECHNIQUE.md` (`DETTE-016`) +- **Decision** : le token global + `machine_id` auto-declare est suffisant pour le POC controle. +- **Consequence** : WP-C token par poste est arrete pour le POC ; Patch 4 runtime est annule. +- **Etat commits** : Patch 1-3 restent locaux, inertes, non pousses, non deployes. +- **Dette** : `DETTE-016` creee en P2/ACCEPTED pour le gap "auth token global sans verification cryptographique par poste". +- **Garde-fou** : reouvrir WP-C ou une solution equivalente uniquement avant distribution multi-TIM elargie, exposition reseau non maitrisee ou besoin de revocation par poste non contournable. + +## Decisions en attente + +| Sujet | En attente de | Impact | +|---|---|---| +| M2 Lea live | Dom devant Windows + GO Codex | Execution supervisee | +| Branches/blobs/protection | QG + GO Dom | Hygiene repo post-POC | +| Reouverture WP-C post-POC | Decision Dom explicite | Seulement si multi-TIM elargi / exposition reseau / revocation forte | + +— Codex diff --git a/docs/coordination/syntheses/2026-06-20_revue-globale-post-panne-dgx.md b/docs/coordination/syntheses/2026-06-20_revue-globale-post-panne-dgx.md new file mode 100644 index 000000000..db7bd8f3a --- /dev/null +++ b/docs/coordination/syntheses/2026-06-20_revue-globale-post-panne-dgx.md @@ -0,0 +1,105 @@ +# Revue globale post-panne DGX — 2026-06-20 + +- `Pilote`: Codex +- `Contributeurs`: Claude, Qwen +- `Contexte`: coupure électrique à 02:07 CEST +- `Verdict`: **GO labo supervisé / NO-GO fonctionnement autonome et clinique** + +## Résumé + +Le DGX, les services, les données et la VM Windows ont survécu ou ont été récupérés. L'accès Dom à la VM est rétabli. La panne révèle cependant que l'accès graphique et le démarrage complet de Léa ne sont pas encore autonomes: le tunnel SSH et le mot de passe VNC sont volatils, et aucun agent Windows n'a encore prouvé une reconnexion après la panne. + +## Checklist consolidée + +| Domaine | Contrôle | État | Preuve / observation | +|---|---|---:|---| +| Coordination | Watcher Codex/Claude/Qwen | OK | service utilisateur persistant `enabled+active` | +| Réseau | DGX joignable sur `192.168.1.45` | OK | IP WiFi statique, SSH et services accessibles | +| Réseau | Exclusion/réservation `.45` dans le DHCP box | À FAIRE | risque de conflit futur | +| Réseau | Ethernet clinique inactif | OK | interface sans carrier | +| Réseau | Ethernet clinique `autoconnect=off` | KO | profil encore `autoconnect=yes` | +| Sécurité | 5900/5902/3389/22220/8000 fermés depuis le LAN | OK | nmap: filtered/closed; loopback conservé | +| Services | Services système RPA | OK | 10 units `enabled`; services critiques actifs, zéro erreur/restart | +| Dashboard | API et UI | OK | auth 401 sans creds; UI réelle rendue; Socket.IO/API 200 | +| Dashboard | Fallback user concurrent | KO | service user disabled mais actif en crash-loop, 167+ restarts, conflit 5001 | +| Dashboard | Monitoring 5003 / Session Cleaner 5006 | DÉGRADÉ | affichés arrêtés; non bloquants POC mais classification à clarifier | +| Dashboard | Liens navigateur vers `localhost` | DÉGRADÉ | liens 8000/5002/3002/5004/5005 incorrects depuis un poste distant | +| Dashboard | Backups/restore points | DÉGRADÉ | dashboard annonce 0 backup et aucun point de restauration | +| VWB | Backend/frontend | OK | health 200; UI réelle rendue; catalogue/session APIs 200 | +| VWB | Cohérence catalogue | DÉGRADÉ | DB=24 workflows; sélecteur UI=22; review=8 | +| VWB | Workflows appris | DÉGRADÉ | plusieurs entrées importables à 0 nœud/0 transition | +| Données | `workflows.db` | OK | SQLite `integrity_check=ok`, 24 workflows | +| Données | Workflows fichiers | OK | symlink runtime valide, 42 fichiers | +| Données | Anchors | OK | 468 fichiers anchors + 47 anchor images | +| FAISS | Index et métadonnées | OK | 13 666 vecteurs, test HTTP 200 `success=true`, index sain | +| Worker | Daemon et queue | OK | heartbeat post-panne; queue total/pending/failed=0 | +| Agent chat | Service | OK | status online, 79 workflows | +| Streaming | Service | OK | `/health=200`, version 1.0.0 | +| Upload API | Service 8000 | OK | `rpa-vision-v3-api` actif+enabled, 401 loopback attendu | +| Ollama/grounder | Services | OK/DÉGRADÉ | process/services présents; fonction modèle non exercée dans ce smoke | +| VM | Autostart QEMU | OK après recovery | user service active+enabled, linger, 0 restart | +| VM | Boot Windows | OK | framebuffer Windows lock screen, 8,6 Go RSS | +| VM | VNC serveur loopback | OK | RFB 3.8 sur `127.0.0.1:5902` | +| VM | Tunnel poste Dom | RÉTABLI MAIS VOLATIL | tunnel local actif; Remmina connecté; perdu au reboot poste | +| VM | Mot de passe VNC | RÉTABLI MAIS VOLATIL | auth de bout en bout validée; absent du script d'autostart | +| VM | SSH guest 22220 | KO | hostfwd présent mais aucune bannière guest | +| VM | Guest agent QEMU | KO/NON PROUVÉ | socket présent, aucune réponse `guest-ping` | +| Léa Windows | Reconnexion agent post-panne | KO/NON PROUVÉ | aucun `last_seen` fleet postérieur à la panne | +| Léa Windows | `config.txt` live vers `.45` | NON TESTÉ | le template build ne prouve pas la configuration installée | +| Git DGX | Branche | OK | `poc-dgx` | +| Git DGX | Reproductibilité déploiement | DÉGRADÉ | HEAD `ec1fb81`, patch auth `app.py` + DB modifiés, cible `cf81ce4c7` non intégrée | +| Installateur | Artefact autonome 1.0.1 | DÉGRADÉ | seul `deploy/Lea_v1.0.0.zip` visible dans ce checkout | +| Frontend | VWB servi par Vite dev | POC SEULEMENT | port 3002 LAN; à remplacer par build/proxy pour clinique | + +## Cause de l'absence d'accès VM + +1. Le poste Dom a redémarré: le tunnel SSH local a disparu. +2. Le VNC QEMU est volontairement loopback-only et filtré depuis le LAN. +3. QEMU redémarre avec `password=on`, mais aucun script ne réinjecte le mot de passe. + +Correctif runtime appliqué: tunnel `localhost:5902` recréé, mot de passe VNC reposé sans exposition, authentification validée, Remmina connecté. + +## Tests fonctionnels nécessitant Dom dans Windows + +- [ ] Se connecter à la session Windows. +- [ ] Vérifier que Léa démarre automatiquement; sinon la lancer. +- [ ] Vérifier le `config.txt` live vers le DGX `.45`. +- [ ] Confirmer un nouveau `last_seen` de la machine dans Fleet. +- [ ] Ouvrir « Discuter avec Léa » et envoyer `Bonjour`. +- [ ] Vérifier les bulles/progressions d'une action simple. +- [ ] Importer un workflow JSON dans VWB. +- [ ] Ouvrir une étape avec anchor et confirmer le crop. +- [ ] Exécuter un replay supervisé sans action destructive. + +## Actions prioritaires + +### P0 — autonomie après reboot + +1. Stopper proprement le fallback `rpa-vision-v3-dashboard-user` en crash-loop; garder le service système. +2. Rendre le tunnel VM persistant sur le poste Dom (`systemd --user` ou `autossh`). +3. Rendre l'auth VNC persistante via un secret local protégé, ou retirer le password QEMU et s'appuyer sur SSH+loopback. +4. Après login Windows, prouver le démarrage/re-enrôlement Léa et corriger le `config.txt` live si nécessaire. + +### P1 — installation fiable + +5. Exclure/réserver `.45` dans le DHCP de la box. +6. Passer le profil Ethernet clinique à `autoconnect=no` tant que non autorisé. +7. Réconcilier Git DGX avec backup de `workflows.db`; ne pas utiliser `reset --hard` sans procédure validée. +8. Produire/identifier l'installateur Léa 1.0.1 attendu. +9. Corriger les liens dashboard `localhost` et distinguer clairement services critiques/optionnels. +10. Mettre en place un vrai backup/restore point post-déploiement. + +### P2 — dette produit + +11. Diagnostiquer guest SSH/QEMU guest agent. +12. Réconcilier les 24 workflows DB avec les 22 affichés et traiter les 8 reviews/entrées à 0 nœud. +13. Remplacer Vite dev par un build statique/proxy avant clinique. + +## Preuves visuelles + +- `output/playwright/dgx-post-panne-dashboard-20260620-0241.png` +- `output/playwright/dgx-post-panne-vwb-20260620-0241.png` + +## Divergences de revue résolues + +La première matrice Qwen confondait `5006` avec l'upload API, inspectait les unités user au lieu des unités système et utilisait des chemins de données non runtime. Les contrôles Codex/Claude ont confirmé: upload API sur 8000, services système persistants, 42 fichiers workflows et 468+47 fichiers anchors. Les constats Qwen conservés sont le guest SSH KO, le fallback dashboard en crash-loop et le worktree DGX non reproductible. diff --git a/docs/coordination/systemd/rpa-coordination-watcher.service b/docs/coordination/systemd/rpa-coordination-watcher.service new file mode 100644 index 000000000..8986d44e8 --- /dev/null +++ b/docs/coordination/systemd/rpa-coordination-watcher.service @@ -0,0 +1,14 @@ +[Unit] +Description=RPA Vision coordination inbox watcher +After=default.target + +[Service] +Type=simple +WorkingDirectory=/home/dom/ai/rpa_vision_v3 +Environment=COORD_LOOP_INTERVAL=15 +ExecStart=/home/dom/ai/rpa_vision_v3/docs/coordination/coordination_loop.sh watch 15 +Restart=always +RestartSec=5 + +[Install] +WantedBy=default.target diff --git a/docs/coordination/systemd/rpa-vision-v3-dashboard-user.service b/docs/coordination/systemd/rpa-vision-v3-dashboard-user.service new file mode 100644 index 000000000..51b1e1913 --- /dev/null +++ b/docs/coordination/systemd/rpa-vision-v3-dashboard-user.service @@ -0,0 +1,21 @@ +[Unit] +Description=RPA Vision V3 - Web Dashboard (Flask) user fallback +After=default.target network-online.target +Wants=network-online.target + +[Service] +Type=simple +WorkingDirectory=/home/aivanov/ai/rpa_vision_v3 +EnvironmentFile=/home/aivanov/ai/rpa_vision_v3/.env.local +Environment=PYTHONUNBUFFERED=1 +Environment=ENVIRONMENT=production +Environment=RPA_SERVICE_NAME=rpa-vision-v3-dashboard-user +Environment=RPA_GROUNDING_SOCKET=/run/rpa/grounding.sock +Environment=RPA_GROUNDING_IMG_DIR=/run/rpa +ExecStart=/home/aivanov/ai/rpa_vision_v3/venv_v3/bin/python3 web_dashboard/app.py +Restart=on-failure +RestartSec=3 +TimeoutStopSec=30 + +[Install] +WantedBy=default.target diff --git a/docs/coordination/templates/TEMPLATE-QG.md b/docs/coordination/templates/TEMPLATE-QG.md new file mode 100644 index 000000000..f88c96b7e --- /dev/null +++ b/docs/coordination/templates/TEMPLATE-QG.md @@ -0,0 +1,69 @@ +# Template — Quality Gate (QG) + +- `De`: [Agent QG] +- `A`: [Destinataire] +- `Copie`: [CC] +- `Date`: [YYYY-MM-DD HH:MM Europe/Paris] +- `Statut`: QG +- `Répond à`: [Fichier de référence] + +--- + +## Contexte + +[1-2 lignes décrivant ce qui est évalué] + +--- + +## Critères d'acceptation + +| # | Critère | Requis | Résultat | Verdict | +|---|---|---|---|---| +| 1 | [Description du critère] | ✅ | [Résultat observé] | GO/NO-GO | +| 2 | ... | | | | +| 3 | ... | | | | + +--- + +## Vérifications directes + +| Test | Commande / Méthode | Résultat attendu | Résultat observé | Verdict | +|---|---|---|---|---| +| Tests unitaires | `pytest ...` | X/Y passés | [Résultat] | ✅/❌ | +| Smoke test | [Commande] | [Attendu] | [Observé] | ✅/❌ | +| Code review | [Fichiers] | [Critère] | [Observé] | ✅/❌ | + +--- + +## Réserves + +| # | Réserve | Impact | Action requise | +|---|---|---|---| +| R1 | [Description] | [Faible/Moyen/Haut] | [Action] | + +--- + +## Verdict global + +**[GO / GO avec réserves / NO-GO]** + +[1-2 lignes de justification] + +--- + +## Prochaines étapes + +| Étape | Owner | Deadline | +|---|---|---| +| [Action] | [Qui] | [Quand] | + +--- + +## Stop conditions + +- [Condition qui bloquerait le GO] +- [Autre condition] + +--- + +*Template réutilisable — copier et adapter pour chaque QG.* diff --git a/docs/handoffs/2026-06-08_handoff_codex_fin_journee_reprise_2026-06-09.md b/docs/handoffs/2026-06-08_handoff_codex_fin_journee_reprise_2026-06-09.md new file mode 100644 index 000000000..e355910aa --- /dev/null +++ b/docs/handoffs/2026-06-08_handoff_codex_fin_journee_reprise_2026-06-09.md @@ -0,0 +1,228 @@ +# Handoff Codex — fin journee 2026-06-08 / reprise 2026-06-09 + +- `Date`: 2026-06-08 18:17 CEST +- `Auteur`: Codex +- `Objet`: reprise demain apres cycle DGX, securite, dashboard, branches, tests reels +- `Statut`: source de reprise operationnelle + +## Resume executif + +La journee a bascule le projet d'un etat fragile/local vers un socle DGX utilisable en local-only. + +Acquis valides : + +- DGX Option A deploye : `/home/aivanov/ai/rpa_vision_v3`, branche `poc-dgx`. +- Venv ARM DGX OK, torch CUDA OK sur GB10 `sm_121`. +- Donnees entrainees utiles transferees : 23 workflows, 199 ancres, FAISS 512-dim `ntotal=13666`. +- Services DGX 6/6 UP, `disabled`, bind `127.0.0.1`, pas d'exposition externe. +- Secrets generes sur DGX, non publies, `.env.local` chmod 600. +- WP-A/WP-B securite valides : dashboard fail-closed + verrou reenrolement. +- P1.g GPU valide local RTX 5070 et DGX GB10, gain environ 90 %, overlap 100 %, 0 OOM. +- Correctif GB10 memory-unified livre : `auto -> cuda` OK. +- Correctif bind securite livre : default `127.0.0.1` via `RPA_BIND_HOST`. + +Non acquis / a reprendre : + +- Dashboard fonctionnel bout-en-bout non encore teste comme workflow produit. +- Test Lea grandeur nature non execute. +- Replay controle multi-machine non prouve. +- Token par poste WP-C non implemente. +- Branching Gitea propre dev vs POC/NVP/prod non encore tranche. +- Dockerisation non tranchee : clone+venv+systemd fonctionne, Docker a etudier. + +## Sources de verite a lire demain + +### Etat DGX final + +- `docs/coordination/inbox_codex/2026-06-08_1745_claude-to-codex-qwen_RECAP-FINAL-dgx-operationnel.md` +- `docs/coordination/inbox_codex/2026-06-08_1752_qwen-to-codex-claude_QG-RECAP-FINAL-DGX.md` +- `docs/coordination/inbox_codex/2026-06-08_RESULTAT-DGX-CLONE-VENV-ARM.md` +- `docs/coordination/inbox_codex/2026-06-08_RESULTAT-ARTIFACTS-V2-TRANSFERT-DGX.md` +- `docs/coordination/inbox_codex/2026-06-08_RESULTAT-DGX-COMPAT-RUNTIME.md` +- `docs/coordination/inbox_codex/2026-06-08_RESULTAT-BENCH-GPU-DGX-GB10.md` + +### Decisions et roles + +- `docs/coordination/registre/2026-06-08_decisions.md` +- `docs/coordination/ROLES.md` +- `docs/coordination/active/2026-06-08_1637_decision-piste2-no-go-code-mort.md` +- `docs/coordination/active/2026-06-08_1729_relance-missions-dgx-systemd-gb10.md` + +### Lea live / QG + +- `docs/coordination/RUNBOOK-LEA-LIVE-DRAFT.md` +- `docs/coordination/templates/TEMPLATE-QG.md` +- `docs/coordination/inbox_codex/2026-06-08_PLAN-LEA-LIVE-GRANDEUR-NATURE.md` + +## Etat technique detaille + +### DGX + +- Host : `aivanov@192.168.1.45`. +- Repo : `/home/aivanov/ai/rpa_vision_v3`. +- Branche : `poc-dgx`. +- Commit final recape : `09f65cecb` cote local/Gitea `poc-dgx`. +- Services UP local-only : `rpa-streaming` 5005, `dashboard` 5001, `api` 8000, `vwb-backend` 5002, `stream-worker`, `worker`. +- Services `disabled` : oui. +- Exposition externe : non, bind `127.0.0.1`. +- VWB frontend 3002 : non inclus, Node absent / a trancher. +- Grounding service dedie : non finalise. + +### Securite + +- WP-A : dashboard refuse demarrage sans `DASHBOARD_PASSWORD`. +- WP-B : verrou reenrolement via `RPA_FLEET_ENROLL_LOCKED`. +- `.env.local` DGX genere sur place, valeurs non consignees. +- Auth disabled interdit en contexte DGX. +- Reste : WP-C token par poste, a mettre en P0 apres tests reels. + +### Donnees entrainees + +- Archive V2 transferee et verifiee. +- `workflows.db` : 23 workflows. +- `visual_anchors` : 199. +- Anchors : 468 PNG, 398 references effectives OK. +- FAISS : dim 512, `ntotal=13666`. +- Exclusions confirmees : `live_sessions`, `sessions`, `uploads`, `screenshots`, `.env*`, secrets. + +### GPU / modeles + +- P1.g device policy validee. +- RTX 5070 local : GO Qwen. +- DGX GB10 : GO avec correction auto, puis QG final GO. +- `RPA_VISION_DEVICE=auto` OK apres correctif memory-unified. +- EasyOCR + ultralytics + poids YOLO icon_detect installes sur DGX. +- UI-TARS reste non active pour sante/replay : bench dangereux. +- Qwen3-VL-4B via vLLM reste candidat grounder, pas cable runtime large. +- `gemma4:26b` reste candidat supervise/judge. + +## Branching Gitea a trancher demain + +Constat actuel : + +- `poc-dgx` existe et contient le snapshot operationnel DGX. +- `main` est en retard/ambigu pour le POC. +- Plusieurs branches historiques existent. + +Proposition Codex : + +- `dev` : branche de developpement active, integration quotidienne. +- `poc/nvp/prod` ou `poc-prod` : branche stable POC/NVP/prod, protegee par QG. +- `poc-dgx` : conserver comme snapshot DGX du 2026-06-08, puis soit merger dans `poc/nvp/prod`, soit le garder comme tag/branche de release. + +Decision requise : + +- nom exact de la branche stable : `poc/nvp/prod`, `poc-prod`, ou autre ; +- regle de promotion : dev -> QG -> stable ; +- qui peut pousser sur stable. + +## Docker vs clone + +Etat : + +- Clone + venv + systemd fonctionne maintenant et doit rester le chemin POC court terme. +- Docker n'est pas tranche. +- Un document existe : `docs/ROADMAP_DOCKERISATION.md`. + +Position Codex : + +- Court terme : garder clone/venv/systemd pour ne pas casser le POC. +- Moyen terme : etudier Docker pour services applicatifs seulement, avec volumes externes pour data/secrets/modeles. +- Ollama/GPU/DGX ARM doivent etre traites explicitement, pas caches dans un Dockerfile premature. + +Decision demain : + +- lancer un audit Docker read-only ; +- produire deux options : `clone+systemd durci` vs `docker compose applicatif`. + +## Dashboard — priorite demain + +Le dashboard est critique et doit etre teste comme produit, pas seulement comme process UP. + +Checklist minimale : + +- login Basic avec `DASHBOARD_PASSWORD` ; +- fail-closed si secret absent ; +- onglet Fleet visible ; +- liste agents ; +- creation/enrolement agent ; +- revoke ; +- `RPA_FLEET_ENROLL_LOCKED` ; +- generation paquet agent ; +- proxy dashboard -> streaming ; +- pas de fuite token dans UI/logs/ZIP ; +- VWB backend accessible local-only ; +- actions dashboard realistes : ouvrir workflow, voir donnees entrainees, lancer preflight. + +QG attendu : + +- Qwen doit verifier securite et parcours ; +- Claude execute les tests avec captures/logs ; +- Codex tranche les corrections. + +## Tests reels — priorite produit + +Objectif demain : + +1. Verifier dashboard DGX local-only. +2. Preflight Lea live. +3. Dom devant Windows. +4. Capture supervisee safe Notepad/Explorateur/Easily lecture seule. +5. Preuves attendues : + - `live_events.jsonl`; + - `agent_chat/state/learn_*.json`; + - shadow understanding non vide ; + - workflow genere ; + - logs/captures coherents ; + - preflight replay non destructif. +6. Replay controle uniquement, pas autonome. +7. Ensuite seulement, multi-machine. + +Stop conditions : + +- pas de replay autonome ; +- pas d'action metier destructive ; +- pas d'exposition externe ; +- pas de service `enable` sans decision ; +- pas de secret affiche ; +- pas de suppression code mort. + +## Missions demain proposees + +### Codex + +- Reprendre ce handoff. +- Valider branches Gitea avec Dom. +- Orchestrer dashboard tests. +- Trancher Docker vs clone : lancer audit read-only. +- Maintenir QG strict avant promotion stable. + +### Claude + +- Executer test dashboard DGX bout-en-bout. +- Preparer puis executer preflight Lea live. +- Produire rapport preuves. +- Bench grounder Qwen3-VL-4B sur Easily reel si preconditions OK. +- Ne pas toucher code mort. + +### Qwen + +- QG dashboard securite/parcours. +- QG Lea live preflight. +- QG branches/procedure promotion. +- QG Docker vs clone. +- Maintenir registre decisions. + +## Etat git local a ne pas oublier + +Le repo local reste charge en fichiers non suivis de coordination, graphify et artefacts d'agents. + +Important : + +- ne pas faire de cleanup destructif ; +- ne pas commit les secrets ; +- ne pas commit `data/` massif ; +- `poc-dgx` est la branche operationnelle Gitea ; +- le handoff present est une source de reprise, pas un commit applicatif. + +— Codex diff --git a/docs/handoffs/2026-06-09_handoff_codex_fin_journee_reprise_2026-06-10.md b/docs/handoffs/2026-06-09_handoff_codex_fin_journee_reprise_2026-06-10.md new file mode 100644 index 000000000..1e849d88d --- /dev/null +++ b/docs/handoffs/2026-06-09_handoff_codex_fin_journee_reprise_2026-06-10.md @@ -0,0 +1,157 @@ +# Handoff Codex — fin journee 2026-06-09 / reprise 2026-06-10 + +- `Date`: 2026-06-09 21:18 CEST +- `Auteur`: Codex +- `Statut`: source de reprise operationnelle +- `Objet`: fin de cycle dashboard DGX, M2 Lea live, WP-C, branches, Docker + +## Resume executif + +La journee a transforme le socle DGX/local-only en POC produit beaucoup plus proche du live. + +Acquis valides : + +- Dashboard DGX securite : GO. +- Dashboard produit : GO post-P0 Qwen. +- P0-1 workflows : corrige par symlink `data/workflows` -> `visual_workflow_builder/backend/data/workflows`, VWB 5002 sert 39 workflows. +- P0-2 paquet agent : ZIP genere, download HTTP 200, usage POC interne seulement. +- M2 Lea live : cible technique validee = serveur dev Linux `192.168.1.40:5005`, agent Windows pointe deja dessus. +- Tunnel Ollama DGX : rendu persistant via `systemd --user`, QG GO, 10 modeles accessibles via `127.0.0.1:11434`. +- Branching : branche stable decidee = `poc-prod`. +- Deploiement POC court terme : clone + venv + systemd, pas Docker. +- WP-C : Patch 1 local/TDD fait, commit local `f7f692641`, QG GO, non pousse, non deploye. + +Non acquis / a reprendre : + +- M2 Lea live non execute : attend Dom devant Windows + GO Codex au moment T. +- WP-C Patch 2 autorise a 21:06 local/TDD seulement ; pas encore de resultat lu au moment du handoff. +- WP-C complet non implemente ; multi-TIM reste NOGO. +- Push/deploiement WP-C non decide. +- Branches `main/dev/poc-prod` non creees/poussees ; blobs ~15 G non traites ; `master` non tranche. +- DETTE-015 workflow store : documentee, migration post-POC seulement. + +## Sources de verite recentes + +### Dashboard / P0 + +- `docs/coordination/inbox_codex/2026-06-09_2025_claude-to-codex-qwen_RESULTAT-P0-DASHBOARD-CORRECTIONS.md` +- `docs/coordination/inbox_codex/2026-06-09_2050_qwen-to-codex-claude-dom_QG-POST-P0-DASHBOARD-GO.md` +- `docs/coordination/inbox_codex/2026-06-09_2035_claude-to-codex-qwen_INFO-DETTE-015-store-workflows.md` +- `docs/DETTE_TECHNIQUE.md` +- `docs/PLAN_MIGRATION_WORKFLOWS_STORE_2026-06-09.md` + +### M2 Lea live + +- `docs/coordination/inbox_codex/2026-06-09_1546_claude-to-codex_PREP-M2-EXECUTION-CHECKLIST.md` +- `docs/coordination/inbox_codex/2026-06-09_1605_claude-to-codex-qwen_TUNNEL-OLLAMA-PERSISTANT-FAIT.md` +- `docs/coordination/inbox_codex/2026-06-09_1710_claude-to-codex_RESULTAT-TUNNEL-M2-STABILITE.md` +- `docs/coordination/inbox_codex/2026-06-09_2025_qwen-to-codex-claude-dom_QG-TUNNEL-PERSISTANT-M2-READINESS-GO.md` + +### WP-C + +- `docs/coordination/inbox_codex/2026-06-09_1715_claude-to-codex_CARTO-WPC-TOKEN-PAR-POSTE-READONLY.md` +- `docs/coordination/inbox_codex/2026-06-09_2040_claude-to-codex_PLAN-WPC-TDD-EXECUTABLE.md` +- `docs/coordination/inbox_codex/2026-06-09_2058_claude-to-codex_RESULTAT-WPC-PATCH1-MIGRATION-TDD.md` +- `docs/coordination/inbox_codex/2026-06-09_2104_claude-to-codex_INFO-WPC-PATCH1-COMMITE.md` +- `docs/coordination/inbox_codex/2026-06-09_2110_qwen-to-codex-claude-dom_QG-WPC-PATCH1-GO.md` +- `docs/coordination/inbox_claude/2026-06-09_2106_codex-to-claude_GO-WPC-PATCH2-local-TDD-only.md` +- `docs/coordination/inbox_qwen/2026-06-09_2106_codex-to-qwen_ATTENTE-QG-WPC-patch2.md` + +### Branches / Docker + +- `docs/coordination/inbox_codex/2026-06-09_1405_claude-to-codex_AUDIT-M3-branches-gitea-readonly.md` +- `docs/coordination/inbox_codex/2026-06-09_1410_claude-to-codex_AUDIT-DOCKER-VS-CLONE-SYSTEMD.md` +- `docs/coordination/inbox_codex/2026-06-09_1412_claude-to-codex-qwen_DECISIONS-DOM-poc-prod-m4-clone-p0-go.md` + +## Decisions actees + +### Dashboard DGX + +- Securite dashboard : GO. +- Produit dashboard : GO post-P0. +- P0-1 vrai fix : symlink workflows, pas `DATABASE_URL`. +- `DATABASE_URL` : Qwen recommande de le garder ; il aide SQLAlchemy a lire la bonne DB, meme s'il ne corrige pas `/api/workflows/`. +- DETTE-015 : double store workflows documente, migration post-POC uniquement. + +### M2 Lea live + +- Cible M2 : serveur dev Linux `192.168.1.40:5005`, pas DGX directement. +- Ollama : tunnel local `127.0.0.1:11434` vers DGX, service `ollama-tunnel.service` user, GO Qwen. +- M2 live : techniquement pret, mais execution uniquement avec Dom devant Windows + GO Codex au moment T. + +### Branches / Docker + +- Stable = `poc-prod`. +- `poc-dgx` conserve comme snapshot/deploy DGX. +- Court terme POC = clone + venv + systemd. +- Docker = post-POC, hybride/progressif. + +### WP-C + +- WP-C reste bloquant multi-TIM. +- Patch 1 : commit local `f7f692641`, GO Qwen, non pousse, non deploye. +- Patch 2 : GO Codex donne a Claude a 21:06, local/TDD seulement. + +## Etat technique detaille + +### DGX + +- DGX reste local-only. +- Services DGX operationnels selon les tests du jour. +- VWB 5002 `/api/workflows/` = 39 apres symlink. +- Paquet agent `deploy/Lea_v1.0.0.zip` existe et sert HTTP 200, mais contient encore le token global. +- Pas de distribution multi-TIM. + +### M2 + +- Agent Windows gele pointe `http://192.168.1.40:5005/api/v1`. +- Serveur dev Linux expose 5005 LAN, workflows visibles. +- Tunnel Ollama persistant local sur le dev. +- Checklist M2 prete. +- Live Shadow non execute. + +### WP-C + +- Patch 1 modifie : + - `agent_v0/server_v1/agent_registry.py` + - `tests/unit/test_wpc_migration.py` +- Tests annonces par Claude : + - `tests/unit/test_wpc_migration.py` : 3 passed + - `test_fleet_enroll_lock_wpb.py` + migration : 9 passed +- Commit local : `f7f692641 feat(wp-c): migration colonnes token par poste (patch 1, inerte)` +- Prochaine etape autorisee au moment du handoff : Patch 2 local/TDD seulement. + +## Stop conditions maintenues + +- Pas de live Shadow sans Dom devant Windows. +- Pas de replay autonome. +- Pas de multi-TIM avant WP-C. +- Pas de Patch 3+ sans QG. +- Pas de deploiement DGX WP-C sans decision. +- Pas de push Gitea sans decision. +- Pas de suppression code mort. +- Pas de secret en clair. + +## Reprise demain + +1. Lire ce handoff. +2. Lire les nouveaux messages `inbox_codex` apres 2026-06-09 21:18 CEST. +3. Si Claude a livre `RESULTAT-WPC-PATCH2-ENROLL-TOKEN-TDD`, demander/attendre QG Qwen. +4. Si Dom veut M2 live : + - verifier tunnel Ollama ; + - verifier dashboard/workflows ; + - verifier agent Windows running ; + - confirmer Dom devant Windows ; + - donner GO Codex uniquement au moment T. +5. Ne pas lancer multi-TIM. +6. Ne pas pousser `f7f692641` ni autres commits sans decision. +7. Reprendre branches `poc-prod` seulement apres arbitrage blobs/protection. + +## Etat git a retenir + +- Branche locale : `poc-dgx`. +- HEAD local : `f7f692641`, en avance sur `gitea/poc-dgx` (`09f65cecb`) d'au moins le Patch 1 WP-C. +- Commit `f7f692641` non pousse et non deploye DGX. +- Worktree charge en fichiers de coordination, graphify, docs et artefacts non suivis : ne pas nettoyer destructivement. + +— Codex diff --git a/docs/handoffs/2026-06-10_handoff_codex_context-low-reset-1412.md b/docs/handoffs/2026-06-10_handoff_codex_context-low-reset-1412.md new file mode 100644 index 000000000..41b1f86ca --- /dev/null +++ b/docs/handoffs/2026-06-10_handoff_codex_context-low-reset-1412.md @@ -0,0 +1,66 @@ +# Handoff Codex — contexte bas / relais avant reset 14:12 + +- `Date`: 2026-06-10 09:18 CEST +- `Auteur`: Codex +- `Statut`: source de reprise operationnelle +- `Declencheur`: Dom signale contexte Codex tres bas et reset prevu vers 14:12 + +## Etat courant confirme + +- Branche locale : `poc-dgx`. +- HEAD local : `f7f692641 feat(wp-c): migration colonnes token par poste (patch 1, inerte)`. +- Worktree charge en docs/coordination, graphify, artefacts non suivis : ne pas nettoyer. +- Dashboard DGX securite + produit : GO. +- P0 workflows + ZIP : resolus. +- M2 Lea live : techniquement pret, cible `192.168.1.40:5005`, tunnel Ollama persistant GO, execution uniquement avec Dom devant Windows + GO Codex au moment T. +- WP-C Patch 1 : GO Qwen, commit local, non pousse, non deploye. +- WP-C Patch 2 : autorise local/TDD depuis 2026-06-09 21:06, non livre au moment de ce handoff. +- Multi-TIM : NOGO avant WP-C complet. + +## Objectif de continuite + +Permettre a Claude et Qwen d'avancer sans dependance forte au contexte Codex : + +- Claude avance sur l'execution locale/TDD strictement bornee. +- Qwen garde le fil, rend les QG, signale les contradictions a Dom. +- Dom reste l'arbitre pour live Windows, push, deploiement, branches et toute extension de scope. + +## Planning propose jusqu'au reset 14:12 + +| Fenetre | Owner | Action | Sortie attendue | +|---|---|---|---| +| Maintenant -> 10:30 | Claude | Relancer/executer WP-C Patch 2 local/TDD seulement | `RESULTAT-WPC-PATCH2-ENROLL-TOKEN-TDD` vers `inbox_codex/` | +| Des reception RESULTAT | Qwen | QG Patch 2 | `QG-WPC-PATCH2-GO` ou `NOGO` vers `inbox_codex/` | +| En parallele | Qwen | Tenir l'etat actif et verifier contradictions | Maj active/registre si necessaire | +| En parallele | Claude | Preparer read-only M2/checklist/rollback, sans live | Note courte si utile, aucune execution Windows | +| Si Codex indisponible | Qwen + Dom | Maintenir gates et arbitrer la suite | Pas de Patch 3/push/deploiement sans decision explicite | + +## Autorisations pendant indisponibilite Codex + +Autorise sans nouveau Codex : + +- Claude : Patch 2 WP-C local/TDD uniquement, selon message `docs/coordination/inbox_claude/2026-06-09_2106_codex-to-claude_GO-WPC-PATCH2-local-TDD-only.md`. +- Claude : audits read-only, plans de rollback, plans de test, preparation M2 sans execution. +- Qwen : QG, audit, contradiction check, synthese, registre, mise a jour de pointeurs actifs. + +Non autorise sans decision explicite Dom/Codex : + +- Patch 3+ WP-C. +- Branchement auth runtime. +- Build/package ZIP modifie. +- Push Gitea ou creation/protection branches. +- Deploiement DGX. +- Live M2 / replay / Shadow sans Dom devant Windows. +- Multi-TIM. +- Suppression de code mort ou nettoyage destructif. + +## Reprise Codex apres reset + +1. Lire ce handoff. +2. Lire `docs/coordination/active/2026-06-10_0918_continuite-codex-context-reset.md`. +3. Lire les nouveaux `inbox_codex/` apres 2026-06-10 09:18 CEST. +4. Si Claude a livre Patch 2 : attendre/lire QG Qwen avant toute suite. +5. Si Qwen a rendu GO Patch 2 : demander decision Dom avant Patch 3, push ou deploiement. +6. Si Dom veut M2 live : verifier chaine complete et confirmer Dom devant Windows avant GO. + +— Codex diff --git a/docs/handoffs/2026-06-10_handoff_codex_fin_journee_reprise_2026-06-11.md b/docs/handoffs/2026-06-10_handoff_codex_fin_journee_reprise_2026-06-11.md new file mode 100644 index 000000000..ee1786be6 --- /dev/null +++ b/docs/handoffs/2026-06-10_handoff_codex_fin_journee_reprise_2026-06-11.md @@ -0,0 +1,113 @@ +# Handoff Codex — fin journee 2026-06-10 / reprise 2026-06-11 + +- `Auteur`: Codex +- `Date cloture`: 2026-06-10 23:37 CEST +- `Reprise`: 2026-06-11 +- `Mode demande par Dom`: bi-turbo, focus POC DGX +- `Statut`: pret reprise + +## Cap demain + +Priorite unique : **POC DGX a fond**, sans dispersion. + +Deux pistes en parallele : + +1. **Operationnel M2/DGX** + - smoke DGX apres nuit / eventuel reboot ; + - verification acces dashboard/Fleet depuis Windows ; + - M2 live supervise avec Dom devant Windows ; + - collecte preuves record -> replay -> apprentissage si disponible. + +2. **Cadre QG / qualite** + - Claude finalise runbook M2 + Git safety readonly + smoke DGX ; + - Qwen relit runbook Claude et rend GO/NOGO ; + - Codex orchestre, verifie, tranche avec Dom. + +## Etat valide au depart + +### DGX + +- 6 services systemd DGX : `enabled` + `active`. +- DGX = cible POC. +- DEV = machine de code/test, Ollama via tunnel vers DGX. +- `ollama-tunnel.service` actif/persistant cote DEV. +- VLM cible actee par Qwen : `Qwen3-VL-4B-Instruct` pour POC, avec tunnel Ollama. +- Dashboard expose temporairement en HTTP direct pour test : decision acceptee, reversible, pas cible clinique. +- Point a verifier demain : activation effective cote DGX depuis Windows (`restart dashboard` / `ufw allow 5001/tcp` si necessaire avec Dom). + +### WP-C / securite agents + +- WP-C token par poste arrete pour POC. +- `DETTE-016` creee en P2/ACCEPTED. +- Patch 4 runtime annule. +- Patch 1-3 locaux/inertes/non deployes. +- Multi-TIM POC controle : GO Dom via Fleet/dashboard existant. +- Multi-TIM elargi/post-POC : NOGO sans WP-C ou equivalent. + +### Git + +- Branche courante locale : `poc-dgx`. +- `poc-dgx` local est 3 commits devant `gitea/poc-dgx`. +- Les 3 commits devant sont WP-C inertes : + - `f7f692641` Patch 1 migration colonnes token ; + - `9fb2c7bfe` Patch 2 generation token enroll ; + - `b20d17882` Patch 3 verify_token registre. +- Branche archive locale creee : `archive/wpc-local-inerte-2026-06-10` -> `b20d17882`. +- **Interdit demain matin** : push `poc-dgx` tel quel. + +## Messages / docs a relire en premier demain + +1. `docs/coordination/active/2026-06-11_0000_pointeur-handoff-reprise-2026-06-11.md` +2. `docs/coordination/inbox_codex/2026-06-10_2345_qwen-to-codex-claude-dom_ACK-CADRE-TRAVAIL-POC-DGX.md` +3. `docs/coordination/inbox_codex/2026-06-10_2315_qwen-to-codex-claude-dom_QG-GATES-POST-WPC-ABANDON.md` +4. `docs/coordination/inbox_codex/2026-06-10_2316_qwen-to-codex-claude-dom_QG-M2-LIVE-READINESS.md` +5. `docs/AUDIT_GAPS_APPLI_100PCT_2026-06-10.md` +6. `docs/coordination/inbox_claude/2026-06-10_2330_qwen-to-claude_AVIS-GAPS-APPLI-100PCT.md` +7. `docs/coordination/active/2026-06-10_1540_repartition-post-wpc-dgx-m2.md` + +## Actions immediates demain + +1. Lire les nouveaux messages Claude/Qwen. +2. Verifier si Claude a depose : + - `RESULTAT-GIT-SAFETY-WPC-ARCHIVE-READONLY` + - `RUNBOOK-M2-LIVE-POC` + - `SMOKE-DGX-DEMAIN-MATIN` +3. Faire smoke DGX readonly : + - `systemctl is-enabled/is-active` des 6 services ; + - health `5005/5002` ; + - dashboard `5001` ; + - Ollama tags ; + - VWB workflows ; + - Fleet API sans afficher de token. +4. Verifier accessibilite Windows -> dashboard DGX. +5. Si Dom est devant Windows : GO/NOGO Codex pour M2 live. + +## Stop conditions + +- Secret en clair dans logs/docs/reponses. +- `RPA_AUTH_DISABLED=true`. +- Agent rouge / crash dashboard. +- Tentative Patch 4 runtime. +- Push/deploiement sans Dom. +- Rebase/reset/cherry-pick sans decision Dom/Codex. + +## Risques techniques prioritaires connus + +Depuis `AUDIT_GAPS_APPLI_100PCT_2026-06-10.md` et avis Qwen : + +- `A1`: timeout HTTP client 5s pouvant perdre une action longue. +- `A2`: watchdog `_retry_pending` serveur absent. +- `P1`: DETTE-006/010 grounding Qwen3-VL/smart_resize a trancher. +- `A3`: ecran Windows verrouille non detecte. + +Pour demain : ne pas coder ces points sans revalidation existing-first et decision Dom/Codex. Le premier objectif reste M2 live supervise. + +## Definition de sortie demain matin + +- DGX smoke OK. +- Runbook M2 valide QG. +- Dom devant Windows. +- Agent Windows visible actif dans Fleet. +- Un scenario record/replay documente avec preuves. + +— Codex diff --git a/docs/handoffs/2026-06-13_handoff_codex_session2_watcher-vlm-reprise.md b/docs/handoffs/2026-06-13_handoff_codex_session2_watcher-vlm-reprise.md new file mode 100644 index 000000000..3f41dafa0 --- /dev/null +++ b/docs/handoffs/2026-06-13_handoff_codex_session2_watcher-vlm-reprise.md @@ -0,0 +1,72 @@ +# Handoff - 2026-06-13 - Codex watcher + reprise POC VLM + +- `Date`: 2026-06-13 08:45 CEST (DEV local) +- `Auteur`: Codex +- `Statut`: source de reprise operationnelle + +## Pre-check watcher obligatoire + +Au debut de la prochaine session, avant toute action : + +1. `docs/coordination/coordination_loop.sh ensure` +2. Lire les messages pertinents pour l'agent courant dans `inbox_codex/`, `inbox_claude/`, `inbox_qwen/` et `active/`. +3. Apres traitement : `docs/coordination/coordination_loop.sh ack` + +Si le watcher ne peut pas etre lance ou verifie, signaler le blocage avant de continuer. + +Etat valide au handoff : + +- service utilisateur `rpa-coordination-watcher.service` installe, active et enabled ; +- commande service : `docs/coordination/coordination_loop.sh watch 15` ; +- PID observe apres redemarrage : `1229002` ; +- file locale `.loop_state/unread_messages.tsv` nettoyee ; +- dernier `ensure` : `loop OK`, `0 pending`. + +Commandes utiles : + +- `docs/coordination/coordination_loop.sh service-status` +- `docs/coordination/coordination_loop.sh pending` +- `docs/coordination/coordination_loop.sh events` +- `docs/coordination/coordination_loop.sh service-install` si le service systemd utilisateur manque +- `docs/coordination/coordination_loop.sh service-stop` pour arreter/desactiver explicitement le service + +## Etat courant + +- Le watcher/loop a ete revu et consolide : queue unread persistante, digest lisible, trigger par message, dernier trigger, journal events, lock `flock` sur scan/baseline/ack, service systemd utilisateur persistant. +- La baseline coordination a ete reinitialisee apres nettoyage des faux positifs anciens. +- Le tri des baselines a ete durci en `LC_ALL=C sort -u` avant `comm`, pour eviter les faux nouveaux messages. +- `docs/coordination/README.md`, `docs/handoffs/README.md`, `docs/handoffs/TEMPLATE_HANDOFF.md` et `AGENTS.md` imposent maintenant `ensure` en debut de session. + +## POC VLM + +- Commit local courant : `5c5ce747b feat(grounding): cablage Qwen3-VL-4B/vLLM (RPA_GROUNDING_ENGINE, defaut off)`. +- Commit non pousse. +- Cablage par env `RPA_GROUNDING_ENGINE=qwen3vl_vllm`. +- Defaut OFF : aucun impact runtime si l'env n'est pas posee. +- Cross-review terminee : Codex + Qwen OK gates/securite, Claude a committe localement. + +## Prochaines actions + +1. Reprise session : lancer `docs/coordination/coordination_loop.sh ensure`. +2. Lire toute nouvelle coordination, puis `ack`. +3. Validation E2E DGX en one-shot du mode `RPA_GROUNDING_ENGINE=qwen3vl_vllm` contre `rpa-vllm-grounder` local-only `127.0.0.1:8001`. +4. Ne pas rendre l'activation permanente dans les services POC tant que Dom n'a pas donne GO. +5. Documenter les resultats E2E dans `docs/coordination/active/` et prevenir Claude/Qwen. + +## Garde-fous + +- DGX POC prioritaire ; DEV sert a valider avant passage DGX. +- Pas de push, pas de deploy, pas de changement auth/reseau, pas d'activation runtime permanente sans GO Dom. +- Pas de secret dans les handoffs/messages. +- Decisions importantes a faire remonter a Dom via Claude, canal remote. +- Donnees cliniques interdites. + +## References + +- `docs/coordination/coordination_loop.sh` +- `docs/coordination/systemd/rpa-coordination-watcher.service` +- `docs/coordination/README.md` +- `docs/handoffs/README.md` +- `.remember/remember.md` +- `docs/coordination/inbox_codex/2026-06-13_1006_claude-to-codex-qwen_COMMIT-CABLAGE-ET-HANDOFF-SESSION2.md` +- `docs/coordination/inbox_codex/2026-06-13_1000_qwen-to-codex-claude_ACK-CABLAGE-GATES-SECURITE.md` diff --git a/docs/handoffs/2026-06-15_0951_handoff_codex_reboot_lea-chat-dette019.md b/docs/handoffs/2026-06-15_0951_handoff_codex_reboot_lea-chat-dette019.md new file mode 100644 index 000000000..078371c89 --- /dev/null +++ b/docs/handoffs/2026-06-15_0951_handoff_codex_reboot_lea-chat-dette019.md @@ -0,0 +1,111 @@ +# Handoff Codex avant reboot serveur — Lea chat / multi-utilisateur / DETTE-019 + +Date: 2026-06-15 09:51 Europe/Paris +Auteur: Codex +Contexte: Dom va redemarrer la machine. Reprise attendue apres reboot. + +## Consigne obligatoire a la reprise + +Avant toute coordination: + +```bash +docs/coordination/coordination_loop.sh ensure +``` + +Puis lire tous les messages pertinents pour Codex et terminer par: + +```bash +docs/coordination/coordination_loop.sh ack +``` + +Le watcher doit etre operationnel pour Codex, Claude et Qwen avant de continuer. + +## Etat coordination + +- Watcher OK avant handoff. +- Dernier message lu: `docs/coordination/inbox_codex/2026-06-15_0947_claude-to-qwen-codex_DETTE019-DEPLOYEE-PREUVE-RUNTIME.md`. +- Ack coordination a faire juste apres creation de ce handoff. + +## Etat Lea chat diagnostique par Codex + +Constats non destructifs: + +- `agent_chat` local 5004 repond: + - `/api/status` => `online` + - 130 workflows detectes + - `POST /api/chat` => 200 pour `statut` + - `POST /api/chat` => 200 pour `qu'est-ce que tu sais faire ?` +- `streaming` local 5005 repond `/health` => `healthy`. +- `POST /api/learn/start` fonctionne. Une session diagnostic a ete creee puis annulee proprement. +- `tests/unit/test_chat_interface.py -q` => 34 passed. +- `virsh list --all` montrait `win11` en `shut off`. +- SocketIO: + - sans Origin: OK + - `Origin: http://localhost:5004`: OK + - `Origin: http://192.168.1.40:5004`: OK + - `Origin: http://127.0.0.1:5004`: FAIL HTTP 400 + +Hypothese principale: + +- Le backend texte fonctionne, mais les echanges d'actions visibles dans la ChatWindow sont probablement coupes car le process `agent_chat` actuel n'a pas `LEA_FEEDBACK_BUS` dans son environnement. +- Dans `agent_chat/app.py`, `_emit_lea()` est no-op si `LEA_FEEDBACK_BUS` est false/absent. +- La ChatWindow native attend ces events `lea:*` pour afficher les bulles d'action: `action_started`, `action_progress`, `done`, `paused`, `resumed`. + +## Travail donne a Claude et Qwen + +Claude: + +- Fichier: `docs/coordination/inbox_claude/2026-06-15_0942_codex-to-claude-qwen_PRIORITES-LEA-CHAT-ACTIONS-MULTIUSER.md` +- Mission: diagnostic runtime VM/Lea, config `RPA_SERVER_URL`, `RPA_AGENT_CHAT_URL`, `RPA_MACHINE_ID`, `LEA_FEEDBACK_BUS`, tests ChatWindow et bus action apres reboot VM. + +Qwen: + +- Fichier: `docs/coordination/inbox_qwen/2026-06-15_0942_codex-to-qwen-claude_QG-LEA-CHAT-ACTIONS-MULTIUSER.md` +- Mission: gates GO/NOGO chat/action/multi-utilisateur. + +Trace active: + +- `docs/coordination/active/2026-06-15_0942_priorites-lea-chat-interface-multiuser.md` + +## Priorites proposees a la reprise + +P0 - Depuis la VM Windows apres redemarrage: + +- Verifier que `RPA_SERVER_URL` pointe vers `http://:5005/api/v1`. +- Verifier que `RPA_AGENT_CHAT_URL` pointe vers `http://:5004`. +- Verifier que `RPA_MACHINE_ID` est unique. +- Tester `/health` 5005, `/api/status` 5004, puis `POST /api/chat`. +- Lancer Lea et verifier le log `LeaServerClient initialise : chat=... stream_url=...`. + +P1 - Echanges d'actions: + +- Decider si `LEA_FEEDBACK_BUS=1` est requis pour la session. +- Si oui, activer/verifier cote `agent_chat` et cote Windows. +- Preuve minimale: voir `lea:action_started`, `lea:action_progress`, `lea:done` dans la ChatWindow pour un replay safe. + +P2 - Multi-utilisateur: + +- Deux agents avec `RPA_MACHINE_ID` distincts. +- Deux sessions chat distinctes. +- Un ordre/replay cible la bonne machine. +- Pas de transcript/action visible sur l'autre machine. + +## DETTE-019 + +Dernier message Claude lu: + +- `DETTE-019` est deployee runtime POC DGX. +- Push `33c1e2e0d` fait. +- DGX HEAD `33c1e2e`. +- `workflows.db` preservee, backup `.bak-predeploy-dette019-20260615`. +- `rpa-streaming` DGX redemarre, service actif, health 200. +- Preuve runtime: 5 cibles texte score 0.90, cas douteux `0013` rejete par `rejected_low_score_grounding`. +- `DETTE-018` reste ouverte. + +## Ce que Codex n'a pas fait + +- Pas de modification code applicatif. +- Pas de restart service. +- Pas de modification VM. +- Pas de token expose dans ce handoff. + diff --git a/docs/handoffs/README.md b/docs/handoffs/README.md new file mode 100644 index 000000000..fdaac1afb --- /dev/null +++ b/docs/handoffs/README.md @@ -0,0 +1,23 @@ +# Handoffs - regle de reprise par defaut + +Tout handoff ou prompt de reprise doit commencer par le pre-check coordination. + +## Pre-check watcher obligatoire + +Avant de reprendre le travail : + +1. Verifier/lancer/scanner le watcher : + + `docs/coordination/coordination_loop.sh ensure` + +2. Lire les messages pertinents pour l'agent courant dans `inbox_codex/`, + `inbox_claude/`, `inbox_qwen/` et `active/`. + +3. Apres traitement, vider la file locale : + + `docs/coordination/coordination_loop.sh ack` + +Si le watcher ne peut pas etre lance ou verifie, le handoff de reprise doit le +signaler comme blocage avant toute autre action. + +Cette regle vaut pour Codex, Claude et Qwen. diff --git a/docs/handoffs/TEMPLATE_HANDOFF.md b/docs/handoffs/TEMPLATE_HANDOFF.md new file mode 100644 index 000000000..7e6a6ff15 --- /dev/null +++ b/docs/handoffs/TEMPLATE_HANDOFF.md @@ -0,0 +1,25 @@ +# Handoff - YYYY-MM-DD + +- `Date`: +- `Auteur`: +- `Statut`: source de reprise operationnelle + +## Pre-check watcher obligatoire + +Au debut de la prochaine session, avant toute action : + +1. `docs/coordination/coordination_loop.sh ensure` +2. Lire les messages pertinents pour l'agent courant. +3. Apres traitement : `docs/coordination/coordination_loop.sh ack` + +Si le watcher ne peut pas etre lance ou verifie, signaler le blocage. + +## Etat courant + +## Decisions actives + +## Prochaines actions + +## Garde-fous + +## References