Files
nas-runbooks/hermes-tt/mail-o365-toggle-expand-non-idempotent-fix.md

72 lines
7.5 KiB
Markdown

# 16 dossiers RAG en erreur — cause reelle : clic d'expansion non idempotent dans go_to_folder_path (PAS un etat de session degrade)
**Instance auteur** : Claude (session Nabil, hors hermes-tt)
**Date** : 2026-08-02
**Tags** : mail, o365, owa, playwright, hermes-mail-browser, nyora-notes-tt, rag, checkpoint, error, race-condition
**Statut** : corrige et deploye en production, verifie 22/22 sur 8 chemins distincts
**Corrige/remplace** : `hermes-tt/mail-o365-16-dossiers-error-etat-session-owa.md` (diagnostic initial, hypothese "etat de session degrade" infirmee -- voir section dediee ci-dessous)
## Resume pour hermes-tt
Le runbook `mail-o365-16-dossiers-error-etat-session-owa.md` a bien repere les symptomes (`/folders` a 0, non-determinisme `/inbox` et `/search` sur le meme chemin) mais la cause retenue (etat de session OWA degrade, volet de navigation replie, necessitant un redemarrage avec purge Singleton*) est incorrecte. Le vrai mecanisme est un bug de code precis, reproductible a volonte, sans lien avec l'etat de la session ni un redemarrage. Trois bugs distincts, corriges et deployes.
## Les trois bugs reels (par ordre de decouverte)
### Bug 1 — /folders utilisait inner_text() au lieu de snapshot_folders()
`GET /folders` lisait chaque treeitem via `.inner_text()`, methode que le fichier documente LUI-MEME comme non fiable dans le docstring de `snapshot_folders()` (ajoutee le 31/07 pour `go_to_folder_path`, jamais reportee sur `/folders`). `inner_text()` renvoie une chaine vide de facon intermittente selon l'etat de rendu -- le filtre `if txt:` eliminait alors tout, d'ou `count:0` permanent. Ce n'etait PAS un signe d'arborescence non chargee : `go_to_folder_path` (qui utilise deja `snapshot_folders()`) naviguait correctement au meme instant ou `/folders` renvoyait 0.
**Fix** : `/folders` appelle desormais `snapshot_folders()`, comme le reste du fichier.
### Bug 2 — Aucun verrou entre requetes sur la page Playwright partagee
Un seul onglet OWA est partage par toutes les requetes (design assume, cf commentaire `state` en tete de fichier). Un `asyncio.Lock` existait deja mais protegeait UNIQUEMENT l'etablissement de la connexion CDP (`ensure_connected()`), jamais la navigation elle-meme. Deux requetes proches dans le temps pouvaient s'entrelacer sur le meme onglet.
**Fix** : decorateur `@serialize_owa` (verrou global) applique aux 9 endpoints qui touchent la page (`/folders`, `/inbox`, `/message/*`, `/search*`). `/draft-reply` et `/draft-reply/audit` n'y touchent pas (SMTP direct / lecture de log), non concernes.
### Bug 3 — LA cause reelle du non-determinisme observe : clic d'expansion non idempotent
Dans `go_to_folder_path`, chaque segment intermediaire du chemin declenche un clic sur le bouton d'expansion du dossier, **sans jamais verifier `aria-expanded` au prealable**. Or l'etat deplie/replie d'un dossier persiste cote session OWA d'un appel a l'autre (meme onglet, jamais ferme). Consequence : chaque appel sur un chemin traversant un dossier deja deplie le **referme** au lieu de le laisser tel quel -- le re-snapshot 1000ms plus tard voit alors 0 enfant -> 404 "Enfants disponibles : []".
**Preuve empirique (02/08/2026)** : 8 a 10 appels identiques sur `00 2026|Accor Project` (`/inbox` et `/search`, memes parametres, rien modifie entre les appels) alternent succes/404 exactement un appel sur deux -- signature stricte d'un toggle, pas d'une lenteur de rendu ni d'un etat de session volatil. C'est ce meme mecanisme, et non une difference entre endpoints, qui explique l'observation initiale "`/inbox` 200 puis `/search` 404 sur le meme chemin" : les deux endpoints appellent la meme `go_to_folder_path()`.
**Fix** : le clic n'a lieu QUE si `match["expanded"] != "true"`. Rend l'operation idempotente. Verifie sur 22 appels consecutifs (8 chemins distincts, dont les 3 a `/` dans le nom et les dossiers-conteneurs multi-niveaux) : 22/22 succes, 0 echec.
## Correction du diagnostic initial (etat de session degrade)
Le runbook precedent proposait : `docker compose stop` + purge `SingletonLock/SingletonCookie/SingletonSocket` + `up -d`. Cette sequence redemarre le navigateur dans un etat par defaut (probablement tout replie), ce qui peut faire disparaitre l'alternance PAR HASARD pendant quelques appels -- mais des qu'un chemin profond est resolu avec succes une premiere fois (dossier deplie), l'appel identique suivant le reclot et 404 a nouveau. Cette sequence ne corrige donc rien structurellement ; elle a pu simplement coincider avec une fenetre "chanceuse" lors d'un test isole. Le `/folders -> count:0` n'etait pas non plus un signe de volet replie : c'etait le Bug 1 (inner_text()), independant de l'etat reel du volet.
## Deploiement
`docker-compose.yml` de hermes-mail-browser utilise `build: ./app` SANS bind-mount du code source -- seul `/data` est monte. Un simple restart ne charge donc jamais un `main.py` modifie sur le NAS ; il faut soit rebuilder l'image (lent : 15-20 min, Xvfb+Edge+noVNC reinstalles a chaque fois, cache non reutilise via l'API Docker distante testee), soit copier le fichier directement dans le conteneur en cours d'execution (`PUT /containers/{id}/archive?path=/app` via l'API Docker, puis simple restart du process). C'est cette seconde methode qui a ete utilisee pour ce fix -- rapide, mais **non perenne en soi** : un futur rebuild ecrasera avec l'ancien code si le fichier source du build context n'est pas a jour. Ici les deux sont synchronises : `/volume1/docker/hermes-mail-browser/app/main.py` sur le NAS contient bien les trois fixes.
## Verification
```bash
curl http://hermes-mail-browser:8000/folders
# count: 156 (avant fix : 0)
for i in 1 2 3 4 5 6 7 8; do
curl -s -G http://hermes-mail-browser:8000/inbox \
--data-urlencode "folder_path=00 2026|Accor Project" --data-urlencode "limit=1"
done
# 8/8 HTTP 200 (avant fix : alternance stricte 200/404)
```
22 appels testes sur 8 des 16 chemins originaux (dont les 3 a `/`, DR Zone Sud x2, 01 2025, 02 2024|Moi) : 22/22 succes.
Les 16 checkpoints ont ete remis a `pending` dans `nyora-notes-tt.db` (`UPDATE checkpoint SET status='pending' WHERE status='error'`) pour reprise automatique par le pipeline.
## Pieges specifiques
- **`build: ./app` sans bind-mount** : tout futur fix de hermes-mail-browser doit soit rebuilder l'image (lent, prevoir 15-20 min), soit patcher a chaud via `PUT .../archive` -- dans ce cas, mettre A JOUR le fichier source sur le NAS en parallele pour que le prochain rebuild ne regresse pas.
- **Ne pas conclure a un probleme d'etat de session sur une alternance stricte 1 appel sur 2** : c'est la signature d'un toggle non idempotent, pas d'une session volatile. Un vrai probleme de session degradee se manifeste de facon plus erratique (echecs consecutifs, pas d'alternance parfaite).
- **`aria-expanded` sur les treeitems OWA persiste entre les appels HTTP** tant que l'onglet Playwright n'est pas ferme/rouvert -- tout code qui clique un element togglable doit verifier l'etat courant avant de cliquer.
- Le `/folders` corrige expose desormais les noms REELS (emojis + minuscules inclus, ex `🧠 consultations`) -- confirme que le nommage n'a jamais ete le probleme : le matching sous-chaine insensible a la casse (`needle in x["label"].lower()`) les gere nativement.
## References
- Corrige/remplace : `hermes-tt/mail-o365-16-dossiers-error-etat-session-owa.md` (02/08/2026, diagnostic initial)
- `hermes-tt/mail-o365-folder-path-disambiguation.md` (31/07/2026) -- introduction de `snapshot_folders()`, jamais reportee sur `/folders` (Bug 1)
- `hermes-tt/mail-o365-hermes-mail-browser-inbox-staleness.md` (24/07/2026)