fix(rpc): va-et-vient Get → Set cassé par l'union LM-300

GetLoadConfig sérialise via pack<LoadConfig>(), donc via le méta-objet : toutes les
propriétés déclarées sortent, y compris un « sgReady » VIDE sur une charge qui n'est pas
SG-Ready. Le schéma SetLoadConfig exigeait « states » dès que la clé était présente, et
nymea valide les paramètres AVANT le handler : lire puis réécrire une configuration
relay-router était donc impossible — c'est-à-dire le geste ordinaire de l'app.

C'est le mur que spec_loadmodel.md §3 annonçait à la frontière RPC, touché par un côté
qu'il n'avait pas prévu : les clés INTERNES d'une charge utile optionnelle doivent être
optionnelles elles aussi, sans quoi elles contraignent les charges des AUTRES mécanismes.
L'exigence réelle (states non vide, état 2 présent) reste tenue par isValid().

Constaté au banc à la première tentative réelle de SetLoadConfig. Aucun test ne rejouait
le va-et-vient de l'app ; testLoadConfigRpc §3bis le fait maintenant, et il a été vérifié
échouant sans le correctif.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Patrick Schurig 2026-08-10 06:32:32 +02:00
parent a1e6b0ce56
commit 8a4e73adce
2 changed files with 18 additions and 1 deletions

View File

@ -187,7 +187,13 @@ NymeaEnergyJsonHandler::NymeaEnergyJsonHandler(SpotMarketManager *spotMarketMana
sgStateItem.insert("relays", QVariantList() << enumValueName(String));
sgStateItem.insert("estimatedPowerW", enumValueName(Double));
QVariantMap sgItem;
sgItem.insert("states", QVariantList() << sgStateItem);
// « states » est OPTIONNEL ici, et c'est le mur documenté en LM-302 : GET sérialise la
// charge via le méta-objet, donc TOUTES les propriétés déclarées — y compris un sgReady
// VIDE pour un relay-router. Exiger « states » à l'intérieur casserait le va-et-vient
// Get → Set de l'app sur toute charge non SG-Ready. Défaut constaté au banc le
// 2026-08-10, sur la première tentative réelle de SetLoadConfig.
// L'exigence réelle (states non vide, état 2 présent) est tenue par LoadConfig::isValid().
sgItem.insert("o:states", QVariantList() << sgStateItem);
sgItem.insert("o:minStateHoldS", enumValueName(Uint));
loadItem.insert("o:sgReady", sgItem); // sg-ready (LM-300)
loadItem.insert("o:minOnS", enumValueName(Uint));

View File

@ -486,6 +486,17 @@ void Simulation::testLoadConfigRpc()
QCOMPARE(relays.last().toMap().value("powerW").toInt(), 1500);
QCOMPARE(relayGot.value("minOnS").toInt(), 60);
// 3bis. VA-ET-VIENT : réinjecter tel quel ce que GET vient de rendre. C'est le geste réel
// de l'app — lire, modifier un champ, réécrire — et il échouait : GET sérialise via le
// méta-objet, donc TOUTES les propriétés déclarées, y compris un « sgReady » VIDE sur une
// charge qui n'est pas SG-Ready. Le schéma SET exigeait « states » dès que la clé était
// présente, et nymea rejetait AVANT le handler. Constaté au banc le 2026-08-10, sur la
// première tentative réelle de SetLoadConfig — aucun test ne faisait ce va-et-vient.
resp = injectAndWait("NymeaEnergy.SetLoadConfig", {{"loadConfigs", got}});
QCOMPARE(resp.toMap().value("params").toMap().value("energyError").toString(), QString("EnergyErrorNoError"));
resp = injectAndWait("NymeaEnergy.GetLoadConfig");
QCOMPARE(resp.toMap().value("params").toMap().value("loadConfigs").toList().size(), 3);
// 4. Fichier persisté.
QVERIFY(QFileInfo::exists(cfgPath));