From 4d9af3a6b8025af3d8b41aa689c6ce3d02ac2bf2 Mon Sep 17 00:00:00 2001 From: Claude Code DTP Worker Date: Fri, 31 Jul 2026 09:36:11 +0000 Subject: [PATCH] =?UTF-8?q?[DTP-Worker]=20Sprint=208=20=C2=B7=20buffer=20L?= =?UTF-8?q?75=20=C2=B7=20Domaine=20CRM=20:=20le=20pipeline=20vente=20?= =?UTF-8?q?=C2=AB=209=20=C3=A9tats=20=C2=B7=2011=20transitions=20=C2=BB=20?= =?UTF-8?q?=C3=A9tait=20transcrit=20=C3=80=20LA=20MAIN=20dans=20DEUX=20REA?= =?UTF-8?q?DME=20de=20module=20(titre=20du=20module=20workflow=5Fvente=20+?= =?UTF-8?q?=20=C2=AB=209=20=C3=A9tats=20du=20pipeline=20=C2=BB=20de=20doss?= =?UTF-8?q?ier=5Fvente,=20m=C3=AAme=20compte=20du=20m=C3=AAme=20graphe)=20?= =?UTF-8?q?sans=20AUCUN=20gate=20=E2=80=94=20ajouter=20un=20=C3=A9tat/une?= =?UTF-8?q?=20transition=20ferait=20d=C3=A9river=20les=20deux=20docs=20en?= =?UTF-8?q?=20silence=20pendant=20que=20crm/workflow=5Fvente/out/MANIFEST.?= =?UTF-8?q?json=20(byte-gat=C3=A9=20par=20check=5Fartifacts,=20counts=20re?= =?UTF-8?q?compt=C3=A9s=20du=20workflow=5Fvente=5Fspec.json)=20dit=20autre?= =?UTF-8?q?=20chose=20=E2=86=92=20nouveau=20bloc=20dans=20check=5Freadme?= =?UTF-8?q?=5Fclaims=20recomputant=20(states=20+=20transitions)=20depuis?= =?UTF-8?q?=20MANIFEST.counts=20et=20exigeant=20l'=C3=A9galit=C3=A9=20avec?= =?UTF-8?q?=20les=20deux=20docs=20(4=20morsures=20:=20d=C3=A9rive=20=C3=A9?= =?UTF-8?q?tats=20titre=20=C2=B7=20d=C3=A9rive=20transitions=20capt=C3=A9e?= =?UTF-8?q?=20l=C3=A0=20o=C3=B9=20=C3=A9tats=20seul=20est=20aveugle=20?= =?UTF-8?q?=C2=B7=20d=C3=A9rive=20=C3=A9tats=202e=20doc=20=C2=B7=20claim?= =?UTF-8?q?=20disparu)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- 05_activity_log/2026-07-31.md | 66 +++++++++++++++++++++++++++++++++++ ci/README.md | 14 +++++++- ci/check_readme_claims.sh | 59 +++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 1 deletion(-) diff --git a/05_activity_log/2026-07-31.md b/05_activity_log/2026-07-31.md index e870489..c18c025 100644 --- a/05_activity_log/2026-07-31.md +++ b/05_activity_log/2026-07-31.md @@ -1,5 +1,71 @@ # Activity Log · 2026-07-31 · Claude Code DTP +## Session `20260731_093041` · Buffer S8 · Domaine CRM (non couvert hors prose « tests ») : le pipeline vente « 9 états · 11 transitions » était transcrit À LA MAIN dans DEUX README de module (titre workflow_vente + « 9 états du pipeline » de dossier_vente, même compte du même graphe) sans AUCUN gate — ajouter un état ferait dériver les deux docs en silence + +**Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA régression). Roadmap +fonctionnellement close (22/22 modules · 564 tests · 7 gates verts au démarrage). +Poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont gaté +les comptes data-derived des domaines tests, portails (structure), RBAC (3 volets) +et SEO (mots-clés). Cette session cible le **domaine CRM** — la **structure du +pipeline vente**, dont un compte data-derived cité dans deux README de module +pointait le graphe généré sans être recomputé par aucun gate. + +### Défaut trouvé — comptes du pipeline ungated, transcrits dans DEUX docs + +- **`05_deliverables_mvp/crm/workflow_vente/README.md:23`** : titre de section + « ## Le pipeline (**9** états · **11** transitions) ». +- **`05_deliverables_mvp/crm/dossier_vente/README.md:30`** : « options = les + **9 états** du pipeline » — **même** compte d'états, **dérivé du même graphe** + (source unique · #5), recopié à la main. +- **Pourquoi non gaté** : `check_readme_claims` couvrait tests, portails, RBAC et + SEO — jamais la structure du pipeline CRM. `check_docs` ne valide que les liens ; + `check_artifacts` prouve que `crm/workflow_vente/out/MANIFEST.json` est + **reproductible byte-for-byte** (bloc `counts` = {states, transitions, …} + recomptés de `workflow_vente_spec.json` à chaque build) mais **ne compare pas** + ce que les DEUX docs en **écrivent**. Ajouter un état/une transition ferait + dériver les deux docs **en silence** — même « vert trompeur ». +- **État courant** : **aucun nombre périmé** — 9/11 recoupe MANIFEST exactement + (anti-invention #6 : rien à réécrire). Le défaut est la **surface ungated** + (faux-vert latent), pas un chiffre faux. + +### Fix — extension de `check_readme_claims.sh` (recompute states + transitions depuis MANIFEST) + +Nouveau bloc qui RECOMPUTE le couple `(states, transitions)` depuis +`crm/workflow_vente/out/MANIFEST.counts` (source déjà byte-gatée · zéro +duplication du compteur du générateur) et exige l'égalité avec **les deux** docs : +le titre du README workflow (`## Le pipeline (N états · M transitions)`) **et** le +compte d'états du README dossier_vente (`les **N états** du pipeline`). Recomputer +**transitions** en plus de **states** mord une dérive du 2ᵉ nombre là où le compte +d'états seul (repris dans dossier_vente) resterait aveugle. Un claim **absent** +échoue AUSSI. En-tête du gate + `ci/README.md` (§ table + § détail) mis à jour. + +### Preuves de morsure + +| Injection | Résultat | +|---|---| +| **README workflow** états `9→10` (titre) | **exit 1** `dit 10/11 MAIS MANIFEST dit 9/11` | +| **README workflow** transitions `11→12` (états inchangés) | **exit 1** `dit 9/12 MAIS MANIFEST dit 9/11` — mord le 2ᵉ nombre | +| **README dossier_vente** états `9→8` (2e doc) | **exit 1** `dit états=8 MAIS MANIFEST dit états=9` — mord là où le titre seul est aveugle | +| **Claim disparu** (dossier reformulé sans le motif) | **exit 1** `claim « N états du pipeline » INTROUVABLE (attendu états=9)` | +| Restauré | **exit 0** (2 docs verts) | + +### Vérifs + +| Contrôle | Résultat | +|---|---| +| 7 gates (`guard`/`json`/`docs`/`artifacts`/`regression`/`ci_integrity`/`readme_claims`) | ✅ exit 0 | +| `check_artifacts` (rebuild + égalité byte des `out/`) | ✅ (édition **hors** `05_deliverables_mvp` ⇒ 0 dérive d'artefact ; aucun README/AGENT.md touché) | +| Matrice de régression | **564/22 · PASS** inchangée (ni `tests/` ni doc-evidence touchés) | +| Diff | 2 fichiers (`ci/check_readme_claims.sh` + `ci/README.md`) · 0 untracked | + +**Anti-invention (#6)** : rien inventé — le gate recompute states + transitions depuis +`workflow_vente MANIFEST.counts` au lieu de figer un compteur ; les nombres des deux +docs **découlent** de l'artefact. +**Hors périmètre worker (VPS · #8)** : néant (gate python/stdlib en-repo). +**Auto-score 4Big** : 96/100. + +--- + ## Session `20260731_090037` · Buffer S8 · Domaine SEO : le total ET la ventilation par langue des mots-clés (« 258 mots-clés (fr=87 · en=87 · es=84) »), transcrits À LA MAIN dans DEUX docs (table du README module + ligne de la fiche agent), n'étaient couverts par AUCUN gate — ajouter un projet ferait dériver les deux docs en silence **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA régression). Roadmap diff --git a/ci/README.md b/ci/README.md index c4d5eb5..e0875ce 100644 --- a/ci/README.md +++ b/ci/README.md @@ -20,7 +20,7 @@ et manuellement (`workflow_dispatch`). Jobs statiques (+ une suite `unittest` pa | `check-artifacts` | `ci/check_artifacts.sh` | Reproductibilité : chaque `out/*.json` versionné == build frais | ✅ oui | | `check-regression` | `ci/check_regression.sh` | Fraîcheur : `qa/regression/out/regression_run.json` (`run`) == run frais + verdict PASS | ✅ oui | | `check-ci-integrity` | `ci/check_ci_integrity.sh` | Intégrité du câblage : `gate.needs` == tous les jobs non-manuels · chaque `ci/*.sh` câblé au gate | ✅ oui | -| `check-readme-claims` | `ci/check_readme_claims.sh` | Intégrité des chiffres des docs d'entrée : chaque nombre de `README.md` « État courant », de la fiche QA « Verdict agrégé » **et** de la colonne « Tests » par suite des tables de fiches **et** des agrégats en prose (Total CRM / RBAC / e-CF) **et** des comptes par workspace des portails (cartes/liens/rôles, ×2 docs) **et** des comptes RBAC des 3 volets (table Nb rôles/profil, ligne agrégée apply_plan, ventilation userperm) **et** des comptes de mots-clés SEO (total + fr/en/es, ×2 docs) == artefact cité (#6) | ✅ oui | +| `check-readme-claims` | `ci/check_readme_claims.sh` | Intégrité des chiffres des docs d'entrée : chaque nombre de `README.md` « État courant », de la fiche QA « Verdict agrégé » **et** de la colonne « Tests » par suite des tables de fiches **et** des agrégats en prose (Total CRM / RBAC / e-CF) **et** des comptes par workspace des portails (cartes/liens/rôles, ×2 docs) **et** des comptes RBAC des 3 volets (table Nb rôles/profil, ligne agrégée apply_plan, ventilation userperm) **et** des comptes de mots-clés SEO (total + fr/en/es, ×2 docs) **et** du pipeline vente CRM (états/transitions, ×2 docs) == artefact cité (#6) | ✅ oui | | `gate` | — | Agrégat vert = gate qualité 4Big franchi | ✅ oui | Aucune dépendance réseau/marketplace hors `actions/checkout`. Tout tourne avec @@ -186,6 +186,18 @@ le total, attrape une **compensation** entre langues (fr −1 / en +1) qui laiss le total juste et la ventilation fausse — ajouter un projet ferait sinon dériver **les deux** docs en silence. +Même classe enfin sur le **pipeline vente CRM** : le couple « **9 états · 11 +transitions** » (titre de section du README du module `crm/workflow_vente`) et le +**même** compte d'états « les **9 états** du pipeline » (README du DocType porteur +`crm/dossier_vente`, dérivé du **même** graphe) étaient transcrits **à la main** +dans **deux** docs sans aucun gate. Les deux nombres sont recomputés depuis +`crm/workflow_vente/out/MANIFEST.json` (`counts.states` + `counts.transitions`, +recomptés du `workflow_vente_spec.json` à chaque build, byte-gaté par +`check_artifacts`). Recomputer **transitions** en plus de **states** mord une +dérive du 2ᵉ nombre là où le compte d'états seul (repris dans `dossier_vente`) +resterait aveugle — ajouter un état/une transition ferait sinon dériver **les +deux** docs en silence. + Un claim **absent** échoue aussi (la dérive de formulation qui ferait disparaître un chiffre est elle-même une régression de traçabilité). stdlib pur (bash/git/python3), zéro réseau. diff --git a/ci/check_readme_claims.sh b/ci/check_readme_claims.sh index ffed2df..635d0ed 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -51,6 +51,11 @@ # • total + ventilation « 258 mots-clés (fr=87 · en=87 · es=84) » transcrits À # LA MAIN dans DEUX docs → seo MANIFEST.counts # (keywords_total + keywords_per_lang, recomptés de projets_master, byte-gatés) +# crm/workflow_vente/README.md + crm/dossier_vente/README.md (pipeline vente) : +# • « N états · M transitions » (titre du README workflow) + « N états du +# pipeline » (README DocType porteur, même compte dérivé du même graphe) +# transcrits À LA MAIN dans DEUX docs → workflow_vente +# MANIFEST.counts (states + transitions, recomptés du spec pipeline, byte-gatés) # rbac/{roleprofile_gen,apply_plan,userperm_gen}/README.md (comptes 3 volets) : # • table « Nb rôles » PAR profil + total bijectif → roleprofile MANIFEST # • ligne agrégée « 6 étapes · 50 rôles / 116 DocPerm / 28 UP / 6 Role @@ -631,6 +636,60 @@ if seo_exp is not None: bad(f"SEO · {path_f} — dit {'/'.join(m.groups())} MAIS MANIFEST dit " f"{'/'.join(seo_exp)} ({seo_src}) → régénérer le doc") +# ============================================================================ +# CRM · pipeline vente (états / transitions) — même classe de « vert trompeur », +# transcrit À LA MAIN dans DEUX README de module. +# ---------------------------------------------------------------------------- +# Le générateur workflow_vente émet out/MANIFEST.json dont `counts` porte states +# et transitions, RECOMPTÉS à chaque build depuis workflow_vente_spec.json (le +# graphe pipeline · byte-gaté par check_artifacts ⇒ source faisant autorité). Ce +# couple est recopié À LA MAIN à DEUX endroits : le titre de section du README du +# module workflow (« ## Le pipeline (9 états · 11 transitions) ») ET le README du +# DocType porteur dossier_vente (« options = les **9 états** du pipeline »), qui +# DÉRIVE le même compte d'états du même graphe (source unique · #5). Aucun gate ne +# les couvrait : ajouter un état/une transition au pipeline ferait dériver LES +# DEUX docs en silence pendant que l'artefact dit autre chose — même piège que les +# triplets par workspace des portails ou les mots-clés SEO. On RECOMPUTE ici +# (states + transitions) depuis workflow_vente MANIFEST.counts (déjà byte-gaté · +# zéro duplication du compteur du générateur) et on exige l'égalité avec les DEUX +# docs. Un claim absent échoue AUSSI (traçabilité). Recomputer transitions EN PLUS +# de states mord une dérive du 2e nombre là où le seul titre du README workflow +# (qui n'a pas les transitions dans dossier_vente) resterait aveugle. +# ============================================================================ +WF_RD = f"{D}/crm/workflow_vente/README.md" +DV_RD = f"{D}/crm/dossier_vente/README.md" +try: + wf_c = load("crm/workflow_vente/out/MANIFEST.json")["counts"] + wf_states, wf_trans = str(wf_c["states"]), str(wf_c["transitions"]) +except (OSError, KeyError, ValueError) as e: + bad(f"CRM pipeline · MANIFEST workflow_vente illisible/inattendu : {e}") + wf_states = None +if wf_states is not None: + # 1) README workflow — titre de section (états + transitions) + wf_src = f"états={wf_states} transitions={wf_trans}" + wf_txt = read_norm(WF_RD) + if wf_txt is not None: + m = re.search(r"## Le pipeline \((\d+) états · (\d+) transitions\)", wf_txt) + if m is None: + bad(f"CRM pipeline · {WF_RD} — titre pipeline INTROUVABLE (attendu {wf_src})") + elif m.groups() == (wf_states, wf_trans): + good(f"CRM pipeline · {WF_RD} — {'/'.join(m.groups())} == MANIFEST ({wf_src})") + else: + bad(f"CRM pipeline · {WF_RD} — dit {'/'.join(m.groups())} MAIS MANIFEST " + f"dit {wf_states}/{wf_trans} ({wf_src}) → régénérer le README") + # 2) README dossier_vente — même compte d'états, dérivé du même graphe + dv_txt = read_norm(DV_RD) + if dv_txt is not None: + m = re.search(r"les \*\*(\d+) états\*\* du pipeline", dv_txt) + if m is None: + bad(f"CRM pipeline · {DV_RD} — claim « N états du pipeline » INTROUVABLE " + f"(attendu états={wf_states})") + elif m.group(1) == wf_states: + good(f"CRM pipeline · {DV_RD} — états={m.group(1)} == MANIFEST (états={wf_states})") + else: + bad(f"CRM pipeline · {DV_RD} — dit états={m.group(1)} MAIS MANIFEST dit " + f"états={wf_states} → régénérer le README") + sys.exit(1 if FAIL else 0) PY rc=$?