Une image Docker publique. Sur GitHub Container Registry. Un ENTRYPOINT ["cidx"] bien propre. Des releases successives, une CI verte à chaque fois.

Et pas de binaire dedans. Jamais. Depuis le début.

.dockerignore excluait bin/. Le Dockerfile faisait COPY bin/cidx /usr/local/bin/cidx. Le chemin était donc absent du contexte de build, kaniko ne matchait rien — et ne signalait pas d’erreur. Le pipeline était vert, la release publiée, l’ENTRYPOINT pointait vers un fichier qui n’existait pas.

Ni un test ni une relecture ne pouvaient l’attraper. Tout, à chaque étape, avait l’air de marcher.

C’est la meilleure illustration de ce qu’est devenue la v3.0.0 — le premier majeur de cidx depuis la v2.0.0. Cinq ruptures, 70 commits, 55 issues fermées en sept jours. Et en les relisant à froid, elles racontent toutes exactement la même histoire.

Cinq ruptures, un seul motif

Une rupture, en général, ça enlève quelque chose de moche, ou ça renomme, ou ça modernise. Ici, non. Chacune des cinq enlève une fonctionnalité qui donnait l’illusion de fonctionner.

1. local_behavior = "no-push" — un dry-run avec une promesse qu’il ne pouvait pas tenir.

La documentation disait :

local_behavior = "no-push" ✅ Recommandé pour Docker Build sans push — valide le Dockerfile et le processus de build

Le code disait autre chose : no-push positionnait IsDryRun, exactement comme dry-run. La commande n’était jamais transmise au backend. Rien n’était construit. Les deux modes étaient rigoureusement identiques, et celui des deux qui promettait le plus était le mensonge.

2. [branch] auto_cleanup — parsée, et lue par personne.

Une clé de configuration présente, acceptée, documentée. Aucun code ne la consultait. On la réglait, on relançait, il ne se passait rien — et rien ne le disait.

3. Les clés inconnues de cidx.toml — une faute de frappe indiscernable d’un réglage valide.

[branch]
stale_dayz = 15          # faute de frappe sur stale_days
typo_key = "whatever"    # même pas une clé
$ cidx validate
✓ Configuration is valid

stale_days gardait sa valeur par défaut de 30, et rien n’expliquait pourquoi. Vous aviez configuré quelque chose. L’outil vous confirmait que c’était valide. Ça ne l’était pas.

4. go-test ne couvrait pas tous les paquets. Le preset testait moins qu’il n’en avait l’air. La barre verte parlait d’un périmètre plus large que le périmètre réel.

5. L’arbre de commandes cidx action — supprimé après 3,5 mois de dépréciation. La seule des cinq qui soit une rupture ordinaire : elle enlève quelque chose d’annoncé comme partant, pas quelque chose qui trompait.

Quatre sur cinq, donc : pas des défauts de qualité, des défauts d’honnêteté. Un outil qui affiche ✓ valid sur une faute de frappe ne vous fait pas perdre du temps par lenteur — il vous en fait perdre en vous donnant confiance.

Pourquoi c’était trouvable maintenant

Ces choses-là ne se voient pas en lisant le code : tout compile, tout passe, tout est vert. Il faut un mécanisme qui cherche l’écart entre ce qui est promis et ce qui se produit.

Le mensonge du no-push a été trouvé par une passe BDD : un scénario affirmait « l’image Docker devrait être construite » en local sous no-push… et ce scénario ne pouvait pas passer. Ce n’est pas le code qui a dénoncé le code — c’est une promesse écrite confrontée au comportement réel.

Même chose pour l’image vide : elle a été découverte en remplaçant l’image kaniko, et confirmée comme préexistante — identique sur l’ancienne version, donc pas une régression. Personne ne la cherchait ; quelqu’un est allé voir.

C’est la suite directe de ce que je racontais en juillet : couper une release avec son propre outil avait produit sept accrocs, filés le jour même. On avait la thèse ; voici la démonstration à l’échelle d’un majeur. Ces sept-là sont fermés, avec quarante-huit autres.

Le côté porteur : /v3

Une rupture n’était pas une suppression. Le chemin de module est passé à github.com/cidx-org/cidx/v3 — sans quoi les tags v3 ne sont tout simplement pas installables par go install. C’est la règle des modules Go : à partir de la v2, la version majeure fait partie du chemin. Sauter cette étape, c’est publier un majeur que personne ne peut installer.

Et le garde-fou qui va avec, dans le même esprit que le reste : release create refuse désormais de couper un majeur que le chemin de module courant ne peut pas publier. Plutôt que de laisser l’outil produire un tag mort avec le sourire.

Ce que ça apprend

Le même motif est apparu ailleurs la même semaine, dans un autre projet. Sur verbose, le défaut le plus grave fermé cette semaine : le compilateur auto-hébergé abandonnait silencieusement toutes les règles situées après un élément de premier niveau qu’il ne reconnaissait pas — et sortait avec le code 0. Votre code n’était pas compilé, et on vous disait que tout allait bien.

Deux projets, deux domaines, la même semaine, le même défaut de fond : le succès annoncé alors que le travail n’a pas eu lieu.

Un outil ne devient pas fiable en accumulant des fonctionnalités. Il le devient quand on retire celles qui font semblant. Un no-push supprimé est plus honnête qu’un no-push qui prétend valider un build ; un cidx.toml qui refuse une clé inconnue est plus utile qu’un ✓ valid complaisant ; une image absente serait moins grave qu’une image publiée et vide.

Et la limite, nommée comme d’habitude : il reste cinq issues ouvertes, toutes des questions de conception, pas des bugs. La v3.1.0 est tombée le lendemain matin (détection de reconstruction de tag, outils de scan épinglés par digest, refus de merger sur un commit que le remote n’a pas). Le rythme retombera — deux semaines de sprint d’affilée, ce n’est pas une cadence, c’est une fenêtre.

Ce qu’il en reste, c’est une règle de tri simple pour la suite : quand une fonctionnalité et sa documentation ne disent pas la même chose, ce n’est pas la documentation qu’il faut corriger en premier. C’est se demander depuis combien de temps on y croit.