diff --git a/specs/spec_ecs.md b/specs/spec_ecs.md index 3732c66..15be2b9 100644 --- a/specs/spec_ecs.md +++ b/specs/spec_ecs.md @@ -1,6 +1,6 @@ # SPEC — ECS multi-palier -Version : 0.5.2 +Version : 0.5.3 Dépôt : `etm-powersync-energy-plugin-etm` (experience-plugin ems) Branche : `feature/beta-rulebased` Contrats faisant autorité : `docs/OPTIMIZER_PROTOCOL.md`, `docs/SAFETY.md`, `AGENTS.md` @@ -428,6 +428,46 @@ il est simplement ignoré. (`relayrouter.h:106`) et aucun état de relais n'est relu. Le palier courant DOIT être déduit de l'état réel des Things. +**ECS-413 — Désactivation en état sûr.** Désactiver une charge (`enabled: false`) +ou la retirer de la configuration DOIT laisser son matériel dans l'**état sûr +défini par son adaptateur**, et non dans son dernier état commandé. L'état sûr est +appliqué **avant** la destruction de l'adaptateur ; si l'écriture échoue, +l'échelle d'ECS-410 s'applique. + +Constaté au banc le 2026-08-09 : un `SetLoadConfig` posant `enabled: false` sur une +charge alors au palier 3500 W a détruit l'adaptateur en laissant **les trois relais +fermés**. Plus personne ne les commandait ; ils y seraient restés indéfiniment, +juste avant une intervention de câblage. + +**L'état sûr est propre à chaque adaptateur** — une exigence formulée « tout +couper » serait FAUSSE : + +| Adaptateur | État sûr | +|---|---| +| `RelayRouter` | tous relais **ouverts** | +| `EtmVariableLoadAdapter` | consigne **0 W** | +| `SgReadyAdapter` | **état 2** (normal, mains off) — **jamais** l'état 1 (blocage), cf. `docs/SAFETY.md` | + +Couper une PAC en la bloquant serait une régression de sécurité, pas une mise en +sécurité. + +**Mécanisme.** L'application passe par le **chemin d'action normal avec +`force = true`**, comme le mode dégradé L2 (`energyarbitrator.cpp:339-347`) : +c'est le mécanisme qui existe déjà pour contourner les verrous dans une situation +où l'arrêt prime. Rien de nouveau à inventer. + +> **Cette exigence NE S'APPLIQUE PAS à l'arrêt du plugin ni à un redémarrage de +> `nymead`.** Là, l'état DOIT être conservé : c'est précisément ce qu'ECS-411 relit +> au démarrage, et couper l'eau chaude à chaque redémarrage de service serait une +> régression. La distinction est **intentionnelle** — la désactivation est un acte +> délibéré de l'opérateur, un redémarrage n'en est pas un. + +**Test : `testEcsDisableLeavesSafeState`.** Charge à un palier non nul, +`SetLoadConfig` avec `enabled: false` ; vérifier que les relais sont **ouverts** et +que l'adaptateur n'est détruit qu'**ensuite**. Second cas obligatoire : un simple +`rebuild` **sans** désactivation NE DOIT PAS déclencher la mise en sécurité — sans +quoi ECS-412 serait annulé, chaque changement de rang coupant la charge. + --- ## §6 — Configuration @@ -550,6 +590,7 @@ mécanique reste **L0**. Perte de sonde → mode sans sonde, pas arrêt d'urgenc | ECS-304 | unitaire | `testEcsLevelsPure` | | ECS-410 | simulation | `testEcsPartialFailure` | | ECS-411 | simulation | `testEcsRestartRecovery` | +| ECS-413 | simulation | `testEcsDisableLeavesSafeState` — + cas négatif : un rebuild sans désactivation ne coupe pas | | ECS-110, ECS-111 | unitaire | `testEcsConfigValidation` — DOIT s'exécuter aussi en build release (`QT_NO_DEBUG`), sinon il ne prouve rien du binaire livré | | ECS-501, ECS-502 | simulation | `testEcsStageFault` | | ECS-601, ECS-602 | simulation | `testEcsTemperatureTarget` | @@ -620,6 +661,7 @@ protocole dans le même lot.** | 2026-08-08 | **Étape 1 OUVERTE** — trois exigences : ECS-306, ECS-411, ECS-412 | | 2026-08-08 | ECS-412 : au démarrage à froid, le verrou est **ARMÉ** (défaut sûr), jamais purgé | | 2026-08-09 | ECS-412 précisé : cet armement est **TRANSITOIRE** et posé paresseusement au premier `now`. Défaut de blocage circulaire constaté **au banc**, corrigé, couvert par `testEcsColdStartLockExpires` | +| 2026-08-09 | **ECS-413 créé** — `enabled: false` laissait les relais fermés (constat de banc, juste avant une intervention de câblage). État sûr propre à chaque adaptateur ; ne s'applique PAS à un redémarrage de `nymead`, qu'ECS-411 couvre | | 2026-08-08 | ~~Câblage 500/1000/2000~~ — **donnée fausse**, corrigée le jour même (cf. ligne suivante) | | 2026-08-08 | ~~Câblage 500/1000/1500 (source simulateur)~~ — **donnée fausse** : le simulateur ne reflète pas l'installation | | 2026-08-08 | ~~ECS-302 exercé~~ — découlait de la donnée fausse |