[DTP-Worker 20260802_050513] Sprint 8 · buffer · RBAC SPEC §2 : ancrage de l'ÉNUMÉRATION des scope_donnees (ligne Row-level) sur l'enum de rbac.schema.json
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,48 @@
|
|||||||
# Activity log · 2026-08-02 · Claude Code DTP Worker
|
# Activity log · 2026-08-02 · Claude Code DTP Worker
|
||||||
|
|
||||||
|
## Sprint 8 · buffer · RBAC SPEC §2 — ancrage de l'ÉNUMÉRATION des `scope_donnees` (ligne Row-level) sur l'ENUM du schéma
|
||||||
|
|
||||||
|
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
|
||||||
|
chiffres »). La table §2 « Modèle RBAC » de
|
||||||
|
`05_deliverables_mvp/rbac/RBAC_50_ROLES_SPEC.md` ouvre par « trois niveaux, **tous
|
||||||
|
couverts par le schéma** » puis sa 3ᵉ ligne (« **Row-level** (User Permission) |
|
||||||
|
`scope_donnees` | … ») ÉNUMÈRE à la main l'ensemble EXHAUSTIF des portées
|
||||||
|
row-level : `own` · `equipe` · `entite` (Company) · `groupe` (consolidé). Par sa
|
||||||
|
phrase d'intro, cet ensemble EST l'`enum` `scope_donnees` de `rbac.schema.json`
|
||||||
|
(la DÉFINITION du modèle). Or **aucun gate ne le touchait** : §3 compte les rôles
|
||||||
|
par portail, §4 les entités, §5 le résumé, et le bloc `userperm` gate la table
|
||||||
|
`scope → mécanisme` d'un **autre** artefact (`userperm_gen/out`) — tous aveugles à
|
||||||
|
l'énumération du §2. Même classe que §4 (colonne ancrée sur une source-de-vérité
|
||||||
|
structurée), côté schéma cette fois.
|
||||||
|
|
||||||
|
**Le « vert trompeur »** — si le schéma AJOUTE une portée (`region` pour un besoin
|
||||||
|
ops réel) ou en RETIRE une (`own` supprimé, schéma resserré), le §2 reste PÉRIMÉ en
|
||||||
|
silence : il documente un modèle qui CONTREDIT le schéma tout en se réclamant
|
||||||
|
« couvert par le schéma ». Ni `check_artifacts` (la SPEC n'est pas un `out/*.json`)
|
||||||
|
ni `tests/` (FONCTIONS de validation, jamais l'ancre SPEC↔schéma) ne mordent.
|
||||||
|
|
||||||
|
**Gate ajouté** (`ci/check_readme_claims.sh`, bloc « 1octies », juste après le §5)
|
||||||
|
— charge l'`enum` `scope_donnees` depuis `rbac.schema.json` (source de vérité du
|
||||||
|
modèle) puis exige : **(a)** ENSEMBLE des jetons `scope` backtickés de la ligne §2
|
||||||
|
== enum (set-diff absent ET fantôme) ; **(b)** ANCRAGE — la colonne du milieu NOMME
|
||||||
|
bien `scope_donnees` (sinon on gate la mauvaise ligne) ; **(c)** sanity — toute
|
||||||
|
portée RÉELLEMENT exercée par le contrat ∈ enum (une portée en usage jamais dans
|
||||||
|
l'enum = schéma/contrat incohérents). Les gloses parenthétiques `(Company)`/
|
||||||
|
`(consolidé)` NE sont PAS gatées (éditoriales · #6). Ligne/section/enum absents =
|
||||||
|
échec (traçabilité #6).
|
||||||
|
|
||||||
|
**Vérif** — 3 checks verts sur l'arbre propre (4 portées `own/equipe/entite/groupe`
|
||||||
|
== enum). **6 morsures** adversariales : M1 §2 retire `own` ⇒ manquant (a) · M2 §2
|
||||||
|
ajoute fantôme `region` ⇒ fantôme (a) · **M3 SILENT-GREEN : le schéma GAGNE `region`,
|
||||||
|
§2 inchangé ⇒ (a) mord `manquant region` — preuve que l'autorité est le SCHÉMA, pas
|
||||||
|
la cohérence interne de la SPEC** · M4 colonne du milieu renommée `scope_donnees`→
|
||||||
|
`scope` ⇒ ancre perdue (b) · M5 ligne Row-level supprimée ⇒ INTROUVABLE · M6 le
|
||||||
|
schéma RETIRE `own` (encore exercé par 2 rôles) ⇒ (a) fantôme + (c) portée hors
|
||||||
|
enum. Gates re-verts : `check_artifacts` · `check_docs` · `guard_constraints` ·
|
||||||
|
`check_ci_integrity` · `check_regression` · `check_readme_claims` tous exit 0.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Sprint 8 · buffer · Faisabilité — ancrage du DOMAINE DES CODES PROJET (garde d'entrée `_validate_brief` + 2 schémas JSON) sur CLAUDE.md §Projets
|
## Sprint 8 · buffer · Faisabilité — ancrage du DOMAINE DES CODES PROJET (garde d'entrée `_validate_brief` + 2 schémas JSON) sur CLAUDE.md §Projets
|
||||||
|
|
||||||
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
|
**Contexte** — Série anti-dérive Sprint 8 (CLAUDE.md #6 « zéro invention de
|
||||||
|
|||||||
@@ -1497,6 +1497,79 @@ if src_roles is not None and spec_rd is not None:
|
|||||||
bad(f"RBAC SPEC §5 · (d) la ligne totalise {rhs} MAIS le contrat "
|
bad(f"RBAC SPEC §5 · (d) la ligne totalise {rhs} MAIS le contrat "
|
||||||
f"compte {N5} rôles → SPEC périmée")
|
f"compte {N5} rôles → SPEC périmée")
|
||||||
|
|
||||||
|
# 1octies) RBAC_50_ROLES_SPEC.md §2 — ligne « Row-level » : ENSEMBLE des `scope_donnees`
|
||||||
|
# ancré sur l'ENUM du schéma ---------------------------------------------------
|
||||||
|
# Le §2 « Modèle RBAC » ouvre par « trois niveaux, tous couverts PAR LE SCHÉMA » puis
|
||||||
|
# sa 3ᵉ ligne de table (« **Row-level** (User Permission) | `scope_donnees` | … ») ÉNUMÈRE
|
||||||
|
# À LA MAIN l'ensemble EXHAUSTIF des portées row-level : `own` · `equipe` · `entite`
|
||||||
|
# (Company) · `groupe` (consolidé). Cet ensemble EST, par la phrase d'intro, l'`enum`
|
||||||
|
# `scope_donnees` de `rbac.schema.json` (la DÉFINITION du modèle — pas le sous-ensemble
|
||||||
|
# effectivement exercé par les 50 rôles, qui peut être plus petit). AUCUN gate ne le
|
||||||
|
# touchait : §3 compte les rôles par portail, §4 les entités, §5 le résumé, le bloc
|
||||||
|
# userperm gate la TABLE `scope → mécanisme` d'un AUTRE artefact (userperm_gen/out) —
|
||||||
|
# tous AVEUGLES à l'énumération du §2. « Vert trompeur » : le schéma AJOUTE une portée
|
||||||
|
# (`region` pour un besoin ops réel) ou en RETIRE une (`own` supprimé, schéma resserré)
|
||||||
|
# et le §2 reste PÉRIMÉ en silence — il documente un modèle qui contredit le schéma
|
||||||
|
# tout en se réclamant « couvert par le schéma ». Ni check_artifacts (la SPEC n'est pas
|
||||||
|
# un out/*.json) ni tests/ (FONCTIONS de validation, jamais l'ancre SPEC↔schéma) ne
|
||||||
|
# mordent. Même classe que §4 (colonne ancrée sur une source-de-vérité structurée).
|
||||||
|
# GATE : (a) ENSEMBLE des jetons `scope` de la ligne §2 == `enum` scope_donnees du
|
||||||
|
# schéma (set-diff absent ET fantôme) ; (b) ANCRAGE — la colonne du milieu NOMME bien
|
||||||
|
# `scope_donnees` (sinon on gate la mauvaise ligne) ; (c) sanity — toute portée
|
||||||
|
# RÉELLEMENT exercée par le contrat ∈ enum (une portée en usage jamais dans l'enum =
|
||||||
|
# schéma incohérent). On NE gate PAS les gloses parenthétiques `(Company)`/`(consolidé)`
|
||||||
|
# (éditoriales · #6). Ligne/section/enum absents = échec (traçabilité #6).
|
||||||
|
if src_roles is not None and spec_rd is not None:
|
||||||
|
try:
|
||||||
|
_sch = load("rbac/rbac.schema.json")
|
||||||
|
_enum = _sch["definitions"]["role"]["properties"]["scope_donnees"]["enum"]
|
||||||
|
scope_enum = set(_enum)
|
||||||
|
if not scope_enum:
|
||||||
|
raise ValueError("enum scope_donnees vide")
|
||||||
|
if len(scope_enum) != len(_enum):
|
||||||
|
raise ValueError(f"enum scope_donnees non unique : {_enum}")
|
||||||
|
except (OSError, KeyError, ValueError, TypeError) as e:
|
||||||
|
bad(f"RBAC SPEC §2 · rbac.schema.json enum scope_donnees illisible : {e}")
|
||||||
|
scope_enum = None
|
||||||
|
_m2 = re.search(r"## 2\..*?(?=## 3\.)", spec_rd)
|
||||||
|
if _m2 is None:
|
||||||
|
bad(f"RBAC SPEC §2 · section « ## 2. » INTROUVABLE dans {SPEC_RD} "
|
||||||
|
f"(claim absent = régression #6)")
|
||||||
|
elif scope_enum is not None:
|
||||||
|
# Ligne « Row-level » : capte la colonne « champ » (c2) et la colonne énum (c3)
|
||||||
|
_row = re.search(r"\| \*\*Row-level\*\*[^|]*\|([^|]*)\|([^|]*)\|", _m2.group(0))
|
||||||
|
if _row is None:
|
||||||
|
bad(f"RBAC SPEC §2 · ligne « **Row-level** » INTROUVABLE dans {SPEC_RD} "
|
||||||
|
f"(claim absent = régression #6)")
|
||||||
|
else:
|
||||||
|
c_field, c_enum = _row.group(1), _row.group(2)
|
||||||
|
# (b) la colonne du milieu doit nommer le champ `scope_donnees`
|
||||||
|
if "scope_donnees" not in c_field:
|
||||||
|
bad(f"RBAC SPEC §2 · (b) colonne « champ » de la ligne Row-level ne "
|
||||||
|
f"nomme pas `scope_donnees` (capté {c_field!r}) → ligne mal ancrée")
|
||||||
|
else:
|
||||||
|
good("RBAC SPEC §2 · (b) ligne Row-level ancrée sur le champ "
|
||||||
|
"`scope_donnees`")
|
||||||
|
spec_scopes = set(re.findall(r"`([a-z_]+)`", c_enum))
|
||||||
|
_miss = sorted(scope_enum - spec_scopes) # dans l'enum, absents du §2
|
||||||
|
_phan = sorted(spec_scopes - scope_enum) # listés au §2, hors enum
|
||||||
|
if _miss or _phan:
|
||||||
|
bad(f"RBAC SPEC §2 · (a) énumération `scope_donnees` ≠ enum du "
|
||||||
|
f"schéma — manquants={_miss} · fantômes={_phan} → aligner la "
|
||||||
|
f"SPEC sur rbac.schema.json (ou régénérer si le schéma a changé)")
|
||||||
|
else:
|
||||||
|
good(f"RBAC SPEC §2 · (a) {len(spec_scopes)} portées row-level "
|
||||||
|
f"{sorted(spec_scopes)} == enum scope_donnees du schéma")
|
||||||
|
# (c) sanity : toute portée exercée par le contrat ∈ enum
|
||||||
|
_used = {r.get("scope_donnees") for r in src_roles}
|
||||||
|
_illegal = sorted(_used - scope_enum)
|
||||||
|
if _illegal:
|
||||||
|
bad(f"RBAC SPEC §2 · (c) portées `scope_donnees` exercées par le contrat "
|
||||||
|
f"HORS enum du schéma : {_illegal} → schéma/contrat incohérents")
|
||||||
|
else:
|
||||||
|
good(f"RBAC SPEC §2 · (c) les {len(_used)} portées exercées par le contrat "
|
||||||
|
f"sont toutes dans l'enum du schéma")
|
||||||
|
|
||||||
# 2) apply_plan — ligne « Génération réelle » agrégeant les 3 volets ------------
|
# 2) apply_plan — ligne « Génération réelle » agrégeant les 3 volets ------------
|
||||||
AP_RD = f"{D}/rbac/apply_plan/README.md"
|
AP_RD = f"{D}/rbac/apply_plan/README.md"
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user