Verrou de release explicable — fusionne résultats de tests, couverture et flakiness en un verdict GO / NO GO argumenté, prêt à bloquer un pipeline.
Des tests verts ne suffisent pas à autoriser une mise en production. La décision de release agrège en réalité plusieurs signaux : le rapport de tests, le tableau de bord de couverture, la connaissance tribale des échecs « habituellement flaky ». Cette synthèse vit dans la tête d'un release manager — non auditable, non répétable, perdue quand la personne est absente.
ReleaseGuard transforme cette synthèse en quality gate déterministe et explicable, posée sur des artefacts CI standard : JUnit XML (tests), Cobertura XML (couverture), rapport FlakySense JSON (flakiness).
À ne pas confondre avec anomaly-sentinel : là-bas, l'IA est le système sous test — on valide un classifieur LLM comme composant critique. Ici, le processus de livraison est l'objet : ReleaseGuard verrouille la décision de livrer n'importe quel build, et l'IA n'y décide rien. FlakySense fournit l'un des trois signaux d'entrée — aucun couplage à l'exécution, un rapport JSON suffit.
Jamais de moyenne pure. Moyenner est l'anti-pattern classique du release gate : une excellente couverture peut masquer arithmétiquement un smoke test qui échoue. Un blocant ne doit pas être compensable (ADR-001).
| Étage | Règle |
|---|---|
| G1 — échec réel | ≥ 1 test en échec non identifié comme flaky ⇒ NO GO |
| G2 — plancher de couverture | couverture ligne < 60 % ⇒ NO GO |
| Score (si aucun verrou déclenché) | 0.50·tests + 0.25·couverture + 0.25·flakiness — ≥ 0.80 ⇒ GO, sinon CONDITIONAL GO |
Trois engagements structurent le modèle :
- La règle inter-signaux. Un test en échec que FlakySense identifie comme flaky ne
déclenche pas G1 : l'échec est excusé nominativement dans les conditions du verdict,
plafonné à
CONDITIONAL GO. Aucun parseur seul ne peut produire cette décision — elle n'existe que dans la fusion. La jointure repose sur une identité complète, préfixée du fichier, construite depuis les attributs JUnit dans un dialecte déclaré (ADR-004, qui remplace l'ADR-002) :pytestreconstruit un node id depuis un classname pointé,pathprend tel quel un classname qui est déjà un chemin —--junit-dialect pathpour un rapport Playwright, Jest ou Vitest. Un raté de jointure ne peut que durcir le verdict, jamais excuser un échec à tort. Le design rejeté (jointure par nom court) aurait laissé passer un faux GO par collision de noms — contrefactuel vérifié et rejoué en test de régression (bug evidence #2) ; et deviner le dialecte a été rejeté parce qu'un verrou de release ne doit pas inférer ce qu'on peut lui déclarer — mesure à l'appui (bug evidence #4). NO GOexige un blocant nommé. Le score seul ne peut jamais opposer un veto : un score bas sans blocant identifiable est une livraison conditionnelle avec risques listés, pas un blocage au ressenti.- Le verdict ne dépend jamais d'un LLM. La couche IA (optionnelle, activée par
ANTHROPIC_API_KEY) rédige le rationale desCONDITIONAL GO— rien d'autre. Elle ne peut modifier ni le verdict, ni le score, ni les conditions : c'est verrouillé par des tests. Sans clé API, tout fonctionne à l'identique en mode déterministe.
flowchart LR
subgraph artifacts [Artefacts CI]
J[junit.xml]
C[coverage.xml]
F[flakysense-report.json]
end
subgraph ingest [releaseguard.ingest]
PJ[Parseur JUnit<br/>reconstruction des node ids]
PC[Parseur Cobertura]
PF[Parseur FlakySense]
end
J --> PJ --> S[ReleaseSignals]
C --> PC --> S
F --> PF --> S
S --> E{ReleaseEvaluator}
E -->|verrou declenche| NG[NO GO]
E -->|score >= 0.80, rien d'excuse| GO[GO]
E -->|sinon| CG[CONDITIONAL GO]
CG -.->|narration optionnelle| LLM[LLM System 2]
Sortie réelle sur le scénario d'exemple scenario_conditional :
$ releaseguard --junit junit.xml --coverage coverage.xml --flaky flakysense-report.json
ReleaseGuard verdict: CONDITIONAL GO (score 0.62)
Gates:
[pass ] G1 real failure: no non-flaky failure
[pass ] G2 coverage floor: line coverage 72% vs floor 60%
Signals:
tests 1.00 (weight 0.50) 9/9 non-excused tests passed
coverage 0.48 (weight 0.25) line coverage 72% on the 60%-85% ramp
flakiness 0.00 (weight 0.25) 2 flaky of 10 executed tests
Conditions:
- excused flaky failure: tests/test_search.py::test_search_pagination (source: flakysense)
- weakest signal: flakiness at 0.00 (2 flaky of 10 executed tests)
Rationale (system1):
No blocker; score 0.62 below GO threshold 0.80. [...]
Code de sortie 1 — un pipeline verrouille directement dessus : 0 GO,
1 CONDITIONAL GO, 2 NO GO, 3 erreur (ADR-003).
Sortie JSON complète disponible via --json.
ReleaseGuard s'insère dans n'importe quelle CI par ses codes de sortie — GitHub Actions, GitLab CI, Azure DevOps :
# bloque le pipeline sur NO GO, laisse passer GO et CONDITIONAL GO
releaseguard --junit reports/junit.xml --coverage coverage.xml || test $? -le 1Ce dépôt le prouve sur lui-même : à chaque push, la CI exécute la suite de tests puis ReleaseGuard évalue les artefacts de son propre build et publie le verdict dans le job summary (.github/workflows/ci.yml). L'outil n'est pas une démo posée à côté du projet — il est le verrou de release du projet.
git clone https://github.com/BazanJeremy/ReleaseGuard.git
cd ReleaseGuard
python -m venv .venv && source .venv/bin/activate # Windows : .\.venv\Scripts\Activate.ps1
pip install -e .[dev]
python -m pytest # 110 tests — aucune clé API requise
# les trois scénarios canoniques (codes de sortie attendus : 0, 2, 1)
releaseguard --junit data/samples/scenario_go/junit.xml --coverage data/samples/scenario_go/coverage.xml --flaky data/samples/scenario_go/flakysense-report.json
releaseguard --junit data/samples/scenario_no_go/junit.xml --coverage data/samples/scenario_no_go/coverage.xml --flaky data/samples/scenario_no_go/flakysense-report.json
releaseguard --junit data/samples/scenario_conditional/junit.xml --coverage data/samples/scenario_conditional/coverage.xml --flaky data/samples/scenario_conditional/flakysense-report.json
# un rapport de runner JS : les mêmes octets bloquent sans le dialecte (sortie 2)
# et passent en conditionnel avec (sortie 1) — ADR-004
releaseguard --junit data/samples/dialect_playwright/junit.xml --flaky data/samples/dialect_playwright/flakysense-report.json
releaseguard --junit data/samples/dialect_playwright/junit.xml --flaky data/samples/dialect_playwright/flakysense-report.json --junit-dialect pathNarration System 2 optionnelle : pip install -e .[llm] et définir
ANTHROPIC_API_KEY. Tout ce qui précède fonctionne à l'identique sans.
| Couche | Choix |
|---|---|
| Langage | Python 3.12+ |
| Modèles de données | Pydantic v2 |
| Couche IA (optionnelle) | Anthropic Claude — narration System 2 uniquement |
| Framework de test | pytest — 110 tests, zéro clé API requise |
| CI | GitHub Actions — verrou dogfood bloquant |
| Décisions d'architecture | 4 ADRs (docs/adr/) + journal de bugs |
Des choix assumés, documentés dans les ADRs :
- Trois parseurs seulement (JUnit, Cobertura, FlakySense). Tout nouveau type de signal est un point d'extension, pas du périmètre v1.
- Le dialecte JUnit se déclare, il ne se devine pas. Un rapport de runner JS exige
--junit-dialect path; l'oublier reste sûr mais durcit le verdict, et rien n'est normalisé d'un dialecte à l'autre (ADR-004). - Les pondérations ne sont pas réglables en ligne de commande. Les seuils le sont ; changer les poids est une décision de gouvernance qui passe par un ADR remplaçant, pas par un flag de pipeline (ADR-003).
- Pas de conteneurisation. L'histoire de déploiement de ce dépôt est son propre verrou CI (dogfood) ; le packaging conteneur est démontré par FlakySense.
- Narration IA conditionnelle à une clé. Sans
ANTHROPIC_API_KEY, le rationale System 1 déterministe est rendu — le verdict est identique dans les deux cas.
Ces outils partagent les mêmes principes : le déterministe d'abord, l'IA là où elle apporte — le QA reste l'arbitre. Tous tournent en local, aucune clé API requise.
| Projet | Focus |
|---|---|
| EvalForge | Évaluation de LLM & calibration du juge |
| ReleaseGuard ← ce repo | Verrou de release GO/NO-GO explicable |
| FlakySense | Diagnostic statistique des tests flaky |
| Anomaly Sentinel | Tester les IA de détection d'anomalies (medtech · fintech) |
| TestScribe | Enrichissement de bug reports assisté par IA |
| SkyGuard | Quality gate sécurité pour systèmes critiques avioniques |
| loadlens | Verdict de performance argumenté sur des tests de charge |
Jérémy Bazan — Ingénieur QA / Lead Tech QA, spécialisation AI-driven Quality. ISTQB Foundation v4. Intégration de LLM (Claude, GPT) dans des pipelines QA de production au sein d'un grand groupe du secteur de l'énergie.