1# Preuves du hors-constats — vérification phrase par phrase du document publié 2 3**Agent :** `audit-infra` · **Date :** 09/09/2026 4**Régime :** lecture seule stricte. Aucun accès serveur, aucune sonde vers `62.210.87.147`, `91.99.117.197` ni `95.179.222.52`, aucun appel réseau, aucune exécution de code applicatif. Écriture dans `analyse/` uniquement. 5 6**Document vérifié :** `/private/tmp/claude-501/-Users-gregorydelaloeuf-Documents-scalewayxflexylead/ff38fb78-ccda-4574-ab16-1a49423c4d29/scratchpad/audit-v3.html` 7**Périmètre :** tout le document **sauf** la section « Les constats ». La section « Dans quel ordre » et la carte, non listées au mandat, font l'objet de l'annexe A (contrôles d'arithmétique interne seulement). 8 9**Dépôts lus, en lecture seule :** 10- `/Users/gregorydelaloeuf/Documents/scalewayxflexylead/code/flexylead-crm-0908/` — `730a5e79cbbb4191930f2de2f208f42ba483fb46` 11- `/Users/gregorydelaloeuf/Documents/scalewayxflexylead/code/flexylead-crm/` — `888cf02a19c76b977595612782293856ff0ab327` 12- `/Users/gregorydelaloeuf/Documents/scalewayxflexylead/code/apteva-platform/` — copie de travail **sans dépôt git** (`git log` refuse : « ni ceci ni aucun de ses répertoires parents n'est un dépôt git »), datée du 07/09 21 h 07. 13 14**Méthode.** Je n'ai repris **aucun** décompte, y compris ceux de `analyse/contre-confrontation-2026-09-08.md`. J'ai rejoué moi-même la substitution de jeton de `scripts/deploy-local-application-api.mjs:166-185` sur les 68 déclarations du manifeste, puis comparé octet à octet aux instantanés de `backend/`. Script de reconstruction conservé hors dépôt (`scratchpad/recon.py`). Convention de comptage de lignes : `wc -l`. 15 16**Résultat de contrôle :** ma reconstruction retrouve **exactement** les nombres de la contre-confrontation — 28 reproductibles / 38 divergentes / 1 empaquetée / 1 sans instantané sur 68 déclarations. La table de décomptes qui fait foi est donc confirmée par une seconde mesure indépendante. 17 18--- 19 20## 1. Titre et chapeau 21 22| Phrase exacte du document | Verdict | Fonction(s) / fichier(s) à nommer | Preuve | Texte de remplacement | 23|---|---|---|---|---| 24| « Le logiciel calcule bien plus qu'il n'affiche » | **VRAI** | `evaluateMonthlyFormula` — produit un tableau `breakdown` (indicateur, valeur, seuil, opérateur, montant, éligibilité, et `skipped_reason: "exclusive_group"` pour un palier écarté), renvoyé dans la réponse et **jamais écrit** | `backend/flexylead-app-pilotage-remuneration-query.mjs:2264` (définition), `:2270-2328` (breakdown), `:916` (renvoi). Le fichier déployé ne contient **aucun** `rows_insert` / `rows_update` / `rows_delete` / `tables_create` / `tables_alter` : `grep -cE "rows_insert\|rows_update\|rows_delete\|tables_create\|tables_alter" backend/flexylead-app-pilotage-remuneration-query.mjs` → **0** | tient tel quel ; peut être nommé : « la fonction `evaluateMonthlyFormula` établit palier par palier pourquoi un montant est dû, et n'écrit pas une ligne » | 25| « et rien n'écrit quelle version tourne » | **VRAI** | aucun instantané de `backend/` ne porte de constante de version | `grep -rn "APP_VERSION\|SOURCE_VERSION\|BUILD_VERSION\|__VERSION__" backend/*.mjs` → **aucun résultat**. Les champs `version:` renvoyés par les fonctions sont des curseurs de fraîcheur de données (ex. `backend/flexylead-app-commercial-prospects-query.mjs:1700` — `version: \`${range.from}:${range.toExclusive}:…\``), pas des versions de code. Côté interface, la version vient de `package.json` via `vite.config.ts:7,33` → `src/lib/appVersion.ts:6` | tient tel quel | 26| « On nous a remis les écrans, le code du serveur tel qu'il est déployé, puis le lendemain les sources de travail. » | **VRAI** | commits `888cf02` puis `730a5e7` | `git log` : `888cf02` 07/09 18 h 03 « Archive deployed sources for 73 production functions with retained invocations » ; `730a5e7` 08/09 13 h 51 « Complete backend working sources… ». `git ls-tree -r --name-only 888cf02 \| grep -c "^functions/"` → **0** | tient tel quel | 27| « Les deux ne coïncident pas, et le prestataire l'écrit lui-même. » | **VRAI** | `README.md:160`, section « Backend working sources (added 2026-09-08) » — ajoutée par le commit du 08/09 (le README de `888cf02` fait 151 lignes et s'arrête avant cette section) | anglais exact : *« The source directory includes current local, undeployed fixes; review and test before deployment. »* | tient tel quel | 28| « Sept analyses spécialisées ont lu ce code ; une huitième a été chargée d'attaquer leurs conclusions » | **À BORNER** | six confrontations du 08/09 (`argent`, `donnees`, `infra`, `integrations`, `qualite`, `routage`) + `agent-declencheur-2026-09-08.md` = 7 ; l'attaquant est `contre-confrontation-2026-09-08.md`, **qui écrit lui-même « les six rendus du 08/09 »** | `analyse/contre-confrontation-2026-09-08.md:1` ; `ls analyse/*2026-09-08*.md` | « Sept postes spécialisés ont lu le code ; six ont rendu une confrontation le 8 septembre, qu'une huitième analyse a été chargée d'attaquer. » | 29| « sept affirmations n'y ont pas survécu et sont retirées à la fin de ce document » | **VRAI** | section « Ce que nous avons retiré » : liste ordonnée de 7 entrées | comptage direct dans le HTML | tient tel quel | 30| Bandeau verdict : « 23 constats · 10 bloquants » | **VRAI** | — | `grep -c '<div class="f">'` → **23** ; occurrences de `sev-b` hors CSS → **10**, `sev-m` → **12**, `sev-n` → **1** ; 10+12+1 = 23, conforme au pied de page | tient tel quel | 31 32--- 33 34## 2. Les deux blocs de provenance 35 36| Phrase exacte du document | Verdict | Preuve | Texte de remplacement | 37|---|---|---|---| 38| « 7 septembre 2026 à 18 h 03 (heure de Barcelone) — dépôt `jeremy-flexylead/flexylead-crm`, branche `main`, référence `888cf02` » | **VRAI** | `git log --date=iso` → `888cf02 2026-09-07 18:03:32 +0200` ; `git reflog` → `clone: from https://github.com/jeremy-flexylead/flexylead-crm.git` | — | 39| « empreinte de l'arborescence `f68816ff` » | **VRAI** | `git rev-parse 888cf02^{tree}` → `f68816ff669a16470c1bad60ae24c9ead2f17480`. **Préciser la méthode** : c'est l'empreinte de l'objet-arbre git, pas celle de `git ls-tree \| sha256` (qui donne `67fafcdf`, cf. `analyse/confrontation-infra-2026-09-08.md:15`) — deux mesures différentes, toutes deux exactes | « empreinte de l'arborescence git `f68816ff` » | 40| « récupérée **46 secondes** après publication » | **VRAI** | `git reflog --date=iso` de `code/flexylead-crm` → `HEAD@{2026-09-07 17:04:18 +0100}: clone` = 18 h 04 min 18 s heure de Barcelone ; commit à 18 h 03 min 32 s → **46 s** | — | 41| « 872 fichiers » | **VRAI** | `git ls-tree -r --name-only 888cf02 \| wc -l` → **872** | — | 42| « 363 d'interface, 73 fonctions serveur, 38 scripts, 356 paquets compilés » | **VRAI**, base à préciser | répartition par répertoire de tête : `src` **363**, `backend` **73** (72 `.mjs` + 1 `.go`), `scripts` **38**, `prebuilt-dist` **322** + `dist` **34** = **356**. Le message du commit dit lui-même « 73 production functions » | « 363 fichiers d'interface, 73 instantanés de fonctions serveur, 38 scripts, 356 fichiers de paquets déjà compilés (`prebuilt-dist/` + `dist/`) » | 43| « 8 septembre 2026 à 13 h 51 — référence `730a5e7`, empreinte `11bf212e` » | **VRAI** | `git log` → `730a5e7 2026-09-08 13:51:31 +0200` ; `git rev-parse 730a5e79^{tree}` → `11bf212eca95d…` | — | 44| « récupérée trente-sept minutes après publication » | **VRAI** | `git reflog` de `code/flexylead-crm-0908` → `HEAD@{2026-09-08 13:28:21 +0100}` = 14 h 28 min 21 s Barcelone ; commit 13 h 51 min 31 s → **36 min 50 s** | « trente-sept minutes » tient (36 min 50 s) | 45| « 200 fichiers, 101 612 lignes ajoutées » | **VRAI** | `git diff --shortstat 888cf02 730a5e79` → `200 files changed, 101612 insertions(+), 146 deletions(-)` ; 190 ajouts + 10 modifications | « 200 fichiers touchés — 190 créés, 10 modifiés — et 101 612 lignes ajoutées » | 46| « les sources de travail du serveur, les fonctions qui manquaient » | **VRAI** | `functions/` = 156 fichiers, absent à `888cf02` ; 11 fichiers ajoutés à `backend/` : **10 `.mjs`** + `SNAPSHOT-ADDITIONS-20260908.json` | « les dix instantanés qui manquaient, avec leur relevé » | 47| « 85 fichiers de test » | **FAUX** | recomptage : `git ls-files \| grep -c '\.test\.mjs$'` → **83** (82 dans `functions/`, 1 dans `scripts/` : `scripts/application-api-manifest.test.mjs`) ; `_test.go` → **3** (`functions/ringover-call-events/{ai_handling,main,revision_update}_test.go`). Total **86**. Les 86 sont tous arrivés le 08/09 | « **86 fichiers de test** — 83 en JavaScript, 3 en Go » (ou « 82 fichiers de test serveur dans `functions/` » si l'on borne au serveur) | 48| « cinq grilles de notation » | **À BORNER** | 5 fichiers `config/*.json`, mais **6 grilles** : `hop_selectra_quality_v2.json` porte à la fois `grid` (`hop_selectra_universelle_v2`) et `compensation_grid` (`selectra_compensation_carbone_v1`). Identifiants : `conso_pilote_v2`, `free_v3`, `hop_selectra_universelle_v1`, `hop_selectra_universelle_v2`, `selectra_compensation_carbone_v1`, `mint_energie_v2` | « cinq fichiers de grille, qui décrivent **six** grilles » | 49| « La version applicative n'a pas bougé : 1.1.20. » | **VRAI** | `package.json:4` → `"version": "1.1.20"` dans les deux commits (`git show 888cf02:package.json`) | — | 50| « Le dépôt a été créé le 7 septembre : son historique compte sept enregistrements » | **VRAI** | `git rev-list --count HEAD` → **7** ; le premier est `236241b` « Initialize Flexylead CRM repository », 07/09 16 h 47 | — | 51| « tous du même week-end » | **FAUX** | les 7 commits sont du **lundi 7** (6 commits) et du **mardi 8 septembre** (1 commit). `git log --date=format:'%A'` → `lundi`, `mardi`. Amplitude : 21 heures | « tous des 7 et 8 septembre, sur vingt et une heures » | 52| « Il ne contient aucune trace de l'évolution du logiciel » | **VRAI** | 6 des 7 commits sont des versements de masse signés `jeremy-flexylead` ; le 7ᵉ (`f82bdde`, « Preserve local files before connecting remote ») est signé `Apteva Code`. Le commit du 08/09 verse 101 612 lignes en un bloc | — | 53 54--- 55 56## 3. « Ce qu'il faut retenir » — les cinq points 57 58| Phrase exacte | Verdict | Fonction(s) / fichier(s) à nommer | Preuve | Remplacement | 59|---|---|---|---|---| 60| **01** — « Sur soixante-huit fonctions, vingt-huit se reconstituent à l'identique depuis les sources ; **trente-huit divergent**. » | **À BORNER** | manifeste `scripts/application-api-manifest.mjs` (68 déclarations) ; transformation `scripts/deploy-local-application-api.mjs:166-185` | reconstruction rejouée : **28 identiques, 38 divergentes, 1 non comparable** (`flexylead-manage-facturation`, `bundle: true` au `manifest:66`, passée par `esbuild` au `deploy:168-175`), **1 sans instantané** (`flexylead-manage-telephony-numbers`, conditionnée à `FLEXYLEAD_APTEVA_TELEPHONY_ENABLED`, `manifest:1-3,57`, dite au `README.md:162`). 28 + 38 = 66, pas 68. **Et 68 n'est pas la taille de la production : `backend/` compte 83 instantanés** (82 `.mjs` + 1 `.go`) | « Sur les soixante-huit fonctions déclarées au manifeste, vingt-huit se reconstituent à l'identique, trente-huit divergent, une n'est pas comparable parce qu'elle est empaquetée avant d'être envoyée, une n'a pas d'instantané. Sur les quatre-vingt-trois instantanés de production, trente-quatre sont reproductibles et quarante-quatre ne le sont pas. » | 61| **01** — « Une même source est déployée à deux endroits, une copie à jour et l'autre avec **691 lignes** de retard. » | **FAUX sur le nombre** | `functions/quality-partner-gateway.mjs` → déployée sous **deux noms** : `flexylead-app-quality-query` (à jour) et `quality-partner-gateway` (en retard) | `scripts/application-api-manifest.mjs:27` déclare `flexylead-app-quality-query` avec pour source `../functions/quality-partner-gateway.mjs`. `sha256` : `functions/quality-partner-gateway.mjs` ≡ `backend/flexylead-app-quality-query.mjs` = `45d32753415333978832e0c3dbbb6262378f29bc…` (1 391 lignes, 51 308 o). `backend/quality-partner-gateway.mjs` = `2dba74ea…` (**1 222 lignes**, 43 652 o). Écart : **169 lignes nettes**, **441 lignes différentes** (305 `>`, 136 `<`). Le chiffre 691 vient de `analyse/balayage-correctifs-2026-09-08.md:75` et est une **transposition de 169**. Les deux fichiers sont bien le **même programme** : mêmes premières lignes (`MAX_PAGE_SIZE`, `ACCESS_TABLE = "quality_partner_access"`), l'ancien ne connaît ni `QUALITY_READ_PERMISSION` ni `criteria_page` | « La même source est déployée sous deux noms — `flexylead-app-quality-query` et `quality-partner-gateway`. La première est à jour, au bit près. La seconde a **169 lignes de retard**, et 441 lignes les séparent. » | 62| **01** — « Ce n'est pas une déduction : c'est une comparaison d'empreintes. » | **VRAI** | — | 3 empreintes `sha256` ci-dessus ; 10 empreintes de `backend/SNAPSHOT-ADDITIONS-20260908.json` recalculées, **les dix concordent** | tient | 63| **02** — « **Vingt-trois correctifs** sont déjà écrits, et ne tournent pas. » | **À BORNER** | classement de `analyse/lecture-profonde-divergences-2026-09-08.md:535` | le décompte publié — « correctifs : 23 · fonctionnalités neuves : 6 · remaniement : 1 · régression : 0 » — porte sur **18 programmes distincts et 44 divergences** (`:511`), pas sur 38. Il compte des **chantiers**, pas des fonctions | « Vingt-trois chantiers de correction sont déjà écrits dans les sources et ne tournent pas. Ils se répartissent sur dix-huit programmes. » | 64| **02** — « les sources sont postérieures à ce qui tourne » | **VRAI** | **la fonction à nommer : `reducePilotageInteractionFacts`** | `functions/flexylead-app-pilotage-query.mjs:3432` la **définit et l'exporte**, et c'est sa **seule occurrence dans le fichier** — plus personne ne l'appelle. Le déployé, lui, l'appelle : `backend/flexylead-app-pilotage-summary-query.mjs:3445` (`const rows = reducePilotageInteractionFacts(facts,dateRanges,…)`), définition `:3452`. On laisse derrière soi la fonction qu'on vient de remplacer ; on n'écrit pas une fonction exportée et inutilisée avant d'en avoir besoin | tient ; **nommer la fonction** | 65| **02** — « Trois de ces correctifs réparent notre premier constat, celui des chiffres d'appels » | **VRAI** | trois écarts nommables sur `flexylead-app-dashboard-query` : (1) **`missedCallSql`**, fonction exportée qui donne **une seule** définition de « manqué » — `functions/flexylead-app-dashboard-query.mjs:153`, **absente du déployé** ; (2) l'exclusion du serveur vocal — `functions/…:142`, `lower(COALESCE(CAST(handled_by AS TEXT),'')) != 'ivr'`, **zéro occurrence de `ivr` dans `backend/flexylead-app-dashboard-query.mjs`** ; (3) la colonne « uniques » du total | (3) est la preuve la plus nette : déployé `backend/flexylead-app-dashboard-query.mjs:858-859` — la ligne `__total__` lit `unique_received` / `unique_answered` **dans `google_acquisition_metrics`** ; source `functions/…:866-867` — remplacé par `(SELECT COUNT(DISTINCT opportunity_key) FROM scoped)` et `(SELECT COUNT(DISTINCT CASE WHEN answered = 1 THEN opportunity_key END) FROM scoped)` | tient ; **nommer `missedCallSql`, le prédicat `handled_by != 'ivr'` et la lecture dans `google_acquisition_metrics`** | 66| **02** — « un autre est **la réponse du prestataire** aux neuf appels mal attribués **qu'il a lui-même trouvés** le 7 septembre » | **FAUX** | le correctif Go : `functions/ringover-call-events/main.go:625-628`, commentaire *« A commercial inherited from a ringing event is a target, not proof of pickup. »*, contre `backend/ringover-call-events.go:612` — `if strings.TrimSpace(commercialID) != "" \|\| !truthy(payload.Data["is_ivr"])` | deux failles. (a) **Rien ne date `functions/`** : ce répertoire n'existe que dans le commit du 08/09, aucun fichier ne porte de date propre. Affirmer que ce correctif « est la réponse » au signalement du 07/09 suppose qu'il a été écrit après — non établi. (b) Le lien de cause est explicitement `SUGGÉRÉ` : `analyse/bug-attribution-appels-2026-09-07.md:357` — « Le chemin de code est certain ; qu'il soit la cause des 9 appels attribués à David Reix ne l'est pas », avec une hypothèse concurrente non écartée (`:404-411`, artefact d'affichage). Et le signalement vient du côté FLEXYLEAD : `analyse/journee-2026-09-07.md:631` — réconciliation faite « de tête par Jeremy à 13:12, et par Marco à 14:49 » | « Un autre correctif, écrit dans les sources et non déployé, porte exactement sur le mécanisme soupçonné derrière les neuf appels mal attribués relevés le 7 septembre : `functions/ringover-call-events/main.go:625-628`. Nous ne savons pas quand il a été écrit, et le lien avec ces neuf appels reste **suggéré**, pas établi. » | 67| **02** — « Le diagnostic n'est donc pas contesté. » | **À BORNER** | `README.md:5` et `:160` | le prestataire ne conteste ni ne confirme : il écrit seulement que les sources contiennent des correctifs non déployés (`README.md:160`) et qu'« *A GitHub commit does not deploy the UI, backend functions or database changes* » (`README.md:5`). Il n'a jamais dit que ces correctifs répondaient à nos constats | « Le prestataire écrit lui-même que ses sources contiennent des correctifs non déployés. Nous constatons que plusieurs d'entre eux portent sur les mêmes mécanismes que nos constats. » | 68| **03** — « Chaque décision d'aiguillage est motivée et conservée. » | **VRAI** | table `call_routing_decisions` (`backend/flexylead-ringover-smart-routing.mjs:16` — `const DECISION_TABLE = "call_routing_decisions"`), avec un motif écrit : `decision_reason` (`:631` `emergency_${phase}`, `:835` `response_budget_exceeded`) et les motifs de sélection `:188` `no_matching_rule`, `:324` `sticky_owner_unavailable`, `:1471` `sticky_repeat` | — | tient | 69| **03** — « **Pour chaque vente, la règle de prime appliquée est établie — puis jetée sans être écrite ni montrée.** » | **FAUX** | `remunerationFor` **écrit** la règle sur la vente | `backend/flexylead-create-sales.mjs:699-701` — `prime_montant`, `prime_devise`, **`prime_rule_id`** ; `backend/flexylead-update-sale.mjs:83-85`, idem. Ce qui est calculé puis jeté est **l'explication du montant dû au centre**, pas la prime du commercial : `evaluateMonthlyFormula` (`backend/flexylead-app-pilotage-remuneration-query.mjs:2264-2328`). **La phrase contredit le point « solide » du même document** (« La prime du commercial… Écrite sur la vente ») | « Pour chaque montant dû à un centre, le système établit quelle règle s'est appliquée et pourquoi — puis jette ce raisonnement sans l'écrire ni l'afficher. » | 70| **04** — « Aucun montant dû à un centre n'est jamais figé. […] Le logiciel sait pourtant figer un montant : ce dispositif existe, complet — uniquement du côté des fournisseurs. » | **VRAI** | `backend/flexylead-app-pilotage-remuneration-query.mjs` (recalcul, zéro écriture) contre `backend/flexylead-manage-facturation.mjs` (`factures`, `facture_lignes`, `facturation_matches`, `facturation_rules` déclarées au manifeste `:112-116` de la liste d'invalidation) | le fichier de rémunération déployé ne contient **aucun** appel d'écriture (vérifié ci-dessus). Aucune table de facture de centre n'existe : la seule chaîne de facture du système est celle des `facturation_*` | tient | 71| **05** — « Aucun seuil, aucune alerte, aucun déclenchement automatique. Un contrôle de cohérence existe, il est correct, et **rien ne le lance**. » | **À BORNER** | `flexylead-integrity-audit` — atteignable, mais par personne | le dispatcheur **déployé** l'expose : `backend/flexylead-app-command-dispatch.mjs:8` — `'integrity.audit': 'flexylead-integrity-audit'`. Donc il **est** appelable par `/v1/actions/execute`. Ce qui manque : (a) aucun écran ne l'appelle — recherche de `integrity.audit` dans `src/` → **aucun résultat** ; (b) aucune tâche automatique — les six `config/jobs/*.json` sont `work-routing`, `call-routing-reconciliation`, `ringover-audio-reconcile`, `ringover-presence-refresh`, `routing-performance-scores`, `google-ads-cost-sync`. Les deux seuls appelants sont `scripts/verify-staging-action-contracts.mjs:24` et `scripts/verify-production-reliability-contracts-20260824.mjs:39` (ce dernier importe `production-project-client.mjs` au `:5`) | « La route existe et le serveur sait l'exécuter : `backend/flexylead-app-command-dispatch.mjs:8`. **Aucun écran ne l'appelle et aucune tâche automatique ne la déclenche.** Seuls deux programmes de vérification l'appellent à la main, dont un qui vise la base de production. » | 72| **05** — « Les **480 signalements** du dossier viennent tous d'un être humain » | **FAUX** | — | le dépouillement du fil ne connaît qu'un chiffre : **283 signalements** (`analyse/registre-incidents-2026-09-02.md:13`, et onze reprises identiques ailleurs dans `analyse/`). Aucune source ne donne 480 pour des signalements. Le seul « 480 » du corpus est « environ 480 captures n'ont pas été examinées individuellement » (`analyse/analyse-pieces-jointes-2026-09-02.md:203`) — probable confusion. **Ce chiffre ne vient pas du code : il vient du dépouillement du fil de discussion WhatsApp, 7 010 messages du 02/03 au 02/09/2026** | « 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. » | 73 74--- 75 76## 4. « Ce qui tient » — les sept points 77 78| Phrase exacte | Verdict | Fonction(s) / fichier(s) à nommer | Preuve | Remplacement | 79|---|---|---|---|---| 80| « Le serveur ne croit jamais l'écran. Il retrouve lui-même qui pose la question et limite lui-même ce qu'il montre. » | **VRAI** | **`resolveActor`** et **`resolveCommercial`** | `backend/flexylead-app-commercial-prospects-query.mjs:1704` — `if (clean(gatewayAuth.kind ?? gatewayAuth.Kind) !== 'auth_jwt') throw httpError(401, 'Authenticated Gateway route required.')`, sujet pris dans `event.auth`, jamais dans le corps ; contrôle d'état actif `:1710`. `resolveCommercial` `:1715` retrouve la fiche par `auth_user_id`/`email` et **refuse** l'identifiant d'un autre commercial sauf `commercial:all_centres` ou `commercial:team:supervise` + même centre (`:1723-1725`) | tient ; **nommer `resolveActor` et `resolveCommercial`** | 81| « **Sur 41 fonctions, tout ce qui n'est pas prévu est refusé.** » | **FAUX comme énoncé** | le refus par défaut vit dans **une seule** fonction : **`enforceCommercialRead`** | (a) le nombre 41 est le résultat de `ls backend/flexylead-app-*.mjs \| wc -l` → **41** : c'est un décompte de nommage, pas de vérification. Déjà signalé comme **NON VÉRIFIABLE** par `analyse/ligne-par-ligne-lot2.md:108`. (b) Sur ces 41, **10 ne contiennent aucun refus explicite** (`Unsupported`/`Unknown`/`not supported`) : `flexylead-app-acquisition-query`, `-dashboard-query`, les six `-pilotage-*-query`, `-sales-audio-query`, `-session-query`. (c) Le refus par défaut réel est `enforceCommercialRead` (`backend/flexylead-app-business-tool-bridge.mjs:127`), qui se termine par `throw httpError(403, 'Table is outside the commercial workspace.')` (`:167`) — **mais il est percé pour neuf tables de référence** : `:128` `if (actor.canReadOperations \|\| referenceTable(table)) return;`, liste `:221-222` (`campaigns, centres, commercial_group_members, commercial_groups, commercial_objectives, offer_remuneration_rules, offres, routing_endpoints, stats_periods`). Le filtre de colonnes `COMMERCIAL_REFERENCE_COLUMNS` (`:18-28`) laisse passer `prime_montant`, `bonus_type`, `bonus_value`, `tiers` | « **Quarante des quarante et une fonctions d'application exigent une route authentifiée** — `if (gatewayAuth.kind !== 'auth_jwt') throw 401` —, la quarante et unième (`flexylead-app-quality-query`) contrôlant à la place un jeton partenaire. Et le cloisonnement des lectures tient dans **un seul programme**, `enforceCommercialRead`, qui refuse par défaut tout ce qu'il ne connaît pas — **sauf neuf tables de référence**, où un commercial authentifié peut lire les objectifs et les primes des autres. » (mesure : `grep -l "Authenticated Gateway route required" backend/flexylead-app-*.mjs \| wc -l` → **40**) | 82| « L'exclusivité client de trois mois est réellement appliquée. Le système croise **sept traces** avant de décider » | **VRAI, sur code déployé** | **`buildCentreAffinityLookup`** | `backend/flexylead-ringover-smart-routing.mjs:2121` (source `functions/…:2391`) : `UNION ALL` de sept branches — `recent_sale` (`:2131`), `prospect_centre_lock` (`:2161`), `released_ko` (`:2180`), `assigned_prospect` (`:2192`), `delivered_routing` (`:2208`), `inbound_call` (`:2222`), `planned_routing` (`:2240`), triées par `source_priority` puis `observed_at`, `LIMIT 1`. Borne à ajouter : **cinq des sept** sont bornées à 90 jours, deux ne le sont pas, et la troisième (`released_ko`) **lève** l'exclusivité au lieu de la poser | « Le système croise sept traces avant de décider — cinq bornées à trois mois, une sans borne, et une septième qui **libère** le client au lieu de le retenir. » | 83| « le réglage qui la gouverne ne peut pas envoyer un client chez un concurrent » | **VRAI, et je le confirme sur le déployé** — ce que la confrontation du 08/09 avait laissé « à confirmer » | **`selectRoutingRule`**, atteinte seulement si `repeat` est faux ; **`emergencyRoutingResponse`** pour la voie de secours | `backend/flexylead-ringover-smart-routing.mjs:311-327` : `const stickyRule = repeat ? scopeRules.find(…) : null;` puis `let selection = repeat && !stickyRule ? {…reason:"sticky_owner_unavailable"…} : stickyRule ? selectionForSticky(…) : selectRoutingRule({…})`. Structure **identique** à la source (`functions/…:574-593`). Voie de secours : `emergencyRoutingResponse` (`backend/…:697`), garde `:756-759` avec le commentaire *« an unscoped chain can silently send a call to a partner centre when Tables is slow »*, présent des deux côtés. Le réglage lui-même : `sticky_repeat`, écrit par `functions/flexylead-manage-call-routing.mjs:198` (défaut vrai), libellé écran « Appels répétés / Conserver la destination » (`src/dashboard/pages/OperationsRouting.tsx:574,814`) | tient ; la réserve « à confirmer sur la version déployée » de `analyse/confrontation-routage-2026-09-08.md:213` **peut être levée** | 84| « La disponibilité d'un commercial est la partie la mieux écrite du logiciel. Le serveur exige de savoir quel état l'écran croyait avoir, refuse un même changement envoyé deux fois, et écrit une trace horodatée avec son auteur. » | **VRAI, sur code déployé** | **`setAvailability`** et **`availabilityRequestEventId`** | `backend/flexylead-commercial-availability.mjs:131`. (a) refus du doublon : `:143-150`, recherche de `availabilityRequestEventId(commercial.id, requestId)` dans `telephony_events`, retour `{ ok:true, changed:false, duplicate:true }` ; (b) état attendu : `:152-155`, `if (expectedStatus !== currentStatus) throw httpError(409, "Votre disponibilité a changé dans un autre onglet…")` ; (c) trace : `:183-211`, insertion dans `telephony_events` avec `previous_status`, `status`, `actor_auth_user_id`, `actor_email`, `client_instance_id`, `request_id`, `received_at`. Garde d'identité supplémentaire : `assertExpectedSelfIdentity` (`:625-635`) | tient ; **nommer `setAvailability`** | 85| « Quand la réponse de l'IA revient malformée, aucune note n'est écrite plutôt qu'une note fausse. » | **VRAI, avec une borne** | le garde de `backend/process-appel-db-only.mjs:3512` ; **`parseRapport`** (`:3502`), **`nextEvalFailure`** (`:3519`) | `if (evaluations.length !== criteres.length \|\| score_total > score_max) { … }` — commentaire de l'auteur au `:3509-3511` : *« Eval produced nothing usable (Claude returned no parsable critère) → do NOT save a misleading 0/100 rapport »*. Seule la table `appels` est mise à jour (`:3520-3529`), jamais `rapports`. **Borne** : une note antérieure du même appel n'est pas effacée et continue de compter (`analyse/confrontation-qualite-2026-09-08.md:264`) | « … aucune note **nouvelle** n'est écrite plutôt qu'une note fausse. Une note antérieure, elle, subsiste. » | 86| « La note de qualité n'entre pas dans la prime du commercial. » | **VRAI** | **`remunerationFor`** | `backend/flexylead-update-sale.mjs:238-274` : les seules entrées sont l'offre, le type de vente, le centre, la date de vente et le chiffre d'affaires fournisseur. Contrôle d'absence : `grep -c "score\|ai_score\|rapport" backend/flexylead-update-sale.mjs backend/flexylead-create-sales.mjs` → **0 et 0** | tient ; **nommer `remunerationFor`** | 87| « Les barèmes sont versionnés » | **VRAI** | `offer_remuneration_rules` avec `starts_at` / `ends_at` | `backend/flexylead-manage-offer-catalog.mjs:8`, `:179-190` ; sélection par date `backend/flexylead-update-sale.mjs:246-248` (`effectiveOn`) | tient | 88| « les fuseaux horaires gérés — changement d'heure compris » | **VRAI** | — | `functions/local-audit-regressions.test.mjs:10` — *« Paris day boundaries agree across functions, including 23/25-hour DST days »* ; `:24` idem pour les seaux quotidiens SQLite ; `functions/pilotage-date-cache.test.mjs:6` ; `functions/pilotage-inbound-reduction.test.mjs:7` | tient | 89| « une suppression est **toujours** une désactivation, **jamais** un effacement » | **FAUX** | trois effacements réels dans le code **déployé** | (a) `backend/process-appel-db-only.mjs:2291` — `rows_delete` sur `rapports`, journal *« stale rapport deleted due to non-evaluable mismatch »* ; (b) `:3465` — idem, *« stale rapport deleted due to product mismatch »* ; (c) `backend/flexylead-admin-data-mutation.mjs:55` — `rows_delete` sur une table quelconque, opération `delete` de plein exercice ; (d) `backend/flexylead-app-business-action-bridge.mjs:4` expose `rows_delete` parmi les `WRITE_TOOLS`. Ce qui est vrai : la suppression **d'une règle de prime depuis l'écran** est une désactivation (`backend/flexylead-manage-offer-catalog.mjs:142` — `{ active: false, ends_at: now, updated_at_iso: now }`) ; et `deleteByLocalId` (`:222-224`) n'est qu'une compensation de reprise sur erreur | « Supprimer un **barème** depuis l'écran est une désactivation, jamais un effacement (`flexylead-manage-offer-catalog:142`). **Ce n'est pas une règle générale du logiciel** : la notation efface physiquement des rapports devenus incohérents, et la porte d'administration sait supprimer n'importe quelle ligne. » | 90| « **85 fichiers de test** décrivent le comportement attendu » | **FAUX** | — | **86** : 83 `.test.mjs` + 3 `_test.go` (détail au §2) | « 86 fichiers de test » | 91| « et **l'un d'eux cite quarante tickets d'incident** avec leur énoncé » | **FAUX** | le fichier qui en cite le plus est `functions/flexylead-app-pilotage-query.test.mjs` — **9** tickets | recomptage : `grep -roh "TKT-[0-9]\+" . \| sort -u \| wc -l` → **43** tickets distincts dans tout le dépôt, de **TKT-9 à TKT-116** ; **37** si l'on se limite aux tests et aux scénarios de bout en bout ; **26** dans les seuls `functions/*.test.mjs`. Record par fichier : 9 (`flexylead-app-pilotage-query.test.mjs`), 7 (`e2e/staging-all-in-progress-ui.spec.ts`), 6 (`process-appel-db-only.test.mjs`). La liste de 40 de `analyse/lecture-profonde-tests-2026-09-08.md:182` agrège tests **et** e2e et omet quatre tickets présents ailleurs | « Le code et ses tests citent nommément **quarante-trois tickets d'incident distincts**, de TKT-9 à TKT-116, avec leur énoncé. Trente-sept d'entre eux sont cités dans les fichiers de test. » | 92 93--- 94 95## 5. « Le problème de fond » 96 97### 5.1 Les trois citations du document d'accompagnement 98 99La section « Backend working sources (added 2026-09-08) » du `README.md` **n'existe pas** dans le commit du 07/09 (`git show 888cf02:README.md` s'arrête à 151 lignes, avant cette section). Les trois citations sont donc bien du 08/09. `FAIT` 100 101| Rendu français du document | Anglais exact | Emplacement | Verdict sur la traduction | 102|---|---|---|---| 103| « contiennent des correctifs locaux non déployés » | *« The source directory includes **current** local, undeployed fixes; review and test before deployment. »* | `README.md:160` | **Fidèle, mais tronqué.** Le français perd « current » (actuels) et la seconde moitié (« review and test before deployment »), qui est une **réserve du prestataire** sur la qualité de ces correctifs. La citer renforce la position de l'audit plutôt qu'elle ne l'affaiblit : elle dit que ces correctifs ne sont pas prêts à partir sans relecture. **Recommandation : citer la phrase entière.** | 104| « n'ont été ni rafraîchies ni écrasées » | *« Existing snapshots were not refreshed or overwritten. »* | `README.md:156` | **Fidèle.** Précision utile : le sujet est « Existing snapshots », défini au même endroit comme « historical deployed snapshots ». Le français « les copies de production » est exact. | 105| « la publication n'est pas un certificat de mise en production » | *« Git publication is not a production-readiness **certification**. »* | `README.md:160` | **Traduction infidèle, et elle durcit dans le sens de l'audit.** L'anglais dit : publier sur Git ne certifie pas que le code est **prêt pour la production**. Le français dit : publier ne prouve pas que le code **est en production**. Ce sont deux affirmations différentes ; la seconde est celle qui arrange l'audit. **Remplacement : « la publication sur Git ne vaut pas certificat d'aptitude à la mise en production ».** Et si l'on veut la phrase qui dit vraiment ce que l'audit veut dire, elle existe ailleurs, mot pour mot : `README.md:5` — *« A GitHub commit does not deploy the UI, backend functions or database changes. »* **C'est celle-là qu'il faut citer.** | 106| « compatibilité de déploiement progressif uniquement » | *« Rolling deployment compatibility only. Never retry deterministic SQL timeouts or permission failures with another expensive query. »* | `backend/flexylead-app-commercial-operations-sales-query.mjs:1422` ; `backend/flexylead-app-commercial-operations-sales-export-query.mjs:1422` ; source `functions/flexylead-app-commercial-resource-query.mjs:1439` | **Fidèle sur le fragment cité** (« rolling deployment » = déploiement par vagues / progressif). **Trois occurrences en tout dans le dépôt**, et elles ne sont **que** dans la génération récente et sa source — les 21 instantanés de la génération ancienne ne la portent pas. La phrase du document « Le code de la version récente porte le commentaire de son auteur » est donc **VRAIE et vérifiable par un `grep`**. Attention : la **seconde moitié** du même commentaire est celle que le document cite ailleurs (constat sur les relances) — c'est le même commentaire, cité en deux morceaux à deux endroits. | 107 108### 5.2 Les autres phrases de la section 109 110| Phrase exacte | Verdict | Fonction(s) / fichier(s) | Preuve | Remplacement | 111|---|---|---|---|---| 112| « sur soixante-huit fonctions, vingt-huit se reconstituent à l'identique, trente-huit divergent » | **À BORNER** | idem §3, point 01 | idem | idem | 113| « la même source est déployée à deux endroits — une copie à jour, l'autre avec **691 lignes** de retard » | **FAUX sur le nombre** | `flexylead-app-quality-query` / `quality-partner-gateway` | **169 lignes nettes**, 441 lignes différentes (§3). **Attention** : la phrase enchaîne ensuite sur le commentaire « rolling deployment », qui appartient à un **autre** couple — la famille commerciale, où l'écart est de **63 lignes nettes** (2 413 − 2 350), soit **65 ajoutées et 2 réécrites**. Le paragraphe mélange deux faits distincts | « 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, 63 lignes les séparent, et le code de la plus récente porte le commentaire de son auteur : *« Rolling deployment compatibility only »*. » | 114| « Vingt-trois correctifs, six fonctionnalités neuves, et aucune régression. C'est le classement de **ces trente-huit divergences**. » | **À BORNER** | — | le classement de `analyse/lecture-profonde-divergences-2026-09-08.md:511-535` porte sur **44 divergences ramenées à 18 programmes**, pas 38. Et 23 + 6 + 1 (remaniement) = 30 chantiers, pas 38 divergences : ce sont deux unités de compte différentes | « C'est le classement des quarante-quatre divergences, ramenées aux dix-huit programmes distincts qui les portent. » | 115| « Le sens est établi sur **cinq indices convergents** » | **VRAI** | `analyse/lecture-profonde-divergences-2026-09-08.md:24-31` | cinq indices numérotés, dont le n° 5 est la vérification en sens inverse (« aucune divergence où la production porte un commentaire explicatif absent de la source ») | tient | 116| « la source a cessé d'appeler une fonction que la production appelle encore, et l'a laissée derrière elle » | **VRAI** | **`reducePilotageInteractionFacts`** | `functions/flexylead-app-pilotage-query.mjs:3432` (définie, exportée, **occurrence unique**) contre `backend/flexylead-app-pilotage-summary-query.mjs:3445` (appelée) et `:3452` (définie) | tient ; **nommer la fonction** | 117| « **la production ne déploie pas un programme : elle déploie certaines de ses instances** » | **VRAI** | `functions/flexylead-app-commercial-resource-query.mjs` → 23 instantanés en production | groupement par empreinte après neutralisation de la ligne du jeton : **21 fichiers à 2 350 lignes** (empreinte `824966076c…`) et **2 fichiers à 2 413 lignes** (`b72073ce5d…`, `operations-sales-query` et `operations-sales-export-query`) | tient | 118| « Un même fichier est publié **vingt-deux** fois. » | **À BORNER — et le vrai chiffre est pire** | le 23ᵉ exemplaire : **`flexylead-app-commercial-call-audio-query`** | le manifeste en déclare **22** (`scripts/application-api-manifest.mjs:8-29`), mais `backend/` en contient **23**. Le 23ᵉ, `flexylead-app-commercial-call-audio-query.mjs`, porte `RESOURCE = 'call-audio'`, appartient à la grappe ancienne (empreinte identique aux 20 autres), et **n'est plus déclaré au manifeste** : le script de déploiement ne le mettra plus jamais à jour. Il est figé | « 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. » | 119| « **Trois chantiers** y sont déployés sur deux exemplaires, et sur aucun des vingt autres. » | **VRAI sur les trois chantiers, À BORNER sur « vingt »** | (1) le mode par lot `if (Array.isArray(request.sale_ids))` — `backend/flexylead-app-commercial-operations-sales-query.mjs:1253-1258` ; (2) le chargement en deux temps `if (request.enrichment === 'deferred' && !fixed)` — `:1338-1354` ; (3) **`salesReportReferenceQuery`**, requête indexée sur les rapports + repli — `:2210` (définition) et `:1418-1431` (usage) | diff des deux générations, ligne du jeton neutralisée : **5 blocs, 65 lignes ajoutées, 2 réécrites, +63 nettes**. Les trois chantiers sont absents des 21 autres exemplaires. « vingt » compte les seuls exemplaires **déclarés** ; il y en a **vingt et un** en service | « Trois chantiers y sont déployés sur deux exemplaires, et sur aucun des vingt et un autres. » | 120| « Deux comportements différents du même programme tournent donc en même temps. » | **VRAI** | — | deux empreintes distinctes en production pour le même fichier source | tient | 121| « Un relevé partiel livré le 8 septembre donne le numéro de version de dix fonctions » | **VRAI** | `backend/SNAPSHOT-ADDITIONS-20260908.json` | 10 entrées, chacune avec `name`, `id`, `active_version_id`, `sha256`. **J'ai recalculé les dix `sha256` : les dix concordent** | tient | 122| « elles s'échelonnent **de la 1751 à la 1915** — **cent soixante-quatre** mises en ligne d'écart » | **FAUX, deux fois** | — | `active_version_id` triés : **1742**, 1751, 1754, 1755, 1758, 1759, 1761, 1858, 1868, **1915**. Le minimum est **1742** (`commercial-supervision-session`), pas 1751. Écart réel : **173** | « elles s'échelonnent de la **1742** à la **1915** — **cent soixante-treize** mises en ligne d'écart à l'intérieur d'un même lot » | 123| « le dossier contient **onze constructions différentes de l'interface**, de la version **1.1.12 à la 1.1.20**, jamais nettoyées » | **VRAI** | `prebuilt-dist/assets/index-*.js` | 11 fichiers d'entrée. Versions extraites du littéral injecté par `vite.config.ts:33` : 1.1.12, 1.1.13, 1.1.14, 1.1.15, **1.1.16 ×3**, 1.1.17, 1.1.18, 1.1.19, 1.1.20 | tient | 124| « Un même numéro de version en désigne trois. » | **VRAI** | `index-CR8_O9Mw.js`, `index-BYb6uCK1.js`, `index-Dlo_L0u9.js` | les trois portent `"1.1.16"` ; le premier est compilé contre `1780643332205-d42e91c16732f3ab`, les deux autres contre `1778674225986-34533e84d347a861` | tient ; **nommer les trois fichiers** | 125| « Deux versions consécutives sont fichier pour fichier identiques. » | **VRAI** | `index-zO26z-l9.js` (1.1.18) ≡ `index-mc2GaOmQ.js` (1.1.19) | après neutralisation du numéro de version et des empreintes de nom de fichier : fermeture transitive de **29 fichiers** de chaque côté, mêmes noms normalisés, **zéro fichier au contenu différent**. (`analyse/lecture-profonde-divergences-2026-09-08.md:…` annonce 38 fichiers de fermeture ; ma reconstruction, plus étroite, en trouve 29 — **la conclusion est la même**.) | « La version 1.1.19 est, fichier pour fichier, la version 1.1.18. » | 126| « Le dossier de sortie habituel contient la 1.1.12, compilée contre un projet de développement » | **VRAI** | `dist/assets/index-C7kU5e-N.js` | `vite.config.ts:74` — `outDir: 'dist'`. Le fichier de `dist/` est **octet pour octet identique** à `prebuilt-dist/assets/index-C7kU5e-N.js` (`cmp` silencieux), version `"1.1.12"`, projet `1780643332205-d42e91c16732f3ab`, qui n'est ni la production (`1778674225986-…`, `scripts/production-project-client.mjs:2`) ni Staging (`1783685516365-…`, `scripts/staging-project-client.mjs:2`) | tient | 127| « dont l'identifiant est écrit en clair dans **cinq** de ces paquets » | **VRAI** | `index-C7kU5e-N.js` (1.1.12), `index-CFuhr76Y.js` (1.1.13), `index-BPKYZim5.js` (1.1.14), `index-CqHhu2iR.js` (1.1.15), `index-CR8_O9Mw.js` (1.1.16) | les cinq portent `1780643332205-d42e91c16732f3ab` **et** la chaîne littérale `dev-209` | tient ; **nommer les cinq fichiers** — et signaler que ce n'est pas seulement l'identifiant de projet : la clé littérale `dev-209` y est aussi | 128| « un composant extérieur s'est mis à jour de lui-même et a cassé l'authentification […] Réparé en une demi-heure, retombé vingt minutes plus tard. » | **À BORNER — non vérifiable dans le corpus qui m'est ouvert** | — | aucun document de `analyse/` ne relate cet incident ; le fil parsé (`analyse/messages.json`) s'arrête au 02/09. La copie locale de `code/apteva-platform/` date du 07/09 21 h 07 et **n'a pas de dépôt git** : je ne peux ni dater ni recouper. Régime lecture seule, aucun appel réseau | conserver la phrase mais **la sourcer par un message daté du fil** ; sinon la présenter comme une déclaration reçue le 8/09 et non comme un fait établi par l'audit | 129 130--- 131 132## 6. « Ce que nous avons retiré » 133 134| Phrase exacte | Verdict | Fonction(s) / fichier(s) | Preuve | Remplacement | 135|---|---|---|---|---| 136| 1. « Onze fonctions manquent à la livraison. » Elles sont arrivées. | **VRAI, base à préciser** | — | `git diff --name-only 888cf02 730a5e79 -- backend` → **11 fichiers** : **dix `.mjs`** (`commercial-supervision-session`, `flexylead-api-gateway-bootstrap`, `flexylead-claim-ai-reservation`, `flexylead-manage-call-routing`, `flexylead-manage-offer-catalog`, `flexylead-manage-stats-periods`, `flexylead-manage-work-routing`, `flexylead-telephony-control`, `flexylead-unassign-lead`, `flexylead-update-sale`) **plus `SNAPSHOT-ADDITIONS-20260908.json`** | « Dix instantanés de fonctions manquaient ; ils sont arrivés le 8 septembre, avec un onzième fichier qui est leur relevé. » | 137| 2. « Il n'existe aucun test automatique côté serveur. » Quatre-vingt-cinq fichiers de test sont arrivés. | **FAUX sur le nombre, VRAI sur le fond** | — | **86** (83 `.test.mjs` + 3 `_test.go`), tous versés par le commit du 08/09 | « Quatre-vingt-six fichiers de test sont arrivés. » | 138| 2 bis. « Ce qui reste vrai : **aucune commande du projet ne les lance.** » | **VRAI** | `package.json` | `"test": "bun test src"` — **`src` seulement**. `"verify": "bun run test && bun run typecheck && bun run build && bun run test:e2e"` n'en ajoute aucun. Aucun script ne cible `functions/`, aucun ne lance `go test` | tient ; **citer la ligne : `"test": "bun test src"`** | 139| 3. « La prime d'un commercial se modifie sans laisser de trace. » Faux. | **VRAI (le retrait est justifié)** | **`AUDIT_TABLE = "commercial_sale_edit_events"`** | `backend/flexylead-update-sale.mjs:1` ; colonnes déclarées `:177` — `previous_version`, `before_state (json)`, `after_state (json)`, `actor_auth_user_id`, `status`, `occurred_at`, `applied_at`, `error` ; écriture avant l'opération (`:100`), confirmation après relecture (`:118-123`), marquage d'erreur sinon (`:135-140`) | tient | 140| 3 bis. « **le seul endroit de toute la chaîne d'argent où la valeur précédente est conservée** » | **VRAI, périmètre à déclarer** | — | `grep -l "before_state\|after_state" backend/*.mjs` → **3 fichiers** : `flexylead-update-sale`, `flexylead-record-work-result`, `lead-qualification-update`. Les deux derniers ne sont pas des fonctions d'argent. Aucune des autres fonctions d'argent (`flexylead-create-sales`, `flexylead-manage-offer-catalog`, `flexylead-manage-facturation`, `flexylead-manage-production-economics`, `flexylead-app-pilotage-remuneration-query`) n'écrit d'état antérieur | « le seul endroit de la chaîne d'argent — création de vente, catalogue d'offres, facturation, économie de production, rémunération — où la valeur précédente est conservée » | 141| 3 ter. « celui qui modifie n'est pas un administrateur, c'est le commercial propriétaire, sans limite de temps, même après validation » | **VRAI** | **`assertSaleOwnership`** ; **`EDITABLE_STATUSES`** | `backend/flexylead-update-sale.mjs:30` — `assertSaleOwnership(sale, commercial.aliases)` ; `:4` — `EDITABLE_STATUSES = new Set(["brouillon","signee","validee","a_completer","perdue"])`, `validee` compris, aucun test d'ancienneté ni de clôture de période | tient ; **nommer `assertSaleOwnership` et `EDITABLE_STATUSES`** | 142| 4. « La porte non authentifiée peut détourner un appel. » Faux. Elle écrit bien un état de présence, mais l'aiguillage ne le lit pas : il décide sur un autre champ, protégé. | **VRAI — et les deux champs sont nommables** | écrit : **`updateCommercialPresenceFromCallEvent`** → colonne **`presence_status`** ; lu par l'aiguillage : colonne **`availability_status`**, écrite par **`setAvailability`** | `backend/ringover-call-events.go:3127` définit `updateCommercialPresenceFromCallEvent`. `grep -l "presence_status" backend/` → **un seul fichier**, `backend/ringover-call-events.go`. L'aiguilleur ne lit que `availability_status` : `backend/flexylead-ringover-smart-routing.mjs:1817`, `:1922`, `:2329` — **zéro occurrence de `presence_status`** | « Elle écrit `presence_status`. L'aiguillage décide sur `availability_status`, qui n'est écrit que par la fonction de disponibilité, authentifiée et protégée par un état attendu. Aucun code ne relie les deux colonnes. » | 143| 5. « La réserve sur l'exclusivité client […] Le réglage a été retrouvé : il ne commande pas l'exclusivité. » | **VRAI, et je l'étends au déployé** | `sticky_repeat` ; `selectRoutingRule` ; `selectionForSticky` | voir §4. La démonstration de `analyse/confrontation-routage-2026-09-08.md` portait sur la source ; je l'ai refaite sur `backend/flexylead-ringover-smart-routing.mjs:311-327` : structure identique | tient ; on peut retirer la mention « à confirmer sur la version déployée » | 144| 6. « Sans les sources du serveur, aucun correctif n'est exécutable. » Les sources sont là. | **VRAI** | — | `functions/` = 156 fichiers ; les 68 déclarations du manifeste pointent toutes vers un fichier présent (vérifié par ma reconstruction : aucune erreur d'ouverture) | tient | 145| 7. « Notre explication du classement d'équipe qui affiche deux fois la même personne. […] trois mécanismes peuvent la produire. » | **VRAI** | les trois clés : `backend/flexylead-app-pilotage-summary-query.mjs:337` (clé de regroupement, repli sur le **nom**), `:364` (clé de rapprochement avec le roster), `:655` (clé du roster) ; plus `:1794` (`commercialId: null`) et `:2060` (`"unattributed"`) | mêmes numéros de ligne dans la source et le déployé | tient | 146| « la part de copie du serveur est de **six lignes sur dix** et non de deux sur trois » | **VRAI** | — | recompte `wc -l` : `backend/*.mjs` = **120 991** lignes, 82 fichiers ; `ringover-call-events.go` = **4 440**. Les **34** fonctions engendrées pèsent 21×2 350 + 2×2 413 + 6×4 539 + 3×232 + 2×263 = **82 632** lignes, pour **9 797** lignes de contenu distinct (5 grappes). Redondance = **72 835**. 72 835 / 120 991 = **60,2 %**. Avec le Go : 72 835 / 125 431 = **58,1 %** | tient | 147| « l'écart entre deux exemplaires d'une même fonction est de **soixante-trois lignes** et non de soixante-sept » | **VRAI** | famille commerciale | 2 413 − 2 350 = **63** nettes ; le diff donne **65 ajoutées, 2 réécrites** = 67 lignes de différence. Les deux nombres sont vrais, ils ne mesurent pas la même chose. **Contradiction interne à corriger : le point 01 de « Ce qu'il faut retenir » annonce 691 lignes pour ce même sujet.** | tient — mais **le 691 du point 01 doit disparaître** | 148 149--- 150 151## 7. « Ce que nous n'avons pas pu établir » 152 153| Phrase exacte | Verdict | Fonction(s) / fichier(s) | Preuve | Remplacement | 154|---|---|---|---|---| 155| 1. « le dossier précise que les copies conservent leur état de capture d'origine, **sans date individuelle** » | **VRAI** | `README.md:156` ; `backend/SNAPSHOT-ADDITIONS-20260908.json:2` | anglais exact : *« Existing snapshots were not refreshed or overwritten »* ; le relevé porte **une seule** date pour les dix (`"captured_at": "2026-09-08T11:50:46.663Z"`) et un `note` explicite : *« Additive export only. Existing 73 snapshots retain their original capture state. »* | tient | 156| 1 bis. « **Dix fonctions seulement portent une date** » | **À BORNER** | — | les dix ne portent pas dix dates : elles partagent **une** date de capture. Aucune ne porte de **date de mise en ligne** — c'est précisément ce qui manque, et c'est ce qu'il faut demander | « Dix fonctions seulement sont couvertes par un relevé daté — et c'est une seule date de capture pour les dix. **Aucune ne porte sa date de mise en ligne.** » | 157| 1 ter. « neuf sont identiques à leur source, une diverge » | **VRAI** | la divergente : **`flexylead-claim-ai-reservation`** | comparaison `sha256` des dix `backend/*.mjs` avec leur homologue `functions/*.mjs` : neuf identiques, une divergente — et c'est **celle qui porte le plus haut numéro de version des dix (1915)**, donc la mise en service la plus récente qu'on sache dater | tient ; **nommer la fonction et son numéro de version** — l'argument en devient plus fort | 158| 1 quater. « cet échantillon a été choisi par le prestataire : il ne contient ni l'aiguillage, ni le pilotage, ni le tableau de bord » | **VRAI** | absents du relevé : `flexylead-ringover-smart-routing`, les six `flexylead-app-pilotage-*-query`, `flexylead-app-dashboard-query` | les dix noms du relevé sont listés au §5.2 ; aucun des trois n'y figure | tient | 159| 2. « L'existence d'une sauvegarde. Nous n'en avons constaté aucune » | **VRAI, sur le dépôt** | — | aucun script de sauvegarde dans `scripts/` (48 entrées `package.json`, aucune n'est une sauvegarde) ; `README.md:160` — *« Secrets, environment files, production rows, **backups** and platform diagnostic checkouts are intentionally excluded »*, ce qui confirme que le sujet existe et qu'il est hors dépôt | tient ; **on peut citer le README : le prestataire dit lui-même que les sauvegardes sont exclues de la livraison, pas qu'elles n'existent pas** | 160| 3. « Les fichiers livrés décrivent des déclenchements que **rien, dans le dossier, n'installe.** » | **VRAI** | `reconcileJobs` | recherche de `config/jobs` dans tout le dépôt : **deux occurrences, toutes deux dans des tests** (`functions/ringover-audio-reconcile.test.mjs:409`, `functions/flexylead-routing-system-contract.test.mjs:59`). Le seul installateur d'ordonnanceur livré est `reconcileJobs` (`backend/flexylead-api-gateway-bootstrap.mjs:70-95`), qui interroge `jobs_list` avec `owner_app: 'flexylead'` (`:72`) — or **aucune** des six tâches ne porte cet `owner_app` : elles portent `flexylead-work-routing`, `flexylead-call-routing-reconciliation`, `flexylead-ringover-maintenance`, `flexylead-google-ads-costs`, `flexylead-ringover-presence`, `flexylead-routing-performance`. **Les deux mécanismes vivent dans des espaces disjoints : il n'y a pas de conflit, il y a une absence de recouvrement** | « Les six fichiers livrés ont la forme d'un **export** de l'inventaire de la plateforme — fréquence, fuseau, cible, reprises, et un champ « actif » que seule la plateforme produit (cinq des six le portent). Aucun programme du dossier ne les installe, et le seul installateur livré travaille sur un autre espace de tâches : il ne les voit pas, ne les annule pas, ne les recrée pas. Ce qui manque n'est donc pas le calendrier, c'est **la trace des passages**. » | 161| 4. « Les volumes réels. Aucun montant, aucun décompte de ce document n'est tiré de la base de production » | **VRAI** | — | régime respecté ; aucun accès base dans aucun rendu | tient | 162| 5. « **Deux fonctions du chemin audio et de la transcription sont déployées sans source de travail livrée.** » | **VRAI** | **`flexylead-project-telephony-recording`** (259 l.) et **`flexylead-transcribe-telephony-recording`** (278 l.) | aucun fichier homonyme dans `functions/`. Ce sont les deux seules fonctions déployées sans aucune source (la troisième candidate, `flexylead-app-commercial-call-audio-query`, est engendrée par le gabarit commercial : elle a une source, elle n'a plus de déclaration) | tient ; **nommer les deux fonctions** | 163| 5 bis. « **Ce sont précisément celles qui portent deux de nos constats** : la livraison est incomplète là où l'audit en a le plus besoin. » | **FAUX** | — | les deux fonctions sont **du chemin Twilio**, pas du chemin Ringover : `backend/flexylead-project-telephony-recording.mjs:1` — `const PROVIDER = "twilio"` ; `backend/flexylead-transcribe-telephony-recording.mjs:10` — `if (clean(appel.provider).toLowerCase() !== "twilio") return { ok: true, status: "ignored" }`. Les fonctions qui portent nos deux constats ont **toutes une source livrée** : `ringover-audio-mirror`, `ringover-audio-reconcile`, `process-appel-db-only`. **Et le contre-exemple est dans l'une des deux** : `backend/flexylead-transcribe-telephony-recording.mjs:207` demande **`diarize: true`** à Deepgram, alors que `backend/process-appel-db-only.mjs` ne contient **aucune** occurrence de `diariz`. La séparation des interlocuteurs est donc déjà demandée quelque part en production | « Deux fonctions sont déployées sans source de travail : `flexylead-project-telephony-recording` et `flexylead-transcribe-telephony-recording`. Elles appartiennent au chemin Twilio, celui de l'agent vocal. **Et l'une des deux est un contre-exemple utile : elle demande déjà la séparation des interlocuteurs à Deepgram (`diarize: true`), ce que la chaîne Ringover ne fait pas.** » | 164| 6. « **Une fonction de 2 406 lignes n'a aucun test et n'est mentionnée nulle part. Nous ne savons pas ce qu'elle fait en production.** » | **FAUX** | **`functions/flexylead-production-pilotage.mjs`** (2 406 lignes) | (a) **elle n'est pas en production** : aucun `backend/flexylead-production-pilotage.mjs` ; (b) elle est **désactivée à chaque déploiement** : `scripts/deploy-local-application-api.mjs:226` — `await disableLegacyFunction('flexylead-production-pilotage');`, qui pose `status: 'disabled'` et `function_url: { enabled: false }` (`:262-271`) ; (c) elle est **mentionnée cinq fois** : `scripts/deploy-local-pilotage-function.mjs:97,103` (« obsolete », « legacy »), `scripts/application-api-manifest.test.mjs:17` (un test **exige** son absence du manifeste), `src/lib/pilotageProductionSource.test.ts:19` (un test exige que la source principale ne la référence plus). `analyse/confrontation-qualite-2026-09-08.md:549` avait déjà tranché : « elle est désactivée au déploiement — ce point est réglé, elle ne tourne pas » | **retirer entièrement ce point.** Si l'on veut le conserver, l'écrire ainsi : « Une ancienne version du pilotage, 2 406 lignes, est livrée dans les sources. Elle n'est pas déployée, et le script de mise en service la désactive explicitement à chaque passage. Ce n'est pas un trou : c'est du code mort, correctement neutralisé. » | 165| 7. « Si les **cinq déclencheurs automatiques** livrés sont actifs. Sur le code déployé, ils échouent en trois lignes faute d'authentification. » | **VRAI, à préciser** | les 5 : `workflows/{flexylead-route-ai-reservation, flexylead-route-missed-call, flexylead-vente-readiness-insert, flexylead-vente-readiness-update, process-appel}.yaml`. La voie d'entrée absente : **`isTablesEventRequest`** et **`routeTablesEvent`** ; le mur : **`authorizeInvocation`** | les workflows appellent `flexylead-route-work-items` avec `{table, ids, id, source_topic}`. Dans le déployé, `handler` (`backend/flexylead-route-work-items.mjs:22`) enchaîne trois gardes (`:23` contexte, `:24` méthode, `:25` corps) puis **`await authorizeInvocation(context, body)` au `:26`**, qui exige soit `body.router_secret` égal à `FLEXYLEAD_WORK_ROUTER_SECRET`, soit un acteur administrateur (`:1598-1605`, sinon `throw new Error("Utilisateur non autorisé à lancer le routage.")`). **`isTablesEventRequest` et `routeTablesEvent` n'existent pas dans le déployé** (`grep -c` → 0 et 0) alors qu'ils sont dans la source (`functions/…:27-30`, 2 et 4 occurrences). Et `README.md:158` dit : *« `workflows/`: workflow definitions used by backend contract tests; committing them does not activate them. »* | « Sur le code déployé, ils butent au quatrième énoncé du gestionnaire : la voie d'entrée qu'ils empruntent (`isTablesEventRequest`) n'existe pas encore en production, et `authorizeInvocation` refuse un appel sans secret ni administrateur. » | 166| 8. « Aucun journal d'exécution ne nous a été remis. » | **VRAI** | — | `README.md:160` — les journaux ne font pas partie de la livraison ; aucun fichier de journal dans le dépôt | tient | 167 168### 7.1 La note de contexte 169 170| Phrase exacte | Verdict | Preuve | Remplacement | 171|---|---|---|---| 172| « **L'infrastructure a changé trois fois en quatre jours.** Changement d'hébergeur le 6 septembre. Nouvelle machine annoncée pour le 8 au soir. Puis […] une machine a été créée chez un troisième hébergeur à **19 h 11** » | **VRAI** | `analyse/compte-vultr-2026-09-08.md:3` — « Hetzner, Scaleway le 06/09, nouvelle machine Scaleway le 08/09 au soir, puis Vultr » ; `:65` — « Première machine, créée le 08/09 à **17 h 11 UTC** », soit **19 h 11** heure de Paris/Barcelone | tient ; préciser « 17 h 11 UTC » entre parenthèses évite toute ambiguïté | 173| « Nos mesures portent sur la machine du 8 septembre : deux cœurs, quatre gigaoctets. » | **VRAI** | `analyse/compte-vultr-2026-09-08.md:5` — gabarit **POP2-HC-2C-4G** | tient | 174| « Le processeur y tenait **un plateau de soixante pour cent** […] avec un pic à **soixante-dix-huit**. » | **À BORNER** | la source dit « processeur **en plateau à 60-67 %** » (`:79`) et « plafonnant à **78 %** » (`:5`) | « un plateau de soixante à soixante-sept pour cent […] avec un pic à soixante-dix-huit » | 175| « La mémoire n'a jamais dépassé deux gigaoctets et demi » | **VRAI** | `:5` et `:79` — « mémoire n'ayant jamais dépassé 2,5 Go sur 4 » | tient | 176| « soixante-quatorze mégaoctets écrits par minute en moyenne, cent quatre-vingts en pointe » | **VRAI** | `:5` — « 74 Mo écrits par minute en moyenne, 180 en pointe, pic de lecture à 523 Mo/min ». **Le pic de lecture à 523 Mo/min est omis, et c'est le chiffre le plus élevé** | ajouter : « et un pic de lecture à cinq cent vingt-trois » | 177| « La machine qui la remplace a quatre cœurs et seize gigaoctets, à Paris. » | **VRAI** | `:71-73` — `vx1-g-4c-16g-240s`, région **`cdg` — Paris**, Debian 13, en marche. **Réserve non reprise dans le document** : `:77` — « Ce n'est pas la machine annoncée » (Marco décrivait du dédié Ryzen PRO 3600, 6 cœurs/12 fils, 32 Go, 2×1 To NVMe) ; `:81` — « 240 Go pour 96 Go d'audio déjà basculés » | ajouter la réserve, ou dire « une instance de nuage à quatre cœurs et seize gigaoctets, à Paris — pas la machine dédiée annoncée » | 178| « la prise en charge de cette gamme de machines dans la plateforme d'Apteva a été corrigée à **18 h 11** » ; « **onze publications** ont eu lieu sur la plateforme, et aucune sur le logiciel de FLEXYLEAD » | **À BORNER — non revérifiable en local** | la copie de `code/apteva-platform/` **n'a pas de dépôt git** et date du 07/09 21 h 07 : elle est antérieure aux événements décrits. Aucun document de `analyse/` ne porte ces deux faits. Régime : aucun appel réseau | conserver, mais marquer la source : « relevé sur le dépôt public de la plateforme le 9 septembre ». Une capture datée doit être versée à `analyse/`, sinon ces deux phrases ne sont pas opposables | 179| « les adresses publiques n'ont pas été basculées : le trafic arrive toujours sur une machine du prestataire, qui le renvoie ensuite » | **cohérent avec le corpus, non revérifié ici** | mémoire de session « Migration faite le 06/09 » ; hors périmètre code | à sourcer par un relevé DNS daté versé à `analyse/` | 180 181--- 182 183## 8. Récapitulatif des nombres à corriger 184 185| Écrit dans le document | À écrire | Base de la mesure | 186|---|---|---| 187| « 691 lignes de retard » (×2) | **169 lignes nettes**, 441 lignes différentes | `wc -l` sur `backend/quality-partner-gateway.mjs` (1 222) vs `backend/flexylead-app-quality-query.mjs` (1 391) | 188| « de la 1751 à la 1915 » | **de la 1742 à la 1915** | `backend/SNAPSHOT-ADDITIONS-20260908.json`, `active_version_id` triés | 189| « cent soixante-quatre mises en ligne d'écart » | **cent soixante-treize** | 1915 − 1742 | 190| « 85 fichiers de test » (×2) | **86** (83 `.test.mjs` + 3 `_test.go`) | `git ls-files` | 191| « l'un d'eux cite quarante tickets » | **43 tickets distincts dans le dépôt**, 37 dans les tests et l'e2e ; **9 au maximum dans un même fichier** | `grep -roh "TKT-[0-9]\+" \| sort -u` | 192| « les 480 signalements du dossier » | **283 signalements** — et préciser qu'ils viennent du **dépouillement du fil WhatsApp**, pas du code | `analyse/registre-incidents-2026-09-02.md:13` | 193| « sur soixante-huit fonctions, 28 / 38 » | ajouter « + 1 non comparable + 1 sans instantané », et donner la base de production : **83 instantanés, 34 / 44** | reconstruction rejouée | 194| « Un même fichier est publié vingt-deux fois » | **vingt-trois fois en production, vingt-deux déclarées** | `ls backend/flexylead-app-commercial-*` | 195| « sur aucun des vingt autres » | **vingt et un** | idem | 196| « cinq grilles de notation » | **cinq fichiers, six grilles** | `config/*.json` | 197| « Sur 41 fonctions, tout ce qui n'est pas prévu est refusé » | **40 des 41** exigent une route authentifiée ; le refus par défaut tient dans **une** fonction, percée pour **neuf** tables | `grep -l "Authenticated Gateway route required" backend/flexylead-app-*.mjs` | 198| « ces trente-huit divergences » (classement) | **quarante-quatre divergences, dix-huit programmes** | `analyse/lecture-profonde-divergences-2026-09-08.md:511` | 199| « plateau de soixante pour cent » | **soixante à soixante-sept pour cent** | `analyse/compte-vultr-2026-09-08.md:79` | 200| « tous du même week-end » | **des 7 et 8 septembre** (lundi et mardi) | `git log --date=format:'%A'` | 201 202--- 203 204## Annexe A — arithmétique interne de « Dans quel ordre » (hors mandat, contrôlée par acquit) 205 206| Phrase | Verdict | Contrôle | 207|---|---|---| 208| « additionner les vingt-deux estimations à leurs extrêmes donne soixante-sept à cent vingt-neuf jours » | **VRAI** | 23 constats, dont un sans fourchette (« structurel ») → 22 estimations. Somme des bornes basses = **67**, hautes = **129**. Recalculé à la main | 209| « 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 » | **VRAI** | 5 blocs `fix done` dans le HTML ; leurs fourchettes : 6-12, 3-6, 1-2, 1-2, 1-3 → retrait de 12 et 25 → **55-104** ; milieu 79,5 ≈ **80** | 210| « Mettre en service **les sept correctifs** déjà écrits » | **À BORNER** | 7 n'est dérivable qu'en comptant 3 correctifs pour le constat des chiffres d'appels + 1 + 1 + 2 (les deux moitiés du cycle NRP), et en comptant le dispositif IVR en cinq fichiers comme un seul. **Le même document annonce 23 correctifs au point 02.** Écrire la base : « les sept correctifs qui répondent à cinq de nos constats » | 211| « Trois chantiers font à eux seuls le quart du total […] pas sur les dix-neuf autres » | **VRAI** | 22 estimations − 3 = 19 | 212 213--- 214 215## Ce que je n'ai pas pu établir 216 2171. **Si `backend/` est bien la production.** Tout repose sur une déclaration du prestataire, dont j'ai vérifié la cohérence interne (dix `sha256` concordants) et non la conformité. Le relevé et les fichiers viennent du même versement, à quelques secondes d'intervalle. C'est la limite qui plafonne tout ce document. 2182. **Les deux faits du 8 septembre au soir sur la plateforme Apteva** (correction à 18 h 11, onze publications depuis le 8/09 13 h 51). Copie locale sans dépôt git, antérieure aux faits ; aucun appel réseau autorisé. 2193. **L'incident d'authentification du 8 septembre au matin.** Aucune trace dans `analyse/` ; le fil parsé s'arrête au 02/09. 2204. **Si les cinq constructions portant `dev-209` ont jamais été servies.** Le dépôt ne dit pas laquelle de `dist/` ou `prebuilt-dist/` est servie. 2215. **Si les six tâches de `config/jobs/` tournent réellement.** Leur forme est celle d'un export (champ `status`, bloc `target` complet, forme de lecture de `isCanonicalJob`), ce qui rend l'hypothèse « elles existent » vraisemblable, non prouvée. 222 223## Contre-lecture — ce qui, dans le dépôt, pourrait contredire mes propres corrections 224 225- **Sur `flexylead-production-pilotage`.** `disableLegacyFunction` (`scripts/deploy-local-application-api.mjs:262-271`) ne s'exécute que si le script de déploiement est lancé — et ce script **refuse toute cible qui ne soit ni locale ni Staging** (`:155-160`). Rien ne prouve donc que la désactivation a eu lieu **en production**. Ma correction reste juste sur « n'est mentionnée nulle part » (fausse, cinq mentions) et sur « 2 406 lignes non déployées » (pas d'instantané), mais la phrase « elle ne tourne pas » mérite le niveau `SUGGÉRÉ` et non `FAIT`. 226- **Sur les 43 tickets.** Mon décompte inclut les instantanés de `backend/` et les sources `.mjs`, pas seulement les tests. Un lecteur qui restreint aux fichiers de test trouvera 26. **Toute publication de ce nombre doit porter sa base.** 227- **Sur « 169 lignes de retard ».** Je qualifie `backend/quality-partner-gateway.mjs` d'ancienne version du même programme ; `analyse/contre-confrontation-2026-09-08.md:110` la qualifie de « fonction sans source » et récuse l'écart de 441 lignes comme artefact d'homonymie. **Je maintiens ma lecture** — mêmes constantes d'ouverture, même table `quality_partner_access`, l'ancienne ne connaissant simplement pas `QUALITY_READ_PERMISSION` ni `criteria_page` — mais je signale le désaccord, car il change la portée de la phrase publiée. 228- **Sur les deux fonctions Twilio.** Si le chemin Twilio de l'agent vocal produit lui aussi des enregistrements notés, alors elles portent bien une partie d'un constat. Je n'ai pas tracé si `process-appel-db-only` traite des appels de provenance Twilio ; je borne donc ma réfutation à : « ce ne sont pas les fonctions du chemin Ringover, qui est celui de nos deux constats ». 229 230--- 231 232## Annexe B — deux citations exactes du `README.md` que le document n'utilise pas, et qui disent mieux ce qu'il veut dire 233 234Le document s'appuie sur *« Git publication is not a production-readiness certification »*, qu'il traduit d'une manière qui ne correspond pas à l'original (§5.1). Deux autres phrases du même fichier disent **exactement** ce que l'audit veut établir, sans aucun besoin d'interprétation : 235 236- `README.md:5` — *« This README describes the source snapshot in this repository, not a live production inventory. See package.json for its version (currently `1.1.20`). **A GitHub commit does not deploy the UI, backend functions or database changes.** »* 237- `README.md:11` — *« `backend/` contains deployed-source snapshots; **these are not the editing source of truth**. The working sources include local changes that are **not necessarily deployed or production-ready**. Deployment still requires explicit target configuration and credentials. »* 238 239**Recommandation : remplacer la troisième citation de la section « Le problème de fond » par `README.md:5`.** Elle est plus courte, littérale, et ne peut pas être retournée. 240 241## Annexe C — précision sur la réfutation du point 5 bis de « Ce que nous n'avons pas pu établir » 242 243Le moteur de notation traite bien des appels de provenance Twilio : `backend/process-appel-db-only.mjs:3124-3126` — *« Certains chemins d'ingestion (ligne IA Twilio notamment) créent l'appel sans `campaign_id` […] on rattrape ici »* (`backfillCampaignAttribution`, `TKT-45`/`TKT-13`). 244 245Conséquence, et c'est une observation neuve : **le moteur reçoit des transcriptions de deux natures différentes.** Celles produites par `flexylead-transcribe-telephony-recording` (chemin Twilio) sont demandées avec `diarize: true` (`backend/flexylead-transcribe-telephony-recording.mjs:207`) ; celles du chemin Ringover ne le sont pas (`grep -c diariz backend/process-appel-db-only.mjs` → **0**). Les onze contrôles par recherche de phrase s'appliquent donc à des textes dont **une partie sait dire qui parle et l'autre non**, sans que rien, dans le moteur, ne distingue les deux. 246 247Cela ne rétablit pas la phrase publiée — les deux fonctions sans source restent celles du chemin Twilio, et les fonctions qui portent nos deux constats ont toutes une source. Mais cela ajoute une question à poser, qui n'est dans aucun de nos documents : **le moteur applique-t-il la même grille à des transcriptions de qualité inégale ?** 248