diff --git a/AGENTS.md b/AGENTS.md index eb6bc0e..1bc7393 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 diff --git a/INTERFACE.md b/INTERFACE.md index 9e1ac56..96bde4c 100644 --- a/INTERFACE.md +++ b/INTERFACE.md @@ -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 ``` diff --git a/docs/BRIEF_depuis_plugin.md b/docs/BRIEF_depuis_plugin.md index 0cfd7d7..9a6f42d 100644 --- a/docs/BRIEF_depuis_plugin.md +++ b/docs/BRIEF_depuis_plugin.md @@ -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 à diff --git a/docs/DESIGN_10.md b/docs/DESIGN_10.md index b9cf9d0..6aa8671 100644 --- a/docs/DESIGN_10.md +++ b/docs/DESIGN_10.md @@ -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.)* diff --git a/specs/spec_loadmodel.md b/specs/spec_loadmodel.md index b0eb1e8..25963b4 100644 --- a/specs/spec_loadmodel.md +++ b/specs/spec_loadmodel.md @@ -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