Jalon : develop vers main, socle complet et déployé #129

Merged
lenaic merged 195 commits from develop into main 2026-09-03 15:04:47 +00:00
Owner

Ce que ça change

Premier passage de develop vers main depuis le 31 août : 195 commits.

Tout ce que cette fusion porte est déjà déployé sur le serveur et vérifié, par deux
déclenchements manuels de deploy.yml sur develop cet après-midi. La fusion ne fera que
rejouer le même déploiement.

Preuve, relevée sur le serveur

collecteur     7/7 objets par passe, bronze +21 objets en 3 minutes
supervision    5 conteneurs sains, pg_up 1, 1 790 métriques PostgreSQL
grafana        https://grafana.g2.enervision -> 200
forge          200 avant, pendant et après les deux déploiements
migrations     15 appliquées sur prod et préprod, 0 objet hors du rôle applicatif
crontab        2 lignes, inchangées après un second passage

Le conteneur Caddy affiche toujours « Up 2 days » : il a été rechargé, jamais recréé.

Le seul effet à connaître

postgres et mlflow portent build: true, donc leurs conteneurs sont recréés à
chaque déploiement — quelques secondes de connexions coupées. Le volume survit. C'est
antérieur à ces 195 commits et ça mérite son ticket, pas un correctif de dernière minute.

Ce qui reste ouvert et ne bloque pas

Le passage de la forge en réseau bridge (#102), le retrait de la socket Docker des
conteneurs de tâche (#103), et la chaîne IA en aval de la zone argent (#34).

## Ce que ça change Premier passage de `develop` vers `main` depuis le 31 août : **195 commits**. **Tout ce que cette fusion porte est déjà déployé sur le serveur et vérifié**, par deux déclenchements manuels de `deploy.yml` sur `develop` cet après-midi. La fusion ne fera que rejouer le même déploiement. ## Preuve, relevée sur le serveur ``` collecteur 7/7 objets par passe, bronze +21 objets en 3 minutes supervision 5 conteneurs sains, pg_up 1, 1 790 métriques PostgreSQL grafana https://grafana.g2.enervision -> 200 forge 200 avant, pendant et après les deux déploiements migrations 15 appliquées sur prod et préprod, 0 objet hors du rôle applicatif crontab 2 lignes, inchangées après un second passage ``` Le conteneur Caddy affiche toujours « Up 2 days » : il a été rechargé, jamais recréé. ## Le seul effet à connaître `postgres` et `mlflow` portent `build: true`, donc leurs conteneurs sont **recréés** à chaque déploiement — quelques secondes de connexions coupées. Le volume survit. C'est antérieur à ces 195 commits et ça mérite son ticket, pas un correctif de dernière minute. ## Ce qui reste ouvert et ne bloque pas Le passage de la forge en réseau bridge (#102), le retrait de la socket Docker des conteneurs de tâche (#103), et la chaîne IA en aval de la zone argent (#34).
lenaic self-assigned this 2026-09-03 15:01:20 +00:00
outillage: deux skills Claude Code et un acces a l'API de la forge
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 3s
Intégration / Tests unitaires (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
6aa4c3551d
Reprise de la branche precedente, debarrassee de la convention de branche :
Florian l'a ecrite dans sa demande #14, avec la meme regle. Ne restent que
les elements qui n'existent nulle part ailleurs.

- .claude/skills/point-du-soir : rassemble les faits mesurables de la journee
  et redige un brouillon de journal. Laisse explicitement en attente ce que
  seul un humain sait, plutot que de fabriquer du plausible.
- .claude/skills/ticket : transforme une phrase en ticket bien forme, avec la
  liste des exigences en reference.
- bin/fj : appelle l'API de la forge, par jeton personnel si present, par SSH
  sinon.
docs: exigences collectives du groupe, version 1.0
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 3s
Intégration / Tests unitaires (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
94738ad813
Issues de la reunion d'architecture du 31 aout, des arbitrages du Tech Lead et
des questions tranchees le 1er septembre. Remplacent les exigences des six
dossiers EC01 individuels pour ce que le groupe construit.

Douze EF inchangees : aucune decision d'architecture ne modifie un comportement
produit. Seize ENF revisees, dont quatre nouvelles, autorisation par ressource,
cloisonnement par adresse d'ecoute, attribution nominative et secrets.

Sept ecarts avec les dossiers individuels sont listes pour etre traces comme des
revisions, la qualite des revues de conception etant un critere note.

Points tranches le 1er septembre : entrainement du modele en local sur le T4 du
serveur, Vue plus mandataire FastAPI, medaillon complet avec zone or
materialisee, Keycloak en schema dedie, deploiement par Ansible, deux noms
d'hote sur le meme Caddy, un seul client, secrets dans le magasin Forgejo.

L'abonnement Azure de l'ecole est limite et sans Entra ID : le besoin cloud est
servi par un plan de migration chiffre plutot que par un deploiement complet.
docs: exigences collectives v1.1, architecture cible et plan de charge
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 3s
Intégration / Tests unitaires (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
d3c6dfc605
Ajoute quatre sections au document collectif.

Architecture cible : dix composants avec leur port d'ecoute et leur pilote, le
chemin de la donnee de bronze a la zone or materialisee, et le chemin de
l'authentification par le mandataire FastAPI.

Plan d'adressage : treize ports, dont ceux de MinIO qui manquaient. Tant que le
LXC interdit les reseaux Docker, la colonne exposee est la seule frontiere de
securite du serveur.

Repartition et croisements : qui construit quoi, et qui devra en parler a son
entretien individuel, ce qui n'est pas la meme chose. Un croisement se prouve
par un commit, pas par une intention.

Plan de charge recalcule sur l'architecture collective : 46 jours-homme estimes
pour 31 de capacite utile, soit un deficit de 15 contre 4 dans les dossiers
individuels. L'ecart vient des ajouts collectifs, MinIO, medaillon, Keycloak et
Ansible pesant 11 a eux seuls. Trois reductions sont proposees, elles ne
comblent que 5 jours-homme. Le reste est a arbitrer en equipe.
docs: fixe le flux de branches et la relecture obligatoire
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 3s
Intégration / Tests unitaires (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
fa6ab4285d
Les branches de travail partent de develop et y retournent, main ne reçoit
que develop. Une approbation devient obligatoire pour fusionner, sur les
deux branches, ce que CONVENTIONS.md affirmait déjà sans que la forge le
fasse respecter.
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/49
État constaté du cluster 17/main sur 10.105.200.41 : paquets et dépôts
tiers, port 5433, les trois fichiers de configuration réellement lus,
bases, rôles et accès réseau. Chaque réglage porte son motif.
Script, planification, rétention et format des vidages. Dit aussi ce que
le dispositif ne couvre pas, au regard de l'exigence ENF-05.
Séquence complète, des dépôts tiers jusqu'à la première sauvegarde.
L'ordre compte : la configuration est posée avant la création des rôles.
Douze écarts, chacun avec sa gravité et son correctif. Les quatre premiers
touchent l'exposition réseau, les mots de passe et l'absence d'alerte de
sauvegarde.
Se connecter, contrôler l'état en quatre commandes, et relancer le
service. Toutes les commandes du manuel ont été exécutées sur le serveur.
Connexions, requêtes longues, verrous bloquants, transactions oubliées,
disque, VACUUM, tâches TimescaleDB et lecture du journal.
Contrôler qu'un vidage est valide, le relancer à la main, et restaurer :
à côté d'abord, en écrasement ensuite.
Du 503 de l'API jusqu'à la cause, pour ne pas chercher au hasard en
pleine panne.
La boucle du script nommait encore la base enervision, supprimée à la
scission en enervision_prod et enervision_preprod. Symptôme, portée,
correctif appliqué et preuve d'exécution.
docs: indexe le manuel PostgreSQL dans les runbooks
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 3s
Intégration / Tests unitaires (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
43e893c239
Table des manuels disponibles et liste de ce qui reste à écrire.
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/52
L'expression régulière qui tenait ce contrôle mentait dans les deux sens :
elle refusait « FROM base AS runtime » et « FROM scratch », qui sont
légitimes, et laissait passer « FROM --platform=... node », qui ne l'est
pas. Un contrôle qui rougit sur du code correct finit désarmé.

Le contrôle devient .forgejo/scripts/verifier-images.sh : il lit les FROM
des Dockerfiles et les image: des Compose, connaît les étages de
construction, scratch, les empreintes sha256 et les références bâties par
variable. Ses cas d'essai vivent dans tests/ci et se jouent avant lui.

Ref #40
- pool psycopg (enervision_api.db), migrations appliquées au démarrage
  avec verrou consultatif ; CLI python db/migrate.py
- db/migrations/0001_auth.sql : schéma ops (users, user_sites, auth_sessions)
- auth/service : login, refresh (rotation + détection de rejeu), logout
- auth/repository et auth/models
- tests d'intégration du service, skippés sans PostgreSQL
- venv unique à la racine ; .env résolu depuis services/api/

Refs #38

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
La chaîne annonçait des contrôles qu'elle ne rendait pas.

mypy : « cd services/api && mypy enervision_api » ne chargeait aucune
configuration, mypy ne la cherche que dans le répertoire courant et le
pyproject.toml strict vit un cran plus bas. Du code non annoté passait au
vert. La configuration est passée en --config-file, et tous les services
sont typés, plus seulement l'API.

Couverture des zones sensibles : la garde testait la présence du
répertoire. « services/collector » existe déjà avec son seul README, donc
« coverage report » aurait tourné sur zéro fichier mesuré et échoué sur
« No data to report » — chaîne rouge pour toute l'équipe dès le premier
test unitaire écrit, avec un message qui ne parle pas de couverture. La
garde cherche maintenant du Python, pas un répertoire.

Audit des dépendances de développement : en scalaire YAML simple, « || \ »
se replie en « || \ echo » et bash meurt sur « command not found ». L'étape
« non bloquante » bloquait. Passée en bloc.

Audit des dépendances applicatives : bloquant, sans interrupteur global.
L'échappatoire est --ignore-vuln, une CVE à la fois, justifiée. C'est le
critère d'acceptation du ticket.

La chaîne se joue aussi sur les poussées vers develop, qui est la branche
d'intégration : une fonctionnalité qui casse le lint ne s'y fusionne pas.
Ajout d'une exclusivité par référence et d'un délai maximum par tâche, le
runner est unique. Outillage épinglé au numéro exact dans
requirements-dev.txt, seule source de vérité pour la chaîne comme pour les
postes — et l'inventaire CycloneDX en sort enfin avec de vraies versions.
Les dépendances des services sont lues dans leurs pyproject.toml au lieu
d'être recopiées dans le workflow.

Ref #40
- POST /auth/login, /auth/refresh, /auth/logout, GET /auth/me
- les jetons ne sortent qu'en cookies httpOnly : ev_access (Lax, /),
  ev_refresh (Strict, /auth) ; le SPA ne les voit jamais
- get_current_user : cookie ev_access, repli Authorization: Bearer
- PublicUser en réponse, jamais le password_hash
- tests/integration/test_auth_flow.py : login, me, refresh + rejeu, logout

Refs #38

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ce que chaque tâche prouve, ce qu'elle bloque, et quoi taper quand elle
rouge. Les pièges qui ont coûté du temps y sont nommés : mypy qui ignore
sa configuration sans --config-file, « coverage report » qui échoue sur
« No data to report » plutôt que sur un palier, le contrôle des images qui
rejetait les étages de construction.

Deux sections qui ne parlent pas de commandes : ce que la chaîne ne peut
pas faire seule — c'est la protection de branche de la forge qui refuse la
fusion, pas le workflow — et les points restés ouverts, pour qu'ils soient
lus plutôt que redécouverts.

Ref #40
La chaîne écrit sbom-python.json, sbom-dashboard.json et coverage.xml à la
racine avant de les publier en artefacts. Reproduire la chaîne en local les
laissait en fichiers non suivis, à un « git add . » d'être commités.

Ref #40
ENF-10 demande l'inventaire des composants livrés. La chaîne ne produisait
que celui de requirements-dev.txt : pytest, ruff et mypy ne partent pas en
production, fastapi et ses dépendances transitives si.

L'inventaire applicatif est bâti depuis un environnement résolu depuis les
pyproject.toml des services, donc avec les versions réelles et les
dépendances transitives — 21 composants sur le périmètre actuel, contre 7
pour l'outillage. Les deux documents restent séparés : mélangés, aucun des
deux ne se lit.

Ref #40
« --cov=services » ne mesurait que ce que les tests avaient importé :
coverage élague tout répertoire sans __init__.py, et services/api n'en a
pas. Un unique test sur la sonde de vivacité rendait 100 % de couverture
globale, sur 8 lignes mesurées au lieu de 32. Le palier global aurait été
décoratif quelle que soit sa valeur — 70 % serait passé sans rien prouver.

.coveragerc pose include_namespace_packages, lu aussi bien par pytest-cov
que par les appels directs à « coverage report » du palier ciblé. Le même
test rend maintenant 27 %. Deux exclusions honnêtes au passage : le bloc
__main__ et les gardes TYPE_CHECKING.

Ref #40
Valeurs exigées par ENF-09 et par le critère d'acceptation du ticket #40.
Elles ne coûtent rien tant qu'aucun test n'existe, la tâche se saute.

Le premier test unitaire écrit fera rougir la chaîne : la mesure porte sur
tout le code de services et packages, pas sur ce que ce test importe, et la
base est à 27 %. C'est assumé et documenté — celui qui écrit ce test hérite
de la dette de tous, donc l'écart se traite au point du matin, dans un
ticket dédié, jamais en baissant le seuil pour débloquer sa propre demande
de fusion.

Ref #40
[28] Mise en place MiniO
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 3s
Intégration / Tests unitaires (pull_request) Successful in 2s
Intégration / Aucun secret commité (pull_request) Successful in 2s
28f07d0130
- rôles réduits à user et admin (défaut user), contrainte et Literal alignés
- require_role(*roles) + alias AdminOnly ; POST /auth/users réservé admin
- claim role dans l'access token
- retrait du cloisonnement par site : table user_sites, require_site,
  routes /sites (cf. réunion §2.3, question laissée ouverte)
- tests d'intégration conservés mais non lancés sans PostgreSQL joignable

Refs #38

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs: nomme les contrôles obligatoires comme la forge les voit
Some checks failed
Intégration / Qualité du code Python (pull_request) Successful in 10s
Intégration / Tests unitaires et couverture (pull_request) Successful in 8s
Intégration / Images épinglées par version (pull_request) Successful in 14s
Intégration / Dépendances et inventaire (pull_request) Failing after 17s
Intégration / Aucun secret commité (pull_request) Successful in 3s
cfbb0963a0
La protection de branche Forgejo désigne un contrôle par le « name: »
affiché de la tâche, pas par sa clé dans le YAML. Le manuel listait les
clés : les saisir tel quel donne un contrôle qui n'arrive jamais et une
fusion bloquée en attente, sans message.

Le piège se pose dès cette branche : la tâche « Tests unitaires » devient
« Tests unitaires et couverture ». D'où le geste en deux temps documenté.

Ref #40
- 0002_auth_events.sql : journal d'audit ops.auth_events (EC04)
- record_event sur login ok/ko, refresh ok, rejeu, logout, création de compte
- limite de débit /auth/login : 5 échecs par IP en 15 min -> 429
- middleware Origin : 403 sur méthode mutante si ENERVISION_API_ALLOWED_ORIGIN
  est défini et ne correspond pas (les cookies SameSite restent la 1re barrière)
- run_purge au démarrage : sessions expirées depuis plus de 7 jours
- get_settings via model_validate (robuste au retrait du type: ignore)

Non rejoué contre PostgreSQL (pas de conteneur). pytest tests/integration à passer.

Refs #38

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs: raccourcit le manuel PostgreSQL et le rend reprenable par tous
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 3s
Intégration / Tests unitaires (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
0ff75b0b14
Le manuel faisait 670 lignes, illisible en panne. Il en fait 377, organisées
par symptome et numerotees, avec un arbre de decision qui renvoie aux sections.

- entree unique : le tableau de l'essentiel, les trois pieges de depart,
  les quatre controles de trente secondes
- une section par symptome plutot qu'une par concept PostgreSQL
- requetes de diagnostic reduites aux colonnes qu'on lit vraiment
- les garde-fous rassembles en cinq regles, au lieu d'etre dilues dans le texte
- incident du 1er septembre resume en un paragraphe, la preuve detaillee
  restant dans le ticket

Aucune commande retiree du perimetre operationnel : connexion, relance,
diagnostic, disque, TimescaleDB, sauvegarde et restauration sont tous couverts.
ci: fixe l'image de conteneur des tâches Python et Node
Some checks failed
Intégration / Qualité du code Python (pull_request) Failing after 8s
Intégration / Tests unitaires et couverture (pull_request) Failing after 7s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Failing after 3s
Intégration / Images épinglées par version (pull_request) Successful in 4s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 10s
4d9fd5dd33
pip: command not found sur la tâche « dependances » à l'ouverture de la
PR #57. L'image par défaut du label « docker » n'a jamais eu pip sur le
PATH : en reprenant l'historique des exécutions, la même cause explique
deux échecs jamais élucidés, le jour où le premier code Python est arrivé
sur main (exécutions #14 et #15 de « qualite »). Masqué depuis par les
gardes « if: steps.scan… » qui sautent l'installation tant qu'il n'y a pas
de Python à traiter — seule la tâche dependances installait sans condition.

qualite, tests-unitaires et dependances-python fixent désormais
python:3.12.14-slim-bookworm, épinglé par empreinte. Le tableau de bord
n'a jamais été exercé non plus sur une branche qui joue la chaîne : plutôt
que de découvrir de la même façon que l'image par défaut n'a pas npm, ou
la mauvaise version de Node, dependances-dashboard est scindé de
dependances-python et fixe node:22.18-bookworm-slim — la borne basse
exacte de services/dashboard/package.json.

Les deux images sont vérifiées avec Docker en local, pas seulement
simulées : outillage complet installé, mypy strict et pytest --cov
rejoués sur le code réel de services/api (main), npm ci + npm audit +
npm sbom rejoués sur le vrai tableau de bord (main, 147 composants,
0 vulnérabilité).

Ref #40
ci: installe node et git dans les images Python (checkout en dépendait)
Some checks failed
Intégration / Qualité du code Python (pull_request) Successful in 12s
Intégration / Tests unitaires et couverture (pull_request) Successful in 11s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 4s
Intégration / Images épinglées par version (pull_request) Successful in 2s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Failing after 3m31s
25350e68ea
Corriger l'image a cassé actions/checkout à son tour :
« node: executable file not found in $PATH ». Vérifié dans le journal brut
du job (récupéré directement depuis le stockage de Forgejo, task 80,
qualite) : checkout est écrit en JS et exige node sur le PATH du conteneur,
absent de python:3.12-slim.

La preuve que node seul suffit, sans git : dependances-dashboard tourne sur
l'image Node, n'a jamais eu git, et n'a jamais échoué sur son checkout.
git reste nécessaire pour nos propres étapes (git ls-files, dans mypy et
l'audit), d'où le nouveau premier pas de qualite, tests-unitaires et
dependances-python : apt-get install nodejs git ca-certificates avant tout
le reste.

Revérifié dans le vrai conteneur, dans l'ordre exact des étapes : bootstrap
apt, pip install, mypy strict sur le code réel de services/api (main) ->
succès.

Ref #40
ci: n'échoue plus un job entier sur un envoi de SBOM raté
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 12s
Intégration / Tests unitaires et couverture (pull_request) Successful in 11s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 5s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 3m29s
d6b7cf8641
Le premier envoi d'artefact avec du contenu réel jamais tenté sur ce dépôt
a échoué : unable to get local issuer certificate, vers
https://10.105.200.41/api/actions_pipeline/.../artifacts/... Vérifié dans
le journal réel du job (task 88) puis sur le serveur : le certificat de
10.105.200.41 est émis par une autorité locale à Caddy, reconnue par aucun
magasin de confiance standard, intermédiaire renouvelé chaque semaine,
feuille toutes les 12h — rien de stable à figer dans une image de job.

Ce n'est pas un problème que ce fichier peut corriger : c'est une décision
d'infrastructure (faire pointer les envois d'artefact sur l'adresse locale
en clair, ou donner à Caddy un certificat reconnu), documentée dans
docs/runbooks/ci.md pour être tranchée en équipe.

En attendant, continue-on-error: true sur les trois envois de SBOM. C'est
cohérent avec ce que le runbook dit depuis le début : l'inventaire ne
bloque jamais. Sans ce réglage, un souci réseau sur un envoi non bloquant
faisait échouer tout le job dependances-python — y compris l'audit CVE des
dépendances applicatives, lui bloquant, réuni dans le même job pour de
mauvaises raisons.

Ref #40
- CORSMiddleware dans main.py, activé si ENERVISION_API_ALLOWED_ORIGIN est défini
  (allow_credentials pour les cookies) ; même réglage que le contrôle Origin
- suppression de tests/integration (nécessitaient PostgreSQL)
- tests/unit/test_auth_service.py : login / refresh / rotation / rejeu / logout
  avec un dépôt en mémoire, aucune infra
- retrait de httpx des dépendances de dev

Suite : 21 tests unitaires, pytest tourne sans serveur ni conteneur.

Refs #38

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- /refresh : fenêtre de grâce (refresh_grace_seconds, 10 s) — un double appel
  concurrent ne révoque plus la famille ; corrige la déconnexion multi-onglets
- PATCH /auth/users/{id} (admin) : désactiver, changer le rôle ou le mot de passe ;
  révoque les sessions de l'utilisateur ; refuse l'auto-rétrogradation d'un admin
- limite de débit du login par (ip, email) au lieu de (ip)
- _client_ip ne lit X-Forwarded-For que si trust_proxy, et prend le dernier élément
- 0003_auth_indexes.sql : index family_id, index login_ko (ip, email)
- longueurs bornées sur email et mot de passe (Credentials, NewUser, UserPatch)

25 tests unitaires, toujours sans base.

Refs #38

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/58
docs : documentation de conception de l'API, commentaires et OpenAPI
Some checks failed
Intégration / Qualité du code Python (pull_request) Failing after 2s
Intégration / Tests unitaires (pull_request) Failing after 2s
Intégration / Aucun secret commité (pull_request) Failing after 2s
dca2786a4f
- docs/api/ : 6 fiches (architecture, authentification, sécurité, configuration,
  exploitation, décisions et limites)
- README du service réécrit : variables de config, routes, section tests
- commentaires techniques sur les points non évidents (temps constant, rejeu et
  fenêtre de grâce, verrou de migration, X-Forwarded-For, path=/auth)
- summary et description sur chaque route → /docs lisible

Refs #38

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
infra: squelette du déploiement Ansible et sa validation CI
Some checks failed
Intégration / Qualité du code Python (pull_request) Successful in 3s
Intégration / Tests unitaires (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Infra Ansible / Playbooks Ansible valides (pull_request) Failing after 3s
a1d86bff82
Arborescence et points d'entrée d'infra/ansible/ (site.yml, restore.yml,
rôles vides base/deploy_user/docker/app/backup), runbooks déploiement et
reprise, et un workflow Forgejo qui se borne à valider la syntaxe des
playbooks. Le déploiement reste manuel ; les rôles et la chaîne de
déploiement viennent dans les tickets suivants.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKrHL534hkFX6vo2HZppdE
Les commits dd41634 et a0ed7eb (ticket #27) ont été fusionnés directement dans
main, alors que la convention veut qu'une branche parte de develop et y
retourne, et que main ne reçoive que develop. services/dashboard/ était donc
vide sur develop et le ticket #25 n'avait pas de squelette où se poser.

Aucun conflit : les deux branches ont touché des chemins disjoints.
docs: rapatrie les manuels de la forge dans le dépôt
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 3s
Intégration / Tests unitaires (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
ed8b21f060
Les gestes d'exploitation, la description de l'installation, la surface de
sécurité et le journal du 31 août ne vivaient que sur le poste du Tech Lead.
Le ticket #11 y renvoyait, ce qui faisait trois renvois morts.

Les commandes sont réécrites pour être jouables telles quelles sur le
serveur, sans passer par l'outillage local, et l'accès se fait sous compte
nominatif et non plus sous root.

Closes #11
docs: corrige la relecture de Lénaïc sur ci.md et ci.yml
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 12s
Intégration / Tests unitaires et couverture (pull_request) Successful in 12s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 5s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 3m29s
7ab3def62f
- protection de branche par motif Intégration / * réellement en place,
  pas par noms exacts : la procédure de renommage en deux temps n'a pas
  lieu d'être
- titre du tableau des tâches (six, pas cinq)
- seuils de couverture rattachés à ENF-10 (qualité), pas ENF-09
  (reproductibilité Ansible)
- inventaire SBOM rattaché à ENF-11 (chaîne d'approvisionnement), pas
  ENF-10

Ref #40, relecture https://10.105.200.41/g2/enervision/pulls/57
ci: fixe l'image du job de validation Ansible (pip absent du label docker)
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 3s
Intégration / Tests unitaires (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 41s
ffed26d776
Le job « verifier-playbooks » tournait sur l'image par défaut du label
« docker », sans pip : « pip: command not found », le même échec que la
PR #57. On applique la convention de ci.yml (ticket #40) : image Python
épinglée par empreinte, et node + git installés avant actions/checkout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKrHL534hkFX6vo2HZppdE
Trois des quatre cadres du fichier Figma : « au repos », « identifiants
refusés » et « mobile 390 ». Point de rupture unique à 900 px, colonne
d'identité dans la proportion 620 / 820 du cadre 1440.

Le thème vit dans src/assets/jetons.css, relevé sur les variables Figma et
non sur la planche « Socle visuel » : les deux divergent sur six couleurs, et
c'est la variable que les cadres appliquent. Le détail et les contrastes
recalculés sont dans le README du service. L'ensemble atteint le niveau AA.

Accessibilité : libellés rattachés par « for » et « id », erreurs rattachées
par « aria-describedby », échec annoncé une seule fois dans une zone
« role=alert », focus rendu au champ à corriger, bascule de révélation avec
« aria-pressed », titre de page mis à jour à chaque navigation.

Deux écarts assumés à la maquette, tous deux pour l'ordre de tabulation :
« Mot de passe oublié ? » reste au dessus du bouton aux deux largeurs, et le
panneau d'identité vient après le formulaire dans le DOM.

Ne ferme pas #25 : le critère « l'utilisateur peut se connecter » reste ouvert
tant que le groupe n'a pas tranché entre le formulaire de la maquette et
l'identification déléguée du contrat gelé. Les deux chemins sont écrits dans
src/api/authentification.js, seul fichier à changer.

La démonstration livrée par create-vue est retirée : plus rien ne la référence
depuis que la racine mène à /connexion.

Ticket #25
La chaîne ne connaissait que Python. Les tests du tableau de bord ne
s'exécutaient donc nulle part, et « Tests Unitaires » en preuve du ticket #25
n'aurait rien prouvé.

Même idiome que les autres tâches : un scan décide, et l'étape se saute d'elle
même si services/dashboard/package.json n'existe pas.

Recoupe le ticket #57, qui étend la chaîne : à réconcilier si les deux
arrivent en même temps.

Ticket #25
[28] Ajout installation mc
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 3s
Intégration / Tests unitaires (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
56e3bbe6b7
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/60
Reviewed-by: marvin <marvin@noreply.10.105.200.41>
[28] MàJ ENF & version stricte boto3
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 3s
Intégration / Tests unitaires (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
9e712021b9
outillage: aligne le référentiel du skill ticket sur les exigences du groupe
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 3s
Intégration / Tests unitaires (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
0ecca5ff34
Le fichier de référence portait la numérotation de mon dossier EC01
individuel, douze exigences non fonctionnelles, alors que le groupe en
compte seize et que les deux ne coïncident pas. ENF-02 y valait
Confidentialité au lieu d'Autorisation, ENF-09 Qualité logicielle au lieu
de Reproductibilité.

Conséquence : tous les tickets et toutes les demandes de fusion qui ont
cité une exigence depuis la mise en place du skill portent un numéro
décalé, y compris le ticket #11.

Le fichier est désormais dérivé de docs/EXIGENCES-collectives.md et le
déclare comme source unique.
Merge develop dans la branche de la chaîne
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 12s
Intégration / Tests unitaires et couverture (pull_request) Successful in 13s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 4s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 3m29s
ee2d0c4b2f
Le manuel de la forge et celui de la chaîne s'ajoutent tous deux à l'index
des manuels. Les deux lignes sont conservées.
Merge pull request 'ci: fait de la chaîne une barrière (couverture, mypy, dépendances, images)' (#57) from gabriel/40-ci-barriere into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 12s
Intégration / Tests unitaires et couverture (push) Successful in 11s
Intégration / Dépendances du tableau de bord (push) Successful in 5s
Intégration / Images épinglées par version (push) Successful in 3s
Intégration / Aucun secret commité (push) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 3m33s
8a2d057246
Merge develop, boto3 epingle comme le reste de l'outillage
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 11s
Intégration / Tests unitaires et couverture (pull_request) Successful in 11s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 5s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 3m35s
31286486b6
La liste passe en versions exactes cote develop : boto3 y entre au
meme format, sans quoi l'inventaire des composants produit un
composant sans numero de version.
Merge pull request '[28] Mise en place MiniO' (#56) from justine/28-installation-MiniO into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 11s
Intégration / Tests unitaires et couverture (push) Successful in 11s
Intégration / Dépendances du tableau de bord (push) Successful in 4s
Intégration / Images épinglées par version (push) Successful in 2s
Intégration / Aucun secret commité (push) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 3m37s
bb412fc8b8
dashboard: ajoute six comptes de démonstration pour la page de connexion
Some checks failed
Intégration / Qualité du code Python (pull_request) Failing after 3s
Intégration / Tests unitaires (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 45s
Intégration / Tableau de bord (pull_request) Successful in 1m4s
0afee61568
La route d'ouverture de session n'existe pas côté API : sans jeu de comptes,
la page de connexion ne se démontrait pas et ses trois cadres restaient
inatteignables à la main.

Les comptes sont des données, pas du code : comptes-de-demonstration.json
porte les six adresses <prenom>.g2@enervision.fr, leur rôle et leurs sites.
Ajouter ou retirer quelqu'un ne demande de toucher à aucune logique. Aucune
dépendance ajoutée, Vite et Vitest importent le JSON nativement.

Les rôles suivent l'énumération du contrat gelé — lecteur, gestionnaire,
administrateur — et non les deux du pied de la maquette, où « exploitant »
n'existe pas au contrat. À trancher au ticket #53.

Le jeu rejoue les échecs de la maquette : 401 avec le décompte des tentatives,
puis 429 après quatre essais sur une même adresse.

Le mot de passe n'entre jamais dans une construction de production. La
première version le laissait pourtant dans le paquet : une garde « if » ne
suffit pas, il faut que le module ne soit pas importé statiquement.
tests/unit/paquet-de-production.test.js construit pour de vrai et vérifie que
ni le mot de passe, ni les adresses, ni le module n'y sont.

L'accusé de session ouverte n'est dans aucun cadre. Il tient lieu de
destination tant que le tableau de bord n'existe pas, et se remplace par une
redirection quand #23 et #24 arrivent.

Ticket #25
Merge develop
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 12s
Intégration / Tests unitaires et couverture (pull_request) Successful in 14s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 3s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 47s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 3m33s
5fbe648eda
docs: journal des jours 2 et 3
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 11s
Intégration / Tests unitaires et couverture (pull_request) Successful in 11s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 4s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 3m34s
723826225f
Reconstitué depuis l'historique et les demandes de fusion, complété par
ce qui s'est fait hors dépôt et qui n'y laisse aucune trace.

Les incidents sont consignés avec leur cause et l'exigence en défaut,
y compris la liste d'exigences fausse que j'ai fournie à l'équipe.
Le service tournait en paquets Debian (postgresql@17-main). Il tourne
desormais dans le conteneur ev-postgres, en reseau hote, sur le meme port
5433. Le port, les noms de bases, la collation et les reglages sont
inchanges : un client existant ne voit aucune difference.

Image maison plutot que timescale/timescaledb-ha, pour trois raisons :
aucune image publiee ne porte la collation fr_FR.UTF-8 dont dependent le
tri des libelles et la recherche plein texte ; postgres:17-bookworm est en
17.11 quand Timescale s'arrete a 17.10, et migrer n'a pas a faire reculer
un correctif ; l'image de Timescale pese 4 Go contre 1,5 ici, sur un
disque de 63 Go partage a six groupes. Les trois versions sont epinglees
et verifiees a la construction : l'image ne sort pas si TimescaleDB
n'annonce pas 2.29.2, le toolkit 1.25.0, ou si la locale manque.

Reseau hote et non port publie, apres mesure : dans la chaine FORWARD,
DOCKER-USER est consultee avant les chaines d'UFW et elle est vide, donc
un port publie par Docker echappe entierement au pare-feu. Une regle
`ufw allow from 10.105.200.0/24` aurait laisse la base joignable depuis
tout le reseau de l'ecole en affichant le bon masque. Le mandataire de
Docker reecrivait en outre l'adresse source des clients locaux, ce qui
privait le diagnostic client_addr de tout sens. C'est aussi la convention
de la machine : les trois conteneurs de la forge sont en reseau hote.

Etat cible pose : quatre roles (enervision_prod, enervision_preprod,
mlflow, grafana), trois bases, chaque role sur sa seule base. Le
cloisonnement est double, par paire (base, role) dans pg_hba.conf et par
REVOKE CONNECT ... FROM PUBLIC en base. Le superutilisateur n'a aucun
acces reseau : l'administration passe par le socket du conteneur.
grafana est en lecture seule sur enervision_prod, y compris sur les
tables futures. Le role et la base keycloak ne sont pas repris.

Corrige deux reglages herites : timescaledb.max_background_workers etait
a 4 pour quatre bases, si bien que les ordonnanceurs prenaient toutes les
places et qu'aucun travail de fond ne pouvait s'executer — les agregats
continus auraient cesse de se rafraichir sans que l'ecran le montre. Et
la sonde de sante inscrivait un FATAL au journal toutes les 30 secondes
parce que docker exec entre en tant que root.

La sauvegarde tourne desormais sous root : joindre la base demande
docker exec, et mettre le compte postgres dans le groupe docker
reviendrait a lui donner l'equivalent de root. Elle interroge le
catalogue au lieu d'une liste figee, ecrit son etat dans un fichier
relevable, et un second script verifie que chaque vidage est lisible.
Le repertoire passe en 750 root:root, les vidages en 0640.

Verifie : 16 essais d'acces depuis la boucle locale et depuis l'adresse
reseau, pare-feu eprouve depuis un poste hors du /24, empreintes md5 du
schema ops identiques avant et apres reprise, restauration a cote des
trois bases sans erreur, epreuve de redemarrage passee.

Refs docs/POSTGRESQL.md
Tous les gestes du manuel ont change de forme : plus de systemctl, plus de
sudo -u postgres, plus de fichier dans /var/log/postgresql. Aucun n'a
change de sens. Les commandes ont toutes ete rejouees sur le serveur, y
compris les contre-epreuves.

POSTGRESQL.md decrit l'etat constate au 2 septembre : l'image et ses
versions epinglees, pourquoi une image maison et pourquoi le reseau hote,
l'emplacement de la configuration et le fait que le postgresql.conf du
volume n'est pas lu, les quatre roles et les trois bases, les trois
couches d'acces reseau qui disent toutes 10.105.200.0/24, la sauvegarde,
et le cluster natif conserve comme retour arriere derriere trois verrous.

Consigne aussi ce que la version precedente ne disait pas : le schema ops
d'enervision_preprod porte de vraies donnees — le socle
d'authentification de l'API, quatre tables. C'est le seul contenu
applicatif migre, et sa reprise a ete verifiee par empreinte.

Le tableau des ecarts est repris : cinq sont fermes par la migration
(l'ouverture a 0.0.0.0/0, les droits du repertoire de sauvegarde, le
conf.d imbrique, les bases residuelles, la ligne en doublon). Trois sont
nouveaux : le conflit entre la suppression du role keycloak et
EXIGENCES-collectives.md, qui reste a arbitrer en groupe et pour lequel
ce document ne touche pas au fichier collectif ; le fait qu'un port
publie par Docker echappe a UFW, qui vaut pour toute la pile Compose a
venir ; et le cluster natif a purger apres la demonstration.

L'ecart n°2 est requalifie et il est le plus urgent : les quatre mots de
passe EN SERVICE figurent en clair dans /var/lib/postgresql/.psql_history
et /root/.bash_history, lisibles par quiconque a sudo. Une purge
chirurgicale est prete sur le serveur mais non executee, et purger ne
defait pas la fuite — la rotation des quatre secrets reste a faire.

Le manuel gagne un raccourci qui evite les deux pieges les plus couteux
(l'oubli de -e PGPORT=5433 et de -u postgres), un arbre de decision refait
sur les messages d'erreur reels, une section sur le pare-feu et son
epreuve depuis l'exterieur, et trois pieges mesures pendant la migration :
sudo ne couvre pas la redirection du shell, docker cp depose un fichier
que postgres ne peut pas lire, et pg_restore -l /dev/stdin ne fonctionne
pas a travers docker exec.

Refs infra/compose/postgres/
Merge remote-tracking branch 'origin/develop' into marvin/62-API-REST-brique-authentification
Some checks failed
Intégration / Tests unitaires et couverture (pull_request) Failing after 33s
Intégration / Qualité du code Python (pull_request) Successful in 45s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 5m23s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Failing after 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 7m7s
d6b96aac72
# Conflicts:
#	.gitignore
infra: fait confiance au CA local de Caddy pour l'envoi des artefacts SBOM
Some checks failed
Intégration / Qualité du code Python (pull_request) Successful in 13s
Intégration / Tests unitaires et couverture (pull_request) Successful in 11s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Failing after 42s
Intégration / Dépendances du tableau de bord (pull_request) Failing after 11s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
2a9ea0cc1a
L'envoi des trois artefacts SBOM échouait systématiquement en CI
(« unable to get local issuer certificate » contre le CA auto-signé de
Caddy), masqué par continue-on-error. Sans nom de domaine public
prévu d'ici la fin du projet, jamais de certificat ACME reconnu par un
magasin de confiance standard : on récupère donc la chaîne de
certificats à chaque exécution (le CA de Caddy tourne trop souvent
pour être figé dans le dépôt) et on la passe à
actions/upload-artifact via NODE_EXTRA_CA_CERTS.

Closes #77
tests : couvre la couche HTTP de l'auth sans base
Some checks failed
Intégration / Qualité du code Python (pull_request) Successful in 49s
Intégration / Tests unitaires et couverture (pull_request) Successful in 50s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 5m12s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Failing after 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 7m9s
33e55986a2
- tests/unit/api/ : les tests d'auth regroupés + _fakes.py (dépôt en mémoire)
- test_auth_http.py : routeur, dépendances, cookies, limite de débit, Origin,
  CORS, gestion des comptes -- via TestClient, dépôt monkeypatché, zéro PostgreSQL
- couverture : auth/* 90 % (>= 85 exigé), global services+packages 84 % (>= 70)
- ci : le job tests-unitaires installe aussi les deps des services (jwt, psycopg…)
- requirements-dev : httpx, requis par starlette.testclient

Répond aux bloquants #2 (deps CI) et #3 (couverture) de la relecture de #63.

Refs #62

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJ8iHaDDQJa8jGxQaCu8xS
infra: ferme l'acces distant a PostgreSQL et passe par un tunnel SSH
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 12s
Intégration / Tests unitaires et couverture (pull_request) Successful in 13s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 5s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 3m39s
ecbd9aaa0b
Applique la decision retenue au ticket #65, relu apres coup : le service
n'ecoute plus que sur 127.0.0.1, et les postes du groupe passent par un
tunnel SSH sur le port 22, deja ouvert et deja authentifie par cle.

L'etat precedent — ouverture a 10.105.200.0/24 avec pare-feu resserre —
etait l'option 2 de ce ticket, celle qu'il ecarte explicitement : le
reseau 10.105.200.0/24 est partage avec les quatre autres groupes de la
salle, et une liste d'adresses demande un entretien manuel a chaque bail
DHCP. L'option 1 ne depend d'aucune liste et tient l'ENF-14 telle qu'elle
est ecrite. La regle UFW sur 5433 est retiree, il n'y a plus rien a
ouvrir. Les quatre lignes /24 de pg_hba.conf restent en commentaire avec
leurs motifs, et le fichier dit qu'un retour en arriere demanderait DEUX
changements et pas un.

Une connexion arrivant par le tunnel debouche sur 127.0.0.1 : elle
correspond aux lignes deja en place. Il n'a donc pas fallu elargir
pg_hba.conf pour accueillir le tunnel, il a fallu ne pas l'elargir — et
aucun nouveau poste ne demandera de modification cote serveur.

Corrige au passage un piege trouve en appliquant ce changement : la
configuration etait montee fichier par fichier, et un montage de fichier
ne suit pas un remplacement par renommage. C'est la facon de travailler
de rsync, mv, sed -i et de la plupart des editeurs : l'hote recoit un
nouvel inode et le conteneur continue de lire l'ancien (mesure : hote
3670044, conteneur 3569844 pour le meme chemin). pg_reload_conf() aurait
annonce un succes sans rien changer. La pile monte desormais le
repertoire conf/ en entier, sur /etc/enervision, et le suivi du
renommage est verifie.

Verifie depuis un poste hors de la machine, en interrogeant le decideur
pg_hba par le protocole PostgreSQL : injoignable en direct ; par le
tunnel, les quatre roles autorises sur leur base et le superutilisateur
refuse ; chaque croisement de base refuse ; injoignable des le tunnel
referme. Et 16 essais sur 16 depuis la boucle locale du serveur.

Closes #80
tests : évite le faux positif du scan de secrets
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 55s
Intégration / Tests unitaires et couverture (pull_request) Successful in 52s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 5m23s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 7m11s
f7d40a0619
`password="..."` littéral dans un appel de test déclenchait la règle
`password\s*=\s*"..."` du job « Aucun secret commité ». Passé par une constante.

Refs #62

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJ8iHaDDQJa8jGxQaCu8xS
docs : Création du Glossaire métier et référentiel des sites
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 11s
Intégration / Tests unitaires et couverture (pull_request) Successful in 11s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 4s
Intégration / Images épinglées par version (pull_request) Successful in 2s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 3m27s
6baf085a25
docs: consigne la purge du cluster natif et corrige trois imprecisions
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 11s
Intégration / Tests unitaires et couverture (pull_request) Successful in 11s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 4s
Intégration / Images épinglées par version (pull_request) Successful in 2s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 3m37s
5af3c30b5e
Purge du cluster natif autorisee le 2 septembre 2026, et volontairement
differee : on ne retire pas le retour arriere le jour de la bascule. Le
document porte desormais sa sequence, ses trois conditions de
declenchement et ce qu'elle coute.

La sequence evite deux erreurs que le reflexe dicterait. Elle ne
redemarre PAS le natif pour en prendre un dernier vidage : cela
echouerait, le cluster etant regle sur le port 5433 que le conteneur
occupe, et ce serait inutile puisqu'il est gele depuis la bascule et que
les vidages d'avant migration couvrent ses cinq bases, ses roles et
leurs empreintes — verifies lisibles. Et elle passe par
`pg_dropcluster`, qui emporte configuration et donnees d'un seul geste
en refusant d'agir sur un cluster qui tourne, plutot que par un rm -rf.

La purge fermera aussi l'ecart n°7, le conf.d/conf.d imbrique partant
avec /etc/postgresql/17, et la moitie de l'ecart n°2, l'historique psql
partant avec /var/lib/postgresql. Ce qu'elle coute est ecrit aussi :
plus aucun client PostgreSQL sur l'hote, donc une base injoignable ET
inexaminable en cas de panne de Docker, et plus aucun retour arriere au
sens strict — d'ou la condition d'une restauration deja eprouvee.

Trois imprecisions corrigees apres confrontation de chaque affirmation
chiffree du document a l'etat reel du serveur :

- la taille du volume passe de « 80 Mo » a « ~80 Mo », un chiffre exact
  derivant a chaque ecriture ;
- les deux racines de configuration sont expliquees. La configuration est
  sous /etc/enervision et le certificat sous /etc/postgresql/tls, parce
  que ce sont deux regimes de droits distincts : conf/ est lisible par
  tous, la cle privee est en 0600 pour l'uid 999 seul. Les imbriquer
  ferait dependre les droits de la cle de ceux du repertoire de
  configuration ;
- l'ecart n°8 nomme les paquets reellement installes, dont
  postgresql-common, et la taille reelle du repertoire de donnees.

Tout le reste concordait : versions, bornes du conteneur, chemins,
dimensionnement, locales, bases, roles, droits de grafana, contenu du
schema ops, droits de la sauvegarde, verrous du natif, absence de regle
UFW, echeance du certificat.

Refs #80
Merge branch 'develop' into lenaic/0-referentiel-exigences
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 12s
Intégration / Tests unitaires et couverture (pull_request) Successful in 12s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 5s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 3m30s
6ade470769
Merge pull request 'infra: fait tourner PostgreSQL 17 et TimescaleDB sur Docker' (#81) from olivier/80-postgresql-docker into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 18s
Intégration / Tests unitaires et couverture (push) Successful in 12s
Intégration / Dépendances du tableau de bord (push) Successful in 6s
Intégration / Images épinglées par version (push) Successful in 3s
Intégration / Aucun secret commité (push) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 3m43s
323d29defe
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/81
Reviewed-by: lenaic <lenaic@noreply.10.105.200.41>
docs: le jeton est émis par notre API, Keycloak est reporté
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 12s
Intégration / Tests unitaires et couverture (pull_request) Successful in 11s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 4s
Intégration / Images épinglées par version (pull_request) Successful in 2s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 3m42s
ea0ec01b7a
Le tableau des décisions annonçait Keycloak comme arbitré depuis le 31 août
alors que l'équipe construisait un jeton maison. Marvin a écrit toute la
brique d'authentification sur cette base, Olivier a supprimé la base
keycloak du serveur, et le document disait encore l'inverse.

Corrige les onze mentions de Keycloak, le port de PostgreSQL annoncé à
5432 alors qu'il est à 5433, et le plan de charge : le total passe de 46 à
44 jours-homme, le déficit de 15 à 13.

Ouvre la fiche ADR 0002 et l'index des décisions.
Merge branch 'develop' into lenaic/0-referentiel-exigences
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 13s
Intégration / Tests unitaires et couverture (pull_request) Successful in 13s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 4s
Intégration / Images épinglées par version (pull_request) Successful in 2s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 3m46s
43bf8c83e2
This reverts commit 3deb2a864d8acb75f2c6707cb8e715284ddb5bfe.
infra: adresse la forge en dur dans confiance-caddy, pas github.server_url
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 15s
Intégration / Tests unitaires et couverture (pull_request) Successful in 11s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 8s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 3m32s
e79bbb0a0a
Échec observé sur la PR #78 (11s et 42s, trop rapide pour un blocage
réseau) : ce dépôt n'a jamais utilisé ${{ github.server_url }} au-delà
de github.workflow/github.ref, et rien ne garantit que ce runner
Forgejo le peuple comme GitHub Actions. L'adresse de la forge est déjà
en dur partout ailleurs dans le dépôt (bin/fj, docs/runbooks/ci.md) :
même choix ici, plutôt qu'un contexte non éprouvé sur ce runner.
Merge pull request 'docs: le jeton est émis par notre API, Keycloak est reporté' (#84) from lenaic/0-decision-authentification into develop
Some checks failed
Intégration / Dépendances du tableau de bord (push) Waiting to run
Intégration / Images épinglées par version (push) Waiting to run
Intégration / Aucun secret commité (push) Waiting to run
Intégration / Qualité du code Python (push) Successful in 12s
Intégration / Tests unitaires et couverture (push) Successful in 12s
Intégration / Dépendances Python et inventaire applicatif (push) Has been cancelled
83e54de2c3
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/84
infra: écrit les rôles Ansible de provisionnement système (lot 1)
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 11s
Intégration / Tests unitaires et couverture (pull_request) Successful in 12s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 3s
Intégration / Images épinglées par version (pull_request) Successful in 2s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 3m44s
fe8839be77
Sort les rôles base, deploy_user, ssh_hardening et docker du squelette. Ils
provisionnent une cible Ubuntu 24.04 de zéro : paquets et mises à jour de
sécurité, pare-feu UFW, compte deploy à clés nominatives, coupure de l'accès
root et du mot de passe SSH, moteur Docker et plugin compose. app et backup
restent des squelettes en attente de la pile PostgreSQL-Docker (#80).

- bootstrap.yml : amorçage d'une cible neuve en root (crée deploy), joué une
  fois ; site.yml prend le relais par deploy et ferme root à la passe suivante.
- group_vars/all/ scindé : vars.yml (non sensible) et vault.yml.example ; le
  vault.yml chiffré est ignoré par Git.
- ansible.cfg : profile_tasks (le chronomètre du playbook de restauration) et
  pipelining SSH.
- site.yml : pre_task qui refuse une cible non Ubuntu 24.04, rôle ssh_hardening
  inséré après deploy_user.
- CI : outillage épinglé (requirements-ci.txt), yamllint ajouté, ansible-lint
  rendu bloquant, bootstrap.yml passé au contrôle de syntaxe.
- Relecture de Lénaïc : repo_version corrigé en develop ; docker_default_network_mode
  retiré — le mode réseau se décide pile par pile (ADR 0001), pas en variable d'hôte.

Runbooks deploiement.md et README.md mis à jour et indexés.

Refs #41

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxpHmd3thib9RErZuMvS3R
Merge branch 'develop' into lenaic/0-referentiel-exigences
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 12s
Intégration / Tests unitaires et couverture (pull_request) Successful in 14s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 5s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 3m45s
3af972bfcb
Merge pull request 'docs: journal des jours 2 et 3' (#76) from lenaic/48-journal-j2-j3 into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 12s
Intégration / Tests unitaires et couverture (push) Successful in 12s
Intégration / Dépendances du tableau de bord (push) Successful in 5s
Intégration / Images épinglées par version (push) Successful in 3s
Intégration / Aucun secret commité (push) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 3m32s
89f222fe59
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/76
Reviewed-by: florian <florian@noreply.10.105.200.41>
Merge branch 'develop' into lenaic/0-referentiel-exigences
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 11s
Intégration / Tests unitaires et couverture (pull_request) Successful in 11s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 4s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 3m37s
6c69fce056
api : corrige les points de sécurité de la relecture
Some checks failed
Intégration / Qualité du code Python (pull_request) Failing after 30s
Intégration / Tests unitaires et couverture (pull_request) Successful in 51s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 4s
Intégration / Images épinglées par version (pull_request) Successful in 2s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 7m7s
ffed46bb38
- limite de débit du login comptée par IP (plus par compte) : un attaquant
  ne verrouille plus un compte cible depuis n'importe où. Avertissement au
  démarrage si trust_proxy=false hors debug.
- 0004 : aligne la contrainte de rôle sur user/admin (bases migrées avant le
  changement) et ajoute actor_id + detail au journal d'audit.
- user_created / user_updated enregistrent l'admin auteur et le champ modifié.
- plancher de mot de passe à 8 caractères (NewUser, UserPatch).
- decode_access exige le claim exp.
- 0003 réduit à l'index family_id (l'index login_ko reste celui de 0002).

Points 1 à 4 de la relecture de #63.

Refs #62

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJ8iHaDDQJa8jGxQaCu8xS
infra: épingle la racine Caddy plutôt que de la récupérer à chaque run
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 11s
Intégration / Tests unitaires et couverture (pull_request) Successful in 11s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 43s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 7s
Intégration / Images épinglées par version (pull_request) Successful in 2s
Intégration / Aucun secret commité (pull_request) Successful in 2s
c82606224e
La version précédente (e79bbb0) rendait le job vert mais l'envoi des
artefacts échouait toujours (unable to get issuer certificate) :
une poignée de main TLS ne transmet jamais la racine d'une autorité,
seulement la feuille et l'intermédiaire — NODE_EXTRA_CA_CERTS ne
pouvait donc jamais fermer la chaîne de confiance avec cette approche.

Vérifié côté serveur (docker exec g2-forge-proxy) : contrairement à
l'intermédiaire (renoué chaque semaine) et à la feuille (12h), la
racine « Caddy Local Authority » est générée une fois et valable dix
ans. C'est elle qu'on épingle dans le dépôt (certificat public, pas
de clé privée) plutôt que de la traquer à l'exécution. Une étape
bloquante (pas de continue-on-error) vérifie qu'elle n'a pas expiré,
pour qu'une péremption future rougisse la chaîne au lieu de se
cacher derrière le garde-fou de l'envoi d'artefact.

Supprime .forgejo/scripts/confiance-caddy.mjs et son test, devenus
inutiles.

Voir #77
infra: écrit les rôles app et backup Ansible (lot 2)
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 11s
Intégration / Tests unitaires et couverture (pull_request) Successful in 15s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 3m30s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 5s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m1s
ddb873e8c6
Sort app et backup du squelette, après l'arrivée de la pile PostgreSQL-Docker
sur develop (#81).

Rôle app :
- clone le dépôt dans /opt/enervision/.repo via une clé de déploiement du coffre
- rend /etc/enervision/{postgres,minio}.env depuis le coffre, 0640 root:deploy,
  no_log — jamais dans le dépôt
- produit le certificat TLS de PostgreSQL sur la machine (uid 999), jamais versionné
- lance chaque pile de app_stacks (postgres avec build, minio) par
  docker_compose_v2, en attendant l'état « healthy »

Rôle backup :
- pose pg-backup.sh et verifier-sauvegardes.sh depuis le dépôt vers
  /usr/local/bin (source unique, critère du #64)
- /var/backups/postgresql en 0750, /etc/cron.d/pg-backup à 2 h 30
- premier vidage joué à la main comme le fait cron
- restent hors périmètre du #41, notés : alerte sur échec (écart n°4) et copie
  hors machine

restore.yml : contrôle de lisibilité, filet, coupe des connexions,
pg_restore --clean par base via docker exec, vérification que le serveur
répond. Chronomètre par profile_tasks.

Runbooks deploiement.md et reprise.md mis à jour ; reprise.md documente la
limite « pas de copie hors machine ».

Refs #41

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxpHmd3thib9RErZuMvS3R
Merge pull request 'outillage: aligne la liste des exigences du skill ticket sur celle du groupe' (#66) from lenaic/0-referentiel-exigences into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 12s
Intégration / Tests unitaires et couverture (push) Successful in 11s
Intégration / Dépendances du tableau de bord (push) Successful in 6s
Intégration / Images épinglées par version (push) Successful in 2s
Intégration / Aucun secret commité (push) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 3m48s
6e670aed9e
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/66
Reviewed-by: justine <justine@noreply.10.105.200.41>
api : validation d'email et contrôle Origin en même-origine
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 49s
Intégration / Tests unitaires et couverture (pull_request) Successful in 59s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 5s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 6m55s
a75bef90ca
- Origin : quand ALLOWED_ORIGIN est vide (déploiement même-origine derrière
  Caddy), l'hôte de l'en-tête Origin est comparé au Host de la requête. Une
  requête montée depuis un autre site est refusée en 403, alors qu'avant le
  contrôle ne faisait rien dans ce mode.
- email : type Email = EmailStr + normalisation (strip/lower) avant validation.
  Un email mal formé rend 422 ; « " Jean@Site.fr " » reste accepté.
- email-validator ajouté aux dépendances du service.

Points (c) et nit « EmailStr » de la relecture de #63.

Refs #62

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJ8iHaDDQJa8jGxQaCu8xS
db : token_digest en text plutôt que char(64)
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 44s
Intégration / Tests unitaires et couverture (pull_request) Successful in 54s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 5s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 6m54s
1ce2be8dd8
char(n) remplit par des espaces et compare en ignorant les espaces finaux,
sans gain sur PostgreSQL. Le condensé SHA-256 fait toujours 64 caractères.

- 0001 corrigé pour les bases neuves
- 0005 rattrape celles qui ont déjà tourné
- README des migrations remis à jour (0003 avait changé, 0004 et 0005 manquaient)

Nit « char(n) » de la relecture de #63.

Refs #62

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJ8iHaDDQJa8jGxQaCu8xS
infra: retire le jeton SAS Azure du modèle de coffre (hors périmètre)
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 11s
Intégration / Tests unitaires et couverture (pull_request) Successful in 11s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 4s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 3m24s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m0s
a627c927b7
La copie des sauvegardes vers Azure n'est pas dans cette PR ; la variable
vault_azure_backup_sas_url faisait croire qu'elle était requise.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxpHmd3thib9RErZuMvS3R
api : le service renvoie le profil, plus de relecture en base
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 47s
Intégration / Tests unitaires et couverture (pull_request) Successful in 52s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 4s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 6m30s
6063eeb64e
IssuedTokens porte un PublicUser au lieu du seul user_id : le routeur n'a plus
à relire l'utilisateur que le service venait de charger.

- login : 1 requête au lieu de 2
- refresh : 2 au lieu de 3
- POST /auth/users : construit le profil depuis l'insert, 1 au lieu de 2

_public reste pour patch_user, qui doit relire l'état après écriture.

Refs #62

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJ8iHaDDQJa8jGxQaCu8xS
api : un seul endroit définit les rôles
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 46s
Intégration / Tests unitaires et couverture (pull_request) Successful in 50s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 4s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 6m54s
a8a20ba161
Role, un StrEnum dans auth/models.py, remplace les chaînes recopiées dans le
Literal Pydantic, dans require_role et dans le service. Le dépôt convertit la
valeur lue en base, une valeur inconnue lève au lieu de passer.

La contrainte SQL reste une définition séparée (une migration ne lit pas
l'enum) : un commentaire dans 0001 la rattache.

OpenAPI expose désormais un schéma Role nommé plutôt qu'un Literal recopié.

Nit « rôle défini à trois endroits » de la relecture de #63.

Refs #62

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJ8iHaDDQJa8jGxQaCu8xS
db : l'email est garanti en minuscules
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 47s
Intégration / Tests unitaires et couverture (pull_request) Successful in 54s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 5s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 6m51s
d23cb47a69
La contrainte unique sur ops.users.email est sensible à la casse. Une insertion
SQL directe de « Admin@Site.fr » — le premier admin se crée à la main — donnait
un compte introuvable au login, et laissait passer « admin@site.fr » en doublon.

0006 normalise l'existant puis pose la contrainte. L'invariant que le code
suppose partout est désormais tenu par la base. Pas de citext : l'extension
demande des droits que le rôle de la préprod n'a pas.

Docs : « Limites connues » de docs/api/decisions.md remise à jour, quatre points
étaient corrigés par les étapes précédentes.

Nit « casse de l'email » de la relecture de #63.

Refs #62

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJ8iHaDDQJa8jGxQaCu8xS
infra: passe le dépôt Docker à deb822_repository, exclut .venv du lint
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 13s
Intégration / Tests unitaires et couverture (pull_request) Successful in 11s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 5s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 3m32s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m9s
647ee836f5
apt_repository est déprécié (retrait ansible-core 2.25) et sortait un
avertissement à chaque run. deb822_repository écrit le même dépôt au format
moderne ; le cache APT n'est rafraîchi que si le dépôt change.

yamllint et ansible-lint ignorent aussi .venv/ et le vault chiffré, pour que
les lancers locaux depuis infra/ansible/ restent propres.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxpHmd3thib9RErZuMvS3R
api : purge aussi le journal d'audit
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 49s
Intégration / Tests unitaires et couverture (pull_request) Successful in 58s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 4s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 6m49s
e6413831d1
run_purge ne nettoyait que les sessions ; ops.auth_events grossissait sans fin.
Rétention séparée : 90 jours pour l'audit (réponse à incident) contre 7 pour
les sessions (fonctionnement).

Docs : configuration.md était périmée sur les variables, les colonnes d'audit
et la liste des migrations ; securite.md décrivait encore la limite de débit
par (ip, email) et le contrôle Origin comme inactif en même-origine.

Refs #62

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJ8iHaDDQJa8jGxQaCu8xS
[82] Ajout doc ETL
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 11s
Intégration / Tests unitaires et couverture (pull_request) Successful in 15s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 4s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 3m38s
f207a1e5eb
Merge branch 'develop' into marvin/62-API-REST-brique-authentification
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 43s
Intégration / Tests unitaires et couverture (pull_request) Successful in 54s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 4s
Intégration / Images épinglées par version (pull_request) Successful in 2s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 6m37s
a4b6be672f
[85] Ajout versionning bronze
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 11s
Intégration / Tests unitaires et couverture (pull_request) Successful in 11s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 5s
Intégration / Images épinglées par version (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 3m41s
Intégration / Aucun secret commité (pull_request) Successful in 2s
003fab2fb8
docs : Création du BACKLOG.ms
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 29s
Intégration / Tests unitaires et couverture (pull_request) Successful in 27s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 5s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 4m3s
da0f9ed988
Merge pull request '[85] Ajout versionning bronze' (#87) from justine/85-versionning-bronze into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 13s
Intégration / Tests unitaires et couverture (push) Successful in 13s
Intégration / Dépendances du tableau de bord (push) Successful in 5s
Intégration / Images épinglées par version (push) Successful in 3s
Intégration / Aucun secret commité (push) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 3m40s
3218d5cc93
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/87
Reviewed-by: lenaic <lenaic@noreply.10.105.200.41>
Merge pull request 'docs : Création du BACKLOG.ms' (#89) from florian/88-Ajout-Backlog-et-jalons-en-md into develop
Some checks failed
Intégration / Qualité du code Python (push) Successful in 14s
Intégration / Tests unitaires et couverture (push) Successful in 14s
Intégration / Dépendances du tableau de bord (push) Successful in 4s
Intégration / Images épinglées par version (push) Successful in 3s
Intégration / Aucun secret commité (push) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (push) Has been cancelled
7071b97d91
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/89
Reviewed-by: lenaic <lenaic@noreply.10.105.200.41>
Merge pull request 'docs : Création du Glossaire métier et référentiel des sites' (#83) from florian/46-Glossaire into develop
Some checks failed
Intégration / Qualité du code Python (push) Successful in 13s
Intégration / Tests unitaires et couverture (push) Successful in 12s
Intégration / Dépendances du tableau de bord (push) Successful in 4s
Intégration / Images épinglées par version (push) Successful in 2s
Intégration / Aucun secret commité (push) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (push) Has been cancelled
d5c30f224d
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/83
Reviewed-by: lenaic <lenaic@noreply.10.105.200.41>
Merge pull request 'infra: fait confiance au CA local de Caddy pour l'envoi des artefacts SBOM' (#78) from gabriel/77-confiance-caddy-ci into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 11s
Intégration / Tests unitaires et couverture (push) Successful in 11s
Intégration / Dépendances du tableau de bord (push) Successful in 9s
Intégration / Images épinglées par version (push) Successful in 3s
Intégration / Aucun secret commité (push) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 44s
e3bff4e66e
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/78
Reviewed-by: lenaic <lenaic@noreply.10.105.200.41>
fix(minio): remplace grep par du case POSIX dans init-buckets.sh
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 11s
Intégration / Tests unitaires et couverture (pull_request) Successful in 11s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 7s
Intégration / Images épinglées par version (pull_request) Successful in 2s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 45s
1a19cb550d
L'image quay.io/minio/mc (ubi9-micro) n'embarque pas grep : les trois
vérifications (versioning, rétention, purge des versions) échouaient à
chaque déploiement réel avec "grep: command not found", jamais détecté
avant faute d'avoir tourné le script pour de vrai. Testé contre le MinIO
de serveur-salle : les quatre buckets, le versioning et la rétention
passent.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbkbdXUvBFnQ5KuHan9y3J
Merge pull request 'fix(minio): remplace grep par du case POSIX dans init-buckets.sh' (#90) from gabriel/85-fix-minio-init-posix into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 11s
Intégration / Tests unitaires et couverture (push) Successful in 11s
Intégration / Dépendances du tableau de bord (push) Successful in 9s
Intégration / Images épinglées par version (push) Successful in 2s
Intégration / Aucun secret commité (push) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 50s
b2b1417824
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/90
Reviewed-by: justine <justine@noreply.10.105.200.41>
community.general.yaml plante à l'exécution ("function() argument 'code'
must be code, not str") sur ce venv Python 3.14 : le module patche
AnsibleDumper d'une façon incompatible avec la nouvelle disposition interne
des objets code. Le callback est de toute façon déprécié depuis
ansible-core 2.13 au profit de result_format=yaml sur le callback par
défaut, remplacement officiel adopté ici.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbkbdXUvBFnQ5KuHan9y3J
Le rôle app écrivait app_repo_key_file (opt/enervision/.ssh/id_deploy) sans
jamais créer .ssh au préalable — échec systématique sur une cible où ce
répertoire n'existe pas encore (constaté en déployant #41 sur
serveur-salle : "Destination directory /opt/enervision/.ssh does not
exist").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbkbdXUvBFnQ5KuHan9y3J
Le DNS du campus ENI détourne download.docker.com vers une IP interne qui
refuse la connexion (constaté sur serveur-salle) : le dépôt officiel Docker
est injoignable sur ce réseau. docker.io et docker-compose-v2 sont à jour
dans noble-updates et fournissent bien `docker compose`, donc plus besoin
du dépôt tiers ni de sa clé APT.

serveur-salle a un GPU Tesla T4 avec le toolkit nvidia déjà installé à la
main (hors périmètre de ce rôle) et default-runtime: nvidia en place, dont
dépendent implicitement tous les conteneurs qui y tournent (runners CI
Forgejo notamment). Le template daemon.json l'écrasait silencieusement ;
il le préserve maintenant via docker_nvidia_runtime, activé en host_vars
sur cette cible.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbkbdXUvBFnQ5KuHan9y3J
Merge branch 'develop' into gabriel/41-ansible-squelette
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 11s
Intégration / Tests unitaires et couverture (pull_request) Successful in 11s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 8s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 44s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 58s
e91f5d04d6
Merge remote-tracking branch 'origin/gabriel/41-ansible-squelette' into gabriel/41-ansible-squelette
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 11s
Intégration / Tests unitaires et couverture (pull_request) Successful in 11s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 9s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 47s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 2m2s
448da0a37f
Merge pull request 'infra: déploiement Ansible et sa validation CI' (#61) from gabriel/41-ansible-squelette into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 12s
Intégration / Tests unitaires et couverture (push) Successful in 12s
Intégration / Dépendances du tableau de bord (push) Successful in 8s
Intégration / Images épinglées par version (push) Successful in 3s
Intégration / Aucun secret commité (push) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 47s
Infra Ansible / Playbooks Ansible valides (push) Successful in 1m2s
f9ff812564
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/61
Reviewed-by: lenaic <lenaic@noreply.10.105.200.41>
infra: rouvre les comptes nominatifs au durcissement SSH
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 11s
Intégration / Tests unitaires et couverture (pull_request) Successful in 11s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 7s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 46s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 59s
2f078888e0
AllowUsers ne portait que deploy, ce qui a fermé les six comptes
nominatifs au premier passage du playbook. Six personnes se connectant
sous un même compte, membre du groupe docker donc équivalent root,
l'attribution demandée par l'ENF-15 n'est plus tenue.

Le durcissement visait à corriger le compte root partagé ; il l'avait
remplacé par un autre compte partagé.
Merge pull request 'infra: rouvre les comptes nominatifs au durcissement SSH' (#91) from lenaic/0-comptes-nominatifs-ssh into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 12s
Intégration / Tests unitaires et couverture (push) Successful in 10s
Intégration / Dépendances du tableau de bord (push) Successful in 7s
Intégration / Images épinglées par version (push) Successful in 4s
Intégration / Aucun secret commité (push) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 42s
Infra Ansible / Playbooks Ansible valides (push) Successful in 58s
9c7741dfd0
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/91
Reviewed-by: justine <justine@noreply.10.105.200.41>
Merge branch 'develop' into marvin/62-API-REST-brique-authentification
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 48s
Intégration / Tests unitaires et couverture (pull_request) Successful in 52s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 8s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m16s
6e085f39f7
infra: copie vérifiée des sauvegardes hors du serveur
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 12s
Intégration / Tests unitaires et couverture (pull_request) Successful in 12s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 9s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 44s
809d940698
Les sauvegardes tournaient mais ne quittaient jamais la machine : une
perte du conteneur emportait le dépôt, les tickets, les bases et leurs
sauvegardes ensemble.

Le script rapatrie les deux jeux en une passe et les vérifie plutôt que
de se contenter de les compter : lecture de l'archive de la forge,
présence de forgejo-db.sql et des dépôts, en-tête PGDMP de chaque vidage,
intégrité des rôles du cluster. Une copie qui échoue à un contrôle est
déclarée non fiable et sort en erreur.

Corrige au passage le manuel, qui annonçait gitea-db.sql et gitea-repo/,
noms hérités de l'ancêtre du projet. Forgejo 12 écrit forgejo-db.sql et
repos/, ce que la première vérification a mis au jour.
Merge pull request 'infra: copie vérifiée des sauvegardes hors du serveur' (#93) from lenaic/92-copie-hors-serveur into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 12s
Intégration / Tests unitaires et couverture (push) Successful in 12s
Intégration / Dépendances du tableau de bord (push) Successful in 9s
Intégration / Images épinglées par version (push) Successful in 3s
Intégration / Aucun secret commité (push) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 43s
91d4adc660
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/93
Reviewed-by: florian <florian@noreply.10.105.200.41>
Ajoute .forgejo/workflows/deploy.yml, déclenché uniquement sur push vers
main, qui rejoue ansible-playbook site.yml --tags app avec repo_version
réglé sur le commit fusionné (au lieu de la valeur par défaut develop).
Le socle système (base, deploy_user, ssh_hardening, docker) reste un
geste manuel volontaire.

Commite group_vars/all/vault.yml chiffré par ansible-vault (le
.gitignore l'excluait jusqu'ici) : la chaîne a besoin de le lire, seul
le mot de passe du coffre devient un secret Forgejo.

Ticket #95.
infra: fiabilise le déploiement continu (revue du ticket #95)
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 11s
Intégration / Tests unitaires et couverture (pull_request) Successful in 11s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 8s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 46s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 53s
ef6142cb30
- deploy.yml : amorce known_hosts par ssh-keyscan avant qu'Ansible ne se
  connecte (le conteneur du job est neuf, ansible.cfg impose
  host_key_checking = True) ; passe --vault-password-file à la commande ad
  hoc « État des piles » (vault.yml est désormais commité chiffré) ;
  cancel-in-progress: false pour ne pas couper un playbook en cours ;
  workflow_dispatch pour valider la chaîne hors merge ; clé SSH écrite avec
  un newline final et secrets passés par env.
- ci.yml : joue tests/ci/test-deploiement-continu.sh sur chaque PR.
- test-deploiement-continu.sh : couvre les points ci-dessus et vérifie que
  group_vars/all/vault.yml est commité et chiffré.
- docs : procédure de mise en place (clé CI, rôle deploy_user à jouer
  d'abord, secrets Forgejo) et diagnostic « Host key verification failed ».

Ticket #95.
Merge pull request 'infra: déploiement continu — relance automatique des apps à chaque merge sur main (#95)' (#96) from gabriel/95-deploiement-continu into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 12s
Intégration / Tests unitaires et couverture (push) Successful in 11s
Intégration / Dépendances du tableau de bord (push) Successful in 9s
Intégration / Images épinglées par version (push) Successful in 3s
Intégration / Aucun secret commité (push) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 45s
Infra Ansible / Playbooks Ansible valides (push) Successful in 51s
65d3118cb2
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/96
Reviewed-by: lenaic <lenaic@noreply.10.105.200.41>
docs: restauration hors serveur jouée et chronométrée (9 min 51 s), manuels à jour (#92)
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 12s
Intégration / Tests unitaires et couverture (pull_request) Successful in 11s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 9s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 44s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 53s
4dee229606
Merge branch 'develop' into marvin/62-API-REST-brique-authentification
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 1m17s
Intégration / Tests unitaires et couverture (pull_request) Successful in 1m25s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 14s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m37s
f73c66ab85
Merge pull request 'marvin/62-API-REST-brique-authentification' (#63) from marvin/62-API-REST-brique-authentification into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 51s
Intégration / Tests unitaires et couverture (push) Successful in 1m3s
Intégration / Dépendances du tableau de bord (push) Successful in 8s
Intégration / Images épinglées par version (push) Successful in 3s
Intégration / Aucun secret commité (push) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 1m27s
8c98a3e6de
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/63
Reviewed-by: florian <florian@noreply.10.105.200.41>
infra: pose le registre de modèles MLflow sur PostgreSQL et MinIO
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 45s
Intégration / Tests unitaires et couverture (pull_request) Successful in 54s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 7s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m14s
6e3c7c10d1
Le serveur écoute sur 127.0.0.1:5000 en réseau hôte, garde son suivi dans la
base `mlflow` de PostgreSQL et écrit ses artefacts dans le bucket `mlflow` de
MinIO. Aucun volume : tout l'état est dans les deux magasins, tous deux
sauvegardés, et le conteneur est jetable. C'est précisément ce que le magasin
fichier par défaut de MLflow ne permet pas, et que le ticket demandait d'éviter.

Les artefacts passent par le serveur (ADR 0003) : un client d'entraînement ou
d'inférence n'a besoin que de MLFLOW_TRACKING_URI, et les identifiants MinIO ne
quittent pas la machine. Ce sont ceux d'un utilisateur MinIO dédié, limité au
seul bucket mlflow par une politique que le générateur éprouve avant d'écrire —
et dont il retire l'utilisateur s'il constate le contraire.

Le mot de passe PostgreSQL n'est pas dans l'URI de suivi mais dans PGPASSWORD,
que libpq lit dans l'environnement : dans la ligne de commande, il serait
lisible par tout compte de la machine à travers /proc. Le point de terminaison
S3, lui, est dans la composition versionnée et non dans le fichier de secrets :
absent, boto3 se rabat sur le vrai AWS S3 sans rien en dire.

En service sur ml-stagiaire-02 depuis le 2 septembre, les quatre critères
éprouvés, test d'intégration à 14 cas sur 14.

Closes #30

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
infra: verse la pile de la forge et neutralise son serveur OpenSSH
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 43s
Intégration / Tests unitaires et couverture (pull_request) Successful in 56s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 8s
Intégration / Images épinglées par version (pull_request) Successful in 2s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m22s
c4ccb1dc6e
La pile de la forge ne vivait que sur le serveur depuis le 31 août. Elle
rejoint le dépôt, avec le correctif de la panne du jour.

L'image Forgejo embarque un serveur OpenSSH qui écoute sur le port 22 du
conteneur. En network_mode host, c'est le port 22 de la machine : il l'a
pris dès l'arrêt de ssh.service ce matin, et deux heures d'accès
administrateur ont été perdues.

Un fichier down monté sur le service s6 l'empêche de démarrer et survit à
une recréation du conteneur. Le correctif de fond, la bascule en réseau
bridge, fait l'objet d'un ticket séparé.
Merge pull request 'infra: pose le registre de modèles MLflow sur PostgreSQL et MinIO' (#100) from olivier/30-mlflow-registre into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 47s
Intégration / Tests unitaires et couverture (push) Successful in 54s
Intégration / Dépendances du tableau de bord (push) Successful in 7s
Intégration / Images épinglées par version (push) Successful in 2s
Intégration / Aucun secret commité (push) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 1m18s
2612841074
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/100
Reviewed-by: lenaic <lenaic@noreply.10.105.200.41>
Merge pull request 'infra: verse la pile de la forge et neutralise son serveur OpenSSH' (#101) from lenaic/0-forge-versionnee into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 53s
Intégration / Tests unitaires et couverture (push) Successful in 58s
Intégration / Dépendances du tableau de bord (push) Successful in 8s
Intégration / Images épinglées par version (push) Successful in 3s
Intégration / Aucun secret commité (push) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 1m15s
9b82924ebf
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/101
Reviewed-by: olivier <olivier@noreply.10.105.200.41>

Docker Forgejo good
Documentation claire pour résolution du problème ssh
[28] Fix tests
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 13s
Intégration / Tests unitaires et couverture (pull_request) Successful in 12s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 10s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 49s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 53s
fc3dcbf7be
Merge develop, renumérote les fiches de décision
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 50s
Intégration / Tests unitaires et couverture (pull_request) Successful in 59s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 8s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m15s
35e4829655
Trois fiches de cette branche se percutaient avec celles fusionnées entre
temps : 0002 est pris par l'authentification, 0004 par MLflow.

DuckDB devient 0003, les clés de stockage 0005, l'imputation 0006. Les
titres internes et les renvois croisés de docs/data/etl-pipeline.md et de
docs/CONVENTIONS.md suivent. L'index des décisions et celui des manuels
reprennent les deux séries.
docs: tranche l'état Terraform distant et l'authentification sans service principal
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 47s
Intégration / Tests unitaires et couverture (pull_request) Successful in 54s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 8s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m14s
177194b52f
L'état passe sur le backend azurerm, conteneur créé hors Terraform par
le #67. L'authentification reste celle de l'utilisateur : l'abonnement
école ne donne pas Entra ID, donc pas de service principal, donc pas
d'apply en CI. Compensé par la trace plan/apply en MR et la relecture
obligatoire sur infra/terraform/.

Closes #73

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TLuWGNFhKVvx2T1ckEu8Zi
Merge pull request '[82] Ajout doc ETL' (#86) from justine/82-doc-etl into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 44s
Intégration / Tests unitaires et couverture (push) Successful in 58s
Intégration / Dépendances du tableau de bord (push) Successful in 8s
Intégration / Images épinglées par version (push) Successful in 2s
Intégration / Aucun secret commité (push) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 1m22s
4323f95dfc
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/86
Reviewed-by: florian <florian@noreply.10.105.200.41>
Reviewed-by: olivier <olivier@noreply.10.105.200.41>
Reviewed-by: lenaic <lenaic@noreply.10.105.200.41>
Merge branch 'develop' into justine/28-droits-minio-env
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 48s
Intégration / Tests unitaires et couverture (pull_request) Successful in 1m3s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 9s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m23s
ca9582f49c
db : amorce la zone or, hypertable « mesure » et compression au-delà de 7 jours
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 45s
Intégration / Tests unitaires et couverture (pull_request) Successful in 53s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 9s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m16s
741e1948a6
Le ticket #29 demandait de convertir les tables de mesures en hypertables et
d'y poser une compression. Il n'y avait rien à convertir : les deux bases
comptaient zéro table dans « public », seuls les catalogues internes de
TimescaleDB étaient là. Le #105 le constate d'ailleurs explicitement.

Cette migration pose donc le strict nécessaire pour que #29 ait un objet :
« site », le référentiel des sept sites du GLOSSAIRE, et « mesure » convertie
en hypertable de fragments d'un jour, comprimée au-delà de sept jours. Les
cinq autres tables de la zone or restent au #105.

Le modèle suit le dossier d'architecture collectif, et non la forme esquissée
dans docs/POSTGRESQL.md : cette dernière portait une clé « capteur_id » qui
n'est pas celle des zones bronze et argent, une seule colonne de valeur là où
l'ADR 0006 exige la brute à côté de la retenue, et un « data_quality » par
défaut à « ok » qui n'appartient à aucune des deux énumérations retenues. Le
document est corrigé, avec les motifs de l'écart.

La conversion passe avant le chargement : la production reste vide, elle
attend la collecte du #33. Le jeu de test d'un mois vit en préproduction.

Le « grant select » est explicite plutôt que laissé à la clause de droits par
défaut du schéma : cette clause existait sur enervision_prod mais pas sur
enervision_preprod, où pg_default_acl était vide. Les mêmes tables auraient
été lisibles par Grafana en production et muettes en préproduction. La clause
manquante a été posée sur le serveur en plus de ce grant.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	docs/adr/README.md
docs: resserre l'ADR 0007 au format d'une page
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 47s
Intégration / Tests unitaires et couverture (pull_request) Successful in 56s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 8s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m20s
1a8096eb1f
Le format arrivé avec develop tient en quatre sections et une page. La
fiche en faisait deux fois trop. Aucun argument retiré, seulement la
redite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TLuWGNFhKVvx2T1ckEu8Zi
Huit migrations : les six tables de données dans public, la table
d'autorisation dans ops, et le référentiel des sept sites relu du glossaire.

Refs #105
Un cas unitaire sans base, joué en chaîne : les sept tables, le sens de
retour de chaque migration, et le référentiel relu dans docs/GLOSSAIRE.md.
Un cas d'intégration joué sur le serveur : propriétaire des tables, droits
de grafana sur les six tables de données, et absence de droit sur
ops.acces_site.

Refs #105
Merge pull request 'docs: tranche l'état Terraform distant et l'authentification sans service principal' (#108) from florian/73-adr-terraform-etat-distant into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 47s
Intégration / Tests unitaires et couverture (push) Successful in 54s
Intégration / Dépendances du tableau de bord (push) Successful in 7s
Intégration / Images épinglées par version (push) Successful in 2s
Intégration / Aucun secret commité (push) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 1m14s
072ab1ece5
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/108
Merge branch 'develop' into olivier/29-hypertables-compression
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 47s
Intégration / Tests unitaires et couverture (pull_request) Successful in 1m3s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 8s
Intégration / Images épinglées par version (pull_request) Successful in 2s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m21s
45ff0101b6
[33] Ajout collecteur
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 53s
Intégration / Tests unitaires et couverture (pull_request) Successful in 57s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 7s
Intégration / Images épinglées par version (pull_request) Successful in 2s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m20s
3cbe853ba6
La demande de fusion #106 pose déjà site et mesure sur les deux bases, dans
une forme réduite à ce dont l'hypertable avait besoin. Le lot passe donc en
0008-0015 : il crée les tables sur une base neuve et complète celles du
serveur (localisation, energie_kwh, qualite_source, cle_bronze, execution_id,
régime « measured »), sans toucher au partitionnement ni à la compression.

Refs #105
Fiche ADR sur le choix de public pour les six tables de données et de ops
pour la table des accès, section schéma du manuel PostgreSQL réécrite sur
l'état posé, et convention de retour arrière dans le README des migrations.

Refs #105
Merge branch 'develop' into justine/28-droits-minio-env
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 43s
Intégration / Tests unitaires et couverture (pull_request) Successful in 54s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 12s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m16s
a4c0755fc1
docs: renumérote la fiche en 0008 et raccorde le manuel aux deux tickets
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 43s
Intégration / Tests unitaires et couverture (pull_request) Successful in 1m2s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 9s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m17s
8a0965e361
L'ADR 0007 a été prise par l'état Terraform distant, fusionnée entre-temps.
La fiche de la zone or devient la 0008, et le manuel PostgreSQL dit d'où
vient chaque table : forme réduite du #29, complément du #105.

Refs #105
Merge pull request 'db : amorce la zone or, hypertable « mesure » et compression au-delà de 7 jours' (#106) from olivier/29-hypertables-compression into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 44s
Intégration / Tests unitaires et couverture (push) Successful in 53s
Intégration / Dépendances du tableau de bord (push) Successful in 8s
Intégration / Images épinglées par version (push) Successful in 3s
Intégration / Aucun secret commité (push) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 1m13s
da87c92aaa
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/106
Reviewed-by: marvin <marvin@noreply.10.105.200.41>
Merge branch 'develop' into lenaic/92-restauration-hors-serveur
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 43s
Intégration / Tests unitaires et couverture (pull_request) Successful in 50s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 8s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m14s
1cdeec5654
Merge branch 'develop' of https://10.105.200.41/g2/enervision into olivier/25-page-connexion
Some checks failed
Intégration / Qualité du code Python (pull_request) Successful in 50s
Intégration / Tests unitaires et couverture (pull_request) Successful in 56s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 17s
Intégration / Images épinglées par version (pull_request) Successful in 2s
Intégration / Tableau de bord (pull_request) Failing after 14s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m20s
bda15ea547
# Conflicts:
#	services/dashboard/.gitignore
#	services/dashboard/index.html
#	services/dashboard/package-lock.json
#	services/dashboard/package.json
#	services/dashboard/src/App.vue
#	services/dashboard/src/assets/base.css
#	services/dashboard/src/assets/main.css
#	services/dashboard/src/main.js
#	services/dashboard/src/router/index.js
Merge branch 'develop' into justine/28-droits-minio-env
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 42s
Intégration / Tests unitaires et couverture (pull_request) Successful in 53s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 9s
Intégration / Images épinglées par version (pull_request) Successful in 2s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m15s
a7f265d1f6
dashboard: restaure la configuration vite perdue au revert
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 43s
Intégration / Tests unitaires et couverture (pull_request) Successful in 52s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 12s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 17s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m11s
66b3732dab
Le fichier services/dashboard/vite.config.js avait été supprimé par le
revert 9d010d8 sans jamais être restauré. Sans lui, vite tourne sans le
plugin @vitejs/plugin-vue et refuse de parser les fichiers .vue, tandis
que vitest.config.js échoue à l'importer : le serveur de développement
comme les tests unitaires du tableau de bord étaient cassés.

Contenu repris tel quel de dd41634 : plugins vue et vue-devtools, alias
« @ » vers src.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GnUAobdPeQynk8EnCeMLdW
Merge pull request 'dashboard: intègre la page de connexion de la maquette #25 et son jeu de comptes' (#75) from olivier/25-page-connexion into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 42s
Intégration / Tests unitaires et couverture (push) Successful in 54s
Intégration / Dépendances du tableau de bord (push) Successful in 14s
Intégration / Images épinglées par version (push) Successful in 3s
Intégration / Tableau de bord (push) Successful in 18s
Intégration / Aucun secret commité (push) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 1m18s
1a94b57d03
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/75
Reviewed-by: marvin <marvin@noreply.10.105.200.41>
Merge branch 'develop' into justine/28-droits-minio-env
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 44s
Intégration / Tests unitaires et couverture (pull_request) Successful in 59s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 11s
Intégration / Images épinglées par version (pull_request) Successful in 2s
Intégration / Tableau de bord (pull_request) Successful in 17s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m17s
d144f9bd87
Merge pull request 'justine/28-droits-minio-env' (#104) from justine/28-droits-minio-env into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 48s
Intégration / Tests unitaires et couverture (push) Successful in 59s
Intégration / Dépendances du tableau de bord (push) Successful in 15s
Intégration / Images épinglées par version (push) Successful in 3s
Intégration / Tableau de bord (push) Successful in 29s
Intégration / Aucun secret commité (push) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 1m24s
d410602efb
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/104
Reviewed-by: lenaic <lenaic@noreply.10.105.200.41>
Merge remote-tracking branch 'origin/justine/33-collecte-bronze' into justine/33-collecte-bronze
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 49s
Intégration / Tests unitaires et couverture (pull_request) Successful in 50s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 14s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 21s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m24s
57dc798829
# Conflicts:
#	docs/adr/README.md
db: retire l'update de normalisation et l'index en double de la migration 0010
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 48s
Intégration / Tests unitaires et couverture (pull_request) Successful in 56s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 8s
Intégration / Images épinglées par version (pull_request) Successful in 2s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m22s
d964d82533
Relevé sur enervision_preprod : l'update touchait 347 574 lignes sur 352 807,
soit 98,5 % d'une hypertable dont 28 des 36 fragments sont comprimés — et une
migration s'applique au démarrage du serveur. La zone or étant dérivée, la
normalisation des libellés vient avec le rechargement du #35.

L'index sur (horodatage desc) portait un nom différent de celui que pose
create_hypertable : « if not exists » ne protégeait de rien et un second
index identique aurait été créé sur chacun des 36 fragments.

Deux cas de test tiennent désormais les deux règles.

Refs #105
[33] Rebase & retours MR
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 51s
Intégration / Tests unitaires et couverture (pull_request) Successful in 57s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 14s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 20s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m28s
fccf40992a
Merge pull request '[33] Ajout collecteur' (#109) from justine/33-collecte-bronze into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 57s
Intégration / Tests unitaires et couverture (push) Successful in 57s
Intégration / Dépendances du tableau de bord (push) Successful in 15s
Intégration / Images épinglées par version (push) Successful in 3s
Intégration / Tableau de bord (push) Successful in 20s
Intégration / Aucun secret commité (push) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 1m25s
6613760c4b
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/109
Reviewed-by: lenaic <lenaic@noreply.10.105.200.41>
Merge develop dans olivier/105-migrations-zone-or
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 53s
Intégration / Tests unitaires et couverture (pull_request) Successful in 58s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 14s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 20s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m25s
aa9a19058d
Trois conflits, et une collision de numéro de fiche.

La #109 a été fusionnée entre-temps et sa fiche a pris le numéro 0008, celui
que cette branche revendiquait aussi. La fiche du schéma de la zone or devient
donc 0009, avec ses références : db/migrations/README.md, docs/POSTGRESQL.md,
0008_zone_or_site.sql, 0015_ops_acces_site.sql et les deux fichiers de test.
Les numéros « 0008 » des noms de migration ne sont pas touchés, ils n'ont rien
à voir avec la série des fiches.

docs/POSTGRESQL.md et db/migrations/README.md : la version de la branche est
retenue, elle est le sur-ensemble et connaît déjà la migration 0007.

Une contradiction interne est levée au passage. Un paragraphe annonçait que la
0007 avait converti « mesure » en hypertable comprimée, un autre plus bas la
disait « encore une table ordinaire » : le second était antérieur à la #106.
Seule « prevision » reste ordinaire. Le relevé chiffré de la #106 — 7 lignes,
352 807 mesures, 36 fragments dont 28 comprimés — est conservé, il avait été
perdu par la résolution.

Vérifié : la chaîne complète 0001 à 0015 passe sans erreur sur une base neuve
et pose les dix tables attendues, six en public, acces_site plus les trois
tables d'authentification en ops.
Merge pull request 'db : les sept tables de la zone or et le référentiel des sites (#105)' (#110) from olivier/105-migrations-zone-or into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 48s
Intégration / Tests unitaires et couverture (push) Successful in 53s
Intégration / Dépendances du tableau de bord (push) Successful in 20s
Intégration / Images épinglées par version (push) Successful in 3s
Intégration / Tableau de bord (push) Successful in 22s
Intégration / Aucun secret commité (push) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 1m34s
a2f817ecc5
Complète collector.current par deux modules qui écrivent sous leurs propres
préfixes bronze, sans jamais toucher aux siens (ADR 0005).

collector.backfill remonte l'historique /readings par fenêtres. Le modèle de
pagination de la source a été mesuré et non supposé : elle synthétise
exactement `limit` points répartis sur la fenêtre demandée, elle ne tronque
jamais. Le pas vaut donc fenêtre ÷ limit et se choisit par --pas-minutes ; le
plafond de 1000 se lit comme une résolution minimale. La spécification du §6
décrivait un modèle de troncature, elle est corrigée.

L'état de panne courant d'un site s'applique à tout son historique : le module
lit /api/v1/sensors/status avant tout et saute les sites qui ne sont pas ok, en
retenant le failing_until que la source donne elle-même. Une fenêtre revenue
sans aucune valeur est reportée, jamais écrite comme vérité ni jetée.

collector.alertes est une collecte au fil de l'eau et non un lot : /api/v1/alerts
n'accepte ni bornes de temps ni curseur, il rend les alertes courantes. Son
idempotence tient à la clé, qui porte l'alert_id.

L'écriture accepte désormais un content_type, sans quoi un .json.gz serait
annoncé comme du JSON nu.

28 cas unitaires ajoutés, 88 au total sur le collecteur.
collecteur: filtre le rattrapage sur le capteur de consommation
Some checks failed
Intégration / Qualité du code Python (pull_request) Failing after 30s
Intégration / Tests unitaires et couverture (pull_request) Successful in 57s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 14s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 20s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m25s
8d91991dc2
Le filtre portait sur « overall », il porte désormais sur le capteur
« consumption ». Décision mesurée, pas supposée.

Croisement de l'état annoncé et de ce que /readings rend réellement, 84
observations : « consumption: failing » n'a jamais rien rendu, 77 fois sur 77.
À l'inverse un site « degraded » dont ce capteur va bien rend des valeurs, et
l'ancien filtre les jetait.

Le coût de l'ancien critère se chiffre : un site n'est « ok » que 1,8 % du
temps, contre 28,6 % de « degraded ». Sur dix passes visant 49 fenêtres,
l'ancien filtre écrivait 2 fenêtres, celui-ci en écrit 9.

« consumption: ok » ne garantit rien pour autant, trois cas sur sept n'ont
rien rendu : c'est le report des fenêtres vides qui les rattrape.

4 cas unitaires ajoutés, 92 au total sur le collecteur.
collecteur: mise en forme ruff
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 49s
Intégration / Tests unitaires et couverture (pull_request) Successful in 56s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 12s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 20s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m21s
21119c35b1
`ruff format --check` est bloquant dans la chaîne, et je n'avais joué que
`ruff check`. Deux fichiers reformatés, aucun changement de comportement.
collecteur: les lanceurs acceptent un interpréteur en paramètre
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 50s
Intégration / Tests unitaires et couverture (pull_request) Successful in 57s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 13s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 18s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m24s
d5ff09620d
Le rôle Ansible du #113 pose un environnement Python isolé sur le serveur et
le nomme par ENERVISION_PYTHON. Sans cette variable, les lanceurs appellent le
python3 du système, où les dépendances ne sont pas installées et ne peuvent
pas l'être (PEP 668).
collecteur: retours de relecture de la #111
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 55s
Intégration / Tests unitaires et couverture (pull_request) Successful in 59s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 13s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 20s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m27s
fd4890c81c
Olivier a relu et trouvé un vrai défaut, plus deux corrections de fond et une
série de détails. Tout est traité.

L'IDEMPOTENCE NE TENAIT PAS DEPUIS LA LIGNE DE COMMANDE. main() calait la
grille sur l'heure courante tronquée à la minute : rejouer la même commande une
minute plus tard décalait toutes les bornes, donc toutes les clés, et produisait
deux jeux d'objets chevauchants au lieu de réécrire les mêmes. Exactement ce que
le docstring de fenetres() interdit, et ce que le README promettait. Les cas de
test ne l'attrapaient pas parce qu'ils passaient les bornes en dur et
n'exerçaient jamais main(). Les bornes sont désormais ancrées sur un multiple
entier de la fenêtre depuis l'époque Unix, ce qui règle au passage le
partitionnement : avec une fenêtre de 24 h elles tombent à minuit UTC, au lieu
d'enjamber deux journées civiles.

UN ÉTAT DES CAPTEURS ILLISIBLE SORT DÉSORMAIS EN 1. Journaliser en « error »
puis rendre 0 laissait un cron nocturne qui n'a rien collecté passer pour un
succès.

GZIP N'INSCRIT PLUS L'HEURE DE COMPRESSION. Même contenu, mêmes octets : sur un
seau versionné comme bronze, chaque rejeu créait sinon une version pour rien, et
l'idempotence de la clé se reperdait par la porte de derrière.

LES ALERTES À L'HORODATAGE ILLISIBLE ne retombent plus sur l'heure de réception,
qui change à chaque passe et cassait la déduplication protégée trois lignes plus
haut. Elles vont sous dt=inconnu. Et le décompte sépare l'alerte malformée par
la source, qui ne doit pas faire sonner cron chaque minute, de l'alerte perdue à
l'écriture, qui est une vraie perte.

La limite est recalculée par fenêtre : la dernière, plus courte, recevait la
limite des fenêtres pleines et donc un pas plus fin que celui demandé.

Le client HTTP et l'appel qui ne lève jamais remontent dans http.py : trois
modules importaient un _appeler privé chez leur voisin. Depot.ecrire_json
devient ecrire_objet, le nom mentait depuis qu'il accepte un content_type.
Resultat.a_rejouer devient a_reprendre, il divergeait de Lot.a_rejouer sur un
échec d'écriture.

Documentation : le §6 d'etl-pipeline décrivait encore le filtre sur « overall »
remplacé depuis, le renvoi à l'ADR 0007 pointait sur Terraform au lieu du mode
d'exécution, et docs/runbooks/collecteur.md — cité par deux lanceurs — n'existait
pas. Il existe.

9 cas unitaires ajoutés, 102 au total. ruff check, ruff format --check et mypy
passent.
collecteur: note l'hypothèse de fuseau sur les bornes sortantes
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 54s
Intégration / Tests unitaires et couverture (pull_request) Successful in 56s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 14s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 22s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m25s
f3499274fa
Dernier point mineur de la relecture d'Olivier. cles.py documente déjà
l'hypothèse pour les horodatages entrants ; elle vaut aussi pour ceux qu'on
envoie, et une hypothèse tacite des deux côtés en fait une évidence pour
personne.
infra: supervision Prometheus et Grafana, exporters et tableaux de bord
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 46s
Intégration / Tests unitaires et couverture (pull_request) Successful in 50s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 14s
Intégration / Images épinglées par version (pull_request) Successful in 4s
Intégration / Tableau de bord (pull_request) Successful in 18s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m29s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m2s
1b31f7f636
Pose la pile supervision (infra/compose/supervision), déployée par le rôle app :
Prometheus, Grafana sans compte par défaut, node-exporter, cAdvisor et
postgres-exporter, tous en écoute sur 127.0.0.1. Deux sources Grafana
(Prometheus pour la technique, PostgreSQL pour le métier) et quatre tableaux de
bord versionnés : socle, hote, conteneurs, postgresql.

Nouveau rôle Ansible proxy : aligne le Caddyfile de la forge sur le dépôt, le
valide dans un conteneur jetable puis recharge Caddy à chaud. Pose le vhost
grafana.g2.enervision. Le déploiement continu passe à --tags app,proxy.

Environnement local complet sous infra/compose/supervision/local (réseau bridge,
ports publiés, PostgreSQL jetable avec schéma d'exemple) pour travailler les
tableaux de bord hors serveur.

Contrôle CI tests/ci/test-supervision.sh. Ports 9100, 9101 et 9187 ajoutés au
plan d'adressage. Manuel docs/runbooks/supervision.md.

Closes #31
collecteur: refuse un identifiant d'alerte qui ne peut pas nommer un objet
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 54s
Intégration / Tests unitaires et couverture (pull_request) Successful in 56s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 14s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 19s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m28s
41743cccd5
Relecture critique avant de proposer la fusion. L'alert_id devient un segment
de chemin dans la clé bronze : un « / » y créerait une arborescence là où on
attend un objet, et « .. » sortirait du préfixe. La source rend
« ALR-SITE001-1788429204 », mais rien ne le garantit et une clé mal formée
s'écrirait silencieusement ailleurs.

« . » et « .. » sont écartés à part : ils passent la classe de caractères et
désignent pourtant un répertoire.
Merge branch 'develop' into lenaic/92-restauration-hors-serveur
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 51s
Intégration / Tests unitaires et couverture (pull_request) Successful in 54s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 12s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 20s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m24s
f7fdf9110c
docs: repriorisation du 03/09, redécoupage des jalons et PRD de pilotage
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 52s
Intégration / Tests unitaires et couverture (pull_request) Successful in 57s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 13s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 21s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m27s
17e55337f6
Redécoupe J1/J2/J3 sur l'avancement réel vérifié (serveur, forge, docs),
la fenêtre 03->11/09 restant inchangée. Acte cinq réductions de périmètre
dont le contenu retiré part en tickets Portée/Post-jury, sans rien supprimer.

- docs/BACKLOG.md réécrit : capacité restante, avancement vérifié, trois
  jalons redécoupés, backlog ordonné, réductions assumées, écarté de la
  fenêtre, charge par personne.
- docs/PRD.md nouveau : vision, périmètre daté, statut par exigence
  EF/ENF, état de déploiement réel, décisions produit, risques,
  traçabilité. Les exigences restent définies dans EXIGENCES-collectives.md.
- ancien backlog déplacé sous docs/historisation/.
Merge pull request 'collecteur: rattrapage par lots de l'historique et ingestion des alertes' (#111) from lenaic/107-rattrapage-par-lots into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 51s
Intégration / Tests unitaires et couverture (push) Successful in 59s
Intégration / Dépendances du tableau de bord (push) Successful in 14s
Intégration / Images épinglées par version (push) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 1m25s
Intégration / Tableau de bord (push) Successful in 21s
Intégration / Aucun secret commité (push) Successful in 3s
e0395db2cb
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/111
Reviewed-by: olivier <olivier@noreply.10.105.200.41>
Merge branch 'develop' into lenaic/92-restauration-hors-serveur
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 48s
Intégration / Tests unitaires et couverture (pull_request) Successful in 55s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 13s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 23s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m24s
74ad2aba85
Le clone du serveur datait du 2 septembre à 14 h 06. Tout ce qui a été fusionné
depuis n'existait que dans develop, et ce qui tourne sur la machine y avait été
posé à la main, service par service. Ce lot rend le déploiement complet pour la
chaîne de données.

MLflow entre dans app_stacks. Il tournait parce que quelqu'un l'avait lancé ;
une réinstallation le perdait. Sa composition ne porte aucune interpolation, et
son fichier de secrets est en 0600 root:root parce qu'il contient des
identifiants MinIO dédiés : le lui passer par docker compose ferait échouer la
tâche, deploy n'y ayant pas accès. D'où le drapeau « env » par pile, et une
garde qui refuse de démarrer une pile dont les secrets manquent — mieux vaut
échouer là qu'au démarrage du conteneur, où un env_file absent envoie boto3
vers le vrai AWS S3 pendant deux minutes sans le dire.

Le collecteur obtient ce que l'ADR 0008 annonçait comme « le ticket suivant » :
un environnement Python isolé, le bit d'exécution sur ses lanceurs, et ses
lignes de crontab. L'environnement est isolé et non installé sur le système
parce qu'Ubuntu 24.04 refuse l'installation globale (PEP 668). Le rôle ne
planifie que les lanceurs réellement présents dans le dépôt : ceux du #107
seront ramassés d'eux-mêmes au déploiement suivant, et une ligne de crontab
pointant vers un fichier absent échouerait chaque minute en silence.

Les migrations en attente sont appliquées sur les deux bases, sous le rôle
applicatif et jamais sous postgres : une table posée en superutilisateur
échappe à la clause de droits par défaut et devient invisible de Grafana sans
le moindre message. Le mot de passe passe par PGPASSWORD plutôt que par le DSN,
une ligne de commande étant lisible dans ps.

Vérifié en local : yamllint propre, ansible-lint au profil production, et les
trois playbooks passent le contrôle de syntaxe.
Relecture critique de ma propre demande avant de la proposer. Trois défauts,
tous vérifiés sur le serveur, dont deux qui l'auraient fait échouer au premier
passage.

LES MOTS DE PASSE DU COFFRE ÉTAIENT REFUSÉS par les deux rôles applicatifs. Le
modèle postgres.env n'est lu qu'à la première initialisation du volume : passé
ce moment, le coffre et le serveur dérivent sans que rien ne le signale. Une
reconstruction depuis zéro aurait produit un serveur auquel personne ne se
connecte avec les identifiants documentés. Le rôle réaligne désormais les deux
rôles de collector_bases, et surtout pas mlflow ni grafana, qui lisent leur mot
de passe ailleurs.

LA PROPRIÉTÉ DES OBJETS N'ÉTAIT PAS AU RÔLE APPLICATIF. Sur enervision_prod les
trois tables appartenaient à postgres et le rôle n'avait pas create sur ops ;
sur enervision_preprod il ne l'avait pas sur public. Aucune migration n'y serait
passée. Le geste est celui de db/migrations/README.md, qui note à juste titre
qu'il ne peut pas vivre dans une migration jouée par le rôle applicatif : sa
place est ici, en superutilisateur, et il est rejouable.

LES DÉPENDANCES ÉTAIENT INCOMPLÈTES. migrate.py passe par enervision_api.db, qui
lit la configuration de l'API au niveau module : psycopg seul ne suffit pas, il
manquait pydantic-settings et le reste. Plutôt que de courir après les
dépendances transitives, le rôle lit les pyproject.toml des services par le
script que la chaîne d'intégration utilise déjà pour la même question. Une
dépendance ajoutée à un service arrive au déploiement suivant sans que personne
n'y pense.

Deux corrections plus petites. Le changed_when des migrations cherchait
« Applied » dans stdout ; migrate.py journalise en français et sur stderr, la
tâche serait restée éternellement « ok ». Et la variable de boucle du bloc
PL/pgSQL doit être déclarée, ce que PL/pgSQL n'infère pas dans un bloc anonyme.

Séquence jouée en entier sur le serveur : 14 migrations appliquées sur la
production, 8 sur la préproduction, code 0, et un second passage ne fait rien.
Les deux bases portent maintenant leurs onze tables et leurs quinze migrations.

yamllint, ansible-lint au profil production et les trois contrôles de syntaxe
passent.
Sa section « Conséquences » disait que la ligne de crontab et le déploiement du
lanceur étaient posés à la main et que les porter dans le rôle Ansible était le
ticket suivant. C'est ce ticket, et il est fait. Une fiche qui décrit un état
révolu se retourne contre celui qui s'y fie.
infra: retours de relecture d'Olivier sur la #114
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 1m0s
Intégration / Tests unitaires et couverture (pull_request) Successful in 1m2s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 15s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 20s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m32s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 56s
39e5494837
Trois défauts bloquants, trois importants, et une série de détails. Tout est
traité. Sa relecture a trouvé, entre autres, que ma prémisse centrale était
fausse.

UN env_file EST LU PAR LE CLIENT, PAS PAR LE DÉMON. Je croyais que le démon
Docker, qui tourne en root, lisait mlflow.env et que deploy n'avait donc pas à
y accéder. C'est faux : `docker compose` lit le fichier côté client et en
injecte le contenu avant d'appeler l'API. La pile MLflow aurait échoué, et mon
drapeau « env » ne réglait rien de ce qu'il prétendait régler. Le rôle rend
désormais lisibles par le groupe les fichiers de secrets des piles.

LA GARDE VERROUILLAIT TOUTE RECONSTRUCTION. Posée avant le démarrage et
couvrant les trois piles d'un coup, elle arrêtait le playbook sur une machine
neuve : mlflow.env ne peut pas exister avant que MinIO ne tourne, puisque
genere-identifiants.sh en a besoin. Ni base, ni stockage, ni migrations, ni
collecte — exactement la réinstallation impossible que ce ticket veut fermer.
Une pile sans secrets est maintenant sautée bruyamment, le socle part quand
même, et le déploiement suivant la ramasse.

ENERVISION_RACINE N'ÉTAIT PAS POSÉ. Le lanceur fait « cd $RACINE/services/
collector », dont le défaut vaut /opt/enervision alors que le dépôt est cloné
dans .repo. Avec set -eu la relève sortait chaque minute sans rien collecter,
et ce cd n'est pas décoratif : pip installe les dépendances, pas le paquet
collector, qui ne se résout que par le répertoire courant. C'est ce point qui
faisait tomber le seul critère qui compte, la croissance de la zone bronze.

Le SQL entre désormais par l'entrée standard et non par l'argv, où le mot de
passe était lisible via ps — la ligne que le dépôt tient partout ailleurs, et
l'argument même que cette demande avançait pour les migrations. La réparation
de droits ajoute « create on database », sans quoi le « create schema if not
exists ops » de run_migrations échoue sur une base dont le rôle n'est pas
propriétaire. Elle passe par pg_class plutôt que pg_tables, qui ignorait vues
et séquences. Et une attente explicite de PostgreSQL remplace le pari sur les
quelques dizaines de secondes achetées par les tâches intercalées.

L'idempotence est rétablie : le dépôt livre les lanceurs en 100755 et la tâche
qui les chmodait disparaît. Elle luttait contre « git force: true », qui voyait
le changement de mode et le remettait à chaque passage.

Le reste : collector_paquets était mort, psycopg n'arrivait que par le pyproject
de l'API ; les journaux tournent ; deploy.yml couvre mlflow dans sa preuve ; et
le runbook ne dit plus que no_log protège un mot de passe qui n'est pas là, mais
explique où l'exposition se déplace et comment constater qu'une correction a eu
lieu.

yamllint, ansible-lint au profil production et les trois contrôles de syntaxe
passent. Forme stdin et pg_isready vérifiés sur le serveur.
Merge pull request 'docs: repriorisation du 03/09, jalons redécoupés et PRD' (#119) from gabriel/repriorisation-03-09 into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 1m5s
Intégration / Tests unitaires et couverture (push) Successful in 1m11s
Intégration / Dépendances du tableau de bord (push) Successful in 14s
Intégration / Images épinglées par version (push) Successful in 3s
Intégration / Tableau de bord (push) Successful in 19s
Intégration / Aucun secret commité (push) Successful in 4s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 1m36s
de9dead174
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/119
Reviewed-by: lenaic <lenaic@noreply.10.105.200.41>
docs: précise le partage Marvin/Olivier et rattache les tâches faites à J1
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 48s
Intégration / Tests unitaires et couverture (pull_request) Successful in 57s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 15s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 18s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m24s
Intégration / Aucun secret commité (pull_request) Successful in 2s
9971e21e3d
- rang 1 pointe l'issue #113 (PR #114) au lieu de la seule PR
- ajoute « ce que Marvin et Olivier portent, concrètement » : Marvin sur
  l'API (#38 absorbe #94, #62), Olivier sur le front et la zone or
  (#53, #24, #35 avec Justine)
- note le rattachement des tâches déjà faites à la Milestone J1 et le
  ticket de suivi #120
Merge branch 'develop' into lenaic/92-restauration-hors-serveur
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 1m4s
Intégration / Tests unitaires et couverture (pull_request) Successful in 55s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 13s
Intégration / Images épinglées par version (pull_request) Successful in 4s
Intégration / Tableau de bord (pull_request) Successful in 21s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m32s
5800d334ef
infra: réaligne aussi grafana et lui donne pg_monitor
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 49s
Intégration / Tests unitaires et couverture (pull_request) Successful in 55s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 14s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 18s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m29s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m10s
badc710b97
Trouvé en relisant la #115 de Gabriel, qui ne peut pas fonctionner sans.

Le mot de passe du rôle grafana dans le coffre était refusé par le serveur, la
même dérive que pour les deux rôles applicatifs. Ses deux consommateurs de la
#115 le lisent — la source de données Grafana et postgres-exporter — et
auraient échoué au démarrage : tableau de bord métier vide, exportateur muet.
Vérifié, avec un contre-essai montrant qu'un mauvais mot de passe est bien
rejeté, donc le refus était réel et non un artefact.

mlflow reste volontairement en dehors : il tourne, et il lit son mot de passe
dans /etc/enervision/mlflow.env posé à la main lors du #30. Le réaligner sur le
coffre le couperait de sa base.

Et grafana devient membre de pg_monitor. postgres-exporter lit
pg_stat_replication, pg_stat_wal et le texte des requêtes de pg_stat_activity :
sans ce droit il démarre, rend des métriques partielles et journalise des
erreurs. Mesuré avant et après sur le serveur : les 23 requêtes de
pg_stat_activity passent de masquées à lisibles. Le rôle reste en lecture seule
sur les données métier.
Merge pull request 'docs: restauration hors serveur jouée et chronométrée, manuels à jour (#92)' (#99) from lenaic/92-restauration-hors-serveur into develop
Some checks are pending
Intégration / Qualité du code Python (push) Waiting to run
Intégration / Tests unitaires et couverture (push) Waiting to run
Intégration / Dépendances Python et inventaire applicatif (push) Waiting to run
Intégration / Dépendances du tableau de bord (push) Waiting to run
Intégration / Images épinglées par version (push) Waiting to run
Intégration / Tableau de bord (push) Waiting to run
Intégration / Aucun secret commité (push) Waiting to run
158c9cef7f
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/99
Reviewed-by: florian <florian@noreply.10.105.200.41>
Reviewed-by: gabriel <gabriel@noreply.10.105.200.41>
Merge pull request 'docs: partage Marvin/Olivier et tâches faites rattachées à J1' (#122) from gabriel/repriorisation-03-09 into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 51s
Intégration / Tests unitaires et couverture (push) Successful in 58s
Intégration / Dépendances du tableau de bord (push) Successful in 16s
Intégration / Images épinglées par version (push) Successful in 3s
Intégration / Tableau de bord (push) Successful in 21s
Intégration / Aucun secret commité (push) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 1m32s
7ca40a1730
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/122
Reviewed-by: olivier <olivier@noreply.10.105.200.41>
infra: retours de Gabriel et d'Olivier sur la #114
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 51s
Intégration / Tests unitaires et couverture (pull_request) Successful in 53s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 14s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 23s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m24s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 2m12s
414bacce68
Trois défauts, dont un que j'avais cru corriger et qui ne l'était pas, et une
régression que j'avais introduite.

LE SAUT DE MLFLOW N'ÉTAIT PAS CÂBLÉ. J'avais remplacé la garde par un
avertissement, mais la boucle de démarrage itérait toujours app_stacks entier :
mon filtrage n'avait rien trouvé à remplacer et je ne l'avais pas vérifié. La
pile était donc tentée malgré l'avertissement, docker compose sortait en 1 sur
son env_file absent — qu'il lit depuis la composition, indépendamment du
drapeau env — et tout ce qui suit ne tournait pas. On retombait sur le scénario
d'Olivier, décalé de quelques tâches. La boucle porte maintenant sur la
présence du secret, et la condition est bien celle-là et non le drapeau env :
MLflow a env: false et exige pourtant son fichier.

LA BOUCLE PG_CLASS ÉCHOUAIT SUR LES SÉQUENCES D'IDENTITÉ, régression née de ma
propre réponse à la remarque d'Olivier sur pg_tables. « alter sequence … owner
to » est refusé sur une séquence liée à une colonne, quel que soit son
propriétaire : le test porte sur le lien, pas sur la propriété. Quatre
migrations posent « generated always as identity », donc la réparation échouait
sur les deux bases, sous ON_ERROR_STOP, juste avant les migrations. Les
séquences liées sont écartées ; « alter table … owner to » les emporte de toute
façon. Vérifié sur la préproduction : 4 séquences liées, réparation passée,
0 objet hors du rôle à l'arrivée.

Et deux scories de mon correctif précédent : une tautologie « X or true »
laissée d'une édition, et un failed_when: false qui avalait toute erreur sur
des fichiers de secrets — un chmod échoué en silence aurait laissé mlflow.env
en 0600 root:root et fait échouer la pile bien plus loin, avec le message boto3
vers le vrai AWS S3 que ce ticket veut éviter. Seule l'absence du fichier est
désormais tolérée.
docs: renumérote la fiche du tunnel SSH en 0010 et la porte à l'index
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 50s
Intégration / Tests unitaires et couverture (pull_request) Successful in 53s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 14s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 19s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m25s
06f086755f
Retour de Gabriel. Le numéro 0003 était pris par la fiche DuckDB : cette
demande a été ouverte avant la renumérotation, et la série est allée jusqu'à
0009 depuis, avec le collecteur en cron et le schéma de la zone or. 0010 est le
premier libre.

Et la définition de terminé exige la ligne dans le tableau du README des
fiches : une décision qui n'y figure pas n'existe pas.
outillage: ancre les motifs de CODEOWNERS et dit ce qu'il ne fait pas
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 50s
Intégration / Tests unitaires et couverture (pull_request) Successful in 57s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 12s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 22s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m27s
7366ad1fbb
Retours de Gabriel. Les motifs sont des expressions régulières non ancrées
confrontées au chemin complet : « infra/.* » attrapait aussi « docs/infra/… ».
Aucun chemin de ce genre aujourd'hui, mais un fichier ajouté demain aurait
déclenché une relecture obligatoire sans que personne ne comprenne pourquoi.

Et le fichier dit désormais ce qu'il ne fait pas : il ajoute le propriétaire
comme relecteur, il n'exige rien. Tant que « relecture des Code Owners requise »
n'est pas posé sur la protection de branche, une demande touchant à infra/ reste
fusionnable avec l'approbation d'un pair tiré au sort. Croire l'inverse est pire
que ne rien avoir.
Merge branch 'develop' into lenaic/113-deploiement-complet
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 58s
Intégration / Tests unitaires et couverture (pull_request) Successful in 59s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 15s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 20s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m27s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m23s
4257b7c2fe
Merge pull request 'docs: ADR 0003 — accès base par tunnel SSH, jamais par le réseau (#65)' (#97) from lenaic/65-adr-acces-base-tunnel-ssh into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 50s
Intégration / Tests unitaires et couverture (push) Successful in 52s
Intégration / Dépendances du tableau de bord (push) Successful in 11s
Intégration / Images épinglées par version (push) Successful in 2s
Intégration / Tableau de bord (push) Successful in 18s
Intégration / Aucun secret commité (push) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 1m21s
9a9c89bed3
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/97
Reviewed-by: florian <florian@noreply.10.105.200.41>
Reviewed-by: gabriel <gabriel@noreply.10.105.200.41>
Retour d'Olivier. Mon correctif précédent réintroduisait le blocage quelques
tâches plus tôt : je testais le message du module `file`, qui dit « is absent,
cannot continue » et non « does not exist ». La condition était donc toujours
vraie, le playbook mourait sur le chmod, avant le stat, avant l'avertissement et
avant la boucle filtrée. Tout le dispositif « sauter et continuer » n'avait
jamais l'occasion de tourner. Le test porte maintenant sur l'état, qui est déjà
dans le résultat, correctif repris de lui.

Et trois endroits où l'écrit contredisait le code.

Le commentaire du réalignement et le runbook interdisaient tous deux « surtout
pas mlflow ni grafana » alors que le code réaligne grafana depuis la #115. La
raison a changé et c'est elle qui est écrite : le réalignement suit le fichier
de secrets, pas le rôle. Celui de grafana vient du coffre, celui de mlflow est
posé à la main, donc mlflow reste dehors. Vérifié avant d'ajouter grafana :
aucun Grafana n'est installé sur le serveur, ni conteneur ni service, le
réalignement ne coupe rien.

La composition MLflow et son manuel annonçaient encore mlflow.env en 0600 root
alors que le rôle le pose en 0640 root:deploy. L'élargissement est le bon choix
mais il change la posture, les identifiants MinIO dédiés du #30 devenant
lisibles par le groupe deploy : c'est écrit là où le contraire était affirmé.

Et pg_monitor ne donne pas que des compteurs : il donne aussi le texte des
requêtes des autres sessions, qui porte des valeurs littérales. « Lecture seule
sur les données métier » reste vrai pour les tables, mais l'échange se décide
plutôt qu'il ne se subit.

collector_paquets, mort depuis deux passes, est enfin supprimé.
collector_roles_lecture devient pg_roles_lecture, il n'avait rien à voir avec le
collecteur. Et le commentaire parle de trois tâches SQL, puisqu'elles sont trois.
tests: un banc d'essai qui joue la logique du rôle app
Some checks failed
Intégration / Qualité du code Python (pull_request) Successful in 47s
Intégration / Tests unitaires et couverture (pull_request) Successful in 57s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 13s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 19s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m21s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Infra Ansible / Playbooks Ansible valides (pull_request) Failing after 59s
366d9ee108
Trois défauts sont partis en relecture sur la #114, et aucun n'était atteignable
par yamllint ni par ansible-lint : une condition tautologique, une boucle non
filtrée, et un failed_when qui testait le message d'un module au lieu de son
état. Un linter lit la forme ; ces trois-là ne se voient qu'en exécutant.

Ce playbook exécute. Il rejoue les expressions du rôle sur des données
fabriquées, dans un répertoire temporaire, sans serveur ni Docker :

  - un fichier de secrets absent doit être toléré par le failed_when,
  - la boucle de démarrage ne doit retenir que les piles qui ont le leur,
  - env_files doit rendre une liste ou omettre le paramètre,
  - la ligne de crontab doit porter ENERVISION_RACINE autant qu'ENERVISION_PYTHON,
  - aucune condition du rôle ne doit se terminer par « or true ».

Les cinq ont été vérifiés dans les deux sens : chacun a été mis en échec en
réintroduisant le défaut qu'il vise, puis remis au vert. Ce contrôle a d'ailleurs
trouvé une faute dans le banc lui-même — un double antislash en YAML donnait un
antislash littéral, et la détection de tautologie ne cherchait pas ce que je
croyais. Un test qui passe ne prouve rien tant qu'il n'a pas échoué.

Branché à « Infra Ansible », en amont d'ansible-lint.
Merge pull request 'outillage: CODEOWNERS — relecture Tech Lead requise sur les chemins sensibles' (#98) from lenaic/0-codeowners into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 49s
Intégration / Tests unitaires et couverture (push) Successful in 1m9s
Intégration / Dépendances du tableau de bord (push) Successful in 12s
Intégration / Images épinglées par version (push) Successful in 3s
Intégration / Tableau de bord (push) Successful in 20s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 1m38s
Intégration / Aucun secret commité (push) Successful in 3s
6440d456a6
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/98
Reviewed-by: florian <florian@noreply.10.105.200.41>
Reviewed-by: gabriel <gabriel@noreply.10.105.200.41>
tests: le banc ne dépend plus d'un groupe qui n'existe pas dans la chaîne
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 55s
Intégration / Tests unitaires et couverture (pull_request) Successful in 59s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 13s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 18s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m24s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m31s
c20fc38f8b
Mon propre banc faisait échouer « Infra Ansible » : il posait un groupe lu dans
la variable USER, absente du conteneur de la chaîne, et le module rendait
« chgrp failed: failed to look up group ». La tâche échouait donc pour une
raison étrangère à ce qu'elle vérifie — le comportement du failed_when sur un
fichier absent.

Le groupe est retiré. Rejoué dans les trois environnements : avec USER, sans
USER, et sans HOME. Dix tâches au vert dans chaque cas.
tests: le banc lit enfin le rôle au lieu d'en recopier les expressions
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 49s
Intégration / Tests unitaires et couverture (pull_request) Successful in 58s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 11s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 18s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m25s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 2m7s
2b13e9bfac
Olivier a démontré que mon banc restait vert sur un rôle cassé : quatre de ses
cinq contrôles rejouaient une COPIE de l'expression, réécrite dans le banc.
Seule la détection de tautologie lisait vraiment main.yml. Un banc vert sur un
rôle cassé est pire qu'une absence de banc, parce que le suivant lui fait
confiance — et mon message de commit affirmait le contraire de ce que le code
faisait.

Tout est désormais extrait de roles/app/tasks/main.yml à l'exécution.

Le point délicat est qu'Ansible RETEMPLATE la valeur d'une variable : une
expression du rôle stockée telle quelle est évaluée au lieu d'être lue, et
l'assertion porte alors sur son résultat. Deux contrôles sont passés au vert
sur un rôle cassé pour cette raison, pendant que je les écrivais. Les accolades
sont donc neutralisées dès la lecture, dans la même expression.

Éprouvé dans les deux sens, et c'est le seul point qui compte : vert sur le
rôle sain, ROUGE sur les cinq défauts réintroduits un par un — le failed_when
qui teste le message, ENERVISION_RACINE retiré du cron, la boucle dé-filtrée,
le filtre pg_depend retiré, et la tautologie. Le script d'injection est lui-même
passé par deux corrections : il frappait la mauvaise occurrence d'une expression
présente deux fois, et son « vert » ne prouvait rien.

genere-identifiants.sh pose enfin /etc/enervision en 0710 root:deploy et
mlflow.env en 0640 root:deploy, au lieu de contredire la documentation que le
commit précédent venait d'aligner. C'est ce script qu'un exploitant lance à la
main, avant tout déploiement.

Reste ce que ce banc ne remplace pas, et qu'Olivier a raison d'exiger : une
exécution réelle sur le serveur, deux fois, et le décompte bronze.
Merge branch 'develop' into lenaic/113-deploiement-complet
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 53s
Intégration / Tests unitaires et couverture (pull_request) Successful in 55s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 14s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 21s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m30s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m8s
73df1acb6b
Merge pull request 'infra: le déploiement pose MLflow, le collecteur et les migrations' (#114) from lenaic/113-deploiement-complet into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 53s
Intégration / Tests unitaires et couverture (push) Successful in 59s
Intégration / Dépendances du tableau de bord (push) Successful in 14s
Intégration / Images épinglées par version (push) Successful in 3s
Intégration / Tableau de bord (push) Successful in 20s
Intégration / Aucun secret commité (push) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 1m25s
Infra Ansible / Playbooks Ansible valides (push) Successful in 58s
f4f4d7ee0e
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/114
Reviewed-by: olivier <olivier@noreply.10.105.200.41>
Merge develop dans gabriel/31-supervision-prometheus-grafana
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 1m15s
Intégration / Tests unitaires et couverture (pull_request) Successful in 56s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 15s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 19s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m28s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 56s
12a7f90150
Deux conflits, tous deux dus à la #114 fusionnée entre-temps.

app_stacks : on ajoutait chacun une pile après minio. Les quatre y sont
maintenant, dans l'ordre postgres, minio, mlflow, supervision. La supervision
passe en dernier pour la raison que Gabriel donnait déjà : Grafana teste sa
source « métier » au démarrage, et cette source est PostgreSQL, qui doit donc
être debout et saine — ce que le `wait: true` des piles précédentes garantit.

Elle reçoit `env: true` : son `supervision.env` sert à l'interpolation du
provisionnement Grafana, pas seulement au conteneur.

deploy.yml : la preuve d'après-déploiement couvre désormais les quatre piles au
lieu de deux listes qui s'ignoraient.

roles/app/tasks/main.yml fusionne sans conflit, Gabriel ajoutant en haut et moi
en bas.

Vérifié : yamllint, ansible-lint au profil production, les trois contrôles de
syntaxe, et le banc d'essai du rôle app.
Merge pull request 'infra: supervision Prometheus et Grafana, exporters et tableaux de bord' (#115) from gabriel/31-supervision-prometheus-grafana into develop
All checks were successful
Intégration / Qualité du code Python (push) Successful in 50s
Intégration / Tests unitaires et couverture (push) Successful in 55s
Intégration / Dépendances du tableau de bord (push) Successful in 14s
Intégration / Images épinglées par version (push) Successful in 3s
Intégration / Tableau de bord (push) Successful in 19s
Intégration / Aucun secret commité (push) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (push) Successful in 1m22s
Infra Ansible / Playbooks Ansible valides (push) Successful in 1m21s
Intégration / Qualité du code Python (pull_request) Successful in 50s
Intégration / Tests unitaires et couverture (pull_request) Successful in 56s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 11s
Intégration / Images épinglées par version (pull_request) Successful in 4s
Intégration / Tableau de bord (pull_request) Successful in 20s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m27s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 2m30s
e3282bbb43
Reviewed-on: https://10.105.200.41/g2/enervision/pulls/115
Reviewed-by: lenaic <lenaic@noreply.10.105.200.41>
lenaic requested review from gabriel 2026-09-03 15:02:58 +00:00
gabriel approved these changes 2026-09-03 15:04:12 +00:00
lenaic merged commit 4a78583f85 into main 2026-09-03 15:04:47 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
g2/enervision!129
No description provided.