Preuve · rapport

contre-familles-2026-09-14.md

Rapport de vérification de l'audit, texte brut · sur GitHub (dépôt privé, accès requis)

385 lignes · ← retour à l'audit · catalogue des preuves

1# Contre-audit des quatre rendus du 14/09/2026
2
3**Poste :** `audit-contradicteur` · **Date :** 14/09/2026 · **Régime :** lecture seule, aucun appel réseau, aucune clé, aucune exécution de code applicatif. Écriture dans `analyse/` uniquement.
4
5**Cibles attaquées :** `famille1-chiffres-vs-deploye-2026-09-14.md` · `famille2-argent-vs-deploye-2026-09-14.md` · `famille3-notation-vs-deploye-2026-09-14.md` · `famille4-travail-vs-deploye-2026-09-14.md`.
6
7**Corpus :** `code/flexylead-crm-2026-09-14/` (`ccbbaa07`) · `code/flexylead-crm-0908/` (`730a5e79`) · `analyse/liste-23-constats-reconstituee-2026-09-13.md` · les cinq `preuves-*-2026-09-09.md` · `analyse/contre-contrat-2026-09-09.md`.
8
9Toutes les mesures ci-dessous ont été refaites par moi, sur les deux arbres, sans reprendre aucun chiffre des quatre rapports.
10
11---
12
13## 0. La question centrale, tranchée
14
15> **Un verdict « corrigé » est-il prononçable sur ce corpus ?**
16
17**Oui — mais seulement avec son étiquette de portée, et pour une raison qu'aucun des quatre rapports n'a trouvée.**
18
19Les quatre rendus convergent sur une même phrase : « il ne reste, pour affirmer que `backend/` est la production, que le message de commit — une déclaration, au dernier rang de la hiérarchie de preuve ». **Cette phrase est fausse, et elle prive l'audit de son meilleur instrument.**
20
21### 0.1 Le corpus contient une attestation de production vérifiable par hachage — `FAIT`
22
23`backend/SNAPSHOT-ADDITIONS-20260908.json` n'est pas un simple relevé. Il porte, pour chacune des dix fonctions versées le 08/09, un triplet `name` / `id` / `active_version_id` / **`sha256`** — c'est-à-dire l'empreinte de la version **active sur la plateforme** au moment de l'export (`"captured_at": "2026-09-08T11:50:46.663Z"`, `"note": "Additive export only. Existing 73 snapshots retain their original capture state."`).
24
25J'ai recalculé les dix empreintes contre les fichiers réels, dans les deux arbres :
26
27| Fichier de `backend/` | sha256 au **08/09** | sha256 au **14/09** |
28|---|---|---|
29| `flexylead-update-sale.mjs` | **conforme** | non conforme |
30| `flexylead-manage-stats-periods.mjs` | **conforme** | non conforme |
31| `flexylead-manage-call-routing.mjs` | **conforme** | non conforme |
32| `flexylead-manage-work-routing.mjs` | **conforme** | non conforme |
33| `flexylead-manage-offer-catalog.mjs` | **conforme** | non conforme |
34| `flexylead-telephony-control.mjs` | **conforme** | non conforme |
35| `flexylead-unassign-lead.mjs` | **conforme** | non conforme |
36| `flexylead-claim-ai-reservation.mjs` | **conforme** | non conforme |
37| `commercial-supervision-session.mjs` | **conforme** | non conforme |
38| `flexylead-api-gateway-bootstrap.mjs` | **conforme** | **conforme** |
39
40**10 sur 10 au 08/09. 1 sur 10 au 14/09.**
41
42Trois conséquences, et elles commandent tout le reste :
43
441. **Le socle du 08/09 n'était pas une déclaration : c'était une attestation, et elle est juste.** `backend/` au 08/09 **était** un tirage de la production, vérifiable sans Marco et sans réseau. La garantie que les quatre rapports croient perdue avec la phrase du README n'a jamais reposé sur le README.
452. **Le commit du 14/09 a modifié neuf fichiers attestés sans émettre de nouvelle attestation.** Le contenu du 14/09 n'est couvert par aucun certificat. C'est cela, le vrai défaut de provenance — pas la réécriture d'un README.
463. **Le motif de divergence n'est pas aléatoire, et il affaiblit la thèse de la recopie.** Les neuf fichiers qui ne correspondent plus portent tous l'adaptateur d'autorisation (`requireTrustedPrincipal` = 2 occurrences chacun) ; le seul qui correspond encore, `flexylead-api-gateway-bootstrap.mjs`, **ne le porte pas** (0 occurrence). Leurs diffs vont de +52/−1 à +108/−14, soit l'ordre de grandeur des 49 lignes d'adaptateur plus quelques lignes d'appel.
47
48### 0.2 Arbitrage de l'hypothèse « recopie des sources locales par-dessus l'instantané » — **AFFAIBLIE, non écartée**
49
50C'est l'hypothèse que `audit-routage` pose (§ 4) et que `audit-argent` étaye (C2, via `flexylead-admin-mcp.mjs` byte-à-byte identique à la source du 08/09). Elle résiste mal à trois faits mesurés :
51
52- **`functions/` ne contient pas l'adaptateur** : `requireTrustedPrincipal` → **0 fichier sur 147**. Une recopie de `functions/` par-dessus `backend/` aurait **retiré** l'adaptateur, pas ajouté. Or 63 fichiers de `backend/` le portent.
53- **La recopie aurait dû être aveugle.** Elle a épargné exactement le fichier qui n'avait pas besoin de l'adaptateur, et modifié exactement les neuf qui l'ont reçu.
54- **Le chantier d'autorisation a sa contrepartie côté déploiement, et elle est neuve** : `scripts/application-trusted-invocations.mjs` (+29/−0, fichier neuf) installe les routes `auth_jwt` et la politique `authenticated_callers`, et `scripts/deploy-local-application-api.mjs` bouge de +90/−58. Un chantier d'autorisation réel, outillé, a eu lieu entre les deux dates.
55
56**Lecture la plus économe : la production a réellement changé entre le 08 et le 14/09 (l'adaptateur y a été déployé), et l'instantané a suivi — sans que personne ne réémette le relevé qui le prouverait.** L'anomalie de `flexylead-admin-mcp.mjs` relevée par `audit-argent` reste entière et non expliquée ; elle ne suffit pas à qualifier tout le répertoire.
57
58**Donc : « corrigé » est prononçable, à la condition stricte de porter son étiquette de portée.** J'introduis trois marques, à reprendre dans tout livrable :
59
60| Marque | Sens |
61|---|---|
62| **[A]** | établi sur le socle **attesté** du 08/09 (empreinte vérifiée) |
63| **[I]** | établi sur l'**instantané** `backend/` du 14/09, **non attesté** |
64| **[S]** | établi sur `src/`, **absent des bundles livrés** (`dist/` et `prebuilt-dist/` : `diff -rq` → zéro différence) |
65
66Un « CORRIGÉ » sans marque est retiré. Un « CORRIGÉ [I] » est publiable.
67
68### 0.3 Trois sur-lectures partagées par les quatre rapports, à borner — `FAIT`
69
70**(a) « Le dépôt se déclare candidat au staging ».** `CANONICAL_SOURCE.json` porte bien `"deployment_status": "staging-candidate"`. Mais il est **byte-à-byte identique** entre les deux livraisons **et identique sur la branche `origin/apteva/local-before-connect`** (07/09 14:47, auteur « Apteva Code »), branche dont l'arbre **ne contient ni `backend/` ni `functions/`**. C'est un fichier de métadonnées **antérieur à l'arrivée des instantanés** ; il ne qualifie pas `backend/`, il n'a jamais été mis à jour. Et le dépôt nomme par ailleurs un domaine de **production** : `playwright.config.ts:18` — `productionSmoke ? 'https://partenaires.flexylead.com' : …`, repris par `src/components/AppReleaseGuard.test.ts:18-20`, avec dans `e2e/` des fichiers `production-outbound-centres.spec.ts` et `production-pilotage-formula-fix.spec.ts` à côté des `staging-*.spec.ts`.
71→ **À reformuler** : « un fichier de métadonnées non mis à jour depuis avant l'arrivée de `backend/` porte `staging-candidate` ; le dépôt nomme par ailleurs un domaine de production et exécute des sondes contre lui ».
72
73**(b) « Le message de commit est une déclaration de Marco ».** Tous les commits, **y compris `ccbbaa07`**, sont écrits **et** validés par `jeremy-flexylead <j.normand@flexylead.com>`. Aucun n'est signé Marco. La chaîne d'attribution vers le prestataire n'est pas établie par le dépôt.
74→ **À reformuler** : « une déclaration portée par le compte FLEXYLEAD ». Et la question « tirage de production ou recopie locale ? » se pose à **celui qui a lancé la synchronisation**, que le dépôt désigne comme le compte de Jeremy.
75
76**(c) « Plus rien ne rattache `backend/` à la production ».** L'historique le fait, deux fois : `888cf02` — « **Archive deployed sources for 73 production functions** with retained invocations » (73 fichiers, tous dans `backend/`) — et `730a5e79`. La phrase du README de 08/09 existe toujours dans l'historique. Ce sont des déclarations, mais elles sont plus nombreuses et plus anciennes que ne le dit le rapport, et l'une d'elles est **corroborée par hachage** (§ 0.1).
77
78---
79
80## 1. `audit-donnees` — famille 1 (constats 1 à 5)
81
82### Ce qui tient
83
84- **§ 1.2, le message de commit démenti sur sa moitié « Functions ».** Vérifié indépendamment : `diff -rq` entre les deux `functions/` → **aucune sortie**, 147 fichiers de chaque côté. `dist/` et `prebuilt-dist/` : `diff -rq` → aucune différence. **TIENT, `FAIT`.**
85- **§ 1.3, l'inversion `backend/` en avance sur `functions/`.** Vérifié : `functions/flexylead-app-pilotage-query.mjs:2` porte `const DEPLOYED_RESOURCE = "__FLEXYLEAD_PILOTAGE_RESOURCE__"` (gabarit) contre `backend/…summary-query.mjs:51` — `"summary"` (valeur substituée). **TIENT, `FAIT`.** C'est le meilleur argument du rendu : il établit que `backend/` est un **artefact engendré** par une chaîne absente de la livraison.
86- **Constat 1 — PARTIELLEMENT CORRIGÉ.** Vérifié : `missedCallSql` 0 occurrence au 08/09 → **5 au 14/09** (`:204` déf., `:283, 312, 791, 820`) ; `ringover-call-events.go` et `functions/ringover-call-events/main.go` ont le même md5 `855fd6fb…`. La 5ᵉ divergence sur « répondu » est réelle : `'ivr'` 0 occurrence au 08/09 dans le tableau de bord, **1 au 14/09** (`:193`). **TIENT [I], `FAIT`.**
87- **Constats 2 et 5 — INTACT.** Vérifiés ligne à ligne. Sur le constat 5, le diagnostic des +55/−4 est exact (`git diff --numstat` → 55/4) et l'effet de bord est bien décrit : `assertAuditor` passe de `if (!id || actor.gateway_verified !== true)` (0908 `:117`) à `if (!id)` (`:168`), le contrôle étant déplacé en amont dans `trustedActor`. **TIENT, `FAIT`.**
88- **Constat 3 — PARTIELLEMENT CORRIGÉ.** Vérifié : `.catch(` → **17** dans `flexylead-app-commercial-metrics-query.mjs`, au 08/09 comme au 14/09 ; `throw new Error('Commercial queue scope missing')` présent en `:337`. **TIENT [I] serveur, [S] écran, `FAIT`.**
89
90### Ce qui doit être borné
91
92**§ 1.1 et § 10.1 — la réserve de provenance.** Elle est juste dans son intention et fausse dans sa démonstration : elle ignore l'attestation par hachage (§ 0.1). **Reformulation exacte :**
93
94> « Le socle du 08/09 est attesté : les dix empreintes `sha256` de `backend/SNAPSHOT-ADDITIONS-20260908.json` correspondent exactement aux fichiers livrés. Au 14/09, neuf de ces dix fichiers ont été modifiés **sans qu'aucune attestation nouvelle n'accompagne la livraison**. Les verdicts ci-dessous portent donc sur un instantané non attesté, dont le socle, lui, l'était. »
95
96**§ 6.2 — l'arithmétique du constat 4 doit être refaite.** Le fond est juste, les chiffres ne le sont pas, et ceux du cadrage non plus. Mesure réelle :
97
98| | 08/09 | 14/09 |
99|---|---|---|
100| écriture du partitionnement des interactions | `for (let bucket = 0; bucket < 4; bucket++)` — `:3429` | `for (let partition = 0; partition < 8; partition++)` — `:3531` |
101| occurrences, sur les **six** clones de pilotage | 6 (une par clone) | 6 (une par clone) |
102| `queryCampaignNewUniqueCallers` | `:3678`, **sans boucle** | `:3782`, **4 partitions** via `queryCampaignCallerPartition` `:3797` |
103| `partition <` dans **tout** l'arbre | **0** | **12** |
104
105> **Note pour le cadrage :** le motif `partition < 4` ne ramène rien au 08/09 **parce que la variable s'y appelle `bucket`**. C'est ce qui explique les comptes discordants. Les bons chiffres sont **0 → 6** pour la boucle de 8 et **0 → 6** pour la boucle de 4, la boucle de 4 du 08/09 s'écrivant `bucket < 4` (6 occurrences).
106
107Le levier 1 est bien corrigé : le motif de reprise perdait la branche nue `context deadline exceeded` (0908 `:4412` la contient, 0914 `:4532` ne la contient plus). **Reformulation exacte du bilan de charge :**
108
109> « Par chargement de pilotage, la lecture des interactions passe de **quatre** à **huit** requêtes séquentielles, et le décompte des appelants de campagne de **une** à **quatre** : de cinq à douze lectures. En regard, une requête qui dépasse son délai d'**exécution** n'est plus rejouée du tout. »
110
111Verdict **PARTIELLEMENT CORRIGÉ [I] / AGGRAVÉ** maintenu, `FAIT` sur les chiffres ainsi corrigés, `SUGGÉRÉ` sur l'effet net. La contradiction des deux commentaires d'auteur (§ 6.3) est réelle et l'auteur a raison de ne pas trancher.
112
113**§ 8 — les scripts `tmp-*`.** « Cela conforte le constat 1 par un chemin indépendant du code » est une inférence sur une intention. Le `SUGGÉRÉ` est bien posé ; la phrase « si les compteurs se recoupaient, ces scripts n'auraient pas lieu d'être » doit sauter : elle suppose le motif pour le démontrer.
114
115### Ce qui doit être retiré
116
117**§ 1.1, la phrase « `CANONICAL_SOURCE.json` … ne désigne pas `backend/` : il déclare `staging-candidate` ».** Vraie littéralement, trompeuse en portée. Voir § 0.3 (a).
118
119---
120
121## 2. `audit-argent` — famille 2 (constats 6 à 9)
122
123### Ce qui tient — y compris l'affirmation la plus lourde de la journée
124
125**C11 à C15, `flexylead-sync` et la suppression de lignes de vente : LE RAISONNEMENT TIENT, ET LA BORNE EST AU BON ENDROIT.**
126
127Tout est vérifié par moi, pièce par pièce :
128
129- `upsertTable` — `deleteMissing = true` **par défaut**, `backend/flexylead-sync.mjs:1991`.
130- Le calcul des suppressions est **non filtré** : `:2028-2031` retient toute clé présente en base et absente de l'entrée, sur un ensemble de référence issu de `loadAllRows(ctx, 'ventes')` (`:1947-1959`, pagination simple de 100, **aucun filtre de centre**).
131- L'exécution est réelle : `:2049` — `ctx.call('tables', 'rows_delete', …)`.
132- L'appel sur `ventes` (`:2393-2394`) ne passe **que** `diffFields` → `deleteMissing` vaut `true`. **`appels` (`:2396-2399`) est dans le même cas.** Les quatre tables voisines sont explicitement protégées (`:2381, 2388, 2390, 2392`).
133- `local_id = ${centreId}::${tab}::${++seq}` — rang positionnel, `:1670` ; la charge utile (`:1669-1691`) ne contient **ni `prime_montant`, ni `prime_devise`, ni `prime_rule_id`**.
134
135**Le contrôle à deux sens tient également**, et il est même plus fort que ce qui est écrit.
136
137> **Correction de comptage — `FAIT`.** Le prédécesseur `flexylead-sync-local-google.mjs` ne surcharge pas `deleteMissing: false` « à ses cinq points d'appel » sur `ventes` : il le surcharge **à ses quinze points d'appel, toutes tables confondues, sans une seule exception** — dont **quatre** sur `ventes` (`:4661`, `:4692`, `:4882`, `:5550`). **Reformulation exacte :**
138>
139> « La fonction qu'elle remplace surcharge `deleteMissing: false` à **la totalité de ses quinze points d'appel**, dont quatre sur `ventes`. La nouvelle fonction laisse `ventes` et `appels` au défaut, qui est `true`. »
140
141C'est plus dur, et c'est exact : l'omission n'est pas un oubli parmi cinq, c'est une rupture avec une discipline tenue quinze fois sur quinze. Vérifié aussi : le prédécesseur est **byte-à-byte identique** entre 0908 et 0914, et sa liste `diffFields` pour `ventes` (`:5551`) contient bien `prime_montant`, `prime_devise`, `prime_rule_id`, que la nouvelle a retirés.
142
143**La borne `HYPOTHÈSE` sur la réalisation est au bon endroit** — et je la maintiens contre moi-même, car j'ai trouvé un élément qui tire dans l'autre sens et que le rendu n'a pas relevé : `flexylead-sync.mjs` ne porte pas seulement une « intention » de planification. Il porte un **verrou de concurrence** motivé par une panne datée, `:1940-1946` — « *IPC buffer cap … blow it up and crash the worker mid-response (« unexpected EOF at drain » in node_harness.mjs — **diagnosed 2026-05-20 with platform team**)* » — et `:2069-2073` — « *The cron's HTTP caller times out at ~30s but the sync runs **~150s server-side*** ». On n'écrit pas ces deux commentaires sans avoir exécuté la fonction quelque part. **Cela ne prouve pas l'exécution contre la table `ventes` de production**, et le `HYPOTHÈSE` reste juste ; mais le faisceau penche un peu plus vers « a déjà tourné » que ne le dit C16. À porter dans la question à Marco.
144
145**C2, l'anomalie `flexylead-admin-mcp.mjs` : le fait tient, la conclusion est bien bornée.** Vérifié : md5 `087af87d…` identique pour `backend/` (14/09), `functions/` (14/09) **et** `functions/` (08/09). L'agent conclut « le corpus ne permet pas de dire qu'il est déployé » et non « il ne l'est pas » — c'est la bonne position, et elle survit au § 0.2.
146
147**C7, C8, C9, C10 tiennent.** Le bloc `evaluateMonthlyFormula` → `saleRemunerationForEntry` est bien inchangé ; `defaultFormula()` porte bien ses cinq lignes (`:413-422`) ; `missing` est bien normalisé à `"ignore"` (`:380`) ; `maximum_amount` est bien plafonné à 100 par défaut (`:385`) ; `config/tables/production-economics.json` n'a toujours ni `created_by` ni `updated_by`. L'auto-critique de C9 (avoir cherché la collision de clé et ne pas l'avoir trouvée, puis l'écrire) est exactement la discipline attendue.
148
149### Ce qui doit être borné
150
151**X1 — le chiffrage le plus exposé du rendu, et il doit descendre d'un cran.** `+22 €/contrat` suppose que `defaultFormula()` s'applique. Or `normalizeMonthlyFormula:343` ne l'invoque **que si `parsed.adjustments` est absent**. Et la seule formule réellement observée en production — capture du 03/09 lue dans `analyse/contre-contrat-2026-09-09.md:13`, panneau « BARÈME ACTIF », 68 €/contrat — est une formule **configurée de douze lignes** (base, transformation, huit paliers Météore, plafond ConsoPilote, malus qualité < 85), qui ne ressemble en rien aux cinq lignes de `defaultFormula()`. **Pour ce centre-là, le défaut de code ne s'applique pas.** L'agent le pressent (§ 13.4) ; il faut l'écrire dans le tableau :
152
153> **Reformulation exacte de X1 :** « `+22 €/contrat` **uniquement pour un centre dont aucune ligne d'ajustement n'est enregistrée**, cas dans lequel le code substitue son barème par défaut. Le seul centre dont le barème ait été observé porte une formule configurée de douze lignes et n'est pas dans ce cas. Niveau : `HYPOTHÈSE` quant à l'existence d'un tel centre. »
154
155Même borne pour X2, X3 et X4, qui reposent sur les mêmes valeurs par défaut.
156
157**§ 9, la phrase du 09/09 à retirer.** L'agent a raison de dire que la garantie « les sept fichiers sont identiques entre `functions/` et `backend/` » est perdue. Mais la conclusion « la phrase doit être retirée » est trop forte : elle doit être **redatée**, pas retirée. Reformulation : « au 08/09, les quatre constats décrivaient un code attesté par hachage ; au 14/09, ils décrivent un instantané non attesté dont le socle l'était ».
158
159### Ce qui doit être retiré
160
161**Rien.** C'est le rendu le plus solide des quatre : il a cherché la collision de clé qui l'aurait fait tomber, il a innocenté le chemin de mise à jour avant d'accuser le chemin de suppression, et il a refusé de conclure au déploiement du MCP alors que le cadrage l'y invitait.
162
163---
164
165## 3. `audit-qualite-ia` — famille 3 (constats 10 à 13)
166
167### Ce qui tient
168
169**L'absence de `lead_telecom_v3` : vérifiée par moi, elle est totale.** `grep -rn` sur les deux arbres entiers : **5 occurrences, toutes dans `backend/process-appel-db-only.mjs`** (`:1221`, `:3306`, `:3307`, `:3309`, `:3310`). **Zéro** dans `config/`, `functions/`, `src/`, `scripts/`, `e2e/`, `workflows/` ; **zéro dans tout le corpus du 08/09**. Le diff du moteur est bien **exactement** les deux blocs décrits (10 lignes de consignes conditionnées à `grille?.local_id === 'lead_telecom_v3'`, et deux substitutions `free_v3` → `lead_telecom_v3` aux `:3306` et `:3309`) — vérifié par `diff` complet, rien d'autre n'a bougé. **TIENT, `FAIT`.**
170
171Les mécanismes du chiffrage tiennent aussi, isolément : `average_ai_score` vaut **0** quand aucun rapport n'est noté (`:2664`), `latestReportScoresForPeriod` (`:2676-2695`) saute bien les rapports sans score fini, et `saleRuleMatches` (`:2508-2513`) avec `missing: "ignore"` (`:380`) fait bien qu'**une vente sans note est payée**.
172
173**Constats 10 et 13 : INTACT.** Vérifiés (`temperature|seed|top_p` → 0 sur les deux moteurs ; `handled_by`/dédoublonnage ; second moteur `backend/process-appel.mjs` écrivant `rapports` aux `:1584` et `:1587` avec une somme brute `:1244-1246` et une clé `WHERE appel_id = ? ORDER BY id DESC LIMIT 1` `:1564-1566`). La prudence sur la dormance (`SUGGÉRÉ`) est justifiée : `workflows/process-appel.yaml:12` ne déclenche que `process-appel-db-only`, seul nom présent au manifeste (`:76`).
174
175### Ce qui doit être borné, et c'est la plus grosse correction de la journée
176
177**Le chiffrage « +68 € / −9 €, non borné et de signe variable » doit être ramené à une seule direction établie et une conditionnelle.**
178
179**(a) Le `−9 €` doit devenir `−4 €`.** Il additionne deux règles de nature différente :
180
181- le **malus de 4 € sous 85** : `delta: -4` → **zéro occurrence dans tout le dépôt**. La règle n'existe, dans le code livré, que dans des **jeux d'essai** (`functions/flexylead-app-pilotage-query.test.mjs:1684-1685` et `:1736-1737` — `{ effect: "add", value: -4, conditions:[{ metric:"average_ai_score", operator:"lt", threshold:85 }] }`). **Mais elle est corroborée par la production** : la capture du 03/09 (`contre-contrat-2026-09-09.md:13`) montre la ligne « Qualité moyenne < 85/100 — malus de 4 € », non appliquée, −4,00 €, sur un panneau « BARÈME ACTIF » à 68 €/contrat. **Le −4 est réel pour ce centre.** `FAIT`, avec la capture pour source — pas le jeu d'essai.
182- le **bonus `quality-90` de +5 €** : il vient de `defaultFormula()` (`:420`), qui ne s'applique **que faute d'ajustements enregistrés** (`:343`). La formule réellement observée le 03/09 compte douze lignes et **ne contient aucune ligne « ≥ 90 → +5 »**. Pour ce centre, **ce bonus n'existe pas et ne peut donc pas être perdu.**
183
184> **Reformulation exacte :** « Sous le scénario H-B, un centre dont la formule porte le malus « qualité moyenne < 85 » subit **−4 € par contrat du mois**, la moyenne tombant mécaniquement à 0. Les centres dont la formule ne porte pas cette règle ne subissent rien. Le bonus « ≥ 90 → +5 € » n'est un défaut que pour un centre sans aucun ajustement enregistré ; le seul barème observé n'est pas dans ce cas. »
185
186**(b) Le `+68 €` doit cesser d'être un chiffrage et redevenir une conditionnelle.** Il suppose qu'existe une règle de vente « exclure sous 80/100 ». Or : `defaultFormula()` ne renvoie **aucune** `sale_rules` ; `normalizeMonthlyFormula:363` les initialise à `[]` en l'absence de configuration ; la règle « Vente non rémunérée sous 80/100 » n'existe que dans deux jeux d'essai (`flexylead-app-pilotage-query.test.mjs:1785`, `flexylead-manage-production-economics.test.mjs:216`) et comme charge par défaut du bouton « Ajouter une règle » (`src/routes/AdminEconomicsSettings.tsx:291`) ; et `contre-contrat-2026-09-09.md` a établi que `sale_rules` a **0 occurrence dans `src/` et 0 dans les bundles** au 08/09 — *l'écran ne savait pas les écrire*.
187
188> **Reformulation exacte :** « Si un centre a configuré une règle d'exclusion des ventes sous 80/100 — ce qu'aucune pièce du corpus n'établit, et que l'écran ne savait pas écrire au 08/09 — chaque vente cessant d'être notée échappe à cette exclusion et est payée. Le montant unitaire serait celui du barème du centre. Niveau : `HYPOTHÈSE`. »
189
190**(c) En conséquence, la phrase « l'erreur n'est pas bornée, et son signe dépend du volume » tombe.** Le seul effet établi est **négatif pour le centre et borné à −4 €/contrat**. C'est la phrase la plus frappante du rendu et c'est celle qui résiste le moins. Ce qui reste vrai, et qui suffit : **sous H-A comme sous H-B, deux référentiels de tailles différentes (25 critères contre 7 annoncés) se mélangent dans la moyenne qui déclenche le palier des 85, sans aucune normalisation autre que `score_total / score_max * 100` (`:2694`).**
191
192**Constat 11 : c'est une inférence, pas une conclusion — et elle doit descendre à `SUGGÉRÉ`.**
193
194Le fait est vérifié : `scoring_mode` et `base_score` n'apparaissent que dans `config/*.json`, les deux moteurs, quatre fichiers de test, `src/dashboard/lib/*` et le schéma — et dans **aucun** semeur (0 occurrence sur `backend/flexylead-sync.mjs`, `backend/flexylead-sync-local-google.mjs`, `functions/flexylead-sync-local-google.mjs`). J'ajoute un élément qui **renforce** l'agent : la politique d'écriture administrateur (`backend/flexylead-admin-data-mutation.mjs:56-70`) ne comporte **aucune entrée `grilles`** — ce chemin-là non plus ne peut pas écrire la colonne.
195
196**Mais le pas de trop est là :** le moteur lit `grille.scoring_mode` **dans la base**, pas dans le dépôt. Que le dépôt ne sache pas écrire une colonne ne dit rien de sa valeur en production. La démonstration se réfute elle-même : **`free_v3` n'est semée par rien non plus** — l'agent l'écrit — et pourtant neuf offres la désignent comme existante (`functions/flexylead-sync-local-google.mjs:488-497`), et toute la chaîne Free en dépend. Un semeur n'est pas la production ; l'absence de semeur n'est pas l'absence de donnée.
197
198> **Reformulation exacte du titre proposé :** « **Aucun chemin du dépôt n'écrit le mode « malus » d'une grille — ni les semeurs, ni la politique d'écriture administrateur.** Si une grille le porte en base, elle y a été mise par un moyen que la livraison ne contient pas. » Niveau : `FAIT` sur l'absence de chemin, **`SUGGÉRÉ`** sur l'inatteignabilité de la branche.
199
200La question à poser à Marco, telle que l'agent la formule, est la bonne et doit être conservée.
201
202**Constat 12 : le verdict tient, un de ses deux appuis doit être retiré.** L'agent étaye la lecture « (a) le serveur existe en production mais n'est pas livré » par « `backend/flexylead-app-quality-query.mjs` … a grossi de 110 lignes ». Mesure réelle : **+84/−26** (1 391 → 1 449 lignes), et le fichier **commence par les 49 lignes de l'adaptateur** (`requireTrustedPrincipal` = 2 occurrences, `:1-5`). La croissance est l'adaptateur, pas un gestionnaire neuf. `grep -ci "correct"` → **0**, confirmé sur les trois passerelles candidates, et `rapport_correct` n'existe dans aucun fichier de `backend/` ni de `functions/` (seulement `README.md`, `e2e/`, `src/`). La lecture (a) survit **par le seul texte du README** (`:104-111`), qui décrit le mécanisme comme opérationnel. **L'argument de croissance doit être retiré.**
203
204### Ce qui doit être retiré
205
206- La phrase « conséquence financière **non bornée et de signe variable** » (§ 5 et résumé).
207- L'argument « a grossi de 110 lignes » au soutien de la lecture (a) du constat 12.
208- Le comptage « **61 fichiers sur 101** portent l'adaptateur » (§ 2) : il porte sur `__authParityEvent`. Sur `requireTrustedPrincipal`, c'est **63**. Les deux sont vrais sur des marqueurs différents — il faut nommer le marqueur. (`trustedActor` : 61 également.)
209
210---
211
212## 4. `audit-routage` — famille 4 (constats 14 à 23)
213
214### Ce qui tient
215
216- **Constat 16, la reprise durcie après la remontée du poste « données ».** Tout est vérifié : `ivr` au 08/09 → 0 / 0 / 0 / 1 sur les quatre compteurs ; au 14/09 → 1 (`dashboard:193`) / 1 (`acquisition:62`) / 0 / 4. Les quatre occurrences du pilotage sont bien hors compteur : `:1494` est un commentaire français (déjà là au 08/09 en `:1404`), `:4727`, `:4771`, `:4775` sont dans un bloc de reconstruction d'aiguillage. `hasAnswerEvidence` (`:2789-2793`) ne teste pas `handled_by`, dans le fichier de rémunération comme dans celui de pilotage. **La correction de référence de ligne (`:193` et non `:191`) est juste** : `:190` porte la signature, `:191` la lambda `column`. **TIENT [I], `FAIT`.**
217- **L'effet de bord rétroactif** — le filtre s'applique à la lecture, sur des lignes marquées depuis avant le 08/09, donc les séries historiques de deux écrans baissent d'un coup sur des périodes closes. **C'est la meilleure trouvaille des quatre rendus.** `FAIT` sur le mécanisme, `SUGGÉRÉ` sur l'ampleur : bien posé.
218- **Constats 14, 17, 18, 19, 20, 22, 23 : INTACT.** Vérifiés par sondage : `flexylead-update-sale.mjs` → `centre_lock` = 0 ; `flexylead-app-commercial-bootstrap-query.mjs` → `commercial_supervision_sessions|rows_insert` = 0 ; `commercial-supervision-session.mjs` 570 → 619 lignes (+49, soit l'adaptateur) ; `ringover-audio-mirror.mjs` **identique** au 08/09 ; `flexylead-project-telephony-event.mjs` → 0 occurrence de tout marqueur d'authentification ; `ringover-call-events.go` → 0 `signature`, 0 `hmac`, 0 `.Headers`, et md5 identique à `functions/ringover-call-events/main.go`.
219- **Constat 21 — AGGRAVÉ, 13 → 16.** Vérifié exactement : 13 fichiers au 08/09, 16 au 14/09, les trois ajouts étant `flexylead-discard-missed-call.mjs`, `flexylead-sync.mjs`, `leadbyte-prospect-dispatcher.mjs`. **TIENT, `FAIT`.**
220- **§ 0.5, la correction sur les tâches planifiées.** Vérifiée et juste : **six** fichiers dans `config/jobs/` des deux côtés, `diff -r` → aucune différence, et `ringover-presence-refresh.json` existait déjà. La trouvaille annexe est exacte : **deux des six tâches n'ont aucun bloc `target`** (`ringover-presence-refresh.json` et `routing-performance-scores.json` → 0 occurrence de `target`, contre 1 pour les quatre autres).
221
222### Ce qui doit être borné
223
224**Constats 15 et 16, « CORRIGÉ » : l'agent pose la réserve puis la laisse hors du tableau. Je tranche — la réserve doit entrer dans la case du verdict.**
225
226Les faits de code sont solides et je les confirme : `nrpWaiting` → 0 occurrence au 08/09, **3** au 14/09 (`flexylead-start-callback.mjs:673, 677, 685`) ; `recoverFailedCallbackPointer` → 0 au 08/09, **4 occurrences dans 2 fichiers** au 14/09 (`start-callback:91, 1396` ; `claim-missed-call:77, 904`). Le constat 15 est réellement corrigé **dans l'arbre examiné**.
227
228Mais « CORRIGÉ » seul, dans une colonne de tableau, affirme un état de la production. Compte tenu du § 0.1 — le socle est attesté, l'instantané du 14/09 ne l'est pas —, le verdict publiable est :
229
230> **Constat 15 : « CORRIGÉ [I] » — les trois moitiés sont en service dans l'instantané `backend/` du 14/09, instantané qu'aucune attestation ne couvre.**
231> **Constat 16 : « PARTIELLEMENT CORRIGÉ [I] — deux compteurs sur quatre ».**
232
233**Ce n'est pas un abaissement du constat** : le fait de code reste `FAIT`. C'est la portée qui est nommée. La réserve de la § 4 est juste ; elle doit voyager avec le verdict, parce que c'est le tableau qu'on lit.
234
235La réserve annexe sur `recoverFailedCallbackPointer` recopiée dans deux fichiers, avec `functions/shared/` aplati au déploiement, est exacte et utile.
236
237### Ce qui doit être retiré
238
239**La « correction au cadrage » du § 2, ligne 2 du tableau final — elle est fausse, et c'est le cadrage qui avait raison.** Mesure sur `backend/flexylead-ringover-smart-routing.mjs`, **identique dans les deux dépôts** pour ces marqueurs :
240
241| marqueur | `grep -c` (lignes, sensible à la casse) | `grep -o` (occurrences) | `grep -ci` (lignes, insensible) |
242|---|---|---|---|
243| `signature` | **4** | **5** | **13** |
244| `hmac` | **0** | 0 | **3** |
245| `secret` | 5 | — | 5 |
246
247Donc : « `signature` = 5 » est un compte d'**occurrences** (pas de lignes), et **« `hmac` = 0 (pas 3) » est faux** — il y a bien **3** lignes portant `Hmac` (`:1` `import { createHmac, timingSafeEqual }`, `:3075` `createHmac("sha256", …)`, `:3090` `createHmac("sha512", …)`). **La mesure du cadrage (13 et 3) est la bonne** ; c'est une différence de casse, exactement comme supposé. La phrase « les chiffres annoncés ne le sont pas » doit être retirée et remplacée par son inverse.
248
249**Laquelle des deux lectures soutient l'affirmation de fond ? La lecture insensible à la casse, sans hésitation — et elle répond « oui ».**
250
251> **Cette porte vérifie bel et bien les signatures.** `verifyRingoverSignature(request, config)` est appelée **avant tout traitement** (`:194`), et la fonction (`:3070-3095`) contrôle d'abord un en-tête V3 (`x-ringover-webhook-signature-v3`) par `createHmac("sha256")` sur un horodatage, puis un jeton hérité en trois parties par `createHmac("sha512")`, chaque comparaison passant par `safeEqual` (temps constant) et chaque échec levant un **401**.
252
253Un compte sensible à la casse donnant « `hmac` = 0 » suggérerait l'absence de tout HMAC : c'est l'exact contraire de la vérité. **Sur ce fichier, la mesure sensible à la casse induit en erreur sur le fond.** Le constat 17 n'a d'ailleurs jamais visé cette porte : il vise `ringover-call-events.go` (0 / 0 / 0, vérifié) et `flexylead-project-telephony-event.mjs` (0, vérifié). **L'asymétrie est réelle, la porte 1 est protégée, et le constat 17 tient sans cette correction.**
254
255**Deux autres chiffres à retirer :**
256
257- **§ « constat 19 » : « (34 au 08/09 ; la colonne ajoutée n'est pas un consentement) ».** Faux. La table `prospects` compte **35 colonnes dans les deux arbres** — le fichier `functions/test-support/table-schema.json` est byte-à-byte identique (54 tables de chaque côté). Aucune colonne n'a été ajoutée. Le constat 19 lui-même est intact (aucune colonne de consentement, vérifié).
258- **§ « constat 21 » : « Le compte des sources est passé de 15 à 15 ».** Hors fichiers de test, `functions/` porte **16** écrivains de schéma dans les deux arbres (dont `sqlite-routing-context.mjs`). Sans effet sur le verdict.
259
260---
261
262## 5. Les corrections du cadrage, contrôlées
263
264| # | Correction annoncée | Contrôle |
265|---|---|---|
266| 1 | « sept tâches planifiées → **six** » | **JUSTE.** 6 fichiers de chaque côté, `diff -r` vide, mêmes noms. |
267| 2 | « sept scripts `tmp-*` → **huit** » | **JUSTE.** 8 fichiers, tous neufs au 14/09 (0 au 08/09). |
268| 3 | « constat 4 corrigé → surévalué » | **JUSTE dans le sens**, faux dans les chiffres de remplacement. Voir § 1 : les bons comptes sont `bucket < 4` → 6 occurrences au 08/09, `partition < 8` → 6 et `partition < 4` → 6 au 14/09. |
269
270**Deux erreurs supplémentaires, non encore corrigées :**
271
272| # | Énoncé | Contrôle |
273|---|---|---|
274| 4 | « `functions/` n'a pas changé d'un octet : **156 fichiers** de chaque côté » | La **parité est juste** (`diff -rq` → aucune sortie). Le nombre ne l'est pas : **147** fichiers de chaque côté. 156 est le nombre de fichiers *versés dans* `functions/` par le commit `730a5e79` — une autre grandeur. La même confusion figure au retrait **R6** de la liste reconstituée (« `functions/` = 156 fichiers ») : elle s'y est propagée. |
275| 5 | « le dépôt **se déclare** candidat au staging » | À borner. Voir § 0.3 (a) : fichier de métadonnées non mis à jour depuis avant l'arrivée de `backend/`, identique sur une branche du 07/09 qui ne contient pas `backend/` ; et `playwright.config.ts:18` nomme `https://partenaires.flexylead.com`. |
276
277---
278
279## 6. Le motif de méthode : les constats chiffrés qui se périment
280
281`audit-routage` a raison — mais le phénomène est plus large que le constat 21, et il recouvre **deux défauts distincts qu'il ne faut pas confondre.**
282
283**(a) Les chiffres qui se périment** — justes à leur date, faux ensuite :
284
285| constat | chiffre publié | valeur au 14/09 |
286|---|---|---|
287| 21 | « treize fonctions modifient la structure » | **seize** |
288| 17 | « 4 440 lignes en service » | **4 565** (le Go est devenu identique à sa source) |
289| 16 | « aucun compteur ne lit la marque » | **deux des quatre** la lisent |
290| 1 | « trois correctifs existent, aucun n'est en service » | **deux des trois** sont en service |
291| 4 | « quatre morceaux, jusqu'à douze fois » | **huit morceaux**, plus une boucle de quatre |
292
293**(b) Les chiffres qui étaient faux dès l'écriture**, parce que mesurés sur un arbre et publiés pour un autre :
294
295| constat / pièce | chiffre publié | valeur réelle, aux deux dates |
296|---|---|---|
297| 19 | « 34 colonnes » puis « 35 au 14/09 » | **35 des deux côtés**, fichier identique |
298| R6 / cadrage | « `functions/` = 156 fichiers » | **147** |
299| famille 4 | « `signature` = 5, `hmac` = 0 » | **13 et 3** en lecture insensible à la casse |
300| famille 2 | « cinq points d'appel » | **quinze**, dont quatre sur `ventes` |
301
302**La règle qui en découle, en deux volets :**
303
304> **1. Tout chiffre publié est daté et attaché à son arbre.** Format obligatoire : *« N au JJ/MM sur `<arbre>`, commit `<sha>` »*. Un compte sans son arbre et son commit n'est pas un fait : c'est une mesure dont on a effacé le référent, et elle devient fausse sans que personne ne le voie.
305>
306> **2. Tout chiffre est re-mesuré sur l'arbre qu'on cite, jamais recopié depuis un rendu antérieur.** Les quatre erreurs du volet (b) ne sont pas des péremptions : elles étaient fausses le jour de leur écriture, parce qu'un compte pris sur `functions/` a été publié pour `backend/`, ou un compte d'occurrences présenté comme un compte de lignes. **Et la casse fait partie de la mesure** : sur `flexylead-ringover-smart-routing.mjs`, `grep -c hmac` = 0 et `grep -ci hmac` = 3 conduisent à deux conclusions opposées sur la sécurité d'une porte.
307>
308> **Corollaire d'ordonnancement :** un constat dont l'énoncé *est* un nombre (« treize fonctions… ») ne devrait pas être publié comme un constat, mais comme un constat assorti d'une mesure. Le constat est « la structure de la base est modifiée en pleine requête, par des fonctions de production » ; le nombre en est l'illustration datée. Ainsi formulé, il ne se périme plus.
309
310---
311
312## 7. Le tableau des 23 verdicts, corrigé
313
314Marques de portée : **[A]** attesté par hachage (08/09) · **[I]** instantané du 14/09, non attesté · **[S]** `src/` seul, absent des bundles.
315
316| n° | Constat | Verdict du rendu | **Verdict corrigé** | Certitude |
317|---|---|---|---|---|
318| 1 | Quatre fonctions comptent les appels reçus | PARTIELLEMENT CORRIGÉ | **PARTIELLEMENT CORRIGÉ [I]** — 2 correctifs sur 3 en service, 5ᵉ divergence née sur « répondu » | `FAIT` |
319| 2 | Deux compteurs justes portent le même nom | INTACT | **INTACT** — et 3ᵉ copie de `prime_montant` identifiée | `FAIT` |
320| 3 | Onze tables facultatives, et le geste serveur | PARTIELLEMENT CORRIGÉ | **PARTIELLEMENT CORRIGÉ [I] serveur / [S] écran** | `FAIT` |
321| 4 | Une requête trop lente est relancée jusqu'à douze fois | PART. CORRIGÉ / AGGRAVÉ | **PARTIELLEMENT CORRIGÉ [I] / AGGRAVÉ** — reprise corrigée ; 4 → 8 morceaux + boucle neuve de 4 ; **arithmétique refaite (§ 1)** | `FAIT` (chiffres corrigés) · `SUGGÉRÉ` (effet net) |
322| 5 | Un vérificateur que rien ne déclenche | INTACT | **INTACT** — les +55/−4 sont 49 lignes d'adaptateur + 6 de forme | `FAIT` |
323| 6 | Aucun montant dû à un centre n'est figé | Tient | **TIENT [I]** — réserve : commande A non rejouée sur 0914 | `FAIT` |
324| 7 | Supprimer une règle l'efface de tout le passé | Tient | **TIENT**, verbatim, désormais derrière un cache | `FAIT` |
325| 8 | Asymétrie des dates d'effet | Tient | **TIENT** | `FAIT` |
326| 9 | Le détail du calcul est produit puis jeté | Tient | **TIENT** | `FAIT` |
327| 10 | Le texte ne dit pas qui parle, onze contrôles y cherchent des phrases | INTACT | **INTACT** | `FAIT` |
328| 11 | Une grille en mode malus est additionnée | Tranché / déplacé | **À RÉÉCRIRE** — « aucun chemin du dépôt n'écrit `scoring_mode` » ; l'inatteignabilité est une **inférence** | `FAIT` (absence de chemin) · **`SUGGÉRÉ`** (conclusion) |
329| 12 | Corriger une note à la main la fait tomber sous le seuil | PART. CORRIGÉ [S] / non vérifiable | **PARTIELLEMENT CORRIGÉ [S] / NON VÉRIFIABLE serveur** — appui « +110 lignes » retiré | `FAIT` · `HYPOTHÈSE` (serveur) |
330| 13 | Le même appel peut recevoir deux notes différentes | INTACT, AGGRAVÉ | **INTACT [I]** ; aggravation (2ᵉ moteur) | `FAIT` (coexistence) · `SUGGÉRÉ` (dormance) |
331| 14 | Un rendez-vous jamais appelé est enregistré injoignable | INTACT | **INTACT** | `FAIT` |
332| 15 | La relance d'un numéro qui n'a pas répondu est morte | **CORRIGÉ** | **CORRIGÉ [I]** — les trois moitiés en service dans l'instantané non attesté | `FAIT` (code) |
333| 16 | Un appel pris par le serveur vocal compté comme pris par un commercial | PART. CORRIGÉ | **PARTIELLEMENT CORRIGÉ [I] — deux compteurs sur quatre** ; rupture de série rétroactive | `FAIT` · `SUGGÉRÉ` (ampleur) |
334| 17 | Deux portes d'entrée n'authentifient rien | INTACT | **INTACT** — et **la porte 1 vérifie bien les signatures** ; correction de comptage du rendu **retirée** | `FAIT` |
335| 18 | Le classement énergie ou télécom est une devinette | INTACT | **INTACT**, rayon étendu (un consommateur de plus) | `FAIT` |
336| 19 | Le consentement n'est rangé nulle part | INTACT | **INTACT** — **35 colonnes des deux côtés**, schéma identique | `FAIT` |
337| 20 | Des enregistrements finissent introuvables | INTACT, aggravé | **INTACT**, aggravé — 3 chemins de recopie, 2 sans déclencheur | `FAIT` |
338| 21 | Rien ne relie les tables entre elles | AGGRAVÉ 13 → 16 | **AGGRAVÉ — 13 (08/09) → 16 (14/09)**, à publier daté | `FAIT` |
339| 22 | La supervision est tracée d'un côté et pas de l'autre | PART. CORRIGÉ | **PARTIELLEMENT CORRIGÉ [I]** — identité oui, trace non | `FAIT` |
340| 23 | Une vente annulée garde son verrou de trois mois | INTACT | **INTACT** | `FAIT` |
341
342**Faits neufs hors des 23, à ne pas y fondre :** X7/X9 (`flexylead-sync`, suppression de lignes de vente — `FAIT` sur le code, `HYPOTHÈSE` sur la réalisation) · la grille `lead_telecom_v3` (`FAIT` sur l'absence, `HYPOTHÈSE` sur l'état en base) · le second moteur `process-appel.mjs` · l'adaptateur d'autorisation sur 63 fichiers.
343
344**Aucun des 23 n'est invalidé. Aucun n'est corrigé au sens plein.** Deux verdicts descendent d'un cran de certitude (11, et l'appui du 12), cinq gagnent une marque de portée, un compte est retiré (famille 4 sur `hmac`), quatre chiffrages sont corrigés.
345
346---
347
348## 8. Angles morts — ce que personne n'a regardé
349
3501. **`SNAPSHOT-ADDITIONS-20260908.json` n'a jamais été vérifié par hachage.** C'est le seul instrument de provenance du corpus, il est vérifiable hors ligne, et il répond partiellement à la question du jour (§ 0.1). **À demander pour le 14/09 : l'export équivalent.** C'est une pièce d'une minute qui clôt six réserves.
3512. **L'auteur des commits.** Personne n'a regardé `git log`. Tous les commits sont signés `jeremy-flexylead`. La « déclaration de Marco » n'en est pas une.
3523. **La branche `origin/apteva/local-before-connect`** (07/09, auteur « Apteva Code ») : un état antérieur sans `backend/` ni `functions/`. Elle date l'arrivée des instantanés et permettrait de vérifier si les bundles `dist/` correspondent à un `src/` d'avant le 07/09 — ce qui trancherait la moitié écran du constat 3.
3534. **`package.json` : React 18 → 19, `@apteva/web-sdk` ^0.5.5 → 0.10.0, version 1.1.20 → 1.1.36.** Un saut de version majeure de l'ossature de l'écran, livré dans un commit annoncé comme une simple synchronisation d'instantanés — **avec `dist/` inchangé**. Personne n'a regardé. Cela pèse directement sur la question « les bundles sont-ils reconstruits au déploiement ? » : des bundles compatibles React 18 ne peuvent pas être ceux d'un `src/` React 19.
3545. **`scripts/application-trusted-invocations.mjs` (neuf, +29) et `scripts/deploy-local-application-api.mjs` (+90/−58).** C'est la contrepartie déploiement de l'adaptateur : installation des routes `auth_jwt`, politique `authenticated_callers`, exclusion de cibles de commande. Non instruit par les quatre postes, alors qu'il porte sur le constat 17 et sur la réalité du chantier d'autorisation.
3556. **Les 33 fichiers de `e2e/`.** Aucun poste ne les a ouverts. Ils contiennent des sondes `production-*` **et** `staging-*`. Et `staging-smoke.mjs` porte **un compte et un mot de passe de recette en clair** (`commercial.atlas.staging@flexylead.com` / `Stg-Atlas-Entrant-2026!9a`). Consigné factuellement, sans relance ni recommandation, conformément à l'arbitrage du 02/09 — pertinent pour la régénération des secrets au changement d'hébergeur, au même titre que la clé Composio `ak_cpsw-…` relevée par deux postes (`backend/flexylead-sync.mjs:31`, `backend/process-appel.mjs:25`).
3567. **L'écart résiduel non caractérisé de `flexylead-start-callback.mjs`** (130 lignes d'écart `functions/` → `backend/` pour un adaptateur de ~49). `audit-routage` le déclare en limite ; personne ne l'a ouvert. C'est précisément le genre d'endroit où un changement de comportement non déclaré se loge.
3578. **`appels` est laissé au défaut `deleteMissing = true` comme `ventes`.** `audit-argent` le mentionne en passant et n'en tire rien. La table `appels` porte le dénominateur de la transformation (constat 6) et le rattachement audio (constat 20). Le rayon d'impact de X7 est donc plus large que les ventes.
358
359---
360
361## 9. Objections que Marco pourra formuler, et la réponse préparée
362
363| # | Objection | Réponse |
364|---|---|---|
365| 1 | « `backend/` **est** la production, je l'ai tirée ce matin. » | Alors fournis l'export équivalent à `SNAPSHOT-ADDITIONS-20260908.json`. Celui du 08/09 se vérifie par hachage, et il est **juste sur dix fichiers sur dix**. Neuf de ces dix ne correspondent plus au 14/09. Un tirage sans relevé n'est pas opposable ; avec le relevé, il l'est en une minute. **Nous ne te demandons pas de nous croire, nous te demandons le fichier que tu as déjà su produire.** |
366| 2 | « Le message de commit dit “production”, cela suffit. » | Il est signé `jeremy-flexylead`, pas de toi ; et sa moitié « **Functions** » est démentie par le dépôt lui-même — `functions/` : zéro octet modifié, 147 fichiers identiques. Un message dont une moitié est fausse ne porte pas l'autre. |
367| 3 | « `CANONICAL_SOURCE` dit “staging-candidate” parce que c'est un vieux fichier ; vous sur-interprétez. » | **Accordé, et c'est nous qui le disons en premier** (§ 0.3 a) : le fichier est antérieur à l'arrivée de `backend/`, identique sur une branche du 07/09 qui n'en contient pas, et le dépôt nomme par ailleurs `partenaires.flexylead.com`. Nous avons borné ce point contre nous-mêmes. |
368| 4 | « Vous prétendez que j'ai recopié mes sources locales par-dessus la production. » | **Nous concluons l'inverse** (§ 0.2) : `functions/` ne contient pas l'adaptateur, donc une recopie l'aurait retiré, pas ajouté ; et le seul fichier attesté resté conforme est justement celui qui n'a pas reçu l'adaptateur. La thèse de la recopie est **affaiblie par nos mesures**. Reste une anomalie, une seule : `flexylead-admin-mcp.mjs`, byte-à-byte identique à ta source du 08/09 et seul écrivain sans adaptateur. Explique celle-là et le sujet est clos. |
369| 5 | « `flexylead-sync` n'a jamais tourné, c'est un portage en cours. » | Dis-le et le chiffrage tombe — nous l'avons déjà classé `HYPOTHÈSE` quant à sa réalisation. Mais le fichier est **dans le répertoire présenté comme la production**, il porte un verrou de concurrence écrit contre une panne datée du 20/05 diagnostiquée « avec l'équipe plateforme », et il a perdu un garde-fou que son prédécesseur pose à **quinze points d'appel sur quinze**. La question n'est pas « est-ce grave aujourd'hui » mais « qu'est-ce qui empêche qu'il soit lancé demain ». |
370| 6 | « `lead_telecom_v3` existe en base, tout va bien. » | Alors donne trois valeurs : son `scoring_mode`, son `base_score`, et le nombre de ses critères. Si elle en porte sept là où `free_v3` en porte vingt-cinq, les notes d'avant et d'après se mélangent sans normalisation dans la moyenne qui déclenche le palier des 85 — c'est un sujet **même quand tout fonctionne**. |
371| 7 | « Votre −9 €/contrat est faux. » | **Accordé, et corrigé par nous avant toi** (§ 3) : c'est **−4 €**, et seulement pour les centres dont la formule porte le malus « qualité < 85 ». Le +5 de `quality-90` est un défaut de code que la seule formule réellement observée — capture du 03/09, « BARÈME ACTIF », 68 €/contrat — ne porte pas. Le « +68 € » redevient une conditionnelle : aucune règle d'exclusion sous 80/100 n'est constatée nulle part, et l'écran ne savait pas l'écrire. |
372| 8 | « Vos comptes ne sont pas fiables : `signature` = 13, vraiment ? » | **Le 13 est le bon** ; c'est le 5 qui était faux (compte d'occurrences sensible à la casse). Et sur le fond, **cette porte-là vérifie bien les signatures** : `verifyRingoverSignature` est appelée avant tout traitement, en HMAC-SHA256 puis SHA512, comparaison à temps constant, 401 à l'échec. Le constat 17 ne l'a jamais visée : il vise `ringover-call-events.go` et `flexylead-project-telephony-event.mjs`, à zéro marqueur d'authentification l'un comme l'autre. |
373| 9 | « Le partitionnement en huit est une correction, pas une régression. » | **Recevable, et non écartée.** Ton propre commentaire `:3515-3516` dit que la requête unique dépasse le plafond de 4 Mio, quand celui de la source dit qu'elle tient à 1,43 Mo. Nous ne tranchons pas : nous disons que la charge passe de cinq à douze lectures par chargement, et que le dépassement de délai d'exécution n'est plus rejoué — **ce second point est un vrai correctif et nous te le créditons.** |
374| 10 | « Vous auditez un instantané d'archive, pas mon système. » | C'est exactement ce que nous écrivons, et c'est pourquoi chaque verdict porte désormais sa marque de portée. Deux pièces suffisent à lever la réserve pour de bon : l'export attesté du 14/09, et la réponse à « `dist/` est-il reconstruit au déploiement ? ». |
375
376---
377
378## 10. Contre-lecture de ce contre-audit
379
3801. **Mon résultat le plus lourd — l'attestation par hachage — peut être retourné contre moi.** Qu'un export listant des `sha256` existe ne prouve pas qu'il ait été produit par la plateforme : rien n'interdit de fabriquer un tel fichier. Ce qui le rend crédible est que **les dix empreintes tombent juste** sur des fichiers de 200 à 2 500 lignes — coïncidence impossible à obtenir autrement qu'en hachant les fichiers réels. Cela établit que le fichier décrit fidèlement l'arbre du 08/09 ; **cela n'établit pas que cet arbre venait de la production**, seulement que le relevé et l'arbre sont cohérents. Je maintiens donc « attesté » au sens de *vérifiable et cohérent*, non au sens de *prouvé venir de la production*.
3812. **J'ai affaibli la thèse de la recopie sur un raisonnement de motif, pas sur une preuve.** Un prestataire qui aurait déployé l'adaptateur **puis** recopié son arbre local produirait exactement ce que j'observe. Mon § 0.2 dit « lecture la plus économe », pas « démontré ».
3823. **Sur le constat 11, j'abaisse une conclusion à `SUGGÉRÉ` en m'appuyant sur un parallèle** (`free_v3` n'est semée par rien et existe pourtant). Si l'on m'objectait que `free_v3` a pu être créée par un import manuel aujourd'hui disparu, mon parallèle perdrait de sa force — mais l'inférence attaquée resterait une inférence.
3834. **Je crédite `audit-argent` du meilleur rendu et je dois dire pourquoi ce n'est pas de la complaisance :** c'est le seul des quatre qui a cherché activement le fait qui l'aurait fait tomber (la collision de clé du cache, la sémantique de patch de `rows_update`) et qui a publié le résultat quand il l'innocentait. C'est le critère de la charte, pas une préférence.
3845. **Je n'ai invalidé aucun des 23 constats.** C'est inhabituel pour ce poste et je le signale plutôt que de chercher une victime : les quatre rendus ont borné correctement l'essentiel. Ce qui a cédé, ce sont **des chiffrages et des portées** — c'est-à-dire précisément ce qui se voit dans un livrable et ce qui se retourne en réunion.
385