Files
nas-runbooks/hermes-tt/nyora-notes-tt-message-id-elem-id-volatile-08-08-2026.md
bolbolandClaude Opus 4.8 c3a74b565d docs(hermes-tt): runbook elem_id OWA volatile (piste message_id natif fermee) + PROTOCOL-INFRA
- hermes-tt/nyora-notes-tt-message-id-elem-id-volatile-08-08-2026.md : cycle complet --
  hypothese elem_id stable, test insuffisant (1 msg position 0, appels rapproches) qui l'a
  faussement valide, test rigoureux (19 msg, ensembles disjoints, 0/20 meme index) qui
  l'infirme, cause (id de rendu DOM volatile), decision (message_id reste hash SHA256 stable,
  conservation conversation_id data-convid niveau thread + fix retry /search count:0), garde-fou
  dedup (399 notes en dossiers 'done' jamais re-scannes -> pas de doublon).
- PROTOCOL-INFRA.md : regle "id DOM d'un SPA jamais presume stable" (tester >=2 rendus reels +
  plusieurs echantillons) + fix /search count:0 transitoire + piege split('|') sur attrs.
- _INDEX.md : entree ajoutee.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-08 12:10:03 +01:00

6.6 KiB

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_<sha256(subject|date|sender)[:16]>) 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_<sha256(subject|date|sender)[:16]> — 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_<hash>, toutes dans des dossiers de checkpoint done. pending_folders (mcp_server.py) ne resélectionne jamais un dossier donepas 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.