Files
rpa_vision_v3/docs/superpowers/specs/2026-05-05-qw-suite-mai-design.md
Dom 2a07d8084b docs(qw): spec design QW suite mai 2026 (multi-écrans + LoopDetector + safety_checks hybrides)
Spec issu d'un brainstorming structuré (7 questions clarifiantes,
décisions tranchées) inspiré par l'exploration comparative de 5 frameworks
computer-use (Simular Agent-S, browser-use, OpenAI CUA sample, Coasty
open-cu, Showlab OOTB).

3 quick wins ciblés :
- QW1 multi-écrans : capture/grounding par monitor_index avec fallbacks
- QW2 LoopDetector composite : screen_static (CLIP) + action_repeat + retry
- QW4 safety_checks hybrides : déclaratif workflow + LLM contextuel
  (medgemma:4b, timeout 5s, fallback safe, kill-switch env)

Contraintes inviolables : 100% vision, 100% local Ollama, backward compat.
Plan livraison : QW1+QW2 avant démo GHT, QW4 enchaîné dès validation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 22:23:10 +02:00

468 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Spec — QW Suite Mai 2026
| Champ | Valeur |
|---|---|
| Date | 2026-05-05 |
| Auteur | Dom + Claude (brainstorming structuré) |
| Branche | `feature/qw-suite-mai` (depuis `feature/feedback-bus`) |
| Backup | `backup/pre-qw-suite-mai-2026-05-05` à pousser sur Gitea avant 1er commit |
| Statut | Design approuvé — spec à valider par Dom avant `writing-plans` |
| Cibles démo | GHT Sud 95 (1ère sem mai 2026, date à confirmer) |
| Contraintes inviolables | 100% vision · 100% local (Ollama) · backward compatible |
## 1. Contexte & motivation
Suite à l'exploration comparative de 5 frameworks computer-use (Simular Agent-S, browser-use, OpenAI CUA sample, Coasty open-cu, Showlab OOTB), trois quick wins ont été identifiés comme améliorations à fort ratio valeur/risque pour RPA Vision V3, alignés avec la philosophie du projet (vision pure, souveraineté, supervision médicale) :
- **QW1 — Multi-écrans propre** (inspiré OOTB) : capture et grounding sur l'écran cible plutôt que sur le composite tous écrans. Gain de perf grounding + correction des coordonnées.
- **QW2 — LoopDetector composite** (inspiré browser-use) : détecter quand Léa exécute des actions techniquement valides mais que l'écran ne progresse pas, et escalader vers l'humain plutôt que de tourner en rond muettement.
- **QW4 — Safety checks hybrides** (inspiré OpenAI CUA + browser-use Pydantic registry) : enrichir l'action `pause_for_human` avec une liste de vérifications à acquitter, mêlant déclaratif (workflow) et contextuel (LLM local).
Effet cumulé attendu : Léa devient observable, robuste et auditable sans rien céder sur le 100% local.
## 2. Décisions de design (récap)
| Sujet | Décision |
|---|---|
| Activation | Default-ON pour tous les workflows (Dom recréera ce qui en a besoin) |
| QW1 — Stratégie ciblage écran | `monitor_index` enregistré à la capture → fallback focus actif → fallback composite (backward) |
| QW1 — Niveau de stack | Client Agent V1 (capture) + serveur (routeur) + `core/execution/input_handler.py` (capture locale) |
| QW2 — Signal de boucle | Composite OR : screen_static (CLIP) + action_repeat + retry_threshold |
| QW2 — Sortie | `replay_state["status"] = "paused_need_help"` avec `pause_reason` structuré |
| QW4 — Source des checks | Hybride : déclaratif workflow + LLM contextuel sur `safety_level: "medical_critical"` |
| QW4 — Robustesse LLM | `medgemma:4b` + timeout 5s + `format=json` Ollama + JSON Schema strict + fallback safe (zéro check additionnel) + kill-switch env var |
| QW4 — UX VWB | Bulle existante préservée + `<ChecklistPanel>` au-dessus de Continuer (bouton désactivé tant que required non cochés) |
| Ordre de livraison | QW1 → QW2 → QW4 (du moins invasif au plus visible) |
| Plan timing | Option A : QW1+QW2 avant démo ; QW4 enchaîné dès validation des deux premiers |
| Kill-switches | Env vars sur QW2 et QW4, surchargeables par `systemctl edit` |
| Backward compatibility | 100% — aucun champ obligatoire ajouté au DSL ; workflows existants se comportent comme avant |
## 3. Architecture globale
```
┌─────────────────────────┐ ┌─────────────────────────────────┐
│ Agent V1 (Windows) │ │ Serveur Streaming (5005) │
│ │ │ │
│ ┌──────────────────┐ │ │ ┌───────────────────────────┐ │
│ │ ScreenCapture │ │ │ │ LoopDetector [QW2] │ │
│ │ + monitor_index │───┼────────▶│ │ • screen_static (CLIP) │ │
│ │ [QW1] │ │ HTTP │ │ • action_repeat │ │
│ └──────────────────┘ │ │ │ • retry_threshold │ │
│ │ │ │ → paused_need_help │ │
│ ┌──────────────────┐ │ │ └───────────────────────────┘ │
│ │ FeedbackBus lea:*│◀──┼─────────┤ │
│ │ chat_window │ │ │ ┌───────────────────────────┐ │
│ └──────────────────┘ │ │ │ SafetyChecksProvider │ │
└─────────────────────────┘ │ │ [QW4] │ │
│ │ • declarative (workflow) │ │
│ │ • LLM contextual │ │
│ │ medgemma:4b 5s/JSON │ │
│ │ fallback safe │ │
│ │ • kill-switch env var │ │
│ └───────────────────────────┘ │
│ │
│ ┌───────────────────────────┐ │
│ │ MonitorRouter [QW1] │ │
│ │ • cible monitor_index │ │
│ │ • fallback focus actif │ │
│ └───────────────────────────┘ │
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ VWB Frontend (3002) │
│ │
│ PauseDialog (étendu) [QW4-UX] │
│ • bulle existante préservée │
│ • + ChecklistPanel │
│ (cases à cocher acquittables)│
│ • + pause_reason si loop │
│ Continuer désactivé tant que │
│ required-checks non cochés │
└─────────────────────────────────┘
```
### Principes invariants
1. Aucun nouveau service, aucune nouvelle DB. Tout dans la stack existante (Agent V1 + serveur 5005 + VWB 3002).
2. 3 modules serveur isolés (`monitor_router.py`, `loop_detector.py`, `safety_checks_provider.py`) — couplage faible, testables individuellement, désactivables par env var.
3. Backward compatible : workflows sans nouveaux champs se comportent comme avant.
4. Kill-switches env vars sur QW2 et QW4, override possible via `systemctl edit` pendant la démo.
5. 100% vision : QW1 pure capture + grounding ; QW2 réutilise le `_clip_embedder` déjà chargé ; QW4 LLM = Ollama local strict.
6. Bus `lea:*` étendu de 4 events d'observabilité : `lea:loop_detected`, `lea:safety_checks_generated`, `lea:safety_checks_llm_failed`, `lea:monitor_routed`.
### Surface de modification (ordre A)
| QW | Fichiers nouveaux | Fichiers modifiés |
|---|---|---|
| QW1 | `agent_v0/server_v1/monitor_router.py` | `agent_v0/agent_v1/capture/screen_capture.py`, `core/execution/input_handler.py`, `agent_v0/server_v1/api_stream.py` (~10 lignes) |
| QW2 | `agent_v0/server_v1/loop_detector.py` | `agent_v0/server_v1/replay_engine.py` (~30 lignes), `agent_v0/server_v1/api_stream.py` (~20 lignes) |
| QW4 | `agent_v0/server_v1/safety_checks_provider.py`, `visual_workflow_builder/frontend_v4/src/components/PauseDialog.tsx` | `agent_v0/server_v1/replay_engine.py`, `agent_v0/server_v1/api_stream.py` (`/replay/resume`), `visual_workflow_builder/frontend_v4/src/types.ts`, `visual_workflow_builder/frontend_v4/src/components/PropertiesPanel.tsx` |
## 4. QW1 — Multi-écrans
### 4.1 Composants
**Client Agent V1**`agent_v0/agent_v1/capture/screen_capture.py` (existant à modifier)
- Enrichit chaque heartbeat / event avec :
- `monitor_index: int`
- `monitors_geometry: [{idx, x, y, w, h, primary}]`
- Détection via `screeninfo` (port direct depuis Showlab OOTB)
- Capture de l'écran *actif uniquement* (poids réseau identique à aujourd'hui)
- Si `screeninfo` indisponible côté Windows : envoie `monitors_geometry: []`, comportement composite préservé
**Serveur** — nouveau `agent_v0/server_v1/monitor_router.py` (~80 lignes)
- API : `resolve_target_monitor(action: dict, session_state: dict) → MonitorTarget`
- `MonitorTarget = {idx, offset_x, offset_y, w, h, source: "action" | "focus" | "composite_fallback"}`
- Stratégie :
1. Lit `action.get("monitor_index")` si présent → cible cet écran
2. Sinon `session_state.get("last_focused_monitor")` → cible focus actif
3. Sinon `monitors[0]` composite (comportement actuel — backward)
**Input local Linux**`core/execution/input_handler.py` modifs ciblées
- Signature changée : `_capture_screen(monitor_idx=None) → (image, w, h, offset_x, offset_y)`
- Quand `monitor_idx` fourni : capture uniquement ce monitor
- Toutes les fonctions `_grounding_*` (`_grounding_ocr`, `_grounding_ui_tars`, `_grounding_vlm`) propagent l'offset pour traduire les coords retournées en coords absolues écran
### 4.2 Data flow replay
```
Action [monitor_index=1] reçue par serveur
→ MonitorRouter.resolve()
→ target_monitor = {idx:1, offset:(1920,0), w:1920, h:1080, source:"action"}
→ grounding capture monitor 1 uniquement (image 1920×1080, pas 3840×1080)
→ UI-TARS / OCR / VLM cherche cible → coords locales (640, 540)
→ coords absolues = (640+1920, 540+0) = (2560, 540)
→ pyautogui.click(2560, 540)
→ bus.emit("lea:monitor_routed", {idx:1, source:"action"})
```
### 4.3 Error handling
| Cas | Comportement |
|---|---|
| `monitor_index` absent (vieille session) | Fallback focus actif, log info `lea:monitor_routed source=focus` |
| Monitor enregistré n'existe plus (2nd écran débranché) | Fallback focus actif, event `lea:monitor_unavailable` warning |
| `mss.monitors[i]` hors limites | Fallback `monitors[0]` composite, event `lea:monitor_invalid_index` error |
| `screeninfo` non installé côté Agent V1 | `monitors_geometry: []`, fallback composite (comportement actuel) — pas de blocage |
### 4.4 Tests QW1
- `tests/unit/test_monitor_router.py` : 4 cas (cible OK, fallback focus, fallback composite, monitor débranché)
- `tests/integration/test_grounding_offset.py` : capture 1 monitor + clic résolu avec offset (mock pyautogui)
- Smoke : 1 workflow Easily rejoué, vérification visuelle que le clic atterrit au bon endroit
### 4.5 Compat workflows existants
Aucune action n'a `monitor_index` aujourd'hui → 100% des workflows existants partent en fallback focus actif → comportement quasi-identique au composite actuel mais sur un seul écran (gain de perf grounding même sans recréation de workflow).
## 5. QW2 — LoopDetector composite
### 5.1 Composants
**Nouveau** `agent_v0/server_v1/loop_detector.py` (~150 lignes)
- Classe `LoopDetector` avec 3 sous-détecteurs
- API : `evaluate(replay_state, screenshot_history, action_history) → LoopVerdict`
- `LoopVerdict = {detected: bool, reason: str, signal: str, evidence: dict}`
**Hook** dans `agent_v0/server_v1/api_stream.py`
- Après chaque `report_action_result`, appel `loop_detector.evaluate(...)` si `RPA_LOOP_DETECTOR_ENABLED=1` (défaut)
- Si `verdict.detected` :
- `replay_state["status"] = "paused_need_help"`
- `replay_state["pause_reason"] = verdict.reason`
- `replay_state["pause_message"] = f"Léa semble bloquée — {verdict.signal}"`
- bus.emit `lea:loop_detected` avec `{signal, evidence, replay_id}`
**Étendu** dans `replay_engine.py` :
- `_create_replay_state()` ajoute :
- `"_screenshot_history": []` (anneau de 5 derniers embeddings CLIP)
- `"_action_history": []` (anneau des 5 dernières actions)
- `_pre_check_screen_state()` continue indépendamment (signal différent : check pré-action vs détection post-action de stagnation)
### 5.2 Signaux composites
| Signal | Détecteur | Seuil par défaut | Source |
|---|---|---|---|
| `screen_static` | A | 4 captures consécutives avec CLIP similarity > 0.99 | `_clip_embedder` déjà chargé serveur |
| `action_repeat` | B | 3 actions consécutives identiques (type + coords) | `_action_history` |
| `retry_threshold` | C | 3 retries sur même `action_id` | `replay_state["retried_actions"]` (déjà existant) |
Un seul signal positif suffit à déclencher l'escalade.
### 5.3 Data flow
```
Action exécutée → result reçu via /replay/result
LoopDetector.evaluate(state, screenshots, actions) si RPA_LOOP_DETECTOR_ENABLED=1
├─ A.check_screen_static() → embed(latest), compare aux N-1 derniers
├─ B.check_action_repeat() → compare action_history[-3:]
└─ C.check_retry_threshold() → state["retried_actions"] >= 3
Si verdict.detected:
state["status"] = "paused_need_help"
state["pause_reason"] = verdict.reason
state["pause_message"] = f"Léa semble bloquée — {verdict.signal} ({evidence})"
bus.emit("lea:loop_detected", {signal, evidence, replay_id})
```
### 5.4 Error handling
| Cas | Comportement |
|---|---|
| CLIP embedder unavailable | Signal A désactivé (warning log 1×), B+C continuent. Pas de blocage. |
| `_screenshot_history` < N | Signal A skip silencieusement (pas assez d'historique) |
| `embed_image()` lève une exception | Catch + log warning, replay continue (verdict = `detected=False`) |
| `RPA_LOOP_DETECTOR_ENABLED=0` | Module entier bypassé, comportement antérieur |
| Faux positif détecté en pleine démo | `RPA_LOOP_DETECTOR_ENABLED=0` via `systemctl edit rpa-streaming` + restart → reprise immédiate |
### 5.5 Configuration env vars
- `RPA_LOOP_DETECTOR_ENABLED=1` (défaut)
- `RPA_LOOP_SCREEN_STATIC_THRESHOLD=0.99`
- `RPA_LOOP_SCREEN_STATIC_N=4`
- `RPA_LOOP_ACTION_REPEAT_N=3`
- `RPA_LOOP_RETRY_THRESHOLD=3`
### 5.6 Tests QW2
- `tests/unit/test_loop_detector.py` : 8 cas (chaque signal isolé, chaque combinaison, kill-switch, embedder absent)
- `tests/integration/test_loop_detector_replay.py` : 3 cas — replay simulé qui boucle → vérifier transition `running → paused_need_help` avec bonne raison
- Pas de smoke démo (impossible à reproduire fiable, on s'appuie sur les tests intégration)
### 5.7 Compat VWB
Aucune côté frontend pour QW2 : la pause `paused_need_help` existe déjà. Le `pause_reason` enrichi sera affiché par le composant `PauseDialog` étendu en QW4. Avant la livraison de QW4, la raison s'affichera en texte dans le `pause_message` (donc utile dès le commit QW2).
## 6. QW4 — Safety checks hybrides
### 6.1 Contrat de l'action étendue (rétro-compatible)
```json
{
"type": "pause_for_human",
"parameters": {
"message": "Validation T2A avant codage",
"safety_level": "medical_critical",
"safety_checks": [
{"id": "check_ipp", "label": "Vérifier IPP patient", "required": true},
{"id": "check_cim10", "label": "Confirmer code CIM-10", "required": true}
]
}
}
```
`safety_level` et `safety_checks` sont **optionnels**. Action sans ces champs → comportement actuel (bulle simple, aucun appel LLM).
### 6.2 Composants serveur
**Nouveau** `agent_v0/server_v1/safety_checks_provider.py` (~180 lignes)
- API : `build_pause_payload(action, replay_state, last_screenshot) → PausePayload`
- Concatène : checks déclaratifs (workflow) + checks contextuels (LLM si `safety_level == "medical_critical"`)
- Chaque check porte sa source : `source: "declarative" | "llm_contextual"` et son `evidence` (vide pour déclaratif, justification courte pour LLM)
- Format check final :
```json
{
"id": "check_xxx",
"label": "...",
"required": true,
"source": "declarative" | "llm_contextual",
"evidence": null | "..."
}
```
**LLM contextual call** — sous-fonction `_call_llm_for_contextual_checks()`
- Modèle : `medgemma:4b` (env `RPA_SAFETY_CHECKS_LLM_MODEL`)
- Timeout dur : 5s (env `RPA_SAFETY_CHECKS_LLM_TIMEOUT_S`)
- `format=json` natif Ollama + JSON Schema strict :
```json
{"additional_checks": [{"label": "string", "evidence": "string"}]}
```
- Max 3 checks ajoutés (env `RPA_SAFETY_CHECKS_LLM_MAX_CHECKS`)
- Prompt : screenshot heartbeat actuel + workflow message + liste des checks déclaratifs (évite doublons)
- Tout échec (timeout, exception, JSON invalide post-schema) → `additional_checks = []`, event `lea:safety_checks_llm_failed`, replay continue
**Hook** dans `replay_engine.py` — branche `action_type == "pause_for_human"`
- Avant de basculer en `paused_need_help`, appel `safety_checks_provider.build_pause_payload(...)`
- Stocke `replay_state["safety_checks"] = payload.checks`
- Stocke `replay_state["pause_payload"] = payload` (pour debug/audit)
**Modif** `api_stream.py` — endpoint `/replay/resume`
- Reçoit `{acknowledged_check_ids: [...]}` dans le body POST
- Vérifie : tous les checks `required=true` doivent être dans `acknowledged_check_ids`
- Sinon : `400 {error: "required_checks_missing", missing: [...]}`
- Stocke `replay_state["checks_acknowledged"] = acknowledged_check_ids` (audit trail)
- Reprise normale du replay
### 6.3 Composants frontend VWB
**Nouveau** `visual_workflow_builder/frontend_v4/src/components/PauseDialog.tsx` (~200 lignes)
- Props : `pauseMessage`, `pauseReason`, `safetyChecks`, `onResume(ackIds)`, `onCancel`
- Si `safetyChecks.length === 0` : rend la bulle existante (legacy, comportement actuel)
- Sinon : bulle + `<ChecklistPanel>` avec checkboxes
- Bouton Continuer disabled tant que `checks.filter(c => c.required && !checked).length > 0`
- POST `/replay/resume` avec body `{acknowledged_check_ids: [...]}`
- Visuel source :
- Badge `[Léa]` pour `source: "llm_contextual"` (avec tooltip `evidence`)
- Badge `[obligatoire]` pour `required: true`
**Étendu** `types.ts`
- `PauseAction['parameters']` : ajout `safety_level?`, `safety_checks?`
- `Execution` : ajout `pause_reason?`, `safety_checks?`
**Étendu** `PropertiesPanel.tsx:1356` — éditeur de l'action `pause_for_human`
- Section "Niveau de sécurité" : dropdown `standard | medical_critical`
- Section "Checks à valider" : liste éditable (id + label + required)
### 6.4 Data flow complet
```
Action pause_for_human (medical_critical, 2 checks déclaratifs) atteinte
SafetyChecksProvider.build_pause_payload()
├─ checks = [...declarative] (2 entrées)
├─ if safety_level == "medical_critical" and RPA_SAFETY_CHECKS_LLM_ENABLED=1:
│ llm_checks = _call_llm_for_contextual_checks() (max 3, timeout 5s)
│ checks += llm_checks
└─ return PausePayload(checks, pause_reason, message)
replay_state["status"] = "paused_need_help"
replay_state["safety_checks"] = checks
bus.emit("lea:safety_checks_generated", {count, sources})
Frontend VWB poll /replay/state → reçoit pause_payload
<PauseDialog> rend ChecklistPanel
Médecin coche les 4 checks → clique Continuer
POST /replay/resume {acknowledged_check_ids: [4 ids]}
Serveur valide (tous required acquittés) → reprise du replay
replay_state["checks_acknowledged"] = [...] (audit trail conservé)
```
### 6.5 Error handling
| Cas | Comportement |
|---|---|
| `safety_level` absent | Pas d'appel LLM ; checks déclaratifs uniquement (peut être `[]`) → bulle simple si vide, checklist sinon |
| Ollama timeout 5s | Event `lea:safety_checks_llm_failed`, `additional_checks=[]`, fallback safe (déclaratifs seuls) |
| Ollama JSON malformé (post `format=json` — théoriquement impossible) | Idem timeout, fallback safe |
| LLM produit un check absurde | Accepté tel quel, le superviseur ignore (pas de filtrage en V1) |
| Frontend reçoit `safety_checks=[]` | Bulle simple, comportement legacy |
| `RPA_SAFETY_CHECKS_LLM_ENABLED=0` | Couche LLM bypassée, déclaratifs gardés |
| `/replay/resume` sans `acknowledged_check_ids` sur required | `400 required_checks_missing` |
| Frontend POST `/replay/resume` rejeté | Toast d'erreur côté UI, état pause conservé, possibilité de cocher manquants et réessayer |
### 6.6 Configuration env vars
- `RPA_SAFETY_CHECKS_LLM_ENABLED=1` (défaut)
- `RPA_SAFETY_CHECKS_LLM_MODEL=medgemma:4b`
- `RPA_SAFETY_CHECKS_LLM_TIMEOUT_S=5`
- `RPA_SAFETY_CHECKS_LLM_MAX_CHECKS=3`
### 6.7 Tests QW4
- `tests/unit/test_safety_checks_provider.py` : 7 cas (déclaratif seul, hybride réussi, LLM timeout, LLM JSON invalide, kill-switch, max_checks respecté, déclaratif vide)
- `tests/integration/test_replay_resume_acknowledgments.py` : 3 cas (resume OK, missing required → 400, audit trail enregistré dans `checks_acknowledged`)
- Frontend : `tests/components/PauseDialog.test.tsx` si suite Vitest existe (à confirmer pendant l'implémentation), sinon test manuel avec checklist écrite
- Smoke : 1 workflow Easily avec `pause_for_human medical_critical` enrichi → vérification full chain
### 6.8 Compat workflows existants
100% backward — `pause_for_human` actuels n'ont ni `safety_level` ni `safety_checks` → comportement strictement identique. Aucune recréation forcée. Dom enrichira uniquement les workflows qu'il veut promouvoir au niveau `medical_critical`.
## 7. Tests, sécurité de la branche, livraison
### 7.1 Filet de sécurité avant TOUT commit sur `feature/qw-suite-mai`
1. Branche backup poussée Gitea : `backup/pre-qw-suite-mai-2026-05-05`
2. Capture baseline E2E :
```
pytest tests/test_pipeline_e2e.py \
tests/test_phase0_integration.py \
tests/integration/test_stream_processor.py \
-q 2>&1 | tee .qw-baseline.log
```
3. Smoke démo : 1 dérouler complet d'un workflow Easily Assure, archivage screenshot/vidéo de référence
4. État VWB validé : démarrage Vite local, ouverture d'un workflow, lancement d'un replay simple, screenshot "tout va bien"
### 7.2 Discipline TDD légère par QW
- Test unitaire écrit AVANT le code de production (1 test rouge → 1 implémentation → vert)
- Pas de TDD complet sur le frontend (Vitest + React = trop d'outillage à valider en parallèle), test manuel cadré avec checklist écrite
- Re-run de la suite baseline après chaque commit QW, comparaison au log archivé
- Toute régression bloque le passage au QW suivant tant qu'elle n'est pas comprise et résolue
### 7.3 Compat VWB — checklist explicite avant commit QW4
- [ ] Workflow ancien (sans `safety_checks`) → bulle simple s'affiche normalement
- [ ] Workflow nouveau avec `safety_checks` déclaratifs uniquement → checklist visible, **pas** d'appel Ollama (vérification logs)
- [ ] Workflow `medical_critical` → checklist + checks LLM apparaissent (vérification logs Ollama call dans les 5s)
- [ ] Continuer désactivé tant que required non cochés
- [ ] POST `/replay/resume` avec mauvais payload → toast d'erreur côté UI, pas de crash
- [ ] PropertiesPanel : édition de `safety_checks` ne casse pas l'édition d'autres params de `pause_for_human`
- [ ] DB `workflows.db` : ouverture après commit, aucune migration cassante (schéma JSON est libre)
### 7.4 Plan de commits
```
1. test(qw1): tests monitor_router + grounding_offset (rouges)
2. feat(qw1): multi-écrans piloté par monitor_index (verts)
3. test(qw2): tests loop_detector composite (rouges)
4. feat(qw2): LoopDetector composite avec kill-switch env
5. test(qw4): tests safety_checks_provider + replay_resume (rouges)
6. feat(qw4): safety_checks hybride déclaratif + LLM contextuel
7. feat(vwb): PauseDialog + ChecklistPanel + extension PropertiesPanel
8. docs(qw): docs/QW_SUITE_MAI.md + mise à jour MEMORY.md
```
Chaque commit signé Co-Authored-By Claude. Branche poussée régulièrement sur Gitea pour backup distant.
### 7.5 Stratégie en cas de régression critique pendant la démo
Kill-switches env vars surchargeables sans redéploiement code :
```
systemctl edit rpa-streaming
# Ajouter sous [Service] :
Environment=RPA_LOOP_DETECTOR_ENABLED=0
Environment=RPA_SAFETY_CHECKS_LLM_ENABLED=0
systemctl restart rpa-streaming
```
Si problème grave au-delà des kill-switches : rollback à `backup/pre-qw-suite-mai-2026-05-05`.
```
git checkout backup/pre-qw-suite-mai-2026-05-05
./svc.sh restart
```
### 7.6 Plan de livraison (Option A validée)
**Avant démo GHT (cette semaine) — Sprint priorité 1**
- QW1 : tests + code + smoke (~1j)
- QW2 : tests + code + tests intégration (~2j)
- Capture baseline + replay smoke entre chaque
- Si QW1+QW2 validés et probants → on enchaîne sur QW4 dès que possible (Dom accepte le weekend si "effet waouh" auprès de spécialistes RPA)
**Après démo / dès validation QW1+QW2 — Sprint priorité 2**
- QW4 serveur (provider + LLM + endpoint resume) (~3j)
- QW4 frontend (PauseDialog + PropertiesPanel) (~2j)
- Doc + mise à jour MEMORY.md
**Total estimé** : ~8.5j-h ingénieur senior, étalable selon le retour démo.
## 8. Ce qui n'est PAS dans ce spec (out of scope)
- F1 (DSL d'actions Pydantic-first) : refactor de fond, sera son propre spec après la démo.
- F2 (Mixture-of-Grounding routeur adaptatif) : nécessite F1, son propre spec.
- F3 (Best-of-N + Reflection) : nécessite F1, son propre spec.
- QW3 (`output_model_schema` Pydantic pour `extract_text`) : opportuniste, sera intégré quand on touchera `extract_text` pour autre chose.
- Toute introduction de Pydantic-AI / instructor / Playwright / accessibility-tree : interdit (contraintes inviolables).
- Refonte du composant pause en `<PauseDialog>` à 3 modes (option C de Q6) : reportée après démo si retour utilisateurs justifie l'investissement.
## 9. Open questions
Aucune. Toutes les décisions de design ont été tranchées via les 7 questions clarifiantes du brainstorming du 5 mai 2026.