docs: LM-1012/LM-1013, et R3 est bloquée par une destination qui n'en est pas une

LM-1013 — « grid » l'emporte dès qu'un watt est acheté, écrit comme RÈGLE et non
comme approximation. L'argument de l'agent app emporte la décision : tant qu'on
la décrit comme approximative, quelqu'un voudra la rendre exacte — part
dominante, seuil de majorité — et sur 900/480 le libellé basculerait d'un cycle à
l'autre sans qu'aucune décision n'ait changé. Déterministe, indépendante des
proportions, alignée sur le fait actionnable, et vraie sans décision nouvelle pour
le plancher éco du §10.

Pas de « mixed » publié, et c'est l'app qui l'a prouvé par son propre code : tout
client qui teste funding == "grid" a une branche par défaut, et cette branche est
« surplus ». Une troisième valeur y tomberait SANS ERREUR et rangerait au soleil
un niveau dont une part est achetée.

LM-1012 — on publie ce qui ne se dérive pas, on dérive ce qui se dérive. La ligne
est de l'app et elle vaut au-delà du cas : une valeur dérivée LOCALE est légitime
PARCE QU'elle est dérivable ; publiée, elle est illégitime POUR LA MÊME RAISON.
Elle explique après coup trois refus pris séparément — l'index d'ordre de
service, le « mixed », et stagesW qui est publié précisément parce que la
combinatoire n'est PAS dérivable côté client. Symétrique : rankOrigin est publié
parce que rien dans un rang ne dit qui l'a posé.

Ses trois demandes : clés opaques jamais réutilisées (engagement pris, même règle
que les numéros de règle retirés) ; counts publié même à zéro avec sa destination
(confirmé) ; et counts fait foi pour la COMPTABILITÉ, decision.params pour la
PHRASE du motif (sa lecture est la bonne).

MAIS R3 NE PEUT PAS ÊTRE ÉCRITE TELLE QUELLE, et c'est le résultat de la journée.
budget.evReservedW n'est pas un registre d'allocation : c'est un terme de
correction du budget, et il mélange la correction A — commandé non encore mesuré,
calculé AVANT la cascade — avec la part réseau d'EV_GRID_START, qui est une vraie
tranche d'allocation. Une borne servie au réseau qui tire déjà ce qu'on lui a
commandé y contribue 0 alors que son targetW vaut 7 000 W : Σ counts == targetW
serait vrai au premier cycle et faux ensuite. Ça passerait les tests et
dériverait en production.

Ce que R3 a mis au jour : les watts achetés n'ont AUCUN registre aujourd'hui.
draw.committedW devient un préalable à R3, pas une suite — petit, et sans effet
sur l'arithmétique, la ligne 355 étant de la publication seule. Ce qui change est
le SENS d'evReservedW, qui redevient une correction. Proposé à l'app, en attente :
c'est son identité de réconciliation.

Aucun code : counts n'est pas livré sur une base fausse.

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 08:29:24 +02:00
parent a305a0c4ee
commit 4e5af02d01
3 changed files with 137 additions and 9 deletions

View File

@ -345,12 +345,24 @@ en ligne, `funding` a disparu du niveau de la charge, et l'identité de réconci
dans `INTERFACE.md` — **vérifiée sur la vraie box** : Σ targetW(surplus) = 5 000 =
`budget.allocatedW`.
**PROCHAINE EXIGENCE : R3 — chaque watt dans exactement un compteur, mapping publié.** Trois
destinations coexisteront : `budget.allocatedW` (surplus), `budget.evReservedW` (réseau, proxy
VE), `draw.committedW` (réseau, plancher éco — n'existe pas encore). **`EV_GRID_START` la viole
déjà** : il partage une allocation entre deux compteurs, et sa contribution au surplus est
`decision.params.budgetW`, pas `targetW`. L'exception est **écrite** dans `INTERFACE.md`, elle
n'est pas **résolue** — c'est ce que R3 doit faire.
**PROCHAINE EXIGENCE : R3 — chaque watt dans exactement un compteur, mapping publié.**
**BLOQUÉE, et le blocage est un résultat** : une des trois destinations n'en est pas une.
> **`budget.evReservedW` n'est PAS un registre d'allocation.** C'est un **terme de correction du
> budget**, et il mélange deux natures : la correction A — commandé non encore mesuré, calculé
> *avant* la cascade (`rulebasedscheduler.cpp:90-106`) — et la part réseau d'`EV_GRID_START`,
> qui est une vraie tranche d'allocation (`:355`).
>
> Donc une borne servie au réseau qui tire déjà ce qu'on lui a commandé y contribue **0** alors
> que son `targetW` vaut 7 000 W : `Σ counts == targetW` serait **vrai au premier cycle et faux
> ensuite**. Ça passerait les tests et dériverait en production — la pire forme d'échec.
>
> **Ce que R3 a mis au jour** : les watts **achetés** n'ont aucun registre aujourd'hui.
> `draw.committedW` devient donc un **préalable** à R3, pas une suite. Il est petit — somme des
> `targetW` financés au réseau — et ne change **aucune arithmétique** : la ligne 355 est de la
> publication seule. Ce qui change est le **sens** d'`evReservedW`, qui redevient une correction.
> **Proposé à l'app** (brief §10), en attente de sa réponse : c'est son identité de
> réconciliation.
*Ancienne prochaine action, faite :*
**R2 — `funding` descend au NIVEAU.** C'est la seule des huit

View File

@ -700,9 +700,81 @@ choisir en connaissance de cause, et les deux ne dérivent pas l'un de l'autre
condition que ce soit dit**, sinon quelqu'un « simplifiera » un jour en supprimant celui qui a
l'air redondant.
Reste votre arbitrage sur le cas mixte : `funding` y garde-t-il un code unique (approximatif mais
lisible), ou faut-il l'y omettre et laisser `counts` parler seul ? **Nous penchons pour le code
unique** — il porte l'intention, que `counts` ne porte pas — mais c'est votre écran.
### Vos trois règles sont retenues, et deux corrigent la nôtre
**`grid` dès qu'un watt est acheté** — écrit comme **règle**, pas comme approximation
(`spec_loadmodel.md` **LM-1013**). Votre argument emporte la décision : tant qu'on la décrit
comme approximative, quelqu'un voudra la rendre exacte, et sur 900/480 le libellé basculerait
d'un cycle à l'autre sans qu'aucune décision n'ait changé.
**Pas de `"mixed"` publié** — et c'est vous qui l'avez prouvé, par votre propre code : tout
client qui teste `funding == "grid"` a une branche par défaut, et cette branche est « surplus ».
Une troisième valeur y tomberait **sans erreur** et rangerait au soleil un niveau dont une part
est achetée. La panne de l'omission, avec un champ présent en plus pour faire croire le
contraire.
**Et votre ligne est au contrat** (**LM-1012**) : *on publie ce qui ne se dérive pas, on dérive
ce qui se dérive.* Votre `mixed` est légitime **parce qu'**il est dérivable et local ; le nôtre
serait illégitime **pour la même raison**. Elle explique après coup trois refus qu'on avait pris
séparément — l'index d'ordre de service, votre `mixed`, et **`stagesW`**, qui est publié
précisément parce que la combinatoire des paliers **n'est pas** dérivable chez vous. Symétrique :
`rankOrigin` est publié parce que rien dans un rang ne dit qui l'a posé.
### Vos trois demandes : deux confirmées, une qui découvre un blocage
**1. Clés opaques, jamais réutilisées pour un sens nouveau — engagement pris.** Une clé
inconnue de votre version laisse `Σ counts == targetW` vérifiable et s'affiche « destination
inconnue », jamais zéro. Nous nous interdisons de recycler un identifiant : une clé retirée ne
revient pas, comme un numéro de règle retiré (`tools/rules-retired.txt`).
**2. `counts` publié même à zéro, avec sa destination — confirmé**, et votre mesure de 06:14 est
la bonne raison : un niveau à `targetW: 0` est bien publié, et `counts: {}` satisferait
l'identité en perdant la destination. Sur le cas le plus fréquent.
**3. Qui fait foi : votre lecture est la bonne, et nous l'écrivons.** `counts` fait foi pour la
**comptabilité** ; `decision.params` fait foi pour la **phrase du motif**. Ils ne disent pas la
même chose : `params.budgetW`/`gridW` expliquent *pourquoi* la charge démarre malgré un budget
insuffisant, `counts` dit *où* les watts sont comptés. Si les deux divergeaient un jour, c'est
`counts` qui décrit le bilan.
### ⚠️ Mais R3 ne peut pas être écrite telle quelle : une de ses trois destinations n'en est pas une
En câblant `counts`, nous avons dû regarder ce que chaque compteur reçoit vraiment. **`budget.evReservedW`
n'est pas un registre d'allocation.** C'est un **terme de correction du budget**, et il mélange
deux natures :
1. la **correction A** — Σ des puissances commandées **non encore mesurées** des bornes servies
au réseau, calculée *avant* la cascade (`rulebasedscheduler.cpp:90-106`) ;
2. la **part réseau d'`EV_GRID_START`**, qui est une vraie tranche d'allocation
(`rulebasedscheduler.cpp:355`).
**Conséquence directe pour vous** : une borne servie au réseau qui tire déjà ce qu'on lui a
commandé contribue `max(0, targetW − mesuré)` = **0** à `evReservedW`, alors que son `targetW`
vaut 7 000 W. `Σ counts == targetW` serait donc **faux** — et faux de la pire façon : **vrai au
premier cycle** après un changement de consigne, faux ensuite. Ça passerait nos tests et
dériverait chez vos clients.
**Nous ne livrerons pas `counts` sur cette base.** Ce que R3 a réellement mis au jour, c'est que
**les watts achetés n'ont aucun registre aujourd'hui** : `evReservedW` corrige le budget de
surplus, il ne comptabilise pas un achat. Leur compteur, c'est `draw.committedW` — celui que
votre maquette place au §10 et qui n'existe pas encore.
**Donc `draw.committedW` devient un préalable à R3, pas une suite.** Il est petit : la somme des
`targetW` financés au réseau, publiée à côté de `budget`. Il ne change **aucune arithmétique** —
nous avons vérifié que la ligne 355 est de la **publication seule**, sans retour dans le calcul du
budget. Ce qui change est le **sens** d'`evReservedW`, qui redevient ce qu'il est : une
correction, pas un compteur.
**Votre décision, parce que c'est votre identité de réconciliation** — nous proposons :
| Destination | Ce qu'elle compte |
|---|---|
| `budget.allocatedW` | les watts **de surplus** alloués par la cascade |
| `draw.committedW` | les watts **achetés au réseau**, quelle qu'en soit la raison |
| `budget.evReservedW` | **cesse d'être une destination** — redevient une correction du budget, et le contrat le dira |
Avec cette forme, `Σ counts == targetW` tient **sans exception**, `EV_GRID_START` comprise
(900 → `allocatedW`, 480 → `draw.committedW`). Dites-nous, et nous l'écrivons dans la foulée.
> Et deux fils antérieurs de votre brief sont **clos**, au cas où ils ne vous seraient pas
> parvenus : l'**entrelacement des domaines** est tranché — grouper à l'affichage, **jamais à

View File

@ -879,6 +879,50 @@ motif faux envoie diagnostiquer le mauvais problème, avec l'autorité d'une exp
> elle est en aval de la coupure, elle n'est pas la coupure. Sans cette précédence, deux charges
> successives auraient produit deux points de rupture concurrents dans le même cycle.
**LM-1012 — On PUBLIE ce qui ne se dérive pas ; on DÉRIVE ce qui se dérive.** *(Ligne tracée par
l'agent app le 2026-08-30. Elle vaut bien au-delà du §10, et elle explique après coup plusieurs
refus qu'on avait pris un par un.)*
La légitimité d'une valeur **dérivée** ne dépend pas de son utilité, mais de **l'endroit où elle
vit** :
- **dérivée, locale, jamais transportée** → légitime, et **précisément parce qu'**elle est
dérivable : chaque lecteur la recalcule, elle ne peut pas mentir.
- **dérivée et PUBLIÉE** → illégitime, **pour la même raison** : elle devient une seconde source
de vérité pour un fait qui en a déjà une, et les deux divergeront.
C'est ce qui distingue un `"mixed"` calculé côté client — parfaitement légitime, il se relit à
chaque affichage — d'un `"mixed"` publié par le moteur, qui serait une valeur à maintenir en
accord avec une ventilation qui, elle, fait foi.
> **Elle couvre, après coup, trois refus déjà pris séparément** : l'**index d'ordre de service**
> (dérivable d'un tri sur `(niveau, priority)`), le **`"mixed"` de `funding`** (dérivable de
> `counts`), et le **`stagesW`** publié — qui, lui, est publié *parce que* la table de paliers
> n'est PAS dérivable côté client, la combinatoire vivant dans l'adaptateur.
>
> **Et son symétrique** : `rankOrigin` est publié parce qu'il **ne se dérive pas** — rien dans un
> rang ne dit qui l'a posé.
**LM-1013 — `grid` l'emporte dès qu'un seul watt est acheté.** *(Règle posée par l'agent app le
2026-08-30, retenue telle quelle : c'est une RÈGLE, pas une approximation, et la nuance décide.)*
Le financement d'un niveau vaut `"grid"` **dès qu'une part, si petite soit-elle, est achetée au
réseau** ; `"surplus"` seulement si la totalité vient du surplus.
**Pourquoi ce n'est pas « une approximation qu'on assume »** : tant qu'on la décrit ainsi,
quelqu'un voudra un jour la rendre *exacte* — part dominante, seuil de majorité. Sur un niveau à
900 W de surplus et 480 W de réseau, un tel libellé **basculerait d'un cycle à l'autre au gré des
proportions**, sans qu'aucune décision n'ait changé. La règle « un watt acheté suffit » est
**déterministe, indépendante des proportions**, alignée sur le fait actionnable — *de l'argent
a-t-il été dépensé ?* — et elle vaudra **sans décision nouvelle** pour le plancher éco du §10.
> **Et il n'y aura pas de troisième valeur `"mixed"`.** L'argument est celui de l'app, et il est
> décisif : **tout client qui teste `funding == "grid"` a une branche par défaut, et cette
> branche est « surplus »**. Une valeur inconnue y tomberait **sans erreur**, et rangerait au
> soleil un niveau dont une part est achetée — c'est la panne de l'omission, avec un champ
> présent en plus pour faire croire le contraire. Le client qui veut la nuance la **dérive** de
> `counts` (LM-1012).
**LM-1007 — Compatibilité descendante, et elle est gratuite.** Une charge qui ne déclare
**aucun** besoin éco ne participe qu'à la passe 2 — soit exactement le comportement
d'aujourd'hui. Le modèle à deux niveaux **dégrade proprement** vers le modèle actuel, ce qui