Mise en production : la dérive du modèle, la chaîne aux dix minutes, les règles hors du code #252
No reviewers
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!252
Loading…
Reference in a new issue
No description provided.
Delete branch "develop"
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?
Ce que ça change
Mise en production de tout ce qui est entré dans
developdepuis la livraisondu 09/09 au matin (#233) : la détection de dérive du modèle de prévision, la
chaîne ETL ramenée aux dix minutes, les règles de recommandation sorties du
code, la garde Terraform en intégration, le bundle Git du dépôt, le jeton SAS
de l'archive de secours, et la grille de recette qui manquait au dossier.
main=develop.Refs #48
Preuve
Les demandes livrées :
Elles servent les tickets #47, #48, #70, #72, #74, #116, #117, #244 et #246.
Chacune est entrée dans
developavec ses tâches de chaîne au vert etl'approbation d'un pair. Aucune n'a été forcée. La chaîne est au vert sur la
tête de
develop(7344d44), les six tâches.Deux migrations dans cette livraison, et c'est la différence avec la #233 :
0021_recommandation_jeu_de_regles.sql(deux colonnes surpublic.recommandation) et0022_cadence_dix_minutes.sql(la politique derafraîchissement de
mesure_horaire). Le retour arrière n'est donc pas unsimple
git revertdu commit de fusion : voir le paragraphe 1 ci-dessous..forgejo/workflows/ci.ymlchange aussi — deux tâches d'intégration en plus,couvertes par le contrôle requis
Intégration / *qui est un motif.Ce qui change pour la démonstration
entrées et l'erreur du modèle promu à une référence figée à sa promotion,
et rend un verdict à trois valeurs —
stable,surveillance,dérive—publié pour Prometheus. Onzième règle d'alerte : « Modèle de prévision en
dérive ». Deux correctifs de relecture sont dans le lot : une passe sans
entrée ne peut plus rendre « stable », et la surveillance cesse d'annoncer
« stable » quand elle ne mesure plus.
public.mesure— ce que le pavé « état des sites » affiche — tombe de 9-23minutes à 6-15, et le seau horaire devient lisible à
(H+1):10. La crontabet la politique de l'agrégat continu partent dans le même lot, dans cet
ordre.
#153 étaient trois classes Python à seuils constants ; elles deviennent des
données dans un
regles.tomlde formes paramétrées, modifiable sansredéploiement. La version et l'empreinte du jeu appliqué sont écrites sur
chaque recommandation (migration 0021), donc on peut toujours dire quel jeu a
produit quoi.
fmt,validate,tflintetcheckovtournent sans jamais joindre Azure —init -backend=false, aucun secret, aucune variableARM_*. Les tâches sedésistent quand la demande ne touche pas Terraform, et les neuf constats de
checkov portent désormais leur motif écrit sur la ressource.
git bundle --alltous lesjours à 03:10, sept bundles gardés dans
/var/backups/depot. Le serveur nepousse rien vers l'extérieur : c'est un poste d'équipier qui vient chercher.
stored access policy révocable sans toucher à la clé du compte, au coffre
ansible-vault, avec son runbook de rotation et de révocation.
docs/RECETTE.md, la grille des 28 exigences,jouée contre la production
415af1a: 22 tenus, 5 tenus avec réserve, 1 nontenu, 2 non joués. Le chemin complet, de la minute collectée à la
recommandation affichée, rejoué d'un seul tenant en 3 min 25 s.
vivait quinze minutes sans rien pour le renouveler : un écran actif était
coupé quinze minutes après la connexion. La coupe se fait maintenant sur
quinze minutes d'inactivité.
Relecture
Où regarder en priorité
1. Le retour arrière n'est plus gratuit. Les deux migrations partent avec le
déploiement, appliquées par le rôle
app. La 0021 est additive et se défaitsans perte (deux colonnes nullables, les lignes antérieures marquées
avant-116). La 0022 change une politique de rafraîchissement continu : ladéfaire demande de reposer les offsets du #205, et il faut le faire dans le même
geste que le retour de la crontab, sinon l'agrégat reste en arrière de la série
qu'il résume. Un
git revertseul remettrait la crontab au quart d'heure enlaissant la politique aux dix minutes — l'inverse exact du défaut d'origine du
#205. À dire avant de fusionner, pas pendant l'incident.
2. L'ordre crontab → politique est un invariant, pas une préférence. Le seau
H:00entre dans la fenêtre de rafraîchissement à(H+1):10; la chaîne achèvel'heure
Hen base à(H+1):05au pire. Cinq minutes séparent les deux. Ledéploiement pose les deux ensemble,
tests/ci/test-fraicheur-chaine.shtientl'invariant en lisant la cadence déclarée. Le point à surveiller après la
fusion est le premier rafraîchissement qui suit le passage de la crontab.
3.
regles.tomlest posé avecforce: false, et le serveur diverge du dépôtà partir de là. C'est la condition du #116 — le déploiement continu rejoue
--tags app,proxy,backupà chaque fusion, une copie qui écrase effacerait touterègle ajoutée en exploitation. Conséquence à assumer : après cette mise en
production,
services/recommendations/regles.tomlest un jeu de référence, etce qui tourne est
/etc/enervision/regles.toml.recommandation.jeu_empreintedit lequel a produit une recommandation donnée. La tâche est placée après
l'installation des dépendances Python parce que son
validatelancel'interpréteur du venv — plus haut, elle arrêtait le rôle avant les migrations
sur un hôte neuf.
4. Onze alertes chargées, dix annoncées.
rules.yamlporte onze règlesdepuis le #117, mais
docs/PRD.md(ENF-08),docs/BACKLOG.mdetdocs/runbooks/supervision.mddisent encore dix — le même écart que la #232avait corrigé de cinq à dix. C'est de la documentation seule, rien ne casse,
mais l'ENF-08 est une exigence sur laquelle le jury compte, et le dossier doit
dire le vrai. À corriger avant le 11, dans ce lot ou juste après.
5. Le #116 est un ticket
Portée/Post-jury, fusionné quand même. Il esthors fenêtre par la décision du 03/09 (réduction 1 de
docs/BACKLOG.md). Letravail a été fait le 09 à la demande, le libellé du ticket n'a pas bougé, et le
backlog comme le PRD portent l'écart en clair. Le signaler ici parce que c'est
la fusion vers
mainqui le met en production, et que c'est une décisiond'équipe, pas un acquis.
6. Le déploiement n'est toujours pas gardé par la chaîne.
deploy.ymlsedéclenche sur
push: main, sansworkflow_runnineeds. Sur un exécuteurunique, le déploiement peut partir avant le verdict. La barrière reste une
convention de fusion. Inchangé depuis la #220, redit ici parce que cette
livraison porte deux migrations et que le coût d'un départ anticipé n'est plus
le même.
7. Le rôle
appdéploierepo_version: develop, pasmain. La fusion versmaindéclenche donc un déploiement dont le contenu vient dedevelop—identique à ce qui est fusionné à cet instant, mais seulement parce que les deux
branches coïncident au moment du push. Constat antérieur, sans ticket, redit ici
comme à la #233.
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>Passage enchaîné du 09/09, 12:10:58 -> 12:14:23 UTC : étapes 1 à 4, 7 et 8 dans l'ordre, sans incident, 3 min 25 s. La ligne entre au journal des répétitions avec sa portée exacte — les étapes 5 et 6 n'ont pas été rejouées, la session du navigateur avait expiré, et l'étape 9 est un document. Ce que ce chiffre établit et ce qu'il n'établit pas est écrit sous le tableau : l'exécution technique est le plancher, la répétition du 10 mesure autre chose, le récit à cinq voix dans un budget de 20 minutes. R1 est repris avec sa cause, désormais prouvée à la source : {"timestamp":"2026-09-09T14:13:07.790800", ...} à 12:13:07 UTC L'horloge de la source avance de deux heures et n'écrit aucun fuseau. Les deux chemins d'ingestion en tirent deux conclusions différentes : les mesures ramènent la valeur en UTC et sont justes, les alertes et le nommage des objets bruts la rangent telle quelle et sont fausses de deux heures. Le défaut est donc localisé, et le chemin des mesures porte déjà la conduite à recopier. ENF-08 gagne une preuve : l'issue #243 est refermée par le relais au retour à la normale. Le cycle de vie est tenu de bout en bout, pas seulement l'ouverture. Refs #47