Fonctions pures (ni E/S ni horloge, « maintenant » = ctx.timestamp) qui
portent toute la sémantique du plan reçu ; le SocketScheduler les enchaînera.
- valider() : JSON → plan interne ou verdict à code stable. Créneaux à
décalage horaire explicite, ordonnés, sans chevauchement ; loadId connu et
unique ; kind cohérent avec le mécanisme et annoncé dans capabilities.kinds ;
reason non vide ; valeurs dans leur domaine ; validUntil ; créneau courant ;
COUVERTURE Q1 (toute charge disponible nommée). Abstention / expiré /
invalide distingués ; un plan invalide n'est jamais converti en partie.
`force` et champs réservés (annexe F) ignorés et listés.
- motifFiltre() : code hors liste blanche → OPTIMIZER_DECISION sans
paramètres ; sinon seules les clés documentées (optimizerParamKeys, testé
contre le tableau d'INTERFACE.md), valeurs nombre/booléen/code [A-Za-z]+.
Le texte libre reste dans ActionRecue::texte, assaini, jamais dans le motif.
- substituer() : le créneau courant remplace les actions de l'ombre à leur
place ; charges indisponibles → action de l'ombre ; bornes commandées
sorties du proxy (waterfallEvIds) ; budget/draw de l'ombre jamais gardés.
- finaliser() : après dispatch, puissance APPLIQUÉE, niveau unique
« optimizer », financement dérivé de la mesure (surplus puis réseau),
counts cohérents, budget et draw recalculés (Q3, Q4), `clamped`.
LoadAction::clamped ajouté (interne, non publié dans ce lot).
Aucun appelant dans ce commit : le moteur est inchangé.
Tests (loadmodel) : 7 fonctions, dont 31 verdicts data-driven.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>