Files
rpa_vision_v3/docs/AUDIT_CONTROLES_DEBRANCHES_2026-05-08.md
2026-05-09 11:32:52 +02:00

42 KiB
Raw Permalink Blame History

AUDIT — Contrôles débranchés (serveur)

Date : 2026-05-08 Branche : feature/qw-suite-mai HEAD : 56e869c46 Périmètre : agent_v0/server_v1/ + core/* importés. Client exclu.

1. Inventaire des fichiers audités

Fichier Lignes
agent_v0/server_v1/init.py 0
agent_v0/server_v1/visual_wait.py 54
agent_v0/server_v1/monitor_router.py 99
agent_v0/server_v1/replay_failure_logger.py 143
agent_v0/server_v1/vm_controller.py 143
agent_v0/server_v1/loop_detector.py 154
agent_v0/server_v1/worker_stream.py 172
agent_v0/server_v1/workflow_replay.py 185
agent_v0/server_v1/safety_checks_provider.py 195
agent_v0/server_v1/session_worker.py 253
agent_v0/server_v1/agent_registry.py 296
agent_v0/server_v1/replay_memory.py 323
agent_v0/server_v1/execution_plan_runner.py 373
agent_v0/server_v1/audit_trail.py 393
agent_v0/server_v1/replay_learner.py 395
agent_v0/server_v1/run_worker.py 397
agent_v0/server_v1/live_session_manager.py 464
agent_v0/server_v1/task_planner.py 596
agent_v0/server_v1/chat_interface.py 622
agent_v0/server_v1/replay_verifier.py 632
agent_v0/server_v1/domain_context.py 1020
agent_v0/server_v1/replay_engine.py 1643
agent_v0/server_v1/resolve_engine.py 2585
agent_v0/server_v1/stream_processor.py 5137
agent_v0/server_v1/api_stream.py 5445

Total serveur : 21 719 lignes.

Modules core/ effectivement importés par le serveur :

  • core.detection.omniparser_adapter (resolve_engine.py:272)
  • core.detection.ollama_client, core.detection.vlm_config (resolve_engine.py:502-503, api_stream.py:790)
  • core.detection.som_engine (resolve_engine.py:977)
  • core.embedding.clip_embedder (resolve_engine.py:1658)
  • core.anonymisation (api_stream.py:47)
  • core.auth.credential_vault, core.auth.auth_handler (api_stream.py:84-85)
  • core.llm.ocr_extractor (api_stream.py:824)
  • core.models.workflow_graph (api_stream.py:845)
  • core.workflow.shadow_observer, shadow_validator, execution_plan, execution_compiler, ir_builder (api_stream.py:1601-2656)
  • core.federation.learning_pack, faiss_global (api_stream.py:4647-4690)
  • core.learning.target_memory_store (replay_memory.py:62)

2. Findings par catégorie

2.1 Validations désactivées ou non consommées

F2.1.1 — Pré-check OCR sémantique (_validate_text_at_position) gardé par flag off-by-default

  • agent_v0/server_v1/api_stream.py:4519-4533
  • Citation :
_text_precheck_enabled = os.environ.get(
    "RPA_ENABLE_TEXT_PRECHECK", "false"
).lower() in ("true", "1", "yes")
if _text_precheck_enabled and result and result.get("resolved"):
    _by_text = (request.target_spec.get("by_text") or "").strip()
    if _by_text:
        from agent_v0.server_v1.resolve_engine import _validate_text_at_position
        _is_valid, _observed, _ocr_ms = _validate_text_at_position(
            tmp_path,
            float(result.get("x_pct", 0) or 0),
            float(result.get("y_pct", 0) or 0),
            _by_text,
            effective_w,
            effective_h,
        )
  • Statut : off-by-default — l'appel à _validate_text_at_position ne s'exécute QUE si RPA_ENABLE_TEXT_PRECHECK=true. La fonction reste définie en resolve_engine.py:2239-2289 mais n'est jamais consommée en production tant que la variable env n'est pas positionnée.

F2.1.2 — _validate_text_at_position retourne True en cas d'échec OCR (politique permissive)

  • agent_v0/server_v1/resolve_engine.py:2253-2261, 2280, 2287-2289
  • Citation :
Politique en cas d'échec OCR (lib absente, exception) : retourne
(True, "", 0.0) pour ne pas bloquer le flux. Mieux vaut un faux positif
rare qu'une régression bloquante introduite par la validation elle-même.
"""
reader = _get_validation_ocr_reader()
if reader is None:
    return True, "", 0.0
if not expected_text or not expected_text.strip():
    return True, "", 0.0
[...]
if x2 - x1 < 10 or y2 - y1 < 10:
    return True, "", 0.0
[...]
except Exception as e:
    logger.warning("[REPLAY] _validate_text_at_position erreur (%s) — pas de blocage", e)
    return True, "", 0.0
  • Statut : actif (quand le flag global est on) mais résultat documenté comme intentionnellement permissif sur tout chemin d'erreur. Une erreur OCR = pas de blocage.

F2.1.3 — _pre_check_screen_state (CLIP) bascule match=True sur exception

  • agent_v0/server_v1/replay_engine.py:1374-1379
  • Citation :
except Exception as e:
    # Ne jamais bloquer le replay en cas d'erreur du pre-check
    logger.error(f"Pre-check échoué (non bloquant): {e}")
    result["match"] = True  # Fallback permissif
    result["reason"] = f"precheck_error: {e}"
  • Statut : actif mais permissif explicitement (commentaire # Fallback permissif). Toute exception interne du pre-check CLIP renvoie match=True et l'action passe.

F2.1.4 — Vérification post-action (verify_action/verify_with_critic) skippée pour type/key_combo/wait et popup gérée

  • agent_v0/server_v1/api_stream.py:3394-3399
  • Citation :
action_type_for_verify = (original_action or {}).get("type", "unknown")
skip_verify = action_type_for_verify in ("type", "key_combo", "wait")
# Skip aussi la vérification serveur si l'agent a déjà géré la popup
skip_verify = skip_verify or agent_handled_popup
verification = None
if report.success and screenshot_after and not skip_verify:
  • Statut : actif. La vérification visuelle post-action ne tourne que pour les click et seulement si agent_handled_popup est faux.

F2.1.5 — _validate_match_context consommé uniquement dans la branche template strict

  • agent_v0/server_v1/resolve_engine.py:201, 1864
  • Le seul appel est resolve_engine.py:1864 à l'intérieur du mode strict, pour le fallback template. Pas appelé dans _resolve_with_precompiled_order (V4) ni dans le mode classique.
  • Statut : actif sur un seul chemin de la cascade.

2.2 Garde-fous court-circuités (seuils, flags, conditions)

F2.2.1 — Drift > 0.20 ignoré quand template_matching ≥ 0.95 ou hybrid_text_direct ≥ 0.80

  • agent_v0/server_v1/resolve_engine.py:2367-2390
  • Citation :
if dx > _RESOLUTION_MAX_DRIFT or dy > _RESOLUTION_MAX_DRIFT:
    [...]
    _high_confidence_method = (
        (method.startswith("template_matching") and score >= 0.95)
        or (method == "hybrid_text_direct" and score >= 0.80)
    )
    if _high_confidence_method:
        logger.info(
            "[REPLAY] Drift (%.3f, %.3f) > %.2f IGNORÉ : score=%.3f "
            "sur %s — résultat visuel fiable, on l'utilise",
            dx, dy, _RESOLUTION_MAX_DRIFT, score, method,
        )
        return result
  • Statut : actif. Exemption introduite par 35b27ae49 (template ≥ 0.95) puis élargie par 40440f1ca à hybrid_text_direct ≥ 0.80. La garde de drift est neutralisée pour deux familles de méthodes.

F2.2.2 — Drift check inactif si fallback x/y_pct ressemblent à un placeholder 0.5/0.5 ou 0.0/0.0

  • agent_v0/server_v1/resolve_engine.py:2359-2363
  • Citation :
_has_recorded_coords = (
    fallback_x_pct > 0.001
    and fallback_y_pct > 0.001
    and not (abs(fallback_x_pct - 0.5) < 0.001 and abs(fallback_y_pct - 0.5) < 0.001)
)
if _has_recorded_coords:
  • Statut : actif. Sans coords enregistrées exploitables, la garde drift est inerte.

F2.2.3 — Self-healing Win+D au retry 1 désactivé (revert)

  • agent_v0/server_v1/replay_engine.py (commit 22c0a2ba6, branche next_retry == 2 conservée seule)
  • Citation post-revert :
if next_retry == 2:
    # Retry 2 : injecter un wait de 2s avant l'action
    wait_action = {
        "action_id": f"wait_retry_{uuid.uuid4().hex[:6]}",
        "type": "wait",
        "duration_ms": 2000,
    }
    actions_to_insert.append(wait_action)
  • Statut : retiré. Aucune injection de gesture de récupération avant le retry 1 — boucle directe sur la même action.

F2.2.4 — Pre-check skip si heartbeat > 10s ou timeout > 500ms

  • agent_v0/server_v1/api_stream.py:999-1001, 3100-3130
  • Citation :
_HEARTBEAT_MAX_AGE_SECONDS = 10.0
_PRECHECK_SIMILARITY_THRESHOLD = 0.85
[...]
if age <= _HEARTBEAT_MAX_AGE_SECONDS:
    [...]
    precheck_result = await asyncio.wait_for(
        loop.run_in_executor(...),
        timeout=0.5,  # Max 500ms pour le pre-check
    )
except asyncio.TimeoutError:
    logger.warning(...)
    precheck_result = None
  • Statut : actif. Le pre-check CLIP est skip silencieusement si heartbeat trop ancien ou si l'embed prend > 500ms.

F2.2.5 — VLM Quick Find : confidence < 0.3 → ignoré (résultat valide perdu sous le seuil)

  • agent_v0/server_v1/resolve_engine.py:655-662
  • Citation :
if x_pct is None or y_pct is None or confidence < 0.3:
    logger.info(
        "VLM Quick Find : élément non trouvé ou confiance trop basse "
        "(%.1fs, confidence=%.2f) pour '%s'",
  • Statut : actif. Tout retour VLM avec confidence < 0.3 est dropé.

F2.2.6 — Image client tronquée → remplacement silencieux par dernier heartbeat

  • agent_v0/server_v1/api_stream.py:4422
  • Citation :
if img.height < 800 or img.width < 1200:
    logger.warning(
        "[RESOLVE_TARGET] Image client tronquée %dx%d (declared %dx%d) — "
        "fallback heartbeat full screen",
  • Statut : actif. Toute image reçue < 1200x800 est remplacée par un screenshot heartbeat (mémoire ou disque) avant cascade. Seuil élargi par 7233df2bb (était 400x200 avant).

F2.2.7 — CLIP mismatch < 0.75 retourne resolved=False mais ne bloque qu'en mode strict avec embedding fourni

  • agent_v0/server_v1/resolve_engine.py:1655-1691
  • Citation :
clip_embedding = target_spec.get("clip_embedding")
if clip_embedding:
    [...]
    if clip_sim < 0.75:
        logger.warning(
            f"CLIP MISMATCH : sim={clip_sim:.3f} < 0.75 — "
            f"écran actuel trop différent de l'enregistrement"
        )
        return {
            "resolved": False,
            "method": "clip_mismatch",
  • Statut : actif uniquement si clip_embedding fourni ET mode strict. Pour les workflows qui n'embarquent pas l'embedding, ce filet est inerte.

2.3 Flags d'environnement avec défaut permissif

F2.3.1 — RPA_ENABLE_TEXT_PRECHECK, défaut "false"

  • agent_v0/server_v1/api_stream.py:4519-4521
  • Citation :
_text_precheck_enabled = os.environ.get(
    "RPA_ENABLE_TEXT_PRECHECK", "false"
).lower() in ("true", "1", "yes")
  • Statut : off par défaut. Sans surcharge en environnement, le pré-check OCR ne s'exécute jamais.

F2.3.2 — RPA_AUTH_DISABLED, défaut absent (auth obligatoire) mais permet de tout débrayer

  • agent_v0/server_v1/api_stream.py:107-119
  • Citation :
_AUTH_DISABLED = os.environ.get("RPA_AUTH_DISABLED", "").lower() in (
    "1", "true", "yes",
)
[...]
if _AUTH_DISABLED:
    logger.warning(
        "[SÉCURITÉ] RPA_AUTH_DISABLED=true — authentification Bearer DÉSACTIVÉE. ..."
    )
    API_TOKEN = _API_TOKEN_ENV or secrets.token_hex(32)
  • Statut : par défaut auth obligatoire, mais flag explicite documenté pour la débrayer.

F2.3.3 — RPA_LOOP_DETECTOR_ENABLED, défaut "1" (activé)

  • agent_v0/server_v1/loop_detector.py:42-47, 78-79
  • Citation :
def _env_bool_enabled(name: str) -> bool:
    val = os.environ.get(name, "1").strip().lower()
    return val not in ("0", "false", "no", "off", "")
[...]
if not _env_bool_enabled("RPA_LOOP_DETECTOR_ENABLED"):
    return LoopVerdict(detected=False)
  • Statut : on par défaut, désactivable via RPA_LOOP_DETECTOR_ENABLED=0.

F2.3.4 — RPA_SAFETY_CHECKS_LLM_ENABLED, défaut "1" (activé)

  • agent_v0/server_v1/safety_checks_provider.py:42-44, 70
  • Citation :
if safety_level == "medical_critical" and _env_bool_enabled("RPA_SAFETY_CHECKS_LLM_ENABLED"):
  • Statut : on par défaut, mais ne tourne que si safety_level == "medical_critical" dans l'action.

F2.3.5 — RPA_PII_BLUR_SERVER, défaut "true" (activé)

  • agent_v0/server_v1/api_stream.py:1023
  • Citation :
_PII_BLUR_ENABLED = os.environ.get("RPA_PII_BLUR_SERVER", "true").lower() in ("true", "1", "yes")
  • Statut : on par défaut.

2.4 Étapes de cascade neutralisées

F2.4.1 — _resolve_by_yolo défini, importé, jamais appelé

  • Définition : agent_v0/server_v1/resolve_engine.py:293
  • Import : agent_v0/server_v1/api_stream.py:4363
  • Recherche _resolve_by_yolo( dans le serveur entier : 0 site d'appel.
  • Statut : fonction morte. La détection OmniParser/YOLO n'est plus dans la cascade exécutée.

F2.4.2 — _resolve_with_precompiled_order (V4) appelé seulement si target_spec["resolve_order"] présent

  • agent_v0/server_v1/resolve_engine.py:1613-1635
  • Citation :
resolve_order = target_spec.get("resolve_order")
if resolve_order and isinstance(resolve_order, list):
    [...]
    result = _resolve_with_precompiled_order(...)
    if result and result.get("resolved"):
        return result
    [...]
    logger.info(
        "V4 resolve : toutes les méthodes pré-compilées ont échoué, "
        "fallback cascade legacy"
    )
  • Statut : actif quand un plan V4 est compilé ; sinon inerte. Fallback cascade legacy systématique en cas d'échec.

F2.4.3 — Étape grounding VLM directe conditionnée à by_text_source ∈ {ocr, vlm} ET has_window

  • agent_v0/server_v1/resolve_engine.py:1696-1715
  • Citation :
by_text_source = target_spec.get("by_text_source", "")
has_window = bool(target_spec.get("window_capture", {}).get("rect"))

if by_text_strict and by_text_source in ("ocr", "vlm") and has_window:
    grounding_result = _resolve_by_grounding(...)
  • Statut : actif sur ces deux conditions seulement. Si by_text_source est vide ou autre, ou si window_capture.rect absent, le grounding direct est sauté.

F2.4.4 — _resolve_by_ocr_text (hybrid_text_direct) reconnecté le 2026-05-06 dans la cascade strict (commit 1cbec2806)

  • agent_v0/server_v1/resolve_engine.py:1750-1790
  • Citation du commit :
fix(resolve): rebrancher hybrid_text_direct dans _resolve_target_sync
[...] la fonction _resolve_by_ocr_text (resolve_engine.py:1447) existait
déjà mais [...] n'était appelée QUE depuis le runtime V4 [...]
  • Statut : actif depuis 1cbec2806. Avant : étape OCR direct n'était pas dans la cascade strict pour les workflows non-V4.

F2.4.5 — Template matching mode strict : seuil 0.90 (étape 2 fallback)

  • agent_v0/server_v1/resolve_engine.py:1733, 1847-1875
  • Citation :
result = _resolve_by_template_matching(
    [...]
    confidence_threshold=0.90,
)
if result:
    score = result.get("score", 0)
    # Score >= 0.95 : match quasi-parfait, pas besoin de valider le contexte
    if score >= 0.95:
        [...]
        return result
    elif _validate_match_context(result, fallback_x_pct, fallback_y_pct, target_spec):
        [...]
  • Statut : actif. _validate_match_context skippé si score ≥ 0.95.

2.5 Fonctions améliorantes définies mais non appelées

F2.5.1 — _resolve_by_yolo (résolution OmniParser+template, défini resolve_engine.py:293, jamais appelé)

  • Voir F2.4.1.

F2.5.2 — _fuzzy_match importé dans api_stream.py mais jamais appelé

  • Définition : agent_v0/server_v1/resolve_engine.py:2086
  • Import : agent_v0/server_v1/api_stream.py:4372
  • Recherche _fuzzy_match( dans api_stream.py : 0 appel hors la ligne d'import.
  • Statut : import mort. Le fuzzy match utilisé en runtime est _text_match_fuzzy (resolve_engine.py:2213), distinct.

F2.5.3 — _get_omniparser, _build_target_description importés dans api_stream.py mais non appelés directement

  • agent_v0/server_v1/api_stream.py:4362, 4365
  • Statut : imports utilisés indirectement via _resolve_target_sync qui les appelle en interne. Pas un finding bloquant — pas de fonction améliorante hors usage interne.

2.6 Marqueurs de dette (TODO/FIXME/disabled/démo) dans le serveur

F2.6.1 — TODO task_planner.py:400

  • Citation : # Boucle : TODO — lister les éléments puis itérer
  • Statut : commentaire de dette dans task_planner.py.

F2.6.2 — Commentaire « 8 mai 2026 : désactivé par défaut pour la démo GHT »

  • agent_v0/server_v1/api_stream.py:4512
  • Citation :
# 8 mai 2026 : désactivé par défaut pour la démo GHT. Calibrage du
# radius_px et min_token_ratio à finaliser post-démo (cf. rapport
# docs/E2E_TEST_RUN_2026-05-08.md). Le pré-check était trop strict
# sur les onglets à 2 tokens (Examens cliniques, Synthèse Urgences)
# → faux rejets → cascade locale Léa V1 → clic au pif. Réactivable
# via env RPA_ENABLE_TEXT_PRECHECK=true. Le code et les tests
# restent en place pour reprise post-démo.
  • Statut : marqueur démo explicite.

F2.6.3 — Mention « Fallback permissif » dans _pre_check_screen_state

  • agent_v0/server_v1/replay_engine.py:1377result["match"] = True # Fallback permissif

F2.6.4 — Ré-introduction explicite « non-bloquant » dans _validate_text_at_position

  • agent_v0/server_v1/resolve_engine.py:2288logger.warning("[REPLAY] _validate_text_at_position erreur (%s) — pas de blocage", e)

F2.6.5 — Mode autonome → pause_for_human ignorée silencieusement

  • agent_v0/server_v1/api_stream.py:3011-3017
  • Citation :
# Mode autonome sans safety_checks → skip (comportement legacy)
logger.info(
    "pause_for_human ignorée (mode autonome) — replay %s continue",
    owning_replay["replay_id"] if owning_replay else "?"
)
queue.pop(0)
_replay_queues[session_id] = queue
continue
  • Statut : actif. La supervision n'est utilisée que si execution_mode != "autonomous" ou si safety_level/safety_checks déclarés. Ce câblage execution_mode → supervised a été corrigé par 7233df2bb.

F2.6.6 — Commentaire # Fallback permissif/pas de blocage cumulés

  • Présents dans 3 fonctions de validation : _pre_check_screen_state (replay_engine.py:1377), _validate_text_at_position (resolve_engine.py:2288), et politique de _get_validation_ocr_reader (resolve_engine.py:2196).

3. Commits récents qui ont désactivé des contrôles

(Sur les 20 derniers commits du dossier agent_v0/server_v1/)

  • 56e869c46 (8 mai) — fix(replay): bug TypeError log + flag pré-check OCR off par défaut (démo GHT) : ajoute RPA_ENABLE_TEXT_PRECHECK (default "false") qui débraye intégralement l'appel à _validate_text_at_position.
  • 40440f1ca (7 mai) — fix(replay): cure régression b584bbabc — fallback recorded_coords aveugle : restaure resolved=False sur drift trop grand (annule le fallback aveugle introduit par b584bbabc) ; étend l'exemption drift à hybrid_text_direct ≥ 0.80 (resolve_engine.py:2380-2390).
  • 7233df2bb (7 mai) — fix(replay): câblage execution_mode supervised + seuil large fallback heartbeat : élargit le seuil de détection image tronquée à < 1200×800 (était < 400×200) → fallback heartbeat plus fréquent ; force execution_mode='supervised' par défaut quand non précisé.
  • f62fda575 (7 mai) — fix(stream): /resolve_target — fallback heartbeat full si image client tronquée : introduit le remplacement silencieux de l'image client par un heartbeat disque/mémoire si tronquée.
  • 22c0a2ba6 (6 mai) — revert: désactiver self-healing Win+D auto (cercle vicieux) : retire l'injection automatique de Win+D au retry 1 sur verification_failed/no_screen_change.
  • c969f93a2 (6 mai) — fix(replay): self-healing Win+D auto au retry 1 : commit de la fonctionnalité, reverté par 22c0a2ba6.
  • 1cbec2806 (6 mai) — fix(resolve): rebrancher hybrid_text_direct dans _resolve_target_sync : reconnecte _resolve_by_ocr_text dans la cascade strict, qui auparavant n'était appelé que par le chemin V4 pré-compilé (non actif dans la majorité des workflows).
  • b584bbabc (1 mai) — fix(stream): robustesse proxy VWB→streaming + ciblage textuel pour démo UHCD : avait remplacé le rejet strict du drift par un fallback_recorded_coords (resolved=True). Reverté factuellement par 40440f1ca le 7 mai.
  • 35b27ae49 (2 mai) — fix(stream+vwb): chaîne replay robuste — auth, anchor type_text, lock async, drift, prompt LLM : introduit l'exemption drift pour template_matching ≥ 0.95 (point d'entrée du finding F2.2.1).

4. Pistes prioritaires (P0 « pré-check OCR rejette systématiquement »)

Findings directement reliés au P0 (motif factuel : « pré-check OCR rejette systématiquement, contrôles débranchés suite à checkout antérieur ») :

  1. F2.3.1 + F2.6.2agent_v0/server_v1/api_stream.py:4519-4521 : flag RPA_ENABLE_TEXT_PRECHECK à défaut "false". C'est le geste d'extinction explicite mentionné dans le commit 56e869c46 ; le pré-check OCR ne peut rejeter quoi que ce soit en l'état tant que la variable d'environnement n'est pas positionnée à "true" côté service rpa-streaming. Confirme directement la piste signalée.

  2. F2.1.1agent_v0/server_v1/resolve_engine.py:2239-2289 (_validate_text_at_position) + agent_v0/server_v1/api_stream.py:4525-4533 (point d'appel) : la fonction est conservée en place mais son point d'appel est conditionné par F2.3.1. Tant que le flag est off, la fonction est définie mais non consommée — état exact « contrôle débranché ».

  3. F2.6.5agent_v0/server_v1/api_stream.py:3011-3017 : pause_for_human ignorée (mode autonome) — replay continue. Si execution_mode n'est pas propagé jusqu'au replay (cf. commit 7233df2bb qui corrige ce câblage), la pause supervisée censée intercepter un rejet OCR est skipée et la file d'actions continue. Combiné à F2.3.1, donne le motif observé : « rejets pré-check silencieux → cascade locale Léa → clic au pif » (extrait commit message 56e869c46).

5. Findings côté CLIENT (agent_v0/agent_v1/core/executor.py)

Fichier audité : agent_v0/agent_v1/core/executor.py (2893 lignes). Aucune occurrence de RPA_ENABLE_*, RPA_DISABLE_*, if False: ou if 0: dans ce fichier.

5.1 Validations désactivées ou non consommées

Aucun finding (le client ne contient aucun bloc de validation neutralisé : la pré-vérif titre fenêtre, _check_and_pause_on_system_dialog et la cascade Observer→Policy sont toutes appelées en flux nominal).

5.2 Garde-fous court-circuités (seuils, flags, conditions)

F5.2.1 — _check_and_pause_on_system_dialog fail-closed sur exception (durcissement, pas une désactivation)

  • agent_v0/agent_v1/core/executor.py:2001-2043
  • Citation :
except Exception as e:
    # Fix P0-D : fail-closed (principe "faux positif tolérable,
    # faux négatif catastrophique"). [...]
    self._system_dialog_pause = {
        "category": "unknown_check_failed",
        [...]
    }
    [...]
    return True
  • Statut : actif. C'est un fail-closed (toute erreur de détection → pause supervisée), pas un fallback permissif. Listé pour traçabilité.

F5.2.2 — Seuil template-matching _find_text_on_screen durci à 0.75

  • agent_v0/agent_v1/core/executor.py:2367
  • Citation :
threshold = 0.75  # Démo GHT 8 mai — éviter faux positifs (placeholders italiques, tabs voisins). En dessous, mieux vaut tomber en mode apprentissage humain qu'un clic au pif.
  • Statut : actif. Seuil élevé en démo GHT (commit 7847a0e82, 7 mai) — under threshold = pas de match retourné. Pas une désactivation, un durcissement.

F5.2.3 — Skip conditional_on_window : action acquittée success=True quand le dialogue n'est pas apparu

  • agent_v0/agent_v1/core/executor.py:567-592
  • Citation :
if not match:
    [...]
    print(
        f"    [SKIP] Dialogue '{cond_window}' absent → action skippée"
    )
    result["success"] = True
    result["warning"] = "conditional_skipped"
    return result
  • Statut : actif. Comportement attendu (skip explicite avec warning=conditional_skipped) mais l'action est rapportée success=True au serveur — pas une erreur côté replay engine.

F5.2.4 — wrong_window_skipped : action skippée silencieusement après timeout apprentissage

  • agent_v0/agent_v1/core/executor.py:754-764
  • Citation :
else:
    # Timeout ou pas d'action → skipper cette action
    # L'état est peut-être déjà correct (ex: Ctrl+S
    # a sauvé sans dialogue → action de dialogue inutile)
    result["success"] = True
    result["warning"] = "wrong_window_skipped"
    logger.info(
        f"[LEA] Wrong window sans correction → skip "
        f"(l'état est peut-être déjà atteint)"
    )
  • Statut : actif. Si l'humain ne corrige pas dans les 120s (_capture_human_correction(timeout_s=120)), l'action est marquée success=True. Mêmes lignes pour policy_skip (executor.py:993-996).

F5.2.5 — Polling timeout REPLAY étendu à 30s pour démo GHT

  • agent_v0/agent_v1/core/executor.py:1786-1794
  • Citation :
# 8 mai 2026 — démo GHT : 5s → 30s. Le serveur peut exécuter
# extract_text (5-7s) PUIS dispatcher l'action suivante dans
# la même réponse HTTP. À 5s, le client coupait avant la
# réponse [...]
timeout=30,
  • Statut : actif. Marqueur démo, modification non commitée à HEAD (status Not Committed Yet 2026-05-08). Pas un contrôle débranché, contournement coûts d'IO.

5.3 Flags d'environnement avec défaut permissif

F5.3.1 — RPA_OLLAMA_HOST, défaut "localhost"

  • agent_v0/agent_v1/core/executor.py:2224 (et autres sites)
  • Citation :
ollama_host = os.environ.get("RPA_OLLAMA_HOST", "localhost")
  • Statut : configuration uniquement, pas un garde-fou.

Aucun autre flag environnemental dans le client : pas de RPA_ENABLE_*/RPA_DISABLE_* côté agent V1.

5.4 Étapes de cascade neutralisées

F5.4.1 — Self-healing désactivé côté client (revert miroir du serveur)

  • Le client n'embarque pas de logique self-healing autonome — l'injection Win+D était purement serveur (cf. F2.2.3, commit 22c0a2ba6). Côté client, la branche d'apprentissage humain (_capture_human_correction) reste l'unique recours en cas d'échec retry.
  • Statut : pas un finding spécifique au client.

5.5 Fonctions améliorantes définies mais non appelées

F5.5.1 — _handle_possible_popup (legacy clavier Enter/Escape/Tab+Enter) toujours définie

  • agent_v0/agent_v1/core/executor.py:2430-2472
  • Citation :
def _handle_possible_popup(self) -> bool:
    """Tenter de gerer une popup imprevue.
    [...]
    Strategie simple (non bloquante, max ~3s) :
    1. Essayer Enter (valide le bouton par defaut de la popup)
    2. Si ca ne marche pas, essayer Escape (ferme la popup)
    3. Si ca ne marche pas, essayer Tab + Enter [...]
  • Recherche _handle_possible_popup( dans le client : 1 site (la définition). 0 site d'appel hors la définition.
  • Statut : fonction morte côté client. Le chemin actif est _handle_popup_vlm (executor.py:2102) + Observer/Policy. La version "clavier seul" est conservée mais non câblée.

5.6 Marqueurs de dette (TODO/FIXME/disabled/démo) dans le client

F5.6.1 — Marqueur explicite « démo GHT » multiple

  • agent_v0/agent_v1/core/executor.py:1786, 1813, 1835, 2367 : 4 commentaires « 8 mai 2026 — démo GHT » documentant des changements ciblés (timeout polling, plan B pause UX, threshold FIND-TEXT 0.75).

F5.6.2 — Plusieurs except Exception: pass silencieux

  • agent_v0/agent_v1/core/executor.py:455-456, 722-723, 958-959, 1017-1018, 1127-1128, 1244-1245, 1286-1287, 2619-2620
  • Statut : 8 sites, tous sur des chemins best-effort (notification, snapshot UIA, log d'apprentissage). Aucun ne masque une décision de sécurité.

6. Autres fichiers .py modifiés < 14 jours (hors serveur/client déjà audités)

Périmètre : 23 fichiers Python modifiés depuis 2026-04-24 hors tests/, docs/, visual_workflow_builder/, web_dashboard/, agent_chat/, _archive/, tools/ et le dossier agent_v0/server_v1/ (déjà audité §1-§4) et agent_v0/agent_v1/core/executor.py (déjà audité §5).

6.1 core/execution/observe_reason_act.py (2008 lignes)

F6.1.1 — Bloc if False: désactivant le pre-check VLM par-clic

  • core/execution/observe_reason_act.py:1704-1713
  • Citation :
# --- Pas de pre-check VLM (le pipeline FAST→SMART→THINK a déjà validé) ---
if False:
    try:
        pre_check = self._verify_pre_click(x, y, target_text, target_desc)
        if not pre_check:
            print(f"⛔ [ORA/pre-check] L'élément à ({x}, {y}) ne correspond PAS à '{target_text}' — abandon du clic")
            return False
    except Exception as e:
        print(f"⚠️ [ORA/pre-check] Erreur vérification: {e}")
  • Statut : désactivation explicite par if False:. Le commentaire justifie : « le pipeline FAST→SMART→THINK a déjà validé ».

F6.1.2 — _verify_pre_click : return True permissif sur erreur HTTP/exception

  • core/execution/observe_reason_act.py:1917, 1921
  • Citation :
return True  # En cas d'erreur HTTP, on laisse passer
[...]
return True  # En cas d'erreur, on laisse passer
  • Statut : fonction conservée mais devenue inerte (cf. F6.1.1). Si réactivée, retourne True (passe le clic) sur toute erreur Ollama/réseau.

F6.1.3 — _act_type : texte vide → return True

  • core/execution/observe_reason_act.py:1740-1742
  • Citation :
if not decision.value:
    logger.warning("🎯 [ORA/type] Pas de texte à saisir")
    return True  # Vide = rien à faire, pas un échec
  • Statut : actif. Comportement documenté.

F6.1.4 — Cascade post-shortcut : timeout retourne True après ≥1 dialog géré

  • core/execution/observe_reason_act.py:1547-1550
  • Citation :
if _elapsed() >= total_timeout:
    print(f"⏳ [ORA/post-shortcut] Timeout cascade ({total_timeout:.0f}s, "
          f"{dialogs_handled} dialog(s) géré(s))")
    return True  # au moins un dialog traité → considéré OK
  • Statut : actif. Politique permissive sur timeout cascade dialogues.

F6.1.5 — Flag RPA_USE_FAST_PIPELINE, défaut "1" (activé)

  • core/execution/observe_reason_act.py:1634
  • Citation :
_use_fast = os.environ.get('RPA_USE_FAST_PIPELINE', '1') == '1'
  • Statut : on par défaut. Désactivable via env.

6.2 core/grounding/fast_pipeline.py (216 lignes)

F6.2.1 — Expression mort-née if False else screenshot_pil dans appel arbiter

  • core/grounding/fast_pipeline.py:163
  • Citation :
screenshot_pil=screenshot_pil or snapshot.elements[0] if False else screenshot_pil,
  • Statut : à cause du if False, l'expression est équivalente à screenshot_pil=screenshot_pil. La branche snapshot.elements[0] n'est jamais évaluée. Probable reliquat d'expérimentation.

6.3 core/grounding/title_verifier.py (174 lignes)

F6.3.1 — has_title_changed retourne True si un seul titre est vide

  • core/grounding/title_verifier.py:73-74
  • Citation :
if not title_before or not title_after:
    return True  # Un des deux est vide = changement
  • Statut : actif. Politique documentée — not bloquante (échec lecture titre = signal de changement).

6.4 core/grounding/ui_tars_grounder.py (288 lignes)

F6.4.1 — available toujours True sans vérifier le worker

  • core/grounding/ui_tars_grounder.py:135-137
  • Citation :
@property
def available(self) -> bool:
    return True  # Toujours disponible — le script se lance à la demande
  • Statut : actif. Pas de probe socket — la disponibilité est assumée et l'erreur réelle remonte au moment de ground().

6.5 Fichiers sans finding

Audit pattern par pattern (if False, return True/False suspects, RPA_ENABLE_/RPA_DISABLE_, # disabled, # bypass, # TODO re-enable, marqueurs démo, blocs try swallow exception sur fonction de validation) :

Fichier Findings
agent_v0/agent_v1/main.py aucun finding
agent_v0/agent_v1/network/feedback_bus.py aucun finding
agent_v0/agent_v1/ui/chat_window.py aucun finding (le marqueur # démo GHT ligne 846 documente uniquement un comportement UX)
agent_v0/agent_v1/ui/notifications.py aucun finding (idem ligne 143)
agent_v0/agent_v1/ui/paused_toast.py aucun finding
agent_v0/agent_v1/vision/capturer.py aucun finding
core/execution/input_handler.py aucun finding
core/grounding/dialog_handler.py aucun finding
core/grounding/element_signature.py aucun finding
core/grounding/fast_detector.py aucun finding
core/grounding/infigui_worker.py aucun finding
core/grounding/pipeline.py aucun finding
core/grounding/server.py aucun finding
core/grounding/shadow_learning_hook.py aucun finding
core/grounding/smart_matcher.py aucun finding
core/grounding/template_matcher.py aucun finding
core/grounding/think_arbiter.py aucun finding (available = True ligne 38 même pattern que F6.4.1, mais arbiter délègue au grounder qui détient F6.4.1)
core/knowledge/ui_patterns.py aucun finding
core/llm/ocr_extractor.py aucun finding
core/llm/t2a_decision.py aucun finding

7. Datation git des findings (toutes sections confondues)

Finding Fichier:ligne Commit Date Message commit (raccourci)
F2.1.1 api_stream.py:4519-4533 56e869c46 (gate) + 40440f1ca (corps) 2026-05-08 / 2026-05-07 flag pré-check OCR off / cure régression b584bbabc
F2.1.2 resolve_engine.py:2253-2289 40440f1ca 2026-05-07 cure régression b584bbabc
F2.1.3 replay_engine.py:1374-1379 4509038bf 2026-04-09 refactor api_stream.py 6400→3350
F2.1.4 api_stream.py:3394-3399 d5deac302 (corps) + ae65be255 (3398) 2026-03-26 / 2026-03-18 feat replay visuel VLM-first
F2.1.5 resolve_engine.py:201, 1864 4509038bf 2026-04-09 refactor api_stream.py
F2.2.1 resolve_engine.py:2367-2390 35b27ae49 (intro) → 40440f1ca (élargi) 2026-05-02 / 2026-05-07 chaîne replay robuste / cure régression
F2.2.2 resolve_engine.py:2359-2363 a21f1ea9f 2026-04-11 garde qualité résolution
F2.2.3 replay_engine.py (revert) c969f93a2 (intro) → 22c0a2ba6 (revert) 2026-05-06 self-healing Win+D / désactivation cercle vicieux
F2.2.4 api_stream.py:999-1001 d5deac302 2026-03-26 feat replay visuel VLM-first
F2.2.5 resolve_engine.py:655-662 4509038bf 2026-04-09 refactor api_stream.py
F2.2.6 api_stream.py:4422 7233df2bb 2026-05-07 câblage execution_mode + seuil heartbeat élargi
F2.2.7 resolve_engine.py:1655-1691 4509038bf 2026-04-09 refactor api_stream.py
F2.3.1 api_stream.py:4519-4521 56e869c46 2026-05-08 flag pré-check OCR off par défaut (démo GHT)
F2.3.2 api_stream.py:107-119 93ef93e56 2026-04-14 API streaming fail-closed
F2.3.3 loop_detector.py:42-47 2a51a844b 2026-05-05 LoopDetector composite
F2.3.4 safety_checks_provider.py:42-44 7c6945171 2026-05-05 SafetyChecksProvider hybride
F2.3.5 api_stream.py:1023 93ef93e56 2026-04-14 API streaming fail-closed
F2.4.1 resolve_engine.py:293 (def) 4509038bf 2026-04-09 refactor api_stream.py
F2.4.2 resolve_engine.py:1613-1635 f6ad5ff2b 2026-04-10 runtime V4 honore resolve_order
F2.4.3 resolve_engine.py:1696-1715 4509038bf 2026-04-09 refactor api_stream.py
F2.4.4 resolve_engine.py:1750-1790 1cbec2806 2026-05-06 rebrancher hybrid_text_direct
F2.4.5 resolve_engine.py:1733, 1847-1875 4509038bf 2026-04-09 refactor api_stream.py
F2.5.1 resolve_engine.py:293 4509038bf 2026-04-09 refactor api_stream.py
F2.5.2 api_stream.py:4372 (import mort) 4509038bf 2026-04-09 refactor api_stream.py
F2.5.3 api_stream.py:4362, 4365 4509038bf 2026-04-09 refactor api_stream.py
F2.6.1 task_planner.py:400 99041f011 2026-04-09 pipeline complet MACRO/MÉSO/MICRO
F2.6.2 api_stream.py:4512 56e869c46 2026-05-08 flag pré-check OCR off par défaut
F2.6.3 replay_engine.py:1377 4509038bf 2026-04-09 refactor api_stream.py
F2.6.4 resolve_engine.py:2288 40440f1ca 2026-05-07 cure régression b584bbabc
F2.6.5 api_stream.py:3011-3017 964856ab3 (intro) → 35b27ae49 / 65da55731 (raffinements) 2026-04-29 / 2026-05-02 / 2026-05-05 extract_text serveur / chaîne replay robuste / SafetyChecksProvider
F2.6.6 replay_engine.py:1377 + resolve_engine.py:2196, 2288 4509038bf + 40440f1ca 2026-04-09 / 2026-05-07 refactor / cure régression
F5.2.1 executor.py:2001-2043 (déjà fail-closed antérieur) durcissement, pas désactivation
F5.2.2 executor.py:2367 7847a0e82 2026-05-07 toast paused supervisée + threshold FIND-TEXT 0.75
F5.2.3 executor.py:567-592 (antérieur) (chemin conditionnel intégré)
F5.2.4 executor.py:754-764 (antérieur) mode apprentissage humain
F5.2.5 executor.py:1786-1794 non commité (workdir) 2026-05-08 démo GHT (uncommitted change)
F5.5.1 executor.py:2430-2472 (antérieur) legacy popup handler
F5.6.1 executor.py:1786, 1813, 1835, 2367 7847a0e82 + workdir 2026-05-07 / 2026-05-08 démo GHT
F6.1.1 observe_reason_act.py:1705 e2046837c 2026-04-25 Phase 5 — pipeline FAST→SMART→THINK dans ORA
F6.1.2 observe_reason_act.py:1917, 1921 8903f3543 2026-04-22 feat ORA — vérification pré-action VLM
F6.1.3 observe_reason_act.py:1742 0c5fffe95 2026-04-22 boucle ORA observe→raisonne→agit
F6.1.4 observe_reason_act.py:1550 487bcb861 2026-04-26 cascade post-raccourci DialogHandler/OCR
F6.1.5 observe_reason_act.py:1634 e2046837c 2026-04-25 Phase 5 — FAST→SMART→THINK dans ORA
F6.2.1 fast_pipeline.py:163 b30d4b665 2026-04-25 Phase 4 — Pipeline orchestré FAST→SMART→THINK
F6.3.1 title_verifier.py:73-74 343d6fbe9 2026-04-26 EasyOCR remplace docTR (FastDetector + TitleVerifier)
F6.4.1 ui_tars_grounder.py:137 487bcb861 2026-04-26 cascade post-raccourci DialogHandler/OCR

8. Code original avant désactivation (quand récupérable)

F2.1.1 / F2.3.1 / F2.6.2 — Pré-check OCR (api_stream.py:4519-4533)

Avant (commit 40440f1ca, 2026-05-07) — pré-check appelé inconditionnellement après résolution :

if result and result.get("resolved"):
    _by_text = (request.target_spec.get("by_text") or "").strip()
    if _by_text:
        from agent_v0.server_v1.resolve_engine import _validate_text_at_position
        _is_valid, _observed, _ocr_ms = _validate_text_at_position(
            tmp_path,
            float(result.get("x_pct", 0) or 0),
            float(result.get("y_pct", 0) or 0),
            _by_text,
            effective_w,
            effective_h,
        )

Après (HEAD 56e869c46, 2026-05-08) — pré-check gardé par flag off-by-default :

_text_precheck_enabled = os.environ.get(
    "RPA_ENABLE_TEXT_PRECHECK", "false"
).lower() in ("true", "1", "yes")
if _text_precheck_enabled and result and result.get("resolved"):
    _by_text = (request.target_spec.get("by_text") or "").strip()
    if _by_text:
        from agent_v0.server_v1.resolve_engine import _validate_text_at_position
        _is_valid, _observed, _ocr_ms = _validate_text_at_position(...)

F2.2.3 — Self-healing Win+D au retry 1 (revert)

Avant (commit c969f93a2, 2026-05-06) — code introduit (non récupéré ici via git show car branche restaurée par revert immédiat).

Après (HEAD via 22c0a2ba6, 2026-05-06) — branche next_retry == 1 retirée, seule next_retry == 2 (wait 2s) conservée :

if next_retry == 2:
    wait_action = {
        "action_id": f"wait_retry_{uuid.uuid4().hex[:6]}",
        "type": "wait",
        "duration_ms": 2000,
    }
    actions_to_insert.append(wait_action)

F2.2.6 — Seuil image tronquée

Avant (f62fda575, 2026-05-07) — seuil minimal < 400×200 (placeholders triviaux) :

if img.height < 200 or img.width < 400:
    [...]

Après (7233df2bb, 2026-05-07) — seuil élargi à < 1200×800 :

if img.height < 800 or img.width < 1200:
    [...]

F2.4.4 — Reconnect hybrid_text_direct dans cascade strict

Avant (avant 1cbec2806, ≤ 2026-05-05) — _resolve_by_ocr_text n'était appelée QUE depuis le runtime V4 pré-compilé (extrait commit message). Code original non re-extrait ligne par ligne (commit message factuel suffit).

Après (HEAD via 1cbec2806, 2026-05-06) — appel ajouté dans _resolve_target_sync (cascade strict, resolve_engine.py:1750-1790).

F6.1.1 — Désactivation pre-check VLM par-clic (observe_reason_act.py:1704-1713)

Avant (commit 8903f3543, 2026-04-22) :

# --- Vérification pré-action (skip si UI-TARS a déjà validé visuellement) ---
if target_text and method_used not in ('template', 'ui_tars') and MSS_AVAILABLE and PIL_AVAILABLE:
    try:
        pre_check = self._verify_pre_click(x, y, target_text, target_desc)
        if not pre_check:
            print(f"⛔ [ORA/pre-check] L'élément à ({x}, {y}) ne correspond PAS à '{target_text}' — abandon du clic")
            return False
    except Exception as e:
        print(f"⚠️ [ORA/pre-check] Erreur vérification: {e}")

Après (HEAD via e2046837c, 2026-04-25) :

# --- Pas de pre-check VLM (le pipeline FAST→SMART→THINK a déjà validé) ---
if False:
    try:
        pre_check = self._verify_pre_click(x, y, target_text, target_desc)
        if not pre_check:
            print(f"⛔ [ORA/pre-check] L'élément à ({x}, {y}) ne correspond PAS à '{target_text}' — abandon du clic")
            return False
    except Exception as e:
        print(f"⚠️ [ORA/pre-check] Erreur vérification: {e}")

Findings sans version « avant » récupérable (≤ 14 jours)

Les findings suivants n'ont pas de version « activée » dans la fenêtre 14 jours (la désactivation est antérieure ou native dès l'introduction du code) :

  • F2.1.3, F2.1.4, F2.1.5 (commits 4509038bf du 2026-04-09 et d5deac302 du 2026-03-26)
  • F2.2.2, F2.2.4, F2.2.5, F2.2.7 (commits 2026-04-09 à 2026-04-11)
  • F2.3.2, F2.3.5 (commit 93ef93e56 du 2026-04-14)
  • F2.4.1, F2.4.3, F2.4.5, F2.5.1, F2.5.2, F2.5.3 (commit 4509038bf)
  • F2.6.1 (commit 99041f011 du 2026-04-09)
  • F6.1.2, F6.1.3, F6.1.4 (commits 2026-04-22 à 2026-04-26 — return True permissif natif)
  • F6.2.1, F6.3.1, F6.4.1 (commits 2026-04-25 à 2026-04-26 — états natifs)
  • F5.2.1, F5.2.3, F5.2.4, F5.5.1 (chemins clients antérieurs, non touchés < 14 jours)

Pour ces findings, le motif factuel est « introduction native du contrôle déjà à l'état permissif/désactivé », pas une bascule postérieure.