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
88 lines
4.9 KiB
Markdown
88 lines
4.9 KiB
Markdown
# 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.** `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.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é.
|
|
|
|
## 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, plutôt qu'un
|
|
`powerSwitch` détourné comme aujourd'hui.
|
|
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.
|