recommandations : un moteur de regles charge sans redeploiement (#116) #241
Labels
No labels
Compat/Breaking
EC01
EC02
EC03
EC04
EC05
EC06
Kind/BDD
Kind/Back
Kind/Bug
Kind/CICD
Kind/Cloud
Kind/Contenu
Kind/Data
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Front
Kind/IA
Kind/Infra
Kind/Monitoring
Kind/Security
Kind/Testing
Portée/Post-jury
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
ops/alerte
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
g2/enervision!241
Loading…
Reference in a new issue
No description provided.
Delete branch "olivier/116-moteur-regles-generique"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Ferme #116 (
Portée/Post-jury) — la fusion est une décision d'équipe, pas un acquis.L'écart à signaler d'abord
Ce ticket est hors fenêtre par la décision du 03/09 (
docs/BACKLOG.md, réduction 1). Le développement a été fait le 09/09 quand même, à la demande. La décision n'est pas rouverte pour autant : le ticket garde son libellé, et le backlog comme le PRD portent l'écart en clair. Si le gel du 09 au soir s'applique, cette demande attend le 11.Ce qui change
Les trois règles du #153 étaient trois classes Python, seuils en constantes de module : les recaler demandait un redéploiement. Elles deviennent des données, dans un fichier TOML versionné, modifiable sans redéploiement.
Des formes paramétrées, pas un langage d'expressions. Chaque forme est un raisonnement écrit une fois en Python — un seuil, un ratio soumis à une échéance, une série de N points consécutifs. Un langage d'expressions aurait couvert R2 aussi, mais il demande d'évaluer du texte venu d'un fichier que le ticket veut modifiable sans revue :
evalsur cette entrée donne au fichier le pouvoir du processus qui le lit. Motif complet, et les deux alternatives écartées, dans l'ADR 0014.Ajouter une règle du genre d'une forme existante ne coûte aucun Python : c'est le critère du ticket, et
test_formes.pyle démontre sur des règles d'un genre que le #153 n'a jamais décrit — comparateur inversé, pas au quart d'heure, gravité différente.Ce qui garantit que rien n'a changé pour les exploitants
Le risque n'était pas qu'un test rougisse : c'est que tout passe et que les recommandations aient quand même changé — un arrondi, une garde perdue, un motif d'indécision devenu muet. Personne ne l'aurait vu avant qu'un exploitant reçoive un conseil faux.
tests/unit/rules/verdicts-de-reference.jsonporte les 84 verdicts produits par les trois classes sur 28 observations couvrant chaque branche de chaque règle, capturés avant leur retrait, puis confrontés aux mêmes classes extraites dedevelop: aucun écart. Rejouable sans rien débrancher :La comparaison est stricte : message, action, gravité, valeurs déclenchantes et motif d'indécision au caractère près. C'est pour tenir ce dernier point que les motifs sont des gabarits du fichier — une reformulation « équivalente » aurait été indistinguable d'une garde perdue.
Les décisions à relire en priorité
job.py, ADR 0014/etc,root:deploy 0640— modifier une règle demande doncsudoapp, ADR 0014force: false)appformes.py,entrepot.pyLe
force: falsen'est pas une précaution : le déploiement continu joue--tags app,proxy,backupà chaque fusion, et une copie qui écrase effacerait à la fusion suivante toute règle ajoutée sur le serveur — le ticket se retournant contre lui-même.Relecture faite, six défauts corrigés
Une relecture systématique a trouvé six défauts, tous corrigés dans
6a2416f, chacun avec son cas de test. Le plus grave était bloquant : la tâche Ansible validait le jeu avec un venv créé 220 lignes plus loin dans le même rôle. Sur un hôte neuf,validateéchouait, le rôle s'arrêtait avant les migrations — dont la 0021 — et ce n'était pas auto-réparant.Les cinq autres : une table écrite en liste faisait perdre le code 5 au profit d'une trace brute ;
Template.get_identifiersignore les placeholders invalides, donc un$littéral n'explosait qu'au premier rendu — le jour où le site est en pointe ;profondeur_heuresajoutait un nombre de points à des heures et ignorait le pas ; trois appels lisaient/etclà où ils documentaient lire le dépôt ; le repli du lanceur confondait « absent » et « illisible ». Le témoin est inchangé après les six correctifs : aucun verdict n'a bougé.Cette demande corrige aussi une affirmation que j'avais écrite et que la fusion de
developa rendue fausse : le code 5 est surveillé depuis le #228, par l'alerte « Tâche planifiée en retard » (trois heures de latence). L'ADR et le manuel le disent maintenant.Vérifications
Rejouées sur le serveur, en Python 3.12, le venv du poste n'ayant aucune dépendance applicative :
tests/unit/rules,tests/unit/db,test_reglages_inference.py) ; +155 par rapport àdevelopservices/recommendations/recommendations, palier des zones sensibles à 85ruff format, mypy--strict, shellcheck, yamllint et ansible-lint propres (ansible-lint passe le profilproduction)tests/ci/test-supervision.sh: le lanceur publie son état et garde la maindevelopavec le même venv, donc aucune régressionMigration 0021 jouée sur
enervision_prod— 11 lignes réelles — dans une transaction annulée :UPDATE 11, les deuxset not nullpassent, la contrainte est posée, puisROLLBACK. Rejeu vérifié dans la même transaction :UPDATE 0, aucune contrainte dupliquée. Les lignes d'avant le #116 reçoiventavant-116, valeur qui se lit et se cherche, plutôt qu'une chaîne vide.Le geste d'exploitation — modifier une règle sans redéploiement, la vérifier, lire un code 5 — est dans
docs/runbooks/recommandations.md, et chaque commande y a été exécutée sur le serveur.Ce qui reste à faire après fusion
Le rôle
appdoit être rejoué pour poser/etc/enervision/regles.toml. En attendant, le lanceur replie sur le jeu de référence du dépôt et le journalise : la passe tourne, avec les mêmes règles.🤖 Generated with Claude Code
1. BLOQUANT — la tache Ansible validait le jeu avec {{ collector_venv }}/bin/ python, place 220 lignes AVANT la tache qui cree ce venv. Sur un hote neuf la commande n'a pas d'interpreteur, `validate` echoue, et le role s'arrete avant de demarrer les piles et d'appliquer les migrations — dont la 0021 dont l'insertion depend. Ce n'etait pas auto-reparant : chaque relance s'arretait au meme endroit. La tache passe apres l'installation des dependances et avant la pose de la crontab, et le commentaire dit pourquoi les deux bornes comptent. 2. Une table ecrite en liste — `valeurs = ["seuil"]`, confusion de syntaxe TOML facile a la main — levait AttributeError, que _construire ne rattrape pas : la passe mourait sur une trace en code 1, quand le journal, le manuel et le validate d'Ansible annoncent tous un code 5. Meme cas pour `motifs` et pour `requises` donne en scalaire. 3. `Template.get_identifiers` IGNORE les placeholders invalides — seul `is_valid` les refuse. Un « $ » litteral (« 30 $/MWh ») passait le chargement, `--verifier` et le validate, puis faisait lever `substitute` au premier RENDU : la regle tombait le jour ou le site etait en pointe, et nulle part avant. C'est le mode de panne que cette validation existe pour supprimer. 4. `profondeur_heures` ajoutait un NOMBRE DE POINTS a des HEURES et ignorait le pas : juste par coincidence au pas horaire du #153, faux des qu'une regle declare autre chose. Au pas de trois heures, la regle avait besoin de douze heures et n'en demandait que neuf — le #175 refermé d'un cote, reouvert de l'autre, atteignable par une modification du fichier. Le jeu de reference reste a 9 h : la production ne change pas. 5. Trois appels a `charger()` sans chemin honoraient ENERVISION_REGLES_FICHIER alors qu'ils documentent lire le jeu du depot. Le temoin d'equivalence aurait ete regenere depuis /etc — le fichier meme dont la conception dit qu'il peut diverger. 6. Le repli du lanceur se declenchait sur `! -r`, qui couvre « illisible » autant que « absent ». Un fichier reste en 0600 root:root apres une modification a la main aurait fait tourner la passe sur les regles du DEPOT sous une autre empreinte. Absent : repli, comme avant. Present et illisible : code 2, comme le controle de postgres.env juste au-dessus. Le temoin est inchange apres les six correctifs : aucun verdict n'a bouge. 292 tests sur le service, ruff, mypy strict, shellcheck, yamllint et ansible-lint propres. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>