deliver: 交付收口(引擎代为提交)

This commit is contained in:
agent.develop 2026-09-22 17:37:36 +08:00
parent a17330c395
commit c787b2581c

View File

@ -2,92 +2,186 @@
# -*- coding: utf-8 -*-
"""pbl_appcodes 模块 RBAC / 路径声明脚本(per-module,中央层 apps/pbls 聚合)。
═══════════════════════════════════════════════════════════════════════════════
结论:本模块 **零 Web 可访问路径**(NO_WEB_PATHS = True,显式声明)
═══════════════════════════════════════════════════════════════════════════════
pbl_appcodes 只做枚举编码注入(appcodes / appcodes_kv 种子,幂等,零自有表),
只交付 Python 库函数(``pbl_appcodes/*.py``)与 init 种子数据,
**不提供任何 .dspy / .ui / .js / .css 文件**,因此 RBAC permission 表零登记项。
为什么写成显式声明而不是留空(M11b-2c-T3 QC 退回意见 #2 的连带治理):
中央聚合器把「有 scripts/load_path.py 但一条路径都没聚合到」判为聚合失败
(rc=1),除非模块显式声明 ``NO_WEB_PATHS``。「无路径」是事实断言,须可 grep。
用法:
python3 load_path.py # 打印声明(零路径,无需登记)
python3 load_path.py --check # 自检:无通配符 + 与磁盘 wwwroot 一致性
python3 load_path.py # 找到 Sage 根时逐条 set_role_perm 登记;找不到则打印跳过说明
python3 load_path.py --check # 离线自检:无通配符 + 声明与磁盘 wwwroot 一致
═══════════════════════════════════════════════════════════════════════════════
里程碑 RBAC 声明台账(每个里程碑必须显式登记一行,禁止留空/待补)
═══════════════════════════════════════════════════════════════════════════════
M11b-2(T3,2026-09-22):
结论 = 本里程碑新增路径 0 条(M11b-2 只交付库函数与表定义,见 world_sync 侧台账)。
顺带闭环(QC 退回意见 #2「同类缺陷未闭环」):
本模块此前 scripts/load_path.py 只有一行注释、**没有任何 PATHS 常量**,中央聚合器
把它当「empty (skipped)」静默跳过;而磁盘 wwwroot 下真实存在 3 个 Web 文件
(index.ui + api/pbl_appcodes_inject.dspy + api/pbl_appcodes_list.dspy),
即「文件在、路由在、RBAC 不在」= 点进去一律 403 的同类缺口。
本次补齐**逐条显式**登记,并把「声明与磁盘一致」做进 --check 门禁。
铁律 = 禁通配符(% / *),所有路径逐条显式;新增路径须在本台账追加一行,
并确认模块名在中央 apps/pbls/scripts/load_path.py 的 MODULES 中(聚合式双写)。
═══════════════════════════════════════════════════════════════════════════════
"""
import os
import sys
MODULE = 'pbl_appcodes'
MODULE_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
WWWROOT = os.path.join(MODULE_ROOT, 'wwwroot')
NO_WEB_PATHS = True
DECLARATION = (
'pbl_appcodes 仅提供枚举编码注入(库函数 + init 种子数据,零自有表),'
'不新增任何 .dspy/.ui/.js/.css 可访问路径,故 RBAC 零登记项;'
'禁通配符(%/*),后续若新增 Web 路径须按 any/logined/owner.* 分层逐条登记。'
)
# ── any 层:无需登录即可访问的公共资源(本模块无)────────────────────────────
PATHS_ANY = []
PATHS_LOGINED = []
# ── logined 层:模块首页 + api/*.dspy(与磁盘 wwwroot 一一对应,逐条显式)────
PATHS_LOGINED = [
'/{m}'.format(m=MODULE),
'/{m}/index.ui'.format(m=MODULE),
'/{m}/api/pbl_appcodes_inject.dspy'.format(m=MODULE),
'/{m}/api/pbl_appcodes_list.dspy'.format(m=MODULE),
]
# ── owner.* 层:当前无受限路径,显式声明为空(避免「漏登记」被误判为「待补」)─
PATHS_OWNER_SUPERUSER = []
PATHS_OWNER_OPERATOR = []
PATHS = []
# ── 兼容扁平写法(中央聚合器两种写法都支持)────────────────────────────────
PATHS = [(p, 'logined') for p in PATHS_LOGINED]
# 本模块**有** Web 路径,故不声明 NO_WEB_PATHS(该常量仅用于纯库函数模块)
NO_WEB_PATHS = False
# ── 里程碑声明台账(供 --check 回显,也是 QC 可 grep 的硬证据)────────────────
MILESTONE_DECLARATIONS = [
{
'milestone': 'M11b-2',
'new_paths': 0,
'declaration': (
'M11b-2 本里程碑不新增 .dspy/.ui/.js/.css 路径,仅提供库函数'
'(world_sync / pbl_runtime_ext 的 *.py 与 models/*.json),'
'故无 RBAC 登记项;禁通配符(%/*),'
'如有路径须按 any/logined/owner.* 分层逐条登记。'
'(本模块存量 4 条路径于 T3 补齐登记,闭环「有文件无 RBAC」的 403 缺口。)'
),
},
]
FORBIDDEN_WILDCARDS = ('%', '*')
WEB_SUFFIXES = ('.dspy', '.ui', '.js', '.css')
def _wwwroot_files():
root = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
'wwwroot')
found = []
for base, _dirs, files in os.walk(root):
def _tiers():
return (('any', PATHS_ANY),
('logined', PATHS_LOGINED),
('owner.superuser', PATHS_OWNER_SUPERUSER),
('owner.operator', PATHS_OWNER_OPERATOR))
def _assert_no_wildcard():
"""通配符硬门禁:任何一层路径出现 % 或 * 直接判失败(非静默)。"""
bad = [t + ':' + p for t, ps in _tiers() for p in ps
if any(w in p for w in FORBIDDEN_WILDCARDS)]
if bad:
print('[%s] FAIL 路径声明含禁用通配符: %s' % (MODULE, bad))
return 1
print('[%s] OK 通配符检查通过(PATHS_ANY/LOGINED/OWNER.* 均为逐条显式路径,无 %% / *)'
% MODULE)
return 0
def _declared_paths():
out = set()
for _t, ps in _tiers():
out.update(ps)
return out
def _disk_web_paths():
"""磁盘 wwwroot 下真实 Web 文件对应的 URL 路径集合(含模块根目录本身)。"""
found = set()
if os.path.isdir(WWWROOT):
for base, _dirs, files in os.walk(WWWROOT):
for fn in files:
if fn.endswith(('.dspy', '.ui', '.js', '.css')):
found.append(os.path.relpath(os.path.join(base, fn), root))
return sorted(found)
if fn.endswith(WEB_SUFFIXES):
rel = os.path.relpath(os.path.join(base, fn), WWWROOT)
found.add('/' + MODULE + '/' + rel.replace(os.sep, '/'))
if found:
found.add('/' + MODULE) # 目录访问(走 index.ui)
return found
def _consistency():
"""声明 ↔ 磁盘 一致性门禁:缺登记或多登记都判 FAIL。"""
declared, on_disk = _declared_paths(), _disk_web_paths()
rc = 0
missing = sorted(on_disk - declared)
extra = sorted(declared - on_disk)
if missing:
print('[%s] FAIL 磁盘存在 Web 文件但未登记 RBAC(点击即 403): %s'
% (MODULE, missing))
rc = 1
else:
print('[%s] OK 磁盘 %d 个 Web 路径已全部登记,无遗漏' % (MODULE, len(on_disk)))
if extra:
print('[%s] FAIL 登记了磁盘上不存在的多余路径: %s' % (MODULE, extra))
rc = 1
return rc
def check():
print('=' * 78)
print('[%s] RBAC 路径声明自检 (--check) module=%s' % (MODULE, MODULE))
print('=' * 78)
print(' NO_WEB_PATHS = %s' % NO_WEB_PATHS)
print(' 声明: %s' % DECLARATION)
rc = 0
tiers = (('any', PATHS_ANY), ('logined', PATHS_LOGINED),
('owner.superuser', PATHS_OWNER_SUPERUSER),
('owner.operator', PATHS_OWNER_OPERATOR))
bad = [t + ':' + p for t, ps in tiers for p in ps
if any(w in p for w in FORBIDDEN_WILDCARDS)]
if bad:
print(' FAIL 含禁用通配符: %s' % bad)
rc = 1
else:
print(' OK 通配符检查通过(无任何路径,自然无通配符)')
total = sum(len(ps) for _t, ps in tiers)
if total:
print(' FAIL NO_WEB_PATHS=True 但声明了 %d 条路径(自相矛盾)' % total)
rc = 1
else:
print(' OK 声明路径合计 0 条,与 NO_WEB_PATHS=True 一致')
on_disk = _wwwroot_files()
if on_disk:
print(' FAIL 磁盘 wwwroot 存在 %d 个 Web 文件却未登记: %s'
% (len(on_disk), on_disk))
rc = 1
else:
print(' OK 磁盘 wwwroot 无 .dspy/.ui/.js/.css 文件,与零路径声明一致')
rc = _assert_no_wildcard()
print('\n[声明台账]')
for d in MILESTONE_DECLARATIONS:
print(' - %s: 新增路径 %d 条 -> %s'
% (d['milestone'], d['new_paths'], d['declaration']))
print('\n[路径登记(逐条显式)]')
for tier, paths in _tiers():
print(' tier=%s count=%d' % (tier, len(paths)))
for p in paths:
print(' %s' % p)
rc |= _consistency()
total = len(_declared_paths())
print('\n[汇总] 登记路径合计 %d 条;M11b-2 新增 0 条(本次为存量补齐登记)' % total)
print('[%s] --check 结论: %s' % (MODULE, 'PASS' if rc == 0 else 'FAIL'))
return rc
def _find_sage_root():
for c in (os.path.expanduser('~/repos/sage'), os.path.expanduser('~/sage'),
'/d/ymq/repos/sage'):
if os.path.isdir(os.path.join(c, 'wwwroot')) and \
os.path.isdir(os.path.join(c, 'py3', 'bin')):
return c
return None
def main():
if '--check' in sys.argv[1:]:
return check()
print('[%s] %s' % (MODULE, DECLARATION))
print('[%s] 无路径需要登记(NO_WEB_PATHS=True);可执行 `--check` 离线复核。' % MODULE)
root = _find_sage_root()
if not root:
print('[%s] 未找到 Sage 根目录,跳过(中央 apps/pbls/scripts/load_path.py 会聚合登记)'
% MODULE)
print('[%s] 本里程碑 M11b-2 无新路径登记;本次补齐的是存量 %d 条路径。'
% (MODULE, len(_declared_paths())))
print('[%s] 可执行 `python3 load_path.py --check` 离线校验声明合规性与磁盘一致性。'
% MODULE)
return 0
sys.path.insert(0, root)
try:
from set_role_perm import set_role_perm
except ImportError:
print('[%s] set_role_perm 不可用,请改用中央 apps/pbls/scripts/load_path.py' % MODULE)
return 0
rc = _assert_no_wildcard()
if rc != 0:
return rc
for tier, paths in _tiers():
for p in paths:
set_role_perm(p, tier)
print('[%s] 已注册 %d 条 RBAC 路径(M11b-2 新增 0 条,全部为存量补齐)'
% (MODULE, len(_declared_paths())))
return 0