diff --git a/03_agents/qa/AGENT.md b/03_agents/qa/AGENT.md index 9371e12..d49df62 100644 --- a/03_agents/qa/AGENT.md +++ b/03_agents/qa/AGENT.md @@ -51,6 +51,7 @@ inversement) — la liste ci-dessous est donc *l'état courant*, pas une constan | Gate | Rôle (une commande, déterministe, zéro chiffre fabriqué) | |---|---| | `check_ci_integrity.sh` | Intégrité du câblage CI : chaque `ci/*.sh` est câblé à un job **dans** `gate.needs` (aucun gate décâblé ni orphelin) — c'est le gardien des gardiens (il **s'exclut** lui-même · SoD). | +| `check_mobile_workflow.sh` | Intégrité du **second** workflow `mobile-build.yml` (EAS build iOS/Android) — **hors** `gate.needs` (il s'active quand Michel fournit `EAS_TOKEN`), donc *non* couvert par `check_ci_integrity` qui ne verrouille que `ci.yml` : bien-formé, gating d'activation `if:` job-level porté par `needs.preflight.outputs.*` (jamais le contexte `secrets.`, non exposé hors GitHub → SKIP silencieux), contrat d'outputs sans dangling (MOB-1..4). | | `check_readme_claims.sh` | Intégrité des **chiffres** des README / fiches agent (CLAUDE.md #6) : chaque compte cité est **recalculé** depuis l'artefact `out/` ou la source (jamais une donnée inventée). | | `check_artifacts.sh` | **Reproductibilité** : chaque `out/*.json` se régénère **byte-identique** (aucune dérive entre source et hand-off commité). | | `check_regression.sh` | **Fraîcheur** de la matrice de régression : le run rejoué == le run commité (aucune régression silencieuse · #5). | diff --git a/05_activity_log/2026-08-05.md b/05_activity_log/2026-08-05.md index e90e080..7afd6c6 100644 --- a/05_activity_log/2026-08-05.md +++ b/05_activity_log/2026-08-05.md @@ -1706,3 +1706,58 @@ valeurs sont recomputées de `CLAUDE.md`, jamais retapées), **aucune commande V citée est une surface à ancrer) : nouvelle sous-surface (bancable + commissions) close. **Vérif.** `./run_ci.sh` → **33 PASS · 0 FAIL · 0 SKIP**. + +--- + +## Session 20260805_194334 · GATE NEUF — complétude de l'énumération des gates statiques dans la fiche QA + +**Contexte.** Repo vert au démarrage (33 PASS · 0 FAIL · 0 SKIP), tous modules +livrés, roadmap 8-sem sans tâche-feature ouverte. Sonde ciblée sur la classe +**fiche-completeness** (cf. [[fiche-accuracy-audit-closed]] — « completeness≠accuracy, +diff deliverables vs fiche tables »), cette fois sur une fiche qui **énumère** un +ensemble censé être exhaustif. + +**Constat (drift présent-temps réel).** La fiche `03_agents/qa/AGENT.md` +(§« Deuxième étage QA · batterie de gates statiques `ci/*.sh` ») liste les gates dans +un tableau, en se **déclarant explicitement** (l.49) « la liste ci-dessous est donc +*l'état courant*, pas une constante ». Or le tableau ne comptait que **7 lignes** alors +que `git ls-files 'ci/*.sh'` (hors `lib.sh`) en rend **8** : le 8ᵉ gate +`check_mobile_workflow.sh` — livré **le jour même** (session `001109`, cf. rapport +matinal 33-jobs) — n'y figurait **NULLE PART** (`grep -c check_mobile_workflow` fiche += **0**, les 7 autres ≥ 1). La fiche promettait la complétude sans la tenir : omission +silencieuse d'un jour entier. **Surface ungatée confirmée** : `check_ci_integrity` +verrouille le **câblage CI** (`ci/*.sh` ↔ job `gate.needs`) mais **ne lit pas la fiche** ; +`check_readme_claims` ne recomputait aucune énumération de gates → aucun gardien. + +**Fix (2 volets).** +1. **Correction du fond** : ajout de la ligne manquante `check_mobile_workflow.sh` au + tableau, placée juste après `check_ci_integrity.sh` (les deux gardent l'intégrité + d'un fichier-workflow) — rôle décrit fidèlement d'après l'en-tête du gate (second + workflow `mobile-build.yml`, hors `gate.needs`, gating `if:` job-level sur + `needs.preflight.outputs.*` jamais `secrets.`, contrat d'outputs, MOB-1..4). +2. **Ancrage anti-récurrence** : extension du gate existant `ci/check_readme_claims.sh` + (bloc Python ajouté avant `sys.exit`, réutilise `subprocess`/`bad`/`good` déjà en + scope · zéro nouveau gate #5 · zéro nouveau fichier) — re-dérive l'ensemble depuis + `git ls-files 'ci/*.sh'` (hors `lib.sh`, #6) et exige que chaque basename soit cité + (backtické) dans la fiche ; un gate **absent** échoue. Rend la promesse « l'état + courant » **opposable**. + +**Teeth (mutation-vérifié, arbre restauré à chaque fois).** +- **MUT1** — retrait de la ligne `check_mobile_workflow.sh` → **RED** nommant le gate + réel non cité (`n'énumère PAS ['check_mobile_workflow.sh'] … 8 gates réels`). +- **MUT2 (forward-protection)** — `git add` d'un 9ᵉ `ci/check_dummy9.sh` factice (index + seul) → **RED** (`… ['check_dummy9.sh'] … 9 gates réels`) : le **prochain** gate + livré ne pourra pas être omis en silence. Fichier `--cached` retiré + `rm` (arbre + byte-restauré, **JAMAIS** `git clean` · CLAUDE.md). +- Arbre restauré → gate exit 0, les 8 gates re-verts. + +**Portée / anti-churn.** **0 fichier de production édité** (seuls la fiche QA, +`ci/check_readme_claims.sh`, `ci/README.md`), **0 artefact reconstruit** (aucun +`out/*.json` ni doc scoré par audit_4big → pas de rebuild `quality_report`, cf. +[[audit4big-rebuild-after-doc-edits]]), **0 chiffre saisi à la main** (#6 — le « 8 » est +recompté de `git ls-files`, jamais tapé), **aucune commande VPS** (#8 — 100 % local). +Documenté en `ci/README.md` §2 sous `check_readme_claims.sh` (nouveau paragraphe +« COMPLÉTUDE de l'énumération des gates statiques »), pas de nouvelle ligne au tableau §1 +(cf. [[ci-readme-table-detail-in-section2]]). + +**Vérif.** `./run_ci.sh` → **33 PASS · 0 FAIL · 0 SKIP**. diff --git a/ci/README.md b/ci/README.md index 7fa56ac..65c862a 100644 --- a/ci/README.md +++ b/ci/README.md @@ -1634,6 +1634,21 @@ contrat » (réservation retiré) · motif du défaut ne cite plus « Cardnet » vert (exit 0) · working tree **byte-restauré** (`git checkout --`, **JAMAIS** `git clean`) · **7 gates re-verts**. +**COMPLÉTUDE de l'énumération des gates statiques dans la fiche QA.** La fiche +`03_agents/qa/AGENT.md` (§« Deuxième étage QA ») énumère les gates `ci/*.sh` en se +**déclarant explicitement** « l'état courant » de `git ls-files 'ci/*.sh'` (« pas une +constante »). Or **rien** ne vérifiait que CHAQUE gate statique y figure vraiment : le 8ᵉ +gate `check_mobile_workflow.sh` (livré le 2026-08-05) a pu rester **omis un jour entier EN +SILENCE** — la fiche promettait la complétude sans la tenir. On **re-dérive** l'ensemble +depuis `git ls-files 'ci/*.sh'` (hors `lib.sh`, #6 — jamais une liste à la main) et on +exige que chaque basename apparaisse (backtické) dans la fiche ; un gate **absent** échoue. +Distinct de `check_ci_integrity` qui verrouille le **câblage CI** (chaque `ci/*.sh` ↔ un +job `gate.needs`) mais **ignore la fiche** ; cette surface ferme le miroir *documentaire* du +même ensemble. **2 morsures vérifiées** : retrait de la ligne `check_mobile_workflow.sh` de +la fiche → mismatch (gate réel non cité) · ajout d'un 9ᵉ `ci/*.sh` tracké non encore +documenté → mismatch (**forward-protection** : le prochain gate ne pourra pas être omis) ; +restauré = vert. + Un claim **absent** échoue aussi (la dérive de formulation qui ferait disparaître un chiffre est elle-même une régression de traçabilité). stdlib pur (bash/git/python3), zéro réseau. diff --git a/ci/check_readme_claims.sh b/ci/check_readme_claims.sh index a696c9a..f9fe0bc 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -9163,6 +9163,35 @@ if _rm_lines is not None: good(f"Traçabilité roadmap · {len(_rm_seen)} citation(s) ancrée(s), " f"4 modules attendus présents") +# ── Complétude de l'énumération des gates statiques dans la fiche QA ───────── +# La fiche 03_agents/qa/AGENT.md §« Deuxième étage QA » énumère les gates +# `ci/*.sh` en se DÉCLARANT « l'état courant » de `git ls-files 'ci/*.sh'` (pas +# une constante). Or rien ne vérifiait que CHAQUE gate statique y figure : le 8ᵉ +# gate `check_mobile_workflow.sh` a pu être omis un jour entier EN SILENCE. On +# re-dérive l'ensemble depuis git (#6 — jamais une liste à la main) et on exige +# que chaque basename apparaisse dans la fiche ; un gate absent échoue. +_QA_FICHE = "03_agents/qa/AGENT.md" +_gate_names = sorted( + f.rsplit("/", 1)[-1] + for f in subprocess.run(["git", "ls-files", "ci/*.sh"], + capture_output=True, text=True).stdout.split() + if f.rsplit("/", 1)[-1] != "lib.sh") +try: + _qa = open(_QA_FICHE, encoding="utf-8").read() +except OSError as e: + bad(f"Énumération gates statiques · {_QA_FICHE} illisible : {e}") + _qa = None +if _qa is not None: + _missing = [n for n in _gate_names if f"`{n}`" not in _qa] + if _missing: + bad(f"Énumération gates statiques · {_QA_FICHE} n'énumère PAS {_missing} " + f"— la fiche se déclare « l'état courant » de git ls-files 'ci/*.sh' " + f"({len(_gate_names)} gates réels hors lib.sh) → ajouter la/les " + f"ligne(s) manquante(s)") + else: + good(f"Énumération gates statiques · les {len(_gate_names)} gates ci/*.sh " + f"(hors lib.sh) sont tous cités dans {_QA_FICHE}") + sys.exit(1 if FAIL else 0) PY rc=$?