13 KiB
| name | description |
|---|---|
| pbl_compiler | PBL Compiler v1(确定性编译 + Game Definition,M3a/M3b)——蓝图版本快照 → GD 10 顶层键,29.6 确定性(sort_keys/零空白/strip_volatile/sha256/registry_hash),UNIQUE(tenant_id, content_fingerprint) 幂等落库,审批+质量双门禁 fail-closed。改本模块前先读「铁律」与「陷阱」两节。 |
pbl_compiler 模块技能
本文档是
README.md的浓缩版(面向 agent)。README 为事实源,两者必须双向同步(同步规则见 README §9)。 磁盘实测:15 个.dspy契约、4 张表、api.py:__all__35 项、自检pass=37 fail=0(exit=0)。
定位
PBL 产线编译层:把 pbl_blueprint 的蓝图版本快照(含 7 类子对象)编译为运行时可直接消费的
Game Definition(GD),保证「同输入必得同输出」(需求 29.6 / US-11 / F-CP-03)。
纯确定性、零 LLM(manifest.llmUsed=False、deterministic=True)。
不做:蓝图 CRUD(pbl_blueprint)、14 维校验实现(pbl_validation,只消费其 quality_state)、
Agent 推理(pbl_agent_runtime)、前端渲染(pbl_scense_ext/scense)、证据采集(pbl_evidence)。
挂载
from pbl_compiler.init import load_pbl_compiler # 应用 apps/pbls/app/pbls.py 的 init() 中按序调用
load_pbl_compiler() # 注册 15 契约 + 13 内部函数到 ServerEnv
本模块是 Python 包(业务模块):无 app.py、无端口、无 Dockerfile、无模块级 build.sh——
安装步骤由宿主应用 apps/pbls/build.sh 承担(pip install → sql/pbl_compiler.sql 建表 → wwwroot 软链 →
scripts/load_path.py RBAC → py_compile + scripts/test_m3a_selfcheck.py 门禁)。
数据表(4 张,models/*.json 四段式 + sql/pbl_compiler.sql)
所有表首列 tenant_id,所有 SQL 强制带 tenant_id;缺失租户上下文 fail-closed 抛错,无「默认租户」兜底。
| 表 | 用途 | 关键约束 |
|---|---|---|
pbl_game_definition |
GD 编译产物(append-only):content_fingerprint/definition_json/compiler_version_id/world_id/scene_id/entity_count/event_count/quality_state/duration_ms |
UNIQUE uk_gd_fp (tenant_id, content_fingerprint) ← 确定性幂等的 DB 级保证;idx_gd_bp (tenant_id, blueprint_id) |
pbl_compiler_version |
编译器版本谱系:code/semver/rules_hash/entrypoint/enabled/notes |
UNIQUE uk_cv_code (tenant_id, code) |
pbl_capability_registry |
能力注册表:capability_key/category/args_schema_json/permission_required/is_enabled/version_no |
UNIQUE uk_cap_key (tenant_id, capability_key, version_no) |
pbl_compile_task |
编译任务与审计载体:task_no(确定性派生)/status/approval_id/game_def_id/content_fingerprint/error_code/error_msg/triggered_by/duration_ms |
UNIQUE uk_ct_task_no (tenant_id, task_no);idx_ct_blueprint、idx_ct_status |
CRUD 定义(json/):compiler_pbl_game_definition.json、compiler_pbl_compiler_version.json、
compiler_pbl_capability_registry.json(pbl_compile_task 为纯后端任务表,无 CRUD 页面)。
契约接口(15 个,路径 /pbl_compiler/api/<name>.dspy)
pbl_compiler_compile(POST 编译主流程) · pbl_compiler_preview(POST 试编译不落库) ·
pbl_compiler_compare(POST 对比) · pbl_compiler_verify_determinism(POST,US-11/F-CP-03 验收唯一入口) ·
pbl_compiler_task_get · pbl_compiler_task_list · pbl_game_definition_get ·
pbl_game_definition_get_by_blueprint · pbl_compiler_version_register · pbl_compiler_version_save ·
pbl_compiler_version_get · pbl_compiler_version_list · pbl_compiler_version_diff ·
pbl_capability_list · pbl_capability_register
内部函数(非 .dspy,经 ServerEnv 供模块内/其它模块调用):compile / compile_blueprint /
get_compile_task / list_compile_tasks / get_game_definition / get_game_definition_by_blueprint /
verify_determinism / register_compiler_version / get_compiler_version / list_compiler_versions /
diff_compiler_versions / is_approved / ladder_at_or_above;
常量:COMPILER_VERSION / RULESET_VERSION / RULES_HASH_SEED / GD_SCHEMA / GD_TOP_KEYS /
QUALITY_LADDER / COMPILE_GATE_MIN_STATE。
代码结构(磁盘实测)
pbl_compiler/{__init__.py, init.py, api.py, canonical.py, gd_builder.py}
models/{pbl_game_definition,pbl_compiler_version,pbl_capability_registry,pbl_compile_task}.json
json/compiler_pbl_*.json sql/pbl_compiler.sql wwwroot/{index.ui, api/*.dspy ×15}
scripts/{load_path.py, test_m3a_selfcheck.py} skill/SKILL.md pyproject.toml README.md
磁盘无 compiler.py / versioning.py / 模块级 build.sh / app.py——编译主流程在 api.py:compile_blueprint(),
GD 构建在 gd_builder.py。文档禁止描述磁盘不存在的文件。
确定性规则(canonical.py,改则历史指纹全变)
canonical_json:json.dumps(sort_keys=True, separators=(',',':'), ensure_ascii=False, allow_nan=False)—— 键排序 + 零空白 + 中文不转义 + 拒绝 NaN/Infinity。- 浮点定点化(固定精度、去尾零);整数不带小数点;
1与1.0稳定且互不混淆。 strip_volatile:递归(含list[dict])剥离id/created_at/updated_at/duration_ms/task_no/started_at/finished_at/created_by/trace_id/nonce等,只剥不改业务值。- 数组稳定化:语义无序集合按元素规范串排序;语义有序(场景步骤、事件时间线)保持原序,由
gd_builder显式决定。 fingerprint = sha256(canonical_bytes(strip_volatile(obj)))→ 64 位小写 hex;short_hash(text, n)定长前缀哈希。registry_hash(capabilities):归一为{capability_key, version_no, args_schema_hash, permission_required, is_enabled}五元组 → 排序 → 规范化 → sha256。入参兼容原始注册表行(args_schema_json)与 GD 派生行 (args_schema_hash),两路同值(口径铁律,见陷阱 P1)。stable_id(blueprint_id, version_no, kind, seq)={bp}:{ver}:{kind}:{seq:05d}——GD 内一切 id 由输入派生,禁 uuid/random。- 禁项:指纹路径出现
time.time()/datetime.now()/uuid/random/os.getpid()/dict 迭代序依赖/无ORDER BY的 SQL 行序依赖。
GD 10 顶层键(gd_builder.GD_TOP_KEYS,缺任一即 PBL_E_COMPILE)
manifest, pbl, world, scenes, entities, events, states, capabilities, assessment, assets
(manifest 含 schema=pbl.game_definition.v1/gdTopKeys/blueprintId/blueprintVersion/compilerVersion/
rulesetVersion/rulesHash/registryHash/fingerprint/deterministic=True/llmUsed=False/counts)
编译主流程 compile_blueprint() ①-⑧(fail-closed)
① 审批门禁 is_approved()(未审批 → PBL_E_STATE_ILLEGAL/403)+ 质量门禁(quality_state 未达
COMPILE_GATE_MIN_STATE → 拒绝)→ ② 建 pbl_compile_task(status=running, approval_id, task_no 确定性派生) →
③ get_version() 取蓝图版本快照(输入锁定)→ ④ build_game_definition(snapshot, ctx, caps) 纯函数生成 GD 10 键(零 LLM)→
⑤ canonical_json+strip_volatile+sha256 算 content_fingerprint → ⑥ 落库 append-only:先按
(tenant_id, content_fingerprint) SELECT,命中即复用(idempotent=true),未命中 INSERT(uk_gd_fp 兜并发)→
⑦ 任务 status=success + 回填 game_def_id/content_fingerprint/duration_ms/finished_at → ⑧ write_audit。
返回 {task_no, status, game_def_id, fingerprint, idempotent, ...}。
失败必做:error_code(PBL_E_*)/error_msg 回写任务 + 落审计 + 不产生 GD 行 + 返回结构化错误(非 500 裸异常)。
自检(scripts/test_m3a_selfcheck.py)
cd modules/pbl_compiler && python3 scripts/test_m3a_selfcheck.py ; echo "exit=$?"
退出码 0 = 6 组全过,1 = 有 FAIL,2 = 被测模块加载失败。纯函数级(importlib 按路径加载 canonical.py/
gd_builder.py),不连库、不依赖 ServerEnv/ahserver,CI 可直接跑。6 组断言:
G1 canonical 键序无关/指纹形态 · G2 零空白/ensure_ascii=False/浮点定点/NaN 拒绝 ·
G3 strip_volatile 易变字段不参与指纹(含递归 list[dict]、算法口径一致) ·
G4 GD 10 顶层键 + 指纹一致 + manifest.registryHash == registry_hash(GD capabilities.items)(统一口径)+ 派生不丢语义 ·
G5 同输入重编译指纹全等(3 次)/ctx 易变字段不影响/入库顺序颠倒不影响/业务变更必变 ·
G6 registry_hash 语义(顺序无关、增删能力/升版/改 schema 必变、易变字段不变、空表确定哈希)。
本轮实测:--- summary: pass=37 fail=0 ---,exit=0。
铁律
- 库名禁硬编码:一律
ServerEnv().get_module_dbname('pbl_compiler')(.py)/get_module_dbname('pbl_compiler')(.dspy 全局)。 模块内不存在DB = '...'/DBNAME = '...'。自查:grep -rn "^DB *=\|DBNAME *= *'" pbl_compiler/ --include='*.py'应无输出。 - 三处同步 + RBAC:新增/删除契约必须同改 ①
api.py(+__all__) ②__init__.py导出 ③init.pyenv.x = x④scripts/load_path.pyPATHS(禁通配符%/*,逐条显式)。漏一处 → 路由不可达 /NameError。 - tenant_id 强制打头:所有读写 SQL 带
tenant_id(pbl_common.api.tenant_id()),缺失即 fail-closed。 - 指纹路径纯函数:禁时间/随机/uuid/行序依赖;GD 内 id 用
stable_id()派生。 - 文档与磁盘一致:README/SKILL 的端点表、目录树、表字段必须与磁盘实测一致;声称 write_file 的文件必须真实落盘 (幽灵文件按造假直接退回)。
- README ↔ SKILL 同一次提交内同步(表名/约束、15 端点、canonical 规则、GD 10 键、陷阱 P1-P7)。
陷阱
- P1 registry_hash 口径不一致(历史两轮退回根因):
gd_builder.build_capabilities()用补齐派生字段后的known.values()算 hash,旧自检对原始注册表行算 → 永不相等 → G4 FAIL、exit=1、安装门禁自证失败。 统一以 GD 内capabilities.items为唯一口径,canonical.registry_hash()兼容两路入参同值。 - P2 端点表与磁盘不符:曾列出磁盘不存在的
pbl_compiler_status.dspy/pbl_game_definition_list.dspy/pbl_capability_registry_list.dspy,漏列磁盘已有的pbl_capability_list/pbl_capability_register/pbl_compiler_version_save。以ls wwwroot/api/*.dspy为准。 - P3 幽灵文件:声称写入的
scripts/test_m3a_selfcheck.py曾磁盘不存在 → 按造假退回。 - P4 硬编码
DB = 'pbl'→ 换库/多租户即断。 - P5 格式串占位符与参数个数不匹配 →
register()TypeError: not enough arguments for format string, RBAC 收口失败('[%s] ... total=%d ok=%d pending=%d' % (MODULE, len(PATHS), done, len(missing)))。 离线环境register()返回False+ 打印PENDING <role> <path>属预期 fail-safe,判据是不抛异常。 - P6 三处同步漏一处 →
.dspy500NameError: name 'xxx' is not defined。 - P7 volatile 混入指纹 → 每次编译指纹不同,
uk_gd_fp幂等失效;新增易变字段必须同步进VOLATILE_KEYS。 - P8 文档描述磁盘不存在的代码文件(
compiler.py/versioning.py/模块build.sh)。 - P9 指纹路径引入不确定性(NaN、无
ORDER BY、dict 迭代序、datetime.now())。 - P10
return写在async with db.sqlorContext(...)内 → 静默None(return data type error, <class 'NoneType'>); 块内收集、退出块后再return。sqlor 只有C/U/D/R/I/sqlExe(sor.I(ns)只 1 个参数); 查询走pbl_common.api的q_all/q_one(已适配)。 - 其它:
.dspy内禁 import(全局已预载)、禁ServerEnv()取 per-request 用户(用request._run_ns)、py_compile对.dspy无效(用 grep 审计)、trigger是 MySQL 关键字(本模块用triggered_by规避)、 手工建表必须COLLATE utf8mb4_unicode_ci(否则Illegal mix of collations)。
依赖
上游:pbl_common(租户/DB 适配/错误码/审计/CRUD 工厂)、pbl_blueprint(蓝图 + 版本快照 + 审批)、
pbl_validation(quality_state 门禁)、pbl_appcodes(枚举)、world/scene(GD 落库目标)、
基础模块 ahserver/sqlor/appbase/rbac(只读,禁改)。
下游:pbl_agent_runtime(M4)、pbl_evidence(M5)、pbl_assessment(M6)、pbl_kdb_ext(M7)、
pbl_scense_ext/scense(M9)、pbl_runtime_ext(M11)。
开发顺序:pbl_common → pbl_appcodes → pbl_blueprint(M1) → pbl_validation(M2) →
pbl_compiler(M3) → pbl_agent_runtime(M4) → pbl_evidence(M5) → pbl_assessment(M6) →
pbl_kdb_ext(M7) → pbl_scense_ext(M9) → pbl_runtime_ext(M11)。