[DTP-Worker] Sprint 8 · buffer · Legal/CONFOTUR : IDENTITÉ des ENTITÉS porteuses du DocType CONFOTUR Application ANCRÉE sur CLAUDE.md §Entités — le champ Select entite_porteuse (le MENU DÉROULANT qui fixe QUELLE entité juridique porte chaque dossier d'incitation touristique) offre pour options les 7 entités canoniques du mandat (WAF·WA SRL·AC Arias Cuevas·Consortium ECR DR·Helios RD·Ploutos·9060 QC), DÉRIVÉES via options_source de confotur_spec.json[entites] et BYTE-GATÉES pour la REPRODUCTIBILITÉ par check_artifacts (l'artefact se reconstruit depuis le spec) mais JAMAIS ANCRÉES à CLAUDE.md §Entités, leur source faisant autorité. Les blocs CONFOTUR amont ne gatent que le COMPTE de champs/rôles et la cross-cohérence des PERMISSIONS, aveugles à l'IDENTITÉ de cette liste. Piège #6 : RENOMMER une entité dans CLAUDE.md §Entités (9060 QC9061 QC) ou dans confotur_spec.json[entites] puis régénérer laisse le DocType offrir un menu qui CONTREDIT/omet une entité canonique pendant que la byte-gate reste VERTE (l'artefact se reconstruit fidèlement… depuis un spec dérivé) — « vert trompeur » de la MÊME classe que la marque SEO org (§Entités) et les tokens branding (#4). L'agent ERPNext Backend importerait un DocType dont le déroulant liste une entité FANTÔME ou en omet une (la restriction même que reqd pose). Aucune suite tests/ (FONCTIONS de génération, jamais l'ancre à CLAUDE.md) ne l'attrape. Gate ajouté (bloc « Legal · CONFOTUR — IDENTITÉ des ENTITÉS porteuses ») : les 7 entités re-dérivées des tokens en gras de la section « ## Entités » de CLAUDE.md, puis (a) options de l'artefact == CLAUDE.md ORDRE exact · (b) spec[entites] == CLAUDE.md l'INPUT ancré · (c) le spec DÉCLARE l'ancrage (entites_source cite CLAUDE.md + chaque entité) · (d) wiring intact options_source == "entites" · (e) README cite l'ancre CLAUDE.md + nomme chaque entité (nom complet OU 1er token · abréviation « AC » tolérée · #6). 7 morsures vérifiées (CLAUDE.md rename → 4 bites a/b/c/e simultanées, l'ancre est vive · artefact drop option · artefact reorder · spec entites renommé · spec source sans CLAUDE.md · spec options_source hardcoded · README retire une entité), restauré vert, 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-08-01 05:41:06 +00:00
parent 3e7bc33fb7
commit e53d60fc0b
3 changed files with 218 additions and 1 deletions
+27 -1
View File
File diff suppressed because one or more lines are too long
+123
View File
@@ -4011,6 +4011,129 @@ if ci_lines is not None:
bad("Job CI fiches · AUCUN token `*-tests` trouvé dans les fiches — la "
"disparition de la colonne « Job CI » est elle-même une régression.")
# ============================================================================
# Legal · CONFOTUR — IDENTITÉ des ENTITÉS PORTEUSES ancrée sur CLAUDE.md §Entités
# ----------------------------------------------------------------------------
# Le champ Select `entite_porteuse` du DocType CONFOTUR Application — le menu
# déroulant qui fixe QUELLE entité juridique porte chaque dossier d'incitation —
# offre pour options les 7 entités canoniques du mandat (WAF · WA SRL · AC Arias
# Cuevas · Consortium ECR DR · Helios RD · Ploutos · 9060 QC). Ces options sont
# DÉRIVÉES via `options_source` de confotur_spec.json[entites] et byte-gatées pour
# la REPRODUCTIBILITÉ par check_artifacts (l'artefact se reconstruit depuis le
# spec) — mais JAMAIS ANCRÉES à CLAUDE.md §Entités, leur source faisant autorité.
# Les blocs CONFOTUR amont gatent le COMPTE de champs/rôles et la cross-cohérence
# des PERMISSIONS, aveugles à l'identité de cette liste d'entités.
# Piège #6 : RENOMMER une entité dans CLAUDE.md §Entités (9060 QC→9061 QC) ou dans
# confotur_spec.json[entites] puis régénérer laisse le DocType offrir un menu qui
# CONTREDIT/omet une entité canonique du mandat pendant que check_artifacts reste
# VERT (l'artefact se reconstruit… depuis un spec dérivé) — « vert trompeur » de
# la MÊME classe que la marque SEO org (§Entités) et les tokens branding (#4).
# Aucune suite tests/ (FONCTIONS de génération) ne l'attrape → l'agent ERPNext
# Backend importerait un DocType dont le déroulant liste une entité fantôme ou en
# omet une (la restriction même que le champ `reqd` pose).
# GATE : (a) options de l'artefact == entités CLAUDE.md §Entités (ORDRE exact) ;
# (b) spec[entites] == CLAUDE.md (l'INPUT ancré) ; (c) le spec DÉCLARE l'ancrage
# (entites_source cite CLAUDE.md + chaque entité) ; (d) wiring intact
# (options_source == "entites" · le déroulant DÉRIVE de la liste, pas d'un dur) ;
# (e) README cite l'ancre CLAUDE.md + nomme chaque entité (nom complet OU 1er
# token — l'abréviation « AC » de « AC Arias Cuevas » est un choix éditorial
# toléré · #6). Un claim absent échoue AUSSI (traçabilité #6).
# ============================================================================
try:
_cm_ce = open("CLAUDE.md", encoding="utf-8").read()
_sec_ce = re.search(r"^## Entités\s*\n(.*?)(?=^## )", _cm_ce, re.S | re.M)
if not _sec_ce:
raise ValueError("section « ## Entités » introuvable")
cm_ents = [_nfc(x.strip()) for x in re.findall(r"\*\*([^*]+?)\*\*", _sec_ce.group(1))]
if not cm_ents:
raise ValueError("aucune entité en gras dans §Entités")
if len(cm_ents) != len(set(cm_ents)):
raise ValueError(f"entités non uniques : {cm_ents}")
except (OSError, ValueError) as e:
bad(f"Confotur entités · CLAUDE.md §Entités illisible/inattendu : {e}"); cm_ents = None
try:
conf_spec_ce = load("legal/confotur/confotur_spec.json")
conf_dt_ce = load("legal/confotur/out/doctype_confotur_application.json")
ep_art = [f for f in conf_dt_ce.get("fields", []) if f.get("fieldname") == "entite_porteuse"]
ep_spec = None
for _grp in conf_spec_ce.get("field_groups", []):
for _f in _grp.get("fields", []):
if _f.get("fieldname") == "entite_porteuse":
ep_spec = _f
spec_ents = [_nfc(str(x).strip()) for x in conf_spec_ce.get("entites", [])]
ents_src = _nfc(str(conf_spec_ce.get("entites_source", "")))
except (OSError, KeyError, ValueError, TypeError) as e:
bad(f"Confotur entités · artefact/spec illisible/inattendu : {e}"); cm_ents = None
if cm_ents is not None:
# (a) ANCRAGE — options du Select de l'artefact == entités CLAUDE.md (ordre exact)
if len(ep_art) != 1:
bad(f"Confotur entités · champ `entite_porteuse` non-singleton dans l'artefact "
f"({len(ep_art)}) → identité du déroulant indéterminable")
else:
art_opts = [_nfc(o.strip()) for o in str(ep_art[0].get("options", "")).split("\n") if o.strip()]
if art_opts == cm_ents:
good(f"Confotur entités · (a) options DocType `entite_porteuse` "
f"({len(art_opts)}) == CLAUDE.md §Entités (ordre exact)")
else:
bad(f"Confotur entités · (a) options DocType {art_opts} ≠ CLAUDE.md §Entités "
f"{cm_ents} → menu périmé (régénérer depuis le spec / aligner CLAUDE.md)")
# (b) l'INPUT ancré — spec[entites] == CLAUDE.md (ordre exact)
if spec_ents == cm_ents:
good(f"Confotur entités · (b) spec[entites] == CLAUDE.md §Entités")
else:
bad(f"Confotur entités · (b) spec[entites] {spec_ents} ≠ CLAUDE.md §Entités "
f"{cm_ents} → l'input du générateur DÉRIVE du mandat")
# (c) le spec DÉCLARE l'ancrage : entites_source cite CLAUDE.md + chaque entité
if "CLAUDE.md" not in ents_src:
bad(f"Confotur entités · (c) `entites_source` NE CITE PAS CLAUDE.md "
f"(ancrage non déclaré) : {ents_src!r}")
else:
_miss_src = [e for e in cm_ents if e not in ents_src]
if _miss_src:
bad(f"Confotur entités · (c) `entites_source` cite CLAUDE.md mais OMET "
f"{_miss_src} → déclaration d'ancrage périmée")
else:
good(f"Confotur entités · (c) spec DÉCLARE l'ancrage (entites_source cite "
f"CLAUDE.md + les {len(cm_ents)} entités)")
# (d) wiring intact — le champ dérive ses options de la liste `entites`, pas d'un dur
if ep_spec is None:
bad("Confotur entités · (d) champ `entite_porteuse` ABSENT du spec (field_groups)")
elif ep_spec.get("options_source") != "entites":
bad(f"Confotur entités · (d) `entite_porteuse.options_source` = "
f"{ep_spec.get('options_source')!r} ≠ \"entites\" → déroulant DÉCROCHÉ de la "
f"liste ancrée (options potentiellement écrites en dur)")
else:
good("Confotur entités · (d) wiring intact (options_source == \"entites\")")
# (e) README cite l'ancre CLAUDE.md + nomme chaque entité (nom complet OU 1er token)
CONF_RD3 = f"{D}/legal/confotur/README.md"
try:
conf_rd3 = re.sub(r"\s+", " ", open(CONF_RD3, encoding="utf-8").read())
except OSError as e:
bad(f"Confotur entités · {CONF_RD3} illisible : {e}"); conf_rd3 = None
if conf_rd3 is not None:
_m_ep = re.search(r"`entite_porteuse`.{0,240}?9060 QC", conf_rd3)
if not _m_ep:
bad(f"Confotur entités · (e) ligne `entite_porteuse` INTROUVABLE dans "
f"{CONF_RD3} (claim absent = régression #6)")
else:
_epline = _m_ep.group(0)
if "CLAUDE.md" not in _epline:
bad(f"Confotur entités · (e) ligne `entite_porteuse` ne cite pas l'ancre "
f"« CLAUDE.md »")
else:
_miss_rd = []
for _e in cm_ents:
_first = _e.split()[0]
if _e in _epline or re.search(r"(?<![\w-])" + re.escape(_first) + r"(?![\w-])", _epline):
continue
_miss_rd.append(_e)
if _miss_rd:
bad(f"Confotur entités · (e) README omet {_miss_rd} de la ligne "
f"`entite_porteuse` (nom complet ni 1er token)")
else:
good(f"Confotur entités · (e) README cite CLAUDE.md + nomme les "
f"{len(cm_ents)} entités")
sys.exit(1 if FAIL else 0)
PY
rc=$?