Constat de T6 (question 2) : la trace masquait un token recopié dans reason ou planId, mais
les lignes [Optimiseur] (planId, texte, détail des refus, nom du handshake) et
GetOptimizerStatus.name le portaient en clair.
- OptimizerSecret : UNE fonction de masquage (texte brut et échappé JSON, objet JSON à toute
profondeur, clés comprises ; ligne de trace sérialisée), partagée par le client et les traces.
- OptimizerClient masque chaque message reçu dès son analyse, avec le token envoyé sur cette
connexion : rien en aval (journal, traces, app) ne voit le secret ; dit une fois.
- Test testAnEchoedTokenLeaksNowhere : secret avec " et \ recopié partout ; aucune ligne de
journal (tous niveaux), aucune trace, aucune réponse RPC. 4 contre-épreuves mordent.
decisions sans endpoint : IDENTIQUE (23 792 lignes), 0 rejeu.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OptimizerClient : QLocalSocket ou QTcpSocket, NDJSON, JSON-RPC 2.0, identifiants
croissants sur toute la vie du client, une requête GetPlan en vol au plus (la plus
récente remplace l'attente ; la réponse obsolète est reconnue par son id et ignorée).
Handshake §3 : majeure comparée, capabilities mémorisé (présent ≠ absent), délai de
5 s compté depuis le début de la tentative ; reconnexion au backoff 1 → 60 s, remis
à 1 s après un handshake accepté. GetPlan : 10 s, connexion conservée. Ligne non
JSON, message sans id, id jamais émis, ligne de plus de 1 Mio : erreur comptée,
connexion conservée.
Horloge et réveil injectables (OptimizerClientClock, monotone) : aucune lecture de
l'horloge murale. Token relu à chaque connexion dans settingsPath()/
energy-optimizer.token, jamais journalisé ; writeToken() pose le fichier par
QSaveFile en 0600 (droits posés avant l'écriture du secret). Pas de RPC.
Le client n'est instancié nulle part dans le moteur. Suite légère
nymea-energy-optimizerclient : faux serveurs QLocalServer/QTcpServer, temps simulé,
37 cas ; 17 mutations du client, toutes mordent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>