pbl_blueprint/scripts/load_path.py
2026-09-16 12:49:06 +08:00

256 lines
9.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""pbl_blueprint RBAC 路径注册清单(显式枚举,无 %/* 通配符)。
本文件是 RBAC 路径的**唯一声明源**。规则:
1. 每条路径都是完整字面量,以 /api/ 开头、以 .dspy 结尾;
2. 严禁 % / * 通配符RBAC 必须逐条授权,不可模糊匹配);
3. 严禁重复;
4. 必须**覆盖** pbl_blueprint/json/*.json 中 editable 声明的全部 3×11 条路径
selfcheck.py F 组逐条比对,缺一条即退出码非 0
5. 表名以设计定稿 B1~B11 为权威
projects/pbls/docs/01-design/modules/pbl_blueprint.md §2
用法:
python3 modules/pbl_blueprint/scripts/load_path.py # 打印清单
python3 modules/pbl_blueprint/scripts/load_path.py --check # 与 json editable 比对
"""
import json
import os
import sys
MODULE_NAME = "pbl_blueprint"
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
JSON_DIR = os.path.join(REPO_ROOT, "pbl_blueprint", "json")
# 设计定稿 B1~B11 主清单(权威表名,顺序即编号)
MAIN_TABLES = (
"pbl_blueprint", # B1 聚合根
"pbl_blueprint_version", # B2 版本append-only
"pbl_blueprint_approval", # B3 审批记录
"pbl_learner", # B4 学习者画像
"pbl_learning_goal", # B5 学习目标
"pbl_problem", # B6 真实世界问题
"pbl_driving_question", # B7 驱动问题
"pbl_project", # B8 项目constraints
"pbl_role", # B9 角色
"pbl_mission", # B10 任务
"pbl_artifact_def", # B11 产出物定义
)
# 运维支撑表(在途已有实现,归档保留,路径仍需授权)
SUPPORT_TABLES = (
"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",
)
# ---------------------------------------------------------------- B1 聚合根
PATHS_BLUEPRINT = (
"/api/pbl_blueprint/list.dspy",
"/api/pbl_blueprint/edit.dspy",
"/api/pbl_blueprint/view.dspy",
"/api/pbl_blueprint/create.dspy",
"/api/pbl_blueprint/get.dspy",
"/api/pbl_blueprint/update.dspy",
"/api/pbl_blueprint/delete.dspy",
"/api/pbl_blueprint/tree.dspy",
"/api/pbl_blueprint/fork.dspy",
"/api/pbl_blueprint/forks.dspy",
"/api/pbl_blueprint/stats.dspy",
"/api/pbl_blueprint/change_status.dspy",
"/api/pbl_blueprint/set_quality_level.dspy",
"/api/pbl_blueprint/submit_approval.dspy",
"/api/pbl_blueprint/approve.dspy",
"/api/pbl_blueprint/reject.dspy",
)
# ---------------------------------------------------------------- B2 版本
PATHS_VERSION = (
"/api/pbl_blueprint_version/list.dspy",
"/api/pbl_blueprint_version/edit.dspy",
"/api/pbl_blueprint_version/view.dspy",
"/api/pbl_blueprint_version/get.dspy",
"/api/pbl_blueprint_version/commit.dspy",
"/api/pbl_blueprint_version/delta.dspy",
"/api/pbl_blueprint_version/rollback.dspy",
)
# ---------------------------------------------------------------- B3 审批
PATHS_APPROVAL = (
"/api/pbl_blueprint_approval/list.dspy",
"/api/pbl_blueprint_approval/edit.dspy",
"/api/pbl_blueprint_approval/view.dspy",
"/api/pbl_blueprint_approval/get.dspy",
"/api/pbl_blueprint_approval/create.dspy",
"/api/pbl_blueprint_approval/update.dspy",
"/api/pbl_blueprint_approval/delete.dspy",
)
# ------------------------------------------- B4~B11 七类子对象(统一 5 动作 CRUD
_SUBOBJECT_ACTIONS = ("list", "edit", "view", "create", "get", "update", "delete")
PATHS_LEARNER = tuple(
"/api/pbl_learner/%s.dspy" % a for a in _SUBOBJECT_ACTIONS)
PATHS_LEARNING_GOAL = tuple(
"/api/pbl_learning_goal/%s.dspy" % a for a in _SUBOBJECT_ACTIONS)
PATHS_PROBLEM = tuple(
"/api/pbl_problem/%s.dspy" % a for a in _SUBOBJECT_ACTIONS)
PATHS_DRIVING_QUESTION = tuple(
"/api/pbl_driving_question/%s.dspy" % a for a in _SUBOBJECT_ACTIONS)
PATHS_PROJECT = tuple(
"/api/pbl_project/%s.dspy" % a for a in _SUBOBJECT_ACTIONS)
PATHS_ROLE = tuple(
"/api/pbl_role/%s.dspy" % a for a in _SUBOBJECT_ACTIONS)
PATHS_MISSION = tuple(
"/api/pbl_mission/%s.dspy" % a for a in _SUBOBJECT_ACTIONS)
PATHS_ARTIFACT_DEF = tuple(
"/api/pbl_artifact_def/%s.dspy" % a for a in _SUBOBJECT_ACTIONS)
# ---------------------------------------------------------------- 运维支撑表
PATHS_SUPPORT = (
# 审计append-only只读
"/api/pbl_blueprint_audit/list.dspy",
"/api/pbl_blueprint_audit/edit.dspy",
"/api/pbl_blueprint_audit/view.dspy",
"/api/pbl_blueprint_audit/get.dspy",
# 关系边
"/api/pbl_blueprint_edge/list.dspy",
"/api/pbl_blueprint_edge/edit.dspy",
"/api/pbl_blueprint_edge/view.dspy",
"/api/pbl_blueprint_edge/create.dspy",
"/api/pbl_blueprint_edge/get.dspy",
"/api/pbl_blueprint_edge/update.dspy",
"/api/pbl_blueprint_edge/delete.dspy",
# fork 溯源
"/api/pbl_blueprint_fork/list.dspy",
"/api/pbl_blueprint_fork/edit.dspy",
"/api/pbl_blueprint_fork/view.dspy",
"/api/pbl_blueprint_fork/get.dspy",
"/api/pbl_blueprint_fork/lineage.dspy",
# 编辑锁
"/api/pbl_blueprint_lock/list.dspy",
"/api/pbl_blueprint_lock/edit.dspy",
"/api/pbl_blueprint_lock/view.dspy",
"/api/pbl_blueprint_lock/lock.dspy",
"/api/pbl_blueprint_lock/unlock.dspy",
"/api/pbl_blueprint_lock/clean_expired.dspy",
# 泛化节点(在途实现)
"/api/pbl_blueprint_node/list.dspy",
"/api/pbl_blueprint_node/edit.dspy",
"/api/pbl_blueprint_node/view.dspy",
"/api/pbl_blueprint_node/create.dspy",
"/api/pbl_blueprint_node/get.dspy",
"/api/pbl_blueprint_node/update.dspy",
"/api/pbl_blueprint_node/delete.dspy",
# 离线包
"/api/pbl_blueprint_offline/list.dspy",
"/api/pbl_blueprint_offline/edit.dspy",
"/api/pbl_blueprint_offline/view.dspy",
"/api/pbl_blueprint_offline/get.dspy",
"/api/pbl_blueprint_offline/export.dspy",
"/api/pbl_blueprint_offline/import.dspy",
# 发布
"/api/pbl_blueprint_publish/list.dspy",
"/api/pbl_blueprint_publish/edit.dspy",
"/api/pbl_blueprint_publish/view.dspy",
"/api/pbl_blueprint_publish/get.dspy",
"/api/pbl_blueprint_publish/create.dspy",
"/api/pbl_blueprint_publish/revoke.dspy",
# 模板
"/api/pbl_blueprint_template/list.dspy",
"/api/pbl_blueprint_template/edit.dspy",
"/api/pbl_blueprint_template/view.dspy",
"/api/pbl_blueprint_template/create.dspy",
"/api/pbl_blueprint_template/get.dspy",
"/api/pbl_blueprint_template/update.dspy",
"/api/pbl_blueprint_template/delete.dspy",
"/api/pbl_blueprint_template/instantiate.dspy",
# 版本增量明细
"/api/pbl_blueprint_version_delta/list.dspy",
"/api/pbl_blueprint_version_delta/edit.dspy",
"/api/pbl_blueprint_version_delta/view.dspy",
"/api/pbl_blueprint_version_delta/get.dspy",
)
# ---------------------------------------------------------------- 汇总(唯一出口)
PATHS = (
PATHS_BLUEPRINT
+ PATHS_VERSION
+ PATHS_APPROVAL
+ PATHS_LEARNER
+ PATHS_LEARNING_GOAL
+ PATHS_PROBLEM
+ PATHS_DRIVING_QUESTION
+ PATHS_PROJECT
+ PATHS_ROLE
+ PATHS_MISSION
+ PATHS_ARTIFACT_DEF
+ PATHS_SUPPORT
)
def json_editable_paths():
"""读取 json/*.json 的 editable返回其对应的 /api/ 路径集合。"""
out = set()
if not os.path.isdir(JSON_DIR):
return out
for fn in sorted(os.listdir(JSON_DIR)):
if not fn.endswith(".json"):
continue
try:
with open(os.path.join(JSON_DIR, fn), "r", encoding="utf-8") as f:
d = json.load(f)
except Exception: # noqa: BLE001
continue
for u in (d.get("editable") or []):
if isinstance(u, str) and u.startswith("api/"):
out.add("/" + u)
return out
def validate():
"""自检:无通配符 / 全部 /api/ 开头 / 无重复 / 覆盖 json editable。"""
errs = []
wild = [p for p in PATHS if "%" in p or "*" in p]
if wild:
errs.append("存在通配路径: {}".format(wild))
bad = [p for p in PATHS if not p.startswith("/api/") or not p.endswith(".dspy")]
if bad:
errs.append("非法路径格式: {}".format(bad))
dup = sorted({p for p in PATHS if PATHS.count(p) > 1})
if dup:
errs.append("重复路径: {}".format(dup))
need = json_editable_paths()
miss = sorted(need - set(PATHS))
if miss:
errs.append("未覆盖 json editable 路径: {}".format(miss))
return errs
def main(argv):
if "--check" in argv:
errs = validate()
if errs:
print("RBAC 路径校验失败:")
for e in errs:
print(" - " + e)
return 1
print("RBAC 路径校验通过: {} 条(无通配符/无重复/覆盖 json editable {} 条)".format(
len(PATHS), len(json_editable_paths())))
return 0
print("# {} RBAC 路径清单({} 条,显式枚举)".format(MODULE_NAME, len(PATHS)))
for p in PATHS:
print(p)
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))