ci: fait de la chaîne une barrière (couverture, mypy, dépendances, images) #57

Merged
lenaic merged 13 commits from gabriel/40-ci-barriere into develop 2026-09-02 08:54:18 +00:00
Member

Ce que ça change

La chaîne d'intégration devient une barrière : mypy strict et l'audit des
dépendances applicatives sont réellement bloquants (ils ne l'étaient qu'en
apparence), les seuils de couverture sont armés à 70 % / 85 %, les images
non épinglées font échouer la chaîne, et elle se joue aussi sur develop.

Ref #40

Preuve

Chaque mécanisme a été rejoué hors chaîne, comparé avant/après sur le code
réel du dépôt :

  • mypy strict : passait à tort sans --config-file (config jamais chargée),
    bloque désormais sur une fonction non annotée.
  • Couverture : --cov=services ne mesurait que les fichiers importés — un
    test trivial affichait 100 % sur 8 lignes au lieu de 32. .coveragerc
    corrige la mesure ; la base réelle est à 27 %, les seuils sont posés à
    70 / 85 comme l'exige le ticket, donc le premier test unitaire écrit fera
    rougir la chaîne — c'est documenté dans le runbook, pas un bug.
  • Audit applicatif : reproduit rouge (idna==3.6, PYSEC-2024-60/2026-215)
    puis vert après retrait.
  • Images : script + 4 cas d'essai (tests/ci/test-verifier-images.sh),
    l'ancienne expression régulière rejetait FROM base AS runtime.

Il manque la preuve exigée par le ticket lui-même : une dépendance
vulnérable introduite dans cette PR, le journal d'exécution en échec
puis en succès après retrait, à joindre au ticket #40. Je le fais dans un
commit séparé une fois la PR ouverte, avant de demander la relecture.

(journaux d'exécution à joindre après le premier passage de la chaîne sur
cette PR, et après la preuve rouge/vert dédiée au ticket #40)

Relecture

  • Un pair a relu et laissé un commentaire, même court
  • Ses remarques sont traitées, ou une réponse explique pourquoi elles ne le sont pas

Ce qui suit le code

  • docs/runbooks/ci.md mis à jour

Où regarder en priorité

  • Les seuils de couverture (70 / 85) sont armés tels que le ticket les
    demande, alors que la base mesurée est à 27 % : le premier test unitaire
    écrit par l'équipe fera rougir la chaîne. C'est assumé et documenté
    (section dédiée dans docs/runbooks/ci.md), mais ça mérite un second
    avis avant de fusionner.
  • actions/upload-artifact@v3 n'est pas épinglé à un commit : sur le
    miroir d'actions de la forge, @v3 ne résout vers aucun tag de patch
    connu, je préfère ne pas pinner à l'aveugle un job qui publie les SBOM.
  • Cette PR ne suffit pas à rendre la chaîne bloquante : il faut aussi
    déclarer les cinq tâches en contrôles obligatoires dans la protection de
    branche de develop et main, avec leur nom affiché (table dans le
    runbook) — sans quoi tout ce qui précède reste décoratif.
## Ce que ça change La chaîne d'intégration devient une barrière : mypy strict et l'audit des dépendances applicatives sont réellement bloquants (ils ne l'étaient qu'en apparence), les seuils de couverture sont armés à 70 % / 85 %, les images non épinglées font échouer la chaîne, et elle se joue aussi sur `develop`. Ref #40 ## Preuve Chaque mécanisme a été rejoué hors chaîne, comparé avant/après sur le code réel du dépôt : - mypy strict : passait à tort sans `--config-file` (config jamais chargée), bloque désormais sur une fonction non annotée. - Couverture : `--cov=services` ne mesurait que les fichiers importés — un test trivial affichait 100 % sur 8 lignes au lieu de 32. `.coveragerc` corrige la mesure ; la base réelle est à 27 %, les seuils sont posés à 70 / 85 comme l'exige le ticket, donc le premier test unitaire écrit fera rougir la chaîne — c'est documenté dans le runbook, pas un bug. - Audit applicatif : reproduit rouge (`idna==3.6`, PYSEC-2024-60/2026-215) puis vert après retrait. - Images : script + 4 cas d'essai (`tests/ci/test-verifier-images.sh`), l'ancienne expression régulière rejetait `FROM base AS runtime`. Il manque la preuve exigée par le ticket lui-même : une dépendance vulnérable introduite dans **cette** PR, le journal d'exécution en échec puis en succès après retrait, à joindre au ticket #40. Je le fais dans un commit séparé une fois la PR ouverte, avant de demander la relecture. ``` (journaux d'exécution à joindre après le premier passage de la chaîne sur cette PR, et après la preuve rouge/vert dédiée au ticket #40) ``` ## Relecture - [ ] Un pair a relu et laissé un commentaire, même court - [ ] Ses remarques sont traitées, ou une réponse explique pourquoi elles ne le sont pas ## Ce qui suit le code - [x] `docs/runbooks/ci.md` mis à jour ## Où regarder en priorité - Les seuils de couverture (70 / 85) sont armés tels que le ticket les demande, alors que la base mesurée est à 27 % : le premier test unitaire écrit par l'équipe fera rougir la chaîne. C'est assumé et documenté (section dédiée dans `docs/runbooks/ci.md`), mais ça mérite un second avis avant de fusionner. - `actions/upload-artifact@v3` n'est pas épinglé à un commit : sur le miroir d'actions de la forge, `@v3` ne résout vers aucun tag de patch connu, je préfère ne pas pinner à l'aveugle un job qui publie les SBOM. - Cette PR ne suffit pas à rendre la chaîne bloquante : il faut aussi déclarer les cinq tâches en contrôles obligatoires dans la protection de branche de `develop` et `main`, avec leur nom **affiché** (table dans le runbook) — sans quoi tout ce qui précède reste décoratif.
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
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
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
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
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
gabriel 2026-09-02 07:10:36 +00:00
gabriel self-assigned this 2026-09-02 07:12:01 +00:00
lenaic left a comment

Relu en entier, scripts et cas d'essai compris. J'ai joué verifier-images.sh sur le compose de la #56 et sur celui de la forge : il accepte les deux, aucun faux positif.

Deux choix que je retiens. Les tâches qui tournent toujours pour que seules leurs étapes se sautent, sinon une tâche filtrée par on.paths resterait en attente et bloquerait un contrôle obligatoire pour toujours. Et les cas d'essai écrits pour le contrôle d'images avant de s'en servir, ce que personne ne fait spontanément.

Un point de fond et trois détails, posés sur les lignes concernées.

Relu en entier, scripts et cas d'essai compris. J'ai joué `verifier-images.sh` sur le compose de la #56 et sur celui de la forge : il accepte les deux, aucun faux positif. Deux choix que je retiens. Les tâches qui tournent toujours pour que seules leurs étapes se sautent, sinon une tâche filtrée par `on.paths` resterait en attente et bloquerait un contrôle obligatoire pour toujours. Et les cas d'essai écrits pour le contrôle d'images avant de s'en servir, ce que personne ne fait spontanément. Un point de fond et trois détails, posés sur les lignes concernées.
@ -14,0 +42,4 @@
#
# Ils ne coûtent rien tant qu'aucun test n'existe : la tâche se saute. Le jour
# où le premier test unitaire est écrit, la mesure porte sur tout le code de
# « services » et « packages », pas seulement sur ce que ce test importe — et
Owner

Les seuils de couverture relèvent de l'ENF-10, qualité logicielle, qui les cite mot pour mot. L'ENF-09 est la reproductibilité par Ansible.

Les seuils de couverture relèvent de l'ENF-10, qualité logicielle, qui les cite mot pour mot. L'ENF-09 est la reproductibilité par Ansible.
@ -60,0 +300,4 @@
python -m venv /tmp/venv-applicatif
/tmp/venv-applicatif/bin/pip install --quiet -r /tmp/deps-applicatif.txt
cyclonedx-py environment /tmp/venv-applicatif --of JSON -o sbom-applicatif.json
python -c "import json,sys; d=json.load(open('sbom-applicatif.json')); print(len(d['components']), 'composants applicatifs inventoriés')"
Owner

L'inventaire des composants relève de l'ENF-11, chaîne d'approvisionnement, avec les images épinglées et le scan bloquant. L'ENF-10 est la qualité logicielle.

L'inventaire des composants relève de l'ENF-11, chaîne d'approvisionnement, avec les images épinglées et le scan bloquant. L'ENF-10 est la qualité logicielle.
@ -0,0 +9,4 @@
dessous se lancent **à la racine du dépôt** et ont été exécutées telles quelles.
## Les cinq tâches
Owner

« Les cinq tâches » au-dessus d'un tableau qui en compte six.

« Les cinq tâches » au-dessus d'un tableau qui en compte six.
@ -0,0 +363,4 @@
- une approbation obligatoire, invalidée par toute nouvelle poussée ;
- interdiction de la poussée directe ;
- contrôles d'état obligatoires — la forge les désigne par le `name:` **affiché**
de la tâche, pas par sa clé dans le YAML :
Owner

Cette section décrit une protection par noms exacts, mais ce n'est pas notre configuration : develop et main exigent le motif Intégration / *. Tes six tâches sont donc déjà obligatoires sans que personne inscrive quoi que ce soit, et la procédure de renommage en deux temps décrite plus bas n'a pas lieu d'être. Le tableau des six noms peut rester à titre indicatif, mais il faut dire que le motif les couvre tous.

Cette section décrit une protection par noms exacts, mais ce n'est pas notre configuration : `develop` et `main` exigent le motif `Intégration / *`. Tes six tâches sont donc déjà obligatoires sans que personne inscrive quoi que ce soit, et la procédure de renommage en deux temps décrite plus bas n'a pas lieu d'être. Le tableau des six noms peut rester à titre indicatif, mais il faut dire que le motif les couvre tous.
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
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.
lenaic approved these changes 2026-09-02 08:49:28 +00:00
lenaic left a comment

Relu en entier, scripts et cas d'essai compris. Les images sont épinglées par empreinte, le contrôle qui le vérifie a ses propres cas d'essai, et les tâches tournent toujours pour que seules leurs étapes se sautent, ce qui évite qu'un contrôle obligatoire reste en attente pour toujours. Les six contrôles sont verts. Le conflit sur l'index des manuels venait de ma demande #60, je l'ai résolu en gardant les deux lignes.

Relu en entier, scripts et cas d'essai compris. Les images sont épinglées par empreinte, le contrôle qui le vérifie a ses propres cas d'essai, et les tâches tournent toujours pour que seules leurs étapes se sautent, ce qui évite qu'un contrôle obligatoire reste en attente pour toujours. Les six contrôles sont verts. Le conflit sur l'index des manuels venait de ma demande #60, je l'ai résolu en gardant les deux lignes.
lenaic approved these changes 2026-09-02 08:54:12 +00:00
lenaic left a comment

Relu en entier, scripts et cas d'essai compris. Les six contrôles sont verts. Le conflit sur l'index des manuels venait de ma demande #60, résolu en gardant les deux lignes.

Relu en entier, scripts et cas d'essai compris. Les six contrôles sont verts. Le conflit sur l'index des manuels venait de ma demande #60, résolu en gardant les deux lignes.
lenaic merged commit 8a2d057246 into develop 2026-09-02 08:54:18 +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!57
No description provided.