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
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:
306
docs/technique/ARCHITECTURE_CONFIG_AGENT.md
Normal file
306
docs/technique/ARCHITECTURE_CONFIG_AGENT.md
Normal 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. |
|
||||
Reference in New Issue
Block a user