11 KiB
Work Log — 2026-09-22 · M11b-2c:world_sync 单事务库函数(write_event_with_state / read_entity_state)
- 模块:
world_sync(宿主镜像/消费方侧,运行时事件与实体状态单事务写入器) - 里程碑:M11b-2(单事务写两表);上游真源模块
pbl_runtime_ext(表结构与tx_write/tx_read能力权威源) - 任务:
[M11b-2c-T2] 补 M11b-2 开发文档 dev-notes.md + code_files.md(task_id=kICTWazBqgIWtKNO7sz8A,classify_task=new_dev) - 本文按 module-development-spec「Work Logs 强制项」产出:本次改了什么、数据模型、契约接口、陷阱与裁决、验证证据。
- 范围纪律:本任务只补文档,未修改任何业务代码 /
models/*.json/scripts/load_path.py(后者归 T3)。
1. 数据模型
M11b-2 写路径涉及两张表,结构权威源在 pbl_runtime_ext/models/,world_sync/models/ 下是消费方内联副本(不是引用式声明——database-table-definition-spec 只认 summary/fields/indexes/codes 四段式,scripts/validate_models_json.py 对 key=schema 引用式写法直接判 FAIL,QC #5 结论)。
1.1 pbl_runtime_event(运行时事件,append-only)
- 真源:
modules/pbl_runtime_ext/models/pbl_runtime_event.json;副本:modules/world_sync/models/pbl_runtime_event.json(8782B) - 一致性:由
scripts/m11b2b_column_diff.py+scripts/m11b2_selftest.py机械核对,mirror-sha256-equal断言真源与apps/scense/pkgs/world_sync镜像字节级一致(本次重跑 PASS)。 - 语义要点:
- append-only:写路径只做 INSERT,不 UPDATE / 不 DELETE(
TestScopeAndSqlContract::test_event_sql_is_insert_only守这条)。 event_uid是幂等/重试定位键(默认 uuid4 hex,可由入参指定以便测试因果链);旧口径的event_id已废弃。state_version与状态行生效后的版本号对齐(同事务内第 4 步算出再写第 5 步),供 M11b-3 广播去重与 M11b-4 轮询增量拉取。seq_no严格单调递增(探针断言 PASS);source默认world_sync.m11b2;broadcast标记位由 M11b-3 消费。payload以 JSON 文本入库(json.dumps后写text列;抽象类型归一json→text、bigint→long,不保留物理修饰)。
- append-only:写路径只做 INSERT,不 UPDATE / 不 DELETE(
1.2 pbl_entity_state(实体状态,服务端权威版本)
- 真源:
modules/pbl_runtime_ext/models/pbl_entity_state.json;副本:modules/world_sync/models/pbl_entity_state.json(6099B) - 列集(真源实测 10 列,早期「8 列」口径作废):
tenant_id / id / session_id / entity_id / state_json / state_version / checksum / updated_by / created_at / updated_at - 定位键 = 唯一键
uk_es (tenant_id, session_id, entity_id),与write_event_with_state/read_entity_state的 WHERE 完全一致(没有world_id列,world_id仅作历史兼容入参被忽略)。 state_version单调 +1,客户端禁写;state_json存完整新状态(非 diff)。id类型偏离:真源BIGINT AUTO_INCREMENT,world_sync 副本内联为str/length=32(spec 合规写法),主键由应用层生成 32 位 uuid。写路径 INSERT 不显式提交 id——生产 MySQL 交给真源 DDL 的 AUTO_INCREMENT,本地/测试 sqlite DDL 用应用层生成值。此偏离为有意选择(副本侧不走「登记放行 id 非 str(32)」路线,因为本任务范围禁改校验器脚本),评审勿判结构缺陷。- 主键只在
summary[0].primary=["id"]单点声明,indexes[]内不出现PRIMARY条目(spec 规则index_name_primary)。
2. 契约接口
2.1 两个库函数(M11b-2 的全部对外面)
# world_sync/pbl_runtime_tx.py:351
def write_event_with_state(event_data=None, state_update=None, conn_factory=None,
conn=None, event_uid=None, source=None, clock=None) -> dict
# world_sync/pbl_runtime_tx.py:310
def read_entity_state(conn_factory=None, tenant_id=None, session_id=None,
entity_id=None, world_id=None, conn=None) -> dict | None
event_data必填:tenant_id/entity_id/event_type;可选payload、session_id、scene_id、world_id、actor_id、causation_id、idem_key、seq/seq_no、tx_group、source、broadcast。state_update必填:tenant_id/session_id/entity_id;新状态键state(或state_json,dict 或 JSON 文本皆可);可选expected_version(乐观锁)、updated_by。传None= 只写事件不改状态(返回action="event_only")。- 成功返回:
{event_uid, state_version, action: updated|inserted|event_only, elapsed_ms, isolation_level_applied, dialect, ...}。elapsed_ms是 P99 统计的采样来源;isolation_level_applied=False在 sqlite 下是正常(无该概念),MySQL/PG 下应为 True。
read_entity_state返回{"state": ..., "state_version": int, "updated_at": ...},查不到返回None;它是独立短事务,不与写入事务共用(避免长事务),供上层冲突后重读版本再重试。- 复用
conn时仍由本函数负责该连接的 commit/rollback,调用方不要自行 commit。
2.2 三处同步注册(module-development-spec 铁律 4,已 grep 核验)
| 环节 | 位置 |
|---|---|
| ① 实现 | world_sync/pbl_runtime_tx.py:310,351(__all__ 已导出) |
| ② 包导出 | world_sync/__init__.py:119-120(import)+ :209-210(__all__) |
| ③ 宿主挂载 | world_sync/init.py:513-514 → env.write_event_with_state / env.read_entity_state |
适配层 world_sync/pbl_runtime_tx_sqlor.py:126,154 提供同名薄包装(宿主连接池接线,不重复实现事务逻辑)。
2.3 无 HTTP 端点(重要)
M11b-2 不新增 .dspy / .ui,因此 scripts/load_path.py 中没有这两个函数的权限条目,也不该有——它们是 Python 库函数,经 load_world_sync() 挂到 ServerEnv 供宿主调用,不走 HTTP 路由,不存在 401/403 面。wwwroot/api/README-N-A-M11b2.md 是「Not Applicable」的显式留档。正式声明见 projects/pbls/docs/02-develop/m11b2c-rbac-path-declaration.md。
2.4 事务与冲突契约(详版在 dev-notes.md [M11b-2c] 章 §2)
BEGIN(sqlite BEGIN IMMEDIATE)→ 会话级 READ COMMITTED(MySQL/PG;失败降级不阻断,靠乐观锁兜底并透出标志)→ 读 state_version(MySQL/PG 加 FOR UPDATE)→ expected_version 预检 → 状态 CAS 更新/INSERT → 事件 INSERT → COMMIT;任一步失败 _rollback_quiet + 明确异常(ConcurrentStateConflict / StateWriteError / EventWriteError / TxAbortError / TxConfigError / ConnFactoryNotConfigured),两表 0 变更,绝不吞错。
3. 陷阱(本模块改动前必读)
- 别在 world_sync 侧建表。DDL 归
pbl_runtime_ext;world_sync/models/*.json只是消费方契约视图。改真源必须同步本副本并重跑m11b2b_column_diff.py+m11b2_selftest.py。 - 列名不许写死。列集由
pbl_runtime_authority从真源机械导出,_assert_contract()在建 SQL 之前 fail-closed(AuthorityResolutionError)。新增列只改真源 JSON + 同步副本,代码里出现字面量列名清单 = 双写分叉隐患。 - 定位键是
tenant_id + session_id + entity_id,不是world_id(表里没这列)。传world_id只被忽略——别指望它做隔离。 expected_version两级校验缺一不可:预检给友好错误(带 expected/actual),CAS(rowcount != 1)防预检与写入之间的窄窗口。只留预检会丢失更新。- 状态行 INSERT 不带
id(见 §1.2 偏离说明),且 UNIQUE 列空串必须归一为None(''会互相撞唯一键)。 - 写序是先状态、后事件。反过来会让事件携带一个可能被回滚的
state_version。 isolation_level_applied=False不等于失败(sqlite 无该概念)。测试断言要按 dialect 分支。conn_factory三级优先级:显式注入(可传 DB URL)→ 复用conn→ 宿主ServerEnv连接池(POOL_KEYS=("sqlor_pools","db_pools","pools","sqlorPools"),库名键("pbl","world_sync","default"))。拿不到池就抛ConnFactoryNotConfigured,禁止回落到裸连或读env/{test,prod}.json凭据(project-directory-spec §8.1)。m11b2_selftest.py的cross-check-sql检查器:M11b-2c 重构后EVENT_COLUMNS = columns_of(EVENT_TABLE)(运行时派生),旧正则抓字面量元组会误报「未能解析」。已由 commit6532d8f改为三级解析(A 字面量 / B1 运行时 import / B2 静态回落)。再遇到同类 FAIL 先怀疑检查器而非业务代码。- 遗留测试文件是技术债不是验收依据:
tests/test_m11b2_single_tx.py、tests/test_m11b2_wiring.py的 29 条失败源于脚手架 DDL 与真源不一致、仍用已废弃的event_id关键字、缺显式 dialect、init.py相对路径等(详见m11b2c-tests-result.md§4.1 逐条归因)。等价覆盖已在pbl_runtime_ext/tests/test_m11b2c_runtime_ext.py(9 passed,含 2 条 P99 门禁)。重写需单列 rework 任务。 - 广播/轮询不在此模块的 M11b-2 路径里:post-commit 广播由
init.py的 hook 把write_event_with_state包一层(M11b-3),3s 轮询兜底在pbl_runtime_polling.py(M11b-4)。摘掉这两层不影响 M11b-2 写入正确性——排查时先确认看到的是哪一层。 - P99 数据是离线 sqlite/内存引擎实测,不含网络 RTT 与真实 InnoDB 锁等待,只能作为逻辑时延下界;上线后在测试环境按
pbl_runtime_latency.py口径复测。
4. 验证证据(本次实跑)
$ cd modules/world_sync && .venv/bin/python scripts/m11b2_selftest.py # exit code 0
PASS [mirror-sha256-equal] pbl_runtime_event 真源与镜像字节级一致 -> True
PASS [mirror-sha256-equal] pbl_entity_state 真源与镜像字节级一致 -> True
断言合计 554:PASS 554 / FAIL 0
RESULT: PASS
- 全文落盘:
projects/pbls/docs/02-develop/m11b2c-t2-selftest-rerun.log(64695B) - 真源侧:
pbl_runtime_extpytest9 passed in 0.41s(0 失败);m11b_selftest.py总计 110 项,PASS 110,FAIL 0 → RESULT: PASS - P99(
m11b2c_p99_probe.py,n=60,SLA=200ms):write_event_with_state[INSERT] P99=0.2414ms/read_entity_state P99=0.2241ms/write_event_with_state[UPDATE] P99=0.4984ms,均 PASS;seq_no严格递增、读回entity_id一致性 PASS - 模块 HEAD:
world_sync@5e3c48d、pbl_runtime_ext@9a79e63(git status --porcelain本任务无代码改动)
5. 关联文档
- 应用级开发说明(本里程碑正文):
projects/pbls/docs/02-develop/dev-notes.md→[M11b-2c] world_sync 库函数章 - 真源侧:
dev-notes-m11b2c-runtime-ext.md;迁移:dev-notes-m11b2a-world-sync-migration.md;A3 修复:m11b2c-a3-fix-notes.md - RBAC 口径:
m11b2c-rbac-path-declaration.md;实跑留证:m11b2c-tests-result.md及各m11b2c-tests-*-raw.log - 模块技能:
modules/world_sync/skill/SKILL.md;上一份工作日志:modules/world_sync/docs/work-log-2026-09-20.md