test: l'absence de draw.authorisedW est ÉPINGLÉE, plus seulement écrite

« C'est le genre de champ qu'on ajoute pour compléter, six mois plus tard, sans
se rappeler pourquoi il manquait. » — et un contrat ne fait échouer aucune
compilation. L'absence était défendue en prose dans INTERFACE.md, dans le
changelog et dans un commentaire de code : trois endroits qui n'arrêtent
personne.

testLoadTelemetryRpc épingle désormais la FORME de `draw` : exactement une clé,
`committedW`. Les quatre du §14a — authorisedW, remainingW, binding,
perPhaseBound — sont absents, et leur ajout casse le test avec la raison.

VÉRIFIÉ EN L'AJOUTANT : `draw.insert("authorisedW", 0.0)` « pour compléter
l'objet » fait échouer le test — 2 clés au lieu d'1. C'est la contre-épreuve qui
compte ici : un test d'absence qui ne casserait pas sur l'ajout ne protégerait
rien.

Le commentaire dit quoi faire quand il casse, parce que le premier réflexe devant
un test rouge est de corriger le test : ce n'est pas lui qu'il faut changer,
c'est le champ ajouté qui doit arriver AVEC son transport §14a et une valeur
vraie — jamais à zéro. Un `authorisedW: 0` assorti d'un `binding` par défaut
afficherait un plafond de soutirage qui n'existe pas, avec sa source.

Épinglé aussi en mode dégradé : `draw` y est absent comme `budget`, pour la même
raison — sans plan, il n'y a pas de soutirage DÉCIDÉ, et un `committedW: 0` s'y
lirait « ce cycle n'a rien acheté » alors qu'il n'y a pas eu de cycle.

Aucun changement de comportement. simulation 33/33.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015F7G5VeaPVSMeVNjiGj36p
This commit is contained in:
Patrick Schurig 2026-08-30 09:17:12 +02:00
parent 4576b3bd96
commit 970a7de531

View File

@ -4285,6 +4285,20 @@ void Simulation::testLoadTelemetryRpc()
QCOMPARE(qRound(budget.value("evReservedW").toDouble()), 0);
QCOMPARE(qRound(budget.value("allocatedW").toDouble()), 5600);
QCOMPARE(qRound(budget.value("remainingW").toDouble()), 1400);
// ---- L'ABSENCE de `draw` se défend, parce que rien ne casse quand on la comble -----------
//
// `draw` ne porte qu'un champ : `committedW`. Les quatre autres du §14a — authorisedW,
// remainingW, binding, perPhaseBound — sont ABSENTS, jamais à 0. Le contrat le dit, mais un
// contrat ne fait échouer aucune compilation : le jour où quelqu'un ajoutera `authorisedW:
// 0` « pour compléter l'objet », six mois plus tard, rien ne le retiendra — et l'app
// affichera un plafond de soutirage qui n'existe pas, avec sa source, faute d'avoir un
// moyen de distinguer « pas de plafond » de « plafond nul ».
//
// Ce test est ce moyen. S'il casse, ce n'est pas lui qu'il faut corriger : c'est le champ
// ajouté qui doit arriver AVEC son transport §14a et une valeur vraie, pas à zéro.
const QVariantMap draw = notif.value("draw").toMap();
QCOMPARE(draw.keys(), QStringList{QStringLiteral("committedW")});
// Le relais tire déjà ses 1000 W depuis le cycle précédent : ils lui sont RECRÉDITÉS avant
// arrondi (anti-clignotement). Sans ce terme publié, l'identité serait fausse ici même.
QCOMPARE(qRound(budget.value("recreditedW").toDouble()), 1000);
@ -4442,6 +4456,10 @@ void Simulation::testLoadTelemetryRpc()
// budget ABSENT : la planification est suspendue, il n'y a pas de budget pour ce cycle.
// Publier des zéros se lirait « arbitrage exécuté, rien à allouer » — l'inverse du vrai.
QVERIFY2(!dgr.contains("budget"), "budget publié en mode dégradé (planification suspendue)");
// Et `draw` avec lui, pour la même raison : sans plan, il n'y a pas de soutirage DÉCIDÉ.
// Un `committedW: 0` s'y lirait « ce cycle n'a rien acheté », alors qu'il n'y a pas eu de
// cycle du tout.
QVERIFY2(!dgr.contains("draw"), "draw publié en mode dégradé (aucun soutirage décidé)");
QVERIFY(!dgr.value("loads").toList().isEmpty());
QVariantMap dgrParId;
for (const QVariant &v : dgr.value("loads").toList()) {