diff --git a/common/PROTOCOL-INFRA.md b/common/PROTOCOL-INFRA.md index 5153550..b3c8f32 100644 --- a/common/PROTOCOL-INFRA.md +++ b/common/PROTOCOL-INFRA.md @@ -298,3 +298,17 @@ ligne de `raw_text` porte le libelle du dossier du message -- s'il ne correspond demande, la navigation a echoue. Corollaire : un audit de completude bati sur une source non verifiee produit des chiffres qui ont l'air precis et ne veulent rien dire. Detail : hermes-tt/nyora-notes-tt-parsing-owa-pua-10-08-2026.md (bug D). + +## REGLE -- relever un plafond de pagination change le profil temporel : verifier les timeouts en meme temps (2026-08-10) + +Effet de bord constate juste apres le fix du plafond /search (20 -> 200) sur nyora-notes-tt. +hermes-mail-browser accumule ses resultats par SCROLL CLAVIER PROGRESSIF dans une liste virtualisee : +la duree de l'appel croit avec le nombre d'items demande. Les appels sont passes de ~30 s a 40-240 s +selon le dossier, alors que `search_folder` gardait `timeout=60` code en dur -- dimensionne pour +l'ancien plafond. Resultat : les gros dossiers echouaient en timeout et le pipeline les aurait tous +marques `error`. Corrige par `SEARCH_TIMEOUT_S = 300`. + +REFLEXE : un plafond de pagination et un timeout sont couples. Relever l'un sans revoir l'autre +transforme une troncature silencieuse en echec generalise -- on remplace un bug par un autre. +Verifier aussi les timeouts en AVAL (retry, cron n8n, healthcheck) qui peuvent etre calibres sur +l'ancienne duree. diff --git a/hermes-tt/nyora-notes-tt-parsing-owa-pua-10-08-2026.md b/hermes-tt/nyora-notes-tt-parsing-owa-pua-10-08-2026.md index ccf66d3..91b1536 100644 --- a/hermes-tt/nyora-notes-tt-parsing-owa-pua-10-08-2026.md +++ b/hermes-tt/nyora-notes-tt-parsing-owa-pua-10-08-2026.md @@ -2,7 +2,7 @@ **Date** : 10/08/2026 **Instance** : hermes-tt -**Statut** : A et B CORRIGÉS + déployés · C diagnostiqué (correctif non appliqué) · **D NON RÉSOLU — bloquant backfill** +**Statut** : A et B CORRIGÉS + déployés · C diagnostiqué (correctif non appliqué) · **D NON RÉSOLU** — garde-fou de portée déployé en mitigation (erreur visible au lieu de corruption silencieuse) **Fichiers** : `/volume1/docker/nyora-notes-tt/hermes_mail_client.py` (`_parse_item`, `search_folder`) · `/volume1/docker/hermes-mail-browser/app/main.py` (bug C, non modifié) Quatre défauts distincts de la même chaîne d'extraction, découverts ensemble. Ils partagent une @@ -172,7 +172,7 @@ réel : rebuild et retest Playwright d'un **second** container. ## Bug D — `/search` renvoie le contenu de la Boîte de réception pour certains `folder_path`, en HTTP 200 -**Découvert en auditant le bug B. NON RÉSOLU — bloquant pour tout backfill.** +**Découvert en auditant le bug B. Cause NON RÉSOLUE (session dédiée) — mitigé côté client par un garde-fou de portée, cf plus bas.** ### Symptôme Deux `folder_path` différents renvoient ~77 items dont 63 en commun, et **chaque ligne porte le @@ -214,10 +214,69 @@ introuvable, jamais si la navigation échoue **silencieusement** après un clic 3. **Bloquant pour le backfill** : ingérer à grande échelle avec une portée fausse polluerait la base massivement. -### Contrôle de portée à systématiser -La dernière ligne de `raw_text` porte le libellé du dossier du message. C'est un **contrôle de -portée gratuit** : après un `/search` scopé, si ce libellé ne correspond pas au dossier demandé, -la navigation a échoué. À câbler dans `search_folder` avant toute ingestion. +### Mitigation déployée : garde-fou de portée côté client (10/08/2026) + +Le correctif de fond reste à faire côté `hermes-mail-browser`. En attendant, un contrôle a été +câblé dans `hermes_mail_client.search_folder` pour transformer la **corruption silencieuse en +erreur visible**. + +**Aucun signal structuré n'existe** — vérifié avant d'implémenter, conformément à la règle +« préférer un attribut structuré au texte rendu ». La réponse `/search` expose au top-level +`query`, `count`, `items` ; par item `raw_text`, `aria_label`, `elem_id`, `attrs`. **Aucun** ne +porte le dossier réellement atteint. En exposer un imposerait de modifier `hermes-mail-browser`, +donc relève de la session dédiée au bug D. Faute de mieux, le signal retenu est la **dernière +ligne de `raw_text`**, qui porte le libellé du dossier du message. + +**Fiabilité mesurée avant implémentation** (même méthode que le recensement PUA), 5 dossiers de +types différents : + +| Type | Items | Libellé uniforme | Correspond au dossier demandé | +|---|---|---|---| +| normal Réception (`00 2026\|⏱️ suivi`) | 81 | 81/81 | ✅ `⏱️ Suivi` | +| veille/notif (`00 2026\|🤖 veille`) | 37 | 37/37 | ✅ `🤖 Veille` | +| profond niveau 5 (`…\|🤖 projet auto`) | 6 | 6/6 | ✅ `🤖 Projet Auto` | +| ancien 2024 (`02 2024\|DR Zone Sud\|Tozeur`) | 84 | 84/84 | ✅ `Tozeur` | +| **vérolé** (`00 2026\|Relance marchés RLA`) | 77 | 77/77 | ❌ `Boîte de réception` | + +Signal **uniforme à 100 % dans les 5 cas**, correspondant dans les 4 cas sains, divergent dans le +seul cas vérolé : fiable **et** discriminant. Le blocage dur est donc justifié par la mesure. + +**Conception — volontairement conservatrice.** Le contrôle ne bloque que sur une *preuve positive* +de mauvaise portée. Quand il ne peut pas conclure (aucun item, libellé absent, libellés +hétérogènes sous le seuil de 90 %, noyau comparable vide), il **journalise et laisse passer** : un +faux positif bloquerait une ingestion légitime, ce qui serait pire que le défaut couvert. La +comparaison se fait sur un « noyau » alphanumérique en minuscules (glyphes, emoji et ponctuation +retirés), car la même entité s'écrit de trois façons : chemin stocké (`00 2026|Suivi`), chemin réel +(`⏱️ suivi`), libellé rendu (`⏱️ Suivi`). + +`HermesFolderScopeError` hérite de `HermesMailClientError`, ce qui lui donne le bon comportement +chez les **deux appelants de production sans les modifier** : `pipeline_processor.process_folder` +la reçoit dans son `except Exception` → checkpoint en statut `error` (visible, re-tentable) au lieu +d'ingérer ; `download_attachments_phase1._fetch_folder_or_none` renvoie `None` (« on ne sait pas, à +retenter »), sans rien marquer. + +**Vérifié en conditions réelles**, depuis le container après rebuild : +``` +SAIN '00 2026|⏱️ suivi' -> 81 items INGERES +VEROLE '00 2026|Relance marchés RLA' -> BLOQUE + Portee /search non respectee : dossier demande '00 2026|Relance marchés RLA', + mais les messages recus appartiennent a 'Boîte de réception' (bug D). + Ingestion refusee pour eviter un classement errone. +``` +Non-régression vérifiée sur les `raw_text` du mock de test (`test_api_mock_server.py`), qui ne +portent pas de libellé de dossier : le contrôle les laisse passer. + +### Effet de bord du fix B : le timeout de 60 s devenait trop court + +Constaté en testant le garde-fou. `/search` accumule ses résultats par **scroll clavier progressif** +dans une liste virtualisée : la durée croît avec le nombre d'items demandé. Passer le plafond de 20 +à 200 (fix B) a fait passer les appels de ~30 s à **40–240 s** selon le dossier, alors que +`search_folder` gardait `timeout=60` codé en dur — les gros dossiers échouaient donc en timeout, +et le pipeline les aurait tous marqués `error`. + +`SEARCH_TIMEOUT_S = 300` désormais. **Leçon** : relever un plafond de pagination change le profil +temporel des appels — vérifier les timeouts en même temps, ils sont dimensionnés pour l'ancien +volume. ---