docs: LM-1011 dans la spec, les trois nombres au contrat, R3 proposée

TROIS choses, plus une sauvegarde.

1. LM-1011 — la précédence BELOW_MIN_POWER / DRAW_CAP est dans la SPEC. Je
   l'avais écrite hier dans DESIGN_10.md : une note de conception se périme, la
   spec fait autorité, et l'index ne voyait donc pas la règle. DESIGN_10 n'en
   garde qu'un renvoi.

   Autorisation résiduelle non nulle mais sous le plus petit palier →
   BELOW_MIN_POWER ; DRAW_CAP réservé au résiduel nul. Ce n'est pas une règle
   nouvelle : c'est celle de +etm19 transposée à une seconde ressource — le
   surplus a déjà SURPLUS_INSUFFICIENT (« il n'y a rien ») contre BELOW_MIN_POWER
   (« il y en a, mais pas au bon format »), et il aurait été absurde qu'une
   ressource se lise autrement que l'autre. Ce qui décide n'est pas la véracité
   mais le GESTE appelé : revoir le plafond, ou lire la fiche technique.

   Conséquence de l'agent app retenue : le point de rupture de l'écran 2 est
   ancré sur le DRAW_CAP, donc sur l'épuisement réel. Une charge refusée APRÈS
   lui pour cause de palier ne le déplace pas — sans quoi deux charges
   successives produiraient deux points de rupture concurrents dans un cycle.

2. LES TROIS NOMBRES au contrat — décidé, appliqué, mesuré. Ma correction sur
   allocatedW a fait apparaître un troisième nombre que ni la maquette ni nous
   n'avions nommé. INTERFACE.md porte le tableau ET ce que chaque écart signifie,
   parce que c'est là qu'un écran se trompe de diagnostic : décidé ≠ appliqué est
   une affaire de FORMAT, appliqué ≠ mesuré une affaire d'OBÉISSANCE — et cette
   seconde lecture dépend encore de measurement.source.

3. R3 proposée à l'app avant d'être écrite : funding dit d'où viennent les watts,
   il ne dit pas dans quel compteur ils tombent. Un niveau publierait sa
   ventilation (counts), Σ counts == targetW toujours, et l'identité devient
   calculable SANS exception — y compris quand draw.committedW arrivera.

Et la PRÉCONDITION du §10 est consignée : il ne part pas sans l'extension du
délestage au waterfall (LM-1006 couplage 1). La protection de surcharge ne
déleste aujourd'hui que les bornes ; livrer l'éco sans elle donnerait un plancher
capable de faire disjoncter une installation.

Aucun code. ci-quality 3/3.

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:12:26 +02:00
parent df20be1404
commit b6a6c7eebc
5 changed files with 128 additions and 27 deletions

View File

@ -333,7 +333,27 @@ actifs.
est tranché. Décisions et exigences : `specs/spec_loadmodel.md` **LM-1010**. Précédence des
motifs et réponse à R4 : `docs/DESIGN_10.md`.
**PROCHAINE ACTION DU §10 : R2 — `funding` descend au NIVEAU.** C'est la seule des huit
> ⚠️ **PRÉCONDITION DU §10, et elle n'est pas dans la maquette** *(Patrick, 2026-08-30)* : **le
> §10 ne part pas sans l'extension du DÉLESTAGE au waterfall.** C'est LM-1006 couplage 1 — si
> tous les planchers éco se servent au réseau le même matin sans soleil, ils s'additionnent sur
> le disjoncteur de branchement. Or la protection de surcharge d'aujourd'hui ne déleste **que
> les bornes**. Éco et délestage sont **le même lot** ; les séparer livrerait un plancher
> capable de faire disjoncter une installation.
**FAIT — R2 : `funding` est descendu au NIVEAU** (`1.15.2+etm30`, posé sur `.75`). `levels[]` est
en ligne, `funding` a disparu du niveau de la charge, et l'identité de réconciliation est écrite
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.
*Ancienne prochaine action, faite :*
**R2 — `funding` descend au NIVEAU.** C'est la seule des huit
exigences qui soit un **changement de forme d'un champ existant** et non un ajout : chaque écran
qui s'appuiera d'ici là sur un `funding` par charge en augmentera le coût de déplacement, les
sept autres attendent sans se renchérir. Au §10, la même charge sera financée **au réseau pour

View File

@ -593,6 +593,38 @@ le cas normal.
> niveau il parle (`decision.level`, R7). On conserve un champ hérité **quand on peut en dire
> quelque chose de vrai**, jamais par symétrie.
### Les TROIS nombres d'une charge — décidé, appliqué, mesuré
*(Rendu explicite le 2026-08-30, après qu'une prémisse de la maquette app eut confondu les deux
premiers. Trois nombres coexistent pour une même charge dans un même cycle, ils disent trois
choses différentes, et **aucun ne se déduit d'un autre**.)*
| | Où | Ce qu'il dit | Ce qu'un écart avec le suivant signifie |
|---|---|---|---|
| **DÉCIDÉ** | `allocatedW`, et `levels[].targetW` | ce que la cascade a **alloué** | → l'**arrondi** du mécanisme : 1 400 W décidés tombent sur un palier de 1 500 W |
| **APPLIQUÉ** | `mechanism.stageW` · `setpointW` · `state` · `currentA` | ce qui a été **commandé au matériel** | → la charge **n'obéit pas** : véhicule qui refuse, thermostat ouvert, verrou qui a mordu |
| **MESURÉ** | `measurement.powerW` (avec sa `source`) | ce qu'un appareil ou un compteur a **vu** | — |
> ⚠️ **`allocatedW` est du DÉCIDÉ, pas du commandé.** `buildTelemetry()` publie le plan, et
> `applyActionsToAdapters()` reçoit le slot **`const`** : rien n'est réécrit après le dispatch.
> Mesurable — `testUnpluggedChargerTakesNoBudget` attend `allocatedW == 3000` sur une borne dont
> le courant commandé donne 2 990 W.
>
> **Conséquence** : `Σ levels[].targetW == allocatedW` tient **exactement**, les deux étant du
> décidé. L'écart « décidé → appliqué » ne se lit donc **pas** entre eux, mais entre
> `allocatedW` et la charge utile de **mécanisme**.
>
> **Et l'arrondi ne se répartit pas entre les niveaux.** Toute clé de répartition serait une
> fiction, dont un client hériterait comme d'un fait : l'écart appartient à la **charge**, pas à
> l'un de ses niveaux.
> **Pourquoi trois et pas deux.** Chaque frontière répond à une question différente, et les
> confondre envoie diagnostiquer le mauvais problème (règle 7-b). Décidé ≠ appliqué est une
> affaire de **format** — le mécanisme ne sait pas prendre n'importe quelle valeur. Appliqué ≠
> mesuré est une affaire d'**obéissance** — et ce que cet écart autorise à conclure dépend
> encore de `measurement.source` (§11) : sous `device` il accuse la charge, sous `meter` il
> ouvre trois hypothèses, sous `none` il n'est pas calculable.
### L'identité de réconciliation — écrite ici, jamais reconstruite
```

View File

@ -629,6 +629,47 @@ que **R3** généralisera ; d'ici là l'exception est dans le contrat, pas dans
> `levels[]` **absent** = box antérieure à `+etm30`, **ou mode dégradé** — sans plan, il n'y a
> pas de financement. Votre repli sur `allocatedW`/`decision` reste donc juste, et il couvre les
> deux cas.
>
> **`+etm30` est posé sur `.75`** : `levels[]` y est en ligne, `entry.funding` a disparu, et
> l'identité a été vérifiée sur la vraie box — Σ targetW(surplus) = 5 000 = `budget.allocatedW`.
> C'est donc votre branche neuve qui s'exécute désormais, plus le repli.
### LES TROIS NOMBRES — porté au contrat, parce que votre R4 en confondait deux
Votre correction a fait apparaître un troisième nombre que ni la maquette ni nous n'avions
nommé. `INTERFACE.md` porte désormais le tableau :
| | Où | Ce qu'il dit |
|---|---|---|
| **DÉCIDÉ** | `allocatedW`, `levels[].targetW` | ce que la cascade a alloué |
| **APPLIQUÉ** | `mechanism.stageW` · `setpointW` · `state` · `currentA` | ce qui a été commandé au matériel |
| **MESURÉ** | `measurement.powerW` + sa `source` | ce qu'un appareil ou un compteur a vu |
Et surtout **ce que chaque écart signifie**, parce que c'est là qu'un écran se trompe de
diagnostic : *décidé ≠ appliqué* est une affaire de **format** — le mécanisme ne sait pas prendre
n'importe quelle valeur ; *appliqué ≠ mesuré* est une affaire d'**obéissance**, et ce qu'elle
autorise à conclure dépend encore de `measurement.source` (sous `device` elle accuse la charge,
sous `meter` elle ouvre trois hypothèses, sous `none` elle n'est pas calculable).
Votre carte « 1 400 W décidés → palier 1 500 W » lit donc le **premier** écart, entre
`allocatedW` et `mechanism.*` — pas entre `targetW` et `allocatedW`, qui sont égaux par
construction.
### R3 : ce qu'il nous faudra de vous
Nous la prenons ensuite, et `EV_GRID_START` est le cas qui la force. Notre intention, à
critiquer avant qu'on l'écrive : **`funding` dit d'où viennent les watts, il ne dit pas dans
quel compteur ils tombent** — ce sont deux questions, et R3 demande la seconde. Un niveau
publierait donc sa ventilation :
```jsonc
{ "level": "comfort", "targetW": 1380, "funding": "grid",
"counts": { "budget.allocatedW": 900, "budget.evReservedW": 480 } }
```
`Σ counts == targetW`, toujours. L'identité devient calculable **sans exception**, et elle vaudra
telle quelle quand `draw.committedW` arrivera. Dites-nous si cette forme vous va, ou si vous
préférez lire la ventilation autrement — c'est votre réconciliation qui en dépend.
> 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

@ -231,32 +231,11 @@ une entrée de clôture (`testDecisionReasonRendering`).
|---|---|---|
| `DRAW_CAP` | `capW`, `source`, `requiredW`, `o:phase` | Le plafond de soutirage refuse, et **`source` dit lequel** : raccordement, gestionnaire de réseau, ou auto-imposé. `phase` présent si c'est une phase qui borne. **Réservé à l'autorisation résiduelle NULLE** — voir la précédence ci-dessous. |
**PRÉCÉDENCE `DRAW_CAP` ↔ `BELOW_MIN_POWER` — tranchée le 2026-08-29 (Patrick), sur une question
de l'agent app.**
Autorisation résiduelle **non nulle mais inférieure au plus petit palier** du mécanisme :
**`BELOW_MIN_POWER`**. Les deux motifs sont vrais, mais pas au même niveau.
`DRAW_CAP` dit « **il n'y a plus d'autorisation** ». Or il en reste — simplement **pas au bon
format**. C'est exactement la distinction posée en `+etm19` entre `SURPLUS_INSUFFICIENT` (« il
n'y a rien ») et `BELOW_MIN_POWER` (« il y en a, mais pas au bon format »), **transposée telle
quelle au soutirage** : ce n'est pas une règle nouvelle, c'est la même appliquée à une seconde
ressource.
**Ce qui la décide est le GESTE qu'elle appelle** — la règle 7-b : un motif faux envoie
diagnostiquer le mauvais problème, avec l'autorité d'une explication.
| Motif | Ce qu'il dit | Le geste utile |
|---|---|---|
| `DRAW_CAP` | l'autorisation est **épuisée** | revoir le plafond, ou attendre |
| `BELOW_MIN_POWER` | il reste de l'autorisation, **la charge ne sait pas la prendre** | lire la fiche technique du mécanisme |
> **Conséquence sur le « point de rupture » de l'écran 2** : il est bien un `DRAW_CAP`. Une
> charge refusée **après** lui pour cause de palier porte `BELOW_MIN_POWER` et **ne déplace pas**
> le point de rupture — elle est en aval de la coupure, pas la coupure elle-même.
>
> **La règle est écrite ICI, une fois, par le moteur** — pas établie de chaque côté de la
> frontière. Deux précédences dérivées séparément divergeraient au premier motif ajouté.
**PRÉCÉDENCE `DRAW_CAP` ↔ `BELOW_MIN_POWER`** — tranchée le 2026-08-29, et son domicile est
`specs/spec_loadmodel.md` **LM-1011**, pas ici : une note de conception se périme, la spec fait
autorité. En un mot : autorisation résiduelle **non nulle mais sous le plus petit palier** →
`BELOW_MIN_POWER` ; `DRAW_CAP` réservé au résiduel **nul**. Le point de rupture de l'écran 2 est
ancré sur le `DRAW_CAP`.
**RÉPONSE À R4 — ce que les compteurs de budget comptent : le DÉCIDÉ.** *(Question de l'agent
app, 2026-08-29.)*

View File

@ -850,6 +850,35 @@ se déduit.
> La maquette **vaut comme contrat, pas comme feuille de route** : elle fixe la forme de ce qui
> sera publié, pas une date. Et côté app, rien ne commence avant que `levels[]` soit en ligne.
**LM-1011 — `BELOW_MIN_POWER` prime sur `DRAW_CAP` dès qu'il reste de l'autorisation.**
*(Précédence tranchée par Patrick le 2026-08-29, sur une question de l'agent app. Écrite ICI,
une fois, pour qu'aucun côté de la frontière ne l'établisse pour son compte.)*
Autorisation résiduelle **non nulle mais inférieure au plus petit palier** du mécanisme :
**`BELOW_MIN_POWER`**. `DRAW_CAP` reste réservé à l'autorisation résiduelle **nulle**.
Les deux motifs sont vrais, mais pas au même niveau. `DRAW_CAP` dit « **il n'y a plus
d'autorisation** » — or il en reste, simplement **pas au bon format**.
**Ce n'est pas une règle nouvelle : c'est celle de `+etm19`, transposée à une seconde
ressource.** Le surplus l'a déjà : `SURPLUS_INSUFFICIENT` dit « il n'y a rien »,
`BELOW_MIN_POWER` dit « il y en a, mais pas au bon format ». Le soutirage hérite du même couple,
et il aurait été absurde qu'une ressource se lise autrement que l'autre.
**Ce qui décide n'est pas la véracité, c'est le GESTE que le motif appelle** — règle 7-b : un
motif faux envoie diagnostiquer le mauvais problème, avec l'autorité d'une explication.
| Motif | Ce qu'il dit | Le geste utile |
|---|---|---|
| `DRAW_CAP` | l'autorisation est **épuisée** | revoir le plafond, ou attendre |
| `BELOW_MIN_POWER` | il en reste, **la charge ne sait pas la prendre** | lire la fiche technique du mécanisme |
> **Conséquence retenue de l'agent app** : le **point de rupture** de l'écran « ordre de service
> du cycle » est **ancré sur le `DRAW_CAP`**, donc sur l'épuisement réel. Une charge refusée
> **après** lui pour cause de palier porte `BELOW_MIN_POWER` et **ne déplace pas** ce point :
> 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-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