# 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.