# QG Review Framework — D1 NavigateCoords Patch **Auteur**: Qwen **Date**: 2026-07-02 **Statut**: EN ATTENTE patch Codex **Scope**: Review du patch D1 (Option 1 — Compiler Injection) produit par Codex --- ## Baseline test coverage (pré-patch) | Fichier | Classes | Tests | Rôle | |---------|---------|-------|------| | `test_navigate_handler_e2e.py` | 4 | 8 | Handler mock — nominal, OCR miss, no screenshot, never-fail | | `test_navigate_wiring.py` | 4 | 5 | Import/wiring non-regression | | `test_action_resolver.py` | 6 | 10 | NavigateCoords, NavigateResult, grounded_to_coords, navigate_login | | `test_coords_consumption_gap.py` | 3 | 10 | **GAP DOCUMENTATION** — résolution viable, compiler gap, navigate→[] | | **Total** | **17** | **33** | | **Tests critiques à mettre à jour après D1 patch**: - `test_coords_consumption_gap.py::test_navigate_action_type_unknown` — affirme actuellement `actions == []`; doit affirmer `len(actions) >= 1` et `actions[0]["type"] == "navigate"` après D1 - `test_coords_consumption_gap.py::TestCompilerGapLiteralFloats` — 4 tests documentant le gap literal-floats; après D1, les tests coords_var doivent affirmer templates strings ARE produites quand coords_var présent **Point d'insertion exact D1**: - Fichier: `replay_engine.py` - Entre `elif action_type == "llm_generate"` (retourne `[normalized]` ~L1949) et `else:` clause (~L1953) - Navigate branch: `elif action_type == "navigate"` → `normalized["type"] = "navigate"` + parameters dict → `return [normalized]` **P1-C root cause**: - `_resolve_runtime_vars_in_str` (L2025): `return str(value)` — tout {{var.field}} résolu devient string "0.35" pas float 0.35 - Coercion helper `_coerce_action_coords` doit agir APRÈS `_resolve_runtime_vars` (L4335), AVANT `type_ = action.get("type")` (L4337) --- ## Critères de review — Checklist ### C1 : Branche navigate dans `_edge_to_normalized_actions` (Gap C) | # | Critère | GO | NOGO | |---|---------|----|------| | C1-1 | Branche `elif action_type == "navigate"` ajoutée entre `llm_generate` (L1949) et `else` (L1951) | Present, position correcte | Absente ou mal positionnée | | C1-2 | `normalized["type"] = "navigate"` | Oui | Type incorrect | | C1-3 | Parameters dict avec `login_coords_var`, `password_coords_var`, `submit_coords_var` | Noms exacts, valeurs default | Noms divergent ou absents | | C1-4 | Retourne `[normalized]` (1 action serveur-side) | `[normalized]` | `[]` ou autre | | C1-5 | Test TR-1 : `test_navigate_action_type_unknown` mis à jour — affirme `len(result) >= 1` et `result[0]["type"] == "navigate"` | Test updated + passes | Test non mis à jour ou fails | ### C2 : coords_var dans branches mouse_click / text_input (Gap A) | # | Critère | GO | NOGO | |---|---------|----|------| | C2-1 | `coords_var = action_params.get("coords_var")` check dans mouse_click | Present | Absent | | C2-2 | Si coords_var → `x_pct = f"{{{{{coords_var}.x_pct}}}"` et `y_pct = f"{{{{{coords_var}.y_pct}}}"` | Template strings correctes | Syntaxe template incorrecte ou .y_pct pour x_pct | | C2-3 | Si coords_var absent → literal floats comme avant (fallback existant) | Branch else intacte | Branch else modifiée ou supprimée | | C2-4 | `normalized["coords_var"] = coords_var` ajouté pour traçabilité | Oui | Absent | | C2-5 | Même mécanisme dans text_input branch | Identique à mouse_click | Absent ou divergent | | C2-6 | BUG vérifié : text_input x_pct template = `{{coords_var.x_pct}}` (pas `.y_pct` deux fois) | Correct | y_pct en double | ### C3 : `_coerce_action_coords()` helper (Gap B / P1-C) | # | Critère | GO | NOGO | |---|---------|----|------| | C3-1 | Helper défini dans api_stream.py (pas replay_engine.py) | api_stream.py | Autre fichier | | C3-2 | Appel APRÈS `_resolve_runtime_vars` (L4335), AVANT `type_ = action.get("type")` (L4337) | Position correcte | Avant resolver ou après type_ check | | C3-3 | float pass-through : `isinstance(val, float) → continue` | Idempotent sur actions existantes | Pas de float check → conversion inutile | | C3-4 | string→float : `try: action[key] = float(val)` | Conversion correcte | Pas de try/except → crash possible | | C3-5 | Template non résolu → pause_for_human (pas fallback 0.0/0.0) | `val.startswith("{{") and val.endswith("}}")` → pause_for_human | Fallback 0.0/0.0 ou autre coords dangereux | | C3-6 | Conversion impossible → pause_for_human | ValueError/TypeError → pause_for_human | Exception non catchée | | C3-7 | `_skip_reason` documenté pour debug | Oui | Absent | | C3-8 | `safety_level = "high"` pour pause_for_human | Oui | Absent ou autre valeur | | C3-9 | Retourne action mutée (pas de new dict) | Mutation in-place | Copie → risque race | | C3-10 | Keys itérées = ("x_pct", "y_pct") uniquement | Pas de sur-itération | Autres keys modifiées | ### C4 : Never-fail contract | # | Critère | GO | NOGO | |---|---------|----|------| | C4-1 | `_handle_navigate_action` ne lance jamais d'exception non catchée | Contract preserved | Nouvelle exception possible | | C4-2 | `_coerce_action_coords` ne lance jamais — tout cas couvert par try/except ou pause_for_human | Contract preserved | Exception possible | ### C5 : Limites de scope POC | # | Critère | GO | NOGO | |---|---------|----|------| | C5-1 | Maximum 4 fichiers modifiés | ≤ 4 | > 4 | | C5-2 | Pas de changement schema VWB dans POC patch | Pas de modification VWB code | VWB code modifié | | C5-3 | Pas de nouvelle dépendance pip | 0 nouvelles deps | Nouvelle dep | | C5-4 | Pas de modification OmniParser wiring | `_omniparser_available = False` intact | Modifié | ### C6 : Test coverage | # | Critère | GO | NOGO | |---|---------|----|------| | C6-1 | TR-1 : navigate compile à 1 action (pas []) | Passes | Fails | | C6-2 | TR-2 : coords_var template resolution + float conversion | Passes | Fails | | C6-3 | Test `_coerce_action_coords` : float pass-through | Passes | Absent | | C6-4 | Test `_coerce_action_coords` : string→float conversion | Passes | Absent | | C6-5 | Test `_coerce_action_coords` : template non résolu → pause_for_human | Passes | Absent | | C6-6 | Test `_coerce_action_coords` : conversion impossible → pause_for_human | Passes | Absent | | C6-7 | Test idempotence : action existante float non modifiée | Passes | Absent | | C6-8 | `pytest tests/unit/` passe en intégralité | 0 failures | ≥1 failure | ### C7 : Risques additionnels (3 identifiés dans PLAN_D1) | # | Risque | Mitigation attendue | GO | NOGO | |---|--------|--------------------|----|------| | C7-1 | Résolution partielle (x_pct résolu, y_pct template) | `_coerce_action_coords` → pause_for_human si ANY key unresolved | Mitigation presente | Pas de mitigation | | C7-2 | Idempotence sur mouse_click existant | `isinstance(val, float) → continue` | Idempotent | Risque de double conversion | | C7-3 | Race condition sur variables dict partagé | BFS séquentiel garantit navigate→click ordre | Note dans code/doc | Pas de mention | --- ## Procédure de review 1. **Lire le patch** : `git diff` sur les fichiers modifiés par Codex 2. **Vérifier chaque critère C1-C7** : GO/NOGO par ligne 3. **Exécuter les tests** : `cd /home/dom/ai/rpa_vision_v3 && .venv/bin/python -m pytest tests/unit/ -x -v` 4. **Produire le verdict** : Table GO/NOGO avec justification + verdict global ## Format verdict ``` ## QG Verdict — D1 NavigateCoords Patch | Critère | GO/NOGO | Note | |---------|---------|------| | C1 | GO | Branche navigate correcte | | C2 | NOGO | BUG: y_pct en double dans text_input | | ... | ... | ... | **Verdict global**: GO / NOGO (avec réserves listées) ``` --- *Qwen — framework QG prêt, awaiting Codex patch pour exécution.*