Chaque échec de migration qu’on a vu dans d’autres logiciels vient du même endroit : traiter « nettoyez d’abord vos données » comme le problème du client plutôt que celui du produit. On ne voulait pas construire ça. Alors très tôt, on est tombés sur un cas réel, et il nous a appris quelque chose qu’on n’attendait pas : parfois, la bonne réponse à « est-ce que l’IA devrait s’en occuper » est non.
La migration qui nous a appris la différence
On avait un vrai export d’un CRM historique à récupérer : 349 entreprises, 491 notes, la plupart un historique d’appels rédigé en slovène, 393 contacts. La solution évidente aurait été de le faire passer par Neo, notre import assisté par IA, celui-là même qui lit un tableur en désordre et en propose des fiches propres.
On a décidé que non. Les données source étaient déjà structurées, déjà résolues, déjà correctes. Les faire passer par une passe d’IA n’aurait rien ajouté à part du risque : un modèle qui paraphrase ou résume de vraies notes d’appel en slovène, perdant discrètement en précision sur un texte qui devait rester exactement tel qu’il était écrit. Alors on a construit un deuxième chemin, séparé, délibérément sans IA : copie exacte et déterministe, associée par ID de fiche historique, préservant les horodatages d’origine, sans aucun modèle de langage nulle part dedans.
Construire ce chemin a fait remonter de vraies lacunes qu’on n’aurait pas trouvées autrement : un champ de formulaire silencieusement abandonné par le backend depuis on ne sait combien de temps parce que la colonne de base de données derrière lui n’avait jamais existé, des contacts entassés dans des notes en texte libre faute d’avoir un vrai endroit où exister. On a réglé les deux, définitivement, pas seulement pour cette migration-là.
C’est la forme réelle du Migration Engine aujourd’hui : Neo pour le désordre honnête, jetez-nous votre pire tableur et on vous dira franchement ce qu’on a réussi à comprendre et ce qu’on n’a pas réussi, et un chemin de copie exacte et discret pour les données déjà fiables qui ont juste besoin d’arriver intactes. Deux problèmes différents. On a arrêté de faire comme s’il n’y en avait qu’un.
Arriver à quelque chose qui vaut la peine d’être testé
Rien de ce qui suit ne compte s’il n’y a rien de réel en dessous. Honnêtement, une partie de la façon dont on en est arrivés là a commencé par accident. Les enregistrements d’écran étaient sans cesse repoussés, surtout par simple réticence à s’asseoir et à enregistrer quelque chose proprement, alors la véritable solution de contournement était simplement de regarder l’IA cliquer en direct dans l’appli et de noter ce qui se passait. Ça s’est révélé être l’une des meilleures méthodes de débogage qu’on ait trouvées sur tout le projet, pas un plan malin, un sous-produit du fait de ne pas vouloir faire autre chose. Quelques vraies fonctionnalités existent aujourd’hui parce qu’un bug a été repéré exactement comme ça.
La construction plus large a suivi une forme similaire, moins par grand dessein que par nécessité : un vrai morceau à la fois, chacun testé de bout en bout avant que le suivant commence, parce qu’un projet de cette taille ne reste sain d’esprit d’aucune autre façon. Ce qui nous a vraiment surpris, c’est que la suite de tests de base a fini par prédire des choses que personne ne lui avait demandé de prédire. On ajoute une nouvelle fonctionnalité des mois plus tard, et Velvet détectait parfois qu’une pièce voisine était restée à moitié finie tout ce temps, en silence, en attendant d’être remarquée. Pas parce que quiconque impliqué est particulièrement affûté. Un test qui clique vraiment sur le bouton et vérifie le résultat réel a simplement une façon de trouver des choses qu’une personne qui survole le code ne trouverait pas.
Velvet
Quelque part en chemin, notre propre suite de tests de bout en bout a reçu un nom au lieu de rester simplement « les tests ». On l’appelle Velvet. Environ cinq mille lignes de Playwright, réparties en douze suites nommées, smoke, régression, le Vault, l’AI Village, les flux action-bus, les cas limites musclés, le stress, entre autres, exécutées contre cinq entreprises synthétiques et peuplées séparées. Elle pilote l’application réelle, en cours d’exécution, backend et frontend tous deux en direct, aucun substitut simulé pour l’un ou l’autre.
À l’intérieur des suites de régression nommées, les appels IA sont simulés vers des réponses déterministes, exprès, pas comme raccourci. À volume réel, de vrais appels IA se heurtent aux limites de connexion du navigateur et produisent des échecs qui n’ont rien à voir avec le fait que le produit fonctionne réellement ou non. Simuler à cet endroit isole une question honnête, est-ce que la plomberie fonctionne, d’une autre, est-ce que la réponse réelle de l’IA est bonne, qui est testée séparément, pour de vrai.
La discipline sous chaque couche
Cinq règles s’appliquent partout où on teste dans ce projet, pas seulement dans Velvet :
Verrouiller une prédiction par écrit avant d’exécuter le test, pour qu’une réussite ne puisse pas être redéfinie discrètement après coup comme ce qui s’est trouvé se produire. Un test qui confirme seulement qu’un bouton existe n’est pas un test ; il doit cliquer sur le bouton, attendre la vraie réponse, y compris la vraie latence de l’IA, et vérifier l’état résultant réel. Chaque fonctionnalité est essayée en faisant ce qu’elle est censée faire, et essayée de la façon dont une vraie personne, parfois négligente, parfois hostile, l’utiliserait réellement mal. Quand un « échec » se révèle être un bug dans le script de test plutôt que dans le produit, ça aussi c’est signalé, pas discrètement corrigé et oublié. Et un correctif n’est pas terminé quand le code compile ; il est terminé quand le scénario exact qui a cassé a été rejoué pour de vrai et a réussi.
Au-delà de Velvet : sept couches, pas une seule
Velvet est une couche parmi sept. Une vérification IA réelle, non simulée, tourne séparément, un script ponctuel contre l’appli en direct avec de vrais appels au modèle, nettoyé ensuite, spécifiquement pour attraper la catégorie de bug qui n’apparaît que parce que la réponse d’un vrai modèle varie d’une exécution à l’autre. La simulation à long horizon fait tourner des histoires d’entreprise simulées, un mois, un an, cinq ans, contre de vrais locataires avec de vrais appels IA en continu, en posant les mêmes questions canoniques à plusieurs points pour vérifier si les réponses sont réellement devenues plus fines à mesure que l’historique s’accumulait, pas seulement si elles revenaient tout court. Une couche adversariale délibérée, huit tests hostiles qu’on appelle le Parcours d’obstacles, essaie de casser directement les affirmations d’honnêteté et de sécurité du produit : injection de prompt, fuite de données entre locataires, instructions contradictoires, résistance à la suppression, réconciliation financière réelle, gestion des comptes en doublon. Les tests unitaires et d’intégration backend couvrent la Sacred Spine et l’ingestion de documents de bout en bout. Des parcours guidés testent l’interface réelle à des tailles d’écran réelles, bureau et un vrai viewport mobile, avec des captures d’écran comme preuve, pas une simple case cochée. Même les vidéos de présentation ont servi de couche de test : chacune est un vrai script qui frappe la vraie appli en direct, et chaque moment qui paraissait légèrement bizarre à la révision a été individuellement retracé jusqu’à un vrai bug ou un problème de timing du script, jamais supposé être l’un ou l’autre sans vérification.
Sur l’ensemble, environ vingt-cinq années simulées d’activité d’entreprise, plusieurs secteurs, plus de neuf mille vraies entrées de Vault générées en chemin, onze vrais bugs trouvés et corrigés. Dépense API réelle totale pour tout le programme : 9,57 dollars.
Ce qu’on ne va pas prétendre
On n’a pas testé l’échelle entreprise, des milliers de locataires simultanés à la fois. On n’a pas testé en charge ce qui arrive au coût de l’IA sous un usage quotidien de chat vraiment intensif et ouvert, par opposition à l’activité d’entreprise scriptée autour de laquelle la majorité de ce testing a été construit. Pendant le travail décrit ci-dessus, on a trouvé un vrai risque de scalabilité dans notre propre rattrapage des embeddings, un rattrapage ponctuel d’un grand Vault qui s’approchait dangereusement d’un délai d’expiration de requête, et on vous le dit au lieu de le corriger discrètement en ne disant rien.
Rien ici n’est parfait, parce que rien ne l’est. Ce qu’on peut vraiment revendiquer, c’est qu’on a regardé, sérieusement, nos propres affirmations avant de vous les présenter, et qu’on vous dit ce qu’on a trouvé dans un sens comme dans l’autre. C’est toute la discipline. On continuera à la pratiquer.