diff --git a/_INDEX.md b/_INDEX.md index 99adc45..2c6a37c 100644 --- a/_INDEX.md +++ b/_INDEX.md @@ -197,3 +197,4 @@ - [hermes-tt/nyora-notes-tt-ao-reference-endpoint-pj-archive-fix-08-08-2026.md](hermes-tt/nyora-notes-tt-ao-reference-endpoint-pj-archive-fix-08-08-2026.md) -- Suite session 08/08 : outil MCP search_linked_documents productionise (normalisation regex NN/AAAA, 3 modes d'entree ao_reference/attachment_id/note_id, teste reel 1082 correspondances). /mnt/pj-archive elucide : pas de montage manquant, bug de donnees (local_path avec un prefixe ATTACH_ROOT perime pre-bootstrap), fichiers reels presents sous /app/attachments -- corrige, 20/20 PJ ont desormais un content_hash. JOIN exact content_hash : 1re correspondance confirmee (0 le 07/08), hypothese de resorption progressive validee. Piege : UPDATE concurrent au sweep actif = database table is locked, fix PRAGMA busy_timeout au lieu de wal_checkpoint (08/08/2026) - [hermes-tt/nyora-notes-tt-phase2-embedding-relance-post-sweep-08-08-2026.md](hermes-tt/nyora-notes-tt-phase2-embedding-relance-post-sweep-08-08-2026.md) -- Sweep Phase1 toutes annees termine seul via son plafond MAX_BATCHES=15 (3 notes restantes, cas OWA permanents deja connus, aucune intervention necessaire). Phase 2 + embedding relances sur le volume complet via nouveau script run_phase2_embed_full.sh (boucle jusqu'a epuisement + embedding + audit) : 24/27 PJ telechargees extraites avec succes, 24/24 embeddees (100%), content_hash 27/27 (100% des telechargees), ao_reference 23/27. JOIN exact content_hash avec onedrive_documents : 3 correspondances (1 seul le 08/08 matin, confirme la resorption progressive). Piege : docker exec -i obligatoire pour un heredoc Python en stdin ; sqlite_vec.load(conn) obligatoire avant toute requete sur *_vec, y compris en audit ponctuel (08/08/2026) - [hermes-tt/download-attachments-phase1-bug-timeout.md](hermes-tt/download-attachments-phase1-bug-timeout.md) -- Sweep PJ 2026 degradait la session OWA jusqu'a marquer des notes attachments_scanned=1 sans verification reelle : deux endpoints (search_folder par dossier, list_attachments par note) catchaient un timeout exactement comme un 404 et le mettaient en cache comme vide. Fix : contrat None (inconnu, a retenter) vs liste vide (verifie vide/404) strict + une retentative + circuit-breaker 3 echecs consecutifs. Filtre year_bucket ajoute (absent avant, bug distinct meme session), parametrable ATTACH_YEAR_BUCKET pour reprendre sur 2024/2025 apres 2026. Teste 2x5 notes (07/08/2026) +- [hermes-tt/nyora-notes-tt-message-id-elem-id-volatile-08-08-2026.md](hermes-tt/nyora-notes-tt-message-id-elem-id-volatile-08-08-2026.md) -- Piste "message_id natif via elem_id OWA" FERMEE : elem_id (attribut id de ligne) s'est revele VOLATILE (GUID de rendu regenere a chaque appel -- test rigoureux 0/19 messages stables, ensembles disjoints, meme index -> id different). Un premier test a 1 seul message en position 0 sur appels rapproches l'avait faussement valide (DOM re-utilise). Decision : message_id reste le hash SHA256(subject|date|sender) STABLE ; conversation_id (data-convid, niveau THREAD, regex ciblee jamais split sur |) et fix retry /search count:0 transitoire CONSERVES. Garde-fou dedup verifie (message_id = cle de dedup ; 399 notes toutes en dossiers 'done' jamais re-scannes ; 0 note degradee inseree pendant la fenetre elem_id). Lecon SPA : tester un id DOM sur >=2 rendus reels et plusieurs messages (08/08/2026) diff --git a/common/PROTOCOL-INFRA.md b/common/PROTOCOL-INFRA.md index 202f0d1..c067be1 100644 --- a/common/PROTOCOL-INFRA.md +++ b/common/PROTOCOL-INFRA.md @@ -216,3 +216,27 @@ Tous les tools MCP RAG mail cassaient juste apres chaque backup n8n (toutes les ## FIX -- cron n8n orphelin, boucle infinie sur statuts finaux (error_409/404), cause reelle instabilite hermes-mail-browser (2026-08-02) Workflow n8n 'nyora-notes-tt -- Ingestion continue' actif depuis le 31/07, toutes les 10 min (292 executions), en boucle sur les memes dossiers error_409/error_404 -- statuts FINAUX (jamais retraitables avec succes), donc sa condition d'arret 'automatique quand tout est done' ne se declenchait jamais. `neverError:true` sur le noeud HTTP masquait le probleme (292/292 executions n8n 'success' malgre des 409 systematiques). Consequence : contention reguliere sur la page Playwright partagee de hermes-mail-browser, prise a tort pour de l'instabilite structurelle OWA. Desactive (unpublish_workflow). Verifie ensuite : hermes-mail-browser sain (RAM/CPU), timings /search et /message/attachments conformes aux baselines deja documentees une fois le bruit de fond supprime. Reflexe a generaliser : toute boucle n8n a condition d'arret basee sur un etat DB doit distinguer statut final vs statut retry-able, sinon elle ne se videra jamais ; et `neverError:true` cache ce genre de boucle inutile au monitoring standard n8n -- verifier le contenu des reponses, pas juste le statut d'execution. Avant de blamer un service externe pour de la lenteur persistante, chercher d'abord un consommateur interne recurrent via `docker logs` filtre par IP source. Detail : hermes-tt/n8n-cron-orphelin-hermes-mail-browser-instabilite.md. + +## REGLE -- id DOM d'un SPA (OWA/React) : jamais presume stable, tester sur plusieurs rendus + plusieurs echantillons (2026-08-08) + +Un attribut `id=`/`data-*` scrape sur une ligne d'un SPA n'est PAS presume stable dans le temps. +Cas nyora-notes-tt : `elem_id` (attribut `id` de ligne OWA), exploite comme `message_id` natif, +s'est revele VOLATILE -- OWA/React regenere un GUID frais a chaque rendu (test rigoureux : 0/19 +messages stables entre deux appels /search identiques, ensembles d'id disjoints, meme index -> id +different ; l'ordre de liste est stable mais pas l'id, `aria-posinset` non peuple). Un premier test +a 1 seul message toujours en position 0 sur appels rapproches l'avait faussement valide (le DOM +n'etait pas re-rendu entre les appels -> meme id volatile reutilise). Reflexe : pour juger la +stabilite d'un id DOM, faire >= 2 rendus REELS distincts (appels espaces OU requetes forcant un +re-render), sur PLUSIEURS elements, apparier par une identite externe stable (ex. texte de la ligne) +et verifier le RECOUVREMENT D'ENSEMBLES -- jamais un seul echantillon en position fixe. Seul +`data-convid` (ConversationId Exchange, niveau THREAD) est stable cote ligne OWA ; aucun id natif +par-message n'est expose par le scraping DOM. Detail : hermes-tt/nyora-notes-tt-message-id-elem-id-volatile-08-08-2026.md. + +## FIX -- /search hermes-mail-browser : count:0 transitoire sur appels rapproches (2026-08-08) + +Deux appels `/search` a < ~10 s d'ecart : le 2e renvoie parfois `count:0` (liste virtualisee OWA pas +encore stabilisee cote Playwright). Fix cote client (`hermes_mail_client.search_folder`) : si `items` +est vide de facon inattendue, retry UNIQUE apres `time.sleep(4)` avant d'abandonner (simple, pas de +backoff). Piege de parsing associe : `attrs` (string pipe-delimitee) reinjecte `aria-label` en fin de +chaine (texte libre non echappe, `|` et sauts de ligne possibles) -> NE JAMAIS `split('|')` global ; +extraire par regex ciblee (`data-convid=([^|]+)`, base64 sans `|`). Detail : meme runbook ci-dessus. diff --git a/hermes-tt/nyora-notes-tt-message-id-elem-id-volatile-08-08-2026.md b/hermes-tt/nyora-notes-tt-message-id-elem-id-volatile-08-08-2026.md new file mode 100644 index 0000000..60b6111 --- /dev/null +++ b/hermes-tt/nyora-notes-tt-message-id-elem-id-volatile-08-08-2026.md @@ -0,0 +1,95 @@ +# nyora-notes-tt : elem_id OWA VOLATILE — le message_id natif par-message n'existe pas via scraping DOM + +**Date** : 08/08/2026 +**Instance** : hermes-tt +**Statut** : PISTE FERMÉE (elem_id abandonné) — conversation_id (data-convid) + fix retry /search conservés +**Fichier** : `/volume1/docker/nyora-notes-tt/hermes_mail_client.py` (`_parse_item`, `search_folder`) + +## Objectif initial +Remplacer le `message_id` du pipeline mail RAG (jusqu'ici `msg_`) +par un identifiant **natif par-message** stable, pour un ancrage propre (dédup, liaison RAG). +`hermes-mail-browser` avait été patché pour exposer sur `/search` deux attributs DOM OWA en plus +de `raw_text`/`aria_label` : `elem_id` (attribut `id` de la ligne) et `attrs` (string +pipe-délimitée contenant notamment `data-convid=…`). + +## Le faux positif (à ne pas reproduire) +Un premier « test de stabilité » avait conclu que `elem_id` était **stable et unique par message** +(« vérifié sur 3 appels /search séparés, requêtes différentes, même message → même elem_id »), +avec un exemple de valeur **base64** type `AQAAAAAAAQwCAAAFYk58JQAAAAA=`. + +**Ce test était insuffisant** : un seul message, toujours en **position 0** de la liste, sur des +appels **rapprochés**. OWA (SPA React) ne re-rend pas forcément la liste virtualisée entre deux +appels rapprochés → le même DOM (donc le même `id` volatile) est réutilisé → **stabilité +illusoire**. C'est le piège classique du test à un seul échantillon. + +## Le test rigoureux (qui infirme) +Deux appels `/search` **query identique** (`q="*"`), **même dossier** (`02 2024|DR Zone Sud|Tozeur`, +20 messages), appariement des messages par `raw_text` (identité stable pour du mail 2024) : + +| Mesure | Résultat | +|---|---| +| `elem_id` identique pour le même message entre 2 appels | **0 / 19** | +| Recouvrement des ensembles d'`elem_id` (\|A ∩ B\|) | **0** (\|A\|=17, \|B\|=17, disjoints) | +| Même **index** → même `elem_id` | **0 / 20** | +| Même index → même `raw_text` (ordre de liste) | **20 / 20** (ordre stable) | +| `aria-posinset` dans `attrs` | **toujours 0** (non peuplé par cette build OWA) | +| `data-convid` (conversation_id) entre appels | **stable** (même ConversationId Exchange) | + +## Cause +`elem_id` = **id de rendu DOM volatile** : OWA/React génère un **GUID frais à chaque rendu** +(format `1da682c2-7ada-f6a1-4cef-83fe279a6bcd`, ≠ le base64 du faux positif → l'attribut/la build +a changé). Il **n'encode ni l'identité du message ni sa position** (l'ordre de liste est pourtant +stable, mais l'id ne l'est pas ; `aria-posinset` reste à 0). **Inutilisable comme `message_id`.** +Le seul identifiant natif stable exposé au niveau **ligne** est `data-convid` — mais il est de +**niveau THREAD** (plusieurs mails d'un même fil le partagent), pas par-message. + +→ Conclusion : **aucun identifiant natif par-message stable n'est disponible via le scraping des +attributs de ligne OWA** dans l'état actuel. Cohérent avec la conclusion générale « les handles du +SPA OWA ne sont pas stables ». Un vrai ItemId Exchange par-message nécessiterait une autre voie +(ouverture du message, Graph API, autre attribut DOM) — hors périmètre, non fait. + +## Décision finale (appliquée le 08/08/2026) +1. **`message_id` reste le hash SHA256** `msg_` — déterministe et + STABLE (le hash n'était pas cassé, juste « non natif »). La branche `elem_id` est **retirée + entièrement** (pas juste mise en fallback : `elem_id` n'est jamais falsy, le fallback + `if not native_id` ne se serait jamais déclenché → il aurait toujours servi l'id volatile). + Au passage, l'ancien code mort `item.get("id")` (attribut jamais renvoyé par le bridge) est + supprimé aussi. +2. **`conversation_id` conservé** : extrait de `attrs` par **regex ciblée** `r'data-convid=([^|]+)'`. + **NE JAMAIS** faire de `split('|')` global sur `attrs` : `aria-label` y est réinjecté en fin de + chaîne, texte libre non échappé pouvant contenir des `|` et des sauts de ligne. `data-convid` + est du base64 sans `|`, donc `[^|]+` est sûr même si le reste est pollué. (Non persisté pour + l'instant : la table `notes` n'a pas de colonne `conversation_id` — enhancement futur possible.) +3. **Fix fiabilité `/search` conservé** : voir section suivante. + +## Bug annexe corrigé : /search count:0 transitoire +Deux appels `/search` à < ~10 s d'écart : le 2ᵉ renvoie parfois `count:0` (liste virtualisée OWA +pas encore stabilisée côté Playwright). Fix dans `search_folder` : si `items` est vide de façon +inattendue, **retry unique après `time.sleep(4)`** avant d'abandonner (simple, pas de backoff). + +## Garde-fou dédup (vérifié — pas de doublons) +- `message_id` **EST** la clé de dédup à l'ingestion (`pipeline_processor.py`, + `SELECT id FROM notes WHERE message_id = ?`). +- Au moment du chantier : **399 notes, 399 au format `msg_`**, toutes dans des dossiers de + checkpoint **`done`**. `pending_folders` (`mcp_server.py`) ne resélectionne jamais un dossier + `done` → **pas de re-scan → pas de risque de doublon**. Les checkpoints non-`done` (138 + `error_404`, 5 `error_409`) ont 0 note. +- Fenêtre de risque « code elem_id déployé » : le conteneur a tourné le code dégradé (message_id = + id volatile) entre deux rebuilds. Vérifié après coup : **0 note insérée** dans cette fenêtre + (toujours 399, aucune au format non-hash) → aucun nettoyage nécessaire. +- **Caveat** documenté : si un jour un checkpoint `done` est remis manuellement en `pending`, les + anciennes notes ne seraient pas reconnues par la dédup `message_id` et retomberaient sur le filet + vectoriel `find_duplicate_in_folder` (distance L2 < 0.05 + sujet identique → `duplicate_count++`). + +## Voir aussi +- `hermes-tt/mail-o365-recherche-scroll-virtualisation.md` — virtualisation de la liste OWA (même + origine que le count:0 transitoire). +- `hermes-tt/mail-o365-hermes-mail-browser-inbox-staleness.md` — état de session OWA / staleness. +- `hermes-tt/mail-o365-16-dossiers-error-etat-session-owa.md` — 502 « Racine du compte introuvable + dans le panneau de dossiers » = état OWA dégradé → clear cache + `docker restart hermes-mail-browser`. + +## Leçon transférable +Un attribut DOM `id=`/`data-*` d'un SPA n'est PAS présumé stable. Le tester sur **≥ 2 rendus réels +distincts** (appels espacés OU requêtes forçant un re-render), sur **plusieurs messages**, en +appariant par une identité externe stable (`raw_text`), et vérifier le **recouvrement d'ensembles** +— pas un seul échantillon en position fixe. Ajouté à `PROTOCOL-INFRA.md`.