docs: track design docs, plans, audits, coordination infrastructure, handoffs
- 21 docs/*.md: audits, design notes, deployment plans, checklists, memos - Coordination: ROLES, runbooks (DGX reboot, Lea live), patches, registre, syntheses, systemd, QG template - Handoffs: 6 Codex handoff documents + README + template
This commit is contained in:
81
docs/coordination/ROLES.md
Normal file
81
docs/coordination/ROLES.md
Normal file
@@ -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.*
|
||||
233
docs/coordination/RUNBOOK-DGX-POST-REBOOT-CHECK.md
Normal file
233
docs/coordination/RUNBOOK-DGX-POST-REBOOT-CHECK.md
Normal file
@@ -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.
|
||||
97
docs/coordination/RUNBOOK-LEA-LIVE-DRAFT.md
Normal file
97
docs/coordination/RUNBOOK-LEA-LIVE-DRAFT.md
Normal file
@@ -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.*
|
||||
208
docs/coordination/patches/2026-06-18_api_workflows_fix.patch
Normal file
208
docs/coordination/patches/2026-06-18_api_workflows_fix.patch
Normal file
@@ -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:
|
||||
@@ -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
|
||||
? '<span style="color:#f59e0b;">⚠️</span>'
|
||||
- : '<span style="color:#22c55e;">✅</span>';
|
||||
+ : processingArmed
|
||||
+ ? '<span style="color:#3b82f6;">⏸️</span>'
|
||||
+ : '<span style="color:#22c55e;">✅</span>';
|
||||
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});
|
||||
}
|
||||
318
docs/coordination/patches/2026-06-19_vwb_basic_auth.patch
Normal file
318
docs/coordination/patches/2026-06-19_vwb_basic_auth.patch
Normal file
@@ -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 <b64>
|
||||
+# 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 <b64> 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"])
|
||||
83
docs/coordination/registre/2026-06-08_decisions.md
Normal file
83
docs/coordination/registre/2026-06-08_decisions.md
Normal file
@@ -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" |
|
||||
145
docs/coordination/registre/2026-06-09_decisions.md
Normal file
145
docs/coordination/registre/2026-06-09_decisions.md
Normal file
@@ -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
|
||||
62
docs/coordination/registre/2026-06-10_decisions.md
Normal file
62
docs/coordination/registre/2026-06-10_decisions.md
Normal file
@@ -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
|
||||
@@ -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.
|
||||
14
docs/coordination/systemd/rpa-coordination-watcher.service
Normal file
14
docs/coordination/systemd/rpa-coordination-watcher.service
Normal file
@@ -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
|
||||
@@ -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
|
||||
69
docs/coordination/templates/TEMPLATE-QG.md
Normal file
69
docs/coordination/templates/TEMPLATE-QG.md
Normal file
@@ -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.*
|
||||
Reference in New Issue
Block a user