From 3cb1bf06e18d8306982b6a905e20776bcc465079 Mon Sep 17 00:00:00 2001 From: Claude Code DTP Worker Date: Sun, 2 Aug 2026 09:42:13 +0000 Subject: [PATCH] =?UTF-8?q?[DTP-Worker=2020260802=5F093532]=20Sprint=208?= =?UTF-8?q?=20=C2=B7=20buffer=20=C2=B7=20Ledger=20d'invariants=209?= =?UTF-8?q?=E1=B5=89=20instance=20:=20gate=20d'IDENTIT=C3=89=20du=20COMPTE?= =?UTF-8?q?=20de=20`=5Fvalidate=5Fbundle`=20de=20crm/workflow=5Fvente=20(d?= =?UTF-8?q?ialecte=20`#=20Invariant=20N=20=C2=B7`,=203=E1=B5=89=20oubli=20?= =?UTF-8?q?de=20la=20classe=20=C2=AB=20close=20=C2=BB)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le générateur workflow_vente_gen.py (Livrable Sprint 4 l.50) porte un ledger de 9 invariants métier de graphe dans `_validate_bundle` (marqueurs `# Invariant N ·`, phrasé « schéma + 9 invariants »), avec le nombre « 9 » recopié à la main à 4 surfaces vives (gen l.223 + README l.78/114/125), aucune gatée. Prouvé ungate avant ajout (README 9→10 laissait check_readme_claims exit 0). Gate ajouté (a)(b)(d) sur le modèle Mobile : recompute le ledger depuis le SEUL registre-source `# Invariant N ·`, exige contiguïté 1..N + toute mention « N invariants » du .py et du README == |ledger|. Pas de gate (e) enum : la liste « ## Invariants vérifiés par le CLI » compte le schéma en item 1 et fusionne les invariants 8&9 → n'est pas le ledger (faux positif fragile évité). « 25 tests » exclu nativement par l'ancre « invariants » (aucun faux positif). 3 checks verts arbre propre · 7 morsures adversariales (dont M3 SILENT-GREEN `# Invariant 10 ·` ⇒ autorité=CODE, M7 no-false-positive) · unittest 25 OK · suite CI complète re-verte. Co-Authored-By: Claude Opus 4.8 (1M context) --- 05_activity_log/2026-08-02.md | 64 +++++++++++++++++++ ci/check_readme_claims.sh | 113 ++++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+) diff --git a/05_activity_log/2026-08-02.md b/05_activity_log/2026-08-02.md index f310079..27899f7 100644 --- a/05_activity_log/2026-08-02.md +++ b/05_activity_log/2026-08-02.md @@ -1,5 +1,69 @@ # Activity log · 2026-08-02 · Claude Code DTP Worker +## Sprint 8 · buffer · CRM/workflow_vente — gate d'IDENTITÉ du COMPTE D'INVARIANTS de `_validate_bundle` (9ᵉ instance du ledger · dialecte `# Invariant N ·`) + +**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de +chiffres »). La classe « ledger d'invariants » (recompute le nombre de contrôles +CI depuis le registre-source des marqueurs numérotés de la fonction de validation, +jamais une liste à la main) était consignée « close à 8 générateurs » (Chat OTOIA · +SEO · Fiscal e-CF · Legal CONFOTUR · CRM Dossier Vente · QA Audit 5D · CRM +Commissions · Mobile) + 2 (deploy_runbook/acceptance, dialecte `# INVn`). La mémoire +`invariant-ledger-count-gate` avertit précisément : « CLOSED a été prouvé FAUX DEUX +FOIS ; re-scanner TOUS les dialectes de marqueur AVANT de croire la clôture ». Un +**3ᵉ oubli** a été exhumé : `crm/workflow_vente/workflow_vente_gen.py` (Livrable +Sprint 4 l.50 · workflow de vente Frappe lead→…→CONFOTUR) porte un ledger de **9 +invariants métier de graphe** dans `_validate_bundle`, marqueurs `# Invariant N ·` +(style Mobile), phrasé « schéma + 9 invariants ». Le nombre « 9 » est recopié À LA +MAIN à **4 surfaces vives**, aucune gatée : générateur l.223 (message CLI de succès) +· README l.78 (bloc de commande) · README l.114 (prose de vérification) · README +l.125 (prose d'auto-score). + +**Le « vert trompeur » — PROUVÉ ungate avant ajout** (mémoire +`verify-uncovered-before-gating`) : muter « 9 invariants »→« 10 » dans le README +laissait `check_readme_claims` **exit 0**. Ni `check_artifacts` (le `.py` source +n'est pas un `out/*.json`) ni les suites `tests/` (FONCTIONS de validation, jamais +la prose) n'attrapent la dérive. Ajouter un contrôle `# Invariant 10 ·` à +`_validate_bundle` sans toucher les 4 chaînes ⇒ README + self-report CLI mentent en +silence pendant que la CI applique 10 contrôles. + +**Particularités du registre** (vs les 8 blocs `# N ·`) — style `# Invariant N ·` + +fonction `_validate_bundle` (comme Mobile ; les blocs ancrés `# N ·`/`def _validate\b` +ne le voient pas). Ledger PROPRE 1..9 compté À PART du schéma (« schéma + N +invariants » · pas de schéma-#1, pas de split a/b ; filet doublon nu conservé). Un +SECOND nombre distinct — « **25 tests** » (suite unittest, déjà gatée) — présent au +README : l'ancre sur le mot « invariants » l'exclut nativement ⇒ **aucun faux +positif**. Le README N'A NI titre « ## Les N invariants » (son titre « ## Invariants +vérifiés par le CLI » ne porte aucun chiffre) NI énumération 1:1 avec le ledger — sa +liste compte le SCHÉMA en item 1 puis **FUSIONNE les invariants 8 & 9** dans son item +9 (« Maîtres … ; comptes du manifeste cohérents ») : elle N'EST PAS le ledger, la +gater comme contiguë==ledger serait un faux positif fragile (présentation éditoriale). +On gate donc UNIQUEMENT (a)(b)(d), comme Audit 5D. + +**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « CRM · workflow_vente · IDENTITÉ +du COMPTE D'INVARIANTS », juste après le bloc Mobile) — RECOMPUTE le ledger depuis +le SEUL registre-source (les `# Invariant N ·` de `_validate_bundle`, corps extrait +jusqu'au `def` suivant) et exige : **(a)** ledger CONTIGU 1..N (lacune OU doublon nu += vrai défaut de registre) ; **(b)** toute mention « N invariants » du `.py` == +|ledger| ; **(d)** toute mention « N invariants » du README == |ledger|. Mention ou +registre absent = échec (anti-évaporation #6). + +**Vérif** — 3 checks verts sur l'arbre propre (ledger 1..9 = 9 marqueurs `# Invariant +N ·`, schéma compté à part). **7 morsures** adversariales, gate lancé SEUL, reverts +par `cp` ciblé (jamais `git checkout` large — cf. incident consigné) : **M1** `.py` +9→10 (b) · **M2** README 9→10 métier (d) · **M3 SILENT-GREEN** `# Invariant 10 ·` +injecté ⇒ ledger→10 pendant que la prose reste 9 (mord `.py` ET README, preuve que +l'autorité est le CODE) · **M4** `# Invariant 5 ·` supprimé ⇒ ledger non contigu (a) + +self-reports désync · **M5** doublon nu `# Invariant 3 ·` ⇒ vrai doublon (a) · **M6** +toutes les mentions « 9 invariants » retirées du README ⇒ self-report ÉVAPORÉ · **M7 +no-false-positive** : muter « 25 tests »→« 26 » ⇒ le gate ledger reste SILENCIEUX +(l'ancre « invariants » l'exclut ; le rouge vient du gate test-count préexistant, pas +du mien). Arbre restauré (`git status` propre, seule l'addition +113 subsiste). Suite +unittest re-verte : **25 tests OK** (générateur inchangé). Suite CI complète re-verte : +`check_artifacts` · `check_docs` · `guard_constraints` · `check_ci_integrity` · +`check_regression` · `check_readme_claims` tous exit 0. **La classe ledger passe à 9 +générateurs (+2 dialecte `# INVn`) ; re-scanner tout NOUVEAU dialecte AVANT de +re-déclarer close — ce 3ᵉ oubli confirme l'avertissement de la mémoire.** + ## Sprint 8 · buffer · Faisabilité/bancable — ÉLIMINATION du taux `0.52` codé en dur dans l'oracle anti-invention (source unique CANONICAL · #6/#5) **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 7bb2f36..4229db5 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -8650,6 +8650,119 @@ if mbi_src is not None: 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") +# ============================================================================ +# CRM · workflow_vente · IDENTITÉ du COMPTE D'INVARIANTS de `_validate_bundle` — +# 9ᵉ 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 · Mobile), appliquée au générateur du workflow de vente Frappe +# (roadmap Sprint 4 l.50 « lead → visite → devis → réservation → contrat → +# CONFOTUR »). +# ---------------------------------------------------------------------------- +# Le générateur workflow_vente_gen.py applique un COMPTE d'invariants métier de +# graphe (unicité des états · transitions vers états déclarés · monotonie +# doc_status · unicité (état,action) · atteignabilité depuis Lead · terminal de +# succès · séparation des pouvoirs #6 · maîtres = états/actions · cohérence des +# comptes du manifeste) 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..9) qui +# ouvrent chaque contrôle. Le nombre « 9 » est transcrit À LA MAIN à QUATRE +# endroits qu'AUCUN gate ne couvrait : UNE chaîne du .py (message de succès CLI +# « … schéma + 9 invariants verts. ») + TROIS du README (bloc de commande +# « schéma + 9 invariants métier », prose de vérification « 9 invariants de +# graphe », prose d'auto-score « 9 invariants de graphe »). Ajouter un contrôle +# `# Invariant 10 ·` à `_validate_bundle` sans toucher ces chaînes ⇒ le README ET +# le self-report CLI mentent en silence pendant que la CI applique 10 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. +# Prouvé ungate par mutation avant ajout (mémoire verify-uncovered-before-gating) : +# « 9 invariants » → « 10 » dans le README laissait check_readme_claims exit 0. +# PARTICULARITÉS vs les 8 blocs précédents : +# · le REGISTRE-source utilise le style `# Invariant N ·` + la fonction +# `_validate_bundle` (comme Mobile — les blocs ancrés sur le style nu `# N ·` / +# `def _validate\b` ne le voient PAS) ; +# · 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 ; +# · le module porte un SECOND nombre distinct — « 25 tests » (la suite unittest, +# déjà gatée par ailleurs) présent dans le README : l'ancre sur le mot +# « invariants » l'exclut nativement (« 25 tests » ≠ mot « invariants ») ⇒ aucun +# faux positif ; +# · ce README N'A NI titre « ## Les N invariants » (son titre « ## Invariants +# vérifiés par le CLI » ne porte AUCUN chiffre) NI énumération 1:1 avec le +# ledger — sa liste « Invariants vérifiés par le CLI » compte le SCHÉMA en item 1 +# puis FUSIONNE les invariants 8 & 9 dans son item 9 (« Maîtres … ; comptes du +# manifeste cohérents ») : elle N'EST PAS le ledger et la gater comme contiguë +# == ledger serait un faux positif fragile (présentation éditoriale, #6). On +# gate donc UNIQUEMENT (a)(b)(d), comme Audit 5D. +# 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| ; (d) TOUTE mention « N invariants » du README == |ledger|. Mention ou +# registre absent = échec (disparition d'un chiffre = régression de traçabilité #6). +# ============================================================================ +WVI_GEN = f"{D}/crm/workflow_vente/workflow_vente_gen.py" +WVI_RD = f"{D}/crm/workflow_vente/README.md" +try: + wvi_src = open(WVI_GEN, encoding="utf-8").read() +except OSError as e: + bad(f"workflow_vente invariants · générateur {WVI_GEN} illisible : {e}"); wvi_src = None +if wvi_src is not None: + _mfn = re.search(r"def _validate_bundle\b", wvi_src) + if _mfn is None: + bad(f"workflow_vente invariants · fonction `_validate_bundle` INTROUVABLE dans " + f"{WVI_GEN} (registre-source du ledger absent → impossible de re-dériver)") + else: + _bs = _mfn.end() + _mn = re.search(r"\ndef ", wvi_src[_bs:]) + _body = wvi_src[_bs: _bs + _mn.start()] if _mn else wvi_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"workflow_vente invariants · aucun marqueur `# Invariant N ·` dans " + f"`_validate_bundle` de {WVI_GEN} (ledger vide → impossible de re-dériver)") + elif _dup_bare: + bad(f"workflow_vente invariants · marqueur(s) `# Invariant N ·` dupliqué(s) sans " + f"suffixe a/b dans `_validate_bundle` : {_dup_bare} — vrai doublon de registre") + else: + wvi_ninv = len(_top) + if _top != list(range(1, wvi_ninv + 1)): + bad(f"workflow_vente invariants · ledger `_validate_bundle` NON contigu : {_top} " + f"(attendu 1..{wvi_ninv}) — lacune/doublon dans les marqueurs `# Invariant N ·`") + else: + good(f"workflow_vente invariants · ledger `_validate_bundle` contigu 1..{wvi_ninv} " + f"({wvi_ninv} marqueurs `# Invariant N ·` · schéma compté à part)") + # (b) self-report(s) du .py « N invariants » (le distinct « N tests » est + # exclu nativement par l'ancre sur le mot « invariants »). + wvi_py_rep = sorted({int(x) for x in re.findall(r"(\d+)\s+invariants", wvi_src)}) + if not wvi_py_rep: + bad(f"workflow_vente invariants · aucune mention « N invariants » dans {WVI_GEN} " + f"(self-report absent → attendu {wvi_ninv})") + elif wvi_py_rep == [wvi_ninv]: + good(f"workflow_vente invariants · {WVI_GEN} — self-report(s) {wvi_py_rep} " + f"== ledger ({wvi_ninv})") + else: + bad(f"workflow_vente invariants · {WVI_GEN} — self-report(s) « N invariants » " + f"{wvi_py_rep} MAIS ledger en compte {wvi_ninv} " + "(une chaîne du générateur désynchronisée du registre)") + # (d) toute mention « N invariants » du README == ledger. + try: + wvi_rd_raw = open(WVI_RD, encoding="utf-8").read() + except OSError as e: + bad(f"workflow_vente invariants · {WVI_RD} illisible : {e}"); wvi_rd_raw = None + if wvi_rd_raw is not None: + wvi_rd_rep = sorted({int(x) for x in re.findall(r"(\d+)\s+invariants", wvi_rd_raw)}) + if not wvi_rd_rep: + bad(f"workflow_vente invariants · aucune mention « N invariants » dans {WVI_RD} " + f"(self-report README absent → attendu {wvi_ninv})") + elif wvi_rd_rep == [wvi_ninv]: + good(f"workflow_vente invariants · {WVI_RD} — mention(s) {wvi_rd_rep} " + f"== ledger (aucune dérive)") + else: + bad(f"workflow_vente invariants · {WVI_RD} — mention(s) « N invariants » " + f"{wvi_rd_rep} MAIS ledger en compte {wvi_ninv} → régénérer le README") + # ============================================================================ # DevOps deploy_runbook + QA acceptance · IDENTITÉ du COMPTE d'INVARIANTS de # `validate_bundle` — DEUXIÈME famille de « ledger d'invariants », distincte des