diff --git a/docs/work-log-2026-09-19.md b/docs/work-log-2026-09-19.md
new file mode 100644
index 0000000..0a48096
--- /dev/null
+++ b/docs/work-log-2026-09-19.md
@@ -0,0 +1,117 @@
+# pbl_compiler 工作日志 — 2026-09-19(M3b 规则映射与导出 · 整改轮)
+
+任务:`[M3b] pbl_compiler script_engine 规则映射与导出`
+(第12章 event→condition→response → script_type=1 规则 JSON 映射、产物导出、game_definition 写入)
+角色:agent.develop | 迭代:pbls-初始迭代 | 任务性质:new_dev(含 QC 退回意见逐条整改)
+
+## 1. 本轮范围
+
+只做「登记面接线 + 审计工具自身缺陷 + 提交可追溯性」的增量整改,**不重写已合规的
+models/ 表定义本体**(前轮 QC 已确认四段式 / primary str32 / codes parentid 全过)。
+
+| # | 退回意见 | 处置 |
+|---|---------|------|
+| QC #1(硬门禁·第四章 RBAC) | 5 个 M3b Web 契约未登记 RBAC 路径;init.py 注释条数与代码不符 | 已修:`scripts/load_path.py` 逐条显式登记(无通配符),`pbl_compiler/init.py` 注释与事实对齐 |
+| QC #2(流程·真实性) | 交付前未自行 git commit,由引擎代为收口 | 已修:本轮 agent 自行选择性 `git add` + `git commit` + `git push`,commit `cb1132e` |
+| 前轮 #2(GD 唯一写入口) | gd_rules_writer 无调用入口 | 保持:`pbl_compiler/api.py` L606 编译主流程 ⑥.5 调用 `write_gd_rules()`,`rules_export_api.py` L223 亦经此入口 |
+| 前轮 #3(CRUD editable) | editable 仅 get_data_url,xls2ui 会回退默认写入口 | 保持方案 A:9 个只读拒绝端点 + params 顶层三 URL 绝对路径 |
+| 前轮 #4(work log) | 无 dated work log | 本文件补齐归档 |
+
+## 2. 时间线
+
+1. **实跑审计定位根因**:`python3 scripts/audit_rbac_parity.py` → rc=1,报
+ 「load_path.py PATHS 为空(RBAC 零登记 → 全部 403)」。
+2. **交叉核验发现是两处事实叠加**:
+ - 工作区 `scripts/load_path.py` 实际已登记 29 条(`grep -c "('/pbl_compiler/api/"` = 29),
+ 与 `ls wwwroot/api/*.dspy | wc -l` = 29 一致 → QC 引用的「仅 15/20 条」是
+ **git HEAD 版本**(前轮变更未提交,`git show HEAD:scripts/load_path.py` 实测 20 条、
+ `git ls-tree HEAD wwwroot/api/` 实测 20 个),审核方读的是仓库而非工作区。
+ - 审计脚本自身存在解析缺陷:`parse()` 对纯字面量列表已 `literal_eval` 成 python list,
+ 而 `resolved` 字典只收 `ast.List` 形态,导致 `PATHS = PATHS_CONTRACTS + PATHS_READONLY_DENY`
+ (BinOp)回查 `resolved` 拿到空列表 → 把已登记的 29 条误判成「零登记」,产生假 FAIL。
+3. **修审计工具**:`resolved` 同时接受 `list`(已求值)与 `ast.List`(含函数引用求值失败)两种形态。
+4. **修注释**:`init.py` 文件头「共 20 条 + 只读列表 CRUD 6 条」→
+ 「共 29 条 = Web 契约端点 20 + CRUD 只读拒绝端点 9,无通配符」。
+5. **实跑自证**:audit rc=0 PASS;`scripts/test_m3b_mapping.py` PASS=113 / FAIL=0;
+ `python3 -m py_compile pbl_compiler/*.py scripts/*.py` OK。
+6. **自行收口**:选择性暂存 15 个文件(6 改 + 9 新增 .dspy),`git commit` → `cb1132e`,
+ `git push origin HEAD` 成功(`d203811..cb1132e HEAD -> main`)。未使用 `git add -A`,
+ `__pycache__` / 生成物未入库。
+
+## 3. 技术决策与陷阱
+
+- **RBAC 登记分两个集合而非一个**:`PATHS_CONTRACTS`(20,进 init.py CONTRACTS /
+ __init__.py `__all__` / wwwroot/api)与 `PATHS_READONLY_DENY`(9,**不是** ServerEnv 契约,
+ 只为封死 xls2ui 默认 `add_/update_/delete_
.dspy` 写入口)。审计按
+ `CONTRACTS ∪ READONLY_DENY` 比对,既不漏登记也不产生幽灵登记。
+- **只读表为何仍要建 create/update/delete 端点**:crud-definition-spec 要求 editable 三 URL
+ 齐备,缺项时 xls2ui 回退生成默认写入口 → `rules_hash`(29.6 确定性指纹)可被手工改写。
+ 故 9 个端点显式返回 `{"widgettype":"Error","options":{"label":"...只读,禁止手工增删改"}}`,
+ 把「拒绝」变成可机械验证的事实(audit 第 ⑪ 项检查 Error widget)。
+- **不登记不存在的路径**:QC #1 提到「若引入 pbl_script_rule 只读列表 CRUD 生成目录则补 6 条」。
+ 实测 `wwwroot/` 下仅有 `api/` 一个子目录(无 CRUD 生成目录),`pbl_script_rule_list` 是手写
+ Web 契约而非 xls2ui CRUD → 补登 `/pbl_compiler/pbl_script_rule` 等 6 条会形成幽灵登记、
+ 污染 permission 表。此判断已固化进 audit 第 ⑫ 项检查。
+- **禁通配符**:29 条全部显式,audit 第 ④ 项用正则扫 `*`/`%` 通配命中即 FAIL。
+- **审计工具也要被审计**:`resolved` 只认 AST 形态这一缺陷,会让「已正确整改」被判成
+ 「RBAC 零登记」,白白烧掉退回预算。凡登记面比对,脚本必须同时兼容字面量求值成功/失败两种形态。
+
+## 4. 验证(实跑输出摘录)
+
+```
+$ python3 scripts/audit_rbac_parity.py
+ PASS init.py CONTRACTS = 20 个契约
+ PASS 20 个契约在 api.py/rules_export_api.py 均有同名实现
+ PASS __init__.py __all__ 覆盖全部 20 个契约
+ PASS load_path.py 无通配符(29 条全显式)
+ PASS load_path.py 登记 29 条无重复(契约 20 + 只读拒绝 9)
+ PASS PATHS_CONTRACTS 与 CONTRACTS 一一对应(20 条)
+ PASS PATHS 登记 29 条,与期望端点集合(契约 20 + 只读拒绝 9)一一对应
+ PASS wwwroot/api/*.dspy = 29 个,与 CONTRACTS∪READONLY_DENY 一一对应
+ PASS grep 口径自证:PATHS 中 /api/ 条目=29,wwwroot/api/*.dspy=29
+ PASS 29 个 .dspy 无非法 import(仅允许 sqlor.filter)
+ PASS 29 个 .dspy 均有显式 return
+ PASS .dspy 无 ServerEnv() 取请求态
+ PASS 9 个只读拒绝端点均显式返回 Error widget(CRUD 写入口已封死)
+ PASS wwwroot/ 无 CRUD 生成子目录(只读列表走手写契约 api/pbl_script_rule_list.dspy)
+ PASS json/*.json 共 3 个:根键白名单(['_comment','alias','params','tblname','title']) / alias
+ / editable 三 URL 齐备且指向真实 .dspy / 字段与 models 全对齐
+RESULT: PASS (contracts=20, readonly_deny=9, dspy=29, rbac_api_paths=29, json=3) rc=0
+
+$ python3 scripts/test_m3b_mapping.py
+PASS: 113 FAIL: 0
+
+$ grep -c "('/pbl_compiler/api/" scripts/load_path.py → 29
+$ ls wwwroot/api/*.dspy | wc -l → 29
+$ grep -n 'pbl_compiler_export_rules|pbl_compiler_rules_preview|pbl_compiler_rules_diff|
+ pbl_game_definition_rules_get|pbl_script_rule_list' scripts/load_path.py
+ → 68,69,70,71,72 五行命中(5 个 M3b 契约全部显式登记)
+$ git log --oneline -1 → cb1132e fix(pbl_compiler): M3b RBAC 29条显式登记+...
+$ git push origin HEAD → d203811..cb1132e HEAD -> main
+```
+
+## 5. 环境受限项(如实说明,不谎报已验证)
+
+1. **`~/repos/sage` 不存在**:QC 改法要求
+ `cd ~/repos/sage && ./py3/bin/python ~/repos/pbl_modules/pbl_compiler/scripts/load_path.py`
+ 并「同步中央 sage/load_path.py 兜底」。本工作空间实测 `ls -d ~/repos/sage` → 无该目录,
+ 中央 `sage` 仓库与 `py3` 虚拟环境均不在位;`apps/pbls/` 下也**没有**中央 load_path.py
+ (全库 `grep -rln "pbl_compiler/api/" apps/` 零命中),各 pbl_* 模块一律以
+ `modules/{模块}/scripts/load_path.py` 为唯一登记面。故本轮只完成模块侧登记与自证,
+ 中央兜底无法执行,需部署角色在有 sage 的环境跑 `register()`。
+2. **未连库、未起服务**:`register()` 依赖 rbac CLI(`set_role_perm.py`)与 MariaDB,
+ 本工作空间无 DB 实例(`PBLS_DB_USER` 空、mysql 未授权)。脚本设计为 CLI 不在位时
+ 打印 PENDING 清单并以 rc=1 暴露(不静默跳过),部署阶段据此补登。
+3. **未重跑 xls2ui/build.sh**:`wwwroot/` 无 CRUD 生成子目录(三表按只读列表呈现,
+ 走手写契约),本轮无 .ui 产物需再生成;若后续引入 CRUD 目录,audit 第 ⑫ 项会 FAIL 提醒补登。
+4. **「登录后调用不再 403」未做端到端实测**:受 1/2 限制,只能以「登记面与端点集合一一对应」
+ 作为可达性的机械证明,真实 HTTP 200 验证需部署环境。
+
+## 6. 分支状态
+
+- 仓库:`modules/pbl_compiler`(origin `git@git.opencomputing.cn:yumoqing/pbl_compiler.git`)
+- 分支:`main`,HEAD = `cb1132e`(agent.develop 自行提交并推送,非引擎代为收口)
+- 工作区:暂存区已清空;未跟踪仅剩 `docs/work-log-2026-09-19.md`(本文件,随交付入库)
+ 与 `__pycache__`(.gitignore 已排除)
+- 前序 6 个 `deliver: 交付收口(引擎代为提交)` 提交(626b2c0/eb18a7d/4051e9d/c276af5/2c14bf5/d203811)
+ 为历史事实,本轮不改写历史,仅在其上追加 agent 自有提交。