From 40ee6f6a46951a0fdbefe3a9fbe258905579c6ca Mon Sep 17 00:00:00 2001 From: Claude Code DTP Worker Date: Sun, 2 Aug 2026 01:40:40 +0000 Subject: [PATCH] =?UTF-8?q?[DTP-Worker=2020260802=5F013501]=20Sprint=208?= =?UTF-8?q?=20=C2=B7=20buffer=20=C2=B7=20CRM=20Dossier=20Vente=20:=20gate?= =?UTF-8?q?=20d'IDENTIT=C3=89=20du=20COMPTE=20D'INVARIANTS=20de=20=5Fvalid?= =?UTF-8?q?ate=20(5=E1=B5=89=20instance=20du=20ledger)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 5ᵉ instance de la classe « ledger d'invariants » (après Chat OTOIA · SEO · Fiscal e-CF · Legal CONFOTUR). Le compte « 12 invariants » de crm/dossier_vente/doctype_dossier_vente_gen.py était transcrit à la main à 7 endroits (4 chaînes du .py + 3 du README : commande, titre « ## Les 12 invariants », prose de score), aucun gaté — invisibles à check_artifacts (le .py n'est pas un out/*.json) et aux suites tests/ (qui testent des fonctions, pas la prose). Ajouter un contrôle # 13 · sans toucher les 7 chaînes ⇒ CI applique 13 contrôles pendant que docs+self-reports disent 12 en silence (vert trompeur · #6). Ledger PROPRE 1..12 (≈ CONFOTUR) : schéma compté à part (« schéma + N invariants »), pas de schéma-#1 non marqué, pas de split a/b ; détection de doublon nu conservée en filet. Le gate 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 de la section contiguë 1..N. 5 checks verts sur l'arbre propre. 7 morsures adversariales (M1 self-report .py · M2 titre README · M3 prose README · M4 ajout # 13 · = ledger 13 vs prose 12, mord .py+README+énumération = autorité au CODE · M5 gap # 7 · · M6 doublon nu # 5 · · M7 énumération 7→8). 6 gates re-verts exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) --- 05_activity_log/2026-08-02.md | 55 ++++++++++++++++ ci/check_readme_claims.sh | 116 ++++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+) diff --git a/05_activity_log/2026-08-02.md b/05_activity_log/2026-08-02.md index 3b585a5..f39fbea 100644 --- a/05_activity_log/2026-08-02.md +++ b/05_activity_log/2026-08-02.md @@ -1,5 +1,60 @@ # Activity log · 2026-08-02 · Claude Code DTP Worker +## Sprint 8 · buffer · CRM Dossier Vente — gate d'IDENTITÉ du COMPTE D'INVARIANTS de `_validate` (5ᵉ instance du ledger) + +**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de +chiffres »). Le pattern « ledger d'invariants » (recompute le nombre de contrôles +CI depuis le registre-source `# N ·` de `_validate`, jamais une liste à la main) +a déjà couvert **Chat OTOIA**, **SEO**, **Fiscal e-CF** et **Legal CONFOTUR**. Le +**5ᵉ générateur** portant le même nombre magique restait NON gaté : +`crm/dossier_vente/doctype_dossier_vente_gen.py` (candidats restants recensés : +`qa/audit_5d` ·15, `crm/commissions` ·10, tous ledgers propres non gatés). + +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. docstring module (l.14 « schéma + 12 invariants de ») ; +2. docstring `_validate` (l.77 « 12 invariants de cross-cohérence ») ; +3. message de succès CLI `validate` « … schéma + 12 invariants verts. » (l.218) ; +4. aide du sous-parseur `validate` (l.230) ; +5. README bloc de commande « schéma + 12 invariants » (l.56) ; +6. README titre de section « ## Les 12 invariants » (l.60) ; +7. README prose de score « 12 invariants de cross-cohérence » (l.83). + +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 Dossier Vente** (≈ CONFOTUR, ≠ Fiscal) : le ledger est +**PROPRE 1..12** — le **schéma est compté À PART** (phrasé « schéma + N invariants »), +donc **pas de schéma-#1 non marqué** et **pas de split a/b**. On garde néanmoins la +détection de doublon nu comme filet. Le README porte AUSSI une **énumération +numérotée** sous le titre de section (item 1 = conformité au schéma, puis les +contrôles), gatée en contiguïté 1..N. + +**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « CRM · Dossier Vente · IDENTITÉ +du COMPTE D'INVARIANTS ») — RECOMPUTE le ledger depuis le SEUL registre-source +(`# N ·` de `_validate`) et exige : +- **(a)** ledger CONTIGU 1..N (lacune OU doublon nu = vrai défaut de registre) ; +- **(b)** toute mention « N invariants » du `.py` == |ledger| ; +- **(c)** l'annonce du titre README « ## Les N invariants » == |ledger| ; +- **(d)** toute mention « N invariants » du README == |ledger| ; +- **(e)** l'ÉNUMÉRATION numérotée de la section (« 1. … 2. … N. ») contiguë 1..N. +Une mention ou le registre absent échoue AUSSI (traçabilité #6). + +**Vérif** — 5 checks verts sur l'arbre propre (ledger 1..12 = 12 marqueurs `# N ·`, +schéma compté à part). **7 morsures** adversariales : M1 self-report `.py` 12→13 +(b) · M2 titre README 12→13 (c+d) · M3 prose README 12→13 (d) · **M4 ajout marqueur +`# 13 ·` ⇒ ledger→13 pendant que la prose reste 12 (mord `.py` ET README ET +l'énumération, preuve que l'autorité est le CODE)** · M5 suppression `# 7 ·` ⇒ gap +non-contigu (a) · M6 doublon nu `# 5 ·` ⇒ détection de vrai doublon (a) · M7 +énumération README 7.→8. ⇒ deux 8/pas de 7 (e). Gates re-verts : `check_artifacts` +· `check_docs` · `guard_constraints` · `check_ci_integrity` · `check_regression` · +`check_readme_claims` tous exit 0. + +--- + ## Sprint 8 · buffer · Legal CONFOTUR — gate d'IDENTITÉ du COMPTE D'INVARIANTS de `_validate` (4ᵉ instance du ledger) **Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de diff --git a/ci/check_readme_claims.sh b/ci/check_readme_claims.sh index 8417558..eea0bac 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -7676,6 +7676,122 @@ if conf_dt_wd is not None: good(f"Confotur champs dérivés · (d) README `estado` DÉCLARE dériver des " f"états confotur_* du workflow vente") +# ============================================================================ +# CRM · Dossier Vente · IDENTITÉ du COMPTE D'INVARIANTS de `_validate` — 5ᵉ +# instance de la classe « ledger d'invariants » (après Chat OTOIA · SEO · Fiscal +# e-CF · Legal CONFOTUR), appliquée au générateur du DocType porteur. +# ---------------------------------------------------------------------------- +# Le générateur doctype_dossier_vente_gen.py applique un COMPTE d'invariants de +# cross-cohérence workflow ↔ DocType 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` : les marqueurs `# N ·` (1..12) qui ouvrent chaque +# contrôle appliqué. 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 « … schéma + 12 invariants verts. », aide du +# sous-parseur `validate`) + TROIS du README (bloc de commande « schéma + 12 +# invariants », titre de section « ## 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 de validation, jamais la prose) ni le byte-gate +# d'artefact (aveugle au .py source) n'attrapent. +# PARTICULARITÉ vs Fiscal : ledger PROPRE 1..N (le schéma est compté À PART — +# phrasé « schéma + N invariants » — donc PAS de schéma-#1 non marqué, pas de split +# a/b) ; on garde la détection de doublon nu comme filet. On RECOMPUTE le ledger +# depuis le SEUL registre-source (les `# N ·` de `_validate`) 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| ; (c) le compte annoncé du +# titre README « ## Les N invariants » == |ledger| ; (d) TOUTE mention « N +# invariants » du README == |ledger| ; (e) l'ÉNUMÉRATION numérotée de la section +# (« 1. … 2. … N. ») elle-même contiguë 1..N (item ni sauté ni dupliqué). Mention +# ou registre absent = échec (disparition d'un chiffre = régression de traçabilité +# #6). +# ============================================================================ +DVV_GEN = f"{D}/crm/dossier_vente/doctype_dossier_vente_gen.py" +DVV_RD = f"{D}/crm/dossier_vente/README.md" +try: + dvv_src = open(DVV_GEN, encoding="utf-8").read() +except OSError as e: + bad(f"Dossier Vente invariants · générateur {DVV_GEN} illisible : {e}"); dvv_src = None +if dvv_src is not None: + _mfn = re.search(r"def _validate\b", dvv_src) + if _mfn is None: + bad(f"Dossier Vente invariants · fonction `_validate` INTROUVABLE dans {DVV_GEN} " + "(registre-source du ledger absent → impossible de re-dériver)") + else: + _bs = _mfn.end() + _mn = re.search(r"\ndef ", dvv_src[_bs:]) + _body = dvv_src[_bs: _bs + _mn.start()] if _mn else dvv_src[_bs:] + _pairs = re.findall(r"#\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"Dossier Vente invariants · aucun marqueur `# N ·` dans `_validate` de " + f"{DVV_GEN} (ledger vide → impossible de re-dériver)") + elif _dup_bare: + bad(f"Dossier Vente invariants · marqueur(s) `# N ·` dupliqué(s) sans suffixe " + f"a/b dans `_validate` : {_dup_bare} — vrai doublon de registre") + else: + dvv_ninv = len(_top) + if _top != list(range(1, dvv_ninv + 1)): + bad(f"Dossier Vente invariants · ledger `_validate` NON contigu : {_top} " + f"(attendu 1..{dvv_ninv}) — lacune/doublon dans les marqueurs `# N ·`") + else: + good(f"Dossier Vente invariants · ledger `_validate` contigu 1..{dvv_ninv} " + f"({dvv_ninv} marqueurs `# N ·` · schéma compté à part)") + # (b) self-reports du .py « N invariants ». + dvv_py_rep = sorted({int(x) for x in re.findall(r"(\d+)\s+invariants", dvv_src)}) + if not dvv_py_rep: + bad(f"Dossier Vente invariants · aucune mention « N invariants » dans {DVV_GEN} " + f"(self-report absent → attendu {dvv_ninv})") + elif dvv_py_rep == [dvv_ninv]: + good(f"Dossier Vente invariants · {DVV_GEN} — self-report(s) {dvv_py_rep} " + f"== ledger ({dvv_ninv})") + else: + bad(f"Dossier Vente invariants · {DVV_GEN} — self-report(s) « N invariants » " + f"{dvv_py_rep} MAIS ledger en compte {dvv_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: + dvv_rd_raw = open(DVV_RD, encoding="utf-8").read() + except OSError as e: + bad(f"Dossier Vente invariants · {DVV_RD} illisible : {e}"); dvv_rd_raw = None + if dvv_rd_raw is not None: + _mh = re.search(r"##\s+Les\s+(\d+)\s+invariants", dvv_rd_raw) + if _mh is None: + bad(f"Dossier Vente invariants · {DVV_RD} — section « ## Les N invariants » " + f"INTROUVABLE (attendu {dvv_ninv})") + else: + # (c) compte annoncé du titre. + if int(_mh.group(1)) == dvv_ninv: + good(f"Dossier Vente invariants · {DVV_RD} — annonce ({_mh.group(1)}) " + f"== ledger ({dvv_ninv})") + else: + bad(f"Dossier Vente invariants · {DVV_RD} — annonce ({_mh.group(1)}) MAIS " + f"ledger en compte {dvv_ninv} → régénérer le README") + # (d) toute mention « N invariants » du README == ledger. + dvv_rd_rep = sorted({int(x) for x in re.findall(r"(\d+)\s+invariants", dvv_rd_raw)}) + if dvv_rd_rep == [dvv_ninv]: + good(f"Dossier Vente invariants · {DVV_RD} — mention(s) {dvv_rd_rep} " + f"== ledger (aucune dérive)") + else: + bad(f"Dossier Vente invariants · {DVV_RD} — mention(s) « N invariants » " + f"{dvv_rd_rep} MAIS ledger en compte {dvv_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)", + dvv_rd_raw, re.S) + dvv_enum = ([int(x) for x in + re.findall(r"(?