L'intégration continue d'Actual Budget a laissé passer un bogue qui enregistrait 10.23 comme 1,023. J'ai confié la publication à GitLab Duo.
Still Works déplace Actual Budget sur GitLab avec un pipeline qui ne publie une version que si elle fait encore tout ce que la production fait déjà. GitLab Duo écrit des vérifications de navigateur, garde celles qui passent en production, et une version retenue passe à Duo Developer et à Duo Code Review jusqu'à ce que la barrière la laisse sortir. Rejoué sur onze régressions étiquetées par les responsables : 2 à 3 arrêtées, contre 1 pour un test de fumée.

L'intégration continue teste ce que l'auteur a pensé tester
En juillet 2025, Actual Budget a publié la version 25.7.0. Tapez 10.23 dans le montant d'une nouvelle opération planifiée et elle enregistrait 1,023. Le changement avait passé tous les tests de l'intégration continue du projet, et il est resté deux jours dans les budgets des gens avant que la 25.7.1 le corrige. Ce genre de bogue n'est pas rare. Quand j'ai sorti la liste le 19 octobre, les responsables d'Actual avaient étiqueté 122 tickets comme régressions, et chacune avait aussi passé l'intégration continue, parce que celle-ci teste ce que l'auteur d'un changement a pensé tester. Rien ne vérifie qu'une version fait encore ce que la production fait déjà.

Aucun modèle ne décide de publier. Un navigateur le fait.
Une fusion vers main est testée, analysée et empaquetée, OpenTofu configure Cloud Run, et la version part en préproduction. Ensuite, le flux Release Guard de GitLab Duo lit les commits entre la production et la version, nomme ce sur quoi les utilisateurs comptent et que ce changement pourrait briser, et écrit des vérifications Playwright pour ça. Il exécute chaque vérification sur la production d'abord et ne garde que celles qui y passent : une vérification qui échoue en production se trompe sur l'application, ce n'est pas une preuve contre la version. Duo ne voit jamais la nouvelle version tourner.
Ensuite, le pipeline décide sans rien demander au modèle. Les vérifications gardées sont exécutées deux autres fois en production, puis en préproduction. Si une vérification qui tenait en production échoue en préproduction, la version reste en préproduction et le flux Regression Report ouvre un ticket : le commit qu'il blâme, la vérification qui a échoué, ce que la préproduction a montré et ce qu'un correctif doit préserver. Quand tout tient, la version passe en production avec une GitLab Release, les notes de version de Duo et une note sur chaque ticket qu'elle ferme, et les vérifications qui l'ont appuyée roulent sur la production toutes les six heures.

Le refus
J'ai remis le changement derrière la 25.7.0 sous forme de demande de fusion. Duo Code Review, avec la consigne de révision de ce fork sur les montants et l'argent, l'a signalé (« typing 12. returns 12, so the . is dropped ») et je l'ai fait passer quand même, comme on contourne une révision en pratique. Duo a lu la version et écrit une vérification qui tape 12.50 dans une nouvelle opération planifiée. La production affichait 12.50. La préproduction affichait 1,250. La version est restée en préproduction, Duo a ouvert un ticket qui nomme le commit, et la production n'a jamais été touchée.

Duo vérifie Duo
Ensuite, je voulais savoir si Duo pouvait aussi corriger ce qu'il attrapait. J'ai remis le changement une autre fois et confié chaque ticket ouvert par la barrière à Duo Developer. Il a fallu sept demandes de fusion. Duo Code Review a approuvé les sept avant leur fusion, et la barrière en a quand même retenu cinq. Neuf des échecs attrapés après cette approbation étaient de mauvais montants : 5.5+2.5 enregistré comme 30.50, 1250.50 enregistré comme 125.050,00, 1,00 tapé par-dessus un montant sélectionné appliqué comme 100.00. Aucun n'a atteint la production. La production affiche maintenant les séparateurs de milliers pendant la saisie, une fonction que les responsables d'Actual ont retirée deux fois pour ce même genre de bogue.

Du ticket à la production, sans personne entre les deux
Dans cette boucle, je fusionnais encore chaque correctif et j'assignais chaque ticket moi-même. Alors j'ai déplacé les deux dans le pipeline, et je lui ai donné un bogue signalé en amont par de vrais utilisateurs et toujours ouvert dans la 26.10.0 : on saute la prochaine date d'une facture, on renomme l'opération planifiée, et la facture redevient due (#9167). Je l'ai reproduit en production, je l'ai déclaré et je l'ai assigné à Duo Developer à 19 h 58. Personne n'y a touché ensuite. Duo Developer a ouvert un correctif neuf minutes plus tard. Duo Code Review l'a renvoyé une fois, parce que le nouveau test pouvait passer sans le correctif, et le pipeline a remis ça à Duo Developer. La deuxième version a été approuvée à 20 h 26 et le pipeline l'a fusionnée. Duo a écrit cinq vérifications pour la modification d'opérations planifiées, les cinq ont tenu en production et en préproduction, et le correctif était en production à 21 h 40.



Comment je sais que ça marche
Je n'ai pas écrit les bogues. J'ai pris onze régressions étiquetées par les responsables d'Actual, chacune ayant passé l'intégration continue, été publiée puis corrigée dans une version suivante, avec des images publiées pour chaque version. Chacune a été rejouée à partir d'une branche qui ne contient que la version d'avant et celle qui l'a brisée, alors le correctif n'existait pas pendant que Duo écrivait ses vérifications. Une régression compte comme arrêtée seulement si une vérification passait avant, échouait sur la version brisée et passait sur le correctif. Un test de fumée sans Duo en a arrêté 1 sur 11. La barrière qui écrit des vérifications sans les exécuter en a arrêté 3. La barrière qui essaie d'abord ses vérifications sur la production, celle qui tourne en direct, en a arrêté 2. Chaque prise a été rejouée cinq autres fois sur chacune de ses trois versions, et un script fait échouer la construction si le README et les données ne concordent pas.
Ce qui n'est pas revendiqué
Le rejeu est corrigé par les propres étiquettes et images publiées des responsables, pas par moi, mais ce sont onze régressions, et la barrière laisse passer la plupart. La version en direct en arrête 2 sur 11, une de moins que la version qui ne se calibre pas, parce que la calibration garde plus de vérifications et retient plus de changements voulus (14 blocages qui ne sont pas des prises contre 6). Elle fige ce que la production fait aujourd'hui, y compris ses propres bogues. Elle rate les régressions dans des endroits que le diff ne touche pas de façon évidente, comme une page vide. Elle ne retient que ce que ses vérifications couvrent : une version a laissé passer un mauvais montant (effacer deux chiffres de 1,250 appliquait 1.20) parce qu'aucune vérification ne le tapait, et un correctif de Duo plus tard a publié la correction. Elle ne peut pas montrer qu'un correctif fonctionne, seulement qu'il n'a rien brisé de ce que la production fait déjà; pour #9167, le test unitaire de Duo, Code Review et ma propre vérification en production après la publication ont couvert le correctif. Les versions qui changent un comportement exprès attendent qu'une personne appuie sur promote-held-release, et une exécution de la barrière prend environ une demi-heure de temps de Duo.
Still Works: Actual Budget moved onto GitLab, where GitLab Duo writes browser checks from what production already does and a release only ships if it still does all of it
Voir le projet