docs(motif): un interdit d'écriture doit dire ce qu'il fait d'un écho inchangé
§10 du contrat de données, à côté du §9. Le §9 disait que deux réponses de la même box peuvent employer deux conventions sans le signaler ; celui-ci dit que deux RÈGLES posées séparément, chacune juste, peuvent se contredire au point de rendre une écriture impossible. Symptôme identique : muet. L'app écrit verbatim (sinon un champ qu'une box plus récente porte serait perdu, en silence et pour toutes les charges) ; le moteur refuse rankOrigin: "auto" à tout client (sinon un écran fabriquerait une affirmation sur un geste du moteur). Leur produit : un simple renommage rejeté en bloc dès qu'une installation porte une borne auto-provisionnée. La règle doit porter sur le CHANGEMENT, pas sur la PRÉSENCE — même distinction que l'effacement de rankOrigin, qui porte sur « rang différent pour cet id » et non sur « écriture reçue ». Sinon la marque est fragile par construction : nettoyée à chaque écriture, le badge s'éteint sur une borne que personne n'a classée au premier renommage d'une autre charge. Une information qui s'efface au premier geste voisin ne vaut pas d'être publiée. Le §10 porte aussi le contrôle à passer une fois par règle d'écriture : l'app peut-elle lire la valeur (alors elle la renverra) · l'écho inchangé est-il tranché par écrit (sinon c'est un trou) · que devient l'information au premier geste voisin (si elle disparaît, la publier est un leurre). Et le motif derrière le motif, parce qu'il s'est répété : TROIS FOIS EN DEUX JOURS, le chemin exposé n'était pas celui qu'on regardait — l'écriture du mode visait la première borne (trouvé en déplaçant les boutons), le refus sur rangs doublés menaçait la suppression et non le glisser-déposer, le refus de "auto" menace le renommage et non la gestion des rangs. Le point commun n'est pas la malchance : ces chemins TRANSPORTENT des données qu'ils ne regardent pas, donc ils sont exposés à tout ce qui a été ajouté après eux. Le brief moteur passe de la question à la demande argumentée ; le nettoyage côté app reste, documenté comme contournement — le jour où l'écho passe, la fonction disparaît. 222 tests inchangés, analyze inchangé. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQbZKrWqsMFP1Lh2jjjd9f
This commit is contained in:
parent
e26000945d
commit
761e1e369d
@ -23,10 +23,23 @@ sortie unique (`NymeaService.setLoadConfig`). L'écriture passe, mais la marque
|
||||
la box » disparaîtra donc d'une borne que personne n'a classée, au premier renommage d'une
|
||||
autre charge.
|
||||
|
||||
**La question** : un écho **inchangé** — même valeur, même id, rang inchangé — peut-il être
|
||||
toléré ? Si oui, l'app le renverra tel quel et la marque survivra aux gestes qui ne la
|
||||
concernent pas. Sinon, la marque est par construction fragile, et il vaut mieux le savoir
|
||||
que le découvrir sur une installation client.
|
||||
**La demande** : tolérer l'écho **inchangé** — même id, même rang, même valeur.
|
||||
|
||||
Le refus vise à empêcher un client de **fabriquer une affirmation** sur un geste du moteur.
|
||||
Renvoyer à l'identique ce que le moteur a écrit n'affirme rien de neuf : **la règle doit
|
||||
porter sur le changement, pas sur la présence** — exactement comme l'effacement de
|
||||
`rankOrigin` porte sur « rang différent pour cet id » et non sur « écriture reçue ». Même
|
||||
distinction, un cran plus loin.
|
||||
|
||||
L'alternative rend la marque **fragile par construction** : l'app doit alors la nettoyer sur
|
||||
toutes les charges à chaque écriture, donc le badge s'éteint sur une borne que personne n'a
|
||||
classée, au premier renommage d'une autre charge. Une information qui s'efface au premier
|
||||
geste voisin ne vaut pas d'être publiée.
|
||||
|
||||
**Et une règle pour le contrat, parce que ça se reproduira** : une règle qui interdit une
|
||||
valeur à l'écriture doit dire ce qu'elle fait d'un **écho inchangé**, sous peine de rendre
|
||||
l'écriture verbatim impossible. Le motif est écrit côté app (`docs/UI_data_contract.md`
|
||||
§10) avec le contrôle à passer une fois par règle.
|
||||
|
||||
Trois options écartées côté app, pour situer : refuser l'écriture (une installation avec
|
||||
une borne neuve deviendrait non modifiable), renvoyer `"auto"` (refus systématique),
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
|
||||
> Compagnon de `DASHBOARD_SPEC.md`. Tranche la question récurrente
|
||||
> « **qui calcule les valeurs affichées ?** » pour chaque écran.
|
||||
> Rev. 7 — ajout du **§10** : une valeur interdite à l'écriture doit dire ce qu'elle fait
|
||||
> d'un écho inchangé, sinon l'écriture verbatim devient impossible. Et le motif derrière :
|
||||
> trois fois en deux jours, le chemin exposé n'était pas celui qu'on regardait.
|
||||
> Rev. 6 — ajout du **§9** : deux réponses de la même box peuvent employer deux
|
||||
> conventions sans qu'aucun signal ne le dise, et l'erreur est toujours muette.
|
||||
> Deux occurrences en une semaine (clés de log sans préfixe `currentPower`,
|
||||
@ -409,6 +412,74 @@ chance de découvrir le bug ; afficher `0` l'enterre.
|
||||
|
||||
---
|
||||
|
||||
## 10. Une valeur interdite à l'écriture doit dire ce qu'elle fait d'un ÉCHO inchangé
|
||||
|
||||
> **Motif, comme le §9 — et il vient de le compléter.** Le §9 dit que deux réponses de la
|
||||
> même box peuvent employer deux conventions sans le signaler. Celui-ci dit que deux
|
||||
> **règles** posées séparément, chacune juste, peuvent se contredire au point de rendre une
|
||||
> écriture impossible. Le symptôme est le même : muet.
|
||||
|
||||
### Le cas qui l'a produit
|
||||
|
||||
Deux règles, écrites à des moments différents, chacune correcte :
|
||||
|
||||
| Règle | Où | Pourquoi elle est bonne |
|
||||
|---|---|---|
|
||||
| L'app écrit **verbatim** — elle recopie ce que la box lui a rendu | `payloadWithoutLoad`, `flattenToPayload` | `SetLoadConfig` remplace tout l'ensemble : reconstruire depuis le modèle typé perdrait, en silence et pour toutes les charges, un champ qu'une box plus récente porterait |
|
||||
| `rankOrigin: "auto"` est **refusé** à tout client | `isValid()` côté moteur | empêche un écran de fabriquer une affirmation sur un geste du moteur |
|
||||
|
||||
Leur produit : recopier verbatim une entrée marquée `"auto"`, **c'est écrire `"auto"`**. Donc
|
||||
un simple **renommage** serait rejeté en bloc dès qu'une installation porte une borne
|
||||
auto-provisionnée — motif invisible, le refus ne franchissant pas la frontière RPC. Le banc
|
||||
ne pouvait pas le produire : aucune borne n'y a été détectée depuis `+etm27`.
|
||||
|
||||
### La règle, et pourquoi elle porte sur le CHANGEMENT
|
||||
|
||||
**Un interdit d'écriture doit se prononcer sur l'écho inchangé** — même id, même valeur,
|
||||
même contexte. Renvoyer à l'identique ce que le moteur a écrit n'affirme rien de neuf : la
|
||||
règle doit porter sur le **changement**, pas sur la **présence**. C'est exactement la
|
||||
distinction que le moteur applique déjà à l'effacement de `rankOrigin` — il porte sur « un
|
||||
rang différent pour cet id », pas sur « une écriture reçue » — à un cran de plus.
|
||||
|
||||
L'alternative rend la marque **fragile par construction** : l'app doit alors la nettoyer sur
|
||||
toutes les charges à chaque écriture, et le badge s'éteint sur une borne que personne n'a
|
||||
classée, au premier renommage d'une autre charge. **Une information qui s'efface au premier
|
||||
geste voisin ne vaut pas d'être publiée.**
|
||||
|
||||
### Ce que ça impose à l'app, aujourd'hui
|
||||
|
||||
Le nettoyage vit à un **point de sortie unique** (`NymeaService.setLoadConfig`), jamais sur
|
||||
chaque chemin : un chemin ajouté demain hériterait sinon du refus sans que rien ne
|
||||
l'annonce. C'est un contournement assumé, avec son coût écrit à côté — pas une solution.
|
||||
|
||||
### Le contrôle à faire, une fois par règle d'écriture
|
||||
|
||||
Pour chaque valeur qu'une box refuse en écriture, poser la question **avant** de la
|
||||
rencontrer :
|
||||
|
||||
1. l'app peut-elle la **lire** ? Si oui, elle finira par la **renvoyer**, puisqu'elle écrit
|
||||
verbatim ;
|
||||
2. l'écho inchangé est-il toléré ? Si la réponse n'est pas écrite, **c'est un trou**, pas un
|
||||
détail d'implémentation ;
|
||||
3. si l'écho est refusé, que devient l'information au premier geste voisin ? Si elle
|
||||
disparaît, la publier est un leurre.
|
||||
|
||||
### Et le motif derrière le motif
|
||||
|
||||
**Trois fois en deux jours, le chemin exposé n'était pas celui qu'on regardait** :
|
||||
|
||||
- l'écriture du mode de recharge visait « la première borne configurée » — trouvé en
|
||||
**déplaçant** les boutons, pas en les testant ;
|
||||
- le refus sur rangs doublés menaçait la **suppression** (qui réécrit verbatim), pas le
|
||||
glisser-déposer qu'on surveillait ;
|
||||
- le refus de `"auto"` menace le **renommage**, pas la gestion des rangs.
|
||||
|
||||
Le point commun n'est pas la malchance : c'est que ces chemins **transportent** des données
|
||||
qu'ils ne regardent pas. Un chemin qui recopie est exposé à tout ce que la charge utile
|
||||
contient, y compris ce qui a été ajouté après lui.
|
||||
|
||||
---
|
||||
|
||||
## Récap des décisions
|
||||
|
||||
1. Héos au centre du flow : **Option A — métaphore « cerveau » assumée**
|
||||
|
||||
@ -153,8 +153,16 @@ List<LoadConfigEntry> tiedByPriority(List<LoadConfigEntry> entries) {
|
||||
///
|
||||
/// C'est le moindre mal entre trois options, aucune bonne : refuser l'écriture (une
|
||||
/// installation avec une borne neuve deviendrait non modifiable), renvoyer `"auto"` (refus
|
||||
/// systématique), ou effacer une marque informative. **Question posée au moteur** : un
|
||||
/// écho *inchangé* de `"auto"` peut-il être toléré ? Cf. `docs/BRIEF_plugin_depuis_maquettes.md`.
|
||||
/// systématique), ou effacer une marque informative.
|
||||
///
|
||||
/// **Contournement, pas solution.** La demande est portée au moteur : tolérer l'écho
|
||||
/// *inchangé*, la règle devant porter sur le **changement** et non sur la **présence** —
|
||||
/// même distinction que l'effacement de `rankOrigin`, qui porte sur « rang différent pour
|
||||
/// cet id » et non sur « écriture reçue ». Le jour où l'écho passe, cette fonction disparaît
|
||||
/// et la marque survit aux gestes qui ne la concernent pas.
|
||||
///
|
||||
/// Le motif général — *un interdit d'écriture doit se prononcer sur l'écho inchangé* — est
|
||||
/// écrit en `docs/UI_data_contract.md` §10, avec le contrôle à faire une fois par règle.
|
||||
List<Map<String, dynamic>> sansRankOriginAuto(List<Map<String, dynamic>> payload) => [
|
||||
for (final m in payload)
|
||||
if (m['rankOrigin'] == 'auto') {...m, 'rankOrigin': ''} else m,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user