diff --git a/05_activity_log/2026-08-05.md b/05_activity_log/2026-08-05.md index 372a829..7f1581c 100644 --- a/05_activity_log/2026-08-05.md +++ b/05_activity_log/2026-08-05.md @@ -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). 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). + + +**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é. diff --git a/05_deliverables_mvp/qa/audit_4big/tests/test_audit_4big.py b/05_deliverables_mvp/qa/audit_4big/tests/test_audit_4big.py index 0cc8c02..d3518d6 100644 --- a/05_deliverables_mvp/qa/audit_4big/tests/test_audit_4big.py +++ b/05_deliverables_mvp/qa/audit_4big/tests/test_audit_4big.py @@ -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):