ci: keep the boot check off proxies, catch continue-on-error in any literal spelling (#35) #42

Merged
maximus merged 7 commits from issue-35-ci-pin-followups into main 2026-10-01 22:00:54 +00:00
Owner

Fixes #35

Les trois constats non bloquants de la revue de la PR #34 (commentaire 17003), plus les suggestions des revues de cette PR : 17525 (appliquées dans 92bfb42), 17546 (a03e4d4) et 17559 (13d46e0, d712006).

1. Aucun curl du boot check ne passe par un proxy de l'environnement. Seul le pré-contrôle du port de scripts/boot-from-copy.sh portait --noproxy : la boucle d'attente et les deux assertions suivaient encore http_proxy. Le script exporte maintenant NO_PROXY='*' et no_proxy='*' en tête, ce qui couvre tous les appels, et le --noproxy devenu redondant est retiré. Les deux graphies sont exportées parce que curl lit d'abord la minuscule : un no_proxy hérité l'emporterait sinon.

Nouveau test dans __tests__/ci.test.js : un boot complet derrière un faux proxy qui répond à tout et note chaque requête reçue. Le test prouve d'abord que le proxy capte bien les requêtes (un curl nu, avec le même environnement, l'atteint), puis exige qu'aucune requête du script ne l'atteigne et que le script passe. http_proxy et ALL_PROXY sont posés tous les deux, pour qu'un simple unset http_proxy ne suffise pas. no_proxy y vaut une liste qui laisse la boucle locale de côté, comme un no_proxy hérité pourrait le faire : un script qui n'exporterait que NO_PROXY échoue. NO_PROXY est retiré, pour que rien d'hérité ne décide du résultat.

2. continue-on-error, sous toute écriture littérale. Le détecteur est le mot nu, /continue-on-error/, sans ancrage ni deux-points exigés, toujours appliqué à ciCode (sans les lignes de commentaire). Il ferme le style flow signalé par la revue et, au passage, la clé entre guillemets ("continue-on-error": true) et une ancre YAML réutilisée comme clé, qui passaient de la même façon. Une clé écrite avec une séquence d'échappement ("continue\u002don-error") passe encore : l'épingle lit du texte, pas du YAML. Contrepartie assumée : le mot cité dans un script run:, un nom d'étape ou un commentaire de fin de ligne fait aussi échouer l'épingle, bruyamment. Le détecteur est vérifié pour lui-même : il doit attraper la clé en bloc, en élément de liste, en style flow, entre guillemets et via une ancre, et ignorer une ligne de commentaire entière. Les six écritures citées ici donnent bien continue-on-error: True sous PyYAML.

3. Heuristique « require en colonne 0 » : deux trous fermés, les autres consignés. L'épingle lit maintenant aussi require ( et import (, avec une espace avant la parenthèse. Restent ouverts, entre autres : une fonction écrite entière sur une ligne en colonne 0 (const lazy = () => require("x");), un require( renvoyé à la ligne en colonne 0 dans une fonction, et toute indirection, comme un alias de require. Aucune de ces formes ne correspond au style du dépôt, et la deuxième a la forme exacte du } = require("node:fs"); légitime qui ferme l'import multiligne d'index.js : seul un parseur les distinguerait. Les trous restants sont écrits au-dessus de l'épingle et dans CLAUDE.md. Le détecteur, sorti dans loadsLate(line), est vérifié pour lui-même sur six lignes, puisqu'aucun fichier runtime n'enfreint l'épingle : require indenté et import à toute colonne, avec ou sans espace, sont signalés ; un require de premier niveau et une ligne de commentaire passent.

Preuve par mutation, première passe (fichiers restaurés depuis une copie, git diff --stat identique avant et après) :

Mutation Résultat
étape en style flow ajoutée à ci.yml, ancienne épingle 12/12 vert : le trou est réel
script de main (--noproxy sur le seul pré-contrôle) 1/13 rouge : le proxy reçoit les deux GET /health de la boucle d'attente et de la première assertion
export NO_PROXY retiré du script corrigé 1/13 rouge : le pré-contrôle passe par le proxy, qui fait paraître le port pris
étape en style flow ajoutée à ci.yml 1/18 rouge : never lets a failing step pass
clé entre guillemets ajoutée à ci.yml 1/18 rouge : même test
détecteur ré-ancré (ancienne regex) 2/18 rouges : lignes style flow et clé entre guillemets
classe de guillemets retirée du détecteur 1/18 rouge : ligne clé entre guillemets
retrait des lignes de commentaire désactivé 1/18 rouge : ligne commentaire

Les passes suivantes, qui répondent aux revues 17525, 17546 et 17559, ont chacune leur tableau en commentaire (17541, 17553, puis celui qui suit cette mise à jour).

Reproduction exacte de la revue de #34, refaite : http_proxy=http://127.0.0.1:9 sh scripts/boot-from-copy.sh donne, sur le script de main, listening on :3097 puis FAIL: the server did not answer on :3097 within 10 s après 10 s ; sur cette branche, boot-from-copy: PASS, y compris avec no_proxy=example.invalid.

Suite locale : 353/353 vitest (ci.test.js passe de 12 à 25 cas), 45/45 test-curl.sh, boot-from-copy: PASS. En local, le nouveau test prend la branche non root du script ; la branche root (su node, Alpine) ne tourne qu'en CI, d'où la vérification du statut de commit avant la revue.

Hors périmètre : test-curl.sh a la même exposition aux proxys (mesuré : code 7 après 11 s, sans aucune ligne de sortie). Suivi dans #41.

Aucun fichier runtime touché : pas de redéploiement après le merge.

🤖 Generated with Claude Code

Fixes #35 Les trois constats non bloquants de la revue de la PR #34 (commentaire 17003), plus les suggestions des revues de cette PR : 17525 (appliquées dans `92bfb42`), 17546 (`a03e4d4`) et 17559 (`13d46e0`, `d712006`). **1. Aucun `curl` du boot check ne passe par un proxy de l'environnement.** Seul le pré-contrôle du port de `scripts/boot-from-copy.sh` portait `--noproxy` : la boucle d'attente et les deux assertions suivaient encore `http_proxy`. Le script exporte maintenant `NO_PROXY='*'` et `no_proxy='*'` en tête, ce qui couvre tous les appels, et le `--noproxy` devenu redondant est retiré. Les deux graphies sont exportées parce que curl lit d'abord la minuscule : un `no_proxy` hérité l'emporterait sinon. Nouveau test dans `__tests__/ci.test.js` : un boot complet derrière un faux proxy qui répond à tout et note chaque requête reçue. Le test prouve d'abord que le proxy capte bien les requêtes (un `curl` nu, avec le même environnement, l'atteint), puis exige qu'aucune requête du script ne l'atteigne et que le script passe. `http_proxy` et `ALL_PROXY` sont posés tous les deux, pour qu'un simple `unset http_proxy` ne suffise pas. `no_proxy` y vaut une liste qui laisse la boucle locale de côté, comme un `no_proxy` hérité pourrait le faire : un script qui n'exporterait que `NO_PROXY` échoue. `NO_PROXY` est retiré, pour que rien d'hérité ne décide du résultat. **2. `continue-on-error`, sous toute écriture littérale.** Le détecteur est le mot nu, `/continue-on-error/`, sans ancrage ni deux-points exigés, toujours appliqué à `ciCode` (sans les lignes de commentaire). Il ferme le style flow signalé par la revue et, au passage, la clé entre guillemets (`"continue-on-error": true`) et une ancre YAML réutilisée comme clé, qui passaient de la même façon. Une clé écrite avec une séquence d'échappement (`"continue\u002don-error"`) passe encore : l'épingle lit du texte, pas du YAML. Contrepartie assumée : le mot cité dans un script `run:`, un nom d'étape ou un commentaire de fin de ligne fait aussi échouer l'épingle, bruyamment. Le détecteur est vérifié pour lui-même : il doit attraper la clé en bloc, en élément de liste, en style flow, entre guillemets et via une ancre, et ignorer une ligne de commentaire entière. Les six écritures citées ici donnent bien `continue-on-error: True` sous PyYAML. **3. Heuristique « `require` en colonne 0 » : deux trous fermés, les autres consignés.** L'épingle lit maintenant aussi `require (` et `import (`, avec une espace avant la parenthèse. Restent ouverts, entre autres : une fonction écrite entière sur une ligne en colonne 0 (`const lazy = () => require("x");`), un `require(` renvoyé à la ligne en colonne 0 dans une fonction, et toute indirection, comme un alias de `require`. Aucune de ces formes ne correspond au style du dépôt, et la deuxième a la forme exacte du `} = require("node:fs");` légitime qui ferme l'import multiligne d'`index.js` : seul un parseur les distinguerait. Les trous restants sont écrits au-dessus de l'épingle et dans `CLAUDE.md`. Le détecteur, sorti dans `loadsLate(line)`, est vérifié pour lui-même sur six lignes, puisqu'aucun fichier runtime n'enfreint l'épingle : `require` indenté et `import` à toute colonne, avec ou sans espace, sont signalés ; un `require` de premier niveau et une ligne de commentaire passent. **Preuve par mutation, première passe** (fichiers restaurés depuis une copie, `git diff --stat` identique avant et après) : | Mutation | Résultat | |---|---| | étape en style flow ajoutée à `ci.yml`, **ancienne** épingle | 12/12 vert : le trou est réel | | script de `main` (`--noproxy` sur le seul pré-contrôle) | 1/13 rouge : le proxy reçoit les deux `GET /health` de la boucle d'attente et de la première assertion | | `export NO_PROXY` retiré du script corrigé | 1/13 rouge : le pré-contrôle passe par le proxy, qui fait paraître le port pris | | étape en style flow ajoutée à `ci.yml` | 1/18 rouge : `never lets a failing step pass` | | clé entre guillemets ajoutée à `ci.yml` | 1/18 rouge : même test | | détecteur ré-ancré (ancienne regex) | 2/18 rouges : lignes style flow et clé entre guillemets | | classe de guillemets retirée du détecteur | 1/18 rouge : ligne clé entre guillemets | | retrait des lignes de commentaire désactivé | 1/18 rouge : ligne commentaire | Les passes suivantes, qui répondent aux revues 17525, 17546 et 17559, ont chacune leur tableau en commentaire (17541, 17553, puis celui qui suit cette mise à jour). Reproduction exacte de la revue de #34, refaite : `http_proxy=http://127.0.0.1:9 sh scripts/boot-from-copy.sh` donne, sur le script de `main`, `listening on :3097` puis `FAIL: the server did not answer on :3097 within 10 s` après 10 s ; sur cette branche, `boot-from-copy: PASS`, y compris avec `no_proxy=example.invalid`. **Suite locale** : 353/353 vitest (`ci.test.js` passe de 12 à 25 cas), 45/45 `test-curl.sh`, `boot-from-copy: PASS`. En local, le nouveau test prend la branche non root du script ; la branche root (`su node`, Alpine) ne tourne qu'en CI, d'où la vérification du statut de commit avant la revue. **Hors périmètre** : `test-curl.sh` a la même exposition aux proxys (mesuré : code 7 après 11 s, sans aucune ligne de sortie). Suivi dans #41. Aucun fichier runtime touché : pas de redéploiement après le merge. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
maximus added 3 commits 2026-10-01 00:57:05 +00:00
Only the port pre-check of scripts/boot-from-copy.sh carried --noproxy.
The wait loop and the two assertions still honoured http_proxy, so on a
workstation with a proxy configured the script talked to the proxy
instead of the server it had just started, and failed loudly.

Export NO_PROXY='*' and no_proxy='*' at the top of the script, which
covers every curl call, and drop the now-redundant --noproxy.

The new test boots the script for real behind a fake proxy that answers
everything and records what reaches it. It first proves the proxy is
live (a bare curl with the same environment reaches it), then requires
that nothing of the boot check reaches it and that the script passes.
Red on the previous script (the wait loop and the first assertion went
through the proxy), red again when the export is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pin was anchored at the start of the line, so a step written as a
flow mapping, `- { name: x, run: false, continue-on-error: true }`,
passed it: 12/12 green with that step appended to ci.yml. A quoted key,
`"continue-on-error": true`, escaped it the same way.

The detector is now unanchored and accepts a closing quote before the
colon. It still runs on `ciCode`, so full comment lines may quote the
key; anything else that quotes it (a run: script, a step name, a
trailing comment) now fails the pin, loudly, by design.

The detector is checked on its own: through the same comment stripping
as `ciCode`, it must catch a block key, a list item, a flow mapping and
a quoted key, and ignore a full comment line. Proven by mutation: the
flow-style and quoted-key steps appended to ci.yml each turn the pin
red; the old anchored regex turns the flow-mapping and quoted-key rows
red; dropping the quote class turns the quoted-key row red; disabling
the comment stripping turns the comment row red.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs: record the known holes of the column-0 require pin (#35)
All checks were successful
CI / verification (pull_request) Successful in 21s
ae27c7d504
The PR #34 review listed two forms that pass the "require at column 0"
heuristic: a function written whole on a column-0 line, and a require(
wrapped onto a column-0 line inside a function. Both stay open: neither
fits the style of the repository, and the second has the very shape of
the legitimate `} = require("node:fs");` that closes index.js's
multi-line import, so only a parser could tell them apart. They are now
written next to the pin and in CLAUDE.md, rather than only in a closed
PR and its follow-up issue.

CLAUDE.md also records the proxy bypass of boot-from-copy.sh, the wider
continue-on-error pin and its self-check, and the new counts (ci.test.js
12 -> 18 cases, vitest 340 -> 346).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

Review — APPROVE

Résumé : les trois constats de #35 sont traités comme l'issue les proposait, et chaque ligne du tableau de mutations de la PR se reproduit chez le relecteur. Restent trois angles morts non bloquants : le test du proxy ne voit pas la perte de la graphie minuscule, l'épingle « colonne 0 » a deux trous de plus que les deux consignés, et « quelle qu'en soit l'écriture » promet plus que le détecteur ne tient.

CI verte sur ae27c7d (CI / verification (pull_request) : success). Le log du run 11 montre 346/346, ci.test.js à 18 cas dont le nouveau test du proxy, exécuté sous root dans node:22-alpine (curl 8.22), puis boot-from-copy: PASS.

Vérification par mutation (faite par le relecteur, sur une copie hors dépôt, chaque fichier restauré et comparé par cmp)

Mutation Résultat
script de main (--noproxy sur le seul pré-contrôle) 1/18 rouge : le proxy reçoit deux GET /health
ligne export retirée du script 1/18 rouge : le pré-contrôle passe par le proxy
détecteur ré-ancré (ancienne regex) 2/18 rouges (style flow, clé entre guillemets)
classe de guillemets retirée du détecteur 1/18 rouge
retrait des lignes de commentaire désactivé 1/18 rouge
étape en style flow, puis clé entre guillemets, ajoutées à ci.yml 1/18 rouge chacune (never lets a failing step pass)
clé explicite sur deux lignes (? continue-on-error / : true) 1/18 rouge : le \s* franchit le saut de ligne
export NO_PROXY='*' seul, sans la minuscule 18/18 vert (suggestion 1)

Local : 346/346 vitest, 45/45 test-curl.sh, boot-from-copy: PASS, y compris avec http_proxy=http://127.0.0.1:9. Timeline de #35 : un seul pull_ref en closes, cette PR ; #41 reste en none. Aucun fichier du COPY touché : pas de redéploiement.

Suggestions (non bloquantes)

  1. La graphie minuscule n'est pas épinglée (__tests__/ci.test.js:185). Le script exporte les deux graphies parce que curl lit no_proxy avant NO_PROXY (scripts/boot-from-copy.sh:26-27, CLAUDE.md:75). Le test retire le no_proxy hérité : il ne peut donc pas voir cette raison disparaître, et export NO_PROXY='*' seul le laisse vert. Le défaut est réel : ce script muté, lancé avec http_proxy=<faux proxy> et un no_proxy=example.invalid hérité, échoue en port 3197 is already taken (curl exit 0), le pré-contrôle ayant interrogé le proxy ; le script de la PR passe dans le même environnement. Correctif d'une ligne, vérifié : no_proxy: "example.invalid" au lieu de undefined, soit une valeur héritée qui ne couvre pas la boucle locale. Le curl nu atteint toujours le proxy, le test reste vert sur la PR et rougit sur la mutation (1/18) comme sur le script de main. Le commentaire de la l. 184 est à ajuster en conséquence.

  2. Deux trous de plus dans l'épingle « require en colonne 0 », fermables sans faux positif (__tests__/ci.test.js:232-233). Un espace avant la parenthèse échappe aux deux regex : return require ("./metrics.js"); indenté dans une fonction reste vert, return import ("./metrics.js"); aussi. Le second pèse plus lourd : le contrôle import( n'a pas de condition de colonne, c'est la seule partie absolue de l'épingle, et un espace la contourne. /\brequire\s*\(/ et /\bimport\s*\(/ attrapent les deux formes et ne trouvent aucun contrevenant dans index.js ni metrics.js (vérifié). À défaut, les ajouter à la liste des trous (l. 211-217, et CLAUDE.md:72, qui en annonce « deux »).

  3. « Quelle qu'en soit l'écriture » promet trop (CLAUDE.md:72, __tests__/ci.test.js:85). Deux écritures YAML valides passent encore l'épingle (18/18 vert), toutes deux volontaires, et PyYAML lit chacune comme continue-on-error: True : une séquence d'échappement dans une clé entre guillemets doubles ("continue-on-error": true), et une ancre réutilisée comme clé (KEYS: &coe continue-on-error sous env:, puis *coe : true dans une étape). La seconde se ferme sans frais en retirant le suffixe ["']?\s*: (/continue-on-error/) : les faux positifs restent ceux que la PR accepte déjà, et le ci.yml actuel n'a aucune occurrence hors commentaires. La première suppose une intention : « toute écriture littérale » décrit l'épingle exactement.

🤖 Generated with Claude Code

## Review — APPROVE **Résumé** : les trois constats de #35 sont traités comme l'issue les proposait, et chaque ligne du tableau de mutations de la PR se reproduit chez le relecteur. Restent trois angles morts non bloquants : le test du proxy ne voit pas la perte de la graphie minuscule, l'épingle « colonne 0 » a deux trous de plus que les deux consignés, et « quelle qu'en soit l'écriture » promet plus que le détecteur ne tient. CI verte sur `ae27c7d` (`CI / verification (pull_request)` : success). Le log du run 11 montre 346/346, `ci.test.js` à 18 cas dont le nouveau test du proxy, exécuté sous root dans `node:22-alpine` (curl 8.22), puis `boot-from-copy: PASS`. ### Vérification par mutation (faite par le relecteur, sur une copie hors dépôt, chaque fichier restauré et comparé par `cmp`) | Mutation | Résultat | |---|---| | script de `main` (`--noproxy` sur le seul pré-contrôle) | 1/18 rouge : le proxy reçoit deux `GET /health` | | ligne `export` retirée du script | 1/18 rouge : le pré-contrôle passe par le proxy | | détecteur ré-ancré (ancienne regex) | 2/18 rouges (style flow, clé entre guillemets) | | classe de guillemets retirée du détecteur | 1/18 rouge | | retrait des lignes de commentaire désactivé | 1/18 rouge | | étape en style flow, puis clé entre guillemets, ajoutées à `ci.yml` | 1/18 rouge chacune (`never lets a failing step pass`) | | clé explicite sur deux lignes (`? continue-on-error` / `: true`) | 1/18 rouge : le `\s*` franchit le saut de ligne | | `export NO_PROXY='*'` seul, sans la minuscule | **18/18 vert** (suggestion 1) | Local : 346/346 vitest, 45/45 `test-curl.sh`, `boot-from-copy: PASS`, y compris avec `http_proxy=http://127.0.0.1:9`. Timeline de #35 : un seul `pull_ref` en `closes`, cette PR ; #41 reste en `none`. Aucun fichier du `COPY` touché : pas de redéploiement. ### Suggestions (non bloquantes) 1. **La graphie minuscule n'est pas épinglée** (`__tests__/ci.test.js:185`). Le script exporte les deux graphies parce que curl lit `no_proxy` avant `NO_PROXY` (`scripts/boot-from-copy.sh:26-27`, `CLAUDE.md:75`). Le test retire le `no_proxy` hérité : il ne peut donc pas voir cette raison disparaître, et `export NO_PROXY='*'` seul le laisse vert. Le défaut est réel : ce script muté, lancé avec `http_proxy=<faux proxy>` et un `no_proxy=example.invalid` hérité, échoue en `port 3197 is already taken (curl exit 0)`, le pré-contrôle ayant interrogé le proxy ; le script de la PR passe dans le même environnement. Correctif d'une ligne, vérifié : `no_proxy: "example.invalid"` au lieu de `undefined`, soit une valeur héritée qui ne couvre pas la boucle locale. Le `curl` nu atteint toujours le proxy, le test reste vert sur la PR et rougit sur la mutation (1/18) comme sur le script de `main`. Le commentaire de la l. 184 est à ajuster en conséquence. 2. **Deux trous de plus dans l'épingle « `require` en colonne 0 », fermables sans faux positif** (`__tests__/ci.test.js:232-233`). Un espace avant la parenthèse échappe aux deux regex : ` return require ("./metrics.js");` indenté dans une fonction reste vert, ` return import ("./metrics.js");` aussi. Le second pèse plus lourd : le contrôle `import(` n'a pas de condition de colonne, c'est la seule partie absolue de l'épingle, et un espace la contourne. `/\brequire\s*\(/` et `/\bimport\s*\(/` attrapent les deux formes et ne trouvent aucun contrevenant dans `index.js` ni `metrics.js` (vérifié). À défaut, les ajouter à la liste des trous (l. 211-217, et `CLAUDE.md:72`, qui en annonce « deux »). 3. **« Quelle qu'en soit l'écriture » promet trop** (`CLAUDE.md:72`, `__tests__/ci.test.js:85`). Deux écritures YAML valides passent encore l'épingle (18/18 vert), toutes deux volontaires, et PyYAML lit chacune comme `continue-on-error: True` : une séquence d'échappement dans une clé entre guillemets doubles (`"continue-on-error": true`), et une ancre réutilisée comme clé (`KEYS: &coe continue-on-error` sous `env:`, puis `*coe : true` dans une étape). La seconde se ferme sans frais en retirant le suffixe `["']?\s*:` (`/continue-on-error/`) : les faux positifs restent ceux que la PR accepte déjà, et le `ci.yml` actuel n'a aucune occurrence hors commentaires. La première suppose une intention : « toute écriture littérale » décrit l'épingle exactement. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
maximus added 1 commit 2026-10-01 19:56:34 +00:00
test(ci): close the gaps found in the review of PR #42 (#35)
All checks were successful
CI / verification (pull_request) Successful in 20s
92bfb426ca
Three findings of the review, each first measured green on the previous
tree (18/18), then red on this one.

The proxy test dropped the inherited no_proxy, so a script exporting
NO_PROXY alone stayed green, although curl reads the lowercase variable
first and an inherited no_proxy would win. The test now sets no_proxy
to a list that leaves the loopback out. Red when the script exports
NO_PROXY alone, and on the script from main.

The column-0 require pin missed a space before the parenthesis:
an indented `require (` and an `import (` both passed. The regexes
now accept whitespace there. Red on each.

The continue-on-error detector required a colon after the word, so a
YAML anchor reused as a key (`&coe continue-on-error`, then
`*coe : true`) passed, while PyYAML reads it as the key. The detector
is now the bare word, with a self-check row for the anchor; red when
the colon is required again, three rows red with the original anchored
regex. An escaped key still passes, since the pin reads text and not
YAML: the comment and CLAUDE.md now claim any literal spelling, no
longer any spelling, and list the indirections the require pin cannot
follow.

ci.test.js 18 -> 19 cases, vitest 346 -> 347.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

Suggestions de la revue 17525 appliquées — 92bfb42

Les trois suggestions, chacune d'abord mesurée verte sur ae27c7d, puis rouge sur 92bfb42. Fichiers restaurés depuis une copie, git diff --stat identique avant et après chaque mutation.

Mutation ae27c7d 92bfb42
script qui n'exporte que NO_PROXY='*' 18/18 vert 1/19 rouge : test du proxy (pré-contrôle par le proxy, port vu pris)
script de main 1/18 rouge 1/19 rouge : test du proxy
require ("node:path"); ajouté à index.js 18/18 vert 1/19 rouge : index.js requires only at module level
import ("node:path"); ajouté à index.js 18/18 vert 1/19 rouge : même test
ancre YAML réutilisée comme clé, ajoutée à ci.yml 18/18 vert 1/19 rouge : never lets a failing step pass
deux-points de nouveau exigés après le mot (sans objet) 1/19 rouge : ligne ancre
détecteur ré-ancré (regex d'origine) 2/18 rouges 3/19 rouges : lignes style flow, clé entre guillemets, ancre

Les écritures de la table du détecteur, plus la clé échappée, sont vérifiées sous PyYAML 6.0.1 : toutes donnent continue-on-error: True. La clé échappée reste un trou assumé, et la doc dit maintenant « toute écriture littérale ».

Suite locale : 347/347 vitest, 45/45 test-curl.sh, boot-from-copy: PASS, et PASS aussi avec http_proxy=http://127.0.0.1:9 no_proxy=example.invalid. Le corps de la PR est mis à jour pour décrire l'état final.

## Suggestions de la revue 17525 appliquées — `92bfb42` Les trois suggestions, chacune d'abord mesurée verte sur `ae27c7d`, puis rouge sur `92bfb42`. Fichiers restaurés depuis une copie, `git diff --stat` identique avant et après chaque mutation. | Mutation | `ae27c7d` | `92bfb42` | |---|---|---| | script qui n'exporte que `NO_PROXY='*'` | 18/18 vert | 1/19 rouge : test du proxy (pré-contrôle par le proxy, port vu pris) | | script de `main` | 1/18 rouge | 1/19 rouge : test du proxy | | ` require ("node:path");` ajouté à `index.js` | 18/18 vert | 1/19 rouge : `index.js requires only at module level` | | `import ("node:path");` ajouté à `index.js` | 18/18 vert | 1/19 rouge : même test | | ancre YAML réutilisée comme clé, ajoutée à `ci.yml` | 18/18 vert | 1/19 rouge : `never lets a failing step pass` | | deux-points de nouveau exigés après le mot | (sans objet) | 1/19 rouge : ligne ancre | | détecteur ré-ancré (regex d'origine) | 2/18 rouges | 3/19 rouges : lignes style flow, clé entre guillemets, ancre | Les écritures de la table du détecteur, plus la clé échappée, sont vérifiées sous PyYAML 6.0.1 : toutes donnent `continue-on-error: True`. La clé échappée reste un trou assumé, et la doc dit maintenant « toute écriture littérale ». Suite locale : 347/347 vitest, 45/45 `test-curl.sh`, `boot-from-copy: PASS`, et `PASS` aussi avec `http_proxy=http://127.0.0.1:9 no_proxy=example.invalid`. Le corps de la PR est mis à jour pour décrire l'état final.
Author
Owner

Review — REQUEST_CHANGES

Résumé : revue de 92bfb42. Les trois suggestions de la revue 17525 sont appliquées, et chacune se vérifie par mutation chez le relecteur. Un point bloque : l'exemple de « clé échappée » que ce commit ajoute au commentaire du test et à CLAUDE.md n'est pas échappé. C'est la clé entre guillemets, que l'épingle attrape : la doc affirme le contraire de ce que le test prouve.

CI verte sur 92bfb42 (CI / verification (pull_request) : success, run 12). Le log montre HEAD is now at 92bfb42, 347/347 dont ci.test.js à 19 cas avec le test du proxy, dans node:22-alpine (curl 8.22.0), puis 45/45 test-curl.sh et boot-from-copy: PASS.

Problème bloquant

  1. L'exemple de clé échappée n'est pas échappé (__tests__/ci.test.js:91, CLAUDE.md:72). Les deux fichiers donnent "continue-on-error", avec un tiret ASCII (vérifié à l'octet par od -c), comme exemple de clé qui « passe encore ». C'est la clé entre guillemets, et elle est attrapée : la ligne a quoted key de l'autocontrôle (__tests__/ci.test.js:107) porte exactement ce texte, et la même clé ajoutée à ci.yml fait rougir never lets a failing step pass (1/19, tableau). La parenthèse de CLAUDE.md se contredit donc elle-même : un détecteur « vérifié [...] entre guillemets », puis la même clé qui « passe encore ». Le \u002d s'est perdu en route : la couche qui transmet un texte à un outil peut décoder la séquence en tiret (le relecteur l'a constaté sur ses propres commandes), et l'exemple de la revue 17525 a subi la même perte à sa publication. Le corps de la PR, lui, porte bien "continue\u002don-error". Correctif : écrire "continue\u002don-error" aux deux endroits, puis vérifier dans les fichiers et non dans l'outil d'édition : grep -c u002d CLAUDE.md __tests__/ci.test.js doit rendre 1 pour chacun (0 aujourd'hui).

Vérification par mutation (faite par le relecteur, sur une copie hors dépôt, chaque fichier restauré et comparé au dépôt par cmp)

Mutation Résultat
script de main 1/19 rouge : test du proxy
export NO_PROXY='*' seul 1/19 rouge : test du proxy. Avec l'environnement de test d'ae27c7d (no_proxy retiré), 19/19 vert : c'est bien le changement du test qui l'attrape
ligne export retirée 1/19 rouge : test du proxy
export no_proxy='*' seul 19/19 vert, attendu : curl ne lit NO_PROXY qu'en l'absence de la minuscule
ancre réutilisée comme clé, ajoutée à ci.yml 1/19 rouge : never lets a failing step pass
"continue-on-error": true ajouté à ci.yml, l'exemple que la doc dit passer 1/19 rouge : même test (point bloquant)
"continue\u002don-error": true ajouté à ci.yml 19/19 vert : le trou assumé
deux-points de nouveau exigés après le mot 1/19 rouge : ligne ancre
détecteur ré-ancré (regex de main) 3/19 rouges : style flow, clé entre guillemets, ancre
retrait des lignes de commentaire désactivé 1/19 rouge : ligne commentaire
return require ("node:path"); indenté dans une fonction d'index.js 1/19 rouge : index.js requires only at module level
return import ("node:path"); indenté dans une fonction 1/19 rouge : même test
require puis saut de ligne puis ("node:path") ; la même chose avec import ; require /* c */ ( ; require?.( 19/19 vert chacun (suggestion 1)
if: false sur l'étape Unit tests ; shell: true {0} sur l'étape du boot check 19/19 vert chacun (suggestion 2)

PyYAML 6.0.1 : les cinq écritures de l'autocontrôle, plus trois écritures échappées (\u002d, \x2d, saut de ligne échappé dans une clé explicite), donnent toutes continue-on-error: True ; seules les cinq premières laissent le mot dans ciCode. Sans séquence d'échappement, YAML ne recolle deux lignes qu'avec une espace ou un saut de ligne : la clé tient sur une seule ligne, et une ligne qui commence par # est soit un commentaire, soit un contenu qui ferait entrer le # dans la clé. La promesse « toute écriture littérale » tient.

Local : 347/347 vitest, 45/45 test-curl.sh, boot-from-copy: PASS, y compris avec http_proxy et ALL_PROXY sur 127.0.0.1:9 et no_proxy=example.invalid. Avec http_proxy=http://127.0.0.1:9, le script de main échoue en the server did not answer on :3197 within 10 s. Timeline de #35 : un seul pull_ref en closes, cette PR ; #41 reste en none. Base à jour (48c9a40, tête de main). Aucun fichier du COPY touché : pas de redéploiement.

Suggestions (non bloquantes)

  1. La liste des trous de l'épingle « colonne 0 » n'est pas complète (__tests__/ci.test.js:225-234, CLAUDE.md:72). Quatre autres appels littéraux passent (tableau) : un saut de ligne entre le mot et la parenthèse, avec require comme avec import (ce dernier contourne la seule partie absolue de l'épingle), un commentaire au même endroit, et l'appel optionnel require?.(. Même classe que les trous consignés, et aucune de ces formes ne sort d'un formateur : inutile de courir après chacune. Un « entre autres » en tête de liste suffit, pour qu'elle ne se lise pas comme complète.

  2. Hors périmètre : continue-on-error n'est pas le seul moyen de faire passer une étape rouge au vert (.forgejo/workflows/ci.yml:54-55). if: false sur l'étape Unit tests, la façon courante de couper une étape « pour un temps », la saute sans rien rougir ; shell: true {0} sur l'étape du boot check la fait réussir sans lancer le script. Les deux laissent la ligne run: en place, et les 19 cas passent. Une liste blanche des clés d'étape (name, uses, run aujourd'hui) fermerait la classe d'un coup, au lieu d'une clé à la fois. À verser dans une issue si le sujet en vaut le coût.

🤖 Generated with Claude Code

## Review — REQUEST_CHANGES **Résumé** : revue de `92bfb42`. Les trois suggestions de la revue 17525 sont appliquées, et chacune se vérifie par mutation chez le relecteur. Un point bloque : l'exemple de « clé échappée » que ce commit ajoute au commentaire du test et à `CLAUDE.md` n'est pas échappé. C'est la clé entre guillemets, que l'épingle attrape : la doc affirme le contraire de ce que le test prouve. CI verte sur `92bfb42` (`CI / verification (pull_request)` : success, run 12). Le log montre `HEAD is now at 92bfb42`, 347/347 dont `ci.test.js` à 19 cas avec le test du proxy, dans `node:22-alpine` (curl 8.22.0), puis 45/45 `test-curl.sh` et `boot-from-copy: PASS`. ### Problème bloquant 1. **L'exemple de clé échappée n'est pas échappé** (`__tests__/ci.test.js:91`, `CLAUDE.md:72`). Les deux fichiers donnent `"continue-on-error"`, avec un tiret ASCII (vérifié à l'octet par `od -c`), comme exemple de clé qui « passe encore ». C'est la clé entre guillemets, et elle est attrapée : la ligne `a quoted key` de l'autocontrôle (`__tests__/ci.test.js:107`) porte exactement ce texte, et la même clé ajoutée à `ci.yml` fait rougir `never lets a failing step pass` (1/19, tableau). La parenthèse de `CLAUDE.md` se contredit donc elle-même : un détecteur « vérifié [...] entre guillemets », puis la même clé qui « passe encore ». Le `\u002d` s'est perdu en route : la couche qui transmet un texte à un outil peut décoder la séquence en tiret (le relecteur l'a constaté sur ses propres commandes), et l'exemple de la revue 17525 a subi la même perte à sa publication. Le corps de la PR, lui, porte bien `"continue\u002don-error"`. Correctif : écrire `"continue\u002don-error"` aux deux endroits, puis vérifier dans les fichiers et non dans l'outil d'édition : `grep -c u002d CLAUDE.md __tests__/ci.test.js` doit rendre 1 pour chacun (0 aujourd'hui). ### Vérification par mutation (faite par le relecteur, sur une copie hors dépôt, chaque fichier restauré et comparé au dépôt par `cmp`) | Mutation | Résultat | |---|---| | script de `main` | 1/19 rouge : test du proxy | | `export NO_PROXY='*'` seul | 1/19 rouge : test du proxy. Avec l'environnement de test d'`ae27c7d` (`no_proxy` retiré), 19/19 vert : c'est bien le changement du test qui l'attrape | | ligne `export` retirée | 1/19 rouge : test du proxy | | `export no_proxy='*'` seul | 19/19 vert, attendu : curl ne lit `NO_PROXY` qu'en l'absence de la minuscule | | ancre réutilisée comme clé, ajoutée à `ci.yml` | 1/19 rouge : `never lets a failing step pass` | | `"continue-on-error": true` ajouté à `ci.yml`, l'exemple que la doc dit passer | 1/19 rouge : même test (point bloquant) | | `"continue\u002don-error": true` ajouté à `ci.yml` | 19/19 vert : le trou assumé | | deux-points de nouveau exigés après le mot | 1/19 rouge : ligne ancre | | détecteur ré-ancré (regex de `main`) | 3/19 rouges : style flow, clé entre guillemets, ancre | | retrait des lignes de commentaire désactivé | 1/19 rouge : ligne commentaire | | `return require ("node:path");` indenté dans une fonction d'`index.js` | 1/19 rouge : `index.js requires only at module level` | | `return import ("node:path");` indenté dans une fonction | 1/19 rouge : même test | | `require` puis saut de ligne puis `("node:path")` ; la même chose avec `import` ; `require /* c */ (` ; `require?.(` | 19/19 vert chacun (suggestion 1) | | `if: false` sur l'étape `Unit tests` ; `shell: true {0}` sur l'étape du boot check | 19/19 vert chacun (suggestion 2) | PyYAML 6.0.1 : les cinq écritures de l'autocontrôle, plus trois écritures échappées (`\u002d`, `\x2d`, saut de ligne échappé dans une clé explicite), donnent toutes `continue-on-error: True` ; seules les cinq premières laissent le mot dans `ciCode`. Sans séquence d'échappement, YAML ne recolle deux lignes qu'avec une espace ou un saut de ligne : la clé tient sur une seule ligne, et une ligne qui commence par `#` est soit un commentaire, soit un contenu qui ferait entrer le `#` dans la clé. La promesse « toute écriture littérale » tient. Local : 347/347 vitest, 45/45 `test-curl.sh`, `boot-from-copy: PASS`, y compris avec `http_proxy` et `ALL_PROXY` sur `127.0.0.1:9` et `no_proxy=example.invalid`. Avec `http_proxy=http://127.0.0.1:9`, le script de `main` échoue en `the server did not answer on :3197 within 10 s`. Timeline de #35 : un seul `pull_ref` en `closes`, cette PR ; #41 reste en `none`. Base à jour (`48c9a40`, tête de `main`). Aucun fichier du `COPY` touché : pas de redéploiement. ### Suggestions (non bloquantes) 1. **La liste des trous de l'épingle « colonne 0 » n'est pas complète** (`__tests__/ci.test.js:225-234`, `CLAUDE.md:72`). Quatre autres appels littéraux passent (tableau) : un saut de ligne entre le mot et la parenthèse, avec `require` comme avec `import` (ce dernier contourne la seule partie absolue de l'épingle), un commentaire au même endroit, et l'appel optionnel `require?.(`. Même classe que les trous consignés, et aucune de ces formes ne sort d'un formateur : inutile de courir après chacune. Un « entre autres » en tête de liste suffit, pour qu'elle ne se lise pas comme complète. 2. **Hors périmètre : `continue-on-error` n'est pas le seul moyen de faire passer une étape rouge au vert** (`.forgejo/workflows/ci.yml:54-55`). `if: false` sur l'étape `Unit tests`, la façon courante de couper une étape « pour un temps », la saute sans rien rougir ; `shell: true {0}` sur l'étape du boot check la fait réussir sans lancer le script. Les deux laissent la ligne `run:` en place, et les 19 cas passent. Une liste blanche des clés d'étape (`name`, `uses`, `run` aujourd'hui) fermerait la classe d'un coup, au lieu d'une clé à la fois. À verser dans une issue si le sujet en vaut le coût. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
maximus added 1 commit 2026-10-01 20:13:58 +00:00
docs: keep the escaped-key example escaped, bound the holes list (#35)
All checks were successful
CI / verification (pull_request) Successful in 21s
a03e4d4296
The example of a key that still passes the continue-on-error pin had
lost its u002d escape sequence on the way into ci.test.js and CLAUDE.md:
both read as the plain quoted key, which the pin catches (the "a quoted
key" self-check row holds that very text). CLAUDE.md thus said the
detector was checked on the quoted key, then that the same key passed.
The example now carries the escape, written through a backslash built
at runtime so that no tool decodes it: `grep -c u002d` gives 1 per
file, PyYAML reads the key as continue-on-error, the pin misses it.

The list of known holes of the column-0 require pin now opens with
"among others" ("entre autres" in CLAUDE.md): a line break or a comment
between require and its parenthesis, and require?.(, pass as well, as
the review of 92bfb42 measured.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

Revue 17546 traitée — a03e4d4

Bloquant : l'exemple de clé échappée. Il avait perdu sa séquence d'échappement en route vers __tests__/ci.test.js et CLAUDE.md : les deux lisaient la clé entre guillemets ordinaire, que l'épingle attrape. L'exemple porte maintenant la séquence, écrite par une barre oblique construite à l'exécution (chr(92)), pour qu'aucun outil ne la décode en route. Vérifications :

  • grep -c u002d CLAUDE.md __tests__/ci.test.js rend 1 par fichier (0 sur 92bfb42) ;
  • od -c montre la barre oblique devant u002d dans le commentaire du test ;
  • PyYAML lit cette clé comme continue-on-error: True, et /continue-on-error/ ne la trouve pas dans le texte : « passe encore » est exact.

Suggestion 1 appliquée. La liste des trous de l'épingle « colonne 0 » commence par « entre autres » dans CLAUDE.md, et par « among others » dans le commentaire du test.

Suggestion 2 non traitée : if: false et shell: true {0} sortent du périmètre de #35. La liste blanche des clés d'étape reste à décider.

Suite locale : 347/347 vitest. Aucun test ne change ; seuls deux commentaires et une ligne de CLAUDE.md bougent.

## Revue 17546 traitée — `a03e4d4` **Bloquant : l'exemple de clé échappée.** Il avait perdu sa séquence d'échappement en route vers `__tests__/ci.test.js` et `CLAUDE.md` : les deux lisaient la clé entre guillemets ordinaire, que l'épingle attrape. L'exemple porte maintenant la séquence, écrite par une barre oblique construite à l'exécution (`chr(92)`), pour qu'aucun outil ne la décode en route. Vérifications : - `grep -c u002d CLAUDE.md __tests__/ci.test.js` rend 1 par fichier (0 sur `92bfb42`) ; - `od -c` montre la barre oblique devant `u002d` dans le commentaire du test ; - PyYAML lit cette clé comme `continue-on-error: True`, et `/continue-on-error/` ne la trouve pas dans le texte : « passe encore » est exact. **Suggestion 1 appliquée.** La liste des trous de l'épingle « colonne 0 » commence par « entre autres » dans `CLAUDE.md`, et par « among others » dans le commentaire du test. **Suggestion 2 non traitée** : `if: false` et `shell: true {0}` sortent du périmètre de #35. La liste blanche des clés d'étape reste à décider. Suite locale : 347/347 vitest. Aucun test ne change ; seuls deux commentaires et une ligne de `CLAUDE.md` bougent.
Author
Owner

Review — APPROVE

Résumé : revue de a03e4d4. Le point bloquant de la revue 17546 est corrigé à l'octet près, la suggestion 1 est appliquée, et les mesures de la PR se reproduisent chez le relecteur. Reste un angle mort non bloquant qu'aucune revue précédente n'a mesuré : le détecteur de l'épingle « require en colonne 0 » n'a pas d'autocontrôle. On peut l'affaiblir, ou le supprimer, sans qu'un seul test rougisse.

CI verte sur a03e4d4 (CI / verification (pull_request) : success, run 13). Le log montre HEAD is now at a03e4d4, puis 347/347, dont ci.test.js à 19 cas avec le test du proxy (1008 ms), dans node:22-alpine sous root (Node 22.23.3, curl 8.22.0). Suivent 45/45 test-curl.sh et boot-from-copy: PASS.

Point bloquant de 17546 : corrigé

  • grep -c u002d rend 1 pour CLAUDE.md et 1 pour __tests__/ci.test.js. od -c montre la barre oblique devant u002d aux deux endroits (__tests__/ci.test.js:91, CLAUDE.md:72).
  • PyYAML 6.0.1 lit cette clé comme continue-on-error: True, et le mot nu n'apparaît pas dans le texte. Ajoutée à ci.yml, elle laisse les 19 cas au vert : « passe encore » est exact.
  • La liste des trous commence par « among others » (__tests__/ci.test.js:226) et par « entre autres » (CLAUDE.md:72).

Vérification par mutation (faite par le relecteur, sur une copie hors dépôt tirée par git archive, chaque fichier restauré puis comparé à a03e4d4 par cmp)

Mutation Résultat
script de main 1/19 rouge : test du proxy
export NO_PROXY='*' seul 1/19 rouge : test du proxy
export no_proxy='*' seul 19/19 vert, attendu : curl lit la minuscule d'abord
ligne export retirée 1/19 rouge : test du proxy
unset http_proxy à la place de l'export 1/19 rouge. Avec ALL_PROXY retiré du test, 19/19 vert : c'est bien ALL_PROXY qui porte ce cas
détecteur ré-ancré (regex de main) 3/19 rouges : style flow, clé entre guillemets, ancre
/continue-on-error["']?\s*:/ 1/19 rouge : ancre
/continue-on-error\s*:/, la forme que #35 proposait 2/19 rouges : clé entre guillemets, ancre
clé échappée ajoutée à ci.yml 19/19 vert : le trou assumé
require ( indenté, puis import (, dans une fonction ajoutée à index.js 1/19 rouge chacun : index.js requires only at module level
regex require, puis regex import, ramenées à leur forme de main (/\brequire\(/, /\bimport\(/) 19/19 vert chacune
les mêmes, avec require ( puis import ( injectés dans index.js 19/19 vert chacune
clause require, puis clause import, supprimées du détecteur 19/19 vert chacune

Local : 347/347 vitest, 45/45 test-curl.sh et boot-from-copy: PASS. Le script passe aussi avec http_proxy, ALL_PROXY et all_proxy sur 127.0.0.1:9, et no_proxy/NO_PROXY à example.invalid. Avec http_proxy=http://127.0.0.1:9 seul, le script de main échoue en the server did not answer on :3197 within 10 s, après 10,8 s. Timeline de #35 : un seul pull_ref en closes, cette PR ; #41 reste en none. Base à jour (48c9a40, tête de main). Commits au format conventionnel, chacun lié à #35. Aucun fichier du COPY touché : pas de redéploiement.

Suggestions (non bloquantes)

  1. Le détecteur de l'épingle « colonne 0 » n'a pas d'autocontrôle (__tests__/ci.test.js:247-251). Les sources du COPY n'ont aucun contrevenant. L'épingle rougit donc quand un fichier source change, jamais quand le détecteur s'affaiblit. Mesuré (tableau) : avec les regex ramenées à leur forme de main, ou les clauses supprimées, les 19 cas restent verts, et un require ( injecté reste vert sous la regex ramenée. Les deux trous que cette PR ferme peuvent se rouvrir sans bruit, et la règle de base posée par #33 avec eux. L'épingle continue-on-error a reçu son autocontrôle pour cette raison précise, et la même forme s'applique ici. Sortir le test de ligne dans une fonction (loadsLate(line)), que le test.each des fichiers réutilise. Ajouter une table qui exige que require( et require ( indentés, import( et import ( soient attrapés, et qu'un require en colonne 0 et une ligne de commentaire soient ignorés. Prototype vérifié sur la copie : 25/25 vert sur la PR, 1/25 rouge quand l'une ou l'autre regex revient à sa forme de main.

  2. Verser la suggestion 2 de 17546 dans une issue (if: false, shell: true {0}, liste blanche des clés d'étape). Le commentaire 17553 la laisse « à décider », mais aucune issue ouverte ne la porte (#35, #38, #40, #41). Au merge, #35 se ferme et le constat ne vivra plus que dans une PR fermée. C'est la situation que #35 a été ouverte pour éviter.

  3. Trois imprécisions mineures.

    • __tests__/ci.test.js:101-102 : « with a colon required after the word, the anchor » ne vaut que pour la regex à classe de guillemets. /continue-on-error\s*:/, la forme que #35 proposait, perd aussi la clé entre guillemets (2/19, tableau).
    • scripts/boot-from-copy.sh:75-76 : « NO_PROXY is exported at the top » nomme la graphie que curl ne lit pas ici. C'est no_proxy qui agit, comme le dit la ligne 27.
    • Le titre de la PR promet encore « in any spelling », alors que la doc dit maintenant « toute écriture littérale ».

🤖 Generated with Claude Code

## Review — APPROVE **Résumé** : revue de `a03e4d4`. Le point bloquant de la revue 17546 est corrigé à l'octet près, la suggestion 1 est appliquée, et les mesures de la PR se reproduisent chez le relecteur. Reste un angle mort non bloquant qu'aucune revue précédente n'a mesuré : le détecteur de l'épingle « `require` en colonne 0 » n'a pas d'autocontrôle. On peut l'affaiblir, ou le supprimer, sans qu'un seul test rougisse. CI verte sur `a03e4d4` (`CI / verification (pull_request)` : success, run 13). Le log montre `HEAD is now at a03e4d4`, puis 347/347, dont `ci.test.js` à 19 cas avec le test du proxy (1008 ms), dans `node:22-alpine` sous root (Node 22.23.3, curl 8.22.0). Suivent 45/45 `test-curl.sh` et `boot-from-copy: PASS`. ### Point bloquant de 17546 : corrigé - `grep -c u002d` rend 1 pour `CLAUDE.md` et 1 pour `__tests__/ci.test.js`. `od -c` montre la barre oblique devant `u002d` aux deux endroits (`__tests__/ci.test.js:91`, `CLAUDE.md:72`). - PyYAML 6.0.1 lit cette clé comme `continue-on-error: True`, et le mot nu n'apparaît pas dans le texte. Ajoutée à `ci.yml`, elle laisse les 19 cas au vert : « passe encore » est exact. - La liste des trous commence par « among others » (`__tests__/ci.test.js:226`) et par « entre autres » (`CLAUDE.md:72`). ### Vérification par mutation (faite par le relecteur, sur une copie hors dépôt tirée par `git archive`, chaque fichier restauré puis comparé à `a03e4d4` par `cmp`) | Mutation | Résultat | |---|---| | script de `main` | 1/19 rouge : test du proxy | | `export NO_PROXY='*'` seul | 1/19 rouge : test du proxy | | `export no_proxy='*'` seul | 19/19 vert, attendu : curl lit la minuscule d'abord | | ligne `export` retirée | 1/19 rouge : test du proxy | | `unset http_proxy` à la place de l'`export` | 1/19 rouge. Avec `ALL_PROXY` retiré du test, 19/19 vert : c'est bien `ALL_PROXY` qui porte ce cas | | détecteur ré-ancré (regex de `main`) | 3/19 rouges : style flow, clé entre guillemets, ancre | | `/continue-on-error["']?\s*:/` | 1/19 rouge : ancre | | `/continue-on-error\s*:/`, la forme que #35 proposait | 2/19 rouges : clé entre guillemets, ancre | | clé échappée ajoutée à `ci.yml` | 19/19 vert : le trou assumé | | `require (` indenté, puis `import (`, dans une fonction ajoutée à `index.js` | 1/19 rouge chacun : `index.js requires only at module level` | | regex `require`, puis regex `import`, ramenées à leur forme de `main` (`/\brequire\(/`, `/\bimport\(/`) | **19/19 vert** chacune | | les mêmes, avec `require (` puis `import (` injectés dans `index.js` | **19/19 vert** chacune | | clause `require`, puis clause `import`, supprimées du détecteur | **19/19 vert** chacune | Local : 347/347 vitest, 45/45 `test-curl.sh` et `boot-from-copy: PASS`. Le script passe aussi avec `http_proxy`, `ALL_PROXY` et `all_proxy` sur `127.0.0.1:9`, et `no_proxy`/`NO_PROXY` à `example.invalid`. Avec `http_proxy=http://127.0.0.1:9` seul, le script de `main` échoue en `the server did not answer on :3197 within 10 s`, après 10,8 s. Timeline de #35 : un seul `pull_ref` en `closes`, cette PR ; #41 reste en `none`. Base à jour (`48c9a40`, tête de `main`). Commits au format conventionnel, chacun lié à #35. Aucun fichier du `COPY` touché : pas de redéploiement. ### Suggestions (non bloquantes) 1. **Le détecteur de l'épingle « colonne 0 » n'a pas d'autocontrôle** (`__tests__/ci.test.js:247-251`). Les sources du `COPY` n'ont aucun contrevenant. L'épingle rougit donc quand un fichier source change, jamais quand le détecteur s'affaiblit. Mesuré (tableau) : avec les regex ramenées à leur forme de `main`, ou les clauses supprimées, les 19 cas restent verts, et un `require (` injecté reste vert sous la regex ramenée. Les deux trous que cette PR ferme peuvent se rouvrir sans bruit, et la règle de base posée par #33 avec eux. L'épingle `continue-on-error` a reçu son autocontrôle pour cette raison précise, et la même forme s'applique ici. Sortir le test de ligne dans une fonction (`loadsLate(line)`), que le `test.each` des fichiers réutilise. Ajouter une table qui exige que `require(` et `require (` indentés, `import(` et `import (` soient attrapés, et qu'un `require` en colonne 0 et une ligne de commentaire soient ignorés. Prototype vérifié sur la copie : 25/25 vert sur la PR, 1/25 rouge quand l'une ou l'autre regex revient à sa forme de `main`. 2. **Verser la suggestion 2 de 17546 dans une issue** (`if: false`, `shell: true {0}`, liste blanche des clés d'étape). Le commentaire 17553 la laisse « à décider », mais aucune issue ouverte ne la porte (#35, #38, #40, #41). Au merge, #35 se ferme et le constat ne vivra plus que dans une PR fermée. C'est la situation que #35 a été ouverte pour éviter. 3. **Trois imprécisions mineures.** - `__tests__/ci.test.js:101-102` : « with a colon required after the word, the anchor » ne vaut que pour la regex à classe de guillemets. `/continue-on-error\s*:/`, la forme que #35 proposait, perd aussi la clé entre guillemets (2/19, tableau). - `scripts/boot-from-copy.sh:75-76` : « NO_PROXY is exported at the top » nomme la graphie que curl ne lit pas ici. C'est `no_proxy` qui agit, comme le dit la ligne 27. - Le titre de la PR promet encore « in any spelling », alors que la doc dit maintenant « toute écriture littérale ». 🤖 Generated with [Claude Code](https://claude.com/claude-code)
maximus added 2 commits 2026-10-01 21:42:04 +00:00
The self-check comment of the continue-on-error detector said that
requiring a colon after the word loses the anchor row only. That holds
when a closing quote may sit before the colon; with the colon right
after the word, the quoted-key row goes too. Measured: 2 rows red with
the colon right after the word, 1 with a closing quote allowed, 3 with
the original anchored regex.

The port check of boot-from-copy.sh named NO_PROXY as what keeps a
proxy out, while curl reads no_proxy first: it now names both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test(ci): check the load-late detector on its own (#35)
All checks were successful
CI / verification (pull_request) Successful in 21s
CI / verification (push) Successful in 21s
d7120063ad
No runtime file breaks the "require at column 0" pin, so nothing
exercised its detector: on a03e4d4, bringing either regex back to its
form on main, or deleting the require or the import clause, left the 19
cases green, and the two holes this PR closes could reopen unnoticed.

The rule moves into loadsLate(line), which the pin on index.js and
metrics.js now calls, and six rows check it directly: an indented
require and a dynamic import at any column are flagged, each with and
without a space before the parenthesis; a top-level require and a
comment line quoting require are let through.

Measured on the new tree: either regex back to its main form turns 1
row red, removing the require or the import clause turns 2 rows red,
removing the column condition turns 3 red (the two real files and the
top-level row), removing the comment skip turns 1 red.

ci.test.js 19 -> 25 cases, vitest 347 -> 353.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
maximus changed title from ci: keep the boot check off proxies, catch continue-on-error in any spelling (#35) to ci: keep the boot check off proxies, catch continue-on-error in any literal spelling (#35) 2026-10-01 21:42:31 +00:00
Author
Owner

Revue 17559 traitée — 13d46e0, d712006

Détecteur require / import vérifié pour lui-même (d712006). La règle est sortie dans loadsLate(line), que l'épingle sur index.js et metrics.js appelle, et six lignes la vérifient directement. Le trou est d'abord remesuré sur a03e4d4, puis chaque mutation est rejouée sur d712006 (fichier restauré depuis une copie, git diff --stat identique) :

Mutation a03e4d4 d712006
regex require ramenée à sa forme de main 19/19 vert 1/25 rouge : ligne require indenté avec espace
regex import ramenée à sa forme de main 19/19 vert 1/25 rouge : ligne import avec espace
clause require supprimée 19/19 vert 2/25 rouges : les deux lignes require
clause import supprimée 19/19 vert 2/25 rouges : les deux lignes import
condition de colonne supprimée (déjà rouge) 3/25 rouges : index.js, metrics.js, ligne require de premier niveau
saut des lignes de commentaire supprimé (non mesuré) 1/25 rouge : ligne commentaire

Trois imprécisions corrigées.

  • Commentaire du détecteur continue-on-error (13d46e0) : mesuré, il y a 2 lignes rouges avec les deux-points juste après le mot, 1 avec un guillemet fermant permis, et 3 avec la regex d'origine.
  • Pré-contrôle de boot-from-copy.sh (13d46e0) : il nomme maintenant no_proxy et NO_PROXY.
  • Titre de la PR : il dit maintenant « in any literal spelling ».

Liste blanche des clés d'étape (if: false, shell: true {0}) : écartée, pas d'issue ouverte.

Suite locale : 353/353 vitest, 45/45 test-curl.sh, boot-from-copy: PASS, et PASS derrière un proxy mort avec un no_proxy hostile. Exemple de clé échappée intact : grep -c u002d rend toujours 1 par fichier.

## Revue 17559 traitée — `13d46e0`, `d712006` **Détecteur `require` / `import` vérifié pour lui-même** (`d712006`). La règle est sortie dans `loadsLate(line)`, que l'épingle sur `index.js` et `metrics.js` appelle, et six lignes la vérifient directement. Le trou est d'abord remesuré sur `a03e4d4`, puis chaque mutation est rejouée sur `d712006` (fichier restauré depuis une copie, `git diff --stat` identique) : | Mutation | `a03e4d4` | `d712006` | |---|---|---| | regex `require` ramenée à sa forme de `main` | 19/19 vert | 1/25 rouge : ligne `require` indenté avec espace | | regex `import` ramenée à sa forme de `main` | 19/19 vert | 1/25 rouge : ligne `import` avec espace | | clause `require` supprimée | 19/19 vert | 2/25 rouges : les deux lignes `require` | | clause `import` supprimée | 19/19 vert | 2/25 rouges : les deux lignes `import` | | condition de colonne supprimée | (déjà rouge) | 3/25 rouges : `index.js`, `metrics.js`, ligne `require` de premier niveau | | saut des lignes de commentaire supprimé | (non mesuré) | 1/25 rouge : ligne commentaire | **Trois imprécisions corrigées.** - Commentaire du détecteur `continue-on-error` (`13d46e0`) : mesuré, il y a 2 lignes rouges avec les deux-points juste après le mot, 1 avec un guillemet fermant permis, et 3 avec la regex d'origine. - Pré-contrôle de `boot-from-copy.sh` (`13d46e0`) : il nomme maintenant `no_proxy` et `NO_PROXY`. - Titre de la PR : il dit maintenant « in any literal spelling ». **Liste blanche des clés d'étape** (`if: false`, `shell: true {0}`) : écartée, pas d'issue ouverte. Suite locale : 353/353 vitest, 45/45 `test-curl.sh`, `boot-from-copy: PASS`, et `PASS` derrière un proxy mort avec un `no_proxy` hostile. Exemple de clé échappée intact : `grep -c u002d` rend toujours 1 par fichier.
Author
Owner

Review — APPROVE

Résumé : revue de d712006, sur le diff complet contre main (48c9a40, sa tête actuelle). Les deux commits qui répondent à la revue 17559 tiennent ce qu'ils annoncent, et chaque ligne du tableau du commentaire 17571 se reproduit chez le relecteur. Reste un angle mort non bloquant, commun aux deux détecteurs : leur exemption des commentaires est ancrée en début de ligne, et aucune ligne d'autocontrôle n'épingle cette ancre.

CI verte sur d712006 (CI / verification (pull_request) : success, run 14). Le log montre HEAD is now at d712006, puis 353/353, dont ci.test.js à 25 cas avec le test du proxy (1113 ms), dans node:22-alpine (Node 22.23.3, curl 8.22.0). Suivent 45/45 test-curl.sh et boot-from-copy: PASS.

Corrections de 13d46e0 et d712006 : exactes

  • Les trois variantes que nomme le commentaire de l'autocontrôle (__tests__/ci.test.js:99-103) donnent ce qu'il annonce : regex de main 3/25 rouges (style flow, clé entre guillemets, ancre), deux-points juste après le mot 2/25 (clé entre guillemets, ancre), guillemet fermant permis 1/25 (ancre).
  • Le pré-contrôle de scripts/boot-from-copy.sh:75-76 nomme les deux graphies.
  • loadsLate() porte désormais toute la règle de l'épingle sur index.js et metrics.js. Ses six lignes tiennent les deux regex, les deux clauses, la condition de colonne et le saut des commentaires : chacune de ces mutations rougit au moins une ligne (tableau). Seule l'ancre du saut leur échappe (suggestion 1).

Vérification par mutation (faite par le relecteur, sur une copie hors dépôt tirée par git archive d712006, chaque fichier restauré puis comparé à l'original par cmp)

Mutation Résultat
script de main 1/25 rouge : test du proxy
export NO_PROXY='*' seul 1/25 rouge : test du proxy
regex require, puis regex import, ramenées à leur forme de main 1/25 rouge chacune : la ligne « avec espace » correspondante
clause require, puis clause import, supprimées 2/25 rouges chacune : les deux lignes de la clause
condition de colonne supprimée 3/25 rouges : index.js, metrics.js, ligne require de premier niveau
saut des lignes de commentaire supprimé de loadsLate() 1/25 rouge : ligne commentaire
continue-on-error: true # temporary ajouté à la dernière étape de ci.yml 1/25 rouge : never lets a failing step pass
fonction ajoutée à index.js, avec return require("node:path"); // loaded late 1/25 rouge : index.js requires only at module level
ancre ^\s* retirée de stripCommentLines() (/#/) 25/25 vert
la même, avec la clé à commentaire de fin dans ci.yml 25/25 vert (suggestion 1)
ancre ^\s* retirée du saut de commentaire de loadsLate() 25/25 vert
la même, avec le require à commentaire de fin dans index.js 25/25 vert (suggestion 1)
saut étendu à toute ligne qui contient //, avec le même require 25/25 vert (suggestion 1)
if: false sur Unit tests ; shell: true {0} sur l'étape du boot check 25/25 vert chacun (suggestion 2)

Local : 353/353 vitest, 45/45 test-curl.sh, boot-from-copy: PASS, y compris derrière un proxy mort (http_proxy, ALL_PROXY et all_proxy sur 127.0.0.1:9) avec no_proxy et NO_PROXY à example.invalid. PyYAML 6.0.1 lit les cinq écritures de l'autocontrôle, la clé échappée et la clé à commentaire de fin comme continue-on-error: True. Timeline de #35 : un seul pull_ref en closes, cette PR ; #41 reste en none. Commits au format conventionnel, chacun lié à #35. Aucun fichier du COPY touché : pas de redéploiement.

Suggestions (non bloquantes)

  1. L'exemption des commentaires n'est épinglée que dans un sens, dans les deux détecteurs (__tests__/ci.test.js:21, __tests__/ci.test.js:244). Chaque autocontrôle exige qu'une ligne de commentaire entière soit ignorée ; aucun n'exige qu'une ligne de code suivie d'un commentaire reste vue. C'est pourtant l'ancre ^\s* qui tient la promesse des lignes 89-90 (« the word in [...] a trailing comment fails the pin too, loudly. Only full comment lines are exempt ») et de CLAUDE.md:72 (« seules les lignes de commentaire entieres en sont exemptees »). Mesuré (tableau) : sans elle, les 25 cas restent verts, et une clé continue-on-error: true # temporary passe, comme un require indenté suivi d'un // .... Le commentaire de fin est une forme courante d'ajout temporaire, et étendre le saut aux lignes qui contiennent // pour faire taire un faux positif rouvre le même trou. Une ligne par table suffit ; prototype vérifié sur la copie : 27/27 vert sur la PR, 1/27 rouge sur chacune des trois mutations.

    ["a key followed by a trailing comment", "      - name: x\n        continue-on-error: true # temporary"],
    
    ["an indented require followed by a trailing comment", '  return require("./x"); // late'],
    
  2. Consigner près de l'épingle ce que la décision de 17571 laisse passer (__tests__/ci.test.js:84-97). La liste blanche des clés d'étape est écartée : c'est tranché, et ce n'est pas l'objet de ce point. Mais le test s'appelle never lets a failing step pass, alors que if: false sur Unit tests et shell: true {0} sur le boot check laissent les 25 cas au vert. La PR écrit les trous de l'épingle « colonne 0 » au-dessus d'elle et dans CLAUDE.md, pour qu'ils ne vivent pas que dans une PR fermée. Une phrase au même endroit, ou un titre de test qui ne promet que l'absence de continue-on-error, garderait cette décision visible après le merge ; aujourd'hui, elle ne vit que dans le commentaire 17571.

🤖 Generated with Claude Code

## Review — APPROVE **Résumé** : revue de `d712006`, sur le diff complet contre `main` (`48c9a40`, sa tête actuelle). Les deux commits qui répondent à la revue 17559 tiennent ce qu'ils annoncent, et chaque ligne du tableau du commentaire 17571 se reproduit chez le relecteur. Reste un angle mort non bloquant, commun aux deux détecteurs : leur exemption des commentaires est ancrée en début de ligne, et aucune ligne d'autocontrôle n'épingle cette ancre. CI verte sur `d712006` (`CI / verification (pull_request)` : success, run 14). Le log montre `HEAD is now at d712006`, puis 353/353, dont `ci.test.js` à 25 cas avec le test du proxy (1113 ms), dans `node:22-alpine` (Node 22.23.3, curl 8.22.0). Suivent 45/45 `test-curl.sh` et `boot-from-copy: PASS`. ### Corrections de `13d46e0` et `d712006` : exactes - Les trois variantes que nomme le commentaire de l'autocontrôle (`__tests__/ci.test.js:99-103`) donnent ce qu'il annonce : regex de `main` 3/25 rouges (style flow, clé entre guillemets, ancre), deux-points juste après le mot 2/25 (clé entre guillemets, ancre), guillemet fermant permis 1/25 (ancre). - Le pré-contrôle de `scripts/boot-from-copy.sh:75-76` nomme les deux graphies. - `loadsLate()` porte désormais toute la règle de l'épingle sur `index.js` et `metrics.js`. Ses six lignes tiennent les deux regex, les deux clauses, la condition de colonne et le saut des commentaires : chacune de ces mutations rougit au moins une ligne (tableau). Seule l'ancre du saut leur échappe (suggestion 1). ### Vérification par mutation (faite par le relecteur, sur une copie hors dépôt tirée par `git archive d712006`, chaque fichier restauré puis comparé à l'original par `cmp`) | Mutation | Résultat | |---|---| | script de `main` | 1/25 rouge : test du proxy | | `export NO_PROXY='*'` seul | 1/25 rouge : test du proxy | | regex `require`, puis regex `import`, ramenées à leur forme de `main` | 1/25 rouge chacune : la ligne « avec espace » correspondante | | clause `require`, puis clause `import`, supprimées | 2/25 rouges chacune : les deux lignes de la clause | | condition de colonne supprimée | 3/25 rouges : `index.js`, `metrics.js`, ligne `require` de premier niveau | | saut des lignes de commentaire supprimé de `loadsLate()` | 1/25 rouge : ligne commentaire | | `continue-on-error: true # temporary` ajouté à la dernière étape de `ci.yml` | 1/25 rouge : `never lets a failing step pass` | | fonction ajoutée à `index.js`, avec ` return require("node:path"); // loaded late` | 1/25 rouge : `index.js requires only at module level` | | ancre `^\s*` retirée de `stripCommentLines()` (`/#/`) | **25/25 vert** | | la même, avec la clé à commentaire de fin dans `ci.yml` | **25/25 vert** (suggestion 1) | | ancre `^\s*` retirée du saut de commentaire de `loadsLate()` | **25/25 vert** | | la même, avec le `require` à commentaire de fin dans `index.js` | **25/25 vert** (suggestion 1) | | saut étendu à toute ligne qui contient `//`, avec le même `require` | **25/25 vert** (suggestion 1) | | `if: false` sur `Unit tests` ; `shell: true {0}` sur l'étape du boot check | 25/25 vert chacun (suggestion 2) | Local : 353/353 vitest, 45/45 `test-curl.sh`, `boot-from-copy: PASS`, y compris derrière un proxy mort (`http_proxy`, `ALL_PROXY` et `all_proxy` sur `127.0.0.1:9`) avec `no_proxy` et `NO_PROXY` à `example.invalid`. PyYAML 6.0.1 lit les cinq écritures de l'autocontrôle, la clé échappée et la clé à commentaire de fin comme `continue-on-error: True`. Timeline de #35 : un seul `pull_ref` en `closes`, cette PR ; #41 reste en `none`. Commits au format conventionnel, chacun lié à #35. Aucun fichier du `COPY` touché : pas de redéploiement. ### Suggestions (non bloquantes) 1. **L'exemption des commentaires n'est épinglée que dans un sens, dans les deux détecteurs** (`__tests__/ci.test.js:21`, `__tests__/ci.test.js:244`). Chaque autocontrôle exige qu'une ligne de commentaire entière soit ignorée ; aucun n'exige qu'une ligne de code suivie d'un commentaire reste vue. C'est pourtant l'ancre `^\s*` qui tient la promesse des lignes 89-90 (« the word in [...] a trailing comment fails the pin too, loudly. Only full comment lines are exempt ») et de `CLAUDE.md:72` (« seules les lignes de commentaire entieres en sont exemptees »). Mesuré (tableau) : sans elle, les 25 cas restent verts, et une clé `continue-on-error: true # temporary` passe, comme un `require` indenté suivi d'un `// ...`. Le commentaire de fin est une forme courante d'ajout temporaire, et étendre le saut aux lignes qui contiennent `//` pour faire taire un faux positif rouvre le même trou. Une ligne par table suffit ; prototype vérifié sur la copie : 27/27 vert sur la PR, 1/27 rouge sur chacune des trois mutations. ```js ["a key followed by a trailing comment", " - name: x\n continue-on-error: true # temporary"], ``` ```js ["an indented require followed by a trailing comment", ' return require("./x"); // late'], ``` 2. **Consigner près de l'épingle ce que la décision de 17571 laisse passer** (`__tests__/ci.test.js:84-97`). La liste blanche des clés d'étape est écartée : c'est tranché, et ce n'est pas l'objet de ce point. Mais le test s'appelle `never lets a failing step pass`, alors que `if: false` sur `Unit tests` et `shell: true {0}` sur le boot check laissent les 25 cas au vert. La PR écrit les trous de l'épingle « colonne 0 » au-dessus d'elle et dans `CLAUDE.md`, pour qu'ils ne vivent pas que dans une PR fermée. Une phrase au même endroit, ou un titre de test qui ne promet que l'absence de `continue-on-error`, garderait cette décision visible après le merge ; aujourd'hui, elle ne vit que dans le commentaire 17571. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
maximus merged commit d7120063ad into main 2026-10-01 22:00:54 +00:00
maximus deleted branch issue-35-ci-pin-followups 2026-10-01 22:00:54 +00:00
Sign in to join this conversation.
No reviewers
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: maximus/vps-health-api#42
No description provided.