[DTP-Worker 20260802_070524] Sprint 8 · buffer · Mobile app_config : gate d'IDENTITÉ du COMPTE D'INVARIANTS de _validate_bundle (8ᵉ instance de la classe ledger)

Recompute le compte « 12 invariants » depuis le SEUL registre-source
(`# Invariant N ·` de `_validate_bundle`) — 4 surfaces prose hand-transcrites
(CLI .py, 2× README, docstring test) ancrées, aucune n'était gatée. Style de
marqueur `# Invariant N ·` + fonction `_validate_bundle` = deux différences
structurelles vs les 7 blocs `# N ·`/`_validate` précédents ; ajoute la surface
fichier-de-tests (b'). 4 verts arbre propre · 8 morsures adversariales · 6 gates
CI exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-08-02 07:11:34 +00:00
parent 335d449346
commit 655eeeb4a1
2 changed files with 181 additions and 0 deletions
+58
View File
@@ -718,3 +718,61 @@ CODE, pas la prose) · M5 suppression `# 7 ·` ⇒ gap non-contigu (a) · M6 dou
7 (e) · M8 suppression `validate(bundle, schema)` ⇒ invariant 1 introuvable. Gates 7 (e) · M8 suppression `validate(bundle, schema)` ⇒ invariant 1 introuvable. Gates
re-verts : `check_artifacts` · `check_docs` · `guard_constraints` · re-verts : `check_artifacts` · `check_docs` · `guard_constraints` ·
`check_ci_integrity` · `check_readme_claims` · `check_regression` tous exit 0. `check_ci_integrity` · `check_readme_claims` · `check_regression` tous exit 0.
---
## Sprint 8 · buffer · Mobile app_config : gate d'IDENTITÉ du COMPTE D'INVARIANTS de `_validate_bundle` (8ᵉ instance)
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres »). La classe « ledger d'invariants » (recompute le nombre d'invariants CI
depuis le registre-source numéroté de la fonction de validation, jamais une liste à
la main) était déjà appliquée à **7 générateurs** : Chat OTOIA · SEO · Fiscal e-CF ·
Legal CONFOTUR · CRM Dossier Vente · QA Audit 5D · CRM Commissions. Le **8ᵉ**
générateur portant le même nombre magique restait NON couvert :
`mobile/app_config/app_config_gen.py`.
Le compte **« 12 invariants »** y est transcrit **à la main à QUATRE endroits**,
aucun gaté, tous invisibles à `check_artifacts` (le `.py`/`.md`/test n'est pas un
`out/*.json`) :
1. `app_config_gen.py` message de succès CLI « … schéma + 12 invariants verts. »
(l.228) ;
2. `README.md` bloc de commande « schéma + 12 invariants » (l.48) ;
3. `README.md` prose de récap « Schéma + 12\ninvariants verts » (l.67-68 — le « 12 »
et le mot séparés par un saut de ligne) ;
4. `tests/test_mobile_app_config.py` docstring « … + les 12 invariants du CLI »
(l.4).
Ajouter un contrôle `# Invariant 13 ·` à `_validate_bundle` sans toucher les 4
chaînes ⇒ le README, le self-report CLI ET la docstring de test mentent en silence
pendant que la CI applique 13 contrôles. Aucune suite `tests/` (qui EXÉCUTE la
validation mais ne compte jamais la prose) ne l'attrape.
**Particularités vs les 7 blocs précédents** (documentées dans l'en-tête du gate) :
- le registre-source utilise le style **`# Invariant N ·`** (le mot « Invariant »
précède le numéro) — les blocs ancrés sur le style nu `# N ·` NE le voient PAS ;
ancrage sur `#\s*Invariant\s+(\d+)…` ;
- la fonction s'appelle **`_validate_bundle`** (pas `_validate`) — l'ancre `def
_validate\b` des autres blocs échoue (`\b` échoue avant `_bundle`) ;
- ce README n'a NI titre « ## Les N invariants » NI énumération numérotée → on gate
**(a)(b)(b')(d)**, en AJOUTANT la surface **fichier-de-tests (b')**, absente des 7
blocs précédents (leurs tests ne portaient pas le chiffre).
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « Mobile · app_config · IDENTITÉ
du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source
(`# Invariant N ·` de `_validate_bundle`) et exige :
- **(a)** ledger CONTIGU 1..N (schéma compté à part ; lacune OU doublon nu = défaut
mordu) ;
- **(b)** toute mention « N invariants » du `.py` == |ledger| ;
- **(b')** toute mention « N invariants » du fichier de tests == |ledger| ;
- **(d)** toute mention « N invariants » du README == |ledger|.
**Vérif** — 4 checks verts sur l'arbre propre (ledger 1..12 = 12 marqueurs). **8
morsures** adversariales : M1 self-report CLI 12→13 (b) · M2 README bloc-commande
12→13 (d) · M3 README récap (newline-split) 12→13 (d, preuve que `\s+` traverse le
saut de ligne) · M4 docstring test 12→13 (b', la surface neuve) · M5 ajout marqueur
`# Invariant 13 ·` ⇒ ledger→13 pendant que les 3 surfaces prose restent 12 (b+b'+d,
preuve que l'autorité est le CODE, pas la prose) · M6 suppression `# Invariant 7 ·`
⇒ gap non-contigu (a) + désync prose · M7 doublon nu `# Invariant 5 ·` ⇒ détection
de vrai doublon · M8 renommage `_validate_bundle` ⇒ registre introuvable. Gates
re-verts : `check_artifacts` · `check_docs` · `guard_constraints` ·
`check_ci_integrity` · `check_readme_claims` · `check_regression` tous exit 0.
+123
View File
@@ -8523,6 +8523,129 @@ if cmi_src is not None:
bad(f"Commissions invariants · {CMI_RD} — énumération prose {cmi_enum} " bad(f"Commissions invariants · {CMI_RD} — énumération prose {cmi_enum} "
f"≠ 1..{cmi_ninv} (item sauté/dupliqué) → régénérer le README") f"≠ 1..{cmi_ninv} (item sauté/dupliqué) → régénérer le README")
# ============================================================================
# Mobile · app_config · IDENTITÉ du COMPTE D'INVARIANTS de `_validate_bundle` —
# 8ᵉ instance de la classe « ledger d'invariants » (après Chat OTOIA · SEO ·
# Fiscal e-CF · Legal CONFOTUR · CRM Dossier Vente · QA Audit 5D · CRM
# Commissions), appliquée au générateur de la config app mobile (roadmap Sprint 5
# l.56-57 « Rebuild Expo 54 + submit »).
# ----------------------------------------------------------------------------
# Le générateur app_config_gen.py applique un COMPTE d'invariants de cohérence
# (onglets=portails RBAC · rôles=surface exacte · marque #4 · devises #10 · Expo
# SDK · anti-invention a_confirmer · déterminisme…) dont la source d'autorité
# n'est PAS un artefact JSON (invisible à check_artifacts qui byte-gate les
# out/*.json) mais le LEDGER NUMÉROTÉ de `_validate_bundle` : les marqueurs
# `# Invariant N ·` (1..12) qui ouvrent chaque contrôle. Le nombre « 12 » est
# transcrit À LA MAIN à QUATRE endroits qu'AUCUN gate ne couvrait : UNE chaîne du
# .py (message de succès CLI « … schéma + 12 invariants verts »), DEUX du README
# (bloc de commande « schéma + 12 invariants », prose de récap « Schéma + 12
# invariants verts » — le « 12 » et le mot séparés par un saut de ligne) + UNE du
# fichier de tests (docstring « … + les 12 invariants du CLI »). Ajouter un
# contrôle `# Invariant 13 ·` à `_validate_bundle` sans toucher ces chaînes ⇒ le
# README, le self-report CLI ET la docstring de test mentent en silence pendant
# que la CI applique 13 contrôles — « vert trompeur » (#6) que ni les suites
# tests/ (qui EXÉCUTENT la validation mais ne comptent jamais la prose) ni le
# byte-gate d'artefact (aveugle au .py source) n'attrapent.
# PARTICULARITÉS vs les 7 blocs précédents :
# · le REGISTRE-source utilise le style `# Invariant N ·` (le mot « Invariant »
# précède le numéro) — les blocs Chat OTOIA/SEO/Fiscal/CONFOTUR/Dossier/Audit
# 5D/Commissions ancrés sur le style nu `# N ·` NE le voient PAS ; on ancre
# donc sur `#\s*Invariant\s+(\d+)…` ;
# · la fonction s'appelle `_validate_bundle` (pas `_validate`) — l'ancre `def
# _validate\b` des autres blocs ne matche pas (`\b` échoue avant `_bundle`) ;
# · ledger PROPRE 1..N compté À PART du schéma (phrasé « schéma + N invariants »)
# — pas de schéma-#1 non marqué, pas de split a/b ; on garde le filet doublon
# nu ;
# · ce README n'a NI titre « ## Les N invariants » NI énumération numérotée du
# ledger → on gate (a)(b)(b')(d), en AJOUTANT la surface fichier-de-tests (b')
# absente des blocs précédents (leurs tests ne portaient pas le chiffre).
# On RECOMPUTE le ledger depuis le SEUL registre-source (les `# Invariant N ·` de
# `_validate_bundle`) et on exige : (a) ledger CONTIGU 1..N (lacune OU doublon nu
# = vrai défaut de registre, mordu) ; (b) TOUTE mention « N invariants » du .py ==
# |ledger| ; (b') TOUTE mention « N invariants » du fichier de tests == |ledger| ;
# (d) TOUTE mention « N invariants » du README == |ledger|. Mention ou registre
# absent = échec (disparition d'un chiffre = régression de traçabilité #6).
# ============================================================================
MBI_GEN = f"{D}/mobile/app_config/app_config_gen.py"
MBI_RD = f"{D}/mobile/app_config/README.md"
MBI_TS = f"{D}/mobile/app_config/tests/test_mobile_app_config.py"
try:
mbi_src = open(MBI_GEN, encoding="utf-8").read()
except OSError as e:
bad(f"Mobile invariants · générateur {MBI_GEN} illisible : {e}"); mbi_src = None
if mbi_src is not None:
_mfn = re.search(r"def _validate_bundle\b", mbi_src)
if _mfn is None:
bad(f"Mobile invariants · fonction `_validate_bundle` INTROUVABLE dans {MBI_GEN} "
"(registre-source du ledger absent → impossible de re-dériver)")
else:
_bs = _mfn.end()
_mn = re.search(r"\ndef ", mbi_src[_bs:])
_body = mbi_src[_bs: _bs + _mn.start()] if _mn else mbi_src[_bs:]
_pairs = re.findall(r"#\s*Invariant\s+(\d+)([a-z]?)\s*·", _body)
_dup_bare = sorted({n for n in {p[0] for p in _pairs}
if [s for num, s in _pairs if num == n and s == ""]
and sum(1 for num, _ in _pairs if num == n) > 1})
_top = sorted({int(n) for n, _ in _pairs})
if not _top:
bad(f"Mobile invariants · aucun marqueur `# Invariant N ·` dans `_validate_bundle` "
f"de {MBI_GEN} (ledger vide → impossible de re-dériver)")
elif _dup_bare:
bad(f"Mobile invariants · marqueur(s) `# Invariant N ·` dupliqué(s) sans suffixe "
f"a/b dans `_validate_bundle` : {_dup_bare} — vrai doublon de registre")
else:
mbi_ninv = len(_top)
if _top != list(range(1, mbi_ninv + 1)):
bad(f"Mobile invariants · ledger `_validate_bundle` NON contigu : {_top} "
f"(attendu 1..{mbi_ninv}) — lacune/doublon dans les marqueurs `# Invariant N ·`")
else:
good(f"Mobile invariants · ledger `_validate_bundle` contigu 1..{mbi_ninv} "
f"({mbi_ninv} marqueurs `# Invariant N ·` · schéma compté à part)")
# (b) self-report(s) du .py « N invariants ».
mbi_py_rep = sorted({int(x) for x in re.findall(r"(\d+)\s+invariants", mbi_src)})
if not mbi_py_rep:
bad(f"Mobile invariants · aucune mention « N invariants » dans {MBI_GEN} "
f"(self-report absent → attendu {mbi_ninv})")
elif mbi_py_rep == [mbi_ninv]:
good(f"Mobile invariants · {MBI_GEN} — self-report(s) {mbi_py_rep} "
f"== ledger ({mbi_ninv})")
else:
bad(f"Mobile invariants · {MBI_GEN} — self-report(s) « N invariants » "
f"{mbi_py_rep} MAIS ledger en compte {mbi_ninv} "
"(une chaîne du générateur désynchronisée du registre)")
# (b') self-report du fichier de tests « N invariants » (docstring).
try:
mbi_ts_raw = open(MBI_TS, encoding="utf-8").read()
except OSError as e:
bad(f"Mobile invariants · {MBI_TS} illisible : {e}"); mbi_ts_raw = None
if mbi_ts_raw is not None:
mbi_ts_rep = sorted({int(x) for x in re.findall(r"(\d+)\s+invariants", mbi_ts_raw)})
if not mbi_ts_rep:
bad(f"Mobile invariants · aucune mention « N invariants » dans {MBI_TS} "
f"(self-report tests absent → attendu {mbi_ninv})")
elif mbi_ts_rep == [mbi_ninv]:
good(f"Mobile invariants · {MBI_TS} — docstring {mbi_ts_rep} "
f"== ledger ({mbi_ninv})")
else:
bad(f"Mobile invariants · {MBI_TS} — docstring « N invariants » "
f"{mbi_ts_rep} MAIS ledger en compte {mbi_ninv} → régénérer la docstring")
# (d) toute mention « N invariants » du README == ledger.
try:
mbi_rd_raw = open(MBI_RD, encoding="utf-8").read()
except OSError as e:
bad(f"Mobile invariants · {MBI_RD} illisible : {e}"); mbi_rd_raw = None
if mbi_rd_raw is not None:
mbi_rd_rep = sorted({int(x) for x in re.findall(r"(\d+)\s+invariants", mbi_rd_raw)})
if not mbi_rd_rep:
bad(f"Mobile invariants · aucune mention « N invariants » dans {MBI_RD} "
f"(self-report README absent → attendu {mbi_ninv})")
elif mbi_rd_rep == [mbi_ninv]:
good(f"Mobile invariants · {MBI_RD} — mention(s) {mbi_rd_rep} "
f"== ledger (aucune dérive)")
else:
bad(f"Mobile invariants · {MBI_RD} — mention(s) « N invariants » "
f"{mbi_rd_rep} MAIS ledger en compte {mbi_ninv} → régénérer le README")
# ============================================================================ # ============================================================================
# Traçabilité roadmap · le `roadmap_ref` VERBATIM des artefacts — la CITATION # Traçabilité roadmap · le `roadmap_ref` VERBATIM des artefacts — la CITATION
# (n° de ligne + « libellé bullet ») ANCRÉE sur la ligne RÉELLE de la roadmap, # (n° de ligne + « libellé bullet ») ANCRÉE sur la ligne RÉELLE de la roadmap,