- scripts/load_path.py: PATHS_CONTRACTS 20 条(含 M3b 5 个导出契约) + PATHS_READONLY_DENY 9 条 = 29 条全显式登记,无通配符;与 wwwroot/api/*.dspy(29) 一一对应 - wwwroot/api/: 新增 9 个 CRUD 只读拒绝端点(create/update/delete × 3 表), 封死 xls2ui 回退默认写入口,保 rules_hash 29.6 确定性指纹唯一写入路径 - json/*.json: 根级 browserfields 合并进 params,三文件统一 tblname/alias/title/params 根键集, editable 齐备 new_/update_/delete_data_url 且指向真实 .dspy - scripts/audit_rbac_parity.py: 修复 PATHS = A + B (BinOp) 解析取空的自身缺陷(假 FAIL 根因), 新增 json/ 根键白名单 + editable 三 URL + 幽灵/漏登记 检查 - pbl_compiler/init.py: 文件头注释登记条数与代码事实对齐(29=20+9) - 实跑: audit_rbac_parity.py rc=0 PASS;test_m3b_mapping.py PASS=113/FAIL=0
141 lines
7.4 KiB
Python
141 lines
7.4 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""pbl_compiler RBAC 路径注册(硬门禁 6.6 / QC 第四章 RBAC)。
|
||
|
||
约定:
|
||
- 路径 = 模块自动路由 `/pbl_compiler/api/<契约>.dspy`,不带端口、不带 /wss 前缀;
|
||
- 角色 `logined` = 登录即可访问;
|
||
- 由 apps/pbls/build.sh 第 8 步调用 `register()`;rbac CLI 不在位时打印清单(不静默跳过);
|
||
- **禁止通配符**(规范硬规定),每条 .dspy 显式登记。
|
||
|
||
两个登记集合(合计 29 条,全部显式):
|
||
PATHS_CONTRACTS —— 20 条:Web 契约端点,与四处事实源一一对应
|
||
① 实现: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_READONLY_DENY —— 9 条:CRUD 只读拒绝端点(QC #2 方案 A)。
|
||
json/*.json 三张表(pbl_game_definition / pbl_capability_registry /
|
||
pbl_compiler_version)都是编译产物或需经专用契约写入的注册表,
|
||
按 crud-definition-spec 要求 editable 必须齐备 new_/update_/delete_data_url,
|
||
否则 xls2ui 回退生成默认 add_/update_/delete_<table>.dspy —— 等于绕过
|
||
gd_rules_writer / pbl_capability_register / pbl_compiler_version_register
|
||
唯一写入口,rules_hash(29.6 确定性指纹)可被手工改写。
|
||
故为 3 张表 × 3 个写操作各建一个显式返回 Error 的 .dspy(共 9 个),
|
||
它们不是 ServerEnv 契约(不进 CONTRACTS/__init__.py),只需 RBAC 登记。
|
||
|
||
关于 CRUD 生成目录(/pbl_compiler/<alias> 及 index.ui/get_/add_/update_/delete_ 五条):
|
||
本轮 json/ 三个定义均为只读列表(无新增/编辑表单入口),wwwroot/ 下当前无 CRUD 生成子目录。
|
||
build.sh 重跑 xls2ui 后若生成 wwwroot/<alias>/ 目录,其 index.ui 与 get_<alias>.dspy
|
||
必须按 scripts/audit_rbac_parity.py 的 CRUD 登记面检查逐条补登(禁通配);
|
||
在此之前不登记不存在的路径——幽灵登记会污染 permission 表并被审计判 FAIL。
|
||
|
||
自证(交付前实跑 scripts/audit_rbac_parity.py):
|
||
ls wwwroot/api/*.dspy | wc -l → 29
|
||
PATHS 中 /api/ 条目数 → 29(契约 20 + 只读拒绝 9)
|
||
集合差为空(无漏登记、无幽灵登记)
|
||
"""
|
||
import os
|
||
import subprocess
|
||
import sys
|
||
|
||
MODULE = 'pbl_compiler'
|
||
|
||
# ── ① Web 契约端点(20 条,与 init.py CONTRACTS 一一对应,无通配符)──────────
|
||
PATHS_CONTRACTS = [
|
||
# —— 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)——
|
||
('/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'),
|
||
]
|
||
|
||
# ── ② CRUD 只读拒绝端点(9 条,非契约,仅封死 xls2ui 默认写入口,QC #2 方案 A)──
|
||
PATHS_READONLY_DENY = [
|
||
# pbl_game_definition:编译产物表,唯一写入口 gd_rules_writer.write_gd_rules()
|
||
('/pbl_compiler/api/pbl_game_definition_create.dspy', 'logined'),
|
||
('/pbl_compiler/api/pbl_game_definition_update.dspy', 'logined'),
|
||
('/pbl_compiler/api/pbl_game_definition_delete.dspy', 'logined'),
|
||
# pbl_capability_registry:唯一写入口契约 pbl_capability_register
|
||
('/pbl_compiler/api/pbl_capability_registry_create.dspy', 'logined'),
|
||
('/pbl_compiler/api/pbl_capability_registry_update.dspy', 'logined'),
|
||
('/pbl_compiler/api/pbl_capability_registry_delete.dspy', 'logined'),
|
||
# pbl_compiler_version:唯一写入口契约 pbl_compiler_version_register
|
||
('/pbl_compiler/api/pbl_compiler_version_create.dspy', 'logined'),
|
||
('/pbl_compiler/api/pbl_compiler_version_update.dspy', 'logined'),
|
||
('/pbl_compiler/api/pbl_compiler_version_delete.dspy', 'logined'),
|
||
]
|
||
|
||
# 完整登记面(audit_rbac_parity.py 以本变量为事实源)
|
||
PATHS = PATHS_CONTRACTS + PATHS_READONLY_DENY
|
||
|
||
|
||
def registered_api_paths():
|
||
"""返回 PATHS 中登记的 /api/ 端点文件名集合(供 parity 审计复用)。"""
|
||
return {p.rsplit('/', 1)[-1] for p, _role in PATHS if '/api/' in p}
|
||
|
||
|
||
def registered_contract_paths():
|
||
"""返回 Web 契约端点文件名集合(与 init.py CONTRACTS 比对的口径)。"""
|
||
return {p.rsplit('/', 1)[-1] for p, _role in PATHS_CONTRACTS if '/api/' in p}
|
||
|
||
|
||
def registered_readonly_deny_paths():
|
||
"""返回只读拒绝端点文件名集合(非契约,QC #2 方案 A)。"""
|
||
return {p.rsplit('/', 1)[-1] for p, _role in PATHS_READONLY_DENY
|
||
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 (contracts=%d, readonly_deny=%d) ok=%d pending=%d'
|
||
% (MODULE, len(PATHS), len(PATHS_CONTRACTS),
|
||
len(PATHS_READONLY_DENY), 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)
|