L'étage d'intégration échoue au hasard sur connect ETIMEDOUT 127.0.0.1:<port éphémère> quand la suite complète tourne — jamais fichier par fichier #57

Closed
opened 2026-09-12 09:14:01 +00:00 by nicolas-fryder · 2 comments

Sur une suite d'intégration de seize fichiers, un test échoue à peu près une fois sur deux — et ce n'est jamais le même. L'échec est toujours de la même forme : la connexion HTTP vers l'application sous test expire, sur une adresse de loopback. Rejoué fichier par fichier, tout passe.

Comme c'est le pre-push qui joue cette porte, l'effet pratique est qu'un push sur deux est refusé pour une raison qui n'existe pas — ce qui est exactement ce qui apprend à ne plus lire le rouge.

Ce qu'on observe

Deux exécutions consécutives de pnpm --filter api test:integration, sans aucune modification du code entre les deux :

 FAIL  test/integration/forgejo.test.ts > tache.publier > crée une issue avec le titre et le corps venus de chez nous
      Tests  1 failed | 176 passed (177)      Duration 137.77s

puis

 FAIL  test/integration/appliquer.test.ts > graphe.appliquer > écrit le sous-graphe et rend la correspondance des identifiants
TypeError: fetch failed
Caused by: Error: connect ETIMEDOUT 127.0.0.1:50617
Serialized Error: { errno: -4039, code: 'ETIMEDOUT', syscall: 'connect', address: '127.0.0.1', port: 50617 }
      Tests  1 failed | 176 passed (177)      Duration 152.77s

Rejoués isolément, les deux fichiers passent :

$ pnpm --filter api exec vitest run --config vitest.integration.mts test/integration/forgejo.test.ts
      Tests  16 passed (16)

Le port 50617 est un port éphémère : c'est celui sur lequel l'application sous test écoute (listen(0)). Ce n'est donc pas un service externe qui manque — c'est une connexion en loopback vers un serveur qui vient d'être levé dans le même processus.

Ce que j'ai vérifié, et écarté

L'épuisement des ports éphémères était mon hypothèse. Elle est fausse, ou au moins pas établie :

$ netstat -an | grep -c TIME_WAIT
340
$ netsh int ipv4 show dynamicport tcp
Port de démarrage : 1024
Nombre de ports   : 64511

340 sur 64511. La mesure est prise juste après la suite, donc après décroissance — mais l'ordre de grandeur ne plaide pas pour une saturation.

Je ne prétends donc pas connaître la cause. Ce que je peux affirmer : c'est reproductible en fréquence (une fois sur deux environ), jamais sur le même test, uniquement en suite complète, et toujours au niveau de la connexion.

La configuration

vitest.integration.mts du satellite, qui suit le gabarit :

fileParallelism: false,   // en série : deux fichiers qui écrivent dans la même base en parallèle produisent des échecs qui dépendent de l'ordre
testTimeout: 60_000,
hookTimeout: 120_000,

Seize fichiers, chacun levant une application Nest complète et une base jetable, en série. Durée totale ~150 s.

Noter que testTimeout est à 60 s alors que l'échec survient à ~5 s : ce n'est donc pas le délai de Vitest qui se déclenche, c'est bien connect qui rend ETIMEDOUT de lui-même.

Ce que je proposerais

Je n'ai pas de correctif à proposer avec confiance, faute de cause établie. Des pistes, par ordre de ce que je tenterais :

  1. Réutiliser l'application entre fichiers quand la configuration est identique. Seize démarrages Nest + seize conteneurs Postgres en série, c'est ce qui distingue la suite complète du fichier isolé — et c'est la seule variable qui change entre les deux cas.
  2. Un retry: 1 sur l'étage d'intégration seulement. Ça masque le symptôme et je ne l'aime pas, mais une porte qui refuse un push sur deux pour rien est pire qu'une porte qui retente une fois. À arbitrer.
  3. Instrumenter : logger l'adresse et le port sur lesquels l'application écoute, au moment du listen, pour vérifier que le port sur lequel la connexion échoue est bien celui-là et qu'il écoute encore.

Si le comportement est connu sur d'autres satellites ou d'autres plateformes, ça vaudrait la peine de le dire dans le README de @eqs/testing — pour l'instant, quelqu'un qui le rencontre croit avoir cassé quelque chose.

Ce que ça coûte

Non bloquant : on rejoue. Mais chaque rejeu coûte 150 s, et surtout la porte perd son crédit. Deux occurrences distinctes dans la même session de travail, sur deux tests sans rapport.

Aucun contournement posé dans le dépôt.

Versions

  • @eqs/testing, @eqs/cli : 0.8.1
  • Vitest 5.0.0, Node v24.15.0, pnpm@11.8.0
  • Windows 11 Pro 26200, Docker Desktop
  • PostgreSQL 18 en conteneur jetable

Trouvé en développant le satellite Atelier (ex-Pilotage), sur une suite de 177 tests répartis en 16 fichiers.

Sur une suite d'intégration de seize fichiers, **un test échoue à peu près une fois sur deux — et ce n'est jamais le même**. L'échec est toujours de la même forme : la connexion HTTP vers l'application sous test expire, sur une adresse de loopback. Rejoué fichier par fichier, tout passe. Comme c'est le `pre-push` qui joue cette porte, l'effet pratique est qu'**un push sur deux est refusé pour une raison qui n'existe pas** — ce qui est exactement ce qui apprend à ne plus lire le rouge. ## Ce qu'on observe Deux exécutions consécutives de `pnpm --filter api test:integration`, sans aucune modification du code entre les deux : ``` FAIL test/integration/forgejo.test.ts > tache.publier > crée une issue avec le titre et le corps venus de chez nous Tests 1 failed | 176 passed (177) Duration 137.77s ``` puis ``` FAIL test/integration/appliquer.test.ts > graphe.appliquer > écrit le sous-graphe et rend la correspondance des identifiants TypeError: fetch failed Caused by: Error: connect ETIMEDOUT 127.0.0.1:50617 Serialized Error: { errno: -4039, code: 'ETIMEDOUT', syscall: 'connect', address: '127.0.0.1', port: 50617 } Tests 1 failed | 176 passed (177) Duration 152.77s ``` Rejoués isolément, les deux fichiers passent : ``` $ pnpm --filter api exec vitest run --config vitest.integration.mts test/integration/forgejo.test.ts Tests 16 passed (16) ``` Le port `50617` est un port éphémère : c'est celui sur lequel l'application sous test écoute (`listen(0)`). Ce n'est donc pas un service externe qui manque — c'est une connexion en loopback vers un serveur qui vient d'être levé dans le même processus. ## Ce que j'ai vérifié, et écarté **L'épuisement des ports éphémères** était mon hypothèse. Elle est fausse, ou au moins pas établie : ``` $ netstat -an | grep -c TIME_WAIT 340 $ netsh int ipv4 show dynamicport tcp Port de démarrage : 1024 Nombre de ports : 64511 ``` 340 sur 64511. La mesure est prise juste après la suite, donc après décroissance — mais l'ordre de grandeur ne plaide pas pour une saturation. **Je ne prétends donc pas connaître la cause.** Ce que je peux affirmer : c'est reproductible en fréquence (une fois sur deux environ), jamais sur le même test, uniquement en suite complète, et toujours au niveau de la connexion. ## La configuration `vitest.integration.mts` du satellite, qui suit le gabarit : ```ts fileParallelism: false, // en série : deux fichiers qui écrivent dans la même base en parallèle produisent des échecs qui dépendent de l'ordre testTimeout: 60_000, hookTimeout: 120_000, ``` Seize fichiers, chacun levant une application Nest complète et une base jetable, en série. Durée totale ~150 s. Noter que `testTimeout` est à 60 s alors que l'échec survient à ~5 s : ce n'est donc **pas** le délai de Vitest qui se déclenche, c'est bien `connect` qui rend `ETIMEDOUT` de lui-même. ## Ce que je proposerais Je n'ai pas de correctif à proposer avec confiance, faute de cause établie. Des pistes, par ordre de ce que je tenterais : 1. **Réutiliser l'application entre fichiers** quand la configuration est identique. Seize démarrages Nest + seize conteneurs Postgres en série, c'est ce qui distingue la suite complète du fichier isolé — et c'est la seule variable qui change entre les deux cas. 2. **Un `retry: 1` sur l'étage d'intégration seulement.** Ça masque le symptôme et je ne l'aime pas, mais une porte qui refuse un push sur deux pour rien est pire qu'une porte qui retente une fois. À arbitrer. 3. **Instrumenter** : logger l'adresse et le port sur lesquels l'application écoute, au moment du `listen`, pour vérifier que le port sur lequel la connexion échoue est bien celui-là et qu'il écoute encore. Si le comportement est connu sur d'autres satellites ou d'autres plateformes, ça vaudrait la peine de le dire dans le README de `@eqs/testing` — pour l'instant, quelqu'un qui le rencontre croit avoir cassé quelque chose. ## Ce que ça coûte Non bloquant : on rejoue. Mais chaque rejeu coûte 150 s, et surtout la porte perd son crédit. Deux occurrences distinctes dans la même session de travail, sur deux tests sans rapport. Aucun contournement posé dans le dépôt. ## Versions - `@eqs/testing`, `@eqs/cli` : 0.8.1 - Vitest 5.0.0, Node v24.15.0, `pnpm@11.8.0` - Windows 11 Pro 26200, Docker Desktop - PostgreSQL 18 en conteneur jetable Trouvé en développant le satellite Atelier (ex-Pilotage), sur une suite de 177 tests répartis en 16 fichiers.
Author
Owner

Votre ticket me fait corriger une conclusion que j'avais écrite hier, et c'est la partie la plus utile de ma réponse.

Ce que j'avais conclu, et pourquoi c'était prématuré

J'ai chassé exactement cette signature dans eqs-platform toute la journée d'hier : pnpm check échouait environ une fois sur trois, connect ETIMEDOUT 127.0.0.1, jamais le même paquet. J'ai éliminé quatre hypothèses par la mesure, puis j'ai écrit dans reference/chasses.md :

Ce qui reste ressemble à une propriété de cette machine — un pilote de filtrage sur la boucle locale, un antivirus — et non du dépôt.

Votre ticket dément ça : autre dépôt, autre suite, autre poste, même signature, et vous avez écarté l'épuisement des ports avec la même mesure que moi (340 sur 64 511 chez vous, 196 chez moi). Ce n'est pas une machine, c'est la plateforme de la flotte. Le registre a été corrigé.

Deux hypothèses de plus, éprouvées et écartées

J'ai profité de votre rapport pour en tester deux que je n'avais pas faites :

Une socket morte resservie par le pool. fetch de Node est undici, qui garde des connexions par origine. Si un port éphémère est réutilisé par un serveur suivant, l'origine est identique — une socket morte pourrait être resservie. J'ai levé un serveur, l'ai joint, l'ai fermé sans vider le pool, puis relevé un serveur sur le même port :

1. premier serveur            200    14 ms
   (le second serveur a repris le port 43854)
2. même port, nouveau serveur 200     3 ms

undici jette bien la socket morte. Écartée.

Une collision avec une socket sortante en TIME_WAIT. Le bind réussit (le quadruplet diffère) mais les SYN entrants pourraient être avalés. 600 connexions clientes pour fabriquer du TIME_WAIT, puis 600 serveurs levés et joints aussitôt :

600 tours de churn puis 600 serveurs joints
échecs : 0

Écartée.

Ce qui est mesuré, et qui oriente sans conclure

500 serveurs HTTP successifs, joints trois fois chacun, machine au repos :

500 serveurs x3 appels — échecs : 0, appel le plus lent : 919 ms

Zéro échec, mais 919 ms pour un aller-retour en boucle locale sur un serveur qui ne fait rien. La latence loopback pique sur cette plateforme. Sous charge — seize applications Nest et seize conteneurs Postgres dans votre cas — un pic peut dépasser la patience du client. Ça explique « jamais le même test » sans expliquer la cause.

(Au passage, un de mes essais a fait sortir une assertion libuv sur Windows : Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), src\win\async.c:76, en fermant des serveurs à la chaîne. Je ne sais pas si c'est lié ; je le note parce que c'est le genre de chose qu'on ne retrouve pas quand on la cherche.)

Ce que j'ai fait, et pourquoi ce n'est pas votre option 2

Vous proposiez un retry: 1 en disant « ça masque le symptôme et je ne l'aime pas ». Vous avez raison de ne pas l'aimer : un retry global rejoue aussi les vrais échecs, et c'est exactement comme ça qu'un test cassé devient un test qui passe une fois sur deux.

Le fetch du harnais de @eqs/testing retente donc une fois, et uniquement quand la connexion n'a pas abouti :

const ECHECS_DE_CONNEXION = new Set([
  'ETIMEDOUT', 'ECONNREFUSED', 'EHOSTUNREACH', 'UND_ERR_CONNECT_TIMEOUT',
])

La distinction est ce qui rend la reprise sûre. Une connexion qui n'a pas abouti garantit que le serveur n'a rien vu : rejouer un POST ne risque pas de créer deux fois la même chose. ECONNRESET est volontairement exclu — il peut survenir après l'envoi, et on ne rejoue pas une écriture dont on ignore le sort.

Et elle est bruyante :

[testing] http://127.0.0.1:50617/x : ETIMEDOUT avant d’atteindre le serveur —
  une reprise. Si cela se répète, ce n’est plus le réseau (eqs-platform #57).

Une porte qui rattrape en silence finit par cacher un vrai défaut. Là, si la fréquence monte, ça se voit dans la sortie.

Vos autres pistes

Réutiliser l'application entre fichiers (votre 1). C'est la piste la plus prometteuse sur le fond, et je ne l'ai pas prise : seize démarrages Nest, c'est aussi seize isolations. Partager l'application entre fichiers échange une instabilité contre une autre — l'ordre des tests se met à compter, et ce sont les échecs les plus coûteux à diagnostiquer. Ça mérite son propre arbitrage, pas d'être décidé dans la foulée d'un correctif de robustesse.

Instrumenter (votre 3). L'avertissement de la reprise porte l'URL et le port : si le port qui échoue n'est pas celui qui écoute, ça se verra.

Ce que ça ne règle pas

La reprise rend la porte utilisable ; elle ne dit toujours pas pourquoi. La chasse 6 de reference/chasses.md reste ouverte avec, maintenant, six hypothèses éliminées et leurs mesures — pour que le prochain ne refasse pas le même chemin.

Et si vous voyez l'avertissement se répéter chez vous, c'est le signal qu'il faut rouvrir : une reprise qui rattrape souvent n'est plus une reprise, c'est un pansement.

Branche chasse/knip-et-code-mort. Ticket laissé ouvert jusqu'à la publication de 0.9.0, qui part maintenant.

Votre ticket me fait corriger une conclusion que j'avais écrite **hier**, et c'est la partie la plus utile de ma réponse. ## Ce que j'avais conclu, et pourquoi c'était prématuré J'ai chassé exactement cette signature dans `eqs-platform` toute la journée d'hier : `pnpm check` échouait environ une fois sur trois, `connect ETIMEDOUT 127.0.0.1`, jamais le même paquet. J'ai éliminé quatre hypothèses par la mesure, puis j'ai écrit dans `reference/chasses.md` : > Ce qui reste ressemble à une propriété de **cette machine** — un pilote de filtrage sur la boucle locale, un antivirus — et non du dépôt. Votre ticket dément ça : autre dépôt, autre suite, autre poste, même signature, et vous avez écarté l'épuisement des ports avec la même mesure que moi (340 sur 64 511 chez vous, 196 chez moi). **Ce n'est pas une machine, c'est la plateforme de la flotte.** Le registre a été corrigé. ## Deux hypothèses de plus, éprouvées et écartées J'ai profité de votre rapport pour en tester deux que je n'avais pas faites : **Une socket morte resservie par le pool.** `fetch` de Node est undici, qui garde des connexions par origine. Si un port éphémère est réutilisé par un serveur suivant, l'origine est identique — une socket morte pourrait être resservie. J'ai levé un serveur, l'ai joint, l'ai fermé sans vider le pool, puis relevé un serveur **sur le même port** : ``` 1. premier serveur 200 14 ms (le second serveur a repris le port 43854) 2. même port, nouveau serveur 200 3 ms ``` undici jette bien la socket morte. **Écartée.** **Une collision avec une socket sortante en `TIME_WAIT`.** Le bind réussit (le quadruplet diffère) mais les SYN entrants pourraient être avalés. 600 connexions clientes pour fabriquer du `TIME_WAIT`, puis 600 serveurs levés et joints aussitôt : ``` 600 tours de churn puis 600 serveurs joints échecs : 0 ``` **Écartée.** ## Ce qui est mesuré, et qui oriente sans conclure 500 serveurs HTTP successifs, joints trois fois chacun, **machine au repos** : ``` 500 serveurs x3 appels — échecs : 0, appel le plus lent : 919 ms ``` Zéro échec, mais **919 ms pour un aller-retour en boucle locale sur un serveur qui ne fait rien**. La latence loopback pique sur cette plateforme. Sous charge — seize applications Nest et seize conteneurs Postgres dans votre cas — un pic peut dépasser la patience du client. Ça explique « jamais le même test » sans expliquer la cause. (Au passage, un de mes essais a fait sortir une assertion libuv sur Windows : `Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), src\win\async.c:76`, en fermant des serveurs à la chaîne. Je ne sais pas si c'est lié ; je le note parce que c'est le genre de chose qu'on ne retrouve pas quand on la cherche.) ## Ce que j'ai fait, et pourquoi ce n'est pas votre option 2 Vous proposiez un `retry: 1` en disant « ça masque le symptôme et je ne l'aime pas ». Vous avez raison de ne pas l'aimer : un `retry` global rejoue **aussi** les vrais échecs, et c'est exactement comme ça qu'un test cassé devient un test qui passe une fois sur deux. Le `fetch` du harnais de `@eqs/testing` retente donc **une fois**, et uniquement quand la connexion n'a **pas abouti** : ```ts const ECHECS_DE_CONNEXION = new Set([ 'ETIMEDOUT', 'ECONNREFUSED', 'EHOSTUNREACH', 'UND_ERR_CONNECT_TIMEOUT', ]) ``` La distinction est ce qui rend la reprise sûre. Une connexion qui n'a pas abouti garantit que **le serveur n'a rien vu** : rejouer un POST ne risque pas de créer deux fois la même chose. `ECONNRESET` est volontairement exclu — il peut survenir *après* l'envoi, et on ne rejoue pas une écriture dont on ignore le sort. Et elle est **bruyante** : ``` [testing] http://127.0.0.1:50617/x : ETIMEDOUT avant d’atteindre le serveur — une reprise. Si cela se répète, ce n’est plus le réseau (eqs-platform #57). ``` Une porte qui rattrape en silence finit par cacher un vrai défaut. Là, si la fréquence monte, ça se voit dans la sortie. ## Vos autres pistes **Réutiliser l'application entre fichiers (votre 1).** C'est la piste la plus prometteuse sur le fond, et je ne l'ai pas prise : seize démarrages Nest, c'est aussi seize isolations. Partager l'application entre fichiers échange une instabilité contre une autre — l'ordre des tests se met à compter, et ce sont les échecs les plus coûteux à diagnostiquer. Ça mérite son propre arbitrage, pas d'être décidé dans la foulée d'un correctif de robustesse. **Instrumenter (votre 3).** L'avertissement de la reprise porte l'URL et le port : si le port qui échoue n'est pas celui qui écoute, ça se verra. ## Ce que ça ne règle pas La reprise rend la porte utilisable ; elle ne dit toujours pas **pourquoi**. La chasse 6 de `reference/chasses.md` reste ouverte avec, maintenant, six hypothèses éliminées et leurs mesures — pour que le prochain ne refasse pas le même chemin. Et si vous voyez l'avertissement se répéter chez vous, c'est le signal qu'il faut rouvrir : une reprise qui rattrape souvent n'est plus une reprise, c'est un pansement. Branche `chasse/knip-et-code-mort`. Ticket laissé ouvert jusqu'à la publication de 0.9.0, qui part maintenant.
nicolas-fryder referenced this issue from a commit 2026-09-12 14:30:20 +00:00
Author
Owner

La reprise étroite est la bonne réponse, et le raisonnement qui l'y amène vaut plus que le correctif : distinguer « la connexion n'a pas abouti » de « la connexion a abouti puis a cassé » est exactement ce qui rend une reprise sûre sur un POST. Exclure ECONNRESET parce qu'il peut survenir après l'envoi, c'est la précaution que je n'avais pas formulée en proposant mon retry: 1 — et qui explique pourquoi je ne l'aimais pas sans savoir le dire.

Le refus de mon option 1 me convainc aussi : seize démarrages, c'est seize isolations, et échanger une instabilité contre des échecs qui dépendent de l'ordre serait un mauvais marché.

Un bémol, petit et unique : l'avertissement est bruyant, mais rien ne le compte.

Le correctif repose sur « si la fréquence monte, ça se voit dans la sortie » et sur « si vous voyez l'avertissement se répéter chez vous, c'est le signal qu'il faut rouvrir ». Or la sortie d'une suite d'intégration fait ici environ 150 secondes et plusieurs centaines de lignes, et personne ne la lit en entier quand elle est verte — c'est même tout l'intérêt d'une porte verte. Une ligne d'avertissement noyée dans une sortie qu'on ne lit pas ne déclenchera pas le signal qu'elle est censée déclencher.

Ce qui le déclencherait : un compte en fin d'exécution, à côté du décompte des tests.

Test Files  27 passed (27)
     Tests  277 passed (277)
  Reprises  3 — connexions qui n'ont pas abouti (eqs-platform #57)

Trois choses en découlent, et c'est pour ça que je le propose plutôt que de m'en tenir à l'avertissement :

  1. Zéro reprise devient une information. Aujourd'hui, ne pas voir l'avertissement peut vouloir dire « aucune reprise » ou « je ne l'ai pas vu ». Un compte à zéro tranche.
  2. La tendance devient lisible sans archéologie. « C'était 1 la semaine dernière, c'est 12 aujourd'hui » est le genre de constat qu'on ne fait jamais en relisant des lignes éparses.
  3. C'est le même remède que celui évoqué pour les tâches sautées dans #51 (« un compte des tâches sautées à la fin de pnpm check — aujourd'hui la ligne se perd »). Deux fois le même symptôme, une ligne utile qui se perd dans une sortie verte : ça ressemble à une règle plutôt qu'à deux cas.

Accessoirement, si le compte est disponible en fin de course, il devient aussi le seuil naturel d'une escalade : au-delà de N reprises sur une exécution, échouer plutôt qu'avertir. Je ne le demande pas — je note seulement que « une reprise qui rattrape souvent n'est plus une reprise, c'est un pansement », votre phrase, devient vérifiable au lieu d'être laissée à l'attention de qui lit.

Les 919 ms d'aller-retour en boucle locale sur une machine au repos, c'est le chiffre que je retiens de votre réponse. Il ne conclut rien, mais il est le seul qui rende l'histoire plausible de bout en bout — et il vaut d'être dans la chasse 6 en tête, avant les hypothèses écartées : c'est lui qu'un prochain chercheur doit voir en premier.

Ce que je n'ai pas pu vérifier

Rien, pour l'instant. @eqs/*@0.9.0 n'est pas sur le registre — mesuré à l'instant :

$ pnpm view @eqs/platform-next dist-tags
{ "latest": "0.8.1", "next": "0.8.1" }

$ pnpm view @eqs/cli versions
[ …, "0.7.0", "0.8.0", "0.8.1" ]

La réponse dit « la publication de 0.9.0, qui part maintenant » ; elle n'est pas arrivée. Je le signale ici plutôt que dans un ticket séparé parce que c'est vrai pour les neuf tickets d'un coup — aucun n'est vérifiable tant que rien n'est installable, et nos contournements (celui-ci, #55, #56, #58) restent en place.

Je rejouerai la suite trois fois dès que la 0.9.0 sera montable, et je rendrai le compte de reprises observé — ce qui sera la première mesure de la fréquence réelle, celle qui manque des deux côtés.

La reprise étroite est la bonne réponse, et le raisonnement qui l'y amène vaut plus que le correctif : distinguer « la connexion n'a pas abouti » de « la connexion a abouti puis a cassé » est exactement ce qui rend une reprise sûre sur un POST. Exclure `ECONNRESET` parce qu'il peut survenir *après* l'envoi, c'est la précaution que je n'avais pas formulée en proposant mon `retry: 1` — et qui explique pourquoi je ne l'aimais pas sans savoir le dire. Le refus de mon option 1 me convainc aussi : seize démarrages, c'est seize isolations, et échanger une instabilité contre des échecs qui dépendent de l'ordre serait un mauvais marché. **Un bémol, petit et unique : l'avertissement est bruyant, mais rien ne le compte.** Le correctif repose sur « si la fréquence monte, ça se voit dans la sortie » et sur « si vous voyez l'avertissement se répéter chez vous, c'est le signal qu'il faut rouvrir ». Or la sortie d'une suite d'intégration fait ici environ 150 secondes et plusieurs centaines de lignes, et personne ne la lit en entier quand elle est **verte** — c'est même tout l'intérêt d'une porte verte. Une ligne d'avertissement noyée dans une sortie qu'on ne lit pas ne déclenchera pas le signal qu'elle est censée déclencher. Ce qui le déclencherait : **un compte en fin d'exécution**, à côté du décompte des tests. ``` Test Files 27 passed (27) Tests 277 passed (277) Reprises 3 — connexions qui n'ont pas abouti (eqs-platform #57) ``` Trois choses en découlent, et c'est pour ça que je le propose plutôt que de m'en tenir à l'avertissement : 1. **Zéro reprise devient une information.** Aujourd'hui, ne pas voir l'avertissement peut vouloir dire « aucune reprise » ou « je ne l'ai pas vu ». Un compte à zéro tranche. 2. **La tendance devient lisible sans archéologie.** « C'était 1 la semaine dernière, c'est 12 aujourd'hui » est le genre de constat qu'on ne fait jamais en relisant des lignes éparses. 3. C'est **le même remède** que celui évoqué pour les tâches sautées dans [#51](https://git.equilibre-structures.fr/Infrastructure/eqs-platform/issues/51) (« un compte des tâches sautées à la fin de `pnpm check` — aujourd'hui la ligne se perd »). Deux fois le même symptôme, une ligne utile qui se perd dans une sortie verte : ça ressemble à une règle plutôt qu'à deux cas. Accessoirement, si le compte est disponible en fin de course, il devient aussi le seuil naturel d'une escalade : au-delà de N reprises sur une exécution, échouer plutôt qu'avertir. Je ne le demande pas — je note seulement que « une reprise qui rattrape souvent n'est plus une reprise, c'est un pansement », votre phrase, devient vérifiable au lieu d'être laissée à l'attention de qui lit. **Les 919 ms d'aller-retour en boucle locale sur une machine au repos**, c'est le chiffre que je retiens de votre réponse. Il ne conclut rien, mais il est le seul qui rende l'histoire plausible de bout en bout — et il vaut d'être dans la chasse 6 en tête, avant les hypothèses écartées : c'est lui qu'un prochain chercheur doit voir en premier. ## Ce que je n'ai pas pu vérifier Rien, pour l'instant. `@eqs/*@0.9.0` **n'est pas sur le registre** — mesuré à l'instant : ``` $ pnpm view @eqs/platform-next dist-tags { "latest": "0.8.1", "next": "0.8.1" } $ pnpm view @eqs/cli versions [ …, "0.7.0", "0.8.0", "0.8.1" ] ``` La réponse dit « la publication de 0.9.0, qui part maintenant » ; elle n'est pas arrivée. Je le signale ici plutôt que dans un ticket séparé parce que c'est vrai pour les neuf tickets d'un coup — aucun n'est vérifiable tant que rien n'est installable, et nos contournements (celui-ci, [#55](https://git.equilibre-structures.fr/Infrastructure/eqs-platform/issues/55), [#56](https://git.equilibre-structures.fr/Infrastructure/eqs-platform/issues/56), [#58](https://git.equilibre-structures.fr/Infrastructure/eqs-platform/issues/58)) restent en place. Je rejouerai la suite trois fois dès que la 0.9.0 sera montable, et je rendrai le compte de reprises observé — ce qui sera la première mesure de la fréquence réelle, celle qui manque des deux côtés.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Infrastructure/eqs-platform#57
No description provided.