writeRelay() jetait le ThingActionInfo* : aucun acquittement, aucun retour
arrière, available codé en dur à true, et m_currentStage mis à jour comme si tout
avait réussi — on annonçait une puissance non appliquée.
MODÈLE ASYNCHRONE. executeAction est asynchrone et update() ne doit jamais
attendre (AGENTS règle 5). « Attendre le résultat » ne veut donc pas dire bloquer
le cycle : l'écriture est émise, l'adaptateur retient combien d'acquittements il
attend (m_pending), le verdict tombe quand le compteur retombe à zéro, et la
conséquence est traitée au cycle suivant. Motif repris tel quel d'EvCharger
(evcharger.cpp:293-299), y compris `this` en contexte de connexion — si
l'adaptateur meurt, les callbacks sont coupés proprement.
INDÉTERMINATION. Pendant une transition, telemetry() annonce
max(m_stagePrev, m_stageTarget) : on ne sait pas ce qui est fermé, on annonce donc
la plus haute des deux puissances possibles. Même direction qu'ECS-411 (relais
injoignable supposé fermé) — ne jamais annoncer moins que ce qui peut être
appliqué. Sous-estimer fait sur-allouer les charges suivantes ; surestimer ne fait
que retarder une montée.
ÉCHELLE BORNÉE à trois barreaux, une tentative chacun, aucune boucle : cible →
retour arrière → arrêt total → défaut. Le retour arrière est asynchrone au même
titre et passe par le même compteur.
DÉFAUT COLLANT, pas clignotant. m_faulted est un verrou posé une seule fois, sans
délai ni expiration : available ne peut pas osciller d'un cycle à l'autre. Seul
NymeaEnergy.ClearLoadFault le lève — acte délibéré et journalisé de l'opérateur.
La reconstruction le lève aussi, mais par construction : un adaptateur neuf n'a
pas d'historique. À la levée, l'état matériel est RELU (ECS-411), pas supposé.
CANAL OUVERT. LoadContext n'avait AUCUN champ available : le publier aurait été
décoratif. Ajouté à LoadContextTelemetry, avec sa sémantique écrite noir sur
blanc — il gouverne l'allocation, PAS la comptabilité. Une charge en défaut ne
reçoit rien mais reste comptée : une puissance qu'on ne sait plus couper est de la
conso fixe, au même titre que la base de la maison. Le figeage est porté par
lockMin == lockMax == puissance crue engagée, jamais un plafond nul sous un
plancher non nul. Le même canal servira ECS-601.
OPTIMIZER_PROTOCOL.md mis à jour dans le MÊME lot, comme l'exige le §11 de la
spec : available, lockMinPowerW et lockMaxPowerW documentés avec leur sémantique.
clearFault() est PURE VIRTUELLE sur ILoadAdapter : les trois autres adaptateurs la
déclarent sans effet plutôt que d'hériter d'un défaut vide. Leur généralisation est
portée par ECS-414.
Test testEcsPartialFailure : cas nominal sans défaut, puis échelle complète via un
relais introuvable — défaut atteint, relais valide bien ramené à l'ouverture par la
tentative d'arrêt total, available faux, plancher == plafond == puissance comptée,
plus aucune commande une heure plus tard, puis levée délibérée.
Build amd64 0 erreur. Simulation : 16/16.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>