Anonymisation déterministe de la cible par regex DÉDIÉES (email/date/tél/IPP →
tokens) avant hashing : deux sessions sur le même champ (patients/dates
différents) → même signature. Normalisation casse/accents/espaces (logique
action_executor._norm_text, redéfinie localement pour rester léger).
Choix QG Qwen (2026-06-25) : PAS de pii_blur (il protège les dates qu'on veut
neutraliser), PAS de NER (un hash d'identité doit être déterministe/portable
labo↔DGX). Noms propres sans titre non gérés (stratégie b ; gate = audit
agrégat by_text DGX avant prod). R2 fallback coords RETIRÉ (casserait F1).
R3 (machine_id hors hash) déjà conforme.
TDD: +4 tests (RED→GREEN, 9/9). Primitive non wirée (0 consommateur runtime)
→ changement de calcul sans impact.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le champ by_role remontait la methode de detection (yolo/ocr/vlm), instable entre
sessions : deux apprentissages du meme parcours detectes differemment produisaient
deux signatures -> fusion (create-or-update) ratee. On sort by_role de la signature
et on s'appuie sur le texte semantique de la cible (by_text), independant du moteur
de grounding. Fallback quand by_text vide : titre de fenetre / description VLM.
Test TDD: test_signature_stable_despite_grounding_role_difference (RED->GREEN).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extrait d'un workflow core (dict) la sequence ordonnee (action_type, target stable)
via traversee BFS depuis entry_nodes (comme le bridge d'import), en n'utilisant que
des champs stables (by_role/by_text/window) et en ignorant coords/IDs de noeuds.
Branche la primitive trajectory_signature sur de vrais workflows.
Test TDD: tests/unit/test_workflow_trajectory_signature.py (3 tests, RED->GREEN).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Primitive partagee (SP-4/SP-2/competences) : hashe la sequence ordonnee
(action_type, target) d'un parcours en ignorant les champs session-specifiques
(node_id, timestamp, coordonnees) -> deux apprentissages du meme parcours = meme
signature = base du create-or-update (decision F1). Le target stable peut etre
compose avec screen_signature() existante.
Test TDD: tests/unit/test_trajectory_signature.py (5 tests, RED->GREEN).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Câble l'éditeur adresses/ports du dashboard (services.streaming) vers le
RPA_SERVER_URL généré pour chaque agent Léa. Priorité config > env > défaut ;
host loopback/vide = non configuré (fallback env → pas de régression).
Permet de changer l'IP serveur (labo .45 → clinique .178) depuis l'UI sans
toucher l'env ni le code. +3 tests TDD.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le score/confidence figés à 0.85 dans _resolve_by_grounding rendaient le
garde-seuil (_RESOLUTION_MIN_SCORES["grounding"]=0.60) inopérant (0.85>0.60
toujours accepté). Le grounding VLM n'a pas de confiance modèle native (prompt
{"x","y"}, pas de logprob de localisation — confirmé QG Qwen 2026-06-15). On
dérive une confiance SÉMANTIQUE : le texte cible est-il à la position trouvée ?
(_validate_text_at_position). Confirmé→0.90, absent→0.45 (<seuil→rejet),
non vérifiable→0.70. Confiance contextuelle documentée, PAS une proba modèle.
TDD : 5 tests (score varie / présent accepté / absent rejeté / score==confidence
/ sans by_text neutre), RED→GREEN. Non-régression : 24 tests resolve_engine +
câblage qwen3vl + legacy bbox verts. E2E panel inchangé (15/15). Pré-check OCR
non impacté. DETTE-018 (legacy non gardé) reste séparée.
refs DETTE-019
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ajoute AgentRegistry.verify_token(token) -> machine_id|None : compare le
SHA-256 du token aux token_hash des agents 'active' via hmac.compare_digest
(temps constant). Agent désinstallé/révoqué refusé ; rotation à l'enroll
invalide l'ancien token.
Inerte au runtime : méthode non branchée sur l'auth HTTP (le branchement
derrière flag RPA_FLEET_PER_AGENT_TOKEN sera le Patch 4). api_stream.py
intouché. TDD : 6 tests + non-régression WP-C/WP-B (53 verts). Voir
PLAN-WPC-TDD-EXECUTABLE.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Génère un token unique (secrets.token_hex(32)) à chaque (ré)enrôlement,
persiste uniquement son empreinte SHA-256 dans token_hash, renseigne
token_issued_at, retourne le clair une seule fois dans le résultat de
enroll. Le clair n'est jamais journalisé ni persisté.
Inerte au runtime : api_stream.py intouché, l'endpoint /agents/enroll ne
propage ni le clair ni le hash (api_token global inchangé). Auth runtime
non modifiée. Aucun branchement _verify_token. TDD : 8 tests + non-régression
WP-B/WP-C (47 verts). Voir PLAN-WPC-TDD-EXECUTABLE / DETTE-015.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ajoute token_hash + token_issued_at à enrolled_agents via ALTER TABLE
idempotent (_init_db). Colonnes inertes : aucun branchement auth, runtime
inchangé (tests WP-B verts). Base du token par poste (WP-C, cf DETTE-015).
TDD: tests/unit/test_wpc_migration.py (présence, idempotence, préservation
des données d'une base existante). 3 tests + non-régression WP-B = 9 passed.
refs DETTE-015
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
resolve_device('auto') renvoyait 'cpu' sur le GB10 : le plafond max_total_gb=6
(pensé pour la RTX 12 Go dédiés) voyait used≈99 Go car la mémoire UNIFIÉE compte
la RAM système. Au-dessus de DEFAULT_LARGE_VRAM_GB=24 (grosse carte / mémoire
unifiée), le plafond n'est plus appliqué ; seul free >= min_free_gb décide.
RTX (<=24 Go) inchangée.
Détecté au bench GB10 2026-06-08 (auto->cpu, OCR 10x plus lent). +2 tests (17/17).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ferme le contournement "poste révoqué + nouveau machine_id + token global" :
quand RPA_FLEET_ENROLL_LOCKED=true, l'enrôlement d'un machine_id INCONNU est refusé
(FleetEnrollLockedError). Les machines déjà connues conservent leur comportement :
active -> AlreadyEnrolled, désinstallé non-revoke -> réactivable, admin_revoke -> Revoked.
- agent_registry.py : _fleet_enroll_locked() + FleetEnrollLockedError + gate avant INSERT
- tests/unit/test_fleet_enroll_lock_wpb.py : 6 tests (verts)
NB : le handler HTTP 403 (api_stream.py /api/v1/agents/enroll) reste dans le WIP de la
branche (api_stream déjà modifié par le préflight non committé) — sera embarqué au commit
de consolidation api_stream. La logique de sécurité (gate) est dans agent_registry, committée.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le dashboard refuse de démarrer si DASHBOARD_PASSWORD absent ET auth non
explicitement désactivée (DASHBOARD_AUTH_DISABLED). Supprime le mot de passe
par défaut hardcodé exploitable.
- web_dashboard/app.py : _require_dashboard_password() fail-closed (lève en prod
sans secret ; mode dev/test = DASHBOARD_AUTH_DISABLED=true)
- tests/unit/conftest.py : DASHBOARD_AUTH_DISABLED=true par défaut pour les tests
- tests/unit/test_dashboard_failclosed_wpa.py : 5 tests (fail-closed, anti-régression défaut)
- tests/unit/test_dashboard_auth_p0a.py : fixture _restore_module restaure un état neutre sûr
48 tests dashboard verts (WP-A + non-régression auth/routes).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
resolve_device(auto/cuda/cpu) avec garde-fou VRAM et fallback CPU propre.
Bascule EasyOCR/SoM/docTR sur GPU si VRAM libre, rollback env sans toucher au code.
- core/gpu/device_policy.py (nouveau) : resolve_device + garde-fou VRAM (max_total_gb)
- core/detection/som_engine.py, core/llm/ocr_extractor.py,
agent_v0/server_v1/resolve_engine.py : câblage device auto (35 lignes)
- tests/unit/test_device_policy.py : 15 tests (verts venv réel)
Rollback sans toucher au code : RPA_VISION_DEVICE=cpu (force CPU global) / RPA_EASYOCR_GPU=0.
Bench GPU réel (latence) + activation large après verdict Qwen. QG Qwen deja valide sur le patch.
Mergé depuis worktree agent-a4f390f410e00ad7c (base 5b2afa362), 3 fichiers cibles non modifiés
dans le principal (zéro écrasement), dry-run apply propre.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Détecte les modèles VLM/grounding « aveugles » (capabilities sans vision, ex.
UI-TARS réimporté sans mmproj) pour éviter le HTTP 500 silencieux masqué par
la cascade de grounding.
- core/detection/model_health.py : has_vision_capability() (cache, fail-open)
+ smoke_check_models()
- core/execution/input_handler.py : gate vision dans _grounding_ui_tars
(skip propre vers niveau 3 si modèle aveugle, plus de 500 silencieux)
- tests/unit/test_model_health.py : 6 tests (vision/aveugle/fail-open/cache/smoke)
Incident 2026-06-08 : UI-TARS sans mmproj -> niveau 2 cascade en 500 silencieux,
non détecté (hors chemin runtime démo + échec avalé par fallback + zéro test).
NB : le smoke non bloquant au démarrage (api_stream.py startup) reste dans le WIP
de la branche, mélangé au préflight non committé.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sans env RPA_VLM_MODEL/VLM_MODEL, get_vlm_model() tombait sur le default
gemma4:latest, qui peut etre absent du tunnel DGX (depull) -> 404 Ollama et
echec de tout le pipeline VLM avant un test Lea humain.
- core/detection/vlm_config.py : DEFAULT_VLM_MODEL gemma4:latest -> qwen2.5vl:7b-rpa
(confirme present DGX, deja default reasoning + fallback bbox grounding).
+ DGX_SAFE_VLM_MODELS allow-list documentee.
- tests/unit/test_vlm_default_dgx_safe.py : 5 tests (default != gemma4:latest,
default in allow-list, no-env -> DGX-safe, env garde priorite).
Logique de resolution inchangee, pas d'appel reseau a l'import.
gemma4:latest reste accessible via env explicite.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adapter de benchmark isole (hors runtime Lea) ciblant un serveur
/v1/chat/completions a support vision (vLLM/SGLang/TGI), pour comparer
plus tard a Ollama via LeaBench. Ne controle jamais le desktop.
- core/evaluation/openai_compat_lea_bench_adapter.py : payload data-URL
image_url, parsing choices[0].message.content. Reutilise par import la
logique prompt/parse/normalisation de ollama_lea_bench_adapter (zero refactor).
- tools/lea_bench_openai_compat.py : wrapper CLI (--base-url defaut :8001).
- tests/unit/test_openai_compat_lea_bench_adapter.py : 6 tests mockes HTTP
(data URL, pas de fuite expectation/click_region, prediction valide,
abstain safe sur HTTP!=200 et reponse malformee, JSONL rechargeable).
Aucun runtime Lea modifie. Aucun service lance.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a private in-flight helper for replay dispatch, block machine retargeting while an action is still pending on the previous session, and warn on duplicate in-flight entries for the same replay triplet.
Freeze the Notepad runtime dialog success path and add integration coverage for single in-flight dispatch, watchdog late-report documentation, and the known concurrent-poll race as an xfail.
Backup état complet après enregistrement vidéo démo de bout en bout.
À utiliser comme point de référence pour la consolidation post-démo.
Changements majeurs de la session 18-19 mai :
- AIVA-URGENCE : page autonome avec preset URL + auto-focus chain
- Workflow Demo_urgence_3_db : merge linux_db + steps AIVA + pause humaine NoMachine
- Bypass LLM (static_result / static_text) dans replay_engine
pour démos déterministes sans appel Ollama
- Fix api_stream:3013 — replay_paused au premier polling /next
- dag_execute : lift duration_ms vers top-level pour wait runtime
- NPM bypass auth /aiva-urgence/ via location ^~ (proxy_host/10.conf hors git)
- scripts/cancel-replays.sh — workaround Stop VWB qui ne purge pas la queue
Anchors visuels (468) forcés dans le commit pour garantir restorabilité.
DB workflows actuelle + ~12 .bak DB de la journée incluses.
Sujets identifiés pour consolidation post-démo (TODO) :
1. Bug VWB recapture anchor ne régénère pas le PNG
2. Léa client accumule état mémoire (restart périodique requis)
3. Stop VWB ne purge pas la queue serveur (lien manquant vers /replay/cancel)
4. Bug coord client mss tronqué 2560x60 → mapping Y cassé
5. delay_before/delay_after ignorés au runtime (fix partiel duration_ms)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Préprocesseur Python qui injecte un bloc FAITS_CALCULÉS en tête du DPI
avant l'appel LLM, pour neutraliser l'hallucination de durée (bug "23h"
sur cas MOREL, confusion avec "depuis 23h" de l'Observ. IDE Urg).
Extrait depuis le bandeau Easily Assure et la Synthèse Urgences :
- âge (dateutil.relativedelta)
- date admission / sortie + durée passage (format humain + décimal)
- CCMU / GEMSA libellé complet (parser multi-ligne)
- priorité IAO, mode de venue / médicalisation / mode d'entrée
- diagnostic principal
- decision_terrain + orientation_terrain (metadata only, jamais injectés
dans le prompt pour ne pas biaiser le LLM)
Retour tuple (dpi_enriched, metadata) pour permettre les garde-fous
serveur Python ↔ LLM au commit 2.
Robustesse :
- re.search 1re occurrence + WARNING si bandeau divergent multi-occurrences
- Synthèse Urgences priorité sur bandeau pour dates
- Valeur exigée sur même ligne que label (évite capture de section title)
- Cas négatif (horaires absents) → "NON CALCULABLE" + parsing_warnings
- Jamais de crash, retour tuple toujours valide
Tests : 4/4 verts (golden MOREL string + metadata, négatif sortie absente,
DPI vide). Pas de régression sur tests/integration/test_t2a_extract.py.
Brief complet : docs/handoffs/2026-05-12_brief_S1_build_dpi_enriched.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>