#!/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"