[DTP-Worker] hardening correctness · le critère CLI de l'audit 4Big (q4lib/criteria.py) détectait l'entrypoint __main__ par un littéral figé double-quoté → loupait silencieusement les gardes simple-quotées / « Yoda » / sans-espaces (toutes du Python valide) → sous-scorage CLI −20 pts d'un module sain (violation #6)

Même racine que le fix ASCII ac595d0 : une détection assume une graphie là où le langage admet plusieurs formes. Fix = regex de FORME (_MAIN_GUARD_RE : 2 styles de guillemets · ordre inversé · espacement libre) + teeth test_cli_detects_single_quoted_and_yoda_guards (3 fixtures, prouvé mordant : ancien littéral rend False sur les 3, nouvelle regex True). Robustesse pure : les 50 gardes du dépôt sont double-quotées et toujours matchées → aucun score ne bouge, quality_report.json byte-identique (audit_4big s'auto-exclut de son scoring). Cascade régression uniquement : 626/609 → 627/610 (nouveau test) + fiches qa/audit_4big 35→36 & agrégat & backend 626→627 re-dérivés (RED check_readme_claims). Sweep préalable : classe ASCII-vs-Unicode close au niveau CODE (générateurs+libs+gates shell tous ASCII-par-domaine sauf les 2 compteurs test déjà \w).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-08-11 08:39:51 +00:00
parent 9e02c38902
commit fd99362660
8 changed files with 64 additions and 12 deletions
@@ -17,6 +17,15 @@ import re
_TEST_DEF_RE = re.compile(r"^\s*def (test_\w+)\s*\(", re.MULTILINE)
_ADD_PARSER_RE = re.compile(r"add_parser\(|add_subparsers\(")
# Garde `if __name__ == "__main__":` — les DEUX styles de guillemets sont du
# Python valide (PEP 8 n'impose rien), l'ordre des opérandes peut être inversé
# (« Yoda »), et l'espacement autour de `==` est libre. Un littéral figé
# `'__name__ == "__main__"'` louperait un entrypoint valide simple-quoté →
# sous-scorage silencieux du critère CLI. Détecter la forme, pas une graphie.
_MAIN_GUARD_RE = re.compile(
r"""__name__\s*==\s*['"]__main__['"]"""
r"""|['"]__main__['"]\s*==\s*__name__"""
)
def _direct_files(mod_dir: str, suffix: str) -> list[str]:
@@ -75,7 +84,7 @@ def crit_cli(mod_dir: str) -> dict:
for py in _direct_files(mod_dir, ".py"):
with open(py, encoding="utf-8") as fh:
src = fh.read()
if '__name__ == "__main__"' in src and _ADD_PARSER_RE.search(src):
if _MAIN_GUARD_RE.search(src) and _ADD_PARSER_RE.search(src):
return {"passed": True,
"evidence": f"{os.path.basename(py)} (argparse + __main__)"}
return {"passed": False,
@@ -137,6 +137,27 @@ class CriteriaTest(unittest.TestCase):
mod = _make_module(d, cli=False)
self.assertFalse(criteria.crit_cli(mod)["passed"])
def test_cli_detects_single_quoted_and_yoda_guards(self):
# Robustesse : `if __name__ == '__main__':` (simple quote) et la forme
# « Yoda » `if "__main__" == __name__:` sont du Python VALIDE — un
# entrypoint réel. Le littéral figé `'__name__ == "__main__"'` les
# loupait → sous-scorage silencieux du critère CLI (20 pts sur un
# module sain), en violation du cœur anti-invention #6 de l'audit.
for guard in ("if __name__ == '__main__':\n pass\n",
'if "__main__" == __name__:\n pass\n',
"if __name__=='__main__':\n pass\n"):
with tempfile.TemporaryDirectory() as d:
mod = os.path.join(d, "mod")
os.makedirs(mod)
with open(os.path.join(mod, "mod_gen.py"), "w",
encoding="utf-8") as fh:
fh.write('import argparse\n'
'ap = argparse.ArgumentParser()\n'
'sub = ap.add_subparsers()\n'
'sub.add_parser("build")\n' + guard)
self.assertTrue(criteria.crit_cli(mod)["passed"],
f"garde non détectée : {guard!r}")
def test_handoff_fails_without_manifest(self):
with tempfile.TemporaryDirectory() as d:
mod = _make_module(d, handoff=False)
@@ -5,7 +5,7 @@
"version": "1.0",
"verdict": "PASS",
"suites": 24,
"test_methods": 626,
"test_methods": 627,
"coverage_ok": true,
"artifacts": [
"regression_plan.json"
@@ -211,7 +211,7 @@
"in_gate": true,
"has_tests_dir": true,
"test_files": 1,
"test_methods": 35
"test_methods": 36
},
{
"id": "qa.audit_5d",
@@ -295,7 +295,7 @@
"totals": {
"suites": 24,
"test_files": 24,
"test_methods": 626,
"test_methods": 627,
"min_methods": 10,
"under_threshold": 0
},
@@ -198,9 +198,9 @@
{
"id": "qa.audit_4big",
"path": "qa/audit_4big",
"expected_methods": 35,
"ran": 35,
"passed": 35,
"expected_methods": 36,
"ran": 36,
"passed": 36,
"failures": 0,
"errors": 0,
"skipped": 0,
@@ -296,8 +296,8 @@
"suites": 24,
"green": 24,
"red": 0,
"ran": 626,
"passed": 609,
"ran": 627,
"passed": 610,
"failures": 0,
"errors": 0,
"skipped": 17