La classe `dryRelay` commute et ne mesure pas. Elle ouvre le cas que l'inventaire à froid avait identifié comme structurellement introuvable : une charge SAINE et NON MESURÉE. Jusqu'ici `measurement.source: none` n'était atteignable qu'avec un Thing manquant — ce qui traîne `available: false` et mélange deux affirmations distinctes, « rien ne mesure cette charge » et « cette charge est en panne ». Un relais sec est banal sur le terrain, et parfaitement sain. Le test vérifie ce que rien ne pouvait vérifier avant : deux charges, même source: none, available opposés, faultCode sur la seule en défaut. Il n'aurait pas pu s'écrire hier. Et l'ajout a révélé que le mock avait SA PROPRE prémisse fausse : executeAction() déréférençait le contrôleur HTTP de chaque Thing sans vérifier, supposant que toute classe en possède un. Un relais sec n'en a pas besoin. Le plantage fut immédiat et net ; l'hypothèse, elle, était invisible tant qu'aucune classe ne la contredisait — l'homogénéité du mock la protégeait, exactement comme elle masquait les cas qu'il ne savait pas produire. Ajouter de la diversité au banc n'a pas seulement ouvert un test : ça a trouvé un défaut. AGENTS.md reçoit la règle générale sur les identités : une identité dont les deux termes viennent de la même source ne vérifie rien. Elle est vraie par construction et rassure là où rien ne protège — la question n'est pas « tient-elle ? » mais « de quoi parle-t-elle vraiment ? ». Et c'est le même défaut que la contre-épreuve qui ne mord pas, vu de l'autre côté : perturber une grandeur qui ne pouvait rien changer, et vérifier une identité qui se referme sur elle-même, sont deux gestes de vérification qui ne touchent rien. Le réflexe commun : demander ce que ce contrôle aurait attrapé s'il avait échoué. S'il n'y a pas de réponse, il ne contrôle rien. Suite : simulation 62/62, loadmodel 22/22, charging 48/48 identique à la référence, spotmarket 32/32, doxygen 0 avertissement. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015F7G5VeaPVSMeVNjiGj36p
4.9 KiB
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. — OUVERT le 2026-09-02. La classe
measurement.source: none pour une charge saine.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. currentPhaseX et currentPowerPhaseX sont posés
ensemble par le mock, dans un rapport fixe de 230. Aucun scénario ne peut produire une tension
réelle ≠ 230 V, ni un compteur qui ne publierait qu'une des deux familles.
3. temperature. Le code teste hasState("temperature") et aucune classe du mock ne le
porte : la branche « sonde présente » n'a jamais été exécutée. Elle est écrite, elle n'est pas
testée.
4. Une borne triphasée à phases déséquilibrées. Les états existent, mais aucun helper de test
ne les pose indépendamment — d'où le pessimisme de LM-1210-a qui n'a jamais été exercé sur un
vrai déséquilibre.
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.mdappliqué 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é.
Ce qu'il faudrait ajouter, par ordre d'utilité
- Une classe pilotable SANS aucune puissance — un relais sec. Débloque
source: nonesur une charge saine, et sépare enfin « non mesurée » de « en panne ». - Un compteur sans
totalEnergyConsumeden tant que classe déclarée, plutôt qu'unpowerSwitchdétourné comme aujourd'hui. - 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.