[DTP-Worker 20260802_003501] Sprint 8 · buffer · Fiscal e-CF DGII : gate d'IDENTITÉ du COMPTE D'INVARIANTS de _validate

3ᵉ application du pattern « ledger d'invariants » (après Chat OTOIA + SEO) : le
nombre magique « 12 invariants » du générateur fiscal ecf_dgii_gen.py est
transcrit à la main à 7 endroits (4 chaînes du .py : docstrings module/_validate
+ succès CLI + aide sous-parseur ; 3 du README : bloc commande + titre « ## Les
12 invariants » + prose de score), aucun gaté, tous invisibles à check_artifacts
(le .py source n'est pas un out/*.json). Ajouter un contrôle # 13 · à _validate
sans toucher les 7 chaînes = 13 contrôles appliqués pendant que README et
self-reports mentent en silence — vert trompeur qu'aucune suite tests/ (fonctions,
pas la prose) n'attrape.

Gate (bloc « Fiscal · e-CF DGII · IDENTITÉ du COMPTE D'INVARIANTS ») : RECOMPUTE
le ledger depuis le SEUL registre-source (# N · de _validate) et exige (a) ledger
CONTIGU 1..N ; (b) toute mention « N invariants » du .py == |ledger| ; (c) annonce
du titre README == |ledger| ; (d) toute mention README == |ledger| ; (e)
énumération numérotée prose du README contiguë 1..N. Particularité fiscale (≠
SEO/Chat OTOIA propres 1..N) : invariant 1 = validation de SCHÉMA non marquée
# 1 · (marqueurs 2..N), invariant 12 SPLITTÉ en # 12a ·/# 12b · (2 volets d'un
même invariant). Le ledger = {1:schéma} ∪ marqueurs-top-level, collapse 12a/12b
mais MORD un vrai doublon nu.

5 checks verts (ledger 1..12). 8 morsures : M1 self-report .py 12→13 (b) · M2
titre 12→13 (c+d) · M3 mention README 12→13 (d) · M4 ajout # 13 · ⇒ ledger→13
pendant que prose reste 12 (b+c+d, preuve que l'autorité est le CODE) · M5 drop
# 7 · = gap non contigu (a) · M6 doublon nu # 5 · · M7 énumération 7.→8. (e) ·
M8 drop validate(bundle, schema) = invariant 1 introuvable. 6 gates re-verts
(check_artifacts · check_docs · guard_constraints · check_ci_integrity ·
check_readme_claims · check_regression) 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 00:47:54 +00:00
parent 1485e4d6ac
commit 87c88beaa2
2 changed files with 182 additions and 0 deletions
+55
View File
@@ -39,3 +39,58 @@ les 4 copies simultanément** (preuve que l'ancre est CLAUDE.md, pas la cohéren
interne) · M6 footer supprimé → INTROUVABLE. Gates re-verts :
`check_readme_claims` · `check_docs` · `guard_constraints` · `check_ci_integrity`
tous exit 0.
---
## Sprint 8 · buffer · Fiscal e-CF DGII : gate d'IDENTITÉ du COMPTE D'INVARIANTS de `_validate`
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
chiffres »). Le pattern « ledger d'invariants » a déjà été appliqué à **Chat
OTOIA** et **SEO** (recompute le nombre d'invariants CI depuis le registre-source
`# N ·` de `_validate`, jamais une liste à la main). Le **3ᵉ générateur** portant
le même nombre magique restait NON couvert : `fiscal/ecf_dgii/ecf_dgii_gen.py`.
Le compte **« 12 invariants »** y est transcrit **à la main à SEPT endroits**,
aucun gaté, tous invisibles à `check_artifacts` (le `.py` source n'est pas un
`out/*.json`) :
1. `ecf_dgii_gen.py` docstring module (l.24) ;
2. docstring `_validate` (l.110) ;
3. message de succès CLI `validate` « … schéma + 12 invariants verts. » (l.277) ;
4. aide du sous-parseur `validate` (l.289) ;
5. README bloc de commande « schéma + 12 invariants » (l.65) ;
6. README titre de section « ## Les 12 invariants » (l.69) ;
7. README prose de score « 12 invariants de cross-cohérence » (l.105).
Ajouter un contrôle `# 13 ·` à `_validate` sans toucher les 7 chaînes ⇒ le README
ET les self-reports du `.py` mentent en silence pendant que la CI applique 13
contrôles. Aucune suite `tests/` (qui teste des FONCTIONS de validation, pas la
prose) n'attrape ce « vert trompeur ».
**Particularité du registre fiscal** (≠ SEO/Chat OTOIA au ledger propre 1..N) :
l'**invariant 1 est la validation de SCHÉMA** (`validate(bundle, schema)`, **non
marquée** `# 1 ·`), les marqueurs couvrent **2..N**, et l'invariant **12 est
splitté** en `# 12a ·` (unicité évènements) / `# 12b ·` (comptes du manifeste) —
deux volets d'un même invariant. Le gate reconstruit donc le ledger = `{1:schéma}`
marqueurs-top-level, en collapsant 12a/12b, tout en **mordant un vrai doublon**
d'un numéro NON suffixé.
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « Fiscal · e-CF DGII · IDENTITÉ
du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source
(`# N ·` de `_validate`) et exige :
- **(a)** ledger CONTIGU 1..N (schéma #1 présent + marqueurs 2..N sans lacune ;
doublon nu = défaut mordu ; 12a/12b comptés 1×) ;
- **(b)** toute mention « N invariants » du `.py` == |ledger| ;
- **(c)** l'annonce du titre README == |ledger| ;
- **(d)** toute mention « N invariants » du README == |ledger| ;
- **(e)** l'ÉNUMÉRATION numérotée du README (« 1. … 2. … N. ») elle-même contiguë
1..N (item ni sauté ni dupliqué).
**Vérif** — 5 checks verts sur l'arbre propre (ledger 1..12 = schéma #1 + 11
marqueurs). **8 morsures** adversariales : M1 self-report `.py` 12→13 (b) · M2
titre README 12→13 (c+d) · M3 mention README 12→13 (d) · M4 ajout marqueur `# 13 ·`
⇒ ledger→13 pendant que la prose reste 12 (b+c+d, preuve que l'autorité est le
CODE, pas la prose) · M5 suppression `# 7 ·` ⇒ gap non-contigu (a) · M6 doublon nu
`# 5 ·` ⇒ détection de vrai doublon · M7 énumération README 7.→8. ⇒ deux 8/pas de
7 (e) · M8 suppression `validate(bundle, schema)` ⇒ invariant 1 introuvable. Gates
re-verts : `check_artifacts` · `check_docs` · `guard_constraints` ·
`check_ci_integrity` · `check_readme_claims` · `check_regression` tous exit 0.
+127
View File
@@ -1717,6 +1717,133 @@ if seo_src is not None:
bad(f"SEO invariants · {SEO_RD} — mention(s) « N invariants » "
f"{seo_rd_rep} MAIS ledger en compte {seo_ninv} → régénérer le README")
# ============================================================================
# Fiscal · e-CF DGII · IDENTITÉ du COMPTE D'INVARIANTS de `_validate` — même
# classe que le ledger SEO / Chat OTOIA, appliquée au générateur fiscal.
# ----------------------------------------------------------------------------
# CLASSE DISTINCTE des blocs Fiscal existants (format e-NCF · cross-cohérence
# emission_events · rôle émetteur — qui gatent le CONTENU de out/ecf_plan.json).
# Ici la donnée gardée est le COMPTE d'invariants de cross-cohérence appliqués
# par `_validate` d'ecf_dgii_gen.py, 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 la fonction : les marqueurs `# N ·` qui ouvrent chaque
# contrôle — analogue du registre SEO/Chat OTOIA. Le nombre « 12 » est transcrit
# À LA MAIN à SEPT endroits qu'AUCUN gate ne couvrait : QUATRE chaînes du .py
# (docstring module, docstring `_validate`, message de succès CLI « … 12
# invariants verts. », aide du sous-parseur `validate`) + TROIS du README (bloc
# de commande « schéma + 12 invariants », titre « ## Les 12 invariants », prose
# de score « 12 invariants de cross-cohérence »). Ajouter un contrôle `# 13 ·` à
# `_validate` sans toucher les chaînes ⇒ le README ET les self-reports du .py
# mentent en silence pendant que la CI applique 13 contrôles — « vert trompeur »
# (#6) que ni les suites tests/ (FONCTIONS, jamais la prose) ni le byte-gate
# d'artefact (aveugle au .py source) n'attrapent. On RECOMPUTE ici le ledger
# depuis le SEUL registre-source (les `# N ·` de `_validate`) et on exige :
# (a) ledger CONTIGU 1..N — l'invariant 1 est la validation de SCHÉMA (ligne
# `validate(bundle, schema)`, non marquée `# 1 ·`) ; les marqueurs couvrent
# 2..N ; l'invariant 12 est SPLITTÉ en `# 12a ·`/`# 12b ·` (deux volets d'un
# même invariant, comptés UNE fois) — un doublon d'un numéro NON suffixé est
# lui un vrai défaut de registre et MORD ; (b) TOUTE mention « N invariants »
# du .py (self-reports) == |ledger| ; (c) le compte annoncé du titre README
# == |ledger| ; (d) TOUTE mention « N invariants » du README == |ledger| ;
# (e) l'ÉNUMÉRATION numérotée du README (section « ## Les N invariants », prose
# « 1. … 2. … N. ») est elle-même CONTIGUË 1..N (la liste ne saute/duplique
# pas un item). Section/compte absent = échec (disparition d'un chiffre =
# régression de traçabilité · #6).
# ============================================================================
ECF_GEN = f"{D}/fiscal/ecf_dgii/ecf_dgii_gen.py"
ECF_RD_INV = f"{D}/fiscal/ecf_dgii/README.md"
try:
ecf_src = open(ECF_GEN, encoding="utf-8").read()
except OSError as e:
bad(f"Fiscal invariants · générateur {ECF_GEN} illisible : {e}"); ecf_src = None
if ecf_src is not None:
_mfn = re.search(r"def _validate\b", ecf_src)
if _mfn is None:
bad(f"Fiscal invariants · fonction `_validate` INTROUVABLE dans {ECF_GEN} "
"(registre-source du ledger absent → impossible de re-dériver)")
else:
_bs = _mfn.end()
_mn = re.search(r"\ndef ", ecf_src[_bs:])
_body = ecf_src[_bs: _bs + _mn.start()] if _mn else ecf_src[_bs:]
# marqueurs `# N ·` / `# Na ·` (suffixe lettre = volet d'un même invariant)
_pairs = re.findall(r"#\s*(\d+)([a-z]?)\s*·", _body)
# doublon d'un numéro NON suffixé = vrai défaut (≠ split 12a/12b légitime)
_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})
_schema = bool(re.search(r"validate\(bundle,\s*schema\)", _body))
_top = {int(n) for n, _ in _pairs}
if not _top:
bad(f"Fiscal invariants · aucun marqueur `# N ·` dans `_validate` de "
f"{ECF_GEN} (ledger vide → impossible de re-dériver)")
elif _dup_bare:
bad(f"Fiscal invariants · marqueur(s) `# N ·` dupliqué(s) sans suffixe "
f"a/b dans `_validate` : {_dup_bare} — vrai doublon de registre")
elif not _schema:
bad(f"Fiscal invariants · l'invariant 1 (validation de schéma, "
f"`validate(bundle, schema)`) INTROUVABLE dans `_validate` de {ECF_GEN} "
"— le ledger ne peut pas débuter à 1")
else:
ecf_led = sorted(_top | {1}) # 1 = validation de schéma (non marquée)
ecf_ninv = len(ecf_led)
if ecf_led != list(range(1, ecf_ninv + 1)):
bad(f"Fiscal invariants · ledger `_validate` NON contigu : {ecf_led} "
f"(attendu 1..{ecf_ninv}) — lacune dans les marqueurs `# N ·` "
"(schéma = 1, marqueurs = 2..N)")
else:
good(f"Fiscal invariants · ledger `_validate` contigu 1..{ecf_ninv} "
f"(schéma #1 + {len(_top)} marqueurs `# N ·`, 12a/12b comptés 1×)")
# (b) self-reports du .py « N invariants ».
ecf_py_rep = sorted({int(x) for x in re.findall(r"(\d+)\s+invariants", ecf_src)})
if not ecf_py_rep:
bad(f"Fiscal invariants · aucune mention « N invariants » dans {ECF_GEN} "
f"(self-report absent → attendu {ecf_ninv})")
elif ecf_py_rep == [ecf_ninv]:
good(f"Fiscal invariants · {ECF_GEN} — self-report(s) {ecf_py_rep} "
f"== ledger ({ecf_ninv})")
else:
bad(f"Fiscal invariants · {ECF_GEN} — self-report(s) « N invariants » "
f"{ecf_py_rep} MAIS ledger en compte {ecf_ninv} "
"(une chaîne du générateur désynchronisée du registre)")
# README : titre de section + toute mention + énumération numérotée.
try:
ecf_rd_raw = open(ECF_RD_INV, encoding="utf-8").read()
except OSError as e:
bad(f"Fiscal invariants · {ECF_RD_INV} illisible : {e}"); ecf_rd_raw = None
if ecf_rd_raw is not None:
_mh = re.search(r"##\s+Les\s+(\d+)\s+invariants", ecf_rd_raw)
if _mh is None:
bad(f"Fiscal invariants · {ECF_RD_INV} — section « ## Les N invariants » "
f"INTROUVABLE (attendu {ecf_ninv})")
else:
# (c) compte annoncé du titre.
if int(_mh.group(1)) == ecf_ninv:
good(f"Fiscal invariants · {ECF_RD_INV} — annonce ({_mh.group(1)}) "
f"== ledger ({ecf_ninv})")
else:
bad(f"Fiscal invariants · {ECF_RD_INV} — annonce ({_mh.group(1)}) MAIS "
f"ledger en compte {ecf_ninv} → régénérer le README")
# (d) toute mention « N invariants » du README == ledger.
ecf_rd_rep = sorted({int(x) for x in re.findall(r"(\d+)\s+invariants", ecf_rd_raw)})
if ecf_rd_rep == [ecf_ninv]:
good(f"Fiscal invariants · {ECF_RD_INV} — mention(s) {ecf_rd_rep} "
f"== ledger (aucune dérive)")
else:
bad(f"Fiscal invariants · {ECF_RD_INV} — mention(s) « N invariants » "
f"{ecf_rd_rep} MAIS ledger en compte {ecf_ninv} → régénérer le README")
# (e) énumération numérotée de la section == contiguë 1..N.
_msec = re.search(r"##\s+Les\s+\d+\s+invariants.*?(?=\n##\s|\Z)",
ecf_rd_raw, re.S)
ecf_enum = ([int(x) for x in
re.findall(r"(?<![\d.])(\d+)\.\s", _msec.group(0))]
if _msec else [])
if ecf_enum == list(range(1, ecf_ninv + 1)):
good(f"Fiscal invariants · {ECF_RD_INV} — énumération prose "
f"contiguë 1..{ecf_ninv} == ledger")
else:
bad(f"Fiscal invariants · {ECF_RD_INV} — énumération prose {ecf_enum} "
f"≠ 1..{ecf_ninv} (item sauté/dupliqué) → régénérer le README")
# ============================================================================
# CRM · pipeline vente (états / transitions) — même classe de « vert trompeur »,
# transcrit À LA MAIN dans DEUX README de module.