[DTP-Worker] Sprint 8 · buffer L75 · Comptes de tests PAR SUITE périmés dans la fiche QA (col « Tests ») : audit_4big 35→34 · regression 25→26 · acceptance 31→37 — dérive silencieuse non gatée (seule la somme 564 l'était ; une compensation +1/−1 passerait) → recompute générique des cellules par-suite depuis plan.suites/count_tests dans check_readme_claims (10 lignes vérifiées) + 3 nombres corrigés

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-07-31 06:39:46 +00:00
parent 25541c365d
commit 402727411d
4 changed files with 139 additions and 4 deletions
+3 -3
View File
@@ -22,9 +22,9 @@ périmètre — un auditeur ne s'audite jamais.
| Module | Sprint | Rôle | Entrée CLI | Job CI | Tests | | 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_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 | | [`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` | 25 | | [`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` | 31 | | [`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) : **Quatre axes distincts, non redondants** (CLAUDE.md #5 · éliminer les doublons) :
`audit_4big` note la **qualité statique** · `regression` prouve l'**exécution** · `audit_4big` note la **qualité statique** · `regression` prouve l'**exécution** ·
+59
View File
@@ -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). **Hors périmètre worker (VPS · #8)** : néant (stdlib pur en-repo).
**Auto-score 4Big** : 96/100. **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/<path>/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.
+12 -1
View File
@@ -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-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-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-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 | | `gate` | — | Agrégat vert = gate qualité 4Big franchi | ✅ oui |
Aucune dépendance réseau/marketplace hors `actions/checkout`. Tout tourne avec 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 ») : 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). - `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 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 disparaître un chiffre est elle-même une régression de traçabilité). stdlib pur
(bash/git/python3), zéro réseau. (bash/git/python3), zéro réseau.
+65
View File
@@ -32,6 +32,9 @@
# 03_agents/erpnext_backend/AGENT.md (« source unique ») : # 03_agents/erpnext_backend/AGENT.md (« source unique ») :
# • N tests · M suites · verdict (matrice de régression du repo) # • N tests · M suites · verdict (matrice de régression du repo)
# → qa/regression run # → 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 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é). # 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 " 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") 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/<path>/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) sys.exit(1 if FAIL else 0)
PY PY
rc=$? rc=$?