docs(architecture): ajoute la vue CI/CD et corrige trois affirmations fausses
La documentation du pipeline est explicitement notée par EC03 (C20) et n'existait pas. L'index des vues justifiait son absence par un manque de matière : quatre workflows et quatorze jobs en sont assez. Trois affirmations de 00-vue-ensemble.md étaient devenues fausses, ce qui coûte plus cher qu'une absence puisqu'on les lit et qu'on construit dessus : - la CI/CD y était déclarée `Cible` / `Rien` alors que quatre workflows tournent ; - le flux bout en bout y était `Cible` avec "aucun maillon n'existe, à l'exception de la base", alors que tout le chemin de lecture et deux ingestions existent ; - l'analyse de dépendances y était listée comme absente alors que pip-audit, npm audit et Dependabot sont en place. Seule celle des images manque. Ferme C20 de la grille d'auto-évaluation.
This commit is contained in:
@@ -77,15 +77,17 @@ collecteur ne vient le lire.
|
|||||||
| Backend | FastAPI, Python 3.14 | `apps/backend` | `En cours` | Factory, configuration, journalisation, 2 sondes de santé, `/metrics`, contrat OpenAPI versionné, routes `sites`, `alerts`, `recommendations`, `stats/summary`, `readings`, `sensors/status` et `predictions` en lecture (endpoints → services → repositories → models) |
|
| Backend | FastAPI, Python 3.14 | `apps/backend` | `En cours` | Factory, configuration, journalisation, 2 sondes de santé, `/metrics`, contrat OpenAPI versionné, routes `sites`, `alerts`, `recommendations`, `stats/summary`, `readings`, `sensors/status` et `predictions` en lecture (endpoints → services → repositories → models) |
|
||||||
| Frontend | Angular 22, Node 24 | `apps/frontend` | `En cours` | Tableau de bord sur route `/dashboard`, authentification complète (garde de route, intercepteur de jeton), cinq services HTTP, graphiques Chart.js. `stats`/`alerts` sur fixtures, `predictions` branché sur l'API réelle |
|
| Frontend | Angular 22, Node 24 | `apps/frontend` | `En cours` | Tableau de bord sur route `/dashboard`, authentification complète (garde de route, intercepteur de jeton), cinq services HTTP, graphiques Chart.js. `stats`/`alerts` sur fixtures, `predictions` branché sur l'API réelle |
|
||||||
| Base | PostgreSQL 17 + TimescaleDB | `db` | `Fait` | Bootstrap de l'extension, base de test, chaîne Alembic. Schéma applicatif créé (`site`, `dataset`, `reading` en hypertable, `prediction`, `alert`, `recommendation`) |
|
| Base | PostgreSQL 17 + TimescaleDB | `db` | `Fait` | Bootstrap de l'extension, base de test, chaîne Alembic. Schéma applicatif créé (`site`, `dataset`, `reading` en hypertable, `prediction`, `alert`, `recommendation`) |
|
||||||
| ML | LightGBM, MLflow | `ml` | `En cours` | Pipeline d'entraînement et de scoring (`enervision_ml.train`/`.score`, features par lags/moyennes glissantes partagées entre les deux, baseline de persistance saisonnière, suivi MLflow local), exposé en lecture via `GET /predictions`. Voir [ADR 0005](../adr/0005-modele-prediction-lightgbm.md) et [ML-START.md](../../ML-START.md). Automatisation (Airflow) et surveillance de dérive (EC06, #44/#45) pas encore construites |
|
| ML | LightGBM, MLflow | `ml` | `En cours` | Pipeline d'entraînement et de scoring (`enervision_ml.train`/`.score`, features par lags/moyennes glissantes partagées entre les deux, baseline de persistance saisonnière, suivi MLflow local), exposé en lecture via `GET /predictions`. Voir [ADR 0005](../adr/0005-modele-prediction-lightgbm.md) et [ML-START.md](../ML-START.md). Automatisation (Airflow) et surveillance de dérive (EC06, #44/#45) pas encore construites |
|
||||||
| Infra | Terraform, k3s single-node | `infra/terraform` | `En cours` | Module d'installation du cluster. Jamais appliqué, aucune ressource Kubernetes déclarée |
|
| Infra | Terraform, k3s single-node | `infra/terraform` | `En cours` | Module d'installation du cluster. Jamais appliqué, aucune ressource Kubernetes déclarée |
|
||||||
| Monitoring | Prometheus, Grafana, Alertmanager | `monitoring` | `Cible` | Rien, hors le `/metrics` exposé par l'API |
|
| Monitoring | Prometheus, Grafana, Alertmanager | `monitoring` | `Cible` | Rien, hors le `/metrics` exposé par l'API |
|
||||||
| ETL | Apache Airflow | `etl/airflow` | `Cible` | Rien |
|
| ETL | Apache Airflow | `etl/airflow` | `Cible` | Rien |
|
||||||
| CI/CD | GitHub Actions | `.github/workflows` | `Cible` | Rien |
|
| CI/CD | GitHub Actions | `.github/workflows` | `En cours` | 4 workflows, 14 jobs : lint, typage, tests avec seuil de couverture bloquant, tests d'intégration sur TimescaleDB réel, audit de dépendances, SAST Bandit, quality gate SonarCloud. Détail dans [50-cicd.md](50-cicd.md). **Aucun job de déploiement** (#21) |
|
||||||
|
|
||||||
## Flux bout en bout
|
## Flux bout en bout
|
||||||
|
|
||||||
Statut : `Cible`. Aucun maillon de cette chaîne n'existe aujourd'hui, à l'exception de la base.
|
Statut : `En cours`. Tout le chemin de lecture existe (base, API, frontend), ainsi que l'ingestion
|
||||||
|
par import depuis un CSV historique et depuis l'API Mock. **Le seul maillon absent est
|
||||||
|
l'orchestration** : Airflow ne tourne pas, l'ingestion et le scoring sont lancés à la main.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
@@ -153,7 +155,9 @@ consolidée.
|
|||||||
- **TLS, HSTS et CSP** : ils appartiennent au terminateur TLS, qui n'existe pas encore.
|
- **TLS, HSTS et CSP** : ils appartiennent au terminateur TLS, qui n'existe pas encore.
|
||||||
- **Limitation de débit au frontal** : celle de l'application protège les identifiants, pas
|
- **Limitation de débit au frontal** : celle de l'application protège les identifiants, pas
|
||||||
l'infrastructure.
|
l'infrastructure.
|
||||||
- **Analyse de dépendances et de conteneurs** dans la CI, qui relève du chantier CI/CD.
|
- **Analyse des images de conteneur** dans la CI. Celle des dépendances, elle, est en place
|
||||||
|
(`pip-audit`, `npm audit`, Dependabot sur 5 écosystèmes), de même que le SAST Bandit. Voir
|
||||||
|
[50-cicd.md](50-cicd.md).
|
||||||
- **Le fichier `environment.ts` de production** pointe encore sur `http://localhost:8000` en HTTP
|
- **Le fichier `environment.ts` de production** pointe encore sur `http://localhost:8000` en HTTP
|
||||||
simple : dans cet état, le cookie `Secure` ne sera pas posé. Voir
|
simple : dans cet état, le cookie `Secure` ne sera pas posé. Voir
|
||||||
[31-contrat-authentification.md](31-contrat-authentification.md).
|
[31-contrat-authentification.md](31-contrat-authentification.md).
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
# Intégration et livraison continues
|
||||||
|
|
||||||
|
Ce document décrit la chaîne qui s'exécute entre un `git push` et un merge autorisé : ce qui est
|
||||||
|
vérifié, ce qui bloque, et ce qui ne l'est pas.
|
||||||
|
|
||||||
|
| Étage | Sert à | Statut |
|
||||||
|
|---|---|---|
|
||||||
|
| Intégration continue | Interdire le merge d'un code qui casse la qualité, les tests ou la sécurité | `Fait` |
|
||||||
|
| Livraison continue | Porter un artefact vérifié jusqu'à la machine de déploiement | `Cible` |
|
||||||
|
|
||||||
|
Le **D** de CI/CD n'existe pas encore : aucun job de déploiement, aucune construction d'image
|
||||||
|
publiée, aucun environnement GitHub. L'issue #21 le porte. C'est la limite principale de cet
|
||||||
|
étage, et elle est nommée ici plutôt que découverte en soutenance.
|
||||||
|
|
||||||
|
## Vue d'ensemble
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
push["push ou pull_request"]
|
||||||
|
|
||||||
|
subgraph back["Backend · .github/workflows/backend.yml"]
|
||||||
|
bv["verification<br/>ruff, mypy, pytest --cov-fail-under=85"]
|
||||||
|
bi["integration<br/>TimescaleDB réel + alembic upgrade head"]
|
||||||
|
bd["security-audit<br/>uv export | pip-audit"]
|
||||||
|
bs["sast<br/>bandit"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph front["Frontend · frontend.yml"]
|
||||||
|
fb["build<br/>npm ci, npm run build"]
|
||||||
|
ft["test<br/>couverture lcov"]
|
||||||
|
fd["security-audit<br/>npm audit --audit-level=high"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph mlw["ML · ml.yml"]
|
||||||
|
mv["verification<br/>ruff, mypy, pytest"]
|
||||||
|
ms["sast<br/>bandit"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph sq["SonarQube · sonarqube.yml"]
|
||||||
|
sb1["build-front / test-front"]
|
||||||
|
sb2["build-back / test-back"]
|
||||||
|
sscan["sonarqube<br/>quality gate SonarCloud"]
|
||||||
|
end
|
||||||
|
|
||||||
|
push --> bv & bi & bd & bs
|
||||||
|
push --> fb --> ft
|
||||||
|
push --> fd
|
||||||
|
push --> mv & ms
|
||||||
|
push --> sb1 & sb2 --> sscan
|
||||||
|
sscan -.-> cd["deploy<br/>issue #21"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Déclenchement
|
||||||
|
|
||||||
|
Les quatre 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/**`,
|
||||||
|
chacun incluant son propre fichier de workflow dans le filtre pour qu'une modification du pipeline
|
||||||
|
déclenche le pipeline.
|
||||||
|
|
||||||
|
**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
|
||||||
|
délibéré pendant le projet (retour au plus tôt, et la CI tournera sur `main` dès la remontée sans
|
||||||
|
rien changer), mais ce serait à borner sur un dépôt à forte fréquence de push.
|
||||||
|
|
||||||
|
`backend.yml` et `ml.yml` déclarent en plus un groupe de concurrence par référence git avec
|
||||||
|
`cancel-in-progress`, ce qui annule un run devenu obsolète par un push plus récent.
|
||||||
|
|
||||||
|
## Ce qui bloque un merge
|
||||||
|
|
||||||
|
| Gate | Où | Seuil | Effet d'un échec |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Formatage `ruff format --check` | backend, ml | zéro écart | Bloque |
|
||||||
|
| Analyse statique `ruff check` | backend, ml | zéro constat | Bloque |
|
||||||
|
| Typage `mypy` | backend (`app`), ml (strict) | zéro erreur | Bloque |
|
||||||
|
| Tests unitaires `pytest` | backend, ml | **`--cov-fail-under=85`** côté backend | Bloque |
|
||||||
|
| Tests d'intégration | backend | marqueur `integration`, base réelle | Bloque |
|
||||||
|
| Audit de dépendances `pip-audit` | backend | sur le **verrou figé** | Bloque |
|
||||||
|
| Audit de dépendances `npm audit` | frontend | `--audit-level=high` | Bloque |
|
||||||
|
| **SAST `bandit`** | backend (`app`), ml (`enervision_ml`) | **MEDIUM et au-dessus** | Bloque |
|
||||||
|
| Quality gate SonarCloud | tout le dépôt | gate par défaut, couverture du **code neuf** | Bloque |
|
||||||
|
| Build `npm run build` | frontend | compilation | Bloque |
|
||||||
|
|
||||||
|
Deux seuils portent une décision qu'il faut savoir défendre :
|
||||||
|
|
||||||
|
- **`npm audit --audit-level=high`** et non `moderate` : une vulnérabilité modérée dans une
|
||||||
|
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
|
||||||
|
21/09/2026, les deux modules sont à **zéro constat, tous niveaux confondus**, sur 5 904 lignes
|
||||||
|
analysées.
|
||||||
|
|
||||||
|
## Le job d'intégration, et pourquoi il ne suffisait pas d'un `postgres`
|
||||||
|
|
||||||
|
`backend.yml` monte un service `timescale/timescaledb-ha:pg17`, **la même image que
|
||||||
|
`docker-compose.yml`**, et non une image `postgres` nue. La première migration s'arrête
|
||||||
|
volontairement si l'extension TimescaleDB manque : un écart d'image entre la CI et le poste
|
||||||
|
rendrait ce job vert sur une base qui n'est pas la nôtre.
|
||||||
|
|
||||||
|
Sur le poste, c'est `db/init/110-test-database.sql` qui pose l'extension. Ce fichier n'est pas
|
||||||
|
monté dans le service GitHub Actions, d'où l'étape `CREATE EXTENSION IF NOT EXISTS timescaledb`
|
||||||
|
avant `alembic upgrade head`.
|
||||||
|
|
||||||
|
La couverture est **désactivée** sur ce job (`pytest -m integration --no-cov`) : il ne joue qu'une
|
||||||
|
partie de la suite, et son taux n'aurait aucun sens face au seuil de 85 %.
|
||||||
|
|
||||||
|
## SonarCloud, et l'incident qui a immobilisé trois PR
|
||||||
|
|
||||||
|
Le workflow `sonarqube.yml` exécute quatre jobs de préparation (`build-front`, `test-front`,
|
||||||
|
`build-back`, `test-back`) qui produisent chacun un rapport de couverture en artefact, puis un
|
||||||
|
cinquième job qui les télécharge et lance `SonarSource/sonarqube-scan-action@v8` avec le secret
|
||||||
|
`SONAR_TOKEN`. Le périmètre est décrit par `sonar-project.properties` à la racine.
|
||||||
|
|
||||||
|
**L'incident, à raconter tel quel.** Les 18 et 19 septembre, trois PR (#103, #105, #107) sont
|
||||||
|
restées bloquées sur une quality gate rouge annonçant une couverture du code neuf à 0 %, alors que
|
||||||
|
la couverture globale du backend dépassait 87 %. Le diagnostic était **hors du code de ces PR** :
|
||||||
|
`sonar.test.inclusions` ne reconnaissait que les fichiers `test_*.py`, si bien que
|
||||||
|
`tests/api/acces.py`, `tests/factories.py` et les `__init__.py` du dossier de tests étaient
|
||||||
|
comptés comme **code de production non couvert**. Le motif `tests` sans joker ne désignait par
|
||||||
|
ailleurs que la racine.
|
||||||
|
|
||||||
|
Deux commits ont corrigé la configuration (`9e6a5c0` classe tout `apps/backend/tests` comme test,
|
||||||
|
`af2b8cb` déclenche l'analyse quand `sonar-project.properties` change). La gate est verte sur
|
||||||
|
toutes les PR depuis. Ce qui compte pour la suite : **la cause a été traitée en configuration, pas
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Stratégie de branche et conventions
|
||||||
|
|
||||||
|
| Règle | Détail |
|
||||||
|
|---|---|
|
||||||
|
| Préfixes de branche | `feat/`, `fix/`, `chore/`, `docs/`, `test/` |
|
||||||
|
| Messages de commit | Conventional Commits |
|
||||||
|
| Branche d'intégration | `dev` ; `main` est la branche par défaut du dépôt public |
|
||||||
|
| Revue | Toute PR passe par une revue écrite avant merge |
|
||||||
|
| ADR | Toute décision structurante porte son ADR dans la même PR |
|
||||||
|
| Vues d'architecture | Toute PR qui change un composant met à jour sa vue **dans la même PR** |
|
||||||
|
|
||||||
|
## Secrets
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
## Ce qui manque, et pourquoi
|
||||||
|
|
||||||
|
| Manque | Issue | Conséquence assumée |
|
||||||
|
|---|---|---|
|
||||||
|
| Job de déploiement (CD) | #21 | La chaîne s'arrête au merge. Rien ne part vers une machine |
|
||||||
|
| DAST (OWASP ZAP) | #41 | Aucune vérification sur l'application en fonctionnement, seulement sur le code et les dépendances |
|
||||||
|
| Tests end to end | #46 | Les parcours utilisateur ne sont pas vérifiés en CI |
|
||||||
|
| Tests de charge | #47 | Aucun garde-fou de performance |
|
||||||
|
| Scan d'image de conteneur | aucune | Les `Dockerfile` sont construits en local, pas analysés |
|
||||||
|
|
||||||
|
## Reproduire la CI en local
|
||||||
|
|
||||||
|
`make check` enchaîne formatage, analyse statique, typage et tests du backend, c'est à dire le job
|
||||||
|
`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`.
|
||||||
@@ -15,10 +15,12 @@ contredisent, c'est l'ADR qui fait foi et la vue qui est en retard.
|
|||||||
| [31-contrat-authentification.md](31-contrat-authentification.md) | Ce que le frontend doit savoir pour coder la connexion |
|
| [31-contrat-authentification.md](31-contrat-authentification.md) | Ce que le frontend doit savoir pour coder la connexion |
|
||||||
| [32-design-systeme-frontend.md](32-design-systeme-frontend.md) | Tokens CSS, composants `ev-*` partagés, règle anti-couleur-en-dur |
|
| [32-design-systeme-frontend.md](32-design-systeme-frontend.md) | Tokens CSS, composants `ev-*` partagés, règle anti-couleur-en-dur |
|
||||||
| [40-data.md](40-data.md) | Frontières `db/` et `alembic/`, cycle de vie d'une mesure, modèle |
|
| [40-data.md](40-data.md) | Frontières `db/` et `alembic/`, cycle de vie d'une mesure, modèle |
|
||||||
|
| [50-cicd.md](50-cicd.md) | Workflows, gates bloquantes, SonarCloud, Dependabot, ce qui manque |
|
||||||
|
|
||||||
L'observabilité et la CI/CD n'ont pas de document propre : ce sont des sections des documents
|
La CI/CD a désormais son document : quatre workflows et quatorze jobs, c'est assez de matière pour
|
||||||
ci-dessus, tant que `monitoring/` et `etl/airflow/` ne contiennent que des `.gitkeep`. Elles en
|
qu'une section de plus dans une autre vue devienne illisible. L'observabilité, elle, n'en a
|
||||||
sortiront le jour où elles auront de la matière. Un fichier vide de plus n'aide personne.
|
toujours pas : `monitoring/` ne contient que des `.gitkeep`. Elle en sortira le jour où elle aura
|
||||||
|
de la matière. Un fichier vide de plus n'aide personne.
|
||||||
|
|
||||||
La sécurité applicative, elle, a désormais de la matière : la vue consolidée reste dans
|
La sécurité applicative, elle, a désormais de la matière : la vue consolidée reste dans
|
||||||
[00-vue-ensemble.md](00-vue-ensemble.md), le détail dans [20-backend.md](20-backend.md), la
|
[00-vue-ensemble.md](00-vue-ensemble.md), le détail dans [20-backend.md](20-backend.md), la
|
||||||
|
|||||||
Reference in New Issue
Block a user