fix(ci,docs): lève les points de revue du SAST et de la vue CI/CD
Backend / Tests exigeant une base (push) Failing after 34s
Backend / Lint, typage et tests (push) Successful in 1m43s
Backend / Analyse statique de sécurité (push) Successful in 7s
Backend / Audit des dépendances (push) Successful in 57s
ML / Analyse statique de sécurité (push) Successful in 7s
ML / Lint, typage et tests (push) Successful in 3m0s

ML-START.md affirmait que l'orchestration Airflow n'existait pas : `ml_score`
tourne en `@hourly` depuis l'issue #115, seuls le mode `--csv` et un lancement
local restent manuels.

50-cicd.md : Dependabot compte six entrées sur cinq écosystèmes et non cinq
entrées, le filtre d'`airflow.yml` couvre aussi `apps/backend/` depuis le DAG
`alertes`, et les issues #21 (job de déploiement) et #22 (secrets) sont
distinguées au lieu d'être citées l'une pour l'autre. Le `continue-on-error` du
second passage Bandit est nommé pour ce qu'il est : le job reste vert même avec
un constat LOW.

Bandit est épinglé à 1.9.4 dans les deux jobs `sast` : sans épingle, une
nouvelle version passe la CI au rouge sans qu'une ligne du dépôt ait changé, et
le rejeu à l'identique documenté n'existe pas. Le `cache-dependency-glob` part :
`uvx` n'installe pas le projet, le verrou n'alimentait aucune clé de cache.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Johan LEROY
2026-09-21 13:41:17 +02:00
co-authored by Claude Opus 5
parent 31886944ab
commit 62d81e901d
4 changed files with 39 additions and 26 deletions
+4 -5
View File
@@ -152,18 +152,17 @@ jobs:
- name: Récupère le dépôt - name: Récupère le dépôt
uses: actions/checkout@v4 uses: actions/checkout@v4
# Pourquoi : pas de cache ici. uvx n'installe pas le projet, le verrou n'alimente donc
# aucune clé de cache ; la seule roue téléchargée est celle de Bandit.
- name: Installe uv - name: Installe uv
uses: astral-sh/setup-uv@v5 uses: astral-sh/setup-uv@v5
with:
enable-cache: true
cache-dependency-glob: apps/backend/uv.lock
# Pourquoi : le périmètre est `app`, le code livré. Les tests emploient légitimement des # Pourquoi : le périmètre est `app`, le code livré. Les tests emploient légitimement des
# secrets factices et des `assert` que Bandit signalerait sans qu'aucun n'atteigne la prod. # secrets factices et des `assert` que Bandit signalerait sans qu'aucun n'atteigne la prod.
- name: Analyse le code livré (bloquant à partir de MEDIUM) - name: Analyse le code livré (bloquant à partir de MEDIUM)
run: uvx bandit --recursive app --severity-level medium --confidence-level medium run: uvx bandit==1.9.4 --recursive app --severity-level medium --confidence-level medium
# Piège : sans cette seconde passe, un constat LOW disparaîtrait du journal sans trace. # Piège : sans cette seconde passe, un constat LOW disparaîtrait du journal sans trace.
- name: Rapport complet, tous niveaux - name: Rapport complet, tous niveaux
continue-on-error: true continue-on-error: true
run: uvx bandit --recursive app run: uvx bandit==1.9.4 --recursive app
+4 -5
View File
@@ -69,15 +69,14 @@ jobs:
- name: Récupère le dépôt - name: Récupère le dépôt
uses: actions/checkout@v4 uses: actions/checkout@v4
# Pourquoi : pas de cache ici. uvx n'installe pas le projet, le verrou n'alimente donc
# aucune clé de cache ; la seule roue téléchargée est celle de Bandit.
- name: Installe uv - name: Installe uv
uses: astral-sh/setup-uv@v5 uses: astral-sh/setup-uv@v5
with:
enable-cache: true
cache-dependency-glob: ml/uv.lock
- name: Analyse le code livré (bloquant à partir de MEDIUM) - name: Analyse le code livré (bloquant à partir de MEDIUM)
run: uvx bandit --recursive enervision_ml --severity-level medium --confidence-level medium run: uvx bandit==1.9.4 --recursive enervision_ml --severity-level medium --confidence-level medium
- name: Rapport complet, tous niveaux - name: Rapport complet, tous niveaux
continue-on-error: true continue-on-error: true
run: uvx bandit --recursive enervision_ml run: uvx bandit==1.9.4 --recursive enervision_ml
+4 -3
View File
@@ -161,9 +161,10 @@ flowchart LR
`prediction` et leurs contraintes de cohérence, vérifiables en SQL. `prediction` et leurs contraintes de cohérence, vérifiables en SQL.
Le corollaire est qu'il n'y a **aucune prévision à la demande** : la fraîcheur d'une prévision est Le corollaire est qu'il n'y a **aucune prévision à la demande** : la fraîcheur d'une prévision est
celle du dernier run de scoring. Tant que l'orchestration Airflow n'existe pas (`etl/airflow/` est celle du dernier run de scoring. Ce run est ordonnancé par Airflow, DAG `ml_score` en `@hourly`
vide), ce run est lancé à la main. C'est la dette la plus visible du module, et elle est portée (issue #115) ; seuls le mode `--csv` et un lancement local restent manuels, tout comme
par les issues #44 et #45. l'entraînement, dont le DAG `ml_train` n'a pas de planification. La dette qui subsiste est la
surveillance de dérive, portée par les issues #44 et #45.
--- ---
+27 -13
View File
@@ -60,12 +60,17 @@ flowchart TB
Les cinq workflows se déclenchent sur `push` **et** sur `pull_request`, filtrés par **chemin** : Les cinq workflows se déclenchent sur `push` **et** sur `pull_request`, filtrés par **chemin** :
`backend.yml` sur `apps/backend/**`, `frontend.yml` sur `apps/frontend/**`, `ml.yml` sur `ml/**`, `backend.yml` sur `apps/backend/**`, `frontend.yml` sur `apps/frontend/**`, `ml.yml` sur `ml/**`,
`airflow.yml` sur `etl/airflow/**` **et sur `ml/**`**, chacun incluant son propre fichier de `airflow.yml` sur `etl/airflow/**` **plus des chemins de `ml/` et de `apps/backend/`**, chacun
workflow dans le filtre pour qu'une modification du pipeline déclenche le pipeline. incluant son propre fichier de workflow dans le filtre pour qu'une modification du pipeline
déclenche le pipeline.
Le filtre d'`airflow.yml` mérite un mot : il inclut `ml/pyproject.toml`, `ml/uv.lock` et Le filtre d'`airflow.yml` mérite un mot : il inclut `ml/pyproject.toml`, `ml/uv.lock`,
`ml/enervision_ml/**` parce que l'image Airflow copie le code et les dépendances du module ML. `ml/enervision_ml/**`, `apps/backend/pyproject.toml`, `apps/backend/uv.lock` et
Une modification de `ml/` peut donc casser la construction de cette image, et le filtre le voit. `apps/backend/app/**` parce que l'image Airflow copie le code et les dépendances des deux
modules : celles du ML pour `ml_train`/`ml_score`, celles du backend depuis que le DAG `alertes`
y exécute les commandes de détection ([ADR 0008](../adr/0008-airflow-execute-le-code-du-backend.md)).
Une modification de l'un ou l'autre peut donc casser la construction de cette image, et le filtre
le voit.
**Piège à connaître** : il n'y a **aucun filtre de branche**. Une branche de travail déclenche la **Piège à connaître** : il n'y a **aucun filtre de branche**. Une branche de travail déclenche la
CI complète à chaque push, et un merge vers n'importe quelle branche la déclenche aussi. C'est CI complète à chaque push, et un merge vers n'importe quelle branche la déclenche aussi. C'est
@@ -102,9 +107,14 @@ Deux seuils portent une décision qu'il faut savoir défendre :
dépendance de développement ne doit pas immobiliser une livraison. Le corollaire est que les dépendance de développement ne doit pas immobiliser une livraison. Le corollaire est que les
`moderate` sont invisibles en CI, et qu'elles se regardent à la main. `moderate` sont invisibles en CI, et qu'elles se regardent à la main.
- **Bandit bloque à partir de MEDIUM**, et une seconde passe sans seuil publie les constats LOW - **Bandit bloque à partir de MEDIUM**, et une seconde passe sans seuil publie les constats LOW
sans bloquer. Sans cette seconde passe, un constat LOW disparaîtrait du journal sans trace. Au sans bloquer. Sans cette seconde passe, un constat LOW disparaîtrait du journal sans trace. Le
revers à connaître : cette seconde étape porte `continue-on-error`, donc le job reste **vert**
même quand elle relève quelque chose ; un LOW ne se voit qu'en ouvrant le journal. Au
21/09/2026, les deux modules sont à **zéro constat, tous niveaux confondus**, sur 5 904 lignes 21/09/2026, les deux modules sont à **zéro constat, tous niveaux confondus**, sur 5 904 lignes
analysées. analysées.
- **La version de Bandit est épinglée** (`uvx bandit==1.9.4`) dans les deux jobs. Sans épingle,
une nouvelle version passerait la CI au rouge sans qu'une seule ligne du dépôt ait changé, et
le rejeu à l'identique promis plus bas n'existerait pas.
## Le job d'intégration, et pourquoi il ne suffisait pas d'un `postgres` ## Le job d'intégration, et pourquoi il ne suffisait pas d'un `postgres`
@@ -142,10 +152,11 @@ contournée** en désactivant la gate ou en excluant les fichiers gênants.
## Dependabot ## Dependabot
`.github/dependabot.yml` déclare **cinq entrées hebdomadaires groupées** : `npm` sur `.github/dependabot.yml` déclare **six entrées hebdomadaires groupées, sur cinq écosystèmes** :
`/apps/frontend`, `uv` sur `/apps/backend`, `github-actions` sur `/`, et `docker` sur les deux `npm` sur `/apps/frontend`, `uv` sur `/apps/backend`, `github-actions` sur `/`, `docker` sur les
dossiers d'application. Les mises à jour arrivent en PR, donc elles traversent les mêmes gates que deux dossiers d'application, et `docker-compose` sur `/`. Les mises à jour arrivent en PR, donc
n'importe quel changement : une montée de version qui casse les tests ne se merge pas. elles traversent les mêmes gates que n'importe quel changement : une montée de version qui casse
les tests ne se merge pas.
## Stratégie de branche et conventions ## Stratégie de branche et conventions
@@ -163,7 +174,9 @@ n'importe quel changement : une montée de version qui casse les tests ne se mer
Un seul secret est consommé par la CI : **`SONAR_TOKEN`**, porté par les dépôts GitHub Actions. Un seul secret est consommé par la CI : **`SONAR_TOKEN`**, porté par les dépôts GitHub Actions.
Les identifiants de la base du job d'intégration sont des valeurs de test en clair dans le Les identifiants de la base du job d'intégration sont des valeurs de test en clair dans le
workflow, ce qui est volontaire : elles ne protègent rien, la base est créée et détruite avec le workflow, ce qui est volontaire : elles ne protègent rien, la base est créée et détruite avec le
run. Aucune clé de déploiement n'existe encore, puisqu'il n'y a pas de déploiement (issue #22). run. Aucune clé de déploiement n'existe encore, puisqu'il n'y a pas de déploiement : le job de
déploiement est porté par l'issue #21, les secrets qu'il consommera et leur injection par
l'issue #22.
## Ce qui manque, et pourquoi ## Ce qui manque, et pourquoi
@@ -181,5 +194,6 @@ run. Aucune clé de déploiement n'existe encore, puisqu'il n'y a pas de déploi
`verification`. `make ml-check` fait la même chose pour le module ML. Les tests d'intégration `verification`. `make ml-check` fait la même chose pour le module ML. Les tests d'intégration
demandent une base : `make db-up` puis `uv run pytest -m integration`. demandent une base : `make db-up` puis `uv run pytest -m integration`.
Le SAST se rejoue à l'identique : `uvx bandit --recursive app --severity-level medium Le SAST se rejoue à l'identique : `uvx bandit==1.9.4 --recursive app --severity-level medium
--confidence-level medium` depuis `apps/backend`. --confidence-level medium` depuis `apps/backend`, et la même commande sur `enervision_ml` depuis
`ml`.