Quand j'ai audité la CI de mon propre SaaS, j'ai testé les gates avant le code. L'un d'eux, un seuil de couverture front-end de 95 % des lignes, 95 % des fonctions, 90 % des branches et 95 % des instructions, n'avait jamais été évalué : le job CI lançait les tests sans le flag --coverage. J'ai corrigé le job et fixé le plancher à 91/93/88/91, un seuil que le pipeline applique désormais vraiment, si bien qu'il échoue dès que la couverture passe en dessous. Le commentaire dans le pipeline en garde maintenant la raison : Previously ran without --coverage = decorative.
C'est la dette de vérification en une ligne de YAML. Écrire du code est devenu bon marché. Savoir que le code est correct, non. Je construis MailHive avec un workflow assisté par IA, donc le volume de code à vérifier est élevé, et une vérification qui ne fait que ressembler à une vérification est pire que pas de vérification du tout, parce qu'on arrête de s'inquiéter.
Ce qu'est vraiment la dette de vérification
Sonar appelle écart de vérification la distance entre se méfier du code généré par l'IA et le vérifier réellement, et affirme que cet écart crée ce que Werner Vogels, CTO d'Amazon, a appelé la dette de vérification. La dette technique, c'est un raccourci que vous savez avoir pris. La dette de vérification, c'est du code dont personne ne peut se porter garant. Il peut être bien structuré, passer la revue au premier coup d'œil et quand même être faux.
L'enquête de Sonar auprès de plus de 1 100 développeurs, publiée en janvier 2026, a chiffré cet écart : 96 % ne font pas entièrement confiance au code généré par l'IA pour être fonctionnellement correct, et seulement 48 % disent toujours le vérifier avant de committer. 38 % disent que le relire demande plus d'effort que relire le code d'un collègue. Sonar vend des outils de qualité de code, alors lisez ces chiffres comme une tendance.
Votre impression de vitesse n'est pas une preuve
L'essai randomisé de METR en 2025 est le meilleur avertissement que je connaisse. 16 développeurs open source expérimentés ont travaillé sur 246 vrais tickets dans des dépôts qu'ils connaissaient. Avec l'IA autorisée, ils ont mis 19 % plus de temps, et après coup ils croyaient encore que l'IA les avait rendus 20 % plus rapides. METR considère maintenant ce résultat comme dépassé, et son suivi de février 2026 dit que les nouvelles données sont un signal peu fiable, surtout parce que les développeurs choisissaient de plus en plus de ne pas participer si cela voulait dire travailler sans IA. Ses estimations plus récentes penchent vers une accélération, avec une large incertitude. Donc ne citez pas les 19 % comme l'état actuel des outils d'IA. Ce qui reste, c'est l'écart entre la perception et la mesure. Se sentir rapide ne prouve rien.
Deux constats tirés de l'audit de mes propres garde-fous
Rien d'exotique, et les deux avaient l'air corrects un jour normal. Je les ai trouvés en auditant les garde-fous eux-mêmes, pas le code qu'ils protègent.
- Des seuils de couverture jamais évalués. Les chiffres étaient dans vitest.config.ts et la CI ne lançait pas la couverture. Corrigé en faisant tourner le script CI avec la couverture activée.
- Un linter qui ne tournait qu'en local. ESLint s'exécutait dans un hook pre-push, que --no-verify contourne, donc aucun pipeline n'évaluait la règle de frontières de modules ni no-explicit-any. Je l'ai déplacé dans un seul job CI qui lint les deux applications.
Les deux avaient l'air couverts. Ce qui manquait, c'était la preuve qu'ils pouvaient échouer.
La couverture vous dit ce qui a tourné, pas ce qui a été vérifié
Même un seuil de couverture qui fonctionne répond à la mauvaise question. Il vous dit qu'une ligne s'est exécutée. Il ne vous dit pas qu'un test remarquerait si cette ligne était fausse. Voici un petit exemple illustratif, tiré d'aucun vrai codebase, et il atteint 100 % de couverture :
export function isOverLimit(spent: number, limit: number): boolean {
return spent > limit;
}
it('blocks over the limit', () => expect(isOverLimit(150, 100)).toBe(true));
it('allows under the limit', () => expect(isOverLimit(50, 100)).toBe(false));
// Change > to >= and both tests still pass.
// Nothing checks spent === limit, which is the case the business cares about.Le mutation testing trouve exactement ça. Un outil comme Stryker, qui est open source, modifie votre code par petites touches et relance les tests. Si les tests passent encore, ce mutant a survécu et vous avez trouvé une vérification qui n'existe pas. Sur le service qui gère l'état des courriels de MailHive, la première exécution a produit une liste précise de mutants survivants, chacun étant un changement précis que les tests ne remarqueraient pas. Un rapport de couverture, même avec un plancher élevé, ne vous donnera jamais cette liste, et chaque entrée désigne le prochain test qui vaut la peine d'être écrit.
Ne commencez pas par en faire un gate de merge. Les exécutions de mutation sont lentes et certains mutants survivants sont inoffensifs. Le mien est un job manuel, qui peut aussi tourner de façon planifiée, limité à un seul service, il ne peut pas bloquer un merge, et le score est un signal à lire. Élargissez la portée à mesure que vous renforcez chaque tranche.
Testez les garde-fous eux-mêmes
Une règle peut être présente dans la config et configurée d'une façon qui ne signale jamais de violation. Une règle qui n'échoue jamais ressemble exactement à un codebase qui ne l'enfreint jamais. Alors le job de lint de MailHive exécute un canari de garde-fous : un script qui plante une violation délibérée par règle clé et échoue si ESLint ne la signale pas. Un canari ne couvre que les règles pour lesquelles il plante une violation, donc tout garde-fou hors d'ESLint, comme un grep qui cherche les tests ciblés, a besoin de sa propre violation plantée.
Décidez de ce que chaque vérification a le droit de faire
L'erreur contre laquelle je vous mettrais le plus en garde, c'est de traiter un reviewer IA comme un gate. MailHive a un job de revue IA sur les merge requests qui publie une note avec ses constats, et il ne bloque jamais un merge. C'est voulu. Les vérifications déterministes peuvent bloquer, parce que quand elles échouent, vous voyez pourquoi et vous pouvez le reproduire. L'avis d'un modèle est un lecteur de plus, utile et parfois pointu, et il n'a ni droit de veto ni le dernier mot.
C'est la même séparation que celle dont j'ai parlé pour les environnements réglementés : les instructions sont une demande, et c'est le pipeline qui fait appliquer. Si vous utilisez aussi Copilot ou Claude Code avec des instructions de projet, gardez la même ligne. Les instructions réduisent le mauvais code qui arrive. Elles ne vérifient rien.
Ce qu'il faut faire cette semaine
Tout ce qui suit est gratuit et fonctionne sur un runner CI normal. Faites-le dans cet ordre, parce que la première étape est celle qui expose les autres.
- Prouvez que chaque gate peut échouer. Ouvrez une branche jetable, cassez une chose par gate (un test qui échoue, une baisse de couverture, une erreur de lint, un secret), et regardez le pipeline passer au rouge. Tout gate qui reste vert est de la décoration. Ensuite, automatisez ceux que vous pouvez avec un canari.
- Assurez-vous que chaque vérification qui compte tourne aussi en CI. Les hooks locaux sont une politesse envers vous-même. Ils sont contournables par conception.
- Faites échouer les tests ciblés. Sur Vitest, vérifiez que allowOnly n'est pas activé en CI. Sur Jest, ajoutez un grep, et plantez une violation pour prouver que le grep tourne dans le bon répertoire.
- Activez la règle que vous évitiez, avec une baseline. Les bulk suppressions d'ESLint laissent en place les violations existantes de niveau erreur, comptées par fichier et par règle, pour que les nouvelles échouent. Vous empêchez la dette de grossir dès aujourd'hui et vous la remboursez plus tard.
- Lancez le mutation testing une fois sur votre module le plus risqué. Choisissez le code qui touche à l'argent, aux permissions ou à l'état, lisez les survivants, et écrivez les tests qu'ils pointent du doigt.
- Gardez une petite suite de smoke tests end-to-end avec Playwright pour les parcours qui ne doivent jamais casser. Gardez-la assez petite pour que les gens ne commencent pas à l'ignorer.
- Dimensionnez la revue humaine selon le risque, pas selon le diff. Un fichier de test généré et un changement à une vérification d'autorisation ne méritent pas la même attention, même à nombre de lignes égal.
Où les gates s'arrêtent
Même quand chaque gate fonctionne, chacun a une limite. Le mutation testing est lent, alors je le restreins d'abord aux tranches les plus risquées et je l'élargis à mesure qu'elles se solidifient. Une suite end-to-end qui a besoin de secrets protégés ne peut souvent pas tourner sur chaque merge request, alors décidez explicitement quels parcours doivent passer avant une mise en production. Et aucun gate ne peut vous dire qu'une règle métier est la mauvaise règle métier. Ça prend encore une personne qui connaît le produit.
Un gate que vous n'avez jamais vu échouer est une hypothèse.
Références
- Sonar. Verification gap press release, January 2026. https://www.sonarsource.com/company/press-releases/sonar-data-reveals-critical-verification-gap-in-ai-coding/
- METR. Early-2025 AI and experienced open source developers study. https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/
- METR. February 2026 update on the experiment design. https://metr.org/blog/2026-02-24-uplift-update/
- StrykerJS mutation testing. https://stryker-mutator.io/docs/stryker-js/introduction/
- ESLint bulk suppressions. https://eslint.org/docs/latest/use/suppressions
- Vitest allowOnly. https://vitest.dev/config/allowonly
- Vitest coverage configuration. https://vitest.dev/config/coverage