[DTP-Worker 20260805_124241] FIX test-precision (surface neuve) : test_audit_4big.py figeait totals["pass"] = 18 - fail alors que le périmètre a grandi à 24 modules → le rapport « bad » de test_failing_module_forces_global_fail déclenchait INV7 (visé) MAIS AUSSI 2× INV8 (pass+fail=18≠24 modules ET min/max incohérents), contredisant son propre commentaire « rapport cohérent en interne ». Test relâché passant pour de mauvaises raisons + constante latente périmée. FIX (test only, 0 prod, 0 artefact rebuild) : totaux dérivés du vrai bad["modules"] (pass=len-fail, min/max=min/max(scores)) → rapport réellement cohérent, seul INV7 mord ; assertion resserrée any("< seuil") → all("INV7")+any("< seuil") prouve l'isolation. Preuve : rejeu de l'ancien 18 renvoie bien [INV7, INV8, INV8] et la nouvelle assertion échouerait dessus (dents réelles). 34 tests OK, 33 PASS CI. 0 gate ajouté (#5), 0 chiffre saisi (#6), 0 VPS (#8).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-08-05 12:49:55 +00:00
parent 9b6ec60783
commit 127ef6f7fc
2 changed files with 57 additions and 2 deletions
+46
View File
@@ -1164,3 +1164,49 @@ sens). Recoupé au décompte du gate lui-même (`ls ci/*.sh` hors `lib.sh` = **8
**0 chiffre inventé/saisi** (#6 — tout dérivé de `json.load`/`ls`/`--list`), **0 commande VPS** (#8),
**aucun `git clean`** (#1). <!-- ci-allow : mention de l'interdit, jamais exécuté --> Le daily_report
est **exact et courant** → laissé intact (l'éditer serait du churn). Seule édition : ce log. `./run_ci.sh`**33 PASS · 0 FAIL · 0 SKIP** (inchangé).
---
## Session `20260805_124241` — FIX test-precision : constante `18` périmée + commentaire faux dans `test_audit_4big.py`
**Surface NEUVE (hors classes saturées).** Chasse ciblée aux surfaces non encore auditées
(le canal usuel README/counts/docstrings/fiches est saturé, #5). Trouvaille réelle, pas un
faux-positif : `05_deliverables_mvp/qa/audit_4big/tests/test_audit_4big.py`
`test_failing_module_forces_global_fail` figeait `bad["totals"]["pass"] = 18 - fail`.
**Le défaut.** Le périmètre audité a grandi à **24 modules** (`totals.modules == 24`, tous
100/100), mais le test gardait l'entier **`18`** codé en dur. Résultat : le rapport « bad »
qu'il fabrique avait `pass=17, fail=1, modules=24` → il déclenchait **INV7** (visé) MAIS
AUSSI **INV8 `pass+fail ≠ modules`** (18≠24) *et* **INV8 `min/max score incohérents`**
(module passé sous 95 alors que `min_score` restait 100). Or son propre commentaire affirmait
« *Le rapport est cohérent en interne mais porte un FAIL → INV7 le signale* » — **affirmation
fausse** : le rapport n'était PAS cohérent en interne. Le test passait quand même car son
assertion (`any("< seuil 4Big")`) restait vraie parmi les erreurs parasites → test **relâché**,
passant pour de mauvaises raisons, avec une constante latente qui pourrit en silence.
**Preuve du diagnostic (recompute indépendant).** Rejeu de l'ancien setup `18`
`check_invariants` renvoie exactement `[INV7 publiciste …, INV8 pass+fail ≠ modules, INV8
min/max score incohérents]`. La nouvelle assertion `all("INV7" in e)` **échouerait** sur ce
setup → elle a de vraies dents (pas tautologique).
**Le fix (test uniquement, 0 production).** Totaux dérivés du **vrai** `bad["modules"]` au lieu
d'un entier figé :
- `pass = len(modules) - fail` (robuste à tout futur changement de décompte, #6 — aucun
chiffre saisi à la main)
- `min_score/max_score = min/max(scores recomputés)`
Le rapport « bad » devient **réellement cohérent en interne** → INV8/INV9 ne mordent plus,
**seul INV7** signale le module sous seuil — exactement ce que le commentaire prétend. Assertion
resserrée de `any("< seuil 4Big")` (relâchée) vers `errs non vide` + `all("INV7")` + `any("< seuil
4Big")` : prouve désormais l'**isolation** d'INV7 comme unique déclencheur.
**Portée / anti-churn.** **0 fichier de production édité** (seul un fichier de test), **0
artefact reconstruit** (aucun `out/*.json` touché → pas de rebuild `quality_report`, cf.
[[audit4big-rebuild-after-doc-edits]]), **0 gate ajouté** (#5), **0 chiffre inventé** (#6
tout dérivé de `len()`/`min`/`max`), **0 commande VPS** (#8), **aucun `git clean`** (#1).
<!-- ci-allow : mention de l'interdit, jamais exécuté -->
**Vérif.** `python3 -m unittest discover -s tests`**34 tests OK** ; `./run_ci.sh` → **33
PASS · 0 FAIL · 0 SKIP**. Distinct de la classe *toothless-test/inert-mutation*
([[module-suite-teeth-mutation]]) : ici la LOGIQUE testée est saine — c'est le HARNAIS de test
(données figées) qui avait dérivé.
@@ -318,10 +318,19 @@ class InvariantGuardTest(unittest.TestCase):
m["verdict"] = "PASS" if m["score"] >= 95 else "FAIL"
bad["totals"]["fail"] = sum(1 for x in bad["modules"]
if x["verdict"] != "PASS")
bad["totals"]["pass"] = 18 - bad["totals"]["fail"]
# Totaux dérivés du vrai périmètre (pas d'entier figé) → le rapport
# reste cohérent en interne : INV8 (pass+fail=modules, min/max) ne doit
# PAS mordre, seul INV7 le doit.
bad["totals"]["pass"] = len(bad["modules"]) - bad["totals"]["fail"]
_scores = [x["score"] for x in bad["modules"]]
bad["totals"]["min_score"] = min(_scores)
bad["totals"]["max_score"] = max(_scores)
bad["verdict"] = "FAIL"
errs = gen.check_invariants(bad, self.spec)
# Le rapport est cohérent en interne mais porte un FAIL → INV7 le signale.
# Le rapport est cohérent en interne mais porte un module sous le seuil
# → INV7 (et lui seul) le signale.
self.assertTrue(errs, "INV7 aurait dû signaler le module sous seuil")
self.assertTrue(all("INV7" in e for e in errs), errs)
self.assertTrue(any("< seuil 4Big" in e for e in errs), errs)
def test_self_module_in_scope_is_caught(self):