From 62d81e901da58bbca2d343474965fc41fc00f253 Mon Sep 17 00:00:00 2001 From: Johan LEROY Date: Mon, 21 Sep 2026 13:41:17 +0200 Subject: [PATCH] =?UTF-8?q?fix(ci,docs):=20l=C3=A8ve=20les=20points=20de?= =?UTF-8?q?=20revue=20du=20SAST=20et=20de=20la=20vue=20CI/CD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .github/workflows/backend.yml | 9 ++++---- .github/workflows/ml.yml | 9 ++++---- docs/ML-START.md | 7 +++--- docs/architecture/50-cicd.md | 40 +++++++++++++++++++++++------------ 4 files changed, 39 insertions(+), 26 deletions(-) diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml index f02eef8..34837cd 100644 --- a/.github/workflows/backend.yml +++ b/.github/workflows/backend.yml @@ -152,18 +152,17 @@ jobs: - name: Récupère le dépôt 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 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 # secrets factices et des `assert` que Bandit signalerait sans qu'aucun n'atteigne la prod. - 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. - name: Rapport complet, tous niveaux continue-on-error: true - run: uvx bandit --recursive app + run: uvx bandit==1.9.4 --recursive app diff --git a/.github/workflows/ml.yml b/.github/workflows/ml.yml index 4700a97..00189e2 100644 --- a/.github/workflows/ml.yml +++ b/.github/workflows/ml.yml @@ -69,15 +69,14 @@ jobs: - name: Récupère le dépôt 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 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) - 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 continue-on-error: true - run: uvx bandit --recursive enervision_ml + run: uvx bandit==1.9.4 --recursive enervision_ml diff --git a/docs/ML-START.md b/docs/ML-START.md index e4d9f93..68518ac 100644 --- a/docs/ML-START.md +++ b/docs/ML-START.md @@ -161,9 +161,10 @@ flowchart LR `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 -celle du dernier run de scoring. Tant que l'orchestration Airflow n'existe pas (`etl/airflow/` est -vide), ce run est lancé à la main. C'est la dette la plus visible du module, et elle est portée -par les issues #44 et #45. +celle du dernier run de scoring. Ce run est ordonnancé par Airflow, DAG `ml_score` en `@hourly` +(issue #115) ; seuls le mode `--csv` et un lancement local restent manuels, tout comme +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. --- diff --git a/docs/architecture/50-cicd.md b/docs/architecture/50-cicd.md index 5b307c3..79313a3 100644 --- a/docs/architecture/50-cicd.md +++ b/docs/architecture/50-cicd.md @@ -60,12 +60,17 @@ flowchart TB 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/**`, -`airflow.yml` sur `etl/airflow/**` **et sur `ml/**`**, chacun incluant son propre fichier de -workflow dans le filtre pour qu'une modification du pipeline déclenche le pipeline. +`airflow.yml` sur `etl/airflow/**` **plus des chemins de `ml/` et de `apps/backend/`**, chacun +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 -`ml/enervision_ml/**` parce que l'image Airflow copie le code et les dépendances du module ML. -Une modification de `ml/` peut donc casser la construction de cette image, et le filtre le voit. +Le filtre d'`airflow.yml` mérite un mot : il inclut `ml/pyproject.toml`, `ml/uv.lock`, +`ml/enervision_ml/**`, `apps/backend/pyproject.toml`, `apps/backend/uv.lock` et +`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 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 `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 - 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 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` @@ -142,10 +152,11 @@ contournée** en désactivant la gate ou en excluant les fichiers gênants. ## Dependabot -`.github/dependabot.yml` déclare **cinq entrées hebdomadaires groupées** : `npm` sur -`/apps/frontend`, `uv` sur `/apps/backend`, `github-actions` sur `/`, et `docker` sur les deux -dossiers d'application. Les mises à jour arrivent en PR, donc elles traversent les mêmes gates que -n'importe quel changement : une montée de version qui casse les tests ne se merge pas. +`.github/dependabot.yml` déclare **six entrées hebdomadaires groupées, sur cinq écosystèmes** : +`npm` sur `/apps/frontend`, `uv` sur `/apps/backend`, `github-actions` sur `/`, `docker` sur les +deux dossiers d'application, et `docker-compose` sur `/`. Les mises à jour arrivent en PR, donc +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 @@ -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. 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 -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 @@ -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 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 ---confidence-level medium` depuis `apps/backend`. +Le SAST se rejoue à l'identique : `uvx bandit==1.9.4 --recursive app --severity-level medium +--confidence-level medium` depuis `apps/backend`, et la même commande sur `enervision_ml` depuis +`ml`.