[DTP-Worker] Sprint 8 · buffer L75 · Domaine CRM : le pipeline vente « 9 états · 11 transitions » était transcrit À LA MAIN dans DEUX README de module (titre du module workflow_vente + « 9 états du pipeline » de dossier_vente, même compte du même graphe) sans AUCUN gate — ajouter un état/une transition ferait dériver les deux docs en silence pendant que crm/workflow_vente/out/MANIFEST.json (byte-gaté par check_artifacts, counts recomptés du workflow_vente_spec.json) dit autre chose → nouveau bloc dans check_readme_claims recomputant (states + transitions) depuis MANIFEST.counts et exigeant l'égalité avec les deux docs (4 morsures : dérive états titre · dérive transitions captée là où états seul est aveugle · dérive états 2e doc · claim disparu)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+13
-1
@@ -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.
|
||||
|
||||
@@ -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=$?
|
||||
|
||||
Reference in New Issue
Block a user