Files
rpa_vision_v3/docs/DESIGN_NAVIGATE_COORDS_CONSUMPTION_2026-07-02.md
Dom 882e4e1f3a
Some checks failed
tests / Lint (ruff + black) (push) Failing after 1m51s
tests / Tests unitaires (sans GPU) (push) Has been cancelled
tests / Tests sécurité (critique) (push) Has been cancelled
docs(design+audit): navigate coords consumption gaps + dead code C-MORT audit
DESIGN_NAVIGATE_COORDS_CONSUMPTION_2026-07-02.md: 3 structural gaps
with code evidence (Gap A/B/C), 2 resolution options with comparative
table, test rouge proposal.

AUDIT_CODE_MORT_2026-07-02.md: 8 C-MORT, 5 B-ORPHELIN, 4 duplicats,
3 QG-gated suppression lots (~1900 lines).
2026-07-02 13:02:04 +02:00

9.5 KiB
Raw Blame History

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 :

# 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"] :

# 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 :

# 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.

# 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.

# 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.

# 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.