From 87c88beaa2c61814acd91d1bade024d68abfdaa8 Mon Sep 17 00:00:00 2001 From: Claude Code DTP Worker Date: Sun, 2 Aug 2026 00:47:54 +0000 Subject: [PATCH] =?UTF-8?q?[DTP-Worker=2020260802=5F003501]=20Sprint=208?= =?UTF-8?q?=20=C2=B7=20buffer=20=C2=B7=20Fiscal=20e-CF=20DGII=20:=20gate?= =?UTF-8?q?=20d'IDENTIT=C3=89=20du=20COMPTE=20D'INVARIANTS=20de=20=5Fvalid?= =?UTF-8?q?ate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- 05_activity_log/2026-08-02.md | 55 +++++++++++++++ ci/check_readme_claims.sh | 127 ++++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+) diff --git a/05_activity_log/2026-08-02.md b/05_activity_log/2026-08-02.md index 6abbc60..77601d0 100644 --- a/05_activity_log/2026-08-02.md +++ b/05_activity_log/2026-08-02.md @@ -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. diff --git a/ci/check_readme_claims.sh b/ci/check_readme_claims.sh index 5fb044d..b83f400 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -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"(?