129 lines
6.2 KiB
Python
129 lines
6.2 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""对齐 selfcheck.py F/H 组判定到设计定稿(幂等,改后 py_compile 校验,失败回滚)。
|
||
|
||
H 组:设计定稿 B4~B11 为**独立建表**(非泛化单表 pbl_subobject),
|
||
核心表判定改为 B1 pbl_blueprint / B2 pbl_blueprint_version / B3 pbl_blueprint_approval,
|
||
并逐表核验 B4~B11 八张子对象表均有 CREATE TABLE。
|
||
F 组:load_path.py 是 RBAC 唯一声明源,必须**覆盖**(超集)json/*.json editable 的 3×11 条,
|
||
故一致性判定改为「json editable ⊆ load_path PATHS」,不再要求双向相等
|
||
(支撑表含 lock/fork/lineage 等额外动作,天然多于 editable 三条)。
|
||
"""
|
||
import os
|
||
import shutil
|
||
import subprocess
|
||
import sys
|
||
|
||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||
SC = os.path.join(ROOT, "scripts", "selfcheck.py")
|
||
|
||
src = open(SC, "r", encoding="utf-8").read()
|
||
orig = src
|
||
bak = SC + ".pbak"
|
||
shutil.copyfile(SC, bak)
|
||
|
||
# ---------------------------------------------------------------- H 组核心表
|
||
old_h = ''' need_core = ["pbl_blueprint", "pbl_blueprint_version", "pbl_subobject"]
|
||
miss = [t for t in need_core if t not in tables]
|
||
check(g, not miss, "DDL 覆盖核心表(主表/版本/泛化子对象)",
|
||
"缺失=%s 实际=%s" % (miss or "无", tables))'''
|
||
new_h = ''' # 设计定稿 B1~B3 核心表(B4~B11 为独立建表,非泛化单表)
|
||
need_core = ["pbl_blueprint", "pbl_blueprint_version", "pbl_blueprint_approval"]
|
||
miss = [t for t in need_core if t not in tables]
|
||
check(g, not miss, "DDL 覆盖核心表 B1~B3(聚合根/版本/审批)",
|
||
"缺失=%s" % (miss or "无"))
|
||
# 设计定稿 B4~B11 八张子对象表必须独立建表
|
||
need_sub = ["pbl_learner", "pbl_learning_goal", "pbl_problem",
|
||
"pbl_driving_question", "pbl_project", "pbl_role",
|
||
"pbl_mission", "pbl_artifact_def"]
|
||
miss_sub = [t for t in need_sub if t not in tables]
|
||
check(g, not miss_sub, "DDL 覆盖子对象表 B4~B11(8 张独立建表)",
|
||
"缺失=%s 建表总数=%d" % (miss_sub or "无", len(tables)))'''
|
||
if old_h in src:
|
||
src = src.replace(old_h, new_h)
|
||
print("[FIX ] H 组核心表判定对齐设计定稿 B1~B11")
|
||
else:
|
||
print("[WARN] H 组未匹配到旧规则(可能已修)")
|
||
|
||
# ---------------------------------------------------------------- H 组承载映射
|
||
old_c = ''' uncovered = []
|
||
for t in mfiles:
|
||
if t in tables:
|
||
continue
|
||
carrier = GENERIC_CARRIER.get(t)
|
||
if carrier and carrier in tables:
|
||
continue
|
||
# 其余子对象表由泛化单表承载
|
||
if "pbl_subobject" in tables and t.startswith("pbl_blueprint_"):
|
||
continue
|
||
uncovered.append(t)
|
||
check(g, not uncovered, "models 11 表均有物理承载(独立建表或泛化单表)",
|
||
"未承载=%s 泛化映射=%s" % (uncovered or "无", GENERIC_CARRIER))'''
|
||
new_c = ''' # 设计定稿:models 主清单 11 表 = DDL 独立建表,逐表必须有 CREATE TABLE
|
||
uncovered = [t for t in mfiles if t not in tables]
|
||
check(g, not uncovered, "models 主清单 11 表均有独立 CREATE TABLE 承载",
|
||
"未承载=%s models=%d ddl建表=%d" % (uncovered or "无", len(mfiles), len(tables)))'''
|
||
if old_c in src:
|
||
src = src.replace(old_c, new_c)
|
||
print("[FIX ] H 组承载判定改为逐表独立建表")
|
||
else:
|
||
print("[WARN] H 组承载规则未匹配(可能已修)")
|
||
|
||
# ---------------------------------------------------------------- F 组一致性
|
||
old_f = ''' only_lp = [p for p in lits if p not in impl]
|
||
only_impl = sorted(p for p in impl if p not in lits)
|
||
check(g, not only_lp and not only_impl,
|
||
"load_path.py 与实现(api*.py/json editable) RBAC 路径一致",
|
||
"load_path=%d 实现=%d 仅load_path=%s 仅实现=%s"
|
||
% (len(lits), len(impl), only_lp[:5] or "无", only_impl[:5] or "无"))'''
|
||
new_f = ''' # load_path.py 是 RBAC 唯一声明源,必须覆盖 json editable 的 3×11 条(超集合法:
|
||
# 支撑表含 lock/fork/lineage 等额外动作)。故只判「json editable ⊆ load_path」。
|
||
missing = sorted(p for p in impl if p not in lits)
|
||
check(g, not missing,
|
||
"load_path.py 覆盖 json/*.json editable 全部路径(超集)",
|
||
"load_path=%d json_editable=%d 未覆盖=%s"
|
||
% (len(lits), len(impl), missing[:5] or "无"))
|
||
# 反向:load_path 中每条路径必须归属已知表(主清单 11 + 支撑表 9),防孤儿路径
|
||
known = set(MAIN_TABLES_KNOWN) | set(SUPPORT_TABLES_KNOWN)
|
||
orphan = []
|
||
for p in lits:
|
||
m = re.match(r"/api/([a-z0-9_]+)/", p)
|
||
if not m or m.group(1) not in known:
|
||
orphan.append(p)
|
||
check(g, not orphan, "load_path.py 无孤儿路径(均归属已知表)",
|
||
"孤儿=%s" % (orphan[:5] or "无"))'''
|
||
if old_f in src:
|
||
src = src.replace(old_f, new_f)
|
||
print("[FIX ] F 组一致性改为超集覆盖 + 孤儿路径检查")
|
||
else:
|
||
print("[WARN] F 组规则未匹配(可能已修)")
|
||
|
||
# 注入已知表名常量
|
||
if "MAIN_TABLES_KNOWN" not in src:
|
||
consts = '''
|
||
MAIN_TABLES_KNOWN = (
|
||
"pbl_blueprint", "pbl_blueprint_version", "pbl_blueprint_approval",
|
||
"pbl_learner", "pbl_learning_goal", "pbl_problem", "pbl_driving_question",
|
||
"pbl_project", "pbl_role", "pbl_mission", "pbl_artifact_def",
|
||
)
|
||
SUPPORT_TABLES_KNOWN = (
|
||
"pbl_blueprint_audit", "pbl_blueprint_edge", "pbl_blueprint_fork",
|
||
"pbl_blueprint_lock", "pbl_blueprint_node", "pbl_blueprint_offline",
|
||
"pbl_blueprint_publish", "pbl_blueprint_template", "pbl_blueprint_version_delta",
|
||
)
|
||
'''
|
||
src = src.replace("\nLINES: list[str] = []", consts + "\nLINES: list[str] = []", 1)
|
||
|
||
if src != orig:
|
||
open(SC, "w", encoding="utf-8").write(src)
|
||
r = subprocess.run([sys.executable, "-m", "py_compile", SC],
|
||
capture_output=True, text=True)
|
||
if r.returncode != 0:
|
||
shutil.move(bak, SC)
|
||
print("[ROLLBACK] selfcheck.py 编译失败已回滚: %s" % (r.stderr or "")[-400:])
|
||
sys.exit(1)
|
||
os.remove(bak)
|
||
print("[OK ] selfcheck.py 已更新并通过 py_compile")
|
||
else:
|
||
os.remove(bak)
|
||
print("[OK ] selfcheck.py 无需修改")
|