diff --git a/docs/AUDIT_CODE_MORT_2026-07-02.md b/docs/AUDIT_CODE_MORT_2026-07-02.md new file mode 100644 index 000000000..3a896ac26 --- /dev/null +++ b/docs/AUDIT_CODE_MORT_2026-07-02.md @@ -0,0 +1,106 @@ +# Audit Code Mort — Classification A/B/C — 2026-07-02 + +**Auteur**: Qwen (vérifié par grep/glob/commandes réelles) +**Date**: 2026-07-02 +**Méthode**: Parallel agent exploration + grep verification + graphify cross-check + +--- + +## Méthodologie + +- **A (WIRED/ACTIF)** : Code importé et appelé dans le runtime de production +- **B (ORPHAN/PROJECTION)** : Code avec lazy import ou projection future, pas appelé actuellement mais structuré pour activation +- **C (MORT/CONFIRMÉ)** : Code zero imports, zero callers, zero runtime activation — candidat suppression + +**Règle**: C-MORT nécessite GO Dom avant suppression. B-ORPHELIN conserve. A-WIRED documenté. + +--- + +## C-MORT Confirmé (8 items, ~843 lignes) + +| # | Fichier/Zone | Lignes | Preuve C-MORT | Risque suppression | +|---|-------------|--------|---------------|-------------------| +| C1 | `agent_v0/deploy_windows.py` | ~244 | Comment "OBSOLETE avril 2026" + zero imports | LOW — standalone script | +| C2 | `core/config.py`: 7 deprecated config classes | ~160 | Zero prod imports, mirrorent SystemConfig | LOW — mais vérifier .env references | +| C3 | `core/detection/owl_detector.py`: 4 methods | ~90 | Zero callers dans prod | LOW — vérifier examples/ | +| C4 | `core/detection/ollama_client.py`: 5 old methods | ~150 | Remplacés par classify_element_complete() | LOW — vérifier examples/ | +| C5 | `ollama_client.py:check_ollama_available()` standalone | ~15 | 8/9 callers in examples/, 1 in VWB (duplicat D2) | LOW — VWB a sa propre copie | +| C6 | `agent_chat/app.py`: 2 Flask 410 endpoints | ~14 | Endpoints déprecated, retour 410 Gone | LOW — API contract check | +| C7 | `core/grounding/smart_resize.py` (77 lines) | 77 | Zero prod callers, DETTE-007 triple impl | LOW — 2 autres impls existent | +| C8 | PP-OCRv5 (paddleocr+paddlepaddle venv) | ~deps | 0 .py imports across entire project | LOW — venv deps uninstall | + +**Total C-MORT**: ~843 lignes code + venv deps + +--- + +## B-ORPHELIN (5 items, ~537 lignes) + +| # | Fichier/Zone | Lignes | Preuve B | Action | +|---|-------------|--------|----------|--------| +| B1 | VWB ui_detection_service OmniParser path | ~70 | HARD-DISABILÉ `_omniparser_available = False # DÉSACTIVÉ` | Conserver, documenter activation condition | +| B2 | `fusion_engine.py:_fuse_concat_projection()` | ~15 | Stub, prévu pour future fusion modes | Conserver, marque PROJECTION | +| B3 | `omniparser_adapter.py` | ~429 | BRANCHABLE DORMANT, try/except import | Conserver, documenter activation condition | +| B4 | `CorrectionStatus.DEPRECATED` enum value | ~3 | Enum value, pas supprimable sans break | Conserver, marque DEPRECATED | +| B5 | `catalog_routes_v2_vlm.py:check_ollama_available()` | ~20 | Duplicat de ollama_client.py (D2) | DÉCISION Dom : unifier ou garder 2 impls | + +--- + +## Duplicats Identifiés (4) + +| # | Item | Impl 1 | Impl 2 | Statut | +|---|------|--------|--------|--------| +| D1 | smart_resize | smart_resize.py (C7) | ui_detection_service.py resize | C-MORT vs WIRED | +| D2 | check_ollama_available | ollama_client.py (C5) | catalog_routes_v2_vlm.py (B5) | C-MORT vs B-ORPHELIN | +| D3 | ground_element | seeclick_adapter.py (B→provenance?) | ollama_client.py old method | B vs C4 | +| D4 | 7 deprecated config classes | core/config.py (C2) | SystemConfig (WIRED) | C-MORT vs A-WIRED | + +--- + +## Classification Updates (C→A upgrades confirmés) + +| Item | Prior Status | Current Status | Preuve upgrade | +|------|-------------|---------------|---------------| +| autonomous_planner.py | C | **A** | Migrated to agent_chat/, wired by app.py | +| seeclick_adapter.py | C | **B** | Lazy re-export, `_seeclick_available` never consulted mais impl ground_element indépendante | +| grounding/server.py | C | **A** | HTTP service port 8200, standalone Flask | +| get_grounding_profile() | C | **A** | Wired via ollama_client.py:303-304 lazy import | + +--- + +## OmniParser — Classification 7 Zones + +| # | Zone | Statut | Activation | Fallback | +|---|------|---------|-----------|----------| +| 1 | SoM engine (som_engine.py) | **A-WIRED** | YOLO weights direct | docTR OCR | +| 2 | resolve_engine (_get_omniparser) | **B-DORMANT** | Lazy Optional[bool] | None → skipped | +| 3 | phase25_analyzer (_OmniParserSafeWrapper) | **B-DORMANT** | Lazy import + healthcheck | docTR-only | +| 4 | api_stream healthcheck | **A-WIRED** | Always 200 omniparser_available:bool | degraded:true | +| 5 | omniparser_adapter.py | **B-DORMANT** | Import phase25 & resolve | empty list | +| 6 | VWB ui_detection_service.py | **B-HARD-DISABILÉ** | `_omniparser_available = False # DÉSACTIVÉ` | ui-detr-1 only | +| 7 | VWB catalog_routes_v2_vlm.py | **B-DORMANT** | try/except, flips True si installé | VLM fallback | + +--- + +## QG-Gated Lots (proposé, nécessite GO Dom) + +### Lot 1 — C-MORT Low Risk (suppression directe après GO Dom) +- C1 deploy_windows.py +- C7 smart_resize.py +- C6 agent_chat 410 endpoints +- C8 PP-OCRv5 venv deps uninstall + +### Lot 2 — C-MORT Medium Risk (vérification examples/ avant suppression) +- C2 7 deprecated config classes (vérifier .env) +- C3 owl_detector 4 methods (vérifier examples/) +- C4 ollama_client 5 old methods (vérifier examples/) +- C5 check_ollama_available standalone (vérifier VWB duplicat) + +### Lot 3 — Duplicats Unification (décision Dom) +- D1 smart_resize: unifier ou garder 2 impls +- D2 check_ollama_available: unifier VWB vs core +- D3 ground_element: unifier seeclick vs ollama +- D4 config classes: supprimer deprecated vs garder compat + +--- + +**Prochaine étape**: Dom review → GO/NOGO par lot → exécution séquentielle avec tests verification après chaque lot. diff --git a/docs/DESIGN_NAVIGATE_COORDS_CONSUMPTION_2026-07-02.md b/docs/DESIGN_NAVIGATE_COORDS_CONSUMPTION_2026-07-02.md new file mode 100644 index 000000000..a23d36e81 --- /dev/null +++ b/docs/DESIGN_NAVIGATE_COORDS_CONSUMPTION_2026-07-02.md @@ -0,0 +1,218 @@ +# Design Note — NavigateCoords Consumption Gap (Write-Only) + +**Auteur**: Qwen +**Date**: 2026-07-02 +**Statut**: DESIGN NOTE — pas de câblage sans GO Dom +**Référence**: `tests/unit/test_coords_consumption_gap.py` (10 tests PASSING documenting the gap) + +--- + +## Executive Summary + +Le module navigation (`core/navigation`) produit des coords normalisés (`NavigateCoords`) via OCR/VLM, les stocke dans `replay_state["variables"]`, mais **aucun consommateur** dans le runtime n'utilise ces coords. Le résultat est un pattern **write-only** : coords générés mais jamais consommés par les actions suivantes (click/type). + +Trois gaps structurels confirmés par code lecture : + +--- + +## Gap A — Compiler Produces Literals, Not Templates + +**Localisation**: `replay_engine.py:1832-1846` (`_edge_to_normalized_actions`) + +**Problème**: Pour `mouse_click`, le compiler bake `x_pct` et `y_pct` comme **floats littéraux** depuis `by_position` : + +```python +# replay_engine.py:1843-1846 +normalized["type"] = "click" +normalized["x_pct"] = x_pct # float littéral (ex: 0.15) +normalized["y_pct"] = y_pct # float littéral (ex: 0.07) +``` + +Ces floats sont **hardcodés** dans le step definition. Il n'existe pas de mécanisme pour référencer les coords navigate via templates comme `{{navigate_login_coords.x_pct}}`. + +**La substitution existante ne couvre pas ce cas** : +- `_substitute_variables()` → `${var}` → appliqué uniquement à `text_input.text` +- `_RUNTIME_VAR_PATTERN` → `{{var.field}}` → compilé regex, **jamais appliqué à `x_pct/y_pct`** + +**Conséquence**: Un navigate step qui résolve coords login à (0.15, 0.07) ne peut PAS injecter ces coords dans un click step suivant, car le click step a ses propres `x_pct/y_pct` hardcodés. + +--- + +## Gap B — Zero Consumers in Runtime + +**Localisation**: `core/navigation/__init__.py:43-113` (`_handle_navigate_action`) + +**Problème**: `_handle_navigate_action` stocke coords dans `replay_state["variables"]` : + +```python +# core/navigation/__init__.py:100-105 +if result.login_coords: + variables[login_var] = result.login_coords.to_dict() +# → {"x_pct": 0.15, "y_pct": 0.07, "method": "ocr_anchor"} +``` + +**Zéro consommateur** : aucun action handler (click, type, double_click, right_click) lit `variables["navigate_login_coords"]` pour résoudre ses propres coords. Chaque action utilise exclusivement `by_position` depuis son edge definition. + +**Preuve par grep** : `navigate_login_coords|navigate_password_coords|navigate_submit_coords` apparaît uniquement dans : +- `core/navigation/__init__.py` (write) +- `tests/unit/test_*.py` (test verification) +- **0 occurrences** dans `replay_engine.py` action dispatch ou `api_stream.py` action handlers + +--- + +## Gap C — Navigate Edge → Empty Actions List + +**Localisation**: `replay_engine.py:1806-1955` (`_edge_to_normalized_actions`) + +**Problème**: Le type `navigate` est dans `_ALLOWED_ACTION_TYPES` (ligne 44) et possède un handler câblé dans `api_stream.py` (ligne 4459-4463 via `_handle_navigate_action`). Mais `_edge_to_normalized_actions` **n'a pas de branche** pour `navigate` : + +```python +# replay_engine.py:1954-1955 (else branch) +else: + logger.warning(f"Type d'action inconnu : {action_type}") + return [] +``` + +**Conséquence** : Quand le BFS traverse un edge navigate, `_edge_to_normalized_actions(edge, params)` retourne `[]`. L'action navigate est **skippée** dans le path. Le handler existe dans `api_stream.py` mais est **inaccessible** car le normalized action dict n'est jamais produit. + +**Paradoxe** : Le navigate handler est câblé et fonctionnel, mais le pipeline edge→action le bloque à l'entrée. + +--- + +## Options de Résolution + +### Option 1 — Compiler Injection (modifier `_edge_to_normalized_actions`) + +**Approche**: Ajouter une branche `navigate` dans `_edge_to_normalized_actions` qui produit un normalized action dict. Modifier les actions click/type pour permettre des template refs `{{navigate_login_coords.x_pct}}` dans `x_pct/y_pct`, avec résolution runtime. + +```python +# Option 1 — Branch navigate dans _edge_to_normalized_actions +elif action_type == "navigate": + normalized["type"] = "navigate" + normalized["parameters"] = { + "action": action_params.get("action", "login"), + "login_coords_var": action_params.get("login_coords_var", "navigate_login_coords"), + "password_coords_var": action_params.get("password_coords_var", "navigate_password_coords"), + "submit_coords_var": action_params.get("submit_coords_var", "navigate_submit_coords"), + } + return [normalized] +``` + +**+ Avantages** : +- Minimal change — 1 branche ajoutée + template resolution dans click/type +- Compatible avec handler existant (`_handle_navigate_action`) +- BFS path inclut navigate → handler appelé → coords stockés → consommés + +**– Risques** : +- Template resolution dans `x_pct/y_pct` nécessite modification de click/type dispatch +- Float vs string : `{{navigate_login_coords.x_pct}}` résout en `"0.15"` (string), pas `0.15` (float) — nécessite conversion +- Ordonnancement : navigate doit s'exécuter AVANT les actions click/type qui consomment ses coords — scheduling implication + +### Option 2 — Declarative YAML Templates (step definitions avec coords_template) + +**Approche**: Ajouter un champ `coords_template` dans les step YAML definitions. Au runtime, le template est résolu par substitution des variables navigate. + +```yaml +# Option 2 — YAML step definition avec coords_template +steps: + - action: navigate + parameters: + action: login + login_coords_var: navigate_login_coords + - action: mouse_click + coords_template: "{{navigate_login_coords}}" + # Au runtime : x_pct/y_pct résolus depuis navigate_login_coords dict +``` + +**+ Avantages** : +- Déclaratif — coords templates dans YAML, pas hardcoded +- Séparation compiler/runtime : compiler produit templates, runtime résout +- Extensible à autres types de coords (search, dossier) + +**– Risques** : +- Plus de changement : schema YAML + template resolver + compiler modifications +- Retro-compatibilité : workflows existants sans coords_template doivent continuer à fonctionner (fallback by_position) +- Validation : templates malformés → runtime errors subtiles + +--- + +## Table Comparative + +| Critère | Option 1 (Compiler Injection) | Option 2 (YAML Templates) | +|---------|-------------------------------|---------------------------| +| Changement code | Small — 1 branch + template resolve | Medium — schema + resolver + compiler | +| Retro-compat | Full — by_position fallback intact | Full — fallback by_position si pas de template | +| Ordonnancement | Navigate avant click (BFS order) | Navigate avant click (step order) | +| Extensibilité | Navigate-specific | General — coords_template applicable à tout | +| Risque runtime | Float/string conversion | Template validation errors | +| Tests impact | 1-3 nouveaux tests | 5-8 nouveaux tests (schema + resolver) | +| GO Dom needed | YES | YES | +| Timeline | ~2h implementation | ~4h implementation + schema design | + +--- + +## Test Rouge Proposal + +**Objectif**: Démontrer Gap C avec 1 test unitaire qui montre qu'un edge navigate produit une empty action list. + +```python +# tests/unit/test_coords_consumption_gap.py — ajout proposé + +def test_gap_c_navigate_edge_produces_empty_actions(): + """Gap C: _edge_to_normalized_actions returns [] for navigate edge. + + Prove: navigate is in _ALLOWED_ACTION_TYPES but has no branch + in _edge_to_normalized_actions → falls into else → empty list. + """ + from agent_v0.server_v1.replay_engine import _edge_to_normalized_actions + + # Minimal mock edge with navigate action type + edge = MockEdge( + edge_id="e1", + from_node="start", + to_node="login", + action=MockAction( + type="navigate", + target=None, + parameters={"action": "login"}, + ), + ) + result = _edge_to_normalized_actions(edge, {}) + + # GAP: navigate edge produces zero actions + assert result == [], f"Expected empty list, got {result}" + # This proves the handler in api_stream.py is unreachable +``` + +**Note**: Ce test est un **red flag** — il doit FAIL quand le gap est résolu (navigate branch ajoutée → result ≠ []). Il sert de guardrail : si quelqu'un câble navigate sans résoudre les gaps A+B, le test rouge continue à signaler le problème. + +--- + +## Decision Required from Dom + +**⚠️ PAS DE CÂBLAGE SANS GO DOM** + +Ce design note documente les gaps et propose des options. La décision appartient à Dom : + +1. **Option préférée** : 1 (compiler injection) ou 2 (YAML templates) ? +2. **Timeline** : implémenter maintenant (POC phase) ou post-POC ? +3. **Scope** : navigate login only, ou general coords template system ? +4. **Test rouge** : ajouter le test gap C maintenant (documentation) ou attendre GO ? + +--- + +## Appendix — Code References + +| Fichier | Lignes | Rôle | +|---------|--------|------| +| `replay_engine.py:44` | `_ALLOWED_ACTION_TYPES` includes "navigate" | Allowlist | +| `replay_engine.py:1806-1955` | `_edge_to_normalized_actions` — no navigate branch | Gap C | +| `replay_engine.py:1843-1846` | mouse_click bakes literal x_pct/y_pct | Gap A | +| `core/navigation/__init__.py:43-113` | `_handle_navigate_action` — writes coords to variables | Gap B (write) | +| `core/navigation/action_resolver.py:47-62` | `NavigateCoords` dataclass definition | Data model | +| `api_stream.py:4459-4463` | navigate handler dispatch | Wired but unreachable | +| `tests/unit/test_coords_consumption_gap.py` | 10 tests documenting write-only gap | Evidence | + +--- + +*Qwen — design note, pas wiring. GO Dom required.*