docs: measuredSince dérive au changement de fuseau, et l'inertie saine

DÉFAUT MESURÉ, non corrigé. Bascule Europe/Paris → Europe/London à 13:37:26Z,
sans redémarrage de nymead :

  periodStartedAt : 1788645600 → 1788649200  (+3600 s)  CORRECT, la journée suit
  measuredSince   : 1788645607 → 1788649207  (+3600 s)  FAUX, un instant absolu
                                                        ne bouge pas

Cause : `m_baselinePosee = now` conserve le QDateTime en spécification
Qt::LocalTime, qui garde des composantes d'horloge murale et résout son époque au
moment de la CONVERSION. Après bascule, la même lecture « 00:00:07 » rend une
époque différente d'une heure.

Le champ posé vendredi pour rendre une couture lisible est lui-même déplacé par la
couture. Correctif : `m_baselinePosee = now.toUTC()`, une ligne — un instant
absolu se stocke en UTC, là où periodStartedAt doit rester construit dans le
fuseau courant puisque c'est une frontière locale. Les deux champs n'ont pas la
même nature et le stockage doit le refléter. Non appliqué : le déploiement
redémarrerait nymead et reposerait la baseline pendant la fenêtre de capture de
l'app.

EV_MIN_GRID NE SERA PAS VÉRIFIABLE SUR MACHINE (DESIGN_MIN_RESEAU §8), et c'est
écrit avant plutôt que découvert en mesurant : la voiture est débranchée,
pluggedIn retombe, la borne sort de loads[] — vérifié, il ne reste que chauffe-eau
et pac-terrain. Le lot tiendra sur ses tests et sa contre-épreuve. Trois
préconditions cumulatives pour lever la réserve, aucune à notre main aujourd'hui.

L'INERTIE SAINE, distinguée (AGENTS.md). Tout ce qui précède traite une
contre-épreuve inerte comme un signal d'alarme ; il existe un cas où elle est
correcte — le mécanisme tire sa valeur du CONTEXTE, pas du champ perturbé. Le
plancher du minimum réseau vient de la borne, pas du champ déclaré.

Le test qui sépare : « qu'est-ce qui déciderait, si ce n'est pas ça ? » Inertie
malsaine, on ne sait pas nommer l'autre source. Inertie saine, on la nomme ET on
peut la perturber elle. D'où la conséquence pratique : une inertie saine n'est
acceptable que si la contre-épreuve est DÉPLACÉE sur la vraie source, jamais
abandonnée — sinon c'est refermer l'identité sur elle-même une fois de plus.
This commit is contained in:
Patrick Schurig 2026-09-06 15:41:06 +02:00
parent 80a19a19dd
commit e1079760e7
3 changed files with 65 additions and 0 deletions

View File

@ -1154,6 +1154,30 @@ aucune, la décision est une intention, et elle se périmera à la vitesse du co
> cran plus fin que « vérifier le test échouant » : **on peut voir un test échouer pour la
> mauvaise raison, et on peut le voir passer pour une raison qui n'est pas la sienne.**
> ### Et le cas INVERSE : une contre-épreuve inerte qui n'est PAS un défaut (2026-09-06)
>
> Tout ce qui précède traite l'inertie comme un signal d'alarme. Il existe un cas où elle est
> **saine**, et il faut savoir le distinguer — sinon on ouvre une chasse au défaut sur du code
> correct, ce qui coûte deux fois : le temps, et la confiance dans le signal.
>
> **Le cas** : le mécanisme tire sa valeur du CONTEXTE, pas de la valeur qu'on perturbe. Perturber
> le champ ne peut alors rien changer, non pas parce que le chemin est mort, mais parce que le
> champ n'est pas ce qui décide. Relevé sur le minimum réseau : le plancher vient de la borne
> (`6 A × phases × tension`), pas du champ déclaré — donc modifier le champ laisse le plancher
> intact, et c'est correct.
>
> **Le test qui sépare les deux**, et c'est le même que partout : *qu'est-ce qui déciderait, si ce
> n'est pas ça ?*
>
> - **Inertie MALSAINE** : on ne sait pas nommer ce qui décide à la place → le chemin est
> probablement mort, ou l'assertion est garantie par un invariant qu'on n'a pas identifié.
> - **Inertie SAINE** : on nomme précisément l'autre source, et on peut la perturber ELLE pour
> voir la contre-épreuve mordre.
>
> **La conséquence pratique** : une inertie saine n'est acceptable que si la contre-épreuve est
> **déplacée sur la vraie source**, jamais abandonnée. Écrire « ne mord pas, c'est normal » sans
> exhiber le contrôle qui mord, c'est refermer l'identité sur elle-même une fois de plus.
> **Un troisième mode d'érosion, et il est le SYMÉTRIQUE des deux autres (2026-08-31).** Le
> garde-fou dit « une règle que rien ne rejoue se périme ». Voici l'inverse : **une règle rendue
> IMMORTELLE par sa propre garde.**

View File

@ -1350,6 +1350,29 @@ se distingue pas d'une panne.
>
> Même paire, même raison que `progress.measuredSince` / `targetWh` de l'avancement éco.
> #### ⚠️ DÉFAUT CONNU — `measuredSince` DÉRIVE au changement de fuseau (2026-09-06)
>
> **Mesuré, non corrigé à ce jour.** Bascule `Europe/Paris` → `Europe/London` à `13:37:26Z`,
> sans redémarrage de `nymead` :
>
> ```
> periodStartedAt : 1788645600 → 1788649200 (+3600 s) ← CORRECT, la journée suit le fuseau
> measuredSince : 1788645607 → 1788649207 (+3600 s) ← FAUX, un instant absolu ne bouge pas
> ```
>
> **Cause** : `m_baselinePosee = now` conserve le `QDateTime` tel quel, en spécification
> `Qt::LocalTime`. Un tel objet garde des composantes d'horloge murale et résout son époque
> **au moment de la conversion**, avec le fuseau courant. Après une bascule, la même lecture
> « 00:00:07 » rend une époque différente d'une heure.
>
> **Conséquence** : le champ affirme que la baseline a été posée une heure plus tard qu'en
> réalité. Le champ posé pour rendre une couture lisible **est lui-même déplacé par la couture**.
>
> **Correctif** : `m_baselinePosee = now.toUTC()` — une ligne. Un instant absolu se stocke en
> UTC ; `periodStartedAt`, lui, doit rester construit dans le fuseau courant, puisque c'est
> précisément une frontière locale. Les deux champs n'ont pas la même nature, et le stockage doit
> le refléter.
> #### Ce que `timeZone` rend possible, et qui n'existait pas
>
> La période de reporting est la **troisième nature d'heure murale** du système, après l'habitude

View File

@ -212,3 +212,21 @@ ne peut pas exercer le cas mixte, celui où le minimum complète un surplus part
Il l'exercera en revanche très bien sur une charge en watts — le chauffe-eau — dès qu'elle aura un
compteur réel (`TEST_TERRAIN.md` §12). **C'est la même précondition que pour l'étape 4 de la
commande perdue**, et elle est maintenant nommée par un quatrième chemin.
## 8. Ce lot ne sera PAS vérifiable sur machine — écrit avant, pas découvert en mesurant
La voiture a été débranchée le 2026-09-06. `pluggedIn` retombe, la borne **sort de `loads[]`**
(vérifié : `['chauffe-eau', 'pac-terrain']`), et rien ne la commande plus.
**Donc `EV_MIN_GRID` tiendra sur ses tests unitaires et sa contre-épreuve, pas sur un relevé.**
C'est acceptable *à condition de le dire maintenant* : le découvrir au moment de mesurer ferait
prendre une absence de mesure pour une absence de défaut.
Ce qu'il faudra pour lever la réserve, et c'est cumulatif :
1. une voiture branchée — trivial, mais elle décide de tout le reste ;
2. un lien V2C sain — trois semaines que c'est le blocage (`PORTING_STATUS.md`) ;
3. et pour le **cas mixte** — minimum complété par un surplus partiel — une charge en watts avec
un compteur réel, parce que 2 kWc contre un plancher de 4 140 W ne le produira jamais.
Les trois sont indépendantes, et aucune n'est à notre main aujourd'hui.