[DTP-Worker] Sprint 8 · buffer L75 · Gate fraîcheur matrice régression (ci/check_regression.sh) : run==commité + verdict PASS

L'artefact le plus cité du dépôt (qa/regression/out/regression_run.json ·
21 suites · 534 tests · PASS) n'avait aucun garde-fou CI : check_artifacts
ne rejoue que 'build' et exclut les artefacts d'exécution 'run'. Nouveau
5e gate statique rejoue 'regression_gen.py run' (~5s, déterministe) et exige
byte-identité + verdict PASS. Rend impossible la re-commission d'une matrice
périmée (dérive type demo 18->21) ou rouge commitée verte. parse_ci inchangé
(gate sans working-directory) → 0 dérive des counts dérivés (check_artifacts
exit 0). Preuve de morsure OK (534->999 => exit 1). 5 gates verts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-07-31 00:07:14 +00:00
parent e16c4f676d
commit 0eaf5bde39
4 changed files with 186 additions and 4 deletions
+20 -3
View File
@@ -18,6 +18,7 @@ et manuellement (`workflow_dispatch`). Jobs statiques (+ une suite `unittest` pa
| `validate-json` | `ci/validate_json.sh` | Parse strict de tous les `*.json` (schémas Faisabilité↔Publiciste) | ✅ oui |
| `check-docs` | `ci/check_docs.sh` | Liens Markdown internes + présence auto-score 4Big | ✅ oui (liens) |
| `check-artifacts` | `ci/check_artifacts.sh` | Reproductibilité : chaque `out/*.json` versionné == build frais | ✅ oui |
| `check-regression` | `ci/check_regression.sh` | Fraîcheur : `qa/regression/out/regression_run.json` (`run`) == run frais + verdict PASS | ✅ oui |
| `gate` | — | Agrégat vert = gate qualité 4Big franchi | ✅ oui |
Aucune dépendance réseau/marketplace hors `actions/checkout`. Tout tourne avec
@@ -60,7 +61,22 @@ artefacts d'**autres** modules (ex. `demo/scenarios` lit
consommateur devient périmé s'il n'est pas régénéré — dérive qu'aucune suite
`tests/` (qui teste des fonctions, pas le fichier commité) n'attrape. Correctif :
`build -o out` puis commit. Les artefacts d'**exécution** non produits par `build`
(p.ex. `qa/regression/out/regression_run.json`, issu de `run`) ne sont pas comparés.
(p.ex. `qa/regression/out/regression_run.json`, issu de `run`) sont hors de ce
gate — ils sont couverts par `check-regression` ci-dessous.
### `check_regression.sh`
Rejoue la **matrice de régression** complète (`qa/regression/regression_gen.py run`,
~5 s, stdlib pur) vers un tmp et exige que le `regression_run.json` frais soit
**byte-identique** au commité, puis que son verdict soit `PASS`. Complément direct
de `check_artifacts` : celui-ci ne rejoue que `build` (→ `regression_plan.json`) et
laisse hors périmètre l'artefact d'**exécution** `regression_run.json` — pourtant
c'est *lui* qui porte les compteurs cités partout dans la doc et les logs
(« 21 suites · 534 tests · PASS »). Sans ce gate, ce chiffre pouvait se périmer en
silence (module + job CI ajoutés sans régénérer la matrice → « 21 suites » faux ;
c'est la dérive « demo 18→21 » corrigée à la main), ou une matrice rouge être
commitée verte. `regression_run.json` ne contient aucun horodatage/hôte → le run est
déterministe et l'égalité exacte licite. Correctif : `regression_gen.py run -o out`
puis commit.
## 3. Exécution locale (avant push)
@@ -68,7 +84,8 @@ consommateur devient périmé s'il n'est pas régénéré — dérive qu'aucune
bash ci/guard_constraints.sh # contraintes CLAUDE.md
bash ci/validate_json.sh # schémas JSON
bash ci/check_docs.sh # liens + score
bash ci/check_artifacts.sh # reproductibilité out/
bash ci/check_artifacts.sh # reproductibilité out/ (build)
bash ci/check_regression.sh # fraîcheur matrice régression (run)
```
Chaque script retourne `0` si conforme, `1` sinon. Reproduit exactement ce que
@@ -100,7 +117,7 @@ Actions** doit être activé, et le runner doit apparaître « Idle ».
- `[ ]` Gitea Actions activé au niveau instance ET repo.
- `[ ]` `act_runner` enregistré, label `ubuntu-latest`, statut Idle.
- `[ ]` Push de test → les 4 jobs apparaissent et passent au vert.
- `[ ]` Push de test → les 5 gates statiques apparaissent et passent au vert.
- `[ ]` PR de test avec violation volontaire → `constraints-guard` bloque (rouge).
---
+88
View File
@@ -0,0 +1,88 @@
#!/usr/bin/env bash
# ============================================================================
# check_regression.sh · OTO Enterprise OS DTP
# ----------------------------------------------------------------------------
# Gate de FRAÎCHEUR de la matrice de régression (roadmap Sprint 8 · QA
# « Regression tests exhaustifs » · CLAUDE.md #5 — zéro régression silencieuse).
# Prouve, en une commande, que l'artefact d'EXÉCUTION commité
# `qa/regression/out/regression_run.json` (totaux « 21 suites · 534 tests ·
# verdict PASS » cité partout dans la doc et les logs) reflète TOUJOURS la
# réalité du disque — en le régénérant et en exigeant l'identité byte-for-byte.
#
# Pourquoi un gate DÉDIÉ (et non `check_artifacts.sh`) : ce dernier ne rejoue
# QUE la sous-commande déterministe `build` (→ regression_plan.json) et exclut
# explicitement les artefacts d'EXÉCUTION issus de `run`. Or `regression_run.json`
# EST un artefact de `run` : il porte les compteurs réellement exécutés
# (ran/passed/failures) que `build` ne connaît pas. Résultat : sans ce gate, la
# matrice agrégée pouvait se périmer en silence (ex. un module + son job CI
# ajoutés sans régénérer la matrice → « 21 suites » devenu faux) sans qu'aucune
# suite `tests/` — qui teste des FONCTIONS, pas le fichier commité — ne l'attrape.
# C'est exactement la dérive « demo 18→21 » déjà corrigée à la main : ce gate la
# rend impossible à re-commettre.
#
# Déterminisme : `regression_run.json` ne contient AUCUN horodatage / hôte /
# chemin absolu — deux `run` successifs sont byte-identiques (vérifié). La
# comparaison exacte est donc licite et constitue la preuve la plus forte.
#
# Contrat vérifié : `regression_gen.py run` régénéré vers un tmp DOIT produire un
# `regression_run.json` byte-identique au commité. Toute divergence (compteur,
# suite ajoutée/retirée, verdict) échoue le gate avec l'ordre de régénérer.
# En bonus : le verdict fraîchement calculé DOIT être « PASS » (une matrice
# rouge ne doit jamais être commitée verte).
#
# Dépendances : bash, git, python3, diff, mktemp. Aucun réseau requis
# (stdlib pur · CLAUDE.md #2 — runner Gitea sans pip).
# ============================================================================
set -uo pipefail
cd "$(git rev-parse --show-toplevel)"
FAIL=0
report() { printf ' \033[31m✗\033[0m %s\n' "$1"; FAIL=1; }
ok() { printf ' \033[32m✓\033[0m %s\n' "$1"; }
MOD="05_deliverables_mvp/qa/regression"
GEN="regression_gen.py"
ARTIFACT="out/regression_run.json"
COMMITTED="$MOD/$ARTIFACT"
echo "== Fraîcheur de la matrice de régression (run frais == commité ?) =="
if [[ ! -f "$MOD/$GEN" ]]; then
report "$MOD/$GEN — générateur introuvable (module déplacé/renommé ?)"
elif [[ ! -f "$COMMITTED" ]]; then
report "$COMMITTED — artefact d'exécution absent du dépôt (lancer 'run -o out')"
else
tmp="$(mktemp -d)"
if ! ( cd "$MOD" && python3 "$GEN" run -o "$tmp" ) >/dev/null 2>"$tmp/.err"; then
report "$MOD — 'run' a échoué (matrice non ré-exécutable) :"
sed 's/^/ /' "$tmp/.err"
else
fresh="$tmp/regression_run.json"
if [[ ! -f "$fresh" ]]; then
report "$MOD — 'run' n'a produit aucun regression_run.json (générateur cassé ?)"
elif ! diff -q "$fresh" "$COMMITTED" >/dev/null 2>&1; then
report "$ARTIFACT — DÉRIVE : le run frais diffère du commité (régénérer)"
printf ' \033[90m# diff résumé (commité → frais) :\033[0m\n'
diff <(python3 -c "import json,sys;print(json.dumps(json.load(open(sys.argv[1]))['totals'],indent=1,sort_keys=True))" "$COMMITTED" 2>/dev/null) \
<(python3 -c "import json,sys;print(json.dumps(json.load(open(sys.argv[1]))['totals'],indent=1,sort_keys=True))" "$fresh" 2>/dev/null) \
| sed 's/^/ /'
else
verdict="$(python3 -c "import json;print(json.load(open('$COMMITTED'))['verdict'])" 2>/dev/null)"
totals="$(python3 -c "import json;t=json.load(open('$COMMITTED'))['totals'];print(f\"{t['suites']} suites · {t['passed']}/{t['ran']} tests\")" 2>/dev/null)"
if [[ "$verdict" != "PASS" ]]; then
report "$ARTIFACT — reproductible MAIS verdict=$verdict (matrice rouge commitée)"
else
ok "$ARTIFACT — reproductible à l'identique ($totals · PASS)"
fi
fi
fi
rm -rf "$tmp"
fi
echo
if [[ "$FAIL" -eq 0 ]]; then
echo -e "\033[32m✅ Matrice de régression à jour (run frais == commité · PASS).\033[0m"
else
echo -e "\033[31m❌ Matrice périmée/rouge — régénérer avec 'regression_gen.py run -o out'.\033[0m"
fi
exit "$FAIL"