Paillasse/docs/postmortems/2026-10-08-console-trames-texte.md

55 lines
3.2 KiB
Markdown
Raw Normal View History

# Post-mortem — la première touche frappée coupait la console de l'élève
- **Date** : 2026-10-08 (défaut introduit à l'étape 9, découvert à l'étape 11)
- **Durée** : défaut latent depuis l'étape 9 ; corrigé dans l'heure suivant sa découverte
- **Gravité** : élevée si elle avait atteint une classe — aucun élève n'aurait pu taper une seule commande dans un vrai navigateur. Impact réel nul : aucun utilisateur.
- **Impact** : avec xterm.js, la première frappe de l'élève faisait fermer la connexion à la VM ; la console restait affichée mais muette.
## Résumé
`nova-serialproxy` (sous-protocole WebSocket `binary`) ferme sa connexion
dès qu'il reçoit une trame **texte**. Or xterm.js, dans le navigateur, envoie
les frappes en trames texte (`ws.send(chaîne)`), et le proxy Go les relayait
vers l'amont avec leur type d'origine.
Le test de l'étape 9 (`proxy-console/test-manuel.py`) envoyait des trames
**binaires** (`send_binary`) : il passait, et le critère « une élève tape
`ip a` et voit le résultat » a été déclaré vérifié sans qu'aucun vrai
navigateur n'ait tapé une touche.
## Chronologie
1. Étape 9 : proxy écrit et testé avec un script qui envoie du binaire. Critère validé.
2. Étape 11 : le script de parcours (`portail/tests/Reels/parcours-lab.py`) imite xterm.js et envoie `"\r"` en trame texte pour réveiller la console : aucune invite `login:`.
3. Journaux : `proxy-console` → « connexion amont fermée » 2 s après l'ouverture, à l'instant de la trame texte ; `nova-serialproxy` ne dit rien de plus.
4. Diagnostic par comparaison directe (`/tmp/diag-trames.py`) : trame texte → rien puis connexion perdue.
5. Correctif : `proxy-console/hub.go`, les frappes partent toujours en `websocket.BinaryMessage` vers l'amont. Redéployé ; texte et binaire font apparaître `login:`.
## Cause racine
Le test de l'étape 9 ne reproduisait pas le vrai client : il prenait un
raccourci (binaire) que le navigateur ne prend pas. La différence entre
« le chemin fonctionne » et « le chemin fonctionne avec le vrai client »
n'était pas testée.
## Ce qui a bien fonctionné
- Écrire le test de l'étape 11 comme un vrai navigateur (connexion, CSRF, jeton de console, trames texte) a fait remonter le défaut avant tout pilote.
- Les journaux du proxy, horodatés à la seconde, ont permis de relier la fermeture à la trame en quelques minutes.
## Actions correctives
- [x] Le proxy normalise : toute frappe part en binaire vers `nova-serialproxy`, quel que soit le client.
- [x] `parcours-lab.py` envoie des trames texte, comme xterm.js : il protège désormais contre ce défaut.
- [ ] Étape 12 : vérifier une fois le parcours dans un vrai navigateur (Playwright), la vérification visuelle de l'étape 8 n'ayant pas pu être faite.
## Comprendre
Un test qui passe ne prouve que ce qu'il teste vraiment. Le script de
l'étape 9 parlait au proxy « comme un outil », pas « comme un navigateur » :
il envoyait des octets bruts, alors qu'un navigateur envoie du texte. Les deux
arrivent au même proxy, mais `nova-serialproxy` les traite différemment. La
leçon générale : pour valider un critère utilisateur, le test doit imiter le
vrai client jusque dans les détails qui paraissent anodins — ici, le type de
trame WebSocket.