diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 067858e..52220b6 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -43,6 +43,23 @@ jobs: - name: Contrôle docs run: bash ci/check_docs.sh + # -------------------------------------------------------------------------- + # Reproductibilité des artefacts (Sprint 8 · DevOps CI/CD). Prouve que chaque + # `05_deliverables_mvp/**/out/*.json` versionné se régénère À L'IDENTIQUE + # depuis son générateur `build`. Attrape la dérive silencieuse d'un artefact + # qui auto-résout une valeur depuis un AUTRE module (ex. demo/scenarios lit + # qa/audit_4big/coverage) et devient périmé quand la source grandit — dérive + # qu'aucune suite `tests/` (qui teste des fonctions, pas le fichier commité) + # n'attrape. stdlib pur, zéro pip. + # -------------------------------------------------------------------------- + check-artifacts: + name: Reproductibilité des artefacts out/ (build == commité) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Contrôle reproductibilité artefacts + run: bash ci/check_artifacts.sh + # -------------------------------------------------------------------------- # Publiciste (Sprint 2) : parser faisabilité → projets_master.json + generator. # Tests unitaires stdlib pur (unittest) → aucune installation pip requise sur @@ -428,7 +445,7 @@ jobs: gate: name: Gate qualité (agrégat) runs-on: ubuntu-latest - needs: [constraints-guard, validate-json, check-docs, publiciste-tests, rbac-tests, faisabilite-gen-tests, rbac-fixtures-tests, rbac-userperm-tests, rbac-roleprofile-tests, rbac-applyplan-tests, bancable-tests, crm-workflow-vente-tests, crm-dossier-vente-tests, crm-commissions-tests, fiscal-ecf-tests, frontend-portails-tests, legal-confotur-tests, qa-audit-5d-tests, seo-tests, chat-otoia-tests, qa-audit-4big-tests, demo-scenario-tests, qa-regression-tests, devops-deploy-runbook-tests, qa-acceptance-tests] + needs: [constraints-guard, validate-json, check-docs, check-artifacts, publiciste-tests, rbac-tests, faisabilite-gen-tests, rbac-fixtures-tests, rbac-userperm-tests, rbac-roleprofile-tests, rbac-applyplan-tests, bancable-tests, crm-workflow-vente-tests, crm-dossier-vente-tests, crm-commissions-tests, fiscal-ecf-tests, frontend-portails-tests, legal-confotur-tests, qa-audit-5d-tests, seo-tests, chat-otoia-tests, qa-audit-4big-tests, demo-scenario-tests, qa-regression-tests, devops-deploy-runbook-tests, qa-acceptance-tests] steps: - name: Résultat run: echo "✅ Gate qualité 4Big franchi — tous les checks verts." diff --git a/05_activity_log/2026-07-30.md b/05_activity_log/2026-07-30.md index 961bde7..33de9e3 100644 --- a/05_activity_log/2026-07-30.md +++ b/05_activity_log/2026-07-30.md @@ -1604,3 +1604,59 @@ rigueur de la spec**, explicitement **pas** un score d'implémentation (le build **Hors périmètre worker (VPS · #8)** : néant (changements in-repo : gate CI locale + doc). **Auto-score 4Big** : 96/100. + +--- + +## Session 20260730_232914 · Buffer S8 · Gate reproductibilité des artefacts + fix dérive run_sheet demo (18→21) + +**Tâche** : **Sprint 8 · « Regression tests exhaustifs » + DevOps CI/CD**. Baseline +sain confirmé au démarrage (4 gates statiques EXIT 0 ; 21/21 suites vertes · 534 +tests via `qa/regression run`). Objectif : durcir la régression au-delà de +l'exécution des tests — prouver la **reproductibilité byte-for-byte** des artefacts +`out/` versionnés (aucun `out/*.json` n'a dérivé de sa source). + +**Découverte d'un VRAI défaut (analyser avant agir · #1)** : en ré-exécutant le +`build` de chaque module vers un tmpdir et en diffant contre le commité, **1 +artefact sur 18 dérivait** — `demo/scenarios/out/run_sheet.json` : `"valeur": 18` +là où le build frais produit `21`. Générateur **déterministe** (deux builds +identiques), donc pas de non-déterminisme : le fichier commité était **périmé**. + +**Cause racine** : le run_sheet auto-résout ses valeurs de preuve depuis les +artefacts d'AUTRES modules — ici `qa/audit_4big` `/coverage/ci_modules_count`. Ce +compteur a grandi **18 → 21** (suites `regression`/`acceptance`/`deploy_runbook` +ajoutées depuis) mais le run_sheet — buildé au Sprint 7 (commit `cac6538`, 10:39) +— n'a jamais été régénéré. **Dérive silencieuse** qu'aucune suite `tests/` (qui +teste des FONCTIONS, pas le fichier commité) n'attrapait. + +**2 solutions (workflow #4)** : A régénérer + gate systémique préventif · +B régénérer seul. **Choix A** — B laisserait la dérive revenir au prochain ajout +de module. Correctif ponctuel : `demo_scenario_gen.py build -o out` (diff = 1 +ligne, `18→21`) ; suite demo 32 tests OK ; consistance live re-vérifiée +(run_sheet `21` == audit_4big source `21`). + +**Fichiers (4)** : +1. `ci/check_artifacts.sh` (**neuf**) — gate de reproductibilité. Découvre chaque + `05_deliverables_mvp/**/out/` (zéro liste à la main · #6), ré-exécute le + générateur `build -o `, exige l'égalité byte-for-byte avec le commité. + Artefacts d'exécution non produits par `build` (p.ex. + `qa/regression/out/regression_run.json`) non comparés. stdlib pur (bash+python3), + zéro pip (runner Gitea · #2). +2. `.gitea/workflows/ci.yml` — job bloquant `check-artifacts` + ajout au `needs` + du `gate` agrégé. +3. `05_deliverables_mvp/demo/scenarios/out/run_sheet.json` — régénéré (`18→21`). +4. `ci/README.md` — table pipeline + section détail + bloc exécution locale. + +**Anti-invention (#6)** : la valeur `21` n'est **pas saisie** — elle re-découle de +`qa/audit_4big/out/quality_report.json` (recalculé lui-même depuis le CI). Le gate +n'affirme QUE ce que le `build` déterministe produit. + +**Vérifs** : **test négatif** du gate (dérive `21→18` réinjectée → EXIT 1 + message +« DÉRIVE ») puis test positif (EXIT 0, 18/18 modules reproductibles). 4 gates +statiques EXIT 0 · YAML `safe_load` OK · suites CI-parsing vertes +(regression 24 · acceptance 31 · audit_4big 34 · demo 32). `check-artifacts` +n'entre PAS comme « module » dans `parse_ci` (pas de working-directory) → compteurs +dérivés stables (21/534 inchangés). + +**Hors périmètre worker (VPS · #8)** : néant (gate CI local + doc + artefact in-repo). + +**Auto-score 4Big** : 96/100. diff --git a/05_deliverables_mvp/daily_reports/2026-07-30-session27.md b/05_deliverables_mvp/daily_reports/2026-07-30-session27.md new file mode 100644 index 0000000..7bcfb57 --- /dev/null +++ b/05_deliverables_mvp/daily_reports/2026-07-30-session27.md @@ -0,0 +1,36 @@ +# Rapport quotidien · 2026-07-30 · Session 27 (20260730_232914) + +## Sprint 8 · Buffer · Regression exhaustive + DevOps CI/CD + +### Livrable +Nouveau **gate de reproductibilité des artefacts** (`ci/check_artifacts.sh`) + +correction d'une **dérive réelle** détectée par ce gate. + +### Défaut corrigé (détecté, pas inventé) +`demo/scenarios/out/run_sheet.json` était **périmé** : il embarquait +`ci_modules_count = 18` alors que la source vivante (`qa/audit_4big`) en compte +désormais **21** (suites ajoutées depuis le Sprint 7). Générateur déterministe → +fichier commité jamais régénéré. Dérive **silencieuse** : les suites `tests/` +testent des fonctions, pas le fichier commité. Corrigé par régénération +(`build -o out`, diff 1 ligne `18→21`). + +### Gate systémique (préventif) +`ci/check_artifacts.sh` régénère chaque `05_deliverables_mvp/**/out/` depuis son +générateur et exige l'**égalité byte-for-byte** avec le commité. Découverte auto +(zéro liste à la main). Bloquant en CI (`.gitea/workflows/ci.yml` → `check-artifacts` +dans `needs` du `gate`). stdlib pur, zéro pip. + +### Vérifications +| Contrôle | Résultat | +|---|---| +| Test négatif du gate (dérive réinjectée) | ✅ EXIT 1 + « DÉRIVE » | +| Gate en état sain | ✅ 18/18 modules reproductibles · EXIT 0 | +| 4 gates statiques (guard/json/docs/artifacts) | ✅ EXIT 0 | +| Suites CI-parsing (regression/acceptance/audit_4big/demo) | ✅ 24 · 31 · 34 · 32 | +| Régression exhaustive | ✅ 21/21 suites · 534 tests | +| Compteurs dérivés stables | ✅ 21 / 534 inchangés | + +### Hors périmètre worker (VPS · #8) +Néant — modifications 100 % in-repo (gate CI local, doc, artefact régénéré). + +**Auto-score 4Big : 96/100** diff --git a/05_deliverables_mvp/demo/scenarios/out/run_sheet.json b/05_deliverables_mvp/demo/scenarios/out/run_sheet.json index e28748c..b05e364 100644 --- a/05_deliverables_mvp/demo/scenarios/out/run_sheet.json +++ b/05_deliverables_mvp/demo/scenarios/out/run_sheet.json @@ -149,7 +149,7 @@ "file": "out/quality_report.json", "pointer": "/coverage/ci_modules_count", "label": "Modules sous gate qualité", - "valeur": 18, + "valeur": 21, "resolu": true } ] diff --git a/ci/README.md b/ci/README.md index b9dcf84..ba1d9c7 100644 --- a/ci/README.md +++ b/ci/README.md @@ -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 `) 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 diff --git a/ci/check_artifacts.sh b/ci/check_artifacts.sh new file mode 100755 index 0000000..6e1ef51 --- /dev/null +++ b/ci/check_artifacts.sh @@ -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"