world_sync/skill/SKILL.md
2026-09-21 13:33:56 +08:00

69 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: world_sync
description: world_sync 模块技能文档。
---
# world_sync 模块
## 概述
世界数据同步模块,管理世界数据的导入/导出/合并同步记录与执行。依赖 world 模块(world 表)、appbase(appcodes 字典 sync_type/sync_status)。通过 load_world_sync() 挂载。
自 M11b-2/M11b-3 起,本模块同时承载 **scense_runtime 服务端权威写入薄扩展**:单事务事件+状态写入(`pbl_runtime_authority.write_event_with_state`)与**事务提交后广播**(`pbl_runtime_broadcast`)。
## 数据模型
### 表 world_sync(models/world_sync.json)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | str(32) | 主键 |
| world_id | str(32) | 所属世界(→world.id) |
| sync_type | str(16) | 同步类型(0导入/1导出/2合并) |
| source | str(255) | 数据来源 |
| status | str(16) | 状态(appcodes sync_status) |
| result_json | text | 同步结果 JSON |
| sync_date | date | 同步日期(业务日期) |
| created_at | timestamp | 创建时间 |
- 主键:["id"];索引:idx_sync_world(world_id)
- 字典:world_id→world、sync_type→appcodes_kv(parentid='sync_type')、status→appcodes_kv(parentid='sync_status')
### 运行时权威表(M11b-2,models/)
`pbl_runtime_event`(append-only 事件)、`pbl_entity_state`(实体状态,state_version 服务端权威)、
`pbl_world_state_snapshot`(世界快照)。所有读写强制 `tenant_id` 打头,缺失 fail-closed。
## 关键接口
- list_world_syncs / get_world_sync / create_world_sync / update_world_sync / delete_world_sync / execute_world_sync
- wwwroot/api/world_sync.dspy(同步契约)、create/world_sync_update/world_sync_delete.dspy、get_search_*.dspy
- M11b-2:`write_event_with_state(event_data, state_update, ...)` —— 事件 + 状态在**同一事务**内提交
- M11b-3 广播 API(`world_sync/pbl_runtime_broadcast.py`,已在 `__init__.py` 导出):
- `install_broadcast_hook()` / `is_broadcast_hook_installed()`:把 `write_event_with_state` 包成 COMMIT 后广播(幂等,`load_world_sync()` 已自动调用)
- `emit_post_commit_broadcast(write_result, context=...)`:手动发一条
- `wrap_with_post_commit_broadcast(fn)`:包装任意返回事务结果的函数
- `configure_broadcast(channels=..., webhook_urls=..., redis_url=..., queue_size=2000, retry_attempts=2, retry_backoff_s=0.02, enabled=True)`:宿主启动时配置通道
- `subscribe_broadcast(handler)` / `unsubscribe_broadcast(handler)`:进程内订阅
- `broadcast_stats()` / `broadcast_last_message()` / `broadcast_latency_percentile_ms(99)` / `drain_broadcast(timeout=2.0)` / `shutdown_broadcast()`:观测与优雅退出
- 通道类:`InProcessChannel`、`WebhookChannel`、`RedisPubSubChannel`、`NoopChannel`(均继承 `BaseChannel`,`publish(message)`)
## 事务提交后广播(M11b-3)要点
- **触发点**:仅事务 COMMIT 成功之后(`commit_ts` 即延迟起点);事务回滚不广播。
- **异步**:单 dispatcher 线程 + 有界 `queue.Queue(2000)`,`submit` 用 `put_nowait` 微秒返回,不阻塞事务返回;队列满降级为当前线程直发一次。
- **SLO**:`BROADCAST_LATENCY_SLO_MS = 300.0`,口径 P99;实测 n=200 p50=2.4ms / p99=4.1ms / max=4.2ms(`scripts/m11b3_selftest_output.txt`)。
- **失败语义**:fire-and-forget + 有限重试;通道/订阅者异常一律吞掉并 `ERROR` 日志 + `stats["failed"]` 计数,**绝不回滚已提交事务**、绝不外抛给业务调用方。
- **无轮询**:广播路径不含 `time.sleep` 轮询、不读 DB;等待靠 `Queue.get(block=True)` 条件变量。
- **环境变量**:`PBL_BROADCAST_DISABLED`(全局关闭)、`PBL_BROADCAST_WEBHOOK_URLS`(逗号分隔 webhook)、`PBL_BROADCAST_REDIS_ENABLED` + `PBL_BROADCAST_REDIS_URL`(Redis pub/sub)。
## 陷阱
- 库名不硬编码:.py 用 ServerEnv().get_module_dbname('world_sync'),.dspy 用 get_sor_context(request._run_ns,'world_sync')
- dspy 无 import
- create/execute 必须设置 created_at、sync_date,否则 sor.C 静默丢记录
- update 剔除 _text 后缀
- codes.table 引用 world 不用 module.table 点号
- **广播 hook 幂等**:`install_broadcast_hook()` 重复调用不得重复包装(已用 `__wrapped_post_commit__` 标记),否则一条事件广播多次
- **广播内禁止再写事务/再发广播**:dispatcher 线程只做 publish,回写 DB 会造成递归与延迟失真
- **测试断言前用 `drain_broadcast()` 等待,禁止 sleep 轮询**(QC 会按「引入轮询」退回)
- `RedisPubSubChannel` 惰性 import `redis`:环境缺包只 `ChannelUnavailable` 跳过,不影响 InProcess/webhook 通道,不要让 Redis 成为硬依赖
- `scripts/sync_code_to_app.py` 的 `collect()` 必须以 `WS_ROOT` 拼绝对路径 —— 用相对路径 `isdir` 会恒返回空清单,导致 `--check` 打印假 PASS(M11b-3 已修,改脚本时勿回退)
## 依赖
- world、appbase、rbac
- M11b-2/M11b-3 广播零第三方硬依赖(stdlib:threading/queue/urllib/hashlib;redis 可选)