TL;DR
La PR-B des Retours (STAB-07A/B/C) est en prod 4.0.20. Avant d’y arriver, on a brûlé
deux tours de CI complète (FULL) pour des raisons évitables : annoncer « lint 0 + 26/26 ciblés »
comme porte de merge, puis traiter un flake E2E d’appel comme un simple timeout. La leçon est
dans .agents/ ; l’habitant, lui, a un onglet Retours et des appels qui ne sonnent plus
chez un fantôme.
Liens proches :
- Suivre tes retours (produit — à publier si encore en 404)
- 18 minutes pour vérifier 3 lignes
- Journal du 30 septembre
1. Le problème rencontré
On livrait le circuit Retours : capture avant ACK, sync GitHub en dry-run, APIs, onglet dans
Nouveautés. Les tests du lot étaient verts — vingt-six sur vingt-six, ciblés. Lint propre.
La tentation : coller ci:full, attendre le vert, merger.
Le FULL est rouge.
Pas à cause du « cœur » du lot. À cause de deux trous que le périmètre ciblé n’avait jamais regardés :
- TDZ dans
anonymize.mjs— une variablehasutilisée avant d’être déclarée. Invisible tant qu’on ne lance pas la suite d’anonymisation hors du lot. - Suites absentes de
AREAS— les nouveaux testsfeedback-*n’étaient pas dans le plan CI. Le ciblé ne les voit pas ; le FULL non plus… jusqu’à ce que quelqu’un les ajoute, ou jusqu’à ce que le reste du graphe les charge autrement. Bref : le plan de preuve mentait par omission.
Puis, deuxième FULL brûlé : flake E2E phase7 « appel refusé » sous charge shard. On a
d’abord allongé un timeout à 45 s. Ça n’a pas suffi — parce que ce n’était pas un délai. C’était
un invite fantôme : grâce de présence encore affichée, zéro socket live chez le pair, et
le serveur envoyait quand même call.invite.
Enfin, après merge, main n’était plus exactement la tête de la PR (une leçon agents intercalée).
La CI de main a rejoué un FULL et a flake ailleurs (canvas qui intercepte un clic). Relancer
une troisième batterie produit pour « être sûr » aurait encore brûlé du temps mur — sans changer
la preuve déjà obtenue sur 74be1aa.
2. La résolution
Produit
- Fix invite : refuser
call.invitesihub.connsOf(peer).length === 0(présent en grâce ≠ joignable). - E2E refuse durci (
waitCallIdle) ; preuve locale phase7 ×3 + intégration « socket coupée mais encore en grâce ». - Circuit Retours + noms Place (≥ 14 px) livrés avec 4.0.20.
Preuve (sans rejouer le FULL pour le plaisir)
| Étape | Résultat |
|---|---|
npm test sur le SHA de fix | 1054 pass / 0 fail |
FULL GitHub 74be1aa | success |
Release --ci-verified depuis la tête PR | tag v4.0.20 |
| Deploy Release | 37254532148 success |
/readyz | 4.0.20 |
Règle (pour ne pas recommencer)
Ciblé ≠ FULL. Avant ready / push ci:full / claim merge, si le diff touche
src|ui|scripts|migrations|test :
npm run lint+npm testsur le SHA (pas « N/N ciblés »).- Tout nouveau test intégration/E2E → entrée
AREASdans la même PR. - Flake → preuve locale ×2–3 et cause produit, pas seulement timeout.
Écrit dans .agents/RULES.md, skills verify / ship-gate / livrer, .agents/TESTS.md,
journal incident/preuve-ciblee-avant-full.
3. Ce que l’habitant voit (le vrai gain)
Pendant qu’on se battait avec la CI, le lot livrait quand même quelque chose de tangible :
- Nouveautés → Retours : l’état de ce que tu as signalé, tant que l’onglet est ouvert.
- Place : écriteaux lisibles.
- Appels : plus d’invitation qui sonne chez quelqu’un qui n’a plus de socket.
La méthode ne compte que si la vague est sortie. Ici, elle l’est.