- 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>
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)
message_idreste le hash SHA256msg_<sha256(subject|date|sender)[:16]>— déterministe et STABLE (le hash n'était pas cassé, juste « non natif »). La brancheelem_idest retirée entièrement (pas juste mise en fallback :elem_idn'est jamais falsy, le fallbackif not native_idne se serait jamais déclenché → il aurait toujours servi l'id volatile). Au passage, l'ancien code mortitem.get("id")(attribut jamais renvoyé par le bridge) est supprimé aussi.conversation_idconservé : extrait deattrspar regex cibléer'data-convid=([^|]+)'. NE JAMAIS faire desplit('|')global surattrs:aria-labely est réinjecté en fin de chaîne, texte libre non échappé pouvant contenir des|et des sauts de ligne.data-convidest du base64 sans|, donc[^|]+est sûr même si le reste est pollué. (Non persisté pour l'instant : la tablenotesn'a pas de colonneconversation_id— enhancement futur possible.)- Fix fiabilité
/searchconservé : 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_idEST 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 checkpointdone.pending_folders(mcp_server.py) ne resélectionne jamais un dossierdone→ pas de re-scan → pas de risque de doublon. Les checkpoints non-done(138error_404, 5error_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
doneest remis manuellement enpending, les anciennes notes ne seraient pas reconnues par la dédupmessage_idet retomberaient sur le filet vectorielfind_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.