[DTP-Worker] Sprint 8 · buffer · RBAC/apply_plan (2e surface) : la TABLE « Run-book généré (SPEC §7) » — le GRAPHE d'application ordonné que l'agent ERPNext suit pas à pas — avait ses colonnes d'IDENTITÉ (# · Responsable · Dépend de) NON GATÉES.
Le bloc « apply_plan » existant de check_readme_claims.sh (bloc 2) ne gate QUE la
ligne AGRÉGÉE « Génération réelle » (8 comptes consolidés). Les 3 colonnes
d'identité de la table run-book (README:38-46) sont data-derived de
out/apply_plan.json (byte-gaté par check_artifacts) : # = order · Responsable =
responsable (worker/vps/worker+vps) · Dépend de = les n°s d'ordre des depends_on.
Elles étaient un WILDCARD. PIÈGE : check_artifacts ne prouve QUE apply_plan==build
(byte-for-byte) et le bloc de comptes est aveugle à l'identité des lignes → passer
le Responsable de l'étape 4 (worker+vps→vps) ou retirer une dépendance de l'étape 6
(4,5→4) laissait la prose périmée pendant que l'artefact dit autre chose. Un
responsable périmé (étape VPS attribuée au worker) ou une dépendance périmée (Role
Profile importé AVANT les Role) est un hazard réel — le risque même que le run-book
veut prévenir — qu'aucune suite tests/ (qui teste des fonctions, pas la table
commitée) n'attrape. Même classe que la colonne « Type » de roleprofile_gen et la
table demo/scenarios.
Nouveau sous-bloc « 2bis) apply_plan — TABLE Run-book » : (1) IDENTITÉ par étape —
chaque ligne porte EXACTEMENT #=order + Responsable=responsable + Dépend de=n°s
d'ordre des depends_on (deps extraits par digits ⇒ robuste au séparateur/em-dash) ;
colonne « Étape » libre (paraphrase). (2) IDENTITÉ d'ensemble — {ordres des lignes}
== {ordres artefact} sans doublon (aucune ligne FANTÔME, aucune étape MANQUANTE).
Cohérences croisées (mordent un plan INTERNEMENT incohérent) : ordres contigus 1..N ·
responsable ∈ {worker,vps,worker+vps} · toute dépendance pointe en ARRIÈRE
(n° < n° de l'étape). Un claim absent échoue AUSSI.
8 morsures vérifiées : README étape4 responsable worker+vps→vps (câblage) · README
étape6 Dépend de 4,5→4 (dep manquante) · README étape3 1,2→1,2,5 (dep fantôme) ·
ligne FANTÔME #7 (7≠6) · étape 5 SUPPRIMÉE (INTROUVABLE + 5≠6) · artefact étape2
responsable vps→worker (README stale) · artefact étape2 dep en AVANT (graphe
incohérent) · artefact étape1 responsable hors domaine (robot) ; restauré = green :
6 lignes == 6 étapes · responsables/deps == artefact · ordres 1..6 · exit 0.
État courant : aucune valeur périmée (anti-invention #6, rien à réécrire) — le
défaut est la surface ungated. ci/README.md (table + détail « 2ᵉ surface
rbac/apply_plan ») mis à jour · working tree byte-restauré · 7 gates re-verts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+18
-2
File diff suppressed because one or more lines are too long
@@ -780,6 +780,80 @@ if ap_man is not None and ap_rd is not None:
|
||||
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")
|
||||
|
||||
# 2bis) apply_plan — IDENTITÉ de la TABLE « Run-book généré » (SPEC §7) ----------
|
||||
# Le bloc 2 ci-dessus ne gate que la ligne AGRÉGÉE « Génération réelle » (comptes).
|
||||
# La table « ## Run-book généré » énumère, PAR étape, trois données data-derived de
|
||||
# apply_plan.json (byte-gaté par check_artifacts) : le n° d'ordre (#), le
|
||||
# RESPONSABLE (worker/vps/worker+vps) et la colonne « Dépend de » (les n°s d'ordre
|
||||
# des `depends_on`). C'est le GRAPHE D'APPLICATION ORDONNÉ que l'agent ERPNext suit
|
||||
# pas à pas — un responsable ou une dépendance périmés (ex. Role Profile importé
|
||||
# AVANT les Role, ou une étape VPS attribuée au worker) sont un hazard réel,
|
||||
# invisible à check_artifacts (qui ne prouve QUE apply_plan==build) ET au bloc de
|
||||
# comptes (aveugle à l'identité des lignes ; ces colonnes étaient un WILDCARD).
|
||||
# Même classe que la colonne « Type » de roleprofile_gen et la table demo/scenarios.
|
||||
# On RECOMPUTE #/responsable/deps depuis apply_plan.json (zéro duplication) et on
|
||||
# exige que la table les énonce EXACTEMENT. Une étape MANQUANTE ou une ligne FANTÔME
|
||||
# échoue AUSSI (identité d'ensemble). La colonne « Étape » reste libre (paraphrase).
|
||||
# Cohérences croisées en bonus (mordent un plan INTERNEMENT incohérent) : ordres
|
||||
# contigus 1..N sans doublon · responsable ∈ {worker, vps, worker+vps} · toute
|
||||
# dépendance pointe en ARRIÈRE (n° < n° de l'étape ⇒ pas de cycle/renvoi avant).
|
||||
try:
|
||||
ap_plan = load("rbac/apply_plan/out/apply_plan.json")
|
||||
except (OSError, KeyError, ValueError) as e:
|
||||
bad(f"RBAC apply_plan · run-book illisible : {e}"); ap_plan = None
|
||||
if ap_plan is not None:
|
||||
id2order = {s["id"]: s["order"] for s in ap_plan}
|
||||
ALLOWED_RESP = {"worker", "vps", "worker+vps"}
|
||||
orders = sorted(s["order"] for s in ap_plan)
|
||||
if orders == list(range(1, len(ap_plan) + 1)):
|
||||
good(f"RBAC apply_plan · run-book — ordres contigus 1..{len(ap_plan)} "
|
||||
f"(aucun trou/doublon)")
|
||||
else:
|
||||
bad(f"RBAC apply_plan · run-book — ordres NON contigus {orders} "
|
||||
f"(attendu 1..{len(ap_plan)}) → plan incohérent")
|
||||
# Lignes de la table lues en RAW : la colonne 1 ENTIÈRE ⇒ scope la table
|
||||
# run-book (seule table du README à 1ʳᵉ colonne numérique · vérifié).
|
||||
try:
|
||||
ap_lines = open(AP_RD, encoding="utf-8").read().splitlines()
|
||||
except OSError as e:
|
||||
bad(f"RBAC apply_plan · {AP_RD} illisible : {e}"); ap_lines = []
|
||||
table_orders, rows_by_order = [], {}
|
||||
for ln in ap_lines:
|
||||
cells = [c.strip() for c in ln.strip().strip("|").split("|")]
|
||||
if len(cells) == 4 and re.fullmatch(r"\d+", cells[0]):
|
||||
o = int(cells[0]); table_orders.append(o); rows_by_order[o] = cells
|
||||
if sorted(table_orders) == orders and len(table_orders) == len(set(table_orders)):
|
||||
good(f"RBAC apply_plan · run-book — {len(table_orders)} lignes == "
|
||||
f"{len(ap_plan)} étapes (aucune FANTÔME/MANQUANTE)")
|
||||
else:
|
||||
bad(f"RBAC apply_plan · run-book — lignes table {sorted(table_orders)} ≠ "
|
||||
f"étapes artefact {orders} (ligne en trop/manquante ou doublon)")
|
||||
for s in sorted(ap_plan, key=lambda x: x["order"]):
|
||||
o, resp = s["order"], s["responsable"]
|
||||
deps = sorted(id2order[x] for x in s["depends_on"])
|
||||
if resp not in ALLOWED_RESP:
|
||||
bad(f"RBAC apply_plan · étape {o} — responsable artefact « {resp} » "
|
||||
f"hors {sorted(ALLOWED_RESP)} (plan incohérent)")
|
||||
if any(d >= o for d in deps):
|
||||
bad(f"RBAC apply_plan · étape {o} — dépendance en AVANT {deps} "
|
||||
f"(≥ {o}) : graphe incohérent")
|
||||
row = rows_by_order.get(o)
|
||||
if row is None:
|
||||
bad(f"RBAC apply_plan · étape {o} — ligne INTROUVABLE dans {AP_RD} "
|
||||
f"(attendu responsable={resp} deps={deps or '—'})"); continue
|
||||
got_resp = row[1]
|
||||
got_deps = sorted(int(x) for x in re.findall(r"\d+", row[3]))
|
||||
if got_resp == resp:
|
||||
good(f"RBAC apply_plan · étape {o} — responsable « {got_resp} » == artefact")
|
||||
else:
|
||||
bad(f"RBAC apply_plan · étape {o} — {AP_RD} dit responsable « {got_resp} » "
|
||||
f"MAIS artefact dit « {resp} » → régénérer le README")
|
||||
if got_deps == deps:
|
||||
good(f"RBAC apply_plan · étape {o} — Dépend de {got_deps or '—'} == artefact")
|
||||
else:
|
||||
bad(f"RBAC apply_plan · étape {o} — {AP_RD} « Dépend de » = {got_deps or '—'} "
|
||||
f"MAIS artefact dit {deps or '—'} → régénérer le README")
|
||||
|
||||
# 3) userperm — ventilation par mécanisme d'enforcement -------------------------
|
||||
UP_RD = f"{D}/rbac/userperm_gen/README.md"
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user