[DTP-Worker] Sprint 8 · buffer · Gate reproductibilité artefacts (ci/check_artifacts.sh) + fix dérive run_sheet demo 18→21
Nouveau gate CI bloquant : régénère chaque 05_deliverables_mvp/**/out/ depuis son générateur `build` et exige l'égalité byte-for-byte avec le fichier commité (découverte auto · stdlib pur · zéro pip). Attrape la dérive silencieuse d'un artefact qui auto-résout une valeur depuis un autre module et devient périmé quand la source grandit — dérive qu'aucune suite tests/ (fonctions, pas fichier commité) n'attrapait. Défaut détecté et corrigé par ce gate : demo/scenarios/out/run_sheet.json embarquait ci_modules_count=18 (buildé Sprint 7) alors que qa/audit_4big en compte désormais 21. Régénéré (diff 1 ligne). Consistance live re-vérifiée. Wiring : job check-artifacts + needs du gate agrégé (.gitea/workflows/ci.yml) · doc (ci/README.md). Vérifs : test négatif du gate OK (dérive → EXIT 1), 4 gates statiques EXIT 0, suites CI-parsing vertes (regression/acceptance/audit_4big/demo), 21/21 suites · 534 tests, compteurs dérivés stables. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+17
-1
@@ -10,13 +10,14 @@
|
||||
## 1. Ce que fait le pipeline
|
||||
|
||||
Workflow : `.gitea/workflows/ci.yml`. Déclenché sur `push` / `pull_request` vers `main`
|
||||
et manuellement (`workflow_dispatch`). Quatre jobs :
|
||||
et manuellement (`workflow_dispatch`). Jobs statiques (+ une suite `unittest` par module) :
|
||||
|
||||
| Job | Script | Rôle | Blocant |
|
||||
|---|---|---|---|
|
||||
| `constraints-guard` | `ci/guard_constraints.sh` | Enforce les contraintes NON-NÉGOCIABLES de CLAUDE.md | ✅ oui |
|
||||
| `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 |
|
||||
| `gate` | — | Agrégat vert = gate qualité 4Big franchi | ✅ oui |
|
||||
|
||||
Aucune dépendance réseau/marketplace hors `actions/checkout`. Tout tourne avec
|
||||
@@ -47,12 +48,27 @@ Un JSON cassé casse le pipeline aval → attrapé ici.
|
||||
- **[SOFT]** livrables `05_deliverables_mvp/*.md` : mention d'auto-score 4Big
|
||||
attendue (≥95/100, CLAUDE.md #5). Avertissement seul, non blocant.
|
||||
|
||||
### `check_artifacts.sh`
|
||||
Régénère chaque artefact `05_deliverables_mvp/**/out/` versionné depuis son
|
||||
générateur (`build -o <tmp>`) et exige une **égalité byte-for-byte** avec le
|
||||
fichier commité. Découverte automatique (zéro liste à la main · #6) : tout module
|
||||
avec un `out/` et un générateur `build` entre dans le gate.
|
||||
|
||||
Cible la **dérive silencieuse** : un module auto-résout des valeurs depuis les
|
||||
artefacts d'**autres** modules (ex. `demo/scenarios` lit
|
||||
`qa/audit_4big/coverage/ci_modules_count`) ; quand la source grandit, l'artefact
|
||||
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.
|
||||
|
||||
## 3. Exécution locale (avant push)
|
||||
|
||||
```bash
|
||||
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/
|
||||
```
|
||||
|
||||
Chaque script retourne `0` si conforme, `1` sinon. Reproduit exactement ce que
|
||||
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================================
|
||||
# check_artifacts.sh · OTO Enterprise OS DTP
|
||||
# ----------------------------------------------------------------------------
|
||||
# Gate de REPRODUCTIBILITÉ des artefacts (roadmap Sprint 8 · « Regression tests
|
||||
# exhaustifs » · DevOps CI/CD). Prouve, en une commande, que CHAQUE artefact
|
||||
# `out/` versionné se régénère À L'IDENTIQUE (byte-for-byte) depuis son
|
||||
# générateur — donc qu'aucun `out/*.json` n'a dérivé de sa source.
|
||||
#
|
||||
# Pourquoi ce gate : plusieurs modules auto-résolvent des valeurs depuis les
|
||||
# artefacts d'AUTRES modules (ex. demo/scenarios lit qa/audit_4big). Quand la
|
||||
# source grandit (un compteur passe de 18 à 21…), l'artefact consommateur
|
||||
# devient périmé s'il n'est pas régénéré — dérive silencieuse qu'aucune suite
|
||||
# `tests/` (qui teste des FONCTIONS, pas le fichier commité) n'attrape. Ce gate
|
||||
# la bloque : il compare le build frais au fichier commité.
|
||||
#
|
||||
# Contrat vérifié : pour chaque `05_deliverables_mvp/**/out/`, le générateur
|
||||
# racine (le .py portant une sous-commande `build`) est ré-exécuté vers un
|
||||
# répertoire temporaire ; tout fichier produit DOIT exister et être identique
|
||||
# à son jumeau 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 — le gate n'assert que ce que `build` (déterministe) produit.
|
||||
#
|
||||
# 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"; }
|
||||
|
||||
ROOT="05_deliverables_mvp"
|
||||
|
||||
# Découverte : tout répertoire `out/` sous les livrables. Zéro liste à la main
|
||||
# (#6) — un module ajouté entre automatiquement dans le gate.
|
||||
mapfile -t outdirs < <(find "$ROOT" -type d -name out | sort)
|
||||
|
||||
if [[ "${#outdirs[@]}" -eq 0 ]]; then
|
||||
echo "Aucun répertoire out/ — rien à vérifier."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "== Reproductibilité des artefacts out/ (build frais == commité ?) =="
|
||||
for outdir in "${outdirs[@]}"; do
|
||||
mod="$(dirname "$outdir")"
|
||||
rel="${mod#"$ROOT"/}"
|
||||
|
||||
# Générateur = l'unique .py à la racine du module portant `add_parser("build")`.
|
||||
gen="$(grep -rln 'add_parser("build"\|add_parser('\''build'\''' "$mod"/*.py 2>/dev/null | head -1)"
|
||||
if [[ -z "$gen" ]]; then
|
||||
report "$rel — répertoire out/ sans générateur 'build' (config invalide)"
|
||||
continue
|
||||
fi
|
||||
|
||||
tmp="$(mktemp -d)"
|
||||
if ! ( cd "$mod" && python3 "$(basename "$gen")" build -o "$tmp" ) >/dev/null 2>"$tmp/.err"; then
|
||||
report "$rel — 'build' a échoué :"
|
||||
sed 's/^/ /' "$tmp/.err"
|
||||
rm -rf "$tmp"
|
||||
continue
|
||||
fi
|
||||
|
||||
drift=0
|
||||
produced=0
|
||||
for f in "$tmp"/*; do
|
||||
[[ -e "$f" ]] || continue
|
||||
bn="$(basename "$f")"
|
||||
produced=$((produced + 1))
|
||||
committed="$outdir/$bn"
|
||||
if [[ ! -f "$committed" ]]; then
|
||||
report "$rel/$bn — produit par 'build' mais ABSENT du dépôt (non commité)"
|
||||
drift=1
|
||||
elif ! diff -q "$f" "$committed" >/dev/null 2>&1; then
|
||||
report "$rel/$bn — DÉRIVE : le fichier commité diffère du build frais (régénérer)"
|
||||
drift=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$produced" -eq 0 ]]; then
|
||||
report "$rel — 'build' n'a produit aucun fichier (générateur cassé ?)"
|
||||
elif [[ "$drift" -eq 0 ]]; then
|
||||
ok "$rel — $produced artefact(s) reproductible(s) à l'identique"
|
||||
fi
|
||||
rm -rf "$tmp"
|
||||
done
|
||||
|
||||
echo
|
||||
if [[ "$FAIL" -eq 0 ]]; then
|
||||
echo -e "\033[32m✅ Tous les artefacts out/ sont reproductibles (aucune dérive).\033[0m"
|
||||
else
|
||||
echo -e "\033[31m❌ Artefact périmé/dérivé détecté — régénérer avec 'build -o out'.\033[0m"
|
||||
fi
|
||||
exit "$FAIL"
|
||||
Reference in New Issue
Block a user