feat: journée 17 avril — tests E2E validés, dashboard fleet+audit, VWB bridge, cleaner C2
Some checks failed
security-audit / Bandit (scan statique) (push) Successful in 14s
security-audit / pip-audit (CVE dépendances) (push) Successful in 10s
security-audit / Scan secrets (grep) (push) Successful in 8s
tests / Lint (ruff + black) (push) Successful in 13s
tests / Tests unitaires (sans GPU) (push) Failing after 14s
tests / Tests sécurité (critique) (push) Has been skipped

Pipeline E2E complet validé :
  Capture VM → streaming → serveur → cleaner → replay → audit trail
  Mode apprentissage supervisé fonctionne (Léa échoue → humain → reprise)

Dashboard :
  - Cleanup 14→10 onglets (RCE supprimée)
  - Fleet : enregistrer/révoquer agents, tokens, ZIP pré-configuré téléchargeable
  - Audit trail MVP (/audit) : filtres, tableau, export CSV, conformité AI Act/RGPD
  - Formulaire Fleet simplifié (nom + email, machine_id auto)

VWB bridge Léa→VWB :
  - Compound décomposés en N steps (saisie + raccourci visibles)
  - Layout serpentin 3 colonnes (plus colonne verticale)
  - Badge OS 🪟/🐧, filtre OS retiré (admin Linux voit Windows)
  - Fix import SQLite readonly

Cleaner intelligent :
  - Descriptions lisibles (UIA/C2) + détection doublons
  - Logique C2 : UIElement identifié = jamais parasite
  - Patterns parasites resserrés
  - Message Léa : "Je n'y arrive pas, montrez-moi comment faire"

Config agent (INC-1 à INC-7) :
  - SERVER_URL + SERVER_BASE unifiés
  - RPA_OLLAMA_HOST séparé
  - allow_redirects=False sur POST
  - Middleware réécriture URL serveur

CI Gitea : fix token + Flask-SocketIO + ruff propre
Fleet endpoints : /agents/enroll|uninstall|fleet + agent_registry SQLite
Backup : script quotidien workflows.db + audit

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dom
2026-04-17 17:46:40 +02:00
parent 2fa864b5c7
commit 4f61741420
27 changed files with 5088 additions and 1543 deletions

View File

@@ -0,0 +1,306 @@
# Architecture de Configuration Agent -- Serveur
**Date** : 2026-04-13
**Auteur** : Analyse automatique (Claude)
**Statut** : Diagnostic + recommandation -- PAS de code modifie
---
## 1. Schema de la chaine de configuration
```
config.txt (fichier plat sur le poste Windows)
|
| Lea.bat (for /f "eol=# tokens=1,* delims==" => set)
v
Variables d'environnement du process Python
|
+---> agent_v1/config.py
| SERVER_URL = os.getenv("RPA_SERVER_URL", "http://localhost:5005/api/v1")
| STREAMING_ENDPOINT = f"{SERVER_URL}/traces/stream"
| API_TOKEN = os.environ.get("RPA_API_TOKEN", "")
|
+---> lea_ui/server_client.py
| _stream_base = RPA_SERVER_URL (si defini) OU http://{RPA_SERVER_HOST}:{5005}
| Utilise _stream_base + "/api/v1/traces/stream/..." (chemin COMPLET en dur)
|
+---> agent_v1/main.py
| _background_heartbeat_loop : utilise SERVER_URL DIRECTEMENT (pas STREAMING_ENDPOINT)
| _replay_poll_loop : passe SERVER_URL a executor.poll_and_execute()
|
+---> agent_v1/core/executor.py
poll_and_execute : construit f"{server_url}/traces/stream/replay/next"
_server_resolve_target : construit f"{server_url}/traces/stream/replay/resolve_target"
_observe_screen : construit f"{server_url}/traces/stream/replay/pre_analyze"
```
### Cote serveur (generation du config.txt)
```
web_dashboard/app.py
|
+---> _RPA_PUBLIC_URL = RPA_PUBLIC_URL ou RPA_SERVER_URL ou "https://lea.labs.laurinebazin.design"
+---> _build_custom_config(machine_id, user_name, token)
| server_url = _RPA_PUBLIC_URL.rstrip("/")
| if not server_url.endswith("/api/v1"):
| server_url += "/api/v1" <=== CORRECTIF RECENT (fonctionne)
| -> RPA_SERVER_URL={server_url}
| -> RPA_SERVER_HOST={host sans schema ni path}
|
+---> deploy/lea_package/config.txt (template statique dans le repo)
+---> deploy/installer/config_template.txt (template pour installeur Inno Setup)
```
### Cote serveur (routes FastAPI, port 5005)
```
agent_v0/server_v1/api_stream.py
|
+---> TOUTES les routes sous /api/v1/traces/stream/...
| /register, /event, /image, /finalize, /replay/next, etc.
|
+---> /health (public, a la racine)
|
+---> Middleware url_compat_rewrite :
/traces/stream/... => /api/v1/traces/stream/...
```
---
## 2. Inventaire des incoherences
### INC-1 : Deux systemes paralleles de resolution d'URL (CRITIQUE)
**Fichiers concernes** :
- `agent_v0/agent_v1/config.py` (ligne 43-45) : `SERVER_URL` = URL complete avec `/api/v1` ; `STREAMING_ENDPOINT = SERVER_URL + "/traces/stream"`
- `agent_v0/lea_ui/server_client.py` (ligne 77-81) : `_stream_base` = `RPA_SERVER_URL` BRUTE (sans garantie de `/api/v1`) ; utilise ensuite `_stream_base + "/api/v1/traces/stream/..."` (chemin complet en dur)
**Probleme** : Si `RPA_SERVER_URL=https://lea.labs.laurinebazin.design/api/v1` :
- `config.py` produit `STREAMING_ENDPOINT = .../api/v1/traces/stream` (CORRECT)
- `server_client.py` produit `_stream_base/api/v1/traces/stream/...` = `.../api/v1/api/v1/traces/stream/...` (DOUBLE `/api/v1` !)
Si `RPA_SERVER_URL=https://lea.labs.laurinebazin.design` :
- `config.py` produit `STREAMING_ENDPOINT = .../traces/stream` (MANQUE `/api/v1`)
- `server_client.py` produit `_stream_base/api/v1/traces/stream/...` (CORRECT)
**Il n'existe aucune valeur de `RPA_SERVER_URL` qui fasse fonctionner les deux modules simultanement.**
### INC-2 : `_background_heartbeat_loop` utilise `SERVER_URL` au lieu de `STREAMING_ENDPOINT`
**Fichier** : `agent_v0/agent_v1/main.py` (ligne 370)
```python
req.post(f"{SERVER_URL}/traces/stream/image", ...)
```
`SERVER_URL` = `http://localhost:5005/api/v1` => URL finale = `/api/v1/traces/stream/image` (CORRECT).
Mais c'est un accident : le heartbeat bypasse `STREAMING_ENDPOINT` (`SERVER_URL + "/traces/stream"`) et reconstruit son propre chemin. Le meme pattern se retrouve dans `executor.py` qui recoit `server_url` (= `SERVER_URL`) et construit `f"{server_url}/traces/stream/replay/next"`.
**Consequence** : Deux conventions coexistent :
1. `STREAMING_ENDPOINT + "/register"` (streamer.py) -- attend que `SERVER_URL` contienne `/api/v1`
2. `SERVER_URL + "/traces/stream/image"` (main.py, executor.py) -- attend aussi que `SERVER_URL` contienne `/api/v1`
Aujourd'hui les deux marchent parce que `SERVER_URL` inclut `/api/v1`. Mais `server_client.py` utilise une troisieme convention (chemin complet en dur), d'ou l'INC-1.
### INC-3 : `LeaServerClient.check_connection()` appelle `/health` sur `_stream_base`
**Fichier** : `agent_v0/lea_ui/server_client.py` (ligne 161)
```python
resp = requests.get(f"{self._stream_base}/health", ...)
```
Si `_stream_base = "https://lea.labs.laurinebazin.design/api/v1"`, l'URL finale est `/api/v1/health` -- **cette route n'existe pas**. La route sante est `GET /health` (racine).
Si `_stream_base = "https://lea.labs.laurinebazin.design"`, l'URL finale est `/health` -- OK.
Encore un conflit : `server_client.py` attend une URL **sans** `/api/v1`, `config.py` la fournit **avec**.
### INC-4 : Template `deploy/lea_package/config.txt` contient de vrais secrets
**Fichier** : `deploy/lea_package/config.txt` (ligne 19)
```
RPA_API_TOKEN=86031addb338e449fccdb1a983f61807aec15d42d482b9c7748ad607dc23caab
```
Ce fichier est versionne dans Git. Le token reel est en clair dans le repo.
### INC-5 : Copie ancienne non maintenue (`agent_v0/deploy/windows_client/`)
**Fichier** : `agent_v0/deploy/windows_client/config.py` (ligne 41)
```python
SERVER_URL = os.getenv("RPA_SERVER_URL", "http://localhost:8000/api/traces/upload")
```
Cette copie pointe encore sur l'ancien serveur API (port 8000, endpoint `/api/traces/upload`). Completement obsolete. Idem pour `agent_v0/config.py` (ligne 41).
### INC-6 : `RPA_SERVER_HOST` sert a deux choses incompatibles
- **`server_client.py`** : utilise `RPA_SERVER_HOST` comme **hostname nu** (ex: `192.168.1.40` ou `lea.labs.laurinebazin.design`) pour construire `http://{host}:5005`
- **`executor.py`** : utilise `RPA_SERVER_HOST` pour construire des URLs **Ollama** (`http://{host}:11434/api/chat`)
- **`main.py`** (ligne 94-95) : passe `RPA_SERVER_HOST` comme `server_host` au `LeaServerClient` et au `ChatWindow`
Le `config.txt` genere par Fleet met `RPA_SERVER_HOST=lea.labs.laurinebazin.design`. Cela provoque :
- `executor.py` tente `http://lea.labs.laurinebazin.design:11434/api/chat` -- **Ollama n'est pas expose sur Internet** (echec silencieux)
### INC-7 : Redirect POST -> GET (Bug 3, non resolu cote client)
La lib Python `requests` suit les redirections 301/302 en transformant les POST en GET (RFC 7231). Quand NPM redirige `http://` vers `https://`, tous les POST streaming (register, event, image, finalize) deviennent des GET et recevront un 405.
**Aucune protection cote client**. Le middleware serveur `url_compat_rewrite` ne resout que le probleme de path (pas le probleme de schema HTTP/HTTPS).
---
## 3. Recommandation architecturale
### Principe : une seule variable, deux composants
```
RPA_SERVER_URL = URL complete incluant le prefixe API
Exemples :
http://localhost:5005/api/v1 (dev local)
http://192.168.1.40:5005/api/v1 (LAN)
https://lea.labs.laurinebazin.design/api/v1 (Internet)
```
**Toutes les URLs de l'agent sont construites par concatenation de `SERVER_URL` + suffixe de route** :
```python
# config.py
SERVER_URL = os.getenv("RPA_SERVER_URL", "http://localhost:5005/api/v1")
# Pour le health-check (route a la racine, pas sous /api/v1) :
SERVER_BASE = SERVER_URL.rsplit("/api/v1", 1)[0] # ex: "https://lea.labs.laurinebazin.design"
# streamer.py, executor.py, main.py : tous utilisent
STREAMING_ENDPOINT = f"{SERVER_URL}/traces/stream" # inchange
HEALTH_ENDPOINT = f"{SERVER_BASE}/health" # NOUVEAU
# server_client.py : supprimer le systeme parallele
# _stream_base = SERVER_URL (plus de re-concatenation de "/api/v1" en dur)
```
### Supprimer `RPA_SERVER_HOST`
Cette variable est source de confusion (INC-6). Elle ne doit pas exister. Le hostname est derive de `RPA_SERVER_URL` si besoin (pour l'affichage dans la chat window, etc.).
L'acces Ollama doit avoir sa propre variable `RPA_OLLAMA_HOST` (defaut : `localhost`), car Ollama n'est JAMAIS accessible via le reverse proxy Internet.
### Protection POST -> GET
Ajouter dans `streamer.py` et `executor.py` :
```python
# Dans chaque session requests :
session = requests.Session()
session.max_redirects = 0 # Refuser les redirections (echouer bruyamment)
```
Ou forcer `https://` cote client si le host est un domaine public (pas localhost/IP privee).
### Template config.txt
```
RPA_SERVER_URL=CONFIGURE_ME
RPA_API_TOKEN=CONFIGURE_ME
RPA_MACHINE_ID=CONFIGURE_ME
```
Pas de token reel, pas de valeur par defaut fonctionnelle. L'agent doit refuser de demarrer si `RPA_SERVER_URL` contient "CONFIGURE_ME".
---
## 4. Matrice scenarios x configuration
| Scenario | RPA_SERVER_URL | RPA_API_TOKEN | Notes |
|---|---|---|---|
| Dev local (meme machine) | `http://localhost:5005/api/v1` | (vide ou token dev) | RPA_AUTH_DISABLED=true cote serveur |
| LAN interne (Dom <-> VM) | `http://192.168.1.40:5005/api/v1` | token prod | HTTP OK en LAN ferme |
| Internet via NPM (TIM) | `https://lea.labs.laurinebazin.design/api/v1` | token prod | HTTPS obligatoire, pas de redirect |
| Futur DGX on-premise | `http://<ip_dgx>:5005/api/v1` ou `https://...` | token prod | Selon reseau client |
---
## 5. Liste des fichiers a corriger
### Priorite HAUTE (bloquant pour le deploiement TIM)
| Fichier | Ligne(s) | Action |
|---|---|---|
| `agent_v0/lea_ui/server_client.py` | 77-81, 161, 230, 287, 321, 349 | Supprimer la double logique `_stream_base`. Utiliser `SERVER_URL` de config.py comme base, ne plus concatener `/api/v1` en dur dans les appels. Pour `/health`, utiliser `SERVER_BASE` (sans `/api/v1`). |
| `agent_v0/agent_v1/main.py` | 94-95 | Supprimer l'utilisation de `RPA_SERVER_HOST` pour construire le `LeaServerClient`. Passer `SERVER_URL` directement. |
| `agent_v0/agent_v1/main.py` | 370 | Utiliser `STREAMING_ENDPOINT` au lieu de reconstruire le chemin manuellement. |
| `agent_v0/agent_v1/network/streamer.py` | 34 | Aucun changement (utilise deja `STREAMING_ENDPOINT` correctement). |
| `deploy/lea_package/config.txt` | 14, 19, 20 | Remplacer les valeurs par des placeholders `CONFIGURE_ME`. Supprimer le token reel. |
| `deploy/installer/config_template.txt` | 26-27 | Idem, remplacer le token reel par un placeholder. |
### Priorite MOYENNE (coherence du codebase)
| Fichier | Ligne(s) | Action |
|---|---|---|
| `agent_v0/agent_v1/config.py` | 43-45 | Ajouter `SERVER_BASE` (URL sans `/api/v1`) pour le health-check. |
| `agent_v0/agent_v1/core/executor.py` | 1144, 1280, 1595 | Remplacer `RPA_SERVER_HOST` par une nouvelle var `RPA_OLLAMA_HOST` (defaut `localhost`). |
| `web_dashboard/app.py` | 2055-2060 | Renommer `_RPA_PUBLIC_URL` en `_RPA_PUBLIC_SERVER_URL`. S'assurer que le `/api/v1` est toujours present dans le config.txt genere (deja fait, ligne 2097-2098). |
| `web_dashboard/app.py` | 2119 | Supprimer la ligne `RPA_SERVER_HOST=` du config.txt genere. |
### Priorite BASSE (nettoyage)
| Fichier | Action |
|---|---|
| `agent_v0/config.py` | Supprimer ou marquer deprecated (ancien agent V0, port 8000). |
| `agent_v0/deploy/windows_client/` | Supprimer l'arborescence entiere (copie obsolete, remplacee par le ZIP Fleet). |
| `agent_v0/deploy/windows_client/config.py` | Port 8000, endpoint `/api/traces/upload` -- completement mort. |
### Protection anti-redirect (Bug 3)
| Fichier | Action |
|---|---|
| `agent_v0/agent_v1/network/streamer.py` | Utiliser `requests.Session()` avec `max_redirects=0` ou forcer HTTPS si domaine public. |
| `agent_v0/agent_v1/core/executor.py` | Idem pour les appels HTTP du replay (resolve_target, pre_analyze, replay/next, replay/result). |
---
## 6. Diagramme de flux (etat cible)
```
config.txt (genere par Fleet ou rempli a la main)
|
| RPA_SERVER_URL=https://lea.labs.laurinebazin.design/api/v1
| RPA_API_TOKEN=<token>
| RPA_MACHINE_ID=<id>
| (plus de RPA_SERVER_HOST)
|
v
Lea.bat -> set variables d'environnement
|
v
agent_v1/config.py
| SERVER_URL = "https://lea.labs.laurinebazin.design/api/v1"
| SERVER_BASE = "https://lea.labs.laurinebazin.design"
| STREAMING_ENDPOINT = "https://lea.labs.laurinebazin.design/api/v1/traces/stream"
| HEALTH_ENDPOINT = "https://lea.labs.laurinebazin.design/health"
|
+-------> streamer.py : STREAMING_ENDPOINT + "/register", "/event", "/image", "/finalize"
+-------> main.py : STREAMING_ENDPOINT + "/image" (heartbeat)
| SERVER_URL (passe a executor.poll_and_execute)
+-------> executor.py : SERVER_URL + "/traces/stream/replay/next", etc.
+-------> server_client.py : SERVER_URL + "/traces/stream/workflows", etc.
| HEALTH_ENDPOINT (pour check_connection)
|
v (HTTPS, port 443)
NPM Reverse Proxy
|
v (HTTP, port 5005)
FastAPI api_stream.py
| /health
| /api/v1/traces/stream/register
| /api/v1/traces/stream/event
| /api/v1/traces/stream/image
| /api/v1/traces/stream/replay/next
| ...
```
---
## 7. Resume des bugs originaux et leur resolution
| Bug | Cause racine | Correction |
|---|---|---|
| Bug 1 (URL obsolete dans config.txt) | Template `deploy/lea_package/config.txt` jamais mis a jour | DEJA CORRIGE (le fichier actuel contient `/api/v1`). Mais le token reel est toujours en clair. |
| Bug 2 (mismatch /api/v1) | Deux modules (`config.py` vs `server_client.py`) avec des conventions incompatibles pour construire les URLs | Unifier sur une seule convention : `RPA_SERVER_URL` inclut TOUJOURS `/api/v1`. `server_client.py` doit utiliser `SERVER_URL` de `config.py` au lieu de reimplementer sa propre logique. |
| Bug 3 (POST -> GET sur redirect) | `requests` suit les 301 en changeant la methode HTTP | Forcer HTTPS cote client quand le domaine est public, OU desactiver les redirections (`max_redirects=0`) pour echouer explicitement. Le middleware serveur `url_compat_rewrite` est un filet de securite pour le path, pas pour le schema. |