[DTP-Worker] Sprint 8 · buffer L60 · Domaine CRM/dossier_vente (2e surface) : le CATALOGUE de projets du DocType porteur (« Projet (Select P01..P09 ancré sur CLAUDE.md) ») était ANCRÉ à CLAUDE.md EN PROSE sans AUCUN gate d'IDENTITÉ. Le bloc CRM pipeline existant ne recompute que le COMPTE d'états (« les 9 états du pipeline ») du MÊME README — jamais la liste des projets. Ce catalogue est DATA-DERIVED : le champ Select projet de crm/dossier_vente/out/doctype_oto_dossier_vente.json (byte-gaté par check_artifacts) porte l'ensemble ORDONNÉ des 9 options (P01 Structure … P09 1069 Crisfer), transcription de la section « ## Projets » de CLAUDE.md (source faisant autorité · contraintes #6/#7). PIÈGE : check_artifacts ne prouve QUE DocType==spec (byte-for-byte), et la spec RECOPIE elle-même le catalogue — donc TOUTE la chaîne (spec → DocType) peut DÉRIVER de CLAUDE.md en restant BYTE-VERTE. RENOMMER un projet (P03 Nakua → P03 Nakua Bay) · en AJOUTER un (P10) · en RETIRER un laisse le Select périmé, contredisant silencieusement CLAUDE.md pendant que la prose « ancré sur CLAUDE.md » devient FAUSSE — « vert trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS de builder, pas l'ancrage à CLAUDE.md) n'attrape → nouveau bloc recomputant le catalogue depuis « ## Projets » de CLAUDE.md (zéro duplication · source unique · même esprit que le roadmap-anchor INV11 qui ancre les roadmap_line au fichier roadmap) et exigeant que les options du Select l'égalent EXACTEMENT (set-diff absent ET en trop, PUIS ordre · NFC) + que la prose porte la plage P01..P<dernier> + le mot d'ancrage. Cohérences croisées en bonus (mordent un catalogue CLAUDE.md internement incohérent) : catalogue NON VIDE, codes SANS DOUBLON et CONTIGUS depuis P01. Un claim absent échoue AUSSI. 6 morsures vérifiées : CLAUDE.md rename P03 Nakua→P03 Nakua Bay (Select DÉRIVE : absents=[P03 Nakua Bay] en trop=[P03 Nakua]) · CLAUDE.md +P10 (Select absents=[P10 …] + README dit P01..P09 MAIS catalogue P01..P10) · DocType réordonné (bon ENSEMBLE mais ORDRE ≠) · DocType renomme 1 libellé (P07 Aqua Terra Las Terrenas→P07 Aqua Terra) · README plage P01..P09→P01..P08 · README ancrage supprimé (claim INTROUVABLE) ; restauré = green : Select projet == CLAUDE.md (9 projets, ordre inclus) · plage P01..P09 + ancrage · catalogue contigu · exit 0. État courant : aucun projet périmé (anti-invention #6, rien à réécrire) — le défaut est la surface ungated. ci/README.md (table + détail « catalogue de projets ancré sur CLAUDE.md ») mis à jour · 7 gates re-verts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-07-31 18:38:10 +00:00
parent 607b22282b
commit 7b775677a6
3 changed files with 168 additions and 1 deletions
+60
View File
@@ -1,5 +1,65 @@
# Activity Log · 2026-07-31 · Claude Code DTP # Activity Log · 2026-07-31 · Claude Code DTP
## Session `20260731_183124` · Buffer S8 · Domaine CRM/dossier_vente (2ᵉ surface) : le **catalogue de projets** du DocType porteur (« Projet (`Select` **P01..P09** ancré sur CLAUDE.md) ») était **ancré à CLAUDE.md EN PROSE** sans AUCUN gate d'identité — le bloc CRM pipeline existant ne recompute que le COMPTE d'états (« les **9 états** du pipeline ») du même README.
**Tâche** : **Sprint 8 · buffer L60** (DevOps CI/CD · QA). Roadmap fonctionnellement
close ; poursuite de la série anti-dérive (CLAUDE.md #6). Cette session cible une
**2ᵉ surface data-derived du README `crm/dossier_vente`** — le champ `Select`
`projet` du DocType porteur, dont les 9 options (`P01 Structure``P09 1069
Crisfer`) sont une transcription de la section « ## Projets » de CLAUDE.md, jamais
ANCRÉE par aucun gate.
**Dérive silencieuse fermée** :
- `05_deliverables_mvp/crm/dossier_vente/README.md:46` : « Projet (`Select`
**P01..P09** ancré sur CLAUDE.md) » ; `doctype_spec.json` `_comment` : « les
Select `projet`/`devise` sont ancrés sur CLAUDE.md … pas inventés ».
- Source faisant autorité : la section **« ## Projets » de CLAUDE.md** (contraintes
#6/#7 · liste canonique des projets). Le DocType (`out/doctype_oto_dossier_vente.json`,
byte-gaté par `check_artifacts`) porte l'ensemble ORDONNÉ des 9 options.
- `check_artifacts` ne prouve QUE _DocType == spec_ (byte-for-byte) ; la spec recopie
elle-même le catalogue → **toute la chaîne (spec → DocType) peut DÉRIVER de
CLAUDE.md en restant byte-verte** : **RENOMMER** un projet (`P03 Nakua``P03
Nakua Bay`), en **AJOUTER** (`P10`) ou en **RETIRER** un laisse le Select périmé,
contredisant silencieusement CLAUDE.md pendant que la prose « ancré sur CLAUDE.md »
devient FAUSSE — « vert trompeur » qu'aucune suite `tests/` (qui teste des FONCTIONS
de builder, pas l'ancrage à CLAUDE.md) n'attrape.
- **État courant** : **aucun projet périmé** — les 9 options recoupent CLAUDE.md
exactement (anti-invention #6, rien à réécrire). Le défaut est la **surface ungated**.
**Nouveau bloc dans `ci/check_readme_claims.sh`** : recompute le catalogue depuis
« ## Projets » de CLAUDE.md (zéro duplication · source unique · même esprit que le
**roadmap-anchor** INV11) et exige que les options du `Select` `projet` l'égalent
**exactement** (set-diff absent ET en trop, **puis ordre** · NFC), plus la plage
`P01..P<dernier>` + le mot d'ancrage dans le README. Cohérences croisées en bonus
(mordent un catalogue CLAUDE.md internement incohérent) : catalogue **non vide,
sans doublon et contigu** depuis `P01`. Un claim absent échoue AUSSI.
### Preuve (6 morsures vérifiées + restauration verte)
| Injection | Résultat |
|---|---|
| CLAUDE.md `P03 Nakua → P03 Nakua Bay` (DocType stale) | ✗ Select DÉRIVE : absents=`[P03 Nakua Bay]` en trop=`[P03 Nakua]` · exit 1 |
| CLAUDE.md `+P10` (catalogue grandit) | ✗ Select absents=`[P10 …]` + README dit P01..P09 MAIS catalogue P01..P10 · exit 1 |
| DocType Select réordonné (bon ensemble, ordre ≠) | ✗ bon ENSEMBLE mais ORDRE ≠ CLAUDE.md · exit 1 |
| DocType renomme 1 libellé (`P07 Aqua Terra Las Terrenas → P07 Aqua Terra`) | ✗ Select DÉRIVE : absents/en trop · exit 1 |
| README plage `P01..P09 → P01..P08` | ✗ README dit P01..P08 MAIS catalogue P01..P09 · exit 1 |
| README ancrage supprimé (reformulé) | ✗ claim « Projet (`Select` P01..P09 ancré sur CLAUDE.md) » INTROUVABLE · exit 1 |
| restauré | ✓ Select `projet` == CLAUDE.md (9 projets, ordre inclus) · plage P01..P09 + ancrage · catalogue contigu · exit 0 |
- **`ci/README.md`** — surface ajoutée à la table récap du pipeline + paragraphe
descriptif (« catalogue de projets ancré sur CLAUDE.md »).
Suite CI complète re-verte (7 gates) : `guard_constraints` · `validate_json` ·
`check_docs` · `check_artifacts` · `check_regression` · `check_ci_integrity` ·
`check_readme_claims`.
**Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ;
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact).
**Auto-score 4Big** : 96/100.
---
## Session `20260731_180119` · Buffer S8 · Domaine SEO (3e surface) : la **carte hreflang** (« carte `alternate` **FR/EN/ES** + `x-default` par page ») était décrite EN PROSE dans le README du module `seo` sans AUCUN gate d'IDENTITÉ — les deux blocs SEO existants gatent le COMPTE de mots-clés (artefact `seo_keywords`) et les `@type` du graphe schema.org (artefact `seo_schema_org`), jamais le jeu de langues hreflang qui vit dans un **troisième** artefact. ## Session `20260731_180119` · Buffer S8 · Domaine SEO (3e surface) : la **carte hreflang** (« carte `alternate` **FR/EN/ES** + `x-default` par page ») était décrite EN PROSE dans le README du module `seo` sans AUCUN gate d'IDENTITÉ — les deux blocs SEO existants gatent le COMPTE de mots-clés (artefact `seo_keywords`) et les `@type` du graphe schema.org (artefact `seo_schema_org`), jamais le jeu de langues hreflang qui vit dans un **troisième** artefact.
**Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA). Roadmap fonctionnellement **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA). Roadmap fonctionnellement
+19 -1
View File
@@ -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-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-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-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) **et** du pipeline vente CRM (états/transitions, ×2 docs + énumération des transitions à séparation des pouvoirs `{action | allow_self_approval=0}` recomputée de `workflow.json`) **et** de l'app mobile Expo (onglets/rôles/langues/identifiants a_confirmer + énumération ordonnée des onglets recomputée de `role_navigation`, bijection onglet⇔portail) **et** du DocType CONFOTUR (champs/données/sections/rôles/évènements de dépôt, ×2 docs) **et** de l'audit 5D (contrôles/dimensions ×2 docs + ventilation verdict pass/fail/à-confirmer + énumération des open items `control→dimension→propriétaire` recomputée de `audit_report.open_items`) **et** du run-book de déploiement VPS (phases ×2 dans la fiche devops + confirmations préalables count & énumération dans le README) **et** du run-sheet de démo (nombre de modules cités dans le diagramme du README) **et** du montage Chat OTOIA par portail (`Custom Block`/configs runtime/portails, tous = 5 aujourd'hui, ×2 docs) **et** du module de recette `qa/acceptance` (couple livrables de sprint + métriques succès MVP, ×2 docs de module au-delà du README d'entrée) **et** du barème de l'auditeur 4Big (nombre de critères en prose + poids par critère de la table + « generator (N critères) », recomputés de `quality_report.criteria`) **et** de la carte de renormalisation par archétype de l'auditeur 4Big (critères EXCLUS de `data_room`/`parser`/`contract`, recomputés des `checks` par archétype de `quality_report.modules`) **et** du contrat de format de l'e-NCF fiscal (`E + tipoeCF(2) + secuencia(10) = 13` : préfixe/longueurs des segments/longueur totale, ×3 README + fiche `erpnext_backend`, recomputés du bloc `e_ncf` d'`ecf_plan.json` + cohérences croisées `1+tipo+seq==longueur` & `|regex digits|==tipo+seq`) **et** de la cross-cohérence du barème de commissions `crm/commissions` (états brouillon exclus recomputés de `{doc_status=0}` de `workflow.json` + champs de base recomputés de `{base_field}` de `commission_plan.json` + invariant 3 « commissionne seulement des états soumis ») **et** du catalogue RBAC `rbac/fixtures_gen` (DocTypes `custom` à créer énumérés == `MANIFEST.custom_doctypes_a_confirmer` + séparation des pouvoirs : le rôle unique porteur de `set_user_permissions` == `custom_docperm.json`) **et** de la composition du graphe schema.org SEO (les `@type` du `@graph``Organization` racine + une `Residence` par projet — recomputés de `seo_schema_org.json` + bijection listing⇔projet & absence d'`offers`) **et** de la carte hreflang SEO (les langues de contenu `alternate` `FR/EN/ES` + `x-default` recomputées de `seo_hreflang.json` + cohérences croisées : carte uniforme sur toutes les pages · `x-default` présent partout · langue par défaut ∈ langues de contenu · `x-default`.href == `canonical` == href de la langue par défaut) == 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 + énumération des transitions à séparation des pouvoirs `{action | allow_self_approval=0}` recomputée de `workflow.json`) **et** de l'app mobile Expo (onglets/rôles/langues/identifiants a_confirmer + énumération ordonnée des onglets recomputée de `role_navigation`, bijection onglet⇔portail) **et** du DocType CONFOTUR (champs/données/sections/rôles/évènements de dépôt, ×2 docs) **et** de l'audit 5D (contrôles/dimensions ×2 docs + ventilation verdict pass/fail/à-confirmer + énumération des open items `control→dimension→propriétaire` recomputée de `audit_report.open_items`) **et** du run-book de déploiement VPS (phases ×2 dans la fiche devops + confirmations préalables count & énumération dans le README) **et** du run-sheet de démo (nombre de modules cités dans le diagramme du README) **et** du montage Chat OTOIA par portail (`Custom Block`/configs runtime/portails, tous = 5 aujourd'hui, ×2 docs) **et** du module de recette `qa/acceptance` (couple livrables de sprint + métriques succès MVP, ×2 docs de module au-delà du README d'entrée) **et** du barème de l'auditeur 4Big (nombre de critères en prose + poids par critère de la table + « generator (N critères) », recomputés de `quality_report.criteria`) **et** de la carte de renormalisation par archétype de l'auditeur 4Big (critères EXCLUS de `data_room`/`parser`/`contract`, recomputés des `checks` par archétype de `quality_report.modules`) **et** du contrat de format de l'e-NCF fiscal (`E + tipoeCF(2) + secuencia(10) = 13` : préfixe/longueurs des segments/longueur totale, ×3 README + fiche `erpnext_backend`, recomputés du bloc `e_ncf` d'`ecf_plan.json` + cohérences croisées `1+tipo+seq==longueur` & `|regex digits|==tipo+seq`) **et** de la cross-cohérence du barème de commissions `crm/commissions` (états brouillon exclus recomputés de `{doc_status=0}` de `workflow.json` + champs de base recomputés de `{base_field}` de `commission_plan.json` + invariant 3 « commissionne seulement des états soumis ») **et** du catalogue RBAC `rbac/fixtures_gen` (DocTypes `custom` à créer énumérés == `MANIFEST.custom_doctypes_a_confirmer` + séparation des pouvoirs : le rôle unique porteur de `set_user_permissions` == `custom_docperm.json`) **et** de la composition du graphe schema.org SEO (les `@type` du `@graph``Organization` racine + une `Residence` par projet — recomputés de `seo_schema_org.json` + bijection listing⇔projet & absence d'`offers`) **et** de la carte hreflang SEO (les langues de contenu `alternate` `FR/EN/ES` + `x-default` recomputées de `seo_hreflang.json` + cohérences croisées : carte uniforme sur toutes les pages · `x-default` présent partout · langue par défaut ∈ langues de contenu · `x-default`.href == `canonical` == href de la langue par défaut) **et** du catalogue de projets du DocType `crm/dossier_vente` (le Select `projet` — les 9 projets `P01..P09` avec libellés — **ancré sur** la section « ## Projets » de `CLAUDE.md`, recomputé de CLAUDE.md : ensemble + ordre == options du DocType byte-gaté, plage `P01..P09` + mot d'ancrage dans le README, catalogue contigu/sans-doublon en bonus) == artefact cité (#6) | ✅ oui |
| `gate` | — | Agrégat vert = gate qualité 4Big franchi | ✅ oui | | `gate` | — | Agrégat vert = gate qualité 4Big franchi | ✅ oui |
Aucune dépendance réseau/marketplace hors `actions/checkout`. Tout tourne avec Aucune dépendance réseau/marketplace hors `actions/checkout`. Tout tourne avec
@@ -218,6 +218,24 @@ contenu (ni vacante ni étrangère), et pour chaque page `x-default`.href ==
défaut = canonical » du README). Un claim supprimé (« carte INTROUVABLE ») échoue défaut = canonical » du README). Un claim supprimé (« carte INTROUVABLE ») échoue
**aussi**. **aussi**.
**Catalogue de projets ancré sur CLAUDE.md** (2ᵉ surface du README
`crm/dossier_vente`) : le champ `Select` `projet` du DocType porteur
(`out/doctype_oto_dossier_vente.json`, byte-gaté par `check_artifacts`) énumère les
**9 projets OTO** (`P01 Structure``P09 1069 Crisfer`) que le README résume
« Projet (`Select` **P01..P09** ancré sur CLAUDE.md) ». Ce catalogue est une
**transcription** : la section « ## Projets » de **CLAUDE.md** est la source
faisant autorité (#6/#7), mais `check_artifacts` ne prouve que _DocType == spec_
(byte-for-byte) — la spec recopie elle-même la liste, donc toute la chaîne peut
**dériver** de CLAUDE.md en restant byte-verte : **renommer** un projet
(`P03 Nakua``P03 Nakua Bay`), en **ajouter** (`P10`) ou en **retirer** un laisse
le Select périmé tandis que la prose « ancré sur CLAUDE.md » devient fausse. Même
esprit que le **roadmap-anchor** (INV11 · `roadmap_line` ancré au fichier roadmap) :
on recompute le catalogue depuis « ## Projets » de CLAUDE.md et on exige que les
options du Select l'égalent **exactement** (set-diff absent ET en trop, **puis
ordre**), plus la plage `P01..P<dernier>` + le mot d'ancrage dans le README. En
bonus, le catalogue CLAUDE.md est vérifié **non vide, sans doublon et contigu**
depuis `P01` (byte-déterminisme du contrat). Un claim supprimé échoue **aussi**.
Même classe enfin sur le **pipeline vente CRM** : le couple « **9 états · 11 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 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 **même** compte d'états « les **9 états** du pipeline » (README du DocType porteur
+89
View File
@@ -1991,6 +1991,95 @@ if hl_pages:
f"{'+x-default' if has_xdef else ''} MAIS carte " f"{'+x-default' if has_xdef else ''} MAIS carte "
f"{sorted(content_langs)}+x-default{detail} → régénérer le README") f"{sorted(content_langs)}+x-default{detail} → régénérer le README")
# ============================================================================
# CRM/dossier_vente (2e surface) · CATALOGUE DE PROJETS « P01..P09 ancré sur
# CLAUDE.md » — le Select `projet` du DocType porteur.
# ----------------------------------------------------------------------------
# Le bloc CRM pipeline ci-dessus recompute le COMPTE d'états du même README
# (« les 9 états du pipeline »). Restait, dans le MÊME README, une surface
# data-derived distincte et jamais gatée : la liste des PROJETS. Le champ Select
# `projet` du DocType (out/doctype_oto_dossier_vente.json, byte-gaté par
# check_artifacts) porte l'ENSEMBLE ORDONNÉ des 9 projets OTO
# (« P01 Structure », « P02 Coral del Sur », …). Le README:46 le résume
# « Projet (`Select` **P01..P09** ancré sur CLAUDE.md) » et le `_comment` du
# doctype_spec.json le déclare « ancré sur CLAUDE.md … pas inventé ». Or ce
# catalogue est une TRANSCRIPTION MANUELLE : la section « ## Projets » de
# CLAUDE.md est la SOURCE FAISANT AUTORITÉ (contraintes #6/#7 · liste des
# projets), mais AUCUN gate ne compare les options du DocType à CLAUDE.md.
# check_artifacts ne prouve que DocType == spec (byte-for-byte) ; la spec
# elle-même recopie la liste — donc TOUTE la chaîne (spec → DocType) peut
# DÉRIVER de CLAUDE.md en restant byte-verte : RENOMMER un projet dans CLAUDE.md
# (« P03 Nakua » → « P03 Nakua Bay »), en AJOUTER un (P10) ou en RETIRER un
# laisse le Select périmé, contredisant silencieusement CLAUDE.md pendant que la
# prose « ancré sur CLAUDE.md » devient FAUSSE — « vert trompeur » qu'aucune
# suite tests/ (qui teste des FONCTIONS de builder, pas l'ancrage à CLAUDE.md)
# n'attrape. Même esprit que le roadmap-anchor (INV11) qui ancre les
# `roadmap_line` de l'artefact au fichier roadmap : ici on ANCRE le catalogue
# projet du DocType à CLAUDE.md. On RECOMPUTE le catalogue depuis « ## Projets »
# de CLAUDE.md (zéro duplication · source unique) et on exige que les options du
# Select l'égalent EXACTEMENT (absent ET en trop via set-diff, puis ORDRE), et
# que la prose porte la bonne plage P01..P<dernier> + le mot d'ancrage. Un claim
# absent échoue AUSSI. Cohérences croisées en bonus (mordent un catalogue
# CLAUDE.md internement incohérent) : catalogue NON VIDE, codes SANS DOUBLON et
# CONTIGUS depuis P01 (P01,P02,…,P0N — byte-déterminisme du contrat).
# ============================================================================
def _nfc(s):
return unicodedata.normalize("NFC", s)
try:
cm_raw = open("CLAUDE.md", encoding="utf-8").read()
mcm = re.search(r"^## Projets\s*\n(.+)$", cm_raw, re.M)
if mcm is None:
raise ValueError("section « ## Projets » INTROUVABLE")
# catalogue faisant autorité = tokens « PNN Label » séparés par « · »
cat = [_nfc(t.strip()) for t in mcm.group(1).split("·") if t.strip()]
cat_codes = [re.match(r"(P\d{2})\b", p).group(1) for p in cat]
except (OSError, ValueError, AttributeError) as e:
bad(f"CRM projets · CLAUDE.md « ## Projets » illisible/inattendu : {e}"); cat = None
if cat is not None:
# bonus : catalogue non vide · codes sans doublon · contigus depuis P01
contig = cat_codes == [f"P{i:02d}" for i in range(1, len(cat_codes) + 1)]
if not cat or len(set(cat_codes)) != len(cat_codes) or not contig:
bad(f"CRM projets · catalogue CLAUDE.md dégénéré : codes={cat_codes} "
f"(attendu P01..P{len(cat_codes):02d} contigus, sans doublon, non vide)")
else:
good(f"CRM projets · catalogue CLAUDE.md sain : {len(cat)} projets "
f"P01..{cat_codes[-1]} contigus")
# 1) ANCRAGE : options du Select `projet` du DocType == catalogue CLAUDE.md
try:
dv_dt = load("crm/dossier_vente/out/doctype_oto_dossier_vente.json")
pf = next(f for f in dv_dt["fields"] if f.get("fieldname") == "projet")
dt_opts = [_nfc(o.strip()) for o in pf["options"].split("\n") if o.strip()]
except (OSError, KeyError, ValueError, StopIteration) as e:
bad(f"CRM projets · DocType Select `projet` illisible/inattendu : {e}"); dt_opts = None
if dt_opts is not None:
if dt_opts == cat:
good(f"CRM projets · Select `projet` du DocType == CLAUDE.md "
f"({len(cat)} projets, ordre inclus)")
elif set(dt_opts) == set(cat):
bad(f"CRM projets · Select `projet` porte le bon ENSEMBLE mais un ORDRE "
f"≠ CLAUDE.md : DocType={dt_opts} vs {cat} → régénérer le DocType")
else:
miss = [p for p in cat if p not in dt_opts]
extra = [p for p in dt_opts if p not in cat]
detail = (f" · absents={miss}" if miss else "") + \
(f" · en trop={extra}" if extra else "")
bad(f"CRM projets · Select `projet` du DocType DÉRIVE de CLAUDE.md"
f"{detail} → régénérer le DocType (ou aligner CLAUDE.md)")
# 2) PROSE : README résume la plage P01..P<dernier> + ancrage CLAUDE.md
dv_rd2 = read_norm(f"{D}/crm/dossier_vente/README.md")
if dv_rd2 is not None:
mp = re.search(r"Projet \(`Select` \*\*(P\d{2})\.\.(P\d{2})\*\* "
r"ancré sur CLAUDE\.md\)", dv_rd2)
if mp is None:
bad(f"CRM projets · {D}/crm/dossier_vente/README.md — claim « Projet "
f"(`Select` P01..P{cat_codes[-1][1:]} ancré sur CLAUDE.md) » INTROUVABLE")
elif (mp.group(1), mp.group(2)) == (cat_codes[0], cat_codes[-1]):
good(f"CRM projets · README plage {mp.group(1)}..{mp.group(2)} "
f"+ ancrage CLAUDE.md == catalogue")
else:
bad(f"CRM projets · README dit {mp.group(1)}..{mp.group(2)} MAIS catalogue "
f"CLAUDE.md va de {cat_codes[0]} à {cat_codes[-1]} → régénérer le README")
sys.exit(1 if FAIL else 0) sys.exit(1 if FAIL else 0)
PY PY
rc=$? rc=$?