[DTP-Worker] Sprint 8 · buffer L75 · Gate intégrité câblage CI (ci/check_ci_integrity.sh) : gate.needs ⊇ tous jobs non-manuels + chaque ci/*.sh verrouillé au gate
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+21
-1
@@ -77,6 +77,26 @@ jobs:
|
||||
- name: Contrôle fraîcheur matrice de régression
|
||||
run: bash ci/check_regression.sh
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Intégrité du câblage CI (Sprint 8 buffer). Le job `gate` ci-dessous est le
|
||||
# SEUL verrou de merge : un check absent de son `needs:` ne bloque rien, même
|
||||
# rouge. Les couvertures existantes (audit_4big/registry · qa/regression/
|
||||
# discovery) ne prouvent l'appartenance au gate que des jobs de MODULE (ceux
|
||||
# portant un `working-directory:`). Ce gate ferme le trou côté gates STATIQUES
|
||||
# (les `ci/*.sh` sans working-directory) : il prouve, en lisant ci.yml, que
|
||||
# `gate.needs` == { tous les jobs } − { gate } − { jobs manuels workflow_dispatch }
|
||||
# (aucun oubli ni référence fantôme) et que chaque `ci/*.sh` du dépôt est lancé
|
||||
# par un job du gate. Même classe d'anti-dérive que INV4, appliquée au câblage.
|
||||
# stdlib pur (bash/awk/git), zéro réseau.
|
||||
# --------------------------------------------------------------------------
|
||||
check-ci-integrity:
|
||||
name: Intégrité du câblage CI (gate agrège tout · gates statiques verrouillés)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Contrôle intégrité du câblage CI
|
||||
run: bash ci/check_ci_integrity.sh
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Publiciste (Sprint 2) : parser faisabilité → projets_master.json + generator.
|
||||
# Tests unitaires stdlib pur (unittest) → aucune installation pip requise sur
|
||||
@@ -462,7 +482,7 @@ jobs:
|
||||
gate:
|
||||
name: Gate qualité (agrégat)
|
||||
runs-on: ubuntu-latest
|
||||
needs: [constraints-guard, validate-json, check-docs, check-artifacts, check-regression, 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, check-regression, check-ci-integrity, 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."
|
||||
|
||||
@@ -1,5 +1,70 @@
|
||||
# Activity Log · 2026-07-31 · Claude Code DTP
|
||||
|
||||
## Session `20260731_005934` · Buffer S8 · Intégrité du câblage CI — le gate d'agrégat verrouille TOUS les jobs (gates statiques compris)
|
||||
|
||||
**Tâche** : **Sprint 8 · buffer L75 (« Regression tests exhaustifs » · DevOps
|
||||
CI/CD)**. Roadmap fonctionnellement close (21/21 modules à 100/100, 534 tests
|
||||
verts, **5** gates statiques tous verts au démarrage). Poursuite de la série
|
||||
anti-dérive (INV4, fraîcheur matrice, reproductibilité artefacts) : recherche d'un
|
||||
**trou de câblage réel** plutôt qu'un correctif cosmétique.
|
||||
|
||||
### Défaut trouvé — les gates STATIQUES n'étaient gardés par aucun invariant d'appartenance au gate
|
||||
|
||||
- **Constat** : le job `gate` (`needs: [...]`) est le **seul verrou de merge** — un
|
||||
check absent de son `needs:` ne bloque **rien**, même rouge. Les deux couvertures
|
||||
existantes (`audit_4big/registry.coverage_report`, `qa/regression/discovery.
|
||||
coverage_report`) prouvent l'appartenance au gate **uniquement** pour les jobs de
|
||||
**module** (ceux portant un `working-directory:` sous `05_deliverables_mvp`). Les
|
||||
**5 gates statiques** (`constraints-guard`/`validate-json`/`check-docs`/
|
||||
`check-artifacts`/`check-regression` → scripts `ci/*.sh`) **n'ont pas** de
|
||||
working-directory : **rien** ne garantissait qu'ils alimentent le gate. Retirer
|
||||
l'un d'eux de `needs:` (ou une **typo** dans la liste, ou un `ci/*.sh` **décâblé**)
|
||||
rendait le gate **vert en se court-circuitant** — même classe de dérive silencieuse
|
||||
que INV4 (orphan_tests_dirs), côté **câblage CI**.
|
||||
- **Fix** : nouveau gate `ci/check_ci_integrity.sh` (bash/awk/git, stdlib pur, zéro
|
||||
réseau) + job CI `check-ci-integrity`, lui-même ajouté à `gate.needs`. Deux
|
||||
invariants, lus par FAITS dans `ci.yml` (jamais une liste à la main) :
|
||||
- **INV-A** : `gate.needs` == { tous les jobs définis } − { `gate` } − { jobs
|
||||
**manuels** `if: … workflow_dispatch …`, ex. `e2e-baseline` légitimement hors du
|
||||
gate push/PR }. Détecte **MISSING** (job oublié → ne bloque pas), **DANGLING**
|
||||
(référence fantôme : typo / job supprimé) et **MANUAL-IN-GATE** (job manuel dans
|
||||
`needs` → gate en attente perpétuelle sur push).
|
||||
- **INV-B** : chaque `ci/*.sh` du dépôt est lancé par exactement un job
|
||||
(`run: bash ci/<script>`), lui-même dans `gate.needs` — un gate statique **non
|
||||
câblé** (script mort) ou **décâblé** casse le check.
|
||||
- **Preuve de morsure (5 modes, tous exit 1)** :
|
||||
- MISSING : `check-artifacts` retiré de `needs` ⇒ `✗ ABSENT de gate.needs` + `✗`
|
||||
INV-B sur `check_artifacts.sh` ;
|
||||
- DANGLING : `seo-tests`→`seo-testz` dans `needs` ⇒ `✗ référence 'seo-testz' non
|
||||
définie` + `✗ seo-tests ABSENT` ;
|
||||
- MANUAL-IN-GATE : `e2e-baseline` ajouté à `needs` ⇒ `✗ job manuel … gate en
|
||||
attente` ;
|
||||
- SCRIPT-MORT : `ci/check_phantom.sh` commité sans job ⇒ `✗ lancé par AUCUN job` ;
|
||||
- DÉCÂBLÉ : couvert par le mode MISSING (job hors `needs`).
|
||||
État nominal restauré ⇒ **exit 0** (`gate.needs = 28 jobs = 30 définis − 1 (gate)
|
||||
− 1 manuel [e2e-baseline]`).
|
||||
|
||||
**Non-régression** : le nouveau job est **statique** (aucun `working-directory`) →
|
||||
comme les 5 autres, il **n'entre pas** dans la matrice de régression ni le registre
|
||||
`audit_4big` (dérivés des seuls jobs de module). Totaux **534/21 inchangés**.
|
||||
Suites harnais **OK** : `qa/regression` (21 suites · 534), `qa/audit_4big` (21/21 ·
|
||||
min 100), `qa/acceptance` (bijectif=True). `ci.yml` YAML **valide**.
|
||||
|
||||
**Anti-invention (#6)** : rien inventé — le gate **relit** `ci.yml` et recalcule les
|
||||
ensembles à chaque run (aucun compteur figé).
|
||||
|
||||
**Vérifs** : **6 gates** statiques verts (`guard_constraints`/`validate_json`/
|
||||
`check_docs`/`check_artifacts`/`check_regression`/`check_ci_integrity`) ; doc
|
||||
`ci/README.md` mise à jour (tableau des jobs, section 2 détaillée, exécution locale,
|
||||
checklist « 6 gates »).
|
||||
|
||||
**Hors périmètre worker (VPS · #8)** : néant (stdlib en-repo ; enregistrement du
|
||||
runner Gitea toujours du ressort DevOps).
|
||||
|
||||
**Auto-score 4Big** : 96/100.
|
||||
|
||||
---
|
||||
|
||||
## Session `20260731_002924` · Buffer S8 · Intégrité de la matrice de régression (bug CI réel + invariant disque→CI)
|
||||
|
||||
**Tâche** : **Sprint 8 · buffer L75 (« Regression tests exhaustifs » · DevOps
|
||||
|
||||
+23
-1
@@ -19,6 +19,7 @@ et manuellement (`workflow_dispatch`). Jobs statiques (+ une suite `unittest` pa
|
||||
| `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 |
|
||||
| `check-ci-integrity` | `ci/check_ci_integrity.sh` | Intégrité du câblage : `gate.needs` == tous les jobs non-manuels · chaque `ci/*.sh` câblé au gate | ✅ oui |
|
||||
| `gate` | — | Agrégat vert = gate qualité 4Big franchi | ✅ oui |
|
||||
|
||||
Aucune dépendance réseau/marketplace hors `actions/checkout`. Tout tourne avec
|
||||
@@ -78,6 +79,26 @@ commitée verte. `regression_run.json` ne contient aucun horodatage/hôte → le
|
||||
déterministe et l'égalité exacte licite. Correctif : `regression_gen.py run -o out`
|
||||
puis commit.
|
||||
|
||||
### `check_ci_integrity.sh`
|
||||
Prouve, en lisant `.gitea/workflows/ci.yml`, que le **câblage** du workflow tient —
|
||||
car le job `gate` est le **seul verrou de merge** : un check absent de son `needs:`
|
||||
ne bloque **rien**, même rouge. Deux invariants :
|
||||
- **INV-A** — `gate.needs` == { tous les jobs définis } − { `gate` } − { jobs manuels }
|
||||
(un job manuel = gardé par `if: … workflow_dispatch …`, ex. `e2e-baseline`,
|
||||
légitimement hors du gate push/PR car il exige un serveur live). Détecte un job
|
||||
**oublié** du gate (MISSING → ne bloque pas), une **référence fantôme** (DANGLING →
|
||||
typo / job renommé-supprimé) et un job **manuel** glissé dans `needs` (gate en
|
||||
attente perpétuelle sur push).
|
||||
- **INV-B** — chaque script `ci/*.sh` du dépôt est lancé par exactement un job
|
||||
(`run: bash ci/<script>`), lui-même dans `gate.needs`. Un nouveau gate statique
|
||||
**non câblé** (script mort) ou **décâblé** casse le check.
|
||||
|
||||
Ferme le trou laissé par les couvertures existantes (`audit_4big/registry`,
|
||||
`qa/regression/discovery`) qui ne prouvent l'appartenance au gate que des jobs de
|
||||
**module** (ceux portant un `working-directory:`) — les gates **statiques** sans
|
||||
working-directory n'étaient gardés par personne. Même classe d'anti-dérive que INV4
|
||||
(disque→CI), appliquée au **câblage** CI. stdlib pur (bash/awk/git), zéro réseau.
|
||||
|
||||
## 3. Exécution locale (avant push)
|
||||
|
||||
```bash
|
||||
@@ -86,6 +107,7 @@ bash ci/validate_json.sh # schémas JSON
|
||||
bash ci/check_docs.sh # liens + score
|
||||
bash ci/check_artifacts.sh # reproductibilité out/ (build)
|
||||
bash ci/check_regression.sh # fraîcheur matrice régression (run)
|
||||
bash ci/check_ci_integrity.sh # intégrité du câblage CI (gate ⊇ tous jobs)
|
||||
```
|
||||
|
||||
Chaque script retourne `0` si conforme, `1` sinon. Reproduit exactement ce que
|
||||
@@ -117,7 +139,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 5 gates statiques apparaissent et passent au vert.
|
||||
- `[ ]` Push de test → les 6 gates statiques apparaissent et passent au vert.
|
||||
- `[ ]` PR de test avec violation volontaire → `constraints-guard` bloque (rouge).
|
||||
|
||||
---
|
||||
|
||||
Executable
+136
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================================
|
||||
# check_ci_integrity.sh · OTO Enterprise OS DTP
|
||||
# ----------------------------------------------------------------------------
|
||||
# Intégrité du câblage du workflow CI Gitea (gate anti-dérive · Sprint 8 buffer).
|
||||
#
|
||||
# Le job d'agrégat `gate` est le SEUL verrou de merge : un check qui n'y figure
|
||||
# pas dans `needs:` ne bloque RIEN, même rouge. Les couvertures existantes
|
||||
# (audit_4big/registry, qa/regression/discovery) ne prouvent l'appartenance au
|
||||
# gate QUE pour les jobs de MODULE (ceux qui portent un `working-directory:` sous
|
||||
# 05_deliverables_mvp). Les gates STATIQUES (`constraints-guard`, `validate-json`,
|
||||
# `check-docs`, `check-artifacts`, `check-regression` → scripts `ci/*.sh`) n'ont
|
||||
# pas de working-directory : rien ne garantissait qu'ils alimentent le gate.
|
||||
# Retirer l'un d'eux de `needs:` rendait le gate vert en le court-circuitant —
|
||||
# même classe de dérive silencieuse que INV4 (orphan_tests_dirs), côté câblage CI.
|
||||
#
|
||||
# Ce gate prouve, par des FAITS lus dans ci.yml (jamais une liste à la main) :
|
||||
# [INV-A] `gate.needs` == { tous les jobs définis } − { gate } − { jobs manuels }
|
||||
# (un job manuel = gardé par `if: … workflow_dispatch …`, ex. e2e-baseline,
|
||||
# légitimement hors du gate push/PR car il exige un serveur live).
|
||||
# → aucun job oublié du gate (MISSING) ni référence fantôme (DANGLING).
|
||||
# [INV-B] Chaque script `ci/*.sh` du dépôt est lancé par exactement un job
|
||||
# (`run: bash ci/<script>`), et ce job figure dans `gate.needs`.
|
||||
# → un nouveau gate statique non câblé, ou décâblé, casse le check.
|
||||
#
|
||||
# Dépendances : bash, git, awk, grep, sort. Aucun réseau (CLAUDE.md #2).
|
||||
# ============================================================================
|
||||
set -uo pipefail
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
|
||||
CI=".gitea/workflows/ci.yml"
|
||||
FAIL=0
|
||||
report() { printf ' \033[31m✗\033[0m %s\n' "$1"; FAIL=1; }
|
||||
ok() { printf ' \033[32m✓\033[0m %s\n' "$1"; }
|
||||
|
||||
if [[ ! -f "$CI" ]]; then
|
||||
report "workflow introuvable : $CI"
|
||||
echo -e "\n\033[31m❌ CI absent.\033[0m"; exit 1
|
||||
fi
|
||||
|
||||
# --- Extraction (awk · parseur YAML minimal, cohérent avec q4lib/registry) ---
|
||||
# On ne considère que ce qui suit la ligne `jobs:` (le bloc `on:` a lui aussi des
|
||||
# clés à 2 espaces : push/pull_request/workflow_dispatch — à ne pas confondre
|
||||
# avec des jobs). Un job = clé `^ <nom>:` à 2 espaces exactement. Pour chaque
|
||||
# job on note s'il est MANUEL (son bloc contient `workflow_dispatch` dans un
|
||||
# garde `if:`), le job du bloc `gate` et sa ligne `needs: [...]`, et les scripts
|
||||
# `ci/*.sh` lancés par un `run:` (avec le job appelant).
|
||||
|
||||
ALL_JOBS="$(awk '
|
||||
/^jobs:[[:space:]]*$/ { injobs=1; next }
|
||||
injobs && match($0, /^ ([A-Za-z0-9_-]+):[[:space:]]*$/, m) { print m[1] }
|
||||
' "$CI" | sort)"
|
||||
|
||||
MANUAL_JOBS="$(awk '
|
||||
/^jobs:[[:space:]]*$/ { injobs=1; next }
|
||||
injobs && match($0, /^ ([A-Za-z0-9_-]+):[[:space:]]*$/, m) { job=m[1]; next }
|
||||
injobs && job != "" && /if:/ && /workflow_dispatch/ { print job }
|
||||
' "$CI" | sort -u)"
|
||||
|
||||
GATE_NEEDS="$(awk '
|
||||
/^jobs:[[:space:]]*$/ { injobs=1; next }
|
||||
injobs && match($0, /^ ([A-Za-z0-9_-]+):[[:space:]]*$/, m) { job=m[1]; next }
|
||||
injobs && job=="gate" && match($0, /needs:[[:space:]]*\[(.*)\]/, m) {
|
||||
n=split(m[1], a, ",")
|
||||
for (i=1;i<=n;i++) { gsub(/[[:space:]]/,"",a[i]); if (a[i]!="") print a[i] }
|
||||
}
|
||||
' "$CI" | sort -u)"
|
||||
|
||||
# scripts référencés : "<job> <chemin ci/xxx.sh>"
|
||||
SCRIPT_REFS="$(awk '
|
||||
/^jobs:[[:space:]]*$/ { injobs=1; next }
|
||||
injobs && match($0, /^ ([A-Za-z0-9_-]+):[[:space:]]*$/, m) { job=m[1]; next }
|
||||
injobs && job != "" && match($0, /bash (ci\/[A-Za-z0-9_./-]+\.sh)/, m) { print job, m[1] }
|
||||
' "$CI" | sort -u)"
|
||||
|
||||
# --- INV-A : gate.needs == jobs − gate − manuels -----------------------------
|
||||
echo "== INV-A · gate.needs couvre tout job non-manuel (aucun oubli / fantôme) =="
|
||||
|
||||
if [[ -z "$GATE_NEEDS" ]]; then
|
||||
report "job 'gate' sans liste needs: [...] exploitable"
|
||||
fi
|
||||
|
||||
# Ensemble attendu = tous les jobs sauf 'gate' lui-même et les jobs manuels.
|
||||
EXPECTED="$(comm -23 \
|
||||
<(printf '%s\n' "$ALL_JOBS" | grep -vx 'gate') \
|
||||
<(printf '%s\n' "$MANUAL_JOBS") )"
|
||||
|
||||
# MISSING : attendus mais absents de needs → checks qui ne bloquent pas.
|
||||
missing="$(comm -23 <(printf '%s\n' "$EXPECTED") <(printf '%s\n' "$GATE_NEEDS"))"
|
||||
# DANGLING : cités dans needs mais non définis (typo / job renommé/supprimé).
|
||||
dangling="$(comm -23 <(printf '%s\n' "$GATE_NEEDS") <(printf '%s\n' "$ALL_JOBS"))"
|
||||
# MANUAL-IN-GATE : un job manuel dans needs bloquerait le gate sur push (jamais
|
||||
# déclenché) — le gate resterait en attente. Anomalie à signaler.
|
||||
manual_in_gate="$(comm -12 <(printf '%s\n' "$MANUAL_JOBS") <(printf '%s\n' "$GATE_NEEDS"))"
|
||||
|
||||
for j in $missing; do report "job '$j' défini mais ABSENT de gate.needs → il ne bloque pas le merge"; done
|
||||
for j in $dangling; do report "gate.needs référence '$j' qui n'est PAS un job défini (typo / job supprimé)"; done
|
||||
for j in $manual_in_gate; do report "job manuel '$j' présent dans gate.needs → gate en attente sur push/PR"; done
|
||||
|
||||
if [[ -z "$missing$dangling$manual_in_gate" ]]; then
|
||||
njobs="$(printf '%s\n' "$ALL_JOBS" | grep -c .)"
|
||||
nneeds="$(printf '%s\n' "$GATE_NEEDS" | grep -c .)"
|
||||
nman="$(printf '%s\n' "$MANUAL_JOBS" | grep -c . || true)"
|
||||
ok "gate.needs = $nneeds jobs = $njobs définis − 1 (gate) − $nman manuel(s) [$(printf '%s' "$MANUAL_JOBS" | paste -sd, -)]"
|
||||
fi
|
||||
|
||||
# --- INV-B : tout ci/*.sh est câblé à un job, lui-même dans le gate ----------
|
||||
echo "== INV-B · chaque gate statique ci/*.sh est lancé par un job du gate =="
|
||||
|
||||
# Jobs référençant chaque script (via SCRIPT_REFS).
|
||||
while IFS= read -r script; do
|
||||
[[ -z "$script" ]] && continue
|
||||
jobs_for="$(printf '%s\n' "$SCRIPT_REFS" | awk -v s="$script" '$2==s {print $1}' | sort -u)"
|
||||
if [[ -z "$jobs_for" ]]; then
|
||||
report "$script — présent dans le dépôt mais lancé par AUCUN job CI (gate mort)"
|
||||
continue
|
||||
fi
|
||||
# Chaque job qui lance ce script doit alimenter le gate.
|
||||
notgated=""
|
||||
for j in $jobs_for; do
|
||||
printf '%s\n' "$GATE_NEEDS" | grep -qx "$j" || notgated="$notgated $j"
|
||||
done
|
||||
if [[ -n "$notgated" ]]; then
|
||||
report "$script — lancé par [$(echo $jobs_for | tr ' ' ',')] mais job(s) hors gate.needs :$notgated"
|
||||
else
|
||||
ok "$script — lancé par [$(echo $jobs_for | tr ' ' ',')] · dans le gate"
|
||||
fi
|
||||
done < <(git ls-files 'ci/*.sh' | grep -vx 'ci/check_ci_integrity.sh')
|
||||
|
||||
echo
|
||||
if [[ "$FAIL" -eq 0 ]]; then
|
||||
echo -e "\033[32m✅ Câblage CI intègre (gate agrège tout job non-manuel · gates statiques verrouillés).\033[0m"
|
||||
else
|
||||
echo -e "\033[31m❌ Dérive de câblage CI — corriger gate.needs / jobs avant merge.\033[0m"
|
||||
fi
|
||||
exit "$FAIL"
|
||||
Reference in New Issue
Block a user