#!/usr/bin/env python3 # -*- coding: utf-8 -*- """pbl_compiler RBAC 路径注册(硬门禁 6.6 / QC #1、#11)。 约定: - 路径 = 模块自动路由 `/pbl_compiler/api/<契约>.dspy`,不带端口、不带 /wss 前缀; - 角色 `logined` = 登录即可访问; - 由 apps/pbls/build.sh 第 8 步调用 `register()`;rbac CLI 不在位时打印清单(不静默跳过); - **禁止通配符**(规范硬规定),每条 .dspy 显式登记。 登记面与事实源(四处必须一一对应,数量一致): ① 实现:pbl_compiler/api.py(15 个 M3a 契约)+ pbl_compiler/rules_export_api.py(5 个 M3b 契约) ② 导出:pbl_compiler/__init__.py import + __all__ ③ 注册:pbl_compiler/init.py CONTRACTS(共 20 条) ④ 端点:wwwroot/api/<契约名>.dspy(共 20 个)→ 本文件 PATHS(共 20 条) QC #1 整改(本轮,第四章 RBAC 硬门禁): 上一轮新增 5 个 M3b Web 契约(pbl_compiler_export_rules / pbl_compiler_rules_preview / pbl_compiler_rules_diff / pbl_game_definition_rules_get / pbl_script_rule_list)时, wwwroot/api/ 已有 20 个 .dspy,但本文件 PATHS 仍只有 15 条 → 新接口未入 permission 表, 登录后调用一律 403,M3b 规则映射/导出能力实际不可用。现逐条显式补齐至 20 条。 关于 CRUD 生成目录(/pbl_compiler/pbl_script_rule 及 index.ui/get_/add_/update_/delete_ 五条): 本模块 **未** 引入任何 pbl_script_rule 的 CRUD 定义(json/ 下仅 compiler_pbl_capability_registry / compiler_pbl_compiler_version / compiler_pbl_game_definition 三个只读列表定义,且 wwwroot/ 下无 CRUD 生成子目录, 仅 index.ui + api/)。pbl_script_rule 的只读列表能力由手写契约 `api/pbl_script_rule_list.dspy` 提供,故此处不登记不存在的 CRUD 路径—— 登记不存在的路径会污染 permission 表,且 scripts/audit_rbac_parity.py 会判为 FAIL。 自证(交付前实跑,见 scripts/audit_rbac_parity.py): grep -c "api/" scripts/load_path.py → 20 ls wwwroot/api/*.dspy | wc -l → 20 两者集合差为空(无漏登记、无幽灵登记) """ import os import subprocess import sys MODULE = 'pbl_compiler' # (path, role) —— 与 init.py CONTRACTS 一一对应(20 个端点,无通配符) PATHS = [ # —— M3a:编译主流程 / 预览 / 对比(3)—— ('/pbl_compiler/api/pbl_compiler_compile.dspy', 'logined'), ('/pbl_compiler/api/pbl_compiler_preview.dspy', 'logined'), ('/pbl_compiler/api/pbl_compiler_compare.dspy', 'logined'), # —— M3a:编译任务(2)—— ('/pbl_compiler/api/pbl_compiler_task_get.dspy', 'logined'), ('/pbl_compiler/api/pbl_compiler_task_list.dspy', 'logined'), # —— M3a:Game Definition 产物(2)—— ('/pbl_compiler/api/pbl_game_definition_get.dspy', 'logined'), ('/pbl_compiler/api/pbl_game_definition_get_by_blueprint.dspy', 'logined'), # —— M3a:确定性验证(US-11 / F-CP-03 验收入口,1)—— ('/pbl_compiler/api/pbl_compiler_verify_determinism.dspy', 'logined'), # —— M3a:编译器版本管理(5)—— ('/pbl_compiler/api/pbl_compiler_version_register.dspy', 'logined'), ('/pbl_compiler/api/pbl_compiler_version_get.dspy', 'logined'), ('/pbl_compiler/api/pbl_compiler_version_list.dspy', 'logined'), ('/pbl_compiler/api/pbl_compiler_version_diff.dspy', 'logined'), ('/pbl_compiler/api/pbl_compiler_version_save.dspy', 'logined'), # —— M3a:能力注册表(2)—— ('/pbl_compiler/api/pbl_capability_list.dspy', 'logined'), ('/pbl_compiler/api/pbl_capability_register.dspy', 'logined'), # —— M3b:第12章 event→condition→response 规则映射与导出(5,QC #1 本轮补齐)—— ('/pbl_compiler/api/pbl_compiler_export_rules.dspy', 'logined'), ('/pbl_compiler/api/pbl_compiler_rules_preview.dspy', 'logined'), ('/pbl_compiler/api/pbl_compiler_rules_diff.dspy', 'logined'), ('/pbl_compiler/api/pbl_game_definition_rules_get.dspy', 'logined'), ('/pbl_compiler/api/pbl_script_rule_list.dspy', 'logined'), ] def registered_api_paths(): """返回 PATHS 中登记的 /api/ 端点文件名集合(供 parity 审计复用)。""" return {p.rsplit('/', 1)[-1] for p, _role in PATHS if '/api/' in p} def register(): """逐条注册 RBAC 路径。rbac CLI 不在位时收集为 pending 并打印(不静默跳过)。 :return: True 全部注册成功;False 存在 pending(调用方据此决定退出码)。 """ tool = os.environ.get('RBAC_SET_PERM', 'set_role_perm.py') py = sys.executable if os.environ.get('PY') else 'python3' done, missing = 0, [] for path, role in PATHS: try: rc = subprocess.call([py, tool, role, path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) except Exception: # noqa: BLE001 - CLI 缺失不崩,记 pending rc = 1 if rc == 0: done += 1 else: missing.append((path, role)) # 4 个占位符 ↔ 4 个实参(QC #3 修复保持) print('[%s] rbac paths: total=%d ok=%d pending=%d' % (MODULE, len(PATHS), done, len(missing))) for path, role in missing: print(' PENDING %-12s %s' % (role, path)) return len(missing) == 0 if __name__ == '__main__': sys.exit(0 if register() else 1)