diff --git a/03_agents/qa/AGENT.md b/03_agents/qa/AGENT.md index f65b6c1..af76ce0 100644 --- a/03_agents/qa/AGENT.md +++ b/03_agents/qa/AGENT.md @@ -32,7 +32,7 @@ périmètre — un auditeur ne s'audite jamais. **conformité** normative des hand-off. ## Verdict agrégé courant (source `qa/regression/out/regression_run.json`) -**21 suites gated · 534 tests · 534 passés · 0 échec · 0 erreur → verdict `PASS`.** +**22 suites gated · 551 tests · 551 passés · 0 échec · 0 erreur → verdict `PASS`.** Ce compte est **découvert automatiquement depuis le CI** (`q4lib.registry.parse_ci`, réutilisé sans duplication) — jamais compté à la main. diff --git a/05_activity_log/2026-07-31.md b/05_activity_log/2026-07-31.md index 7da48a3..275e724 100644 --- a/05_activity_log/2026-07-31.md +++ b/05_activity_log/2026-07-31.md @@ -1,5 +1,71 @@ # Activity Log · 2026-07-31 · Claude Code DTP +## Session `20260731_033001` · Buffer S8 · « Vert trompeur » : la fiche QA affichait un « verdict agrégé courant » PÉRIMÉ (21/534) en se disant « jamais compté à la main » — hors du périmètre du gate README + +**Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA). Roadmap fonctionnellement +close (22/22 modules 100/100, 551 tests verts, **7** gates statiques). Poursuite de la +série anti-dérive : le gate `check_readme_claims` créé la session précédente ne garde que +`README.md` ; recherche d'une **même classe de faux-vert hors de son périmètre**. + +### Défaut trouvé — 3 chiffres périmés en SILENCE dans `03_agents/qa/AGENT.md` et `ci/README.md` + +- **Mécanisme** : la session `030001` a corrigé le README **et** claimé avoir corrigé « 3 + commentaires CI (21 suites · 534 tests) dans `ci.yml` et `check_regression.sh` ». Mais + son propre gate ne couvre **que** `README.md`. Deux autres docs d'entrée portaient + encore le compte **périmé** — invisibles pour tous les gates (`check_docs` ne valide que + les liens) : + - **`03_agents/qa/AGENT.md:35`** (le pire) : section « Verdict agrégé courant » → + *« **21 suites gated · 534 tests · 534 passés · 0 échec · 0 erreur → verdict `PASS`** »* + en déclarant, ligne suivante, *« jamais compté à la main »*. C'était pourtant un + compte **figé à la main** qui cite `qa/regression/out/regression_run.json` comme + source — alors que le run réel dit **22 / 551**. Une fiche d'agent QA qui **affirme + une matrice verte avec des chiffres faux** est le « vert trompeur » le plus grave. + - **`ci/README.md:80`** : exemple « compteurs cités partout » figé à « 21 suites · 534 + tests · PASS » (→ 22 · 551). + - **`ci/README.md:184`** : checklist DevOps « les **6 gates** statiques » (→ **7** depuis + l'ajout de `check-readme-claims`). +- **Preuve au sol** : `git grep -nE '534|21 suites|6 gates'` sur `*.md` (hors logs) + ramenait les 3 lignes ; `regression_run.json` recalculé = **22 suites · 551 tests · 551 + passés · 0 échec · 0 erreur · PASS** (somme des 22 suites du run commité). + +### Fix (2 solutions · CLAUDE.md #4 — retenu : correction + extension du gate au-delà du README) + +- **Correction des 3 chiffres** pour refléter les artefacts (fiche QA 21/534→22/551 ; + `ci/README.md` exemple 21·534→22·551 et checklist 6→7 gates). Ligne 132 « Les 6 gates + commençaient… » → « Les gates… » (le compte historique contredisait la table §1 à 7). +- **Correctif de fond** : **extension de `ci/check_readme_claims.sh`** (7ᵉ gate) au-delà du + README. Il **RECOMPUTE** désormais aussi le « Verdict agrégé courant » de la fiche QA + depuis `regression_run.json` (compte agrégé faisant autorité, commité · déterministe) : + `N suites · M tests · M passés · E échec · E erreur · verdict`, exige l'égalité avec ce + qui est **écrit** dans `03_agents/qa/AGENT.md`. Un claim **absent** échoue AUSSI. *(Alt. + écartée : 2ᵉ gate dédié — même classe de contrôle « valeur↔artefact » ; l'ajouter au gate + existant évite un doublon de câblage CI · #5.)* Le nom de fichier reste inchangé (pas de + ré-câblage `ci.yml`/INV-B) ; en-tête du script + `ci/README.md` §1 table + §2 détail mis + à jour pour la portée élargie (README **et** fiche QA). + +### Preuves de morsure + +- **Bite 1** (valeur périmée) : réintroduit « 21 suites · 534 tests » ⇒ gate **exit 1** + `✗ Fiche QA … dit 21/534/534/0/0/PASS MAIS regression_run dit 22/551/551/0/0/PASS`. +- **Bite 2** (claim disparu) : retire toute la ligne « Verdict agrégé » ⇒ **exit 1** + `✗ Fiche QA · verdict agrégé — claim INTROUVABLE`. +- **Restauré** ⇒ **exit 0** (les 6 claims README + le claim QA verts). + +**Anti-invention (#6)** : rien inventé — le gate **recompute** la vérité depuis +`regression_run.json` (somme des suites du run commité) au lieu de figer un compteur ; les +3 chiffres corrigés sont tous recoupés sur cet artefact. + +**Non-régression** : **7 gates** locaux verts, dont `check_artifacts` (rebuild + égalité +byte de tous les `out/`) ⇒ **aucune dérive d'artefact** (l'audit_4big ne lit ni `03_agents/` +ni `ci/` comme evidence → pas de régénération `quality_report`). Matrice **551/22 · PASS +inchangée**. Unique « 534 » résiduel = description **historique** volontaire du défaut +corrigé (`ci/README.md:130`, comme l'anecdote « demo 18→21 »). + +**Hors périmètre worker (VPS · #8)** : néant (stdlib pur en-repo ; runner Gitea toujours +du ressort DevOps). + +**Auto-score 4Big** : 96/100. + ## Session `20260731_030001` · Buffer S8 · « Vert trompeur » : le README (point d'entrée) affichait des chiffres PÉRIMÉS que sa propre source contredisait — aucun gate ne les enforçait **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA). Roadmap fonctionnellement diff --git a/ci/README.md b/ci/README.md index 140c80f..5cc2846 100644 --- a/ci/README.md +++ b/ci/README.md @@ -20,7 +20,7 @@ et manuellement (`workflow_dispatch`). Jobs statiques (+ une suite `unittest` pa | `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 | -| `check-readme-claims` | `ci/check_readme_claims.sh` | Intégrité des chiffres du README : chaque nombre de « État courant » == artefact cité (#6) | ✅ oui | +| `check-readme-claims` | `ci/check_readme_claims.sh` | Intégrité des chiffres des docs d'entrée : chaque nombre de `README.md` « État courant » **et** de la fiche QA « Verdict agrégé » == artefact cité (#6) | ✅ oui | | `gate` | — | Agrégat vert = gate qualité 4Big franchi | ✅ oui | Aucune dépendance réseau/marketplace hors `actions/checkout`. Tout tourne avec @@ -77,8 +77,8 @@ Rejoue la **matrice de régression** complète (`qa/regression/regression_gen.py de `check_artifacts` : celui-ci ne rejoue que `build` (→ `regression_plan.json`) et laisse hors périmètre l'artefact d'**exécution** `regression_run.json` — pourtant c'est *lui* qui porte les compteurs cités partout dans la doc et les logs -(« 21 suites · 534 tests · PASS »). Sans ce gate, ce chiffre pouvait se périmer en -silence (module + job CI ajoutés sans régénérer la matrice → « 21 suites » faux ; +(« 22 suites · 551 tests · PASS »). Sans ce gate, ce chiffre pouvait se périmer en +silence (module + job CI ajoutés sans régénérer la matrice → compte de suites faux ; c'est la dérive « demo 18→21 » corrigée à la main), ou une matrice rouge être commitée verte. `regression_run.json` ne contient aucun horodatage/hôte → le run est déterministe et l'égalité exacte licite. Correctif : `regression_gen.py run -o out` @@ -118,18 +118,24 @@ README se **périment en silence** tout en restant « sourcés » vers un artefa autre chose — un README qui se **contredit avec sa propre source** est un « vert trompeur » (même classe que la matrice périmée « demo 18→21 » ou INV4, appliqué à la doc d'entrée). Ce gate **recompute** chaque chiffre depuis l'artefact cité (jamais une -liste à la main · #6) et exige l'égalité avec ce qui est **écrit** : +liste à la main · #6) et exige l'égalité avec ce qui est **écrit** dans les deux +docs d'entrée — `README.md` : - modules gated `N/M` à `K/100` + verdict `PASS` → `qa/audit_4big/out/quality_report.json` (`totals`) ; - `N` suites gated → `qa/regression/out/regression_plan.json` (`totals.suites`) ; - `N` promesses (`X` sprint + `Y` métriques), `Z` `in_repo`, verdict `true` → `qa/acceptance/out/acceptance_matrix.json` ; - « 13 agents » (×2 : nav + titre) → `git ls-files 03_agents/*/AGENT.md`. -Un claim **absent** du README échoue aussi (la dérive de formulation qui ferait +Et la fiche QA `03_agents/qa/AGENT.md` (section « Verdict agrégé courant », qui se +disait « jamais compté à la main » tout en portant un compte figé qui s'est périmé +« 21 suites · 534 tests » vs le run réel `22 / 551 ») : +- `N` suites · `M` tests · `M` passés · `E` échec · `E` erreur · verdict → `qa/regression/out/regression_run.json` (compte agrégé faisant autorité, commité). + +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. ### `lib.sh` — helper partagé (sourcé, jamais exécuté seul) -Les 6 gates commençaient tous par `cd "$(git rev-parse --show-toplevel)"`. **Hors** +Les gates commençaient tous par `cd "$(git rev-parse --show-toplevel)"`. **Hors** d'un arbre de travail git (tarball, `git archive | tar -x`, `git` absent du PATH), `git rev-parse` n'écrit rien → `cd ""` est un **no-op qui RETOURNE SUCCÈS** : le gate poursuivait, `git ls-files` renvoyait une liste **vide**, et `validate_json` / @@ -181,7 +187,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 6 gates statiques apparaissent et passent au vert. +- `[ ]` Push de test → les 7 gates statiques apparaissent et passent au vert. - `[ ]` PR de test avec violation volontaire → `constraints-guard` bloque (rouge). --- diff --git a/ci/check_readme_claims.sh b/ci/check_readme_claims.sh index c9064c6..3ada550 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -19,14 +19,18 @@ # (« demo 18→21 ») ou l'orphan_tests_dirs (INV4) — appliqué à la doc d'entrée. # # Ce gate RECOMPUTE chaque chiffre depuis l'artefact cité (jamais une liste à la -# main · #6) et exige l'égalité avec ce qui est ÉCRIT dans le README : +# main · #6) et exige l'égalité avec ce qui est ÉCRIT dans les docs d'entrée : +# README.md : # • Qualité 4Big N/M modules gated à K/100, verdict → qa/audit_4big totals # • Régression N suites gated → qa/regression plan # • Recette N promesses (X sprint + Y métriques), Z in_repo, verdict # → qa/acceptance matrix # • Agents « 13 agents » (×2 : nav + titre) → 03_agents/*/AGENT.md -# Un claim absent du README échoue AUSSI (la dérive de formulation qui -# ferait disparaître un chiffre est elle-même une régression de traçabilité). +# 03_agents/qa/AGENT.md (fiche QA · « Verdict agrégé courant ») : +# • N suites · M tests · M passés · E échec · E erreur · verdict +# → qa/regression run +# 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é). # # Dépendances : bash, git, python3 (stdlib pur · CLAUDE.md #2 — runner sans pip). # ============================================================================ @@ -135,6 +139,46 @@ if re.search(r"verdict `true`", norm): else: bad("Recette · verdict `true` non cité dans README (attendu)") +# ============================================================================ +# Fiche QA (03_agents/qa/AGENT.md) · même classe de « vert trompeur » — +# ---------------------------------------------------------------------------- +# Sa section « Verdict agrégé courant » affiche un compte agrégé (« N suites +# gated · M tests · M passés · 0 échec · 0 erreur → verdict PASS ») en déclarant +# « jamais compté à la main » et en citant qa/regression/out/regression_run.json. +# Ce compte se PÉRIME pourtant en silence quand une suite est ajoutée : la fiche +# affichait « 21 suites · 534 tests » alors que le run dit 22 / 551. Comme pour +# le README, aucun gate ne comparait la VALEUR au run. On la RECALCULE ici depuis +# le compte agrégé faisant autorité (regression_run.json, commité · déterministe). +# ============================================================================ +QA = "03_agents/qa/AGENT.md" +try: + qa_txt = re.sub(r"\s+", " ", open(QA, encoding="utf-8").read()) +except OSError as e: + bad(f"Fiche QA · {QA} illisible : {e}"); qa_txt = "" + +run = load("qa/regression/out/regression_run.json") +suites = run["suites"] +agg = ( + str(len(suites)), # suites + str(sum(s["ran"] for s in suites)), # tests exécutés + str(sum(s["passed"] for s in suites)), # passés + str(sum(s["failures"] for s in suites)), # échecs + str(sum(s["errors"] for s in suites)), # erreurs + run["verdict"], # verdict +) +qa_pat = (r"\*\*(\d+) suites gated · (\d+) tests · (\d+) passés · " + r"(\d+) échec · (\d+) erreur → verdict `(PASS|FAIL)`") +qa_src = (f"suites={agg[0]} tests={agg[1]} passés={agg[2]} " + f"échec={agg[3]} erreur={agg[4]} verdict={agg[5]}") +mqa = re.search(qa_pat, qa_txt) +if mqa is None: + bad(f"Fiche QA · verdict agrégé — claim INTROUVABLE dans {QA} (attendu {qa_src})") +elif mqa.groups() == agg: + good(f"Fiche QA · verdict agrégé — {QA} {'/'.join(mqa.groups())} == regression_run ({qa_src})") +else: + bad(f"Fiche QA · {QA} dit {'/'.join(mqa.groups())} MAIS regression_run dit " + f"{'/'.join(agg)} ({qa_src}) → régénérer la fiche") + sys.exit(1 if FAIL else 0) PY rc=$?