151 lines
13 KiB
Markdown
151 lines
13 KiB
Markdown
---
|
||
name: pbl_compiler
|
||
description: 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`)。
|
||
|
||
## 挂载
|
||
```python
|
||
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`)
|
||
```bash
|
||
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`。**
|
||
|
||
## 铁律
|
||
1. **库名禁硬编码**:一律 `ServerEnv().get_module_dbname('pbl_compiler')`(.py)/ `get_module_dbname('pbl_compiler')`(.dspy 全局)。
|
||
模块内**不存在** `DB = '...'` / `DBNAME = '...'`。自查:`grep -rn "^DB *=\|DBNAME *= *'" pbl_compiler/ --include='*.py'` 应无输出。
|
||
2. **三处同步 + RBAC**:新增/删除契约必须同改 ① `api.py`(+`__all__`) ② `__init__.py` 导出 ③ `init.py` `env.x = x`
|
||
④ `scripts/load_path.py` `PATHS`(**禁通配符 `%`/`*`**,逐条显式)。漏一处 → 路由不可达 / `NameError`。
|
||
3. **tenant_id 强制打头**:所有读写 SQL 带 `tenant_id`(`pbl_common.api.tenant_id()`),缺失即 fail-closed。
|
||
4. **指纹路径纯函数**:禁时间/随机/uuid/行序依赖;GD 内 id 用 `stable_id()` 派生。
|
||
5. **文档与磁盘一致**:README/SKILL 的端点表、目录树、表字段必须与磁盘实测一致;**声称 write_file 的文件必须真实落盘**
|
||
(幽灵文件按造假直接退回)。
|
||
6. **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 三处同步漏一处** → `.dspy` 500 `NameError: 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)。
|