diff --git a/docs/superpowers/specs/2026-05-05-qw-suite-mai-design.md b/docs/superpowers/specs/2026-05-05-qw-suite-mai-design.md new file mode 100644 index 000000000..e37d841a9 --- /dev/null +++ b/docs/superpowers/specs/2026-05-05-qw-suite-mai-design.md @@ -0,0 +1,467 @@ +# 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 + `` 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 + `` 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 + ↓ + 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 `` à 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.