infra : la zone or tourne en cron, et la documentation dit vrai #159

Merged
florian merged 4 commits from lenaic/136-zone-or-en-cron into develop 2026-09-07 12:07:49 +00:00
Owner

infra : la zone or tourne en cron

La zone or en base est ce que lisent l'API, Grafana métier, les règles de
recommandation et le futur modèle. Elle est modélisée, testée et documentée — et
elle ne tourne pas. Vérifié sur le serveur le 06/09 : public.mesure = 0,
qualite_jour = 0, prevision = 0.

Ce que ça change

taches_planifiees porte deux entrées de plus, chaînées derrière la passe argent :

Minute Lanceur Journal
0 silver-daily.sh silver.log
17 gold-daily.sh gold.log
27 load-postgres.sh gold.log

Même journal pour les deux nouvelles : c'est un seul geste, « la journée passe
en base », et lire deux fichiers pour savoir où il s'est arrêté est exactement ce
qu'on ne veut pas faire en panne.

Le piège, et pourquoi la variable est dans la même PR

La ligne de cron seule n'aurait rien changé. load-postgres.sh lit
ENERVISION_ETL_DATABASE_URL dans /etc/enervision/postgres.env ; le gabarit ne
rendait que les PG_*_PASSWORD. Vérifié sur le serveur : la variable est
absente.
Le job serait sorti en code 2 à chaque passe — la zone or grossit
dans MinIO, gold-daily réussit à la minute 17, et la base reste vide. Une passe
sur deux réussit, ce qui rend le cas invisible dans un journal que personne ne lit.

Le manuel donne la signature exacte à chercher dans gold.log.

Hygiène documentaire portée par cette PR

Ces corrections vont dans les PR concernées, jamais en PR à part : une PR
« virgules » est du bruit que personne ne relit.

  • README.md : services/etl/ y était listé deux fois ; tests/ annonçait
    du bout en bout que tests/e2e/ ne contient pas.
  • docs/api/decisions.md : « ADR à écrire » pour le jeton, alors que l'ADR 0002
    existe depuis le 02/09.
  • tests/unit/README.md et tests/e2e/README.md disent ce qu'ils contiennent.
  • .mailmap : git shortlog -sne est la preuve de croisement du §9 des
    exigences collectives, et elle comptait quinze auteurs pour six personnes.
    Elle en compte huit. Aucune adresse n'est inventée ; les deux identités
    restantes sont laissées à part et expliquées dans le fichier. Gabriel, si
    Gabouil est bien toi, la ligne à ajouter est écrite en commentaire.

Preuve

  • ansible-playbook site.yml --syntax-check : sans erreur
  • yamllint infra/ansible : propre
  • ansible-playbook tests/ci/test-role-app.yml : 10 contrôles, 0 échec
  • tests/ci/test-liens-markdown.sh : 149 liens, aucun cassé
  • --check --diff --limit prod : non joué, le coffre est chiffré

⚠️ À savoir pour le déploiement

Le serveur est à eb12df2 et a 52 commits de retard. gold-daily.sh est
arrivé au commit dcc76db, soit deux heures après la version déployée : le
script n'existe pas sur la machine. Ce déploiement doit donc porter develop
et cette PR ensemble
— sinon le cron pointe vers un script absent.

Exigences : EF-12, ENF-04. Suite des #136 et #44.

# infra : la zone or tourne en cron La zone or en base est ce que lisent l'API, Grafana métier, les règles de recommandation et le futur modèle. Elle est modélisée, testée et documentée — et **elle ne tourne pas**. Vérifié sur le serveur le 06/09 : `public.mesure = 0`, `qualite_jour = 0`, `prevision = 0`. ## Ce que ça change `taches_planifiees` porte deux entrées de plus, chaînées derrière la passe argent : | Minute | Lanceur | Journal | |---|---|---| | 0 | `silver-daily.sh` | `silver.log` | | **17** | **`gold-daily.sh`** | **`gold.log`** | | **27** | **`load-postgres.sh`** | **`gold.log`** | Même journal pour les deux nouvelles : c'est un seul geste, « la journée passe en base », et lire deux fichiers pour savoir où il s'est arrêté est exactement ce qu'on ne veut pas faire en panne. ## Le piège, et pourquoi la variable est dans la même PR **La ligne de cron seule n'aurait rien changé.** `load-postgres.sh` lit `ENERVISION_ETL_DATABASE_URL` dans `/etc/enervision/postgres.env` ; le gabarit ne rendait que les `PG_*_PASSWORD`. **Vérifié sur le serveur : la variable est absente.** Le job serait sorti en **code 2 à chaque passe** — la zone or grossit dans MinIO, `gold-daily` réussit à la minute 17, et la base reste vide. Une passe sur deux réussit, ce qui rend le cas invisible dans un journal que personne ne lit. Le manuel donne la signature exacte à chercher dans `gold.log`. ## Hygiène documentaire portée par cette PR Ces corrections vont dans les PR concernées, jamais en PR à part : une PR « virgules » est du bruit que personne ne relit. - `README.md` : `services/etl/` y était listé **deux fois** ; `tests/` annonçait du bout en bout que `tests/e2e/` ne contient pas. - `docs/api/decisions.md` : « ADR à écrire » pour le jeton, alors que l'ADR 0002 existe depuis le 02/09. - `tests/unit/README.md` et `tests/e2e/README.md` disent ce qu'ils contiennent. - **`.mailmap`** : `git shortlog -sne` est la preuve de croisement du §9 des exigences collectives, et elle comptait **quinze auteurs pour six personnes**. Elle en compte huit. Aucune adresse n'est inventée ; les deux identités restantes sont laissées à part et expliquées dans le fichier. **Gabriel, si `Gabouil` est bien toi, la ligne à ajouter est écrite en commentaire.** ## Preuve - `ansible-playbook site.yml --syntax-check` : sans erreur - `yamllint infra/ansible` : propre - `ansible-playbook tests/ci/test-role-app.yml` : 10 contrôles, 0 échec - `tests/ci/test-liens-markdown.sh` : 149 liens, aucun cassé - `--check --diff --limit prod` : **non joué**, le coffre est chiffré ## ⚠️ À savoir pour le déploiement Le serveur est à `eb12df2` et a **52 commits de retard**. `gold-daily.sh` est arrivé au commit `dcc76db`, soit **deux heures après la version déployée** : le script n'existe pas sur la machine. Ce déploiement doit donc porter **`develop` et cette PR ensemble** — sinon le cron pointe vers un script absent. Exigences : EF-12, ENF-04. Suite des #136 et #44.
`taches_planifiees` ne portait que la relève, les alertes et la zone argent.
`gold-daily.sh` et `load-postgres.sh` sont écrits, testés et documentés depuis
le #124, mais rien ne les lançait : `public.mesure` et `public.qualite_jour` ne
se remplissaient que par un geste manuel. Or c'est cette zone or en base que
lisent l'API, Grafana métier, les règles de recommandation et le futur modèle.

Deux entrées, minutes 17 et 27, chaînées derrière la passe de la zone argent de
la minute 0 (§13 de docs/data/etl-pipeline.md). Les deux journalisent dans le
MÊME `gold.log` : elles forment un seul geste, « la journée passe en base », et
ouvrir deux fichiers pour savoir où il s'est arrêté est exactement ce qu'on ne
veut pas faire en panne. La rotation est déjà couverte, la tâche logrotate du
rôle porte sur *.log.

La ligne de cron seule n'aurait rien changé, et c'est le piège de ce lot.
`load-postgres.sh` source /etc/enervision/postgres.env pour y lire
ENERVISION_ETL_DATABASE_URL (l.22, l.35) ; le gabarit ne rendait que les cinq
PG_*_PASSWORD. Le job serait sorti en code 2 à chaque passe
(gold/chargement.py l.148-151, l.269-272) : une ligne par heure dans un journal
que personne ne lit, la zone or qui grossit dans MinIO, et la base vide. Une
passe sur deux réussit, ce qui rend le cas invisible.

Le gabarit rend donc la variable, et porte en commentaire la contrainte qu'elle
impose au mot de passe du coffre : il entre tel quel dans une URL, sans
encodage.

Éprouvé : `ansible-playbook site.yml --syntax-check`, `yamllint infra/ansible`,
`ansible-playbook tests/ci/test-role-app.yml` (10 contrôles, aucun échec), et le
gabarit rendu se source sans bruit en /bin/sh.

Suite du #136.
docs: étends le manuel de l'ETL à la zone or et rends la documentation exacte
All checks were successful
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 19s
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 4s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m4s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 3m27s
08c2d4bfaa
Le manuel ne couvrait qu'un seul programme (« un seul programme,
`etl.silver.job` »), alors que #44 promet un manuel par geste sensible. Il
couvre maintenant les trois passes du médaillon et les deux contrôles à la
demande, en gardant tout ce qu'il disait déjà de la zone argent.

Ce qu'il ajoute, dans l'ordre où on en a besoin en panne : la preuve par étage
(MinIO argent, MinIO or, `max(horodatage)` en base) ; les codes de sortie des
deux étages, relevés dans les scripts et non recopiés d'une documentation ;
comment rejouer une journée dans le bon ordre ; `--forcer` et ce qu'il faut
regarder AVANT de forcer ; le rafraîchissement de l'agrégat continu, que rien ne
documentait alors que sa politique ne revient que sur trois jours ; le lignage
et l'export, qui rendent un verdict en code 5 et non une panne.

La section qui compte est « Le piège de cette chaîne ». Deux étages sortent sous
les mêmes numéros : le lanceur shell écrit une ligne nue préfixée de son nom, le
programme Python une ligne horodatée avec son module. Le manuel donne la
signature exacte de la variable absente dans gold.log, et dit pourquoi le
journal a l'air sain — la ligne « agrégé » de la minute 17 est juste au-dessus.

Corrections portées par cette PR plutôt que par une PR « virgules » que personne
ne relirait :

- README racine : `services/etl/` y était listé deux fois avec deux
  descriptions ; `tests/` annonçait du bout en bout que `tests/e2e/` ne contient
  pas.
- docs/api/decisions.md : « ADR à écrire » pour le jeton, alors que l'ADR 0002
  existe depuis le 02/09. Remplacé par le renvoi à la fiche.
- tests/unit/README.md et tests/e2e/README.md n'étaient plus « à compléter » :
  ils disent ce qu'ils contiennent, et e2e dit qu'il est vide, pourquoi, et à
  qui appartient le sujet (#47) — plutôt que de laisser croire à un oubli.
- services/etl/README.md annonçait les deux lignes de cron « à poser ». Elles le
  sont.

.mailmap : `git shortlog -sne` est la preuve de croisement exigée par
EXIGENCES-collectives.md §9, et elle comptait quinze auteurs pour six personnes.
Elle en compte huit. Les identités sont relevées, jamais devinées : les deux qui
restent à part — un commit sous `Gabouil <gabriel.goldbronn@asphalte.com>` et le
compte partagé `g2-admin` — sont expliquées dans le fichier avec la ligne à
ajouter si Gabriel confirme.

Suite du #44.
lenaic 2026-09-07 07:06:37 +00:00
Member
  1. Rien ne garde la variable qui manquait — moyen

C'est le retour principal. Toute la PR existe parce que ENERVISION_ETL_DATABASE_URL était absente sans que rien ne le dise. Or tests/ci/test-role-app.yml n'assertionne que le texte source des tâches du rôle (bloc_cron, bloc_stat…) et ne regarde aucun gabarit. La variable peut donc redisparaître d'un postgres.env.j2 sans qu'un seul contrôle bronche, et on retombe exactement dans le mode de panne que le manuel décrit si bien.

Le fichier de test lit déjà du source à la main, l'ajout est de trois lignes : charger roles/app/templates/postgres.env.j2 et asserter qu'il porte ENERVISION_ETL_DATABASE_URL=. Idem pour les deux entrées de taches_planifiees si tu veux fermer complètement.

  1. Rien ne sérialise les minutes 17 et 27 — moyen/faible

agreger_un_jour écrit deux partitions successives et non atomiques : ecrire_partition(... TABLE_MESURE) puis qualite.ecrire_qualite_jour (agregation.py:184-191). Si gold-daily dépasse dix minutes — ou si un rejeu manuel tourne à 26 —, load-postgres lit une partition à moitié réécrite : mesure neuve, qualite_jour ancienne, et l'on conflict do update grave le mélange.

Le manuel voit le symptôme (« un écart entre les deux comptes veut dire qu'un rejeu manuel a tourné entre-temps ») mais ne retient que la cause manuelle. Deux issues : un flock sur un même verrou dans les deux lanceurs, ou une phrase dans vars.yml expliquant pourquoi dix minutes suffisent avec marge. Un chiffre de durée observée de gold-daily trancherait.

  1. Le mot de passe entre non encodé dans l'URL — faible, mais évitable maintenant

Le commentaire du gabarit décrit le piège avec précision, puis remet la correction au jour de la panne (« le jour où ce mot de passe doit en contenir un, encoder ici »). C'est le contraire de ce que la PR défend partout ailleurs. Attention si tu corriges : | urlencode de Jinja laisse / dans les caractères sûrs, il ne suffit pas seul.

L'idiome du dépôt existe déjà : la tâche « Refuser de déployer sans les secrets du coffre » (main.yml:12) est l'endroit pour une assertion « le mot de passe applicatif ne contient aucun de @:/?#% ou espace ». Panne au déploiement plutôt qu'en exploitation.

1. Rien ne garde la variable qui manquait — moyen C'est le retour principal. Toute la PR existe parce que ENERVISION_ETL_DATABASE_URL était absente sans que rien ne le dise. Or tests/ci/test-role-app.yml n'assertionne que le texte source des tâches du rôle (bloc_cron, bloc_stat…) et ne regarde aucun gabarit. La variable peut donc redisparaître d'un postgres.env.j2 sans qu'un seul contrôle bronche, et on retombe exactement dans le mode de panne que le manuel décrit si bien. Le fichier de test lit déjà du source à la main, l'ajout est de trois lignes : charger roles/app/templates/postgres.env.j2 et asserter qu'il porte ENERVISION_ETL_DATABASE_URL=. Idem pour les deux entrées de taches_planifiees si tu veux fermer complètement. 2. Rien ne sérialise les minutes 17 et 27 — moyen/faible agreger_un_jour écrit deux partitions successives et non atomiques : ecrire_partition(... TABLE_MESURE) puis qualite.ecrire_qualite_jour (agregation.py:184-191). Si gold-daily dépasse dix minutes — ou si un rejeu manuel tourne à 26 —, load-postgres lit une partition à moitié réécrite : mesure neuve, qualite_jour ancienne, et l'on conflict do update grave le mélange. Le manuel voit le symptôme (« un écart entre les deux comptes veut dire qu'un rejeu manuel a tourné entre-temps ») mais ne retient que la cause manuelle. Deux issues : un flock sur un même verrou dans les deux lanceurs, ou une phrase dans vars.yml expliquant pourquoi dix minutes suffisent avec marge. Un chiffre de durée observée de gold-daily trancherait. 3. Le mot de passe entre non encodé dans l'URL — faible, mais évitable maintenant Le commentaire du gabarit décrit le piège avec précision, puis remet la correction au jour de la panne (« le jour où ce mot de passe doit en contenir un, encoder ici »). C'est le contraire de ce que la PR défend partout ailleurs. Attention si tu corriges : | urlencode de Jinja laisse / dans les caractères sûrs, il ne suffit pas seul. L'idiome du dépôt existe déjà : la tâche « Refuser de déployer sans les secrets du coffre » (main.yml:12) est l'endroit pour une assertion « le mot de passe applicatif ne contient aucun de @:/?#% ou espace ». Panne au déploiement plutôt qu'en exploitation.
infra: les trois retours de Florian sur la #159, avec le banc qui les garde
All checks were successful
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 6s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 19s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 1m25s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 3m25s
fa69e14d53
1. Un verrou partagé entre les deux passes de la zone or.
   Elles étaient chaînées par dix minutes de crontab, et agreger_un_jour écrit
   deux partitions successives et NON atomiques. Une passe qui déborde, ou un
   rejeu manuel entre les deux, et le chargement lit une partition à moitié
   réécrite qu'il grave en base par son on conflict do update.
   flock -w 900 sur /var/log/enervision/.zone-or.lock, code 75 quand l'attente
   expire. 75 et pas 4 : les deux modules rendent déjà 2, 3 et 4, un code
   partagé rendrait « verrou non obtenu » indiscernable de « partition absente »
   dans gold.log.
   Éprouvé en conditions réelles : deux lanceurs concurrents, le second sort en
   75 avec son message, le premier en 0.

2. Le mot de passe est encodé dans l'URL.
   Le gabarit décrivait le piège et remettait le correctif au jour de la panne.
   urlencode seul ne suffit pas, Jinja garde « / » dans les caractères sûrs :
   le replace derrière ferme ce trou.
   Pas l'assertion sur le jeu de caractères que proposait Florian : le coffre
   est chiffré, personne ne relit sa valeur avant de déployer, et une assertion
   sur un mot de passe qu'on ne peut pas inspecter casse le déploiement le jour
   où elle a tort. L'encodage est vrai quelle que soit la valeur.
   Aller-retour prouvé sur « p@ss/w:rd?#100%x ».

3. Le banc du rôle app lit enfin les gabarits.
   Il ne lisait que tasks/main.yml : ENERVISION_ETL_DATABASE_URL pouvait
   disparaître de postgres.env.j2 sans qu'un contrôle bronche. Trois contrôles
   ajoutés, sur le gabarit, sur les tâches planifiées lues en structure, et sur
   le verrou partagé.

Le banc est éprouvé dans les deux sens : vert sur l'arbre sain, ROUGE sur les
cinq défauts réintroduits un par un, et chaque fois sur le contrôle visé. Le
contrôle du verrou est d'ailleurs passé au vert à ma première rédaction, parce
qu'il cherchait le mot « flock », qui survit dans les commentaires. Il lit
maintenant les lignes exécutées.
Author
Owner

Les trois sont traités dans fa69e14, et j'ai suivi ta rédaction sur deux, pas
sur la troisième.

1. Le verrou, et pas une phrase sur les dix minutes

Tu avais raison sur le mode de panne, et j'ai pris ton premier remède. Un chiffre
de durée vieillira avec le volume, le verrou non, et je ne peux même pas le
relever aujourd'hui puisque la zone or n'est pas encore sur le serveur.

VERROU="${ENERVISION_ETL_VERROU:-/var/log/enervision/.zone-or.lock}"
ATTENTE="${ENERVISION_ETL_VERROU_ATTENTE:-900}"
flock -w "$ATTENTE" -E 75 "$VERROU" "$PYTHON" -m etl.gold.agregation "$@"

75 et pas 4, et c'est le détail qui m'a arrêté : agregation.py et
chargement.py rendent déjà 2, 3 et 4 pour leurs propres pannes. Un code
partagé rendrait « verrou non obtenu » indiscernable de « partition absente »
dans gold.log, c'est-à-dire exactement le contraire de ce que tu demandes.
75 est l'EX_TEMPFAIL de sysexits, dont le sens est « réessaie plus tard ».

L'attente est bornée à un quart de la cadence : au delà, mieux vaut que la passe
de l'heure suivante rattrape que d'empiler des processus.

Éprouvé en conditions réelles, deux lanceurs concurrents :

load-postgres: verrou /…/.zone-or.lock non obtenu en 1s, passe abandonnee
load-postgres: l'autre passe de la zone or tourne encore, la suivante rattrapera
code du second = 75
code du premier = 0

2. L'encodage, mais pas l'assertion que tu proposes

Tu as raison sur le fond, et sur le piège : urlencode garde / dans les
caractères sûrs. Le replace derrière ferme ce trou, et l'ordre compte,
l'inverse encoderait le % de %2F.

{{ vault_pg_enervision_prod_password | urlencode | replace('/', '%2F') }}

Je n'ai pas pris l'assertion dans « Refuser de déployer sans les secrets du
coffre », et c'est un désaccord assumé.
Le coffre est chiffré, personne ne
relit la valeur de ce mot de passe avant de déployer, moi le premier. Une
assertion sur une donnée qu'on ne peut pas inspecter casse le déploiement entier
le jour où elle a tort, et le rôle app a déjà vécu ça deux fois cette semaine.
L'encodage, lui, est vrai quelle que soit la valeur : il n'y a plus de condition
à vérifier.

Aller-retour prouvé sur un mot de passe hostile :

mdp d'entrée   p@ss/w:rd?#100%x
mdp relu       'p@ss/w:rd?#100%x'
hôte 127.0.0.1  port 5433  base /enervision_prod  user enervision_prod

Reste le .-sourçage par /bin/sh, qu'un espace ou un $ casserait toujours,
sur les cinq mots de passe. Hors périmètre ici : ces fichiers sont aussi lus en
env_file par Docker, et changer leur guillemetage se paierait ailleurs.

3. Le banc lit enfin les gabarits

Ton retour principal était le bon. Trois contrôles ajoutés, et j'ai fermé
complètement comme tu le suggérais :

  • le gabarit rend ENERVISION_ETL_DATABASE_URL, en postgresql://, encodée ;
  • les tâches planifiées, lues en structure et pas en texte, portent leur
    service et leur script, et les deux passes de la zone or sont bien sous
    etl ;
  • les deux lanceurs prennent le même verrou.

Pas de compte total des tâches : il changerait à chaque job ajouté, et un banc
qui rougit pour une raison légitime finit par être désarmé.

Ce que le banc vaut, éprouvé dans les deux sens

Vert sur l'arbre sain, et rouge sur les cinq défauts réintroduits un par un,
chaque fois sur le contrôle visé :

l'URL disparaît du gabarit      rouge sur 'ENERVISION_ETL_DATABASE_URL=' in gabarit_pg
l'encodage disparaît            rouge sur 'urlencode' in gabarit_pg
gold-daily perd son service     rouge sur selectattr('script','equalto','gold-daily.sh')…
load-postgres perd son verrou   rouge sur code_load | select('match', '\s*flock -w')
les deux verrous divergent      rouge sur verrou_gold == verrou_load

Le quatrième est passé au vert à ma première rédaction. Il cherchait le mot
flock, qui survit dans le commentaire au-dessus et dans le command -v flock.
Il lit maintenant les lignes exécutées, commentaires retirés. C'est la troisième
fois que ce dépôt me prend sur ce point, le contrôle porte la remarque en clair.

Vérifications

tests/ci/test-role-app.yml     13 ok, 0 failed
ansible-lint (infra/ansible)   profil production, 49 fichiers
yamllint (infra/ansible)       propre
--syntax-check                 bootstrap, site, restore
sh -n                          les deux lanceurs
pytest tests/unit              383 passés hors tests/unit/api

tests/unit/api ne tourne pas sur mon poste, il manque email-validator, qui
est bien déclaré dans services/api/enervision_api/pyproject.toml. Rien de ce
que je touche n'est de ce côté.

Les trois sont traités dans `fa69e14`, et j'ai suivi ta rédaction sur deux, pas sur la troisième. ## 1. Le verrou, et pas une phrase sur les dix minutes Tu avais raison sur le mode de panne, et j'ai pris ton premier remède. Un chiffre de durée vieillira avec le volume, le verrou non, et je ne peux même pas le relever aujourd'hui puisque la zone or n'est pas encore sur le serveur. ```sh VERROU="${ENERVISION_ETL_VERROU:-/var/log/enervision/.zone-or.lock}" ATTENTE="${ENERVISION_ETL_VERROU_ATTENTE:-900}" flock -w "$ATTENTE" -E 75 "$VERROU" "$PYTHON" -m etl.gold.agregation "$@" ``` **75 et pas 4**, et c'est le détail qui m'a arrêté : `agregation.py` et `chargement.py` rendent **déjà** 2, 3 et 4 pour leurs propres pannes. Un code partagé rendrait « verrou non obtenu » indiscernable de « partition absente » dans `gold.log`, c'est-à-dire exactement le contraire de ce que tu demandes. 75 est l'`EX_TEMPFAIL` de `sysexits`, dont le sens est « réessaie plus tard ». L'attente est bornée à un quart de la cadence : au delà, mieux vaut que la passe de l'heure suivante rattrape que d'empiler des processus. Éprouvé en conditions réelles, deux lanceurs concurrents : ``` load-postgres: verrou /…/.zone-or.lock non obtenu en 1s, passe abandonnee load-postgres: l'autre passe de la zone or tourne encore, la suivante rattrapera code du second = 75 code du premier = 0 ``` ## 2. L'encodage, mais pas l'assertion que tu proposes Tu as raison sur le fond, et sur le piège : `urlencode` garde `/` dans les caractères sûrs. Le `replace` derrière ferme ce trou, et l'ordre compte, l'inverse encoderait le `%` de `%2F`. ```jinja {{ vault_pg_enervision_prod_password | urlencode | replace('/', '%2F') }} ``` **Je n'ai pas pris l'assertion dans « Refuser de déployer sans les secrets du coffre », et c'est un désaccord assumé.** Le coffre est chiffré, personne ne relit la valeur de ce mot de passe avant de déployer, moi le premier. Une assertion sur une donnée qu'on ne peut pas inspecter casse le déploiement entier le jour où elle a tort, et le rôle `app` a déjà vécu ça deux fois cette semaine. L'encodage, lui, est vrai quelle que soit la valeur : il n'y a plus de condition à vérifier. Aller-retour prouvé sur un mot de passe hostile : ``` mdp d'entrée p@ss/w:rd?#100%x mdp relu 'p@ss/w:rd?#100%x' hôte 127.0.0.1 port 5433 base /enervision_prod user enervision_prod ``` Reste le `.`-sourçage par `/bin/sh`, qu'un espace ou un `$` casserait toujours, sur les cinq mots de passe. Hors périmètre ici : ces fichiers sont aussi lus en `env_file` par Docker, et changer leur guillemetage se paierait ailleurs. ## 3. Le banc lit enfin les gabarits Ton retour principal était le bon. Trois contrôles ajoutés, et j'ai fermé complètement comme tu le suggérais : - le gabarit rend `ENERVISION_ETL_DATABASE_URL`, en `postgresql://`, encodée ; - les tâches planifiées, lues **en structure** et pas en texte, portent leur `service` et leur `script`, et les deux passes de la zone or sont bien sous `etl` ; - les deux lanceurs prennent **le même** verrou. Pas de compte total des tâches : il changerait à chaque job ajouté, et un banc qui rougit pour une raison légitime finit par être désarmé. ## Ce que le banc vaut, éprouvé dans les deux sens Vert sur l'arbre sain, et **rouge sur les cinq défauts réintroduits un par un**, chaque fois sur le contrôle visé : ``` l'URL disparaît du gabarit rouge sur 'ENERVISION_ETL_DATABASE_URL=' in gabarit_pg l'encodage disparaît rouge sur 'urlencode' in gabarit_pg gold-daily perd son service rouge sur selectattr('script','equalto','gold-daily.sh')… load-postgres perd son verrou rouge sur code_load | select('match', '\s*flock -w') les deux verrous divergent rouge sur verrou_gold == verrou_load ``` **Le quatrième est passé au vert à ma première rédaction.** Il cherchait le mot `flock`, qui survit dans le commentaire au-dessus et dans le `command -v flock`. Il lit maintenant les lignes exécutées, commentaires retirés. C'est la troisième fois que ce dépôt me prend sur ce point, le contrôle porte la remarque en clair. ## Vérifications ``` tests/ci/test-role-app.yml 13 ok, 0 failed ansible-lint (infra/ansible) profil production, 49 fichiers yamllint (infra/ansible) propre --syntax-check bootstrap, site, restore sh -n les deux lanceurs pytest tests/unit 383 passés hors tests/unit/api ``` `tests/unit/api` ne tourne pas sur mon poste, il manque `email-validator`, qui est bien déclaré dans `services/api/enervision_api/pyproject.toml`. Rien de ce que je touche n'est de ce côté.
Member

LGTM

LGTM
Merge branch 'develop' into lenaic/136-zone-or-en-cron
All checks were successful
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 6s
Intégration / Aucun secret commité (pull_request) Successful in 4s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 34s
Infra Ansible / Playbooks Ansible valides (pull_request) Successful in 2m20s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 3m44s
e6e67f19c2
florian merged commit 66c14fbfb0 into develop 2026-09-07 12:07:49 +00:00
florian deleted branch lenaic/136-zone-or-en-cron 2026-09-07 12:07:49 +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!159
No description provided.