[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:
@@ -1283,3 +1283,65 @@ descripteurs · `(c)` 7 portées) · exit 0. Working tree byte-restauré (`git c
|
||||
édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact ; le seed réel des
|
||||
rôles/entités `bench` reste côté VPS).
|
||||
- **Auto-score 4Big** : 96/100.
|
||||
|
||||
## Session `20260801_120319` · Buffer S8 · Frontend Console/AGENT.md — IDENTITÉ de l'ENSEMBLE des portails métier + exclusion nommée de la console technique
|
||||
|
||||
**Contexte** : Sprint 8 · buffer (roadmap fonctionnellement close ; poursuite de la
|
||||
série anti-dérive CLAUDE.md #6 — fiche-agent qui restitue une donnée gatée seulement
|
||||
en AGRÉGAT/COMPTE, cf. [[agent-fiche-role-attrs-ungated]]). Même patron que
|
||||
l'énumération ordonnée des onglets Mobile (`role_navigation`) et la colonne « Entité »
|
||||
de la SPEC §4 ([[rbac-spec-portail-count-gate]]) — appliqué à une surface encore ungated
|
||||
de la fiche **Frontend Console**.
|
||||
|
||||
**Dérive silencieuse fermée** :
|
||||
- `03_agents/frontend_console/AGENT.md` restitue à la main, en **deux endroits**
|
||||
data-derived, l'IDENTITÉ de l'ensemble des portails — jamais gatée hors le COMPTE :
|
||||
- **L11** « **5 portails rôle métier** (Ventes / Construction / Achat / Compta /
|
||||
Direction) » — l'ENSEMBLE ORDONNÉ des noms courts.
|
||||
- **L46** « la console technique `plateforme` (**6ᵉ portail RBAC**) est **exclue** »
|
||||
— l'identité + le rang + la non-appartenance au métier de la console exclue.
|
||||
- **Deux artefacts faisant autorité** : (i) `frontend/portails/out/MANIFEST.json`
|
||||
(byte-gaté) — les workspaces métier (`OTO Ventes` … → noms courts sans le préfixe
|
||||
« OTO ») ; (ii) `rbac_50_roles.json` — `portails_business` (l'ordre du contrat) + les
|
||||
portails distincts des rôles (6 : 5 métier + `plateforme`).
|
||||
- Piège : le bloc « triplets par workspace » recompute cartes/liens/rôles **par**
|
||||
portail mais reste **aveugle à QUELS portails** composent l'ensemble ; les autres
|
||||
gates comptent « 5 » sans jamais confronter le SET. Un ÉCHANGE (Achat→Marketing),
|
||||
RETRAIT, RÉORDRE, RENOMMAGE de la console technique dans le contrat
|
||||
(`plateforme`→`console`) ou un contrat qui PROMEUT `plateforme` en métier laisse la
|
||||
fiche contredire les artefacts EN SILENCE — « vert trompeur » qu'aucune suite
|
||||
`tests/` (FONCTIONS RBAC, jamais la prose) n'attrape.
|
||||
- **État courant** : aucune dérive — L11 == workspaces frontend == `portails_business` ·
|
||||
L46 `plateforme` == unique portail RBAC hors métier == 6ᵉ. Le défaut est la **surface
|
||||
ungated**.
|
||||
|
||||
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « Portails ensemble » juste après le
|
||||
bloc triplets, réutilise `ws_list`/`ws_fi`/`WS_FI` déjà en scope · relit le contrat
|
||||
`rbac_50_roles.json`) :
|
||||
- **(a) ENSEMBLE** — noms courts ordonnés (label `MANIFEST.workspaces[].workspace` sans
|
||||
« OTO ») == énumération L11 (compte + absents/en trop + ordre) **ET DOUBLE ANCRAGE**
|
||||
les ids workspaces frontend == `portails_business` du contrat (l'« égalité stricte »
|
||||
que la fiche revendique en L47).
|
||||
- **(b) EXCLUSION** — `plateforme` == l'UNIQUE portail RBAC hors métier
|
||||
(`distinct(roles.portail) ∖ portails_business`, len==1), nommé EXACTEMENT dans L46,
|
||||
de rang == `|distinct|` (6ᵉ), avec `**exclue**` structurellement exigé (regex `\S*`
|
||||
absorbe l'exposant « ᵉ » multi-octets sans le coder en dur).
|
||||
- Claim absent (L11 ou L46 INTROUVABLE) = régression #6.
|
||||
|
||||
**8 morsures vérifiées** (6 côté fiche · 2 côté contrat) : L11 `Achat→Marketing`
|
||||
(`(a)` en trop/absent) · L11 retire `Compta` (`(a)` compte+absent) · L11 réordonne
|
||||
`Ventes↔Construction` (`(a)` ordre) · L46 `plateforme→console` (`(b)` nom) · L46
|
||||
`6ᵉ→7ᵉ` (`(b)` rang) · L46 retire `**exclue**` (INTROUVABLE) · contrat réordonne
|
||||
`portails_business` (ancrage rompu) · contrat `ventes→sales` dans `portails_business`
|
||||
(ancrage rompu + `plateforme` n'est plus l'unique hors-métier ⇒ 2 fails) ; restauré =
|
||||
green (3 ✓ : double ancrage · énumération L11 · console technique L46) · exit 0.
|
||||
Working tree byte-restauré (`git checkout --`, **JAMAIS** `git clean`, interdit absolu) ·
|
||||
**7 gates re-verts** (`check_readme_claims`/`check_artifacts`/`check_docs`/
|
||||
`guard_constraints`/`check_regression`/`check_ci_integrity`/`validate_json`).
|
||||
|
||||
- `ci/README.md` (clause de la ligne récap du pipeline + paragraphe détaillé « Ensemble
|
||||
des portails métier + exclusion nommée de la console technique ») mis à jour.
|
||||
- **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 ; le seed réel des
|
||||
Workspaces/rôles `bench` reste côté VPS).
|
||||
- **Auto-score 4Big** : 96/100.
|
||||
|
||||
+27
-1
File diff suppressed because one or more lines are too long
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user