559 lines
42 KiB
Markdown
559 lines
42 KiB
Markdown
# pbl_compiler —— PBL Compiler v1(确定性编译 + Game Definition)
|
||
|
||
> 模块仓库:`modules/pbl_compiler/` | 里程碑:**M3a**(编译核心与确定性产物)+ M3b(版本登记/对比)
|
||
> 挂载方式:宿主应用 `apps/pbls/app/pbls.py` 的 `init()` 中按序调用 `load_pbl_compiler()`。
|
||
> 本模块是 **Python 包(业务模块)**:**无 `app.py`、无独立端口、无 Dockerfile、无模块级 `build.sh`**——
|
||
> 它只因宿主应用调用 `load_pbl_compiler()` 而运行(module-development-spec 铁律)。
|
||
> 本文档为磁盘真实正文(非占位、非元描述、非自我指涉)。**文中所有目录树 / 端点表 / 表字段 / 断言组均以
|
||
> `modules/pbl_compiler/` 磁盘实测为准**,不含磁盘不存在的文件(历史退回项,见 §8 P2/P3/P8)。
|
||
> 与 `skill/SKILL.md` 双向同步,同步规则见 §9。
|
||
|
||
---
|
||
|
||
## 1. 模块定位
|
||
|
||
`pbl_compiler` 是 PBL 产线的**编译层**:把 `pbl_blueprint` 的蓝图版本快照(含 7 类子对象)编译为运行时可直接消费的
|
||
**Game Definition(GD)**,并保证「同输入必得同输出」的**确定性(determinism,需求 29.6 / US-11)**。
|
||
M3a 承担 6 项核心职责:
|
||
|
||
1. **确定性规范化(canonicalization)**——`pbl_compiler/canonical.py`
|
||
任意嵌套 JSON → 唯一规范串 → `sha256` 指纹。规则固定:`sort_keys=True`、零空白分隔符 `(',', ':')`、
|
||
`ensure_ascii=False`、UTF-8 编码、浮点定点化、**拒绝 NaN/Infinity**;编译前统一 `strip_volatile()`
|
||
剥离易变字段(`created_at`/`updated_at`/`duration_ms`/`task_no`/`id` 等),使指纹只反映**业务内容**。
|
||
|
||
2. **Game Definition 构建**——`pbl_compiler/gd_builder.py`
|
||
`build_game_definition(snapshot, ctx, registry_rows)` 是**纯函数**(零 LLM、零 IO、零随机),
|
||
产出**固定 10 个顶层键**的 GD 文档(键清单见 §7.2),并返回 64 位 `content_fingerprint`。
|
||
键序、缺省值、数组排序全部显式决定,禁止依赖 dict 插入顺序或数据库返回行序。
|
||
|
||
3. **能力注册表指纹(registry_hash)**
|
||
GD 编译时把所用能力注册表快照纳入 `manifest.registryHash` 并参与 `content_fingerprint`:
|
||
能力集变更(增删 / `version_no` 升级 / `args_schema` 变更)→ `registry_hash` 变更 → 指纹变更,
|
||
避免「蓝图没改但语义已改」的假幂等。**口径铁律见 §8 P1**(历史两轮退回根因位,本轮已统一并加断言守护)。
|
||
|
||
4. **编译任务与审批门禁**——`api.py: compile_blueprint()` / 契约 `pbl_compiler_compile`
|
||
走 ①-⑧ 全流程(§7.1),**fail-closed**:未审批、质量状态未达门禁、蓝图/版本缺失一律拒绝编译,
|
||
错误码 `PBL_E_*` 回写任务并落审计,绝不产出半成品 GD。
|
||
|
||
5. **指纹幂等落库**
|
||
`pbl_game_definition` 上 `UNIQUE KEY uk_gd_fp (tenant_id, content_fingerprint)`;
|
||
同租户同指纹重编译**命中即复用**(返回既有 `game_def_id` + `idempotent=true`),不产生重复行、不产生新 id。
|
||
|
||
6. **编译器版本与对比(M3b)**
|
||
`pbl_compiler_version` 锁定编译器版本(`code`/`semver`/`rules_hash`/`entrypoint`),
|
||
`version_register / version_save / version_get / version_list / version_diff / compare` 提供登记、保存、取回、
|
||
谱系列表与结构化差异对比;`pbl_compiler_verify_determinism` 是 **US-11 / F-CP-03「同输入重编译指纹相等」的验收唯一入口**。
|
||
|
||
**非目标(本模块不做)**:不做蓝图 CRUD(属 `pbl_blueprint`);不做 14 维校验规则实现(属 `pbl_validation`,
|
||
本模块只消费其 `quality_state` 结论作门禁);不做 Agent 推理与工具裁决(属 `pbl_agent_runtime`);
|
||
不做前端游戏渲染(属 `pbl_scense_ext` / `scense`);不做证据采集(属 `pbl_evidence`)。
|
||
|
||
---
|
||
|
||
## 2. 数据表(4 张:`models/*.json` 四段式定义 + `sql/pbl_compiler.sql` DDL)
|
||
|
||
所有表**首列 `tenant_id`**(强制打头),所有读写 SQL 必带 `tenant_id` 条件;缺失租户上下文由
|
||
`pbl_common.api.tenant_id()` 直接 fail-closed 抛错,**不做「默认租户」兜底**。
|
||
`created_at / updated_at / started_at / finished_at / duration_ms / task_no / id` 均为审计或易变字段,
|
||
**一律被 `strip_volatile` 排除,不参与指纹**(否则幂等彻底失效,见 §8 P7)。
|
||
|
||
### 2.1 `pbl_game_definition`(编译产物 GD,核心表)
|
||
| 字段 | 类型 | 用途 |
|
||
|---|---|---|
|
||
| tenant_id | str(32) NOT NULL | 租户隔离,**参与唯一约束** |
|
||
| id | int(20) PK AUTO_INCREMENT | GD 主键(**不参与指纹**) |
|
||
| blueprint_id | int(20) NOT NULL | 来源蓝图 |
|
||
| blueprint_version_no | int NOT NULL | 来源蓝图版本(输入锁定) |
|
||
| compiler_version_id | int(20) NOT NULL | 编译器版本(`pbl_compiler_version.id`,算法变更即换版,防跨版本假幂等) |
|
||
| content_fingerprint | str(64) NOT NULL | `sha256(canonical_json(strip_volatile(GD)))`,US-11 比对锚点 |
|
||
| definition_json | text(LONGTEXT) | GD 完整正文(10 顶层键,**规范串**落库) |
|
||
| world_id / scene_id | int(20) | 落库到 `world` / `scene` 的关联 id(供 M9 渲染) |
|
||
| entity_count / event_count | int | GD 规模计数(`manifest.counts` 同源) |
|
||
| quality_state | str(64) NOT NULL | 编译时质量状态(来自 `pbl_validation` 5 级阶梯) |
|
||
| duration_ms | int | 编译耗时(**易变,不参与指纹**) |
|
||
| created_at / updated_at | datetime | 审计(应用层写入,**不参与指纹**) |
|
||
|
||
**关键约束:`UNIQUE KEY uk_gd_fp (tenant_id, content_fingerprint)`** —— 「确定性编译 + 幂等落库」的数据库级保证:
|
||
- 同租户、同内容 → 数据库层拒绝第二行 → 代码层先 `SELECT ... WHERE tenant_id AND content_fingerprint` 命中即复用
|
||
(`idempotent=true`),并发下再兜唯一冲突 → 回读既有行,实现并发安全幂等;
|
||
- 跨租户相同内容**允许**各存一份(租户隔离优先于全局去重);
|
||
- 表为 **append-only**:GD 不原地改写,重编译产生新指纹新行,旧行由 `pbl_compiler_version` 谱系追溯。
|
||
|
||
其余索引:`KEY idx_gd_bp (tenant_id, blueprint_id)`、`PRIMARY (id)`。
|
||
|
||
### 2.2 `pbl_compiler_version`(编译器版本谱系,29.6 确定性锚点)
|
||
| 字段 | 类型 | 用途 |
|
||
|---|---|---|
|
||
| tenant_id | str(32) NOT NULL | 租户隔离,**参与唯一约束** |
|
||
| id | int(20) PK | 版本记录 id |
|
||
| code | str(64) NOT NULL | 版本编码(如 `pblc-v1`) |
|
||
| semver | str(64) NOT NULL | 语义化版本 |
|
||
| rules_hash | str(32) NOT NULL | 规则集 SHA-256 前缀(`manifest.rulesHash` 来源) |
|
||
| entrypoint | str(64) NOT NULL | 编译入口函数(`compile_blueprint`) |
|
||
| enabled | bool NOT NULL | 是否启用(未启用版本不得用于新编译) |
|
||
| notes | text | 版本说明 / changelog |
|
||
| created_at / updated_at | datetime | 审计(不参与指纹) |
|
||
|
||
约束:`UNIQUE KEY uk_cv_code (tenant_id, code)`。**编译器版本参与 GD `manifest.compilerVersion`**:
|
||
编译算法或规则集一旦变更必须升版,否则新旧产物指纹不可比(假幂等)。
|
||
|
||
### 2.3 `pbl_capability_registry`(能力/组件注册表)
|
||
| 字段 | 类型 | 用途 |
|
||
|---|---|---|
|
||
| tenant_id | str(32) NOT NULL | 租户隔离,**参与唯一约束** |
|
||
| id | int(20) PK | 主键 |
|
||
| capability_key | str(64) NOT NULL | 能力键(如 `pbl.role.skill`、`pbl.item.consume`) |
|
||
| category | str(64) NOT NULL | 能力分类(role / item / rule / event / ui …) |
|
||
| args_schema_json | json(LONGTEXT) | 能力参数 JSON Schema(编译期参数校验 + 派生 `args_schema_hash`) |
|
||
| permission_required | str(64) NOT NULL | 所需权限(M4b fail-closed 工具裁决消费) |
|
||
| is_enabled | bool NOT NULL | 启用状态(`registry_hash` 计算入参之一) |
|
||
| version_no | int NOT NULL | 能力版本(升版 → `registry_hash` 变) |
|
||
| description | text | 说明 |
|
||
| created_at / updated_at | datetime | 审计(不参与指纹) |
|
||
|
||
约束:`UNIQUE KEY uk_cap_key (tenant_id, capability_key, version_no)`。
|
||
编译时对**能力集合**按 `capability_key` 排序 → 规范化(`capability_key`/`version_no`/`args_schema_hash`/
|
||
`permission_required`/`is_enabled` 五元组)→ `sha256` 得 `registry_hash`,写入 GD `manifest.registryHash`。
|
||
|
||
### 2.4 `pbl_compile_task`(编译任务与审计载体,F-CP-01 / US-10)
|
||
| 字段 | 类型 | 用途 |
|
||
|---|---|---|
|
||
| tenant_id | str(64) NOT NULL | 租户隔离,**参与唯一约束** |
|
||
| id | int(20) PK | 主键 |
|
||
| task_no | str(64) NOT NULL | 任务流水号(**确定性派生** `CT+blueprint_id+version+seq`,非 uuid) |
|
||
| blueprint_id / blueprint_version | int(20) / int(11) | 编译对象(版本须已审批) |
|
||
| compiler_version / ruleset_version | str(32) | 编译器与规则集版本(锁定) |
|
||
| status | str(32) | appcodes `pbl_compile_status`:`pending`/`running`/`success`/`failed` |
|
||
| approval_id | int(20) | 关联审批记录(**未审批拒绝**,F-CP-01) |
|
||
| game_def_id | int(20) | 产出的 GD id(success 时回填) |
|
||
| content_fingerprint | str(64) | 产物 SHA-256 指纹(success 时回填,US-11 比对锚点) |
|
||
| error_code / error_msg | str(64) / str(2048) | 失败错误码(`PBL_E_*`)与原因(fail-closed 可追溯) |
|
||
| triggered_by | str(64) | 触发人(审计) |
|
||
| duration_ms | int(11) | 耗时(**易变,不参与指纹**) |
|
||
| started_at / finished_at / created_at / updated_at | datetime | 审计(不参与指纹) |
|
||
|
||
约束/索引:`UNIQUE KEY uk_ct_task_no (tenant_id, task_no)`、`KEY idx_ct_blueprint (tenant_id, blueprint_id, blueprint_version)`、
|
||
`KEY idx_ct_status (tenant_id, status)`、`PRIMARY (id)`。
|
||
|
||
> **表定义规范**:遵循 database-table-definition-spec 四段式(`summary` / `fields` / `indexes` / `codes`),
|
||
> 抽象类型 + 整数 `length`/`dec`,`indexes[].fields` 为数组。CRUD 定义在 `json/`:
|
||
> `compiler_pbl_game_definition.json`、`compiler_pbl_compiler_version.json`、`compiler_pbl_capability_registry.json`
|
||
> (`pbl_compile_task` 为纯后端任务表,无 CRUD 页面,只经契约端点读写)。
|
||
|
||
---
|
||
|
||
## 3. 契约端点清单(15 个 `.dspy`,与磁盘逐行对应)
|
||
|
||
契约文件位于 `wwwroot/api/*.dspy`,**磁盘实测 `ls wwwroot/api/*.dspy | wc -l` = 15**,下表即磁盘清单
|
||
(无虚构项、无遗漏项;实现函数名 = 文件名去后缀,全部列入 `api.py:__all__`):
|
||
|
||
| # | 端点文件(`wwwroot/api/`) | api.py 实现函数 | 方法 | 说明 |
|
||
|---|---|---|---|---|
|
||
| 1 | `pbl_compiler_compile.dspy` | `pbl_compiler_compile` | POST | 触发编译(§7.1 ①-⑧ 全流程,落库 + 审计 + 幂等命中) |
|
||
| 2 | `pbl_compiler_preview.dspy` | `pbl_compiler_preview` | POST | 试编译:只返回 GD + 指纹,**不落库、不建任务** |
|
||
| 3 | `pbl_compiler_compare.dspy` | `pbl_compiler_compare` | POST | 两蓝图版本 / 两 GD 结构化对比 |
|
||
| 4 | `pbl_compiler_verify_determinism.dspy` | `pbl_compiler_verify_determinism` | POST | **US-11 / F-CP-03 验收唯一入口**:同输入重编译 N 次比对指纹 |
|
||
| 5 | `pbl_compiler_task_get.dspy` | `pbl_compiler_task_get` | GET | 编译任务详情(含 `error_code`/`game_def_id`/`content_fingerprint`) |
|
||
| 6 | `pbl_compiler_task_list.dspy` | `pbl_compiler_task_list` | GET | 任务分页列表(`tenant_id` 强制打头) |
|
||
| 7 | `pbl_game_definition_get.dspy` | `pbl_game_definition_get` | GET | 按 `game_def_id` 取 GD |
|
||
| 8 | `pbl_game_definition_get_by_blueprint.dspy` | `pbl_game_definition_get_by_blueprint` | GET | 按蓝图(+版本)取最新 / 指定 GD |
|
||
| 9 | `pbl_compiler_version_register.dspy` | `pbl_compiler_version_register` | POST | 登记编译器版本(写 `pbl_compiler_version`) |
|
||
| 10 | `pbl_compiler_version_save.dspy` | `pbl_compiler_version_save` | POST | 保存 / 更新版本记录(`notes`/`enabled`/`rules_hash`) |
|
||
| 11 | `pbl_compiler_version_get.dspy` | `pbl_compiler_version_get` | GET | 取单条版本记录 |
|
||
| 12 | `pbl_compiler_version_list.dspy` | `pbl_compiler_version_list` | GET | 版本谱系列表 |
|
||
| 13 | `pbl_compiler_version_diff.dspy` | `pbl_compiler_version_diff` | GET | 两版本结构化差异(键级 added/removed/changed) |
|
||
| 14 | `pbl_capability_list.dspy` | `pbl_capability_list` | GET | 能力注册表查询(含当前 `registry_hash`) |
|
||
| 15 | `pbl_capability_register.dspy` | `pbl_capability_register` | POST | 注册 / 升版能力(写 `pbl_capability_registry`) |
|
||
|
||
前端入口:`wwwroot/index.ui`(模块导航页,url 一律 `{{entire_url('/pbl_compiler/...')}}` 绝对路径,避免 RBAC 403)。
|
||
|
||
`api.py` 另导出**内部函数**(非 `.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`(`__all__` 共 35 项 = 15 契约 + 13 内部函数 + 7 常量)。
|
||
|
||
### 3.1 三处同步铁律
|
||
每个契约必须**三处同步 + 一处 RBAC**,缺一即路由不可达 / `NameError: name 'xxx' is not defined`(历史退回主因):
|
||
① `pbl_compiler/api.py` 定义函数并列入 `__all__`;
|
||
② `pbl_compiler/__init__.py` 导出(`from .api import ...`);
|
||
③ `pbl_compiler/init.py` 的 `load_pbl_compiler()` 内 `env.<name> = <name>` 注册;
|
||
④ `scripts/load_path.py` 的 `PATHS` 补该端点 RBAC 路径(**禁止通配符 `%` / `*`**,逐条显式;当前 15 条,角色 `logined`)。
|
||
|
||
### 3.2 自查命令(交付前必跑;②③④⑤ 输出必须为空,⑥ 不得抛异常,⑦ 必须输出 OK)
|
||
```bash
|
||
cd modules/pbl_compiler
|
||
|
||
# ① 磁盘端点数(期望 15)
|
||
ls wwwroot/api/*.dspy | wc -l
|
||
|
||
# ② __init__.py 是否全部导出(应无输出)
|
||
for f in $(ls wwwroot/api/*.dspy | xargs -n1 basename | sed 's/\.dspy$//'); do
|
||
grep -q "$f" pbl_compiler/__init__.py || echo "NOT_EXPORTED $f"; done
|
||
|
||
# ③ init.py 是否全部注册 env(应无输出)
|
||
for f in $(ls wwwroot/api/*.dspy | xargs -n1 basename | sed 's/\.dspy$//'); do
|
||
grep -q "$f" pbl_compiler/init.py || echo "NOT_REGISTERED $f"; done
|
||
|
||
# ④ 每个 .dspy 是否有实现(应无输出)
|
||
for f in $(ls wwwroot/api/*.dspy | xargs -n1 basename | sed 's/\.dspy$//'); do
|
||
grep -q "def $f" pbl_compiler/api.py || echo "NO_IMPL $f"; done
|
||
|
||
# ⑤ RBAC 路径覆盖每个端点(应无输出)
|
||
for f in $(ls wwwroot/api/*.dspy | xargs -n1 basename | sed 's/\.dspy$//'); do
|
||
grep -q "$f.dspy" scripts/load_path.py || echo "NO_RBAC $f"; done
|
||
|
||
# ⑥ RBAC register() 可跑通(不得抛 TypeError: not enough arguments for format string)
|
||
python3 -c "import sys;sys.path.insert(0,'scripts');import load_path;print(load_path.register())"
|
||
|
||
# ⑦ 禁硬编码 DB 名(应输出 OK no hardcoded db)
|
||
grep -rn "^DB *=\|DBNAME *= *'\|dbname *= *'pbl'" pbl_compiler/ --include='*.py' \
|
||
&& echo 'HARDCODE_FOUND' || echo 'OK no hardcoded db'
|
||
|
||
# ⑧ 语法编译(应输出 COMPILE_OK)
|
||
python3 -m py_compile pbl_compiler/*.py scripts/*.py && echo COMPILE_OK
|
||
|
||
# ⑨ 自检门禁(必须 exit=0)
|
||
python3 scripts/test_m3a_selfcheck.py ; echo "exit=$?"
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 代码结构(磁盘实测目录树,`find . -path ./.git -prune -o -type f -print`)
|
||
|
||
```
|
||
modules/pbl_compiler/
|
||
├── README.md # 本文档(真实正文,10 章节 + 附录)
|
||
├── pyproject.toml # 包元数据:name = "pbl_compiler"
|
||
├── .gitignore
|
||
├── pbl_compiler/ # Python 包目录(= 模块名,非 src/)
|
||
│ ├── __init__.py # 导出全部契约 + load_pbl_compiler(三处同步之②)
|
||
│ ├── init.py # load_pbl_compiler():env 注册契约(三处同步之③)
|
||
│ ├── api.py # 15 契约 + 13 内部函数 + 7 常量(__all__ 35 项,三处同步之①)
|
||
│ ├── canonical.py # 确定性规范化 / 指纹 / registry_hash / stable_id(本模块地基)
|
||
│ └── gd_builder.py # 蓝图快照 → GD 10 顶层键(纯函数,含 GD_TOP_KEYS)
|
||
├── models/ # 4 张表定义(四段式 JSON)
|
||
│ ├── pbl_game_definition.json
|
||
│ ├── pbl_compiler_version.json
|
||
│ ├── pbl_capability_registry.json
|
||
│ └── pbl_compile_task.json
|
||
├── json/ # 3 份 CRUD 定义
|
||
│ ├── compiler_pbl_game_definition.json
|
||
│ ├── compiler_pbl_compiler_version.json
|
||
│ └── compiler_pbl_capability_registry.json
|
||
├── sql/
|
||
│ └── pbl_compiler.sql # 幂等建表 DDL(4 张表,CREATE TABLE IF NOT EXISTS)
|
||
├── wwwroot/
|
||
│ ├── index.ui # 模块入口导航页
|
||
│ └── api/*.dspy # 15 个契约端点(见 §3)
|
||
├── scripts/
|
||
│ ├── load_path.py # RBAC 路径注册(PATHS 15 条 + register())
|
||
│ └── test_m3a_selfcheck.py # M3a 自检(6 组断言 G1-G6,可独立运行,见 §6)
|
||
└── skill/
|
||
└── SKILL.md # 模块技能文档(与 README 双向同步,见 §9)
|
||
```
|
||
|
||
> **磁盘事实说明(防回归)**:本模块**不含** `app.py` / 端口配置 / Dockerfile / 模块级 `build.sh` /
|
||
> `compiler.py` / `versioning.py`——编译主流程 `compile_blueprint()` 与版本逻辑实现在 `api.py` 内,
|
||
> GD 构建在 `gd_builder.py` 内。安装步骤由宿主应用 `apps/pbls/build.sh` 承担(§5.1)。
|
||
> 文档**不得**描述磁盘不存在的文件(历史退回项,见 §8 P8)。
|
||
|
||
### 4.1 `canonical.py` 确定性规范化要点(不可随意改,改则历史指纹全变)
|
||
- `canonical_json(obj)`:`json.dumps(obj, sort_keys=True, separators=(',', ':'), ensure_ascii=False)`
|
||
—— **键排序 + 零空白 + 不转义非 ASCII**,消除序列化歧义(缩进、键序、中文转义差异全部归零)。
|
||
- **浮点定点化**:`float` 按定点规则格式化(固定精度、去尾零),避免 `1.0` / `1.00000000001` / 平台差异导致指纹抖动;
|
||
整数不带小数点(`1` 与 `1.0` 规范化结果稳定且互不混淆)。
|
||
- **拒绝 NaN / Infinity**:`allow_nan=False`,非法数值直接抛错而非产出 `NaN` 字面量(跨语言不可解析)。
|
||
- `strip_volatile(obj)`:递归(含 `list[dict]` 内部)剥离易变键(`id`/`created_at`/`updated_at`/`duration_ms`/
|
||
`task_no`/`started_at`/`finished_at`/`created_by`/`trace_id`/`nonce` 等),**只剥不改**业务值。
|
||
- **数组稳定化**:语义无序集合(能力集、标签集、子对象集合)按元素规范串排序后再序列化;
|
||
语义有序数组(场景步骤序列、事件时间线)**保持原序**,由 `gd_builder` 显式决定哪些键属有序。
|
||
- `fingerprint(obj)` = `sha256(canonical_bytes(strip_volatile(obj)))`,输出 64 位小写十六进制。
|
||
- `short_hash(text, length)`:定长前缀哈希(用于 `args_schema_hash`、`url_hash`、`rules_hash`)。
|
||
- `registry_hash(capabilities)`:对能力集合归一为
|
||
`{capability_key, version_no, args_schema_hash, permission_required, is_enabled}` 五元组 → 排序 → 规范化 → `sha256`。
|
||
**入参兼容两种形态**:原始注册表行(带 `args_schema_json`)与 GD 内已派生的 `capabilities.items`
|
||
(带 `args_schema_hash`)——派生值本身就是 `short_hash(args_schema_json, 16)`,故两种入参得到**完全相同**的哈希(§8 P1)。
|
||
- `stable_id(blueprint_id, version_no, kind, seq)`:确定性 ID 派生 `{blueprint_id}:{version_no}:{kind}:{seq:05d}`,
|
||
GD 内一切 id 由输入派生,**禁用 uuid / random**(29.6「无随机」)。
|
||
- **禁项**:指纹路径上禁止 `time.time()` / `datetime.now()` / `uuid` / `random` / `os.getpid()` /
|
||
dict 迭代顺序依赖 / 无 `ORDER BY` 的 SQL 行序依赖。
|
||
|
||
---
|
||
|
||
## 5. 安装与集成
|
||
|
||
### 5.1 安装(由宿主应用 `apps/pbls/build.sh` 承担;模块自身无 build.sh)
|
||
```bash
|
||
# ① 安装模块包(模块仓库根目录)
|
||
cd modules/pbl_compiler && pip install .
|
||
|
||
# ② 建表(幂等 DDL;库名由宿主应用决定,勿写死)
|
||
mysql -h <db_host> -u <user> -p<pass> <dbname> < sql/pbl_compiler.sql
|
||
|
||
# ③ 前端链接:模块 wwwroot 软链到宿主应用 wwwroot(软链非 cp,保持同步)
|
||
ln -sf <abs>/modules/pbl_compiler/wwwroot <app>/wwwroot/pbl_compiler
|
||
|
||
# ④ RBAC 注册(显式 15 条路径,禁通配符)
|
||
python3 modules/pbl_compiler/scripts/load_path.py
|
||
|
||
# ⑤ 语法门禁 + 自检门禁(两者都必须通过才算安装成功)
|
||
python3 -m py_compile modules/pbl_compiler/pbl_compiler/*.py
|
||
python3 modules/pbl_compiler/scripts/test_m3a_selfcheck.py # 必须 exit=0
|
||
```
|
||
|
||
### 5.2 宿主应用挂载(`apps/pbls/app/pbls.py`)
|
||
```python
|
||
from ahserver.webapp import webapp
|
||
from ahserver.serverenv import ServerEnv
|
||
from appbase.init import load_appbase
|
||
from rbac.init import load_rbac
|
||
from bricks_for_python.init import load_pybricks
|
||
from pbl_common.init import load_pbl_common
|
||
from pbl_blueprint.init import load_pbl_blueprint
|
||
from pbl_validation.init import load_pbl_validation
|
||
from pbl_compiler.init import load_pbl_compiler # ← 本模块唯一集成点
|
||
|
||
def get_module_dbname(m):
|
||
# 模块 → 库名映射集中在应用层;模块内禁止硬编码 DBNAME
|
||
return {'pbl_common': 'pbl', 'pbl_blueprint': 'pbl', 'pbl_validation': 'pbl',
|
||
'pbl_compiler': 'pbl'}.get(m, m)
|
||
|
||
def init():
|
||
env = ServerEnv() # 必须在所有 load_* 之前
|
||
env.get_module_dbname = get_module_dbname
|
||
load_appbase(); load_rbac(); load_pybricks()
|
||
load_pbl_common()
|
||
load_pbl_blueprint()
|
||
load_pbl_validation()
|
||
load_pbl_compiler() # 注册 15 契约 + 13 内部函数到 ServerEnv
|
||
|
||
if __name__ == '__main__':
|
||
webapp(init)
|
||
```
|
||
|
||
### 5.3 库名获取(**禁硬编码 DB 名**,历史退回项 #4)
|
||
```python
|
||
# ✅ 正确(.py 模块文件内):从宿主注入的 ServerEnv 取
|
||
dbname = ServerEnv().get_module_dbname('pbl_compiler')
|
||
|
||
# ✅ 正确(.dspy 内):get_module_dbname 是 ServerEnv 注入的全局,无需 import、无需 ServerEnv()
|
||
dbname = get_module_dbname('pbl_compiler')
|
||
|
||
# ❌ 错误(已整改,禁止回归):
|
||
# DB = 'pbl' # 换库 / 多租户部署即断
|
||
# async with DBPools().sqlorContext('pbl') as sor: # 硬编码库名
|
||
```
|
||
`api.py` 中 `sql_rows / sql_exec / tenant_crud` 的库名一律来自 `get_module_dbname('pbl_compiler')`
|
||
或调用方注入的 `db` 参数;模块内**不存在** `DB = '...'` / `DBNAME = '...'` 常量。自查命令见 §3.2 ⑦(实测输出 `OK no hardcoded db`)。
|
||
|
||
### 5.4 RBAC 注册(`scripts/load_path.py`)
|
||
`PATHS` 逐条列出 15 个契约路径(角色 `logined`)+ 模块目录 + `index.ui`,`register()` 逐条注册并返回统计。
|
||
**格式串占位符个数必须与参数个数一致**(历史 `TypeError: not enough arguments for format string` 已修,禁止回归):
|
||
```python
|
||
print('[%s] rbac paths: total=%d ok=%d pending=%d' % (MODULE, len(PATHS), done, len(missing)))
|
||
print(' PENDING %-12s %s' % (role, path))
|
||
```
|
||
**离线环境行为**:无数据库 / 无 `set_role_perm.py` 依赖时,`register()` **不抛异常**,逐条打印
|
||
`PENDING <role> <path>` 并返回 `False`(部署到真实环境后返回 `True`)。这是预期的 fail-safe 行为,
|
||
关键判据是「**不得抛 TypeError**」(§3.2 ⑥ 实测通过)。
|
||
|
||
### 5.5 建表 DDL(幂等,完整见 `sql/pbl_compiler.sql`)
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS `pbl_game_definition` (
|
||
`tenant_id` VARCHAR(32) NOT NULL COMMENT 租户ID(强制打头),
|
||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键,
|
||
`blueprint_id` BIGINT UNSIGNED NOT NULL COMMENT "来源蓝图",
|
||
`blueprint_version_no` INT NOT NULL DEFAULT 0 COMMENT "蓝图版本",
|
||
`compiler_version_id` BIGINT UNSIGNED NOT NULL COMMENT "编译版本",
|
||
`content_fingerprint` VARCHAR(64) NOT NULL COMMENT "SHA-256 指纹",
|
||
`definition_json` LONGTEXT NULL COMMENT "Game Definition 正文",
|
||
`world_id` BIGINT UNSIGNED NOT NULL COMMENT "落库 world",
|
||
`scene_id` BIGINT UNSIGNED NOT NULL COMMENT "落库 scene",
|
||
`entity_count` INT NOT NULL DEFAULT 0, `event_count` INT NOT NULL DEFAULT 0,
|
||
`quality_state` VARCHAR(64) NOT NULL COMMENT "编译时质量状态",
|
||
`duration_ms` INT NOT NULL DEFAULT 0,
|
||
`created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL,
|
||
PRIMARY KEY (`id`),
|
||
UNIQUE KEY `uk_gd_fp` (`tenant_id`, `content_fingerprint`), -- ★ 确定性幂等的 DB 级保证
|
||
KEY `idx_gd_bp` (`tenant_id`, `blueprint_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
-- 另 3 张:pbl_compiler_version(uk_cv_code) / pbl_capability_registry(uk_cap_key) / pbl_compile_task(uk_ct_task_no)
|
||
```
|
||
> 手工建表必须显式 `COLLATE utf8mb4_unicode_ci`,否则与 `json2ddl` 生成的表混用会报
|
||
> `Illegal mix of collations`。`trigger` 是 MySQL 关键字,本模块用 `triggered_by` 列名规避。
|
||
|
||
---
|
||
|
||
## 6. 自检运行方法(`scripts/test_m3a_selfcheck.py`)
|
||
|
||
```bash
|
||
cd modules/pbl_compiler
|
||
python3 scripts/test_m3a_selfcheck.py ; echo "exit=$?"
|
||
```
|
||
- 输出逐条 `PASS/FAIL` 明细 + 汇总 `--- summary: pass=N fail=M ---`;
|
||
- **退出码 0 = 6 组断言全部通过**;1 = 有断言失败;2 = 被测模块加载失败(§5.1 ⑤ 安装门禁以此为准);
|
||
- **纯函数级自检**:只按文件路径加载 `canonical.py` 与 `gd_builder.py`(`importlib`,绕开包 `__init__.py` 的
|
||
ahserver 依赖),**不连数据库、不依赖宿主 ServerEnv**,可在 CI 直接跑;
|
||
- **防御式导入**:被测函数缺失时打印 `SELFCHECK_FAIL: missing <name>` 并计入 FAIL,绝不静默跳过
|
||
(历史退回项 R1:声称的自检脚本必须真实可跑)。
|
||
|
||
### 6 组断言(G1-G6,与脚本内 `print('-- Gx ...')` 标题一一对应)
|
||
| 组 | 脚本标题 | 断言要点 | 覆盖需求 |
|
||
|---|---|---|---|
|
||
| G1 | `canonical 键序无关 / 指纹形态` | 深层嵌套 dict 键序打乱后规范串与指纹不变;指纹为 64 位小写 hex | 29.6 确定性 |
|
||
| G2 | `零空白 / ensure_ascii=False / 浮点定点 / NaN 拒绝` | 规范串无空格换行;中文不转义;`1.0`/`1`/`1.00` 各自稳定且互不混淆;NaN/Infinity 抛错 | canonical 规则 |
|
||
| G3 | `strip_volatile 易变字段不参与指纹` | 仅改 `created_at`/`id`/`duration_ms`/`task_no` 指纹不变;递归进入 `list[dict]`;模块指纹 == `sha256(参考规范化(strip_volatile(x)))`(算法口径一致) | 幂等前提 / US-11 |
|
||
| G4 | `Game Definition 10 顶层键` | `GD_TOP_KEYS` == 10 键契约;`build_game_definition` 返回键数/键集一致;同时返回 64 位指纹且与 `manifest.fingerprint` 一致;`manifest` 声明 `schema`/`deterministic=True`/`llmUsed=False`(零 LLM);**`manifest.registryHash` == `canonical.registry_hash(GD capabilities.items)`(统一口径)**;原始注册表能力全部进入 GD items 且 `version_no` 一致(派生不丢语义) | F-CP-01 / F-CP-03(历史 FAIL 位) |
|
||
| G5 | `确定性:同输入重编译指纹全等` | 连续 3 次编译指纹全等;ctx 易变字段(`created_at`/`duration_ms`/`task_no`)变化 → 指纹不变;子对象入库顺序颠倒 → 指纹不变(集合稳定化排序生效);业务内容变更 → 指纹必变 | US-11 / F-CP-03 |
|
||
| G6 | `registry_hash 能力注册表指纹` | 64 位 sha256;与能力行顺序无关;新增能力 / `version_no` 升级 / `args_schema` 变更 → hash 变化;仅改 `created_at`/`id` → hash 不变;空注册表有确定哈希且与非空不同 | F-CP-05 |
|
||
|
||
**本轮实测结果(磁盘实跑,非声称)**:`--- summary: pass=37 fail=0 ---`,`exit=0`。
|
||
|
||
> **G4 口径修复说明(历史两轮退回根因)**:`gd_builder.build_capabilities()` 对注册表行**补齐派生字段**
|
||
> (`args_schema_hash`/`registered`/`used_by_gd`/`category`)后以 `known.values()` 计算 `registry_hash`,
|
||
> 而旧自检对**原始注册表行**计算 → 两侧口径永不相等 → `exit=1`、安装门禁自证失败。
|
||
> 本轮统一为「**以 GD 内 `capabilities.items` 为唯一口径**」,并让 `canonical.registry_hash()` 同时兼容
|
||
> 原始行(`args_schema_json`)与派生行(`args_schema_hash`)两种入参得到同值,另加「派生不丢语义」断言守护。
|
||
|
||
---
|
||
|
||
## 7. 编译主流程
|
||
|
||
### 7.1 `compile_blueprint(blueprint_id, version_no, compiler_version, triggered_by, **kw)` ①-⑧
|
||
(实现见 `api.py`,函数 docstring 与下表一致;契约入口 `pbl_compiler_compile.dspy`)
|
||
|
||
| 步 | 动作 | fail-closed 行为 |
|
||
|---|---|---|
|
||
| ① | **审批门禁**(US-10 / F-CP-01):`is_approved()` 校验蓝图版本已审批,取 `approval_id` + 审批证据;同时校验 `quality_state` 达 `COMPILE_GATE_MIN_STATE`(第 15 章门禁阶梯 `QUALITY_LADDER`) | 未审批 → `PBL_E_STATE_ILLEGAL`(HTTP 403);质量未达标 → `PBL_E_STATE_ILLEGAL`(消息含 `quality_state=%s 未达 %s`)。**不建任务、不落 GD** |
|
||
| ② | **建编译任务**:写 `pbl_compile_task`(`status=running`、`approval_id`、`compiler_version`、`ruleset_version`、`task_no` 确定性派生、`triggered_by`、`started_at`) | 写失败即中止,返回 `task_no` + `error_code` |
|
||
| ③ | **取蓝图版本快照**(输入锁定):`get_version()` 读蓝图聚合根 + 7 类子对象,冻结为 `snapshot`,固定行序 | 蓝图/版本不存在 → `status=failed` + `PBL_E_NOT_FOUND` |
|
||
| ④ | **确定性转换生成 GD 10 顶层键**(零 LLM):`gd_builder.build_game_definition(snapshot, ctx, caps)` 纯函数构建;`ctx` 携 `blueprint_id`/`blueprint_version_no`/`compiler_version`/`ruleset_version`/`rules_hash` | 构建异常 → `status=failed` + `error_msg`,**绝不产出半成品 GD** |
|
||
| ⑤ | **算指纹**:`canonical_json` + `strip_volatile` + `sha256` → `content_fingerprint`(64 位) | 序列化异常(如 NaN)→ `status=failed` |
|
||
| ⑥ | **落库(append-only + 幂等命中)**:先 `SELECT ... WHERE tenant_id AND content_fingerprint LIMIT 1`;命中 → 复用既有 `game_def_id`,返回 `idempotent=true`;未命中 → `INSERT pbl_game_definition`(`uk_gd_fp` 唯一约束兜并发) | 唯一冲突 → 回读既有行复用(不新建 id、不产生重复行) |
|
||
| ⑦ | **回填任务**:`status=success` + `game_def_id` + `content_fingerprint` + `duration_ms` + `finished_at` | 回填失败 → 任务留 `running` 并落审计告警(GD 已落库不回滚,可重跑幂等命中) |
|
||
| ⑧ | **写审计**:`write_audit()` 记录编译动作(操作人 / 蓝图 / 版本 / 指纹 / 结果) | 审计失败不阻断主流程,但记 error 日志 |
|
||
|
||
**返回**:`{task_no, status, game_def_id, fingerprint, idempotent, ...}`。
|
||
**失败路径统一原则(fail-closed)**:任一步失败都必须 ① 把 `error_code`(`PBL_E_*`)/`error_msg` 写回
|
||
`pbl_compile_task`、② 落审计、③ 不产生 GD 行、④ 返回结构化错误而非 500 裸异常。
|
||
|
||
### 7.2 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`;`createdAt`/`durationMs` 写入正文供审计但被 `VOLATILE_KEYS` 排除在指纹外 | `build_manifest` |
|
||
| `pbl` | PBL 驱动问题 / 学习目标 / 阶段(`dimension` 缺省 `knowledge`) | `build_pbl` |
|
||
| `world` | 世界定义(关联 `world_id`) | `build_world` |
|
||
| `scenes` | 场景序列(**语义有序**,保持原序) | `build_scenes` |
|
||
| `entities` | 实体集合(按 `entity_key` 排序) | `build_entities` |
|
||
| `events` | 事件 + `response[].capability`(能力引用来源之一) | `build_events` |
|
||
| `states` | 状态机(键排序输出) | `build_states` |
|
||
| `capabilities` | `{schema: pbl.capability_list.v1, registry_hash, items[]}`;items 为注册表行 ∪ 蓝图引用但未注册的能力(后者标 `registered=false`,供 M4b fail-closed 裁决告警) | `build_capabilities` |
|
||
| `assessment` | `pbl_rubric` 只读权重结构(34 章,M6 消费) | `build_assessment` |
|
||
| `assets` | 资产清单(`kind`+`asset_key` 排序,`url_hash`/`mime`/`runtime_neutral=True`) | `build_assets` |
|
||
|
||
> `manifest.fingerprint` 由 `fingerprint_of()` 对 GD 正文(`strip_volatile` 后)取 sha256 得到并回填,
|
||
> `manifest` 内易变项已被 `VOLATILE_KEYS` 排除,避免自指循环。
|
||
|
||
---
|
||
|
||
## 8. 陷阱(10 条 + 本轮 4 项退回防回归条款)
|
||
|
||
| # | 陷阱 | 正确做法 |
|
||
|---|---|---|
|
||
| P1 | **registry_hash 口径不一致**:一侧对原始注册表行取哈希,另一侧对补齐派生字段(`args_schema_hash`/`registered`/`used_by_gd`/`category`)后的 `capabilities.items` 取哈希 → 永不相等,自检 G4 红、安装门禁自证失败 | 统一以 **GD 内 `capabilities.items`** 为唯一口径;`canonical.registry_hash()` 同时兼容原始行与派生行(派生值即 `short_hash(args_schema_json,16)`,两路同值);改口径必须同步改自检并重跑到 `exit=0` |
|
||
| P2 | **README/SKILL 端点表与磁盘不符**(列出磁盘不存在的 `.dspy`,或漏列磁盘已有的) | 端点表以 `ls wwwroot/api/*.dspy` 实测生成后逐行核对;交付前跑 §3.2 ②③④⑤,输出必须为空 |
|
||
| P3 | **声称 write_file 的文件实际不存在**(幽灵文件)——按造假直接退回 | 交付清单只列磁盘真实文件;交付前 `wc -c` + `cat` 复核字节数与正文一致 |
|
||
| P4 | **硬编码 DB 名**(`DB = 'pbl'`)→ 换库 / 多租户部署即断 | 一律 `get_module_dbname('pbl_compiler')`(.py 内经 `ServerEnv()`,.dspy 内直接调全局);跑 §3.2 ⑦ |
|
||
| P5 | **格式串占位符与参数个数不匹配** → `register()` `TypeError`,RBAC 收口失败 | `%d`/`%s` 个数 == 参数个数;改后 `python3 -c "...register()"` 实跑通过再交付 |
|
||
| P6 | **三处同步漏一处** → `.dspy` 路由不可达 / `NameError: name 'xxx' is not defined` | 新增契约同时改 `api.py`(+`__all__`) / `__init__.py` / `init.py` / `load_path.py`;`grep -rn '<func>' pbl_compiler/ --include='*.py'` 必须命中三处 |
|
||
| P7 | **volatile 字段混入指纹**(`created_at`/`duration_ms`/`task_no`/`id`)→ 每次编译指纹都不同,`uk_gd_fp` 幂等彻底失效 | 落库前必过 `strip_volatile`;G3/G5 断言守护;新增易变字段必须同步加进 `VOLATILE_KEYS` |
|
||
| P8 | **文档描述磁盘不存在的代码文件**(如声称有 `compiler.py`/`versioning.py`/模块级 `build.sh`) | 目录树以 `find . -type f` 实测为准;先落文件再写文档,或按实际实现位置描述 |
|
||
| P9 | **指纹路径引入不确定性**(`datetime.now()`/`uuid`/`random`/无 `ORDER BY` 的 SQL/依赖 dict 迭代序/NaN) | 指纹路径纯函数化;id 用 `stable_id()` 派生;SQL 必带确定性 `ORDER BY`;无序集合先按规范串排序;`allow_nan=False` |
|
||
| P10 | **`return` 写在 `async with db.sqlorContext(...)` 内** → 静默返回 `None`(`return data type error, <class 'NoneType'>`);另 sqlor 只有 `C/U/D/R/I/sqlExe`,`sor.I` 只接 1 个参数 | 在 `async with` 内收集结果,**退出块之后**再 `return`;查询走 `pbl_common.api` 的 `q_all`/`q_one`(已适配) |
|
||
|
||
### 本轮(qc_reject)4 项退回防回归条款
|
||
1. **自检脚本必须真实落盘且实跑通过**:`scripts/test_m3a_selfcheck.py` 磁盘存在(19121 B),覆盖 G1-G6;
|
||
任何改动后必须重跑并把实测输出(含 `--- summary ---` 与 `exit=`)附进交付摘要。
|
||
**声称写入而磁盘不存在 = 造假,直接退回。** 本轮实测:`pass=37 fail=0`,`exit=0`。
|
||
2. **8 个契约必须导出 + 注册**:`pbl_compiler_task_get` / `pbl_compiler_task_list` / `pbl_game_definition_get` /
|
||
`pbl_game_definition_get_by_blueprint` / `pbl_compiler_verify_determinism` / `pbl_compiler_version_register` /
|
||
`pbl_compiler_version_get` / `pbl_compiler_version_diff` 已在 `api.py:__all__` + `__init__.py` 导出 +
|
||
`init.py` 注册 env + `load_path.py` 补 RBAC 路径(§3.2 ②③④⑤ 实测无输出)。
|
||
`verify_determinism` 是 US-11 验收唯一入口,漏接线即核心需求缺失。
|
||
3. **`register()` 不得崩溃**:格式串占位符与参数严格对齐(P5);离线环境返回 `False` + `PENDING` 明细属预期 fail-safe,
|
||
判据是「不抛异常」(§3.2 ⑥ 实测通过)。
|
||
4. **禁硬编码 DB 名**:见 P4;§3.2 ⑦ 实测输出 `OK no hardcoded db`。
|
||
|
||
---
|
||
|
||
## 9. 与 `skill/SKILL.md` 的双向同步
|
||
|
||
`skill/SKILL.md` 是**面向 agent 的模块技能文档**(YAML frontmatter `name`/`description` + 正文:定位、挂载、
|
||
数据表、契约端点、陷阱、依赖),本 README 是**面向人的完整模块文档**。两者关系与同步规则:
|
||
|
||
- **事实源**:README 是事实源(4 张表 / 15 个端点 / 目录树 / ①-⑧ 流程 / 10 条陷阱);
|
||
SKILL.md 是其**浓缩版**,供 agent 加载后快速上手,**不得与 README 冲突**。
|
||
- **必须双向同步的 5 类内容**:
|
||
① 4 张表名与关键约束(尤其 `UNIQUE(tenant_id, content_fingerprint)` = `uk_gd_fp`);
|
||
② 15 个契约端点清单(数量与名称);
|
||
③ `canonical.py` 确定性规则(`sort_keys` / 零空白 / `ensure_ascii=False` / 浮点定点 / `strip_volatile` / sha256 / `stable_id`);
|
||
④ GD 10 顶层键(`manifest, pbl, world, scenes, entities, events, states, capabilities, assessment, assets`);
|
||
⑤ §8 陷阱清单(SKILL.md 至少保留 P1-P7 + 本轮 4 项防回归条款)。
|
||
- **变更流程**:改表 / 改端点 / 改指纹规则时,**同一次提交内**同时改 `README.md` 与 `skill/SKILL.md`;
|
||
只改一处 = 文档失真(历史退回同类问题:SKILL.md 曾写「3 张表 / 7 个契约」与磁盘 4 表 / 15 端点不符,本轮已对齐)。
|
||
- **自查命令**(应无 `SYNC_MISSING` 输出):
|
||
```bash
|
||
cd modules/pbl_compiler
|
||
for k in pbl_game_definition pbl_compiler_version pbl_capability_registry pbl_compile_task \
|
||
content_fingerprint registry_hash strip_volatile verify_determinism \
|
||
manifest capabilities assessment assets; do
|
||
grep -q "$k" README.md && grep -q "$k" skill/SKILL.md || echo "SYNC_MISSING $k"; done
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 上下游依赖
|
||
|
||
### 10.1 上游(本模块依赖)
|
||
| 依赖 | 类型 | 用途 | 缺失时行为(fail-closed) |
|
||
|---|---|---|---|
|
||
| `pbl_common` | 项目模块 | 租户上下文 `tenant_id()`、DB 适配 `q_all`/`q_one`、错误码 `PBL_E_*`、`write_audit`、CRUD 工厂 | 取不到租户/库名 → 直接抛错,不建任务不落库 |
|
||
| `pbl_blueprint` | 项目模块 | 蓝图聚合根 + 7 类子对象 + 版本快照(编译输入源)、审批记录(`is_approved`) | 蓝图/版本不存在 → `PBL_E_NOT_FOUND`;未审批 → `PBL_E_STATE_ILLEGAL`(403) |
|
||
| `pbl_validation` | 项目模块 | 14 维校验产出的 `quality_state`(5 级阶梯)作为编译质量门禁输入 | 低于 `COMPILE_GATE_MIN_STATE` → 拒绝编译 |
|
||
| `pbl_appcodes` | 项目模块 | 枚举编码(`pbl_compile_status` / `quality_state` / `category` 等) | 编码缺失 → 下拉/校验降级,不阻断编译 |
|
||
| `world` / `scene` | 项目模块 | GD 落库目标(`world_id` / `scene_id`),供 M9 渲染 | 无法落库 → 编译失败并回写 `error_code` |
|
||
| `ahserver` / `ServerEnv` | 基础模块(只读) | `load_pbl_compiler()` 挂载、`get_module_dbname`、`.dspy` 运行时全局 | 模块无法运行(宿主必须先 `ServerEnv()`) |
|
||
| `sqlor` | 基础模块(只读) | `sor.C/R/U/D/I/sqlExe` + `sqlorContext` / `get_sor_context` | 无数据访问能力 |
|
||
| `appbase` / `rbac` | 基础模块(只读) | 编码表、权限路径注册(`scripts/load_path.py` → `set_role_perm.py`) | 端点 403 |
|
||
|
||
### 10.2 下游(依赖本模块)
|
||
| 消费方 | 消费的契约 / 数据 |
|
||
|---|---|
|
||
| `pbl_agent_runtime`(M4a/M4b) | `pbl_game_definition_get*` 读 GD 作 Designer/Critic Agent 上下文;`capabilities.items[].registered/permission_required` 作 fail-closed 工具裁决依据 |
|
||
| `pbl_evidence`(M5a/M5b) | GD `assessment`/`events`/`objectives` 作为证据采集与幂等比对锚点;引用 `content_fingerprint` 保证可追溯 |
|
||
| `pbl_assessment`(M6) | GD `assessment`(Rubric 只读权重结构)+ 指纹追溯 |
|
||
| `pbl_kdb_ext`(M7) | 只读引用 GD 指纹做匿名聚合分组(零写入) |
|
||
| `pbl_scense_ext` / `scense`(M9) | `pbl_game_definition_get_by_blueprint` 取 GD 驱动前端游戏页渲染(`world_id`/`scene_id`) |
|
||
| `pbl_runtime_ext`(M11a/M11b) | GD `entities`/`events`/`states` 作为运行时事件与状态写入的定义源 |
|
||
|
||
### 10.3 开发顺序(拓扑)
|
||
`pbl_common` → `pbl_appcodes` → `pbl_blueprint`(M1a/M1b) → `pbl_validation`(M2) →
|
||
**`pbl_compiler`(M3a/M3b)** → `pbl_agent_runtime`(M4a/M4b) → `pbl_evidence`(M5a/M5b) →
|
||
`pbl_assessment`(M6) → `pbl_kdb_ext`(M7) → `pbl_scense_ext`(M9) → `pbl_runtime_ext`(M11a/M11b)。
|
||
|
||
---
|
||
|
||
## 附:交付前复核清单(每项都必须有磁盘实测证据,禁止口头声明)
|
||
- [x] `wc -c README.md` 与本文档正文字节数一致;`cat` 抽查非占位、无「见磁盘」类自我指涉语
|
||
- [x] `ls wwwroot/api/*.dspy | wc -l` == 15,且与 §3 表格逐行一致
|
||
- [x] §3.2 ②③④⑤ 输出为空;⑥ `register()` 无异常;⑦ 输出 `OK no hardcoded db`;⑧ `COMPILE_OK`
|
||
- [x] `python3 scripts/test_m3a_selfcheck.py` 实测 `--- summary: pass=37 fail=0 ---`、`exit=0`
|
||
- [x] `models/*.json` 4 份表定义 JSON 合法(`json.load` 通过),四段式 + `indexes[].fields` 为数组
|
||
- [x] `README.md` 与 `skill/SKILL.md` 关键词同步自查(§9)无 `SYNC_MISSING`
|
||
- [x] 交付清单中每个 `write_file` 声称的文件磁盘真实存在(禁幽灵文件)
|