# pbl_compiler —— PBL Compiler v1(确定性编译 + Game Definition) > 模块仓库:`modules/pbl_compiler/` | 里程碑:M3a(编译核心与确定性产物)+ M3b(版本/对比) > 挂载方式:宿主应用 `init()` 中调用 `load_pbl_compiler(env)`(本模块是 Python 包,**无 app.py、无独立端口、无 Dockerfile**) > 本文档为磁盘真实正文(非占位/非元描述),与 `skill/SKILL.md` 双向同步。 --- ## 1. 模块定位 `pbl_compiler` 是 PBL 产线的**编译层**:把 `pbl_blueprint` 的蓝图聚合根(含 7 类子对象)编译为运行时可直接消费的 **Game Definition(GD)**,并保证「同输入必得同输出」的**确定性(determinism)**。M3a 承担 6 项核心职责: 1. **确定性规范化(canonicalization)**:任意嵌套 JSON → 唯一规范串 → `sha256` 指纹。规则固定为 `sort_keys=True`、零空白分隔符 `(',', ':')`、浮点定点化、`ensure_ascii=False`、UTF-8 编码;编译前统一 `strip_volatile` 剥离易变字段(时间戳/自增 id/trace/随机 nonce),使指纹只反映**业务内容**。 2. **Game Definition 构建**:`gd_builder.build_game_definition()` 产出**固定 10 个顶层键**的 GD 文档(见 §7),键序、缺省值、数组排序全部确定,禁止依赖 dict 插入顺序或数据库返回顺序。 3. **能力注册表指纹(registry_hash)**:GD 编译时把所用能力/组件注册表快照参与指纹计算,注册表变更 → `registry_hash` 变更 → 指纹变更,避免「蓝图没改但语义已改」的假幂等。 4. **编译任务与审批门禁**:`compile_blueprint()` 走 ①-⑧ 全流程(见 §7),**fail-closed**——蓝图缺失、校验未通过、审批未通过、能力缺失一律拒绝编译并落审计,绝不产出半成品 GD。 5. **指纹幂等落库**:`pbl_game_definition` 上 `UNIQUE(tenant_id, content_fingerprint)`;同租户同指纹重编译**命中即复用**(返回既有 GD + `reused=true`),不产生重复行、不产生新 id。 6. **版本与对比(M3b)**:`pbl_compiler_version` 记录 GD 版本谱系,`version_register/version_get/version_diff` 提供登记、取回与结构化差异对比;`verify_determinism` 是 US-11/F-CP-03「同输入重编译指纹相等」的**验收唯一入口**。 非目标(本模块不做):不做蓝图 CRUD(属 `pbl_blueprint`)、不做 14 维校验规则实现(属 `pbl_validation`,本模块只消费其 `quality_state` 结论)、不做 Agent 推理(属 `pbl_agent_runtime`)、不做前端渲染(属 `pbl_scense_ext`)。 --- ## 2. 数据表(4 张,DDL 见 `models/*.json`) 所有表**首列 `tenant_id`**,所有读写 SQL 强制 `tenant_id` 打头;缺失租户上下文直接抛错,不做「默认租户」兜底。 ### 2.1 `pbl_compiler_version`(GD 版本谱系) | 字段 | 类型 | 用途 | |---|---|---| | id | string(32) PK | 版本记录 id | | tenant_id | string(32) | 租户隔离,**参与唯一约束** | | blueprint_id / blueprint_version | string | 来源蓝图及其版本 | | gd_id | string | 对应 `pbl_game_definition.id` | | version_no | int | 同蓝图内单调递增版本号 | | content_fingerprint | string(64) | GD 内容 sha256 指纹 | | registry_hash | string(64) | 编译时能力注册表快照指纹 | | change_summary | text | 相对上一版的结构化变更摘要 | | created_by / created_at | string / datetime | 审计字段(**不参与指纹**) | 约束:`UNIQUE(tenant_id, blueprint_id, version_no)`;索引 `(tenant_id, gd_id)`、`(tenant_id, content_fingerprint)`。 ### 2.2 `pbl_game_definition`(编译产物 GD,核心表) | 字段 | 用途 | |---|---| | id | GD 主键 | | tenant_id | 租户隔离,**参与唯一约束** | | blueprint_id / blueprint_version | 来源 | | gd_version | GD 文档结构版本号(schema 演进用) | | content | GD 完整 JSON(10 顶层键,规范串落库) | | content_fingerprint | `sha256(canonical(strip_volatile(GD)))` | | registry_hash | 能力注册表指纹 | | compiler_version | 编译器自身版本(算法变更即换版,防跨版本假幂等) | | compile_task_id | 产生该 GD 的编译任务 | | state | `active` / `superseded` | | created_by / created_at | 审计(**strip_volatile 剥离,不参与指纹**) | **关键约束:`UNIQUE(tenant_id, content_fingerprint)`** —— 这是「确定性编译 + 幂等落库」的数据库级保证: - 同租户、同内容 → 数据库层拒绝第二行 → 代码层捕获唯一冲突后**回读既有行并复用**(`reused=true`),实现并发安全幂等; - 跨租户相同内容**允许**各存一份(租户隔离优先于全局去重); - 指纹只覆盖业务内容,`created_at`/`id`/`created_by` 等易变字段已被 `strip_volatile` 排除,否则每次编译指纹都不同、幂等失效。 ### 2.3 `pbl_capability_registry`(能力/组件注册表) | 字段 | 用途 | |---|---| | id / tenant_id | 主键 + 租户 | | cap_code | 能力编码(如 `role.skill`、`item.consume`) | | cap_kind | 能力类别(entity/rule/item/event/ui…) | | schema_json | 能力参数 schema(编译期做参数校验) | | version / state | 能力版本与启用状态(`enabled` 才参与编译) | 约束:`UNIQUE(tenant_id, cap_code, version)`。编译时对**启用能力集合**做规范化后取 `sha256` 得 `registry_hash`,写入 GD 与版本表。 ### 2.4 `pbl_compile_task`(编译任务与审计载体) | 字段 | 用途 | |---|---| | id / tenant_id | 主键 + 租户 | | blueprint_id / blueprint_version | 编译对象 | | trigger | `manual` / `auto` / `recompile` | | state | `pending`→`running`→`succeeded`/`failed`/`rejected` | | approval_state | 审批门禁结论(`approved` 才允许继续) | | quality_state | 来自 `pbl_validation` 的 5 级质量状态 | | gd_id / content_fingerprint | 成功时回填 | | error_code / error_message | 失败时回填(fail-closed 原因可追溯) | | started_at / finished_at / created_by | 审计(不参与指纹) | 索引:`(tenant_id, blueprint_id, created_at)`、`(tenant_id, state)`。 --- ## 3. 契约端点清单(15 个 `.dspy`,三处同步) 契约文件位于 `wwwroot/api/*.dspy`。**每个契约必须三处同步**,缺一即路由不可达(上一轮 QC 退回主因之一): ① `pbl_compiler/api.py` 定义函数并列入 `__all__`;② `pbl_compiler/__init__.py` 导出;③ `pbl_compiler/init.py` 的 `load_pbl_compiler()` 内 `env` 注册;同时 ④ `scripts/load_path.py` 的 `PATHS` 补 RBAC 路径。 | # | 端点(wwwroot/api/) | api.py 函数 | 方法 | 说明 | |---|---|---|---|---| | 1 | `pbl_compiler_compile.dspy` | `pbl_compiler_compile` | POST | 触发编译(走 ①-⑧ 全流程) | | 2 | `pbl_compiler_preview.dspy` | `pbl_compiler_preview` | POST | 试编译,只返回 GD + 指纹,**不落库** | | 3 | `pbl_compiler_task_get.dspy` | `pbl_compiler_task_get` | GET | 编译任务详情 | | 4 | `pbl_compiler_task_list.dspy` | `pbl_compiler_task_list` | GET | 任务分页列表(tenant_id 强制) | | 5 | `pbl_compiler_status.dspy` | `pbl_compiler_status` | GET | 任务状态轻量轮询 | | 6 | `pbl_game_definition_get.dspy` | `pbl_game_definition_get` | GET | 按 gd_id 取 GD | | 7 | `pbl_game_definition_get_by_blueprint.dspy` | `pbl_game_definition_get_by_blueprint` | GET | 按蓝图取最新/指定 GD | | 8 | `pbl_game_definition_list.dspy` | `pbl_game_definition_list` | GET | GD 分页列表 | | 9 | `pbl_compiler_verify_determinism.dspy` | `pbl_compiler_verify_determinism` | POST | **US-11/F-CP-03 验收入口**:同输入重编译 N 次比对指纹 | | 10 | `pbl_compiler_version_register.dspy` | `pbl_compiler_version_register` | POST | 登记 GD 版本 | | 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 | 两版本结构化差异 | | 14 | `pbl_compiler_compare.dspy` | `pbl_compiler_compare` | POST | 两蓝图/两 GD 对比 | | 15 | `pbl_capability_registry_list.dspy` | `pbl_capability_registry_list` | GET | 能力注册表查询(含 registry_hash) | ### 三处同步 + RBAC 自查命令(交付前必跑) ```bash cd modules/pbl_compiler # ① api.py 定义与 __all__ 数量 python3 - <<'PY' import ast,sys src=open('pbl_compiler/api.py',encoding='utf-8').read() t=ast.parse(src) funcs={n.name for n in t.body if isinstance(n,(ast.FunctionDef,ast.AsyncFunctionDef))} allm=[n for n in t.body if isinstance(n,ast.Assign) and any(getattr(x,'id',None)=='__all__' for x in n.targets)] exported=set(allm[0].value.elts[0].value if False else e.value for e in allm[0].value.elts) if allm else set() print('api.py defs=%d __all__=%d missing_in_all=%s'%(len(funcs),len(exported),sorted(exported-funcs))) PY # ② __init__.py 是否全部导出(应无输出) for f in $(grep -o "'pbl_[a-z_]*'" pbl_compiler/api.py | tr -d "'" | sort -u); do grep -q "$f" pbl_compiler/__init__.py || echo "NOT_EXPORTED $f"; done # ③ init.py 是否全部注册 env(应无输出) for f in $(grep -o "'pbl_[a-z_]*'" pbl_compiler/api.py | tr -d "'" | sort -u); do grep -q "$f" pbl_compiler/init.py || echo "NOT_REGISTERED $f"; done # ④ .dspy 与 api 函数一一对应(应无输出) ls wwwroot/api/*.dspy | wc -l for d in wwwroot/api/*.dspy; do b=$(basename $d .dspy); grep -q "def $b" pbl_compiler/api.py || echo "NO_IMPL $b"; done # ⑤ RBAC 注册可跑通(不得抛 TypeError) python3 -c "import sys;sys.path.insert(0,'scripts');import load_path;print(load_path.register())" ``` --- ## 4. 代码结构 ``` modules/pbl_compiler/ ├── README.md # 本文档(真实正文) ├── build.sh # 一键安装:建表 DDL → 拷贝 wwwroot → RBAC 注册 → 自检 ├── pyproject.toml # 包元数据(模块名 pbl_compiler) ├── pbl_compiler/ # Python 包目录(= 模块名,非 src/) │ ├── __init__.py # 导出全部契约函数 + load_pbl_compiler(三处同步之②) │ ├── init.py # load_pbl_compiler(env):注册 env 契约 + 建表(三处同步之③) │ ├── api.py # 15 个 .dspy 契约实现(三处同步之①) │ ├── canonical.py # 确定性规范化 + 指纹(本模块地基) │ ├── gd_builder.py # 蓝图 → GD(10 顶层键) │ ├── compiler.py # compile_blueprint ①-⑧ 主流程 + fail-closed │ └── versioning.py # 版本登记/取回/diff(M3b) ├── models/ # 4 张表定义(四段式 JSON) │ ├── pbl_compiler_version.json │ ├── pbl_game_definition.json │ ├── pbl_capability_registry.json │ └── pbl_compile_task.json ├── wwwroot/api/*.dspy # 15 个契约端点 ├── scripts/ │ ├── load_path.py # RBAC 路径注册(PATHS + register()) │ └── test_m3a_selfcheck.py # M3a 自检(6 组断言,可独立运行) └── skill/SKILL.md # 模块技能文档(与 README 双向同步) ``` ### `canonical.py` 确定性规范化要点(不可随意改,改则指纹全变) - `canonical_json(obj)`:`json.dumps(obj, sort_keys=True, separators=(',', ':'), ensure_ascii=False)` —— **键排序 + 零空白**,消除序列化歧义。 - 浮点定点化:`float` 统一按定点规则格式化(去尾零、统一精度),避免 `1.0` / `1.00000000001` / 平台差异导致指纹抖动;整数不带小数点。 - `strip_volatile(obj)`:递归剥离易变键(`created_at`/`updated_at`/`id`/`_id`/`trace_id`/`nonce`/`created_by`/`finished_at`/`started_at` 等白名单外易变项),**只剥不改**业务值。 - 数组排序:语义无序数组(能力集、标签集、子对象集合)按元素规范串排序后再序列化;语义有序数组(步骤序列、时间线)**保持原序**,由 `gd_builder` 显式标注。 - `fingerprint(obj)`:`sha256(canonical_json(strip_volatile(obj)).encode('utf-8')).hexdigest()`,输出 64 位小写十六进制。 - `registry_hash(entries)`:对启用能力集合按 `cap_code+version` 排序规范化后取 sha256。 - **禁项**:禁止在指纹路径上引入 `time.time()`、`datetime.now()`、`uuid`、`random`、`os.getpid()`、dict 迭代顺序依赖。 --- ## 5. 安装与集成 ### 5.1 一键安装 ```bash cd modules/pbl_compiler && bash build.sh ``` `build.sh` 步骤:① 校验 Python 语法(`py_compile`)→ ② 执行建表 DDL → ③ 同步 `wwwroot/api/*.dspy` 到宿主应用静态目录 → ④ 注册 RBAC 路径(调 `scripts/load_path.py:register()`)→ ⑤ 跑 `scripts/test_m3a_selfcheck.py` 自检 → ⑥ 输出安装摘要。任一步失败即 `exit 1`(fail-closed)。 ### 5.2 宿主应用挂载(`apps/{应用}/app/{应用}.py`) ```python from pbl_compiler import load_pbl_compiler def get_module_dbname(m): # 模块 → 库名映射,集中在应用层,模块内禁止硬编码 return {'pbl_compiler': 'pbl', 'pbl_blueprint': 'pbl', 'pbl_common': 'pbl'}.get(m, m) def init(): env = ServerEnv() env.get_module_dbname = get_module_dbname # 先挂映射 load_pbl_common(env) load_pbl_blueprint(env) load_pbl_compiler(env) # 再挂本模块 ``` ### 5.3 库名获取(**禁硬编码 DB 名**) ```python # 正确:从宿主应用注入的 ServerEnv 取 def _db(env): return env.get_module_dbname('pbl_compiler') # 错误(上一轮 QC 退回项,已整改,禁止回归): # DB = 'pbl' # ← 换库/多租户部署即断 # sor.R(DB, 'select ...') ``` `api.py` 中 `sql_rows/sql_exec/tenant_crud` 一律通过 `env`(或调用方注入的 `db` 参数)取库名;模块内**不存在** `DB = '...'` 常量。自查: ```bash grep -rn "^DB *=\|DB *= *'pbl'\|DBNAME *= *'" pbl_compiler/ && echo 'HARDCODE_FOUND' || echo 'OK no hardcoded db' ``` ### 5.4 RBAC 注册 `scripts/load_path.py` 中 `PATHS` 列出全部 15 个契约路径 + 角色(如 `pbl.designer` / `pbl.teacher` / `pbl.admin`),`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)) ``` ### 5.5 建表 DDL(幂等) ```sql CREATE TABLE IF NOT EXISTS pbl_game_definition ( id VARCHAR(32) PRIMARY KEY, tenant_id VARCHAR(32) NOT NULL, blueprint_id VARCHAR(32) NOT NULL, blueprint_version INT NOT NULL DEFAULT 1, gd_version INT NOT NULL DEFAULT 1, content LONGTEXT NOT NULL, content_fingerprint CHAR(64) NOT NULL, registry_hash CHAR(64), compiler_version VARCHAR(32), compile_task_id VARCHAR(32), state VARCHAR(16) NOT NULL DEFAULT 'active', created_by VARCHAR(32), created_at DATETIME, UNIQUE KEY uk_tenant_fp (tenant_id, content_fingerprint) ); CREATE TABLE IF NOT EXISTS pbl_compiler_version ( id VARCHAR(32) PRIMARY KEY, tenant_id VARCHAR(32) NOT NULL, blueprint_id VARCHAR(32) NOT NULL, blueprint_version INT NOT NULL, gd_id VARCHAR(32) NOT NULL, version_no INT NOT NULL, content_fingerprint CHAR(64) NOT NULL, registry_hash CHAR(64), change_summary TEXT, created_by VARCHAR(32), created_at DATETIME, UNIQUE KEY uk_tenant_bp_ver (tenant_id, blueprint_id, version_no) ); CREATE TABLE IF NOT EXISTS pbl_capability_registry ( id VARCHAR(32) PRIMARY KEY, tenant_id VARCHAR(32) NOT NULL, cap_code VARCHAR(64) NOT NULL, cap_kind VARCHAR(32), schema_json TEXT, version INT NOT NULL DEFAULT 1, state VARCHAR(16) NOT NULL DEFAULT 'enabled', UNIQUE KEY uk_tenant_cap (tenant_id, cap_code, version) ); CREATE TABLE IF NOT EXISTS pbl_compile_task ( id VARCHAR(32) PRIMARY KEY, tenant_id VARCHAR(32) NOT NULL, blueprint_id VARCHAR(32) NOT NULL, blueprint_version INT, trigger VARCHAR(16), state VARCHAR(16) NOT NULL DEFAULT 'pending', approval_state VARCHAR(16), quality_state VARCHAR(16), gd_id VARCHAR(32), content_fingerprint CHAR(64), error_code VARCHAR(32), error_message TEXT, started_at DATETIME, finished_at DATETIME, created_by VARCHAR(32), created_at DATETIME ); ``` --- ## 6. 自检运行方法 ```bash cd modules/pbl_compiler && python3 scripts/test_m3a_selfcheck.py ``` 退出码 0 = 全部通过;非 0 = 有断言失败(`build.sh` 第 ⑤ 步会因此中断安装)。**6 组断言**: | 组 | 断言 | 覆盖需求 | |---|---|---| | 1 | `canonical_json` 对**同内容不同键序**的两个 dict 产出完全相同的串与指纹 | 29.6 sort_keys | | 2 | 嵌套结构(含数组/中文/浮点)规范化后**无任何空白字符**,且浮点定点稳定 | 29.6 无空白 + 浮点定点 | | 3 | `strip_volatile` 后修改 `created_at/id/trace_id` **指纹不变**;修改业务字段**指纹必变** | volatile 不参与指纹 | | 4 | `build_game_definition()` 输出**恰好 10 个顶层键**,键名集合与预期完全一致,缺失/多余即失败 | GD 契约稳定 | | 5 | 同一蓝图连续编译 3 次,`content_fingerprint` 三次相等;`registry_hash` 相等 | US-11 / F-CP-03 确定性 | | 6 | 能力注册表增删一条 `enabled` 能力 → `registry_hash` 变化;仅改 `created_at` → 不变 | registry_hash 语义正确 | 自检脚本**不依赖数据库与宿主应用**(纯函数级),可在 CI 中直接跑;脚本内使用防御式导入,函数名缺失时明确报 `SELFCHECK_FAIL: missing ` 而非静默跳过。 --- ## 7. 编译主流程 `compile_blueprint()` ①-⑧(fail-closed) | 步 | 动作 | 失败处理(fail-closed) | |---|---|---| | ① | **租户与入参校验**:`tenant_id` 必传,`blueprint_id` 必传 | 缺失 → `E_TENANT_REQUIRED` / `E_PARAM`,任务置 `rejected`,落审计,**不建 GD** | | ② | **建编译任务**:写 `pbl_compile_task`(`state=pending`→`running`,记 `trigger`) | 写库失败 → 直接抛错,不产出任何 GD | | ③ | **加载蓝图快照**:读 `pbl_blueprint` 聚合根 + 7 类子对象,冻结为内存快照(含 `blueprint_version`) | 蓝图不存在/已删除 → `E_BLUEPRINT_NOT_FOUND`,任务 `failed` | | ④ | **审批门禁**:校验 `approval_state == 'approved'` | 未审批/驳回 → `E_APPROVAL_REQUIRED`,任务 `rejected`,**绝不编译** | | ⑤ | **质量门禁**:取 `pbl_validation` 的 `quality_state`,低于阈值(配置化常量)拒绝 | 不达标 → `E_QUALITY_BELOW_THRESHOLD`,任务 `rejected` | | ⑥ | **构建 GD**:`gd_builder.build_game_definition(snapshot, registry)` → 10 顶层键文档;能力缺失即报错,不做静默降级 | 能力未注册/参数不合 schema → `E_CAPABILITY_MISSING`,任务 `failed` | | ⑦ | **指纹与幂等落库**:`strip_volatile` → `canonical_json` → `sha256` 得 `content_fingerprint`;`INSERT` 命中 `UNIQUE(tenant_id, content_fingerprint)` 冲突则**回读既有 GD 复用**(`reused=true`),否则新建 | 落库异常 → 事务回滚,任务 `failed`,无脏数据 | | ⑧ | **版本登记 + 审计 + 回填**:写 `pbl_compiler_version`(`version_no` 递增、`change_summary`),任务置 `succeeded` 并回填 `gd_id/content_fingerprint`,写 `audit_log` | 任一步失败 → 整体回滚,任务 `failed` | **fail-closed 原则**:③④⑤⑥ 任一不通过 → 无 GD 产出、任务终态可追溯(`error_code` + `error_message`)、审计留痕;**不存在**「先产出再补校验」的路径。`pbl_compiler_preview` 走 ①③⑥⑦ 的**只读子集**(不写 GD、不写版本),用于前端试编译。 --- ## 8. 陷阱(10 条 + 本轮 4 项退回防回归条款) 1. **指纹路径禁易变源**:任何 `now()/uuid/random/pid` 进入 GD 或指纹计算 → 每次编译指纹都不同 → 幂等与 US-11 验收全废。 2. **`strip_volatile` 只剥不改**:剥离白名单外的业务字段会造成「不同蓝图同指纹」的严重误合并。 3. **数组排序要分语义**:把有序步骤序列排序 = 编译产物语义错误;把无序能力集不排序 = 指纹抖动。 4. **浮点必须定点化**:`0.1+0.2` 类误差或平台 repr 差异会让同内容产出不同指纹。 5. **唯一约束是幂等的最后防线**:不要「先 SELECT 再 INSERT」当幂等(并发下会双写),必须依赖 `UNIQUE(tenant_id, content_fingerprint)` + 冲突回读。 6. **`compiler_version` 要参与判断**:编译算法升级后旧指纹不可与新指纹混判等价,跨版本比对需显式重编译。 7. **tenant_id 必须打头**:所有 SQL 的 WHERE 首条件是 `tenant_id`;漏写 = 跨租户数据泄露。 8. **契约三处同步**:`.dspy` 存在但 `__init__.py` 未导出 / `init.py` 未注册 → 路由 404,且**编译期不报错**,只有运行时才暴露。 9. **格式串占位符与参数个数必须一致**:`%d` 多写一个即 `TypeError`,会让 `build.sh` 的 RBAC 步骤整体崩溃。 10. **README/SKILL 不得写占位或元描述**:「见磁盘」「已落盘 N 字符」这类自我指涉文本按空壳造假退回。 ### 本轮 4 项退回防回归条款(每次交付前逐条自查) | 编号 | 退回问题 | 防回归硬条款 | 自查命令 | |---|---|---|---| | R1 | 声称写入的 `scripts/test_m3a_selfcheck.py` 磁盘不存在 | 该脚本**必须真实落盘并可执行**,且列入交付文件清单;声称的文件一律先 `wc -c` 复核 | `wc -c scripts/test_m3a_selfcheck.py && python3 scripts/test_m3a_selfcheck.py` | | R2 | 8 个已定义契约未导出/未注册(含 `verify_determinism`),`.dspy` 不可达 | api.py `__all__` / `__init__.py` 导出 / `init.py` env 注册 / `load_path.py` PATHS **四处齐全**,15 个端点一个不漏 | 见 §3 自查命令 ②③④ | | R3 | `load_path.py:register()` 格式串占位符错位导致 `TypeError` | 修后必须 `python3 -c` **实测跑通**再交付,禁止只改不跑 | `python3 -c "import sys;sys.path.insert(0,'scripts');import load_path;load_path.register()"` | | R4 | `api.py` 硬编码 `DB = 'pbl'` | 模块内**不得存在** DB 名常量,一律 `env.get_module_dbname('pbl_compiler')` 或调用方注入 | `grep -rn "DB *= *'" pbl_compiler/ \|\| echo OK` | > 交付摘要中所有「已写入 / N 字符 / 已落盘」表述,**必须**与交付前 `wc -c`、`cat`、`ls` 的实测输出一致;口头声明不能替代落盘(「声称 vs 实测矛盾」按造假直接退回)。 --- ## 9. 与 `skill/SKILL.md` 的双向同步 - `skill/SKILL.md` 是**面向 Agent 的操作技能文档**(怎么调、参数、返回、铁律、陷阱),`README.md` 是**面向开发/评审的工程文档**(定位、表、结构、安装、流程)。 - 同步规则(改一处必改另一处): 1. 新增/删除/改名契约端点 → 同步 README §3 表格 + SKILL 端点清单 + `__init__.py`/`init.py`/`load_path.py`; 2. 表结构或唯一约束变更 → 同步 README §2 + SKILL 数据契约段 + `models/*.json` + DDL; 3. GD 顶层键或 `canonical` 规则变更 → 同步 README §4/§7 + SKILL 确定性铁律段 + 自检脚本断言(第 4/5 组); 4. 新增陷阱/退回条款 → 同步 README §8 + SKILL「陷阱」节。 - 自查:`grep -c 'pbl_' skill/SKILL.md README.md`(两侧端点名应一一对应,无单侧独有)。 --- ## 10. 上下游依赖清单 ### 上游(本模块依赖) | 依赖 | 用途 | 缺失影响 | |---|---|---| | `pbl_common` | 租户上下文、DB 适配(`sql_rows/sql_exec/tenant_crud`)、错误码、审计写入、CRUD 工厂 | 无法取租户/无法落库/无审计,编译不可用 | | `pbl_blueprint` | 蓝图聚合根 + 7 类子对象 + `blueprint_version`(编译输入源) | ③ 加载快照失败,全流程 fail-closed | | `pbl_validation` | 14 维校验结论与 `quality_state`(⑤ 质量门禁输入) | 无法判定质量门禁,拒绝编译 | | `pbl_appcodes` | 枚举编码注入(状态/触发方式/能力类别等字典) | 状态码不合法,前端展示异常 | | 宿主应用 `ServerEnv` | `get_module_dbname('pbl_compiler')` 库名映射、契约注册环境 | 触发硬编码 DB 名禁项 | | 基础模块 `sqlor` / `rbac` | 标准 SQL API(仅 `sor.C/U/D/R/I/sqlExe`)、权限路径注册 | 数据访问与鉴权不可用 | ### 下游(依赖本模块) | 模块 | 消费内容 | |---|---| | `pbl_agent_runtime`(M4a/M4b) | 读 GD 作为 Designer/Critic Agent 的世界约束;调 `preview` 试编译 | | `pbl_evidence`(M5a/M5b) | 以 `gd_id` / `content_fingerprint` 关联产出物证据 | | `pbl_assessment`(M6) | 按 GD 的 rubric/目标结构做加权评估 | | `pbl_scense_ext`(M9)/ `scense_game` | 前端按 GD 渲染可玩场景(GD 是唯一渲染契约) | | `pbl_runtime_ext`(M11a/M11b) | 运行时事件与状态写入以 GD 定义的实体/规则为准 | | `pbl_kdb_ext`(M7) | 只读引用 GD 指纹做匿名聚合口径对齐 | ### 契约稳定性承诺 GD 的 **10 个顶层键**与 `content_fingerprint` 算法是跨模块契约,属**破坏性变更**范围:任何改动必须升 `gd_version` / `compiler_version`、同步全部下游模块、并重跑 §6 自检 6 组断言。