# INVENTAIRE À FROID DU MOCK — ce qu'il rend structurellement introuvable *(Fait le 2026-09-01, après trois occurrences en trois jours d'un cas réel impossible à reproduire. Ce n'est plus un incident, c'est une propriété du banc d'essai.)* ## Pourquoi cet inventaire Trois fois cette semaine, un défaut réel s'est révélé **inatteignable par la suite** : | Occurrence | Ce qui était introuvable | Comment on l'a su | |---|---|---| | Correctif amont `calculateAllowanceAmpere` | un compteur publiant les courants **sans** les puissances par phase | le mock pose `currentPhaseB = currentPowerPhaseB / 230`, cohérent **par construction** | | Test `command` sous `source: none` | une charge dont rien ne mesure la puissance | **toutes** les classes pilotables portent `currentPower` | | Test `ECO_FLOOR_*` en régime `unmeasurable` | une borne sans `sessionEnergy` | il a fallu **ajouter** l'état à une seule classe pour avoir les deux cas | Le point commun : **le mock est plus homogène que la réalité.** Il a été construit pour faire marcher des scénarios, pas pour représenter la diversité du parc — et une suite verte ne dit alors rien sur les cas qu'il ne sait pas produire. ## Les classes, et ce qu'elles portent | Classe | `currentPower` | `totalEnergyConsumed` | `sessionEnergy` | |---|---|---|---| | `meter` | ✅ | ✅ | — | | `charger` | ✅ | ✅ | ❌ | | `chargerPhaseSwitching` | ✅ | ✅ | ✅ *(ajouté le 2026-08-31)* | | `simpleCharger` | ❌ | ❌ | ❌ | | `powerSwitch` | ✅ | ❌ | — | | `etmVariableLoad` | ✅ *(`currentPowerW`)* | ❌ | — | | `energyStorage` | ✅ | ❌ | — | | `car`, `notification` | ❌ | ❌ | — | **Ce que la table donne, et c'est la bonne nouvelle** : `sessionEnergy` et `totalEnergyConsumed` sont désormais **discriminants** — il existe des classes avec et sans. Les régimes `measured` / `unmeasurable` et les causes `noSessionEnergy` / `meterWithoutEnergy` sont donc exerçables. ## Ce qui reste STRUCTURELLEMENT introuvable **1. ~~`measurement.source: none` pour une charge saine.~~ — OUVERT le 2026-09-02.** La classe `dryRelay` a été ajoutée : elle commute, elle ne mesure pas. Une charge **saine et non mesurée** est désormais fabricable, et `testHealthyButUnmeasuredDiffersFromFaulty` vérifie ce que rien ne pouvait vérifier avant — que le moteur **distingue** « rien ne mesure cette charge » de « cette charge est en panne », deux affirmations que le seul chemin disponible (un Thing manquant) mélangeait. > **Ce que l'ajout a révélé au passage** : `executeAction()` déréférençait le contrôleur HTTP de > **chaque** Thing sans vérifier — le mock supposait que toute classe en possède un. Un relais sec > n'en a pas besoin, n'ayant rien à mesurer. Le plantage était immédiat et net ; l'hypothèse, elle, > était invisible tant qu'aucune classe ne la contredisait. **Le mock avait sa propre prémisse > fausse**, protégée par son homogénéité. **2. ~~Un compteur par phase incohérent.~~ — OUVERT le 2026-09-03.** `voltagePhaseX` et `currentPhaseX` sont désormais réglables **indépendamment** des puissances, par des paramètres OPTIONNELS — sans eux, le comportement dérivé d'origine est conservé et aucun scénario existant ne bouge. > **Et l'ouverture a corrigé une erreur de RAISONNEMENT, pas seulement comblé un trou.** Le > premier test de `LM-1210-a` attendait qu'une phase en export donne une marge *supérieure* au > plafond — c'est ce que le design affirmait. Il a échoué : `calculateAllowanceAmpere()` part du > plafond et ne fait que des `qMin`, donc la marge y est **écrêtée**. Physiquement juste — l'export > ne permet pas de tirer plus que le disjoncteur — mais la phrase du design était fausse. **La > conclusion tenait, la raison non**, et rien ne pouvait le révéler tant que le déséquilibre était > infabricable. **3. ~~`temperature`.~~ — OUVERT le 2026-09-05.** La classe `thermalProbe` porte l'état, et `testThermalProbeFeedsTelemetry` exerce enfin la branche « sonde présente » de `attachMeasurements()`, écrite depuis toujours et **jamais parcourue**. C'était la seule entrée du volet thermique, et rien ne l'exerçait. > **Ce qu'il reste à dire, et qui n'est pas corrigé ici** : une sonde DÉSIGNÉE mais incapable — > un Thing sans état `temperature` — produit exactement la même charge utile qu'aucune sonde du > tout (`temperatureC` absent). Le geste de réparation, lui, diffère : désigner une sonde, ou en > changer. C'est un silence de la famille `progressUnmeasurableCause`, relevé par le test et > laissé ouvert : le fermer demande un catalogue de causes thermiques qui n'existe pas encore. **4. ~~Une triphasée déséquilibrée.~~ — OUVERT le 2026-09-03.** `testWorstPhaseBindsWhenPhasesAreUnbalanced` exerce enfin `LM-1210-a` : phase B saturée à 30 A sous un plafond de 25 pendant que A et C exportent, et la maison **exporte globalement**. La marge retenue est celle de B — négative — et non celle du total, qui rassurerait à tort. Ce manque était nommé **deux fois par deux chemins indépendants** — cet inventaire et le balayage des contrôles — avant d'être comblé. C'est ce qui lui a donné sa priorité. ## La règle qui en sort > **Avant d'écrire un test qui exerce une ABSENCE, vérifier que le mock peut la produire.** > Une assertion sur un cas inatteignable ne passe pas : elle **ne s'exécute pas**, ce qui est > pire — elle compte comme verte. > > Le symptôme se reconnaît : **la contre-épreuve ne mord pas**. C'est le corollaire fin > d'`AGENTS.md` appliqué au banc d'essai plutôt qu'au code. **Et la parade tient en une ligne de test** : quand on itère pour trouver des cas, **compter combien on en a examinés**, et échouer si le compte est nul. C'est ce qui a rattrapé le test de `command`, et ce qui garde celui de la mesurabilité. **5. ~~Un compteur d'énergie FIGÉ.~~ — OUVERT le 2026-09-04.** `totalEnergyConsumed` n'était écrit par personne : il valait sa valeur par défaut, indéfiniment. **Un compteur figé est un compteur qui n'existe pas pour tout ce qui l'intègre** — `deliveredWh` restait à 0, `remainingWh` à 4 000, et l'étalement adaptatif n'était **jamais exercé**. Le comportement se confondait avec `unmeasurable` pour une raison entièrement différente et bénigne. Le mock intègre désormais `currentPower` dans le cumul, une fois par seconde. **En export il ne recule pas — il ne bouge pas** : un compteur de consommation est monotone, et le faire reculer produirait un cas que le terrain ne produit pas, que la garde de sémantique traiterait comme une anomalie. **Et `LM-1014-b` s'y applique, question posée et répondue** : `totalEnergyConsumed` est déclaré `cached`, donc il **continue** au redémarrage du mock. Repartir de zéro ferait reculer un cumul monotone — le mock fabriquerait l'anomalie que le moteur est censé détecter. **Quatrième occurrence.** Après les six états cohérents du `rootmeter`, les classes portant toutes une puissance, et la triphasée équilibrée. **6. ~~Le compteur sans énergie et la sonde thermique.~~ — OUVERTS le 2026-09-05**, et les deux derniers de la liste. `meterNoEnergy` remplace le `powerSwitch` détourné en instrument de mesure — un montage que personne ne fait, et qui faisait tenir DEUX rôles à la même Thing dans le même scénario. `thermalProbe` ouvre le volet thermique. > **Et l'ouverture a révélé un défaut, comme les trois précédentes.** `setupThing()` du mock > n'avait **aucune sortie par défaut** : une classe sans branche ne rencontrait jamais de > `info->finish()`. Le setup ne échouait pas — **il ne se terminait jamais**. Le symptôme est un > Thing éternellement « en cours de configuration » et un test qui expire ailleurs, très loin de > la cause. > > C'est le même mécanisme que le contrôleur déréférencé pour toute classe : **une hypothèse du > mock que son homogénéité protégeait.** Tant que chaque classe avait sa branche, l'absence de > sortie par défaut ne pouvait pas se voir. Corrigé : une classe oubliée se dit maintenant en > `qCWarning` et rend `ThingErrorSetupFailed`. > > **Et une garde a mordu au passage** : la première version du test thermique lisait `loads[]` > sans compteur racine — donc une liste vide, et trois assertions qui ne se seraient jamais > exécutées. `QCOMPARE(parId.count(), 3)` l'a attrapé. C'est la parade écrite plus haut dans ce > document, appliquée le jour même à son auteur. ## Ce qu'il faudrait ajouter, par ordre d'utilité 1. **Une classe pilotable SANS aucune puissance** — un relais sec. Débloque `source: none` sur une charge saine, et sépare enfin « non mesurée » de « en panne ». 2. ~~**Un compteur sans `totalEnergyConsumed`** en tant que classe déclarée.~~ — **FAIT le 2026-09-05** : `meterNoEnergy`. Elle ne déclare PAS l'interface `energymeter`, qui exige `totalEnergyConsumed` — promettre une interface qu'on ne tient pas ferait mentir le mock là où il doit être littéral. 3. **Une tension par phase réglable** indépendamment des puissances — pour exercer le correctif amont ailleurs qu'en inversant la fonction. > Aucune de ces trois n'est urgente. Ce qui l'est, c'est de **savoir qu'elles manquent** : sans > cette liste, chaque lot repaiera le même temps à découvrir qu'un cas est introuvable. --- ## 7. L'homogénéité qui FABRIQUE un cas — 2026-09-05 Les six premières entrées décrivent des cas que le banc ne savait pas **produire**. Celle-ci est l'inverse : un cas que le banc produit **en trop**, et qui n'existe pas ailleurs. `ECS-Meter` publie 1500 W en permanence, `PAC-Meter` 800 W. Ce sont des simulateurs à valeur fixe : ils ne suivent aucune consigne. Le journal de divergence a donc compté **14 épisodes en 24 h** qui ne sont pas des désobéissances, mais la distance entre une consigne variable et un compteur constant. **Un cas masqué se signale par un test qui ne mord pas. Un cas fabriqué se signale par des données qui rassurent** — et rien, dans un compte juste, n'annonce de quelle question il est la réponse. Le critère de tri est écrit avant les données : `docs/DESIGN_COMMANDE_PERDUE.md` §6. **Ce qu'il faudrait au mock, et qui manque** : un compteur dont la valeur SUIT la commande de la charge qu'il mesure. Aucune classe ne le fait aujourd'hui — les compteurs sont des sources indépendantes qu'on pilote à la main. Tant que c'est le cas, aucun test ne peut exercer une charge qui obéit, donc aucun ne peut distinguer l'obéissance de la désobéissance. > **Nommé DEUX FOIS par deux chemins indépendants, le 2026-09-05 et le 2026-09-06** — et c'est ce > qui lui donne sa priorité, comme au manque n° 4 avant lui. > > - Par les **traces de divergence** : quatorze épisodes qui mesurent l'écart entre une consigne > variable et un compteur constant, et non une désobéissance. > - Par la **pose d'une obligation éco** : sur un banc dont le compteur ne suit pas la commande, > on ne peut pas avoir à la fois une cible modeste et un chemin d'achat longuement exercé. Ce > qui borne n'est pas la cible, c'est la constance du compteur. > > Deux lots différents, deux symptômes sans rapport apparent, une seule cause. Quand un manque se > fait nommer deux fois sans qu'on l'ait cherché, il ne s'agit plus d'un confort de test : il > décide de ce que plusieurs lots pourront prouver. --- ## 8. Le sixième manque sous sa forme réelle — mesuré le 2026-09-08 Le §7 disait « des compteurs figés fabriquent de faux cas ». La fenêtre d'observation du 2026-09-08 (12:10 → 18:00, 292 points par charge) donne le chiffre, et il est plus net que la formulation : | charge | σ sur la fenêtre | min → max | |---|---|---| | **V2C Trydan** | **1 879 W** | 0 → 5 871 | | chauffe-eau | **0,0 W** | 0 → 0 | | pac-terrain | **0,0 W** | 0 → 0 | | Simulated wallbox | **0,0 W** | 0 → 0 | > **Aucun compteur du banc ne suit la commande de la charge qu'il mesure — sauf un.** Ce n'est pas « des instruments imprécis ». C'est **un seul instrument valide sur quatre**, et il faut le dire ainsi parce que la conséquence change de nature : toute conclusion tirée de ce banc sur l'**obéissance** des charges est une conclusion **sur cette borne**, jamais sur le moteur. **Le critère de tri du §6 de `DESIGN_COMMANDE_PERDUE.md` rend le manque visible ; il ne le comble pas.** Il écarte correctement les épisodes des trois compteurs constants — sans nommer personne, sur la seule propriété observable de la donnée. Mais un tri qui garde un instrument sur quatre a **trié**, il n'a pas **mesuré**. ### Ce qui débloque, et c'est daté De **vrais SDM120** sur la PAC et l'ECS, prévus en fin de semaine. Deux instruments de plus, et le critère aurait enfin quelque chose à trier. **C'est la condition d'existence de l'étape 4**, pas un confort : décider de rejouer une commande demande de savoir si des commandes se perdent, et cette question n'est pas posable sur un banc qui ne mesure qu'une charge.