[DTP-Worker] Sprint 8 · buffer · Domaine Portails (2e surface) : le CATALOGUE des DocTypes custom à CRÉER avant import (« CONFOTUR Application, Faisabilité, Publiciste Log ») était énuméré À LA MAIN dans le §« Hand-off → agent ERPNext Backend » du README frontend/portails sans AUCUN gate d'IDENTITÉ. Le bloc portails existant ne gate que les COMPTES par workspace (cartes/liens/rôles, ×2 docs) — jamais ce catalogue de hand-off. Ce catalogue est DATA-DERIVED : doctypes_custom_a_creer de frontend/portails/out/MANIFEST.json (byte-gaté par check_artifacts), lui-même l'UNION des doctypes_custom par workspace (flag custom du contrat rbac_50_roles.json, pour les seuls DocTypes CÂBLÉS dans un portail). C'est un ensemble PROPRE au module (sous-ensemble de celui de rbac/fixtures_gen : API Access, custom mais non câblé, en est ABSENT). PIÈGE : check_artifacts ne prouve QUE MANIFEST==build (byte-for-byte) et le build RECOMPUTE l'union — donc AJOUTER une carte visant un nouveau DocType custom au contrat (le MANIFEST rebâtit le catalogue) · en RENOMMER un · en RETIRER un du câblage laisse la consigne de hand-off périmée pendant que l'artefact dit autre chose : l'agent Backend créerait le mauvais jeu de DocTypes (liens pointant dans le vide — le risque même que la consigne veut prévenir) — « vert trompeur » qu'aucune suite tests/ (qui teste des FONCTIONS de mapping, pas la prose de hand-off) n'attrape → nouveau bloc recomputant le catalogue depuis MANIFEST (zéro duplication du contrat du générateur #6 · même patron que le catalogue custom de rbac/fixtures_gen) et exigeant que la prose l'énumère EXACTEMENT (absent ET en trop via set-diff · accents/casse normalisés via unicodedata). Cohérences croisées en bonus (mordent un MANIFEST INTERNEMENT incohérent) : catalogue NON VIDE, sans doublon et TRIÉ (byte-déterminisme) · égal à l'UNION des doctypes_custom par workspace (pas de DocType custom câblé mais oublié du hand-off, ni l'inverse). Un claim absent échoue AUSSI. 5 morsures vérifiées : prose renomme Faisabilité→Faisabilite2 (absents=[faisabilite] en trop=[faisabilite2]) · prose retire Publiciste Log (sous-ensemble) · énumération supprimée (INTROUVABLE) · MANIFEST retire du catalogue un DocType encore câblé (incohérence interne : câblés hors catalogue=[Publiciste Log] + prose en trop) · catalogue MANIFEST non trié (byte-déterminisme) ; restauré = green : [CONFOTUR Application,Faisabilité,Publiciste Log] == MANIFEST == union des workspaces · exit 0. État courant : aucun catalogue périmé (anti-invention #6, rien à réécrire) — le défaut est la surface ungated. ci/README.md (table + détail « 2e surface Portails ») 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 19:06:35 +00:00
parent 7b775677a6
commit 7ee6882eb8
3 changed files with 142 additions and 1 deletions
+82
View File
@@ -2080,6 +2080,88 @@ if cat is not None:
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")
# ============================================================================
# Portails frontend (2e surface) · CATALOGUE des DocTypes `custom` à CRÉER avant
# import — la consigne de hand-off VPS énumérée À LA MAIN, sans gate d'IDENTITÉ.
# ----------------------------------------------------------------------------
# Le bloc portails ci-dessus recompute les COMPTES par workspace (cartes/liens/
# rôles) et la table portail⇔workspace. Restait, dans le MÊME README, une surface
# data-derived DISTINCTE et jamais gatée : le §« Hand-off → agent ERPNext Backend »
# item 2 énumère « DocTypes custom à créer avant import : `CONFOTUR Application`,
# `Faisabilité`, `Publiciste Log` (… le reste est natif v15) ». Ce catalogue est
# DATA-DERIVED : c'est out/MANIFEST.json `doctypes_custom_a_creer` (byte-gaté par
# check_artifacts) = l'UNION des `doctypes_custom` par workspace, eux-mêmes lus du
# flag `custom` du contrat rbac_50_roles.json pour les seuls DocTypes visés par une
# carte/un lien du portail. C'est un ensemble PROPRE au module portails (sous-
# ensemble de celui de rbac/fixtures_gen : ici seuls comptent les DocTypes custom
# effectivement CÂBLÉS dans un workspace — API Access, custom mais non câblé, en est
# ABSENT). check_artifacts ne prouve QUE MANIFEST==build (byte-for-byte), et le
# build RECOMPUTE l'union — donc AJOUTER une carte visant un nouveau DocType custom
# au contrat (le MANIFEST rebâtit le catalogue) · en RENOMMER un · en RETIRER un du
# câblage laisse la prose de hand-off périmée pendant que l'artefact dit autre
# chose : l'agent Backend créerait le mauvais jeu de DocTypes (liens pointant dans
# le vide — le risque même que la consigne veut prévenir) — « vert trompeur »
# qu'aucune suite tests/ (qui teste des FONCTIONS de mapping, pas la prose de hand-
# off) n'attrape. Même patron que le catalogue custom de rbac/fixtures_gen. On
# RECOMPUTE le catalogue depuis MANIFEST (zéro duplication du contrat du générateur
# #6) et on exige que la prose l'énumère EXACTEMENT (absent ET en trop via set-diff ·
# accents/casse normalisés). Cohérences croisées en bonus (mordent un MANIFEST
# INTERNEMENT incohérent) : catalogue NON VIDE, sans doublon et TRIÉ (byte-
# déterminisme) · top-level == UNION des `doctypes_custom` par workspace (pas de
# DocType custom câblé mais oublié du catalogue de hand-off, ni l'inverse). Un claim
# absent échoue AUSSI (traçabilité).
# ============================================================================
PC_RD = f"{D}/frontend/portails/README.md"
def _pc_acc(s):
return "".join(c for c in unicodedata.normalize("NFKD", s)
if unicodedata.category(c) != "Mn").strip().lower()
try:
pc_man = load("frontend/portails/out/MANIFEST.json")
pc_cat = pc_man["doctypes_custom_a_creer"]
pc_union = sorted({d for w in pc_man["workspaces"]
for d in w.get("doctypes_custom", [])})
except (OSError, KeyError, ValueError, TypeError) as e:
bad(f"Portails catalogue · MANIFEST illisible/inattendu : {e}"); pc_cat = None
if pc_cat is not None:
# bonus : le catalogue est un vrai ENSEMBLE trié non vide (byte-déterminisme)
if not pc_cat:
bad("Portails catalogue · doctypes_custom_a_creer VIDE → aucun DocType custom "
"câblé dans un workspace ? (contrat/spec suspect)")
elif pc_cat != sorted(set(pc_cat)):
bad(f"Portails catalogue · doctypes_custom_a_creer {pc_cat} non trié/dédupliqué "
f"→ générateur non déterministe (attendu {sorted(set(pc_cat))})")
else:
good(f"Portails catalogue · custom à créer {pc_cat} — ensemble trié, sans doublon")
# bonus : cohérence interne — catalogue == union des doctypes_custom par workspace
if sorted(set(pc_cat)) != pc_union:
miss = sorted(set(pc_union) - set(pc_cat)); extra = sorted(set(pc_cat) - set(pc_union))
detail = (f" · câblés hors catalogue={miss}" if miss else "") + \
(f" · au catalogue mais non câblés={extra}" if extra else "")
bad(f"Portails catalogue · doctypes_custom_a_creer {sorted(pc_cat)} ≠ UNION des "
f"doctypes_custom par workspace {pc_union}{detail} → MANIFEST incohérent")
else:
good(f"Portails catalogue · catalogue == union des doctypes_custom par workspace "
f"({pc_union})")
pc_rd = read_norm(PC_RD)
if pc_rd is not None:
# README §Hand-off item 2 — énumération du catalogue custom avant « (issus »
mp = re.search(r"custom à créer\*\* avant import[^:]*?: (.+?)\(issus", pc_rd)
if mp is None:
bad(f"Portails catalogue · {PC_RD} — énumération « DocTypes custom à créer … » "
f"INTROUVABLE (attendu {sorted(pc_cat)})")
else:
got = {_pc_acc(t) for t in re.findall(r"`([^`]+)`", mp.group(1))}
exp = {_pc_acc(x) for x in pc_cat}
if got == exp:
good(f"Portails catalogue · {PC_RD} — hand-off {sorted(pc_cat)} == "
f"MANIFEST.doctypes_custom_a_creer")
else:
miss = sorted(exp - got); extra = sorted(got - exp)
detail = (f" · absents={miss}" if miss else "") + \
(f" · en trop={extra}" if extra else "")
bad(f"Portails catalogue · {PC_RD} — hand-off énumère {sorted(got)} MAIS "
f"MANIFEST {sorted(exp)}{detail} → régénérer le README")
sys.exit(1 if FAIL else 0)
PY
rc=$?