Patrick Schurig 8c7c33be39 Optimiseur de référence : aligné sur le protocole 1.3
- Annonce 1.3. error.data du -32602 d'un contexte invalide dans la forme normée :
  {"problems":[{"path":"params.timestamp","problem":"missing"|"type"|"format"}]}, 50 problèmes
  au plus et truncated au-delà — plus de chemin « /timestamp » ni de phrase libre.
- Handshake : le token était déjà jugé avant la majeure ; l'ordre est désormais écrit et nommé.
- Pour les contre-épreuves de la conformité : --announce VERSION (se présenter en 1.2) et trois
  modes, old-data (forme d'avant 1.3, {"problemes": [phrases]}), echo-value (recopie de la valeur
  reçue), major-first (la majeure jugée avant le token).

Suite refoptimizer 8/8 ; test du validateur vert avec l'outil inchangé.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 11:13:17 +02:00
..

etm-powersync-ref-optimizer — optimiseur de référence

Python 3, bibliothèque standard seule, GPL-3.0 comme le dépôt. Protocole : docs/OPTIMIZER_PROTOCOL.md 1.3 (annexe E). Il répond un plan à un créneau qui reproduit le waterfall du rule-based (passe confort : même ordre, même budget, mêmes écrêtages) en ne lisant que le contexte GetPlan — aucun accès réseau sortant, aucun fichier hors le token. Usages : test de bout en bout du moteur, point de comparaison d'un banc, exemple pour qui écrit son propre optimiseur.

Nom annoncé au handshake : powersync-optimizer (c'est le name que GetOptimizerStatus publie). powersync-optimizer = exemple de référence GPL (ce dépôt) ; etm-optimizer = optimiseur ETM (propriétaire, hors de ce dépôt). Le fichier et son répertoire gardent le préfixe des artefacts du dépôt, etm-powersync-ref-optimizer.

Ce qu'il ne reproduit pas (le contexte 1.1 ne le porte pas) : passe éco, dérogations, plafond de soutirage, paliers d'un relay-router (il envoie une consigne continue que le routeur arrondit), refus de commande, proxy VE. L'en-tête du script le détaille.

Lancer

# socket local (défaut : unix:///run/powersync/optimizer.sock, mode 0660)
./tools/etm-powersync-ref-optimizer/etm-powersync-ref-optimizer

# sur le LAN, avec un token (lu dans un FICHIER, jamais en argument)
./tools/etm-powersync-ref-optimizer/etm-powersync-ref-optimizer --listen tcp://0.0.0.0:7600 \
    --token-file /etc/powersync/optimizer.token

Il écrit ÉCOUTE <endpoint> sur la sortie standard quand il écoute (le port réel de tcp://…:0), et son journal sur la sortie d'erreur. Plusieurs clients à la fois (un fil par connexion, aucun état partagé) : le moteur et un rejeu peuvent lui parler ensemble. SIGTERM l'arrête proprement.

Erreurs (protocole 1.2 et 1.3)

Codes JSON-RPC 2.0, plus -32001 (protocole §7) :

Reçu Réponse Connexion
Handshake sans le token attendu, ou avec un autre — même d'une autre majeure (1.3 : le token d'abord) -32001 fermée
Handshake d'une autre majeure, token accepté ou non exigé -32602 fermée
GetPlan avant un handshake réussi -32600 gardée
GetPlan dont le timestamp est absent, n'est pas une chaîne, ou est illisible ou sans décalage -32602, error.data = {"problems":[{"path":"params.timestamp","problem":"missing"|"type"|"format"}]} (forme 1.3, protocole §7), jamais la valeur gardée
ligne non JSON -32700, id: null gardée
lot […] -32600, id: null, aucun message du lot traité gardée
ligne de plus de 1 Mio (\n non compté) -32600, id: null, jetée sans être gardée en mémoire gardée
requête d'une autre méthode, dont UpdateLearned avec id -32601 gardée

Sans --token-file, un token reçu au handshake est accepté sans être lu. Une notification (sans id) est ignorée.

Pointer le moteur dessus

# méthodes NymeaEnergy (permission Admin) — INTERFACE.md, « Optimiseur »
NymeaEnergy.SetOptimizerEndpoint  {"endpoint": "unix:///run/powersync/optimizer.sock"}
NymeaEnergy.SetOptimizerToken     {"token": "<le même secret que dans --token-file>"}   # clé seule
NymeaEnergy.GetOptimizerStatus    # optimizerAlive, activeStrategy, name, lastErrorCode…

Sur tcp:// sans TLS, le token circule en clair sur le LAN (protocole §3).

Traces, puis rejeu

NymeaEnergy.SetOptimizerTrace {"enabled": true}      # défaut : désactivé
NymeaEnergy.GetOptimizerTrace                         # → directory
# une ligne par cycle : <directory>/etm-powersync-optimizer-trace-AAAA-MM-JJ.ndjson

# rejouer une journée sur un optimiseur (celui-ci, ou un candidat)
./tools/etm-powersync-trace-replay <directory>/etm-powersync-optimizer-trace-2026-10-17.ndjson \
    --endpoint unix:///run/powersync/optimizer.sock --fail-on-divergence

Le rejeu compare, cycle par cycle, les actions du créneau courant au plan enregistré (tools/README.md).

Modes d'injection de défauts (--mode, tests)

normal · abstain · slow (répond après --slow-s, défaut 12 s, au-delà des 10 s du moteur) · invalid (slots n'est pas une liste) · expired (validUntil passé) · wrong-major (handshake 2.0) · drop (ferme la connexion à chaque GetPlan) · unknown-load · forbidden-kind (kind constraint) · missing-load (une charge disponible absente du créneau courant) · echo-token (recopie le token dans reason et planId) · protocol-1.0 (optimiseur 1.0 : sans champs 1.1) · deviate (cascade en ordre inverse : doit diverger) · wrong-type (planId numérique : le moteur l'accepte, le contrat OpenRPC le refuse — contre-épreuve de la suite de conformité) · old-data (contexte invalide : error.data dans la forme d'avant 1.3, {"problemes": [phrases]}) · echo-value (contexte invalide : error.data recopie la valeur reçue) · major-first (la majeure jugée avant le token).

--announce VERSION : la version annoncée au handshake, et dont la majeure est exigée du moteur (défaut 1.3). Avec --announce 1.2, il se présente en optimiseur 1.2 : la suite de conformité n'exige plus de lui la forme 1.3 d'error.data ni l'ordre token puis majeure, elle avertit.

--mode-file FICHIER : le mode est relu dans ce fichier à chaque message — un test change de mode sans reconnexion. --capture FICHIER : chaque message reçu et envoyé, une ligne JSON-RPC par message — la session que tools/etm-powersync-schema-check --ndjson juge contre le contrat ; le token y devient autant d'astérisques (forme gardée, valeur jamais écrite). --list-codes : le catalogue des codes émissibles qu'il embarque (le test d'intégration le confronte au moteur).

Tests : tests/auto/refoptimizer (suite etm-powersync-refoptimizer, moteur réel + ce script en sous-processus).