1# Famille 1 « Les chiffres » (constats 1 à 5) — reprise contre le déployé du 14/09/2026 2 3**Date :** 14/09/2026 · **Agent :** `audit-donnees` · **Régime :** lecture seule, aucun appel réseau, aucune clé, aucune exécution de code applicatif. Écriture dans `analyse/` uniquement. 4 5**Corpus comparé :** 6 7| Repère | Chemin | Commit | Horodatage | 8|---|---|---|---| 9| Nouveau déployé | `/Users/gregorydelaloeuf/Documents/scalewayxflexylead/code/flexylead-crm-2026-09-14/` | `ccbbaa07870e0f786729003dd6f8862e4bcc8256` | 2026-09-14 08:46:08 +0200 | 10| Référence | `/Users/gregorydelaloeuf/Documents/scalewayxflexylead/code/flexylead-crm-0908/` | `730a5e79cbbb4191930f2de2f208f42ba483fb46` | 2026-09-08 13:51:31 +0200 | 11 12**Documents de départ :** `analyse/liste-23-constats-reconstituee-2026-09-13.md` (famille 1 = lignes 39 à 43) · `analyse/preuves-chiffres-2026-09-09.md`. 13 14--- 15 16## 0. Périmètre traité, et ce qui en est exclu 17 18**Traité :** les constats 1 à 5 tels que bornés au 09/09, phrase par phrase, contre l'instantané du 14/09 ; l'étendue du correctif du constat 4 ; la nature des +55/−4 sur le vérificateur d'intégrité ; l'adaptateur d'autorisation ; les scripts `tmp-*`. 19 20**Exclu :** les constats 6 à 23 (autres familles) ; toute mesure de performance réelle (aucune exécution) ; le contenu des tables de production ; les fichiers `flexylead-admin-mcp.mjs`, `flexylead-sync.mjs`, `flexylead-mirror-specific-audios.mjs`, `leadbyte-prospect-dispatcher.mjs`, `process-appel.mjs` — ajoutés à `backend/` par ce commit, hors périmètre famille 1, signalés en §7 pour `audit-agents-ia`, `audit-integrations` et `audit-infra`. 21 22--- 23 24## 1. Réserve de méthode — la convention de lecture a disparu, et elle s'est inversée 25 26C'est le constat qui commande tous les autres. Il doit être lu avant les cinq verdicts. 27 28### 1.1 La phrase qui désignait `backend/` comme le déployé n'existe plus — `FAIT` 29 30Au 08/09, toute la famille 1 reposait sur une phrase du README (`preuves-chiffres-2026-09-09.md:9-10`) : 31 32> `code/flexylead-crm-0908/README.md:11` — « The repository includes the frontend, application manifests/scripts, and working backend sources under `functions/`. **`backend/` contains deployed-source snapshots; these are not the editing source of truth.** » 33 34Cette phrase **n'existe plus**. Le README est passé de 162 à 111 lignes. Un balayage sur `deploy`, `snapshot`, `source of truth`, `editing` dans `code/flexylead-crm-2026-09-14/README.md` ne ramène que trois lignes, toutes sans rapport (`:57`, `:77`, `:100` — cette dernière ne parle que des secrets). Les lignes `:133-139`, `:151`, `:156`, `:160`, `:162` du README du 08/09, qui décrivaient `backend/` comme « historical deployed snapshots », ont toutes disparu. 35 36`CANONICAL_SOURCE.json` est **identique** dans les deux dépôts, octet pour octet, et ne désigne pas `backend/` : il déclare `"deployment_status": "staging-candidate"` et un `canonical_path` local chez le prestataire (`/Users/marcoschwartz/Documents/code/clients/flexylead/partenaires-app`), absent de la livraison. 37 38**Il ne reste donc, pour affirmer que `backend/` est la production, que le message de commit** — « Sync production Code and Functions snapshots ». C'est une **déclaration**, au dernier rang de la hiérarchie de preuve du cadre de travail. 39 40### 1.2 Le message de commit est démenti sur sa moitié « Functions » — `FAIT` 41 42`git diff --name-only 730a5e79 ccbbaa07` : 295 fichiers, répartis en **146 `src/`, 86 `backend/`, 33 `e2e/`, 18 `scripts/`**, plus une douzaine de fichiers de racine. 43 44- **`functions/` : zéro fichier modifié.** `git diff --name-only 730a5e79 ccbbaa07 -- functions` ne renvoie rien. Les 147 entrées de `functions/` sont celles du 08/09. 45- **`dist/` et `prebuilt-dist/` : zéro fichier modifié**, alors que 146 fichiers de `src/` l'ont été. 46 47Un commit intitulé « Sync production Code and **Functions** snapshots » qui ne touche aucun fichier de `functions/` ne peut pas être pris au mot. 48 49### 1.3 L'inversion : `backend/` est désormais **en avance** sur `functions/` — `FAIT` 50 51Au 08/09, la règle était : `functions/` = source de travail postérieure, `backend/` = déployé antérieur, les correctifs sont dans `functions/` et pas dans `backend/`. **Cette règle est renversée sur la famille de pilotage.** 52 53Le déployé `backend/flexylead-app-pilotage-summary-query.mjs` contient un code que la source `functions/flexylead-app-pilotage-query.mjs` **ne contient pas** : 54 55| Dans `backend/` (14/09) | Dans `functions/` | Ligne | 56|---|---|---| 57| `cachedRule` / `buildPilotageProductionUncached` (mémoïsation des règles) | absent | `backend/…summary-query.mjs:670-686` | 58| colonne `outsideCentreReceived` | absente | `:1332, 1424, 1642, 1972, 2019, 2153-2155` | 59| bloc `opportunityAvailability` embarqué (163 lignes) | absent | `:4659-4821` | 60| partitionnement en 8 de `queryReducedPilotageInteractions` | requête unique | `:3531` contre `functions/…:3426` | 61| partitionnement en 4 de `queryCampaignNewUniqueCallers` | pas de partitionnement | `:3786` contre `functions/…:3661` | 62| `meteore_attached_contracts`, `consopilote_attached_contracts` | absents | `:2659-2661` | 63 64Et le signe décisif : `functions/flexylead-app-pilotage-query.mjs:51` porte `const DEPLOYED_RESOURCE = "__FLEXYLEAD_PILOTAGE_RESOURCE__"` — un **gabarit**, non un code exécutable. Les six fichiers de `backend/` portent la valeur substituée (`"summary"`, `"remuneration"`…). `backend/` est donc un **artefact engendré** à partir d'une source qui **n'est pas dans la livraison**. 65 66**Conséquence pour la lecture des cinq constats — `FAIT` :** 67 681. La formule « le correctif existe dans les sources, il n'est pas en service » perd son sens général : sur le pilotage, c'est `functions/` qui est en retard. 692. La livraison ne contient **toujours pas** la source d'édition réelle. Ce qui manquait au 08/09 manque encore, et la preuve en est désormais interne au dépôt. 703. Tout verdict ci-dessous qui dit « corrigé **dans le déployé** » doit se lire : « corrigé dans `backend/`, arbre que le seul message de commit présente comme la production ». Je ne rehausse ce niveau nulle part. 71 72**Hypothèse alternative examinée :** `backend/` ne serait pas le déployé mais une branche d'intégration plus avancée, et `functions/` resterait la référence. Elle n'est **pas écartée** — rien dans le dépôt ne permet de trancher. Elle rendrait les verdicts « corrigé » de cette note caducs. La question à poser à Marco est en §9. 73 74### 1.4 Aucun relevé n'accompagne les 17 ajouts — `FAIT` 75 76Le commit du 08/09 avait livré son propre relevé, `backend/SNAPSHOT-ADDITIONS-20260908.json` (retrait R1 de la liste des 23). Ce fichier est toujours là ; **aucun équivalent daté du 14/09 n'a été produit**. Les 17 fichiers ajoutés à `backend/` arrivent sans inventaire. 77 78--- 79 80## 2. L'adaptateur d'autorisation — ce qu'il touche, ce qu'il ne touche pas 81 82`FAIT` — 63 des 101 entrées de `backend/` portent `requireTrustedPrincipal` / `trustedActor` / `__authParityDenied`. **Zéro occurrence dans `functions/`.** Le bloc fait 49 lignes, identiques d'un fichier à l'autre, en tête de fichier, et porte le commentaire `backend/flexylead-manage-call-routing.mjs:3` — « Business identity is never inferred from event.auth, actor flags or headers. » 83 84**Il touche les fichiers porteurs des cinq constats :** 85 86| Constat | Fichier porteur | Adaptateur | 87|---|---|---| 88| 1 | `flexylead-app-dashboard-query.mjs`, `flexylead-app-acquisition-query.mjs`, famille commerciale | oui | 89| 1 (volet fan-out) | `ringover-call-events.go` | **non** (fichier Go, hors du gabarit) | 90| 2 | les 6 `flexylead-app-pilotage-*`, `flexylead-create-sales.mjs` | oui | 91| 3 | famille commerciale, les 6 pilotage | oui | 92| 4 | les 6 pilotage | oui | 93| 5 | `flexylead-integrity-audit.mjs` | oui | 94 95**Mais il n'est la substance d'aucun d'eux.** Sur `backend/flexylead-app-pilotage-summary-query.mjs`, les +377/−50 se décomposent en 49 lignes d'adaptateur et **~328 lignes de changement métier** (mémoïsation, partitionnement, `outsideCentreReceived`, `opportunityAvailability`). Attribuer les +377/−50 à l'adaptateur serait une erreur de lecture : il n'en explique que 13 %. 96 97Un seul fichier voit son diff **presque entièrement** expliqué par l'adaptateur : `flexylead-integrity-audit.mjs` (+55/−4, dont 49 d'adaptateur — voir §6). 98 99--- 100 101## 3. Constat 1 — « Quatre fonctions comptent les appels reçus, et aucune ne compte comme les autres » 102 103### Verdict : **PARTIELLEMENT CORRIGÉ** — deux des trois correctifs sont passés en service, le cœur du constat est intact, et une cinquième divergence est née. 104 105**1.4 « Trois correctifs existent déjà dans les sources […] Aucun n'est en service » → cette phrase est désormais FAUSSE.** `FAIT` 106 107| Correctif | État au 08/09 | État au 14/09 | Preuve | 108|---|---|---|---| 109| **1** — définition de « manqué » | source seule | **déployé** | `backend/flexylead-app-dashboard-query.mjs:204` définit `missedCallSql`, appliquée `:283, 312, 791, 820` (5 occurrences, contre 0 au 08/09). Elle teste `status` **OU** `issue` **OU** `motif` sur 5 valeurs, là où le déployé du 08/09 s'arrêtait au premier champ non vide (`0908/backend/…:224, 253, 732, 761`) | 110| **2** — colonne du total | source seule | **toujours non déployé** | `backend/…:917-918` : `COALESCE((SELECT unique_received FROM google_acquisition_metrics), 0)`. La source porte `(SELECT COUNT(DISTINCT opportunity_key) FROM scoped)` à `functions/…:866-867`. Le `diff` les oppose directement | 111| **3** — rattachement au bon commercial | source seule | **déployé** | `backend/ringover-call-events.go` et `functions/ringover-call-events/main.go` ont le **même `md5`** (`855fd6fbea58994d2e4c8eb704c573f8`) : le fichier déployé est byte-à-byte la source. `isUnattributedIVRAnswer:625`, `reconcileConfirmedAIHandling:686`, `aiHandlingRepairable:730`, `confirmedAIHandlingFields:735` — 12 occurrences contre 0 au 08/09 | 112 113**Chaîne cause → effet :** Marco déploie deux des trois correctifs ; le tableau de bord change sa définition de « manqué » et le connecteur Ringover se met à réparer l'attribution des appels pris par le serveur vocal. **Chaîne effet → cause :** un chiffre de « manqués » du tableau de bord relevé après le 14/09 ne sera plus comparable à un chiffre relevé avant — la définition a changé sous les pieds de l'utilisateur, sans que rien à l'écran ne le signale. Tout recoupement d'historique franchissant le 14/09 est faux par construction. 114 115**1.1 « Quatre définitions de "manqué" » → INTACT, et l'écart s'est creusé.** `FAIT` 116 117Les quatre définitions sont toujours quatre, et **le correctif n'en a aligné qu'une** : 118 119| Fonction | Définition au 14/09 | Ligne | 120|---|---|---| 121| tableau de bord | `missedCallSql` : 3 champs, 5 valeurs | `backend/flexylead-app-dashboard-query.mjs:204` | 122| famille commerciale | `lower(CAST(status AS TEXT)) IN ('manque','missed')` — 1 champ, 2 valeurs, **sans condition de non-réponse** | `backend/flexylead-app-commercial-metrics-query.mjs:732` | 123| acquisition | `is_new = 1 AND period_answered = 0` — aucun statut lu | `backend/flexylead-app-acquisition-query.mjs:227` | 124| pilotage | **négation** de `('appel_manque','missed','no_answer')` sur `issue`/`motif` | `backend/flexylead-app-pilotage-summary-query.mjs:2797, 3263, 3403` | 125 126`missedCallSql` n'est importée nulle part ailleurs : `grep -rl` ne la trouve que dans `backend/flexylead-app-dashboard-query.mjs`, son homologue source et le test associé. **Le titre substitué du 09/09 reste vrai mot pour mot.** 127 128**NOUVEAU, non couvert par le constat d'origine — une cinquième divergence est née.** `FAIT` 129 130La définition de **« répondu »** a changé dans deux compteurs sur quatre. `confirmedBusinessAnswerSql` exclut désormais les appels pris par le serveur vocal : 131 132> `backend/flexylead-app-dashboard-query.mjs:191` — `lower(COALESCE(CAST(handled_by AS TEXT), '')) != 'ivr'` 133> `backend/flexylead-app-acquisition-query.mjs:62` — même prédicat 134 135Au 08/09, `'ivr'` n'apparaissait **dans aucun des quatre fichiers** (`grep -c "'ivr'"` → 0, 0, 0, 0 ; au 14/09 → 1, 0, 1, 0). La famille commerciale continue de compter « répondu » sur `answered_at` non vide (`:731`), sans exclusion ; le pilotage n'a aucune occurrence. 136 137**Conséquence :** à compter du 14/09, un appel pris par le serveur vocal est retiré du « répondu » du tableau de bord et de l'acquisition, et reste compté comme répondu par les compteurs commerciaux et le pilotage. Le taux de décroché des deux premiers baisse mécaniquement sans qu'aucun appel réel n'ait changé. **Ce point recoupe le constat 16** (« Un appel pris par le serveur vocal est compté comme un appel pris par un commercial ») : la substitution du 09/09 disait « La marque existe en production… Aucun compteur ne la lit ». **Deux compteurs la lisent désormais, deux non.** À transmettre à l'agent qui porte le constat 16. 138 139**1.2 « Un seul appel qui sonne chez trois personnes laisse trois lignes » → INTACT.** `FAIT` 140Le commentaire d'auteur et le `return nil, nil` sont là (`backend/ringover-call-events.go:2119-2123`) ; `suppressMissedSiblingChannelsAfterAnswer:2794-2828` sort toujours les lignes sœurs de la file sans toucher leur `status` ; le filtre `parent_appel_id = ''` du comptage subsiste (`backend/flexylead-app-dashboard-query.mjs:297, 326, 803, 832`). 141 142**1.3 « La colonne "uniques" du total ne compte que les appels venus de la publicité » → INTACT.** `FAIT` 143`backend/flexylead-app-dashboard-query.mjs:917-918` puise toujours dans `google_acquisition_metrics` (CTE `:864-867`, alimentée par `google_candidates:847` et le filtre `mapping_status = 'matched'` `:771`), tandis que les lignes par commercial font `COUNT(DISTINCT opportunity_key)` sur `scoped` (`:912-913`). L'impossibilité structurelle de recoupement est inchangée. **À noter, une ironie documentée :** le commentaire `:884-889` invoque le ticket TKT-90 et « Keep one cohort for both levels » — la cohorte a bien été unifiée, mais **les deux colonnes d'uniques du total continuent d'aller ailleurs**. Le commentaire décrit une intention que les deux lignes suivantes contredisent. 144 145**Hypothèse alternative examinée :** les quatre définitions pourraient viser quatre objets métier différents (appel entrant brut / appel commercial / contact publicitaire nouveau / jambe d'interaction), auquel cas elles ne seraient pas contradictoires mais complémentaires. Elle n'est **pas écartée sur le fond** — c'est même la lecture la plus charitable. Mais elle ne sauve pas le constat contraire : aucune de ces définitions n'est écrite nulle part dans le dépôt, et les quatre chiffres sont affichés à des utilisateurs sous le même mot. C'est l'ambiguïté, pas la fausseté, qui est établie. 146 147--- 148 149## 4. Constat 2 — « Deux compteurs justes portent le même nom » 150 151### Verdict : **INTACT.** Aucune des quatre phrases n'est tombée. 152 153- **2.1** `reportSaleRevenue` suit la règle en vigueur à la date : `backend/flexylead-app-pilotage-remuneration-query.mjs:2278-2281`, précédé du commentaire TKT-116 inchangé `:2274-2277`. `remunerationRuleFor` filtre par `effectiveOn(rule, date)` `:2190-2203` ; `remunerationRuleAmount` recalcule `:2206-2212`. `FAIT` 154- **2.2** La vente garde le montant figé : `backend/flexylead-create-sales.mjs:749-755` écrit toujours `revenue_cents_snapshot`, `prime_montant`, `prime_devise`, `prime_rule_id` ; `resolveRemunerationSnapshot:787-813` choisit la règle au moment de la création. `FAIT` 155- **2.3** Le test qui exige la divergence n'est **toujours** que dans la source : `functions/flexylead-app-pilotage-query.test.mjs:2489` et `:2516` (`assert.notEqual`). `ls backend/*.test.mjs` → **zéro fichier**. Le comportement testé, lui, est bien en service (`:2278-2281`). La substitution du 09/09 tient telle quelle. `FAIT` 156- **2.4** La substitution du 09/09 — « les deux chiffres puisent dans la même case, `prime_montant` » — tient, et **se renforce**. `FAIT` 157 158Sur ce dernier point, j'applique la discipline de la troisième copie. `prime_montant` existe en **trois exemplaires** dans la chaîne : 159 1601. sur la **ligne de vente**, figé à la création (`flexylead-create-sales.mjs:753`) ; 1612. dans la **table de règles** `offer_remuneration_rules`, lu pour recalculer (`…remuneration-query.mjs:2209`) ; 1623. dans un **agrégat SQL**, `SUM(prime_montant) AS bonus_total` (`:4000`), consommé par `row.bonusCost += aggregateSale ? nonNegative(sale.bonus_total) : nonNegative(sale.prime_montant)` (`:843`). 163 164Et la même case sert de repli au **tarif du centre** quand aucune règle ne s'applique : `fixedCentreRemuneration` retombe sur `nonNegative(entry.sale.prime_montant)` (`:1081-1085`). Prime du commercial et rémunération du centre partagent donc la même colonne, dans trois représentations. **Rayon d'impact :** toute correction portée sur l'une des trois n'atteindra pas les deux autres. 165 166**Un seul mouvement à l'écran, et il ne répond pas au constat.** Une phrase nouvelle est apparue : `src/routes/PilotageProductionRoute.tsx:1114` — « Certaines ventes ne disposent ni d'une règle contractuelle applicable ni d'un instantané historique de rémunération. » C'est un avertissement d'**absence des deux**, pas une indication de **laquelle des deux** a servi au chiffre affiché. « Rien, à l'écran, ne dit lequel on regarde » reste vrai. Et cette phrase est dans `src/`, dont §5 établit qu'il n'est pas dans les bundles livrés. 167 168**Hypothèse alternative examinée :** le nouvel avertissement pourrait être le début du correctif attendu. Écartée pour ce constat : il se déclenche sur `remunerationIncomplete`, c'est-à-dire quand **aucune** des deux sources n'existe — exactement le cas où la question « laquelle des deux ? » ne se pose pas. 169 170--- 171 172## 5. Constat 3 — « Onze tables facultatives, et le serveur qui fait le même geste » 173 174### Verdict : **PARTIELLEMENT CORRIGÉ** — un masquage serveur sur trois est passé en service ; la moitié écran est corrigée en source mais **absente des bundles livrés**. 175 176**3.1 — moitié écran : corrigé dans `src/`, non démontrable en service.** `FAIT` 177 178L'ensemble des onze tables « facultatives » a **disparu**. Au 08/09 : `code/flexylead-crm-0908/src/dashboard/api/client.ts:1007-1023` — `const optional = new Set<StateTable>([…])` de onze entrées, puis `return optional.has(table) ? promise.catch(() => []) : promise;`. Au 14/09, `grep -n "const optional = new Set"` sur `src/dashboard/api/client.ts` → **aucun résultat**. `loadFullState` subsiste (`:1020`) et porte désormais le commentaire inverse : 179 180> `src/dashboard/api/client.ts:1034-1035` — « A failed requested dependency is not an empty business table. Preserve the previous successful Query result and surface its refresh error. » 181 182**Mais ce correctif n'est pas démontrable comme étant en service.** `git diff --numstat 730a5e79 ccbbaa07 -- dist prebuilt-dist` → **zéro ligne**, alors que 146 fichiers de `src/` ont changé (dont `client.ts`, +325/−491, et `PilotageProductionRoute.tsx`, +211/−77). Contrôle indépendant : le texte neuf « Rémunération à qualifier » est présent **1 fois dans `src/`** et **0 fois dans `dist/` et `prebuilt-dist/`**, alors que le texte ancien « CA incomplet » est présent dans **12 fichiers de bundle**. Les bundles livrés sont antérieurs à `src/`. 183 184**3.2 — serveur, famille commerciale : INTACT.** `FAIT` 185`grep -c "\.catch("` sur `backend/flexylead-app-commercial-metrics-query.mjs` → **17**, exactement comme au 08/09. La substitution du 09/09 (« seize conversions d'erreur en résultat vide » dans la fonction unique dont sont tirées les 23 fonctions commerciales) reste vérifiable au même chiffre. 186 187**3.4 — le correctif partiel est passé en service, à l'identique de ce qui était annoncé.** `FAIT` 188Le `throw` qui remplaçait le masquage de la file d'attente est désormais dans le déployé : 189 190> `backend/flexylead-app-commercial-missed-calls-query.mjs:337` — `if (!centreId || !aliases.length) throw new Error('Commercial queue scope missing');` 191 192(zéro occurrence dans le déployé du 08/09). Et les masquages vides de ce fichier passent de **3 à 2** (`:465` et `:470` subsistent, tous deux dans `salesResource`). C'est **exactement** la borne posée le 09/09 : « Le correctif ne couvre qu'un des trois masquages de cette fonction. » La phrase reste vraie, mais son sujet change : ce n'est plus « un correctif existe dans les sources et la production fait l'ancien geste », c'est « le correctif est en service et il est partiel ». 193 194**3.3 — les fonctions de pilotage refusent toujours de masquer : INTACT, et pas d'aggravation.** `FAIT` 195`callTables` relance l'erreur (`backend/flexylead-app-pilotage-summary-query.mjs:4518-4540`, `throw error` `:4533`). Les tolérances au vide restent au **même nombre qu'au 08/09** : `grep -c 'unresolved \{placeholder\}'` → **9** dans les deux instantanés. J'ai spécifiquement testé l'hypothèse d'un élargissement des motifs tolérés (`no such function: json_object`, `compound select`) : **ils existaient déjà au 08/09** (`0908/backend/…:595` et `:3015`, contre `:667` et `:3114` au 14/09). **Ce n'est pas une aggravation** — je le note parce que le décalage des numéros de ligne pouvait le faire croire. 196 197**Hypothèse alternative examinée :** `dist/` et `prebuilt-dist/` pourraient être engendrés au déploiement, et leur non-modification serait alors sans portée. Elle n'est **pas écartée** — mais le README du 08/09 (`:31`) les décrivait comme « Generated build artifacts **included in the imported snapshot** », c'est-à-dire livrés, pas reconstruits. Dans le doute, je maintiens le verdict à « partiellement corrigé » et ne crédite pas la moitié écran. 198 199--- 200 201## 6. Constat 4 — « Quand une requête est trop lente, la production la relance — jusqu'à douze fois » 202 203### Verdict : **PARTIELLEMENT CORRIGÉ sur un levier, AGGRAVÉ sur l'autre.** 204 205**C'est le point sur lequel je m'écarte de ce qui m'a été transmis comme établi.** Le constat que `queryPartitionedPilotageInteractions` (2 occurrences au 08/09) a cédé la place à `queryReducedPilotageInteractions` (2 occurrences au 14/09) est exact. **Mais le changement de nom ne s'accompagne pas du changement de comportement que `preuves-chiffres:46` décrivait.** 206 207### 6.1 Levier 1 — la reprise sur dépassement d'exécution : **CORRIGÉ, et sur les six fonctions** `FAIT` 208 209Le motif de reprise ne contient plus la branche nue `context deadline exceeded` : 210 211> `backend/flexylead-app-pilotage-summary-query.mjs:4532` — `if (attempt >= TRANSIENT_READ_RETRIES || !/read_queue.*deadline exceeded|deadline exceeded.*read_queue/i.test(...))` 212 213Le commentaire d'auteur est déployé, `:4530-4531` — « An execution deadline is not an admission/queue timeout. Re-running the same expensive SELECT multiplies load without changing its plan. » 214 215**Vérifié sur les six fonctions de pilotage, pas une seule** (réponse à la question posée) : `flexylead-app-pilotage-{summary,trend,campaigns,commercials,operational,remuneration}-query.mjs` portent toutes `TRANSIENT_READ_RETRIES = 2` en `:155` et la condition corrigée en `:4532`. Les six restent des clones : `diff` entre `summary` et `remuneration` → **une seule ligne**, `const DEPLOYED_RESOURCE` (`:51`). Le correctif est donc uniforme par construction, pas par diligence. 216 217### 6.2 Levier 2 — le découpage en quatre : **NON corrigé. Il a doublé.** `FAIT` 218 219`preuves-chiffres:46` décrivait le second levier ainsi : « `queryPartitionedPilotageInteractions` cède la place à `queryReducedPilotageInteractions`, **une seule requête**. Résultat : 1 exécution au pire cas contre 12. » 220 221La fonction déployée sous ce nom **partitionne en huit**, séquentiellement : 222 223> `backend/flexylead-app-pilotage-summary-query.mjs:3531` — `for (let partition = 0; partition < 8; partition++) {` 224> `:3525-3527` — « Partition by the logical call group, never by date/centre […] Sequential reads bound per-request database pressure and the result-size cap. » 225 226La version **source**, elle, fait bien une requête unique : `functions/flexylead-app-pilotage-query.mjs:3426` — `return callTables(context, "tables_query", {...input, sql});`. Les deux fonctions portent le même nom et font deux choses différentes. 227 228**Et une seconde boucle de partition est apparue dans le déployé**, absente de la source : `queryCampaignNewUniqueCallers` (`backend/…:3782-3796`) itère sur **4 partitions** via `queryCampaignCallerPartition`, et elle est appelée à chaque chargement (`:252`). La source ne partitionne pas (`functions/…:3661`). 229 230**Arithmétique, dans la même unité que celle du document :** 231 232| | 08/09 | 14/09 | 233|---|---|---| 234| morceaux, interactions de pilotage | 4 | **8** | 235| morceaux, appelants de campagne | 1 | **4** | 236| reprises par morceau | 3 (1 + 2) | 3 (1 + 2) | 237| pire cas déclenché par un **dépassement d'exécution** | **12** | **0** (la reprise ne s'arme plus) | 238| pire cas déclenché par une **attente en file** | 12 | **24** + 12 | 239 240**Chaîne cause → effet :** le titre « la production la relance — jusqu'à douze fois » n'est plus vrai **pour la cause qu'il désigne** (une requête trop lente). La chaîne causale de 4.5 — « le dépassement de délai est aussi une cause de la surcharge » — est **coupée** : une requête qui dépasse son délai d'exécution n'est plus rejouée. 241 242**Chaîne effet → cause :** mais le nombre de requêtes émises par chargement de pilotage a **doublé, puis quadruplé** sur un second axe. Si l'on remonte d'un dépassement de délai observé après le 14/09 vers sa cause, on ne trouve plus une relance, on trouve douze lectures séquentielles là où il y en avait cinq. La pression déplacée, pas supprimée. 243 244### 6.3 Deux commentaires d'auteur qui se contredisent — `FAIT`, arbitrage impossible ici 245 246- Déployé, `:3515-3516` : « **One unpartitioned compact response exceeded** Tables' 4 MiB result guard on the populated month. » 247- Source, `functions/…:3427-3429` : « Group legs, deduplicate callers across the WHOLE period, then aggregate dimensions in SQL. Transferring every compact interaction first exceeded Tables' 4 MiB result guard on a 53k-call month (**4.78 MB vs 1.43 MB grouped**). » 248 249Le premier dit que la requête unique dépasse le plafond. Le second dit qu'elle tient, à 1,43 Mo. **Les deux ne peuvent pas être vrais du même code.** Deux lectures possibles : le déployé a rencontré un volume que la source n'avait pas mesuré (et le partitionnement en 8 est alors une correction légitime, non une régression) ; ou le partitionnement en 8 est un contournement conservé après que la requête unique l'a rendu inutile. **Je ne tranche pas** — cela exigerait une mesure de production, dont nous n'avons aucune. 250 251**Verdict consolidé :** le titre du constat doit être **resubstitué**. Proposition, à soumettre au contradicteur : « Une requête de pilotage trop lente n'est plus rejouée — mais elle est désormais découpée en huit, et une seconde requête l'est en quatre. » Niveau : `FAIT` pour les chiffres, `SUGGÉRÉ` pour l'effet net sur la charge (aucune mesure). 252 253--- 254 255## 7. Constat 5 — « Un vérificateur que rien ne déclenche » 256 257### Verdict : **INTACT.** Pas une phrase n'a bougé. Le diff observé est de l'autorisation, pas du contrôle. 258 259**Ce que sont les +55/−4** (réponse directe à la question posée) — `FAIT`. Le `diff` entre les deux instantanés de `backend/flexylead-integrity-audit.mjs` (123 → 174 lignes) se décompose intégralement en **quatre blocs, dont aucun ne touche un contrôle** : 260 2611. **49 lignes** : l'adaptateur d'autorisation, en tête de fichier (`:1-48`) — `__authParityEvent`, `requireTrustedPrincipal`, `trustedActor`, `__authParityDenied`. Le même bloc que dans 62 autres fichiers. 2622. **2 lignes** (`:54-55`) : l'injection du contexte vérifié, `context = { ...context, [__authParityEvent]: event };`. 2633. **1 ligne** (`:58`) : `assertAuditor(await trustedActor(event, context, body?.actor, { required: true }));` remplace `assertAuditor(body?.actor);`. 2644. **3 lignes** (`:164, 168, 170`) : reformatage de guillemets simples en guillemets doubles. 265 266**Effet de bord à consigner, sans jugement :** la condition `if (!id || actor.gateway_verified !== true)` de `assertAuditor` est devenue `if (!id)` (`:168`). Le contrôle n'a pas été supprimé, il a été **déplacé en amont** dans `trustedActor`, qui est plus strict (il rappelle le service d'authentification et compare l'identité revendiquée à l'identité vérifiée). Net : renforcement, pas affaiblissement. Je le signale parce que lu isolément, ce retrait ressemble à une régression. 267 268**Les quatre phrases du constat, une à une :** 269 270- **5.1** Les cinq contrôles sont là, aux mêmes noms : `duplicate_active_opportunities` (`:60`), `callback_reservations_without_call` (`:70`), `calls_with_broken_reservation_link` (`:95`), `failed_qualification_commands` (`:115`), `failed_followup_commands` (`:123`). `FAIT` 271- **5.2** (borné le 09/09) **INTACT dans les deux moitiés** : les cinq `LIMIT 100` sont en place (`:68, 93, 113, 121, 129`), et `runCheck` (`:148-161`) renvoie toujours `{ name, available: false, count: 0, rows: [] }` en cas d'échec (`:160`). `issue_count` (`:141`) somme des comptes écrêtés et compte zéro pour tout contrôle en échec. `FAIT` 272- **5.3 « Aucun écran ne le lance » INTACT.** `grep -rIn "integrity\.audit|integrity_audit|integrityAudit"` sur tout le dépôt → **10 résultats**, tous dans `backend/flexylead-app-command-dispatch.mjs:23`, `functions/flexylead-app-command-dispatch.mjs:8` et les deux scripts de 5.4. **Aucun** sous `src/`, `dist/`, `prebuilt-dist/`, `config/`, `e2e/`. La fonction reste routée au manifeste (`scripts/application-api-manifest.mjs:50`). `FAIT` 273- **5.4 INTACT.** Les deux sondes demandent toujours `limit: 25` (`scripts/verify-production-reliability-contracts-20260824.mjs:39`, `scripts/verify-staging-action-contracts.mjs:24`). `verify-production-reliability-contracts-20260824.mjs` n'a reçu **aucune** modification (`diff` vide). `verify-staging-action-contracts.mjs` a reçu un sélecteur de sondes par variable d'environnement (`:44-48`) — sans rapport avec ce que la sonde vérifie. `FAIT` 274 275**Correction à porter sur ce qui m'a été transmis — `FAIT`.** Il n'y a **pas** de septième tâche. `ls config/jobs/` donne **six** entrées dans les **deux** dépôts, et ce sont les mêmes six. `ringover-presence-refresh.json` n'est pas apparue : elle figurait déjà dans les six du 08/09, et `preuves-chiffres-2026-09-09.md:50` la nomme explicitement dans sa liste. `git diff --name-only 730a5e79 ccbbaa07 -- config` → **aucun fichier**. Aucune tâche ne vise le vérificateur, ni avant ni après. 276 277**Hypothèse alternative examinée :** le vérificateur pourrait être déclenché par un ordonnanceur externe à ces trois dépôts (plateforme Apteva, cron d'hébergeur). Elle n'est **pas écartée** — aucune de nos sources ne couvre l'ordonnancement côté plateforme. C'est une question à poser, pas un constat à durcir. 278 279--- 280 281## 8. Les scripts `tmp-*` et les autres ajouts à `backend/` 282 283`FAIT` — le commit dépose **17 fichiers neufs** dans `backend/`, dont **huit** (et non sept) portant le préfixe `tmp-` : 284 285`tmp-azs-drive-query.mjs` (565 o) · `tmp-mgk-header-raw.mjs` (433 o) · `tmp-mgk-mint-full-count.mjs` (597 o) · `tmp-mgk-sheet-inspect.mjs` (964 o) · `tmp-reda-april-contract-compare.mjs` (3 860 o) · `tmp-reda-april-inspect.mjs` (2 882 o) · `tmp-reda-april-phone-match.mjs` (1 733 o) · `tmp-reda-sheet-inspect.mjs` (1 420 o). 286 287**Ce qu'ils font, factuellement :** 288 289- **Lecture seule.** `grep -l "rows_insert|rows_update|rows_delete"` sur les huit → **aucun**. Ils n'écrivent pas en base. 290- Ils lisent des feuilles Google : 7 appels `context.integration('google-sheets','batch_get')` et 3 `get_spreadsheet`. 291- **Deux identifiants de classeurs Google sont en dur** dans le code déposé : `10eom2UU6NGh77ykV7YmfHGFcM29BZEEkIMcHcj7n5v4` et `1NqcfK6ee6PjeA7Xxot2B58_GxDXed3KdouQ5IFL6sro`. Ce sont des identifiants de ressource, pas des secrets d'authentification. 292- **Aucun n'est déclaré au manifeste** (`scripts/application-api-manifest.mjs`) : ils ne sont ni routés ni appelables par la passerelle. 293 294**Ce que cela signifie pour la famille 1 — `SUGGÉRÉ`.** `tmp-reda-april-contract-compare.mjs` rapproche les numéros de contrat d'avril 2026 entre trois onglets d'un classeur (« Réponses au formulaire 1 », « Suivi Ventes BOX », « Suivi Ventes MOBILES ») et en calcule les écarts (`function diff(a,b)`, `function uniq(arr)`). `tmp-mgk-mint-full-count.mjs` produit un décompte complet. Ce sont des **recoupements de comptage effectués à la main, hors du logiciel**, sur la période d'avril. C'est un indice — non une preuve — que les chiffres du CRM sont vérifiés par confrontation à des feuilles Google plutôt que par le CRM lui-même. **Cela conforte le constat 1 par un chemin indépendant du code** : si les compteurs se recoupaient, ces scripts n'auraient pas lieu d'être. Je maintiens `SUGGÉRÉ` : je n'ai ni la date d'écriture réelle, ni le motif, ni le résultat de ces scripts. 295 296**Pour les autres agents, hors périmètre famille 1 :** neuf autres fichiers arrivent dans `backend/`, dont quatre sont absents de `functions/` (`flexylead-debug-list-drive-folder-reda.mjs`, `flexylead-mirror-specific-audios.mjs`, `flexylead-sync.mjs`, `process-appel.mjs`) et **deux écrivent en base** (`flexylead-mirror-specific-audios.mjs`, `flexylead-sync.mjs` — 2 439 lignes). `flexylead-admin-mcp.mjs` (1 497 lignes) relève de `audit-agents-ia`. Aucun de ces neuf n'est déclaré au manifeste sous son propre nom. 297 298--- 299 300## 9. Tableau de synthèse 301 302| n° | Constat (titre au 09/09) | Verdict au 14/09 | Certitude | Source principale | 303|---|---|---|---|---| 304| **1** | « Quatre fonctions comptent les appels reçus, et aucune ne compte comme les autres » | **PARTIELLEMENT CORRIGÉ** — 2 correctifs sur 3 en service ; les 4 définitions divergent toujours ; **une 5ᵉ divergence est née** sur « répondu » | `FAIT` | `backend/flexylead-app-dashboard-query.mjs:191, 204, 917-918` · `md5` de `ringover-call-events.go` | 305| **2** | « Deux compteurs justes portent le même nom » | **INTACT** — et la troisième copie de `prime_montant` est identifiée | `FAIT` | `backend/flexylead-app-pilotage-remuneration-query.mjs:843, 1081-1085, 2267, 2278, 4000` | 306| **3** | « Onze tables facultatives » (+ le geste serveur) | **PARTIELLEMENT CORRIGÉ** — 1 masquage serveur sur 3 en service ; moitié écran corrigée dans `src/` mais **absente des bundles** | `FAIT` | `backend/…missed-calls-query.mjs:337, 465, 470` · `src/dashboard/api/client.ts:1020, 1034-1035` · `numstat` sur `dist/` | 307| **4** | « Quand une requête est trop lente, la production la relance — jusqu'à douze fois » | **PARTIELLEMENT CORRIGÉ / AGGRAVÉ** — reprise corrigée sur les 6 fonctions ; découpage passé de 4 à 8, plus une 2ᵉ boucle de 4 | `FAIT` (chiffres) · `SUGGÉRÉ` (effet net sur la charge) | `backend/…pilotage-summary-query.mjs:3531, 3786, 4530-4532` contre `functions/…:3426, 3661` | 308| **5** | « Un vérificateur que rien ne déclenche » | **INTACT** — les +55/−4 sont 49 lignes d'adaptateur + 6 lignes d'appel et de forme | `FAIT` | `backend/flexylead-integrity-audit.mjs:1-48, 58, 68-129, 148-161` · `ls config/jobs/` | 309 310**Aucun des cinq n'est corrigé au sens plein.** Deux sont intacts, trois partiellement corrigés, et l'un des trois est aggravé sur son second levier. 311 312--- 313 314## 10. Ce que je n'ai pas pu établir, et pourquoi 315 3161. **Si `backend/` est réellement la production.** La seule pièce qui l'affirmait a été retirée du README, et la seule qui subsiste est un message de commit démenti sur sa moitié « Functions ». Tous mes verdicts « corrigé » en héritent. 3172. **Quelle est la source d'édition du pilotage.** `backend/` contient du code absent de `functions/` ; `functions/` contient un gabarit non substitué (`__FLEXYLEAD_PILOTAGE_RESOURCE__`). L'arbre d'où sort `backend/` n'est dans aucun des trois dépôts. 3183. **Si le partitionnement en 8 améliore ou dégrade la charge.** Deux commentaires d'auteur s'opposent sur le même fait mesuré (4 Mio dépassés / 1,43 Mo groupé). Aucune mesure de production, aucun journal. 3194. **Si les bundles `dist/` sont reconstruits au déploiement.** S'ils le sont, la moitié écran du constat 3 est corrigée en service ; sinon non. Question de procédure de déploiement, pas de code. 3205. **Si le vérificateur d'intégrité est déclenché hors dépôt** (ordonnanceur de la plateforme Apteva ou de l'hébergeur). Hors de nos trois dépôts. 3216. **La date et le motif réels des scripts `tmp-*`.** Leur horodatage de fichier est celui du clonage, pas de l'écriture. 3227. **Les chiffres affichés.** Aucun n'a été reproduit : phase 1, aucune exécution contre des données réelles. 323 324**Questions à poser à Marco, formulées pour être répondues par un fait :** 325 326- `backend/` est-il l'instantané de ce qui tourne, ou une branche d'intégration ? Quel arbre a engendré `backend/flexylead-app-pilotage-summary-query.mjs` du 14/09 ? 327- `dist/` et `prebuilt-dist/` sont-ils reconstruits au déploiement, ou déployés tels que livrés ? 328- `queryReducedPilotageInteractions` : la version en huit partitions ou la version à requête unique tourne-t-elle en production ? 329- Qu'est-ce qui déclenche `flexylead-integrity-audit` en production, si quoi que ce soit ? 330- Les huit scripts `tmp-*` ont-ils été exécutés contre la production, et quand ? 331 332--- 333 334## 11. Contre-lecture — ce qui, dans le corpus, pourrait contredire mes conclusions 335 336Je me relis à l'envers, comme l'impose la charte. 337 3381. **Mon verdict le plus fragile est celui du constat 4.** Je dis « aggravé sur le second levier » en m'appuyant sur un compte de partitions. Si le commentaire déployé (`:3515-3516`) dit vrai — la requête unique dépasse le plafond de résultat — alors le partitionnement en 8 n'est pas une régression mais la seule forme qui fonctionne, et mon « aggravé » devient injuste. **Je ne peux pas trancher.** C'est pourquoi le verdict est double (`FAIT` sur les chiffres, `SUGGÉRÉ` sur l'effet) et pourquoi je propose une resubstitution de titre plutôt qu'une condamnation. 339 3402. **Mon « partiellement corrigé » sur le constat 3 repose sur l'absence de modification de `dist/`.** Si les bundles sont reconstruits au déploiement — ce qui est la pratique courante — alors la correction de `loadFullState` est en service et mon verdict est trop sévère d'un demi-cran. Le README du 08/09 (`:31`) me donne raison en les décrivant comme « included in the imported snapshot », mais ce README a été réécrit et je viens de démontrer qu'il ne fait plus foi. **Je m'appuie donc sur une source que je disqualifie par ailleurs** — je le signale plutôt que de le masquer. 341 3423. **La cinquième divergence sur « répondu » (`handled_by != 'ivr'`) pourrait être une correction, pas un défaut.** C'est même la lecture la plus probable : exclure du décroché commercial un appel pris par le serveur vocal est juste. Ce que je constate n'est pas que c'est faux, c'est que **deux compteurs sur quatre l'ont fait et deux non** — et que ce genre de correction partielle est précisément le mécanisme qui a produit les quatre définitions d'origine. Le défaut est la partialité, pas le geste. 343 3444. **Le constat 5 « intact » pourrait être un faux négatif.** J'ai cherché un déclencheur par `grep` sur les noms connus (`integrity.audit`, `integrity_audit`, `integrityAudit`). Un déclencheur passant par le nom de fonction (`flexylead-integrity-audit`) depuis une configuration de plateforme hors dépôt m'échapperait entièrement. Mon « aucun écran ne le lance » est solide ; mon « rien ne le déclenche » est borné aux trois dépôts. 345 3465. **Enfin, la réserve qui les enveloppe tous.** J'ai comparé deux arbres `backend/`. Si aucun des deux n'est la production, cette note compare deux instantanés d'archive et ne dit rien de ce que les utilisateurs voient. **Elle resterait vraie sur les faits de code et fausse sur ses verdicts.** C'est la conséquence directe de la disparition de la phrase du README, et c'est la raison pour laquelle elle ouvre ce document plutôt que de le conclure. 347