Doxygen : 1 avertissement introduit par 5768c36 (paramètre non documenté) ;
0 après. Commentaire seul.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Question 4 de T7 : le masquage à la réception remplace TOUTE occurrence du
token dans ce que l'optimiseur renvoie ; un token court, présent par hasard
dans un identifiant de charge, corromprait le plan reçu (unknown-load).
- SetOptimizerToken (writeToken) refuse un token non vide de moins de
16 caractères (points de code UTF-8) : EnergyErrorInvalidParameter, fault
`token-refused` comme les autres refus ; rien n'est modifié ; le motif est
journalisé, jamais la valeur. Vide = retrait, inchangé.
- Le client relit le fichier au début de chaque tentative ; plus court que
16 caractères, il ne se connecte PAS (ni avec ce token, ni sans token : les
mesures du site ne partent pas vers un optimiseur que rien n'authentifie).
lastErrorCode `token-too-short`, dit une fois par série, nouvelle tentative
au backoff jusqu'au remplacement du fichier.
INTERFACE.md (SetOptimizerToken, lastErrorCode) et protocole §3 (E-T2-8,
forme du token). Tests : 15 refusé, 16 accepté, 15 « é » refusés (caractères,
pas octets), vide = retrait, fichier court sans connexion puis token conforme
envoyé ; tokens des suites portés à 16 au moins. Contre-épreuves M4a (refus
retiré) et M4b (fichier court envoyé) : mordent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>