On nous a remis les écrans, puis une copie de ce qui tourne réellement sur le serveur, puis le lendemain le texte de travail du développeur — celui qu'il modifie, et dont il tire ce qu'il met en ligne. Les deux ne coïncident pas, et le prestataire l'écrit lui-même. Neuf analyses spécialisées ont travaillé sur ce code : sept sur leur domaine, une chargée d'attaquer leurs conclusions, une dernière de chercher si un correctif existait déjà. Sept affirmations n'ont pas survécu à la contradiction et sont retirées à la fin de ce document. Depuis, ce document a été confronté à autre chose que du code : deux lectures en direct de la plateforme et de l'hébergeur le 13 septembre, et la première synchronisation du dossier de production depuis la livraison, le 14. Chaque constat porte désormais son état — avéré, en partie corrigé, non confirmé — et deux affirmations supplémentaires sont retirées.
Première version, 7 septembre 2026 à 18 h 03 (heure de Barcelone) — dépôt jeremy-flexylead/flexylead-crm, branche main, référence 888cf02, signature de contrôle de l'ensemble f68816ff, récupérée 46 secondes après publication. 872 fichiers, dont 363 pour les écrans, 73 programmes de serveur, 38 scripts et 356 paquets prêts à l'emploi. Le manifeste — la liste officielle de ce qui doit être mis en ligne — en déclare soixante-huit ; le dossier de production en contient quatre-vingt-trois, parce qu'un même programme y est installé sous plusieurs noms.
Seconde version, 8 septembre 2026 à 13 h 51 — référence 730a5e7, signature 11bf212e, récupérée trente-sept minutes après publication. 200 fichiers, 101 612 lignes ajoutées : le texte de travail du serveur, les programmes qui manquaient, 86 fichiers de test — 83 en JavaScript, 3 en Go — et cinq fichiers décrivant six grilles de notation. Le numéro de version de l'application, lui, n'a pas bougé : 1.1.20 — cette livraison a ajouté du texte de travail, pas une nouvelle mise en ligne.
Le dépôt a été créé le 7 septembre : il ne garde que sept étapes de sauvegarde, toutes des 7 et 8 septembre, sur vingt et une heures. L'histoire du logiciel — qui a changé quoi, et quand — n'y est pas. C'est ce qui empêche de dater le moindre défaut, donc de savoir depuis combien de temps il coûte de l'argent.
Troisième version, 14 septembre 2026 à 08 h 46 (heure de Barcelone) — référence ccbbaa07, « Sync production Code and Functions snapshots ». 295 fichiers, 17 programmes de serveur nouveaux, un adaptateur d'autorisation posé sur 63 programmes sur 100. Mais le texte de travail du serveur n'a pas changé d'un octet — cent quarante-sept programmes, tous identiques —, la phrase qui présentait le dossier de production comme tel a disparu du mode d'emploi, et l'attestation par empreinte qui couvrait dix programmes le 8 septembre n'en couvre plus qu'un : neuf ont été modifiés sans être ré-attestés. Le numéro de version de l'application, lui, saute de 1.1.20 à 1.1.36 et l'ossature de l'écran change de génération — sans qu'aucun paquet compilé ne soit refait. Les paquets livrés ne peuvent donc pas être ceux de la source livrée. Cette version n'est donc pas certifiable, et tout ce qui est dit d'elle plus bas le dit.
Ce que « attesté » veut dire ici. Le 8 septembre, le prestataire a livré, pour dix programmes, une empreinte de contrôle exportée de la plateforme (SNAPSHOT-ADDITIONS-20260908.json). Nous l'avons recalculée : dix sur dix concordaient le 8 septembre — le dossier de production en était bien un, et cela se vérifie sans la parole de personne. Un sur dix concorde le 14. C'est la seule mesure de ce dossier qui se vérifie sans déclaration, et c'est la première demande de ce document : la refaire sur l'état d'aujourd'hui.
Preuve · backend/SNAPSHOT-ADDITIONS-20260908.json, captured_at 2026-09-08T11:50:46.663Z, 10 entrées {name, id, active_version_id, sha256} ; recalcul sha256 de backend/<name>.mjs : 10/10 conformes à 730a5e7, 1/10 à ccbbaa07 (flexylead-api-gateway-bootstrap) ; fait deux fois, par deux postes.
Cinq points. Le reste du document les démontre.
Rien n'écrit, fonction par fonction, quelle version est en service.
Le serveur du CRM n'est pas un seul programme : c'est une collection de petits programmes, un par question que les écrans posent — « donne-moi les appels de ce commercial », « calcule ce qui est dû à ce centre ». Il y en a quatre-vingt-trois en service. Le manifeste n'en déclare que soixante-huit, parce qu'un même programme est publié plusieurs fois sous des noms différents : celui de l'espace commercial est déployé vingt-trois fois à lui seul.
Nous avons comparé chacun d'eux à la source censée l'avoir produit. Vingt-huit sont identiques au caractère près, trente-huit diffèrent, deux ne sont pas comparables. Et deux copies du même programme, déployées sous deux noms, ne portent pas la même version : l'une est à jour, l'autre a cent soixante-neuf lignes de retard. Ce n'est pas une déduction : les deux fichiers ont été comparés caractère par caractère.
Au 14 septembre, c'est pire. La seule attestation existante — dix programmes, avec leur empreinte — est rompue pour neuf d'entre eux par la synchronisation du jour, sans être refaite. Et la lecture en direct de la plateforme n'ouvre pas cette porte : la clé remise est en lecture seule et ne voit ni les programmes ni leurs versions, et la seule voie d'outil qui les listerait a échoué à ses six tentatives, un matin d'août, et n'a jamais été rejouée. Avéré, et aggravé.
Vingt-trois corrections étaient écrites dans le texte de travail sans tourner. Depuis le 14 septembre, une partie est en service — sur un instantané que rien n'atteste.
Le 8 septembre, le sens était établi : le texte de travail était plus récent que ce qui tournait. Trois de ces corrections réparaient notre premier constat, celui des chiffres d'appels ; une autre était la réponse du prestataire aux neuf appels mal attribués qu'il avait lui-même trouvés le 7 septembre.
Le 14 septembre, le sens s'est inversé. La synchronisation a modifié le dossier de production et laissé le texte de travail intact : le dossier de production est désormais en avance sur la source. Des cinq constats dont le correctif était écrit, un est en service (la relance des numéros sans réponse), trois le sont en partie (les chiffres d'appels, la requête relancée, l'appel pris par le serveur vocal), un ne l'est qu'à l'écran. Le décompte de vingt-trois correctifs, lui, n'a pas été refait. Et aucun de ces « en service » n'est attesté : c'est ce que la première demande de ce document permettrait de dire.
Rien de ce qui est dû à un centre n'est écrit nulle part.
Aucun montant n'est jamais figé : tout est recalculé à chaque ouverture d'écran, à partir des barèmes du jour, et il n'existe aucune facture de centre. Pour chacun de ces montants, le système établit pourtant quelle règle s'est appliquée et pourquoi — puis jette ce raisonnement sans l'écrire ni l'afficher. Le jour où un centre conteste sa facture, ni lui ni vous n'avez le détail du calcul à mettre sur la table.
Le logiciel sait pourtant très bien figer un montant : il le fait déjà, complètement, sur l'argent qui rentre. Quand un fournisseur d'énergie ou de télécom envoie son relevé de commissions, chaque ligne est rapprochée d'une vente, une facture est émise avec son numéro, et le montant ne bouge plus. Rien de tel n'existe sur l'argent qui sort.
Le système ne sait pas dire quand il se trompe.
Aucun seuil, aucune alerte, aucun déclenchement automatique. Un contrôle de cohérence existe, il est correct, et rien ne le lance. Les 283 signalements relevés dans le fil de discussion viennent tous d'un être humain qui a vu quelque chose d'anormal à l'écran. Jamais du système.
Le barème que vous avez négocié est bien appliqué — mais vous ne pouvez ni le lire, ni le corriger.
L'écran par lequel on règle la rémunération d'un centre ne sait exprimer que la moitié de vos règles. Le barème réellement en service a donc été écrit par un autre chemin, que seul le prestataire peut emprunter. Et l'écran censé le restituer affiche zéro sur dix de ses douze lignes : le terrain l'a signalé le 3 septembre, et la réponse du prestataire était juste. Le problème n'est pas le calcul. C'est que rien ne vous permet de vérifier ce que vous payez.
Depuis, ce chemin a été lu, et il est plus fragile que nous ne l'écrivions. Le programme d'administration piloté par l'IA est arrivé dans le dossier de production le 14 septembre — sans l'adaptateur d'autorisation que 63 autres programmes ont reçu le même jour, identique octet pour octet à sa version de travail du 8, et toujours absent de la liste de mise en ligne. Son garde-fou avant écriture ne vérifie qu'une case que l'agent coche lui-même. Et il n'a jamais servi à écrire : sur tout l'historique lisible, deux lectures, aucune écriture. Ce qui vous manque n'a pas changé — mais le seul instrument qui pourrait le faire n'est pas surveillé.
Cinq étages. Ce qui entre, ce qui décide, qui travaille, ce que ça produit, ce qui repart — et en dessous, ce sur quoi tout repose. Les encadrés rouges signalent un défaut établi plus bas, les verts un point solide.
Le CRM ne va rien chercher : il reçoit. Quatre choses lui arrivent, par quatre portes différentes — et c'est là que se décide déjà une partie des erreurs.
Un vendeur de listes envoie une fiche : quelqu'un a rempli un formulaire sur internet.
DéfautLe système devine « énergie » ou « télécom » en cherchant des mots. Il teste l'énergie en premier : l'erreur va toujours dans le même sens.Le client compose le numéro. L'opérateur téléphonique demande aussitôt au CRM à qui le donner.
SolideLe CRM se donne une seconde pour répondre, deux et demie au grand maximum ; au-delà il renonce sans bloquer la ligne, et il note pourquoi il a choisi ce commercial.Un robot téléphonique a pris rendez-vous avec un client. Le CRM le range avec le reste à rappeler.
Ce qui a été dépensé en publicité pour obtenir ces contacts, récupéré automatiquement.
DéfautToutes les dix minutes, un automate réécrit les chiffres de la semaine écoulée, plus une seule autre semaine tirée au hasard dans les trois derniers mois. Les dépenses plus anciennes ne sont donc jamais toutes à jour, et le coût par contact du passé bouge sans prévenir.Deux mécanismes séparés, qui n'ont rien à voir. Le premier répond en deux secondes pendant que le téléphone sonne. Le second distribue à tête reposée tout ce qu'il faudra rappeler plus tard.
Cherche l'historique du numéro à sept endroits, vérifie les quotas et qui est disponible, puis fait sonner un ou plusieurs commerciaux.
DéfautIl ouvre une ligne par commercial chez qui ça sonne, et refuse volontairement de les regrouper : un appel qui sonne chez trois personnes en laisse trois. Tout compteur d'appels bâti là-dessus est gonflé.Leads achetés, appels manqués, rendez-vous et rappels attendent au même endroit. Chacun est confié à une personne, ou proposé à un groupe entier.
DéfautUn lead, un rappel ou un rendez-vous qui n'est pas confié à quelqu'un dans la journée ne le sera jamais : il est payé, et personne ne l'appellera.Quatre rôles, quatre écrans. Chacun ne voit que ce que son rôle autorise, et sur ce point le logiciel est bien fait. Ce qu'ils voient, en revanche, ne concorde pas toujours.
Prend un lead ou un rappel dans la file, appelle, puis qualifie : vente, rappel, ou refus. Quatre tentatives pour un numéro qui ne répond pas.
Voit son centre seulement : ses appels, ses ventes, ses notes de qualité, et ce qui lui sera versé.
Voit plusieurs centres à la fois, compare, et arbitre les litiges.
Voit tout : volumes d'appels, coût d'acquisition d'un contact, rentabilité centre par centre.
DéfautLe mot « manqué » a trois définitions différentes selon l'écran, et les compteurs ne se recoupent jamais entre eux.Une fois l'appel passé, le CRM fabrique quatre choses : le texte de l'appel, sa note de qualité, la prime du commercial, et ce qui sera versé au centre. Les deux dernières sont de l'argent.
L'enregistrement est envoyé à un prestataire extérieur, qui le transforme en texte.
DéfautLe texte ne dit pas qui parle. Le client et le commercial sont mélangés dans un même bloc.Une intelligence artificielle relit ce texte et note l'appel critère par critère, selon la grille de qualité négociée avec le centre.
DéfautOnze contrôles cherchent des phrases dans ce texte. Un client qui dit « je n'ai pas d'impayé » fait créditer le commercial à sa place.Calculée au moment de la vente, à partir de l'offre, du type de vente, du centre et de la date. Écrite sur la vente.
SolideLa note de qualité n'entre pas dans ce calcul. Une mauvaise note ne retire rien au commercial.Refait à neuf à chaque ouverture de l'écran, à partir des ventes du mois, de leurs notes de qualité et du barème négocié avec ce centre.
DéfautAucun montant n'est jamais enregistré. Il n'existe aucune facture de centre dans le système.Le CRM ne fait pas que recevoir : il répond au vendeur de leads, et il alimente les chiffres sur lesquels la direction décide où mettre son argent.
À la seconde où la fiche arrive, le CRM répond au vendeur : accepté, ou refusé.
DéfautSi le vendeur renvoie deux fois la même fiche, le CRM répond « accepté » sans rien créer. Et si le CRM est en panne, il répond « refusé » : la fiche est perdue, alors qu'elle peut rester facturable selon le contrat.Ce que la direction regarde pour décider où mettre l'argent : combien coûte un contact, ce que rapporte chaque centre.
DéfautLe coût par contact est divisé par un quatrième compteur d'appels, qui n'existe que pour lui. Il est donc impossible de le retrouver à partir des chiffres que voient les centres et les superviseurs.La plateforme d'Apteva — écrite par Marco, et utilisée aussi pour ses autres projets. C'est elle qui porte la base de données, les fichiers audio, les identités, et qui exécute les fonctions du serveur. Elle pèse plusieurs fois le volume du code écrit pour FLEXYLEAD — une proportion que nous n'avons pas recomptée dans cette version du document. Les clés de la transcription, de l'intelligence artificielle et de l'accès aux enregistrements lui appartiennent : ce ne sont pas des accès que FLEXYLEAD emporte en changeant d'hébergeur.
Un jugement d'ensemble serait commode, et faux. Plusieurs de nos alertes de départ ont été retirées après lecture du code : nous les avions écrites en ne regardant que les écrans.
Il n'est pas dans la qualité du code. Il est dans le fait que personne ne peut dire ce qui tourne.
Le 8 septembre, les sources de travail du serveur ont été livrées. Le document qui les accompagne prévient, en toutes lettres, qu'elles « contiennent des correctifs locaux non déployés », que les copies de production « n'ont été ni rafraîchies ni écrasées », et que « la publication n'est pas un certificat de mise en production ».
Vérification faite : sur les soixante-huit fonctions déclarées, vingt-huit se reconstituent à l'identique et trente-huit divergent — une n'est pas comparable parce qu'elle est empaquetée avant d'être envoyée, une dernière n'a pas de copie de production du tout. Et la même source est déployée sous deux noms, flexylead-app-quality-query et quality-partner-gateway : la seconde a 169 lignes de retard. Ailleurs, deux générations d'un même programme commercial cohabitent, soixante-trois lignes les séparent, et le code de la plus récente porte le commentaire de son auteur : « compatibilité de déploiement progressif uniquement ».
C'est l'objection qu'on nous fera, et elle est légitime : si personne ne peut dire quelle version tourne, que valent vos constats ? La réponse tient en deux registres, et le document les distingue.
Une partie des constats est établie par différence. Les deux copies ne sont pas identiques : quelle que soit celle qui tourne, l'une des deux est fausse, et personne ne peut dire laquelle. Ces constats-là ne dépendent d'aucune hypothèse sur la production.
L'autre partie décrit un comportement de production. Le 8 septembre, elle reposait sur la déclaration du prestataire — son document d'accompagnement présentait ce dossier comme l'archive de ce qui est déployé. Ce n'est plus une déclaration : c'est vérifié. Le relevé de dix programmes livré ce jour-là porte, pour chacun, une empreinte de contrôle exportée de la plateforme ; nous l'avons recalculée, dix sur dix concordent. Pour le 8 septembre, l'archive était bien la production, et cela se prouve hors ligne.
Pour le 14 septembre, c'est l'inverse. La synchronisation a modifié neuf de ces dix programmes sans refaire l'attestation, le texte de travail n'a pas bougé, et la phrase du mode d'emploi qui posait la convention a disparu. Le dossier de production d'aujourd'hui n'est certifiable pour aucun des constats de ce document. La contradiction n'est pas dans l'audit : elle est dans la livraison — et elle s'est déplacée du 8 au 14.
Et elle se referme d'une seule façon. Le numéro de version en service de chaque fonction, avec son empreinte, existe côté plateforme et coûte une commande — le relevé du 8 septembre en est la preuve : il portait les deux. Il ne couvrait que dix fonctions, choisies par le prestataire, sans l'aiguillage, sans le pilotage, sans le tableau de bord. Le refaire, sur toutes les fonctions, à la date d'aujourd'hui : c'est la première demande de ce document, et depuis le 14 septembre c'est aussi la seule façon de savoir si ce qui est annoncé « en service » l'est.
Vingt-trois correctifs, six fonctionnalités neuves, et aucune régression. C'est le classement des quarante-quatre divergences relevées, ramenées aux dix-huit programmes distincts qui les portent. Le sens est établi sur cinq indices convergents — la source est postérieure au déployé — dont un qui ne dépend d'aucune déclaration : la source a cessé d'appeler une fonction que la production appelle encore, et l'a laissée derrière elle. On ne fait pas ça en sens inverse.
Et la production ne déploie pas un programme : elle déploie certains de ses exemplaires. Un même fichier est déployé vingt-trois fois. Le script n'en connaît que vingt-deux : la vingt-troisième, flexylead-app-commercial-call-audio-query, est sortie de la liste sans être arrêtée. Elle ne sera plus jamais mise à jour. Trois chantiers y sont déployés sur deux exemplaires, et sur aucun des vingt et un autres. Deux comportements différents du même programme tournent donc en même temps.
Une conséquence qu'il faut mesurer. Tant qu'on ignore quelle version est en service, on peut écrire les correctifs, on ne peut en vérifier aucun. Le relevé du 8 septembre donne le numéro de version de dix fonctions : elles s'échelonnent de la 1742 à la 1915 — cent soixante-treize mises en ligne d'écart à l'intérieur d'un même lot de dix fonctions. Le 14 septembre, neuf de ces dix ont changé sans nouveau numéro ni nouvelle empreinte. La seule qui n'ait pas changé est aussi la seule qui n'ait pas reçu l'adaptateur d'autorisation posé ce jour-là.
Le même jour, d'après les échanges de la matinée du 8 septembre, un composant extérieur s'est mis à jour de lui-même et a cassé l'authentification : les commerciaux n'ont plus pu se connecter, donc plus recevoir d'appel. Réparé en une demi-heure, retombé vingt minutes plus tard. Cette dépendance est datée et racontée par ceux qui l'ont subie ; elle n'est pas reconstituée depuis le code.
Et le désordre se voit jusque dans les paquets livrés. Le dossier contient onze constructions différentes de l'interface, de la version 1.1.12 à la 1.1.20, jamais nettoyées. Un même numéro de version en désigne trois. Deux versions consécutives sont fichier pour fichier identiques. Le dossier de sortie habituel contient la 1.1.12, compilée contre un projet de développement — dont l'identifiant est écrit en clair dans cinq de ces paquets.
Jusqu'ici ce document ne comparait le logiciel qu'à lui-même. Le 13 septembre, une clé en lecture seule remise par le prestataire a ouvert la plateforme, et le compte de l'hébergeur a été lu avec la clé de FLEXYLEAD. Le 14, le dossier de production a été synchronisé pour la première fois depuis la livraison. Chaque fait ci-dessous a été contredit avant d'être écrit ; ceux qui ne le sont qu'à moitié le disent.
Elle tourne en version 0.51.3, reconstruite le 13 septembre à 8 h 14, temps universel — un numéro que personne ne pouvait donner le 11. Treize agents d'intelligence artificielle y vivent, dans deux projets : cinq en production, huit en recette. Ce que la clé en lecture seule voit : les agents, leurs instructions complètes, les outils qui leur sont attachés, et les mille premiers octets de chaque résultat qu'un agent obtient. Fait connexe, daté : les quatre agents de production ont démarré ce matin-là trente-neuf secondes après notre premier appel. Nos lectures ne peuvent pas être la cause — le seul point de démarrage joignable en lecture n'est pas dans la liste que la clé autorise, et n'a pas été appelé — et le même échelonnement s'observe à d'autres heures ; l'explication la plus simple est la matinée de migration en cours. Nous ne l'attribuons à personne : l'hébergeur n'a aucun journal d'actions. Ce qu'elle ne voit pas : les tables du CRM, les programmes du serveur, leurs versions, les tâches automatiques — tout ce sur quoi portent les vingt-trois constats. C'est pourquoi aucun d'eux n'a pu être confirmé ni infirmé par cette lecture, à une exception près : le constat « rien ne relie les tables », dont le socle est le code public de la plateforme, relu le jour même et confirmé.
Preuve · pièces : GET /api/version (non authentifié) 13/09 09:45 et 09:59 UTC → apteva 0.51.3, build 2026-09-13T08:14:18Z ; instances.json / instances2.json (GET /api/instances, 09:45 et 09:54) : 13 agents, 4 de production démarrés 09:47:39–09:47:40, PID 960807–960846, staging inchangé ; auth_keys.json : clé sk-63d18b48, access: read_only · plateforme apteva/server à b1d72aa, api_key_access.go:57-86 (liste blanche, sans /apps/), telemetry.go:619 (troncature à 1 000 octets) ; startManagedAgent : seul appelant joignable en GET /platform/helper, main.go:1230, hors liste blanche ; échelonnement 0,33 s contre boot_resume_delay: 5s.
tcall_5_full.json, GET /api/telemetry?agent_id=5&type=tool.call&limit=1000, 14/09 14:15 UTC : 1 000 événements rendus (borne), 43 flexylead-bookings_create_booking du 2026-09-09T07:56:22Z au 2026-09-12T13:29:36Z, 40 provider_call_id distincts ; agents 1, 3, 166, 167, 168, 169, 170 : 0 outil d'écriture métier sur leurs historiques (tcall_*.json, tc*.json).threads_1.json (GET /api/instances/1/threads, 13/09 09:50) : fil chat-quality, mcp_names: ["tables-db","storage"], directive « Utilise tables_query (SQL escape hatch) … SELECT * FROM t_<id> » ; tc1.json : 0 appel d'outil ; man1.json : 0 manifeste ; stats_1.json : 0 événement sur 7 jours · plateforme apteva/apps à 5a64116 : tables_meta et motif t_[0-9]+ sur liste noire depuis v0.1.10 (7764b465, 10/07).threads_170.json : fil chat-conv-7f1f1967…, mcp_names: ["channels","flexylead-admin"] ; tc170.json (type=tool.call&limit=200, fenêtre 2026-08-19 → 2026-09-13) : 73 appels — pace 60, channels_send 7, send 3, search_tools 1, flexylead-admin_admin_centres_list 2 ; tr170.json : ces deux appels le 19/08 11:39 et 11:47, success: true, 2 759 o tronqués à 1 003 ; man170b.json : 11 manifestes du 08/09 au 13/09, 0 outil admin_*.man170b.json : 13 outils functions_* dans chacun des 11 manifestes, dont functions_create, functions_update, functions_deploy, functions_delete, functions_rollback, functions_invoke ; aucun functions_* hors functions_list dans tc170.json, tc169.json, tcall_3_full.json, tcall_167.json, tcall_5_full.json, tcall_166.json ; tres169.json : 6 × functions_functions_list, 2026-08-19 09:00:13–09:00:24, success: false, « error: parse: unexpected end of JSON input ».tcall_3_full.json (agent 3, type=tool.call, 257 événements, 2026-08-14 → 2026-09-13) : 13 update, 2 spawn, arguments conservés ; tres3.json (type=tool.result) : 9 success: true, 4 false — 29/08 08:17:36 et 08:23:43 « update requires id », 27/08 « section_append requires section and content », 01/09 07:57:38 « full directive replacement is disabled for structured Markdown directives » ; 29/08 08:23:03, tools contenant code_issues_create, code_issues_update, code_repos_*, « thread … updated » ; threads_3.json au 13/09 : fils de conversation à 7 outils, 0 code_issues_*.tcall_167.json (agent 167, 2026-08-18 → 2026-09-13 11:03) : 27 appels, tous pace ; directive_167.txt : « Votre configuration est en cours. Aucun contrôle périodique n'est encore défini. » ; threads_167.json : mcp_names: ["agent-output","channels","tasks"] · plateforme apteva/apps, mcp/tasks/apteva.yaml : process_task marqué exposure: app_only.err168_llm.provider_error.json : 8 événements, 4 × « Anthropic API error 401 … API key is invalid » — 08/09 18:50, 10/09 11:27, 11/09 07:00, 13/09 08:20 — chacun précédé d'un « Gemini API error 400 … Unknown name "additionalProperties" » ; stats_168.json : 0 llm_calls sur 7 jours ; done_166.json, done_169.json : appels google aboutis ; done_3.json, done_167.json, done_170.json : cost_usd présent 18/18 (anthropic) ; done_4.json, done_5.json, done_166.json, done_169.json : absent 0/33 (google) · plateforme apteva/core : provider_google.go:189-235 (normalizeGeminiSchema), runtime_budgets.go:49-59 (400 et 401 classés permanents).La machine créée chez le nouvel hébergeur le 8 septembre est bien celle de FLEXYLEAD — la question restait ouverte. Elle a reçu 289 Gio le 13 septembre, en une journée, et un stockage d'objets a été créé le même matin — à Amsterdam, quand la machine est à Paris. Au matin du 14, le gros du transfert était derrière : moins d'un gigaoctet sur la journée entamée.
Sur cette machine, relevée le 13 puis le 14 septembre : aucun instantané, aucune sauvegarde, calendrier de sauvegarde désactivé, aucun pare-feu de l'hébergeur, aucune clé d'accès déclarée, accès administrateur en root, port d'administration ouvert. Les quatre premiers relevés deux jours de suite, résultat identique ; les trois autres une seule fois, le 13. Ce que cela dit, exactement : le filet le plus simple, celui que l'hébergeur propose, n'est pas posé. Ce que cela ne dit pas : qu'aucune sauvegarde de la base n'existe ailleurs — nous ne l'avons pas vérifié, et c'est la question que ce document posait déjà.
Preuve · pièces Vultr, jeton FLEXYLEAD : vinstances.json (GET /v2/instances, 13/09 10:19 UTC) : 1 instance, main_ip 95.179.222.52, cdg, créée 2026-09-08T17:11:34Z ; vinst1.json : firewall_group_id vide, user_scheme: root, vpcs: [] ; vssh.json : 0 clé ; v_snapshots/v_backups/v_backup_schedule/v_firewalls : 0 / 0 / false / 0, relevés 13/09 10:19 et 14/09 10:55 (*_0914.json) ; port 22 : sondage TCP depuis l'extérieur, 13/09 (ouvert ; 80 et 443 filtrés).
Les adresses publiques pointent toujours sur la machine du prestataire, chez l'hébergeur précédent, qui a renouvelé un certificat le 11 septembre. La nouvelle machine reçoit des données ; rien n'établit qu'elle serve. Son coût projeté est de l'ordre de cent trente dollars par mois, contre quarante-cinq euros annoncés — projection sur le tarif horaire facturé, aucune facture n'ayant encore été émise. Et l'hébergeur n'offre aucun journal d'actions : on ne saura jamais, par cette voie, qui a créé quoi.
Preuve · DNS, serveurs autoritaires Google, 13/09 : agents, dashboard, commerciaux, qualite, partenaires, commerciaux.test → 91.99.117.197 (Hetzner, TTL 14400) ; journal public de certificats (certspotter) : commerciaux.test.flexylead.com, Let's Encrypt, notBefore 2026-09-11T05:43:45Z ; vpend2.json (GET /v2/billing/pending-charges) : 114 h × 0,153 $ = 17,45 $ ; vinv.json : 0 facture ; GET /v2/account/activity → 404.
ccbbaa07 : requireTrustedPrincipal dans 63 fichiers de backend/ (99 .mjs + 1 .go), 0 dans functions/ ; backend/flexylead-admin-mcp.mjs : 0 ; backend/flexylead-route-work-items.mjs : 2 ; flexylead-reconcile-call-routing-decisions, flexylead-sync-google-ads-costs, ringover-audio-reconcile : 0.ccbbaa07 : lead_telecom_v3, 5 occurrences, toutes dans backend/process-appel-db-only.mjs (:3306, :3309 et le bloc :1221-1231), 0 dans config/, scripts/, src/, functions/ ; à 730a5e7 : 0 ; branche no_grille :3341-3369.ccbbaa07 : backend/flexylead-sync.mjs:1991 (deleteMissing = true par défaut), :2393 (appel sur ventes sans surcharge) ; prédécesseur backend/flexylead-sync-local-google.mjs : deleteMissing: false à ses 15 appels (dont :4664, :4695, :4885, :5551 sur ventes), diffFields incluant prime_montant, prime_devise, prime_rule_id ; flexylead-sync : 0 occurrence dans scripts/application-api-manifest.mjs, config/jobs/, workflows/.ccbbaa07 : backend/tmp-*.mjs, 8 fichiers (433 à 3 860 o), 0 au manifeste ; clé de service identique dans backend/flexylead-sync.mjs:31 et backend/process-appel.mjs:25 ; compte de recette dans staging-smoke.mjs.C'est la seule affirmation de ce document que la synchronisation du 14 septembre invalide. Nous écrivions, plus bas, que les fichiers décrivant les tâches automatiques « ne sont installés par rien, dans le dossier ». C'est faux : le programme d'amorçage de la passerelle — le seul dont l'empreinte tienne encore au 14 septembre — liste, retire et planifie lui-même deux tâches en production, dont celle qui lance le moteur de notation toutes les soixante secondes. Elle est ajoutée à la liste de nos retraits.
Preuve · dépôt ccbbaa07 : backend/flexylead-api-gateway-bootstrap.mjs:4 RETIRED_ROUTING_JOBS, :5-24 CANONICAL_JOBS (flexylead-process-ai-ready-appels, 60 s, function_name: 'process-appel-db-only' ; flexylead-reconcile-sale-revenue, 300 s), :71-96 reconcileJobs — jobs_list, jobs_cancel, jobs_schedule, résultat scheduled_and_started ; fichier identique à 730a5e7 et à functions/, seule empreinte encore conforme à SNAPSHOT-ADDITIONS-20260908.json.
Vingt-trois, en quatre familles. Pour chacun : ce qui se passe, sa gravité, le temps et la difficulté de la correction. Quand un correctif existe déjà dans les sources, c'est dit.
Le mot « manqué » a trois définitions selon l'écran. Un seul appel qui sonne chez trois personnes laisse trois lignes, que le code refuse volontairement de regrouper. Et la colonne « uniques » du total ne compte que les appels venus de la publicité : elle ne peut pas, structurellement, égaler la somme des lignes en dessous.
Trois correctifs existaient dans les sources. Ils réparent la définition de « manqué », la colonne du total, et le rattachement au bon commercial. Il reste à trancher, une fois pour toutes, ce que « reçu » et « manqué » veulent dire.
État au 14 septembre : en partie corrigé, sur un instantané non attesté. Deux compteurs sur quatre excluent désormais les appels pris par le serveur vocal ; les deux autres non. La divergence entre les quatre s'est donc creusée, et un mécanisme de surcomptage neuf apparaît : la garde contre le double comptage entre les deux opérateurs téléphoniques est une fenêtre de dix secondes, que le retard connu de l'un suffit à franchir. Ce dernier point est déduit du code, non mesuré.
Preuve · dépôt ccbbaa07 : exclusion de l'IVR dans backend/flexylead-app-dashboard-query.mjs:193 et backend/flexylead-app-acquisition-query.mjs:62 (0 occurrence de ivr dans ces deux fichiers à 730a5e7) ; absente de backend/flexylead-app-commercial-metrics-query.mjs:731 et de hasAnswerEvidence, backend/flexylead-app-pilotage-summary-query.mjs:2789-2793 · fenêtre de 10 s : backend/flexylead-app-dashboard-query.mjs:479-493.
Le pilotage suit la règle en vigueur à la date. La vente, elle, garde le montant figé au moment où elle a été conclue. Ce ne sont pas deux compteurs faux, ce sont deux compteurs justes — un test du prestataire exige même qu'ils diffèrent. Mais rien, à l'écran, ne dit lequel on regarde.
Renommer, et afficher la règle appliquée à côté du chiffre. C'est un problème d'intitulé, pas de calcul.
État au 14 septembre : intact — et le rayon du défaut s'est élargi. La case prime_montant existe en trois exemplaires dans la chaîne : figée sur la ligne de vente, relue dans la table des règles, et ré-agrégée en somme dans le pilotage. La même case sert en outre de repli au tarif du centre quand aucune règle ne s'applique. Une correction portée sur l'un des trois n'atteindra pas les deux autres. À l'écran, un avertissement nouveau signale les ventes qui n'ont ni règle ni instantané — ce qui ne dit toujours pas, dans les autres cas, lequel des deux on regarde.
Preuve · dépôt ccbbaa07 : prime_montant écrit sur la vente backend/flexylead-create-sales.mjs:753, relu dans la table des règles backend/flexylead-app-pilotage-remuneration-query.mjs:2209, agrégé SUM(prime_montant) :4000.
Un écran réclame toutes ses données d'un coup, et onze d'entre elles sont marquées « facultatives » dans la fonction qui les rassemble, loadFullState : si l'une ne répond pas, l'écran la remplace par une liste vide et n'affiche aucune erreur. Deux de ces onze données sont les barèmes et les périodes de paie. Le serveur fait la même chose. Le cas le plus lourd est la fonction unique dont sont tirées les vingt-trois fonctions de l'espace commercial : elle transforme seize erreurs en résultat vide. Les fonctions de pilotage, elles, refusent de le faire.
Un correctif existait dans les sources.
État au 14 septembre : corrigé dans la source de l'écran seulement. Les onze données « facultatives » ont disparu du texte de l'écran, mais aucun des paquets compilés n'a changé : le texte neuf n'est dans aucun d'eux, l'ancien y est encore douze fois. Côté serveur, un masquage sur trois a bien été réparé — la file d'attente du commercial refuse désormais de rendre une liste vide quand elle ne sait pas de quel centre il s'agit. Les deux autres subsistent, et les seize conversions d'erreur en résultat vide de la fonction unique n'ont pas bougé. Écran : dans la source seulement. Serveur : sur un instantané non attesté.
Preuve · dépôt ccbbaa07 : backend/flexylead-app-commercial-missed-calls-query.mjs:337 — throw new Error('Commercial queue scope missing'), 0 occurrence à 730a5e7 · src/ : 146 fichiers modifiés entre les deux références ; dist/ et prebuilt-dist/ : diff vide · texte « Rémunération à qualifier » présent dans src/, absent des 11 paquets ; « CA incomplet » présent dans 12.
Le pilotage découpe ses grosses requêtes en quatre morceaux et réessaie deux fois en cas d'échec. Or la production compte le dépassement de délai parmi les échecs qui méritent une reprise. Une seule demande d'écran peut donc déclencher douze exécutions de la même requête coûteuse. La source a retiré ce comportement, avec le commentaire de son auteur : « rejouer la même requête coûteuse multiplie la charge sans rien changer ».
Le dépassement de délai n'est donc pas seulement un symptôme de la surcharge : il en est aussi une cause.
Le correctif était écrit dans les sources.
État au 14 septembre : en partie corrigé, en partie aggravé. La reprise sur dépassement de délai a bien disparu du dossier de production — c'est un gain réel. Mais le découpage a doublé : la requête est désormais coupée en huit morceaux au lieu de quatre, et une seconde boucle de quatre s'y ajoute. Recompté sur les deux versions : cinq lectures par ouverture d'écran le 8 septembre, douze le 14. Le remède annoncé « une requête au lieu de quatre » est dans la source de travail, pas dans ce qui est déployé. Non attesté, et non mesuré en production.
Preuve · dépôt ccbbaa07 : backend/flexylead-app-pilotage-summary-query.mjs:3513-3531, boucle partition < 8 ; :3782, queryCampaignNewUniqueCallers, boucle partition < 4 ; reprise sur expiration retirée, TRANSIENT_READ_RETRIES :155, :4532, identique sur les six fonctions de pilotage · à 730a5e7 : une seule boucle, bucket < 4 · source de travail : functions/flexylead-app-pilotage-query.mjs:3414, requête unique.
Le logiciel contient un vérificateur qui cherche les incohérences : une opportunité attribuée deux fois, un rappel dont l'appel a disparu. Il est écrit avec soin, mais il ne compte que les cent premières anomalies de chaque type, et il rend zéro quand il échoue. Aucun écran ne le lance. Deux programmes de vérification l'appellent, à la main, dont un qui vise la base de production.
Lui donner un bouton et une exécution quotidienne. C'est l'instrument avec lequel on jugera toutes les autres corrections — il doit passer en premier.
État au 14 septembre : intact. Aucune des tâches automatiques ne le vise, ni celles décrites dans les fichiers, ni celles que le code installe lui-même. Il a reçu l'adaptateur d'autorisation, et rien d'autre : les cinq limites à cent anomalies sont toujours là.
Preuve · dépôt ccbbaa07 : config/jobs/, 6 fichiers, diff -r vide contre 730a5e7, aucun ne vise flexylead-integrity-audit ; backend/flexylead-api-gateway-bootstrap.mjs:5-24, CANONICAL_JOBS, deux tâches, aucune vers le vérificateur · backend/flexylead-integrity-audit.mjs : +55/−4 = 49 lignes d'adaptateur, 2 d'injection de contexte, 1 appel, 3 de guillemets ; LIMIT 100 × 5.
Tout est recalculé à chaque ouverture d'écran, avec les seules règles encore actives aujourd'hui, dans leur version datée du jour de la vente. Une formule désactivée disparaît donc aussi de toutes les périodes passées : c'est monthlyFormulaRuleFor qui écarte l'inactif avant de lire les dates. Il n'existe aucune facture de centre. Le logiciel sait pourtant le faire, et il le fait déjà dans l'autre sens — sur ce que les fournisseurs d'énergie et de télécom doivent à FLEXYLEAD. flexylead-manage-facturation importe leur relevé, rapproche chaque ligne d'une vente du CRM, puis émet une facture : totaux enregistrés par createInvoice, transitions d'état contrôlées par transitionInvoice — irréversibles une fois la facture approuvée — et copie de la règle appliquée gelée dans chaque ligne. Ce dispositif ne connaît que l'argent qui rentre. Aucune de ses tables ne porte de colonne « centre ».
Appliquer aux centres le modèle qui existe déjà pour l'argent qui rentre. Le code de référence est écrit et éprouvé : il n'a jamais été appliqué au sujet qui fâche.
État au 14 septembre : intact, et renforcé. Nous avons cherché le contraire : « l'historique n'est jamais recalculé » est vrai dans le code, par double contrôle de date. Mais « arrêté » n'existe pas — aucune clôture, aucun verrou, aucun montant stocké — et il n'existe toujours aucune facture de centre. Ce qui a pu changer est un montant communiqué, jamais facturé.
Preuve · dépôt ccbbaa07 : double contrôle de date backend/flexylead-manage-production-economics.mjs:535-537 et :562-570 ; versionnage par clôture à J−1 :524-541 ; aucune colonne « centre » dans les tables de flexylead-manage-facturation ; bloc de calcul evaluateMonthlyFormula → saleRemunerationForEntry de backend/flexylead-app-pilotage-remuneration-query.mjs, identique entre 730a5e7:2264-2560 et ccbbaa07:2360-2656.
Le montant déjà écrit sur la vente survit ; c'est la règle qui l'explique, et qui permettrait de le recalculer, qui disparaît. Au premier réenregistrement de la vente, le montant part avec elle.
Retirer une règle du catalogue devrait l'empêcher de s'appliquer aux ventes futures. Elle disparaît aussi de tout l'historique, parce que la recherche écarte les règles inactives avant de regarder les dates. Rouvrir une vente ancienne et y changer l'offre ou la nature de la vente efface sa prime — ou bloque la modification. existingRemunerationSnapshot protège le cas où rien de tout cela ne bouge ; remunerationFor gouverne tous les autres.
Quatre endroits à corriger — pas deux lignes, comme une première version l'écrivait —, et il faut vérifier ce qui a déjà été effacé.
État au 14 septembre : intact. La recherche écarte toujours les règles inactives avant de lire les dates ; elle se fait désormais derrière un cache, ce qui ne change rien au résultat.
Preuve · dépôt ccbbaa07 : backend/flexylead-app-pilotage-remuneration-query.mjs:2327-2328, .filter(activeRow) avant effectiveOn ; mémoïsation activeRuleCache :670-685 · quatre endroits : flexylead-manage-offer-catalog.mjs:134-143, flexylead-update-sale.mjs:246-248, flexylead-create-sales.mjs:733-762, monthlyFormulaRuleFor côté centre.
Côté centre, impossible d'entrer une date passée, et changer le barème referme l'ancien la veille au lieu de l'écraser. Côté prime du commercial : date facultative, rétroactivité libre, écriture par-dessus l'ancienne valeur. Et l'écran ne rattrape rien.
Appliquer au second barème la règle déjà écrite pour le premier. Le modèle est dans le fichier voisin, flexylead-manage-production-economics : validatedEffectiveFrom pour la date, versionRule pour la nouvelle version. Rien à inventer, tout à recopier.
État au 14 septembre : intact — et le garde-fou du côté centre a une faille de deux heures. Ses deux contrôles « au plus tôt demain » calculent la date du jour en temps universel, alors que la date d'effet se compare en heure de Paris : entre minuit et deux heures du matin en été — une heure en hiver —, une application datée du jour est acceptée et prend effet aussitôt. Improbable pour un geste humain ; moins pour un automate.
Preuve · dépôt ccbbaa07 : contrôles « au plus tôt demain » en temps universel backend/flexylead-manage-production-economics.mjs:563 et backend/flexylead-admin-mcp.mjs:1469 ; comparaison de la date d'effet en Europe/Paris flexylead-app-pilotage-remuneration-query.mjs:3, :2723, :2746-2750 ; starts_at stocké à minuit UTC :558-560.
Pour chaque contrat d'énergie, le système établit quelle règle s'est appliquée et pourquoi. Il ne l'écrit nulle part et ne l'affiche nulle part, sauf pour le montant mensuel. Un centre qui conteste un montant n'a donc rien à opposer, et FLEXYLEAD non plus.
Écrire ce que le code produit déjà. Le calcul existe ; il lui manque une destination.
État au 14 septembre : intact, et renforcé un étage plus haut. Les tables qui portent le barème d'un centre n'ont aucune colonne d'auteur — alors que les périodes statistiques, dans le même logiciel, en ont deux. Une modification faite depuis l'écran d'administration et une modification faite par un agent d'intelligence artificielle y sont indiscernables après coup.
Preuve · dépôt ccbbaa07 : aucune colonne created_by/updated_by sur offer_economic_rules, offer_remuneration_rules, production_cost_rules ; comparer backend/flexylead-manage-stats-periods.mjs:197-200, qui les renseigne.
La transcription est demandée sans séparer les interlocuteurs : client et commercial sont mélangés dans un même bloc. Onze contrôles automatiques y cherchent des tournures. Un client qui dit « je n'ai pas d'impayé » fait créditer le commercial à sa place — cette phrase est littéralement dans la règle de recherche. Sur une grille, trente-trois points sur cent se décident ainsi ; sur une autre, vingt-cinq.
Demander la séparation des interlocuteurs, et vérifier qu'elle arrive jusqu'au bout : la plateforme écarte aujourd'hui le détail mot à mot où vivent ces étiquettes. Activer l'option ne suffira pas. En attendant, ces onze contrôles devraient être coupés — mais il faut l'annoncer aux centres, car deux d'entre eux retirent des points : les couper relèvera certaines notes.
État au 14 septembre : intact — et la documentation du service de transcription dit exactement pourquoi. Le service est Deepgram, modèle nova-3. Il sait distinguer des voix quand on le lui demande (diarize) : chaque mot reçoit alors un numéro de locuteur — 0, 1, 2 — et un degré de confiance. Il ne dit jamais qui est qui : aucune notion de vendeur ni de client, seulement des numéros, et c'est au programme de deviner lequel est le commercial. Il existe une voie plus sûre, et elle n'est pas ouverte ici : quand l'enregistrement est en deux pistes, une par interlocuteur, Deepgram transcrit chaque piste séparément et la séparation devient certaine. Cette option n'existe pas dans l'intégration que la plateforme expose, et aucun programme ne demande aux opérateurs un enregistrement en deux canaux — nous avons cherché les deux, il n'y a rien.
Sur la chaîne Ringover, le moteur ne demande pas la séparation des voix — il demande des paragraphes, qui ne portent de locuteur que si la séparation est demandée. Sur la chaîne de l'agent vocal, elle est demandée — mais les étiquettes arrivent dans la liste des mots, que la plateforme retire de la réponse avant de la rendre au programme — et quand bien même elle ne les retirerait pas, le programme ne lit que le bloc de texte, jamais le détail mot à mot. Deux causes, un seul résultat. L'option qui ferait survivre les locuteurs par phrase (utterances) n'est pas demandée non plus. Résultat identique des deux côtés : un seul bloc de texte, sans locuteur. Le remède tient en une ligne côté agent vocal (demander les phrases avec leur locuteur), en deux côté Ringover — et dans les deux cas il restera à décider, dans le programme, lequel des numéros est le commercial. Les sondes de phrases, elles, sont inchangées au caractère près ; dix-sept contrôles portent sur un critère, onze d'entre eux par recherche de tournure.
Preuve · dépôt ccbbaa07 : Ringover backend/process-appel-db-only.mjs:951-957 — model: 'nova-3', smart_format, punctuate, paragraphs, pas de diarize ; agent vocal backend/flexylead-transcribe-telephony-recording.mjs:201-207 — diarize: true, pas d'utterances, lecture du seul results.channels[0].alternatives[0].transcript :213-214 ; multichannel, stereo, dual_channel : 0 occurrence dans l'arbre · plateforme, apteva/server à b1d72aa, integrations-catalog/deepgram.json, outil listen : response_omit contient results.channels[].alternatives[].words ; 19 paramètres acceptés, sans multichannel ; retrait appliqué à tout appel, connections.go:2586-2591 · doc : developers.deepgram.com/docs/diarization, …/docs/multichannel-vs-diarization, lues le 14/09.
Deux des grilles livrées fonctionnent par retrait de points sur une note de départ. Deux versions du moteur cohabitent dans la livraison : celle qui est archivée comme déployée applique bien le retrait ; une seconde copie, nommée « avant correction des instructions », l'ignore et additionne. Si c'était l'ancienne qui tournait : sur hop_selectra_universelle_v1, un appel qui devrait valoir 90 sur 100 serait rapporté 59,5 sur 69,5 ; sur selectra_compensation_carbone_v1, 96 sur 100 deviendrait 10 sur 14.
État au 14 septembre : la question s'est resserrée, et le défaut s'est déplacé. Un mot sur le décompte, pour éviter un malentendu : il y a bien deux versions du moteur, et trois fichiers. La version à jour existe en deux exemplaires — un dans le dossier de production, un dans le texte de travail ; la version « avant correction des instructions » est le troisième fichier. Les deux copies vivantes du moteur — celle du dossier de production et celle du texte de travail — étaient identiques octet pour octet le 8 septembre et appliquent toutes deux le retrait correctement. La troisième copie est toujours là, inchangée, et rien ne la date. Mais la tâche automatique que le code installe lui-même, toutes les soixante secondes, nomme la copie vivante. Ce qui reste à faire trancher tient en une question : cette troisième copie est-elle un vestige ?
Le vrai défaut est ailleurs, et il est non confirmé. Le mode « retrait » n'est appliqué que si la grille, en base, le déclare ; à défaut, le moteur additionne. Or aucun des programmes qui créent des grilles n'écrit jamais ce réglage. Si les deux grilles à retrait ont été créées par ces programmes, le retrait n'a jamais eu lieu, quel que soit le moteur. Un programme de création n'est pas la production : c'est une déduction, pas un fait. Et la synchronisation du 14 a ajouté au moteur une consigne « conserver le calcul additif » — une instruction au modèle de langage, qui ne change pas ce que le serveur calcule.
Preuve · dépôt ccbbaa07 : backend/process-appel-db-only.mjs:1243-1247 (normalizeScoringMode, repli 'additive') et :1254-1266 (calculateEvaluationScore, branche deduction) ; empreintes de blob 45449b3f (déployé, 222 534 o), 11bd2734 (travail, 221 296 o), 9a94426c (« before-prompt-fix », 123 894 o, 0 occurrence de normalizeScoringMode/scoring_mode/base_score) ; consigne « conserver le calcul additif » :1221-1231 ; tâche planifiée backend/flexylead-api-gateway-bootstrap.mjs:5-24, function_name: 'process-appel-db-only', 60 s ; semeurs sans scoring_mode : 0 occurrence dans scripts/seed-*.mjs.
Deux questions, en lecture seule : la troisième copie est-elle un vestige, et les deux grilles à retrait portent-elles, en base, le réglage qui déclenche le retrait ? Si la seconde réponse est non, recalculer les notes déjà rendues sur ces grilles. Et vérifier que la grille nouvelle introduite le 14 septembre pour huit centres existe en base : elle n'existe nulle part dans la livraison.
Le dispositif de correction existe : il garde le verdict et le score d'origine de chaque ligne — mais pas la note globale d'origine, qui est écrasée, il note qui a corrigé et quand, il affiche un bandeau. Mais il n'additionne pas la note globale comme le moteur, dès que la grille fonctionne par malus. L'écran possède pourtant la fonction qui sait le faire, grilleScoringSummary ; l'écran de correction ne l'appelle pas. Sur une grille, un appel parfait passe de 100 à 69,5 après une simple correction ; sur une autre, de 100 à 14. Sous le seuil configuré pour le centre, la vente cesse d'être rémunérée. Ce seuil n'est pas dans le code : il est enregistré dans la formule de rémunération du centre. Le geste de contrôle qualité fabrique le défaut de qualité.
Et la correction ne survit pas : si l'appel repasse dans la chaîne, le rapport est réécrit de bout en bout ; si l'IA conclut que l'appel ne correspond pas à l'offre, il est supprimé. Rien, dans le moteur, ne sait qu'un humain était passé.
Faire calculer la correction comme le moteur, et marquer les rapports corrigés pour qu'une réévaluation ne les écrase pas. Avant tout : compter combien de notes ont déjà été corrigées à la main.
État au 14 septembre : en partie corrigé à l'écran, non vérifiable côté serveur. L'écran de correction n'envoie plus de note globale du tout — la somme fautive a quitté le fil. Mais aucun programme de serveur livré ne reçoit cette correction, et la fonction de l'écran qui sait calculer le retrait n'est toujours appelée nulle part. Dans la source de l'écran seulement ; les paquets compilés n'ont pas changé. Une conséquence utile : ce constat et le précédent peuvent désormais être lus ensemble sans se contredire.
Preuve · dépôt ccbbaa07 : src/dashboard/api/client.ts:7039-7050, correctRapport sans score ; src/dashboard/components/RapportViewer.tsx, 0 appel de grilleScoringSummary (src/dashboard/lib/grilleScoring.ts présent) ; rapport_correct : 0 occurrence côté serveur ; dist/, prebuilt-dist/ inchangés.
La notation est confiée à un modèle qui ne rend pas deux fois exactement la même réponse. Rien n'enregistre la version du modèle ni les instructions qui lui ont été données. Le calcul retient d'office la plus récente. Faute d'avoir gardé le modèle et les instructions, personne ne peut dire si c'est la bonne.
Enregistrer, avec chaque note, le modèle et les instructions employés. Les cases existent déjà sur la fiche d'appel ; seuls les programmes de démonstration les remplissent. C'est le seul dont l'estimation soit ferme.
État au 14 septembre : intact, et aggravé. Rien n'a changé sur le fond — ni température, ni graine, ni version de modèle enregistrée. Mais un second programme, arrivé avec la synchronisation, écrit dans la même table de notes, en additionnant toujours et avec une autre règle pour reconnaître un appel déjà noté. Le déclencheur connu pointe sur le premier ; le second est probablement dormant, ce n'est pas établi.
Preuve · dépôt ccbbaa07 : backend/process-appel.mjs (73 186 o, absent de functions/), agrégation additive :1244-1246, dédoublonnage WHERE appel_id = ? ; déclencheur workflows/process-appel.yaml:11 → process-appel-db-only · temperature, seed, top_p, top_k : 0 occurrence ; aucune colonne de modèle dans les 17 de rapports.
Un rendez-vous pris par l'agent vocal et non distribué dans la journée n'est pas seulement perdu : le système lui écrit « absent au rendez-vous » et « injoignable ». Il sort donc des statistiques comme un échec de contact, et non comme une opportunité que le logiciel n'a pas su distribuer. Personne, en lisant les chiffres, ne peut faire la différence.
Distinguer les deux états, et compter combien de clients ont déjà été classés ainsi.
État au 14 septembre : intact. Un chiffre voisin est désormais connu — l'agent vocal a créé quarante-trois réservations entre le 9 et le 12 septembre — mais ce sont des rappels, que ce constat exclut par construction : il ne dit rien de sa population. Compter exige la base.
Preuve · dépôt ccbbaa07 : backend/flexylead-route-work-items.mjs:1040-1066, filtre reservation_type = 'ai_booking' · pièce tcall_5_full.json, GET /api/telemetry?agent_id=5&type=tool.call&limit=1000, 14/09 14:15 UTC : 43 appels create_booking, tous service: "rappel_conseiller".
Quand un rappel se termine, le système est censé libérer la fiche pour qu'un nouvel essai soit possible. La production ne la libère pas — et la fonction qui déclenche le rappel suivant refuse alors, avec le message « un rappel existe déjà pour cet appel ». Trois fonctions, un seul défaut : buildTransition ne libère pas la fiche, assertActiveMissedCallClaim refuse alors le rappel suivant, et le rattrapage recoverFailedCallbackPointer n'existait, le 8 septembre, que dans les sources. Le piège se refermait sur lui-même : le cycle des quatre tentatives ne pouvait pas se dérouler.
État au 14 septembre : corrigé — les trois pièces sont dans le dossier de production. La fiche est libérée, la dérogation est en place, et le rattrapage est défini et appelé dans deux programmes. C'est le seul des vingt-trois constats dont le remède soit entièrement en service. Sur un instantané non attesté : la première demande de ce document le confirmerait. Une réserve à garder : le dossier de production ne connaît pas de modules partagés — les correctifs y sont recopiés en plusieurs exemplaires, exactement le motif qui a coûté cher au constat suivant.
Preuve · dépôt ccbbaa07 : backend/flexylead-record-work-result.mjs:226 (followup_appel_id: null) ; backend/flexylead-start-callback.mjs:673,677,685 (nrpWaiting) ; recoverFailedCallbackPointer défini et appelé backend/flexylead-start-callback.mjs:91,1396 et backend/flexylead-claim-missed-call.mjs:77,904 · à 730a5e7 : 0 occurrence de nrpWaiting et de recoverFailedCallbackPointer dans backend/ · backend/shared/ : inexistant.
La marque existe en production — isUnattributedIVRAnswer écrit « serveur vocal » sur l'appel. Le 8 septembre, aucun compteur ne la lisait : confirmedBusinessAnswerSql, la règle qui décide de ce qu'est « un appel pris », ignorait ce champ. Le prestataire avait écrit la correction dans huit fichiers, plus un module neuf, sans la déployer. Depuis le 14 septembre, deux des quatre compteurs la lisent — le tableau de bord et l'acquisition, ceux que le commentaire du code nommait — et les deux autres continuent de compter un appel pris par le serveur vocal comme un appel pris par un commercial.
État au 14 septembre : en partie corrigé — deux compteurs sur quatre, sur un instantané non attesté. Deux effets que personne n'avait annoncés. Le premier est rétroactif : le filtre s'applique à la lecture, sur des appels marqués depuis des semaines — toute la série historique de ces deux écrans baisse d'un coup, périodes closes comprises. Le mois d'août consulté avant et après le 14 septembre peut donc ne plus donner le même taux — le mécanisme est établi dans le code, son ampleur ne l'est pas : elle dépend du nombre d'appels marqués, que seule la base dirait. Le second touche l'argent, à l'envers de ce qu'on croirait : le calcul de rémunération n'utilise pas ce filtre, aucun montant ne bouge — mais l'écran et la paie ne disent plus la même chose sous le même nom.
Preuve · dépôt ccbbaa07 : marque écrite par isUnattributedIVRAnswer, backend/ringover-call-events.go:625-637, row["handled_by"] = "ivr" :3030-3036, présente à 730a5e7:611 ; commentaire nommant ses destinataires :3032-3033 ; lecture dans backend/flexylead-app-dashboard-query.mjs:193 et backend/flexylead-app-acquisition-query.mjs:62 ; absente de hasAnswerEvidence dans backend/flexylead-app-pilotage-remuneration-query.mjs:2789-2793.
Trois fonctions reçoivent les événements des opérateurs téléphoniques. Deux sont publiquement joignables de façon établie ; pour la troisième, le dépôt ne dit pas si son adresse est ouverte. Une seule vérifie qui l'appelle — et elle le fait très bien. Les deux autres ne vérifient rien : sur 4 565 lignes, le champ prévu pour ce contrôle n'apparaît qu'une seule fois : là où on le crée, et nulle part ailleurs. Qui connaît l'adresse peut créer un appel, créer ou modifier un prospect, en supprimer un, ou écrire une décision d'aiguillage. En revanche, rien de ce qu'elles écrivent ne détourne un appel : l'aiguillage décide sur un autre champ, bien gardé.
Appliquer le contrôle déjà en place sur la première porte. Pour la troisième, écrite dans le même langage, c'est une recopie. Pour la deuxième, écrite dans un autre, il faut le réécrire et lui donner accès au secret partagé — elle ne lit aujourd'hui aucun réglage d'environnement.
État au 14 septembre : intact. La porte qui écrit les appels — donc les compteurs facturés — a reçu cent trente-deux lignes avec la synchronisation, et toujours aucune vérification de signature. La première porte, elle, vérifie bien : chiffrement d'authentification, comparaison à temps constant, refus avant tout traitement. Nous avons relu cette vérification ligne à ligne — vingt-six lignes et son point d'appel — et une première mesure qui laissait croire l'inverse a été retirée. L'adaptateur d'autorisation posé ce jour-là ne peut pas s'appliquer à une porte d'opérateur, par construction : ce n'est pas un oubli, c'est une autre réparation qu'il faut.
Preuve · dépôt ccbbaa07 : backend/ringover-call-events.go, 4 440 → 4 565 lignes (+132/−7), 0 occurrence de signature, hmac, secret, os.Getenv aux deux références ; backend/flexylead-ringover-smart-routing.mjs:1 import { createHmac, timingSafeEqual }, appel verifyRingoverSignature :194, définition :3070-3095 (HMAC-SHA256 v3, puis signature en trois parties, 401) ; adaptateur exigeant event.requestContext.authorizer.principal, inapplicable à une porte d'opérateur.
Quand le vendeur de leads n'indique pas la campagne, le système cherche des mots dans le formulaire. Il teste l'énergie en premier : l'erreur va donc toujours du télécom vers l'énergie. Un lead mal classé part dans la mauvaise file d'attribution. Et en production, une vente d'une offre télécom précise n'est classée ni en télécom ni en énergie — le correctif est dans les sources, pas en service.
Marquer « à classer » plutôt que de deviner — d'autant qu'aujourd'hui le seul reclassement possible porte sur la campagne, par l'éditeur d'administration, et jamais sur le lead lui-même. Un second classeur, mieux écrit, existe déjà dans le même fichier.
État au 14 septembre : intact. Le correctif des offres télécom précises est toujours dans les sources et absent du dossier de production. Un programme neuf de répartition des leads est arrivé avec la synchronisation ; il touche ce périmètre et n'a pas encore été lu à fond.
Preuve · dépôt ccbbaa07 : preciseTelecomOfferId, 0 occurrence dans backend/ (présent dans functions/flexylead-sync-local-google.mjs:2921-2940) ; backend/leadbyte-prospect-dispatcher.mjs, nouveau, 16 621 o, 7 écritures.
Le client donne trois accords quand il remplit le formulaire : courriel, téléphone, SMS. Les trois traversent le système sans jamais entrer dans une case qu'on puisse relire — au mieux ils finissent noyés dans un champ de notes en texte libre, tronqué. On ne peut donc pas prouver, dossier par dossier, qu'on avait le droit d'appeler.
Trois colonnes, et les remplir à l'arrivée. Il n'y a rien à compter d'abord — une première version proposait de recenser les fiches sans accord ; aucune fiche n'en a, par construction. Deux des trois accords n'atteignent même pas le champ de notes : le filtre qui écarte les données sensibles supprime tout champ dont le nom contient « email » ou « phone ». Seul l'accord SMS peut survivre.
État au 14 septembre : intact — et le problème s'est déplacé vers la notation. Les consignes ajoutées au moteur ce jour-là érigent le consentement RGPD en critère bloquant de la note de qualité, à signaler explicitement, même si la vente est signée. Le logiciel juge donc, à l'oreille, un accord qu'il ne range dans aucune case — et rien ne permet de rapprocher ce que le client a dit au téléphone de ce qu'il avait coché dans le formulaire.
Preuve · dépôt ccbbaa07 : backend/process-appel-db-only.mjs:1224 — « LT002 (consentement RGPD) et LT006 (accord de recontact) sont bloquants » ; consent/optin : aucune colonne dans le schéma ; filtre isSensitiveKey, backend/leadbyte-event-inspector.mjs:343-358.
Deux filets rattrapent les enregistrements que le chemin principal a manqués. Le second est bien réglé ; le premier n'est déclenché par rien, nulle part. Et quand un rattrapage échoue, il marque lui-même l'enregistrement « indisponible » — ce qui l'exclut définitivement des deux filets.
Déclencher le premier filet, et lui faire choisir les appels en attente du plus ancien au plus récent, comme le second le fait déjà. Et cesser de marquer « indisponible » de façon définitive.
État au 14 septembre : intact, dans un contexte qui s'est aggravé. Le premier filet n'est toujours déclenché par rien ; un troisième programme de copie d'enregistrements est apparu, lui aussi lancé par rien. Pendant ce temps, le corpus audio a changé d'hébergeur le 13 septembre — 289 Gio en une journée, vers une machine sans aucune sauvegarde et un stockage situé dans une autre région.
Preuve · dépôt ccbbaa07 : ringover-audio-mirror, 0 occurrence dans config/jobs/, scripts/application-api-manifest.mjs, backend/flexylead-api-gateway-bootstrap.mjs ; backend/flexylead-mirror-specific-audios.mjs, nouveau, 4 638 o · pièces Vultr vbw2.json (GET /v2/instances/{id}/bandwidth, 14/09 08:27 UTC) : 13/09 entrant 310 468 815 045 o = 289,1 Gio ; vobj.json : stockage objet ams créé 2026-09-13T07:16:58Z ; v_snapshots_0914.json, v_backups_0914.json, v_backup_schedule_0914.json : 0, 0, enabled: false.
Dans une base ordinaire, on déclare qu'une vente appartient à un commercial : la base refuse alors une vente orpheline. Ici aucun lien de ce genre n'est déclaré, et le format employé ne permet pas d'en déclarer un. Dans le même esprit, des fonctions modifient la structure de la base en pleine requête, faute de procédure prévue : treize le 8 septembre, seize le 14. Une première version affirmait que l'une d'elles créait la colonne dont l'absence avait provoqué un incident en août : c'est faux et retiré — cette colonne est créée par un script de maintenance qui refuse de tourner ailleurs qu'en local.
Ce n'est pas corrigible à court terme : c'est une contrainte de la plateforme. À porter au cahier des charges de toute cible future, avec l'interdiction d'écrire dans plusieurs tables d'un seul geste, qui oblige aujourd'hui le code à tenir un journal de rattrapage à la main.
État au 14 septembre : avéré, et aggravé. C'est le seul des vingt-trois constats confirmé par une lecture du jour : son socle est le code public de la plateforme, relu le 13 septembre sur la version publiée le matin même — la table créée ne porte toujours aucune contrainte de lien, et rien ne permet d'en demander une. Fait neuf, du même périmètre : l'outil qui donne aux agents d'intelligence artificielle l'accès aux tables expose neuf opérations d'écriture, dont la suppression de lignes et de tables entières, sans aucune marque qui les réserve.
Preuve · plateforme, apteva/apps à 5a64116 (13/09 11:59) : mcp/tables/tables.go, buildCreateTableSQL, aucune clause FOREIGN KEY/UNIQUE/CHECK, grep -rniE sur le paquet → 0 ; 17 outils du MCP tables dont 9 en écriture, 0 exposure: app_only · dépôt ccbbaa07 : fichiers de backend/ portant tables_alter/tables_create : 16 (13 à 730a5e7).
Consulter le portefeuille d'un commercial par la fonction prévue laisse une trace complète : un laissez-passer signé, valable quinze minutes, en lecture seule, et une ligne d'audit. Une seconde porte donne exactement le même accès sur simple présence d'un droit — sans laissez-passer, sans durée, sans écrire une ligne. Une réponse à « qui a consulté le portefeuille de X ? » sera donc incomplète, et rien ne l'indiquera.
Autre asymétrie : prendre une opportunité n'est journalisé nulle part ; la rendre, si. Et un rendez-vous pris par l'agent vocal est détenu sans limite de temps, alors qu'un appel manqué repart au bout de dix minutes.
Faire passer les deux portes par le même chemin, et poser une expiration sur les rendez-vous.
État au 14 septembre : en partie corrigé, sur un instantané non attesté. L'adaptateur d'autorisation couvre désormais les deux portes : l'identité de celui qui consulte est verrouillée. La trace, elle, ne l'est pas — la seconde porte n'écrit toujours aucune ligne. La gravité « mineure » de ce constat reste contestée par l'analyse qui l'a relevé, et n'est pas modifiée ici.
Preuve · dépôt ccbbaa07 : requireTrustedPrincipal — backend/commercial-supervision-session.mjs 2 occurrences, backend/flexylead-app-commercial-bootstrap-query.mjs 3 ; resolveCommercial :1764-1773, 0 écriture.
Une vente conclue réserve le client à ce centre pour quatre-vingt-dix jours à compter de la date de vente. Si elle est annulée, le verrou reste. Le programme qui libère les verrous ne connaît que deux des cinq origines de verrou, et pas celle de la vente. Aucune fonction du logiciel ne peut l'enlever : il faut attendre les quatre-vingt-dix jours — le verrou n'est pas éternel, il expire seul.
Libérer le verrou à l'annulation, et recenser les verrous déjà posés par des ventes annulées.
État au 14 septembre : intact. Les douze points d'écriture du verrou et l'unique remise à zéro sont inchangés — au 14 septembre, sur le dossier de production.
Preuve · dépôt ccbbaa07, centre_lock_source: à valeur littérale : sale ×7 (flexylead-create-sales.mjs:494, flexylead-reconcile-telephony-handoffs.mjs:600,625, flexylead-start-callback.mjs:1277, flexylead-sync-local-google.mjs:4463,4485,4492), answered_call ×3 (ringover-call-events.go:1411,1541,1709), commercial_comment ×1 (lead-qualification-update.mjs:857), work_assignment ×1 (flexylead-reconcile-telephony-handoffs.mjs:630) ; remise à zéro released_ko, lead-qualification-update.mjs:980 ; identique à 730a5e7.
Tout ce qui précède compare le logiciel à lui-même. Cette section le compare à autre chose : au modèle de rémunération que FLEXYLEAD a elle-même fourni. C'est la partie la plus récente du document. Elle a été attaquée une fois — et sa conclusion en est sortie inversée. Ses points ne sont pas comptés parmi les vingt-trois constats : ils n'ont pas encore reçu de chiffrage en jours.
La source ne vient pas du prestataire, elle vient de vous : deux documents envoyés par Laurent le 1er septembre 2026, intitulés « Document officiel » et marqués « Mis à jour : Août 2026 » — l'un pour l'énergie, l'autre pour le télécom. Quinze règles y sont écrites. Nous avons cherché chacune d'elles dans le code.
Le barème d'un centre se saisit sur un écran d'administration. Cet écran ne sait écrire qu'une seule phrase, toujours la même — quand tel indicateur passe au-dessus ou au-dessous de tel seuil, ajouter tel montant. Quatre cases, et rien d'autre.
Il ne sait donc dire ni « ces bonus ne se cumulent pas », ni « ce bonus dépend d'un second indicateur », ni « ce plafond s'applique », ni « cette vente n'est pas payée ». Les mots qui portent ces quatre notions n'apparaissent pas une seule fois — ni dans le texte de travail du développeur, ni dans aucune des onze versions compilées de cet écran présentes dans la livraison. Nous les avons ouvertes une par une.
L'écran intitule d'ailleurs sa section « Bonus et malus cumulatifs », quand votre document titre la sienne, en majuscules, « BONUS DE PERFORMANCE MÉTÉORE (NON CUMULABLES) ». La contradiction est réelle, et bornée : seuls les paliers Météore s'excluent entre eux — le reste du barème se cumule bien, votre document l'écrit lui-même.
Une capture d'écran jointe par Laurent le 3 septembre à 7 h 57 montre le panneau « Voir le calcul » du centre Target, marqué barème actif. On y lit la grille négociée au complet : le montant de base, les huit paliers Météore, le plafond conditionné à un second indicateur, et le malus de qualité sous le second seuil. Pour les quatre indicateurs affichés ce jour-là, votre contrat prévoit un montant par contrat précis. Le panneau affiche ce montant, exactement.
Ce barème n'a donc pas pu être saisi par l'écran : l'écran ne sait pas l'exprimer. Il a été écrit par un autre chemin. Un seul, dans toute la livraison, en est capable — une fonction d'administration pilotée par un agent d'intelligence artificielle, flexylead-admin-mcp. Le 9 septembre, elle ne figurait sur aucun relevé de ce qui est déployé ; le 14, elle est arrivée dans le dossier de production — identique octet pour octet à sa version de travail du 8, sans l'adaptateur d'autorisation que soixante-trois programmes ont reçu ce jour-là, et toujours absente de la liste de mise en ligne. Qu'elle tourne n'est donc pas établi.
Le constat n'est donc pas que le contrat n'est pas appliqué. Il est que sa configuration échappe entièrement à FLEXYLEAD. Elle n'est ni saisissable, ni modifiable, ni relisable depuis un écran ; elle dépend d'un geste que seul le prestataire peut faire ; et une correction saisie à l'écran s'enregistre sans rien changer au montant.
Ce chemin a été lu de près depuis, et trois choses s'y ajoutent — toutes avérées dans le code, aucune constatée en production. D'abord, son garde-fou avant écriture ne vérifie qu'une case que l'agent coche lui-même : ni approbateur distinct, ni trace de l'accord ; et les tables du barème n'ont aucune colonne d'auteur — un geste d'agent et un geste d'administrateur y sont indiscernables après coup. Ensuite, une formule partielle ne complète pas le barème en vigueur : elle le remplace par les valeurs par défaut, qui sont celles d'un autre barème que le vôtre. Recalculé sur les indicateurs du 3 septembre : le barème enregistré donne 68 € par contrat, une application partielle en donnerait 90 — vingt-deux euros de plus par contrat ; sur un centre au volume du seul que nous ayons observé, de l'ordre de cinquante mille euros par an — le volume réel n'est pas dans notre périmètre. Enfin, la règle « date d'effet au plus tôt le 26, début de période » que l'agent applique n'existe nulle part dans le code : les périodes sont des lignes de données posées à la main, et le seul repli du logiciel est le mois civil. Sur tout l'historique lisible, cet agent n'a jamais écrit : deux lectures, le 19 août. Et il se peut qu'il ne fonctionne pas : une ligne de ce programme diffère de ses dix voisines d'une façon qui ferait échouer l'écriture avant même de commencer. Si c'est le cas, le barème n'est pas protégé par un garde-fou — il est protégé par une panne, qu'une seule ligne corrigée ferait disparaître.
Sur ce même panneau, dix des douze lignes du barème sont affichées à zéro — dont celle du plafond, pourtant marquée « appliqué ». La cause est identifiée : le panneau n'affiche que la case « montant qui s'ajoute » de chaque ligne, et un palier écrit comme un montant de remplacement ou comme un plafond — la seule façon d'exprimer votre grille — a cette case à zéro.
Le calcul est juste, et l'écran affiche zéro presque partout. C'est exactement ce que le terrain a signalé, et le prestataire a répondu juste, dans la minute.
3 septembre, 7 h 32 — Marco
« j'ai trouvé pourquoi les rémunérations n'étaient pas passées : le système ne supportait pas toutes les règles décrites dans les documents »3 septembre, 7 h 57 — Laurent
« La grille de Target bien à jour oui mais je n'ai pas l'impression qu'elle tienne compte des bonus car tout à 0 »3 septembre, 7 h 58 — Marco
« C'est juste de l'UI — j'ai ajusté tout le backend, je m'attaque à ça ensuite »
La capture confirme sa réponse. Ce que cet épisode démontre n'est donc pas que le contrat serait mal appliqué : c'est que le seul écran qui expose le barème est incapable de le restituer. Le moteur de calcul a été étendu en quarante-huit heures pour absorber vos règles. L'écran, lui, n'a pas suivi.
La carte « Commission énergie » n'affiche pas une commission. Elle divise le chiffre d'affaires énergie par le nombre de contrats. Sur un mois où le tarif négocié est un montant et le revenu fournisseur un autre, elle affiche le second et l'appelle du nom du premier. Laurent l'a signalée deux fois, les 20 août et 4 septembre. À sa décharge, l'infobulle de la carte explique franchement le calcul — c'est le titre qui trompe, pas le chiffre.
Le modèle télécom se paie au lead, et le logiciel ne sait facturer qu'au forfait journalier. Il connaît quatre coûts par jour travaillé — un budget quotidien par agent y entre donc sans difficulté. Ce qu'il ne sait pas faire, c'est facturer un volume réel de leads. Réserve à garder : ces coûts alimentent la marge de FLEXYLEAD, pas une facture de centre — et il n'existe aucune facture de centre.
Quatre niveaux, à ne pas confondre.
1. Ce que le logiciel sait faire, et ce que l'écran sait exprimer : vérifié — dans le texte de travail et dans les onze versions compilées.
2. Ce que contient le barème livré par défaut : vérifié. Il ne correspond à aucun des deux modèles négociés.
3. Ce qui s'écrit en base à l'ouverture d'un nouveau centre : vérifié. L'écran transmet toujours le barème, même quand on n'y a pas touché : enregistrer un simple coût sur un centre neuf y dépose le barème par défaut. Il reste sans effet tant que le calcul mensuel n'est pas activé — et devient le barème du centre le jour où il l'est.
4. Ce qui est réellement configuré aujourd'hui, centre par centre : non vérifié. Nous savons qu'un centre, Target, portait la grille exacte le 3 septembre. Nous ne savons rien des autres. Et personne, chez FLEXYLEAD, ne peut le savoir depuis un écran.
Une seule lecture de la base lève ce doute : le contenu du barème enregistré pour chaque centre. C'est la sixième demande de la liste plus haut.
Enfin, une précaution sur la source : aucune mise à jour de ces règles n'a été envoyée après le 1er septembre. Les 475 messages échangés depuis l'envoi des deux documents ont été relus ; vingt portent sur la rémunération, trois sont accompagnés d'une capture, et aucun ne modifie une règle — Laurent confirme au contraire, le 4 septembre, le montant en vigueur. Le modèle auquel nous comparons est bien le dernier connu.
C'est la seule question qui compte pour décider. Voici la réponse que nos constats autorisent — ni plus, ni moins.
Oui. Et rien de ce que nous avons trouvé n'oblige à réécrire ce logiciel. Aucun des vingt-trois constats ne dit « c'est mal conçu, il faut recommencer ». Ils disent tous « ce mécanisme est écrit à moitié », « ce chiffre n'a pas la définition qu'on croit », « ce montant n'est enregistré nulle part ». Ce sont des réparations, pas une reconstruction.
Trois conditions, et elles ne coûtent presque rien comparées au reste : savoir ce qui tourne, avoir un filet — une sauvegarde relisible et le contrôle de cohérence branché — et pouvoir prouver qu'une mise en ligne a bien atteint tous les exemplaires. La section suivante les reprend dans l'ordre où il faut les traiter.
Ces trois conditions posées, les vingt-trois constats redeviennent du travail ordinaire — de l'ordre de quatre-vingts jours, étalés, avec un ordre de priorité qui est donné plus bas. Sans elles, le même travail se fait à l'aveugle et rien ne garantit qu'il tienne.
Ce que nous ne pouvons pas promettre. Nous n'avons jamais ouvert la base de données ni lu un journal d'exécution. Nous savons donc que ces mécanismes sont faux ; nous ne savons pas combien de ventes, de leads ou de notes ils ont déjà abîmés. Les trois mesures en lecture seule demandées plus bas répondent à cette question en quelques heures, et elles changeront l'ordre des priorités.
Et une réserve de méthode. Un logiciel devient fiable quand on peut le prouver, pas quand on le déclare. Tout ce document consiste à dire ce qui manque pour pouvoir le prouver — c'est pour cela que le vérificateur de cohérence passe en premier, avant toute correction.
L'ordre compte plus que le total. Trois mesures servent à vérifier les autres, et le plus grave doit passer en dernier.
Elles se font en lecture seule et donnent la taille réelle du problème : combien de ventes ont une note affichée différente de celle qui a servi à payer, combien d'appels n'ont aucun enregistrement rangé, combien de leads n'ont aucun accord du client. Ces trois chiffres changeront les arbitrages.
Compter environ quatre-vingts jours de développement — de l'ordre de quatre mois d'une personne — plus la mise en service de ce qui est déjà écrit, qui ne demande presque pas de développement mais beaucoup de vérification.
Le détail, pour qui veut refaire le calcul : additionner les vingt-deux estimations à leurs extrêmes donne soixante-sept à cent vingt-neuf jours. Ce total-là ne veut rien dire : il suppose que les vingt-deux chantiers aillent tous au mieux, ou tous au pire. En retirant les cinq dont le correctif existe déjà, il reste dix-sept chantiers, soit cinquante-cinq à cent quatre jours — quatre-vingts au milieu.
Et trois chantiers pèsent à eux seuls le tiers du reste : figer ce qui est dû aux centres, brancher le contrôle de cohérence, et séparer les interlocuteurs dans la transcription. C'est là que se joue l'arbitrage, pas sur les quatorze autres. L'estimation vaut pour ce que nous avons pu lire, et sera à revoir dès qu'on saura quelle version est en service.
Une première version de ce document a été établie le 7 septembre. La livraison du lendemain en a démenti sept affirmations ; la relecture du 14 septembre en retire deux de plus — l'une établie par la synchronisation du jour, l'autre relevée dès le 9 septembre et appliquée seulement maintenant. Nous les listons plutôt que de les effacer.
Deux corrections relevées le 9 septembre et appliquées seulement le 14. La première touche le rayon d'un remède : le défaut des règles de prime supprimées vit à quatre endroits, pas dans deux lignes — dont un côté centre, que nous n'avions pas nommé. La seconde est une consigne : il n'y a rien à compter d'abord sur le consentement, aucune fiche n'en a, par construction.
Deux chiffres ont aussi été corrigés à la baisse : la part de copie du serveur est de six lignes sur dix et non de deux sur trois, et l'écart entre deux exemplaires d'une même fonction est de soixante-trois lignes et non de soixante-sept. Deux écarts que nous jugions spectaculaires se sont révélés être des artefacts de mesure : ils ont été retirés.
Ce document ne suppose pas. Voici, explicitement, ce qui reste ouvert.
Un mot sur le contexte, et il compte pour lire ce document. L'infrastructure a changé deux fois en trois jours, et une troisième était annoncée entre les deux. Changement d'hébergeur le 6 septembre ; une nouvelle machine annoncée pour le 8 au soir ; puis, le 8 en fin de journée, changement de fournisseur : une machine a été créée chez un troisième hébergeur à 19 h 11, et la migration a été annoncée terminée le 9 au matin.
Nos mesures portent sur la machine du 8 septembre : deux cœurs, quatre gigaoctets. Le processeur y tenait un plateau de soixante pour cent du milieu de matinée à la fin de journée, avec un pic à soixante-dix-huit. La mémoire n'a jamais dépassé deux gigaoctets et demi — elle n'a donc jamais été le facteur limitant. Le seul indicateur réellement tendu était le disque : soixante-quatorze mégaoctets écrits par minute en moyenne, cent quatre-vingts en pointe. La machine qui la remplace a quatre cœurs et seize gigaoctets, à Paris.
Deux réserves sur cette migration, au 9 septembre au matin — et leur état au 14. Les adresses publiques n'ont pas été basculées : le trafic arrive toujours sur une machine du prestataire, qui le renvoie ensuite. Le point d'entrée du système reste donc en dehors des comptes de FLEXYLEAD, et ce n'est pas la première fois dans ce dossier. Toujours vrai le 13 septembre : six adresses sur l'ancien hébergeur, dont une a renouvelé son certificat le 11. Et les enregistrements audio n'avaient pas suivi. Ils ont suivi le 13 : 289 Gio en une journée, vers une machine sans sauvegarde, avec un stockage d'objets créé le même matin dans une autre région.
Enfin, un fait relevé le 9 septembre sur le dépôt public de la plateforme : la prise en charge de cette gamme de machines dans la plateforme d'Apteva a été corrigée à 18 h 11, une heure avant que la machine de FLEXYLEAD ne soit créée dessus. La production repose donc sur un chemin de déploiement qui avait quelques heures. Au 9 septembre, onze publications avaient eu lieu sur la plateforme depuis notre référence du 8 à 13 h 51, et aucune sur le logiciel de FLEXYLEAD ; la première sur le logiciel est celle du 14 septembre, et elle n'est pas attestée.