outillage : un banc pouvait rendre un rouge sur du code sain #174

Merged
gabriel merged 4 commits from lenaic/bancs-faux-negatif into develop 2026-09-08 08:48:40 +00:00
Owner

La #173 est sortie rouge sur du code sain. Ce n'est pas le fichier, c'est le banc.

Ce qui s'est passé

ÉCHEC  adresse d'écoute attendue absente : web.listen-address=127.0.0.1:9100
ok     écoute bornée : web.listen-address=127.0.0.1:9090

Les deux lignes sont dans infra/compose/supervision/docker-compose.yml, aux lignes 70 et 180. Même boucle, même fichier, même forme.

La cause

grep -q sort dès la PREMIÈRE correspondance et ferme le tuyau
le producteur en amont reçoit un SIGPIPE et meurt en 141
set -o pipefail fait valoir 141 au pipeline entier
le « if » prend la branche « absent » sur une chaîne PRÉSENTE

Reproduit en trois lignes :

set -uo pipefail
gros=$(seq 1 200000)
printf '%s\n' "$gros" | grep -qF -- '1'   # -> 141

Seules les correspondances précoces sont touchées, celles où le producteur écrit encore quand grep s'arrête. Le 9100 est ligne 70, le 9090 ligne 180. D'où un banc intermittent, qui dépend de la taille du fichier et de l'ordre des motifs.

C'est le pire défaut possible pour une chaîne : un rouge cesse d'être une information. Personne ne peut plus distinguer un vrai refus d'un faux, et la réaction naturelle est de relancer jusqu'à ce que ça passe.

Ce que ça change

Six pipelines remplacés par une chaîne ici, grep -q motif <<<"$variable", qui n'a pas de tuyau donc pas de SIGPIPE : dans test-supervision.sh, test-deploiement-continu.sh et test-chaine-tableau-de-bord.sh.

Un banc de plus, tests/ci/test-hygiene-bancs.sh, qui reproduit d'abord le défaut puis interdit la forme dans tout script sous pipefail. Il s'exclut lui-même, à dessein : il porte la démonstration, et c'est écrit dedans plutôt que caché dans un motif.

Il est branché dans ci.yml, sans quoi test-hygiene-workflows.sh le refuse, ce qu'il a d'ailleurs fait pendant que j'écrivais.

Un second piège, rencontré en réécrivant

<<<"" envoie une ligne vide, pas zéro octet. grep -v la fait correspondre, puisqu'elle ne contient pas le motif exclu. Le contrôle du jeton de forge en clair s'est mis à rougir sur zéro occurrence. La garde de vacuité est posée, avec la raison au-dessus.

Preuve

onze bancs         verts
shellcheck         propre sur les quatorze scripts du périmètre
ruff               propre sur services packages infra .forgejo bin db

Ce que ça débloque

La #173 peut repasser. Rien dans la pile de supervision n'a jamais été en défaut.

La #173 est sortie **rouge sur du code sain**. Ce n'est pas le fichier, c'est le banc. ## Ce qui s'est passé ``` ÉCHEC adresse d'écoute attendue absente : web.listen-address=127.0.0.1:9100 ok écoute bornée : web.listen-address=127.0.0.1:9090 ``` Les deux lignes sont dans `infra/compose/supervision/docker-compose.yml`, aux lignes 70 et 180. Même boucle, même fichier, même forme. ## La cause ``` grep -q sort dès la PREMIÈRE correspondance et ferme le tuyau le producteur en amont reçoit un SIGPIPE et meurt en 141 set -o pipefail fait valoir 141 au pipeline entier le « if » prend la branche « absent » sur une chaîne PRÉSENTE ``` Reproduit en trois lignes : ```sh set -uo pipefail gros=$(seq 1 200000) printf '%s\n' "$gros" | grep -qF -- '1' # -> 141 ``` **Seules les correspondances précoces sont touchées**, celles où le producteur écrit encore quand grep s'arrête. Le 9100 est ligne 70, le 9090 ligne 180. D'où un banc intermittent, qui dépend de la taille du fichier et de l'ordre des motifs. C'est le pire défaut possible pour une chaîne : **un rouge cesse d'être une information**. Personne ne peut plus distinguer un vrai refus d'un faux, et la réaction naturelle est de relancer jusqu'à ce que ça passe. ## Ce que ça change Six pipelines remplacés par une chaîne ici, `grep -q motif <<<"$variable"`, qui n'a pas de tuyau donc pas de SIGPIPE : dans `test-supervision.sh`, `test-deploiement-continu.sh` et `test-chaine-tableau-de-bord.sh`. Un banc de plus, `tests/ci/test-hygiene-bancs.sh`, qui **reproduit d'abord le défaut** puis interdit la forme dans tout script sous `pipefail`. Il s'exclut lui-même, à dessein : il porte la démonstration, et c'est écrit dedans plutôt que caché dans un motif. Il est branché dans `ci.yml`, sans quoi `test-hygiene-workflows.sh` le refuse, ce qu'il a d'ailleurs fait pendant que j'écrivais. ## Un second piège, rencontré en réécrivant `<<<""` envoie **une ligne vide**, pas zéro octet. `grep -v` la fait correspondre, puisqu'elle ne contient pas le motif exclu. Le contrôle du jeton de forge en clair s'est mis à rougir sur zéro occurrence. La garde de vacuité est posée, avec la raison au-dessus. ## Preuve ``` onze bancs verts shellcheck propre sur les quatorze scripts du périmètre ruff propre sur services packages infra .forgejo bin db ``` ## Ce que ça débloque La #173 peut repasser. Rien dans la pile de supervision n'a jamais été en défaut.
lenaic self-assigned this 2026-09-07 14:19:08 +00:00
outillage: un banc pouvait rendre un rouge sur du code sain
Some checks failed
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 8s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 22s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 38s
Intégration / Python — qualité, tests et dépendances (pull_request) Has been cancelled
f534f75d6e
La #173 est sortie ROUGE sur « web.listen-address=127.0.0.1:9100 », alors que
la ligne est bien dans infra/compose/supervision/docker-compose.yml. Dans la
MÊME boucle, le 9090 passait au vert.

La cause n'est pas le fichier :

  grep -q sort dès la PREMIÈRE correspondance et ferme le tuyau ;
  le producteur en amont reçoit un SIGPIPE et meurt en 141 ;
  set -o pipefail fait valoir 141 au pipeline entier ;
  le « if » prend la branche « absent » sur une chaîne PRÉSENTE.

Reproduit :

  set -uo pipefail
  gros=$(seq 1 200000)
  printf '%s\n' "$gros" | grep -qF -- '1'   ->  141

Seules les correspondances PRÉCOCES sont touchées, celles où le producteur
écrit encore quand grep s'arrête. Le 9100 est ligne 70, le 9090 ligne 180 :
d'où un banc intermittent, dépendant de la taille du fichier et de l'ordre des
motifs. C'est le pire des défauts pour une chaîne, parce qu'un rouge cesse
d'être une information.

Six pipelines remplacés par une chaîne ici, qui n'a pas de tuyau donc pas de
SIGPIPE, dans test-supervision.sh, test-deploiement-continu.sh et
test-chaine-tableau-de-bord.sh.

Et un banc de plus, tests/ci/test-hygiene-bancs.sh, qui reproduit d'abord le
défaut puis interdit la forme dans tout script sous pipefail. Il s'exclut
lui-même, à dessein : il porte la démonstration.

Au passage, un second piège rencontré en réécrivant : « <<<"" » envoie UNE
LIGNE VIDE, pas zéro octet, et grep -v la fait correspondre. Le contrôle du
jeton de forge rougissait sur zéro occurrence. La garde de vacuité est posée,
avec la raison écrite au-dessus.

Onze bancs verts, shellcheck propre, ruff propre.
lenaic requested review from gabriel 2026-09-07 14:19:50 +00:00
ci: relance la chaîne, la tâche Python avait été annulée
All checks were successful
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 8s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 23s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 38s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m39s
7950471d66
Aucun changement de contenu. La tâche « Python — qualité, tests et
dépendances » de cette demande porte le statut « cancelled », pas « failure » :
elle a été coupée le 07/09 à 14h19 pendant qu'une autre exécution était
interrompue à la main. Les quatre autres contrôles étaient verts.

L'API de la forge n'expose pas de relance de tâche ; un commit vide redéclenche
la chaîne, et c'est la seule façon de distinguer un vrai refus d'une annulation
sans laisser la demande rouge.
Member

Relu en appliquant la branche et en jouant le banc : il passe, et le diagnostic est juste. producteur | grep -q sous pipefail rend bien 141 quand grep sort avant la fin de l'écriture — j'ai reproduit le faux négatif. Les cinq réécritures en <<<"$variable" sont bonnes, et le correctif du test de vacuité sur FORGE_ALERTE_TOKEN (<<<"" envoie une ligne vide, pas zéro octet) est un vrai second défaut trouvé en chemin.

Trois points, dont deux qui me semblent à traiter avant fusion.

1. Le garde-fou ne couvre qu'une orthographe d'un seul consommateur

Le motif est \|[[:space:]]*grep[[:space:]]+-[a-zA-Z]*q. Passé au banc :

ATTRAPE  foo | grep -q x
ATTRAPE  foo | grep -qF x
passe    foo | grep -F -q x
passe    foo | grep --quiet x
passe    foo | head -1
passe    foo | grep -m1 x

Or la classe du défaut n'est pas « grep -q », c'est « un consommateur qui sort avant la fin du producteur » : head, grep -m1, grep -l, sed q, awk '{exit}' ont exactement le même effet, et grep -F -q est la même commande écrite autrement. Un banc qui interdit une forme et laisse passer ses synonymes donne une garantie plus large que ce qu'il tient — ce qui est le reproche que la demande fait aux bancs actuels.

2. Le balayage s'arrête aux bancs, et le premier script au-delà porte le défaut

La boucle ne lit que tests/ci/*.sh et .forgejo/scripts/*.sh. En étendant à git ls-files '*.sh' :

infra/terraform/bootstrap.sh   (set -uo pipefail)
  l.160   if printf '%s' "$erreur" | grep -q 'RequestDisallowedByPolicy'; then
  l.263   elif printf '%s' "$erreur" | grep -q 'RoleAssignmentExists'; then

Deux if dont le verdict dépend du statut du tuyau, dans un script d'exploitation Azure — pas un banc. Le message y est court, donc le faux négatif est peu probable en pratique, mais c'est très exactement la forme que la demande déclare dangereuse, et c'est un script où un « la politique n'a pas refusé » erroné envoie chercher au mauvais endroit. Une glob de plus ferme les deux.

3. Le premier cas d'essai ne peut pas échouer

if [ "$demonstration" = "rate" ]; then ok "..."; else ok "..."; fi

Les deux branches appellent ok. C'est assumé et expliqué, et je comprends l'intention de garder la preuve à côté de la règle — mais il est ensuite compté dans « tous les cas d'essai passent ». Le nommer pour ce qu'il est (une démonstration, pas un contrôle) éviterait qu'on lise deux verts là où il y en a un.

Le reste tient : l'exclusion de ce fichier du balayage est nécessaire et écrite au bon endroit, le filtre grep -q pipefail avant d'inspecter est le bon garde, et l'étape de chaîne est câblée comme ses voisines.

Relu en appliquant la branche et en jouant le banc : il passe, et le diagnostic est juste. `producteur | grep -q` sous `pipefail` rend bien 141 quand grep sort avant la fin de l'écriture — j'ai reproduit le faux négatif. Les cinq réécritures en `<<<"$variable"` sont bonnes, et le correctif du test de vacuité sur `FORGE_ALERTE_TOKEN` (`<<<""` envoie une ligne vide, pas zéro octet) est un vrai second défaut trouvé en chemin. Trois points, dont deux qui me semblent à traiter avant fusion. ## 1. Le garde-fou ne couvre qu'une orthographe d'un seul consommateur Le motif est `\|[[:space:]]*grep[[:space:]]+-[a-zA-Z]*q`. Passé au banc : ``` ATTRAPE foo | grep -q x ATTRAPE foo | grep -qF x passe foo | grep -F -q x passe foo | grep --quiet x passe foo | head -1 passe foo | grep -m1 x ``` Or la classe du défaut n'est pas « grep -q », c'est « un consommateur qui sort avant la fin du producteur » : `head`, `grep -m1`, `grep -l`, `sed q`, `awk '{exit}'` ont exactement le même effet, et `grep -F -q` est la même commande écrite autrement. Un banc qui interdit une forme et laisse passer ses synonymes donne une garantie plus large que ce qu'il tient — ce qui est le reproche que la demande fait aux bancs actuels. ## 2. Le balayage s'arrête aux bancs, et le premier script au-delà porte le défaut La boucle ne lit que `tests/ci/*.sh` et `.forgejo/scripts/*.sh`. En étendant à `git ls-files '*.sh'` : ``` infra/terraform/bootstrap.sh (set -uo pipefail) l.160 if printf '%s' "$erreur" | grep -q 'RequestDisallowedByPolicy'; then l.263 elif printf '%s' "$erreur" | grep -q 'RoleAssignmentExists'; then ``` Deux `if` dont le verdict dépend du statut du tuyau, dans un script d'exploitation Azure — pas un banc. Le message y est court, donc le faux négatif est peu probable en pratique, mais c'est très exactement la forme que la demande déclare dangereuse, et c'est un script où un « la politique n'a pas refusé » erroné envoie chercher au mauvais endroit. Une glob de plus ferme les deux. ## 3. Le premier cas d'essai ne peut pas échouer ``` if [ "$demonstration" = "rate" ]; then ok "..."; else ok "..."; fi ``` Les deux branches appellent `ok`. C'est assumé et expliqué, et je comprends l'intention de garder la preuve à côté de la règle — mais il est ensuite compté dans « tous les cas d'essai passent ». Le nommer pour ce qu'il est (une démonstration, pas un contrôle) éviterait qu'on lise deux verts là où il y en a un. Le reste tient : l'exclusion de ce fichier du balayage est nécessaire et écrite au bon endroit, le filtre `grep -q pipefail` avant d'inspecter est le bon garde, et l'étape de chaîne est câblée comme ses voisines.
outillage: le garde-fou couvre la classe du défaut, et un vrai script le portait (#174)
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 40s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 7s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 17s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m21s
283a06c789
Trois retours de Gabriel, dont un qui trouve le défaut hors des bancs.

1. infra/terraform/bootstrap.sh porte la forme, deux fois.
   Le balayage ne lisait que tests/ci et .forgejo/scripts. Ces deux-là sont
   dans un script d'exploitation Azure, où un « la politique n'a pas refusé »
   erroné envoie chercher au mauvais endroit. Corrigés, et le balayage lit
   maintenant git ls-files '*.sh'.

2. Le motif ne couvrait qu'une orthographe d'un seul consommateur.
   La classe n'est pas « grep -q » : c'est un consommateur qui sort avant la
   fin du producteur. head, grep -m1, grep -F -q ont le même effet.

   Ma première correction a élargi SANS BORNER, et le banc s'est mis à rougir
   sur du code sain : x=$(... | head -1 | cut -d= -f2), où personne ne teste
   le statut, et jusqu'à un awk -F'|' dont le tube est dans une chaîne. Un
   banc qui crie au loup se fait désarmer, ce qui est le reproche que cette
   demande fait aux autres. Le motif est donc borné à ce qui est vraiment
   dangereux : un tuyau dont le statut SERT DE CONDITION, derrière if, elif,
   while, until ou une négation. C'est la forme des deux cas de bootstrap.sh.

3. La démonstration n'est plus comptée comme un cas d'essai.
   Ses deux branches appelaient ok : on lisait deux verts là où il y en a un.
   Elle s'affiche « note » et sort du décompte.

Éprouvé en rouge sur le vrai défaut : la forme réintroduite dans bootstrap.sh
fait rougir le banc, en le nommant.

shellcheck propre, onze bancs verts.
gabriel approved these changes 2026-09-08 08:42:41 +00:00
Merge branch 'develop' into lenaic/bancs-faux-negatif
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 40s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 8s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 17s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m10s
96b0ee9545
gabriel merged commit b41918bd21 into develop 2026-09-08 08:48:40 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
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
g2/enervision!174
No description provided.