docs(§10): débloqué — la maquette des deux passes fait contrat

L'agent app a répondu au §9. Décisions inscrites en LM-1010, précédence des
motifs et réponse à R4 en DESIGN_10.md, réponse à l'app en brief §10.

SON ARGUMENT SUR LA LISTE EST MEILLEUR QUE LE NÔTRE, et il est repris tel quel :
nous disions « le rang doit rester lisible », il dit que l'ORDRE DE SERVICE N'EST
PAS UNE PROPRIÉTÉ DE LA CONFIGURATION — il change d'un cycle à l'autre selon les
planchers atteints. Trier par lui ne rendrait pas le rang illisible, ça rendrait
la LISTE instable, ce qui est pire. Une liste de configuration ne peut pas avoir
une colonne vertébrale qui bouge sous le doigt qui vient d'y glisser une charge.

Les deux autres réponses : une entrée par charge et deux lignes dans la carte —
on dédouble là où l'objet affiché est la SÉQUENCE, jamais là où il est la CHARGE ;
et éco servi / confort non en deux ÉTATS NOMMÉS, jamais une fraction, sans quoi
le régime nominal se lirait comme une dégradation permanente.

PRÉCÉDENCE DRAW_CAP ↔ BELOW_MIN_POWER, tranchée et écrite ICI plutôt que de
chaque côté de la frontière. Autorisation résiduelle non nulle mais sous le plus
petit palier : BELOW_MIN_POWER. DRAW_CAP dit « il n'y a plus d'autorisation », or
il en reste — pas au bon format. C'est la distinction de +etm19 entre
SURPLUS_INSUFFICIENT et BELOW_MIN_POWER, transposée telle quelle au soutirage :
pas une règle nouvelle, la même appliquée à une seconde ressource. Ce qui la
décide est le GESTE qu'elle appelle — revoir le plafond, ou lire la fiche
technique.

RÉPONSE À R4 : les compteurs de budget comptent le DÉCIDÉ. Le budget est le
registre de la cascade, et ce qui détermine ce que reçoit la charge suivante est
la valeur décidée. L'arrondi vit après, dans l'adaptateur, propre à chaque
mécanisme — compter le commandé obligerait à rouvrir le budget après le dispatch,
c'est-à-dire à décider deux fois.

ET UNE PRÉMISSE DE R4 EST FAUSSE, mieux vaut le dire maintenant : la maquette
pose que allocatedW publie « ce qui a été 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. Donc Σ targetW ==
allocatedW tiendra exactement, et l'écart que l'écran veut nommer est entre
allocatedW (décidé) et la charge utile de MÉCANISME (appliqué) — déjà publiée.
L'app a les deux nombres ; leurs noms étaient inversés.

PROCHAINE ACTION DU §10 : R2, funding descend au niveau. Seule des huit à être un
changement de forme d'un champ existant ; les sept autres attendent sans se
renchérir.

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-29 15:46:41 +02:00
parent 8fe7e19382
commit 459ba9b009
2 changed files with 97 additions and 1 deletions

View File

@ -229,7 +229,58 @@ une entrée de clôture (`testDecisionReasonRendering`).
| Code | params | Quand |
|---|---|---|
| `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. |
| `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é.
**RÉPONSE À R4 — ce que les compteurs de budget comptent : le DÉCIDÉ.** *(Question de l'agent
app, 2026-08-29.)*
`budget.allocatedW` et ses voisins comptent **ce que la cascade a décidé**, jamais ce que le
matériel a reçu. Ce n'est pas un choix de commodité : le budget est le **registre de la
cascade**, et ce qui détermine ce que reçoit la charge suivante est la valeur décidée. L'arrondi,
lui, vit **après** — dans l'adaptateur, au dispatch, et il est propre à chaque mécanisme. Compter
le commandé obligerait à **rouvrir le budget après le dispatch**, c'est-à-dire à décider deux
fois.
> ⚠️ **Et cela corrige une prémisse de R4, qu'il vaut mieux corriger maintenant.** La maquette
> écrit « `allocatedW` publie ce qui a été **commandé** ». **C'est faux aujourd'hui** :
> `buildTelemetry()` publie `action.estimatedPowerW` du 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.
>
> Donc **`Σ targetW == allocatedW` tiendra exactement**, par construction — les deux sont du
> décidé. L'écart que l'écran veut nommer (« 1 400 W décidés → palier 1 500 W ») n'est **pas**
> entre `targetW` et `allocatedW` : il est entre `allocatedW` (**décidé**) et la charge utile de
> mécanisme (**appliqué**) — `mechanism.stageW`, `mechanism.setpointW`, `mechanism.currentA` —
> **qui est déjà publiée aujourd'hui**. L'écran a déjà les deux nombres ; ce sont leurs noms qui
> étaient inversés.
| `ECO_FLOOR_GRID` | `deliveredWh`, `targetWh`, `gridW` | Une obligation éco servie **au réseau**. `gridW` chiffre l'achat, comme `EV_GRID_START` le fait déjà. |
| `ECO_FLOOR_MET` | `deliveredWh`, `targetWh` | L'obligation est **atteinte** — et elle ne peut se dire atteinte que sur le régime ÉNERGIE (LM-1009). |
| `COMFORT_SKIPPED` | `o:sensorValue`, `o:setpoint` | La charge n'est plus demandeuse de son bonus (comparateur de LM-1008). Distinct de « pas de budget ». |

View File

@ -805,6 +805,51 @@ publiée, au même titre que la source d'un plafond en LM-1006-1 — **deux caus
---
**LM-1010 — Le §10 est DÉBLOQUÉ : la maquette des deux passes fait contrat.** *(Réponse de
l'agent app au §9 du brief, 2026-08-29. `etm-powersync-app/docs/mockups/`
`deux_passes_eco_confort_mockup.html` **fait foi** ; huit exigences argumentées et la charge
utile complète.)*
Les trois questions de 8.5 sont tranchées, et **l'argument décisif n'est ni le nôtre ni celui du
design** :
1. **La liste reste ordonnée par RANG** — non pas d'abord parce que le rang doit rester lisible,
mais parce que **l'ordre de service n'est PAS une propriété de la configuration** : il dépend
des planchers déjà atteints, donc il change d'un cycle à l'autre sans que personne ne touche à
rien. Une liste de configuration ne peut pas avoir une colonne vertébrale qui bouge sous le
doigt qui vient d'y glisser une charge. Trier par ordre de service ne rendrait pas le rang
illisible — cela rendrait la **liste instable**, ce qui est pire.
2. **Une entrée par charge, deux lignes dans la carte.** La commande est le **parent**, les
niveaux sa **décomposition**. Règle générale : *on dédouble là où l'objet affiché est la
SÉQUENCE, jamais là où l'objet est la CHARGE.* D'où un second écran, en lecture seule,
« ordre de service du cycle » — c'est lui qui montre le rang 4 éco devant le rang 1 confort,
rangs visibles, et qui désamorce « l'ordre n'est pas respecté ».
3. **Éco servi / confort non = deux ÉTATS NOMMÉS**, jamais une fraction. Affiché comme une
fraction, le régime **nominal** se lirait comme une dégradation permanente.
**Ce que le moteur doit publier** : `levels[]`, un élément par passe **réellement parcourue** —
rien de retiré. Les quatre exigences qui coûteraient cher à rattraper :
| | | |
|---|---|---|
| **R2** | `funding` descend au **niveau** | **la plus urgente** : seule des huit à être un *changement de forme* d'un champ existant. Au §10 la même charge est financée au réseau pour son éco et au surplus pour son confort **dans le même cycle** — un `funding` par charge devient indécidable, et c'est lui qui porte l'identité de réconciliation |
| **R3** | chaque watt tombe dans **exactement un** compteur, et le mapping est **publié** | trois destinations coexisteront : `budget.allocatedW`, `budget.evReservedW`, `draw.committedW`. Leçon d'`EV_GRID_START`, qui partage une allocation entre deux compteurs |
| **R4** | **cible ≠ commande**, et l'arrondi **ne se répartit pas** | toute clé de répartition serait une fiction dont l'app hériterait comme d'un fait. *Réponse du moteur sur ce que les compteurs comptent : `docs/DESIGN_10.md`* |
| **R6** | `ECO_FLOOR_MET` **interdit** sous régime `unmeasurable` | servie oui, tenue jamais (LM-1009 C1). Cas réel **dès aujourd'hui** : la V2C ne publie pas `sessionEnergy` |
**Et ce qu'il ne faut PAS fabriquer** — trois refus, tous du même genre : ne pas publier ce qui
se déduit.
- **Aucun index d'ordre de service.** La séquence se dérive d'un tri lexicographique sur
`(niveau, priority)`. Un index publié serait une **seconde source de vérité pour une donnée
déductible**, et divergerait un jour.
- **Aucun second rang**, sous quelque nom que ce soit. La maquette n'a de sens que si LM-1002
tient.
- **Aucun pourcentage d'avancement**, même indicatif.
> 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-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