diff --git a/03_agents/qa/AGENT.md b/03_agents/qa/AGENT.md index 95fd44f..a85e00d 100644 --- a/03_agents/qa/AGENT.md +++ b/03_agents/qa/AGENT.md @@ -22,9 +22,9 @@ périmètre — un auditeur ne s'audite jamais. | Module | Sprint | Rôle | Entrée CLI | Job CI | Tests | |---|---|---|---|---|---| | [`audit_5d/`](../../05_deliverables_mvp/qa/audit_5d/README.md) | 5 (roadmap L57) | Audit **5D** de conformité : 17 contrôles / 5 dimensions, lit les `out/` amont, coche UAF + normes ISA/IFRS | `audit_5d_gen.py build\|validate` | `qa-audit-5d-tests` | 37 | -| [`audit_4big/`](../../05_deliverables_mvp/qa/audit_4big/README.md) | 7 (roadmap L69) | **Gate qualité** : note 4Big de 100 % des livrables, verdict `FAIL` si un module < 95/100 ou couverture incomplète | `audit_4big_gen.py build\|validate` | `qa-audit-4big-tests` | 35 | -| [`regression/`](../../05_deliverables_mvp/qa/regression/README.md) | 8 (roadmap L74) | **Matrice de régression** : agrège l'exécution de **toutes** les suites gated en un verdict unique + le compte agrégé faisant autorité | `regression_gen.py build\|validate\|run` | `qa-regression-tests` | 25 | -| [`acceptance/`](../../05_deliverables_mvp/qa/acceptance/README.md) | 8 (roadmap L75 / L80-87) | **Recette / traçabilité** : mappe chaque promesse roadmap (8 livrables + 7 métriques) vers sa preuve, bijectif vs CI | `acceptance_gen.py build\|validate` | `qa-acceptance-tests` | 31 | +| [`audit_4big/`](../../05_deliverables_mvp/qa/audit_4big/README.md) | 7 (roadmap L69) | **Gate qualité** : note 4Big de 100 % des livrables, verdict `FAIL` si un module < 95/100 ou couverture incomplète | `audit_4big_gen.py build\|validate` | `qa-audit-4big-tests` | 34 | +| [`regression/`](../../05_deliverables_mvp/qa/regression/README.md) | 8 (roadmap L74) | **Matrice de régression** : agrège l'exécution de **toutes** les suites gated en un verdict unique + le compte agrégé faisant autorité | `regression_gen.py build\|validate\|run` | `qa-regression-tests` | 26 | +| [`acceptance/`](../../05_deliverables_mvp/qa/acceptance/README.md) | 8 (roadmap L75 / L80-87) | **Recette / traçabilité** : mappe chaque promesse roadmap (8 livrables + 7 métriques) vers sa preuve, bijectif vs CI | `acceptance_gen.py build\|validate` | `qa-acceptance-tests` | 37 | **Quatre axes distincts, non redondants** (CLAUDE.md #5 · éliminer les doublons) : `audit_4big` note la **qualité statique** · `regression` prouve l'**exécution** · diff --git a/05_activity_log/2026-07-31.md b/05_activity_log/2026-07-31.md index b78568d..a598a86 100644 --- a/05_activity_log/2026-07-31.md +++ b/05_activity_log/2026-07-31.md @@ -733,3 +733,62 @@ compare ; le « 8 + 7 » cesse d'être un compte à la main. **Hors périmètre worker (VPS · #8)** : néant (stdlib pur en-repo). **Auto-score 4Big** : 96/100. + +--- + +## Session `20260731_063014` · Buffer S8 · Comptes de tests PAR SUITE périmés dans la fiche QA — dérive silencieuse (aucun gate ne couvrait le grain fin) + +**Tâche** : **Sprint 8 · buffer L75 (« Regression tests exhaustifs »)** — roadmap +fonctionnellement close (22/22 modules gated · 564 tests · 7 gates verts au démarrage). +Chasse d'un **défaut réel** de la classe « vert trompeur » (#6) plutôt qu'un cosmétique. + +### Défaut trouvé — fiche QA : colonne « Tests » par suite figée, DÉJÀ fausse + +- **Symptôme** : le tableau des livrables de `03_agents/qa/AGENT.md` (col « Tests ») + affichait un compte par suite saisi à la main. Trois lignes étaient **déjà + périmées** au démarrage : `audit_4big` **35** (réel 34), `regression` **25** + (réel 26), `acceptance` **31** (réel 37 — la session précédente avait fait 31→37 + sans propager à cette table). +- **Contradiction** : le gate amont `check_readme_claims` ne couvrait que la **somme + agrégée** (« 564 tests »). Une **compensation** entre deux suites (+1 / −1) + laisserait la somme juste et **les deux lignes fausses** — même classe de « vert + trompeur » que les compteurs agrégés périmés (534→551→558→564) ou INV11, mais au + **grain le plus fin** (par suite), et non gardée. +- **Vérif systématique (#1 analyser)** : recomputé la carte faisant autorité des 22 + suites gated + le self_module → diffusée contre **toutes** les tables de fiches + (`03_agents/*/AGENT.md`). Seule la fiche **QA** dérivait (3 lignes) ; CRM (3), + faisabilité (2), publiciste (1) étaient justes. READMEs de module : justes aussi. + +### Fix (2 volets · corriger + garder) + +1. **`03_agents/qa/AGENT.md`** — 3 nombres corrigés vers le réel : 35→34, 25→26, + 31→37. +2. **`ci/check_readme_claims.sh`** — nouveau bloc générique : scanne **toutes** les + fiches, extrait chaque ligne de table pointant `05_deliverables_mvp//README.md` + + sa cellule « Tests », et **recompute** l'attendu depuis la source faisant + autorité — `regression_plan.json` (`suites[path].test_methods`) pour les 22 suites + gated, et **`reglib.discovery.count_tests`** (même fonction que le plan · zéro + duplication · #5) pour le `self_module` `qa/regression` exclu de la matrice par SoD. + Table « Tests » absente ⇒ red (disparition = régression de traçabilité). + +### Preuves + +- **Passe** : gate vert, **10 lignes par-suite** vérifiées (crm×3 · faisabilite×2 · + publiciste×1 · qa×4), toutes == source. +- **Morsure** : `acceptance` reforcé à 31 ⇒ `✗ … Tests 31 MAIS source dit 37 + (plan.suites/count_tests) → régénérer la fiche` + verdict ❌ ; restauration ⇒ PASS. + +**Régénération des consommateurs** : néant — aucun compte de test n'a changé (fix += docs + gate). Artefacts `out/` **byte-identiques** (`check_artifacts` vert) ; +matrice **564/22 PASS** inchangée. + +**Vérifs** : **7 gates** locaux verts (`guard_constraints` · `validate_json` · +`check_docs` · `check_artifacts` · `check_regression` · `check_ci_integrity` · +`check_readme_claims`) ; matrice **564/22 · PASS** ; 22/22 modules **100/100**. + +**Anti-invention (#6)** : rien inventé — le gate **recompute** chaque cellule depuis +plan.suites / count_tests ; les nombres de la fiche **découlent** de la source. + +**Hors périmètre worker (VPS · #8)** : néant (docs + gate bash/stdlib en-repo). + +**Auto-score 4Big** : 96/100. diff --git a/ci/README.md b/ci/README.md index e1558a7..320abf2 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 des docs d'entrée : chaque nombre de `README.md` « État courant » **et** de la fiche QA « Verdict agrégé » == 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 », de la fiche QA « Verdict agrégé » **et** de la colonne « Tests » par suite des tables de fiches == 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 @@ -135,6 +135,17 @@ Et la fiche ERPNext Backend `03_agents/erpnext_backend/AGENT.md` (ligne « sourc même compte agrégé jadis saisi à la main qui s'était périmé — « 560 tests ») : - `N` tests · `M` suites · verdict → `qa/regression/out/regression_run.json` (matrice de régression du repo, même source faisant autorité que la fiche QA). +Et — au grain le **plus fin** — la colonne « Tests » des **tables de livrables** de +**toutes** les fiches (`03_agents/*/AGENT.md` · crm, faisabilite, publiciste, qa) : +chaque cellule par suite était saisie à la main et se périmait dès qu'un test était +ajouté (fiche QA : `acceptance` 31 alors que la suite en portait 37 · `audit_4big` +35→34 · `regression` 25→26). Le compte **agrégé** ci-dessus (« 564 tests ») ne +suffit pas : une **compensation** entre deux suites (+1 / −1) laisserait la somme +juste et les deux lignes fausses. On recompute donc chaque cellule depuis +`qa/regression/out/regression_plan.json` (`suites[path].test_methods`) — et depuis +`reglib.discovery.count_tests` (même fonction que le plan · zéro duplication) pour le +`self_module` `qa/regression`, exclu de la matrice par SoD mais bien documenté. + 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 2cd31d4..2150917 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -32,6 +32,9 @@ # 03_agents/erpnext_backend/AGENT.md (« source unique ») : # • N tests · M suites · verdict (matrice de régression du repo) # → qa/regression run +# 03_agents/*/AGENT.md (tables de livrables · colonne « Tests ») : +# • chaque cellule « Tests » par suite gatée → plan.suites[path] +# (+ count_tests pour le self_module qa/regression, exclu de la matrice) # 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é). # @@ -216,6 +219,68 @@ else: bad(f"Fiche Backend · {BE} dit {'/'.join(mbe.groups())} MAIS regression_run dit " f"{'/'.join(be_exp)} ({be_src}) → régénérer la fiche") +# ============================================================================ +# Fiches agents · comptes de tests PAR SUITE (colonne « Tests » des tables) — +# même classe de « vert trompeur », au grain le PLUS FIN. +# ---------------------------------------------------------------------------- +# Chaque fiche agent (03_agents/*/AGENT.md) recense ses livrables dans une table +# dont la dernière colonne « Tests » affiche le nombre de méthodes `def test_` de +# la suite. Ce nombre est SAISI À LA MAIN : il se périme dès qu'un test est +# ajouté/retiré. C'est arrivé (fiche QA : `acceptance` disait 31 alors que la +# suite en portait 37 · `audit_4big` 35→34 · `regression` 25→26). Les gates +# amont ne couvraient QUE la SOMME agrégée (« 564 tests ») : une compensation +# entre deux suites (l'une +1, l'autre −1) laisserait la somme juste et les DEUX +# lignes fausses. On RECOMPUTE ici chaque cellule depuis la source faisant +# autorité — plan.suites[path].test_methods pour les suites gated, et +# reglib.discovery.count_tests (même fonction que le plan · zéro duplication) +# pour le self_module (qa/regression, exclu de la matrice par SoD mais bien +# documenté avec son propre compte). +# ============================================================================ +sys.path.insert(0, f"{D}/qa/regression") +try: + from reglib.discovery import count_tests +except Exception as e: # import cassé = red honnête (source de vérité absente) + bad(f"Comptes par suite · import reglib.discovery impossible : {e}") + count_tests = None + +plan = load("qa/regression/out/regression_plan.json") +auth = {s["path"]: s["test_methods"] for s in plan["suites"]} +self_mod = plan["coverage"].get("self_module_excluded") +if count_tests is not None and self_mod: + auth[self_mod] = count_tests(f"{D}/{self_mod}/tests")[1] + +# Ligne de table Tests : lien vers 05_deliverables_mvp//README.md + dernier +# champ numérique. On ne vérifie QUE les lignes pointant une suite gatée connue +# (auth) — toute autre ligne de table est ignorée (pas notre périmètre). +row_re = re.compile( + r"^\|\s*\[`\w+/`\]\([^)]*?05_deliverables_mvp/([\w/]+?)/README\.md\)" + r".*\|\s*(\d+)\s*\|\s*$") +fiches = subprocess.run(["git", "ls-files", "03_agents/*/AGENT.md"], + capture_output=True, text=True).stdout.split() +seen_rows = 0 +for af in fiches: + try: + lines = open(af, encoding="utf-8").read().splitlines() + except OSError as e: + bad(f"Fiche {af} illisible : {e}"); continue + for ln, line in enumerate(lines, 1): + m = row_re.match(line) + if not m: + continue + path = m.group(1) + if path not in auth: # ligne pointant un non-suite → hors périmètre + continue + claimed, exp = int(m.group(2)), auth[path] + seen_rows += 1 + if claimed == exp: + good(f"Fiche {af}:{ln} · {path} — Tests {claimed} == source ({exp})") + else: + bad(f"Fiche {af}:{ln} · {path} — Tests {claimed} MAIS source dit {exp} " + f"(plan.suites/count_tests) → régénérer la fiche") +if seen_rows == 0: + bad("Comptes par suite · AUCUNE ligne de table « Tests » trouvée dans les " + "fiches — la disparition du recensement est elle-même une régression.") + sys.exit(1 if FAIL else 0) PY rc=$?