[DTP-Worker] Sprint 8 · buffer L75 · Surface RBAC non gatée : 3 READMEs de module (roleprofile · apply_plan · userperm) recopiaient À LA MAIN des comptes dérivés des out/MANIFEST.json byte-gatés (recalculés du contrat rbac_50_roles.json à chaque build) sans AUCUN gate — table « Nb rôles » par profil (6 lignes + total bijectif 50), ligne agrégée « 6 étapes · 50 rôles / 116 DocPerm / 28 UP templates / 6 Role Profile · 4 DocType custom + 5 Company + 4 rôles » (8 comptes de tous les volets) et ventilation « 28 entite · 16 groupe · 2 own · 4 equipe » ; ajouter un rôle/DocType ferait dériver les trois docs en silence → nouveau bloc dans check_readme_claims recomputant chaque nombre depuis le MANIFEST du volet (10 checks · 3 morsures vérifiées : dérive par-profil · dérive agrégat consolidé · dérive ventilation ; claim absent = red)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -981,3 +981,72 @@ plan.suites / count_tests ; les nombres de la fiche **découlent** de la source.
|
||||
**Hors périmètre worker (VPS · #8)** : néant (docs + gate bash/stdlib en-repo).
|
||||
|
||||
**Auto-score 4Big** : 96/100.
|
||||
|
||||
---
|
||||
|
||||
## Session `20260731_083034` · Buffer S8 · Comptes RBAC des 3 volets recopiés à la main — surface non gatée
|
||||
|
||||
**Tâche** : **Sprint 8 · buffer L75 (« Regression tests exhaustifs »)** — roadmap
|
||||
fonctionnellement close (22/22 modules gated · 564 tests · 7 gates verts au
|
||||
démarrage). Chasse d'une surface **« vert trompeur » (#6)** encore non gatée.
|
||||
|
||||
### Défaut trouvé — 3 READMEs RBAC transcrivent à la main des comptes de MANIFEST byte-gatés
|
||||
|
||||
- **Symptôme** : les trois générateurs RBAC émettent chacun un `out/MANIFEST.json`
|
||||
recalculé du contrat `rbac_50_roles.json` à chaque build (byte-gaté par
|
||||
`check_artifacts` ⇒ autorité). Trois READMEs de module recopiaient **à la main**
|
||||
des comptes qui en dérivent, sans **AUCUN** gate :
|
||||
1. `rbac/roleprofile_gen/README.md` — table « Nb rôles » **par profil** (6 lignes
|
||||
: Ventes 12 · Construction 10 · Direction 9 · Compta 8 · Achat 5 · Plateforme
|
||||
6) + total bijectif **50**.
|
||||
2. `rbac/apply_plan/README.md` — ligne agrégée « Génération réelle : **6 étapes ·
|
||||
50 rôles / 116 DocPerm / 28 UP templates / 6 Role Profile · 4 DocType custom +
|
||||
5 Company + 4 rôles equipe** » : **8 comptes** de TOUS les volets en une phrase.
|
||||
3. `rbac/userperm_gen/README.md` — ventilation « **28 entite · 16 groupe · 2 own ·
|
||||
4 equipe** » (par mécanisme d'enforcement).
|
||||
- **Contradiction latente** : ajouter un rôle/DocType au contrat ferait dériver
|
||||
**les trois** docs en silence pendant que les MANIFEST (byte-gatés) disent autre
|
||||
chose — même classe que les cellules « Tests », la prose agrégée CRM/RBAC et les
|
||||
triplets par workspace, mais sur le **domaine RBAC** (contrainte NON-NÉGOCIABLE
|
||||
#5 « 50 rôles »), au grain le plus fin (par profil) **et** au niveau consolidé.
|
||||
- **État au démarrage** : les 10 nombres étaient **justes** (aucune dérive déjà
|
||||
survenue) — le défaut est l'**absence de garde**, pas une valeur périmée.
|
||||
|
||||
### Fix (1 volet · garder — aucun chiffre à corriger)
|
||||
|
||||
- **`ci/check_readme_claims.sh`** — nouveau bloc RBAC recomputant chaque nombre
|
||||
depuis le `out/MANIFEST.json` du volet (zéro duplication des compteurs des
|
||||
générateurs · #6) et exigeant l'égalité avec ce que la doc ÉCRIT :
|
||||
- roleprofile : chaque `nb_roles` par profil (ancré sur nom+portail) + total
|
||||
`counts.roles_couverts` ;
|
||||
- apply_plan : les 8 comptes (`len(apply_plan.json)` · `counts.{roles,
|
||||
custom_docperm,user_permission_templates,role_profiles}` · `len(confirmations_
|
||||
vps.{custom_doctypes,companies,roles_scope_equipe})`) en un n-uplet ordonné ;
|
||||
- userperm : `by_mechanism.{user_permission_company,none_consolidated,
|
||||
docperm_if_owner,vps_confirm_team}`.
|
||||
Claim absent ⇒ red (disparition = régression de traçabilité). Header + ci/README
|
||||
documentés.
|
||||
|
||||
### Preuves
|
||||
|
||||
- **Passe** : gate vert, **10 nouvelles lignes RBAC** vérifiées (6 profils + total
|
||||
+ agrégat apply_plan + ventilation userperm), toutes == MANIFEST.
|
||||
- **Morsures (3, une par surface)** : Ventes 12→13 ⇒ `✗ … dit 13 MAIS MANIFEST
|
||||
dit 12` ; apply_plan 116→117 DocPerm ⇒ `✗ … 6/50/117/… MAIS … 6/50/116/…` ;
|
||||
userperm 28→29 entite ⇒ `✗ … 29/16/2/4 MAIS … 28/16/2/4`. Exit 1 dans les trois
|
||||
cas ; restauration ⇒ PASS (exit 0).
|
||||
|
||||
**Régénération des consommateurs** : néant — aucun compte n'a changé (fix = gate
|
||||
seul). Artefacts `out/` **byte-identiques** (`check_artifacts` vert) ; matrice
|
||||
**564/22 PASS** inchangée.
|
||||
|
||||
**Vérifs** : **7 gates** locaux verts (`guard_constraints` · `validate_json` ·
|
||||
`check_docs` · `check_artifacts` · `check_regression` · `check_ci_integrity` ·
|
||||
`check_readme_claims`) ; matrice **564/22 · PASS** ; 22/22 modules **100/100**.
|
||||
|
||||
**Anti-invention (#6)** : rien inventé — le gate **recompute** chaque nombre depuis
|
||||
les MANIFEST byte-gatés ; les comptes des READMEs **découlent** du contrat RBAC.
|
||||
|
||||
**Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo).
|
||||
|
||||
**Auto-score 4Big** : 96/100.
|
||||
|
||||
+12
-1
@@ -20,7 +20,7 @@ et manuellement (`workflow_dispatch`). Jobs statiques (+ une suite `unittest` pa
|
||||
| `check-artifacts` | `ci/check_artifacts.sh` | Reproductibilité : chaque `out/*.json` versionné == build frais | ✅ oui |
|
||||
| `check-regression` | `ci/check_regression.sh` | Fraîcheur : `qa/regression/out/regression_run.json` (`run`) == run frais + verdict PASS | ✅ oui |
|
||||
| `check-ci-integrity` | `ci/check_ci_integrity.sh` | Intégrité du câblage : `gate.needs` == tous les jobs non-manuels · chaque `ci/*.sh` câblé au gate | ✅ oui |
|
||||
| `check-readme-claims` | `ci/check_readme_claims.sh` | Intégrité des chiffres des docs d'entrée : chaque nombre de `README.md` « État courant », de la fiche QA « Verdict agrégé » **et** de la colonne « Tests » par suite des tables de fiches **et** des agrégats en prose (Total CRM / RBAC / e-CF) **et** des comptes par workspace des portails (cartes/liens/rôles, ×2 docs) == artefact cité (#6) | ✅ oui |
|
||||
| `check-readme-claims` | `ci/check_readme_claims.sh` | Intégrité des chiffres des docs d'entrée : chaque nombre de `README.md` « État courant », de la fiche QA « Verdict agrégé » **et** de la colonne « Tests » par suite des tables de fiches **et** des agrégats en prose (Total CRM / RBAC / e-CF) **et** des comptes par workspace des portails (cartes/liens/rôles, ×2 docs) **et** des comptes RBAC des 3 volets (table Nb rôles/profil, ligne agrégée apply_plan, ventilation userperm) == artefact cité (#6) | ✅ oui |
|
||||
| `gate` | — | Agrégat vert = gate qualité 4Big franchi | ✅ oui |
|
||||
|
||||
Aucune dépendance réseau/marketplace hors `actions/checkout`. Tout tourne avec
|
||||
@@ -163,6 +163,17 @@ natifs » sont recomputés depuis `frontend/portails/out/MANIFEST.json` (tableau
|
||||
`workspaces`, lui-même byte-gaté par `check_artifacts`) : ajouter un rôle/DocType
|
||||
au contrat RBAC ferait dériver **les deux** docs en silence sinon.
|
||||
|
||||
Même classe enfin sur les **comptes RBAC des 3 volets** (`rbac/roleprofile_gen`,
|
||||
`rbac/apply_plan`, `rbac/userperm_gen`), recopiés **à la main** dans leurs READMEs
|
||||
de module : la table « Nb rôles » **par profil** (+ total bijectif 50), la ligne
|
||||
agrégée « Génération réelle : 6 étapes · 50 rôles / 116 DocPerm / 28 UP templates
|
||||
/ 6 Role Profile · 4 DocType custom + 5 Company + 4 rôles equipe » (8 comptes de
|
||||
tous les volets en une phrase) et la ventilation « 28 `entite` · 16 `groupe` · 2
|
||||
`own` · 4 `equipe` ». Chaque nombre est recomputé depuis le `out/MANIFEST.json`
|
||||
du volet (recalculé du contrat `rbac_50_roles.json` à chaque build, byte-gaté par
|
||||
`check_artifacts`) : ajouter un rôle/DocType ferait dériver **les trois** READMEs
|
||||
en silence sinon.
|
||||
|
||||
Un claim **absent** échoue aussi (la dérive de formulation qui ferait
|
||||
disparaître un chiffre est elle-même une régression de traçabilité). stdlib pur
|
||||
(bash/git/python3), zéro réseau.
|
||||
|
||||
@@ -47,6 +47,12 @@
|
||||
# DEUX docs (table du README + prose « OTO Ventes 4 cartes/11 liens/12
|
||||
# rôles ») + total « N Workspace natifs » → portails MANIFEST
|
||||
# (tableau `workspaces`, byte-gaté par check_artifacts)
|
||||
# rbac/{roleprofile_gen,apply_plan,userperm_gen}/README.md (comptes 3 volets) :
|
||||
# • table « Nb rôles » PAR profil + total bijectif → roleprofile MANIFEST
|
||||
# • ligne agrégée « 6 étapes · 50 rôles / 116 DocPerm / 28 UP / 6 Role
|
||||
# Profile · 4 DocType custom + 5 Company + 4 rôles » → apply_plan MANIFEST
|
||||
# • ventilation « 28 entite · 16 groupe · 2 own · 4 equipe » → userperm MANIFEST
|
||||
# (les 3 MANIFEST recalculés du contrat rbac_50_roles.json, byte-gatés)
|
||||
# Un claim absent échoue AUSSI (la dérive de formulation qui ferait disparaître
|
||||
# un chiffre est elle-même une régression de traçabilité).
|
||||
#
|
||||
@@ -461,6 +467,123 @@ 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")
|
||||
|
||||
# ============================================================================
|
||||
# RBAC · comptes des 3 volets (roleprofile · apply_plan · userperm) —
|
||||
# même classe de « vert trompeur », recopiés À LA MAIN depuis les MANIFEST.
|
||||
# ----------------------------------------------------------------------------
|
||||
# Les trois générateurs RBAC émettent, PAR volet, un out/MANIFEST.json recalculé
|
||||
# à chaque build depuis le contrat `rbac_50_roles.json` (byte-gaté par
|
||||
# check_artifacts ⇒ source faisant autorité). Trois READMEs de module rédigent à
|
||||
# LA MAIN des comptes qui dérivent de ces manifestes, sans AUCUN gate :
|
||||
# 1) roleprofile_gen/README.md — table « Nb rôles » PAR profil (6 lignes) +
|
||||
# total. Grain le plus fin (par item), comme la colonne « Tests » des fiches.
|
||||
# 2) apply_plan/README.md — ligne AGRÉGÉE « Génération réelle : 6 étapes · 50
|
||||
# rôles / 116 DocPerm / 28 UP templates / 6 Role Profile · confirmations VPS
|
||||
# 4 DocType custom + 5 Company + 4 rôles equipe » : 8 comptes consolidés de
|
||||
# TOUS les volets en une phrase.
|
||||
# 3) userperm_gen/README.md — « Répartition : 28 entite · 16 groupe · 2 own ·
|
||||
# 4 equipe » : la ventilation par mécanisme d'enforcement.
|
||||
# Ajouter un rôle/DocType au contrat ferait dériver LES TROIS docs en silence
|
||||
# pendant que les MANIFEST (byte-gatés) disent autre chose — même piège que les
|
||||
# cellules « Tests », la prose agrégée CRM/RBAC et les triplets par workspace. On
|
||||
# RECOMPUTE ici chaque nombre depuis le MANIFEST du volet (zéro duplication des
|
||||
# compteurs des générateurs) et on exige l'égalité avec ce que la doc ÉCRIT. Un
|
||||
# claim absent échoue AUSSI (disparition = régression de traçabilité).
|
||||
# ============================================================================
|
||||
def read_norm(path):
|
||||
try:
|
||||
return re.sub(r"\s+", " ", open(path, encoding="utf-8").read())
|
||||
except OSError as e:
|
||||
bad(f"RBAC · {path} illisible : {e}"); return None
|
||||
|
||||
# 1) roleprofile_gen — table « Nb rôles » par profil + total bijectif -----------
|
||||
RP_RD = f"{D}/rbac/roleprofile_gen/README.md"
|
||||
try:
|
||||
rp_man = load("rbac/roleprofile_gen/out/MANIFEST.json")
|
||||
except (OSError, KeyError, ValueError) as e:
|
||||
bad(f"RBAC roleprofile · MANIFEST illisible : {e}"); rp_man = None
|
||||
rp_rd = read_norm(RP_RD)
|
||||
if rp_man is not None and rp_rd is not None:
|
||||
for p in rp_man["profiles"]:
|
||||
name, portail, exp = p["role_profile"], p["portail"], str(p["nb_roles"])
|
||||
pat = (rf"\| `{re.escape(name)}` \| {re.escape(portail)} \| "
|
||||
r"[^|]*\| (\d+) \|")
|
||||
m = re.search(pat, rp_rd)
|
||||
if m is None:
|
||||
bad(f"RBAC roleprofile · {name} — ligne INTROUVABLE dans {RP_RD} "
|
||||
f"(attendu nb_roles={exp})")
|
||||
elif m.group(1) == exp:
|
||||
good(f"RBAC roleprofile · {name} — Nb rôles {m.group(1)} == MANIFEST ({exp})")
|
||||
else:
|
||||
bad(f"RBAC roleprofile · {name} — {RP_RD} dit {m.group(1)} MAIS "
|
||||
f"MANIFEST dit {exp} → régénérer le README")
|
||||
exp_tot = str(rp_man["counts"]["roles_couverts"])
|
||||
mt = re.search(r"\*\*Total\*\* \| \*\*(\d+)\*\*", rp_rd)
|
||||
if mt is None:
|
||||
bad(f"RBAC roleprofile · total — ligne « Total » INTROUVABLE dans {RP_RD} "
|
||||
f"(attendu {exp_tot})")
|
||||
elif mt.group(1) == exp_tot:
|
||||
good(f"RBAC roleprofile · total — {mt.group(1)} == MANIFEST ({exp_tot} couverts)")
|
||||
else:
|
||||
bad(f"RBAC roleprofile · total — {RP_RD} dit {mt.group(1)} MAIS MANIFEST "
|
||||
f"dit {exp_tot} → régénérer le README")
|
||||
|
||||
# 2) apply_plan — ligne « Génération réelle » agrégeant les 3 volets ------------
|
||||
AP_RD = f"{D}/rbac/apply_plan/README.md"
|
||||
try:
|
||||
ap_man = load("rbac/apply_plan/out/MANIFEST.json")
|
||||
ap_steps = len(load("rbac/apply_plan/out/apply_plan.json"))
|
||||
except (OSError, KeyError, ValueError) as e:
|
||||
bad(f"RBAC apply_plan · artefact illisible : {e}"); ap_man = None
|
||||
ap_rd = read_norm(AP_RD)
|
||||
if ap_man is not None and ap_rd is not None:
|
||||
c, v = ap_man["counts"], ap_man["confirmations_vps"]
|
||||
ap_exp = (str(ap_steps), str(c["roles"]), str(c["custom_docperm"]),
|
||||
str(c["user_permission_templates"]), str(c["role_profiles"]),
|
||||
str(len(v["custom_doctypes"])), str(len(v["companies"])),
|
||||
str(len(v["roles_scope_equipe"])))
|
||||
ap_src = (f"étapes={ap_exp[0]} rôles={ap_exp[1]} docperm={ap_exp[2]} "
|
||||
f"up={ap_exp[3]} role_profiles={ap_exp[4]} custom_dt={ap_exp[5]} "
|
||||
f"companies={ap_exp[6]} equipe={ap_exp[7]}")
|
||||
ap_pat = (r"\*\*(\d+) étapes\*\* · (\d+) rôles / (\d+) DocPerm / "
|
||||
r"(\d+) UP templates / (\d+) Role Profile · confirmations VPS "
|
||||
r"(\d+) DocType custom \+ (\d+) Company \+ (\d+) rôles")
|
||||
ma = re.search(ap_pat, ap_rd)
|
||||
if ma is None:
|
||||
bad(f"RBAC apply_plan · « Génération réelle » — claim INTROUVABLE dans "
|
||||
f"{AP_RD} (attendu {ap_src})")
|
||||
elif ma.groups() == ap_exp:
|
||||
good(f"RBAC apply_plan · agrégat — {AP_RD} {'/'.join(ma.groups())} == "
|
||||
f"MANIFEST ({ap_src})")
|
||||
else:
|
||||
bad(f"RBAC apply_plan · {AP_RD} dit {'/'.join(ma.groups())} MAIS MANIFEST "
|
||||
f"dit {'/'.join(ap_exp)} ({ap_src}) → régénérer le README")
|
||||
|
||||
# 3) userperm — ventilation par mécanisme d'enforcement -------------------------
|
||||
UP_RD = f"{D}/rbac/userperm_gen/README.md"
|
||||
try:
|
||||
up_bm = load("rbac/userperm_gen/out/MANIFEST.json")["counts"]["by_mechanism"]
|
||||
except (OSError, KeyError, ValueError) as e:
|
||||
bad(f"RBAC userperm · MANIFEST illisible : {e}"); up_bm = None
|
||||
up_rd = read_norm(UP_RD)
|
||||
if up_bm is not None and up_rd is not None:
|
||||
up_exp = (str(up_bm["user_permission_company"]), str(up_bm["none_consolidated"]),
|
||||
str(up_bm["docperm_if_owner"]), str(up_bm["vps_confirm_team"]))
|
||||
up_src = (f"entite={up_exp[0]} groupe={up_exp[1]} own={up_exp[2]} "
|
||||
f"equipe={up_exp[3]}")
|
||||
up_pat = (r"\*\*(\d+)\*\* `entite` · \*\*(\d+)\*\* `groupe` · "
|
||||
r"\*\*(\d+)\*\* `own` · \*\*(\d+)\*\* `equipe`")
|
||||
mu = re.search(up_pat, up_rd)
|
||||
if mu is None:
|
||||
bad(f"RBAC userperm · répartition — claim INTROUVABLE dans {UP_RD} "
|
||||
f"(attendu {up_src})")
|
||||
elif mu.groups() == up_exp:
|
||||
good(f"RBAC userperm · répartition — {UP_RD} {'/'.join(mu.groups())} == "
|
||||
f"MANIFEST ({up_src})")
|
||||
else:
|
||||
bad(f"RBAC userperm · {UP_RD} dit {'/'.join(mu.groups())} MAIS MANIFEST "
|
||||
f"dit {'/'.join(up_exp)} ({up_src}) → régénérer le README")
|
||||
|
||||
sys.exit(1 if FAIL else 0)
|
||||
PY
|
||||
rc=$?
|
||||
|
||||
Reference in New Issue
Block a user