[DTP-Worker] Sprint 8 · buffer · Frontend Console/AGENT.md : gate d'IDENTITÉ de l'ENSEMBLE des portails métier (énumération L11 · double ancrage workspaces frontend ⨯ rbac portails_business) + exclusion nommée de la console technique plateforme (6ᵉ portail RBAC hors métier, L46)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-08-01 12:12:10 +00:00
parent dce409f0f6
commit 64238f6833
3 changed files with 180 additions and 1 deletions
+27 -1
View File
File diff suppressed because one or more lines are too long
+91
View File
@@ -711,6 +711,97 @@ if n_ws:
bad(f"Portails fiche · total — {WS_FI} dit {mtot.group(1)} MAIS "
f"MANIFEST dit {n_ws} → régénérer la fiche")
# ============================================================================
# Portails frontend (3e surface) · IDENTITÉ de l'ENSEMBLE des portails métier +
# exclusion NOMMÉE de la console technique — même fiche, autre « vert trompeur »
# aveugle au seul COMPTE.
# ----------------------------------------------------------------------------
# Le bloc triplets ci-dessus recompute cartes/liens/rôles PAR workspace mais
# reste aveugle à QUELS portails composent l'ensemble : la fiche agent
# (03_agents/frontend_console/AGENT.md) énumère À LA MAIN, ligne 11, « 5 portails
# rôle métier (Ventes / Construction / Achat / Compta / Direction) » et affirme
# ligne 46 que « la console technique `plateforme` (6ᵉ portail RBAC) est exclue ».
# DEUX assertions data-derived, aucune gatée :
# (a) l'ENSEMBLE ORDONNÉ des noms courts = les workspaces métier du frontend
# (out/MANIFEST.json workspaces · byte-gaté ⇒ faisant autorité · label
# « OTO <Nom> » sans le préfixe, comme l'énumération des onglets Mobile) ET,
# DOUBLE ANCRAGE, leurs ids == `portails_business` du contrat RBAC (l'«
# égalité stricte » que la fiche revendique elle-même en :47) ;
# (b) `plateforme` EST un vrai portail RBAC (∈ portails distincts des rôles),
# il est le 6ᵉ (total distinct == 6 = 5 métier + 1) et il est HORS métier
# (∉ portails_business) — donc légitimement « exclu ».
# Un ÉCHANGE (Achat→Marketing), un RETRAIT, un RÉORDRE, un RENOMMAGE de la console
# technique dans le contrat (`plateforme`→`console`) ou un contrat qui PROMEUT
# `plateforme` en métier ferait dériver la fiche EN SILENCE pendant que les
# artefacts (frontend workspaces + contrat RBAC) disent autre chose — dérive
# qu'aucune suite tests/ (FONCTIONS, jamais la prose) n'attrape. Même patron que
# l'énumération des onglets Mobile ou la colonne « Entité » de la SPEC §4. Zéro
# duplication : on relit deux artefacts déjà byte-gatés, on ne fige aucune liste.
# ============================================================================
try:
_fc = load("rbac/rbac_50_roles.json")
_pb = _fc["portails_business"] # ordre du contrat
_distinct = list(dict.fromkeys(r["portail"] for r in _fc["roles"]))
except (OSError, KeyError, ValueError, TypeError) as e:
bad(f"Portails ensemble · contrat rbac_50_roles.json illisible/inattendu : {e}")
_pb = None; _distinct = None
if _pb is not None and ws_list:
# double ancrage : ids métier du frontend == portails_business du contrat
_fe_ids = [w["portail"] for w in ws_list]
if _fe_ids != _pb:
bad(f"Portails ensemble · ancrage rompu : ids workspaces frontend {_fe_ids} "
f"≠ contrat portails_business {_pb} → resynchroniser artefact/contrat")
else:
good(f"Portails ensemble · double ancrage : ids workspaces frontend == "
f"portails_business ({'/'.join(_pb)})")
# (a) noms courts ordonnés = label workspace sans « OTO » (ce que la fiche copie)
_short = [re.sub(r"^OTO\s+", "", w["workspace"]).strip() for w in ws_list]
ma = re.search(r"(\d+) portails rôle métier \(([^)]*)\)", ws_fi)
if ma is None:
bad(f"Portails ensemble · fiche — « N portails rôle métier (…) » INTROUVABLE "
f"dans {WS_FI} (attendu {'/'.join(_short)})")
else:
got = [s.strip() for s in ma.group(2).split("/") if s.strip()]
if ma.group(1) != str(len(_short)):
bad(f"Portails ensemble · fiche — annonce « {ma.group(1)} portails métier » "
f"MAIS les workspaces frontend en comptent {len(_short)}")
if got == _short:
good(f"Portails ensemble · fiche — énumération {'/'.join(got)} == "
f"workspaces frontend ({len(_short)})")
else:
miss = [x for x in _short if x not in got]
extra = [x for x in got if x not in _short]
detail = (f" · absents={miss}" if miss else "") + \
(f" · en trop={extra}" if extra else "") or " · même ensemble, ordre ≠"
bad(f"Portails ensemble · fiche — énumère {got} MAIS workspaces disent "
f"{_short}{detail} → régénérer la fiche")
if _pb is not None and _distinct is not None:
# (b) console technique exclue = l'unique portail RBAC HORS métier
_tech = [p for p in _distinct if p not in _pb]
if len(_tech) != 1:
bad(f"Portails ensemble · attendu exactement 1 portail technique hors métier, "
f"trouvé {_tech} (distinct={_distinct}, métier={_pb}) → contrat incohérent")
else:
tech = _tech[0]
# « console technique `<nom>` (Nᵉ portail RBAC) est **exclue** » — le \S*
# absorbe l'exposant « ᵉ » (multi-octets) sans le coder en dur ici.
mt = re.search(r"console technique `([^`]+)` \((\d+)\S* portail RBAC\) "
r"est \*\*exclue\*\*", ws_fi)
if mt is None:
bad(f"Portails ensemble · fiche — claim « console technique `{tech}` "
f"(Nᵉ portail RBAC) est **exclue** » INTROUVABLE dans {WS_FI}")
elif mt.group(1) != tech:
bad(f"Portails ensemble · fiche — nomme la console technique `{mt.group(1)}` "
f"MAIS le contrat dit `{tech}` (unique portail hors métier) → régénérer")
elif mt.group(2) != str(len(_distinct)):
bad(f"Portails ensemble · fiche — dit `{tech}` = {mt.group(2)}ᵉ portail RBAC "
f"MAIS le contrat compte {len(_distinct)} portails distincts")
else:
good(f"Portails ensemble · fiche — console technique `{tech}` = "
f"{mt.group(2)}ᵉ portail RBAC, hors métier, exclue ✓")
# ============================================================================
# RBAC · comptes des 3 volets (roleprofile · apply_plan · userperm) —
# même classe de « vert trompeur », recopiés À LA MAIN depuis les MANIFEST.