# -*- 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 无需修改")