From 6da4f049137069bee8eefb92031f51b2e8c79635 Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Mon, 3 Aug 2026 08:34:45 +0800 Subject: [PATCH 01/14] =?UTF-8?q?=E6=94=B6=E5=8F=A3=20R1=20P0=20=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E7=AB=AF=E4=BC=9A=E8=AF=9D=E7=94=9F=E5=91=BD=E5=91=A8?= =?UTF-8?q?=E6=9C=9F=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 冻结持久与易失会话的生命周期、忙态和交付原子性边界,并以当前源码验真吸收清单,防止后续迁移跨轨重复实现。 Model: cc-sonnet-high Co-Authored-By: Claude --- docs/plan-refactor-server-session-v1.md | 230 ++++++++++++++++++ .../plan-refactor-server-session-v1.md | 49 ---- 2 files changed, 230 insertions(+), 49 deletions(-) create mode 100644 docs/plan-refactor-server-session-v1.md delete mode 100644 docs/plans-skeleton/plan-refactor-server-session-v1.md diff --git a/docs/plan-refactor-server-session-v1.md b/docs/plan-refactor-server-session-v1.md new file mode 100644 index 000000000..e0b473018 --- /dev/null +++ b/docs/plan-refactor-server-session-v1.md @@ -0,0 +1,230 @@ +# plan-refactor-server-session-v1 — Server 交互 Session 统一生命周期框架(重构轨 R1) + +> 所属总纲:`plan-refactor-master-v1.md`。一句话:把炼丹、手搓、锻造、采集、灵田、矿脉、灵木及相邻世界交互的 server session 收敛到统一生命周期框架,使断线、跨维、关服、重连、忙态与完成交付只保留一套权威语义。 + +## 阶段总览 + +| 阶段 | 交付物 | 状态 | +|---|---|---| +| P0 | 设计收口、吸收清单验真、冻结 `InteractionSession` 合同 | ✅ 2026-08-03 | +| P1 | `server/src/session/` 落地,craft 作为首个宿主 | ⬜ | +| P2 | 迁移 alchemy、forge、lingtian | ⬜ | +| P3 | 迁移 gathering、mineral、spiritwood 与世界交互锁 | ⬜ | +| P4 | bot e2e、吸收项结案、归档 | ⬜ | + +## 1. P0 第一性现状 + +### 1.1 七域存储矩阵 + +| 域 | 当前权威状态 | P0 结论 | +|---|---|---| +| alchemy | `server/src/alchemy/session.rs:68` `AlchemySession`,挂在 furnace | 炉与 session 共同 checkpoint;不能只存玩家侧半张状态 | +| craft | `server/src/craft/session.rs:40` ECS `CraftSession` | 作为 P1 首宿主;保留已落地的 DB/join hydration 行为 | +| forge | `server/src/forge/session.rs:157` `ForgeSessions` Resource | station/session/已扣材料共同 checkpoint | +| gathering | `server/src/gathering/session.rs:57` `GatheringSessionStore` | 短时易失;中断时确定性 teardown | +| lingtian | `server/src/lingtian/systems.rs:196` `ActiveLingtianSessions` | 六类 actor session 统一按易失交互处理 | +| mineral | `server/src/mineral/session.rs:16` `MiningSession` | 短时易失;解除矿点与工具 claim | +| spiritwood | `server/src/spiritwood/session.rs:59` `WoodSessionStore` | 短时易失;`settling` 必须参与原子 teardown | + +当前不存在 `SessionManager`。七域分别以 Component、Resource、facility-owned state 保存;`server/src/network/craft_emit.rs:541` 只用 `With` 限制 craft tick,不表示 UI pause,也不能覆盖其他 store。 + +### 1.2 生命周期缺口仍可达 + +- 玩家保存与 despawn 从 `server/src/player/mod.rs:350` 开始;七域没有一个统一的、排在持久化前的 teardown/checkpoint 门。 +- 普通 external container 已有 `server/src/world/container_open.rs:184` 断线清锁,但跨维仍可保留 `opened_by`;局部补丁不能替代统一 hook。 +- TSY 搜刮入口 `server/src/world/tsy_container_search.rs:306` 与撤离入口 `server/src/world/extract_system.rs:182` 没有双向 busy 声明,搜刮锁也没有统一断线清理。 +- `server/src/cultivation/insight_flow.rs:229` 插入 `PendingInsightOffer`,选择、拒绝或校验失败会移除,但没有 deadline;同连接内 client 未展示/未回应时可长期悬挂。 +- 灵田 Resource 会对所有 entry 持续 `tick_all`(`server/src/lingtian/systems.rs:249`),没有 actor live-state gate。 +- 炼丹取回在 `server/src/network/client_request_handler.rs:16764-16766` 先 `end_session`,随后才从 `:16838` 尝试交付;交付失败时已经失去可重领状态。 + +## 2. 冻结的 `InteractionSession` 合同 + +P1 必须在 `server/src/session/` 暴露以下可 grep 的合同 symbol;实现可以按 Rust 借用约束拆成 trait + adapter,但不得改变本节语义: + +```rust +pub trait InteractionSession { + fn session_key(&self) -> SessionKey; + fn owner_key(&self) -> &PlayerKey; + fn durability(&self) -> SessionDurability; + fn busy_claim(&self) -> BusyClaim; + fn on_disconnect(&mut self, ctx: &mut SessionLifecycleCtx) -> SessionTransition; + fn on_dimension_change( + &mut self, + from: DimensionKind, + to: DimensionKind, + ctx: &mut SessionLifecycleCtx, + ) -> SessionTransition; + fn on_shutdown(&mut self, ctx: &mut SessionLifecycleCtx) -> SessionTransition; + fn on_reconnect( + &mut self, + player: Entity, + ctx: &mut SessionLifecycleCtx, + ) -> SessionTransition; +} +``` + +配套冻结类型: + +- `SessionRegistry`:唯一 server 权威 owner/busy/lifecycle registry。 +- `SessionKey { domain, id }`:稳定 session 身份;持久记录不得以 Bevy `Entity` 为主键。 +- `PlayerKey`:canonical player id;`Entity` 只作当前连接的 runtime binding。 +- `SessionDurability::{Checkpointed, Volatile}`:每个 adapter 注册时必须显式声明,不允许默认值。 +- `SessionPhase::{Running, Paused, Suspended, AwaitingDelivery, Terminal}`:同一 session 不允许同时处于多个 phase。 +- `TerminationCause::{VoluntaryCancel, Disconnect, DimensionChange, Shutdown, InvalidRestore}`。 +- `SessionTransition::{Keep, Pause, SuspendAndCheckpoint, Teardown, AwaitDelivery, CommitTerminal}`。 +- `BusyClaim`:声明 owner 与 world target 上占用的 busy classes;冲突矩阵集中注册。 + +### 2.1 不变量 + +1. `SessionRegistry` 中一个 `SessionKey` 只能有一个 owner;一个 runtime `Entity` 只能绑定同一 `PlayerKey` 的 session。 +2. `Checkpointed` session 断线/关服后进入 `Suspended`,不得离线推进;重连通过 R3 guarded restore 后才重新绑定 `Entity`。 +3. `Volatile` session 遇到断线、跨维或关服必须在同一生命周期门内 teardown;不得留下 owner entity、设施锁、target claim 或 `settling` 标记。 +4. 所有 dimension-scoped session 在维度切换前终止。`TsyPresence` 是 transport 辅助状态,单独 checkpoint/restore,不得用“保留旧交互 session”修复 presence 撕裂。 +5. client 的 screen/store 只能改善 UX,不能授予 session 或 busy 权限;恶意包、重复包和同 tick 竞态最终都由 registry 拒绝。 +6. session 完成后先进入 `AwaitingDelivery`;只有 R10 `InventoryTxn::deliver(items)` 返回 `Delivered` 或 `Spilled(fallback)` 后,才可 `CommitTerminal` 并清除 escrow/session。依赖未就绪或事务未执行时保留可重试 completion,不吞产物。 +7. 涉及真元的 refund/release 必须通过 `qi_physics::ledger::QiTransfer`;session adapter 不得裸写 `qi_current` 或 zone qi。 + +### 2.2 生命周期顺序 + +**断线**: + +1. `SessionRegistry` 在 `despawn_disconnected_clients` 及 R3 player save 前接收 disconnect。 +2. `Checkpointed`:停止 tick → 写 checkpoint → 解绑 runtime `Entity` → 保持 stable owner/设施 claim → `Suspended`。 +3. `Volatile`:停止 tick → 按非自愿中断结算 escrow/refund → 释放 target/busy → `Terminal`。 +4. 完成上述变更后,R3 才保存 player slices,随后 Valence despawn。 + +**跨维**: + +1. 在 `world/dimension_transfer` 写入新 layer/position 前停止接收该 owner 的 session 请求。 +2. 对 dimension-scoped session 执行非自愿 teardown 与返还;释放 busy/target。 +3. teardown 成功后才应用维度转移。失败必须 fail closed,不能让旧维 session 跟随玩家进入新维度。 + +**关服**: + +1. 关闭新 session intake;先结算 `AwaitingDelivery`。 +2. `Checkpointed` 调 `on_shutdown` 生成 R3 checkpoint;`Volatile` 在玩家与 inventory 仍可访问时 teardown。 +3. lifecycle registry 静止后才执行 R3 `flush_on_shutdown`。R3 在 `plan-refactor-persistence-slices-v1.md` 接入面冻结的 `load(guarded) / autosave / flush_on_shutdown / tick_rebase` 是唯一持久化出口。 + +**重连**: + +1. R3 guarded load 先恢复 checkpoint,再由 registry 以 `PlayerKey` 绑定新 `Entity`。 +2. adapter 重验设施/target 存在、owner、维度和版本;通过后恢复为 `Paused` 或 `Running` 并 hydrate client。 +3. 恢复失败按 `InvalidRestore` 非自愿结案;有 escrow 时必须返还/交付。busy 冲突时保留 `Suspended` 并 fail closed,不得静默覆盖另一 session。 + +### 2.3 durability 决议矩阵 + +| 状态族 | durability | 断线/关服 | 跨维 | +|---|---|---|---| +| craft | `Checkpointed` | 保存进度、批次与 escrow;重连保持 paused,显式 reopen 后 resume | 非自愿 teardown,全退未消费 escrow | +| alchemy furnace/session | `Checkpointed` | furnace + session 原子 checkpoint,不离线推进 | 非自愿 teardown;退款/产物先落交付事务 | +| forge station/session | `Checkpointed` | station + session + 已扣材料原子 checkpoint | 非自愿 teardown;不得遗留 `station.session` | +| gathering | `Volatile` | teardown | teardown | +| lingtian actor sessions | `Volatile` | teardown | teardown | +| mineral | `Volatile` | teardown,解除 ore claim | teardown | +| spiritwood | `Volatile` | teardown,清 session 与 `settling` | teardown | +| external container / TSY search / extract | `Volatile` | 清 owner、进度与 target claim | teardown | +| `PendingInsightOffer` | `Volatile` + deadline | disconnect/timeout 清除 | 清除 | +| `TsyPresence` | R3 checkpointed auxiliary state | restore 后再开放 TSY 请求 | 由 transport 事务显式 enter/exit | + +后续某个 volatile 域若改成“起 session 即预扣不可重建资源”,必须先把该域改为 `Checkpointed` 或证明 teardown 可无损返还;不得维持默认易失再补日志。 + +### 2.4 busy 语义 + +- `SessionRegistry::try_acquire(BusyClaim)` 是唯一生产入口;各域私有 `has_session` 只能作为迁移期断言,P3 结束时删除。 +- busy 至少区分 player-exclusive、target-exclusive、facility-exclusive,并由集中 conflict matrix 判断;不能以“两个不同 Component 可以共存”代表允许并发。 +- TSY `Search` 与 `Extract` 必须双向冲突:搜刮中拒绝撤离,撤离中拒绝搜刮;取消/完成/断线/跨维均释放两侧 claim。 +- persistent session `Suspended` 时保留其逻辑 facility/escrow claim,避免其他玩家覆盖炉/站状态;runtime `Entity` binding 必须释放。 +- R4 的 `GateSpec` 消费 R1 busy 查询 API。距离、维度、所有权检查仍由 R4 实现,R1 不修改 `client_request_handler.rs`。 + +### 2.5 pause、cancel、refund 与 delivery + +- screen close 是 `Pause`,不是 `VoluntaryCancel`。当前 wire 只有 `CraftCancel`(`proto/bong/envelope.proto:257,1309`),P1 必须与 R6/R4 协调显式 open/pause/resume intent;不能只删除 client cancel 后让 server 继续 tick。 +- `VoluntaryCancel` 保留域内已公开的经济规则,例如 craft 未完成部分返还 70%;UI 必须有明确取消动作,不能由 Esc、断线、跨维或关服冒充。 +- `Disconnect`、`DimensionChange`、`Shutdown`、`InvalidRestore` 都是非自愿中断:未消费 escrow 全退,不按进度罚损。已经不可逆完成的产物走 delivery,不把 inputs/output 双发。 +- refund 也走 R10 delivery 垫层;满包不得退化为日志告警。R10 冻结的 `deliver(items) -> Delivered | Spilled(fallback)` 见 `plan-refactor-inventory-core-v1.md` 接入面。 + +## 3.1 决议(原开放问题 §N.1) + +### 决议 1:不是“全部持久化”或“复制 craft 表”二选一,而是显式 durability + R3 单一出口 + +- **明确结论**:需要重连/重启恢复的 session 全部注册为 R3 Slice/registry checkpoint;短时可无损结束的 session 显式标 `Volatile`。禁止为每个域复制 craft 私表 + join 自愈代码。 +- **实施方案**:craft 作为 P1 adapter,把现有表/hydration 行为收进 R3 暴露的 guarded load/flush API;P2 的 alchemy/forge 用同一 API;其余按 §2.3 teardown。join hydration 由 `on_reconnect` 统一触发。 +- **边界与拒绝理由**:全部 session 持久化会把 Entity/短时 target claim 写进 DB,扩大 stale restore 面;全部易失会丢已扣材料和长进度;复制 craft 表会继续制造七套 migration、flush 与 hydration。R1 不实现 `persistence/**`,只消费 R3 API。 +- **双锚点**:本 plan §2.2-§2.3;`plan-refactor-persistence-slices-v1.md` 接入面(`load(guarded) / autosave / flush_on_shutdown / tick_rebase`);现有 join 基准 `server/src/network/craft_emit.rs:884`。 + +### 决议 2:非自愿中断全退,主动取消沿用域内公开规则 + +- **明确结论**:断线、跨维、关服、restore 失败不得施加进度折损;未消费 escrow 全退。只有玩家明确触发 `VoluntaryCancel` 时,才执行域内既有损耗规则。 +- **实施方案**:所有 adapter 以 `TerminationCause` 分支;refund/output 经 R10 delivery,qi 经 ledger。craft 保留 70% 主动取消规则,但关屏只 pause。 +- **边界与拒绝理由**:统一按进度折损会让网络故障与服务器维护成为可重复的无责资源损失,也无法跨炼丹/锻造/采集定义同一“进度价值”;一律全退主动取消又会移除既有经济成本并制造取消套利。因此按 cause 而不是按 session 百分比统一。 +- **双锚点**:本 plan §2.1、§2.5;`plan-craft-close-pause-loss-v1.md` P0-P3;当前先 teardown 后 grant 反例 `server/src/network/client_request_handler.rs:16764-16861`;R10 `plan-refactor-inventory-core-v1.md` 接入面。 + +## 4. 吸收清单验真(2026-08-03) + +| plan 短名 | P0 裁决 | R1 处理 | +|---|---|---| +| craft-close-pause-loss | **真缺陷**:client 关屏发 cancel,server 无 paused gate | P1 首宿主冻结 `Running/Paused` 与显式 cancel;跨端 intent 由 R6/R4 接缝 | +| craft-session-reconnect-lock | **已闭环只归档**:CraftStore 已登记 disconnect clear;join 同发 idle/active session state | 不重复实现,以现有 hydration 作为 `on_reconnect` 基准 | +| placed-container-session-lifecycle-gap | **部分闭环**:断线清锁已有测试;跨维 owner lock 仍缺 | P3 收编 lifecycle/lock teardown;请求距离门归 R4 | +| tsy-container-disconnect-lock-leak | **真缺陷**:search progress/`searched_by` 无统一 disconnect cleanup | P3 收编 volatile target claim | +| tsy-search-extract-concurrent-busy | **真缺陷**:search/extract 只查各自进度 | P3 以集中 conflict matrix 双向互斥 | +| world-transport-tsy-relog-presence | **真缺陷**:位置/维度持久化而 `TsyPresence` 未同事务恢复 | R1 定 transport 生命周期,R3 保存 auxiliary state | +| client-insight-offer-strand | **部分真实**:server pending 只有 chosen/reject 清理,无 deadline;client modal 属 R7 | R1 收编 server timeout/teardown;R7 处理展示 | +| alchemy-furnace-persistence | **真缺陷**:furnace/session 仍为内存权威 | P2 adapter + R3 checkpoint | +| alchemy-takeback-full-inventory-loss | **真缺陷**:先 `end_session` 后 grant | R1 改 teardown/commit 顺序;R10 提供 delivery | +| forge-c2s-session-wiring | **已闭环只归档**:start session 与 blueprint page 已真实分发 | 不重复实现 | +| bot-handcraft-craft-outcome-timeout | **旧证据不足,不形成 R1 owner**:报告来自脏 debug server;当前已有 `scripts/bot/scenarios/production_handcraft_stone_knife.py` | P4 仍跑 clean-main craft bot,失败再以新证据立 owner | +| forge-outcome-full-inventory-loss | **真缺陷、R10 主责**:满包只有 `grant skipped` | R1 只提供 AwaitingDelivery→CommitTerminal 合同;R10 实现 fallback | +| lingtian-session-disconnect-server | **真缺陷**:Resource 持续 tick,未按 actor 断线清理 | P2 迁移六类 actor session | + +### 4.1 覆盖审计差分 + +按总纲 §6 要求枚举 active/skeleton 的 session、disconnect、busy、container、full-inventory 与 dimension-gate 候选后: + +- `forge-session-range-dimension-gate` 已由 `plan-refactor-c2s-gate-v1.md` 吸收清单明确登记为在飞项;实现落在 R4 的 `GateSpec`/`client_request_handler.rs`,不追加 R1 owner。R1 的 `on_dimension_change` 与 busy API 仅供 R4 查询。 +- `alchemy-furnace-scope-gate` 同样已由 R4 吸收;R1 负责炉 session 生命周期,不复制距离/维度 gate。 +- `forge-session-enum-unstripped` 是 R6/client bridge 契约修复,不是 server session 生命周期。 +- `tsy-extract-disconnect-stale`、`woliu-vortex-disconnect-residue`、`niche-guardian-cross-session-leak` 及其他 client `*Store` 残留归 R2/R7,不进入 R1 文件域。 +- 本轮未发现应新增到 R1 权威吸收清单、但尚无 owner 的 plan。 + +## 5. 文件所有权与接缝 + +- **R1 独占**:`server/src/session/`、七域 `session.rs`、`network/craft_emit.rs` 的 session tick 区,以及迁移时删除的域内私有生命周期代码。 +- **R3 独占**:`server/src/persistence/**`、player load/autosave/shutdown flush;R1 仅消费 checkpoint/restore/flush hook。 +- **R4 独占**:`server/src/network/client_request_handler.rs` 与 `network/gate/`;R1 仅暴露 busy/session query。 +- **R6 独占**:proto/S2C bridge 与跨端 pause/resume 契约变更。 +- **R7/R2 独占**:client Screen/HUD 与 Store disconnect 清理。 +- **R10 独占**:`server/src/inventory/**` 与 `InventoryTxn::deliver`;R1 只决定何时允许 commit teardown。 +- R3 P1 合入后才能进入 R1 implementation wave;P1 可先落 trait/registry 与 craft adapter,但不得复制临时持久层。 + +## 6. 后续阶段交付物 + +### P1 — 框架 + craft 首宿主 + +- 新增 `server/src/session/{mod.rs,registry.rs,lifecycle.rs}`,包含 §2 全部 symbol。 +- craft 迁移到 `SessionRegistry`;关闭 screen pause、显式 cancel、重开 resume,现有 recipe/session join hydration 行为不变。 +- contract pins:五态转换、stable owner 重绑、disconnect-before-save、dimension-before-transfer、busy 冲突与 delivery commit gate。 + +### P2 — alchemy / forge / lingtian + +- alchemy furnace/session 与 forge station/session 原子 checkpoint;修复先 teardown 后 delivery。 +- 灵田六类 `ActiveSession` 共用 volatile adapter;断线/跨维/关服不再 tick 或结算离线 actor。 +- qi refund/release 测试从 `SPIRIT_QI_TOTAL` 与 ledger 不变量取值,不写新物理常数。 + +### P3 — gathering / mineral / spiritwood / 世界交互 + +- 删除三域私有 store 生命周期分支,迁入 registry。 +- external container、TSY search/extract 使用 target claim;所有终态释放 busy。 +- `TsyPresence` 与 player position/dimension guarded restore 对拍,不再出现“人在 TSY、presence 不在”。 + +### P4 — bot e2e + 归档 + +加入并常绿: + +1. `session_disconnect_cleanup` +2. `session_dimension_transfer` +3. `session_restart_recovery` +4. `session_busy_mutex` +5. `session_full_inventory_delivery` + +另回归现有 `production_craft_disconnect_resume.py`、`production_craft_cancel_full_inventory_refund.py`、`production_handcraft_stone_knife.py`。对应 implementation PR 合入后,按总纲 §7 为吸收项补 Finish Evidence 并做每轨一次 docs-only 批量归档。 diff --git a/docs/plans-skeleton/plan-refactor-server-session-v1.md b/docs/plans-skeleton/plan-refactor-server-session-v1.md deleted file mode 100644 index 1d64b503e..000000000 --- a/docs/plans-skeleton/plan-refactor-server-session-v1.md +++ /dev/null @@ -1,49 +0,0 @@ -# plan-refactor-server-session-v1 — Server 交互 Session 统一生命周期框架(重构轨 R1) - -> 所属总纲:`plan-refactor-master-v1.md`。一句话:把 7 套复制粘贴的 server 端交互 session(炼丹/手搓/锻造/采集/灵田/矿脉/灵木)收敛到一个统一的 Session 生命周期框架——断线清理、跨维清理、重启恢复/显式易失声明、忙态互斥、满包产物交付,全部只写一份。 - -## 现状证据(2026-07-27 侦察) - -- 7 个功能域各自手写 session store/状态机:`alchemy/session.rs`、`craft/session.rs`、`forge/session.rs:157-208`、`gathering/session.rs:57`、`lingtian/session.rs:79-339`(6 个独立 session struct)、`mineral/session.rs`、`spiritwood/session.rs`。全仓 `SessionManager` 零命中。 -- 断线检测唯一手段 `RemovedComponents` 仅 9 个文件在用,**上述 7 个 session 模块全部不在其中**——玩家断线时进行中 session 不清理。 -- `world/dimension_transfer.rs:34` 跨维只改 `EntityLayerId`/Position,不触碰任何 session。 -- 唯一较完整的范本是 craft:`CraftSession` 是玩家实体 Component、`tick_craft_sessions` 带 `With` 过滤、有 `player_craft_sessions` 持久化表 + join 首包 idle 自愈(`network/craft_emit.rs:541,2759`)。其余 6 域既无断线清理也无持久化。 - -## 接入面 - -- **进料**:玩家实体(`With` / `RemovedComponents`)、`world/dimension_transfer`、R3 的持久化 slice 框架(flush/restore 钩子)、R4 的 gate 中间件(session 开启前置校验)。 -- **出料**:各域 session 状态 → 既有 `*_emit.rs` S2C 事件(craft_session_state 等契约不变);session 终止时的产物/材料返还 → `inventory`(满包走 R10 的统一交付垫层)。 -- **共享类型**:新 `server/src/session/`(`trait InteractionSession` + `SessionRegistry`),craft 现有行为是语义基准,不另造平行概念。 -- **跨仓库契约**:不改 wire 形状;client 端对应的 store 清理归 R2。 -- **qi_physics 锚点**:session 中断/取消涉及已扣真元的返还必须走 `qi_physics::ledger`(对齐 R5,禁止各域自写返还)。 - -## 阶段 - -- ⬜ P0 设计收口 + 吸收清单验真:逐个复读吸收清单里的 skeleton,确认仍是真缺陷;冻结 `InteractionSession` trait(生命周期钩子:`on_disconnect` / `on_dimension_change` / `on_shutdown`(接 R3) / `on_reconnect` / busy 互斥语义 / 产物交付语义);写 §N.1 决议。 -- ⬜ P1 框架落地:`server/src/session/` + craft 迁移为第一个宿主(行为不变,bot 场景锁住);断线/跨维/重启三类生命周期系统统一注册。 -- ⬜ P2 迁移批次 A:alchemy、forge、lingtian(含 #1294 在飞 skeleton 对应的 forge/lingtian session 缺陷一并消灭)。 -- ⬜ P3 迁移批次 B + 删旧:gathering、mineral、spiritwood、placed-container/tsy 容器占锁;删除各域私有生命周期代码,不留兼容层。 -- ⬜ P4 bot 验收 + 归档:新增 bot 场景全绿;被吸收 plan 批量归档(docs-only PR,Finish Evidence 指向本轨 PR + bot 场景)。 - -## 吸收清单(促升时 P0 逐个验真,短名省略 plan-bughunt- 前缀与 -v1 后缀) - -skeleton:craft-close-pause-loss、craft-session-reconnect-lock、placed-container-session-lifecycle-gap、tsy-container-disconnect-lock-leak、tsy-search-extract-concurrent-busy、world-transport-tsy-relog-presence、client-insight-offer-strand(server 侧会话悬挂部分;client 弹窗部分归 R7)、alchemy-furnace-persistence(session 持久化经 R3 钩子)、alchemy-takeback-full-inventory-loss(teardown 顺序;满包交付垫层归 R10)、forge-c2s-session-wiring、bot-handcraft-craft-outcome-timeout;在飞 #1294:forge-outcome-full-inventory-loss、lingtian-session-disconnect-server。 - -## 文件所有权与边界 - -- 独占:`server/src/session/`(新)、7 个域的 `session.rs`、`network/craft_emit.rs` 的 session tick 区。 -- 不碰:`persistence/mod.rs`(R3 域,经它暴露的钩子接入)、`client_request_handler.rs`(R4 域)、client 一切(R2/R7 域)。 -- 依赖:R3 P1(flush/restore 钩子)落地后本轨 P2 才开;P0/P1 可先行。 - -## bot 验收场景(加入 scripts/bot/scenarios/) - -1. `session_disconnect_cleanup`:bot 起炉/开工作台后断线 → 重连 → 断言 session 已清理或正确恢复(按域语义),无幽灵占锁。 -2. `session_dimension_transfer`:交互中跨维 → 断言 session 终止 + 材料按规则返还(守恒过 ledger)。 -3. `session_restart_recovery`:交互中关服重启 → 断言持久化域恢复、易失域干净终止不丢材料。 -4. `session_busy_mutex`:并发发起互斥交互(搜刮中撤离)→ 断言忙态拒绝。 -5. `session_full_inventory_delivery`:满包完成 session → 断言产物不丢(联动 R10)。 - -## 开放问题(pre-P0 收口) - -1. session 持久化的粒度:全部入 R3 slice,还是 craft 模式(表 + join 自愈)推广? -2. 中断返还的统一策略:材料全退 / 按进度折损?涉及 worldview 经济锚点,需人工拍板。 From 15a04de3da7bbb7aaf1ffc32a8fd769c262c255b Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Mon, 3 Aug 2026 11:18:29 +0800 Subject: [PATCH 02/14] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20R1=20=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E7=94=9F=E5=91=BD=E5=91=A8=E6=9C=9F=E4=B8=8E=E8=B7=A8?= =?UTF-8?q?=E8=BD=A8=E6=94=BE=E8=A1=8C=E5=A5=91=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 冻结有限 suspension 回收和 termination cause 经济矩阵,并为 craft wire、delivery 与 TSY presence 持久化建立真实生产依赖,避免各轨提前宣称闭环。 Model: cc-sonnet-high Co-Authored-By: Claude --- ...plan-refactor-client-store-lifecycle-v1.md | 2 +- docs/plan-refactor-server-session-v1.md | 67 +++++++++++++------ .../plan-refactor-c2s-gate-v1.md | 6 +- .../plan-refactor-client-ui-base-v1.md | 6 +- .../plan-refactor-inventory-core-v1.md | 6 +- .../plans-skeleton/plan-refactor-master-v1.md | 4 +- .../plan-refactor-persistence-slices-v1.md | 10 +-- .../plan-refactor-wire-s2c-v1.md | 6 +- 8 files changed, 69 insertions(+), 38 deletions(-) diff --git a/docs/plan-refactor-client-store-lifecycle-v1.md b/docs/plan-refactor-client-store-lifecycle-v1.md index 394b29814..fb355b668 100644 --- a/docs/plan-refactor-client-store-lifecycle-v1.md +++ b/docs/plan-refactor-client-store-lifecycle-v1.md @@ -24,7 +24,7 @@ - **模块 / symbol**:`client/src/main/java/com/bong/client/lifecycle/{ClientStoreScopeManifest,SessionScopedStore,SessionStoreHandle,SessionScopedStoreRegistry}.java`;`SessionScopedStore.clearOnDisconnect()`;`SessionStoreHandle.forStore(...)`;`SessionScopedStoreRegistry.clearAllOnDisconnect()`。 - **测试抓手**:`ClientStoreScopeManifestTest` 精确 pin 108 个 Store 的三分类、106 个 session Store、`ClientConnectionStatusStore` 外部 token 管理与 P0 空 registry;`SessionScopedStoreRegistryTest` pin 声明顺序、重复 FQCN fail-fast、Store / reporter 异常隔离和 `Error` 透传。 - **跨仓库契约**:纯 client 生命周期基础设施;schema、Redis key、CustomPayload 均无新增或变更。 -- ⬜ **P1 在册 Store 平移**:把当前 `clearClientStateOnDisconnect()` 中已存在的 Store 清理逐项迁入 registry;断线 helper 改为调用 registry 一次。非 Store 的 renderer / handler / ambience 等生命周期 hook 继续由 helper 显式拥有,P1 不借重构删除它们。 +- ⬜ **P1 在册 Store 平移**:把当前 `clearClientStateOnDisconnect()` 中已存在的 Store 清理逐项迁入 registry;断线 helper 改为调用 registry 一次。非 Store 的 renderer / handler / ambience 等生命周期 hook 继续由 helper 显式拥有,P1 不借重构删除它们。R1 craft 接缝以本阶段已登记的 `CraftStore` 为唯一 client session-state owner;R7 P2 只消费该 Store 实现 close/pause/reopen/resume,不再建立第二份状态。 - **模块 / symbol**:`client/src/main/java/com/bong/client/lifecycle/SessionScopedStoreRegistry.java` 的显式 `REGISTERED`;`client/src/main/java/com/bong/client/BongNetworkHandler.java` 的 `disconnectSession(...)` / `clearClientStateOnDisconnect()`。 - **测试抓手**:`ClientStoreScopeManifestTest` 精确 pin P1 已迁移 FQCN 集;`BongNetworkHandlerTest` pin token invalidation 先于 registry、迟到旧 handler 不清新 session、非 Store hook 仍保留;每个 adapter 以目标 Store 的状态级行为测试证明 method reference 未错绑。 - **跨仓库契约**:保持现有 wire、schema、Redis key 与 CustomPayload 不变。 diff --git a/docs/plan-refactor-server-session-v1.md b/docs/plan-refactor-server-session-v1.md index e0b473018..2a7135004 100644 --- a/docs/plan-refactor-server-session-v1.md +++ b/docs/plan-refactor-server-session-v1.md @@ -70,14 +70,15 @@ pub trait InteractionSession { - `PlayerKey`:canonical player id;`Entity` 只作当前连接的 runtime binding。 - `SessionDurability::{Checkpointed, Volatile}`:每个 adapter 注册时必须显式声明,不允许默认值。 - `SessionPhase::{Running, Paused, Suspended, AwaitingDelivery, Terminal}`:同一 session 不允许同时处于多个 phase。 -- `TerminationCause::{VoluntaryCancel, Disconnect, DimensionChange, Shutdown, InvalidRestore}`。 +- `TerminationCause::{VoluntaryCancel, Disconnect, DimensionChange, Shutdown, InvalidRestore, SuspensionExpired}`。 +- `SuspensionPolicy`:声明 checkpointed session 的 `suspended_at_tick`、有限 `max_suspended_ticks`、过期扫描周期、管理员强制结案入口和 facility claim 回收动作;默认值冻结为 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000`(20 TPS 下 24 小时)与 `SESSION_SUSPENSION_SCAN_INTERVAL_TICKS = 1_200`,不得配置为无限期保留。 - `SessionTransition::{Keep, Pause, SuspendAndCheckpoint, Teardown, AwaitDelivery, CommitTerminal}`。 - `BusyClaim`:声明 owner 与 world target 上占用的 busy classes;冲突矩阵集中注册。 ### 2.1 不变量 1. `SessionRegistry` 中一个 `SessionKey` 只能有一个 owner;一个 runtime `Entity` 只能绑定同一 `PlayerKey` 的 session。 -2. `Checkpointed` session 断线/关服后进入 `Suspended`,不得离线推进;重连通过 R3 guarded restore 后才重新绑定 `Entity`。 +2. `Checkpointed` session 断线/关服后进入 `Suspended`,不得离线推进;必须记录 `suspended_at_tick` 并受 `SuspensionPolicy.max_suspended_ticks` 约束。重连通过 R3 guarded restore 后才重新绑定 `Entity`;超过 TTL 或管理员结案时转入 `InvalidRestore`/`SuspensionExpired` 结算,先完成 escrow delivery/refund,再释放 facility/target claim 并进入 `Terminal`。过期扫描和管理员入口必须幂等,且不会等待 owner 重连。 3. `Volatile` session 遇到断线、跨维或关服必须在同一生命周期门内 teardown;不得留下 owner entity、设施锁、target claim 或 `settling` 标记。 4. 所有 dimension-scoped session 在维度切换前终止。`TsyPresence` 是 transport 辅助状态,单独 checkpoint/restore,不得用“保留旧交互 session”修复 presence 撕裂。 5. client 的 screen/store 只能改善 UX,不能授予 session 或 busy 权限;恶意包、重复包和同 tick 竞态最终都由 registry 拒绝。 @@ -89,14 +90,14 @@ pub trait InteractionSession { **断线**: 1. `SessionRegistry` 在 `despawn_disconnected_clients` 及 R3 player save 前接收 disconnect。 -2. `Checkpointed`:停止 tick → 写 checkpoint → 解绑 runtime `Entity` → 保持 stable owner/设施 claim → `Suspended`。 +2. `Checkpointed`:停止 tick → 写 checkpoint(包含 escrow,但不执行 refund)→ 解绑 runtime `Entity` → 保持 stable owner/设施 claim → `Suspended`。只有恢复窗口结束、管理员结案或恢复校验失败时,才按对应终止原因进入 delivery/refund 结算。 3. `Volatile`:停止 tick → 按非自愿中断结算 escrow/refund → 释放 target/busy → `Terminal`。 4. 完成上述变更后,R3 才保存 player slices,随后 Valence despawn。 **跨维**: 1. 在 `world/dimension_transfer` 写入新 layer/position 前停止接收该 owner 的 session 请求。 -2. 对 dimension-scoped session 执行非自愿 teardown 与返还;释放 busy/target。 +2. 对 dimension-scoped session 执行非自愿 teardown 与返还;释放 busy/target。`Checkpointed` session 在跨维时不进入 `Suspended`,而是终止并全退未消费 escrow;`Disconnect`/`Shutdown` 的 checkpoint 保留语义不适用于跨维。 3. teardown 成功后才应用维度转移。失败必须 fail closed,不能让旧维 session 跟随玩家进入新维度。 **关服**: @@ -111,20 +112,31 @@ pub trait InteractionSession { 2. adapter 重验设施/target 存在、owner、维度和版本;通过后恢复为 `Paused` 或 `Running` 并 hydrate client。 3. 恢复失败按 `InvalidRestore` 非自愿结案;有 escrow 时必须返还/交付。busy 冲突时保留 `Suspended` 并 fail closed,不得静默覆盖另一 session。 +### 2.2.1 Suspended retention and reclamation + +`Checkpointed → Suspended` 是为短期断线/重启恢复服务的有限窗口,不是永久租约。P1 必须冻结并实现: + +- 每个 checkpoint 写入 `suspended_at_tick`、`last_rebase_epoch`、owner `PlayerKey`、facility/target claim 和 escrow 摘要;不得只靠数据库行存在判断是否仍可恢复。 +- 每个宿主显式选择 `SuspensionPolicy.max_suspended_ticks`;P1 craft、P2 alchemy/forge 使用冻结默认值 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000`,扫描 cadence 为 `SESSION_SUSPENSION_SCAN_INTERVAL_TICKS = 1_200`。R3 `tick_rebase` 保持剩余 TTL 的相对时长,关服时间不得让 lease 永久延长;P3 世界交互不得继承无限值。 +- registry 每 tick 或固定 cadence 扫描过期 session;管理员/维护命令可提前结案。扫描、管理员结案、重连与关服 flush 竞争时以 `SessionKey` CAS/registry lock 保证一次结算。 +- 过期结案按 `TerminationCause::SuspensionExpired` 执行:停止 tick → 经 R10 `deliver` 返还未消费 escrow/交付已完成产物 → 清理 checkpoint → 释放 facility/target/busy claim → `Terminal`。R10 不可用或 delivery 未提交时保留 `AwaitingDelivery` 和 claim,重试不得重复退款。 +- claim 回收必须有审计事件(session key、owner key hash、cause、released claims、delivery result),不记录原 payload;恢复窗口内仍允许 owner 正常 reopen,过期后 reopen 明确拒绝。 + +P1/P4 必须覆盖:永不重连的 abandoned session 最终释放设施、TTL 边界前后、管理员结案、过期与重连竞态、重复扫描幂等、delivery 失败重试,以及回收后另一玩家可获得同一 furnace/station claim。 ### 2.3 durability 决议矩阵 | 状态族 | durability | 断线/关服 | 跨维 | |---|---|---|---| -| craft | `Checkpointed` | 保存进度、批次与 escrow;重连保持 paused,显式 reopen 后 resume | 非自愿 teardown,全退未消费 escrow | -| alchemy furnace/session | `Checkpointed` | furnace + session 原子 checkpoint,不离线推进 | 非自愿 teardown;退款/产物先落交付事务 | -| forge station/session | `Checkpointed` | station + session + 已扣材料原子 checkpoint | 非自愿 teardown;不得遗留 `station.session` | +| craft | `Checkpointed` | 保存进度、批次与 escrow;重连保持 paused,显式 reopen 后 resume;到 TTL 结算 `SuspensionExpired` | 非自愿 teardown,全退未消费 escrow | +| alchemy furnace/session | `Checkpointed` | furnace + session 原子 checkpoint,不离线推进;到 TTL 结算并回收 facility claim | 非自愿 teardown;退款/产物先落交付事务 | +| forge station/session | `Checkpointed` | station + session + 已扣材料原子 checkpoint;到 TTL 结算并回收 facility claim | 非自愿 teardown;不得遗留 `station.session` | | gathering | `Volatile` | teardown | teardown | | lingtian actor sessions | `Volatile` | teardown | teardown | | mineral | `Volatile` | teardown,解除 ore claim | teardown | | spiritwood | `Volatile` | teardown,清 session 与 `settling` | teardown | | external container / TSY search / extract | `Volatile` | 清 owner、进度与 target claim | teardown | | `PendingInsightOffer` | `Volatile` + deadline | disconnect/timeout 清除 | 清除 | -| `TsyPresence` | R3 checkpointed auxiliary state | restore 后再开放 TSY 请求 | 由 transport 事务显式 enter/exit | +| `TsyPresence` | R3 checkpointed auxiliary state | R3 Slice 保存 `family_id`、`entered_at_tick`、`return_to` 与版本;guarded restore 通过后才重新 attach component、开放 TSY 请求 | 由 transport 事务显式 enter/exit | 后续某个 volatile 域若改成“起 session 即预扣不可重建资源”,必须先把该域改为 `Checkpointed` 或证明 teardown 可无损返还;不得维持默认易失再补日志。 @@ -140,7 +152,14 @@ pub trait InteractionSession { - screen close 是 `Pause`,不是 `VoluntaryCancel`。当前 wire 只有 `CraftCancel`(`proto/bong/envelope.proto:257,1309`),P1 必须与 R6/R4 协调显式 open/pause/resume intent;不能只删除 client cancel 后让 server 继续 tick。 - `VoluntaryCancel` 保留域内已公开的经济规则,例如 craft 未完成部分返还 70%;UI 必须有明确取消动作,不能由 Esc、断线、跨维或关服冒充。 -- `Disconnect`、`DimensionChange`、`Shutdown`、`InvalidRestore` 都是非自愿中断:未消费 escrow 全退,不按进度罚损。已经不可逆完成的产物走 delivery,不把 inputs/output 双发。 +- cause/refund matrix 固定如下,adapter 与 contract pin 必须逐项覆盖: + - `VoluntaryCancel`:按域内公开规则结算;craft 未完成部分返还 70%。 + - `Disconnect`:`Checkpointed` 进入 `Suspended`,不退款,checkpoint 保留未消费 escrow;`Volatile` 全退并终止。 + - `DimensionChange`:dimension-scoped session 全退未消费 escrow,经 delivery 后终止,不进入 `Suspended`。 + - `Shutdown`:`Checkpointed` 进入 `Suspended`,不退款,checkpoint 保留未消费 escrow;`Volatile` 在 inventory 可访问时全退并终止。 + - `InvalidRestore`:全退未消费 escrow;已完成产物只走一次 delivery,不与 inputs 双发。 + - `SuspensionExpired`:全退未消费 escrow/交付已完成产物,delivery 成功后释放 claim 并终止。 +- `Disconnect`/`Shutdown` 的 checkpoint 保留与 terminal cause 的 refund 互斥;任何 delivery 未提交时保持 `AwaitingDelivery` 和 claim,禁止清 session 或重复退款。 - refund 也走 R10 delivery 垫层;满包不得退化为日志告警。R10 冻结的 `deliver(items) -> Delivered | Spilled(fallback)` 见 `plan-refactor-inventory-core-v1.md` 接入面。 ## 3.1 决议(原开放问题 §N.1) @@ -152,11 +171,11 @@ pub trait InteractionSession { - **边界与拒绝理由**:全部 session 持久化会把 Entity/短时 target claim 写进 DB,扩大 stale restore 面;全部易失会丢已扣材料和长进度;复制 craft 表会继续制造七套 migration、flush 与 hydration。R1 不实现 `persistence/**`,只消费 R3 API。 - **双锚点**:本 plan §2.2-§2.3;`plan-refactor-persistence-slices-v1.md` 接入面(`load(guarded) / autosave / flush_on_shutdown / tick_rebase`);现有 join 基准 `server/src/network/craft_emit.rs:884`。 -### 决议 2:非自愿中断全退,主动取消沿用域内公开规则 +### 决议 2:Checkpointed 暂停保留 escrow;terminal 非自愿中断全退 -- **明确结论**:断线、跨维、关服、restore 失败不得施加进度折损;未消费 escrow 全退。只有玩家明确触发 `VoluntaryCancel` 时,才执行域内既有损耗规则。 -- **实施方案**:所有 adapter 以 `TerminationCause` 分支;refund/output 经 R10 delivery,qi 经 ledger。craft 保留 70% 主动取消规则,但关屏只 pause。 -- **边界与拒绝理由**:统一按进度折损会让网络故障与服务器维护成为可重复的无责资源损失,也无法跨炼丹/锻造/采集定义同一“进度价值”;一律全退主动取消又会移除既有经济成本并制造取消套利。因此按 cause 而不是按 session 百分比统一。 +- **明确结论**:`Checkpointed` 的 `Disconnect`/`Shutdown` 是可恢复暂停,不退款,未消费 escrow 与 checkpoint 一起保留;`DimensionChange`、`InvalidRestore`、`SuspensionExpired` 等 terminal 非自愿原因才全退未消费 escrow。`Volatile` 遇到非自愿中断立即全退并终止。只有玩家明确触发 `VoluntaryCancel` 时,才执行域内既有损耗规则。 +- **实施方案**:所有 adapter 以 §2.5 的完整 `TerminationCause` matrix 分支;checkpoint 和 refund 在同一次 transition 中互斥;refund/output 经 R10 delivery,qi 经 ledger。craft 保留 70% 主动取消规则,但关屏只 pause。 +- **边界与拒绝理由**:若断线/关服同时 checkpoint escrow 与退款,会在恢复时双发;若一律把断线变 terminal,又会破坏短期恢复。terminal 非自愿原因统一全退,避免网络故障、跨维强制 teardown 或恢复失败施加进度折损;主动取消保留成本,避免套利。 - **双锚点**:本 plan §2.1、§2.5;`plan-craft-close-pause-loss-v1.md` P0-P3;当前先 teardown 后 grant 反例 `server/src/network/client_request_handler.rs:16764-16861`;R10 `plan-refactor-inventory-core-v1.md` 接入面。 ## 4. 吸收清单验真(2026-08-03) @@ -195,19 +214,25 @@ pub trait InteractionSession { - **R6 独占**:proto/S2C bridge 与跨端 pause/resume 契约变更。 - **R7/R2 独占**:client Screen/HUD 与 Store disconnect 清理。 - **R10 独占**:`server/src/inventory/**` 与 `InventoryTxn::deliver`;R1 只决定何时允许 commit teardown。 -- R3 P1 合入后才能进入 R1 implementation wave;P1 可先落 trait/registry 与 craft adapter,但不得复制临时持久层。 +- **阶段放行矩阵**: + - framework-only:R3 P1 合入后可落 `InteractionSession`、registry、lifecycle 与不触达生产 wire/delivery 的 contract pins。 + - craft producer path:R6 P1 交付 `CraftOpen`/`CraftPause`/`CraftResume` proto/schema/bridge → R4 P1 交付 production decode/dispatch 与 owner/phase/busy gate → R7 P2 交付 close/pause、显式 cancel、reopen/resume UI producer/consumer(R2 P1 已登记的 `CraftStore` 继续提供 disconnect lifecycle)→ R10 P1 冻结 `deliver` commit contract,且 R10 P2 把 craft production 调用点迁入 `InventoryTxn::deliver`;全部合入后 R1 才能启用并验收 craft adapter。 + - alchemy/forge:还须 R10 P2 将两域 production delivery 调用点迁入 `InventoryTxn::deliver`,否则只能停在 `AwaitingDelivery`。 + - `TsyPresence`:R3 P1 注册 auxiliary Slice,且 R3 P4 restore parity 常绿后,R1 P3 才能 attach 新 runtime `Entity` 并开放 TSY 请求。 +- 禁止用 mock、registry 单测或临时持久层越过上述门宣称端到端完成;依赖未齐时 phase 保持未完成。 ## 6. 后续阶段交付物 ### P1 — 框架 + craft 首宿主 -- 新增 `server/src/session/{mod.rs,registry.rs,lifecycle.rs}`,包含 §2 全部 symbol。 -- craft 迁移到 `SessionRegistry`;关闭 screen pause、显式 cancel、重开 resume,现有 recipe/session join hydration 行为不变。 -- contract pins:五态转换、stable owner 重绑、disconnect-before-save、dimension-before-transfer、busy 冲突与 delivery commit gate。 +- 新增 `server/src/session/{mod.rs,registry.rs,lifecycle.rs}`,包含 §2 全部 symbol;framework-only 可在 R3 P1 后落地,但不启用生产 craft adapter。 +- R6 `CraftOpen`/`CraftPause`/`CraftResume` 契约、R4 production handler/gate、R7 P2 Craft Screen producer/consumer(消费 R2 P1 已登记的 `CraftStore`)、R10 P1 `deliver` contract 与 R10 P2 craft production delivery 全部合入后,craft 才迁移到 `SessionRegistry`:关闭 screen pause、显式 cancel、重开 resume,现有 recipe/session join hydration 行为不变。 +- contract pins:五态转换、stable owner 重绑、disconnect-before-save、dimension-before-transfer、busy 冲突、delivery commit gate,以及 §2.5 六个 `TerminationCause` 的逐变体正反测试;明确断言 `Disconnect`/`Shutdown` checkpoint 不退款,terminal refund 不保留可恢复 escrow。 +- `SuspensionPolicy` pins:TTL 前后、永不重连、管理员结案、过期/重连/关服竞态、重复扫描幂等、delivery 失败重试与 claim 回收后另一玩家可获取同一设施。 ### P2 — alchemy / forge / lingtian -- alchemy furnace/session 与 forge station/session 原子 checkpoint;修复先 teardown 后 delivery。 +- R3 checkpoint API 与 R10 P2 production delivery 都就绪后,alchemy furnace/session 与 forge station/session 才原子 checkpoint;修复先 teardown 后 delivery,失败保持 `AwaitingDelivery` 与 claim。 - 灵田六类 `ActiveSession` 共用 volatile adapter;断线/跨维/关服不再 tick 或结算离线 actor。 - qi refund/release 测试从 `SPIRIT_QI_TOTAL` 与 ledger 不变量取值,不写新物理常数。 @@ -215,7 +240,7 @@ pub trait InteractionSession { - 删除三域私有 store 生命周期分支,迁入 registry。 - external container、TSY search/extract 使用 target claim;所有终态释放 busy。 -- `TsyPresence` 与 player position/dimension guarded restore 对拍,不再出现“人在 TSY、presence 不在”。 +- R3 `TsyPresence` auxiliary Slice 的 guarded load/autosave/shutdown flush/tick-rebase 与 restore parity 常绿后,才把 presence 与 player position/dimension 对拍;新 `Entity` 只在身份/版本/维度校验通过后 attach,随后开放 TSY 请求,不再出现“人在 TSY、presence 不在”。 ### P4 — bot e2e + 归档 @@ -226,5 +251,9 @@ pub trait InteractionSession { 3. `session_restart_recovery` 4. `session_busy_mutex` 5. `session_full_inventory_delivery` +6. `session_suspension_reclamation`:永不重连、TTL 边界、管理员结案、过期与重连竞态、重复扫描和 delivery 失败重试均不泄漏或双发 claim/escrow。 +7. `session_termination_cause_matrix`:逐项命中 `VoluntaryCancel`、`Disconnect`、`DimensionChange`、`Shutdown`、`InvalidRestore`、`SuspensionExpired`,对拍 checkpoint/refund 互斥与 inputs/output 不双发。 +8. `session_craft_pause_resume_wire`:真实 client producer → R6 proto/bridge → R4 production handler/gate → R1 registry → S2C hydrate → client reopen consumer 全链路。 +9. `session_tsy_presence_relog`:R3 guarded presence Slice 与 position/dimension 同事务恢复,校验失败不 attach、不开放 TSY 请求。 另回归现有 `production_craft_disconnect_resume.py`、`production_craft_cancel_full_inventory_refund.py`、`production_handcraft_stone_knife.py`。对应 implementation PR 合入后,按总纲 §7 为吸收项补 Finish Evidence 并做每轨一次 docs-only 批量归档。 diff --git a/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md b/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md index fffb0beec..34e408d73 100644 --- a/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md +++ b/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md @@ -14,12 +14,12 @@ - **进料**:`bong:client_request` 单通道(既有,C2S 本就单轨)、玩家实体(Position/CurrentDimension/状态组件)、R1 session 忙态、R10 inventory 事务。 - **出料**:校验通过的请求进各域 handler;拒绝走统一 reject 回执(带原因码,client 侧 toast/HUD 可消费——对齐 unconsumed-event-feedback 的方向但只做 gate 拒绝部分)。 - **共享类型**:新 `server/src/network/gate/`——`GateSpec { max_distance(度量统一), same_dimension, ownership, state_preconditions }` 按请求类型声明;维度感知 zone 查找 helper。 -- **跨仓库契约**:wire 形状不变(113 变体不动);reject 回执若新增字段走 R6 的契约流程。 +- **跨仓库契约**:wire 形状通常不变(现行 enum 全量);reject 回执若新增字段走 R6 的契约流程。R1 craft pause/resume 例外消费 R6 新增的 `CraftOpen`/`CraftPause`/`CraftResume`,本轨负责 production decode/dispatch,并以 owner、phase 与 busy `GateSpec` 拒绝伪造 resume/重复包。 ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:113 个变体普查(每个标注应有的门禁四元组现状);冻结 `GateSpec` 与拒绝回执语义;等 #1287(冷却重构,同文件大改)merge 定基线。 -- ⬜ P1 门禁中间件落地:gate 层上线,先给"已知漏洞簇"的 ~20 个请求类型挂 spec(吸收清单全命中),旧内联校验保留并行断言一个版本期。 +- ⬜ P1 门禁中间件落地:gate 层上线,先给"已知漏洞簇"与 R1 craft `CraftOpen`/`CraftPause`/`CraftResume` 挂 spec;新增 intent 必须在同一提交进入 production decode/dispatch 和全量 gate matrix,旧内联校验保留并行断言一个版本期。 - ⬜ P2 巨石拆分批次 A:巨型 match 拆为按域 handler 注册表(combat/production/world/social/npc 五组),行为不变,bot 场景锁住。 - ⬜ P3 巨石拆分批次 B + 全量挂 spec + 删旧:113 变体全部声明门禁(含显式 `no_gate` 声明,杜绝静默无门禁);删除各域内联距离常量与重复维度判断。 - ⬜ P4 bot 验收 + 吸收 plan 批量归档。 @@ -33,7 +33,7 @@ skeleton:alchemy-furnace-scope-gate、block-place-reach-gate、coffin-reclaim- - 独占:`network/client_request_handler.rs`(拆解)、新 `network/gate/`、各域内联距离/维度校验行的删除。 - 不碰:`*_emit.rs` S2C 侧(R6)、session 内部(R1)、inventory 事务(R10)。 -- 依赖:基线等 #1287 merge;建议在 R6 的 emit 侧稳定后开 P2(同在 network/ 目录,文件不相交但相邻);P0/P1 可先行。 +- 依赖:基线等 #1287 merge;R1 craft adapter 还要求 R6 先冻结 `CraftOpen`/`CraftPause`/`CraftResume`,本轨随后原子交付 production handler/gate;建议在 R6 emit 侧稳定后开 P2(同在 network/ 目录,文件不相交但相邻),其余 P0/P1 可先行。 ## bot 验收场景 diff --git a/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md b/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md index 2084fc86f..e56b49d41 100644 --- a/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md +++ b/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md @@ -16,13 +16,13 @@ - **进料**:R2 的 `SessionScopedStore`(Screen 订阅的一律是会话态 store);`ServerDataRouter` handler(一律经 client-thread marshal 投递 UI)。 - **出料**:Screen/HUD 展示;HUD 纪律沿用既有 memory 约束(未解锁隐藏不灰掉、沉浸式极简)。 - **共享类型**:新 `BongScreenBase`(生命周期 + 订阅 + 关闭清理)、`DiffListWidget`(推广 craft 范本)、`BongKeybindRegistry`(注册时冲突检测 + 测试期断言)、`ClientThreadMarshal` helper、`ScreenOpenPolicy`(礼貌抢屏:战斗中/已有模态时排队)。 -- **跨仓库契约**:零 wire 改动。 +- **跨仓库契约**:本轨不定义 wire;消费 R6 冻结的 `CraftOpen`/`CraftPause`/`CraftResume` 与 S2C session-state payload。Craft Screen 关闭只发送 `CraftPause`,显式取消按钮只发送 `CraftCancel`,重开后由 server state hydrate 决定是否发送 `CraftResume`。 ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:92 处 fill(100) 全量分类(根节点合法/子节点顶飞);28 Screen 普查;冻结基类 API 与四个共享组件。 - ⬜ P1 基础组件落地:BongScreenBase/DiffListWidget/KeybindRegistry/ClientThreadMarshal/ScreenOpenPolicy 上线;keybind 冲突全数改绑(T/L/O/U/G 簇)。 -- ⬜ P2 Screen 迁移批次 A:炼丹/锻造/手搓/交易屏迁基类,fill(100) 顶飞点与 clearChildren 回弹点随迁修复。 +- ⬜ P2 Screen 迁移批次 A:炼丹/锻造/手搓/交易屏迁基类,fill(100) 顶飞点与 clearChildren 回弹点随迁修复;Craft Screen 完成生产接线:close→`CraftPause`、独立取消按钮→`CraftCancel`、reopen hydrate→`CraftResume`,并以 client 单测锁住三条 intent 不互相替代。 - ⬜ P3 InspectScreen 拆解:按 tab/section 拆组件文件(body/container/tooltip 已有雏形),行为不变。 - ⬜ P4 Screen 迁移批次 B + 网络线程 marshal 强制(handler 层静态检查/测试)+ 删旧。 - ⬜ P5 验收 + 吸收 plan 批量归档。 @@ -36,7 +36,7 @@ skeleton:alchemy-screen-fill100-eviction 与 alchemy-screen-fill-overflow( - 独占:client 全部 Screen/`ui/`/`hud/` 结构性改动、keybind 注册、`InspectScreen.java`。 - 不碰:store 生命周期接口(R2 域,本轨消费);`network/` 桥与 router(R6 域——marshal helper 由本轨提供、在 handler 注册处的接线与 R6 协调);server 一切。 -- 依赖:R2 P1 先合(基类要绑 SessionScopedStore);与 R6 在 handler 投递点有一条接缝,P4 前对齐。 +- 依赖:R2 P1 先合(基类要绑 `SessionScopedStore`);Craft Screen 接线还需 R6 `CraftOpen`/`CraftPause`/`CraftResume` 契约与 R4 production handler/gate 先就绪,R1 craft adapter 不得在本轨 P2 合入前宣称 close/pause/reopen/resume 端到端可达;与 R6 在其他 handler 投递点的接缝于 P4 前对齐。 ## 验收 diff --git a/docs/plans-skeleton/plan-refactor-inventory-core-v1.md b/docs/plans-skeleton/plan-refactor-inventory-core-v1.md index 63e88cae2..67cdbd8e0 100644 --- a/docs/plans-skeleton/plan-refactor-inventory-core-v1.md +++ b/docs/plans-skeleton/plan-refactor-inventory-core-v1.md @@ -19,8 +19,8 @@ ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:mod.rs 职责普查出拆分图(grid/txn/container/corpse/shelflife 接缝);冻结 `InventoryTxn` API 与满包溢出策略;等 craft-refund P4、#1294 相关项定基线。 -- ⬜ P1 巨石拆分:按职责拆文件(行为不变,测试平移),`InventoryTxn` 骨架上线。 -- ⬜ P2 交付路径统一:give/craft/alchemy/forge/loot 全部改走 `deliver`;先校验后扣全量化;满包场景全绿。 +- ⬜ P1 巨石拆分:按职责拆文件(行为不变,测试平移),`InventoryTxn` 骨架上线;先冻结并实现 R1 可直接消费的 `deliver(items) -> Delivered | Spilled(fallback)` 提交语义、幂等键和失败不提交契约,未达到该 gate 时 R1 只能落 registry/adapter 骨架,不能清 escrow/session。 +- ⬜ P2 交付路径统一:give/craft/alchemy/forge/loot 全部改走 `deliver`;先校验后扣全量化;满包场景全绿。R1 craft/alchemy/forge 的 `AwaitingDelivery → CommitTerminal` 生产路径只在本阶段对应调用点迁移后才算完成。 - ⬜ P3 网格/堆叠一致性:拾取合并、占格同步、pack 回执、老存档布局迁移补课。 - ⬜ P4 bot 验收 + 吸收 plan 批量归档。 @@ -33,7 +33,7 @@ skeleton:alchemy-takeback-full-inventory-loss(交付垫层部分;session t - 独占:`server/src/inventory/**`、各域交付调用点的替换行。 - 不碰:`InspectScreen`(R7 域);session 生命周期(R1);掉落物拾取的 gate 校验(R4)。 -- 依赖:R3 P1(persistence 拆分先行,inventories 表接缝清晰);与 R1 的交付接缝 API 由本轨定义、R1 消费。Wave 2 开工,P0 普查可先行。 +- 依赖:R3 P1(persistence 拆分先行,inventories 表接缝清晰);与 R1 的交付接缝 API 由本轨定义、R1 消费。R1 可在本轨 P1 后编译 `deliver` delivery gate,但其 P1 craft 宿主与 P2 alchemy/forge 宿主均须等待本轨 P2 对应 production 调用点迁移后才可验收结案。Wave 2 开工,P0 普查可先行。 ## bot 验收场景 diff --git a/docs/plans-skeleton/plan-refactor-master-v1.md b/docs/plans-skeleton/plan-refactor-master-v1.md index 28e25c1ce..d7b79f3c7 100644 --- a/docs/plans-skeleton/plan-refactor-master-v1.md +++ b/docs/plans-skeleton/plan-refactor-master-v1.md @@ -51,8 +51,8 @@ ## 3. 波次与依赖 - **Wave 0(立即并行)**:V(bot 骨干 + build token 最先)、R3、R5、R2、registry-datafication;同时全部轨道的 P0(设计收口 + 吸收清单验真)都可开工。 -- **Wave 1**:R6(R2 合入后)、R7(R2 合入后)、R1(R3 P1 合入后)。 -- **Wave 2**:R4(#1287 + R6 P1 后)、R9(R5/R6/R2 P1 后)、R10(R3 P1 后)。 +- **Wave 1**:R6(R2 合入后)、R7 基础设施(R2 合入后)、R1 framework-only(仅 `InteractionSession`/registry/lifecycle 骨架,R3 P1 合入后;不得宣称 craft pause/resume 或 delivery 生产闭环)。 +- **Wave 2**:R4(#1287 + R6 P1 后)、R9(R5/R6/R2 P1 后)、R10(R3 P1 后);R1 宿主迁移按显式 gate 分批放行:craft 需 R3 P1 + R6 craft intents + R4 craft handler/gate + R7 P2 Craft Screen + R2 P1 已登记的 `CraftStore` + R10 P1 `deliver` contract + R10 P2 craft production delivery,alchemy/forge 同样需 R10 P2 对应生产调用点,`TsyPresence` 需 R3 P1 auxiliary Slice 与 R3 P4 restore parity。 - 近完成独立 plan(§6.9)在 Wave 0 窗口内优先收尾清场。 - R5 P1(字段收私有的全仓编译大爆破)挑在飞 PR 队列清空的窗口单独合入。 diff --git a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md index 71bcd7ccf..6726090de 100644 --- a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md +++ b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md @@ -13,7 +13,7 @@ ## 接入面 - **进料**:SQLite(bong.db,沿用)、`shutdown.rs`(#1261 之后的关服链路)、`CultivationClock`(相对 tick 基准)。 -- **出料**:统一 Slice API 供各域注册:`load(guarded) / autosave(cadence) / flush_on_shutdown / tick_rebase`;R1 的 session 持久化钩子、各域运行态表全部走它。 +- **出料**:统一 Slice API 供各域注册:`load(guarded) / autosave(cadence) / flush_on_shutdown / tick_rebase`;R1 的 session 持久化钩子、各域运行态表全部走它。`tick_rebase` 对 R1 suspension lease 保持相对剩余时长;R1 冻结默认 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000` 与扫描 cadence `1_200` ticks,本轨不得把重启折算成无限续租。 - **共享类型**:新 `server/src/persistence/` 多文件模块(按域拆表定义 + 迁移链保持线性单入口);`PlayerSliceRegistry`(对齐 #1290 skeleton 的方向,直接吸收它)。 - **跨仓库契约**:零 wire 改动。 - **qi_physics 锚点**:任何带 qi 的快照持久化/恢复不得造成账面变化;恢复失败的兜底路径必须走 `release_dormant_qi_to_zone` 而非丢弃(对齐守恒律红旗清单)。 @@ -21,10 +21,10 @@ ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:53 张表普查归域;冻结 Slice trait(载入守护语义:读失败 = 保留旧行 + 告警 + 只读降级,绝不写回空态;flush registry;tick rebase 协议);等 #1288/#1289/#1261/#1259 merge 后定基线。 -- ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主。 +- ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;注册 R1 所需的 `TsyPresence` auxiliary Slice,字段固定为 `family_id`、`entered_at_tick`、`return_to`、schema/version,并接入 guarded load、autosave、`flush_on_shutdown`、`tick_rebase`。 - ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;身份主键统一(identity-persist-key-mismatch)。 -- ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部"节流落盘"域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥(coffin-autosave-inflight-race 模式)。 -- ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性(heiwushi)等——逐个按 Slice 框架补表。 +- ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部"节流落盘"域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥(coffin-autosave-inflight-race 模式);`TsyPresence` 的关服 flush 与 presence/position/dimension 同事务顺序固定。 +- ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性(heiwushi)等——逐个按 Slice 框架补表。加入 `TsyPresence` guarded relog parity 契约测试和 TSY 维度重启 bot 场景,断言失败加载不 attach presence、成功恢复后才重新开放 TSY 请求。 - ⬜ P5 bot 验收 + 吸收 plan 批量归档。 ## 吸收清单(短名省略 plan-bughunt- 前缀与 -v1 后缀) @@ -44,6 +44,8 @@ skeleton:coffin-autosave-inflight-race、identity-persist-key-mismatch、miner 2. `restart_world_runtime`:触发矿脉枯竭/配方解锁/zone influence→SIGTERM 关服→重启→断言无回滚无复活。 3. `load_failure_guard`:注入一行损坏 slice 数据→启动→断言该玩家进入守护降级而非清零覆盖(配 dev 命令注入)。 4. `tick_rebase`:带冷却/再生倒计时重启→断言倒计时按真实流逝折算(对齐 #1289 的 deadline 折算先例)。 +5. `tsy_presence_relog_parity`:进入 TSY→关服 flush→guarded load→只有 `family_id`、`entered_at_tick`、`return_to`、schema/version 校验通过才 attach `TsyPresence` 并开放 TSY 请求;损坏或缺失 Slice 保持未 attach 且拒绝请求。 +6. `tsy_presence_shutdown_order`:断言 session registry 静止后才 flush presence slice,且 position/dimension 与 presence 同一恢复事务对拍。 ## 开放问题(pre-P0 收口) diff --git a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md index 3ba4aa422..63b6f98f4 100644 --- a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md +++ b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md @@ -14,12 +14,12 @@ - **进料**:各域游戏事件(emit 调用点)、`world` 维度/zone 信息(作用域过滤)。 - **出料**:`bong:server_data` 单通道(目标态:28 旁路全部收编或显式豁免登记);join/重连首包快照集契约(R2 清干净后靠它灌满)。 - **共享类型**:新 server `network/emit/` builder(`scope: Global | Dimension | Zone | Player`);client 桥接层唯一的枚举前缀剥离函数。 -- **跨仓库契约**:proto 形状原则上不动(收编旁路时如需并入 envelope 属破坏性变更,走 buf breaking + samples 同步;agent 侧 TS 只做被动 regenerate,不重构 agent 逻辑)。**不做双轨兼容层**——旁路收编是一次性切换。 +- **跨仓库契约**:proto 形状原则上不动(收编旁路时如需并入 envelope 属破坏性变更,走 buf breaking + samples 同步;agent 侧 TS 只做被动 regenerate,不重构 agent 逻辑)。**不做双轨兼容层**——旁路收编是一次性切换。R1 craft 例外要求显式冻结并一次性切换 `CraftOpen` / `CraftPause` / `CraftResume` C2S intent 与对应 S2C session-state payload;现有 `CraftCancel` 保留为唯一主动取消 intent,关屏不得复用它。 ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:28 旁路逐个普查(收编 vs 豁免理由);100 emit 文件的重复模式取样归纳 builder API;枚举前缀剥离点全量清点;冻结 scope 语义与 join 首包快照集清单。 -- ⬜ P1 emit builder + scope 落地:builder 上线,vfx/audio/env 三类先挂 scope(跨维 bleed 立灭);跨位面切换时 env/season 全量重发。 +- ⬜ P1 emit builder + scope 落地:builder 上线,vfx/audio/env 三类先挂 scope(跨维 bleed 立灭);跨位面切换时 env/season 全量重发;同时交付 R1 craft 所需 `CraftOpen`/`CraftPause`/`CraftResume` proto/schema/`proto_convert` 与 S2C session-state sample pins,生产 decoder/handler 由 R4 消费。 - ⬜ P2 client 桥接层收敛:枚举前缀剥离收敛到单点(含 forge-session 修复);`ServerDataRouter` 注册表整备(分域注册文件,不再单个 1547 行 switch 追加)。 - ⬜ P3 旁路归一批次:28 channel 逐批收编入 server_data envelope 或登记豁免(资源包/握手类可豁免);删除散装 receiver。 - ⬜ P4 契约 pin 全量化:双向 sample 对拍测试补齐(113 C2S + 144 S2C 每变体至少一条正反 sample,schema 改动连 sample 一起改);emit 迁移到 builder 的长尾批次。 @@ -35,7 +35,7 @@ skeleton:vfx-audio-dimension-bleed、q-world-season-dimension-env-resync、for - 独占:server `network/*_emit.rs` 公共模式与新 `network/emit/`、`schema/proto_convert.rs`;client `network/`(ProtoServerDataBridge、ServerDataRouter、BongNetworkHandler 的 channel 注册区段)。 - 不碰:`BongNetworkHandler.clearClientStateOnDisconnect` 区段(R2 域,同文件分区段,merge 前互相 fetch);`client_request_handler.rs`(R4);各 emit 的业务语义。 -- 依赖:无硬前置;R2 先合(同文件低冲突区段);R4 P2 建议在本轨 P1 后开。 +- 依赖:无硬前置;R2 先合(同文件低冲突区段);R1 craft adapter 不得在本轨 P1 的 `CraftOpen`/`CraftPause`/`CraftResume` 契约 pins 合入前宣称 pause/resume 可达;R4 的 craft handler/gate 接缝在该契约合入后落地,R4 P2 其余拆分建议在本轨 P1 后开。 ## bot 验收场景 From c2338d5a1ea2dcdab72018ea94cf67c5d9a489f2 Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Mon, 3 Aug 2026 12:12:03 +0800 Subject: [PATCH 03/14] =?UTF-8?q?=E8=A1=A5=E9=BD=90=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E4=BA=A4=E4=BB=98=E5=8E=9F=E5=AD=90=E6=80=A7=E4=B8=8E=E5=9B=9E?= =?UTF-8?q?=E6=94=B6=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 明确 durable outbox 与 runtime claim 的提交后幂等关系,补全 TSY 快照、管理员授权、dead-letter 退避和 obligation quota,避免崩溃重放与 retained state 无界增长。 Model: cc-sonnet-high Co-Authored-By: Claude --- docs/plan-refactor-server-session-v1.md | 49 +++++++++++-------- .../plan-refactor-inventory-core-v1.md | 16 +++--- .../plan-refactor-persistence-slices-v1.md | 9 ++-- 3 files changed, 42 insertions(+), 32 deletions(-) diff --git a/docs/plan-refactor-server-session-v1.md b/docs/plan-refactor-server-session-v1.md index 2a7135004..47a7ace8a 100644 --- a/docs/plan-refactor-server-session-v1.md +++ b/docs/plan-refactor-server-session-v1.md @@ -73,17 +73,21 @@ pub trait InteractionSession { - `TerminationCause::{VoluntaryCancel, Disconnect, DimensionChange, Shutdown, InvalidRestore, SuspensionExpired}`。 - `SuspensionPolicy`:声明 checkpointed session 的 `suspended_at_tick`、有限 `max_suspended_ticks`、过期扫描周期、管理员强制结案入口和 facility claim 回收动作;默认值冻结为 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000`(20 TPS 下 24 小时)与 `SESSION_SUSPENSION_SCAN_INTERVAL_TICKS = 1_200`,不得配置为无限期保留。 - `SessionTransition::{Keep, Pause, SuspendAndCheckpoint, Teardown, AwaitDelivery, CommitTerminal}`。 +- `SessionDeliveryOutbox { delivery_id, session_key, owner_key, payload_digest, payload, cause, state, attempts, next_retry_tick, created_at_tick }`:R3 持久化的 durable handoff;`delivery_id` 由 `SessionKey + terminal_generation` 稳定派生,禁止仅靠内存幂等键。 +- `DeliveryOutboxState::{Pending, DeadLetter, Committed}` 与 durable `DeliveryCommitReceipt`:R10 delivery 的跨重启 exactly-once 依据。 +- `SessionMaintenancePermissions`:管理员结案命令的显式 allow-list resource;只信 command executor entity 上的权威 `Username`,不得信请求参数中的 operator/owner 字符串。 - `BusyClaim`:声明 owner 与 world target 上占用的 busy classes;冲突矩阵集中注册。 ### 2.1 不变量 1. `SessionRegistry` 中一个 `SessionKey` 只能有一个 owner;一个 runtime `Entity` 只能绑定同一 `PlayerKey` 的 session。 -2. `Checkpointed` session 断线/关服后进入 `Suspended`,不得离线推进;必须记录 `suspended_at_tick` 并受 `SuspensionPolicy.max_suspended_ticks` 约束。重连通过 R3 guarded restore 后才重新绑定 `Entity`;超过 TTL 或管理员结案时转入 `InvalidRestore`/`SuspensionExpired` 结算,先完成 escrow delivery/refund,再释放 facility/target claim 并进入 `Terminal`。过期扫描和管理员入口必须幂等,且不会等待 owner 重连。 +2. `Checkpointed` session 断线/关服后进入 `Suspended`,不得离线推进;必须记录 `suspended_at_tick` 并受 `SuspensionPolicy.max_suspended_ticks` 约束。重连通过 R3 guarded restore 后才重新绑定 `Entity`;超过 TTL 或管理员结案时转入 `InvalidRestore`/`SuspensionExpired` 结算,先把完整 delivery obligation 写入 durable terminal/outbox state,再幂等释放 facility/target claim 并进入 `Terminal`。过期扫描和管理员入口必须幂等,且不会等待 owner 重连。 3. `Volatile` session 遇到断线、跨维或关服必须在同一生命周期门内 teardown;不得留下 owner entity、设施锁、target claim 或 `settling` 标记。 4. 所有 dimension-scoped session 在维度切换前终止。`TsyPresence` 是 transport 辅助状态,单独 checkpoint/restore,不得用“保留旧交互 session”修复 presence 撕裂。 5. client 的 screen/store 只能改善 UX,不能授予 session 或 busy 权限;恶意包、重复包和同 tick 竞态最终都由 registry 拒绝。 -6. session 完成后先进入 `AwaitingDelivery`;只有 R10 `InventoryTxn::deliver(items)` 返回 `Delivered` 或 `Spilled(fallback)` 后,才可 `CommitTerminal` 并清除 escrow/session。依赖未就绪或事务未执行时保留可重试 completion,不吞产物。 -7. 涉及真元的 refund/release 必须通过 `qi_physics::ledger::QiTransfer`;session adapter 不得裸写 `qi_current` 或 zone qi。 +6. session 完成后先进入 `AwaitingDelivery`。R3/R10 必须沿用 `save_player_craft_checkpoint` 的 crash-atomic 基线:同一 SQLite 事务提交 inventory mutation、durable spill、`DeliveryCommitReceipt` 与 session checkpoint 删除/terminalization;事务失败则四者都不发布。若目标 storage 不能共用事务,R3 必须在同一 checkpoint 事务把完整 payload 写入 `SessionDeliveryOutbox` 并 terminalize session,R10 只按稳定 `delivery_id` 消费 outbox;receipt 与 inventory/spill mutation 在 R10 同一事务提交,重启见 receipt 即跳过重复 delivery。只有 receipt 已持久化后才可 `CommitTerminal`;进程在任一边界退出都不得丢失或双发。 +7. `AwaitingDelivery` 不得无限保留 facility/target claim。转入 durable outbox 时,事务内只提交权威的 terminal/outbox 状态;事务成功后释放 runtime gameplay claim 并从 live registry 移除,释放动作必须可由已提交的 terminal/outbox 状态幂等重放。outbox 仅保留 owner delivery obligation,不允许 owner 重新恢复已 terminalize 的 session。 +8. 涉及真元的 refund/release 必须通过 `qi_physics::ledger::QiTransfer`;session adapter 不得裸写 `qi_current` 或 zone qi。 ### 2.2 生命周期顺序 @@ -118,11 +122,13 @@ pub trait InteractionSession { - 每个 checkpoint 写入 `suspended_at_tick`、`last_rebase_epoch`、owner `PlayerKey`、facility/target claim 和 escrow 摘要;不得只靠数据库行存在判断是否仍可恢复。 - 每个宿主显式选择 `SuspensionPolicy.max_suspended_ticks`;P1 craft、P2 alchemy/forge 使用冻结默认值 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000`,扫描 cadence 为 `SESSION_SUSPENSION_SCAN_INTERVAL_TICKS = 1_200`。R3 `tick_rebase` 保持剩余 TTL 的相对时长,关服时间不得让 lease 永久延长;P3 世界交互不得继承无限值。 -- registry 每 tick 或固定 cadence 扫描过期 session;管理员/维护命令可提前结案。扫描、管理员结案、重连与关服 flush 竞争时以 `SessionKey` CAS/registry lock 保证一次结算。 -- 过期结案按 `TerminationCause::SuspensionExpired` 执行:停止 tick → 经 R10 `deliver` 返还未消费 escrow/交付已完成产物 → 清理 checkpoint → 释放 facility/target/busy claim → `Terminal`。R10 不可用或 delivery 未提交时保留 `AwaitingDelivery` 和 claim,重试不得重复退款。 -- claim 回收必须有审计事件(session key、owner key hash、cause、released claims、delivery result),不记录原 payload;恢复窗口内仍允许 owner 正常 reopen,过期后 reopen 明确拒绝。 +- registry 每 tick 或固定 cadence 扫描过期 session;只有通过 `SessionMaintenancePermissions::is_allowed(executor_username)` 的 operator 才能用 `session-maintenance terminate ` 提前结案。executor identity 必须从 `CommandResultEvent.executor` 查询权威 `Username`,不得由命令 payload 声明;普通玩家、未知 executor、伪造 owner 和跨 owner target 一律拒绝且不改变 session/outbox/claim。扫描、授权管理员结案、重连与关服 flush 竞争时以 `SessionKey` CAS/registry lock 保证一次结算。 +- 过期结案按 `TerminationCause::SuspensionExpired` 执行:停止 tick → 在 R3 checkpoint 事务中写入稳定 `delivery_id` 的 `SessionDeliveryOutbox` 并 terminalize session → 事务成功后幂等释放 facility/target/busy claim → 从 live registry 移除。若进程在提交后、runtime claim 清理前退出,启动恢复必须依据已提交的 terminal/outbox 状态补做同一释放,不能重新 attach 或继续占用。R10 异步消费 outbox;不得以 delivery 临时失败为由继续占设施。 +- outbox 重试冻结为指数退避 `min(1_200 * 2^attempts, 72_000)` ticks,并带确定性 `delivery_id` 去重;最多自动尝试 `SESSION_DELIVERY_MAX_ATTEMPTS = 10` 次或保留 `SESSION_DELIVERY_MAX_AGE_TICKS = 12_096_000`(7 天),任一先到转 `DeadLetter` 并停止自动扫描。Dead-letter 仍是有界、可审计的 durable delivery obligation,不占 gameplay claim;只允许授权 operator 执行 inspect/retry/resolve,resolve 不得静默丢 payload。 +- R3 `SessionDeliveryOutbox` 既是 checkpoint terminalization 的 durable owner,也是失败交付的唯一 retained obligation。每个 `Checkpointed` session 在创建/恢复并取得 facility claim 前必须预留一个 durable terminal-obligation slot;无可用 slot 时拒绝创建或恢复该 session,不得先取得 claim 再等待容量。`Suspended` checkpoint 持有的 slot 在 terminal handoff 时与 checkpoint 行在同一事务内转移给 `SessionDeliveryOutbox`,因此 handoff 不会因 quota 耗尽而留下新的不可回收 claim。`Pending`/`DeadLetter` 不重新创建 session、facility 或 target claim;自动扫描只处理有 `next_retry_tick` 且未达到 attempts/age 上限的记录,进入 `DeadLetter` 后从自动扫描集合移除。R3 必须给 `SessionDeliveryOutbox` 配置可观测的 durable row/bytes quota;quota 满时拒绝新的 checkpointed-session admission/restore,保留已有 checkpoint、escrow 与其既有 claim 并告警,不得静默删除 payload、覆盖旧 delivery 或继续接受会使 retained state 增长的 session。quota 释放仅在 `Committed` 或授权 operator 按审计流程完成不可丢 payload 的 `resolve` 后发生。这样失败交付不会永久占用已释放的 gameplay claim,也不会把 durable obligation 变成无界积压。 +- claim 回收与 outbox 状态变化必须有审计事件(session key、owner key hash、operator identity hash/`system`、cause、released claims、delivery id/result/attempt),不记录原 payload;恢复窗口内仍允许 owner 正常 reopen,写入 outbox 后 reopen 明确拒绝。 -P1/P4 必须覆盖:永不重连的 abandoned session 最终释放设施、TTL 边界前后、管理员结案、过期与重连竞态、重复扫描幂等、delivery 失败重试,以及回收后另一玩家可获得同一 furnace/station claim。 +- P1/P4 必须覆盖:永不重连的 abandoned session 最终释放设施、TTL 边界前后、授权管理员结案、普通玩家/未知 executor/伪造 owner/跨 owner target 拒绝、过期与重连竞态、重复扫描幂等、outbox 事务 crash points、receipt 重放去重、退避边界、10 次/7 天 dead-letter、quota 满时 checkpointed admission/restore fail-closed、quota 释放后的重新 admission,以及回收后另一玩家可获得同一 furnace/station claim。 ### 2.3 durability 决议矩阵 | 状态族 | durability | 断线/关服 | 跨维 | @@ -136,7 +142,7 @@ P1/P4 必须覆盖:永不重连的 abandoned session 最终释放设施、TTL | spiritwood | `Volatile` | teardown,清 session 与 `settling` | teardown | | external container / TSY search / extract | `Volatile` | 清 owner、进度与 target claim | teardown | | `PendingInsightOffer` | `Volatile` + deadline | disconnect/timeout 清除 | 清除 | -| `TsyPresence` | R3 checkpointed auxiliary state | R3 Slice 保存 `family_id`、`entered_at_tick`、`return_to` 与版本;guarded restore 通过后才重新 attach component、开放 TSY 请求 | 由 transport 事务显式 enter/exit | +| `TsyPresence` | R3 checkpointed auxiliary state | R3 Slice 保存 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to` 与版本;guarded restore 通过后才重新 attach component、开放 TSY 请求 | 由 transport 事务显式 enter/exit | 后续某个 volatile 域若改成“起 session 即预扣不可重建资源”,必须先把该域改为 `Checkpointed` 或证明 teardown 可无损返还;不得维持默认易失再补日志。 @@ -158,9 +164,9 @@ P1/P4 必须覆盖:永不重连的 abandoned session 最终释放设施、TTL - `DimensionChange`:dimension-scoped session 全退未消费 escrow,经 delivery 后终止,不进入 `Suspended`。 - `Shutdown`:`Checkpointed` 进入 `Suspended`,不退款,checkpoint 保留未消费 escrow;`Volatile` 在 inventory 可访问时全退并终止。 - `InvalidRestore`:全退未消费 escrow;已完成产物只走一次 delivery,不与 inputs 双发。 - - `SuspensionExpired`:全退未消费 escrow/交付已完成产物,delivery 成功后释放 claim 并终止。 -- `Disconnect`/`Shutdown` 的 checkpoint 保留与 terminal cause 的 refund 互斥;任何 delivery 未提交时保持 `AwaitingDelivery` 和 claim,禁止清 session 或重复退款。 -- refund 也走 R10 delivery 垫层;满包不得退化为日志告警。R10 冻结的 `deliver(items) -> Delivered | Spilled(fallback)` 见 `plan-refactor-inventory-core-v1.md` 接入面。 + - `SuspensionExpired`:全退未消费 escrow/交付已完成产物;先提交 durable terminal/outbox state,再幂等释放 claim 并终止,delivery 失败不重新占用 claim。 +- `Disconnect`/`Shutdown` 的 checkpoint 保留与 terminal cause 的 refund 互斥;terminal delivery 使用 §2.1 的 crash-atomic transaction 或 durable outbox/receipt,不得保留既可恢复又待退款的 session,也不得在 receipt 未落盘时宣称交付完成。 +- refund 也走 R10 delivery 垫层;满包不得退化为日志告警。R10 冻结的 `deliver(delivery_id, items) -> Delivered | Spilled(fallback)` 与 durable `delivery_id`/receipt 见 `plan-refactor-inventory-core-v1.md` 接入面。 ## 3.1 决议(原开放问题 §N.1) @@ -209,14 +215,14 @@ P1/P4 必须覆盖:永不重连的 abandoned session 最终释放设施、TTL ## 5. 文件所有权与接缝 - **R1 独占**:`server/src/session/`、七域 `session.rs`、`network/craft_emit.rs` 的 session tick 区,以及迁移时删除的域内私有生命周期代码。 -- **R3 独占**:`server/src/persistence/**`、player load/autosave/shutdown flush;R1 仅消费 checkpoint/restore/flush hook。 +- **R3 独占**:`server/src/persistence/**`、player load/autosave/shutdown flush、`SessionDeliveryOutbox` 与 checkpoint terminalization transaction;R1 仅消费 checkpoint/restore/outbox hook。 - **R4 独占**:`server/src/network/client_request_handler.rs` 与 `network/gate/`;R1 仅暴露 busy/session query。 - **R6 独占**:proto/S2C bridge 与跨端 pause/resume 契约变更。 - **R7/R2 独占**:client Screen/HUD 与 Store disconnect 清理。 -- **R10 独占**:`server/src/inventory/**` 与 `InventoryTxn::deliver`;R1 只决定何时允许 commit teardown。 +- **R10 独占**:`server/src/inventory/**`、`InventoryTxn::deliver`、inventory/spill mutation 与 `DeliveryCommitReceipt` 的原子提交;R1 只生成 stable `delivery_id` 并决定何时 terminalize/释放 claim。 - **阶段放行矩阵**: - framework-only:R3 P1 合入后可落 `InteractionSession`、registry、lifecycle 与不触达生产 wire/delivery 的 contract pins。 - - craft producer path:R6 P1 交付 `CraftOpen`/`CraftPause`/`CraftResume` proto/schema/bridge → R4 P1 交付 production decode/dispatch 与 owner/phase/busy gate → R7 P2 交付 close/pause、显式 cancel、reopen/resume UI producer/consumer(R2 P1 已登记的 `CraftStore` 继续提供 disconnect lifecycle)→ R10 P1 冻结 `deliver` commit contract,且 R10 P2 把 craft production 调用点迁入 `InventoryTxn::deliver`;全部合入后 R1 才能启用并验收 craft adapter。 + - craft producer path:R6 P1 交付 `CraftOpen`/`CraftPause`/`CraftResume` proto/schema/bridge → R4 P1 交付 production decode/dispatch 与 owner/phase/busy gate → R7 P2 交付 close/pause、显式 cancel、reopen/resume UI producer/consumer(R2 P1 已登记的 `CraftStore` 继续提供 disconnect lifecycle)→ R3 P1 交付 durable `SessionDeliveryOutbox`/terminal checkpoint transaction → R10 P1 冻结 `deliver`、stable `delivery_id` 与 durable receipt contract,且 R10 P2 把 craft production 调用点迁入 exactly-once transaction;全部合入后 R1 才能启用并验收 craft adapter。 - alchemy/forge:还须 R10 P2 将两域 production delivery 调用点迁入 `InventoryTxn::deliver`,否则只能停在 `AwaitingDelivery`。 - `TsyPresence`:R3 P1 注册 auxiliary Slice,且 R3 P4 restore parity 常绿后,R1 P3 才能 attach 新 runtime `Entity` 并开放 TSY 请求。 - 禁止用 mock、registry 单测或临时持久层越过上述门宣称端到端完成;依赖未齐时 phase 保持未完成。 @@ -227,12 +233,12 @@ P1/P4 必须覆盖:永不重连的 abandoned session 最终释放设施、TTL - 新增 `server/src/session/{mod.rs,registry.rs,lifecycle.rs}`,包含 §2 全部 symbol;framework-only 可在 R3 P1 后落地,但不启用生产 craft adapter。 - R6 `CraftOpen`/`CraftPause`/`CraftResume` 契约、R4 production handler/gate、R7 P2 Craft Screen producer/consumer(消费 R2 P1 已登记的 `CraftStore`)、R10 P1 `deliver` contract 与 R10 P2 craft production delivery 全部合入后,craft 才迁移到 `SessionRegistry`:关闭 screen pause、显式 cancel、重开 resume,现有 recipe/session join hydration 行为不变。 -- contract pins:五态转换、stable owner 重绑、disconnect-before-save、dimension-before-transfer、busy 冲突、delivery commit gate,以及 §2.5 六个 `TerminationCause` 的逐变体正反测试;明确断言 `Disconnect`/`Shutdown` checkpoint 不退款,terminal refund 不保留可恢复 escrow。 -- `SuspensionPolicy` pins:TTL 前后、永不重连、管理员结案、过期/重连/关服竞态、重复扫描幂等、delivery 失败重试与 claim 回收后另一玩家可获取同一设施。 +- contract pins:五态转换、stable owner 重绑、disconnect-before-save、dimension-before-transfer、busy 冲突、delivery commit gate,以及 §2.5 六个 `TerminationCause` 的逐变体正反测试;明确断言 `Disconnect`/`Shutdown` checkpoint 不退款,terminal refund 不保留可恢复 escrow。delivery pins 必须在 transaction 的“outbox 写入前/写入后 terminalize 前/receipt+inventory commit 后 ack 前”逐点模拟进程退出并重载,断言 payload 总数恰为一次;SQLite 同库路径另断言 inventory/spill、receipt、checkpoint 删除在单事务全成或全败。 +- `SuspensionPolicy` pins:TTL 前后、永不重连、授权管理员结案、普通玩家/未知 executor/伪造 owner/跨 owner target 拒绝、过期/重连/关服竞态、重复扫描幂等、指数退避 cap、10 次/7 天 dead-letter、人工 retry/resolve 权限,以及 claim 回收后另一玩家可获取同一设施。 ### P2 — alchemy / forge / lingtian -- R3 checkpoint API 与 R10 P2 production delivery 都就绪后,alchemy furnace/session 与 forge station/session 才原子 checkpoint;修复先 teardown 后 delivery,失败保持 `AwaitingDelivery` 与 claim。 +- R3 checkpoint API 与 R10 P2 production delivery 都就绪后,alchemy furnace/session 与 forge station/session 才原子 checkpoint;终态 handoff 先写 durable outbox/terminal state,再在提交成功后幂等释放 runtime claim;delivery 失败不得重新 attach session 或 claim。 - 灵田六类 `ActiveSession` 共用 volatile adapter;断线/跨维/关服不再 tick 或结算离线 actor。 - qi refund/release 测试从 `SPIRIT_QI_TOTAL` 与 ledger 不变量取值,不写新物理常数。 @@ -251,9 +257,10 @@ P1/P4 必须覆盖:永不重连的 abandoned session 最终释放设施、TTL 3. `session_restart_recovery` 4. `session_busy_mutex` 5. `session_full_inventory_delivery` -6. `session_suspension_reclamation`:永不重连、TTL 边界、管理员结案、过期与重连竞态、重复扫描和 delivery 失败重试均不泄漏或双发 claim/escrow。 -7. `session_termination_cause_matrix`:逐项命中 `VoluntaryCancel`、`Disconnect`、`DimensionChange`、`Shutdown`、`InvalidRestore`、`SuspensionExpired`,对拍 checkpoint/refund 互斥与 inputs/output 不双发。 -8. `session_craft_pause_resume_wire`:真实 client producer → R6 proto/bridge → R4 production handler/gate → R1 registry → S2C hydrate → client reopen consumer 全链路。 -9. `session_tsy_presence_relog`:R3 guarded presence Slice 与 position/dimension 同事务恢复,校验失败不 attach、不开放 TSY 请求。 +6. `session_suspension_reclamation`:永不重连、TTL 边界、授权管理员结案、普通玩家/未知 executor/伪造 owner/跨 owner target 拒绝、过期与重连竞态、重复扫描、claim 释放与 outbox handoff 均不泄漏或双发 claim/escrow。 +7. `session_delivery_crash_atomicity`:在 outbox/terminal checkpoint/receipt/inventory-or-spill/ack 各持久化边界强杀重启,断言 stable `delivery_id` 最终只交付一次;失败按指数退避,在 10 次或 7 天转 `DeadLetter`,且从 handoff 起不占设施 claim。 +8. `session_termination_cause_matrix`:逐项命中 `VoluntaryCancel`、`Disconnect`、`DimensionChange`、`Shutdown`、`InvalidRestore`、`SuspensionExpired`,对拍 checkpoint/refund 互斥与 inputs/output 不双发。 +9. `session_craft_pause_resume_wire`:真实 client producer → R6 proto/bridge → R4 production handler/gate → R1 registry → S2C hydrate → client reopen consumer 全链路。 +10. `session_tsy_presence_relog`:R3 guarded presence Slice 完整恢复 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version 并与 position/dimension 同事务对拍;校验失败不 attach、不开放 TSY 请求,恢复后 death-drop 仍正确区分原带物与 TSY 所得。 另回归现有 `production_craft_disconnect_resume.py`、`production_craft_cancel_full_inventory_refund.py`、`production_handcraft_stone_knife.py`。对应 implementation PR 合入后,按总纲 §7 为吸收项补 Finish Evidence 并做每轨一次 docs-only 批量归档。 diff --git a/docs/plans-skeleton/plan-refactor-inventory-core-v1.md b/docs/plans-skeleton/plan-refactor-inventory-core-v1.md index 67cdbd8e0..0f9938c8d 100644 --- a/docs/plans-skeleton/plan-refactor-inventory-core-v1.md +++ b/docs/plans-skeleton/plan-refactor-inventory-core-v1.md @@ -12,15 +12,15 @@ ## 接入面 - **进料**:R1 session 产物交付调用、R4 gate 通过后的物品类请求、掉落物系统、R3 的持久化 slice(inventories 表)。 -- **出料**:统一 `InventoryTxn` API:`deliver(items) -> Delivered | Spilled(fallback)`(满包溢出策略统一:脚下掉落/暂存箱,按 worldview 拍板)、`consume_checked`(先校验后扣)、`merge_stack`、占格变更事件(S2C 经 R6)。 +- **出料**:统一 `InventoryTxn` API:`deliver(delivery_id, items) -> Delivered | Spilled(fallback)`(满包溢出策略统一:脚下掉落/暂存箱,按 worldview 拍板)、`consume_checked`(先校验后扣)、`merge_stack`、占格变更事件(S2C 经 R6)。`delivery_id` 必须稳定跨重启;inventory mutation 或 durable spill、`DeliveryCommitReceipt` 与 outbox ack 在同一 SQLite transaction 提交,receipt 已存在时返回原结果且不重复 mutation。 - **共享类型**:`ItemCategory` 合法集不动(无 Material,材料用 Misc——历史坑);`owner_instance_id` 架构不动。 - **worldview 锚点**:物品不凭空消失对齐末法稀缺经济(§十三物资锚点);含真元物品的销毁/溢出走 R5 ledger。 ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:mod.rs 职责普查出拆分图(grid/txn/container/corpse/shelflife 接缝);冻结 `InventoryTxn` API 与满包溢出策略;等 craft-refund P4、#1294 相关项定基线。 -- ⬜ P1 巨石拆分:按职责拆文件(行为不变,测试平移),`InventoryTxn` 骨架上线;先冻结并实现 R1 可直接消费的 `deliver(items) -> Delivered | Spilled(fallback)` 提交语义、幂等键和失败不提交契约,未达到该 gate 时 R1 只能落 registry/adapter 骨架,不能清 escrow/session。 -- ⬜ P2 交付路径统一:give/craft/alchemy/forge/loot 全部改走 `deliver`;先校验后扣全量化;满包场景全绿。R1 craft/alchemy/forge 的 `AwaitingDelivery → CommitTerminal` 生产路径只在本阶段对应调用点迁移后才算完成。 +- ⬜ P1 巨石拆分:按职责拆文件(行为不变,测试平移),`InventoryTxn` 骨架上线;冻结并实现 R1 可直接消费的 `deliver(delivery_id, items) -> Delivered | Spilled(fallback)`,其中 `delivery_id`/`DeliveryCommitReceipt` 必须 durable,receipt 与 inventory/spill mutation 同事务全成或全败,失败不提交。未达到该 gate 时 R1 只能落 registry/adapter 骨架,不能清 escrow/session。 +- ⬜ P2 交付路径统一:give/craft/alchemy/forge/loot 全部改走 `deliver`;先校验后扣全量化;满包场景全绿。R1 craft/alchemy/forge 的 `AwaitingDelivery → CommitTerminal` 生产路径只在本阶段对应调用点迁移后才算完成。消费 R3 `SessionDeliveryOutbox` 时自动重试按 `min(1_200 * 2^attempts, 72_000)` ticks,10 次或 7 天后转 durable `DeadLetter` 并停止自动重试;outbox handoff 后不持有 gameplay claim。授权 operator 可 inspect/retry/resolve,普通玩家不可调用,resolve 不得删除未交付 payload。 - ⬜ P3 网格/堆叠一致性:拾取合并、占格同步、pack 回执、老存档布局迁移补课。 - ⬜ P4 bot 验收 + 吸收 plan 批量归档。 @@ -33,14 +33,16 @@ skeleton:alchemy-takeback-full-inventory-loss(交付垫层部分;session t - 独占:`server/src/inventory/**`、各域交付调用点的替换行。 - 不碰:`InspectScreen`(R7 域);session 生命周期(R1);掉落物拾取的 gate 校验(R4)。 -- 依赖:R3 P1(persistence 拆分先行,inventories 表接缝清晰);与 R1 的交付接缝 API 由本轨定义、R1 消费。R1 可在本轨 P1 后编译 `deliver` delivery gate,但其 P1 craft 宿主与 P2 alchemy/forge 宿主均须等待本轨 P2 对应 production 调用点迁移后才可验收结案。Wave 2 开工,P0 普查可先行。 +- 依赖:R3 P1(persistence 拆分先行,inventories 表与 `SessionDeliveryOutbox` atomic handoff 接缝清晰);与 R1 的交付接缝 API 由本轨定义、R1 消费。R1 可在本轨 P1 后编译 durable delivery gate,但其 P1 craft 宿主与 P2 alchemy/forge 宿主均须等待本轨 P2 对应 production 调用点迁移后才可验收结案。Wave 2 开工,P0 普查可先行。 ## bot 验收场景 1. `inv_full_delivery_matrix`:满包状态下 craft 完工/取丹/锻造出炉/给予→断言产物按统一溢出策略落地,总数不丢。 -2. `inv_stack_merge`:拾取同类掉落→断言合并入既有堆叠。 -3. `inv_footprint_sync`:旋转/移动占格物品→断言 client 快照占格一致(P6 protobuf 深断言)。 -4. `inv_give_visibility`:dev give 后立即快照→断言实例可见可拾取(修 bot 基建自身的假阳性)。 +2. `inv_delivery_crash_atomicity`:在 inventory/spill mutation、receipt commit、outbox ack 各边界强杀重启;相同 durable `delivery_id` 重放只返回原 receipt,物品或掉落实例总数始终恰为一次。 +3. `inv_delivery_dead_letter`:持续失败按指数退避且 cap 为 72,000 ticks,10 次或 7 天后停止自动扫描并进入 durable `DeadLetter`;普通玩家不能 inspect/retry/resolve,且整个失败周期不占 session facility claim。 +4. `inv_stack_merge`:拾取同类掉落→断言合并入既有堆叠。 +5. `inv_footprint_sync`:旋转/移动占格物品→断言 client 快照占格一致(P6 protobuf 深断言)。 +6. `inv_give_visibility`:dev give 后立即快照→断言实例可见可拾取(修 bot 基建自身的假阳性)。 ## 开放问题(pre-P0 收口) diff --git a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md index 6726090de..44b8c271b 100644 --- a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md +++ b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md @@ -13,7 +13,7 @@ ## 接入面 - **进料**:SQLite(bong.db,沿用)、`shutdown.rs`(#1261 之后的关服链路)、`CultivationClock`(相对 tick 基准)。 -- **出料**:统一 Slice API 供各域注册:`load(guarded) / autosave(cadence) / flush_on_shutdown / tick_rebase`;R1 的 session 持久化钩子、各域运行态表全部走它。`tick_rebase` 对 R1 suspension lease 保持相对剩余时长;R1 冻结默认 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000` 与扫描 cadence `1_200` ticks,本轨不得把重启折算成无限续租。 +- **出料**:统一 Slice API 供各域注册:`load(guarded) / autosave(cadence) / flush_on_shutdown / tick_rebase`;R1 的 session 持久化钩子、各域运行态表全部走它。`tick_rebase` 对 R1 suspension lease 保持相对剩余时长;R1 冻结默认 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000` 与扫描 cadence `1_200` ticks,本轨不得把重启折算成无限续租。为 terminal delivery 提供 `SessionDeliveryOutbox`:事务内只提交 outbox 插入与 session checkpoint terminalization/删除,提交成功后 runtime gameplay claim 释放必须可依据 durable terminal/outbox 状态幂等重放;禁止仅持久化 session、把 delivery id 留在内存。每个 checkpointed session 的 admission/restore 先预留 durable terminal-obligation slot,`SessionDeliveryOutbox` 设可观测 row/bytes quota;quota 满时 fail closed,不得新建/恢复会增加 retained obligation 的 checkpointed session。dead-letter 从自动扫描集合移除且不占 claim,quota 仅在 committed 或授权且不可丢 payload 的 audited resolve 后释放。 - **共享类型**:新 `server/src/persistence/` 多文件模块(按域拆表定义 + 迁移链保持线性单入口);`PlayerSliceRegistry`(对齐 #1290 skeleton 的方向,直接吸收它)。 - **跨仓库契约**:零 wire 改动。 - **qi_physics 锚点**:任何带 qi 的快照持久化/恢复不得造成账面变化;恢复失败的兜底路径必须走 `release_dormant_qi_to_zone` 而非丢弃(对齐守恒律红旗清单)。 @@ -21,9 +21,9 @@ ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:53 张表普查归域;冻结 Slice trait(载入守护语义:读失败 = 保留旧行 + 告警 + 只读降级,绝不写回空态;flush registry;tick rebase 协议);等 #1288/#1289/#1261/#1259 merge 后定基线。 -- ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;注册 R1 所需的 `TsyPresence` auxiliary Slice,字段固定为 `family_id`、`entered_at_tick`、`return_to`、schema/version,并接入 guarded load、autosave、`flush_on_shutdown`、`tick_rebase`。 +- ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;注册 R1 所需的 `TsyPresence` auxiliary Slice,字段固定为 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version,并接入 guarded load、autosave、`flush_on_shutdown`、`tick_rebase`;落 `SessionDeliveryOutbox` 表与 atomic handoff API,stable `delivery_id`、完整 payload、cause、attempts/next_retry/created_at/state 全部持久化。 - ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;身份主键统一(identity-persist-key-mismatch)。 -- ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部"节流落盘"域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥(coffin-autosave-inflight-race 模式);`TsyPresence` 的关服 flush 与 presence/position/dimension 同事务顺序固定。 +- ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部"节流落盘"域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥(coffin-autosave-inflight-race 模式);`TsyPresence` 的关服 flush 与 presence/position/dimension 同事务顺序固定;outbox handoff 的 checkpoint 删除/terminalization 与 outbox insert 在同一 transaction 全成或全败,并以 crash injection pin 每个 commit boundary。 - ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性(heiwushi)等——逐个按 Slice 框架补表。加入 `TsyPresence` guarded relog parity 契约测试和 TSY 维度重启 bot 场景,断言失败加载不 attach presence、成功恢复后才重新开放 TSY 请求。 - ⬜ P5 bot 验收 + 吸收 plan 批量归档。 @@ -44,8 +44,9 @@ skeleton:coffin-autosave-inflight-race、identity-persist-key-mismatch、miner 2. `restart_world_runtime`:触发矿脉枯竭/配方解锁/zone influence→SIGTERM 关服→重启→断言无回滚无复活。 3. `load_failure_guard`:注入一行损坏 slice 数据→启动→断言该玩家进入守护降级而非清零覆盖(配 dev 命令注入)。 4. `tick_rebase`:带冷却/再生倒计时重启→断言倒计时按真实流逝折算(对齐 #1289 的 deadline 折算先例)。 -5. `tsy_presence_relog_parity`:进入 TSY→关服 flush→guarded load→只有 `family_id`、`entered_at_tick`、`return_to`、schema/version 校验通过才 attach `TsyPresence` 并开放 TSY 请求;损坏或缺失 Slice 保持未 attach 且拒绝请求。 +5. `tsy_presence_relog_parity`:进入 TSY→关服 flush→guarded load→只有 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version 校验通过才 attach `TsyPresence` 并开放 TSY 请求;损坏或缺失 Slice 保持未 attach 且拒绝请求;恢复后 death-drop 对原带物继续执行 50%/武器保护,对 TSY 所得执行既有 100% 规则。 6. `tsy_presence_shutdown_order`:断言 session registry 静止后才 flush presence slice,且 position/dimension 与 presence 同一恢复事务对拍。 +7. `session_delivery_outbox_atomicity`:分别在 outbox insert 前、insert 后 checkpoint terminalize 前、事务 commit 后 ack 前强杀重启;断言 checkpoint/outbox 恰有一个权威 owner,完整 payload 不丢且 stable `delivery_id` 不变。 ## 开放问题(pre-P0 收口) From 96d6bd539e5ff1914194a36a48fce6e82e55e1c3 Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Mon, 3 Aug 2026 13:36:02 +0800 Subject: [PATCH 04/14] =?UTF-8?q?=E6=94=B6=E7=B4=A7=20R3=20=E4=BA=A4?= =?UTF-8?q?=E4=BB=98=E9=97=A8=E7=A6=81=E4=B8=8E=E8=B7=A8=E8=BD=A8=E5=A5=91?= =?UTF-8?q?=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 atomic handoff 前移至 R3 P1,补齐交付中间态、byte quota、CraftOpen producer、PendingInsightOffer 回归、TSY 拒绝分支与 116 变体计数。 Model: cc-sonnet-high Co-Authored-By: Claude --- docs/plan-refactor-server-session-v1.md | 17 ++++++++++------- .../plans-skeleton/plan-refactor-c2s-gate-v1.md | 10 +++++----- .../plan-refactor-client-ui-base-v1.md | 6 +++--- docs/plans-skeleton/plan-refactor-master-v1.md | 2 +- .../plan-refactor-persistence-slices-v1.md | 8 ++++---- .../plans-skeleton/plan-refactor-wire-s2c-v1.md | 2 +- 6 files changed, 24 insertions(+), 21 deletions(-) diff --git a/docs/plan-refactor-server-session-v1.md b/docs/plan-refactor-server-session-v1.md index 47a7ace8a..03928d35e 100644 --- a/docs/plan-refactor-server-session-v1.md +++ b/docs/plan-refactor-server-session-v1.md @@ -69,10 +69,10 @@ pub trait InteractionSession { - `SessionKey { domain, id }`:稳定 session 身份;持久记录不得以 Bevy `Entity` 为主键。 - `PlayerKey`:canonical player id;`Entity` 只作当前连接的 runtime binding。 - `SessionDurability::{Checkpointed, Volatile}`:每个 adapter 注册时必须显式声明,不允许默认值。 -- `SessionPhase::{Running, Paused, Suspended, AwaitingDelivery, Terminal}`:同一 session 不允许同时处于多个 phase。 +- `SessionPhase::{Running, Paused, Suspended, AwaitingDelivery, DeliveryPending, Terminal}`:同一 session 不允许同时处于多个 phase。`DeliveryPending` 表示 R3 已提交 durable outbox/checkpoint terminalization、但 R10 尚未提交 receipt;它不再是可恢复 gameplay session,不持有 runtime claim。 - `TerminationCause::{VoluntaryCancel, Disconnect, DimensionChange, Shutdown, InvalidRestore, SuspensionExpired}`。 - `SuspensionPolicy`:声明 checkpointed session 的 `suspended_at_tick`、有限 `max_suspended_ticks`、过期扫描周期、管理员强制结案入口和 facility claim 回收动作;默认值冻结为 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000`(20 TPS 下 24 小时)与 `SESSION_SUSPENSION_SCAN_INTERVAL_TICKS = 1_200`,不得配置为无限期保留。 -- `SessionTransition::{Keep, Pause, SuspendAndCheckpoint, Teardown, AwaitDelivery, CommitTerminal}`。 +- `SessionTransition::{Keep, Pause, SuspendAndCheckpoint, Teardown, AwaitDelivery, DeliveryPending, CommitTerminal}`。 - `SessionDeliveryOutbox { delivery_id, session_key, owner_key, payload_digest, payload, cause, state, attempts, next_retry_tick, created_at_tick }`:R3 持久化的 durable handoff;`delivery_id` 由 `SessionKey + terminal_generation` 稳定派生,禁止仅靠内存幂等键。 - `DeliveryOutboxState::{Pending, DeadLetter, Committed}` 与 durable `DeliveryCommitReceipt`:R10 delivery 的跨重启 exactly-once 依据。 - `SessionMaintenancePermissions`:管理员结案命令的显式 allow-list resource;只信 command executor entity 上的权威 `Username`,不得信请求参数中的 operator/owner 字符串。 @@ -81,11 +81,11 @@ pub trait InteractionSession { ### 2.1 不变量 1. `SessionRegistry` 中一个 `SessionKey` 只能有一个 owner;一个 runtime `Entity` 只能绑定同一 `PlayerKey` 的 session。 -2. `Checkpointed` session 断线/关服后进入 `Suspended`,不得离线推进;必须记录 `suspended_at_tick` 并受 `SuspensionPolicy.max_suspended_ticks` 约束。重连通过 R3 guarded restore 后才重新绑定 `Entity`;超过 TTL 或管理员结案时转入 `InvalidRestore`/`SuspensionExpired` 结算,先把完整 delivery obligation 写入 durable terminal/outbox state,再幂等释放 facility/target claim 并进入 `Terminal`。过期扫描和管理员入口必须幂等,且不会等待 owner 重连。 +2. `Checkpointed` session 断线/关服后进入 `Suspended`,不得离线推进;必须记录 `suspended_at_tick` 并受 `SuspensionPolicy.max_suspended_ticks` 约束。重连通过 R3 guarded restore 后才重新绑定 `Entity`;超过 TTL 或管理员结案时转入 `InvalidRestore`/`SuspensionExpired` 结算,先把完整 delivery obligation 写入 durable terminal/outbox state 并转为 `DeliveryPending`,再幂等释放 facility/target claim;R10 receipt 提交后才进入 `Terminal`。过期扫描和管理员入口必须幂等,且不会等待 owner 重连。 3. `Volatile` session 遇到断线、跨维或关服必须在同一生命周期门内 teardown;不得留下 owner entity、设施锁、target claim 或 `settling` 标记。 4. 所有 dimension-scoped session 在维度切换前终止。`TsyPresence` 是 transport 辅助状态,单独 checkpoint/restore,不得用“保留旧交互 session”修复 presence 撕裂。 5. client 的 screen/store 只能改善 UX,不能授予 session 或 busy 权限;恶意包、重复包和同 tick 竞态最终都由 registry 拒绝。 -6. session 完成后先进入 `AwaitingDelivery`。R3/R10 必须沿用 `save_player_craft_checkpoint` 的 crash-atomic 基线:同一 SQLite 事务提交 inventory mutation、durable spill、`DeliveryCommitReceipt` 与 session checkpoint 删除/terminalization;事务失败则四者都不发布。若目标 storage 不能共用事务,R3 必须在同一 checkpoint 事务把完整 payload 写入 `SessionDeliveryOutbox` 并 terminalize session,R10 只按稳定 `delivery_id` 消费 outbox;receipt 与 inventory/spill mutation 在 R10 同一事务提交,重启见 receipt 即跳过重复 delivery。只有 receipt 已持久化后才可 `CommitTerminal`;进程在任一边界退出都不得丢失或双发。 +6. session 完成后先进入 `AwaitingDelivery`。R3/R10 必须沿用 `save_player_craft_checkpoint` 的 crash-atomic 基线:同一 SQLite 事务提交 inventory mutation、durable spill、`DeliveryCommitReceipt` 与 session checkpoint 删除;事务失败则四者都不发布。若目标 storage 不能共用事务,R3 必须在同一 checkpoint 事务把完整 payload 写入 `SessionDeliveryOutbox` 并把 session 转入 durable `DeliveryPending`,R10 只按稳定 `delivery_id` 消费 outbox;receipt 与 inventory/spill mutation 在 R10 同一事务提交,重启见 receipt 即跳过重复 delivery,随后把 `DeliveryPending` 标记为 `Terminal`。进程在任一边界退出都不得丢失或双发。 7. `AwaitingDelivery` 不得无限保留 facility/target claim。转入 durable outbox 时,事务内只提交权威的 terminal/outbox 状态;事务成功后释放 runtime gameplay claim 并从 live registry 移除,释放动作必须可由已提交的 terminal/outbox 状态幂等重放。outbox 仅保留 owner delivery obligation,不允许 owner 重新恢复已 terminalize 的 session。 8. 涉及真元的 refund/release 必须通过 `qi_physics::ledger::QiTransfer`;session adapter 不得裸写 `qi_current` 或 zone qi。 @@ -123,9 +123,10 @@ pub trait InteractionSession { - 每个 checkpoint 写入 `suspended_at_tick`、`last_rebase_epoch`、owner `PlayerKey`、facility/target claim 和 escrow 摘要;不得只靠数据库行存在判断是否仍可恢复。 - 每个宿主显式选择 `SuspensionPolicy.max_suspended_ticks`;P1 craft、P2 alchemy/forge 使用冻结默认值 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000`,扫描 cadence 为 `SESSION_SUSPENSION_SCAN_INTERVAL_TICKS = 1_200`。R3 `tick_rebase` 保持剩余 TTL 的相对时长,关服时间不得让 lease 永久延长;P3 世界交互不得继承无限值。 - registry 每 tick 或固定 cadence 扫描过期 session;只有通过 `SessionMaintenancePermissions::is_allowed(executor_username)` 的 operator 才能用 `session-maintenance terminate ` 提前结案。executor identity 必须从 `CommandResultEvent.executor` 查询权威 `Username`,不得由命令 payload 声明;普通玩家、未知 executor、伪造 owner 和跨 owner target 一律拒绝且不改变 session/outbox/claim。扫描、授权管理员结案、重连与关服 flush 竞争时以 `SessionKey` CAS/registry lock 保证一次结算。 -- 过期结案按 `TerminationCause::SuspensionExpired` 执行:停止 tick → 在 R3 checkpoint 事务中写入稳定 `delivery_id` 的 `SessionDeliveryOutbox` 并 terminalize session → 事务成功后幂等释放 facility/target/busy claim → 从 live registry 移除。若进程在提交后、runtime claim 清理前退出,启动恢复必须依据已提交的 terminal/outbox 状态补做同一释放,不能重新 attach 或继续占用。R10 异步消费 outbox;不得以 delivery 临时失败为由继续占设施。 +- 过期结案按 `TerminationCause::SuspensionExpired` 执行:停止 tick → 在 R3 checkpoint 事务中写入稳定 `delivery_id` 的 `SessionDeliveryOutbox` 并转为 `DeliveryPending` → 事务成功后幂等释放 facility/target/busy claim → 从 live registry 移除;R10 receipt 提交后再标记 `Terminal`。若进程在提交后、runtime claim 清理前退出,启动恢复必须依据已提交的 terminal/outbox 状态补做同一释放,不能重新 attach 或继续占用。R10 异步消费 outbox;不得以 delivery 临时失败为由继续占设施。 - outbox 重试冻结为指数退避 `min(1_200 * 2^attempts, 72_000)` ticks,并带确定性 `delivery_id` 去重;最多自动尝试 `SESSION_DELIVERY_MAX_ATTEMPTS = 10` 次或保留 `SESSION_DELIVERY_MAX_AGE_TICKS = 12_096_000`(7 天),任一先到转 `DeadLetter` 并停止自动扫描。Dead-letter 仍是有界、可审计的 durable delivery obligation,不占 gameplay claim;只允许授权 operator 执行 inspect/retry/resolve,resolve 不得静默丢 payload。 - R3 `SessionDeliveryOutbox` 既是 checkpoint terminalization 的 durable owner,也是失败交付的唯一 retained obligation。每个 `Checkpointed` session 在创建/恢复并取得 facility claim 前必须预留一个 durable terminal-obligation slot;无可用 slot 时拒绝创建或恢复该 session,不得先取得 claim 再等待容量。`Suspended` checkpoint 持有的 slot 在 terminal handoff 时与 checkpoint 行在同一事务内转移给 `SessionDeliveryOutbox`,因此 handoff 不会因 quota 耗尽而留下新的不可回收 claim。`Pending`/`DeadLetter` 不重新创建 session、facility 或 target claim;自动扫描只处理有 `next_retry_tick` 且未达到 attempts/age 上限的记录,进入 `DeadLetter` 后从自动扫描集合移除。R3 必须给 `SessionDeliveryOutbox` 配置可观测的 durable row/bytes quota;quota 满时拒绝新的 checkpointed-session admission/restore,保留已有 checkpoint、escrow 与其既有 claim 并告警,不得静默删除 payload、覆盖旧 delivery 或继续接受会使 retained state 增长的 session。quota 释放仅在 `Committed` 或授权 operator 按审计流程完成不可丢 payload 的 `resolve` 后发生。这样失败交付不会永久占用已释放的 gameplay claim,也不会把 durable obligation 变成无界积压。 +- `DeliveryPending` 已提交 durable handoff 但未 receipt;R10 delivery 失败或进入 `DeadLetter` 时仍保持该 phase 的 durable obligation,绝不回退为 `AwaitingDelivery` 或重新占用 claim。 - claim 回收与 outbox 状态变化必须有审计事件(session key、owner key hash、operator identity hash/`system`、cause、released claims、delivery id/result/attempt),不记录原 payload;恢复窗口内仍允许 owner 正常 reopen,写入 outbox 后 reopen 明确拒绝。 - P1/P4 必须覆盖:永不重连的 abandoned session 最终释放设施、TTL 边界前后、授权管理员结案、普通玩家/未知 executor/伪造 owner/跨 owner target 拒绝、过期与重连竞态、重复扫描幂等、outbox 事务 crash points、receipt 重放去重、退避边界、10 次/7 天 dead-letter、quota 满时 checkpointed admission/restore fail-closed、quota 释放后的重新 admission,以及回收后另一玩家可获得同一 furnace/station claim。 @@ -233,8 +234,9 @@ pub trait InteractionSession { - 新增 `server/src/session/{mod.rs,registry.rs,lifecycle.rs}`,包含 §2 全部 symbol;framework-only 可在 R3 P1 后落地,但不启用生产 craft adapter。 - R6 `CraftOpen`/`CraftPause`/`CraftResume` 契约、R4 production handler/gate、R7 P2 Craft Screen producer/consumer(消费 R2 P1 已登记的 `CraftStore`)、R10 P1 `deliver` contract 与 R10 P2 craft production delivery 全部合入后,craft 才迁移到 `SessionRegistry`:关闭 screen pause、显式 cancel、重开 resume,现有 recipe/session join hydration 行为不变。 -- contract pins:五态转换、stable owner 重绑、disconnect-before-save、dimension-before-transfer、busy 冲突、delivery commit gate,以及 §2.5 六个 `TerminationCause` 的逐变体正反测试;明确断言 `Disconnect`/`Shutdown` checkpoint 不退款,terminal refund 不保留可恢复 escrow。delivery pins 必须在 transaction 的“outbox 写入前/写入后 terminalize 前/receipt+inventory commit 后 ack 前”逐点模拟进程退出并重载,断言 payload 总数恰为一次;SQLite 同库路径另断言 inventory/spill、receipt、checkpoint 删除在单事务全成或全败。 +- contract pins:六态转换、stable owner 重绑、disconnect-before-save、dimension-before-transfer、busy 冲突、delivery commit gate,以及 §2.5 六个 `TerminationCause` 的逐变体正反测试;明确断言 `Disconnect`/`Shutdown` checkpoint 不退款,terminal refund 不保留可恢复 escrow。delivery pins 必须在 transaction 的“outbox 写入前/写入后 terminalize 前/receipt+inventory commit 后 ack 前”逐点模拟进程退出并重载,断言 payload 总数恰为一次;SQLite 同库路径另断言 inventory/spill、receipt、checkpoint 删除在单事务全成或全败。 - `SuspensionPolicy` pins:TTL 前后、永不重连、授权管理员结案、普通玩家/未知 executor/伪造 owner/跨 owner target 拒绝、过期/重连/关服竞态、重复扫描幂等、指数退避 cap、10 次/7 天 dead-letter、人工 retry/resolve 权限,以及 claim 回收后另一玩家可获取同一设施。 +- `PendingInsightOffer` pins:创建后正常选择/拒绝、deadline 前后与 off-by-one、同 tick response-versus-timeout 竞态、断线/跨维/关服清除,以及过期后不得再次接受选择。 ### P2 — alchemy / forge / lingtian @@ -261,6 +263,7 @@ pub trait InteractionSession { 7. `session_delivery_crash_atomicity`:在 outbox/terminal checkpoint/receipt/inventory-or-spill/ack 各持久化边界强杀重启,断言 stable `delivery_id` 最终只交付一次;失败按指数退避,在 10 次或 7 天转 `DeadLetter`,且从 handoff 起不占设施 claim。 8. `session_termination_cause_matrix`:逐项命中 `VoluntaryCancel`、`Disconnect`、`DimensionChange`、`Shutdown`、`InvalidRestore`、`SuspensionExpired`,对拍 checkpoint/refund 互斥与 inputs/output 不双发。 9. `session_craft_pause_resume_wire`:真实 client producer → R6 proto/bridge → R4 production handler/gate → R1 registry → S2C hydrate → client reopen consumer 全链路。 -10. `session_tsy_presence_relog`:R3 guarded presence Slice 完整恢复 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version 并与 position/dimension 同事务对拍;校验失败不 attach、不开放 TSY 请求,恢复后 death-drop 仍正确区分原带物与 TSY 所得。 +10. `session_tsy_presence_relog`:R3 guarded presence Slice 完整恢复 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version 并与 position/dimension 同事务对拍;分别注入 facility 不存在、owner 不匹配、dimension 不匹配、version 不匹配四类 guarded-load 拒绝,任何一类都不 attach、不开放 TSY 请求;恢复后 death-drop 仍正确区分原带物与 TSY 所得。 +11. `session_pending_insight_offer_deadline`:覆盖创建后选择/拒绝、deadline 前后与 off-by-one、同 tick response-versus-timeout 竞态、断线/跨维/关服清除,以及过期后不得再次接受选择。 另回归现有 `production_craft_disconnect_resume.py`、`production_craft_cancel_full_inventory_refund.py`、`production_handcraft_stone_knife.py`。对应 implementation PR 合入后,按总纲 §7 为吸收项补 Finish Evidence 并做每轨一次 docs-only 批量归档。 diff --git a/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md b/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md index 34e408d73..4c11320ae 100644 --- a/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md +++ b/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md @@ -1,10 +1,10 @@ # plan-refactor-c2s-gate-v1 — C2S 请求统一门禁中间件 + client_request_handler 巨石拆分(重构轨 R4) -> 所属总纲:`plan-refactor-master-v1.md`。一句话:给 113 种 C2S 请求建统一的声明式门禁层(距离/维度/所有权/状态前置),同时把 20082 行的 `client_request_handler.rs`(单函数 2438 行、17 个 SystemParam)拆成按域注册的 handler 模块——"只信裸坐标可跨维远程操作"这一整簇(20+ 份 plan)从此在架构上不可能。 +> 所属总纲:`plan-refactor-master-v1.md`。一句话:给 116 种 C2S 请求建统一的声明式门禁层(距离/维度/所有权/状态前置),同时把 20082 行的 `client_request_handler.rs`(单函数 2438 行、17 个 SystemParam)拆成按域注册的 handler 模块——"只信裸坐标可跨维远程操作"这一整簇(20+ 份 plan)从此在架构上不可能。 ## 现状证据(2026-07-27 侦察) -- `network/client_request_handler.rs:522` `handle_client_request_payloads` 单函数跨 522-2960 行,巨型 match 覆盖 `ClientRequestV1` 的 113 个变体;`CombatRequestParams` 一个 SystemParam 30 个字段。 +- `network/client_request_handler.rs:522` `handle_client_request_payloads` 单函数跨 522-2960 行,巨型 match 覆盖 `ClientRequestV1` 的 116 个变体;`CombatRequestParams` 一个 SystemParam 30 个字段。 - 门禁分散手写:8 个文件各自定义 `*_MAX_DISTANCE`/`*_RANGE_SQ`(`client_request_handler.rs:468-469`、`craft/workbench.rs:76` Chebyshev、`mineral/probe.rs`、`npc/relic.rs`、`supply_coffin/authority.rs`、`zhenfa/network_array.rs` 等),距离度量都不统一;`CurrentDimension` 在 handler 内被 13 处内联比对,无统一 helper。 - 后果即 bughunt 大簇:跨维开工作台/布阵/夺舍/交易/拾取、无 reach 校验放方块、先扣物品后校验吞丹、无所有权校验拆棺。 - `zone-lookup-overworld-hardcode`:zone 查找硬编码主世界,是维度感知缺失的底层同源。 @@ -18,10 +18,10 @@ ## 阶段 -- ⬜ P0 设计收口 + 吸收清单验真:113 个变体普查(每个标注应有的门禁四元组现状);冻结 `GateSpec` 与拒绝回执语义;等 #1287(冷却重构,同文件大改)merge 定基线。 +- ⬜ P0 设计收口 + 吸收清单验真:116 个变体普查(每个标注应有的门禁四元组现状);冻结 `GateSpec` 与拒绝回执语义;等 #1287(冷却重构,同文件大改)merge 定基线。 - ⬜ P1 门禁中间件落地:gate 层上线,先给"已知漏洞簇"与 R1 craft `CraftOpen`/`CraftPause`/`CraftResume` 挂 spec;新增 intent 必须在同一提交进入 production decode/dispatch 和全量 gate matrix,旧内联校验保留并行断言一个版本期。 - ⬜ P2 巨石拆分批次 A:巨型 match 拆为按域 handler 注册表(combat/production/world/social/npc 五组),行为不变,bot 场景锁住。 -- ⬜ P3 巨石拆分批次 B + 全量挂 spec + 删旧:113 变体全部声明门禁(含显式 `no_gate` 声明,杜绝静默无门禁);删除各域内联距离常量与重复维度判断。 +- ⬜ P3 巨石拆分批次 B + 全量挂 spec + 删旧:116 变体全部声明门禁(含显式 `no_gate` 声明,杜绝静默无门禁);删除各域内联距离常量与重复维度判断。 - ⬜ P4 bot 验收 + 吸收 plan 批量归档。 ## 吸收清单(短名省略 plan-bughunt- 前缀与 -v1 后缀) @@ -41,7 +41,7 @@ skeleton:alchemy-furnace-scope-gate、block-place-reach-gate、coffin-reclaim- 2. `gate_reach`:超距放方块/开炉/采灵田→拒绝;贴脸→放行。 3. `gate_ownership`:拆他人棺/取他人容器→拒绝。 4. `gate_state_precondition`:给丹先校验后扣(满包/死亡目标不吞丹);丹毒超阈值禁服。 -5. `gate_matrix_sweep`:对 113 变体做参数化扫描(合法/超距/跨维三档),断言与声明的 GateSpec 一致——这是本轨的主回归门。 +5. `gate_matrix_sweep`:对 116 变体做参数化扫描(合法/超距/跨维三档),断言与声明的 GateSpec 一致——这是本轨的主回归门。 ## 开放问题(pre-P0 收口) diff --git a/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md b/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md index e56b49d41..4e2a09aa6 100644 --- a/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md +++ b/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md @@ -16,13 +16,13 @@ - **进料**:R2 的 `SessionScopedStore`(Screen 订阅的一律是会话态 store);`ServerDataRouter` handler(一律经 client-thread marshal 投递 UI)。 - **出料**:Screen/HUD 展示;HUD 纪律沿用既有 memory 约束(未解锁隐藏不灰掉、沉浸式极简)。 - **共享类型**:新 `BongScreenBase`(生命周期 + 订阅 + 关闭清理)、`DiffListWidget`(推广 craft 范本)、`BongKeybindRegistry`(注册时冲突检测 + 测试期断言)、`ClientThreadMarshal` helper、`ScreenOpenPolicy`(礼貌抢屏:战斗中/已有模态时排队)。 -- **跨仓库契约**:本轨不定义 wire;消费 R6 冻结的 `CraftOpen`/`CraftPause`/`CraftResume` 与 S2C session-state payload。Craft Screen 关闭只发送 `CraftPause`,显式取消按钮只发送 `CraftCancel`,重开后由 server state hydrate 决定是否发送 `CraftResume`。 +- **跨仓库契约**:本轨不定义 wire;消费 R6 冻结的 `CraftOpen`/`CraftPause`/`CraftResume` 与 S2C session-state payload。Craft Screen 首次进入/点击继续生产发送 `CraftOpen`,关闭只发送 `CraftPause`,显式取消按钮只发送 `CraftCancel`,重开后由 server state hydrate 决定是否发送 `CraftResume`。P2 client contract pin 覆盖四条 production intent:首次打开→`CraftOpen`、关闭→`CraftPause`、显式取消→`CraftCancel`、重开 hydrate→`CraftResume`。 ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:92 处 fill(100) 全量分类(根节点合法/子节点顶飞);28 Screen 普查;冻结基类 API 与四个共享组件。 - ⬜ P1 基础组件落地:BongScreenBase/DiffListWidget/KeybindRegistry/ClientThreadMarshal/ScreenOpenPolicy 上线;keybind 冲突全数改绑(T/L/O/U/G 簇)。 -- ⬜ P2 Screen 迁移批次 A:炼丹/锻造/手搓/交易屏迁基类,fill(100) 顶飞点与 clearChildren 回弹点随迁修复;Craft Screen 完成生产接线:close→`CraftPause`、独立取消按钮→`CraftCancel`、reopen hydrate→`CraftResume`,并以 client 单测锁住三条 intent 不互相替代。 +- ⬜ P2 Screen 迁移批次 A:炼丹/锻造/手搓/交易屏迁基类,fill(100) 顶飞点与 clearChildren 回弹点随迁修复;Craft Screen 完成生产接线:首次进入/点击继续→`CraftOpen`、close→`CraftPause`、独立取消按钮→`CraftCancel`、reopen hydrate→`CraftResume`,并以 client 单测锁住四条 intent 不互相替代。 - ⬜ P3 InspectScreen 拆解:按 tab/section 拆组件文件(body/container/tooltip 已有雏形),行为不变。 - ⬜ P4 Screen 迁移批次 B + 网络线程 marshal 强制(handler 层静态检查/测试)+ 删旧。 - ⬜ P5 验收 + 吸收 plan 批量归档。 @@ -40,7 +40,7 @@ skeleton:alchemy-screen-fill100-eviction 与 alchemy-screen-fill-overflow( ## 验收 -bot 测不到 client 渲染,本轨主验收 = client 单测(基类生命周期 pin、DiffListWidget 滚动保持、keybind 注册表无冲突断言、marshal 强制扫描)+ `./gradlew runClient` 人工过一遍五大屏。bot 配合:`ui_c2s_smoke`(各屏的 C2S 动作链路照常可达,防拆解断线)。 +bot 测不到 client 渲染,本轨主验收 = client 单测(基类生命周期 pin、DiffListWidget 滚动保持、keybind 注册表无冲突断言、marshal 强制扫描、CraftOpen/CraftPause/CraftCancel/CraftResume 四条 intent producer pin)+ `./gradlew runClient` 人工过一遍五大屏。bot 配合:`ui_c2s_smoke`(各屏的 C2S 动作链路照常可达,防拆解断线)。 ## 开放问题(pre-P0 收口) diff --git a/docs/plans-skeleton/plan-refactor-master-v1.md b/docs/plans-skeleton/plan-refactor-master-v1.md index d7b79f3c7..20206ee04 100644 --- a/docs/plans-skeleton/plan-refactor-master-v1.md +++ b/docs/plans-skeleton/plan-refactor-master-v1.md @@ -98,7 +98,7 @@ 1. 9 条轨道全部归档(各自 bot 场景常绿 + 吸收 plan 全部归档/验伪结案); 2. 三个 2 万行级 god file(inventory/mod.rs、client_request_handler.rs、persistence/mod.rs)不复存在,最大单文件 < 3000 行; -3. `qi_current` 裸写编译不过;client 无未登记的会话态 store;113 C2S 变体全部有显式 GateSpec/no_gate 声明;28 旁路 channel 收编或豁免登记; +3. `qi_current` 裸写编译不过;client 无未登记的会话态 store;116 C2S 变体全部有显式 GateSpec/no_gate 声明;28 旁路 channel 收编或豁免登记; 4. bot 场景数从 ~30 增至 ≥80,CI e2e 是唯一主门禁且无已知假绿。 5. `flash-review` label 下 open issue 全部显式处置(fixed / dup / 验伪关闭 / 促升 skeleton,见 §10),无静默积压。 diff --git a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md index 44b8c271b..6ce2ab293 100644 --- a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md +++ b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md @@ -13,7 +13,7 @@ ## 接入面 - **进料**:SQLite(bong.db,沿用)、`shutdown.rs`(#1261 之后的关服链路)、`CultivationClock`(相对 tick 基准)。 -- **出料**:统一 Slice API 供各域注册:`load(guarded) / autosave(cadence) / flush_on_shutdown / tick_rebase`;R1 的 session 持久化钩子、各域运行态表全部走它。`tick_rebase` 对 R1 suspension lease 保持相对剩余时长;R1 冻结默认 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000` 与扫描 cadence `1_200` ticks,本轨不得把重启折算成无限续租。为 terminal delivery 提供 `SessionDeliveryOutbox`:事务内只提交 outbox 插入与 session checkpoint terminalization/删除,提交成功后 runtime gameplay claim 释放必须可依据 durable terminal/outbox 状态幂等重放;禁止仅持久化 session、把 delivery id 留在内存。每个 checkpointed session 的 admission/restore 先预留 durable terminal-obligation slot,`SessionDeliveryOutbox` 设可观测 row/bytes quota;quota 满时 fail closed,不得新建/恢复会增加 retained obligation 的 checkpointed session。dead-letter 从自动扫描集合移除且不占 claim,quota 仅在 committed 或授权且不可丢 payload 的 audited resolve 后释放。 +- **出料**:统一 Slice API 供各域注册:`load(guarded) / autosave(cadence) / flush_on_shutdown / tick_rebase`;R1 的 session 持久化钩子、各域运行态表全部走它。`tick_rebase` 对 R1 suspension lease 保持相对剩余时长;R1 冻结默认 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000` 与扫描 cadence `1_200` ticks,本轨不得把重启折算成无限续租。为 terminal delivery 提供 `SessionDeliveryOutbox`:事务内只提交 outbox 插入与 session checkpoint terminalization/删除,提交成功后 runtime gameplay claim 释放必须可依据 durable terminal/outbox 状态幂等重放;禁止仅持久化 session、把 delivery id 留在内存。每个 checkpointed session 的 admission/restore 先预留一行和固定 `SESSION_DELIVERY_MAX_PAYLOAD_BYTES` 字节的 durable terminal-obligation capacity;payload 增长不得超过该上限,超限必须在接受新 escrow/产物前 fail closed。`SessionDeliveryOutbox` 仍设可观测 row/bytes quota;quota 满时不得新建/恢复会增加 retained obligation 的 checkpointed session。dead-letter 从自动扫描集合移除且不占 claim,quota 仅在 committed 或授权且不可丢 payload 的 audited resolve 后释放。 - **共享类型**:新 `server/src/persistence/` 多文件模块(按域拆表定义 + 迁移链保持线性单入口);`PlayerSliceRegistry`(对齐 #1290 skeleton 的方向,直接吸收它)。 - **跨仓库契约**:零 wire 改动。 - **qi_physics 锚点**:任何带 qi 的快照持久化/恢复不得造成账面变化;恢复失败的兜底路径必须走 `release_dormant_qi_to_zone` 而非丢弃(对齐守恒律红旗清单)。 @@ -21,9 +21,9 @@ ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:53 张表普查归域;冻结 Slice trait(载入守护语义:读失败 = 保留旧行 + 告警 + 只读降级,绝不写回空态;flush registry;tick rebase 协议);等 #1288/#1289/#1261/#1259 merge 后定基线。 -- ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;注册 R1 所需的 `TsyPresence` auxiliary Slice,字段固定为 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version,并接入 guarded load、autosave、`flush_on_shutdown`、`tick_rebase`;落 `SessionDeliveryOutbox` 表与 atomic handoff API,stable `delivery_id`、完整 payload、cause、attempts/next_retry/created_at/state 全部持久化。 +- ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;注册 R1 所需的 `TsyPresence` auxiliary Slice,字段固定为 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version,并接入 guarded load、autosave、`flush_on_shutdown`、`tick_rebase`;落 `SessionDeliveryOutbox` 表与 atomic handoff API,stable `delivery_id`、完整 payload、cause、attempts/next_retry/created_at/state 全部持久化。P1 acceptance 必须同时锁定 checkpoint terminalization/deletion 与 outbox insert 的同一 SQLite transaction 全成或全败,以及 outbox insert 前、terminalization 前、commit 后 ack 前的 crash-injection pins;P3 只做其余 flush registry/tick-rebase 批次,不得把该 atomic handoff 延后。 - ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;身份主键统一(identity-persist-key-mismatch)。 -- ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部"节流落盘"域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥(coffin-autosave-inflight-race 模式);`TsyPresence` 的关服 flush 与 presence/position/dimension 同事务顺序固定;outbox handoff 的 checkpoint 删除/terminalization 与 outbox insert 在同一 transaction 全成或全败,并以 crash injection pin 每个 commit boundary。 +- ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部"节流落盘"域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥(coffin-autosave-inflight-race 模式);`TsyPresence` 的关服 flush 与 presence/position/dimension 同事务顺序固定。 - ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性(heiwushi)等——逐个按 Slice 框架补表。加入 `TsyPresence` guarded relog parity 契约测试和 TSY 维度重启 bot 场景,断言失败加载不 attach presence、成功恢复后才重新开放 TSY 请求。 - ⬜ P5 bot 验收 + 吸收 plan 批量归档。 @@ -46,7 +46,7 @@ skeleton:coffin-autosave-inflight-race、identity-persist-key-mismatch、miner 4. `tick_rebase`:带冷却/再生倒计时重启→断言倒计时按真实流逝折算(对齐 #1289 的 deadline 折算先例)。 5. `tsy_presence_relog_parity`:进入 TSY→关服 flush→guarded load→只有 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version 校验通过才 attach `TsyPresence` 并开放 TSY 请求;损坏或缺失 Slice 保持未 attach 且拒绝请求;恢复后 death-drop 对原带物继续执行 50%/武器保护,对 TSY 所得执行既有 100% 规则。 6. `tsy_presence_shutdown_order`:断言 session registry 静止后才 flush presence slice,且 position/dimension 与 presence 同一恢复事务对拍。 -7. `session_delivery_outbox_atomicity`:分别在 outbox insert 前、insert 后 checkpoint terminalize 前、事务 commit 后 ack 前强杀重启;断言 checkpoint/outbox 恰有一个权威 owner,完整 payload 不丢且 stable `delivery_id` 不变。 +7. `session_delivery_outbox_atomicity`:分别在 outbox insert 前、insert 后 checkpoint terminalize 前、事务 commit 后 ack 前强杀重启;另注入已 admission 但 payload 达到 `SESSION_DELIVERY_MAX_PAYLOAD_BYTES` 的边界,断言超限在接受新 escrow/产物前 fail closed;正常路径断言 checkpoint/outbox 恰有一个权威 owner,完整 payload 不丢且 stable `delivery_id` 不变。 ## 开放问题(pre-P0 收口) diff --git a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md index 63b6f98f4..804405f5f 100644 --- a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md +++ b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md @@ -22,7 +22,7 @@ - ⬜ P1 emit builder + scope 落地:builder 上线,vfx/audio/env 三类先挂 scope(跨维 bleed 立灭);跨位面切换时 env/season 全量重发;同时交付 R1 craft 所需 `CraftOpen`/`CraftPause`/`CraftResume` proto/schema/`proto_convert` 与 S2C session-state sample pins,生产 decoder/handler 由 R4 消费。 - ⬜ P2 client 桥接层收敛:枚举前缀剥离收敛到单点(含 forge-session 修复);`ServerDataRouter` 注册表整备(分域注册文件,不再单个 1547 行 switch 追加)。 - ⬜ P3 旁路归一批次:28 channel 逐批收编入 server_data envelope 或登记豁免(资源包/握手类可豁免);删除散装 receiver。 -- ⬜ P4 契约 pin 全量化:双向 sample 对拍测试补齐(113 C2S + 144 S2C 每变体至少一条正反 sample,schema 改动连 sample 一起改);emit 迁移到 builder 的长尾批次。 +- ⬜ P4 契约 pin 全量化:双向 sample 对拍测试补齐(116 C2S + 144 S2C 每变体至少一条正反 sample,schema 改动连 sample 一起改);emit 迁移到 builder 的长尾批次。 - ⬜ P5 bot 验收 + 吸收 plan 批量归档。 ## 吸收清单(短名省略 plan-bughunt- 前缀与 -v1 后缀) From e8279c6d939aa2fe7955fa79f64fd1c727677266 Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Mon, 3 Aug 2026 15:45:16 +0800 Subject: [PATCH 05/14] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20R1=20P0=20=E5=AE=89?= =?UTF-8?q?=E5=85=A8=E5=8E=9F=E5=AD=90=E6=80=A7=E4=B8=8E=20craft=20?= =?UTF-8?q?=E8=B7=A8=E8=BD=A8=E5=A5=91=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Model: cc-sonnet-high Co-Authored-By: Claude --- docs/plan-refactor-server-session-v1.md | 14 +++++++------- docs/plans-skeleton/plan-refactor-c2s-gate-v1.md | 10 +++++----- .../plan-refactor-client-ui-base-v1.md | 4 ++-- .../plan-refactor-inventory-core-v1.md | 4 ++-- .../plan-refactor-persistence-slices-v1.md | 8 ++++---- docs/plans-skeleton/plan-refactor-wire-s2c-v1.md | 6 +++--- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/plan-refactor-server-session-v1.md b/docs/plan-refactor-server-session-v1.md index 03928d35e..5b9e3d181 100644 --- a/docs/plan-refactor-server-session-v1.md +++ b/docs/plan-refactor-server-session-v1.md @@ -74,8 +74,8 @@ pub trait InteractionSession { - `SuspensionPolicy`:声明 checkpointed session 的 `suspended_at_tick`、有限 `max_suspended_ticks`、过期扫描周期、管理员强制结案入口和 facility claim 回收动作;默认值冻结为 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000`(20 TPS 下 24 小时)与 `SESSION_SUSPENSION_SCAN_INTERVAL_TICKS = 1_200`,不得配置为无限期保留。 - `SessionTransition::{Keep, Pause, SuspendAndCheckpoint, Teardown, AwaitDelivery, DeliveryPending, CommitTerminal}`。 - `SessionDeliveryOutbox { delivery_id, session_key, owner_key, payload_digest, payload, cause, state, attempts, next_retry_tick, created_at_tick }`:R3 持久化的 durable handoff;`delivery_id` 由 `SessionKey + terminal_generation` 稳定派生,禁止仅靠内存幂等键。 -- `DeliveryOutboxState::{Pending, DeadLetter, Committed}` 与 durable `DeliveryCommitReceipt`:R10 delivery 的跨重启 exactly-once 依据。 -- `SessionMaintenancePermissions`:管理员结案命令的显式 allow-list resource;只信 command executor entity 上的权威 `Username`,不得信请求参数中的 operator/owner 字符串。 +- `DeliveryOutboxState::{Pending, InFlight { lease_id, lease_until }, DeadLetter, Committed, ResolvedDisposition}`,单调 `generation` 与 durable `DeliveryCommitReceipt`:R10 delivery 的跨重启 exactly-once 依据;worker、dead-letter scanner、operator retry/resolve 均以 `(state,generation)` CAS 串行化。 +- `SessionMaintenancePermissions`:只接受 server console origin,或 client 不可选择且已绑定当前 executor entity 的 authenticated principal/capability;`Username` 仅审计展示,绝不参与授权。offline mode 的 player executor 一律无 maintenance authority。 - `BusyClaim`:声明 owner 与 world target 上占用的 busy classes;冲突矩阵集中注册。 ### 2.1 不变量 @@ -122,14 +122,14 @@ pub trait InteractionSession { - 每个 checkpoint 写入 `suspended_at_tick`、`last_rebase_epoch`、owner `PlayerKey`、facility/target claim 和 escrow 摘要;不得只靠数据库行存在判断是否仍可恢复。 - 每个宿主显式选择 `SuspensionPolicy.max_suspended_ticks`;P1 craft、P2 alchemy/forge 使用冻结默认值 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000`,扫描 cadence 为 `SESSION_SUSPENSION_SCAN_INTERVAL_TICKS = 1_200`。R3 `tick_rebase` 保持剩余 TTL 的相对时长,关服时间不得让 lease 永久延长;P3 世界交互不得继承无限值。 -- registry 每 tick 或固定 cadence 扫描过期 session;只有通过 `SessionMaintenancePermissions::is_allowed(executor_username)` 的 operator 才能用 `session-maintenance terminate ` 提前结案。executor identity 必须从 `CommandResultEvent.executor` 查询权威 `Username`,不得由命令 payload 声明;普通玩家、未知 executor、伪造 owner 和跨 owner target 一律拒绝且不改变 session/outbox/claim。扫描、授权管理员结案、重连与关服 flush 竞争时以 `SessionKey` CAS/registry lock 保证一次结算。 +- registry 每 tick 或固定 cadence 扫描过期 session;只有 `SessionMaintenancePermissions::is_allowed(execution_origin, authenticated_principal, executor)` 才能用 `session-maintenance terminate `。仅 server console origin 或 server 已认证且绑定 executor 的 principal/capability 可通过;offline-mode player `Username`、命令 payload 或 owner 字符串均不能产生授权。普通玩家、未知 executor、伪造 allow-listed username/owner 和跨 owner target 一律拒绝且不改变 session/outbox/claim。扫描、授权管理员结案、重连与关服 flush 竞争时以 `SessionKey` CAS/registry lock 保证一次结算。 - 过期结案按 `TerminationCause::SuspensionExpired` 执行:停止 tick → 在 R3 checkpoint 事务中写入稳定 `delivery_id` 的 `SessionDeliveryOutbox` 并转为 `DeliveryPending` → 事务成功后幂等释放 facility/target/busy claim → 从 live registry 移除;R10 receipt 提交后再标记 `Terminal`。若进程在提交后、runtime claim 清理前退出,启动恢复必须依据已提交的 terminal/outbox 状态补做同一释放,不能重新 attach 或继续占用。R10 异步消费 outbox;不得以 delivery 临时失败为由继续占设施。 -- outbox 重试冻结为指数退避 `min(1_200 * 2^attempts, 72_000)` ticks,并带确定性 `delivery_id` 去重;最多自动尝试 `SESSION_DELIVERY_MAX_ATTEMPTS = 10` 次或保留 `SESSION_DELIVERY_MAX_AGE_TICKS = 12_096_000`(7 天),任一先到转 `DeadLetter` 并停止自动扫描。Dead-letter 仍是有界、可审计的 durable delivery obligation,不占 gameplay claim;只允许授权 operator 执行 inspect/retry/resolve,resolve 不得静默丢 payload。 -- R3 `SessionDeliveryOutbox` 既是 checkpoint terminalization 的 durable owner,也是失败交付的唯一 retained obligation。每个 `Checkpointed` session 在创建/恢复并取得 facility claim 前必须预留一个 durable terminal-obligation slot;无可用 slot 时拒绝创建或恢复该 session,不得先取得 claim 再等待容量。`Suspended` checkpoint 持有的 slot 在 terminal handoff 时与 checkpoint 行在同一事务内转移给 `SessionDeliveryOutbox`,因此 handoff 不会因 quota 耗尽而留下新的不可回收 claim。`Pending`/`DeadLetter` 不重新创建 session、facility 或 target claim;自动扫描只处理有 `next_retry_tick` 且未达到 attempts/age 上限的记录,进入 `DeadLetter` 后从自动扫描集合移除。R3 必须给 `SessionDeliveryOutbox` 配置可观测的 durable row/bytes quota;quota 满时拒绝新的 checkpointed-session admission/restore,保留已有 checkpoint、escrow 与其既有 claim 并告警,不得静默删除 payload、覆盖旧 delivery 或继续接受会使 retained state 增长的 session。quota 释放仅在 `Committed` 或授权 operator 按审计流程完成不可丢 payload 的 `resolve` 后发生。这样失败交付不会永久占用已释放的 gameplay claim,也不会把 durable obligation 变成无界积压。 +- outbox 每行携带单调 `generation`,状态为 `Pending | InFlight { lease_id, lease_until } | DeadLetter | Committed | ResolvedDisposition`。自动 worker 仅以 expected `(state,generation)` CAS claim `Pending→InFlight`;delivery receipt + inventory/spill + `InFlight→Committed` 同事务提交。dead-letter scanner 仅 CAS 非活跃/已过期 lease,operator retry/resolve 也必须 CAS 当前 generation;CAS loser 重读,不得覆盖 worker/scanner/operator 的并发结果或重复释放 quota。重试冻结为指数退避 `min(1_200 * 2^attempts, 72_000)` ticks;10 次或 7 天后转 `DeadLetter` 并停止自动扫描。 +- R3 `SessionDeliveryOutbox` 是失败交付的唯一 retained obligation。每个 `Checkpointed` session 在取得 claim 前预留 durable terminal-obligation capacity;不足则拒绝创建/恢复。terminal handoff 与 checkpoint 行同事务转移 reservation。`Pending`/`InFlight`/`DeadLetter` 不重建 session 或 claim;quota 满时 fail closed,禁止删除/覆盖 payload。`resolve` 必须在同一事务把完整 payload 移入持久 `ResolvedDisposition { delivery_id, operator_principal, reason, payload }`(或已提交 receipt),审计成功后才删除 dead-letter row/释放 quota;禁止无 disposition 删除未交付 payload。 - `DeliveryPending` 已提交 durable handoff 但未 receipt;R10 delivery 失败或进入 `DeadLetter` 时仍保持该 phase 的 durable obligation,绝不回退为 `AwaitingDelivery` 或重新占用 claim。 - claim 回收与 outbox 状态变化必须有审计事件(session key、owner key hash、operator identity hash/`system`、cause、released claims、delivery id/result/attempt),不记录原 payload;恢复窗口内仍允许 owner 正常 reopen,写入 outbox 后 reopen 明确拒绝。 -- P1/P4 必须覆盖:永不重连的 abandoned session 最终释放设施、TTL 边界前后、授权管理员结案、普通玩家/未知 executor/伪造 owner/跨 owner target 拒绝、过期与重连竞态、重复扫描幂等、outbox 事务 crash points、receipt 重放去重、退避边界、10 次/7 天 dead-letter、quota 满时 checkpointed admission/restore fail-closed、quota 释放后的重新 admission,以及回收后另一玩家可获得同一 furnace/station claim。 +- P1/P4 必须覆盖:永不重连的 abandoned session 最终释放设施、TTL 边界前后、授权管理员结案、offline client 伪造 allow-listed `Username` 仍拒绝、普通玩家/未知 executor/伪造 owner/跨 owner target 拒绝、过期与重连竞态、重复扫描幂等、outbox 事务 crash points、receipt 重放去重、worker↔scanner 与 worker↔operator retry/resolve generation-CAS 竞态、过期 lease 恢复、退避边界、10 次/7 天 dead-letter、授权 resolve 无 receipt/durable disposition 时 payload 与 quota 保留、quota 满时 checkpointed admission/restore fail-closed、quota 释放后的重新 admission,以及回收后另一玩家可获得同一 furnace/station claim。 ### 2.3 durability 决议矩阵 | 状态族 | durability | 断线/关服 | 跨维 | @@ -235,7 +235,7 @@ pub trait InteractionSession { - 新增 `server/src/session/{mod.rs,registry.rs,lifecycle.rs}`,包含 §2 全部 symbol;framework-only 可在 R3 P1 后落地,但不启用生产 craft adapter。 - R6 `CraftOpen`/`CraftPause`/`CraftResume` 契约、R4 production handler/gate、R7 P2 Craft Screen producer/consumer(消费 R2 P1 已登记的 `CraftStore`)、R10 P1 `deliver` contract 与 R10 P2 craft production delivery 全部合入后,craft 才迁移到 `SessionRegistry`:关闭 screen pause、显式 cancel、重开 resume,现有 recipe/session join hydration 行为不变。 - contract pins:六态转换、stable owner 重绑、disconnect-before-save、dimension-before-transfer、busy 冲突、delivery commit gate,以及 §2.5 六个 `TerminationCause` 的逐变体正反测试;明确断言 `Disconnect`/`Shutdown` checkpoint 不退款,terminal refund 不保留可恢复 escrow。delivery pins 必须在 transaction 的“outbox 写入前/写入后 terminalize 前/receipt+inventory commit 后 ack 前”逐点模拟进程退出并重载,断言 payload 总数恰为一次;SQLite 同库路径另断言 inventory/spill、receipt、checkpoint 删除在单事务全成或全败。 -- `SuspensionPolicy` pins:TTL 前后、永不重连、授权管理员结案、普通玩家/未知 executor/伪造 owner/跨 owner target 拒绝、过期/重连/关服竞态、重复扫描幂等、指数退避 cap、10 次/7 天 dead-letter、人工 retry/resolve 权限,以及 claim 回收后另一玩家可获取同一设施。 +- `SuspensionPolicy` pins:TTL 前后、永不重连、授权管理员结案、offline client 伪造 allow-listed `Username` 仍拒绝、普通玩家/未知 executor/伪造 owner/跨 owner target 拒绝、过期/重连/关服竞态、重复扫描幂等、指数退避 cap、10 次/7 天 dead-letter、人工 retry/resolve 权限,以及 claim 回收后另一玩家可获取同一设施。另须持久化一半已消耗的 lease,重启后 rebasing 只保留原剩余 TTL;连续第二次/第三次重启不得刷新 `suspended_at_tick`,并覆盖 `remaining-1`、精确边界、`remaining+1`。 - `PendingInsightOffer` pins:创建后正常选择/拒绝、deadline 前后与 off-by-one、同 tick response-versus-timeout 竞态、断线/跨维/关服清除,以及过期后不得再次接受选择。 ### P2 — alchemy / forge / lingtian diff --git a/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md b/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md index 4c11320ae..dc5eaca90 100644 --- a/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md +++ b/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md @@ -1,6 +1,6 @@ # plan-refactor-c2s-gate-v1 — C2S 请求统一门禁中间件 + client_request_handler 巨石拆分(重构轨 R4) -> 所属总纲:`plan-refactor-master-v1.md`。一句话:给 116 种 C2S 请求建统一的声明式门禁层(距离/维度/所有权/状态前置),同时把 20082 行的 `client_request_handler.rs`(单函数 2438 行、17 个 SystemParam)拆成按域注册的 handler 模块——"只信裸坐标可跨维远程操作"这一整簇(20+ 份 plan)从此在架构上不可能。 +> 所属总纲:`plan-refactor-master-v1.md`。一句话:给当前 113 种 C2S 请求建统一的声明式门禁层(距离/维度/所有权/状态前置),并在 R6 的三个 craft lifecycle intent 落地后扩展到 116 种,同时把 20082 行的 `client_request_handler.rs`(单函数 2438 行、17 个 SystemParam)拆成按域注册的 handler 模块——"只信裸坐标可跨维远程操作"这一整簇(20+ 份 plan)从此在架构上不可能。 ## 现状证据(2026-07-27 侦察) @@ -18,10 +18,10 @@ ## 阶段 -- ⬜ P0 设计收口 + 吸收清单验真:116 个变体普查(每个标注应有的门禁四元组现状);冻结 `GateSpec` 与拒绝回执语义;等 #1287(冷却重构,同文件大改)merge 定基线。 -- ⬜ P1 门禁中间件落地:gate 层上线,先给"已知漏洞簇"与 R1 craft `CraftOpen`/`CraftPause`/`CraftResume` 挂 spec;新增 intent 必须在同一提交进入 production decode/dispatch 和全量 gate matrix,旧内联校验保留并行断言一个版本期。 +- ⬜ P0 设计收口 + 吸收清单验真:当前 113 个变体普查(每个标注应有的门禁四元组现状);冻结 `GateSpec` 与拒绝回执语义;R6 P1 合入 `CraftOpen`/`CraftPause`/`CraftResume` 后,基线目标扩为 116;等 #1287(冷却重构,同文件大改)merge 定基线。 +- ⬜ P1 门禁中间件落地:gate 层上线,先给"已知漏洞簇"与 R1 craft `CraftOpen`/`CraftPause`/`CraftResume` 挂 spec;R6 落地三种 intent 后,同一提交进入 production decode/dispatch 和全量 gate matrix,旧内联校验保留并行断言一个版本期。 - ⬜ P2 巨石拆分批次 A:巨型 match 拆为按域 handler 注册表(combat/production/world/social/npc 五组),行为不变,bot 场景锁住。 -- ⬜ P3 巨石拆分批次 B + 全量挂 spec + 删旧:116 变体全部声明门禁(含显式 `no_gate` 声明,杜绝静默无门禁);删除各域内联距离常量与重复维度判断。 +- ⬜ P3 巨石拆分批次 B + 全量挂 spec + 删旧:R6 craft intents 合入后 116 变体全部声明门禁(含显式 `no_gate` 声明,杜绝静默无门禁);删除各域内联距离常量与重复维度判断。 - ⬜ P4 bot 验收 + 吸收 plan 批量归档。 ## 吸收清单(短名省略 plan-bughunt- 前缀与 -v1 后缀) @@ -41,7 +41,7 @@ skeleton:alchemy-furnace-scope-gate、block-place-reach-gate、coffin-reclaim- 2. `gate_reach`:超距放方块/开炉/采灵田→拒绝;贴脸→放行。 3. `gate_ownership`:拆他人棺/取他人容器→拒绝。 4. `gate_state_precondition`:给丹先校验后扣(满包/死亡目标不吞丹);丹毒超阈值禁服。 -5. `gate_matrix_sweep`:对 116 变体做参数化扫描(合法/超距/跨维三档),断言与声明的 GateSpec 一致——这是本轨的主回归门。 +5. `gate_matrix_sweep`:当前 113 变体做参数化合法/超距/跨维扫描;R6 三个 craft intent 落地后追加 owner mismatch、wrong phase、conflicting busy claim、duplicate/replay、stale/mismatched session identity 负例,以及合法 owner + 正确 Running/Paused phase 正例,目标覆盖 116 变体并断言与声明的 `GateSpec` 一致——这是本轨的主回归门。 ## 开放问题(pre-P0 收口) diff --git a/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md b/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md index 4e2a09aa6..c4d9df3ad 100644 --- a/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md +++ b/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md @@ -16,13 +16,13 @@ - **进料**:R2 的 `SessionScopedStore`(Screen 订阅的一律是会话态 store);`ServerDataRouter` handler(一律经 client-thread marshal 投递 UI)。 - **出料**:Screen/HUD 展示;HUD 纪律沿用既有 memory 约束(未解锁隐藏不灰掉、沉浸式极简)。 - **共享类型**:新 `BongScreenBase`(生命周期 + 订阅 + 关闭清理)、`DiffListWidget`(推广 craft 范本)、`BongKeybindRegistry`(注册时冲突检测 + 测试期断言)、`ClientThreadMarshal` helper、`ScreenOpenPolicy`(礼貌抢屏:战斗中/已有模态时排队)。 -- **跨仓库契约**:本轨不定义 wire;消费 R6 冻结的 `CraftOpen`/`CraftPause`/`CraftResume` 与 S2C session-state payload。Craft Screen 首次进入/点击继续生产发送 `CraftOpen`,关闭只发送 `CraftPause`,显式取消按钮只发送 `CraftCancel`,重开后由 server state hydrate 决定是否发送 `CraftResume`。P2 client contract pin 覆盖四条 production intent:首次打开→`CraftOpen`、关闭→`CraftPause`、显式取消→`CraftCancel`、重开 hydrate→`CraftResume`。 +- **跨仓库契约**:本轨不定义 wire;消费 R6 冻结的 `CraftOpen`/`CraftPause`/`CraftResume` 与 S2C session-state payload。Craft Screen 首次进入/点击继续生产发送 `CraftOpen`,关闭只发送 `CraftPause`,显式取消按钮只发送 `CraftCancel`,重开后由 server state hydrate 决定是否发送 `CraftResume`。P2 client contract pin 覆盖四条 production intent:首次打开→`CraftOpen`、关闭→`CraftPause`、显式取消→`CraftCancel`、匹配的 paused session hydrate→恰好一次 `CraftResume`;idle/no-session、terminal、delivery-pending、missing/stale/mismatched session key 或 generation hydrate 均不得发送 `CraftResume`。 ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:92 处 fill(100) 全量分类(根节点合法/子节点顶飞);28 Screen 普查;冻结基类 API 与四个共享组件。 - ⬜ P1 基础组件落地:BongScreenBase/DiffListWidget/KeybindRegistry/ClientThreadMarshal/ScreenOpenPolicy 上线;keybind 冲突全数改绑(T/L/O/U/G 簇)。 -- ⬜ P2 Screen 迁移批次 A:炼丹/锻造/手搓/交易屏迁基类,fill(100) 顶飞点与 clearChildren 回弹点随迁修复;Craft Screen 完成生产接线:首次进入/点击继续→`CraftOpen`、close→`CraftPause`、独立取消按钮→`CraftCancel`、reopen hydrate→`CraftResume`,并以 client 单测锁住四条 intent 不互相替代。 +- ⬜ P2 Screen 迁移批次 A:炼丹/锻造/手搓/交易屏迁基类,fill(100) 顶飞点与 clearChildren 回弹点随迁修复;Craft Screen 完成生产接线:首次进入/点击继续→`CraftOpen`、close→`CraftPause`、独立取消按钮→`CraftCancel`、仅对匹配的 server-hydrated paused session reopen→`CraftResume`,并以 client 单测锁住四条 intent 不互相替代及 idle/terminal/delivery-pending/stale hydration 不发 Resume。 - ⬜ P3 InspectScreen 拆解:按 tab/section 拆组件文件(body/container/tooltip 已有雏形),行为不变。 - ⬜ P4 Screen 迁移批次 B + 网络线程 marshal 强制(handler 层静态检查/测试)+ 删旧。 - ⬜ P5 验收 + 吸收 plan 批量归档。 diff --git a/docs/plans-skeleton/plan-refactor-inventory-core-v1.md b/docs/plans-skeleton/plan-refactor-inventory-core-v1.md index 0f9938c8d..a3d917902 100644 --- a/docs/plans-skeleton/plan-refactor-inventory-core-v1.md +++ b/docs/plans-skeleton/plan-refactor-inventory-core-v1.md @@ -20,7 +20,7 @@ - ⬜ P0 设计收口 + 吸收清单验真:mod.rs 职责普查出拆分图(grid/txn/container/corpse/shelflife 接缝);冻结 `InventoryTxn` API 与满包溢出策略;等 craft-refund P4、#1294 相关项定基线。 - ⬜ P1 巨石拆分:按职责拆文件(行为不变,测试平移),`InventoryTxn` 骨架上线;冻结并实现 R1 可直接消费的 `deliver(delivery_id, items) -> Delivered | Spilled(fallback)`,其中 `delivery_id`/`DeliveryCommitReceipt` 必须 durable,receipt 与 inventory/spill mutation 同事务全成或全败,失败不提交。未达到该 gate 时 R1 只能落 registry/adapter 骨架,不能清 escrow/session。 -- ⬜ P2 交付路径统一:give/craft/alchemy/forge/loot 全部改走 `deliver`;先校验后扣全量化;满包场景全绿。R1 craft/alchemy/forge 的 `AwaitingDelivery → CommitTerminal` 生产路径只在本阶段对应调用点迁移后才算完成。消费 R3 `SessionDeliveryOutbox` 时自动重试按 `min(1_200 * 2^attempts, 72_000)` ticks,10 次或 7 天后转 durable `DeadLetter` 并停止自动重试;outbox handoff 后不持有 gameplay claim。授权 operator 可 inspect/retry/resolve,普通玩家不可调用,resolve 不得删除未交付 payload。 +- ⬜ P2 交付路径统一:give/craft/alchemy/forge/loot 全部改走 `deliver`;先校验后扣全量化;满包场景全绿。R1 craft/alchemy/forge 的 `AwaitingDelivery → CommitTerminal` 生产路径只在本阶段对应调用点迁移后才算完成。消费 R3 `SessionDeliveryOutbox` 时自动重试按 `min(1_200 * 2^attempts, 72_000)` ticks,10 次或 7 天后转 durable `DeadLetter` 并停止自动重试;outbox 行以单调 `generation` + `Pending → InFlight(lease) → Committed/DeadLetter` 的 expected-state CAS 串行化,worker、scanner、operator retry/resolve 的冲突 loser 不得覆盖新状态或重复释放 quota。授权 operator 可 inspect/retry/resolve,普通玩家不可调用;resolve 只有在已有 receipt 或完整 payload 已原子转入另一 durable retained disposition 后才可释放原行/quota,禁止删除未交付 payload。 - ⬜ P3 网格/堆叠一致性:拾取合并、占格同步、pack 回执、老存档布局迁移补课。 - ⬜ P4 bot 验收 + 吸收 plan 批量归档。 @@ -39,7 +39,7 @@ skeleton:alchemy-takeback-full-inventory-loss(交付垫层部分;session t 1. `inv_full_delivery_matrix`:满包状态下 craft 完工/取丹/锻造出炉/给予→断言产物按统一溢出策略落地,总数不丢。 2. `inv_delivery_crash_atomicity`:在 inventory/spill mutation、receipt commit、outbox ack 各边界强杀重启;相同 durable `delivery_id` 重放只返回原 receipt,物品或掉落实例总数始终恰为一次。 -3. `inv_delivery_dead_letter`:持续失败按指数退避且 cap 为 72,000 ticks,10 次或 7 天后停止自动扫描并进入 durable `DeadLetter`;普通玩家不能 inspect/retry/resolve,且整个失败周期不占 session facility claim。 +3. `inv_delivery_dead_letter`:持续失败按指数退避且 cap 为 72,000 ticks,10 次或 7 天后停止自动扫描并进入 durable `DeadLetter`;worker claim、scanner dead-letter、operator retry/resolve 并发时按 expected `(state,generation)` CAS,过期 lease 可安全恢复,任何 loser 不覆盖状态、不重复交付、不释放 quota。普通玩家不能 inspect/retry/resolve;授权 resolve 在无 committed receipt 或 durable retained handoff 时必须拒绝,payload digest/body 保持可恢复、原行/quota 继续保留,只有 durable disposition 提交后才允许删除/释放。 4. `inv_stack_merge`:拾取同类掉落→断言合并入既有堆叠。 5. `inv_footprint_sync`:旋转/移动占格物品→断言 client 快照占格一致(P6 protobuf 深断言)。 6. `inv_give_visibility`:dev give 后立即快照→断言实例可见可拾取(修 bot 基建自身的假阳性)。 diff --git a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md index 6ce2ab293..9f68231d3 100644 --- a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md +++ b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md @@ -21,9 +21,9 @@ ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:53 张表普查归域;冻结 Slice trait(载入守护语义:读失败 = 保留旧行 + 告警 + 只读降级,绝不写回空态;flush registry;tick rebase 协议);等 #1288/#1289/#1261/#1259 merge 后定基线。 -- ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;注册 R1 所需的 `TsyPresence` auxiliary Slice,字段固定为 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version,并接入 guarded load、autosave、`flush_on_shutdown`、`tick_rebase`;落 `SessionDeliveryOutbox` 表与 atomic handoff API,stable `delivery_id`、完整 payload、cause、attempts/next_retry/created_at/state 全部持久化。P1 acceptance 必须同时锁定 checkpoint terminalization/deletion 与 outbox insert 的同一 SQLite transaction 全成或全败,以及 outbox insert 前、terminalization 前、commit 后 ack 前的 crash-injection pins;P3 只做其余 flush registry/tick-rebase 批次,不得把该 atomic handoff 延后。 +- ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;注册 R1 所需的 `TsyPresence` auxiliary Slice,字段固定为 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version,并接入 guarded load、autosave、`flush_on_shutdown`、`tick_rebase`;落 `SessionDeliveryOutbox` 表与 atomic handoff API,stable `delivery_id`、完整 payload、cause、attempts/next_retry/created_at/state、单调 generation 与 in-flight lease 全部持久化。P1 acceptance 必须同时锁定 checkpoint terminalization/deletion 与 outbox insert 的同一 SQLite transaction 全成或全败,以及 outbox insert 前、terminalization 前、commit 后 ack 前的 crash-injection pins;P3 只做其余 flush registry/tick-rebase 批次,不得把该 atomic handoff 延后。 - ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;身份主键统一(identity-persist-key-mismatch)。 -- ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部"节流落盘"域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥(coffin-autosave-inflight-race 模式);`TsyPresence` 的关服 flush 与 presence/position/dimension 同事务顺序固定。 +- ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部"节流落盘"域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥(coffin-autosave-inflight-race 模式);`TsyPresence` 的关服 flush 与 presence/position/dimension 必须在同一 SQLite transaction 内提交,crash injection 只能观察到三者全旧或全新。 - ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性(heiwushi)等——逐个按 Slice 框架补表。加入 `TsyPresence` guarded relog parity 契约测试和 TSY 维度重启 bot 场景,断言失败加载不 attach presence、成功恢复后才重新开放 TSY 请求。 - ⬜ P5 bot 验收 + 吸收 plan 批量归档。 @@ -43,9 +43,9 @@ skeleton:coffin-autosave-inflight-race、identity-persist-key-mismatch、miner 1. `restart_player_slices`:bot 建号→修炼/学功法/受伤→关服重启→重连→断言功法/伤势/濒死后果/buff 全部还原。 2. `restart_world_runtime`:触发矿脉枯竭/配方解锁/zone influence→SIGTERM 关服→重启→断言无回滚无复活。 3. `load_failure_guard`:注入一行损坏 slice 数据→启动→断言该玩家进入守护降级而非清零覆盖(配 dev 命令注入)。 -4. `tick_rebase`:带冷却/再生倒计时重启→断言倒计时按真实流逝折算(对齐 #1289 的 deadline 折算先例)。 +4. `tick_rebase`:带冷却/再生倒计时重启→断言倒计时按真实流逝折算(对齐 #1289 的 deadline 折算先例);另持久化一半已消耗的 `SuspensionPolicy` lease,重启/rebase 只保留原剩余 TTL,连续重复重启不刷新 lease,并覆盖剩余时长前一 tick、精确边界、后一 tick。 5. `tsy_presence_relog_parity`:进入 TSY→关服 flush→guarded load→只有 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version 校验通过才 attach `TsyPresence` 并开放 TSY 请求;损坏或缺失 Slice 保持未 attach 且拒绝请求;恢复后 death-drop 对原带物继续执行 50%/武器保护,对 TSY 所得执行既有 100% 规则。 -6. `tsy_presence_shutdown_order`:断言 session registry 静止后才 flush presence slice,且 position/dimension 与 presence 同一恢复事务对拍。 +6. `tsy_presence_shutdown_order`:断言 session registry 静止后才 flush presence slice;向 presence、position、dimension 三个逻辑写入之间注入 shutdown crash,重启后断言三者只能全部保留旧快照或全部提交新快照,不接受 clean restore 对拍掩盖分事务写入。 7. `session_delivery_outbox_atomicity`:分别在 outbox insert 前、insert 后 checkpoint terminalize 前、事务 commit 后 ack 前强杀重启;另注入已 admission 但 payload 达到 `SESSION_DELIVERY_MAX_PAYLOAD_BYTES` 的边界,断言超限在接受新 escrow/产物前 fail closed;正常路径断言 checkpoint/outbox 恰有一个权威 owner,完整 payload 不丢且 stable `delivery_id` 不变。 ## 开放问题(pre-P0 收口) diff --git a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md index 804405f5f..4530dbc88 100644 --- a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md +++ b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md @@ -14,15 +14,15 @@ - **进料**:各域游戏事件(emit 调用点)、`world` 维度/zone 信息(作用域过滤)。 - **出料**:`bong:server_data` 单通道(目标态:28 旁路全部收编或显式豁免登记);join/重连首包快照集契约(R2 清干净后靠它灌满)。 - **共享类型**:新 server `network/emit/` builder(`scope: Global | Dimension | Zone | Player`);client 桥接层唯一的枚举前缀剥离函数。 -- **跨仓库契约**:proto 形状原则上不动(收编旁路时如需并入 envelope 属破坏性变更,走 buf breaking + samples 同步;agent 侧 TS 只做被动 regenerate,不重构 agent 逻辑)。**不做双轨兼容层**——旁路收编是一次性切换。R1 craft 例外要求显式冻结并一次性切换 `CraftOpen` / `CraftPause` / `CraftResume` C2S intent 与对应 S2C session-state payload;现有 `CraftCancel` 保留为唯一主动取消 intent,关屏不得复用它。 +- **跨仓库契约**:proto 形状原则上不动(收编旁路时如需并入 envelope 属破坏性变更,走 buf breaking + samples 同步;agent 侧 TS 只做被动 regenerate,不重构 agent 逻辑)。**不做双轨兼容层**——旁路收编是一次性切换。R1 craft 例外要求显式冻结并一次性切换 `CraftOpen` / `CraftPause` / `CraftResume` C2S intent 与对应 S2C session-state payload;现有 `CraftCancel` 保留为唯一主动取消 intent,关屏不得复用它。R6 同时拥有 `agent/packages/schema/src/client-request.ts` TypeBox source、生成 JSON Schema 与提交的 `@bong/schema` dist、protobuf/Rust mirror/converter、samples,以及 client `ClientRequestProtocol.encodeCraftOpen/Pause/Resume` 与 `ClientRequestSender.sendCraftOpen/Pause/Resume` producer APIs;R7 只消费这些接口。 ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:28 旁路逐个普查(收编 vs 豁免理由);100 emit 文件的重复模式取样归纳 builder API;枚举前缀剥离点全量清点;冻结 scope 语义与 join 首包快照集清单。 -- ⬜ P1 emit builder + scope 落地:builder 上线,vfx/audio/env 三类先挂 scope(跨维 bleed 立灭);跨位面切换时 env/season 全量重发;同时交付 R1 craft 所需 `CraftOpen`/`CraftPause`/`CraftResume` proto/schema/`proto_convert` 与 S2C session-state sample pins,生产 decoder/handler 由 R4 消费。 +- ⬜ P1 emit builder + scope 落地:builder 上线,vfx/audio/env 三类先挂 scope(跨维 bleed 立灭);跨位面切换时 env/season 全量重发;同时交付 R1 craft 所需 `CraftOpen`/`CraftPause`/`CraftResume` TypeBox source→generated JSON Schema/dist→proto/Rust mirror/converter→samples 全链,以及 client `ClientRequestProtocol` encode / `ClientRequestSender` send APIs 与 producer contract tests,生产 decoder/handler 由 R4 消费。 - ⬜ P2 client 桥接层收敛:枚举前缀剥离收敛到单点(含 forge-session 修复);`ServerDataRouter` 注册表整备(分域注册文件,不再单个 1547 行 switch 追加)。 - ⬜ P3 旁路归一批次:28 channel 逐批收编入 server_data envelope 或登记豁免(资源包/握手类可豁免);删除散装 receiver。 -- ⬜ P4 契约 pin 全量化:双向 sample 对拍测试补齐(116 C2S + 144 S2C 每变体至少一条正反 sample,schema 改动连 sample 一起改);emit 迁移到 builder 的长尾批次。 +- ⬜ P4 契约 pin 全量化:R6 P1 落地三种 craft intent 后,以 116 C2S + 144 S2C 每变体至少一条正反 sample 对拍(在此之前当前 C2S baseline 仍为 113;schema 改动连 sample 一起改);emit 迁移到 builder 的长尾批次。 - ⬜ P5 bot 验收 + 吸收 plan 批量归档。 ## 吸收清单(短名省略 plan-bughunt- 前缀与 -v1 后缀) From dccf1ae9ed5bdf6be676eb823c303d0897fa1792 Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Mon, 3 Aug 2026 17:07:11 +0800 Subject: [PATCH 06/14] =?UTF-8?q?=E6=94=B6=E7=B4=A7=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E4=BA=A4=E4=BB=98=E4=B8=8E=E8=B7=A8=E8=BD=A8=E5=A5=91=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Model: claude-sonnet-5 Co-Authored-By: Claude --- docs/plan-refactor-server-session-v1.md | 13 +++++++------ docs/plans-skeleton/plan-refactor-c2s-gate-v1.md | 2 +- .../plan-refactor-persistence-slices-v1.md | 8 ++++---- docs/plans-skeleton/plan-refactor-wire-s2c-v1.md | 8 ++++---- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/docs/plan-refactor-server-session-v1.md b/docs/plan-refactor-server-session-v1.md index 5b9e3d181..e843e4ea5 100644 --- a/docs/plan-refactor-server-session-v1.md +++ b/docs/plan-refactor-server-session-v1.md @@ -70,11 +70,11 @@ pub trait InteractionSession { - `PlayerKey`:canonical player id;`Entity` 只作当前连接的 runtime binding。 - `SessionDurability::{Checkpointed, Volatile}`:每个 adapter 注册时必须显式声明,不允许默认值。 - `SessionPhase::{Running, Paused, Suspended, AwaitingDelivery, DeliveryPending, Terminal}`:同一 session 不允许同时处于多个 phase。`DeliveryPending` 表示 R3 已提交 durable outbox/checkpoint terminalization、但 R10 尚未提交 receipt;它不再是可恢复 gameplay session,不持有 runtime claim。 -- `TerminationCause::{VoluntaryCancel, Disconnect, DimensionChange, Shutdown, InvalidRestore, SuspensionExpired}`。 +- `TerminationCause::{Completed, VoluntaryCancel, Disconnect, DimensionChange, Shutdown, InvalidRestore, SuspensionExpired}`;`Completed` 专用于正常完成后的 durable outbox handoff,不触发 refund。 - `SuspensionPolicy`:声明 checkpointed session 的 `suspended_at_tick`、有限 `max_suspended_ticks`、过期扫描周期、管理员强制结案入口和 facility claim 回收动作;默认值冻结为 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000`(20 TPS 下 24 小时)与 `SESSION_SUSPENSION_SCAN_INTERVAL_TICKS = 1_200`,不得配置为无限期保留。 - `SessionTransition::{Keep, Pause, SuspendAndCheckpoint, Teardown, AwaitDelivery, DeliveryPending, CommitTerminal}`。 -- `SessionDeliveryOutbox { delivery_id, session_key, owner_key, payload_digest, payload, cause, state, attempts, next_retry_tick, created_at_tick }`:R3 持久化的 durable handoff;`delivery_id` 由 `SessionKey + terminal_generation` 稳定派生,禁止仅靠内存幂等键。 -- `DeliveryOutboxState::{Pending, InFlight { lease_id, lease_until }, DeadLetter, Committed, ResolvedDisposition}`,单调 `generation` 与 durable `DeliveryCommitReceipt`:R10 delivery 的跨重启 exactly-once 依据;worker、dead-letter scanner、operator retry/resolve 均以 `(state,generation)` CAS 串行化。 +- `SessionDeliveryOutbox { delivery_id, session_key, owner_key, payload_digest, payload, cause, state, generation, attempts, next_retry_tick, created_at_tick, tick_epoch }`:R3 持久化的 durable handoff;`delivery_id` 由 `SessionKey + terminal_generation` 稳定派生,禁止仅靠内存幂等键。`generation` 是每次状态 CAS 成功后递增的 durable token;`next_retry_tick`/`created_at_tick` 必须与 `tick_epoch`(持久化的 runtime tick、wall-clock capture 与 epoch id)一起经 R3 `tick_rebase` 转换,按重启时已过去的 wall-clock duration 计算新 epoch 下的剩余 retry/age,禁止跨重启直接比较旧 process-local tick。 +- `DeliveryOutboxState::{Pending, InFlight { lease_id, lease_until }, DeadLetter, Committed, ResolvedDisposition { delivery_id, operator_principal, reason, payload }}` 与 durable `DeliveryCommitReceipt`:R10 delivery 的跨重启 exactly-once 依据;worker、dead-letter scanner、operator retry/resolve 均以 outbox 行的 `(state,generation)` CAS 串行化;`ResolvedDisposition` 必须保留完整 payload,不能退化为 unit variant。 - `SessionMaintenancePermissions`:只接受 server console origin,或 client 不可选择且已绑定当前 executor entity 的 authenticated principal/capability;`Username` 仅审计展示,绝不参与授权。offline mode 的 player executor 一律无 maintenance authority。 - `BusyClaim`:声明 owner 与 world target 上占用的 busy classes;冲突矩阵集中注册。 @@ -124,12 +124,12 @@ pub trait InteractionSession { - 每个宿主显式选择 `SuspensionPolicy.max_suspended_ticks`;P1 craft、P2 alchemy/forge 使用冻结默认值 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000`,扫描 cadence 为 `SESSION_SUSPENSION_SCAN_INTERVAL_TICKS = 1_200`。R3 `tick_rebase` 保持剩余 TTL 的相对时长,关服时间不得让 lease 永久延长;P3 世界交互不得继承无限值。 - registry 每 tick 或固定 cadence 扫描过期 session;只有 `SessionMaintenancePermissions::is_allowed(execution_origin, authenticated_principal, executor)` 才能用 `session-maintenance terminate `。仅 server console origin 或 server 已认证且绑定 executor 的 principal/capability 可通过;offline-mode player `Username`、命令 payload 或 owner 字符串均不能产生授权。普通玩家、未知 executor、伪造 allow-listed username/owner 和跨 owner target 一律拒绝且不改变 session/outbox/claim。扫描、授权管理员结案、重连与关服 flush 竞争时以 `SessionKey` CAS/registry lock 保证一次结算。 - 过期结案按 `TerminationCause::SuspensionExpired` 执行:停止 tick → 在 R3 checkpoint 事务中写入稳定 `delivery_id` 的 `SessionDeliveryOutbox` 并转为 `DeliveryPending` → 事务成功后幂等释放 facility/target/busy claim → 从 live registry 移除;R10 receipt 提交后再标记 `Terminal`。若进程在提交后、runtime claim 清理前退出,启动恢复必须依据已提交的 terminal/outbox 状态补做同一释放,不能重新 attach 或继续占用。R10 异步消费 outbox;不得以 delivery 临时失败为由继续占设施。 -- outbox 每行携带单调 `generation`,状态为 `Pending | InFlight { lease_id, lease_until } | DeadLetter | Committed | ResolvedDisposition`。自动 worker 仅以 expected `(state,generation)` CAS claim `Pending→InFlight`;delivery receipt + inventory/spill + `InFlight→Committed` 同事务提交。dead-letter scanner 仅 CAS 非活跃/已过期 lease,operator retry/resolve 也必须 CAS 当前 generation;CAS loser 重读,不得覆盖 worker/scanner/operator 的并发结果或重复释放 quota。重试冻结为指数退避 `min(1_200 * 2^attempts, 72_000)` ticks;10 次或 7 天后转 `DeadLetter` 并停止自动扫描。 +- outbox 每行携带单调 `generation`,状态为 `Pending | InFlight { lease_id, lease_until } | DeadLetter | Committed | ResolvedDisposition { delivery_id, operator_principal, reason, payload }`。自动 worker 仅以 expected `(state,generation)` CAS claim `Pending→InFlight`;delivery receipt + inventory/spill + `InFlight→Committed` 同事务提交。dead-letter scanner 仅 CAS 非活跃/已过期 lease,operator retry/resolve 也必须 CAS 当前 generation;CAS loser 重读,不得覆盖 worker/scanner/operator 的并发结果或重复释放 quota。重试冻结为指数退避 `min(1_200 * 2^attempts, 72_000)` ticks;10 次或 7 天后转 `DeadLetter` 并停止自动扫描。`next_retry_tick`、`created_at_tick`、`lease_until` 与 `tick_epoch` 一并持久化;启动 guarded load 必须在 worker/scanner 运行前用 R3 `tick_rebase` 原子换算到当前 epoch,保持 retry 剩余时长与已消耗 age,连续重启不得刷新退避、7 天 age 或 lease。 - R3 `SessionDeliveryOutbox` 是失败交付的唯一 retained obligation。每个 `Checkpointed` session 在取得 claim 前预留 durable terminal-obligation capacity;不足则拒绝创建/恢复。terminal handoff 与 checkpoint 行同事务转移 reservation。`Pending`/`InFlight`/`DeadLetter` 不重建 session 或 claim;quota 满时 fail closed,禁止删除/覆盖 payload。`resolve` 必须在同一事务把完整 payload 移入持久 `ResolvedDisposition { delivery_id, operator_principal, reason, payload }`(或已提交 receipt),审计成功后才删除 dead-letter row/释放 quota;禁止无 disposition 删除未交付 payload。 - `DeliveryPending` 已提交 durable handoff 但未 receipt;R10 delivery 失败或进入 `DeadLetter` 时仍保持该 phase 的 durable obligation,绝不回退为 `AwaitingDelivery` 或重新占用 claim。 - claim 回收与 outbox 状态变化必须有审计事件(session key、owner key hash、operator identity hash/`system`、cause、released claims、delivery id/result/attempt),不记录原 payload;恢复窗口内仍允许 owner 正常 reopen,写入 outbox 后 reopen 明确拒绝。 -- P1/P4 必须覆盖:永不重连的 abandoned session 最终释放设施、TTL 边界前后、授权管理员结案、offline client 伪造 allow-listed `Username` 仍拒绝、普通玩家/未知 executor/伪造 owner/跨 owner target 拒绝、过期与重连竞态、重复扫描幂等、outbox 事务 crash points、receipt 重放去重、worker↔scanner 与 worker↔operator retry/resolve generation-CAS 竞态、过期 lease 恢复、退避边界、10 次/7 天 dead-letter、授权 resolve 无 receipt/durable disposition 时 payload 与 quota 保留、quota 满时 checkpointed admission/restore fail-closed、quota 释放后的重新 admission,以及回收后另一玩家可获得同一 furnace/station claim。 +- P1/P4 必须覆盖:永不重连的 abandoned session 最终释放设施、TTL 边界前后、授权管理员结案、offline client 伪造 allow-listed `Username` 仍拒绝、普通玩家/未知 executor/伪造 owner/跨 owner target 拒绝、过期与重连竞态、重复扫描幂等、正常 `Completed` handoff 不退款且 cause 可 roundtrip、outbox 事务 crash points、receipt 重放去重、worker↔scanner 与 worker↔operator retry/resolve generation-CAS 竞态、过期 lease 恢复、退避边界、10 次/7 天 dead-letter、重启前后 retry/age/lease deadline rebase(含连续重启不刷新)、授权 resolve 无 receipt/durable disposition 时 payload 与 quota 保留、quota 满时 checkpointed admission/restore fail-closed、两个 admission/restore 并发竞争最后一行或最后一份 bytes 时仅一个成功、quota 释放后的重新 admission,以及回收后另一玩家可获得同一 furnace/station claim。 ### 2.3 durability 决议矩阵 | 状态族 | durability | 断线/关服 | 跨维 | @@ -160,6 +160,7 @@ pub trait InteractionSession { - screen close 是 `Pause`,不是 `VoluntaryCancel`。当前 wire 只有 `CraftCancel`(`proto/bong/envelope.proto:257,1309`),P1 必须与 R6/R4 协调显式 open/pause/resume intent;不能只删除 client cancel 后让 server 继续 tick。 - `VoluntaryCancel` 保留域内已公开的经济规则,例如 craft 未完成部分返还 70%;UI 必须有明确取消动作,不能由 Esc、断线、跨维或关服冒充。 - cause/refund matrix 固定如下,adapter 与 contract pin 必须逐项覆盖: + - `Completed`:正常完成后把完整产物交给 durable outbox,cause 固定为 `Completed`;不 refund inputs,不允许借用取消/断线原因。 - `VoluntaryCancel`:按域内公开规则结算;craft 未完成部分返还 70%。 - `Disconnect`:`Checkpointed` 进入 `Suspended`,不退款,checkpoint 保留未消费 escrow;`Volatile` 全退并终止。 - `DimensionChange`:dimension-scoped session 全退未消费 escrow,经 delivery 后终止,不进入 `Suspended`。 @@ -234,7 +235,7 @@ pub trait InteractionSession { - 新增 `server/src/session/{mod.rs,registry.rs,lifecycle.rs}`,包含 §2 全部 symbol;framework-only 可在 R3 P1 后落地,但不启用生产 craft adapter。 - R6 `CraftOpen`/`CraftPause`/`CraftResume` 契约、R4 production handler/gate、R7 P2 Craft Screen producer/consumer(消费 R2 P1 已登记的 `CraftStore`)、R10 P1 `deliver` contract 与 R10 P2 craft production delivery 全部合入后,craft 才迁移到 `SessionRegistry`:关闭 screen pause、显式 cancel、重开 resume,现有 recipe/session join hydration 行为不变。 -- contract pins:六态转换、stable owner 重绑、disconnect-before-save、dimension-before-transfer、busy 冲突、delivery commit gate,以及 §2.5 六个 `TerminationCause` 的逐变体正反测试;明确断言 `Disconnect`/`Shutdown` checkpoint 不退款,terminal refund 不保留可恢复 escrow。delivery pins 必须在 transaction 的“outbox 写入前/写入后 terminalize 前/receipt+inventory commit 后 ack 前”逐点模拟进程退出并重载,断言 payload 总数恰为一次;SQLite 同库路径另断言 inventory/spill、receipt、checkpoint 删除在单事务全成或全败。 +- contract pins:六态转换、stable owner 重绑、disconnect-before-save、dimension-before-transfer、busy 冲突、delivery commit gate,以及 §2.5 七个 `TerminationCause` 的逐变体正反测试;明确断言 `Completed` 只交付产物且不退款,`Disconnect`/`Shutdown` checkpoint 不退款,terminal refund 不保留可恢复 escrow。delivery pins 必须在 transaction 的“outbox 写入前/写入后 terminalize 前/receipt+inventory commit 后 ack 前”逐点模拟进程退出并重载,断言 payload 总数恰为一次;SQLite 同库路径另断言 inventory/spill、receipt、checkpoint 删除在单事务全成或全败。 - `SuspensionPolicy` pins:TTL 前后、永不重连、授权管理员结案、offline client 伪造 allow-listed `Username` 仍拒绝、普通玩家/未知 executor/伪造 owner/跨 owner target 拒绝、过期/重连/关服竞态、重复扫描幂等、指数退避 cap、10 次/7 天 dead-letter、人工 retry/resolve 权限,以及 claim 回收后另一玩家可获取同一设施。另须持久化一半已消耗的 lease,重启后 rebasing 只保留原剩余 TTL;连续第二次/第三次重启不得刷新 `suspended_at_tick`,并覆盖 `remaining-1`、精确边界、`remaining+1`。 - `PendingInsightOffer` pins:创建后正常选择/拒绝、deadline 前后与 off-by-one、同 tick response-versus-timeout 竞态、断线/跨维/关服清除,以及过期后不得再次接受选择。 diff --git a/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md b/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md index dc5eaca90..af13d4e0f 100644 --- a/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md +++ b/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md @@ -4,7 +4,7 @@ ## 现状证据(2026-07-27 侦察) -- `network/client_request_handler.rs:522` `handle_client_request_payloads` 单函数跨 522-2960 行,巨型 match 覆盖 `ClientRequestV1` 的 116 个变体;`CombatRequestParams` 一个 SystemParam 30 个字段。 +- `network/client_request_handler.rs:522` `handle_client_request_payloads` 单函数跨 522-2960 行,巨型 match 当前覆盖 `ClientRequestV1` 的 113 个生产变体;R6 P1 新增并接通 `CraftOpen`/`CraftPause`/`CraftResume` 后才扩为 116。`CombatRequestParams` 一个 SystemParam 30 个字段。 - 门禁分散手写:8 个文件各自定义 `*_MAX_DISTANCE`/`*_RANGE_SQ`(`client_request_handler.rs:468-469`、`craft/workbench.rs:76` Chebyshev、`mineral/probe.rs`、`npc/relic.rs`、`supply_coffin/authority.rs`、`zhenfa/network_array.rs` 等),距离度量都不统一;`CurrentDimension` 在 handler 内被 13 处内联比对,无统一 helper。 - 后果即 bughunt 大簇:跨维开工作台/布阵/夺舍/交易/拾取、无 reach 校验放方块、先扣物品后校验吞丹、无所有权校验拆棺。 - `zone-lookup-overworld-hardcode`:zone 查找硬编码主世界,是维度感知缺失的底层同源。 diff --git a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md index 9f68231d3..3875cb8cc 100644 --- a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md +++ b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md @@ -13,7 +13,7 @@ ## 接入面 - **进料**:SQLite(bong.db,沿用)、`shutdown.rs`(#1261 之后的关服链路)、`CultivationClock`(相对 tick 基准)。 -- **出料**:统一 Slice API 供各域注册:`load(guarded) / autosave(cadence) / flush_on_shutdown / tick_rebase`;R1 的 session 持久化钩子、各域运行态表全部走它。`tick_rebase` 对 R1 suspension lease 保持相对剩余时长;R1 冻结默认 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000` 与扫描 cadence `1_200` ticks,本轨不得把重启折算成无限续租。为 terminal delivery 提供 `SessionDeliveryOutbox`:事务内只提交 outbox 插入与 session checkpoint terminalization/删除,提交成功后 runtime gameplay claim 释放必须可依据 durable terminal/outbox 状态幂等重放;禁止仅持久化 session、把 delivery id 留在内存。每个 checkpointed session 的 admission/restore 先预留一行和固定 `SESSION_DELIVERY_MAX_PAYLOAD_BYTES` 字节的 durable terminal-obligation capacity;payload 增长不得超过该上限,超限必须在接受新 escrow/产物前 fail closed。`SessionDeliveryOutbox` 仍设可观测 row/bytes quota;quota 满时不得新建/恢复会增加 retained obligation 的 checkpointed session。dead-letter 从自动扫描集合移除且不占 claim,quota 仅在 committed 或授权且不可丢 payload 的 audited resolve 后释放。 +- **出料**:统一 Slice API 供各域注册:`load(guarded) / autosave(cadence) / flush_on_shutdown / tick_rebase`;R1 的 session 持久化钩子、各域运行态表全部走它。`tick_rebase` 对 R1 suspension lease 与 outbox 的 `next_retry_tick`/`created_at_tick`/`lease_until` 保持相对剩余时长/已消耗 age;R1 冻结默认 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000` 与扫描 cadence `1_200` ticks,本轨不得把重启折算成无限续租或刷新 outbox 退避/7 天 age。为 terminal delivery 提供 `SessionDeliveryOutbox`:事务内只提交 outbox 插入与 session checkpoint terminalization/删除,提交成功后 runtime gameplay claim 释放必须可依据 durable terminal/outbox 状态幂等重放;禁止仅持久化 session、把 delivery id 留在内存。每个 checkpointed session 的 admission/restore 必须先调用 `reserve_terminal_obligation(session_key)`:在同一 `BEGIN IMMEDIATE` 事务中 conditional-update 单例 `SessionDeliveryQuota { used_rows, used_bytes, generation }`(仅当 row/bytes 上限均有余量)并插入唯一 `SessionDeliveryReservation { session_key, reserved_bytes = SESSION_DELIVERY_MAX_PAYLOAD_BYTES }`;任一步失败整笔回滚。等价非 SQLite 实现必须以 quota generation CAS 提供同一线性化点,禁止先读剩余额度再另事务插 reservation。terminal handoff 在 checkpoint/outbox 同事务把 reservation 转移给 outbox,不重复计量;只有 receipt `Committed` 或带完整 payload 的 audited `ResolvedDisposition` 在同一事务删除 obligation 并扣减 counter,CAS loser 不得释放 quota。payload 增长不得超过 reservation,上限不足必须在接受新 escrow/产物前 fail closed。dead-letter 从自动扫描集合移除且不占 claim,但继续占 quota。 - **共享类型**:新 `server/src/persistence/` 多文件模块(按域拆表定义 + 迁移链保持线性单入口);`PlayerSliceRegistry`(对齐 #1290 skeleton 的方向,直接吸收它)。 - **跨仓库契约**:零 wire 改动。 - **qi_physics 锚点**:任何带 qi 的快照持久化/恢复不得造成账面变化;恢复失败的兜底路径必须走 `release_dormant_qi_to_zone` 而非丢弃(对齐守恒律红旗清单)。 @@ -21,7 +21,7 @@ ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:53 张表普查归域;冻结 Slice trait(载入守护语义:读失败 = 保留旧行 + 告警 + 只读降级,绝不写回空态;flush registry;tick rebase 协议);等 #1288/#1289/#1261/#1259 merge 后定基线。 -- ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;注册 R1 所需的 `TsyPresence` auxiliary Slice,字段固定为 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version,并接入 guarded load、autosave、`flush_on_shutdown`、`tick_rebase`;落 `SessionDeliveryOutbox` 表与 atomic handoff API,stable `delivery_id`、完整 payload、cause、attempts/next_retry/created_at/state、单调 generation 与 in-flight lease 全部持久化。P1 acceptance 必须同时锁定 checkpoint terminalization/deletion 与 outbox insert 的同一 SQLite transaction 全成或全败,以及 outbox insert 前、terminalization 前、commit 后 ack 前的 crash-injection pins;P3 只做其余 flush registry/tick-rebase 批次,不得把该 atomic handoff 延后。 +- ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;注册 R1 所需的 `TsyPresence` auxiliary Slice,字段固定为 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version,并接入 guarded load、autosave、`flush_on_shutdown`、`tick_rebase`;落 `SessionDeliveryOutbox`、`SessionDeliveryQuota`、`SessionDeliveryReservation` 表与 atomic handoff/reservation API,stable `delivery_id`、完整 payload、cause、attempts/next_retry/created_at/tick_epoch/state、单调 generation 与 in-flight lease 全部持久化。P1 acceptance 必须同时锁定:reservation 的 quota conditional update + unique insert 原子性;terminal handoff 时 reservation→outbox 不重复计量;Committed/ResolvedDisposition 扣减 quota 与 obligation 删除原子性;checkpoint terminalization/deletion 与 outbox insert 同一 SQLite transaction 全成或全败;以及 outbox insert 前、terminalization 前、commit 后 ack 前的 crash-injection pins。P3 只做其余 flush registry/tick-rebase 批次,不得把这些 P1 原子边界延后。 - ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;身份主键统一(identity-persist-key-mismatch)。 - ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部"节流落盘"域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥(coffin-autosave-inflight-race 模式);`TsyPresence` 的关服 flush 与 presence/position/dimension 必须在同一 SQLite transaction 内提交,crash injection 只能观察到三者全旧或全新。 - ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性(heiwushi)等——逐个按 Slice 框架补表。加入 `TsyPresence` guarded relog parity 契约测试和 TSY 维度重启 bot 场景,断言失败加载不 attach presence、成功恢复后才重新开放 TSY 请求。 @@ -43,10 +43,10 @@ skeleton:coffin-autosave-inflight-race、identity-persist-key-mismatch、miner 1. `restart_player_slices`:bot 建号→修炼/学功法/受伤→关服重启→重连→断言功法/伤势/濒死后果/buff 全部还原。 2. `restart_world_runtime`:触发矿脉枯竭/配方解锁/zone influence→SIGTERM 关服→重启→断言无回滚无复活。 3. `load_failure_guard`:注入一行损坏 slice 数据→启动→断言该玩家进入守护降级而非清零覆盖(配 dev 命令注入)。 -4. `tick_rebase`:带冷却/再生倒计时重启→断言倒计时按真实流逝折算(对齐 #1289 的 deadline 折算先例);另持久化一半已消耗的 `SuspensionPolicy` lease,重启/rebase 只保留原剩余 TTL,连续重复重启不刷新 lease,并覆盖剩余时长前一 tick、精确边界、后一 tick。 +4. `tick_rebase`:带冷却/再生倒计时重启→断言倒计时按真实流逝折算(对齐 #1289 的 deadline 折算先例);另持久化一半已消耗的 `SuspensionPolicy` lease,重启/rebase 只保留原剩余 TTL,连续重复重启不刷新 lease,并覆盖剩余时长前一 tick、精确边界、后一 tick;outbox 同样覆盖 `next_retry_tick` 剩余退避、`created_at_tick` 已消耗 age、`lease_until` 剩余 lease 在单次/连续重启后的前一 tick、精确边界、后一 tick,断言不会把旧 process-local tick 直接带入新 epoch。 5. `tsy_presence_relog_parity`:进入 TSY→关服 flush→guarded load→只有 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version 校验通过才 attach `TsyPresence` 并开放 TSY 请求;损坏或缺失 Slice 保持未 attach 且拒绝请求;恢复后 death-drop 对原带物继续执行 50%/武器保护,对 TSY 所得执行既有 100% 规则。 6. `tsy_presence_shutdown_order`:断言 session registry 静止后才 flush presence slice;向 presence、position、dimension 三个逻辑写入之间注入 shutdown crash,重启后断言三者只能全部保留旧快照或全部提交新快照,不接受 clean restore 对拍掩盖分事务写入。 -7. `session_delivery_outbox_atomicity`:分别在 outbox insert 前、insert 后 checkpoint terminalize 前、事务 commit 后 ack 前强杀重启;另注入已 admission 但 payload 达到 `SESSION_DELIVERY_MAX_PAYLOAD_BYTES` 的边界,断言超限在接受新 escrow/产物前 fail closed;正常路径断言 checkpoint/outbox 恰有一个权威 owner,完整 payload 不丢且 stable `delivery_id` 不变。 +7. `session_delivery_outbox_atomicity`:分别在 outbox insert 前、insert 后 checkpoint terminalize 前、事务 commit 后 ack 前强杀重启;另注入已 admission 但 payload 达到 `SESSION_DELIVERY_MAX_PAYLOAD_BYTES` 的边界,断言超限在接受新 escrow/产物前 fail closed;正常路径断言 checkpoint/outbox 恰有一个权威 owner,完整 payload 不丢且 stable `delivery_id` 不变。以两个独立 SQLite connection/线程同时竞争最后一个 row reservation、以及分别竞争不足以容纳两份 reservation 的最后 bytes,屏障同步到事务入口后并发调用 `reserve_terminal_obligation`,每种场景必须恰好一个 commit、一个 quota-full,最终 counter 与唯一 reservation 对拍且重启后不超配;再覆盖 CAS loser 不释放 quota、handoff 转移不双计、Committed/ResolvedDisposition 原子释放后新 admission 才成功。 ## 开放问题(pre-P0 收口) diff --git a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md index 4530dbc88..906de366a 100644 --- a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md +++ b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md @@ -14,12 +14,12 @@ - **进料**:各域游戏事件(emit 调用点)、`world` 维度/zone 信息(作用域过滤)。 - **出料**:`bong:server_data` 单通道(目标态:28 旁路全部收编或显式豁免登记);join/重连首包快照集契约(R2 清干净后靠它灌满)。 - **共享类型**:新 server `network/emit/` builder(`scope: Global | Dimension | Zone | Player`);client 桥接层唯一的枚举前缀剥离函数。 -- **跨仓库契约**:proto 形状原则上不动(收编旁路时如需并入 envelope 属破坏性变更,走 buf breaking + samples 同步;agent 侧 TS 只做被动 regenerate,不重构 agent 逻辑)。**不做双轨兼容层**——旁路收编是一次性切换。R1 craft 例外要求显式冻结并一次性切换 `CraftOpen` / `CraftPause` / `CraftResume` C2S intent 与对应 S2C session-state payload;现有 `CraftCancel` 保留为唯一主动取消 intent,关屏不得复用它。R6 同时拥有 `agent/packages/schema/src/client-request.ts` TypeBox source、生成 JSON Schema 与提交的 `@bong/schema` dist、protobuf/Rust mirror/converter、samples,以及 client `ClientRequestProtocol.encodeCraftOpen/Pause/Resume` 与 `ClientRequestSender.sendCraftOpen/Pause/Resume` producer APIs;R7 只消费这些接口。 +- **跨仓库契约**:proto 形状原则上不动(收编旁路时如需并入 envelope 属破坏性变更,走 buf breaking + samples 同步;agent 侧 TS 只做被动 regenerate,不重构 agent 逻辑)。**不做双轨兼容层**——旁路收编是一次性切换。R1 craft 例外要求显式冻结并一次性切换 `CraftOpen` / `CraftPause` / `CraftResume` C2S intent,并用 `CraftSessionStateV2` 一次性替换既有 S2C `CraftSessionStateV1`:V2 除现有进度字段外,固定携带 `session_key`、单调 `generation`、`phase: Idle | Running | Paused | Suspended | AwaitingDelivery | DeliveryPending | Terminal`;`Idle` 明确无 session identity,其余 phase 必须携带 identity/version。现有 `CraftCancel` 保留为唯一主动取消 intent,关屏不得复用它。R6 同时拥有 C2S 的 `agent/packages/schema/src/client-request.ts` TypeBox source、生成 JSON Schema 与提交的 `@bong/schema` dist、protobuf/Rust mirror/converter、samples、client encode/send APIs;并拥有 S2C `CraftSessionStateV2` 在 `agent/packages/schema/src/craft.ts`→generated JSON Schema/dist→`proto/bong/envelope.proto`→Rust `server/src/schema/craft.rs`/`proto_convert.rs`→`network/craft_emit.rs` producer→`ProtoServerDataBridge`→`CraftSessionStateHandler`/`CraftStore` consumer 的一次性全链替换,同一提交删除 V1 schema/proto/bridge 分支。R7 只消费这些冻结接口。 ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:28 旁路逐个普查(收编 vs 豁免理由);100 emit 文件的重复模式取样归纳 builder API;枚举前缀剥离点全量清点;冻结 scope 语义与 join 首包快照集清单。 -- ⬜ P1 emit builder + scope 落地:builder 上线,vfx/audio/env 三类先挂 scope(跨维 bleed 立灭);跨位面切换时 env/season 全量重发;同时交付 R1 craft 所需 `CraftOpen`/`CraftPause`/`CraftResume` TypeBox source→generated JSON Schema/dist→proto/Rust mirror/converter→samples 全链,以及 client `ClientRequestProtocol` encode / `ClientRequestSender` send APIs 与 producer contract tests,生产 decoder/handler 由 R4 消费。 +- ⬜ P1 emit builder + scope 落地:builder 上线,vfx/audio/env 三类先挂 scope(跨维 bleed 立灭);跨位面切换时 env/season 全量重发;同时交付 R1 craft 所需 C2S `CraftOpen`/`CraftPause`/`CraftResume` TypeBox source→generated JSON Schema/dist→proto/Rust mirror/converter→正反 samples,以及 client `ClientRequestProtocol` encode / `ClientRequestSender` send APIs 与 producer contract tests,生产 decoder/handler 由 R4 消费;同阶段原子交付 S2C `CraftSessionStateV2` hydration 扩展的 TypeBox/schema/dist、proto/Rust mirror/converter、`craft_emit` phase/session_key/generation producer、bridge/router/handler/CraftStore consumer 与 samples。contract pins 必须证明:guarded restore 后首包按 server 权威 phase/identity/version hydrate;匹配 `Paused` 使 R7 恰发一次 Resume;Idle、Running、Suspended、DeliveryPending、Terminal、missing/stale/mismatched session key 或 generation 均不发 Resume;断线重连、乱序旧 generation 与同 key 新 generation 不得回退 store。 - ⬜ P2 client 桥接层收敛:枚举前缀剥离收敛到单点(含 forge-session 修复);`ServerDataRouter` 注册表整备(分域注册文件,不再单个 1547 行 switch 追加)。 - ⬜ P3 旁路归一批次:28 channel 逐批收编入 server_data envelope 或登记豁免(资源包/握手类可豁免);删除散装 receiver。 - ⬜ P4 契约 pin 全量化:R6 P1 落地三种 craft intent 后,以 116 C2S + 144 S2C 每变体至少一条正反 sample 对拍(在此之前当前 C2S baseline 仍为 113;schema 改动连 sample 一起改);emit 迁移到 builder 的长尾批次。 @@ -41,8 +41,8 @@ skeleton:vfx-audio-dimension-bleed、q-world-season-dimension-env-resync、for 1. `wire_scope_dimension`:主世界 bot + TSY bot 双开,主世界触发 vfx/audio→断言 TSY bot 收不到(P6 protobuf 深断言配合)。 2. `wire_dimension_transfer_resync`:bot 跨位面→断言 env/season/zone 大气全量重发。 -3. `wire_contract_sweep`:对 144 S2C 变体的 sample 对拍在 CI 常绿(配 proto-breaking 深检,联动 V 轨)。 -4. `wire_join_snapshot`:重连首包快照集完整(与 R2 的 `reconnect_state_freshness` 同场景)。 +3. `wire_contract_sweep`:对 144 S2C 变体的 sample 对拍在 CI 常绿(配 proto-breaking 深检,联动 V 轨);`CraftSessionStateV2` 另有 phase 全变体、identity/generation required/forbidden 组合、stale generation 拒绝、proto→bridge→handler→CraftStore roundtrip pins。 +4. `wire_join_snapshot`:重连首包快照集完整(与 R2 的 `reconnect_state_freshness` 同场景);checkpointed craft guarded restore 后必须包含单个权威 `CraftSessionStateV2` hydration,server `Paused`/identity/generation 与 client store 对拍,idle/terminal/delivery-pending 不得被伪装成可 Resume。 ## 开放问题(pre-P0 收口) From 2314a37354627b8dec4cff637bd1db875b92ac8d Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Mon, 3 Aug 2026 20:45:46 +0800 Subject: [PATCH 07/14] =?UTF-8?q?=E8=A1=A5=E9=BD=90=20craft=20=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E8=BA=AB=E4=BB=BD=E4=B8=8E=20Resume=20=E5=A5=91?= =?UTF-8?q?=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Model: claude-sonnet-5 Co-Authored-By: Claude --- docs/plans-skeleton/plan-refactor-client-ui-base-v1.md | 4 ++-- docs/plans-skeleton/plan-refactor-wire-s2c-v1.md | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md b/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md index c4d9df3ad..b24791601 100644 --- a/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md +++ b/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md @@ -16,13 +16,13 @@ - **进料**:R2 的 `SessionScopedStore`(Screen 订阅的一律是会话态 store);`ServerDataRouter` handler(一律经 client-thread marshal 投递 UI)。 - **出料**:Screen/HUD 展示;HUD 纪律沿用既有 memory 约束(未解锁隐藏不灰掉、沉浸式极简)。 - **共享类型**:新 `BongScreenBase`(生命周期 + 订阅 + 关闭清理)、`DiffListWidget`(推广 craft 范本)、`BongKeybindRegistry`(注册时冲突检测 + 测试期断言)、`ClientThreadMarshal` helper、`ScreenOpenPolicy`(礼貌抢屏:战斗中/已有模态时排队)。 -- **跨仓库契约**:本轨不定义 wire;消费 R6 冻结的 `CraftOpen`/`CraftPause`/`CraftResume` 与 S2C session-state payload。Craft Screen 首次进入/点击继续生产发送 `CraftOpen`,关闭只发送 `CraftPause`,显式取消按钮只发送 `CraftCancel`,重开后由 server state hydrate 决定是否发送 `CraftResume`。P2 client contract pin 覆盖四条 production intent:首次打开→`CraftOpen`、关闭→`CraftPause`、显式取消→`CraftCancel`、匹配的 paused session hydrate→恰好一次 `CraftResume`;idle/no-session、terminal、delivery-pending、missing/stale/mismatched session key 或 generation hydrate 均不得发送 `CraftResume`。 +- **跨仓库契约**:本轨不定义 wire;消费 R6 冻结的 `CraftOpen`/`CraftPause`/`CraftResume` 与 S2C session-state payload。Craft Screen 首次进入/点击继续生产发送 `CraftOpen`,关闭只发送 `CraftPause`,显式取消按钮只发送 `CraftCancel`,重开后由 server state hydrate 决定是否发送 `CraftResume`。三种新 intent 均必须回显 server-hydrated 的 `session_key` 与 `generation`,不得由 client 省略、默认或自行生成。R7 P2 client contract pin 覆盖四条 production intent:首次打开→`CraftOpen`、关闭→`CraftPause`、显式取消→`CraftCancel`、匹配的 paused session hydrate→恰好一次 `CraftResume`;idle/no-session、terminal、`AwaitingDelivery`、`DeliveryPending`、missing/stale/mismatched session key 或 generation hydrate 均不得发送 `CraftResume`。R6 P1 只冻结并验证 wire/bridge/store 的字段与负向身份契约;匹配 paused hydrate 的 production Resume producer 留在本轨 P2,并依赖总纲 `plan-refactor-master-v1.md §3 Wave 2` 的 R6 + R4 + R2 前置。 ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:92 处 fill(100) 全量分类(根节点合法/子节点顶飞);28 Screen 普查;冻结基类 API 与四个共享组件。 - ⬜ P1 基础组件落地:BongScreenBase/DiffListWidget/KeybindRegistry/ClientThreadMarshal/ScreenOpenPolicy 上线;keybind 冲突全数改绑(T/L/O/U/G 簇)。 -- ⬜ P2 Screen 迁移批次 A:炼丹/锻造/手搓/交易屏迁基类,fill(100) 顶飞点与 clearChildren 回弹点随迁修复;Craft Screen 完成生产接线:首次进入/点击继续→`CraftOpen`、close→`CraftPause`、独立取消按钮→`CraftCancel`、仅对匹配的 server-hydrated paused session reopen→`CraftResume`,并以 client 单测锁住四条 intent 不互相替代及 idle/terminal/delivery-pending/stale hydration 不发 Resume。 +- ⬜ P2 Screen 迁移批次 A:炼丹/锻造/手搓/交易屏迁基类,fill(100) 顶飞点与 clearChildren 回弹点随迁修复;Craft Screen 完成生产接线:首次进入/点击继续→携带 hydrated `session_key`/`generation` 的 `CraftOpen`、close→携带同一 identity/version 的 `CraftPause`、独立取消按钮→`CraftCancel`、仅对匹配的 server-hydrated paused session reopen→携带同一 identity/version 且恰好一次 `CraftResume`,并以 client 单测锁住四条 intent 不互相替代及 idle/terminal/`AwaitingDelivery`/`DeliveryPending`/stale/mismatched hydration 不发 Resume。 - ⬜ P3 InspectScreen 拆解:按 tab/section 拆组件文件(body/container/tooltip 已有雏形),行为不变。 - ⬜ P4 Screen 迁移批次 B + 网络线程 marshal 强制(handler 层静态检查/测试)+ 删旧。 - ⬜ P5 验收 + 吸收 plan 批量归档。 diff --git a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md index 906de366a..80bb9fef8 100644 --- a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md +++ b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md @@ -14,12 +14,12 @@ - **进料**:各域游戏事件(emit 调用点)、`world` 维度/zone 信息(作用域过滤)。 - **出料**:`bong:server_data` 单通道(目标态:28 旁路全部收编或显式豁免登记);join/重连首包快照集契约(R2 清干净后靠它灌满)。 - **共享类型**:新 server `network/emit/` builder(`scope: Global | Dimension | Zone | Player`);client 桥接层唯一的枚举前缀剥离函数。 -- **跨仓库契约**:proto 形状原则上不动(收编旁路时如需并入 envelope 属破坏性变更,走 buf breaking + samples 同步;agent 侧 TS 只做被动 regenerate,不重构 agent 逻辑)。**不做双轨兼容层**——旁路收编是一次性切换。R1 craft 例外要求显式冻结并一次性切换 `CraftOpen` / `CraftPause` / `CraftResume` C2S intent,并用 `CraftSessionStateV2` 一次性替换既有 S2C `CraftSessionStateV1`:V2 除现有进度字段外,固定携带 `session_key`、单调 `generation`、`phase: Idle | Running | Paused | Suspended | AwaitingDelivery | DeliveryPending | Terminal`;`Idle` 明确无 session identity,其余 phase 必须携带 identity/version。现有 `CraftCancel` 保留为唯一主动取消 intent,关屏不得复用它。R6 同时拥有 C2S 的 `agent/packages/schema/src/client-request.ts` TypeBox source、生成 JSON Schema 与提交的 `@bong/schema` dist、protobuf/Rust mirror/converter、samples、client encode/send APIs;并拥有 S2C `CraftSessionStateV2` 在 `agent/packages/schema/src/craft.ts`→generated JSON Schema/dist→`proto/bong/envelope.proto`→Rust `server/src/schema/craft.rs`/`proto_convert.rs`→`network/craft_emit.rs` producer→`ProtoServerDataBridge`→`CraftSessionStateHandler`/`CraftStore` consumer 的一次性全链替换,同一提交删除 V1 schema/proto/bridge 分支。R7 只消费这些冻结接口。 +- **跨仓库契约**:proto 形状原则上不动(收编旁路时如需并入 envelope 属破坏性变更,走 buf breaking + samples 同步;agent 侧 TS 只做被动 regenerate,不重构 agent 逻辑)。**不做双轨兼容层**——旁路收编是一次性切换。R1 craft 例外要求显式冻结并一次性切换 `CraftOpen` / `CraftPause` / `CraftResume` C2S intent:三者均必须携带 `session_key: string` 与 `generation: uint64`,且两字段均 required;`CraftOpen` 使用 server hydration 后的当前 identity/version,`CraftPause` 与 `CraftResume` 必须回显该次 hydration 的 identity/version,禁止省略、默认值或仅以 client Entity 代替。并用 `CraftSessionStateV2` 一次性替换既有 S2C `CraftSessionStateV1`:V2 除现有进度字段外,固定携带 `session_key`、单调 `generation`、`phase: Idle | Running | Paused | Suspended | AwaitingDelivery | DeliveryPending | Terminal`;`Idle` 明确无 session identity,其余 phase 必须携带 identity/version。现有 `CraftCancel` 保留为唯一主动取消 intent,关屏不得复用它。R6 同时拥有 C2S 的 `agent/packages/schema/src/client-request.ts` TypeBox source、生成 JSON Schema 与提交的 `@bong/schema` dist、protobuf/Rust mirror/converter、samples、client encode/send APIs;并拥有 S2C `CraftSessionStateV2` 在 `agent/packages/schema/src/craft.ts`→generated JSON Schema/dist→`proto/bong/envelope.proto`→Rust `server/src/schema/craft.rs`/`proto_convert.rs`→`network/craft_emit.rs` producer→`ProtoServerDataBridge`→`CraftSessionStateHandler`/`CraftStore` consumer 的一次性全链替换,同一提交删除 V1 schema/proto/bridge 分支。R7 只消费这些冻结接口;R6 不复制或提前实现 R7 的 production Resume producer。 ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:28 旁路逐个普查(收编 vs 豁免理由);100 emit 文件的重复模式取样归纳 builder API;枚举前缀剥离点全量清点;冻结 scope 语义与 join 首包快照集清单。 -- ⬜ P1 emit builder + scope 落地:builder 上线,vfx/audio/env 三类先挂 scope(跨维 bleed 立灭);跨位面切换时 env/season 全量重发;同时交付 R1 craft 所需 C2S `CraftOpen`/`CraftPause`/`CraftResume` TypeBox source→generated JSON Schema/dist→proto/Rust mirror/converter→正反 samples,以及 client `ClientRequestProtocol` encode / `ClientRequestSender` send APIs 与 producer contract tests,生产 decoder/handler 由 R4 消费;同阶段原子交付 S2C `CraftSessionStateV2` hydration 扩展的 TypeBox/schema/dist、proto/Rust mirror/converter、`craft_emit` phase/session_key/generation producer、bridge/router/handler/CraftStore consumer 与 samples。contract pins 必须证明:guarded restore 后首包按 server 权威 phase/identity/version hydrate;匹配 `Paused` 使 R7 恰发一次 Resume;Idle、Running、Suspended、DeliveryPending、Terminal、missing/stale/mismatched session key 或 generation 均不发 Resume;断线重连、乱序旧 generation 与同 key 新 generation 不得回退 store。 +- ⬜ P1 emit builder + scope 落地:builder 上线,vfx/audio/env 三类先挂 scope(跨维 bleed 立灭);跨位面切换时 env/season 全量重发;同时交付 R1 craft 所需 C2S `CraftOpen`/`CraftPause`/`CraftResume` TypeBox source→generated JSON Schema/dist→proto/Rust mirror/converter→正反 samples,以及 client `ClientRequestProtocol` encode / `ClientRequestSender` send APIs 与 producer contract tests。三种 intent 均必须携带并 roundtrip required 的 `session_key` + `generation`,生产 decoder/handler 由 R4 消费;同阶段原子交付 S2C `CraftSessionStateV2` hydration 扩展的 TypeBox/schema/dist、proto/Rust mirror/converter、`craft_emit` phase/session_key/generation producer、bridge/router/handler/CraftStore consumer 与 samples。R6 contract pins 只证明 schema/bridge/store 的身份字段、phase 全量序列化及 stale/mismatched identity 拒绝;不得在 R6 复制或提前实现 R7 的 production Resume producer。R7 的匹配 `Paused`→恰发一次 `CraftResume` 证明移交 R7 P2,依赖顺序与总纲 `plan-refactor-master-v1.md §3 Wave 2` 一致。R6 必须证明 Idle、Running、Suspended、AwaitingDelivery、DeliveryPending、Terminal、missing/stale/mismatched session key 或 generation 均不发 Resume;断线重连、乱序旧 generation 与同 key 新 generation 不得回退 store。 - ⬜ P2 client 桥接层收敛:枚举前缀剥离收敛到单点(含 forge-session 修复);`ServerDataRouter` 注册表整备(分域注册文件,不再单个 1547 行 switch 追加)。 - ⬜ P3 旁路归一批次:28 channel 逐批收编入 server_data envelope 或登记豁免(资源包/握手类可豁免);删除散装 receiver。 - ⬜ P4 契约 pin 全量化:R6 P1 落地三种 craft intent 后,以 116 C2S + 144 S2C 每变体至少一条正反 sample 对拍(在此之前当前 C2S baseline 仍为 113;schema 改动连 sample 一起改);emit 迁移到 builder 的长尾批次。 @@ -48,3 +48,4 @@ skeleton:vfx-audio-dimension-bleed、q-world-season-dimension-env-resync、for 1. 28 旁路的收编/豁免分界(低频大 payload 如资源包显然豁免;npc bubble 这类高频小包是否值得并入 envelope)。 2. join 首包快照集的权威清单放哪维护(emit builder 注册时声明 `replay_on_join` 标志?)。 +3. R7 production `CraftResume` producer 的验收证明不在 R6 P1 提前实现;由 R7 P2 在 R6 wire/bridge、R4 handler/gate 与 R2 store 前置合入后完成,依据总纲 `plan-refactor-master-v1.md §3 Wave 2`。R6 P1 只关闭 identity/phase/schema/bridge/store 的负向契约,避免复制未来 producer。 From aa20664378bfca5be6dc776bb8a7bbbe684135bd Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Mon, 3 Aug 2026 22:24:25 +0800 Subject: [PATCH 08/14] =?UTF-8?q?=E5=86=BB=E7=BB=93=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E6=8E=A5=E7=BC=9D=E4=B8=8E=E6=8C=81=E4=B9=85=E5=8C=96=E5=8E=9F?= =?UTF-8?q?=E5=AD=90=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 明确 Idle 初开由服务端分配会话身份,并冻结 TsyPresence 耦合快照;其余深度验收留待 Wave 2 接缝收口。 Model: claude-sonnet-5 Co-Authored-By: Claude --- docs/plans-skeleton/plan-refactor-client-ui-base-v1.md | 5 +++-- docs/plans-skeleton/plan-refactor-inventory-core-v1.md | 1 + .../plan-refactor-persistence-slices-v1.md | 10 ++++++---- docs/plans-skeleton/plan-refactor-wire-s2c-v1.md | 5 +++-- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md b/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md index b24791601..7cef34701 100644 --- a/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md +++ b/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md @@ -16,13 +16,13 @@ - **进料**:R2 的 `SessionScopedStore`(Screen 订阅的一律是会话态 store);`ServerDataRouter` handler(一律经 client-thread marshal 投递 UI)。 - **出料**:Screen/HUD 展示;HUD 纪律沿用既有 memory 约束(未解锁隐藏不灰掉、沉浸式极简)。 - **共享类型**:新 `BongScreenBase`(生命周期 + 订阅 + 关闭清理)、`DiffListWidget`(推广 craft 范本)、`BongKeybindRegistry`(注册时冲突检测 + 测试期断言)、`ClientThreadMarshal` helper、`ScreenOpenPolicy`(礼貌抢屏:战斗中/已有模态时排队)。 -- **跨仓库契约**:本轨不定义 wire;消费 R6 冻结的 `CraftOpen`/`CraftPause`/`CraftResume` 与 S2C session-state payload。Craft Screen 首次进入/点击继续生产发送 `CraftOpen`,关闭只发送 `CraftPause`,显式取消按钮只发送 `CraftCancel`,重开后由 server state hydrate 决定是否发送 `CraftResume`。三种新 intent 均必须回显 server-hydrated 的 `session_key` 与 `generation`,不得由 client 省略、默认或自行生成。R7 P2 client contract pin 覆盖四条 production intent:首次打开→`CraftOpen`、关闭→`CraftPause`、显式取消→`CraftCancel`、匹配的 paused session hydrate→恰好一次 `CraftResume`;idle/no-session、terminal、`AwaitingDelivery`、`DeliveryPending`、missing/stale/mismatched session key 或 generation hydrate 均不得发送 `CraftResume`。R6 P1 只冻结并验证 wire/bridge/store 的字段与负向身份契约;匹配 paused hydrate 的 production Resume producer 留在本轨 P2,并依赖总纲 `plan-refactor-master-v1.md §3 Wave 2` 的 R6 + R4 + R2 前置。 +- **跨仓库契约**:本轨不定义 wire;消费 R6 冻结的 `CraftOpen`/`CraftPause`/`CraftResume` 与 S2C session-state payload。Craft Screen 在权威 `Idle`/no-session 初次进入时发送 identity-free `CraftOpen`,由 server/R4 按 owner/target gate 校验并分配 session identity;已有 session 的关闭只发送携带 hydrated identity/version 的 `CraftPause`,显式取消按钮只发送 `CraftCancel`,重开后仅匹配的 server-hydrated `Paused` session 发送携带同一 identity/version 的 `CraftResume`。R7 P2 client contract pin 覆盖四条 production intent:Idle 初次打开→identity-free `CraftOpen`、关闭→`CraftPause`、显式取消→`CraftCancel`、匹配的 paused session hydrate→恰好一次 `CraftResume`;idle/no-session、terminal、`AwaitingDelivery`、`DeliveryPending`、missing/stale/mismatched session key 或 generation hydrate 均不得发送 `CraftResume`。R6 P1 只冻结并验证 wire/bridge/store 的字段与负向身份契约;匹配 paused hydrate 的 production Resume producer 留在本轨 P2,并依赖总纲 `plan-refactor-master-v1.md §3 Wave 2` 的 R6 + R4 + R2 前置。 ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:92 处 fill(100) 全量分类(根节点合法/子节点顶飞);28 Screen 普查;冻结基类 API 与四个共享组件。 - ⬜ P1 基础组件落地:BongScreenBase/DiffListWidget/KeybindRegistry/ClientThreadMarshal/ScreenOpenPolicy 上线;keybind 冲突全数改绑(T/L/O/U/G 簇)。 -- ⬜ P2 Screen 迁移批次 A:炼丹/锻造/手搓/交易屏迁基类,fill(100) 顶飞点与 clearChildren 回弹点随迁修复;Craft Screen 完成生产接线:首次进入/点击继续→携带 hydrated `session_key`/`generation` 的 `CraftOpen`、close→携带同一 identity/version 的 `CraftPause`、独立取消按钮→`CraftCancel`、仅对匹配的 server-hydrated paused session reopen→携带同一 identity/version 且恰好一次 `CraftResume`,并以 client 单测锁住四条 intent 不互相替代及 idle/terminal/`AwaitingDelivery`/`DeliveryPending`/stale/mismatched hydration 不发 Resume。 +- ⬜ P2 Screen 迁移批次 A:炼丹/锻造/手搓/交易屏迁基类,fill(100) 顶飞点与 clearChildren 回弹点随迁修复;Craft Screen 完成生产接线:Idle 初次进入→identity-free `CraftOpen`,已有 session close→携带同一 identity/version 的 `CraftPause`,独立取消按钮→`CraftCancel`,仅对匹配的 server-hydrated paused session reopen→携带同一 identity/version 且恰好一次 `CraftResume`,并以 client 单测锁住四条 intent 不互相替代及 idle/terminal/`AwaitingDelivery`/`DeliveryPending`/stale/mismatched hydration 不发 Resume。 - ⬜ P3 InspectScreen 拆解:按 tab/section 拆组件文件(body/container/tooltip 已有雏形),行为不变。 - ⬜ P4 Screen 迁移批次 B + 网络线程 marshal 强制(handler 层静态检查/测试)+ 删旧。 - ⬜ P5 验收 + 吸收 plan 批量归档。 @@ -46,3 +46,4 @@ bot 测不到 client 渲染,本轨主验收 = client 单测(基类生命周 1. InspectScreen 拆解粒度(按 tab 还是按 section);拆解与 R10 server 侧 inventory 拆分是否同窗口进行。 2. ScreenOpenPolicy 的排队语义(战斗中挂起邀请到何时弹出)——涉及玩法体验,需人工拍板。 +3. `Running`/`Suspended` hydration 的 Resume producer negative test 枚举本轮不扩展;延期到总纲 `plan-refactor-master-v1.md §3 Wave 2` 的 R7 producer 验收窗口,理由是需与完整 phase enum 及 R6 store generation-CAS 一次性对拍。 diff --git a/docs/plans-skeleton/plan-refactor-inventory-core-v1.md b/docs/plans-skeleton/plan-refactor-inventory-core-v1.md index a3d917902..d9685421c 100644 --- a/docs/plans-skeleton/plan-refactor-inventory-core-v1.md +++ b/docs/plans-skeleton/plan-refactor-inventory-core-v1.md @@ -48,3 +48,4 @@ skeleton:alchemy-takeback-full-inventory-loss(交付垫层部分;session t 1. 满包溢出策略正典拍板:脚下掉落(可被他人捡走,符合末法残酷)vs 个人暂存箱(体验友好)——需人工定。 2. pre-#249 老存档迁移是否并入本轨 P3(倾向并入,一次清账)。 +3. `loot` production caller 的满包交付与 stable `delivery_id` crash/replay 专项覆盖本轮不扩展;延期到总纲 `plan-refactor-master-v1.md §3 Wave 2` 的 R10 production-delivery 验收窗口,理由是 API 级原子性测试不能证明该调用点已实际迁移。 diff --git a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md index 3875cb8cc..c14d0e9c0 100644 --- a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md +++ b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md @@ -13,7 +13,7 @@ ## 接入面 - **进料**:SQLite(bong.db,沿用)、`shutdown.rs`(#1261 之后的关服链路)、`CultivationClock`(相对 tick 基准)。 -- **出料**:统一 Slice API 供各域注册:`load(guarded) / autosave(cadence) / flush_on_shutdown / tick_rebase`;R1 的 session 持久化钩子、各域运行态表全部走它。`tick_rebase` 对 R1 suspension lease 与 outbox 的 `next_retry_tick`/`created_at_tick`/`lease_until` 保持相对剩余时长/已消耗 age;R1 冻结默认 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000` 与扫描 cadence `1_200` ticks,本轨不得把重启折算成无限续租或刷新 outbox 退避/7 天 age。为 terminal delivery 提供 `SessionDeliveryOutbox`:事务内只提交 outbox 插入与 session checkpoint terminalization/删除,提交成功后 runtime gameplay claim 释放必须可依据 durable terminal/outbox 状态幂等重放;禁止仅持久化 session、把 delivery id 留在内存。每个 checkpointed session 的 admission/restore 必须先调用 `reserve_terminal_obligation(session_key)`:在同一 `BEGIN IMMEDIATE` 事务中 conditional-update 单例 `SessionDeliveryQuota { used_rows, used_bytes, generation }`(仅当 row/bytes 上限均有余量)并插入唯一 `SessionDeliveryReservation { session_key, reserved_bytes = SESSION_DELIVERY_MAX_PAYLOAD_BYTES }`;任一步失败整笔回滚。等价非 SQLite 实现必须以 quota generation CAS 提供同一线性化点,禁止先读剩余额度再另事务插 reservation。terminal handoff 在 checkpoint/outbox 同事务把 reservation 转移给 outbox,不重复计量;只有 receipt `Committed` 或带完整 payload 的 audited `ResolvedDisposition` 在同一事务删除 obligation 并扣减 counter,CAS loser 不得释放 quota。payload 增长不得超过 reservation,上限不足必须在接受新 escrow/产物前 fail closed。dead-letter 从自动扫描集合移除且不占 claim,但继续占 quota。 +- **出料**:统一 Slice API 供各域注册:`load(guarded) / autosave(cadence) / flush_on_shutdown / tick_rebase`;R1 的 session 持久化钩子、各域运行态表全部走它。`tick_rebase` 对 R1 suspension lease 与 outbox 的 `next_retry_tick`/`created_at_tick`/`lease_until` 保持相对剩余时长/已消耗 age;R1 冻结默认 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000` 与扫描 cadence `1_200` ticks,本轨不得把重启折算成无限续租或刷新 outbox 退避/7 天 age。为 terminal delivery 提供 `SessionDeliveryOutbox`:事务内只提交 outbox 插入与 session checkpoint terminalization/删除,提交成功后 runtime gameplay claim 释放必须可依据 durable terminal/outbox 状态幂等重放;禁止仅持久化 session、把 delivery id 留在内存。每个 checkpointed session 的 admission/restore 必须先调用 `reserve_terminal_obligation(session_key)`:在同一 `BEGIN IMMEDIATE` 事务中 conditional-update 单例 `SessionDeliveryQuota { used_rows, used_bytes, generation }`(仅当 row/bytes 上限均有余量)并插入唯一 `SessionDeliveryReservation { session_key, reserved_bytes = SESSION_DELIVERY_MAX_PAYLOAD_BYTES }`;任一步失败整笔回滚。等价非 SQLite 实现必须以 quota generation CAS 提供同一线性化点,禁止先读剩余额度再另事务插 reservation。terminal handoff 在 checkpoint/outbox 同事务把 reservation 转移给 outbox,不重复计量;只有 receipt `Committed` 或带完整 payload 的 audited `ResolvedDisposition` 在同一事务删除 obligation 并扣减 counter,CAS loser 不得释放 quota。payload 增长不得超过 reservation,上限不足必须在接受新 escrow/产物前 fail closed。dead-letter 从自动扫描集合移除且不占 claim,但继续占 quota。`TsyPresence` 与 player position/dimension 作为同一 coupled snapshot:routine autosave、disconnect save、shutdown flush 均须共用同一事务/版本边界,不能由独立 Slice 提交不同快照。 - **共享类型**:新 `server/src/persistence/` 多文件模块(按域拆表定义 + 迁移链保持线性单入口);`PlayerSliceRegistry`(对齐 #1290 skeleton 的方向,直接吸收它)。 - **跨仓库契约**:零 wire 改动。 - **qi_physics 锚点**:任何带 qi 的快照持久化/恢复不得造成账面变化;恢复失败的兜底路径必须走 `release_dormant_qi_to_zone` 而非丢弃(对齐守恒律红旗清单)。 @@ -21,9 +21,9 @@ ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:53 张表普查归域;冻结 Slice trait(载入守护语义:读失败 = 保留旧行 + 告警 + 只读降级,绝不写回空态;flush registry;tick rebase 协议);等 #1288/#1289/#1261/#1259 merge 后定基线。 -- ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;注册 R1 所需的 `TsyPresence` auxiliary Slice,字段固定为 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version,并接入 guarded load、autosave、`flush_on_shutdown`、`tick_rebase`;落 `SessionDeliveryOutbox`、`SessionDeliveryQuota`、`SessionDeliveryReservation` 表与 atomic handoff/reservation API,stable `delivery_id`、完整 payload、cause、attempts/next_retry/created_at/tick_epoch/state、单调 generation 与 in-flight lease 全部持久化。P1 acceptance 必须同时锁定:reservation 的 quota conditional update + unique insert 原子性;terminal handoff 时 reservation→outbox 不重复计量;Committed/ResolvedDisposition 扣减 quota 与 obligation 删除原子性;checkpoint terminalization/deletion 与 outbox insert 同一 SQLite transaction 全成或全败;以及 outbox insert 前、terminalization 前、commit 后 ack 前的 crash-injection pins。P3 只做其余 flush registry/tick-rebase 批次,不得把这些 P1 原子边界延后。 +- ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;注册 R1 所需的 `TsyPresence` auxiliary Slice,字段固定为 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version,并接入 guarded load、**与 player position/dimension 共用同一 snapshot/version 的原子 autosave**、`flush_on_shutdown`、`tick_rebase`;任何 autosave、disconnect save 或 shutdown flush 都不得让三者跨快照提交,crash 后只能全旧或全新。落 `SessionDeliveryOutbox`、`SessionDeliveryQuota`、`SessionDeliveryReservation` 表与 atomic handoff/reservation API,stable `delivery_id`、完整 payload、cause、attempts/next_retry/created_at/tick_epoch/state、单调 generation 与 in-flight lease 全部持久化。P1 acceptance 必须同时锁定:reservation 的 quota conditional update + unique insert 原子性;terminal handoff 时 reservation→outbox 不重复计量;Committed/ResolvedDisposition 扣减 quota 与 obligation 删除原子性;checkpoint terminalization/deletion 与 outbox insert 同一 SQLite transaction 全成或全败;以及 outbox insert 前、terminalization 前、commit 后 ack 前的 crash-injection pins。P3 只做其余 flush registry/tick-rebase 批次,不得把这些 P1 原子边界延后。 - ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;身份主键统一(identity-persist-key-mismatch)。 -- ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部"节流落盘"域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥(coffin-autosave-inflight-race 模式);`TsyPresence` 的关服 flush 与 presence/position/dimension 必须在同一 SQLite transaction 内提交,crash injection 只能观察到三者全旧或全新。 +- ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部"节流落盘"域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥(coffin-autosave-inflight-race 模式);R3 的其余非耦合 slice 按 registry 收编。`TsyPresence` 三者原子 autosave/disconnect/shutdown 语义已在 P1 冻结,P3 仅补全 flush registry 接线与 crash 回归,不得把 coupled snapshot boundary 延后或缩窄为 shutdown-only。 - ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性(heiwushi)等——逐个按 Slice 框架补表。加入 `TsyPresence` guarded relog parity 契约测试和 TSY 维度重启 bot 场景,断言失败加载不 attach presence、成功恢复后才重新开放 TSY 请求。 - ⬜ P5 bot 验收 + 吸收 plan 批量归档。 @@ -45,10 +45,12 @@ skeleton:coffin-autosave-inflight-race、identity-persist-key-mismatch、miner 3. `load_failure_guard`:注入一行损坏 slice 数据→启动→断言该玩家进入守护降级而非清零覆盖(配 dev 命令注入)。 4. `tick_rebase`:带冷却/再生倒计时重启→断言倒计时按真实流逝折算(对齐 #1289 的 deadline 折算先例);另持久化一半已消耗的 `SuspensionPolicy` lease,重启/rebase 只保留原剩余 TTL,连续重复重启不刷新 lease,并覆盖剩余时长前一 tick、精确边界、后一 tick;outbox 同样覆盖 `next_retry_tick` 剩余退避、`created_at_tick` 已消耗 age、`lease_until` 剩余 lease 在单次/连续重启后的前一 tick、精确边界、后一 tick,断言不会把旧 process-local tick 直接带入新 epoch。 5. `tsy_presence_relog_parity`:进入 TSY→关服 flush→guarded load→只有 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version 校验通过才 attach `TsyPresence` 并开放 TSY 请求;损坏或缺失 Slice 保持未 attach 且拒绝请求;恢复后 death-drop 对原带物继续执行 50%/武器保护,对 TSY 所得执行既有 100% 规则。 -6. `tsy_presence_shutdown_order`:断言 session registry 静止后才 flush presence slice;向 presence、position、dimension 三个逻辑写入之间注入 shutdown crash,重启后断言三者只能全部保留旧快照或全部提交新快照,不接受 clean restore 对拍掩盖分事务写入。 +6. `tsy_presence_snapshot_atomicity`:分别在 routine autosave、disconnect save、shutdown flush 中,向 presence、position、dimension 三个逻辑写入之间注入 crash;重启后断言三者只能全部保留旧 snapshot/version 或全部提交新 snapshot/version,不接受各 Slice 独立提交后碰巧通过 clean restore 对拍。shutdown 路径另断言 session registry 静止后才 flush coupled snapshot。 7. `session_delivery_outbox_atomicity`:分别在 outbox insert 前、insert 后 checkpoint terminalize 前、事务 commit 后 ack 前强杀重启;另注入已 admission 但 payload 达到 `SESSION_DELIVERY_MAX_PAYLOAD_BYTES` 的边界,断言超限在接受新 escrow/产物前 fail closed;正常路径断言 checkpoint/outbox 恰有一个权威 owner,完整 payload 不丢且 stable `delivery_id` 不变。以两个独立 SQLite connection/线程同时竞争最后一个 row reservation、以及分别竞争不足以容纳两份 reservation 的最后 bytes,屏障同步到事务入口后并发调用 `reserve_terminal_obligation`,每种场景必须恰好一个 commit、一个 quota-full,最终 counter 与唯一 reservation 对拍且重启后不超配;再覆盖 CAS loser 不释放 quota、handoff 转移不双计、Committed/ResolvedDisposition 原子释放后新 admission 才成功。 ## 开放问题(pre-P0 收口) 1. 载入守护的玩家体验:只读降级 vs 拒绝进服 vs 回滚到上一备份?需人工拍板。 2. 迁移链是否借机做一次 squash(v1-v39 合并基线)?风险与老存档兼容性需评估。 +3. `ResolvedDisposition` 的完整 payload 不能在释放 obligation quota 后无限增长;本轮不冻结 retention、compaction 或独立 disposition quota,避免引入新的存储策略。延期到总纲 `plan-refactor-master-v1.md §3 Wave 2` 的 R10 durable-delivery 收口窗口,由 R3/R10 共同定义有界保留与释放规则。 +4. TsyPresence 三者 coupled snapshot 的 routine autosave/disconnect crash-injection 细节已由 P1 声明 atomic boundary,但具体 fault-injection harness 与跨连接时序留待 R3 P3 收口;不得因此退回独立 Slice 写入。 diff --git a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md index 80bb9fef8..d15d7ef6e 100644 --- a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md +++ b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md @@ -14,12 +14,12 @@ - **进料**:各域游戏事件(emit 调用点)、`world` 维度/zone 信息(作用域过滤)。 - **出料**:`bong:server_data` 单通道(目标态:28 旁路全部收编或显式豁免登记);join/重连首包快照集契约(R2 清干净后靠它灌满)。 - **共享类型**:新 server `network/emit/` builder(`scope: Global | Dimension | Zone | Player`);client 桥接层唯一的枚举前缀剥离函数。 -- **跨仓库契约**:proto 形状原则上不动(收编旁路时如需并入 envelope 属破坏性变更,走 buf breaking + samples 同步;agent 侧 TS 只做被动 regenerate,不重构 agent 逻辑)。**不做双轨兼容层**——旁路收编是一次性切换。R1 craft 例外要求显式冻结并一次性切换 `CraftOpen` / `CraftPause` / `CraftResume` C2S intent:三者均必须携带 `session_key: string` 与 `generation: uint64`,且两字段均 required;`CraftOpen` 使用 server hydration 后的当前 identity/version,`CraftPause` 与 `CraftResume` 必须回显该次 hydration 的 identity/version,禁止省略、默认值或仅以 client Entity 代替。并用 `CraftSessionStateV2` 一次性替换既有 S2C `CraftSessionStateV1`:V2 除现有进度字段外,固定携带 `session_key`、单调 `generation`、`phase: Idle | Running | Paused | Suspended | AwaitingDelivery | DeliveryPending | Terminal`;`Idle` 明确无 session identity,其余 phase 必须携带 identity/version。现有 `CraftCancel` 保留为唯一主动取消 intent,关屏不得复用它。R6 同时拥有 C2S 的 `agent/packages/schema/src/client-request.ts` TypeBox source、生成 JSON Schema 与提交的 `@bong/schema` dist、protobuf/Rust mirror/converter、samples、client encode/send APIs;并拥有 S2C `CraftSessionStateV2` 在 `agent/packages/schema/src/craft.ts`→generated JSON Schema/dist→`proto/bong/envelope.proto`→Rust `server/src/schema/craft.rs`/`proto_convert.rs`→`network/craft_emit.rs` producer→`ProtoServerDataBridge`→`CraftSessionStateHandler`/`CraftStore` consumer 的一次性全链替换,同一提交删除 V1 schema/proto/bridge 分支。R7 只消费这些冻结接口;R6 不复制或提前实现 R7 的 production Resume producer。 +- **跨仓库契约**:proto 形状原则上不动(收编旁路时如需并入 envelope 属破坏性变更,走 buf breaking + samples 同步;agent 侧 TS 只做被动 regenerate,不重构 agent 逻辑)。**不做双轨兼容层**——旁路收编是一次性切换。R1 craft 例外要求显式冻结并一次性切换 `CraftOpen` / `CraftPause` / `CraftResume` C2S intent:`CraftOpen` 是仅用于权威 `Idle`/no-session 初次进入的 identity-free open,由 R4 按 owner/target gate 校验并由 server 分配 session identity;`CraftPause` 与 `CraftResume` 必须携带并回显 hydrated session 的 `session_key: string` 与 `generation: uint64`,且两字段均 required。并用 `CraftSessionStateV2` 一次性替换既有 S2C `CraftSessionStateV1`:V2 除现有进度字段外,固定携带 `session_key`、单调 `generation`、`phase: Idle | Running | Paused | Suspended | AwaitingDelivery | DeliveryPending | Terminal`;`Idle` 明确无 session identity,其余 phase 必须携带 identity/version。现有 `CraftCancel` 保留为唯一主动取消 intent,关屏不得复用它。R6 同时拥有 C2S 的 `agent/packages/schema/src/client-request.ts` TypeBox source、生成 JSON Schema 与提交的 `@bong/schema` dist、protobuf/Rust mirror/converter、samples、client encode/send APIs;并拥有 S2C `CraftSessionStateV2` 在 `agent/packages/schema/src/craft.ts`→generated JSON Schema/dist→`proto/bong/envelope.proto`→Rust `server/src/schema/craft.rs`/`proto_convert.rs`→`network/craft_emit.rs` producer→`ProtoServerDataBridge`→`CraftSessionStateHandler`/`CraftStore` consumer 的一次性全链替换,同一提交删除 V1 schema/proto/bridge 分支。R7 只消费这些冻结接口;R6 不复制或提前实现 R7 的 production Resume producer。 ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:28 旁路逐个普查(收编 vs 豁免理由);100 emit 文件的重复模式取样归纳 builder API;枚举前缀剥离点全量清点;冻结 scope 语义与 join 首包快照集清单。 -- ⬜ P1 emit builder + scope 落地:builder 上线,vfx/audio/env 三类先挂 scope(跨维 bleed 立灭);跨位面切换时 env/season 全量重发;同时交付 R1 craft 所需 C2S `CraftOpen`/`CraftPause`/`CraftResume` TypeBox source→generated JSON Schema/dist→proto/Rust mirror/converter→正反 samples,以及 client `ClientRequestProtocol` encode / `ClientRequestSender` send APIs 与 producer contract tests。三种 intent 均必须携带并 roundtrip required 的 `session_key` + `generation`,生产 decoder/handler 由 R4 消费;同阶段原子交付 S2C `CraftSessionStateV2` hydration 扩展的 TypeBox/schema/dist、proto/Rust mirror/converter、`craft_emit` phase/session_key/generation producer、bridge/router/handler/CraftStore consumer 与 samples。R6 contract pins 只证明 schema/bridge/store 的身份字段、phase 全量序列化及 stale/mismatched identity 拒绝;不得在 R6 复制或提前实现 R7 的 production Resume producer。R7 的匹配 `Paused`→恰发一次 `CraftResume` 证明移交 R7 P2,依赖顺序与总纲 `plan-refactor-master-v1.md §3 Wave 2` 一致。R6 必须证明 Idle、Running、Suspended、AwaitingDelivery、DeliveryPending、Terminal、missing/stale/mismatched session key 或 generation 均不发 Resume;断线重连、乱序旧 generation 与同 key 新 generation 不得回退 store。 +- ⬜ P1 emit builder + scope 落地:builder 上线,vfx/audio/env 三类先挂 scope(跨维 bleed 立灭);跨位面切换时 env/season 全量重发;同时交付 R1 craft 所需 C2S `CraftOpen`/`CraftPause`/`CraftResume` TypeBox source→generated JSON Schema/dist→proto/Rust mirror/converter→正反 samples,以及 client `ClientRequestProtocol` encode / `ClientRequestSender` send APIs 与 producer contract tests。`CraftOpen` 为 Idle 初次进入的 identity-free request;`CraftPause`/`CraftResume` 必须携带并 roundtrip required 的 `session_key` + `generation`,生产 decoder/handler 由 R4 消费;同阶段原子交付 S2C `CraftSessionStateV2` hydration 扩展的 TypeBox/schema/dist、proto/Rust mirror/converter、`craft_emit` phase/session_key/generation producer、bridge/router/handler/CraftStore consumer 与 samples。R6 contract pins 只证明 schema/bridge/store 的身份字段、phase 全量序列化及 stale/mismatched identity 拒绝;不得在 R6 复制或提前实现 R7 的 production Resume producer。R7 的匹配 `Paused`→恰发一次 `CraftResume` 及其余 hydration 不发送的 production 证明移交 R7 P2,依赖顺序与总纲 `plan-refactor-master-v1.md §3 Wave 2` 一致;R6 仅锁定断线重连、乱序旧 generation 与同 key 新 generation 不得回退 store。 - ⬜ P2 client 桥接层收敛:枚举前缀剥离收敛到单点(含 forge-session 修复);`ServerDataRouter` 注册表整备(分域注册文件,不再单个 1547 行 switch 追加)。 - ⬜ P3 旁路归一批次:28 channel 逐批收编入 server_data envelope 或登记豁免(资源包/握手类可豁免);删除散装 receiver。 - ⬜ P4 契约 pin 全量化:R6 P1 落地三种 craft intent 后,以 116 C2S + 144 S2C 每变体至少一条正反 sample 对拍(在此之前当前 C2S baseline 仍为 113;schema 改动连 sample 一起改);emit 迁移到 builder 的长尾批次。 @@ -49,3 +49,4 @@ skeleton:vfx-audio-dimension-bleed、q-world-season-dimension-env-resync、for 1. 28 旁路的收编/豁免分界(低频大 payload 如资源包显然豁免;npc bubble 这类高频小包是否值得并入 envelope)。 2. join 首包快照集的权威清单放哪维护(emit builder 注册时声明 `replay_on_join` 标志?)。 3. R7 production `CraftResume` producer 的验收证明不在 R6 P1 提前实现;由 R7 P2 在 R6 wire/bridge、R4 handler/gate 与 R2 store 前置合入后完成,依据总纲 `plan-refactor-master-v1.md §3 Wave 2`。R6 P1 只关闭 identity/phase/schema/bridge/store 的负向契约,避免复制未来 producer。 +4. `CraftSessionStateV2.generation` 的 numeric boundary pins(`0`、`u64::MAX`、`MAX→MAX` overflow refusal、off-by-one)本轮只冻结 wire 类型与基本 roundtrip,不扩展 P1 实施;延期到总纲 `plan-refactor-master-v1.md §3 Wave 2` 的 R6/R7 接缝验收窗口,理由是边界策略需与 producer/store generation-CAS 一起收口。 From bbdfcc8c2d5e7d794fd7e7c1b40e99cd554bbc99 Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Tue, 4 Aug 2026 12:40:25 +0800 Subject: [PATCH 09/14] =?UTF-8?q?=E6=94=B6=E5=8F=A3=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E7=BB=88=E6=AD=A2=E5=8E=9F=E5=9B=A0=E4=B8=8E=20R6=20schema=20?= =?UTF-8?q?=E6=89=80=E6=9C=89=E6=9D=83=E5=A5=91=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补齐 Completed 的 P4 终止矩阵覆盖,新增授权管理员结案原因及审计结算语义,并将 R6 的 agent schema/dist 改为冻结依赖而非所有权。 Model: cc-haiku-max Co-Authored-By: Claude --- docs/plan-refactor-server-session-v1.md | 9 +++++---- docs/plans-skeleton/plan-refactor-wire-s2c-v1.md | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/plan-refactor-server-session-v1.md b/docs/plan-refactor-server-session-v1.md index e843e4ea5..078a3a39d 100644 --- a/docs/plan-refactor-server-session-v1.md +++ b/docs/plan-refactor-server-session-v1.md @@ -70,7 +70,7 @@ pub trait InteractionSession { - `PlayerKey`:canonical player id;`Entity` 只作当前连接的 runtime binding。 - `SessionDurability::{Checkpointed, Volatile}`:每个 adapter 注册时必须显式声明,不允许默认值。 - `SessionPhase::{Running, Paused, Suspended, AwaitingDelivery, DeliveryPending, Terminal}`:同一 session 不允许同时处于多个 phase。`DeliveryPending` 表示 R3 已提交 durable outbox/checkpoint terminalization、但 R10 尚未提交 receipt;它不再是可恢复 gameplay session,不持有 runtime claim。 -- `TerminationCause::{Completed, VoluntaryCancel, Disconnect, DimensionChange, Shutdown, InvalidRestore, SuspensionExpired}`;`Completed` 专用于正常完成后的 durable outbox handoff,不触发 refund。 +- `TerminationCause::{Completed, VoluntaryCancel, Disconnect, DimensionChange, Shutdown, InvalidRestore, SuspensionExpired, AuthorizedAdministratorClosure}`;`Completed` 专用于正常完成后的 durable outbox handoff,不触发 refund。 - `SuspensionPolicy`:声明 checkpointed session 的 `suspended_at_tick`、有限 `max_suspended_ticks`、过期扫描周期、管理员强制结案入口和 facility claim 回收动作;默认值冻结为 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000`(20 TPS 下 24 小时)与 `SESSION_SUSPENSION_SCAN_INTERVAL_TICKS = 1_200`,不得配置为无限期保留。 - `SessionTransition::{Keep, Pause, SuspendAndCheckpoint, Teardown, AwaitDelivery, DeliveryPending, CommitTerminal}`。 - `SessionDeliveryOutbox { delivery_id, session_key, owner_key, payload_digest, payload, cause, state, generation, attempts, next_retry_tick, created_at_tick, tick_epoch }`:R3 持久化的 durable handoff;`delivery_id` 由 `SessionKey + terminal_generation` 稳定派生,禁止仅靠内存幂等键。`generation` 是每次状态 CAS 成功后递增的 durable token;`next_retry_tick`/`created_at_tick` 必须与 `tick_epoch`(持久化的 runtime tick、wall-clock capture 与 epoch id)一起经 R3 `tick_rebase` 转换,按重启时已过去的 wall-clock duration 计算新 epoch 下的剩余 retry/age,禁止跨重启直接比较旧 process-local tick。 @@ -81,7 +81,7 @@ pub trait InteractionSession { ### 2.1 不变量 1. `SessionRegistry` 中一个 `SessionKey` 只能有一个 owner;一个 runtime `Entity` 只能绑定同一 `PlayerKey` 的 session。 -2. `Checkpointed` session 断线/关服后进入 `Suspended`,不得离线推进;必须记录 `suspended_at_tick` 并受 `SuspensionPolicy.max_suspended_ticks` 约束。重连通过 R3 guarded restore 后才重新绑定 `Entity`;超过 TTL 或管理员结案时转入 `InvalidRestore`/`SuspensionExpired` 结算,先把完整 delivery obligation 写入 durable terminal/outbox state 并转为 `DeliveryPending`,再幂等释放 facility/target claim;R10 receipt 提交后才进入 `Terminal`。过期扫描和管理员入口必须幂等,且不会等待 owner 重连。 +2. `Checkpointed` session 断线/关服后进入 `Suspended`,不得离线推进;必须记录 `suspended_at_tick` 并受 `SuspensionPolicy.max_suspended_ticks` 约束。重连通过 R3 guarded restore 后才重新绑定 `Entity`;超过 TTL 时转入 `SuspensionExpired`,管理员结案时转入 `AuthorizedAdministratorClosure`,恢复校验失败时转入 `InvalidRestore`,分别按对应终止原因结算,先把完整 delivery obligation 写入 durable terminal/outbox state 并转为 `DeliveryPending`,再幂等释放 facility/target claim;R10 receipt 提交后才进入 `Terminal`。过期扫描和管理员入口必须幂等,且不会等待 owner 重连。 3. `Volatile` session 遇到断线、跨维或关服必须在同一生命周期门内 teardown;不得留下 owner entity、设施锁、target claim 或 `settling` 标记。 4. 所有 dimension-scoped session 在维度切换前终止。`TsyPresence` 是 transport 辅助状态,单独 checkpoint/restore,不得用“保留旧交互 session”修复 presence 撕裂。 5. client 的 screen/store 只能改善 UX,不能授予 session 或 busy 权限;恶意包、重复包和同 tick 竞态最终都由 registry 拒绝。 @@ -167,6 +167,7 @@ pub trait InteractionSession { - `Shutdown`:`Checkpointed` 进入 `Suspended`,不退款,checkpoint 保留未消费 escrow;`Volatile` 在 inventory 可访问时全退并终止。 - `InvalidRestore`:全退未消费 escrow;已完成产物只走一次 delivery,不与 inputs 双发。 - `SuspensionExpired`:全退未消费 escrow/交付已完成产物;先提交 durable terminal/outbox state,再幂等释放 claim 并终止,delivery 失败不重新占用 claim。 + - `AuthorizedAdministratorClosure`:授权管理员主动结案仍按 durable terminal/outbox handoff 结算;全退未消费 escrow/交付已完成产物,审计记录 operator principal 与 reason,不得伪装为 `InvalidRestore` 或 `SuspensionExpired`,delivery 失败不重新占用 claim。 - `Disconnect`/`Shutdown` 的 checkpoint 保留与 terminal cause 的 refund 互斥;terminal delivery 使用 §2.1 的 crash-atomic transaction 或 durable outbox/receipt,不得保留既可恢复又待退款的 session,也不得在 receipt 未落盘时宣称交付完成。 - refund 也走 R10 delivery 垫层;满包不得退化为日志告警。R10 冻结的 `deliver(delivery_id, items) -> Delivered | Spilled(fallback)` 与 durable `delivery_id`/receipt 见 `plan-refactor-inventory-core-v1.md` 接入面。 @@ -235,7 +236,7 @@ pub trait InteractionSession { - 新增 `server/src/session/{mod.rs,registry.rs,lifecycle.rs}`,包含 §2 全部 symbol;framework-only 可在 R3 P1 后落地,但不启用生产 craft adapter。 - R6 `CraftOpen`/`CraftPause`/`CraftResume` 契约、R4 production handler/gate、R7 P2 Craft Screen producer/consumer(消费 R2 P1 已登记的 `CraftStore`)、R10 P1 `deliver` contract 与 R10 P2 craft production delivery 全部合入后,craft 才迁移到 `SessionRegistry`:关闭 screen pause、显式 cancel、重开 resume,现有 recipe/session join hydration 行为不变。 -- contract pins:六态转换、stable owner 重绑、disconnect-before-save、dimension-before-transfer、busy 冲突、delivery commit gate,以及 §2.5 七个 `TerminationCause` 的逐变体正反测试;明确断言 `Completed` 只交付产物且不退款,`Disconnect`/`Shutdown` checkpoint 不退款,terminal refund 不保留可恢复 escrow。delivery pins 必须在 transaction 的“outbox 写入前/写入后 terminalize 前/receipt+inventory commit 后 ack 前”逐点模拟进程退出并重载,断言 payload 总数恰为一次;SQLite 同库路径另断言 inventory/spill、receipt、checkpoint 删除在单事务全成或全败。 +- contract pins:六态转换、stable owner 重绑、disconnect-before-save、dimension-before-transfer、busy 冲突、delivery commit gate,以及 §2.5 八个 `TerminationCause` 的逐变体正反测试;明确断言 `Completed` 只交付产物且不退款,`Disconnect`/`Shutdown` checkpoint 不退款,terminal refund 不保留可恢复 escrow。delivery pins 必须在 transaction 的“outbox 写入前/写入后 terminalize 前/receipt+inventory commit 后 ack 前”逐点模拟进程退出并重载,断言 payload 总数恰为一次;SQLite 同库路径另断言 inventory/spill、receipt、checkpoint 删除在单事务全成或全败。 - `SuspensionPolicy` pins:TTL 前后、永不重连、授权管理员结案、offline client 伪造 allow-listed `Username` 仍拒绝、普通玩家/未知 executor/伪造 owner/跨 owner target 拒绝、过期/重连/关服竞态、重复扫描幂等、指数退避 cap、10 次/7 天 dead-letter、人工 retry/resolve 权限,以及 claim 回收后另一玩家可获取同一设施。另须持久化一半已消耗的 lease,重启后 rebasing 只保留原剩余 TTL;连续第二次/第三次重启不得刷新 `suspended_at_tick`,并覆盖 `remaining-1`、精确边界、`remaining+1`。 - `PendingInsightOffer` pins:创建后正常选择/拒绝、deadline 前后与 off-by-one、同 tick response-versus-timeout 竞态、断线/跨维/关服清除,以及过期后不得再次接受选择。 @@ -262,7 +263,7 @@ pub trait InteractionSession { 5. `session_full_inventory_delivery` 6. `session_suspension_reclamation`:永不重连、TTL 边界、授权管理员结案、普通玩家/未知 executor/伪造 owner/跨 owner target 拒绝、过期与重连竞态、重复扫描、claim 释放与 outbox handoff 均不泄漏或双发 claim/escrow。 7. `session_delivery_crash_atomicity`:在 outbox/terminal checkpoint/receipt/inventory-or-spill/ack 各持久化边界强杀重启,断言 stable `delivery_id` 最终只交付一次;失败按指数退避,在 10 次或 7 天转 `DeadLetter`,且从 handoff 起不占设施 claim。 -8. `session_termination_cause_matrix`:逐项命中 `VoluntaryCancel`、`Disconnect`、`DimensionChange`、`Shutdown`、`InvalidRestore`、`SuspensionExpired`,对拍 checkpoint/refund 互斥与 inputs/output 不双发。 +8. `session_termination_cause_matrix`:逐项命中 `Completed`、`VoluntaryCancel`、`Disconnect`、`DimensionChange`、`Shutdown`、`InvalidRestore`、`SuspensionExpired`、`AuthorizedAdministratorClosure`,对拍 checkpoint/refund 互斥与 inputs/output 不双发,并断言管理员 principal/reason 被审计记录。 9. `session_craft_pause_resume_wire`:真实 client producer → R6 proto/bridge → R4 production handler/gate → R1 registry → S2C hydrate → client reopen consumer 全链路。 10. `session_tsy_presence_relog`:R3 guarded presence Slice 完整恢复 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version 并与 position/dimension 同事务对拍;分别注入 facility 不存在、owner 不匹配、dimension 不匹配、version 不匹配四类 guarded-load 拒绝,任何一类都不 attach、不开放 TSY 请求;恢复后 death-drop 仍正确区分原带物与 TSY 所得。 11. `session_pending_insight_offer_deadline`:覆盖创建后选择/拒绝、deadline 前后与 off-by-one、同 tick response-versus-timeout 竞态、断线/跨维/关服清除,以及过期后不得再次接受选择。 diff --git a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md index d15d7ef6e..582b7261c 100644 --- a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md +++ b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md @@ -14,7 +14,7 @@ - **进料**:各域游戏事件(emit 调用点)、`world` 维度/zone 信息(作用域过滤)。 - **出料**:`bong:server_data` 单通道(目标态:28 旁路全部收编或显式豁免登记);join/重连首包快照集契约(R2 清干净后靠它灌满)。 - **共享类型**:新 server `network/emit/` builder(`scope: Global | Dimension | Zone | Player`);client 桥接层唯一的枚举前缀剥离函数。 -- **跨仓库契约**:proto 形状原则上不动(收编旁路时如需并入 envelope 属破坏性变更,走 buf breaking + samples 同步;agent 侧 TS 只做被动 regenerate,不重构 agent 逻辑)。**不做双轨兼容层**——旁路收编是一次性切换。R1 craft 例外要求显式冻结并一次性切换 `CraftOpen` / `CraftPause` / `CraftResume` C2S intent:`CraftOpen` 是仅用于权威 `Idle`/no-session 初次进入的 identity-free open,由 R4 按 owner/target gate 校验并由 server 分配 session identity;`CraftPause` 与 `CraftResume` 必须携带并回显 hydrated session 的 `session_key: string` 与 `generation: uint64`,且两字段均 required。并用 `CraftSessionStateV2` 一次性替换既有 S2C `CraftSessionStateV1`:V2 除现有进度字段外,固定携带 `session_key`、单调 `generation`、`phase: Idle | Running | Paused | Suspended | AwaitingDelivery | DeliveryPending | Terminal`;`Idle` 明确无 session identity,其余 phase 必须携带 identity/version。现有 `CraftCancel` 保留为唯一主动取消 intent,关屏不得复用它。R6 同时拥有 C2S 的 `agent/packages/schema/src/client-request.ts` TypeBox source、生成 JSON Schema 与提交的 `@bong/schema` dist、protobuf/Rust mirror/converter、samples、client encode/send APIs;并拥有 S2C `CraftSessionStateV2` 在 `agent/packages/schema/src/craft.ts`→generated JSON Schema/dist→`proto/bong/envelope.proto`→Rust `server/src/schema/craft.rs`/`proto_convert.rs`→`network/craft_emit.rs` producer→`ProtoServerDataBridge`→`CraftSessionStateHandler`/`CraftStore` consumer 的一次性全链替换,同一提交删除 V1 schema/proto/bridge 分支。R7 只消费这些冻结接口;R6 不复制或提前实现 R7 的 production Resume producer。 +- **跨仓库契约**:proto 形状原则上不动(收编旁路时如需并入 envelope 属破坏性变更,走 buf breaking + samples 同步;agent 侧 TS 只做被动 regenerate,不重构 agent 逻辑)。**不做双轨兼容层**——旁路收编是一次性切换。R1 craft 例外要求显式冻结并一次性切换 `CraftOpen` / `CraftPause` / `CraftResume` C2S intent:`CraftOpen` 是仅用于权威 `Idle`/no-session 初次进入的 identity-free open,由 R4 按 owner/target gate 校验并由 server 分配 session identity;`CraftPause` 与 `CraftResume` 必须携带并回显 hydrated session 的 `session_key: string` 与 `generation: uint64`,且两字段均 required。并用 `CraftSessionStateV2` 一次性替换既有 S2C `CraftSessionStateV1`:V2 除现有进度字段外,固定携带 `session_key`、单调 `generation`、`phase: Idle | Running | Paused | Suspended | AwaitingDelivery | DeliveryPending | Terminal`;`Idle` 明确无 session identity,其余 phase 必须携带 identity/version。现有 `CraftCancel` 保留为唯一主动取消 intent,关屏不得复用它。R6 不拥有或修改 `agent/packages/schema/src/client-request.ts`、`agent/packages/schema/src/craft.ts`、生成 JSON Schema 或提交的 `@bong/schema` dist;这些 agent 轨 artifacts 必须先由独立 owner 冻结并提供,R6 只消费冻结 schema。R6 拥有 protobuf/Rust mirror/converter、samples、client encode/send APIs,以及 S2C `CraftSessionStateV2` 从冻结 schema 经 `proto/bong/envelope.proto`→Rust `server/src/schema/craft.rs`/`proto_convert.rs`→`network/craft_emit.rs` producer→`ProtoServerDataBridge`→`CraftSessionStateHandler`/`CraftStore` consumer 的一次性全链替换,同一提交删除 V1 schema/proto/bridge 分支。R7 只消费这些冻结接口;R6 不复制或提前实现 R7 的 production Resume producer。 ## 阶段 From bbcb9f30734cbbcb120b6df1f30f153e560818e5 Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Tue, 4 Aug 2026 15:46:06 +0800 Subject: [PATCH 10/14] =?UTF-8?q?=E5=86=BB=E7=BB=93=20craft=20schema=20?= =?UTF-8?q?=E6=89=80=E6=9C=89=E6=9D=83=E4=B8=8E=E5=B7=A5=E4=BD=9C=E5=8F=B0?= =?UTF-8?q?=E7=9B=AE=E6=A0=87=E6=A1=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 由 Agent 轨原子拥有 TypeBox source 及生成物,R6 仅消费并落 wire;同时把耦合快照强杀证据前移至 R3 P1,并冻结 workbench_key 的跨端校验契约。 Model: cc-haiku-max Co-Authored-By: Claude --- docs/plan-refactor-server-session-v1.md | 6 +++--- docs/plans-skeleton/plan-refactor-client-ui-base-v1.md | 4 ++-- docs/plans-skeleton/plan-refactor-master-v1.md | 4 +++- docs/plans-skeleton/plan-refactor-persistence-slices-v1.md | 4 ++-- docs/plans-skeleton/plan-refactor-wire-s2c-v1.md | 6 +++--- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/docs/plan-refactor-server-session-v1.md b/docs/plan-refactor-server-session-v1.md index 078a3a39d..c5f216efd 100644 --- a/docs/plan-refactor-server-session-v1.md +++ b/docs/plan-refactor-server-session-v1.md @@ -220,12 +220,12 @@ pub trait InteractionSession { - **R1 独占**:`server/src/session/`、七域 `session.rs`、`network/craft_emit.rs` 的 session tick 区,以及迁移时删除的域内私有生命周期代码。 - **R3 独占**:`server/src/persistence/**`、player load/autosave/shutdown flush、`SessionDeliveryOutbox` 与 checkpoint terminalization transaction;R1 仅消费 checkpoint/restore/outbox hook。 - **R4 独占**:`server/src/network/client_request_handler.rs` 与 `network/gate/`;R1 仅暴露 busy/session query。 -- **R6 独占**:proto/S2C bridge 与跨端 pause/resume 契约变更。 -- **R7/R2 独占**:client Screen/HUD 与 Store disconnect 清理。 +- **R6 独占**:proto/Rust mirror/converter、client wire encode/send、S2C bridge/router 与跨端 pause/resume 契约变更;R6 消费 Agent 轨冻结的 TypeBox/schema/dist,不修改 agent-side source-of-truth artifacts。 +- **R7/R2 独占**:client Screen/HUD 与 Store disconnect 清理;R7 的 Craft Screen 只消费 R6 wire 和总纲 §4 的 `CraftOpen.target`,Workbench screen 必须原样保留 server response 提供的 `workbench_key`。 - **R10 独占**:`server/src/inventory/**`、`InventoryTxn::deliver`、inventory/spill mutation 与 `DeliveryCommitReceipt` 的原子提交;R1 只生成 stable `delivery_id` 并决定何时 terminalize/释放 claim。 - **阶段放行矩阵**: - framework-only:R3 P1 合入后可落 `InteractionSession`、registry、lifecycle 与不触达生产 wire/delivery 的 contract pins。 - - craft producer path:R6 P1 交付 `CraftOpen`/`CraftPause`/`CraftResume` proto/schema/bridge → R4 P1 交付 production decode/dispatch 与 owner/phase/busy gate → R7 P2 交付 close/pause、显式 cancel、reopen/resume UI producer/consumer(R2 P1 已登记的 `CraftStore` 继续提供 disconnect lifecycle)→ R3 P1 交付 durable `SessionDeliveryOutbox`/terminal checkpoint transaction → R10 P1 冻结 `deliver`、stable `delivery_id` 与 durable receipt contract,且 R10 P2 把 craft production 调用点迁入 exactly-once transaction;全部合入后 R1 才能启用并验收 craft adapter。 + - craft producer path:Agent 轨先冻结 `CraftOpen.target`、`CraftPause`、`CraftResume` TypeBox/schema/dist;随后 R6 P1 交付这些冻结契约的 proto/Rust mirror/bridge/client wire plumbing 与 `CraftSessionStateV2` → R4 P1 交付 production decode/dispatch 与 owner/phase/busy gate → R7 P2 交付 close/pause、显式 cancel、reopen/resume UI producer/consumer(R2 P1 已登记的 `CraftStore` 继续提供 disconnect lifecycle)→ R3 P1 交付 durable `SessionDeliveryOutbox`/terminal checkpoint transaction → R10 P1 冻结 `deliver`、stable `delivery_id` 与 durable receipt contract,且 R10 P2 把 craft production 调用点迁入 exactly-once transaction;全部合入后 R1 才能启用并验收 craft adapter。 - alchemy/forge:还须 R10 P2 将两域 production delivery 调用点迁入 `InventoryTxn::deliver`,否则只能停在 `AwaitingDelivery`。 - `TsyPresence`:R3 P1 注册 auxiliary Slice,且 R3 P4 restore parity 常绿后,R1 P3 才能 attach 新 runtime `Entity` 并开放 TSY 请求。 - 禁止用 mock、registry 单测或临时持久层越过上述门宣称端到端完成;依赖未齐时 phase 保持未完成。 diff --git a/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md b/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md index 7cef34701..4afed94db 100644 --- a/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md +++ b/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md @@ -16,13 +16,13 @@ - **进料**:R2 的 `SessionScopedStore`(Screen 订阅的一律是会话态 store);`ServerDataRouter` handler(一律经 client-thread marshal 投递 UI)。 - **出料**:Screen/HUD 展示;HUD 纪律沿用既有 memory 约束(未解锁隐藏不灰掉、沉浸式极简)。 - **共享类型**:新 `BongScreenBase`(生命周期 + 订阅 + 关闭清理)、`DiffListWidget`(推广 craft 范本)、`BongKeybindRegistry`(注册时冲突检测 + 测试期断言)、`ClientThreadMarshal` helper、`ScreenOpenPolicy`(礼貌抢屏:战斗中/已有模态时排队)。 -- **跨仓库契约**:本轨不定义 wire;消费 R6 冻结的 `CraftOpen`/`CraftPause`/`CraftResume` 与 S2C session-state payload。Craft Screen 在权威 `Idle`/no-session 初次进入时发送 identity-free `CraftOpen`,由 server/R4 按 owner/target gate 校验并分配 session identity;已有 session 的关闭只发送携带 hydrated identity/version 的 `CraftPause`,显式取消按钮只发送 `CraftCancel`,重开后仅匹配的 server-hydrated `Paused` session 发送携带同一 identity/version 的 `CraftResume`。R7 P2 client contract pin 覆盖四条 production intent:Idle 初次打开→identity-free `CraftOpen`、关闭→`CraftPause`、显式取消→`CraftCancel`、匹配的 paused session hydrate→恰好一次 `CraftResume`;idle/no-session、terminal、`AwaitingDelivery`、`DeliveryPending`、missing/stale/mismatched session key 或 generation hydrate 均不得发送 `CraftResume`。R6 P1 只冻结并验证 wire/bridge/store 的字段与负向身份契约;匹配 paused hydrate 的 production Resume producer 留在本轨 P2,并依赖总纲 `plan-refactor-master-v1.md §3 Wave 2` 的 R6 + R4 + R2 前置。 +- **跨仓库契约**:本轨不定义 wire,消费总纲 §4 与 R6 契约。Idle/no-session 初次 `CraftOpen` 必须带 target:手搓为 `Handcraft`;Workbench handler 从成功 S2C `WorkbenchOpen.entity_id` 读取 u64 decimal-string `workbench_key`,构造 screen 时注入并不可变保留,初次请求原样发送 `Workbench { workbench_key }`,不得猜测或改写。R4 校验后分配 session identity。已有 session close 只发 hydrated identity/version 的 `CraftPause`,显式取消只发 `CraftCancel`,仅匹配的 server-hydrated `Paused` session 发一次 `CraftResume`。P2 pins 覆盖两种 target、u64 key 边界、response→screen→request 不变、missing/malformed key 不开 Workbench screen,以及四条 intent 不互相替代;terminal/`AwaitingDelivery`/`DeliveryPending`/stale identity 不发 Resume。 ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:92 处 fill(100) 全量分类(根节点合法/子节点顶飞);28 Screen 普查;冻结基类 API 与四个共享组件。 - ⬜ P1 基础组件落地:BongScreenBase/DiffListWidget/KeybindRegistry/ClientThreadMarshal/ScreenOpenPolicy 上线;keybind 冲突全数改绑(T/L/O/U/G 簇)。 -- ⬜ P2 Screen 迁移批次 A:炼丹/锻造/手搓/交易屏迁基类,fill(100) 顶飞点与 clearChildren 回弹点随迁修复;Craft Screen 完成生产接线:Idle 初次进入→identity-free `CraftOpen`,已有 session close→携带同一 identity/version 的 `CraftPause`,独立取消按钮→`CraftCancel`,仅对匹配的 server-hydrated paused session reopen→携带同一 identity/version 且恰好一次 `CraftResume`,并以 client 单测锁住四条 intent 不互相替代及 idle/terminal/`AwaitingDelivery`/`DeliveryPending`/stale/mismatched hydration 不发 Resume。 +- ⬜ P2 Screen 迁移批次 A:炼丹/锻造/手搓/交易屏迁基类,随迁修复 fill(100)/clearChildren;Craft Screen 接线为 Idle→带 `Handcraft` 或 retained `Workbench { workbench_key }` 的 `CraftOpen`、close→`CraftPause`、显式取消→`CraftCancel`、匹配 paused hydrate→恰一次 `CraftResume`,并以 client 单测锁住 target key roundtrip、四种 intent 不互相替代及不可恢复 phase/stale identity 不发 Resume。 - ⬜ P3 InspectScreen 拆解:按 tab/section 拆组件文件(body/container/tooltip 已有雏形),行为不变。 - ⬜ P4 Screen 迁移批次 B + 网络线程 marshal 强制(handler 层静态检查/测试)+ 删旧。 - ⬜ P5 验收 + 吸收 plan 批量归档。 diff --git a/docs/plans-skeleton/plan-refactor-master-v1.md b/docs/plans-skeleton/plan-refactor-master-v1.md index 20206ee04..f8bb52c16 100644 --- a/docs/plans-skeleton/plan-refactor-master-v1.md +++ b/docs/plans-skeleton/plan-refactor-master-v1.md @@ -6,7 +6,7 @@ ## 0. 范围与铁律 -- **只重构 `server/` + `client/`**。`agent/`、`worldgen/`、`library-web/` 不动,相关 plan 独立保留(§6.11-6.12)。 +- **重构实施只改 `server/` + `client/`**。`agent/`、`worldgen/`、`library-web/` 不由 R1-R10 修改,相关 plan 独立保留(§6.11-6.12);R 轨需要 agent-side schema 变更时,必须由 §6.11 Agent 轨作为独立 production owner 先交付,R 轨只消费其冻结产物。 - 对外契约(Redis IPC、proto schema)原则上不动形状;确需变更走 buf breaking + samples 同步,agent 侧只做被动 regenerate。**不写兼容层**——切换一次到位,删旧路径。 - 真元守恒律、worldview 正典、招式 A/V 差异化红线全部继续生效。 - **测试方针(用户 2026-07-27 指示,仅限重构轨道,覆盖根 CLAUDE.md「饱和化测试」节)**: @@ -60,6 +60,8 @@ - `persistence/**`+autosave=R3;`session/`+7 域 session.rs=R1;`client_request_handler.rs`+`gate/`=R4;`*_emit.rs` 公共层+`proto_convert.rs`=R6;`qi_physics/**`+qi 字段直写行=R5;`inventory/**`=R10;cast/AV emit+skill 注册=R9。 - client:Store 生命周期+`clearClientStateOnDisconnect` 区段=R2;channel 注册区段+桥+router=R6(与 R2 同文件不同区段,merge 前互 fetch);Screen/hud/keybind/InspectScreen=R7;combat cast store=R9。 +- `agent/packages/schema/src/**`、generated JSON Schema 与提交的 `@bong/schema` dist=§6.11 Agent 轨;craft schema prerequisite 的 production owner 是 Agent 轨的独立 craft-schema 交付批次,必须在 R6 P1 前提交并验收 TypeBox source、generated schema/dist 与变体计数。理由:TypeBox 是 agent-side source of truth,生成物必须与 source 同 owner 原子提交;让 R6 同时改 source 和 wire 会违反本总纲的 agent 排除边界并形成双 owner。R6 独占 proto/Rust mirror/converter、client wire encode/send 与 bridge/router plumbing,只消费该冻结版本并负责一次性 wire 反映。R6、R4、R7、R1 的 craft gate 均以本处 ownership 决议为准。 +- CraftOpen target bridge(跨轨 canonical contract):`CraftOpen` 必须携带 required `target` 判别联合:`Handcraft` 或 `Workbench { workbench_key }`,不得省略。`workbench_key` 是现有成功 S2C `WorkbenchOpen.entity_id` 的 ECS `Entity::to_bits()` locator:逻辑/Rust 类型 `u64`、protobuf `uint64`、JSON/TypeBox 为无符号十进制字符串;它不是授权能力。普通手搓发送 `Handcraft`;`WorkbenchScreen` 必须从 response 保留该 key 并在初次 `CraftOpen` 原样回传。R4 将 key 解析为 entity 后重验实体存活且携带 `WorkbenchBlock`、玩家同维且在既有距离内,并执行 owner/busy/facility gate;R1 仅在校验通过后建立 facility claim。missing、malformed、stale、despawned、跨维或越距 key 均拒绝;R7 不得从 UI 猜测或改写 key。`CraftPause`/`CraftResume` 只携带 hydrated session identity/version,不重复 target。 - 任何轨道碰他轨文件:只允许"消费对方冻结后的 API",不允许改对方独占文件;接缝 API 归被依赖方定义。 ## 5. 工作流(GPT tmux 多会话) diff --git a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md index c14d0e9c0..57e6120db 100644 --- a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md +++ b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md @@ -21,7 +21,7 @@ ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:53 张表普查归域;冻结 Slice trait(载入守护语义:读失败 = 保留旧行 + 告警 + 只读降级,绝不写回空态;flush registry;tick rebase 协议);等 #1288/#1289/#1261/#1259 merge 后定基线。 -- ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;注册 R1 所需的 `TsyPresence` auxiliary Slice,字段固定为 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version,并接入 guarded load、**与 player position/dimension 共用同一 snapshot/version 的原子 autosave**、`flush_on_shutdown`、`tick_rebase`;任何 autosave、disconnect save 或 shutdown flush 都不得让三者跨快照提交,crash 后只能全旧或全新。落 `SessionDeliveryOutbox`、`SessionDeliveryQuota`、`SessionDeliveryReservation` 表与 atomic handoff/reservation API,stable `delivery_id`、完整 payload、cause、attempts/next_retry/created_at/tick_epoch/state、单调 generation 与 in-flight lease 全部持久化。P1 acceptance 必须同时锁定:reservation 的 quota conditional update + unique insert 原子性;terminal handoff 时 reservation→outbox 不重复计量;Committed/ResolvedDisposition 扣减 quota 与 obligation 删除原子性;checkpoint terminalization/deletion 与 outbox insert 同一 SQLite transaction 全成或全败;以及 outbox insert 前、terminalization 前、commit 后 ack 前的 crash-injection pins。P3 只做其余 flush registry/tick-rebase 批次,不得把这些 P1 原子边界延后。 +- ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;注册 R1 所需的 `TsyPresence` auxiliary Slice,字段固定为 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version,并接入 guarded load、**与 player position/dimension 共用同一 snapshot/version 的原子 autosave**、`flush_on_shutdown`、`tick_rebase`;任何 autosave、disconnect save 或 shutdown flush 都不得让三者跨快照提交,crash 后只能全旧或全新。落 `SessionDeliveryOutbox`、`SessionDeliveryQuota`、`SessionDeliveryReservation` 表与 atomic handoff/reservation API,stable `delivery_id`、完整 payload、cause、attempts/next_retry/created_at/tick_epoch/state、单调 generation 与 in-flight lease 全部持久化。P1 acceptance 必须同时锁定:TsyPresence/position/dimension 在 routine autosave、disconnect save、shutdown flush 的每个逻辑写边界均由 fault-injection harness 强杀并重启,结果只能全旧或全新;reservation 的 quota conditional update + unique insert 原子性;terminal handoff 时 reservation→outbox 不重复计量;Committed/ResolvedDisposition 扣减 quota 与 obligation 删除原子性;checkpoint terminalization/deletion 与 outbox insert 同一 SQLite transaction 全成或全败;以及 outbox insert 前、terminalization 前、commit 后 ack 前的 crash-injection pins。P3 只复用 coupled-snapshot harness 做 flush registry 长尾回归并处理其余 flush/tick-rebase 批次,不得把首次原子性证据延后。 - ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;身份主键统一(identity-persist-key-mismatch)。 - ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部"节流落盘"域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥(coffin-autosave-inflight-race 模式);R3 的其余非耦合 slice 按 registry 收编。`TsyPresence` 三者原子 autosave/disconnect/shutdown 语义已在 P1 冻结,P3 仅补全 flush registry 接线与 crash 回归,不得把 coupled snapshot boundary 延后或缩窄为 shutdown-only。 - ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性(heiwushi)等——逐个按 Slice 框架补表。加入 `TsyPresence` guarded relog parity 契约测试和 TSY 维度重启 bot 场景,断言失败加载不 attach presence、成功恢复后才重新开放 TSY 请求。 @@ -53,4 +53,4 @@ skeleton:coffin-autosave-inflight-race、identity-persist-key-mismatch、miner 1. 载入守护的玩家体验:只读降级 vs 拒绝进服 vs 回滚到上一备份?需人工拍板。 2. 迁移链是否借机做一次 squash(v1-v39 合并基线)?风险与老存档兼容性需评估。 3. `ResolvedDisposition` 的完整 payload 不能在释放 obligation quota 后无限增长;本轮不冻结 retention、compaction 或独立 disposition quota,避免引入新的存储策略。延期到总纲 `plan-refactor-master-v1.md §3 Wave 2` 的 R10 durable-delivery 收口窗口,由 R3/R10 共同定义有界保留与释放规则。 -4. TsyPresence 三者 coupled snapshot 的 routine autosave/disconnect crash-injection 细节已由 P1 声明 atomic boundary,但具体 fault-injection harness 与跨连接时序留待 R3 P3 收口;不得因此退回独立 Slice 写入。 +4. TsyPresence 三者 coupled snapshot 的 routine autosave/disconnect/shutdown crash-injection harness 与跨连接屏障时序是 P1 acceptance 的组成部分:P1 必须在每个逻辑写边界注入 crash 并重启,证明结果只能全旧或全新;P3 只复用该 harness 做 flush-registry 长尾回归,不得延期首次原子性证据。 diff --git a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md index 582b7261c..509956861 100644 --- a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md +++ b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md @@ -14,12 +14,12 @@ - **进料**:各域游戏事件(emit 调用点)、`world` 维度/zone 信息(作用域过滤)。 - **出料**:`bong:server_data` 单通道(目标态:28 旁路全部收编或显式豁免登记);join/重连首包快照集契约(R2 清干净后靠它灌满)。 - **共享类型**:新 server `network/emit/` builder(`scope: Global | Dimension | Zone | Player`);client 桥接层唯一的枚举前缀剥离函数。 -- **跨仓库契约**:proto 形状原则上不动(收编旁路时如需并入 envelope 属破坏性变更,走 buf breaking + samples 同步;agent 侧 TS 只做被动 regenerate,不重构 agent 逻辑)。**不做双轨兼容层**——旁路收编是一次性切换。R1 craft 例外要求显式冻结并一次性切换 `CraftOpen` / `CraftPause` / `CraftResume` C2S intent:`CraftOpen` 是仅用于权威 `Idle`/no-session 初次进入的 identity-free open,由 R4 按 owner/target gate 校验并由 server 分配 session identity;`CraftPause` 与 `CraftResume` 必须携带并回显 hydrated session 的 `session_key: string` 与 `generation: uint64`,且两字段均 required。并用 `CraftSessionStateV2` 一次性替换既有 S2C `CraftSessionStateV1`:V2 除现有进度字段外,固定携带 `session_key`、单调 `generation`、`phase: Idle | Running | Paused | Suspended | AwaitingDelivery | DeliveryPending | Terminal`;`Idle` 明确无 session identity,其余 phase 必须携带 identity/version。现有 `CraftCancel` 保留为唯一主动取消 intent,关屏不得复用它。R6 不拥有或修改 `agent/packages/schema/src/client-request.ts`、`agent/packages/schema/src/craft.ts`、生成 JSON Schema 或提交的 `@bong/schema` dist;这些 agent 轨 artifacts 必须先由独立 owner 冻结并提供,R6 只消费冻结 schema。R6 拥有 protobuf/Rust mirror/converter、samples、client encode/send APIs,以及 S2C `CraftSessionStateV2` 从冻结 schema 经 `proto/bong/envelope.proto`→Rust `server/src/schema/craft.rs`/`proto_convert.rs`→`network/craft_emit.rs` producer→`ProtoServerDataBridge`→`CraftSessionStateHandler`/`CraftStore` consumer 的一次性全链替换,同一提交删除 V1 schema/proto/bridge 分支。R7 只消费这些冻结接口;R6 不复制或提前实现 R7 的 production Resume producer。 +- **跨仓库契约**:proto 形状原则上不动(收编旁路时如需并入 envelope 属破坏性变更,走 buf breaking + samples 同步;agent 侧 TS 只做被动 regenerate,不重构 agent 逻辑)。**不做双轨兼容层**——旁路收编是一次性切换。Craft ownership 与 target 以总纲 §4 为准:Agent 轨拥有 TypeBox source/generated schema/`@bong/schema` dist;R6 只消费冻结版本并负责 proto/Rust/client wire plumbing。`CraftOpen.target` 为 required `Handcraft | Workbench { workbench_key }`;`workbench_key` 逻辑/Rust 为 `u64`、protobuf 为 `uint64`、JSON/TypeBox 为无符号十进制字符串,取自成功 S2C `WorkbenchOpen.entity_id` 并由 screen 原样回传,R4 解析后重验实体/维度/距离/facility。`CraftPause`/`CraftResume` 只携带 hydrated `session_key` + `generation`。R6 同时把冻结的 `CraftSessionStateV2` 一次性反映到 proto/Rust converter、`craft_emit`、bridge/router/handler/CraftStore,删除 V1 wire 分支;R7 只消费接口,不在 R6 提前实现 production Resume producer。 ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:28 旁路逐个普查(收编 vs 豁免理由);100 emit 文件的重复模式取样归纳 builder API;枚举前缀剥离点全量清点;冻结 scope 语义与 join 首包快照集清单。 -- ⬜ P1 emit builder + scope 落地:builder 上线,vfx/audio/env 三类先挂 scope(跨维 bleed 立灭);跨位面切换时 env/season 全量重发;同时交付 R1 craft 所需 C2S `CraftOpen`/`CraftPause`/`CraftResume` TypeBox source→generated JSON Schema/dist→proto/Rust mirror/converter→正反 samples,以及 client `ClientRequestProtocol` encode / `ClientRequestSender` send APIs 与 producer contract tests。`CraftOpen` 为 Idle 初次进入的 identity-free request;`CraftPause`/`CraftResume` 必须携带并 roundtrip required 的 `session_key` + `generation`,生产 decoder/handler 由 R4 消费;同阶段原子交付 S2C `CraftSessionStateV2` hydration 扩展的 TypeBox/schema/dist、proto/Rust mirror/converter、`craft_emit` phase/session_key/generation producer、bridge/router/handler/CraftStore consumer 与 samples。R6 contract pins 只证明 schema/bridge/store 的身份字段、phase 全量序列化及 stale/mismatched identity 拒绝;不得在 R6 复制或提前实现 R7 的 production Resume producer。R7 的匹配 `Paused`→恰发一次 `CraftResume` 及其余 hydration 不发送的 production 证明移交 R7 P2,依赖顺序与总纲 `plan-refactor-master-v1.md §3 Wave 2` 一致;R6 仅锁定断线重连、乱序旧 generation 与同 key 新 generation 不得回退 store。 +- ⬜ P1 emit builder + scope 落地:前置为 Agent 轨已按总纲 §4 提交 craft TypeBox source/generated schema/`@bong/schema` dist;R6 不修改这些 artifacts。builder 上线,vfx/audio/env 挂 scope;跨位面时 env/season 全量重发。把冻结 craft schema 一次性反映为 proto/Rust mirror/converter、samples、client encode/send API 与 producer pins:`CraftOpen.target` 正反 roundtrip `Handcraft` 和 `Workbench { workbench_key }`,覆盖 u64/decimal-string 边界及 response→screen→request key 不变;`CraftPause`/`CraftResume` roundtrip required `session_key` + `generation`。同阶段交付 `CraftSessionStateV2` proto/Rust converter、`craft_emit` producer、bridge/router/handler/CraftStore;R6 pins 覆盖 phase/identity、stale generation 与 malformed/out-of-u64 key,R4 pins 覆盖 stale/despawned/跨维/越距 key 拒绝。R7 的匹配 `Paused`→恰发一次 `CraftResume` 留在 R7 P2。 - ⬜ P2 client 桥接层收敛:枚举前缀剥离收敛到单点(含 forge-session 修复);`ServerDataRouter` 注册表整备(分域注册文件,不再单个 1547 行 switch 追加)。 - ⬜ P3 旁路归一批次:28 channel 逐批收编入 server_data envelope 或登记豁免(资源包/握手类可豁免);删除散装 receiver。 - ⬜ P4 契约 pin 全量化:R6 P1 落地三种 craft intent 后,以 116 C2S + 144 S2C 每变体至少一条正反 sample 对拍(在此之前当前 C2S baseline 仍为 113;schema 改动连 sample 一起改);emit 迁移到 builder 的长尾批次。 @@ -35,7 +35,7 @@ skeleton:vfx-audio-dimension-bleed、q-world-season-dimension-env-resync、for - 独占:server `network/*_emit.rs` 公共模式与新 `network/emit/`、`schema/proto_convert.rs`;client `network/`(ProtoServerDataBridge、ServerDataRouter、BongNetworkHandler 的 channel 注册区段)。 - 不碰:`BongNetworkHandler.clearClientStateOnDisconnect` 区段(R2 域,同文件分区段,merge 前互相 fetch);`client_request_handler.rs`(R4);各 emit 的业务语义。 -- 依赖:无硬前置;R2 先合(同文件低冲突区段);R1 craft adapter 不得在本轨 P1 的 `CraftOpen`/`CraftPause`/`CraftResume` 契约 pins 合入前宣称 pause/resume 可达;R4 的 craft handler/gate 接缝在该契约合入后落地,R4 P2 其余拆分建议在本轨 P1 后开。 +- 依赖:Agent 轨独立 craft-schema 批次先合并并冻结 TypeBox source/schema/dist;R2 先合(同文件低冲突区段)。R1 craft adapter 不得在本轨 P1 的 `CraftOpen`/`CraftPause`/`CraftResume` 契约 pins 合入前宣称 pause/resume 可达;R4 的 craft handler/gate 接缝在该契约合入后落地,R4 P2 其余拆分建议在本轨 P1 后开。 ## bot 验收场景 From b928c57a8fd1c62f763dfcaad00a327a5e5c486f Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Tue, 4 Aug 2026 18:23:36 +0800 Subject: [PATCH 11/14] =?UTF-8?q?=E9=97=AD=E5=90=88=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E4=BA=A4=E4=BB=98=E7=94=9F=E5=91=BD=E5=91=A8=E6=9C=9F=E4=B8=8E?= =?UTF-8?q?=E8=B7=A8=E8=BD=A8=E7=94=9F=E4=BA=A7=E9=93=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 以唯一 reservation 状态矩阵消除额度泄漏和重复计量,并为 craft schema、稳定工作台身份及 terminal outbox 建立可执行的生产消费与验收顺序。 Model: claude-sonnet-5 Co-Authored-By: Claude --- docs/plan-refactor-inventory-core-v1.md | 32 +++++++--- docs/plan-refactor-server-session-v1.md | 36 ++++++++--- .../plan-agent-craft-schema-v1.md | 60 +++++++++++++++++++ .../plan-refactor-c2s-gate-v1.md | 2 +- .../plans-skeleton/plan-refactor-master-v1.md | 45 ++++++++++++-- .../plan-refactor-persistence-slices-v1.md | 8 ++- .../plan-refactor-wire-s2c-v1.md | 4 +- 7 files changed, 161 insertions(+), 26 deletions(-) create mode 100644 docs/plans-skeleton/plan-agent-craft-schema-v1.md diff --git a/docs/plan-refactor-inventory-core-v1.md b/docs/plan-refactor-inventory-core-v1.md index d261a58d8..649e5bc2a 100644 --- a/docs/plan-refactor-inventory-core-v1.md +++ b/docs/plan-refactor-inventory-core-v1.md @@ -6,7 +6,7 @@ - ✅ 2026-08-03 P0 完整契约面重写 + absorption audit - ⬜ P1:inventory 拆分 + txn/capacity 骨架 + inventory-layout/dropped-loot 纯 migration helpers(依赖 R3 P1 seam) -- ⬜ P2:production writer 迁移分为 P2a metadata/provider + Public writer path 与 P2b OwnerOnly private-writer activation;P2a 依赖 R3 P2 atomic commit seam 与旧 dropped-loot compatibility,P2b 必须等 R3 P4 dropped-loot migration/hydration、R6 P1 recipient projection/page、R5 P3 + R6 P4、R10 P3 pickup txn 及 R4 pickup consumer 全部完成后才可启用。 +- ⬜ P2:production writer 迁移分为 P2a metadata/provider + Public writer path、P2b OwnerOnly private-writer activation 与 P2c terminal-delivery worker;P2a 依赖 R3 P2 atomic commit seam 与旧 dropped-loot compatibility,P2b 必须等 R3 P4 dropped-loot migration/hydration、R6 P1 recipient projection/page、R5 P3 + R6 P4、R10 P3 pickup txn 及 R4 pickup consumer 全部完成后才可启用;P2c 依赖 R3 P1 outbox/reservation API 与 R10 P1 `deliver`/receipt contract,独立于 dropped-loot visibility 波次,且是 R1 任一 checkpointed 宿主迁移的硬前置。 - ⬜ P3:pickup/merge txn(依赖 R5 P3 attrition API、R6 P4 receipt API) - ⬜ P4:联合 bot/e2e + plan 收口(依赖 R4 handler 与 R3 P4 legacy inventory-layout consumer) @@ -39,6 +39,23 @@ InventoryTxn::pickup_and_merge(PickupRequest, PickupAuthorization, &mut DroppedL `consume_checked` receipt 逐 instance 记录扣除量/剩余量;insufficient、unknown、zero 任一失败不得部分扣除。 +### 1.2 Terminal-delivery production consumer(P2c) + +R10 P2c 必须交付常驻 `SessionDeliveryWorker`,它是 R3 `SessionDeliveryOutbox` 的唯一生产 consumer,而不是测试 helper 或各域直接 `deliver` 调用。接口边界固定为: + +```rust +SessionDeliveryWorker::claim_next(now, worker_id) + -> Result, DeliveryWorkerError> +SessionDeliveryWorker::commit(claimed, DeliveryRequest, Option<&mut SpillContext>) + -> Result +SessionDeliveryWorker::fail(claimed, reason, now) + -> Result +``` + +`claim_next` 通过 R3 expected `(Pending, generation)` CAS 写 `InFlight { lease_id, lease_until }`;`commit` 解码并逐字段校验完整 payload,以 stable `delivery_id` 调 `InventoryTxn::deliver`,并在 R3 提供的同一 durable transaction 中原子提交 inventory/spill mutation、`DeliveryCommitReceipt`、`InFlight→Committed`、obligation 删除与 quota `-Q`。已有 receipt 时返回既有结果,不再次 deliver 或释放 quota。`fail` 只更新 attempts/backoff 或在阈值后 CAS 到 `DeadLetter`,quota 增量为零;lease expiry scanner、operator retry/resolve 与 worker 共用 R1 §2.2.2 generation-CAS 规则。R10 P2c 同时提供启动 worker/scanner、shutdown drain/lease handoff 与监控告警的 production wiring;R3 只持有 SQL/CAS primitive,不运行 inventory consumer。 + +P2c contract pins:空队列、单条、并发 worker 唯一 claim、claim 后崩溃/lease expiry、deliver 前后崩溃、receipt 已存在重放、暂时失败退避、10 次/7 天 dead-letter、worker↔scanner/operator CAS loser、满包 spill、malformed payload fail closed,以及 `Pending/InFlight/DeadLetter` 始终占 quota、`Committed/ResolvedDisposition` 恰释放一次。R1 的 `session_delivery_crash_atomicity` 与 R3 的 `session_delivery_outbox_atomicity` 必须通过真实 P2c worker,不得直接调用 `InventoryTxn::deliver` 冒充 outbox consumer。 + `pickup` receipt 含 request id、revision、removed drop、merge/placement、`target_instance_id`、`incoming_instance_id/count/abs_qi_before`。placement 的 target 等于 incoming;merge 的 target 是提交后既有 stack。R5 只按 incoming absolute qi 做 attrition:`target_after = preexisting_abs_qi + incoming_after`,不得磨损旧数量或由合并后整栈反推。 ## 2. Spill / overflow 守恒 @@ -87,7 +104,7 @@ R10 的 inventory-layout 与 dropped-loot migration 函数均纯且幂等,保 ## 6. 所有权与顺序 -- **R10**:`server/src/inventory/**` model/grid/txn/capacity、writer enumeration、typed outcome、纯 migration。P1 仅在 **R3 P1** 的 inventory/overflow seam 冻结后实现 txn/capacity 骨架;P2a 负责 metadata/provider 与 `Public` writer path,依赖 **R3 P2** durable spill/pickup recoverable-commit seam、旧 dropped-loot migration compatibility 与 crash/retry pins;P2b 才能启用 `OwnerOnly` private writers,且必须等 **R3 P4 dropped-loot migration/hydration、R6 P1 recipient projection/page、R5 P3 + R6 P4、R10 P3 pickup txn、R4 pickup consumer** 全部完成;P3 pickup/attrition consumer 只有在 **R5 P3** incoming-only attrition/ledger API 与 **R6 P4** receipt wire/client API 已合入后才可接通。 +- **R10**:`server/src/inventory/**` model/grid/txn/capacity、writer enumeration、typed outcome、纯 migration,以及 P2c `SessionDeliveryWorker` production consumer。P1 仅在 **R3 P1** 的 inventory/overflow 与 outbox transaction seam 冻结后实现 txn/capacity/receipt 骨架;P2c 在 P1 与 **R3 P1** reservation/outbox CAS API 合入后实现 claim/lease/decode/deliver/receipt/ack/dead-letter wiring,并先于任何 R1 checkpointed 宿主迁移。P2a 负责 metadata/provider 与 `Public` writer path,依赖 **R3 P2** durable spill/pickup recoverable-commit seam、旧 dropped-loot migration compatibility 与 crash/retry pins;P2b 才能启用 `OwnerOnly` private writers,且必须等 **R3 P4 dropped-loot migration/hydration、R6 P1 recipient projection/page、R5 P3 + R6 P4、R10 P3 pickup txn、R4 pickup consumer** 全部完成;P3 pickup/attrition consumer 只有在 **R5 P3** incoming-only attrition/ledger API 与 **R6 P4** receipt wire/client API 已合入后才可接通。 - **R3**:SQL/outbox、spill/pickup recoverable commit、hydration guard、migration consumer;R10 只消费 R3 P1/P2/P4 已冻结并实现的接口。P4 必须拆成 dropped-loot hydration 子批次与 inventory-layout overflow 子批次,前者不等待 R10 P3,后者才等待其实际 durable/capacity 前置。 - **R4**:C2S gate/handler、authoritative pickup context、调用 R10 并转交 R6 outcome;R4 handler/consumer phase 必须等待 **R10 P3 pickup txn、R6 P4** receipt API 与 **R5 P3** attrition API,不得以 R10 mock 或仅 R6 P1 schema 代替。 - **R5**:incoming-only qi attrition/ledger;provider phase 为 R5 P3。 @@ -95,7 +112,7 @@ R10 的 inventory-layout 与 dropped-loot migration 函数均纯且幂等,保 - **R1**:txn stored/spilled 成功后才 teardown,失败保留 session。 - **R7**:UI 消费,不拥有事务。 -顺序:**R3 P1 → R10 P1(含纯 inventory-layout/dropped-loot migration helpers)→ R3 P2 atomic seam 实现 + legacy dropped-loot migration/hydration compatibility pins → R10 P2a metadata/provider + Public writer path → R3 P4 dropped-loot migration/hydration consumer → R6 P1 dropped-loot projection/page consumer → R5 P3 + R6 P4 → R10 P3 pickup/merge txn → R4 handler/pickup consumer → R10 P2b OwnerOnly private-writer activation → R3 P4 inventory-layout overflow consumer → R10 P4 联合 e2e**。R10 P2a 在 R3 P2 atomic seam 与旧 dropped-loot migration compatibility 未合入前不得开始;R3 P4 dropped-loot migration/hydration consumer 必须先于 R6 P1 dropped-loot projection/page consumer,确保旧 `entry_json` 已先升级为带 `owner`/`visibility` 的 canonical entry;R10 P2b 只有在 R3 P4 dropped-loot hydration、R6 P1 projection/page、R5 P3 + R6 P4、R10 P3 pickup txn 与 R4 pickup consumer 全部完成后才可启用,避免 OwnerOnly writer 在授权消费链闭合前广播或转移私有掉落;R10 P3 在 R5 P3/R6 P4 provider 未合入前不得开始 pickup consumer;R4/R3 的外轨交付物只作 R10 P4 验收前置、不计入 R10 自身 phase;R10 不越权改 persistence、wire、handler 或 client。 +顺序分两条无伪依赖的链:**terminal delivery:R3 P1 reservation/outbox/CAS API → R10 P1 `deliver`/receipt contract → R10 P2c production worker → R1 checkpointed craft/alchemy/forge migration → R10 P4 联合 e2e**;**dropped-loot/pickup:R3 P1 → R10 P1(含纯 inventory-layout/dropped-loot migration helpers)→ R3 P2 atomic seam 实现 + legacy dropped-loot migration/hydration compatibility pins → R10 P2a metadata/provider + Public writer path → R3 P4 dropped-loot migration/hydration consumer → R6 P1 dropped-loot projection/page consumer → R5 P3 + R6 P4 → R10 P3 pickup/merge txn → R4 handler/pickup consumer → R10 P2b OwnerOnly private-writer activation → R3 P4 inventory-layout overflow consumer → R10 P4 联合 e2e**。P2c 不等待 R10 P2a/P2b 或 pickup 链,但必须消费真实 R3 outbox 且通过 crash/retry pins;R10 P2a 在 R3 P2 atomic seam 与旧 dropped-loot migration compatibility 未合入前不得开始;R3 P4 dropped-loot migration/hydration consumer 必须先于 R6 P1 dropped-loot projection/page consumer,确保旧 `entry_json` 已先升级为带 `owner`/`visibility` 的 canonical entry;R10 P2b 只有在 R3 P4 dropped-loot hydration、R6 P1 projection/page、R5 P3 + R6 P4、R10 P3 pickup txn 与 R4 pickup consumer 全部完成后才可启用,避免 OwnerOnly writer 在授权消费链闭合前广播或转移私有掉落;R10 P3 在 R5 P3/R6 P4 provider 未合入前不得开始 pickup consumer;R4/R3 的外轨交付物只作 R10 P4 验收前置、不计入 R10 自身 phase;R10 不越权改 persistence、wire、handler 或 client。 ## 7. 审核要求的 contract pins @@ -108,15 +125,16 @@ R10 的 inventory-layout 与 dropped-loot migration 函数均纯且幂等,保 5. pickup 同维成功;跨维、超距/zone、owner/private 拒绝;merge、placement-only、failed attach/capacity/validation/persistence 后 entry 仍在;成功后才删。 6. incoming-only attrition receipt + R5 ledger:旧 stack absolute qi 不变;注入 attrition 后、durable commit 中断与 restart/retry,断言 attrited item + zone/ledger + drop delete 原子且总量守恒。 7. visibility matrix:同维/范围内 `Public` 对非 owner 可见,`OwnerOnly` 对 owner 可见、对普通非 owner 不可见、对 server-authorized admin 可见;另测跨维/超距拒绝。page/revision 按每个 recipient projection;缺页/混 revision 不替换。 -8. accepted/rejected move correlation;pack stow/equip/unequip 与拒绝必须动作级 receipt,stale event 和 snapshot-only baseline 不通过。 -9. forge 深链保留;另锁 `/give hoe_iron → 新 snapshot → 真实非零 instance → held/equip → lingtian_start_till`,禁止 `instance_id=0` 或任意 server-data 冒充成功。 -10. inventory-layout migration pure happy/empty/full/dynamic/idempotent/invalid;dropped-loot migration 覆盖旧 `entry_json` 缺 owner/visibility → `None`/`Public`、已有字段原样保留、malformed/幂等;R3 consumer 对真实 context 成功,缺 context/capacity/persistence/migration failure 保留旧行可重试。 +8. terminal-delivery worker:空队列、并发唯一 claim、lease expiry、commit 各 crash point、receipt 重放、retry/dead-letter/operator CAS;真实消费 R3 outbox,并对拍 R1 §2.2.2 全状态的 quota 增量。 +9. accepted/rejected move correlation;pack stow/equip/unequip 与拒绝必须动作级 receipt,stale event 和 snapshot-only baseline 不通过。 +10. forge 深链保留;另锁 `/give hoe_iron → 新 snapshot → 真实非零 instance → held/equip → lingtian_start_till`,禁止 `instance_id=0` 或任意 server-data 冒充成功。 +11. inventory-layout migration pure happy/empty/full/dynamic/idempotent/invalid;dropped-loot migration 覆盖旧 `entry_json` 缺 owner/visibility → `None`/`Public`、已有字段原样保留、malformed/幂等;R3 consumer 对真实 context 成功,缺 context/capacity/persistence/migration failure 保留旧行可重试。 ## 8. Named bot acceptance(P4) 以下名称即 `scripts/bot/scenarios/.py` 的稳定身份: -1. `inv_full_delivery_matrix`:craft/alchemy/forge/give 满包时 `stored + spilled == requested`,失败不 teardown。 +1. `inv_full_delivery_matrix`:craft/alchemy/forge/give 满包时 `stored + spilled == requested`,失败不 teardown;checkpointed 三域必须经真实 `SessionDeliveryOutbox`→P2c worker→receipt/ack 链,不得直接调用 `deliver` 绕过 worker。 2. `inv_stack_merge`:同 identity merge、异 identity 分栈;placement-only 与拒绝路径保留 drop;attrition durable 中断/restart 仍原子守恒。 3. `inv_footprint_sync`:2×1 rotate 后以 request/instance/from/to/revision 锚定 1×2 authoritative receipt;snapshot 不代替回执。 4. `inv_pack_feedback`:stow/equip/unequip 的 accepted/rejected 均按时间锚与 correlation 匹配,stale event 不通过。 diff --git a/docs/plan-refactor-server-session-v1.md b/docs/plan-refactor-server-session-v1.md index c5f216efd..6bb8530c8 100644 --- a/docs/plan-refactor-server-session-v1.md +++ b/docs/plan-refactor-server-session-v1.md @@ -85,7 +85,7 @@ pub trait InteractionSession { 3. `Volatile` session 遇到断线、跨维或关服必须在同一生命周期门内 teardown;不得留下 owner entity、设施锁、target claim 或 `settling` 标记。 4. 所有 dimension-scoped session 在维度切换前终止。`TsyPresence` 是 transport 辅助状态,单独 checkpoint/restore,不得用“保留旧交互 session”修复 presence 撕裂。 5. client 的 screen/store 只能改善 UX,不能授予 session 或 busy 权限;恶意包、重复包和同 tick 竞态最终都由 registry 拒绝。 -6. session 完成后先进入 `AwaitingDelivery`。R3/R10 必须沿用 `save_player_craft_checkpoint` 的 crash-atomic 基线:同一 SQLite 事务提交 inventory mutation、durable spill、`DeliveryCommitReceipt` 与 session checkpoint 删除;事务失败则四者都不发布。若目标 storage 不能共用事务,R3 必须在同一 checkpoint 事务把完整 payload 写入 `SessionDeliveryOutbox` 并把 session 转入 durable `DeliveryPending`,R10 只按稳定 `delivery_id` 消费 outbox;receipt 与 inventory/spill mutation 在 R10 同一事务提交,重启见 receipt 即跳过重复 delivery,随后把 `DeliveryPending` 标记为 `Terminal`。进程在任一边界退出都不得丢失或双发。 +6. session 完成后先进入 `AwaitingDelivery`。所有 `Checkpointed` terminal obligation 一律按 §2.2.2 走 R3 outbox,不因 inventory 与 checkpoint 恰在同一 SQLite 文件就建立第二条 direct-delivery 协议:R3 在一个 checkpoint transaction 中把 reservation 转成完整 `SessionDeliveryOutbox` 并把 session 转入 durable `DeliveryPending`;R10 P2c 只按稳定 `delivery_id` 消费 outbox,在同一 durable transaction 提交 inventory mutation、durable spill、`DeliveryCommitReceipt`、outbox ack/obligation 删除与 quota 释放。任一步失败则该事务的各侧都不发布;重启见 receipt 即跳过重复 delivery并完成幂等 terminal confirmation。进程在 outbox insert、terminalization、worker claim、inventory/spill、receipt 或 ack 任一边界退出都不得丢失或双发。 7. `AwaitingDelivery` 不得无限保留 facility/target claim。转入 durable outbox 时,事务内只提交权威的 terminal/outbox 状态;事务成功后释放 runtime gameplay claim 并从 live registry 移除,释放动作必须可由已提交的 terminal/outbox 状态幂等重放。outbox 仅保留 owner delivery obligation,不允许 owner 重新恢复已 terminalize 的 session。 8. 涉及真元的 refund/release 必须通过 `qi_physics::ledger::QiTransfer`;session adapter 不得裸写 `qi_current` 或 zone qi。 @@ -125,11 +125,30 @@ pub trait InteractionSession { - registry 每 tick 或固定 cadence 扫描过期 session;只有 `SessionMaintenancePermissions::is_allowed(execution_origin, authenticated_principal, executor)` 才能用 `session-maintenance terminate `。仅 server console origin 或 server 已认证且绑定 executor 的 principal/capability 可通过;offline-mode player `Username`、命令 payload 或 owner 字符串均不能产生授权。普通玩家、未知 executor、伪造 allow-listed username/owner 和跨 owner target 一律拒绝且不改变 session/outbox/claim。扫描、授权管理员结案、重连与关服 flush 竞争时以 `SessionKey` CAS/registry lock 保证一次结算。 - 过期结案按 `TerminationCause::SuspensionExpired` 执行:停止 tick → 在 R3 checkpoint 事务中写入稳定 `delivery_id` 的 `SessionDeliveryOutbox` 并转为 `DeliveryPending` → 事务成功后幂等释放 facility/target/busy claim → 从 live registry 移除;R10 receipt 提交后再标记 `Terminal`。若进程在提交后、runtime claim 清理前退出,启动恢复必须依据已提交的 terminal/outbox 状态补做同一释放,不能重新 attach 或继续占用。R10 异步消费 outbox;不得以 delivery 临时失败为由继续占设施。 - outbox 每行携带单调 `generation`,状态为 `Pending | InFlight { lease_id, lease_until } | DeadLetter | Committed | ResolvedDisposition { delivery_id, operator_principal, reason, payload }`。自动 worker 仅以 expected `(state,generation)` CAS claim `Pending→InFlight`;delivery receipt + inventory/spill + `InFlight→Committed` 同事务提交。dead-letter scanner 仅 CAS 非活跃/已过期 lease,operator retry/resolve 也必须 CAS 当前 generation;CAS loser 重读,不得覆盖 worker/scanner/operator 的并发结果或重复释放 quota。重试冻结为指数退避 `min(1_200 * 2^attempts, 72_000)` ticks;10 次或 7 天后转 `DeadLetter` 并停止自动扫描。`next_retry_tick`、`created_at_tick`、`lease_until` 与 `tick_epoch` 一并持久化;启动 guarded load 必须在 worker/scanner 运行前用 R3 `tick_rebase` 原子换算到当前 epoch,保持 retry 剩余时长与已消耗 age,连续重启不得刷新退避、7 天 age 或 lease。 -- R3 `SessionDeliveryOutbox` 是失败交付的唯一 retained obligation。每个 `Checkpointed` session 在取得 claim 前预留 durable terminal-obligation capacity;不足则拒绝创建/恢复。terminal handoff 与 checkpoint 行同事务转移 reservation。`Pending`/`InFlight`/`DeadLetter` 不重建 session 或 claim;quota 满时 fail closed,禁止删除/覆盖 payload。`resolve` 必须在同一事务把完整 payload 移入持久 `ResolvedDisposition { delivery_id, operator_principal, reason, payload }`(或已提交 receipt),审计成功后才删除 dead-letter row/释放 quota;禁止无 disposition 删除未交付 payload。 +- R3 `SessionDeliveryOutbox` 是失败交付的唯一 retained obligation。每个 `Checkpointed` session 按 §2.2.2 的唯一协议在首次 admission 时、取得 gameplay claim 前预留 durable terminal-obligation capacity;restore 只能复用并校验已有 reservation,绝不再次计量。terminal handoff 与 checkpoint 行同事务转移 reservation。`Pending`/`InFlight`/`DeadLetter` 不重建 session 或 claim;quota 满时 fail closed,禁止删除/覆盖 payload。`resolve` 必须在同一事务把完整 payload 移入持久 `ResolvedDisposition { delivery_id, operator_principal, reason, payload }`(或已提交 receipt),审计成功后才删除 dead-letter row/释放 quota;禁止无 disposition 删除未交付 payload。 - `DeliveryPending` 已提交 durable handoff 但未 receipt;R10 delivery 失败或进入 `DeadLetter` 时仍保持该 phase 的 durable obligation,绝不回退为 `AwaitingDelivery` 或重新占用 claim。 - claim 回收与 outbox 状态变化必须有审计事件(session key、owner key hash、operator identity hash/`system`、cause、released claims、delivery id/result/attempt),不记录原 payload;恢复窗口内仍允许 owner 正常 reopen,写入 outbox 后 reopen 明确拒绝。 -- P1/P4 必须覆盖:永不重连的 abandoned session 最终释放设施、TTL 边界前后、授权管理员结案、offline client 伪造 allow-listed `Username` 仍拒绝、普通玩家/未知 executor/伪造 owner/跨 owner target 拒绝、过期与重连竞态、重复扫描幂等、正常 `Completed` handoff 不退款且 cause 可 roundtrip、outbox 事务 crash points、receipt 重放去重、worker↔scanner 与 worker↔operator retry/resolve generation-CAS 竞态、过期 lease 恢复、退避边界、10 次/7 天 dead-letter、重启前后 retry/age/lease deadline rebase(含连续重启不刷新)、授权 resolve 无 receipt/durable disposition 时 payload 与 quota 保留、quota 满时 checkpointed admission/restore fail-closed、两个 admission/restore 并发竞争最后一行或最后一份 bytes 时仅一个成功、quota 释放后的重新 admission,以及回收后另一玩家可获得同一 furnace/station claim。 +- P1/P4 必须覆盖:永不重连的 abandoned session 最终释放设施、TTL 边界前后、授权管理员结案、offline client 伪造 allow-listed `Username` 仍拒绝、普通玩家/未知 executor/伪造 owner/跨 owner target 拒绝、过期与重连竞态、重复扫描幂等、正常 `Completed` handoff 不退款且 cause 可 roundtrip、outbox 事务 crash points、receipt 重放去重、worker↔scanner 与 worker↔operator retry/resolve generation-CAS 竞态、过期 lease 恢复、退避边界、10 次/7 天 dead-letter、重启前后 retry/age/lease deadline rebase(含连续重启不刷新)、授权 resolve 无 receipt/durable disposition 时 payload 与 quota 保留、quota 满时首次 admission fail-closed、两个首次 admission 并发竞争最后一行或最后一份 bytes 时仅一个成功、busy-claim race loser/后续校验失败取消临时 reservation 且 quota 恰减一次、restore/retry 复用同一 reservation 且 quota 增量为零、冲突 reservation fail-closed、quota 释放后的重新 admission,以及回收后另一玩家可获得同一 furnace/station claim。 + +### 2.2.2 Delivery reservation lifecycle(唯一权威矩阵) + +R1/R3/R10 只实现本表,不另写释放条件。`Q=(1 row,reserved_bytes)`,`ΔQ` 是提交后的 quota 变化;所有 mutation 以 `session_key` + expected reservation/outbox `generation` CAS,loser 重读且 `ΔQ=0`。 + +| 状态(durable owner / claim / quota) | claim | race-loss / admission failure | timeout | restore | retry / lease expiry | terminal | +|---|---|---|---|---|---|---| +| `Absent`(无 / 无 / 0) | `reserve_new_terminal_obligation` 原子 quota update+unique insert→`ReservedPendingClaim`,`+Q`;满则不变 | no-op | no-op | checkpoint 无 reservation:隔离并 fail closed,不补计量/attach | 非法 | 拒绝 | +| `ReservedPendingClaim`(reservation / 无 / Q) | `try_acquire` 胜→`ClaimedLive`,0 | `cancel_unconsumed_reservation` 原子删 row/扣 counter→`Absent`,`-Q`;重复/CAS loser 0 | 同 race-loss | 启动见 reservation 但无 checkpoint/live claim:取消 `-Q`,不得当 restore | 同 restore | 未接 escrow/output,先取消,不 handoff | +| `ClaimedLive`(reservation+session / 有 / Q) | 重复拒绝,0 | claim 后校验失败:释放 claim,再 CAS 取消 `-Q`;崩溃由“无 checkpoint 残留”补做 | 按域 pause/suspend/terminal;禁止只释放 quota | 重复 attach 拒绝 | gameplay retry 0 | `AwaitingDelivery`;R3 原子 reservation→outbox `Pending`,0;commit 后释放 claim | +| `SuspendedReserved`(checkpoint+reservation / stable facility、无 Entity / Q) | 新 claim 拒绝 | 不适用 | TTL 赢 CAS 后按 terminal | `reuse_terminal_obligation` 仅在 session/owner/bytes/checkpoint generation 全匹配时复用,0;随后以 stable `placed_id` 重绑;missing/conflict fail closed,禁止 insert/+Q | 重复/并发 restore 同样复用,0 | 任一 terminal cause 原子 reservation→outbox,0;不得先删 checkpoint | +| `OutboxPending`(`Pending` / 无 / Q) | 永久拒绝 | no-op | 到期可 claim,0 | 只 hydrate obligation | R10 CAS `Pending→InFlight`,0 | 重复 handoff 返回 stable `delivery_id` 既有 row,0 | +| `OutboxInFlight`(lease / 无 / Q) | 拒绝 | no-op | lease expiry CAS 回 `Pending` 或达阈值→`DeadLetter`,0 | rebase lease,不 attach | 暂败写 attempts/backoff→`Pending`,0;CAS loser 0 | inventory/spill+receipt+`Committed`+删 obligation/扣 quota 同事务→`CommittedReleased`,`-Q`;已有 receipt 幂等完成,0 | +| `DeadLetter`(outbox / 无 / Q) | 拒绝 | no-op | 停自动 retry,0 | 只 hydrate obligation | 授权 retry CAS→`Pending`,0 | 授权 resolve 原子持久化完整 `ResolvedDisposition`+删 obligation/扣 quota→`DispositionReleased`,`-Q`;无 disposition 不释放 | +| `CommittedReleased`(receipt / 无 / 0) | 旧 generation 拒绝 | no-op | no-op | 只确认 terminal | receipt 重放,0 | no-op,禁止再扣 | +| `DispositionReleased`(disposition / 无 / 0) | 旧 generation 拒绝 | no-op | no-op | 只确认 terminal | disposition 重放,0 | no-op,禁止再扣 | + +不变量:每个 `session_key` 恰有零或一个 durable owner;counter 等于六个占用态的 `Q` 之和。只有未接收 escrow/output 的 admission 取消,或 receipt/disposition 已持久化的终结事务可 `-Q`;reservation→outbox、restore/retry/timeout 与所有 CAS loser 均为 0。 + ### 2.3 durability 决议矩阵 | 状态族 | durability | 断线/关服 | 跨维 | @@ -225,8 +244,8 @@ pub trait InteractionSession { - **R10 独占**:`server/src/inventory/**`、`InventoryTxn::deliver`、inventory/spill mutation 与 `DeliveryCommitReceipt` 的原子提交;R1 只生成 stable `delivery_id` 并决定何时 terminalize/释放 claim。 - **阶段放行矩阵**: - framework-only:R3 P1 合入后可落 `InteractionSession`、registry、lifecycle 与不触达生产 wire/delivery 的 contract pins。 - - craft producer path:Agent 轨先冻结 `CraftOpen.target`、`CraftPause`、`CraftResume` TypeBox/schema/dist;随后 R6 P1 交付这些冻结契约的 proto/Rust mirror/bridge/client wire plumbing 与 `CraftSessionStateV2` → R4 P1 交付 production decode/dispatch 与 owner/phase/busy gate → R7 P2 交付 close/pause、显式 cancel、reopen/resume UI producer/consumer(R2 P1 已登记的 `CraftStore` 继续提供 disconnect lifecycle)→ R3 P1 交付 durable `SessionDeliveryOutbox`/terminal checkpoint transaction → R10 P1 冻结 `deliver`、stable `delivery_id` 与 durable receipt contract,且 R10 P2 把 craft production 调用点迁入 exactly-once transaction;全部合入后 R1 才能启用并验收 craft adapter。 - - alchemy/forge:还须 R10 P2 将两域 production delivery 调用点迁入 `InventoryTxn::deliver`,否则只能停在 `AwaitingDelivery`。 + - craft producer path:Agent `A-CS` 先冻结 `CraftOpen.target`、`CraftPause`、`CraftResume`、`CraftSessionStateV2` TypeBox/schema/dist;随后 R6 P1 交付冻结契约的 proto/Rust mirror/bridge/client wire plumbing → R4 P1 交付 production decode/dispatch,并把当前进程 `workbench_key` 经 R3 P4 mapping 转成 stable `placed_id` 后执行 owner/phase/busy gate → R7 P2 交付 close/pause、显式 cancel、reopen/resume UI producer/consumer(R2 P1 `CraftStore` 提供 disconnect lifecycle)→ R3 P1 交付 §2.2.2 reservation/outbox/terminal checkpoint transaction,R3 P4 交付 placed workbench hydrate/rebind → R10 P1 冻结 `deliver`/receipt contract,R10 P2c 交付真实 outbox worker;全部合入后 R1 才能启用并验收 craft adapter。 + - alchemy/forge:还须 R10 P2c worker 消费其 terminal outbox;否则只能停在 `AwaitingDelivery`。 - `TsyPresence`:R3 P1 注册 auxiliary Slice,且 R3 P4 restore parity 常绿后,R1 P3 才能 attach 新 runtime `Entity` 并开放 TSY 请求。 - 禁止用 mock、registry 单测或临时持久层越过上述门宣称端到端完成;依赖未齐时 phase 保持未完成。 @@ -235,14 +254,15 @@ pub trait InteractionSession { ### P1 — 框架 + craft 首宿主 - 新增 `server/src/session/{mod.rs,registry.rs,lifecycle.rs}`,包含 §2 全部 symbol;framework-only 可在 R3 P1 后落地,但不启用生产 craft adapter。 -- R6 `CraftOpen`/`CraftPause`/`CraftResume` 契约、R4 production handler/gate、R7 P2 Craft Screen producer/consumer(消费 R2 P1 已登记的 `CraftStore`)、R10 P1 `deliver` contract 与 R10 P2 craft production delivery 全部合入后,craft 才迁移到 `SessionRegistry`:关闭 screen pause、显式 cancel、重开 resume,现有 recipe/session join hydration 行为不变。 -- contract pins:六态转换、stable owner 重绑、disconnect-before-save、dimension-before-transfer、busy 冲突、delivery commit gate,以及 §2.5 八个 `TerminationCause` 的逐变体正反测试;明确断言 `Completed` 只交付产物且不退款,`Disconnect`/`Shutdown` checkpoint 不退款,terminal refund 不保留可恢复 escrow。delivery pins 必须在 transaction 的“outbox 写入前/写入后 terminalize 前/receipt+inventory commit 后 ack 前”逐点模拟进程退出并重载,断言 payload 总数恰为一次;SQLite 同库路径另断言 inventory/spill、receipt、checkpoint 删除在单事务全成或全败。 +- Agent `A-CS`、R6 `CraftOpen`/`CraftPause`/`CraftResume`/StateV2 契约、R4 production handler/gate、R7 P2 Craft Screen producer/consumer(消费 R2 P1 `CraftStore`)、R3 P1 reservation/outbox、R3 P4 stable `placed_id` hydrate/rebind、R10 P1 `deliver`/receipt contract 与 R10 P2c production worker 全部合入后,craft 才迁移到 `SessionRegistry`:关闭 screen pause、显式 cancel、重开 resume;手搓不需要 placed mapping,Workbench checkpoint 必须存 `placed_id` 而非 `workbench_key`/Entity。 +- P1 contract pins 还必须逐行覆盖 §2.2.2:首次 reserve `+Q`、busy race/校验失败取消 `-Q`、重复取消/CAS loser 零变化、restore/retry matching reuse 零变化、missing/conflicting restore fail closed、handoff 零变化、Pending/InFlight/DeadLetter timeout/retry 零变化、receipt/disposition 恰一次 `-Q`;并在 Workbench 路径强杀重启后证明 checkpoint stable `placed_id` 能绑定 hydrate 后的新 Entity,旧 `workbench_key` 不能恢复。 +- contract pins:六态转换、stable owner 重绑、disconnect-before-save、dimension-before-transfer、busy 冲突、delivery commit gate,以及 §2.5 八个 `TerminationCause` 的逐变体正反测试;明确断言 `Completed` 只交付产物且不退款,`Disconnect`/`Shutdown` checkpoint 不退款,terminal refund 不保留可恢复 escrow。delivery pins 必须在 transaction 的“outbox 写入前/写入后 terminalize 前/receipt+inventory commit 后 ack 前”逐点模拟进程退出并重载,断言 payload 总数恰为一次;所有 checkpointed delivery 均经真实 R10 P2c worker。 - `SuspensionPolicy` pins:TTL 前后、永不重连、授权管理员结案、offline client 伪造 allow-listed `Username` 仍拒绝、普通玩家/未知 executor/伪造 owner/跨 owner target 拒绝、过期/重连/关服竞态、重复扫描幂等、指数退避 cap、10 次/7 天 dead-letter、人工 retry/resolve 权限,以及 claim 回收后另一玩家可获取同一设施。另须持久化一半已消耗的 lease,重启后 rebasing 只保留原剩余 TTL;连续第二次/第三次重启不得刷新 `suspended_at_tick`,并覆盖 `remaining-1`、精确边界、`remaining+1`。 - `PendingInsightOffer` pins:创建后正常选择/拒绝、deadline 前后与 off-by-one、同 tick response-versus-timeout 竞态、断线/跨维/关服清除,以及过期后不得再次接受选择。 ### P2 — alchemy / forge / lingtian -- R3 checkpoint API 与 R10 P2 production delivery 都就绪后,alchemy furnace/session 与 forge station/session 才原子 checkpoint;终态 handoff 先写 durable outbox/terminal state,再在提交成功后幂等释放 runtime claim;delivery 失败不得重新 attach session 或 claim。 +- R3 checkpoint API 与 R10 P2c production outbox worker 都就绪后,alchemy furnace/session 与 forge station/session 才原子 checkpoint;终态 handoff 先写 durable outbox/terminal state,再在提交成功后幂等释放 runtime claim;delivery 失败不得重新 attach session 或 claim。 - 灵田六类 `ActiveSession` 共用 volatile adapter;断线/跨维/关服不再 tick 或结算离线 actor。 - qi refund/release 测试从 `SPIRIT_QI_TOTAL` 与 ledger 不变量取值,不写新物理常数。 diff --git a/docs/plans-skeleton/plan-agent-craft-schema-v1.md b/docs/plans-skeleton/plan-agent-craft-schema-v1.md new file mode 100644 index 000000000..7bf31d731 --- /dev/null +++ b/docs/plans-skeleton/plan-agent-craft-schema-v1.md @@ -0,0 +1,60 @@ +# plan-agent-craft-schema-v1 — Craft lifecycle Agent schema 生产批次(总纲 A-CS) + +> 所属总纲:`plan-refactor-master-v1.md` §3/§4/§4.1/§6.11。一句话:由 Agent 轨一次性冻结 craft lifecycle 的 TypeBox source、generated JSON Schema 与 committed `@bong/schema` dist,给 R6 提供唯一、可调度、可验收的 agent-side schema 输入。 + +## 阶段总览 + +| 阶段 | 交付物 | 状态 | +|---|---|---| +| P0 | 对拍现有 craft/client-request/server-data schema,冻结 V2 shape 与计数基线 | ⬜ | +| P1 | TypeBox source + registry/exports + 正反 contract tests | ⬜ | +| P2 | generated JSON Schema + committed dist 原子生成与 freshness gate | ⬜ | +| P3 | R6 handoff manifest、跨轨对拍与归档证据 | ⬜ | + +## 接入面 + +- **进料**:总纲 §4 的 canonical `CraftOpen.target`,R1 的 `SessionPhase`/session identity/generation 合同,现有 `agent/packages/schema/src/{craft.ts,client-request.ts,server-data.ts,schema-registry.ts}` 与 `CraftSessionStateV1` 基线。 +- **出料**:冻结的 `CraftOpen`、`CraftPause`、`CraftResume`、`CraftSessionStateV2` TypeBox exports;对应 generated schemas;可由 agent runtime/import consumer 加载的 committed `@bong/schema` dist;记录 commit SHA、schema digest 与变体计数的 R6 handoff manifest/evidence。 +- **共享类型**:复用 canonical unsigned decimal-string helper、session key 与 generation schema;不新造与 proto/Rust 不同义的 identity。 +- **跨仓库契约**:本 plan 只改 `agent/packages/schema/**`。R6 P1 消费冻结结果并实现 proto/Rust/client wire;R4/R7/R1 不直接修改 Agent artifacts。无 gameplay/worldview/qi 语义。 + +## 冻结合同 + +1. `CraftOpen.target` 是 required discriminated union:`Handcraft` 或 `Workbench { workbench_key }`。`workbench_key` 是无符号 `u64` 十进制字符串;缺失、负数、小数、科学计数法、前后空白、超过 `u64::MAX` 均拒绝。它只用于当前进程初次请求,不是 durable identity。 +2. `CraftPause` 与 `CraftResume` 只携带 required `session_key` + `generation`;不得复带 target 或用 Pause/Resume 替代 `CraftCancel`。 +3. `CraftSessionStateV2` 覆盖 R1 的 `Running | Paused | Suspended | AwaitingDelivery | DeliveryPending | Terminal`,锁定每个 phase 对 identity/generation 的 required/forbidden 组合;删除 production V1 分支由 R6 执行,本轨只提供 V2 source-of-truth。 +4. P0 必须从当前 schema registry 计算真实 C2S/S2C 基线;P1 完成后把新增三种 C2S intent 体现为总纲要求的 113→116,并记录 craft S2C V2 对总 S2C variant count 的影响。若合入前主线计数变化,先更新基线与总纲/R6 pins,不得硬写陈旧数字让 freshness 假绿。 + +## 阶段交付物 + +### P0 — schema inventory 与 shape 冻结 + +- 枚举 `ClientRequestV1`、`ServerDataV1`、`CraftSessionStateV1`、`SCHEMA_REGISTRY`、`GENERATED_SCHEMA_FILES`、package exports/dist 入口;记录 source→registry→generated→dist 完整路径。 +- 对拍总纲 §4 与 R1/R6 contracts,形成 `CraftOpen`/Pause/Resume/StateV2 字段矩阵与 C2S/S2C type-set baseline。 +- 明确与 `plan-bughunt-client-request-schema-drift.md`、`plan-bughunt-server-data-s2c-schema-union-drift-v1.md` 的边界:本 plan 只交付四个 craft lifecycle artifacts;全量 parity guard 仍由各自 drift plan 负责,但本 plan 的新增变体必须进入其现有 union/registry。 + +### P1 — TypeBox source 与 contract tests + +- 在 `agent/packages/schema/src/` 实现并 export 四个合同,注册到 `ClientRequestV1`/`ServerDataV1` 与 `SCHEMA_REGISTRY`;旧 V1 不得继续作为 production union 分支。 +- 正例:Handcraft、Workbench key `0`/`1`/`u64::MAX`,Pause/Resume identity,StateV2 每个 phase。 +- 反例:target 缺失/未知 discriminator、malformed/out-of-u64 key、Pause/Resume 缺 identity/generation 或夹带 target、StateV2 phase/identity/generation 非法组合、额外字段。 +- 测试必须验证对外 observable schema,不绑定私有 helper 调用次数。 + +### P2 — generated/dist 原子交付 + +- 更新 `GENERATED_SCHEMA_FILES`,生成四个单项 schema及命中的 `client-request-v1.json`/`server-data-v1.json`;`npm run build -w @bong/schema` 生成 committed dist。 +- source、registry、generated、dist 必须同一提交;freshness 测试删除/篡改任一层都会失败。禁止让 R6 后补 dist 或手改 generated JSON。 +- 运行 `cd agent/packages/schema && npm test` 与 `cd agent && npm run build -w @bong/schema`,并验证 clean checkout 的 package exports 可 import 新 symbols。 + +### P3 — R6 handoff 与跨轨验收 + +- 在 Finish Evidence 记录合入 SHA、generated schema digest、C2S/S2C 最终计数和导出 symbol 清单;该证据是 R6 P1 唯一可接受输入。 +- R6 对同一 SHA 做 TypeBox↔proto/Rust/client samples roundtrip;字段或计数不一致退回本 plan/R6 对应 owner,不在 R6 越权修改 Agent source。 +- A-CS 未归档前,总纲 Wave 1 的 R6 P1 与所有 craft production gate保持阻塞。 + +## 验收与边界 + +- 必跑:`cd agent/packages/schema && npm test`;`cd agent && npm run build -w @bong/schema`。 +- 必验:source/generated/dist freshness、package runtime import、四合同全部正反样本、ClientRequest/ServerData union membership、最终变体计数。 +- 不改 proto、Rust、Java、R1/R4/R6/R7 文件;不实现 client producer、server handler、session registry 或 workbench `placed_id`。 +- 这是总纲具名 production batch,不吸收或替代 `tiandao-schema-dist-start` 的 clean-start 构建链修复;后者解决启动前 dist 缺失,本 plan 解决具体 craft schema 内容及其原子产物。 diff --git a/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md b/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md index bd26a4aa0..5bce621d2 100644 --- a/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md +++ b/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md @@ -14,7 +14,7 @@ - **进料**:`bong:client_request` 单通道(既有,C2S 本就单轨)、玩家实体(Position/CurrentDimension/状态组件)、R1 session 忙态、R10 inventory 事务。 - **出料**:校验通过的请求进各域 handler;拒绝走统一 reject 回执(带原因码,client 侧 toast/HUD 可消费——对齐 unconsumed-event-feedback 的方向但只做 gate 拒绝部分)。 - **共享类型**:新 `server/src/network/gate/`——`GateSpec { max_distance(度量统一), same_dimension, ownership, state_preconditions }` 按请求类型声明;维度感知 zone 查找 helper。 -- **跨仓库契约**:wire 形状通常不变(现行 enum 全量);reject 回执若新增字段走 R6 的契约流程。R1 craft pause/resume 例外消费 R6 新增的 `CraftOpen`/`CraftPause`/`CraftResume`,本轨负责 production decode/dispatch,并以 owner、phase 与 busy `GateSpec` 拒绝伪造 resume/重复包。 +- **跨仓库契约**:wire 形状通常不变(现行 enum 全量);reject 回执若新增字段走 R6 的契约流程。R1 craft pause/resume 例外消费 R6 新增的 `CraftOpen`/`CraftPause`/`CraftResume`,本轨负责 production decode/dispatch,并以 owner、phase 与 busy `GateSpec` 拒绝伪造 resume/重复包。Workbench 初次 `CraftOpen` 的 `workbench_key` 只解析当前进程 runtime entity;R4 重验实体/维度/距离/facility 后还必须从 R3 P4 registry取得 stable `placed_id` 并把它交给 R1 建 claim,mapping 缺失/重复/未 hydrate 一律拒绝,禁止把 `Entity::to_bits()` 持久化。 ## 阶段 diff --git a/docs/plans-skeleton/plan-refactor-master-v1.md b/docs/plans-skeleton/plan-refactor-master-v1.md index f8bb52c16..328839291 100644 --- a/docs/plans-skeleton/plan-refactor-master-v1.md +++ b/docs/plans-skeleton/plan-refactor-master-v1.md @@ -50,9 +50,9 @@ ## 3. 波次与依赖 -- **Wave 0(立即并行)**:V(bot 骨干 + build token 最先)、R3、R5、R2、registry-datafication;同时全部轨道的 P0(设计收口 + 吸收清单验真)都可开工。 -- **Wave 1**:R6(R2 合入后)、R7 基础设施(R2 合入后)、R1 framework-only(仅 `InteractionSession`/registry/lifecycle 骨架,R3 P1 合入后;不得宣称 craft pause/resume 或 delivery 生产闭环)。 -- **Wave 2**:R4(#1287 + R6 P1 后)、R9(R5/R6/R2 P1 后)、R10(R3 P1 后);R1 宿主迁移按显式 gate 分批放行:craft 需 R3 P1 + R6 craft intents + R4 craft handler/gate + R7 P2 Craft Screen + R2 P1 已登记的 `CraftStore` + R10 P1 `deliver` contract + R10 P2 craft production delivery,alchemy/forge 同样需 R10 P2 对应生产调用点,`TsyPresence` 需 R3 P1 auxiliary Slice 与 R3 P4 restore parity。 +- **Wave 0(立即并行)**:V(bot 骨干 + build token 最先)、R3、R5、R2、registry-datafication;同时全部轨道的 P0(设计收口 + 吸收清单验真)都可开工。§6.11 登记的 Agent `A-CS`(`plan-agent-craft-schema-v1`)也在本波启动并独立提交,必须先于 R6 P1 合入。 +- **Wave 1**:R6(R2 + Agent `A-CS` 合入后)、R7 基础设施(R2 合入后)、R1 framework-only(仅 `InteractionSession`/registry/lifecycle 骨架,R3 P1 合入后;不得宣称 craft pause/resume 或 delivery 生产闭环)。 +- **Wave 2**:R4(#1287 + R6 P1 后)、R9(R5/R6/R2 P1 后)、R10(R3 P1 后);R1 宿主迁移按显式 gate 分批放行:craft 需 Agent `A-CS` + R3 P1 reservation/outbox + R3 P4 placeable-entity stable `placed_id` 持久化/hydration/runtime 映射 + R6 craft intents + R4 craft handler/gate + R7 P2 Craft Screen + R2 P1 已登记的 `CraftStore` + R10 P1 `deliver`/receipt contract + R10 P2c production outbox worker,alchemy/forge 同样需 R3 P1 与 R10 P2c,`TsyPresence` 需 R3 P1 auxiliary Slice 与 R3 P4 restore parity。 - 近完成独立 plan(§6.9)在 Wave 0 窗口内优先收尾清场。 - R5 P1(字段收私有的全仓编译大爆破)挑在飞 PR 队列清空的窗口单独合入。 @@ -61,9 +61,44 @@ - `persistence/**`+autosave=R3;`session/`+7 域 session.rs=R1;`client_request_handler.rs`+`gate/`=R4;`*_emit.rs` 公共层+`proto_convert.rs`=R6;`qi_physics/**`+qi 字段直写行=R5;`inventory/**`=R10;cast/AV emit+skill 注册=R9。 - client:Store 生命周期+`clearClientStateOnDisconnect` 区段=R2;channel 注册区段+桥+router=R6(与 R2 同文件不同区段,merge 前互 fetch);Screen/hud/keybind/InspectScreen=R7;combat cast store=R9。 - `agent/packages/schema/src/**`、generated JSON Schema 与提交的 `@bong/schema` dist=§6.11 Agent 轨;craft schema prerequisite 的 production owner 是 Agent 轨的独立 craft-schema 交付批次,必须在 R6 P1 前提交并验收 TypeBox source、generated schema/dist 与变体计数。理由:TypeBox 是 agent-side source of truth,生成物必须与 source 同 owner 原子提交;让 R6 同时改 source 和 wire 会违反本总纲的 agent 排除边界并形成双 owner。R6 独占 proto/Rust mirror/converter、client wire encode/send 与 bridge/router plumbing,只消费该冻结版本并负责一次性 wire 反映。R6、R4、R7、R1 的 craft gate 均以本处 ownership 决议为准。 -- CraftOpen target bridge(跨轨 canonical contract):`CraftOpen` 必须携带 required `target` 判别联合:`Handcraft` 或 `Workbench { workbench_key }`,不得省略。`workbench_key` 是现有成功 S2C `WorkbenchOpen.entity_id` 的 ECS `Entity::to_bits()` locator:逻辑/Rust 类型 `u64`、protobuf `uint64`、JSON/TypeBox 为无符号十进制字符串;它不是授权能力。普通手搓发送 `Handcraft`;`WorkbenchScreen` 必须从 response 保留该 key 并在初次 `CraftOpen` 原样回传。R4 将 key 解析为 entity 后重验实体存活且携带 `WorkbenchBlock`、玩家同维且在既有距离内,并执行 owner/busy/facility gate;R1 仅在校验通过后建立 facility claim。missing、malformed、stale、despawned、跨维或越距 key 均拒绝;R7 不得从 UI 猜测或改写 key。`CraftPause`/`CraftResume` 只携带 hydrated session identity/version,不重复 target。 +- CraftOpen target bridge(跨轨 canonical contract):`CraftOpen` 必须携带 required `target` 判别联合:`Handcraft` 或 `Workbench { workbench_key }`,不得省略。`workbench_key` 是现有成功 S2C `WorkbenchOpen.entity_id` 的 ECS `Entity::to_bits()` locator:逻辑/Rust 类型 `u64`、protobuf `uint64`、JSON/TypeBox 为无符号十进制字符串;它只在当前进程的首次请求中定位 runtime entity,不是授权能力,也绝不写进 checkpoint。普通手搓发送 `Handcraft`;`WorkbenchScreen` 必须从 response 保留该 key 并在初次 `CraftOpen` 原样回传。R4 将 key 解析为 entity 后重验实体存活且携带 `WorkbenchBlock`、玩家同维且在既有距离内,并执行 owner/busy/facility gate,同时从 R3 P4 hydration registry 取得该 entity 对应的 stable `placed_id`;R1 仅在校验通过后以 `placed_id` 建立并持久化 facility claim。restore 时 R3/R1 用 stable `placed_id` 查询本次启动 hydrate 后的新 runtime entity,再重验 owner/维度/facility;missing/duplicate/unhydrated mapping 必须按 `InvalidRestore` fail closed。missing、malformed、stale、despawned、跨维或越距 `workbench_key` 均拒绝;R7 不得从 UI 猜测或改写 key。`CraftPause`/`CraftResume` 只携带 hydrated session identity/version,不重复 target。 - 任何轨道碰他轨文件:只允许"消费对方冻结后的 API",不允许改对方独占文件;接缝 API 归被依赖方定义。 +### 4.1 跨轨 artifact producer/consumer 闭环表 + +覆盖本计划族为 R1 链新增/改变的 artifact;sample、mock、类型引用或“后续接线”不算 consumer。新增 artifact 必须先增行。 + +| Artifact | Producer | Production consumer | Gate / evidence | +|---|---|---|---| +| craft TypeBox source + generated schema + committed dist | Agent `A-CS` P1-P2 | R6 P1 mirror/generator;agent runtime | A-CS SHA;正反 sample、freshness、variant count | +| craft proto/Rust mirror/converter/samples | R6 P1 | R4 P1 decode;R1 P1 emit | `A-CS`;buf/sample、116 C2S/144 S2C | +| client craft API + bridge/router/handler | R6 P1 | R7 P2 producer;R2 P1 `CraftStore` | R2+A-CS;proto→store/client pins | +| `CraftSessionStateV2` S2C | R6 P1 `craft_emit` 读 R1 | R2 store、R7 reopen | phase/identity/generation roundtrip | +| request-local `workbench_key` | WorkbenchOpen response,R6 P1 固型 | R7 原样回传;R4 runtime lookup | u64 boundary、response→request 不变 | +| stable `placed_id` + hydrate registry | R3 P4 | R4 runtime→stable claim;R1 restore rebind | placeable P0-P2;新 App hydrate/唯一 mapping | +| validated craft admission | R4 P1 | R1 P1 `try_acquire` | R6+R3 P4;stale/跨维/越距/mapping 缺失拒绝 | +| `InteractionSession`/phase/cause | R1 P1 | R1 P1-P3 adapters | R3 P1;状态/原因矩阵 | +| `SessionKey`/`PlayerKey`/generation | R1 P1 | R3/R4/R6/R7/R10 | rebind、stale generation reject | +| durability/transition | R1 P1 | 每个 domain adapter | checkpoint+P2c;显式 durability/六态 pins | +| suspension policy/deadlines | R1 P1;R3 rebase | R1 scanner;R10 P2c scanner | TTL/retry/lease 连续重启不刷新 | +| maintenance permissions | R1 P1 | admin terminate;P2c retry/resolve | authenticated allow;offline name/伪造 deny | +| registry/`BusyClaim` | R1 P1 | R4 `GateSpec`;adapters | conflict matrix + race tests | +| audit event | R1 contract;R3/R10 emit | audit sink | hashed principal;无 payload;transition 可追溯 | +| insight deadline transition | R1 P1 | response gate/timeout scanner | deadline/race/lifecycle cleanup | +| R7 Open/Pause/Cancel/Resume intents | R7 P2 | R6→R4→R1 | R2/R6/R4;四 intent 分离、resume once | +| `CraftStore` lifecycle | R2 P1;R6 handler | R7 P2 | disconnect clear、stale reject | +| checkpoint(escrow/`placed_id`/generation) | R1 payload;R3 P1 writer | R3 guarded restore→R1 | workbench 需 R3 P4;禁止 Entity | +| reservation | R3 P1 new/reuse/cancel API | R1 admission/restore/handoff | R1 §2.2.2 全矩阵 | +| quota | R3 P1 | R1 admission;R3/R10 release | counter=sum(obligations)、row/byte races | +| outbox + `delivery_id` | R3 P1 terminal txn | R10 P2c worker | crash points、唯一 owner、完整 payload | +| lease/retry/dead-letter CAS | R3 primitives;R10 P2c 驱动 | R10 worker/scanner/operator | rebase first;CAS races | +| `InventoryTxn::deliver` | R10 P1 | R10 P2c | stored+spilled=requested、失败不变 | +| `DeliveryCommitReceipt` | R10 P2c 原子 txn | R3 dedupe;R1 terminal confirm | replay 不重发/二次释放 | +| `ResolvedDisposition` | R10 P2c/operator + R3 txn | R3 cleanup/audit;R1 confirm | 无 disposition 不释放;重放幂等 | +| coupled TSY snapshot | R3 P1 | R1 P3 reconnect/gate | routine/disconnect/shutdown 全旧或全新 | + +闭环规则:任一端未合入,下游仅可提交接口/pins,不得启用 adapter。outbox consumer 固定 R10 P2c,stable workbench provider 固定 R3 P4;phase 删除/改名须同步本表与 wave gate。 + ## 5. 工作流(GPT tmux 多会话) 1. **一轨 = 一个 tmux 会话**(claude-code 映射的 gpt-5.6-sol-xhigh,多轮迭代,可自 spawn subagent)。10+ 会话时:9 轨 + V + registry-datafication + 若干近完成收尾会话。 @@ -81,7 +116,7 @@ - **6.1-6.9 已入轨**:见 R1-R10 各文件吸收清单(合计 ~130 份)。 - **6.10 V 轨(bot 骨干 + 测试诚实性)**:bot-e2e-coverage(促升本体)、bot-combat-server-data-type-false-positive、bot-multibot-chat-visibility、bot-multibot-entity-spawn-visibility、e2e-command-anchor-rejected、task13-mutation-qi-zero-green、proto-breaking-check-shallow-skip(深检部分,与 R6 P4 联动);已知 server 侧缺口「fallback 平台 centered on origin 非 spawn」一并修。 -- **6.11 Agent 轨(本次不重构,独立保留逐个消费)**:active——anticheat-tiandao-drop、niche-guardian-redis-dispatch、npc-combat-relic-schema-drift、pseudo-vein-agent-deadwire、war-participate-agent-command-drift、tiandao-schema-dist-start、server-data-s2c-schema-union-drift 的 TS 侧;skeleton——agent-ui-tiandao-revelation-vfx-flag-loss、alchemy-start-intervention-agent-drop、anqi-carrier-charged-agent-narration、arbiter-cjk-redaction-bypass、heart-demon-late-pregen-fallback、narration-target-prefix-routing、poi-novice-tiandao-narration-drain、technique-feedback-bridge、tiandao-agent-ui-click-context-loss、tsy-agent-ui-wrong-player-routing、tsy-enter-exit-agent-silent-drop、worldmodel-rollback-stub、rebirth-tiandao-bridge-gap、tsy-discovery-ui-target-fallback、player-chat-list-unbounded。 +- **6.11 Agent 轨(本次不重构,独立保留逐个消费)**:具名 production plan `A-CS — plan-agent-craft-schema-v1`(`docs/plans-skeleton/plan-agent-craft-schema-v1.md`,Wave 0 独立 Agent-track docs/implementation PR)唯一拥有并原子提交 `CraftOpen.target`、`CraftPause`、`CraftResume`、`CraftSessionStateV2` 的 TypeBox source、generated JSON Schema 与 committed `@bong/schema` dist;验收为 source→generated/dist freshness、全部 discriminated-union/phase 正反 samples 与 C2S 113→116 / craft S2C variant count,合入 SHA 记录为 R6 P1 输入。`A-CS` 不修改 proto/Rust/client,R6 不修改这些 Agent artifacts;若该 plan 未合入,R6/R4/R7/R1 craft gate 全部阻塞。其余独立项:active——anticheat-tiandao-drop、niche-guardian-redis-dispatch、npc-combat-relic-schema-drift、pseudo-vein-agent-deadwire、war-participate-agent-command-drift、tiandao-schema-dist-start、server-data-s2c-schema-union-drift 的 TS 侧;skeleton——agent-ui-tiandao-revelation-vfx-flag-loss、alchemy-start-intervention-agent-drop、anqi-carrier-charged-agent-narration、arbiter-cjk-redaction-bypass、heart-demon-late-pregen-fallback、narration-target-prefix-routing、poi-novice-tiandao-narration-drain、technique-feedback-bridge、tiandao-agent-ui-click-context-loss、tsy-agent-ui-wrong-player-routing、tsy-enter-exit-agent-silent-drop、worldmodel-rollback-stub、rebirth-tiandao-bridge-gap、tsy-discovery-ui-target-fallback、player-chat-list-unbounded。 - **6.12 Worldgen 轨(独立保留)**:active——anomaly-raster-runtime-consumer、baolongwang-poi-consumer-gap、raster-check-required-layers、spirit-eye-raster-candidate-disconnect、structure-manifest-loot-consumer、tribulation-scorch-mineral-node-gap、worldgen-pipeline-root-cwd、worldgen-raster-check-cli-noop;skeleton——animal-air-spawn-gravity、spawn-safe-y-surface-drift、spawn-tutorial-poi-y-drift、sword-sea-zone-overlap、tsy-start-raster-env-gap、tsy-y-strata-overlay、worldgen-uint8-maximum-blend、zone-ecology-global-refuge、qi-density-same-source。 - **6.13 接线拍板轨(module-wiring-gaps-v2 为决策菜单,人工拍板后逐个拆实施 plan;重构后接线成本大降)**:module-wiring-gaps-v2、forge-lingtian-processing-deadpath、poi-trespass-refusal-runtime-gap、silent-signal-runtime-bridge、social-runtime-bridge-gap、k2-identity-social-renown-bridge、war-emergent-group-reputation-gap、npc-combat-gear-v2、social-anonymity-live-refresh-gap、unconsumed-event-feedback、zhenfa-array-flag-e2e-wiring、woliu-dying-master-runtime-gap。 - **6.14 Feature 轨(独立,注意 §5.6 冻结窗口)**:active——beast-horde、client-login-ux、container-filter-and-completion、gameplay-journey、gathering-tool-bind、halfstep-buff-calibration、iris-integration、nested-pack(已 WITHDRAWN)、social-v2、sou-da-che、satiety-hydration(在飞)、ci-redis-pull-resilience(#1291 返工中);skeleton——ancient-relic-payoff、bonecoin-wallet-bridge、craft-chain-items、dandao-mutation-gameplay、dazuo、first-technique-grant、lootcrate、neardeath-ux、newbie-30min-hooks-audit、block-break-integration(#1253,基建 skeleton,建议 Wave 2 后评估与 R4 关系)。 diff --git a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md index 45b55dc01..99047924e 100644 --- a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md +++ b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md @@ -13,7 +13,7 @@ ## 接入面 - **进料**:SQLite(bong.db,沿用)、`shutdown.rs`(#1261 之后的关服链路)、`CultivationClock`(相对 tick 基准)。 -- **出料**:统一 Slice API 供各域注册:`load(guarded) / autosave(cadence) / flush_on_shutdown / tick_rebase`;R1 的 session 持久化钩子、各域运行态表全部走它。`tick_rebase` 对 R1 suspension lease 与 outbox 的 `next_retry_tick`/`created_at_tick`/`lease_until` 保持相对剩余时长/已消耗 age;R1 冻结默认 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000` 与扫描 cadence `1_200` ticks,本轨不得把重启折算成无限续租或刷新 outbox 退避/7 天 age。为 terminal delivery 提供 `SessionDeliveryOutbox`:事务内只提交 outbox 插入与 session checkpoint terminalization/删除,提交成功后 runtime gameplay claim 释放必须可依据 durable terminal/outbox 状态幂等重放;禁止仅持久化 session、把 delivery id 留在内存。每个 checkpointed session 的 admission/restore 必须先调用 `reserve_terminal_obligation(session_key)`:在同一 `BEGIN IMMEDIATE` 事务中 conditional-update 单例 `SessionDeliveryQuota { used_rows, used_bytes, generation }`(仅当 row/bytes 上限均有余量)并插入唯一 `SessionDeliveryReservation { session_key, reserved_bytes = SESSION_DELIVERY_MAX_PAYLOAD_BYTES }`;任一步失败整笔回滚。等价非 SQLite 实现必须以 quota generation CAS 提供同一线性化点,禁止先读剩余额度再另事务插 reservation。terminal handoff 在 checkpoint/outbox 同事务把 reservation 转移给 outbox,不重复计量;只有 receipt `Committed` 或带完整 payload 的 audited `ResolvedDisposition` 在同一事务删除 obligation 并扣减 counter,CAS loser 不得释放 quota。payload 增长不得超过 reservation,上限不足必须在接受新 escrow/产物前 fail closed。dead-letter 从自动扫描集合移除且不占 claim,但继续占 quota。`TsyPresence` 与 player position/dimension 作为同一 coupled snapshot:routine autosave、disconnect save、shutdown flush 均须共用同一事务/版本边界,不能由独立 Slice 提交不同快照。 +- **出料**:统一 Slice API 供各域注册:`load(guarded) / autosave(cadence) / flush_on_shutdown / tick_rebase`;R1 的 session 持久化钩子、各域运行态表全部走它。`tick_rebase` 对 R1 suspension lease 与 outbox 的 `next_retry_tick`/`created_at_tick`/`lease_until` 保持相对剩余时长/已消耗 age;R1 冻结默认 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000` 与扫描 cadence `1_200` ticks,本轨不得把重启折算成无限续租或刷新 outbox 退避/7 天 age。为 terminal delivery 提供 `SessionDeliveryOutbox`:事务内只提交 outbox 插入与 session checkpoint terminalization/删除,提交成功后 runtime gameplay claim 释放必须可依据 durable terminal/outbox 状态幂等重放;禁止仅持久化 session、把 delivery id 留在内存。每个 checkpointed session 的 reservation 必须遵守 P1 addendum 的 `reserve_new_terminal_obligation` / `reuse_terminal_obligation` / `cancel_unconsumed_reservation` 三分协议;只有首次 admission 可在取得 claim 前以同一 `BEGIN IMMEDIATE` 事务 conditional-update 单例 `SessionDeliveryQuota { used_rows, used_bytes, generation }`(仅当 row/bytes 上限均有余量)并插入唯一 `SessionDeliveryReservation { session_key, reserved_bytes = SESSION_DELIVERY_MAX_PAYLOAD_BYTES }`,任一步失败整笔回滚。restore/retry 只能验证并复用既有 reservation,不能 insert 或增加 counter。等价非 SQLite 实现必须以 quota generation CAS 提供同一线性化点,禁止先读剩余额度再另事务插 reservation。terminal handoff 在 checkpoint/outbox 同事务把 reservation 转移给 outbox,不重复计量;只有 receipt `Committed` 或带完整 payload 的 audited `ResolvedDisposition` 在同一事务删除 obligation 并扣减 counter,CAS loser 不得释放 quota。payload 增长不得超过 reservation,上限不足必须在接受新 escrow/产物前 fail closed。dead-letter 从自动扫描集合移除且不占 claim,但继续占 quota。`TsyPresence` 与 player position/dimension 作为同一 coupled snapshot:routine autosave、disconnect save、shutdown flush 均须共用同一事务/版本边界,不能由独立 Slice 提交不同快照。 - **共享类型**:新 `server/src/persistence/` 多文件模块(按域拆表定义 + 迁移链保持线性单入口);`PlayerSliceRegistry`(对齐 #1290 skeleton 的方向,直接吸收它)。 - **跨仓库契约**:零 wire 改动。 - **qi_physics 锚点**:任何带 qi 的快照持久化/恢复不得造成账面变化;恢复失败的兜底路径必须走 `release_dormant_qi_to_zone` 而非丢弃(对齐守恒律红旗清单)。 @@ -22,9 +22,11 @@ - ⬜ P0 设计收口 + 吸收清单验真:53 张表普查归域;冻结 Slice trait(载入守护语义:读失败 = 保留旧行 + 告警 + 只读降级,绝不写回空态;flush registry;tick rebase 协议);等 #1288/#1289/#1261/#1259 merge 后定基线。 - ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;冻结 inventory slice hydration seam 及 `MigrationOutcome` consumer 边界,R3 不复制 inventory 网格规则,待 R10 P1 提供纯幂等迁移函数后接入,不得提前引用 R10 常量/实现。注册 R1 所需的 `TsyPresence` auxiliary Slice,字段固定为 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version,并接入 guarded load、**与 player position/dimension 共用同一 snapshot/version 的原子 autosave**、`flush_on_shutdown`、`tick_rebase`;任何 autosave、disconnect save 或 shutdown flush 都不得让三者跨快照提交,crash 后只能全旧或全新。落 `SessionDeliveryOutbox`、`SessionDeliveryQuota`、`SessionDeliveryReservation` 表与 atomic handoff/reservation API,stable `delivery_id`、完整 payload、cause、attempts/next_retry/created_at/tick_epoch/state、单调 generation 与 in-flight lease 全部持久化。P1 acceptance 必须同时锁定:TsyPresence/position/dimension 在 routine autosave、disconnect save、shutdown flush 的每个逻辑写边界均由 fault-injection harness 强杀并重启,结果只能全旧或全新;reservation 的 quota conditional update + unique insert 原子性;terminal handoff 时 reservation→outbox 不重复计量;Committed/ResolvedDisposition 扣减 quota 与 obligation 删除原子性;checkpoint terminalization/deletion 与 outbox insert 同一 SQLite transaction 全成或全败;以及 outbox insert 前、terminalization 前、commit 后 ack 前的 crash-injection pins。P3 只复用 coupled-snapshot harness 做 flush registry 长尾回归并处理其余 flush/tick-rebase 批次,不得把首次原子性证据延后。 +- **P1 reservation protocol addendum(覆盖接入面旧 `reserve_terminal_obligation` 合称)**:实现 R1 §2.2.2 的 `reserve_new_terminal_obligation`(仅首次 admission,`+Q`)、`reuse_terminal_obligation`(matching restore/retry,`ΔQ=0`)和 `cancel_unconsumed_reservation`(busy loser/claim 后校验失败/无 checkpoint 残留,恰一次 `-Q`)。restore missing/conflicting owner/bytes/generation fail closed,禁止 insert/+Q;handoff、retry、lease expiry与 CAS loser 均 0,receipt/disposition 原子 `-Q`。 - ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;身份主键统一(identity-persist-key-mismatch)。仅在 R10 P1 容量契约合入后,dropped-loot hydration guard 才引用 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 与 `DroppedLootRegistry::try_insert/try_insert_batch`;超限进入 load-failure guard/只读降级并告警,禁止截断、驱逐或空表覆盖。同步实现 spill/pickup recoverable transaction seam,使 source mutation、attrited item、zone/ledger、drop insert/delete 与 transaction id 原子提交;crash/retry pins 常绿后才放行 R10 P2a Public writer path。 - ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部"节流落盘"域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥(coffin-autosave-inflight-race 模式);R3 的其余非耦合 slice 按 registry 收编。`TsyPresence` 三者原子 autosave/disconnect/shutdown 语义已在 P1 冻结,P3 仅补全 flush registry 接线与 crash 回归,不得把 coupled snapshot boundary 延后或缩窄为 shutdown-only。 - ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性(heiwushi)等——逐个按 Slice 框架补表。加入 `TsyPresence` guarded relog parity 契约测试和 TSY 维度重启 bot 场景,断言失败加载不 attach presence、成功恢复后才重新开放 TSY 请求。另拆两个 inventory consumer 子批次:R10 P1 + R3 P2 seam/compatibility pins 后,dropped-loot hydration 调 `migrate_legacy_dropped_loot_entry` 补 `owner=None`/`visibility=Public`,且先于 R6 projection/page;R10 P3 后,inventory-layout overflow 用真实 player/position/dimension `SpillContext` 调 `migrate_legacy_inventory_layout` 并持久化。任一 migration/context/capacity/persistence 失败均保留旧行可重试,两批不得绑成同一 gate。 +- **P4 placeable gate addendum**:可放置实体子批次须落实 `plan-bughunt-placeable-entity-restart-loss-v1` P0-P2:持久化 `placed_id`(非 Entity),world/layer ready 后 hydrate `WorkbenchBlock` 并建立唯一 `placed_id→runtime Entity` registry;missing/duplicate/unhydrated fail closed。它是 R4 runtime target→stable claim 与 R1 restore rebind 的 provider,常绿前不得迁移 checkpointed workbench craft。 - ⬜ P5 bot 验收 + 吸收 plan 批量归档。 ## 吸收清单(短名省略 plan-bughunt- 前缀与 -v1 后缀) @@ -46,7 +48,7 @@ skeleton:coffin-autosave-inflight-race、identity-persist-key-mismatch、miner 4. `tick_rebase`:带冷却/再生倒计时重启→断言倒计时按真实流逝折算(对齐 #1289 的 deadline 折算先例);另持久化一半已消耗的 `SuspensionPolicy` lease,重启/rebase 只保留原剩余 TTL,连续重复重启不刷新 lease,并覆盖剩余时长前一 tick、精确边界、后一 tick;outbox 同样覆盖 `next_retry_tick` 剩余退避、`created_at_tick` 已消耗 age、`lease_until` 剩余 lease 在单次/连续重启后的前一 tick、精确边界、后一 tick,断言不会把旧 process-local tick 直接带入新 epoch。 5. `tsy_presence_relog_parity`:进入 TSY→关服 flush→guarded load→只有 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version 校验通过才 attach `TsyPresence` 并开放 TSY 请求;损坏或缺失 Slice 保持未 attach 且拒绝请求;恢复后 death-drop 对原带物继续执行 50%/武器保护,对 TSY 所得执行既有 100% 规则。 6. `tsy_presence_snapshot_atomicity`:分别在 routine autosave、disconnect save、shutdown flush 中,向 presence、position、dimension 三个逻辑写入之间注入 crash;重启后断言三者只能全部保留旧 snapshot/version 或全部提交新 snapshot/version,不接受各 Slice 独立提交后碰巧通过 clean restore 对拍。shutdown 路径另断言 session registry 静止后才 flush coupled snapshot。 -7. `session_delivery_outbox_atomicity`:分别在 outbox insert 前、insert 后 checkpoint terminalize 前、事务 commit 后 ack 前强杀重启;另注入已 admission 但 payload 达到 `SESSION_DELIVERY_MAX_PAYLOAD_BYTES` 的边界,断言超限在接受新 escrow/产物前 fail closed;正常路径断言 checkpoint/outbox 恰有一个权威 owner,完整 payload 不丢且 stable `delivery_id` 不变。以两个独立 SQLite connection/线程同时竞争最后一个 row reservation、以及分别竞争不足以容纳两份 reservation 的最后 bytes,屏障同步到事务入口后并发调用 `reserve_terminal_obligation`,每种场景必须恰好一个 commit、一个 quota-full,最终 counter 与唯一 reservation 对拍且重启后不超配;再覆盖 CAS loser 不释放 quota、handoff 转移不双计、Committed/ResolvedDisposition 原子释放后新 admission 才成功。 +7. `session_delivery_outbox_atomicity`:分别在 outbox insert 前、insert 后 checkpoint terminalize 前、事务 commit 后 ack 前强杀重启;payload 边界分成两条:序列化后恰为 `SESSION_DELIVERY_MAX_PAYLOAD_BYTES` 必须成功接受并完成 handoff,`SESSION_DELIVERY_MAX_PAYLOAD_BYTES + 1` 必须在接受新增 escrow/产物前 fail closed。正常路径断言 checkpoint/outbox 恰有一个权威 owner,完整 payload 不丢且 stable `delivery_id` 不变。以两个独立 SQLite connection/线程同时竞争最后一个 row reservation、以及分别竞争不足以容纳两份 reservation 的最后 bytes,屏障同步到事务入口后并发调用 `reserve_new_terminal_obligation`,每种场景必须恰好一个 commit、一个 quota-full,最终 counter 与唯一 reservation 对拍且重启后不超配;另以两个不同 `SessionKey` 竞争同一 busy facility,证明 reservation 均可先提交但 busy loser 通过 `cancel_unconsumed_reservation` 恰好释放自身 `Q`,重复取消/CAS loser 不释放赢家额度。checkpoint restore 与并发 retry 必须以 `reuse_terminal_obligation` 命中原 reservation、counter 增量为零;missing 或 owner/bytes/generation 冲突 fail closed。再覆盖 handoff 转移不双计、Pending/InFlight/DeadLetter retry 与 lease expiry 不改 quota、Committed/ResolvedDisposition 原子释放后新 admission 才成功。 ## 开放问题(pre-P0 收口) @@ -62,5 +64,5 @@ skeleton:coffin-autosave-inflight-race、identity-persist-key-mismatch、miner ## 验收与实施边界 -- `cargo test --package bong-server persistence -- --nocapture` 覆盖 Slice guard、flush/tick rebase、migration consumer 与 dropped-loot bound;bot e2e 覆盖 `restart_player_slices`、`restart_world_runtime`、`load_failure_guard`、`tick_rebase`。 +- `cargo test --package bong-server persistence -- --nocapture` 覆盖 Slice guard、flush/tick rebase、migration consumer、dropped-loot bound 与 R1 §2.2.2 reservation 全状态转换;bot e2e 必须覆盖本 plan §bot 验收场景列出的全部七项:`restart_player_slices`、`restart_world_runtime`、`load_failure_guard`、`tick_rebase`、`tsy_presence_relog_parity`、`tsy_presence_snapshot_atomicity`、`session_delivery_outbox_atomicity`。任一场景未合入/未常绿,R3 P5 与下游 craft/TSY 放行均不得完成。 - R3 P1 只冻结 inventory seam,不实现 R10 migration/capacity;P2/P4 等依赖合入后才实现 consumer,且不修改 `server/src/inventory/**`。 diff --git a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md index 3be1f9253..1400f12cd 100644 --- a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md +++ b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md @@ -14,7 +14,7 @@ - **进料**:各域游戏事件(emit 调用点)、`world` 维度/zone 信息(作用域过滤)。 - **出料**:`bong:server_data` 单通道(目标态:28 旁路全部收编或显式豁免登记);join/重连首包快照集契约(R2 清干净后靠它灌满)。 - **共享类型**:新 server `network/emit/` builder(`scope: Global | Dimension | Zone | Player`);client 桥接层唯一的枚举前缀剥离函数。 -- **跨仓库契约**:proto 形状原则上不动(收编旁路时如需并入 envelope 属破坏性变更,走 buf breaking + samples 同步;agent 侧 TS 只做被动 regenerate,不重构 agent 逻辑)。**不做双轨兼容层**——旁路收编是一次性切换。Craft ownership 与 target 以总纲 §4 为准:Agent 轨拥有 TypeBox source/generated schema/`@bong/schema` dist;R6 只消费冻结版本并负责 proto/Rust/client wire plumbing。`CraftOpen.target` 为 required `Handcraft | Workbench { workbench_key }`;`workbench_key` 逻辑/Rust 为 `u64`、protobuf 为 `uint64`、JSON/TypeBox 为无符号十进制字符串,取自成功 S2C `WorkbenchOpen.entity_id` 并由 screen 原样回传,R4 解析后重验实体/维度/距离/facility。`CraftPause`/`CraftResume` 只携带 hydrated `session_key` + `generation`。R6 同时把冻结的 `CraftSessionStateV2` 一次性反映到 proto/Rust converter、`craft_emit`、bridge/router/handler/CraftStore,删除 V1 wire 分支;R7 只消费接口,不在 R6 提前实现 production Resume producer。 +- **跨仓库契约**:proto 形状原则上不动(收编旁路时如需并入 envelope 属破坏性变更,走 buf breaking + samples 同步;agent 侧 TS 只做被动 regenerate,不重构 agent 逻辑)。**不做双轨兼容层**——旁路收编是一次性切换。Craft ownership 与 target 以总纲 §4/§4.1 为准:Agent `A-CS` 批次拥有并先提交 TypeBox source/generated schema/`@bong/schema` dist;R6 只消费该批次记录 SHA 的冻结版本并负责 proto/Rust/client wire plumbing。`CraftOpen.target` 为 required `Handcraft | Workbench { workbench_key }`;`workbench_key` 逻辑/Rust 为 `u64`、protobuf 为 `uint64`、JSON/TypeBox 为无符号十进制字符串,取自成功 S2C `WorkbenchOpen.entity_id` 并由 screen 原样回传;它只在当前进程定位 runtime entity,R4 验证并解析到 R3 stable `placed_id` 后才交 R1 建持久 claim,wire/store/checkpoint 均不得把 `workbench_key` 当 durable identity。`CraftPause`/`CraftResume` 只携带 hydrated `session_key` + `generation`。R6 同时把冻结的 `CraftSessionStateV2` 一次性反映到 proto/Rust converter、`craft_emit`、bridge/router/handler/CraftStore,删除 V1 wire 分支;R7 只消费接口,不在 R6 提前实现 production Resume producer。 ## 阶段 @@ -37,7 +37,7 @@ skeleton:vfx-audio-dimension-bleed、q-world-season-dimension-env-resync、for - 独占:server `network/*_emit.rs` 公共模式与新 `network/emit/`、`schema/proto_convert.rs`;client `network/`(ProtoServerDataBridge、ServerDataRouter、BongNetworkHandler 的 channel 注册区段)。 - 不碰:`BongNetworkHandler.clearClientStateOnDisconnect` 区段(R2 域,同文件分区段,merge 前互相 fetch);`client_request_handler.rs`(R4);各 emit 的业务语义。 -- 依赖:Agent 轨独立 craft-schema 批次先合并并冻结 TypeBox source/schema/dist;R2 先合(同文件低冲突区段)。R1 craft adapter 不得在本轨 P1 的 craft 契约 pins 合入前宣称 pause/resume 可达;R4 handler/gate 随后落地。dropped-loot projection/page 子项另硬依赖 R10 P2a owner/visibility provider 与 R3 P4 migration/hydration consumer。 +- 依赖:Agent `A-CS`(`plan-agent-craft-schema-v1`)先合并并冻结 TypeBox source/schema/dist;R2 先合(同文件低冲突区段)。R1 craft adapter 不得在本轨 P1 的 craft 契约 pins 合入前宣称 pause/resume 可达;R4 handler/gate 随后落地。dropped-loot projection/page 子项另硬依赖 R10 P2a owner/visibility provider 与 R3 P4 migration/hydration consumer。 ## bot 验收场景 From dfe84d974befbd9319096d9de21f37b6f24c0f42 Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Tue, 4 Aug 2026 18:37:51 +0800 Subject: [PATCH 12/14] =?UTF-8?q?=E5=8E=8B=E7=BC=A9=E8=B7=A8=E8=BD=A8?= =?UTF-8?q?=E4=BC=9A=E8=AF=9D=E5=A5=91=E7=BA=A6=E7=9A=84=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E8=A1=A8=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 保留生命周期与生产消费表为唯一权威,将重复规则收敛为规范性引用,使结构整改保持可执行且控制文档体量。 Model: claude-sonnet-5 Co-Authored-By: Claude --- docs/plan-refactor-inventory-core-v1.md | 4 +- docs/plan-refactor-server-session-v1.md | 24 +++---- .../plan-agent-craft-schema-v1.md | 62 +++++++------------ .../plans-skeleton/plan-refactor-master-v1.md | 8 ++- .../plan-refactor-persistence-slices-v1.md | 4 +- 5 files changed, 45 insertions(+), 57 deletions(-) diff --git a/docs/plan-refactor-inventory-core-v1.md b/docs/plan-refactor-inventory-core-v1.md index 649e5bc2a..2c53565e9 100644 --- a/docs/plan-refactor-inventory-core-v1.md +++ b/docs/plan-refactor-inventory-core-v1.md @@ -52,7 +52,7 @@ SessionDeliveryWorker::fail(claimed, reason, now) -> Result ``` -`claim_next` 通过 R3 expected `(Pending, generation)` CAS 写 `InFlight { lease_id, lease_until }`;`commit` 解码并逐字段校验完整 payload,以 stable `delivery_id` 调 `InventoryTxn::deliver`,并在 R3 提供的同一 durable transaction 中原子提交 inventory/spill mutation、`DeliveryCommitReceipt`、`InFlight→Committed`、obligation 删除与 quota `-Q`。已有 receipt 时返回既有结果,不再次 deliver 或释放 quota。`fail` 只更新 attempts/backoff 或在阈值后 CAS 到 `DeadLetter`,quota 增量为零;lease expiry scanner、operator retry/resolve 与 worker 共用 R1 §2.2.2 generation-CAS 规则。R10 P2c 同时提供启动 worker/scanner、shutdown drain/lease handoff 与监控告警的 production wiring;R3 只持有 SQL/CAS primitive,不运行 inventory consumer。 +`claim_next` 以 R3 `(state,generation)` CAS 取得 `InFlight` lease;`commit` 用 stable `delivery_id` 调 `InventoryTxn::deliver`,并在 R3 同一 durable transaction 中提交 inventory/spill、receipt、`Committed`、obligation 删除与 quota `-Q`。receipt replay 不重复 deliver/release。`fail` 只推进 retry/backoff/`DeadLetter`,quota 为零变化。全部状态与 CAS 细节以 R1 §2.2.2 为唯一权威;P2c 还须接通 startup/shutdown worker/scanner/operator 与监控。 P2c contract pins:空队列、单条、并发 worker 唯一 claim、claim 后崩溃/lease expiry、deliver 前后崩溃、receipt 已存在重放、暂时失败退避、10 次/7 天 dead-letter、worker↔scanner/operator CAS loser、满包 spill、malformed payload fail closed,以及 `Pending/InFlight/DeadLetter` 始终占 quota、`Committed/ResolvedDisposition` 恰释放一次。R1 的 `session_delivery_crash_atomicity` 与 R3 的 `session_delivery_outbox_atomicity` 必须通过真实 P2c worker,不得直接调用 `InventoryTxn::deliver` 冒充 outbox consumer。 @@ -112,7 +112,7 @@ R10 的 inventory-layout 与 dropped-loot migration 函数均纯且幂等,保 - **R1**:txn stored/spilled 成功后才 teardown,失败保留 session。 - **R7**:UI 消费,不拥有事务。 -顺序分两条无伪依赖的链:**terminal delivery:R3 P1 reservation/outbox/CAS API → R10 P1 `deliver`/receipt contract → R10 P2c production worker → R1 checkpointed craft/alchemy/forge migration → R10 P4 联合 e2e**;**dropped-loot/pickup:R3 P1 → R10 P1(含纯 inventory-layout/dropped-loot migration helpers)→ R3 P2 atomic seam 实现 + legacy dropped-loot migration/hydration compatibility pins → R10 P2a metadata/provider + Public writer path → R3 P4 dropped-loot migration/hydration consumer → R6 P1 dropped-loot projection/page consumer → R5 P3 + R6 P4 → R10 P3 pickup/merge txn → R4 handler/pickup consumer → R10 P2b OwnerOnly private-writer activation → R3 P4 inventory-layout overflow consumer → R10 P4 联合 e2e**。P2c 不等待 R10 P2a/P2b 或 pickup 链,但必须消费真实 R3 outbox 且通过 crash/retry pins;R10 P2a 在 R3 P2 atomic seam 与旧 dropped-loot migration compatibility 未合入前不得开始;R3 P4 dropped-loot migration/hydration consumer 必须先于 R6 P1 dropped-loot projection/page consumer,确保旧 `entry_json` 已先升级为带 `owner`/`visibility` 的 canonical entry;R10 P2b 只有在 R3 P4 dropped-loot hydration、R6 P1 projection/page、R5 P3 + R6 P4、R10 P3 pickup txn 与 R4 pickup consumer 全部完成后才可启用,避免 OwnerOnly writer 在授权消费链闭合前广播或转移私有掉落;R10 P3 在 R5 P3/R6 P4 provider 未合入前不得开始 pickup consumer;R4/R3 的外轨交付物只作 R10 P4 验收前置、不计入 R10 自身 phase;R10 不越权改 persistence、wire、handler 或 client。 +顺序分两条:**terminal delivery**:R3 P1 outbox/CAS → R10 P1 deliver/receipt → R10 P2c worker → R1 checkpointed hosts → R10 P4;**dropped-loot/pickup**:R3 P1 → R10 P1 migration helpers → R3 P2 seam/compatibility → R10 P2a Public provider/writers → R3 P4 hydration → R6 P1 projection/page → R5 P3 + R6 P4 → R10 P3 pickup → R4 consumer → R10 P2b OwnerOnly writers → R3 P4 layout overflow → R10 P4。P2c 独立于 pickup 链;各箭头未合入不得启用下游或用 mock 宣称完成。 ## 7. 审核要求的 contract pins diff --git a/docs/plan-refactor-server-session-v1.md b/docs/plan-refactor-server-session-v1.md index 6bb8530c8..66529a2cc 100644 --- a/docs/plan-refactor-server-session-v1.md +++ b/docs/plan-refactor-server-session-v1.md @@ -81,13 +81,13 @@ pub trait InteractionSession { ### 2.1 不变量 1. `SessionRegistry` 中一个 `SessionKey` 只能有一个 owner;一个 runtime `Entity` 只能绑定同一 `PlayerKey` 的 session。 -2. `Checkpointed` session 断线/关服后进入 `Suspended`,不得离线推进;必须记录 `suspended_at_tick` 并受 `SuspensionPolicy.max_suspended_ticks` 约束。重连通过 R3 guarded restore 后才重新绑定 `Entity`;超过 TTL 时转入 `SuspensionExpired`,管理员结案时转入 `AuthorizedAdministratorClosure`,恢复校验失败时转入 `InvalidRestore`,分别按对应终止原因结算,先把完整 delivery obligation 写入 durable terminal/outbox state 并转为 `DeliveryPending`,再幂等释放 facility/target claim;R10 receipt 提交后才进入 `Terminal`。过期扫描和管理员入口必须幂等,且不会等待 owner 重连。 -3. `Volatile` session 遇到断线、跨维或关服必须在同一生命周期门内 teardown;不得留下 owner entity、设施锁、target claim 或 `settling` 标记。 -4. 所有 dimension-scoped session 在维度切换前终止。`TsyPresence` 是 transport 辅助状态,单独 checkpoint/restore,不得用“保留旧交互 session”修复 presence 撕裂。 -5. client 的 screen/store 只能改善 UX,不能授予 session 或 busy 权限;恶意包、重复包和同 tick 竞态最终都由 registry 拒绝。 -6. session 完成后先进入 `AwaitingDelivery`。所有 `Checkpointed` terminal obligation 一律按 §2.2.2 走 R3 outbox,不因 inventory 与 checkpoint 恰在同一 SQLite 文件就建立第二条 direct-delivery 协议:R3 在一个 checkpoint transaction 中把 reservation 转成完整 `SessionDeliveryOutbox` 并把 session 转入 durable `DeliveryPending`;R10 P2c 只按稳定 `delivery_id` 消费 outbox,在同一 durable transaction 提交 inventory mutation、durable spill、`DeliveryCommitReceipt`、outbox ack/obligation 删除与 quota 释放。任一步失败则该事务的各侧都不发布;重启见 receipt 即跳过重复 delivery并完成幂等 terminal confirmation。进程在 outbox insert、terminalization、worker claim、inventory/spill、receipt 或 ack 任一边界退出都不得丢失或双发。 -7. `AwaitingDelivery` 不得无限保留 facility/target claim。转入 durable outbox 时,事务内只提交权威的 terminal/outbox 状态;事务成功后释放 runtime gameplay claim 并从 live registry 移除,释放动作必须可由已提交的 terminal/outbox 状态幂等重放。outbox 仅保留 owner delivery obligation,不允许 owner 重新恢复已 terminalize 的 session。 -8. 涉及真元的 refund/release 必须通过 `qi_physics::ledger::QiTransfer`;session adapter 不得裸写 `qi_current` 或 zone qi。 +2. `Checkpointed` session 断线/关服后进入受 `SuspensionPolicy` 限制的 `Suspended`,不离线推进;guarded restore 通过才以 `PlayerKey` 重绑新 `Entity`。TTL、管理员结案或恢复失败分别以对应 cause 按 §2.2.2 handoff,receipt 后才 `Terminal`。 +3. `Volatile` session 遇到断线、跨维或关服必须在同一生命周期门内 teardown;不得留下 owner、claim 或 `settling`。 +4. dimension-scoped session 在转移前终止;`TsyPresence` 仅由 R3 coupled snapshot checkpoint/restore。 +5. client screen/store 不授予权限;registry 裁决恶意包、重放和竞态。 +6. `Checkpointed` completion 经 `AwaitingDelivery` → R3 原子 reservation→outbox/`DeliveryPending` → R10 P2c 原子 deliver/receipt/ack/quota release;所有崩溃点按 §2.2.2 重放,禁止第二条 direct-delivery 协议。 +7. outbox commit 后幂等释放 gameplay claim 并移除 live session;delivery obligation 不可 reopen。 +8. qi refund/release 必须走 `qi_physics::ledger::QiTransfer`。 ### 2.2 生命周期顺序 @@ -124,12 +124,12 @@ pub trait InteractionSession { - 每个宿主显式选择 `SuspensionPolicy.max_suspended_ticks`;P1 craft、P2 alchemy/forge 使用冻结默认值 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000`,扫描 cadence 为 `SESSION_SUSPENSION_SCAN_INTERVAL_TICKS = 1_200`。R3 `tick_rebase` 保持剩余 TTL 的相对时长,关服时间不得让 lease 永久延长;P3 世界交互不得继承无限值。 - registry 每 tick 或固定 cadence 扫描过期 session;只有 `SessionMaintenancePermissions::is_allowed(execution_origin, authenticated_principal, executor)` 才能用 `session-maintenance terminate `。仅 server console origin 或 server 已认证且绑定 executor 的 principal/capability 可通过;offline-mode player `Username`、命令 payload 或 owner 字符串均不能产生授权。普通玩家、未知 executor、伪造 allow-listed username/owner 和跨 owner target 一律拒绝且不改变 session/outbox/claim。扫描、授权管理员结案、重连与关服 flush 竞争时以 `SessionKey` CAS/registry lock 保证一次结算。 - 过期结案按 `TerminationCause::SuspensionExpired` 执行:停止 tick → 在 R3 checkpoint 事务中写入稳定 `delivery_id` 的 `SessionDeliveryOutbox` 并转为 `DeliveryPending` → 事务成功后幂等释放 facility/target/busy claim → 从 live registry 移除;R10 receipt 提交后再标记 `Terminal`。若进程在提交后、runtime claim 清理前退出,启动恢复必须依据已提交的 terminal/outbox 状态补做同一释放,不能重新 attach 或继续占用。R10 异步消费 outbox;不得以 delivery 临时失败为由继续占设施。 -- outbox 每行携带单调 `generation`,状态为 `Pending | InFlight { lease_id, lease_until } | DeadLetter | Committed | ResolvedDisposition { delivery_id, operator_principal, reason, payload }`。自动 worker 仅以 expected `(state,generation)` CAS claim `Pending→InFlight`;delivery receipt + inventory/spill + `InFlight→Committed` 同事务提交。dead-letter scanner 仅 CAS 非活跃/已过期 lease,operator retry/resolve 也必须 CAS 当前 generation;CAS loser 重读,不得覆盖 worker/scanner/operator 的并发结果或重复释放 quota。重试冻结为指数退避 `min(1_200 * 2^attempts, 72_000)` ticks;10 次或 7 天后转 `DeadLetter` 并停止自动扫描。`next_retry_tick`、`created_at_tick`、`lease_until` 与 `tick_epoch` 一并持久化;启动 guarded load 必须在 worker/scanner 运行前用 R3 `tick_rebase` 原子换算到当前 epoch,保持 retry 剩余时长与已消耗 age,连续重启不得刷新退避、7 天 age 或 lease。 -- R3 `SessionDeliveryOutbox` 是失败交付的唯一 retained obligation。每个 `Checkpointed` session 按 §2.2.2 的唯一协议在首次 admission 时、取得 gameplay claim 前预留 durable terminal-obligation capacity;restore 只能复用并校验已有 reservation,绝不再次计量。terminal handoff 与 checkpoint 行同事务转移 reservation。`Pending`/`InFlight`/`DeadLetter` 不重建 session 或 claim;quota 满时 fail closed,禁止删除/覆盖 payload。`resolve` 必须在同一事务把完整 payload 移入持久 `ResolvedDisposition { delivery_id, operator_principal, reason, payload }`(或已提交 receipt),审计成功后才删除 dead-letter row/释放 quota;禁止无 disposition 删除未交付 payload。 -- `DeliveryPending` 已提交 durable handoff 但未 receipt;R10 delivery 失败或进入 `DeadLetter` 时仍保持该 phase 的 durable obligation,绝不回退为 `AwaitingDelivery` 或重新占用 claim。 -- claim 回收与 outbox 状态变化必须有审计事件(session key、owner key hash、operator identity hash/`system`、cause、released claims、delivery id/result/attempt),不记录原 payload;恢复窗口内仍允许 owner 正常 reopen,写入 outbox 后 reopen 明确拒绝。 +- outbox 状态机、generation CAS、retry/dead-letter、tick rebase 与 quota release 唯一语义见 §2.2.2;R3 提供 durable primitive,R10 P2c 驱动 worker/scanner/operator。10 次或 7 天进入 `DeadLetter`;重启不得刷新 retry age 或 lease。 +- 首次 reservation、restore reuse、busy loser cancellation 与 terminal release 只按 §2.2.2;quota 满 fail closed,resolve 无完整 durable disposition 不释放。 +- `DeliveryPending` 只保留 obligation,不恢复 gameplay session/claim。 +- claim 回收与 outbox transition 记录不含 payload 的审计事件;handoff 后拒绝 reopen。 -- P1/P4 必须覆盖:永不重连的 abandoned session 最终释放设施、TTL 边界前后、授权管理员结案、offline client 伪造 allow-listed `Username` 仍拒绝、普通玩家/未知 executor/伪造 owner/跨 owner target 拒绝、过期与重连竞态、重复扫描幂等、正常 `Completed` handoff 不退款且 cause 可 roundtrip、outbox 事务 crash points、receipt 重放去重、worker↔scanner 与 worker↔operator retry/resolve generation-CAS 竞态、过期 lease 恢复、退避边界、10 次/7 天 dead-letter、重启前后 retry/age/lease deadline rebase(含连续重启不刷新)、授权 resolve 无 receipt/durable disposition 时 payload 与 quota 保留、quota 满时首次 admission fail-closed、两个首次 admission 并发竞争最后一行或最后一份 bytes 时仅一个成功、busy-claim race loser/后续校验失败取消临时 reservation 且 quota 恰减一次、restore/retry 复用同一 reservation 且 quota 增量为零、冲突 reservation fail-closed、quota 释放后的重新 admission,以及回收后另一玩家可获得同一 furnace/station claim。 +- P1/P4 必测:abandoned/TTL/admin closure、授权拒绝与竞态、`Completed`、outbox crash/receipt replay、worker/scanner/operator CAS、lease/rebase、10 次/7 天 dead-letter、resolve fail-closed,以及 §2.2.2 的首次 `+Q`、取消/terminal `-Q`、其余零变化;并证明设施可由下一玩家取得。 ### 2.2.2 Delivery reservation lifecycle(唯一权威矩阵) diff --git a/docs/plans-skeleton/plan-agent-craft-schema-v1.md b/docs/plans-skeleton/plan-agent-craft-schema-v1.md index 7bf31d731..3d66537d3 100644 --- a/docs/plans-skeleton/plan-agent-craft-schema-v1.md +++ b/docs/plans-skeleton/plan-agent-craft-schema-v1.md @@ -1,60 +1,46 @@ # plan-agent-craft-schema-v1 — Craft lifecycle Agent schema 生产批次(总纲 A-CS) -> 所属总纲:`plan-refactor-master-v1.md` §3/§4/§4.1/§6.11。一句话:由 Agent 轨一次性冻结 craft lifecycle 的 TypeBox source、generated JSON Schema 与 committed `@bong/schema` dist,给 R6 提供唯一、可调度、可验收的 agent-side schema 输入。 +> 所属总纲:`plan-refactor-master-v1.md` §3/§4/§4.1/§6.11。唯一职责:原子生产 craft lifecycle 的 TypeBox source、generated JSON Schema 与 committed `@bong/schema` dist,供 R6 消费。 ## 阶段总览 | 阶段 | 交付物 | 状态 | |---|---|---| -| P0 | 对拍现有 craft/client-request/server-data schema,冻结 V2 shape 与计数基线 | ⬜ | -| P1 | TypeBox source + registry/exports + 正反 contract tests | ⬜ | -| P2 | generated JSON Schema + committed dist 原子生成与 freshness gate | ⬜ | -| P3 | R6 handoff manifest、跨轨对拍与归档证据 | ⬜ | +| P0 | inventory、shape 与 variant baseline | ⬜ | +| P1 | TypeBox source、registry/exports、contract tests | ⬜ | +| P2 | generated schema、dist、freshness gate | ⬜ | +| P3 | R6 handoff SHA/digest/evidence | ⬜ | -## 接入面 +## 接入面与冻结合同 -- **进料**:总纲 §4 的 canonical `CraftOpen.target`,R1 的 `SessionPhase`/session identity/generation 合同,现有 `agent/packages/schema/src/{craft.ts,client-request.ts,server-data.ts,schema-registry.ts}` 与 `CraftSessionStateV1` 基线。 -- **出料**:冻结的 `CraftOpen`、`CraftPause`、`CraftResume`、`CraftSessionStateV2` TypeBox exports;对应 generated schemas;可由 agent runtime/import consumer 加载的 committed `@bong/schema` dist;记录 commit SHA、schema digest 与变体计数的 R6 handoff manifest/evidence。 -- **共享类型**:复用 canonical unsigned decimal-string helper、session key 与 generation schema;不新造与 proto/Rust 不同义的 identity。 -- **跨仓库契约**:本 plan 只改 `agent/packages/schema/**`。R6 P1 消费冻结结果并实现 proto/Rust/client wire;R4/R7/R1 不直接修改 Agent artifacts。无 gameplay/worldview/qi 语义。 - -## 冻结合同 - -1. `CraftOpen.target` 是 required discriminated union:`Handcraft` 或 `Workbench { workbench_key }`。`workbench_key` 是无符号 `u64` 十进制字符串;缺失、负数、小数、科学计数法、前后空白、超过 `u64::MAX` 均拒绝。它只用于当前进程初次请求,不是 durable identity。 -2. `CraftPause` 与 `CraftResume` 只携带 required `session_key` + `generation`;不得复带 target 或用 Pause/Resume 替代 `CraftCancel`。 -3. `CraftSessionStateV2` 覆盖 R1 的 `Running | Paused | Suspended | AwaitingDelivery | DeliveryPending | Terminal`,锁定每个 phase 对 identity/generation 的 required/forbidden 组合;删除 production V1 分支由 R6 执行,本轨只提供 V2 source-of-truth。 -4. P0 必须从当前 schema registry 计算真实 C2S/S2C 基线;P1 完成后把新增三种 C2S intent 体现为总纲要求的 113→116,并记录 craft S2C V2 对总 S2C variant count 的影响。若合入前主线计数变化,先更新基线与总纲/R6 pins,不得硬写陈旧数字让 freshness 假绿。 +- **进料**:总纲 §4 canonical shape、R1 phase/identity/generation、`agent/packages/schema/src/{craft.ts,client-request.ts,server-data.ts,schema-registry.ts}`。 +- **出料/owner**:本 plan 唯一修改 `agent/packages/schema/**`,原子交付 `CraftOpen`、`CraftPause`、`CraftResume`、`CraftSessionStateV2` 的 source/generated/dist;R6 只消费记录 SHA,不回改 Agent artifacts。 +- `CraftOpen.target` required:`Handcraft | Workbench { workbench_key }`。key 为 unsigned `u64` decimal string;缺失、负数、小数、科学计数法、空白、`>u64::MAX` 均拒绝,且不是 durable identity。 +- Pause/Resume 只含 required `session_key + generation`;不得夹带 target 或替代 Cancel。StateV2 覆盖 `Running | Paused | Suspended | AwaitingDelivery | DeliveryPending | Terminal`,逐 phase 锁定 identity/generation required/forbidden 组合;production union 删除 V1。 +- P0 从 registry 计算实时基线;新增三种 C2S 后目标 113→116。若主线计数变化,先同步总纲/R6 pins,禁止硬编码旧数假绿。 ## 阶段交付物 -### P0 — schema inventory 与 shape 冻结 +### P0 — inventory / shape -- 枚举 `ClientRequestV1`、`ServerDataV1`、`CraftSessionStateV1`、`SCHEMA_REGISTRY`、`GENERATED_SCHEMA_FILES`、package exports/dist 入口;记录 source→registry→generated→dist 完整路径。 -- 对拍总纲 §4 与 R1/R6 contracts,形成 `CraftOpen`/Pause/Resume/StateV2 字段矩阵与 C2S/S2C type-set baseline。 -- 明确与 `plan-bughunt-client-request-schema-drift.md`、`plan-bughunt-server-data-s2c-schema-union-drift-v1.md` 的边界:本 plan 只交付四个 craft lifecycle artifacts;全量 parity guard 仍由各自 drift plan 负责,但本 plan 的新增变体必须进入其现有 union/registry。 +枚举 source→registry→generated→dist/export 全路径,冻结四合同字段矩阵、C2S/S2C type-set baseline;本 plan 只拥有 craft 四合同,全量 drift guard 仍归既有 drift plans。 -### P1 — TypeBox source 与 contract tests +### P1 — TypeBox / tests -- 在 `agent/packages/schema/src/` 实现并 export 四个合同,注册到 `ClientRequestV1`/`ServerDataV1` 与 `SCHEMA_REGISTRY`;旧 V1 不得继续作为 production union 分支。 -- 正例:Handcraft、Workbench key `0`/`1`/`u64::MAX`,Pause/Resume identity,StateV2 每个 phase。 -- 反例:target 缺失/未知 discriminator、malformed/out-of-u64 key、Pause/Resume 缺 identity/generation 或夹带 target、StateV2 phase/identity/generation 非法组合、额外字段。 -- 测试必须验证对外 observable schema,不绑定私有 helper 调用次数。 +实现四合同并注册/export 到 `ClientRequestV1`、`ServerDataV1`、`SCHEMA_REGISTRY`。正例覆盖 Handcraft、Workbench key `0/1/u64::MAX`、Pause/Resume identity 与 StateV2 全 phase;反例覆盖 malformed target/key、identity 缺失或多余字段、非法 phase 组合。 -### P2 — generated/dist 原子交付 +### P2 — generated / dist -- 更新 `GENERATED_SCHEMA_FILES`,生成四个单项 schema及命中的 `client-request-v1.json`/`server-data-v1.json`;`npm run build -w @bong/schema` 生成 committed dist。 -- source、registry、generated、dist 必须同一提交;freshness 测试删除/篡改任一层都会失败。禁止让 R6 后补 dist 或手改 generated JSON。 -- 运行 `cd agent/packages/schema && npm test` 与 `cd agent && npm run build -w @bong/schema`,并验证 clean checkout 的 package exports 可 import 新 symbols。 +更新 `GENERATED_SCHEMA_FILES`、四单项 schema、`client-request-v1.json`、`server-data-v1.json` 与 committed dist。source/registry/generated/dist 必须同一提交;删除或篡改任一层时 freshness test 失败,clean checkout 可 import 新 symbols。 -### P3 — R6 handoff 与跨轨验收 +### P3 — R6 handoff -- 在 Finish Evidence 记录合入 SHA、generated schema digest、C2S/S2C 最终计数和导出 symbol 清单;该证据是 R6 P1 唯一可接受输入。 -- R6 对同一 SHA 做 TypeBox↔proto/Rust/client samples roundtrip;字段或计数不一致退回本 plan/R6 对应 owner,不在 R6 越权修改 Agent source。 -- A-CS 未归档前,总纲 Wave 1 的 R6 P1 与所有 craft production gate保持阻塞。 +记录 merge SHA、schema digest、最终 C2S/S2C count 与 exports。R6 只接受该 SHA并做 TypeBox↔proto/Rust/client roundtrip;不一致退回对应 owner。A-CS 未归档时所有 craft production gate 阻塞。 ## 验收与边界 -- 必跑:`cd agent/packages/schema && npm test`;`cd agent && npm run build -w @bong/schema`。 -- 必验:source/generated/dist freshness、package runtime import、四合同全部正反样本、ClientRequest/ServerData union membership、最终变体计数。 -- 不改 proto、Rust、Java、R1/R4/R6/R7 文件;不实现 client producer、server handler、session registry 或 workbench `placed_id`。 -- 这是总纲具名 production batch,不吸收或替代 `tiandao-schema-dist-start` 的 clean-start 构建链修复;后者解决启动前 dist 缺失,本 plan 解决具体 craft schema 内容及其原子产物。 +必跑 `cd agent/packages/schema && npm test`、`cd agent && npm run build -w @bong/schema`;必验四合同正反样本、union membership、freshness、runtime import 与 variant count。不改 proto、Rust、Java、gameplay handler/session,也不替代 `tiandao-schema-dist-start`。 + +## Finish Evidence + +> 迁入 `finished_plans/` 前填写落地路径、commit SHA/日期、测试结果、四 symbols 的 source/generated/dist/runtime-import 对拍,以及遗留项。 diff --git a/docs/plans-skeleton/plan-refactor-master-v1.md b/docs/plans-skeleton/plan-refactor-master-v1.md index 328839291..a7137936e 100644 --- a/docs/plans-skeleton/plan-refactor-master-v1.md +++ b/docs/plans-skeleton/plan-refactor-master-v1.md @@ -61,7 +61,8 @@ - `persistence/**`+autosave=R3;`session/`+7 域 session.rs=R1;`client_request_handler.rs`+`gate/`=R4;`*_emit.rs` 公共层+`proto_convert.rs`=R6;`qi_physics/**`+qi 字段直写行=R5;`inventory/**`=R10;cast/AV emit+skill 注册=R9。 - client:Store 生命周期+`clearClientStateOnDisconnect` 区段=R2;channel 注册区段+桥+router=R6(与 R2 同文件不同区段,merge 前互 fetch);Screen/hud/keybind/InspectScreen=R7;combat cast store=R9。 - `agent/packages/schema/src/**`、generated JSON Schema 与提交的 `@bong/schema` dist=§6.11 Agent 轨;craft schema prerequisite 的 production owner 是 Agent 轨的独立 craft-schema 交付批次,必须在 R6 P1 前提交并验收 TypeBox source、generated schema/dist 与变体计数。理由:TypeBox 是 agent-side source of truth,生成物必须与 source 同 owner 原子提交;让 R6 同时改 source 和 wire 会违反本总纲的 agent 排除边界并形成双 owner。R6 独占 proto/Rust mirror/converter、client wire encode/send 与 bridge/router plumbing,只消费该冻结版本并负责一次性 wire 反映。R6、R4、R7、R1 的 craft gate 均以本处 ownership 决议为准。 -- CraftOpen target bridge(跨轨 canonical contract):`CraftOpen` 必须携带 required `target` 判别联合:`Handcraft` 或 `Workbench { workbench_key }`,不得省略。`workbench_key` 是现有成功 S2C `WorkbenchOpen.entity_id` 的 ECS `Entity::to_bits()` locator:逻辑/Rust 类型 `u64`、protobuf `uint64`、JSON/TypeBox 为无符号十进制字符串;它只在当前进程的首次请求中定位 runtime entity,不是授权能力,也绝不写进 checkpoint。普通手搓发送 `Handcraft`;`WorkbenchScreen` 必须从 response 保留该 key 并在初次 `CraftOpen` 原样回传。R4 将 key 解析为 entity 后重验实体存活且携带 `WorkbenchBlock`、玩家同维且在既有距离内,并执行 owner/busy/facility gate,同时从 R3 P4 hydration registry 取得该 entity 对应的 stable `placed_id`;R1 仅在校验通过后以 `placed_id` 建立并持久化 facility claim。restore 时 R3/R1 用 stable `placed_id` 查询本次启动 hydrate 后的新 runtime entity,再重验 owner/维度/facility;missing/duplicate/unhydrated mapping 必须按 `InvalidRestore` fail closed。missing、malformed、stale、despawned、跨维或越距 `workbench_key` 均拒绝;R7 不得从 UI 猜测或改写 key。`CraftPause`/`CraftResume` 只携带 hydrated session identity/version,不重复 target。 +- CraftOpen target bridge(跨轨 canonical contract):`CraftOpen` 必须携带 required `target` 判别联合:`Handcraft` 或 `Workbench { workbench_key }`,不得省略。`workbench_key` 是现有成功 S2C `WorkbenchOpen.entity_id` 的 ECS `Entity::to_bits()` locator:逻辑/Rust 类型 `u64`、protobuf `uint64`、JSON/TypeBox 为无符号十进制字符串;它只在当前进程的首次请求中定位 runtime entity,不是授权能力,也绝不写进 checkpoint。普通手搓发送 `Handcraft`;`WorkbenchScreen` 必须从 response 保留该 key 并在初次 `CraftOpen` 原样回传。R4 将 key 解析为 entity 后重验实体存活且携带 `WorkbenchBlock`、玩家同维且在既有距离内,并执行 owner/busy/facility gate。missing、malformed、stale、despawned、跨维或越距 key 均拒绝;R7 不得从 UI 猜测或改写 key。`CraftPause`/`CraftResume` 只携带 hydrated session identity/version,不重复 target。 +- **Stable workbench addendum**:R4 还须从 R3 P4 registry 把 runtime entity 映射为唯一 `placed_id` 后才交 R1 持久化 claim;restore 以 `placed_id` 绑定本次 hydrate 的新 Entity。missing/duplicate/unhydrated mapping 按 `InvalidRestore` fail closed。 - 任何轨道碰他轨文件:只允许"消费对方冻结后的 API",不允许改对方独占文件;接缝 API 归被依赖方定义。 ### 4.1 跨轨 artifact producer/consumer 闭环表 @@ -70,7 +71,7 @@ | Artifact | Producer | Production consumer | Gate / evidence | |---|---|---|---| -| craft TypeBox source + generated schema + committed dist | Agent `A-CS` P1-P2 | R6 P1 mirror/generator;agent runtime | A-CS SHA;正反 sample、freshness、variant count | +| craft TypeBox source + generated schema + committed dist | Agent `A-CS` P1-P2 | R6 P1 mirror/converter;agent runtime | A-CS SHA;正反 sample、freshness、variant count | | craft proto/Rust mirror/converter/samples | R6 P1 | R4 P1 decode;R1 P1 emit | `A-CS`;buf/sample、116 C2S/144 S2C | | client craft API + bridge/router/handler | R6 P1 | R7 P2 producer;R2 P1 `CraftStore` | R2+A-CS;proto→store/client pins | | `CraftSessionStateV2` S2C | R6 P1 `craft_emit` 读 R1 | R2 store、R7 reopen | phase/identity/generation roundtrip | @@ -116,7 +117,8 @@ - **6.1-6.9 已入轨**:见 R1-R10 各文件吸收清单(合计 ~130 份)。 - **6.10 V 轨(bot 骨干 + 测试诚实性)**:bot-e2e-coverage(促升本体)、bot-combat-server-data-type-false-positive、bot-multibot-chat-visibility、bot-multibot-entity-spawn-visibility、e2e-command-anchor-rejected、task13-mutation-qi-zero-green、proto-breaking-check-shallow-skip(深检部分,与 R6 P4 联动);已知 server 侧缺口「fallback 平台 centered on origin 非 spawn」一并修。 -- **6.11 Agent 轨(本次不重构,独立保留逐个消费)**:具名 production plan `A-CS — plan-agent-craft-schema-v1`(`docs/plans-skeleton/plan-agent-craft-schema-v1.md`,Wave 0 独立 Agent-track docs/implementation PR)唯一拥有并原子提交 `CraftOpen.target`、`CraftPause`、`CraftResume`、`CraftSessionStateV2` 的 TypeBox source、generated JSON Schema 与 committed `@bong/schema` dist;验收为 source→generated/dist freshness、全部 discriminated-union/phase 正反 samples 与 C2S 113→116 / craft S2C variant count,合入 SHA 记录为 R6 P1 输入。`A-CS` 不修改 proto/Rust/client,R6 不修改这些 Agent artifacts;若该 plan 未合入,R6/R4/R7/R1 craft gate 全部阻塞。其余独立项:active——anticheat-tiandao-drop、niche-guardian-redis-dispatch、npc-combat-relic-schema-drift、pseudo-vein-agent-deadwire、war-participate-agent-command-drift、tiandao-schema-dist-start、server-data-s2c-schema-union-drift 的 TS 侧;skeleton——agent-ui-tiandao-revelation-vfx-flag-loss、alchemy-start-intervention-agent-drop、anqi-carrier-charged-agent-narration、arbiter-cjk-redaction-bypass、heart-demon-late-pregen-fallback、narration-target-prefix-routing、poi-novice-tiandao-narration-drain、technique-feedback-bridge、tiandao-agent-ui-click-context-loss、tsy-agent-ui-wrong-player-routing、tsy-enter-exit-agent-silent-drop、worldmodel-rollback-stub、rebirth-tiandao-bridge-gap、tsy-discovery-ui-target-fallback、player-chat-list-unbounded。 +- **6.11 Agent 轨(本次不重构,独立保留逐个消费)**:active——anticheat-tiandao-drop、niche-guardian-redis-dispatch、npc-combat-relic-schema-drift、pseudo-vein-agent-deadwire、war-participate-agent-command-drift、tiandao-schema-dist-start、server-data-s2c-schema-union-drift 的 TS 侧;skeleton——agent-ui-tiandao-revelation-vfx-flag-loss、alchemy-start-intervention-agent-drop、anqi-carrier-charged-agent-narration、arbiter-cjk-redaction-bypass、heart-demon-late-pregen-fallback、narration-target-prefix-routing、poi-novice-tiandao-narration-drain、technique-feedback-bridge、tiandao-agent-ui-click-context-loss、tsy-agent-ui-wrong-player-routing、tsy-enter-exit-agent-silent-drop、worldmodel-rollback-stub、rebirth-tiandao-bridge-gap、tsy-discovery-ui-target-fallback、player-chat-list-unbounded。 +- **6.11 A-CS production registration**:`docs/plans-skeleton/plan-agent-craft-schema-v1.md` 在 Wave 0 唯一生产四个 craft TypeBox/generated/dist artifacts,记录 SHA/count 供 R6;未归档时 R6/R4/R7/R1 craft gate 阻塞。 - **6.12 Worldgen 轨(独立保留)**:active——anomaly-raster-runtime-consumer、baolongwang-poi-consumer-gap、raster-check-required-layers、spirit-eye-raster-candidate-disconnect、structure-manifest-loot-consumer、tribulation-scorch-mineral-node-gap、worldgen-pipeline-root-cwd、worldgen-raster-check-cli-noop;skeleton——animal-air-spawn-gravity、spawn-safe-y-surface-drift、spawn-tutorial-poi-y-drift、sword-sea-zone-overlap、tsy-start-raster-env-gap、tsy-y-strata-overlay、worldgen-uint8-maximum-blend、zone-ecology-global-refuge、qi-density-same-source。 - **6.13 接线拍板轨(module-wiring-gaps-v2 为决策菜单,人工拍板后逐个拆实施 plan;重构后接线成本大降)**:module-wiring-gaps-v2、forge-lingtian-processing-deadpath、poi-trespass-refusal-runtime-gap、silent-signal-runtime-bridge、social-runtime-bridge-gap、k2-identity-social-renown-bridge、war-emergent-group-reputation-gap、npc-combat-gear-v2、social-anonymity-live-refresh-gap、unconsumed-event-feedback、zhenfa-array-flag-e2e-wiring、woliu-dying-master-runtime-gap。 - **6.14 Feature 轨(独立,注意 §5.6 冻结窗口)**:active——beast-horde、client-login-ux、container-filter-and-completion、gameplay-journey、gathering-tool-bind、halfstep-buff-calibration、iris-integration、nested-pack(已 WITHDRAWN)、social-v2、sou-da-che、satiety-hydration(在飞)、ci-redis-pull-resilience(#1291 返工中);skeleton——ancient-relic-payoff、bonecoin-wallet-bridge、craft-chain-items、dandao-mutation-gameplay、dazuo、first-technique-grant、lootcrate、neardeath-ux、newbie-30min-hooks-audit、block-break-integration(#1253,基建 skeleton,建议 Wave 2 后评估与 R4 关系)。 diff --git a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md index 99047924e..450e84d9a 100644 --- a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md +++ b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md @@ -13,7 +13,7 @@ ## 接入面 - **进料**:SQLite(bong.db,沿用)、`shutdown.rs`(#1261 之后的关服链路)、`CultivationClock`(相对 tick 基准)。 -- **出料**:统一 Slice API 供各域注册:`load(guarded) / autosave(cadence) / flush_on_shutdown / tick_rebase`;R1 的 session 持久化钩子、各域运行态表全部走它。`tick_rebase` 对 R1 suspension lease 与 outbox 的 `next_retry_tick`/`created_at_tick`/`lease_until` 保持相对剩余时长/已消耗 age;R1 冻结默认 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000` 与扫描 cadence `1_200` ticks,本轨不得把重启折算成无限续租或刷新 outbox 退避/7 天 age。为 terminal delivery 提供 `SessionDeliveryOutbox`:事务内只提交 outbox 插入与 session checkpoint terminalization/删除,提交成功后 runtime gameplay claim 释放必须可依据 durable terminal/outbox 状态幂等重放;禁止仅持久化 session、把 delivery id 留在内存。每个 checkpointed session 的 reservation 必须遵守 P1 addendum 的 `reserve_new_terminal_obligation` / `reuse_terminal_obligation` / `cancel_unconsumed_reservation` 三分协议;只有首次 admission 可在取得 claim 前以同一 `BEGIN IMMEDIATE` 事务 conditional-update 单例 `SessionDeliveryQuota { used_rows, used_bytes, generation }`(仅当 row/bytes 上限均有余量)并插入唯一 `SessionDeliveryReservation { session_key, reserved_bytes = SESSION_DELIVERY_MAX_PAYLOAD_BYTES }`,任一步失败整笔回滚。restore/retry 只能验证并复用既有 reservation,不能 insert 或增加 counter。等价非 SQLite 实现必须以 quota generation CAS 提供同一线性化点,禁止先读剩余额度再另事务插 reservation。terminal handoff 在 checkpoint/outbox 同事务把 reservation 转移给 outbox,不重复计量;只有 receipt `Committed` 或带完整 payload 的 audited `ResolvedDisposition` 在同一事务删除 obligation 并扣减 counter,CAS loser 不得释放 quota。payload 增长不得超过 reservation,上限不足必须在接受新 escrow/产物前 fail closed。dead-letter 从自动扫描集合移除且不占 claim,但继续占 quota。`TsyPresence` 与 player position/dimension 作为同一 coupled snapshot:routine autosave、disconnect save、shutdown flush 均须共用同一事务/版本边界,不能由独立 Slice 提交不同快照。 +- **出料**:统一 Slice API 供各域注册:`load(guarded) / autosave(cadence) / flush_on_shutdown / tick_rebase`;R1 的 session 持久化钩子、各域运行态表全部走它。`tick_rebase` 对 R1 suspension lease 与 outbox 的 `next_retry_tick`/`created_at_tick`/`lease_until` 保持相对剩余时长/已消耗 age;R1 冻结默认 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000` 与扫描 cadence `1_200` ticks,本轨不得把重启折算成无限续租或刷新 outbox 退避/7 天 age。为 terminal delivery 提供 `SessionDeliveryOutbox`:事务内只提交 outbox 插入与 session checkpoint terminalization/删除,提交成功后 runtime gameplay claim 释放必须可依据 durable terminal/outbox 状态幂等重放;禁止仅持久化 session、把 delivery id 留在内存。每个 checkpointed session 的 admission/restore 必须先调用 `reserve_terminal_obligation(session_key)`:在同一 `BEGIN IMMEDIATE` 事务中 conditional-update 单例 `SessionDeliveryQuota { used_rows, used_bytes, generation }`(仅当 row/bytes 上限均有余量)并插入唯一 `SessionDeliveryReservation { session_key, reserved_bytes = SESSION_DELIVERY_MAX_PAYLOAD_BYTES }`;任一步失败整笔回滚。等价非 SQLite 实现必须以 quota generation CAS 提供同一线性化点,禁止先读剩余额度再另事务插 reservation。terminal handoff 在 checkpoint/outbox 同事务把 reservation 转移给 outbox,不重复计量;只有 receipt `Committed` 或带完整 payload 的 audited `ResolvedDisposition` 在同一事务删除 obligation 并扣减 counter,CAS loser 不得释放 quota。payload 增长不得超过 reservation,上限不足必须在接受新 escrow/产物前 fail closed。dead-letter 从自动扫描集合移除且不占 claim,但继续占 quota。`TsyPresence` 与 player position/dimension 作为同一 coupled snapshot:routine autosave、disconnect save、shutdown flush 均须共用同一事务/版本边界,不能由独立 Slice 提交不同快照。 - **共享类型**:新 `server/src/persistence/` 多文件模块(按域拆表定义 + 迁移链保持线性单入口);`PlayerSliceRegistry`(对齐 #1290 skeleton 的方向,直接吸收它)。 - **跨仓库契约**:零 wire 改动。 - **qi_physics 锚点**:任何带 qi 的快照持久化/恢复不得造成账面变化;恢复失败的兜底路径必须走 `release_dormant_qi_to_zone` 而非丢弃(对齐守恒律红旗清单)。 @@ -22,7 +22,7 @@ - ⬜ P0 设计收口 + 吸收清单验真:53 张表普查归域;冻结 Slice trait(载入守护语义:读失败 = 保留旧行 + 告警 + 只读降级,绝不写回空态;flush registry;tick rebase 协议);等 #1288/#1289/#1261/#1259 merge 后定基线。 - ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;冻结 inventory slice hydration seam 及 `MigrationOutcome` consumer 边界,R3 不复制 inventory 网格规则,待 R10 P1 提供纯幂等迁移函数后接入,不得提前引用 R10 常量/实现。注册 R1 所需的 `TsyPresence` auxiliary Slice,字段固定为 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version,并接入 guarded load、**与 player position/dimension 共用同一 snapshot/version 的原子 autosave**、`flush_on_shutdown`、`tick_rebase`;任何 autosave、disconnect save 或 shutdown flush 都不得让三者跨快照提交,crash 后只能全旧或全新。落 `SessionDeliveryOutbox`、`SessionDeliveryQuota`、`SessionDeliveryReservation` 表与 atomic handoff/reservation API,stable `delivery_id`、完整 payload、cause、attempts/next_retry/created_at/tick_epoch/state、单调 generation 与 in-flight lease 全部持久化。P1 acceptance 必须同时锁定:TsyPresence/position/dimension 在 routine autosave、disconnect save、shutdown flush 的每个逻辑写边界均由 fault-injection harness 强杀并重启,结果只能全旧或全新;reservation 的 quota conditional update + unique insert 原子性;terminal handoff 时 reservation→outbox 不重复计量;Committed/ResolvedDisposition 扣减 quota 与 obligation 删除原子性;checkpoint terminalization/deletion 与 outbox insert 同一 SQLite transaction 全成或全败;以及 outbox insert 前、terminalization 前、commit 后 ack 前的 crash-injection pins。P3 只复用 coupled-snapshot harness 做 flush registry 长尾回归并处理其余 flush/tick-rebase 批次,不得把首次原子性证据延后。 -- **P1 reservation protocol addendum(覆盖接入面旧 `reserve_terminal_obligation` 合称)**:实现 R1 §2.2.2 的 `reserve_new_terminal_obligation`(仅首次 admission,`+Q`)、`reuse_terminal_obligation`(matching restore/retry,`ΔQ=0`)和 `cancel_unconsumed_reservation`(busy loser/claim 后校验失败/无 checkpoint 残留,恰一次 `-Q`)。restore missing/conflicting owner/bytes/generation fail closed,禁止 insert/+Q;handoff、retry、lease expiry与 CAS loser 均 0,receipt/disposition 原子 `-Q`。 +- **P1 reservation protocol addendum(规范性覆盖并废止接入面旧 `reserve_terminal_obligation` create-only 语义)**:实现 R1 §2.2.2 的 `reserve_new_terminal_obligation`(仅首次 admission,`+Q`)、`reuse_terminal_obligation`(matching restore/retry,`ΔQ=0`)和 `cancel_unconsumed_reservation`(busy loser/claim 后校验失败/无 checkpoint 残留,恰一次 `-Q`)。restore missing/conflicting owner/bytes/generation fail closed,禁止 insert/+Q;handoff、retry、lease expiry与 CAS loser 均 0,receipt/disposition 原子 `-Q`。 - ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;身份主键统一(identity-persist-key-mismatch)。仅在 R10 P1 容量契约合入后,dropped-loot hydration guard 才引用 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 与 `DroppedLootRegistry::try_insert/try_insert_batch`;超限进入 load-failure guard/只读降级并告警,禁止截断、驱逐或空表覆盖。同步实现 spill/pickup recoverable transaction seam,使 source mutation、attrited item、zone/ledger、drop insert/delete 与 transaction id 原子提交;crash/retry pins 常绿后才放行 R10 P2a Public writer path。 - ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部"节流落盘"域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥(coffin-autosave-inflight-race 模式);R3 的其余非耦合 slice 按 registry 收编。`TsyPresence` 三者原子 autosave/disconnect/shutdown 语义已在 P1 冻结,P3 仅补全 flush registry 接线与 crash 回归,不得把 coupled snapshot boundary 延后或缩窄为 shutdown-only。 - ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性(heiwushi)等——逐个按 Slice 框架补表。加入 `TsyPresence` guarded relog parity 契约测试和 TSY 维度重启 bot 场景,断言失败加载不 attach presence、成功恢复后才重新开放 TSY 请求。另拆两个 inventory consumer 子批次:R10 P1 + R3 P2 seam/compatibility pins 后,dropped-loot hydration 调 `migrate_legacy_dropped_loot_entry` 补 `owner=None`/`visibility=Public`,且先于 R6 projection/page;R10 P3 后,inventory-layout overflow 用真实 player/position/dimension `SpillContext` 调 `migrate_legacy_inventory_layout` 并持久化。任一 migration/context/capacity/persistence 失败均保留旧行可重试,两批不得绑成同一 gate。 From b34d992ed04b78980cdec348995306f2ed90ca1e Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Tue, 4 Aug 2026 22:37:24 +0800 Subject: [PATCH 13/14] =?UTF-8?q?=E9=87=8D=E5=86=99=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E7=BB=88=E6=80=81=E4=BA=A4=E4=BB=98=E8=A7=84=E8=8C=83=E4=B8=8E?= =?UTF-8?q?=E8=B7=A8=E8=BD=A8=E6=8A=95=E5=BD=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Model: claude-sonnet-5 Co-Authored-By: Claude --- ...plan-refactor-client-store-lifecycle-v1.md | 2 +- docs/plan-refactor-inventory-core-v1.md | 43 +- docs/plan-refactor-server-session-v1.md | 411 +++++++----------- .../plan-agent-craft-schema-v1.md | 54 ++- .../plan-refactor-c2s-gate-v1.md | 15 +- .../plan-refactor-client-ui-base-v1.md | 4 +- .../plans-skeleton/plan-refactor-master-v1.md | 77 ++-- .../plan-refactor-persistence-slices-v1.md | 22 +- .../plan-refactor-wire-s2c-v1.md | 16 +- 9 files changed, 280 insertions(+), 364 deletions(-) diff --git a/docs/plan-refactor-client-store-lifecycle-v1.md b/docs/plan-refactor-client-store-lifecycle-v1.md index fb355b668..118d3f914 100644 --- a/docs/plan-refactor-client-store-lifecycle-v1.md +++ b/docs/plan-refactor-client-store-lifecycle-v1.md @@ -111,7 +111,7 @@ skeleton:niche-guardian-cross-session-leak。 - 独占:全部 `*Store.java` 的生命周期接口与登记、`BongNetworkHandler.java` 的 `clearClientStateOnDisconnect` 区段。 - 不碰:`BongNetworkHandler.register()` 的 channel 注册区(R6 域,同文件分区段协作,两轨 merge 前互相 fetch);Screen 结构(R7 域);store 的业务字段语义。 -- 依赖:无前置,Wave 0 即可动工。R7/R9 依赖本轨接口,先于它们合入。 +- 依赖:Wave/start/order/cutover 只引用 master §3/§4.1 与 PR 1902;R7/R9 只消费本轨冻结的 Store interface,不在 R2 复制跨轨箭头。 ## bot 验收场景 diff --git a/docs/plan-refactor-inventory-core-v1.md b/docs/plan-refactor-inventory-core-v1.md index 2c53565e9..91cf5e19e 100644 --- a/docs/plan-refactor-inventory-core-v1.md +++ b/docs/plan-refactor-inventory-core-v1.md @@ -4,13 +4,13 @@ ## 阶段 -- ✅ 2026-08-03 P0 完整契约面重写 + absorption audit -- ⬜ P1:inventory 拆分 + txn/capacity 骨架 + inventory-layout/dropped-loot 纯 migration helpers(依赖 R3 P1 seam) -- ⬜ P2:production writer 迁移分为 P2a metadata/provider + Public writer path、P2b OwnerOnly private-writer activation 与 P2c terminal-delivery worker;P2a 依赖 R3 P2 atomic commit seam 与旧 dropped-loot compatibility,P2b 必须等 R3 P4 dropped-loot migration/hydration、R6 P1 recipient projection/page、R5 P3 + R6 P4、R10 P3 pickup txn 及 R4 pickup consumer 全部完成后才可启用;P2c 依赖 R3 P1 outbox/reservation API 与 R10 P1 `deliver`/receipt contract,独立于 dropped-loot visibility 波次,且是 R1 任一 checkpointed 宿主迁移的硬前置。 -- ⬜ P3:pickup/merge txn(依赖 R5 P3 attrition API、R6 P4 receipt API) -- ⬜ P4:联合 bot/e2e + plan 收口(依赖 R4 handler 与 R3 P4 legacy inventory-layout consumer) +- ⏳ P0 完整契约面重写 + absorption audit +- ⬜ P1:inventory 拆分 + txn/capacity 骨架 + inventory-layout/dropped-loot 纯 migration helpers;对应 master M-06/M-13 的 transaction/provider surface,R3 seam 只按 M-04 提供。 +- ⬜ P2:production writer 迁移分为 metadata/provider、Public/OwnerOnly writer 与 terminal worker;terminal worker 对应 M-06/O-10..O-27,dropped-loot hydration/projection 与 pickup consumer 只按 master M-13/M-14/M-15 启用。 +- ⬜ P3:pickup/merge txn;R4/R5/R6 consumer 与 receipt/attrition 接缝只引用 master M-14/M-15。 +- ⬜ P4:联合 bot/e2e + plan 收口;完成 evidence 需覆盖对应 M-row 与 canonical O-row。 -实现属 Wave 2;跨轨工作须登记 owning plan。 +所有跨轨 start/order/cutover 只引用 `plan-refactor-master-v1.md §3/§4.1` 与 PR 1902;本阶段表不复制箭头。 ## 1. 完整 contract surface @@ -41,20 +41,22 @@ InventoryTxn::pickup_and_merge(PickupRequest, PickupAuthorization, &mut DroppedL ### 1.2 Terminal-delivery production consumer(P2c) -R10 P2c 必须交付常驻 `SessionDeliveryWorker`,它是 R3 `SessionDeliveryOutbox` 的唯一生产 consumer,而不是测试 helper 或各域直接 `deliver` 调用。接口边界固定为: +R10 P2c 交付常驻 `SessionDeliveryWorker`,作为 R3 outbox 的唯一 production consumer。它只实现 R1 canonical obligation reducer 的 O-10..O-21/O-26/O-27,不拥有 gameplay session 状态或 teardown: ```rust SessionDeliveryWorker::claim_next(now, worker_id) - -> Result, DeliveryWorkerError> -SessionDeliveryWorker::commit(claimed, DeliveryRequest, Option<&mut SpillContext>) + -> Result, DeliveryWorkerError> +SessionDeliveryWorker::commit_claimed(claimed, Option<&mut SpillContext>) -> Result SessionDeliveryWorker::fail(claimed, reason, now) -> Result ``` -`claim_next` 以 R3 `(state,generation)` CAS 取得 `InFlight` lease;`commit` 用 stable `delivery_id` 调 `InventoryTxn::deliver`,并在 R3 同一 durable transaction 中提交 inventory/spill、receipt、`Committed`、obligation 删除与 quota `-Q`。receipt replay 不重复 deliver/release。`fail` 只推进 retry/backoff/`DeadLetter`,quota 为零变化。全部状态与 CAS 细节以 R1 §2.2.2 为唯一权威;P2c 还须接通 startup/shutdown worker/scanner/operator 与监控。 +`commit_claimed` 必须先预留 bounded history capacity(不足走 O-26),再校验 digest并从 `claimed.payload` decode/validate 唯一 `DeliveryRequest`;caller 无权另传 request。O-16 transaction 对拍 canonical bytes/digest、semantic item set 与 inventory receipt,并原子提交 inventory/spill、receipt、obligation delete、quota `-Q`。malformed/digest mismatch 命中 O-14,禁止调用 `deliver`;retry/lease/dead-letter 只走 O-12/O-13/O-18,绝不恢复 R1 session/claim。 -P2c contract pins:空队列、单条、并发 worker 唯一 claim、claim 后崩溃/lease expiry、deliver 前后崩溃、receipt 已存在重放、暂时失败退避、10 次/7 天 dead-letter、worker↔scanner/operator CAS loser、满包 spill、malformed payload fail closed,以及 `Pending/InFlight/DeadLetter` 始终占 quota、`Committed/ResolvedDisposition` 恰释放一次。R1 的 `session_delivery_crash_atomicity` 与 R3 的 `session_delivery_outbox_atomicity` 必须通过真实 P2c worker,不得直接调用 `InventoryTxn::deliver` 冒充 outbox consumer。 +P2c pins 直接执行 R1 O-10..O-21/O-26/O-27:并发唯一 claim、claim 后 crash/lease expiry、payload A/request B 拒绝、history quota fail-before-mutation、deliver 前后 crash、receipt replay、retry/dead-letter/operator CAS、满包 spill。R1 `session_delivery_crash_atomicity` 与 R3 `session_delivery_outbox_atomicity` 必须走真实 worker,不得由 domain 直接调用 `InventoryTxn::deliver` 冒充 consumer。 `pickup` receipt 含 request id、revision、removed drop、merge/placement、`target_instance_id`、`incoming_instance_id/count/abs_qi_before`。placement 的 target 等于 incoming;merge 的 target 是提交后既有 stack。R5 只按 incoming absolute qi 做 attrition:`target_after = preexisting_abs_qi + incoming_after`,不得磨损旧数量或由合并后整栈反推。 @@ -104,15 +106,10 @@ R10 的 inventory-layout 与 dropped-loot migration 函数均纯且幂等,保 ## 6. 所有权与顺序 -- **R10**:`server/src/inventory/**` model/grid/txn/capacity、writer enumeration、typed outcome、纯 migration,以及 P2c `SessionDeliveryWorker` production consumer。P1 仅在 **R3 P1** 的 inventory/overflow 与 outbox transaction seam 冻结后实现 txn/capacity/receipt 骨架;P2c 在 P1 与 **R3 P1** reservation/outbox CAS API 合入后实现 claim/lease/decode/deliver/receipt/ack/dead-letter wiring,并先于任何 R1 checkpointed 宿主迁移。P2a 负责 metadata/provider 与 `Public` writer path,依赖 **R3 P2** durable spill/pickup recoverable-commit seam、旧 dropped-loot migration compatibility 与 crash/retry pins;P2b 才能启用 `OwnerOnly` private writers,且必须等 **R3 P4 dropped-loot migration/hydration、R6 P1 recipient projection/page、R5 P3 + R6 P4、R10 P3 pickup txn、R4 pickup consumer** 全部完成;P3 pickup/attrition consumer 只有在 **R5 P3** incoming-only attrition/ledger API 与 **R6 P4** receipt wire/client API 已合入后才可接通。 -- **R3**:SQL/outbox、spill/pickup recoverable commit、hydration guard、migration consumer;R10 只消费 R3 P1/P2/P4 已冻结并实现的接口。P4 必须拆成 dropped-loot hydration 子批次与 inventory-layout overflow 子批次,前者不等待 R10 P3,后者才等待其实际 durable/capacity 前置。 -- **R4**:C2S gate/handler、authoritative pickup context、调用 R10 并转交 R6 outcome;R4 handler/consumer phase 必须等待 **R10 P3 pickup txn、R6 P4** receipt API 与 **R5 P3** attrition API,不得以 R10 mock 或仅 R6 P1 schema 代替。 -- **R5**:incoming-only qi attrition/ledger;provider phase 为 R5 P3。 -- **R6**:receipt wire/client、recipient projection/page、decoder;canonical plan 登记 rotate、pack feedback、dropped sync;dropped-loot projection/page consumer 为 R6 P1,必须在 **R10 P2a owner/visibility metadata provider** 与 **R3 P4 dropped-loot migration/hydration consumer** 完成后才可消费;receipt provider phase 为 R6 P4。 -- **R1**:txn stored/spilled 成功后才 teardown,失败保留 session。 -- **R7**:UI 消费,不拥有事务。 - -顺序分两条:**terminal delivery**:R3 P1 outbox/CAS → R10 P1 deliver/receipt → R10 P2c worker → R1 checkpointed hosts → R10 P4;**dropped-loot/pickup**:R3 P1 → R10 P1 migration helpers → R3 P2 seam/compatibility → R10 P2a Public provider/writers → R3 P4 hydration → R6 P1 projection/page → R5 P3 + R6 P4 → R10 P3 pickup → R4 consumer → R10 P2b OwnerOnly writers → R3 P4 layout overflow → R10 P4。P2c 独立于 pickup 链;各箭头未合入不得启用下游或用 mock 宣称完成。 +- **R10** 独占 `server/src/inventory/**`、纯 migration helpers 与 `SessionDeliveryWorker`;terminal worker 只投影 R1 O-10..O-21/O-26/O-27,inventory/spill transaction 不定义 session teardown。 +- **R3** 独占 SQL/outbox/CAS/reconciliation、hydration guard 与 migration consumer;R10 通过冻结接口 claim/commit/fail。 +- **R4/R5/R6/R7** 分别拥有 C2S authorization、qi attrition ledger、receipt/projection wire 与 UI consumer;R10 不修改其文件。 +- 所有跨轨 start/order/cutover 仅引用 master §3/§4.1 与 PR 1902,不在本 plan 复制箭头。接口可 contract-first 合入;真实 worker activation 必须等 master 列出的 R3 outbox 与 R10 transaction artifacts 存在,且不得用 mock 宣称 production closure。 ## 7. 审核要求的 contract pins @@ -125,7 +122,7 @@ R10 的 inventory-layout 与 dropped-loot migration 函数均纯且幂等,保 5. pickup 同维成功;跨维、超距/zone、owner/private 拒绝;merge、placement-only、failed attach/capacity/validation/persistence 后 entry 仍在;成功后才删。 6. incoming-only attrition receipt + R5 ledger:旧 stack absolute qi 不变;注入 attrition 后、durable commit 中断与 restart/retry,断言 attrited item + zone/ledger + drop delete 原子且总量守恒。 7. visibility matrix:同维/范围内 `Public` 对非 owner 可见,`OwnerOnly` 对 owner 可见、对普通非 owner 不可见、对 server-authorized admin 可见;另测跨维/超距拒绝。page/revision 按每个 recipient projection;缺页/混 revision 不替换。 -8. terminal-delivery worker:空队列、并发唯一 claim、lease expiry、commit 各 crash point、receipt 重放、retry/dead-letter/operator CAS;真实消费 R3 outbox,并对拍 R1 §2.2.2 全状态的 quota 增量。 +8. terminal-delivery worker:执行 R1 O-10..O-21/O-26/O-27,覆盖空队列、并发唯一 claim、lease expiry、payload/digest binding、history quota fail-before-mutation、commit crash、receipt replay、retry/dead-letter/operator CAS;quota effect 逐 row 对拍。 9. accepted/rejected move correlation;pack stow/equip/unequip 与拒绝必须动作级 receipt,stale event 和 snapshot-only baseline 不通过。 10. forge 深链保留;另锁 `/give hoe_iron → 新 snapshot → 真实非零 instance → held/equip → lingtian_start_till`,禁止 `instance_id=0` 或任意 server-data 冒充成功。 11. inventory-layout migration pure happy/empty/full/dynamic/idempotent/invalid;dropped-loot migration 覆盖旧 `entry_json` 缺 owner/visibility → `None`/`Public`、已有字段原样保留、malformed/幂等;R3 consumer 对真实 context 成功,缺 context/capacity/persistence/migration failure 保留旧行可重试。 @@ -134,7 +131,7 @@ R10 的 inventory-layout 与 dropped-loot migration 函数均纯且幂等,保 以下名称即 `scripts/bot/scenarios/.py` 的稳定身份: -1. `inv_full_delivery_matrix`:craft/alchemy/forge/give 满包时 `stored + spilled == requested`,失败不 teardown;checkpointed 三域必须经真实 `SessionDeliveryOutbox`→P2c worker→receipt/ack 链,不得直接调用 `deliver` 绕过 worker。 +1. `inv_full_delivery_matrix`:craft/alchemy/forge/give 满包时 `stored + spilled == requested`;checkpointed 三域经真实 `SessionDeliveryOutbox`→P2c O-10..O-16→receipt 链。handoff 已在 R1 S-14 teardown,worker failure 只留 Pending/DeadLetter obligation,不恢复 session。 2. `inv_stack_merge`:同 identity merge、异 identity 分栈;placement-only 与拒绝路径保留 drop;attrition durable 中断/restart 仍原子守恒。 3. `inv_footprint_sync`:2×1 rotate 后以 request/instance/from/to/revision 锚定 1×2 authoritative receipt;snapshot 不代替回执。 4. `inv_pack_feedback`:stow/equip/unequip 的 accepted/rejected 均按时间锚与 correlation 匹配,stale event 不通过。 @@ -146,7 +143,7 @@ R10 的 inventory-layout 与 dropped-loot migration 函数均纯且幂等,保 | plan | 2026-08-03 验真结论 | 证据/落点 | |---|---|---| -| `alchemy-takeback-full-inventory-loss` | 部分吸收 | 满包 deliver/spill 后才由 R1 teardown。 | +| `alchemy-takeback-full-inventory-loss` | 部分吸收 | terminal handoff 后由 obligation worker deliver/spill;失败走 O-13,不 reopen。 | | `dropped-loot-pickup-stack-merge` | 仍真实,吸收 | 当前 pickup 只找空 footprint;落 `pickup_and_merge`。 | | `force-attach-grid-collision` | 仍真实,吸收 | `(0,0)` 强塞仍可达;改合法 attach/spill。 | | `rotate-footprint-sync` | 仍真实,吸收 | `Moved` 缺权威新 footprint;R6 receipt 补齐。 | diff --git a/docs/plan-refactor-server-session-v1.md b/docs/plan-refactor-server-session-v1.md index 66529a2cc..829665565 100644 --- a/docs/plan-refactor-server-session-v1.md +++ b/docs/plan-refactor-server-session-v1.md @@ -1,45 +1,51 @@ -# plan-refactor-server-session-v1 — Server 交互 Session 统一生命周期框架(重构轨 R1) +# plan-refactor-server-session-v1 — Server 交互 Session 生命周期与终态交付(重构轨 R1) -> 所属总纲:`plan-refactor-master-v1.md`。一句话:把炼丹、手搓、锻造、采集、灵田、矿脉、灵木及相邻世界交互的 server session 收敛到统一生命周期框架,使断线、跨维、关服、重连、忙态与完成交付只保留一套权威语义。 +> 所属总纲:`plan-refactor-master-v1.md`。本轨唯一负责 gameplay session;终态交付的 durable 语义由本文件的 canonical contract 定义,R3/R10 只实现其存储与消费投影。跨轨排序引用总纲 §3 及 PR 1902 五项裁决,不在本文件另建依赖图。 ## 阶段总览 | 阶段 | 交付物 | 状态 | |---|---|---| -| P0 | 设计收口、吸收清单验真、冻结 `InteractionSession` 合同 | ✅ 2026-08-03 | -| P1 | `server/src/session/` 落地,craft 作为首个宿主 | ⬜ | -| P2 | 迁移 alchemy、forge、lingtian | ⬜ | -| P3 | 迁移 gathering、mineral、spiritwood 与世界交互锁 | ⬜ | -| P4 | bot e2e、吸收项结案、归档 | ⬜ | +| P0 | 现状/吸收验真;canonical session + obligation reducer;row-ID acceptance index | ⏳ | +| P1 | `server/src/session/` 框架与 craft contract/adapter 接缝 | ⬜ | +| P2 | alchemy、forge、lingtian adapter | ⬜ | +| P3 | gathering、mineral、spiritwood、世界交互 adapter | ⬜ | +| P4 | derived bot e2e、吸收结案、归档 | ⬜ | -## 1. P0 第一性现状 +## 0. 外部决议与边界 -### 1.1 七域存储矩阵 +- 总纲 `plan-refactor-master-v1.md §3/§4` 是跨轨 start/order/cutover 的唯一 authority。本计划引用 master artifact/phase ID,不复制 A-CS→R6→R4→R7→R3→R10 的箭头。 +- PR 1902(`origin/docs/master-r9-r6-ownership-adjudication`,五项 settled rulings)作为外部已定上下文:TypeBox 是全仓 schema source of truth;domain content 与 generation machinery 分权;contract-first 可先合入但不可宣称 live;production activation 必须原子切换;track plan 不得创建第二套 sequencing authority。 +- R1 独占 gameplay session registry、session reducer 和七域 adapter;R3 独占 SQL/checkpoint/outbox storage;R10 独占 inventory/spill transaction 与 worker;R4/R6/R7/R2 各自拥有其 gate/wire/UI/store。任何轨只消费冻结 API,不复制另一轨的状态语义。 +- durable checkpoint 不以 Bevy `Entity` 为主键。`workbench_key` 是当前进程 `WorkbenchOpen.entity_id` 的 runtime locator;checkpoint 只保存 R3 P4 提供的 stable `placed_id`。 -| 域 | 当前权威状态 | P0 结论 | +## 1. 现状与吸收边界 + +### 1.1 七域现状 + +| 域 | 当前权威状态 | R1 结论 | |---|---|---| -| alchemy | `server/src/alchemy/session.rs:68` `AlchemySession`,挂在 furnace | 炉与 session 共同 checkpoint;不能只存玩家侧半张状态 | -| craft | `server/src/craft/session.rs:40` ECS `CraftSession` | 作为 P1 首宿主;保留已落地的 DB/join hydration 行为 | -| forge | `server/src/forge/session.rs:157` `ForgeSessions` Resource | station/session/已扣材料共同 checkpoint | -| gathering | `server/src/gathering/session.rs:57` `GatheringSessionStore` | 短时易失;中断时确定性 teardown | -| lingtian | `server/src/lingtian/systems.rs:196` `ActiveLingtianSessions` | 六类 actor session 统一按易失交互处理 | -| mineral | `server/src/mineral/session.rs:16` `MiningSession` | 短时易失;解除矿点与工具 claim | -| spiritwood | `server/src/spiritwood/session.rs:59` `WoodSessionStore` | 短时易失;`settling` 必须参与原子 teardown | +| alchemy | `server/src/alchemy/session.rs:68` `AlchemySession`,挂在 furnace | P2 checkpointed adapter;炉与 session 同一 checkpoint | +| craft | `server/src/craft/session.rs:40` ECS `CraftSession` | P1 首宿主;迁入 registry 后保留既有 hydration 语义 | +| forge | `server/src/forge/session.rs:157` `ForgeSessions` Resource | P2 checkpointed adapter;station/material/session 同一快照 | +| gathering | `server/src/gathering/session.rs:57` store | P3 volatile adapter;中断确定性 teardown | +| lingtian | `server/src/lingtian/systems.rs:196` `ActiveLingtianSessions` | P2 volatile actor adapter;断线停止 tick | +| mineral | `server/src/mineral/session.rs:16` `MiningSession` | P3 volatile adapter;释放矿点/工具 claim | +| spiritwood | `server/src/spiritwood/session.rs:59` store | P3 volatile adapter;`settling` 与 teardown 同步 | -当前不存在 `SessionManager`。七域分别以 Component、Resource、facility-owned state 保存;`server/src/network/craft_emit.rs:541` 只用 `With` 限制 craft tick,不表示 UI pause,也不能覆盖其他 store。 +已确认的接线缺口包括保存前未统一 teardown、跨维残留 owner、TSY Search/Extract 双向 busy 缺失、PendingInsightOffer 无 deadline,以及取回路径先 `end_session` 后 delivery 失败。吸收清单沿用本 PR 原 §4 的逐项验真;被 R2/R3/R4/R10 明确拥有的实现不在 R1 重复登记。 -### 1.2 生命周期缺口仍可达 +## 2. 术语与两个 ownership domain -- 玩家保存与 despawn 从 `server/src/player/mod.rs:350` 开始;七域没有一个统一的、排在持久化前的 teardown/checkpoint 门。 -- 普通 external container 已有 `server/src/world/container_open.rs:184` 断线清锁,但跨维仍可保留 `opened_by`;局部补丁不能替代统一 hook。 -- TSY 搜刮入口 `server/src/world/tsy_container_search.rs:306` 与撤离入口 `server/src/world/extract_system.rs:182` 没有双向 busy 声明,搜刮锁也没有统一断线清理。 -- `server/src/cultivation/insight_flow.rs:229` 插入 `PendingInsightOffer`,选择、拒绝或校验失败会移除,但没有 deadline;同连接内 client 未展示/未回应时可长期悬挂。 -- 灵田 Resource 会对所有 entry 持续 `tick_all`(`server/src/lingtian/systems.rs:249`),没有 actor live-state gate。 -- 炼丹取回在 `server/src/network/client_request_handler.rs:16764-16766` 先 `end_session`,随后才从 `:16838` 尝试交付;交付失败时已经失去可重领状态。 +R1 不再用一组 `SessionPhase` 同时表示 gameplay 和 delivery。两者通过一次 handoff 连接: -## 2. 冻结的 `InteractionSession` 合同 +- `GameplaySession`:`SessionKey`、`PlayerKey`、durability、runtime `Entity` binding、facility/target/busy claim、checkpoint/escrow 和 termination cause。 +- `TerminalObligation`:reservation、quota `Q`、canonical payload、payload digest、delivery lease/retry/dead-letter、receipt/disposition 和 retention metadata。 +- `Q = (1 row, reserved_bytes)`;`quota_used = sum(Q)` 对所有占用 obligation 成立。 +- `workbench_key` 只做当前进程 lookup,不是 authorization、durable identity 或 checkpoint 字段;`placed_id` 才是 restore/rebind identity。 +- `SessionKey`、terminal `generation`、`delivery_id` 和 canonical payload bytes 是 exactly-once 的不同概念:前者定位 session,generation 防旧写入,delivery id 防重复 handoff,payload digest 绑定交付内容。 -P1 必须在 `server/src/session/` 暴露以下可 grep 的合同 symbol;实现可以按 Rust 借用约束拆成 trait + adapter,但不得改变本节语义: +### 2.1 R1 提供的 session 类型 ```rust pub trait InteractionSession { @@ -47,245 +53,158 @@ pub trait InteractionSession { fn owner_key(&self) -> &PlayerKey; fn durability(&self) -> SessionDurability; fn busy_claim(&self) -> BusyClaim; - fn on_disconnect(&mut self, ctx: &mut SessionLifecycleCtx) -> SessionTransition; - fn on_dimension_change( - &mut self, - from: DimensionKind, - to: DimensionKind, - ctx: &mut SessionLifecycleCtx, - ) -> SessionTransition; - fn on_shutdown(&mut self, ctx: &mut SessionLifecycleCtx) -> SessionTransition; - fn on_reconnect( - &mut self, - player: Entity, - ctx: &mut SessionLifecycleCtx, - ) -> SessionTransition; + fn reduce(&mut self, event: SessionEvent, ctx: &mut SessionLifecycleCtx) + -> SessionDecision; } ``` -配套冻结类型: - -- `SessionRegistry`:唯一 server 权威 owner/busy/lifecycle registry。 -- `SessionKey { domain, id }`:稳定 session 身份;持久记录不得以 Bevy `Entity` 为主键。 -- `PlayerKey`:canonical player id;`Entity` 只作当前连接的 runtime binding。 -- `SessionDurability::{Checkpointed, Volatile}`:每个 adapter 注册时必须显式声明,不允许默认值。 -- `SessionPhase::{Running, Paused, Suspended, AwaitingDelivery, DeliveryPending, Terminal}`:同一 session 不允许同时处于多个 phase。`DeliveryPending` 表示 R3 已提交 durable outbox/checkpoint terminalization、但 R10 尚未提交 receipt;它不再是可恢复 gameplay session,不持有 runtime claim。 -- `TerminationCause::{Completed, VoluntaryCancel, Disconnect, DimensionChange, Shutdown, InvalidRestore, SuspensionExpired, AuthorizedAdministratorClosure}`;`Completed` 专用于正常完成后的 durable outbox handoff,不触发 refund。 -- `SuspensionPolicy`:声明 checkpointed session 的 `suspended_at_tick`、有限 `max_suspended_ticks`、过期扫描周期、管理员强制结案入口和 facility claim 回收动作;默认值冻结为 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000`(20 TPS 下 24 小时)与 `SESSION_SUSPENSION_SCAN_INTERVAL_TICKS = 1_200`,不得配置为无限期保留。 -- `SessionTransition::{Keep, Pause, SuspendAndCheckpoint, Teardown, AwaitDelivery, DeliveryPending, CommitTerminal}`。 -- `SessionDeliveryOutbox { delivery_id, session_key, owner_key, payload_digest, payload, cause, state, generation, attempts, next_retry_tick, created_at_tick, tick_epoch }`:R3 持久化的 durable handoff;`delivery_id` 由 `SessionKey + terminal_generation` 稳定派生,禁止仅靠内存幂等键。`generation` 是每次状态 CAS 成功后递增的 durable token;`next_retry_tick`/`created_at_tick` 必须与 `tick_epoch`(持久化的 runtime tick、wall-clock capture 与 epoch id)一起经 R3 `tick_rebase` 转换,按重启时已过去的 wall-clock duration 计算新 epoch 下的剩余 retry/age,禁止跨重启直接比较旧 process-local tick。 -- `DeliveryOutboxState::{Pending, InFlight { lease_id, lease_until }, DeadLetter, Committed, ResolvedDisposition { delivery_id, operator_principal, reason, payload }}` 与 durable `DeliveryCommitReceipt`:R10 delivery 的跨重启 exactly-once 依据;worker、dead-letter scanner、operator retry/resolve 均以 outbox 行的 `(state,generation)` CAS 串行化;`ResolvedDisposition` 必须保留完整 payload,不能退化为 unit variant。 -- `SessionMaintenancePermissions`:只接受 server console origin,或 client 不可选择且已绑定当前 executor entity 的 authenticated principal/capability;`Username` 仅审计展示,绝不参与授权。offline mode 的 player executor 一律无 maintenance authority。 -- `BusyClaim`:声明 owner 与 world target 上占用的 busy classes;冲突矩阵集中注册。 - -### 2.1 不变量 - -1. `SessionRegistry` 中一个 `SessionKey` 只能有一个 owner;一个 runtime `Entity` 只能绑定同一 `PlayerKey` 的 session。 -2. `Checkpointed` session 断线/关服后进入受 `SuspensionPolicy` 限制的 `Suspended`,不离线推进;guarded restore 通过才以 `PlayerKey` 重绑新 `Entity`。TTL、管理员结案或恢复失败分别以对应 cause 按 §2.2.2 handoff,receipt 后才 `Terminal`。 -3. `Volatile` session 遇到断线、跨维或关服必须在同一生命周期门内 teardown;不得留下 owner、claim 或 `settling`。 -4. dimension-scoped session 在转移前终止;`TsyPresence` 仅由 R3 coupled snapshot checkpoint/restore。 -5. client screen/store 不授予权限;registry 裁决恶意包、重放和竞态。 -6. `Checkpointed` completion 经 `AwaitingDelivery` → R3 原子 reservation→outbox/`DeliveryPending` → R10 P2c 原子 deliver/receipt/ack/quota release;所有崩溃点按 §2.2.2 重放,禁止第二条 direct-delivery 协议。 -7. outbox commit 后幂等释放 gameplay claim 并移除 live session;delivery obligation 不可 reopen。 -8. qi refund/release 必须走 `qi_physics::ledger::QiTransfer`。 - -### 2.2 生命周期顺序 - -**断线**: - -1. `SessionRegistry` 在 `despawn_disconnected_clients` 及 R3 player save 前接收 disconnect。 -2. `Checkpointed`:停止 tick → 写 checkpoint(包含 escrow,但不执行 refund)→ 解绑 runtime `Entity` → 保持 stable owner/设施 claim → `Suspended`。只有恢复窗口结束、管理员结案或恢复校验失败时,才按对应终止原因进入 delivery/refund 结算。 -3. `Volatile`:停止 tick → 按非自愿中断结算 escrow/refund → 释放 target/busy → `Terminal`。 -4. 完成上述变更后,R3 才保存 player slices,随后 Valence despawn。 - -**跨维**: - -1. 在 `world/dimension_transfer` 写入新 layer/position 前停止接收该 owner 的 session 请求。 -2. 对 dimension-scoped session 执行非自愿 teardown 与返还;释放 busy/target。`Checkpointed` session 在跨维时不进入 `Suspended`,而是终止并全退未消费 escrow;`Disconnect`/`Shutdown` 的 checkpoint 保留语义不适用于跨维。 -3. teardown 成功后才应用维度转移。失败必须 fail closed,不能让旧维 session 跟随玩家进入新维度。 +固定类型: -**关服**: +- `SessionDurability::{Checkpointed, Volatile}`;adapter 必须显式注册。 +- `SessionState::{Running, Paused, Suspended, HandoffPreparing, Ended}`。 +- `TerminationCause::{Completed, VoluntaryCancel, Disconnect, DimensionChange, Shutdown, InvalidRestore, SuspensionExpired, AuthorizedAdministratorClosure}`。 +- `BusyClaim`:player-exclusive、target-exclusive、facility-exclusive 三类集中冲突矩阵。 +- `SuspensionPolicy`:`SESSION_SUSPENSION_TTL_TICKS = 1_728_000`、扫描 cadence `1_200` ticks;R3 tick rebase 只保留真实剩余 TTL,不刷新租约。 +- `SessionMaintenancePermissions`:server console,或已认证且绑定当前 executor 的 principal/capability;Username、owner 字符串、offline player 和跨 executor capability 均无权。 -1. 关闭新 session intake;先结算 `AwaitingDelivery`。 -2. `Checkpointed` 调 `on_shutdown` 生成 R3 checkpoint;`Volatile` 在玩家与 inventory 仍可访问时 teardown。 -3. lifecycle registry 静止后才执行 R3 `flush_on_shutdown`。R3 在 `plan-refactor-persistence-slices-v1.md` 接入面冻结的 `load(guarded) / autosave / flush_on_shutdown / tick_rebase` 是唯一持久化出口。 +## 3. Canonical session reducer(唯一 session 规范) -**重连**: +`reduce_session(state, event) -> {next_state, handoff?, claim_effect, checkpoint_effect, audit_effect}` 是 gameplay 唯一规范。以下 row ID 同时是 acceptance index 的引用;任何 phase prose 必须展开为这些 trace,不得另写 transition。 -1. R3 guarded load 先恢复 checkpoint,再由 registry 以 `PlayerKey` 绑定新 `Entity`。 -2. adapter 重验设施/target 存在、owner、维度和版本;通过后恢复为 `Paused` 或 `Running` 并 hydrate client。 -3. 恢复失败按 `InvalidRestore` 非自愿结案;有 escrow 时必须返还/交付。busy 冲突时保留 `Suspended` 并 fail closed,不得静默覆盖另一 session。 - -### 2.2.1 Suspended retention and reclamation - -`Checkpointed → Suspended` 是为短期断线/重启恢复服务的有限窗口,不是永久租约。P1 必须冻结并实现: +| Row ID | 当前状态 + event | 结果 | claim/checkpoint/delivery effect | +|---|---|---|---| +| S-01 | Absent + admission validation fail | Absent | 无 runtime claim;不创建 obligation | +| S-02 | Absent + `reserve_new_terminal_obligation` 成功且 claim win | Running | obligation O-01→O-03,`+Q`;绑定 owner/target | +| S-03 | Absent + reservation 成功但 busy race-loss | Absent | 执行 O-05→O-07/O-06;只有 O-06 commit 才 `-Q` | +| S-04 | Running + valid pause/close | Paused | claim 保留;不退款、不创建 terminal obligation | +| S-05 | Running + explicit `VoluntaryCancel` | HandoffPreparing | 按域 policy 生成 refund payload;进入 O-08 handoff | +| S-06 | Running + `Completed` | HandoffPreparing | 产物 payload;不 refund inputs;进入 O-08 | +| S-07 | Running + `Disconnect`/`Shutdown` checkpointed | Suspended | 停 tick、写 checkpoint、解绑 runtime Entity;逻辑 facility claim 保留 | +| S-08 | Running + `Disconnect`/`Shutdown` volatile | HandoffPreparing | 全退未消费 escrow;进入 O-08,不可离线推进 | +| S-09 | Running + `DimensionChange` | HandoffPreparing | 停 intake;全退未消费 escrow;不得进入 Suspended | +| S-10 | Suspended + reconnect and guarded restore pass | Paused | `reuse_terminal_obligation`(O-04,`ΔQ=0`);stable `placed_id` rebind | +| S-11 | Suspended + missing/conflicting checkpoint/owner/placed_id | HandoffPreparing | `InvalidRestore`;不得 attach 或覆盖另一 session;进入 O-08 | +| S-12 | Suspended + TTL expiry | HandoffPreparing | `SuspensionExpired`;保留已完成产物交付,未消费 escrow 全退;进入 O-08 | +| S-13 | Suspended + authorized admin closure | HandoffPreparing | `AuthorizedAdministratorClosure`;审计 principal/reason;进入 O-08 | +| S-14 | HandoffPreparing + handoff transaction committed | Ended | 释放 runtime/facility/target claim;obligation 获得唯一 durable payload owner;不可 reopen | +| S-15 | HandoffPreparing + transaction rollback before commit | prior live/checkpoint state | claim/session 保持可重试;不得删除 checkpoint 或扣 quota | +| S-16 | Ended + late gameplay request/reconnect | Ended | stale generation reject;不 attach、不恢复 claim | +| S-17 | any + maintenance authorization failure | same state | no mutation;audit reject reason,不产生 delivery | +| S-18 | any + same-tick timeout/reconnect/admin CAS loser | winner state | loser 重读 generation;不二次 handoff、不重复释放 claim | +| S-19 | Paused + resume with matching owner/generation | Running | claim 保留;继续原 checkpoint/escrow,不增 Q | +| S-20 | Paused + disconnect/shutdown/dimension/complete/cancel | by S-05..S-09 | 与 Running 使用同一 cause policy;不得因 Paused 绕过 handoff | +| S-21 | Suspended + duplicate disconnect/shutdown/pause | Suspended | 幂等 no-op;不刷新 TTL、不增 Q | +| S-22 | HandoffPreparing + duplicate gameplay/terminal event | HandoffPreparing | 重试同一 generation/payload;禁止生成第二 delivery_id | +| S-23 | nonterminal + event not admitted by state/cause matrix | same state | typed reject + audit;无 claim/checkpoint/quota effect | + +**唯一 teardown linearization point 是 S-14 的 durable handoff commit。** 从 S-14 起,worker 的 retry、lease expiry、malformed payload 或 inventory failure 只改变 obligation,不重新 attach gameplay session/claim。R1 不允许 direct terminal-delivery alternative;R10 的同步 `deliver` 只能是 obligation worker 内部的 transaction primitive。 + +### 3.1 Domain policy projection + +- `Checkpointed` 的 `Disconnect`/`Shutdown` 是可恢复暂停,不退款;`DimensionChange`、`InvalidRestore`、`SuspensionExpired`、管理员结案才进入 terminal refund/delivery。 +- `Volatile` 遇 disconnect/dimension/shutdown 立即进入 terminal handoff;不留下 owner、claim 或 settling。 +- `Completed` 只交付完整产物,不退 inputs;`VoluntaryCancel` 才执行域既有规则(craft 未完成部分 70% 返还)。 +- 所有 refund/release 通过 canonical obligation payload 和 R10 worker;qi 变化仍必须走 `qi_physics::ledger::QiTransfer`。 +- maintenance allow 必须分别覆盖 console positive 与 bound capability positive;wrong executor、offline username、伪造 owner 和普通玩家均拒绝。 +- retention 常量固定:`SESSION_DELIVERY_RESULT_REPLAY_TTL_TICKS = 12_096_000`(7 日)、`SESSION_DELIVERY_TOMBSTONE_TTL_TICKS = 51_840_000`(30 日)、`MAX_SESSION_DELIVERY_HISTORY_ROWS = 65_536`、`MAX_SESSION_DELIVERY_HISTORY_BYTES = 134_217_728`。R3 tick rebase 保留真实 age,不刷新 horizon;达到 row 或 byte 上限时 O-26 fail-before-deliver。 + +## 4. Canonical terminal-obligation reducer(R1/R3/R10 共用) + +`reduce_obligation(state, event) -> {next_state, quota_effect, worker_effect, retention_effect, audit_effect}` 是终态交付唯一规范。R3 提供 durable CAS/transaction;R10 提供唯一生产 worker;R1 只发出 handoff command 和消费结果。 + +| Row ID | obligation state + event | next state | `ΔQ` / effect | +|---|---|---|---| +| O-01 | Absent + new admission reservation commit | ReservedAwaitingClaim | `+Q`;quota update + unique reservation 同事务 | +| O-02 | Absent + quota full / unique conflict | Absent | `0`;fail closed | +| O-03 | ReservedAwaitingClaim + runtime busy claim win | ReservedLive | `0`;绑定 live session/generation,不重复计量 | +| O-04 | ReservedLive + matching suspended restore/retry | ReservedLive | `0`;复用 owner/bytes/generation,禁止 insert/+Q | +| O-05 | ReservedAwaitingClaim + busy race-loss/validation failure | CancelPending | `0`;runtime claim先释放,但 durable cleanup owner仍存在 | +| O-06 | CancelPending + cancel CAS commit | Absent | `-Q`;删除 reservation;重复/CAS loser `0` | +| O-07 | CancelPending + persistence failure | CancelPending | `0`;写入/保留 `next_retry`, attempts, generation;live reconciliation 必须重试 | +| O-08 | ReservedLive + terminal handoff transaction commit | Pending | `0`;reservation→outbox,不重复计量;payload bytes/digest 固定 | +| O-09 | ReservedLive + handoff transaction failure before commit | ReservedLive | `0`;checkpoint/claim/reservation 可重试,不能部分删除 | +| O-10 | Pending + worker claim CAS win | InFlight | `0`;写 lease/generation | +| O-11 | Pending + duplicate claim/CAS loser | Pending | `0`;重读 authoritative row | +| O-12 | InFlight + lease expiry | Pending 或 DeadLetter | `0`;按 retry budget/age 转换,不 attach gameplay | +| O-13 | InFlight + retryable inventory/spill failure | Pending | `0`;backoff/attempts 更新 | +| O-14 | InFlight + malformed payload/digest mismatch | DeadLetter | `0`;fail closed、保留完整 payload、告警;禁止调用 deliver | +| O-15 | InFlight + claimed payload decode/validation success | InFlight | `0`;`DeliveryRequest` 只能由 canonical payload decode 派生 | +| O-16 | InFlight + inventory/spill + receipt transaction commit | ReceiptRetained | `-Q`;inventory/spill、receipt、obligation delete、quota release 同事务 | +| O-17 | InFlight + receipt already exists for same digest | ReceiptRetained | `0`;幂等 replay,不二次 deliver 或 release | +| O-18 | DeadLetter + authorized operator retry | Pending | `0`;CAS loser 不改变 quota | +| O-19 | DeadLetter + authorized resolve with complete disposition | DispositionRetained | `-Q`;完整 disposition、obligation delete、quota release 同事务 | +| O-20 | DeadLetter + resolve missing payload/disposition | DeadLetter | `0`;fail closed,继续占 Q | +| O-21 | Receipt/Disposition retained + replay before horizon | same | `0`;返回既有 receipt/disposition | +| O-22 | Receipt/Disposition retained + compaction watermark reached | CompactedTombstone | `0`;保留 bounded digest/idempotency tombstone,不保留无限 payload | +| O-23 | CompactedTombstone + old replay | CompactedTombstone | `0`;按 tombstone reject/ack,不重新交付 | +| O-24 | CompactedTombstone + GC watermark/replay horizon confirmed | GarbageCollected | `0`;只允许在 producer/outbox watermark 证明旧 obligation 不可重现后删除 | +| O-25 | ReservedAwaitingClaim + cancel-mark persistence failure | ReservedAwaitingClaim | `0`;reservation 本身仍是 durable cleanup owner;live retry + startup stale-reservation scanner,禁止超时即 `-Q` | +| O-26 | InFlight + receipt/history quota unavailable before deliver | Pending 或 DeadLetter | `0`;在 inventory mutation 前 fail closed;保留 payload/Q 并 backoff/告警 | +| O-27 | any nonterminal + stale generation/invalid event/CAS loser | authoritative state | `0`;typed reject/audit,重读 row;不得隐式 insert、release、deliver | + +### 4.1 Obligation invariants + +1. 每个 `session_key` 恰有零或一个 durable obligation owner;`quota_used` 等于 `ReservedAwaitingClaim`、`ReservedLive`、`CancelPending`、`Pending`、`InFlight`、`DeadLetter` 的 Q 之和。payload envelope 固定含 `payload_schema_version`、`delivery_id`、`session_key`、terminal `generation`、recipient `PlayerKey`、cause、item/refund entries;O-08 首次写入的 exact bytes 是唯一权威身份,后续不得重序列化替换。 +2. `reserve_new` 只产生 O-01 的 `+Q`;claim win O-03、matching restore/retry O-04、handoff O-08、retry/lease O-12/O-13、所有 CAS loser 均 `0`。 +3. 只有 O-06 成功取消或 O-16/O-19 成功持久化终结结果才 `-Q`。取消标记或删除失败不能把 quota 留成无主 reservation:O-25 由原 reservation 继续担当 durable owner,O-07 由 `CancelPending` row 担当;scanner/retry 均不得凭 age 直接释放。 +4. `payload` 是 canonical serialized bytes;`payload_digest = SHA-256(payload)`。worker 的 `DeliveryRequest` 必须从 claimed payload decode,并在 O-16 transaction 中以 digest/semantic item set 对拍;不存在独立可替换的 caller payload。 +5. payload 序列化后恰为 `SESSION_DELIVERY_MAX_PAYLOAD_BYTES` 可接受;`+1` 在新增 escrow/output 被接受前 fail closed。payload 一旦进入 O-08 不可增长。 +6. `ReceiptRetained`/`DispositionRetained` 不是无限历史:在 replay horizon 内保留完整结果;达到 producer/outbox GC watermark 后转 O-22 bounded tombstone,再满足 O-24 才 GC。具体 horizon、tombstone 上限和告警阈值由 R3 implementation P0 冻结,并受独立有界 storage quota 约束;O-26 要求在 deliver 前预留该容量。 +7. O-08 成功后 gameplay claim 已由 S-14 释放;O-12/O-13/O-14/O-18 不得恢复 session。DeadLetter 无 runtime claim 但继续占 Q。 + +## 5. 接缝与 artifact ledger + +下表只说明 R1 提供/消费的 artifact;跨轨 owner/order 引用 master §4.1 和 PR 1902,不在 R1 复制 sequencing: + +| Artifact / row | R1 提供或消费 | canonical evidence | +|---|---|---| +| `InteractionSession` / S-01..S-23 | R1 producer;domain adapters consumer | session reducer trace | +| `SessionKey`/`PlayerKey`/generation | R1 producer;R3/R4/R6/R7/R10 consumer | stale generation S-16/S-23 | +| reservation/quota/outbox / O-01..O-27 | R3 durable producer;R1 semantic consumer;R10 worker consumer | quota invariant 1-3 | +| payload/digest/receipt/retention | R3 storage + R10 transaction;R1 consumes result | O-14..O-27 | +| stable `placed_id` | R3 P4 provider;R4 lookup;R1 checkpoint/restore consumer | S-10/S-11 | +| craft wire/schema | A-CS/R6/R4/R7 按 master owner;R1 只消费 hydrated identity/phase | PR 1902 contract-first/activation rows | +| coupled `TsyPresence` snapshot | R3 provider;R1 reconnect/gate consumer | full-old/full-new snapshot trace | -- 每个 checkpoint 写入 `suspended_at_tick`、`last_rebase_epoch`、owner `PlayerKey`、facility/target claim 和 escrow 摘要;不得只靠数据库行存在判断是否仍可恢复。 -- 每个宿主显式选择 `SuspensionPolicy.max_suspended_ticks`;P1 craft、P2 alchemy/forge 使用冻结默认值 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000`,扫描 cadence 为 `SESSION_SUSPENSION_SCAN_INTERVAL_TICKS = 1_200`。R3 `tick_rebase` 保持剩余 TTL 的相对时长,关服时间不得让 lease 永久延长;P3 世界交互不得继承无限值。 -- registry 每 tick 或固定 cadence 扫描过期 session;只有 `SessionMaintenancePermissions::is_allowed(execution_origin, authenticated_principal, executor)` 才能用 `session-maintenance terminate `。仅 server console origin 或 server 已认证且绑定 executor 的 principal/capability 可通过;offline-mode player `Username`、命令 payload 或 owner 字符串均不能产生授权。普通玩家、未知 executor、伪造 allow-listed username/owner 和跨 owner target 一律拒绝且不改变 session/outbox/claim。扫描、授权管理员结案、重连与关服 flush 竞争时以 `SessionKey` CAS/registry lock 保证一次结算。 -- 过期结案按 `TerminationCause::SuspensionExpired` 执行:停止 tick → 在 R3 checkpoint 事务中写入稳定 `delivery_id` 的 `SessionDeliveryOutbox` 并转为 `DeliveryPending` → 事务成功后幂等释放 facility/target/busy claim → 从 live registry 移除;R10 receipt 提交后再标记 `Terminal`。若进程在提交后、runtime claim 清理前退出,启动恢复必须依据已提交的 terminal/outbox 状态补做同一释放,不能重新 attach 或继续占用。R10 异步消费 outbox;不得以 delivery 临时失败为由继续占设施。 -- outbox 状态机、generation CAS、retry/dead-letter、tick rebase 与 quota release 唯一语义见 §2.2.2;R3 提供 durable primitive,R10 P2c 驱动 worker/scanner/operator。10 次或 7 天进入 `DeadLetter`;重启不得刷新 retry age 或 lease。 -- 首次 reservation、restore reuse、busy loser cancellation 与 terminal release 只按 §2.2.2;quota 满 fail closed,resolve 无完整 durable disposition 不释放。 -- `DeliveryPending` 只保留 obligation,不恢复 gameplay session/claim。 -- claim 回收与 outbox transition 记录不含 payload 的审计事件;handoff 后拒绝 reopen。 +## 6. Derived acceptance index -- P1/P4 必测:abandoned/TTL/admin closure、授权拒绝与竞态、`Completed`、outbox crash/receipt replay、worker/scanner/operator CAS、lease/rebase、10 次/7 天 dead-letter、resolve fail-closed,以及 §2.2.2 的首次 `+Q`、取消/terminal `-Q`、其余零变化;并证明设施可由下一玩家取得。 +验收只引用 row ID,不重新定义状态: -### 2.2.2 Delivery reservation lifecycle(唯一权威矩阵) +1. `session_trace_matrix`:S-01..S-23 覆盖 admission、pause/resume、complete、cancel、disconnect、dimension、shutdown、restore、TTL、admin、duplicate、invalid、stale/CAS loser;每条 trace 逐步执行 `reduce_session`。 +2. `obligation_trace_matrix`:O-01..O-27 覆盖 quota-full、claim win、busy loser、cancel-mark/delete persistence failure 与 live/restart retry、restore reuse、handoff crash、lease expiry、retry/dead-letter、malformed/mismatched payload、history quota full、receipt replay、resolve fail-closed、retention/GC、invalid/stale event。 +3. `payload_identity`:claimed bytes、digest、derived request、inventory receipt 四者对拍;注入 payload A/request B 必须命中 O-14,不能调用 deliver 或 ack 任一 payload。 +4. `quota_conservation`:两个 SQLite connection 竞争最后 row/bytes;恰一个 O-01 成功,其余 O-02;race loser 的 cancel-mark 成功走 O-05→O-07/O-06,标记失败走 O-25 并由 live/startup reconciliation 收敛,不泄漏或双扣 Q。 +5. `handoff_crash_atomicity`:outbox insert 前、terminal checkpoint 前、commit 后 ack 前强杀;结果只能 O-09 可重试或 O-08/Pending 可重放,S-14 不 reopen。 +6. `delivery_history_bound`:receipt/disposition 在 horizon 前走 O-21 replay,达到 watermark 后为 O-22 tombstone,再由 O-24 GC;history quota 满命中 O-26,inventory 不 mutation,storage 不无限增长。 +7. `maintenance_auth`:console allow、current-executor capability allow、wrong-executor capability deny、offline/username/owner spoof deny。 +8. `workbench_restore`:`workbench_key` 0/1/u64::MAX 与 malformed/stale/despawned/cross-dimension/out-of-range;成功后只保存/恢复 `placed_id`,新 runtime Entity 可 rebind。 +9. `tsy_presence_snapshot`:routine autosave、disconnect、shutdown 每个写边界 crash 后 presence/position/dimension 只能全旧或全新。 +10. named bot scenarios:`session_disconnect_cleanup`、`session_dimension_transfer`、`session_restart_recovery`、`session_busy_mutex`、`session_full_inventory_delivery`、`session_suspension_reclamation`、`session_delivery_crash_atomicity`、`session_termination_cause_matrix`、`session_craft_pause_resume_wire`、`session_tsy_presence_relog`、`session_pending_insight_offer_deadline` 均只引用上述 row/trace ID。 -R1/R3/R10 只实现本表,不另写释放条件。`Q=(1 row,reserved_bytes)`,`ΔQ` 是提交后的 quota 变化;所有 mutation 以 `session_key` + expected reservation/outbox `generation` CAS,loser 重读且 `ΔQ=0`。 +## 7. 本轨实施阶段 -| 状态(durable owner / claim / quota) | claim | race-loss / admission failure | timeout | restore | retry / lease expiry | terminal | -|---|---|---|---|---|---|---| -| `Absent`(无 / 无 / 0) | `reserve_new_terminal_obligation` 原子 quota update+unique insert→`ReservedPendingClaim`,`+Q`;满则不变 | no-op | no-op | checkpoint 无 reservation:隔离并 fail closed,不补计量/attach | 非法 | 拒绝 | -| `ReservedPendingClaim`(reservation / 无 / Q) | `try_acquire` 胜→`ClaimedLive`,0 | `cancel_unconsumed_reservation` 原子删 row/扣 counter→`Absent`,`-Q`;重复/CAS loser 0 | 同 race-loss | 启动见 reservation 但无 checkpoint/live claim:取消 `-Q`,不得当 restore | 同 restore | 未接 escrow/output,先取消,不 handoff | -| `ClaimedLive`(reservation+session / 有 / Q) | 重复拒绝,0 | claim 后校验失败:释放 claim,再 CAS 取消 `-Q`;崩溃由“无 checkpoint 残留”补做 | 按域 pause/suspend/terminal;禁止只释放 quota | 重复 attach 拒绝 | gameplay retry 0 | `AwaitingDelivery`;R3 原子 reservation→outbox `Pending`,0;commit 后释放 claim | -| `SuspendedReserved`(checkpoint+reservation / stable facility、无 Entity / Q) | 新 claim 拒绝 | 不适用 | TTL 赢 CAS 后按 terminal | `reuse_terminal_obligation` 仅在 session/owner/bytes/checkpoint generation 全匹配时复用,0;随后以 stable `placed_id` 重绑;missing/conflict fail closed,禁止 insert/+Q | 重复/并发 restore 同样复用,0 | 任一 terminal cause 原子 reservation→outbox,0;不得先删 checkpoint | -| `OutboxPending`(`Pending` / 无 / Q) | 永久拒绝 | no-op | 到期可 claim,0 | 只 hydrate obligation | R10 CAS `Pending→InFlight`,0 | 重复 handoff 返回 stable `delivery_id` 既有 row,0 | -| `OutboxInFlight`(lease / 无 / Q) | 拒绝 | no-op | lease expiry CAS 回 `Pending` 或达阈值→`DeadLetter`,0 | rebase lease,不 attach | 暂败写 attempts/backoff→`Pending`,0;CAS loser 0 | inventory/spill+receipt+`Committed`+删 obligation/扣 quota 同事务→`CommittedReleased`,`-Q`;已有 receipt 幂等完成,0 | -| `DeadLetter`(outbox / 无 / Q) | 拒绝 | no-op | 停自动 retry,0 | 只 hydrate obligation | 授权 retry CAS→`Pending`,0 | 授权 resolve 原子持久化完整 `ResolvedDisposition`+删 obligation/扣 quota→`DispositionReleased`,`-Q`;无 disposition 不释放 | -| `CommittedReleased`(receipt / 无 / 0) | 旧 generation 拒绝 | no-op | no-op | 只确认 terminal | receipt 重放,0 | no-op,禁止再扣 | -| `DispositionReleased`(disposition / 无 / 0) | 旧 generation 拒绝 | no-op | no-op | 只确认 terminal | disposition 重放,0 | no-op,禁止再扣 | +### P1 — framework + craft adapter -不变量:每个 `session_key` 恰有零或一个 durable owner;counter 等于六个占用态的 `Q` 之和。只有未接收 escrow/output 的 admission 取消,或 receipt/disposition 已持久化的终结事务可 `-Q`;reservation→outbox、restore/retry/timeout 与所有 CAS loser 均为 0。 +只落 `server/src/session/{mod.rs,registry.rs,lifecycle.rs}`、S reducer、registry/busy API 和 contract pins。framework 可在 master Wave 允许时先合入,但 craft production adapter 只有 master 的真实 artifact/cutover rows 全部满足后才启用;不以 mock、fixture 或未存在的 R6/R4/R7 consumer 宣称可达。 -### 2.3 durability 决议矩阵 +### P2 — alchemy / forge / lingtian -| 状态族 | durability | 断线/关服 | 跨维 | -|---|---|---|---| -| craft | `Checkpointed` | 保存进度、批次与 escrow;重连保持 paused,显式 reopen 后 resume;到 TTL 结算 `SuspensionExpired` | 非自愿 teardown,全退未消费 escrow | -| alchemy furnace/session | `Checkpointed` | furnace + session 原子 checkpoint,不离线推进;到 TTL 结算并回收 facility claim | 非自愿 teardown;退款/产物先落交付事务 | -| forge station/session | `Checkpointed` | station + session + 已扣材料原子 checkpoint;到 TTL 结算并回收 facility claim | 非自愿 teardown;不得遗留 `station.session` | -| gathering | `Volatile` | teardown | teardown | -| lingtian actor sessions | `Volatile` | teardown | teardown | -| mineral | `Volatile` | teardown,解除 ore claim | teardown | -| spiritwood | `Volatile` | teardown,清 session 与 `settling` | teardown | -| external container / TSY search / extract | `Volatile` | 清 owner、进度与 target claim | teardown | -| `PendingInsightOffer` | `Volatile` + deadline | disconnect/timeout 清除 | 清除 | -| `TsyPresence` | R3 checkpointed auxiliary state | R3 Slice 保存 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to` 与版本;guarded restore 通过后才重新 attach component、开放 TSY 请求 | 由 transport 事务显式 enter/exit | - -后续某个 volatile 域若改成“起 session 即预扣不可重建资源”,必须先把该域改为 `Checkpointed` 或证明 teardown 可无损返还;不得维持默认易失再补日志。 - -### 2.4 busy 语义 - -- `SessionRegistry::try_acquire(BusyClaim)` 是唯一生产入口;各域私有 `has_session` 只能作为迁移期断言,P3 结束时删除。 -- busy 至少区分 player-exclusive、target-exclusive、facility-exclusive,并由集中 conflict matrix 判断;不能以“两个不同 Component 可以共存”代表允许并发。 -- TSY `Search` 与 `Extract` 必须双向冲突:搜刮中拒绝撤离,撤离中拒绝搜刮;取消/完成/断线/跨维均释放两侧 claim。 -- persistent session `Suspended` 时保留其逻辑 facility/escrow claim,避免其他玩家覆盖炉/站状态;runtime `Entity` binding 必须释放。 -- R4 的 `GateSpec` 消费 R1 busy 查询 API。距离、维度、所有权检查仍由 R4 实现,R1 不修改 `client_request_handler.rs`。 - -### 2.5 pause、cancel、refund 与 delivery - -- screen close 是 `Pause`,不是 `VoluntaryCancel`。当前 wire 只有 `CraftCancel`(`proto/bong/envelope.proto:257,1309`),P1 必须与 R6/R4 协调显式 open/pause/resume intent;不能只删除 client cancel 后让 server 继续 tick。 -- `VoluntaryCancel` 保留域内已公开的经济规则,例如 craft 未完成部分返还 70%;UI 必须有明确取消动作,不能由 Esc、断线、跨维或关服冒充。 -- cause/refund matrix 固定如下,adapter 与 contract pin 必须逐项覆盖: - - `Completed`:正常完成后把完整产物交给 durable outbox,cause 固定为 `Completed`;不 refund inputs,不允许借用取消/断线原因。 - - `VoluntaryCancel`:按域内公开规则结算;craft 未完成部分返还 70%。 - - `Disconnect`:`Checkpointed` 进入 `Suspended`,不退款,checkpoint 保留未消费 escrow;`Volatile` 全退并终止。 - - `DimensionChange`:dimension-scoped session 全退未消费 escrow,经 delivery 后终止,不进入 `Suspended`。 - - `Shutdown`:`Checkpointed` 进入 `Suspended`,不退款,checkpoint 保留未消费 escrow;`Volatile` 在 inventory 可访问时全退并终止。 - - `InvalidRestore`:全退未消费 escrow;已完成产物只走一次 delivery,不与 inputs 双发。 - - `SuspensionExpired`:全退未消费 escrow/交付已完成产物;先提交 durable terminal/outbox state,再幂等释放 claim 并终止,delivery 失败不重新占用 claim。 - - `AuthorizedAdministratorClosure`:授权管理员主动结案仍按 durable terminal/outbox handoff 结算;全退未消费 escrow/交付已完成产物,审计记录 operator principal 与 reason,不得伪装为 `InvalidRestore` 或 `SuspensionExpired`,delivery 失败不重新占用 claim。 -- `Disconnect`/`Shutdown` 的 checkpoint 保留与 terminal cause 的 refund 互斥;terminal delivery 使用 §2.1 的 crash-atomic transaction 或 durable outbox/receipt,不得保留既可恢复又待退款的 session,也不得在 receipt 未落盘时宣称交付完成。 -- refund 也走 R10 delivery 垫层;满包不得退化为日志告警。R10 冻结的 `deliver(delivery_id, items) -> Delivered | Spilled(fallback)` 与 durable `delivery_id`/receipt 见 `plan-refactor-inventory-core-v1.md` 接入面。 - -## 3.1 决议(原开放问题 §N.1) - -### 决议 1:不是“全部持久化”或“复制 craft 表”二选一,而是显式 durability + R3 单一出口 - -- **明确结论**:需要重连/重启恢复的 session 全部注册为 R3 Slice/registry checkpoint;短时可无损结束的 session 显式标 `Volatile`。禁止为每个域复制 craft 私表 + join 自愈代码。 -- **实施方案**:craft 作为 P1 adapter,把现有表/hydration 行为收进 R3 暴露的 guarded load/flush API;P2 的 alchemy/forge 用同一 API;其余按 §2.3 teardown。join hydration 由 `on_reconnect` 统一触发。 -- **边界与拒绝理由**:全部 session 持久化会把 Entity/短时 target claim 写进 DB,扩大 stale restore 面;全部易失会丢已扣材料和长进度;复制 craft 表会继续制造七套 migration、flush 与 hydration。R1 不实现 `persistence/**`,只消费 R3 API。 -- **双锚点**:本 plan §2.2-§2.3;`plan-refactor-persistence-slices-v1.md` 接入面(`load(guarded) / autosave / flush_on_shutdown / tick_rebase`);现有 join 基准 `server/src/network/craft_emit.rs:884`。 - -### 决议 2:Checkpointed 暂停保留 escrow;terminal 非自愿中断全退 - -- **明确结论**:`Checkpointed` 的 `Disconnect`/`Shutdown` 是可恢复暂停,不退款,未消费 escrow 与 checkpoint 一起保留;`DimensionChange`、`InvalidRestore`、`SuspensionExpired` 等 terminal 非自愿原因才全退未消费 escrow。`Volatile` 遇到非自愿中断立即全退并终止。只有玩家明确触发 `VoluntaryCancel` 时,才执行域内既有损耗规则。 -- **实施方案**:所有 adapter 以 §2.5 的完整 `TerminationCause` matrix 分支;checkpoint 和 refund 在同一次 transition 中互斥;refund/output 经 R10 delivery,qi 经 ledger。craft 保留 70% 主动取消规则,但关屏只 pause。 -- **边界与拒绝理由**:若断线/关服同时 checkpoint escrow 与退款,会在恢复时双发;若一律把断线变 terminal,又会破坏短期恢复。terminal 非自愿原因统一全退,避免网络故障、跨维强制 teardown 或恢复失败施加进度折损;主动取消保留成本,避免套利。 -- **双锚点**:本 plan §2.1、§2.5;`plan-craft-close-pause-loss-v1.md` P0-P3;当前先 teardown 后 grant 反例 `server/src/network/client_request_handler.rs:16764-16861`;R10 `plan-refactor-inventory-core-v1.md` 接入面。 - -## 4. 吸收清单验真(2026-08-03) - -| plan 短名 | P0 裁决 | R1 处理 | -|---|---|---| -| craft-close-pause-loss | **真缺陷**:client 关屏发 cancel,server 无 paused gate | P1 首宿主冻结 `Running/Paused` 与显式 cancel;跨端 intent 由 R6/R4 接缝 | -| craft-session-reconnect-lock | **已闭环只归档**:CraftStore 已登记 disconnect clear;join 同发 idle/active session state | 不重复实现,以现有 hydration 作为 `on_reconnect` 基准 | -| placed-container-session-lifecycle-gap | **部分闭环**:断线清锁已有测试;跨维 owner lock 仍缺 | P3 收编 lifecycle/lock teardown;请求距离门归 R4 | -| tsy-container-disconnect-lock-leak | **真缺陷**:search progress/`searched_by` 无统一 disconnect cleanup | P3 收编 volatile target claim | -| tsy-search-extract-concurrent-busy | **真缺陷**:search/extract 只查各自进度 | P3 以集中 conflict matrix 双向互斥 | -| world-transport-tsy-relog-presence | **真缺陷**:位置/维度持久化而 `TsyPresence` 未同事务恢复 | R1 定 transport 生命周期,R3 保存 auxiliary state | -| client-insight-offer-strand | **部分真实**:server pending 只有 chosen/reject 清理,无 deadline;client modal 属 R7 | R1 收编 server timeout/teardown;R7 处理展示 | -| alchemy-furnace-persistence | **真缺陷**:furnace/session 仍为内存权威 | P2 adapter + R3 checkpoint | -| alchemy-takeback-full-inventory-loss | **真缺陷**:先 `end_session` 后 grant | R1 改 teardown/commit 顺序;R10 提供 delivery | -| forge-c2s-session-wiring | **已闭环只归档**:start session 与 blueprint page 已真实分发 | 不重复实现 | -| bot-handcraft-craft-outcome-timeout | **旧证据不足,不形成 R1 owner**:报告来自脏 debug server;当前已有 `scripts/bot/scenarios/production_handcraft_stone_knife.py` | P4 仍跑 clean-main craft bot,失败再以新证据立 owner | -| forge-outcome-full-inventory-loss | **真缺陷、R10 主责**:满包只有 `grant skipped` | R1 只提供 AwaitingDelivery→CommitTerminal 合同;R10 实现 fallback | -| lingtian-session-disconnect-server | **真缺陷**:Resource 持续 tick,未按 actor 断线清理 | P2 迁移六类 actor session | - -### 4.1 覆盖审计差分 - -按总纲 §6 要求枚举 active/skeleton 的 session、disconnect、busy、container、full-inventory 与 dimension-gate 候选后: - -- `forge-session-range-dimension-gate` 已由 `plan-refactor-c2s-gate-v1.md` 吸收清单明确登记为在飞项;实现落在 R4 的 `GateSpec`/`client_request_handler.rs`,不追加 R1 owner。R1 的 `on_dimension_change` 与 busy API 仅供 R4 查询。 -- `alchemy-furnace-scope-gate` 同样已由 R4 吸收;R1 负责炉 session 生命周期,不复制距离/维度 gate。 -- `forge-session-enum-unstripped` 是 R6/client bridge 契约修复,不是 server session 生命周期。 -- `tsy-extract-disconnect-stale`、`woliu-vortex-disconnect-residue`、`niche-guardian-cross-session-leak` 及其他 client `*Store` 残留归 R2/R7,不进入 R1 文件域。 -- 本轮未发现应新增到 R1 权威吸收清单、但尚无 owner 的 plan。 - -## 5. 文件所有权与接缝 - -- **R1 独占**:`server/src/session/`、七域 `session.rs`、`network/craft_emit.rs` 的 session tick 区,以及迁移时删除的域内私有生命周期代码。 -- **R3 独占**:`server/src/persistence/**`、player load/autosave/shutdown flush、`SessionDeliveryOutbox` 与 checkpoint terminalization transaction;R1 仅消费 checkpoint/restore/outbox hook。 -- **R4 独占**:`server/src/network/client_request_handler.rs` 与 `network/gate/`;R1 仅暴露 busy/session query。 -- **R6 独占**:proto/Rust mirror/converter、client wire encode/send、S2C bridge/router 与跨端 pause/resume 契约变更;R6 消费 Agent 轨冻结的 TypeBox/schema/dist,不修改 agent-side source-of-truth artifacts。 -- **R7/R2 独占**:client Screen/HUD 与 Store disconnect 清理;R7 的 Craft Screen 只消费 R6 wire 和总纲 §4 的 `CraftOpen.target`,Workbench screen 必须原样保留 server response 提供的 `workbench_key`。 -- **R10 独占**:`server/src/inventory/**`、`InventoryTxn::deliver`、inventory/spill mutation 与 `DeliveryCommitReceipt` 的原子提交;R1 只生成 stable `delivery_id` 并决定何时 terminalize/释放 claim。 -- **阶段放行矩阵**: - - framework-only:R3 P1 合入后可落 `InteractionSession`、registry、lifecycle 与不触达生产 wire/delivery 的 contract pins。 - - craft producer path:Agent `A-CS` 先冻结 `CraftOpen.target`、`CraftPause`、`CraftResume`、`CraftSessionStateV2` TypeBox/schema/dist;随后 R6 P1 交付冻结契约的 proto/Rust mirror/bridge/client wire plumbing → R4 P1 交付 production decode/dispatch,并把当前进程 `workbench_key` 经 R3 P4 mapping 转成 stable `placed_id` 后执行 owner/phase/busy gate → R7 P2 交付 close/pause、显式 cancel、reopen/resume UI producer/consumer(R2 P1 `CraftStore` 提供 disconnect lifecycle)→ R3 P1 交付 §2.2.2 reservation/outbox/terminal checkpoint transaction,R3 P4 交付 placed workbench hydrate/rebind → R10 P1 冻结 `deliver`/receipt contract,R10 P2c 交付真实 outbox worker;全部合入后 R1 才能启用并验收 craft adapter。 - - alchemy/forge:还须 R10 P2c worker 消费其 terminal outbox;否则只能停在 `AwaitingDelivery`。 - - `TsyPresence`:R3 P1 注册 auxiliary Slice,且 R3 P4 restore parity 常绿后,R1 P3 才能 attach 新 runtime `Entity` 并开放 TSY 请求。 -- 禁止用 mock、registry 单测或临时持久层越过上述门宣称端到端完成;依赖未齐时 phase 保持未完成。 - -## 6. 后续阶段交付物 - -### P1 — 框架 + craft 首宿主 - -- 新增 `server/src/session/{mod.rs,registry.rs,lifecycle.rs}`,包含 §2 全部 symbol;framework-only 可在 R3 P1 后落地,但不启用生产 craft adapter。 -- Agent `A-CS`、R6 `CraftOpen`/`CraftPause`/`CraftResume`/StateV2 契约、R4 production handler/gate、R7 P2 Craft Screen producer/consumer(消费 R2 P1 `CraftStore`)、R3 P1 reservation/outbox、R3 P4 stable `placed_id` hydrate/rebind、R10 P1 `deliver`/receipt contract 与 R10 P2c production worker 全部合入后,craft 才迁移到 `SessionRegistry`:关闭 screen pause、显式 cancel、重开 resume;手搓不需要 placed mapping,Workbench checkpoint 必须存 `placed_id` 而非 `workbench_key`/Entity。 -- P1 contract pins 还必须逐行覆盖 §2.2.2:首次 reserve `+Q`、busy race/校验失败取消 `-Q`、重复取消/CAS loser 零变化、restore/retry matching reuse 零变化、missing/conflicting restore fail closed、handoff 零变化、Pending/InFlight/DeadLetter timeout/retry 零变化、receipt/disposition 恰一次 `-Q`;并在 Workbench 路径强杀重启后证明 checkpoint stable `placed_id` 能绑定 hydrate 后的新 Entity,旧 `workbench_key` 不能恢复。 -- contract pins:六态转换、stable owner 重绑、disconnect-before-save、dimension-before-transfer、busy 冲突、delivery commit gate,以及 §2.5 八个 `TerminationCause` 的逐变体正反测试;明确断言 `Completed` 只交付产物且不退款,`Disconnect`/`Shutdown` checkpoint 不退款,terminal refund 不保留可恢复 escrow。delivery pins 必须在 transaction 的“outbox 写入前/写入后 terminalize 前/receipt+inventory commit 后 ack 前”逐点模拟进程退出并重载,断言 payload 总数恰为一次;所有 checkpointed delivery 均经真实 R10 P2c worker。 -- `SuspensionPolicy` pins:TTL 前后、永不重连、授权管理员结案、offline client 伪造 allow-listed `Username` 仍拒绝、普通玩家/未知 executor/伪造 owner/跨 owner target 拒绝、过期/重连/关服竞态、重复扫描幂等、指数退避 cap、10 次/7 天 dead-letter、人工 retry/resolve 权限,以及 claim 回收后另一玩家可获取同一设施。另须持久化一半已消耗的 lease,重启后 rebasing 只保留原剩余 TTL;连续第二次/第三次重启不得刷新 `suspended_at_tick`,并覆盖 `remaining-1`、精确边界、`remaining+1`。 -- `PendingInsightOffer` pins:创建后正常选择/拒绝、deadline 前后与 off-by-one、同 tick response-versus-timeout 竞态、断线/跨维/关服清除,以及过期后不得再次接受选择。 +checkpointed furnace/station 使用 S-07/S-14 与 O-08/O-16;delivery failure 只推进 O-13,不重新 attach。lingtian 六类 actor 使用 volatile S-08;不在线 tick。 -### P2 — alchemy / forge / lingtian +### P3 — gathering / mineral / spiritwood / world interaction -- R3 checkpoint API 与 R10 P2c production outbox worker 都就绪后,alchemy furnace/session 与 forge station/session 才原子 checkpoint;终态 handoff 先写 durable outbox/terminal state,再在提交成功后幂等释放 runtime claim;delivery 失败不得重新 attach session 或 claim。 -- 灵田六类 `ActiveSession` 共用 volatile adapter;断线/跨维/关服不再 tick 或结算离线 actor。 -- qi refund/release 测试从 `SPIRIT_QI_TOTAL` 与 ledger 不变量取值,不写新物理常数。 +volatile adapter 统一释放 target claim;TSY Search/Extract 双向 busy;TsyPresence 仅在 R3 coupled snapshot guarded restore 通过后 attach。 -### P3 — gathering / mineral / spiritwood / 世界交互 +### P4 — bot/e2e/归档 -- 删除三域私有 store 生命周期分支,迁入 registry。 -- external container、TSY search/extract 使用 target claim;所有终态释放 busy。 -- R3 `TsyPresence` auxiliary Slice 的 guarded load/autosave/shutdown flush/tick-rebase 与 restore parity 常绿后,才把 presence 与 player position/dimension 对拍;新 `Entity` 只在身份/版本/维度校验通过后 attach,随后开放 TSY 请求,不再出现“人在 TSY、presence 不在”。 +执行 §6 derived index,确认所有吸收项已有唯一 owner,再补 Finish Evidence;不修改其他轨文档的 authority 表述。 -### P4 — bot e2e + 归档 +## 8. 开放问题 -加入并常绿: +1. retention 数值已由 §3.1 冻结;R3 P0 只需选择 table/index/compaction scheduling,不得扩大 horizon/上限或在重启时刷新 age。 +2. R3 需选择 `CancelPending` 独立表或 reservation row 内嵌 retry metadata;两者必须实现相同 O-07/O-25 语义。 +3. 具体 domain escrow/refund 数值沿用各域既有 plan;不得在本 canonical protocol 新增经济规则。 -1. `session_disconnect_cleanup` -2. `session_dimension_transfer` -3. `session_restart_recovery` -4. `session_busy_mutex` -5. `session_full_inventory_delivery` -6. `session_suspension_reclamation`:永不重连、TTL 边界、授权管理员结案、普通玩家/未知 executor/伪造 owner/跨 owner target 拒绝、过期与重连竞态、重复扫描、claim 释放与 outbox handoff 均不泄漏或双发 claim/escrow。 -7. `session_delivery_crash_atomicity`:在 outbox/terminal checkpoint/receipt/inventory-or-spill/ack 各持久化边界强杀重启,断言 stable `delivery_id` 最终只交付一次;失败按指数退避,在 10 次或 7 天转 `DeadLetter`,且从 handoff 起不占设施 claim。 -8. `session_termination_cause_matrix`:逐项命中 `Completed`、`VoluntaryCancel`、`Disconnect`、`DimensionChange`、`Shutdown`、`InvalidRestore`、`SuspensionExpired`、`AuthorizedAdministratorClosure`,对拍 checkpoint/refund 互斥与 inputs/output 不双发,并断言管理员 principal/reason 被审计记录。 -9. `session_craft_pause_resume_wire`:真实 client producer → R6 proto/bridge → R4 production handler/gate → R1 registry → S2C hydrate → client reopen consumer 全链路。 -10. `session_tsy_presence_relog`:R3 guarded presence Slice 完整恢复 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version 并与 position/dimension 同事务对拍;分别注入 facility 不存在、owner 不匹配、dimension 不匹配、version 不匹配四类 guarded-load 拒绝,任何一类都不 attach、不开放 TSY 请求;恢复后 death-drop 仍正确区分原带物与 TSY 所得。 -11. `session_pending_insight_offer_deadline`:覆盖创建后选择/拒绝、deadline 前后与 off-by-one、同 tick response-versus-timeout 竞态、断线/跨维/关服清除,以及过期后不得再次接受选择。 +## Finish Evidence -另回归现有 `production_craft_disconnect_resume.py`、`production_craft_cancel_full_inventory_refund.py`、`production_handcraft_stone_knife.py`。对应 implementation PR 合入后,按总纲 §7 为吸收项补 Finish Evidence 并做每轨一次 docs-only 批量归档。 +> 迁入 `finished_plans/` 前填写各 S/O row 的实现路径、关键 commit、测试命令与数量、真实 producer→consumer→cutover evidence、receipt retention evidence,以及未完成的跨轨依赖。 diff --git a/docs/plans-skeleton/plan-agent-craft-schema-v1.md b/docs/plans-skeleton/plan-agent-craft-schema-v1.md index 3d66537d3..71f0c7610 100644 --- a/docs/plans-skeleton/plan-agent-craft-schema-v1.md +++ b/docs/plans-skeleton/plan-agent-craft-schema-v1.md @@ -1,46 +1,60 @@ # plan-agent-craft-schema-v1 — Craft lifecycle Agent schema 生产批次(总纲 A-CS) -> 所属总纲:`plan-refactor-master-v1.md` §3/§4/§4.1/§6.11。唯一职责:原子生产 craft lifecycle 的 TypeBox source、generated JSON Schema 与 committed `@bong/schema` dist,供 R6 消费。 +> 所属总纲:`plan-refactor-master-v1.md`。遵循 PR 1902 settled rulings:TypeBox 是 shape/validation source of truth;A-CS 拥有 craft domain contracts,R6 只拥有 generation/wire machinery 与 atomic activation。 ## 阶段总览 | 阶段 | 交付物 | 状态 | |---|---|---| -| P0 | inventory、shape 与 variant baseline | ⬜ | -| P1 | TypeBox source、registry/exports、contract tests | ⬜ | +| P0 | 实际 source/union/registry/generated/dist inventory 与 row set | ⬜ | +| P1 | TypeBox domain contracts、union/registry/exports、contract tests | ⬜ | | P2 | generated schema、dist、freshness gate | ⬜ | -| P3 | R6 handoff SHA/digest/evidence | ⬜ | +| P3 | R6 handoff SHA/digest/derived counts | ⬜ | -## 接入面与冻结合同 +## 1. P0 实际 inventory(禁止虚构 baseline) -- **进料**:总纲 §4 canonical shape、R1 phase/identity/generation、`agent/packages/schema/src/{craft.ts,client-request.ts,server-data.ts,schema-registry.ts}`。 -- **出料/owner**:本 plan 唯一修改 `agent/packages/schema/**`,原子交付 `CraftOpen`、`CraftPause`、`CraftResume`、`CraftSessionStateV2` 的 source/generated/dist;R6 只消费记录 SHA,不回改 Agent artifacts。 -- `CraftOpen.target` required:`Handcraft | Workbench { workbench_key }`。key 为 unsigned `u64` decimal string;缺失、负数、小数、科学计数法、空白、`>u64::MAX` 均拒绝,且不是 durable identity。 -- Pause/Resume 只含 required `session_key + generation`;不得夹带 target 或替代 Cancel。StateV2 覆盖 `Running | Paused | Suspended | AwaitingDelivery | DeliveryPending | Terminal`,逐 phase 锁定 identity/generation required/forbidden 组合;production union 删除 V1。 -- P0 从 registry 计算实时基线;新增三种 C2S 后目标 113→116。若主线计数变化,先同步总纲/R6 pins,禁止硬编码旧数假绿。 +2026-08-04 对当前 source 的核对结果:`craft.ts` 有 standalone `CraftStartReqV1` 与 `CraftSessionStateV1`,但 `ClientRequestV1`、`ServerDataV1`、`SCHEMA_REGISTRY` 对 `craft_start`、`craft_cancel`、`workbench_open`、craft session state 均无 membership。Rust/proto 的生产变体数不是 TypeBox baseline,不能写成 `113→116`。 -## 阶段交付物 +| Row ID | production chain 所需 contract | 当前 TypeBox 状态 | A-CS 终态 | +|---|---|---|---| +| A-01 | `CraftOpen` | absent;取代 standalone/legacy `CraftStartReqV1` 的 lifecycle intent | required `target = Handcraft | Workbench { workbench_key }` | +| A-02 | `CraftPause` | absent | required `session_key + generation` | +| A-03 | `CraftResume` | absent | required `session_key + generation` | +| A-04 | `CraftCancel` | server/proto 已 live,TypeBox envelope absent | 纳入 authoritative C2S union;不由 Pause 替代 | +| A-05 | `WorkbenchOpen` | server/proto 已 live,TypeBox S2C union absent | 纳入 authoritative S2C union;产生 request-local `workbench_key` | +| A-06 | `CraftSessionStateV2` | 只有 standalone V1,S2C union absent | V2 纳入 union;删除 V1 production export/membership | -### P0 — inventory / shape +P0 必须枚举每个 row 的 source→export→`ClientRequestV1`/`ServerDataV1`→`SCHEMA_REGISTRY`→generated→dist/runtime import 状态,并计算**当时真实** C2S/S2C type set。P1/P2 完成后的目标 count 是该集合实际去重后的派生值;主线 drift 先由其 owner 修复或显式登记,不把 Rust count 冒充 TypeBox count,也不为凑常量越界修无关 contract。 -枚举 source→registry→generated→dist/export 全路径,冻结四合同字段矩阵、C2S/S2C type-set baseline;本 plan 只拥有 craft 四合同,全量 drift guard 仍归既有 drift plans。 +## 2. 冻结 shape -### P1 — TypeBox / tests +- A-01 `CraftOpen.target` required:`Handcraft | Workbench { workbench_key }`。key 是 unsigned `u64` decimal string;缺失、负数、小数、科学计数法、空白、`>u64::MAX` 均拒绝;它不是 durable identity 或 capability。 +- A-02/A-03 只含 required `session_key + generation`,不得夹带 target 或替代 Cancel。 +- A-04 保留显式取消语义与既有 production discriminant;字段 inventory 必须与 proto/Rust live contract 对拍后冻结。 +- A-05 `WorkbenchOpen.entity_id` 是 A-01 `workbench_key` 的 producer;TypeBox/JSON 使用 decimal string,并锁 `0/1/u64::MAX`。 +- A-06 覆盖 R1 `Running | Paused | Suspended | HandoffPreparing | Ended` 的 client projection;delivery obligation 状态不伪装成 resumable gameplay phase。逐 phase 锁定 identity/generation required/forbidden 组合。 -实现四合同并注册/export 到 `ClientRequestV1`、`ServerDataV1`、`SCHEMA_REGISTRY`。正例覆盖 Handcraft、Workbench key `0/1/u64::MAX`、Pause/Resume identity 与 StateV2 全 phase;反例覆盖 malformed target/key、identity 缺失或多余字段、非法 phase 组合。 +## 3. 阶段交付物 + +### P1 — TypeBox domain content + +在 `agent/packages/schema/src/{craft.ts,client-request.ts,server-data.ts,schema-registry.ts,index.ts}` 落 A-01..A-06,注册/export 到相应 envelope 与 registry。正反样本覆盖 target/key、Pause/Resume identity、Cancel、WorkbenchOpen producer 和 StateV2 phase。P1 只可声明 contract-first,不宣称 producer→consumer production 可达。 ### P2 — generated / dist -更新 `GENERATED_SCHEMA_FILES`、四单项 schema、`client-request-v1.json`、`server-data-v1.json` 与 committed dist。source/registry/generated/dist 必须同一提交;删除或篡改任一层时 freshness test 失败,clean checkout 可 import 新 symbols。 +更新 `GENERATED_SCHEMA_FILES`、六个单项 schema、envelope schemas 与 committed dist。source/registry/generated/dist 同一提交;删除或篡改任一层时 freshness test 失败,clean checkout 可 runtime import A-01..A-06。 ### P3 — R6 handoff -记录 merge SHA、schema digest、最终 C2S/S2C count 与 exports。R6 只接受该 SHA并做 TypeBox↔proto/Rust/client roundtrip;不一致退回对应 owner。A-CS 未归档时所有 craft production gate 阻塞。 +记录 merge SHA、schema digest、A-row source/generated/dist/runtime-import 对拍,以及从最终 registry **程序化计算**的 C2S/S2C counts/type sets。R6 只接受该冻结版本,并按 PR 1902 负责 proto/Rust/client machinery;production activation 由 master cutover row 管理,不是 A-CS P3 的下游实现验收。 -## 验收与边界 +## 4. 验收与边界 -必跑 `cd agent/packages/schema && npm test`、`cd agent && npm run build -w @bong/schema`;必验四合同正反样本、union membership、freshness、runtime import 与 variant count。不改 proto、Rust、Java、gameplay handler/session,也不替代 `tiandao-schema-dist-start`。 +- 必跑 `cd agent/packages/schema && npm test` 与 `cd agent && npm run build -w @bong/schema`。 +- acceptance 逐 A-01..A-06 证明正反 sample、union membership、registry membership、freshness、generated/dist/runtime import;count 断言从 registry 派生,不出现手写 113/116。 +- 不改 proto、Rust、Java、gameplay handler/session;不吸收全量 schema drift plan。若无关 drift 阻断 envelope freshness,记录真实 owner/prerequisite,不擅自扩 scope。 +- 跨轨 owner/order/cutover 仅引用 master §3/§4.1 与 PR 1902,不在本 plan 复制依赖箭头。 ## Finish Evidence -> 迁入 `finished_plans/` 前填写落地路径、commit SHA/日期、测试结果、四 symbols 的 source/generated/dist/runtime-import 对拍,以及遗留项。 +> 迁入 `finished_plans/` 前填写 A-01..A-06 的落地路径、commit SHA/日期、测试结果、registry-derived counts/type sets、source/generated/dist/runtime-import 对拍及遗留 drift owner。 diff --git a/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md b/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md index 5bce621d2..04447eb61 100644 --- a/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md +++ b/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md @@ -1,10 +1,10 @@ # plan-refactor-c2s-gate-v1 — C2S 请求统一门禁中间件 + client_request_handler 巨石拆分(重构轨 R4) -> 所属总纲:`plan-refactor-master-v1.md`。一句话:给当前 113 种 C2S 请求建统一的声明式门禁层(距离/维度/所有权/状态前置),并在 R6 的三个 craft lifecycle intent 落地后扩展到 116 种,同时把 20082 行的 `client_request_handler.rs`(单函数 2438 行、17 个 SystemParam)拆成按域注册的 handler 模块——"只信裸坐标可跨维远程操作"这一整簇(20+ 份 plan)从此在架构上不可能。 +> 所属总纲:`plan-refactor-master-v1.md`。一句话:给 authoritative registry 派生的全部 C2S 请求建立声明式门禁层(距离/维度/所有权/状态前置),并把 20082 行的 `client_request_handler.rs` 拆成按域注册的 handler 模块。 ## 现状证据(2026-07-27 侦察) -- `network/client_request_handler.rs:522` `handle_client_request_payloads` 单函数跨 522-2960 行,巨型 match 当前覆盖 `ClientRequestV1` 的 113 个生产变体;R6 P1 新增并接通 `CraftOpen`/`CraftPause`/`CraftResume` 后才扩为 116。`CombatRequestParams` 一个 SystemParam 30 个字段。 +- `network/client_request_handler.rs:522` `handle_client_request_payloads` 单函数跨 522-2960 行;当前 production Rust match 与 TypeBox union 已知存在 drift,因此 P0 必须分别 inventory 并在 A-CS/R6 完成后以 authoritative registry 派生目标集合,禁止手写 113/116。`CombatRequestParams` 一个 SystemParam 30 个字段。 - 门禁分散手写:8 个文件各自定义 `*_MAX_DISTANCE`/`*_RANGE_SQ`(`client_request_handler.rs:468-469`、`craft/workbench.rs:76` Chebyshev、`mineral/probe.rs`、`npc/relic.rs`、`supply_coffin/authority.rs`、`zhenfa/network_array.rs` 等),距离度量都不统一;`CurrentDimension` 在 handler 内被 13 处内联比对,无统一 helper。 - 后果即 bughunt 大簇:跨维开工作台/布阵/夺舍/交易/拾取、无 reach 校验放方块、先扣物品后校验吞丹、无所有权校验拆棺。 - `zone-lookup-overworld-hardcode`:zone 查找硬编码主世界,是维度感知缺失的底层同源。 @@ -18,10 +18,9 @@ ## 阶段 -- ⬜ P0 设计收口 + 吸收清单验真:当前 113 个变体普查(每个标注应有的门禁四元组现状);冻结 `GateSpec` 与拒绝回执语义;R6 P1 合入 `CraftOpen`/`CraftPause`/`CraftResume` 后,基线目标扩为 116;等 #1287(冷却重构,同文件大改)merge 定基线。 -- ⬜ P1 门禁中间件落地:gate 层上线,先给"已知漏洞簇"与 R1 craft `CraftOpen`/`CraftPause`/`CraftResume` 挂 spec;R6 落地三种 intent 后,同一提交进入 production decode/dispatch 和全量 gate matrix,旧内联校验保留并行断言一个版本期。 -- ⬜ P2 巨石拆分批次 A:巨型 match 拆为按域 handler 注册表(combat/production/world/social/npc 五组),行为不变,bot 场景锁住;inventory pickup handler 必须从 ECS `CurrentDimension`、authoritative position/observation range 与 owner/private permission 构造 R10 `PickupAuthorization`,禁止仅凭 client XYZ/instance id 调用 txn,且把 R10 accepted/rejected outcome 交给 R6 emit API。该 pickup consumer 仅在 R10 P3 pickup/merge txn、R5 P3 attrition API 与 R6 P4 receipt API 均合入后实施,不得以 mock 或旧 R6 P1 schema 接线。 -- ⬜ P3 巨石拆分批次 B + 全量挂 spec + 删旧:R6 craft intents 合入后 116 变体全部声明门禁(含显式 `no_gate` 声明,杜绝静默无门禁);删除各域内联距离常量与重复维度判断。 +- ⬜ P0 设计收口 + 吸收清单验真:分别普查 production Rust match 与 TypeBox authoritative registry,记录 drift owner;每个 registry-derived C2S variant 标注门禁四元组,冻结 `GateSpec` 与拒绝回执。 +- ⬜ P1 门禁中间件落地:gate 层上线,先给已知漏洞簇挂 spec;craft production decode/dispatch 只在 master M-07/M-10 activation 时接入 A-CS A-01..A-04,runtime key negatives 属本阶段 owner evidence。contract-first handler declarations 可先合入,但不以未激活 stub 宣称 live。 +- ⬜ P2 巨石拆分批次 A:巨型 match 拆为按域 handler 注册表(combat/production/world/social/npc 五组),行为不变,bot 场景锁住;pickup handler 的 authorization/txn/receipt 接线只在 master M-14/M-15 与 R5 ledger artifact 可用后实施,不得以 mock 或旧 schema 接线。- ⬜ P3 巨石拆分批次 B + 全量挂 spec + 删旧:authoritative registry-derived C2S type set 全部声明门禁(含显式 `no_gate`);删除重复距离/维度判断。 - ⬜ P4 bot 验收 + 吸收 plan 批量归档。 ## 吸收清单(短名省略 plan-bughunt- 前缀与 -v1 后缀) @@ -33,7 +32,7 @@ skeleton:alchemy-furnace-scope-gate、block-place-reach-gate、coffin-reclaim- - 独占:`network/client_request_handler.rs`(拆解)、新 `network/gate/`、各域内联距离/维度校验行的删除。 - 不碰:`*_emit.rs` S2C 侧(R6)、session 内部(R1)、inventory 事务(R10)。 -- 依赖:基线等 #1287 merge;R1 craft adapter 还要求 R6 先冻结 `CraftOpen`/`CraftPause`/`CraftResume`,本轨随后原子交付 production handler/gate;建议在 R6 emit 侧稳定后开 P2(同在 network/ 目录,文件不相交但相邻),其余 P0/P1 可先行。 +- 依赖/order/cutover 只引用 master M-02/M-05/M-07/M-10 与 PR 1902;本 plan 不建立第二套 sequencing。R4 P1 的 runtime rejection pins 在 R4 自有阶段完成,不作为 R6 P1 验收。 ## bot 验收场景 @@ -41,7 +40,7 @@ skeleton:alchemy-furnace-scope-gate、block-place-reach-gate、coffin-reclaim- 2. `gate_reach`:超距放方块/开炉/采灵田→拒绝;贴脸→放行。 3. `gate_ownership`:拆他人棺/取他人容器→拒绝。 4. `gate_state_precondition`:给丹先校验后扣(满包/死亡目标不吞丹);丹毒超阈值禁服。 -5. `gate_matrix_sweep`:当前 113 变体做参数化合法/超距/跨维扫描;R6 三个 craft intent 落地后追加 owner mismatch、wrong phase、conflicting busy claim、duplicate/replay、stale/mismatched session identity 负例,以及合法 owner + 正确 Running/Paused phase 正例,目标覆盖 116 变体并断言与声明的 `GateSpec` 一致——这是本轨的主回归门。 +5. `gate_matrix_sweep`:从 authoritative registry 派生 type set,逐 variant 执行声明的合法/超距/跨维/no_gate trace;craft 另覆盖 owner mismatch、wrong phase、conflicting busy claim、duplicate/replay、stale identity、runtime-key malformed/stale/despawned/cross-dimension/out-of-range 与合法 S-02 admission。 ## 开放问题(pre-P0 收口) diff --git a/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md b/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md index 4afed94db..eb7940dfe 100644 --- a/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md +++ b/docs/plans-skeleton/plan-refactor-client-ui-base-v1.md @@ -16,7 +16,7 @@ - **进料**:R2 的 `SessionScopedStore`(Screen 订阅的一律是会话态 store);`ServerDataRouter` handler(一律经 client-thread marshal 投递 UI)。 - **出料**:Screen/HUD 展示;HUD 纪律沿用既有 memory 约束(未解锁隐藏不灰掉、沉浸式极简)。 - **共享类型**:新 `BongScreenBase`(生命周期 + 订阅 + 关闭清理)、`DiffListWidget`(推广 craft 范本)、`BongKeybindRegistry`(注册时冲突检测 + 测试期断言)、`ClientThreadMarshal` helper、`ScreenOpenPolicy`(礼貌抢屏:战斗中/已有模态时排队)。 -- **跨仓库契约**:本轨不定义 wire,消费总纲 §4 与 R6 契约。Idle/no-session 初次 `CraftOpen` 必须带 target:手搓为 `Handcraft`;Workbench handler 从成功 S2C `WorkbenchOpen.entity_id` 读取 u64 decimal-string `workbench_key`,构造 screen 时注入并不可变保留,初次请求原样发送 `Workbench { workbench_key }`,不得猜测或改写。R4 校验后分配 session identity。已有 session close 只发 hydrated identity/version 的 `CraftPause`,显式取消只发 `CraftCancel`,仅匹配的 server-hydrated `Paused` session 发一次 `CraftResume`。P2 pins 覆盖两种 target、u64 key 边界、response→screen→request 不变、missing/malformed key 不开 Workbench screen,以及四条 intent 不互相替代;terminal/`AwaitingDelivery`/`DeliveryPending`/stale identity 不发 Resume。 +- **跨仓库契约**:本轨不定义 wire,只消费 A-CS A-row、master M-02/M-07/M-09/M-10 与 R1 S-row。Idle/no-session 初次 `CraftOpen` target 为 `Handcraft` 或 retained `Workbench { workbench_key }`;close 发 `CraftPause`,explicit cancel 发 `CraftCancel`,仅匹配 server-hydrated `Paused` session 发一次 `CraftResume`。R1 `HandoffPreparing`/`Ended` 或 stale identity 均不发 Resume;delivery Pending/InFlight/DeadLetter 是 obligation 状态,R7 不把它们存为 resumable gameplay phase。 ## 阶段 @@ -36,7 +36,7 @@ skeleton:alchemy-screen-fill100-eviction 与 alchemy-screen-fill-overflow( - 独占:client 全部 Screen/`ui/`/`hud/` 结构性改动、keybind 注册、`InspectScreen.java`。 - 不碰:store 生命周期接口(R2 域,本轨消费);`network/` 桥与 router(R6 域——marshal helper 由本轨提供、在 handler 注册处的接线与 R6 协调);server 一切。 -- 依赖:R2 P1 先合(基类要绑 `SessionScopedStore`);Craft Screen 接线还需 R6 `CraftOpen`/`CraftPause`/`CraftResume` 契约与 R4 production handler/gate 先就绪,R1 craft adapter 不得在本轨 P2 合入前宣称 close/pause/reopen/resume 端到端可达;与 R6 在其他 handler 投递点的接缝于 P4 前对齐。 +- 依赖:本轨只引用 master M-02/M-07/M-08/M-09/M-10;R2 Store、R6 machinery、R4 gate 与 R1 session 的 production 接缝在 master atomic activation row 完成前只能提交 contract pins,不宣称端到端可达。 ## 验收 diff --git a/docs/plans-skeleton/plan-refactor-master-v1.md b/docs/plans-skeleton/plan-refactor-master-v1.md index a7137936e..c73603e31 100644 --- a/docs/plans-skeleton/plan-refactor-master-v1.md +++ b/docs/plans-skeleton/plan-refactor-master-v1.md @@ -48,57 +48,44 @@ | V | `plan-bot-e2e-coverage-v1`(既有 skeleton 直接促升,不另立) | bot 场景 P1-P6 扩容 + CI 假绿修复 + build token 脚本 | `scripts/bot/**`、CI | ~9 | | 基建 | `plan-registry-datafication-v1`(既有 skeleton 直接促升) | 硬编码配方/功法/方块表迁数据 + fail-fast | 三张表 | 自身 | -## 3. 波次与依赖 +## 3. 唯一跨轨波次与裁决 -- **Wave 0(立即并行)**:V(bot 骨干 + build token 最先)、R3、R5、R2、registry-datafication;同时全部轨道的 P0(设计收口 + 吸收清单验真)都可开工。§6.11 登记的 Agent `A-CS`(`plan-agent-craft-schema-v1`)也在本波启动并独立提交,必须先于 R6 P1 合入。 -- **Wave 1**:R6(R2 + Agent `A-CS` 合入后)、R7 基础设施(R2 合入后)、R1 framework-only(仅 `InteractionSession`/registry/lifecycle 骨架,R3 P1 合入后;不得宣称 craft pause/resume 或 delivery 生产闭环)。 -- **Wave 2**:R4(#1287 + R6 P1 后)、R9(R5/R6/R2 P1 后)、R10(R3 P1 后);R1 宿主迁移按显式 gate 分批放行:craft 需 Agent `A-CS` + R3 P1 reservation/outbox + R3 P4 placeable-entity stable `placed_id` 持久化/hydration/runtime 映射 + R6 craft intents + R4 craft handler/gate + R7 P2 Craft Screen + R2 P1 已登记的 `CraftStore` + R10 P1 `deliver`/receipt contract + R10 P2c production outbox worker,alchemy/forge 同样需 R3 P1 与 R10 P2c,`TsyPresence` 需 R3 P1 auxiliary Slice 与 R3 P4 restore parity。 -- 近完成独立 plan(§6.9)在 Wave 0 窗口内优先收尾清场。 -- R5 P1(字段收私有的全仓编译大爆破)挑在飞 PR 队列清空的窗口单独合入。 +本节与 §4.1 是计划族唯一 start/order/cutover authority。各 track plan 只能引用 row ID,不能复制或反写依赖图。PR 1902(`origin/docs/master-r9-r6-ownership-adjudication`)五项裁决作为 settled context:TypeBox source of truth、domain content/generation machinery 分权、contract-first 不等于 live、production activation 原子切换、master Wave 表唯一 authority。 -## 4. 文件所有权矩阵(防并行打架,冲突时以本表为准) +- **Wave 0 — source/provider foundations**:V、R2、R3、R5、registry-datafication 与各轨 P0;Agent A-CS 执行 M-01。 +- **Wave 1 — contract-first infrastructure**:R6 M-02(A-CS 后)、R1 M-03 framework-only(R3 storage seam 后)、R7 UI 基础设施(R2 后)。这些 row 可交付 declarations/converters/reducers/tests/stubs,但不得宣称 craft live。 +- **Wave 2 — durable providers and atomic activation**:R3 M-04/M-05/M-12、R10 M-06/M-13/M-15、R4 M-07/M-15、R7 M-08、R1 M-09/M-10、R6 M-14/M-15。M-10 是唯一 craft production cutover:real state producer、wire emit/bridge、C2S gate/handler、client producer/store 与旧 receiver removal 必须在安全 merge unit 内一起启用;dropped-loot projection/pickup 则分别由 M-14/M-15 原子切换,禁止长期 dual emit 或以 fixture 冒充 live。 +- R1 其余 checkpointed adapters 在 M-04/M-06 后迁移;volatile adapters 仍须使用 canonical S/O traces。TSY attach 另等 R3 coupled snapshot guarded restore。 -- `persistence/**`+autosave=R3;`session/`+7 域 session.rs=R1;`client_request_handler.rs`+`gate/`=R4;`*_emit.rs` 公共层+`proto_convert.rs`=R6;`qi_physics/**`+qi 字段直写行=R5;`inventory/**`=R10;cast/AV emit+skill 注册=R9。 -- client:Store 生命周期+`clearClientStateOnDisconnect` 区段=R2;channel 注册区段+桥+router=R6(与 R2 同文件不同区段,merge 前互 fetch);Screen/hud/keybind/InspectScreen=R7;combat cast store=R9。 -- `agent/packages/schema/src/**`、generated JSON Schema 与提交的 `@bong/schema` dist=§6.11 Agent 轨;craft schema prerequisite 的 production owner 是 Agent 轨的独立 craft-schema 交付批次,必须在 R6 P1 前提交并验收 TypeBox source、generated schema/dist 与变体计数。理由:TypeBox 是 agent-side source of truth,生成物必须与 source 同 owner 原子提交;让 R6 同时改 source 和 wire 会违反本总纲的 agent 排除边界并形成双 owner。R6 独占 proto/Rust mirror/converter、client wire encode/send 与 bridge/router plumbing,只消费该冻结版本并负责一次性 wire 反映。R6、R4、R7、R1 的 craft gate 均以本处 ownership 决议为准。 -- CraftOpen target bridge(跨轨 canonical contract):`CraftOpen` 必须携带 required `target` 判别联合:`Handcraft` 或 `Workbench { workbench_key }`,不得省略。`workbench_key` 是现有成功 S2C `WorkbenchOpen.entity_id` 的 ECS `Entity::to_bits()` locator:逻辑/Rust 类型 `u64`、protobuf `uint64`、JSON/TypeBox 为无符号十进制字符串;它只在当前进程的首次请求中定位 runtime entity,不是授权能力,也绝不写进 checkpoint。普通手搓发送 `Handcraft`;`WorkbenchScreen` 必须从 response 保留该 key 并在初次 `CraftOpen` 原样回传。R4 将 key 解析为 entity 后重验实体存活且携带 `WorkbenchBlock`、玩家同维且在既有距离内,并执行 owner/busy/facility gate。missing、malformed、stale、despawned、跨维或越距 key 均拒绝;R7 不得从 UI 猜测或改写 key。`CraftPause`/`CraftResume` 只携带 hydrated session identity/version,不重复 target。 -- **Stable workbench addendum**:R4 还须从 R3 P4 registry 把 runtime entity 映射为唯一 `placed_id` 后才交 R1 持久化 claim;restore 以 `placed_id` 绑定本次 hydrate 的新 Entity。missing/duplicate/unhydrated mapping 按 `InvalidRestore` fail closed。 -- 任何轨道碰他轨文件:只允许"消费对方冻结后的 API",不允许改对方独占文件;接缝 API 归被依赖方定义。 +## 4. 文件所有权矩阵 -### 4.1 跨轨 artifact producer/consumer 闭环表 +- `persistence/**`+autosave=R3;`session/`+七域 adapter=R1;`client_request_handler.rs`+`gate/`=R4;emit/converter/client network machinery=R6;`qi_physics/**`=R5;`inventory/**`=R10;cast domain=R9。 +- client Store lifecycle=R2;Screen/hud/keybind=R7;combat cast store=R9。 +- `agent/packages/schema/**` craft domain content/generated/dist=A-CS;R6 不修改 Agent artifacts,只消费冻结 SHA。 +- `workbench_key` 是 request-local runtime locator;R4 校验当前 Entity 后必须映射 R3 stable `placed_id`,R1 checkpoint 仅保存 `placed_id`。missing/duplicate/unhydrated mapping fail closed。 +- 任一轨碰他轨 artifact 只消费冻结 API;API owner、production consumer 和 cutover evidence 均以 §4.1 为准。 -覆盖本计划族为 R1 链新增/改变的 artifact;sample、mock、类型引用或“后续接线”不算 consumer。新增 artifact 必须先增行。 +### 4.1 Artifact / cutover ledger -| Artifact | Producer | Production consumer | Gate / evidence | +| Row ID | Artifact / cutover | Producer | Production consumer / completion evidence | |---|---|---|---| -| craft TypeBox source + generated schema + committed dist | Agent `A-CS` P1-P2 | R6 P1 mirror/converter;agent runtime | A-CS SHA;正反 sample、freshness、variant count | -| craft proto/Rust mirror/converter/samples | R6 P1 | R4 P1 decode;R1 P1 emit | `A-CS`;buf/sample、116 C2S/144 S2C | -| client craft API + bridge/router/handler | R6 P1 | R7 P2 producer;R2 P1 `CraftStore` | R2+A-CS;proto→store/client pins | -| `CraftSessionStateV2` S2C | R6 P1 `craft_emit` 读 R1 | R2 store、R7 reopen | phase/identity/generation roundtrip | -| request-local `workbench_key` | WorkbenchOpen response,R6 P1 固型 | R7 原样回传;R4 runtime lookup | u64 boundary、response→request 不变 | -| stable `placed_id` + hydrate registry | R3 P4 | R4 runtime→stable claim;R1 restore rebind | placeable P0-P2;新 App hydrate/唯一 mapping | -| validated craft admission | R4 P1 | R1 P1 `try_acquire` | R6+R3 P4;stale/跨维/越距/mapping 缺失拒绝 | -| `InteractionSession`/phase/cause | R1 P1 | R1 P1-P3 adapters | R3 P1;状态/原因矩阵 | -| `SessionKey`/`PlayerKey`/generation | R1 P1 | R3/R4/R6/R7/R10 | rebind、stale generation reject | -| durability/transition | R1 P1 | 每个 domain adapter | checkpoint+P2c;显式 durability/六态 pins | -| suspension policy/deadlines | R1 P1;R3 rebase | R1 scanner;R10 P2c scanner | TTL/retry/lease 连续重启不刷新 | -| maintenance permissions | R1 P1 | admin terminate;P2c retry/resolve | authenticated allow;offline name/伪造 deny | -| registry/`BusyClaim` | R1 P1 | R4 `GateSpec`;adapters | conflict matrix + race tests | -| audit event | R1 contract;R3/R10 emit | audit sink | hashed principal;无 payload;transition 可追溯 | -| insight deadline transition | R1 P1 | response gate/timeout scanner | deadline/race/lifecycle cleanup | -| R7 Open/Pause/Cancel/Resume intents | R7 P2 | R6→R4→R1 | R2/R6/R4;四 intent 分离、resume once | -| `CraftStore` lifecycle | R2 P1;R6 handler | R7 P2 | disconnect clear、stale reject | -| checkpoint(escrow/`placed_id`/generation) | R1 payload;R3 P1 writer | R3 guarded restore→R1 | workbench 需 R3 P4;禁止 Entity | -| reservation | R3 P1 new/reuse/cancel API | R1 admission/restore/handoff | R1 §2.2.2 全矩阵 | -| quota | R3 P1 | R1 admission;R3/R10 release | counter=sum(obligations)、row/byte races | -| outbox + `delivery_id` | R3 P1 terminal txn | R10 P2c worker | crash points、唯一 owner、完整 payload | -| lease/retry/dead-letter CAS | R3 primitives;R10 P2c 驱动 | R10 worker/scanner/operator | rebase first;CAS races | -| `InventoryTxn::deliver` | R10 P1 | R10 P2c | stored+spilled=requested、失败不变 | -| `DeliveryCommitReceipt` | R10 P2c 原子 txn | R3 dedupe;R1 terminal confirm | replay 不重发/二次释放 | -| `ResolvedDisposition` | R10 P2c/operator + R3 txn | R3 cleanup/audit;R1 confirm | 无 disposition 不释放;重放幂等 | -| coupled TSY snapshot | R3 P1 | R1 P3 reconnect/gate | routine/disconnect/shutdown 全旧或全新 | - -闭环规则:任一端未合入,下游仅可提交接口/pins,不得启用 adapter。outbox consumer 固定 R10 P2c,stable workbench provider 固定 R3 P4;phase 删除/改名须同步本表与 wave gate。 +| M-01 | craft TypeBox A-01..A-06 source/generated/dist | A-CS P1-P3 | R6 M-02;registry-derived type sets、freshness、runtime import | +| M-02 | craft proto/Rust/client wire machinery、emit API/stub | R6 P1 | M-10 activation;只验 converter/roundtrip,不要求 downstream live | +| M-03 | `InteractionSession`、S reducer、registry/busy API | R1 P1 framework | R1 adapters;S-01..S-23 traces | +| M-04 | reservation/quota/outbox/CAS/history storage | R3 P1 | R1 handoff + R10 worker;O-01..O-27 durable traces | +| M-05 | stable `placed_id` hydrate registry | R3 P4 placeable batch | R4 runtime→stable lookup;R1 S-10 restore | +| M-06 | `InventoryTxn::deliver` + `SessionDeliveryWorker` | R10 P1/P2c | O-10..O-21/O-26/O-27;payload digest binding、receipt atomicity | +| M-07 | craft C2S production gate/handler | R4 P1 | R1 admission;malformed/stale/despawned/cross-dimension/out-of-range rejection | +| M-08 | Craft Screen Open/Pause/Cancel/Resume producers | R7 P2 | R4 handler;intent separation、Resume once | +| M-09 | `CraftStore` lifecycle + wire handler contract | R2 P1 + R6 M-02 | R7 P2;disconnect clear/stale generation reject | +| M-10 | craft production atomic activation | R1/R4/R6/R7 integration owner | prerequisites M-01..M-09;real response→screen→request→session→emit/store trace;删除 V1/旧 receiver | +| M-11 | checkpointed domain migration | R1 P1/P2 adapters | M-04 + M-06;S-07/S-14 与 O-08/O-16 crash trace | +| M-12 | coupled TSY presence/position/dimension snapshot | R3 P1 | R1 P3 attach;routine/disconnect/shutdown 全旧或全新 | +| M-13 | dropped-loot metadata、capacity 与纯 migration provider | R10 P1/P2 | R3 hydration;Public/OwnerOnly admission、spill/delete atomic seam | +| M-14 | dropped-loot guarded hydration 与 recipient-specific projection/page | R3 P2 + R6 P1/P2 | 同 revision 分片替换、visibility/dimension/range/owner filter;超限 fail closed | +| M-15 | pickup authorization、merge/placement、qi attrition 与 receipt atomic activation | R10 P3 + R5 ledger | R4 handler + R6 receipt;authorize→txn→attrition→drop delete 单事务 | + +闭环规则:类型引用、mock、fixture 或单独 stub 不算 production consumer。上游 artifact 可 contract-first 合入;只有表中 consumer/cutover evidence 存在后才可宣称 live。任何新增跨轨 artifact 先登记本表;track plan 只引用 M-row 与 canonical S/O/A-row。 ## 5. 工作流(GPT tmux 多会话) @@ -137,7 +124,7 @@ 1. 9 条轨道全部归档(各自 bot 场景常绿 + 吸收 plan 全部归档/验伪结案); 2. 三个 2 万行级 god file(inventory/mod.rs、client_request_handler.rs、persistence/mod.rs)不复存在,最大单文件 < 3000 行; -3. `qi_current` 裸写编译不过;client 无未登记的会话态 store;116 C2S 变体全部有显式 GateSpec/no_gate 声明;28 旁路 channel 收编或豁免登记; +3. `qi_current` 裸写编译不过;client 无未登记的会话态 store;authoritative registry 派生的全部 C2S 变体均有显式 GateSpec/no_gate 声明;28 旁路 channel 收编或豁免登记; 4. bot 场景数从 ~30 增至 ≥80,CI e2e 是唯一主门禁且无已知假绿。 5. `flash-review` label 下 open issue 全部显式处置(fixed / dup / 验伪关闭 / 促升 skeleton,见 §10),无静默积压。 diff --git a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md index 450e84d9a..8f415ad85 100644 --- a/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md +++ b/docs/plans-skeleton/plan-refactor-persistence-slices-v1.md @@ -13,7 +13,7 @@ ## 接入面 - **进料**:SQLite(bong.db,沿用)、`shutdown.rs`(#1261 之后的关服链路)、`CultivationClock`(相对 tick 基准)。 -- **出料**:统一 Slice API 供各域注册:`load(guarded) / autosave(cadence) / flush_on_shutdown / tick_rebase`;R1 的 session 持久化钩子、各域运行态表全部走它。`tick_rebase` 对 R1 suspension lease 与 outbox 的 `next_retry_tick`/`created_at_tick`/`lease_until` 保持相对剩余时长/已消耗 age;R1 冻结默认 `SESSION_SUSPENSION_TTL_TICKS = 1_728_000` 与扫描 cadence `1_200` ticks,本轨不得把重启折算成无限续租或刷新 outbox 退避/7 天 age。为 terminal delivery 提供 `SessionDeliveryOutbox`:事务内只提交 outbox 插入与 session checkpoint terminalization/删除,提交成功后 runtime gameplay claim 释放必须可依据 durable terminal/outbox 状态幂等重放;禁止仅持久化 session、把 delivery id 留在内存。每个 checkpointed session 的 admission/restore 必须先调用 `reserve_terminal_obligation(session_key)`:在同一 `BEGIN IMMEDIATE` 事务中 conditional-update 单例 `SessionDeliveryQuota { used_rows, used_bytes, generation }`(仅当 row/bytes 上限均有余量)并插入唯一 `SessionDeliveryReservation { session_key, reserved_bytes = SESSION_DELIVERY_MAX_PAYLOAD_BYTES }`;任一步失败整笔回滚。等价非 SQLite 实现必须以 quota generation CAS 提供同一线性化点,禁止先读剩余额度再另事务插 reservation。terminal handoff 在 checkpoint/outbox 同事务把 reservation 转移给 outbox,不重复计量;只有 receipt `Committed` 或带完整 payload 的 audited `ResolvedDisposition` 在同一事务删除 obligation 并扣减 counter,CAS loser 不得释放 quota。payload 增长不得超过 reservation,上限不足必须在接受新 escrow/产物前 fail closed。dead-letter 从自动扫描集合移除且不占 claim,但继续占 quota。`TsyPresence` 与 player position/dimension 作为同一 coupled snapshot:routine autosave、disconnect save、shutdown flush 均须共用同一事务/版本边界,不能由独立 Slice 提交不同快照。 +- **出料**:统一 Slice API:`load(guarded) / autosave(cadence) / flush_on_shutdown / tick_rebase`。R1 session persistence 只消费该接口;tick rebase 保留 suspension/retry/lease 的真实剩余时长与已消耗 age,不刷新租约。R3 durable 实现严格投影 R1 O-01..O-27:`reserve_new_terminal_obligation`(O-01/O-02)、`reuse_terminal_obligation`(O-04)、durable `CancelPending` reconciliation(O-05..O-07)、reservation→outbox atomic handoff(O-08/O-09)、claim/retry/dead-letter CAS(O-10..O-20)、receipt/disposition retention、bounded tombstone 与 watermark GC(O-21..O-24)。`TsyPresence` 与 player position/dimension 仍为独立 coupled snapshot contract;routine autosave、disconnect、shutdown 必须共用 transaction/version,crash 后只能全旧或全新。 - **共享类型**:新 `server/src/persistence/` 多文件模块(按域拆表定义 + 迁移链保持线性单入口);`PlayerSliceRegistry`(对齐 #1290 skeleton 的方向,直接吸收它)。 - **跨仓库契约**:零 wire 改动。 - **qi_physics 锚点**:任何带 qi 的快照持久化/恢复不得造成账面变化;恢复失败的兜底路径必须走 `release_dormant_qi_to_zone` 而非丢弃(对齐守恒律红旗清单)。 @@ -21,11 +21,11 @@ ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:53 张表普查归域;冻结 Slice trait(载入守护语义:读失败 = 保留旧行 + 告警 + 只读降级,绝不写回空态;flush registry;tick rebase 协议);等 #1288/#1289/#1261/#1259 merge 后定基线。 -- ⬜ P1 框架落地 + 巨石拆分:`persistence/` 按域拆文件(迁移链不变、行为不变);Slice 框架上线,KnownTechniques/Lifecycle(在飞 PR 的成果)平移为首批宿主;冻结 inventory slice hydration seam 及 `MigrationOutcome` consumer 边界,R3 不复制 inventory 网格规则,待 R10 P1 提供纯幂等迁移函数后接入,不得提前引用 R10 常量/实现。注册 R1 所需的 `TsyPresence` auxiliary Slice,字段固定为 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version,并接入 guarded load、**与 player position/dimension 共用同一 snapshot/version 的原子 autosave**、`flush_on_shutdown`、`tick_rebase`;任何 autosave、disconnect save 或 shutdown flush 都不得让三者跨快照提交,crash 后只能全旧或全新。落 `SessionDeliveryOutbox`、`SessionDeliveryQuota`、`SessionDeliveryReservation` 表与 atomic handoff/reservation API,stable `delivery_id`、完整 payload、cause、attempts/next_retry/created_at/tick_epoch/state、单调 generation 与 in-flight lease 全部持久化。P1 acceptance 必须同时锁定:TsyPresence/position/dimension 在 routine autosave、disconnect save、shutdown flush 的每个逻辑写边界均由 fault-injection harness 强杀并重启,结果只能全旧或全新;reservation 的 quota conditional update + unique insert 原子性;terminal handoff 时 reservation→outbox 不重复计量;Committed/ResolvedDisposition 扣减 quota 与 obligation 删除原子性;checkpoint terminalization/deletion 与 outbox insert 同一 SQLite transaction 全成或全败;以及 outbox insert 前、terminalization 前、commit 后 ack 前的 crash-injection pins。P3 只复用 coupled-snapshot harness 做 flush registry 长尾回归并处理其余 flush/tick-rebase 批次,不得把首次原子性证据延后。 -- **P1 reservation protocol addendum(规范性覆盖并废止接入面旧 `reserve_terminal_obligation` create-only 语义)**:实现 R1 §2.2.2 的 `reserve_new_terminal_obligation`(仅首次 admission,`+Q`)、`reuse_terminal_obligation`(matching restore/retry,`ΔQ=0`)和 `cancel_unconsumed_reservation`(busy loser/claim 后校验失败/无 checkpoint 残留,恰一次 `-Q`)。restore missing/conflicting owner/bytes/generation fail closed,禁止 insert/+Q;handoff、retry、lease expiry与 CAS loser 均 0,receipt/disposition 原子 `-Q`。 -- ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;身份主键统一(identity-persist-key-mismatch)。仅在 R10 P1 容量契约合入后,dropped-loot hydration guard 才引用 `MAX_DURABLE_DROPPED_LOOT_ENTRIES` 与 `DroppedLootRegistry::try_insert/try_insert_batch`;超限进入 load-failure guard/只读降级并告警,禁止截断、驱逐或空表覆盖。同步实现 spill/pickup recoverable transaction seam,使 source mutation、attrited item、zone/ledger、drop insert/delete 与 transaction id 原子提交;crash/retry pins 常绿后才放行 R10 P2a Public writer path。 -- ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部"节流落盘"域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥(coffin-autosave-inflight-race 模式);R3 的其余非耦合 slice 按 registry 收编。`TsyPresence` 三者原子 autosave/disconnect/shutdown 语义已在 P1 冻结,P3 仅补全 flush registry 接线与 crash 回归,不得把 coupled snapshot boundary 延后或缩窄为 shutdown-only。 -- ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性(heiwushi)等——逐个按 Slice 框架补表。加入 `TsyPresence` guarded relog parity 契约测试和 TSY 维度重启 bot 场景,断言失败加载不 attach presence、成功恢复后才重新开放 TSY 请求。另拆两个 inventory consumer 子批次:R10 P1 + R3 P2 seam/compatibility pins 后,dropped-loot hydration 调 `migrate_legacy_dropped_loot_entry` 补 `owner=None`/`visibility=Public`,且先于 R6 projection/page;R10 P3 后,inventory-layout overflow 用真实 player/position/dimension `SpillContext` 调 `migrate_legacy_inventory_layout` 并持久化。任一 migration/context/capacity/persistence 失败均保留旧行可重试,两批不得绑成同一 gate。 +- ⬜ P1 框架落地 + 巨石拆分:保持 migration 单入口;上线 Slice guard/flush/rebase 与 inventory seam。实现 `SessionDeliveryQuota`、`SessionDeliveryReservation`、`SessionDeliveryOutbox`、receipt/disposition/history/tombstone storage 及 R1 O-01..O-27 所需 atomic CAS API。O-05 后 cancel-mark 写失败仍由原 reservation 作为 durable owner(O-25),写成功但后续取消失败则留下 `CancelPending` owner、retry metadata 与 live reconciliation scanner(O-07);只有 O-06 可释放 Q。O-08 同事务固定 payload bytes/SHA-256 digest、terminalize checkpoint 并转 reservation 为 outbox;commit 后 R1 执行 S-14,ack 丢失可从 durable 状态重放。P1 acceptance 覆盖 O-row trace、最后 row/bytes 双连接竞争、exact-max accept/+1 reject、handoff crash points、cancel failure/restart retry、payload digest、receipt/disposition atomic release。另在同阶段交付 `TsyPresence`/position/dimension routine/disconnect/shutdown coupled-snapshot crash harness;P3 仅复用,不延期首次证据。 +- **P1 obligation storage projection**:API、状态、quota effect 与 acceptance 只引用 R1 O-01..O-27;R3 不另设 lifecycle table。实现可选择独立 `CancelPending` 表或 reservation row metadata,但必须保持 O-07 durable retry owner。 +- ⬜ P2 载入守护推广:全部玩家 slice(SkillSet/Wounds/状态 buff/身份键……)收编,#1290 模式全量落地;身份主键统一。dropped-loot hydration guard 使用 R10 M-06 提供的容量/迁移 contract 与 R3 M-04 durable seam;超限进入 load-failure guard/只读降级并告警,禁止截断、驱逐或空表覆盖。spill/pickup recoverable transaction 只在对应 master M-row provider/consumer 全部存在后接入。 +- ⬜ P3 关服 flush + tick rebase 批次:shutdown flush registry 收编全部节流落盘域;绝对 tick 全部改相对基准;autosave/事件写入竞态互斥。`TsyPresence` 三者原子 autosave/disconnect/shutdown 语义已在 P1 冻结,P3 仅补全 flush registry 接线与 crash 回归。 +- ⬜ P4 遗漏运行态补持久化批次:ActiveEvents、TiandaoAttention、状态效果、化虚冷却、灵眼、地表遗缴、散灵珠、可放置实体、dormant 往返身份完整性等按 Slice 框架补表。`TsyPresence` guarded relog parity 与 placed-id hydrate 分别引用 M-12/M-05;migration consumer 只按 master ledger 进入,不复制跨轨顺序。 - **P4 placeable gate addendum**:可放置实体子批次须落实 `plan-bughunt-placeable-entity-restart-loss-v1` P0-P2:持久化 `placed_id`(非 Entity),world/layer ready 后 hydrate `WorkbenchBlock` 并建立唯一 `placed_id→runtime Entity` registry;missing/duplicate/unhydrated fail closed。它是 R4 runtime target→stable claim 与 R1 restore rebind 的 provider,常绿前不得迁移 checkpointed workbench craft。 - ⬜ P5 bot 验收 + 吸收 plan 批量归档。 @@ -48,21 +48,21 @@ skeleton:coffin-autosave-inflight-race、identity-persist-key-mismatch、miner 4. `tick_rebase`:带冷却/再生倒计时重启→断言倒计时按真实流逝折算(对齐 #1289 的 deadline 折算先例);另持久化一半已消耗的 `SuspensionPolicy` lease,重启/rebase 只保留原剩余 TTL,连续重复重启不刷新 lease,并覆盖剩余时长前一 tick、精确边界、后一 tick;outbox 同样覆盖 `next_retry_tick` 剩余退避、`created_at_tick` 已消耗 age、`lease_until` 剩余 lease 在单次/连续重启后的前一 tick、精确边界、后一 tick,断言不会把旧 process-local tick 直接带入新 epoch。 5. `tsy_presence_relog_parity`:进入 TSY→关服 flush→guarded load→只有 `family_id`、`entered_at_tick`、`entry_inventory_snapshot`、`return_to`、schema/version 校验通过才 attach `TsyPresence` 并开放 TSY 请求;损坏或缺失 Slice 保持未 attach 且拒绝请求;恢复后 death-drop 对原带物继续执行 50%/武器保护,对 TSY 所得执行既有 100% 规则。 6. `tsy_presence_snapshot_atomicity`:分别在 routine autosave、disconnect save、shutdown flush 中,向 presence、position、dimension 三个逻辑写入之间注入 crash;重启后断言三者只能全部保留旧 snapshot/version 或全部提交新 snapshot/version,不接受各 Slice 独立提交后碰巧通过 clean restore 对拍。shutdown 路径另断言 session registry 静止后才 flush coupled snapshot。 -7. `session_delivery_outbox_atomicity`:分别在 outbox insert 前、insert 后 checkpoint terminalize 前、事务 commit 后 ack 前强杀重启;payload 边界分成两条:序列化后恰为 `SESSION_DELIVERY_MAX_PAYLOAD_BYTES` 必须成功接受并完成 handoff,`SESSION_DELIVERY_MAX_PAYLOAD_BYTES + 1` 必须在接受新增 escrow/产物前 fail closed。正常路径断言 checkpoint/outbox 恰有一个权威 owner,完整 payload 不丢且 stable `delivery_id` 不变。以两个独立 SQLite connection/线程同时竞争最后一个 row reservation、以及分别竞争不足以容纳两份 reservation 的最后 bytes,屏障同步到事务入口后并发调用 `reserve_new_terminal_obligation`,每种场景必须恰好一个 commit、一个 quota-full,最终 counter 与唯一 reservation 对拍且重启后不超配;另以两个不同 `SessionKey` 竞争同一 busy facility,证明 reservation 均可先提交但 busy loser 通过 `cancel_unconsumed_reservation` 恰好释放自身 `Q`,重复取消/CAS loser 不释放赢家额度。checkpoint restore 与并发 retry 必须以 `reuse_terminal_obligation` 命中原 reservation、counter 增量为零;missing 或 owner/bytes/generation 冲突 fail closed。再覆盖 handoff 转移不双计、Pending/InFlight/DeadLetter retry 与 lease expiry 不改 quota、Committed/ResolvedDisposition 原子释放后新 admission 才成功。 +7. `session_delivery_outbox_atomicity`:执行 R1 O-01..O-27 的 durable traces;覆盖双 connection quota race、busy loser O-05→O-25 或 O-07/O-06、restore O-04、exact-max/+1、handoff crash、lease/dead-letter、payload digest mismatch、history quota O-26、O-16/O-19 atomic release,以及 O-21→O-24 bounded retention/GC。每步断言 `quota_used = sum(active Q)`。 ## 开放问题(pre-P0 收口) 1. 载入守护的玩家体验:只读降级 vs 拒绝进服 vs 回滚到上一备份?需人工拍板。 2. 迁移链是否借机做一次 squash(v1-v39 合并基线)?风险与老存档兼容性需评估。 -3. `ResolvedDisposition` 的完整 payload 不能在释放 obligation quota 后无限增长;本轮不冻结 retention、compaction 或独立 disposition quota,避免引入新的存储策略。延期到总纲 `plan-refactor-master-v1.md §3 Wave 2` 的 R10 durable-delivery 收口窗口,由 R3/R10 共同定义有界保留与释放规则。 +3. receipt/disposition history 的 replay horizon、tombstone 上限、GC watermark 与 history quota 已由 R1 §3.1 冻结;R3 P0 只决定表结构、索引与 compaction 调度,必须执行 O-21..O-27,不能永久保留或在重启时刷新 age。 4. TsyPresence 三者 coupled snapshot 的 routine autosave/disconnect/shutdown crash-injection harness 与跨连接屏障时序是 P1 acceptance 的组成部分:P1 必须在每个逻辑写边界注入 crash 并重启,证明结果只能全旧或全新;P3 只复用该 harness 做 flush-registry 长尾回归,不得延期首次原子性证据。 ## § P0 决议锚点(待 R3 P0 开工时补齐) -- R3 P2/P4 仅在 R10 P1 merge 后引用 `MAX_DURABLE_DROPPED_LOOT_ENTRIES`,不复制常量;dropped-loot hydration consumer 在 R10 migration helper、R3 P2 seam 与 compatibility pins 后执行,且先于 R6 projection/page。 -- inventory-layout overflow consumer 在 R10 P3 后以真实 `SpillContext` 持久化;任一 consumer 失败均保留旧行可重试。 +- **R3 P2/P4** 在 master M-13/M-14 与 R10 migration/provider artifact 完成后接入 dropped-loot hydration;不复制 R10 常量,失败保留旧行可重试。 +- **R3 P1** 的 outbox/reservation 与 coupled snapshot 是 M-04/M-12 的 implementation surface;R1/R10 仅消费冻结接口。 ## 验收与实施边界 -- `cargo test --package bong-server persistence -- --nocapture` 覆盖 Slice guard、flush/tick rebase、migration consumer、dropped-loot bound 与 R1 §2.2.2 reservation 全状态转换;bot e2e 必须覆盖本 plan §bot 验收场景列出的全部七项:`restart_player_slices`、`restart_world_runtime`、`load_failure_guard`、`tick_rebase`、`tsy_presence_relog_parity`、`tsy_presence_snapshot_atomicity`、`session_delivery_outbox_atomicity`。任一场景未合入/未常绿,R3 P5 与下游 craft/TSY 放行均不得完成。 +- `cargo test --package bong-server persistence -- --nocapture` 覆盖 Slice guard、flush/tick rebase、migration consumer、dropped-loot bound 与 R1 O-01..O-27 durable traces;bot e2e 必须覆盖本 plan §bot 验收场景列出的全部七项:`restart_player_slices`、`restart_world_runtime`、`load_failure_guard`、`tick_rebase`、`tsy_presence_relog_parity`、`tsy_presence_snapshot_atomicity`、`session_delivery_outbox_atomicity`。任一场景未合入/未常绿,R3 P5 与下游 craft/TSY 放行均不得完成。 - R3 P1 只冻结 inventory seam,不实现 R10 migration/capacity;P2/P4 等依赖合入后才实现 consumer,且不修改 `server/src/inventory/**`。 diff --git a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md index 1400f12cd..e0d63cfc4 100644 --- a/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md +++ b/docs/plans-skeleton/plan-refactor-wire-s2c-v1.md @@ -14,15 +14,15 @@ - **进料**:各域游戏事件(emit 调用点)、`world` 维度/zone 信息(作用域过滤)。 - **出料**:`bong:server_data` 单通道(目标态:28 旁路全部收编或显式豁免登记);join/重连首包快照集契约(R2 清干净后靠它灌满)。 - **共享类型**:新 server `network/emit/` builder(`scope: Global | Dimension | Zone | Player`);client 桥接层唯一的枚举前缀剥离函数。 -- **跨仓库契约**:proto 形状原则上不动(收编旁路时如需并入 envelope 属破坏性变更,走 buf breaking + samples 同步;agent 侧 TS 只做被动 regenerate,不重构 agent 逻辑)。**不做双轨兼容层**——旁路收编是一次性切换。Craft ownership 与 target 以总纲 §4/§4.1 为准:Agent `A-CS` 批次拥有并先提交 TypeBox source/generated schema/`@bong/schema` dist;R6 只消费该批次记录 SHA 的冻结版本并负责 proto/Rust/client wire plumbing。`CraftOpen.target` 为 required `Handcraft | Workbench { workbench_key }`;`workbench_key` 逻辑/Rust 为 `u64`、protobuf 为 `uint64`、JSON/TypeBox 为无符号十进制字符串,取自成功 S2C `WorkbenchOpen.entity_id` 并由 screen 原样回传;它只在当前进程定位 runtime entity,R4 验证并解析到 R3 stable `placed_id` 后才交 R1 建持久 claim,wire/store/checkpoint 均不得把 `workbench_key` 当 durable identity。`CraftPause`/`CraftResume` 只携带 hydrated `session_key` + `generation`。R6 同时把冻结的 `CraftSessionStateV2` 一次性反映到 proto/Rust converter、`craft_emit`、bridge/router/handler/CraftStore,删除 V1 wire 分支;R7 只消费接口,不在 R6 提前实现 production Resume producer。 +- **跨仓库契约**:proto 形状原则上不动;破坏性变化走 buf/samples gate,不做双轨兼容。craft domain shape 只消费 A-CS A-01..A-06 的冻结 SHA;R6 拥有 proto/Rust/client generation machinery、converter、encode/send API、bridge/router 与 emit API。contract-first artifacts 可先合入但不得宣称 live;真实 producer/consumer 切换只按 master §3/§4.1 与 PR 1902 atomic activation 执行。`workbench_key` 仅为当前进程 runtime locator,wire/store/checkpoint 不把它当 durable identity。 ## 阶段 - ⬜ P0 设计收口 + 吸收清单验真:28 旁路逐个普查(收编 vs 豁免理由);100 emit 文件的重复模式取样归纳 builder API;枚举前缀剥离点全量清点;冻结 scope 语义与 join 首包快照集清单;登记 `rotate-footprint-sync`、`bot-inventory-pack-feedback`,并冻结 `dropped_loot_sync` 分片 envelope(`snapshot_revision/page_index/page_count`、`DROPPED_LOOT_SYNC_PAGE_SIZE = 256`)。 -- ⬜ P1 emit builder + scope 落地:前置为 Agent 轨已按总纲 §4 提交 craft TypeBox source/generated schema/`@bong/schema` dist;R6 不修改这些 artifacts。builder 上线,vfx/audio/env 挂 scope;跨位面时 env/season 全量重发。把冻结 craft schema 一次性反映为 proto/Rust mirror/converter、samples、client encode/send API 与 producer pins:`CraftOpen.target` 正反 roundtrip `Handcraft` 和 `Workbench { workbench_key }`,覆盖 u64/decimal-string 边界及 response→screen→request key 不变;`CraftPause`/`CraftResume` roundtrip required `session_key` + `generation`。同阶段交付 `CraftSessionStateV2` proto/Rust converter、`craft_emit` producer、bridge/router/handler/CraftStore;R6 pins 覆盖 phase/identity、stale generation 与 malformed/out-of-u64 key,R4 pins 覆盖 stale/despawned/跨维/越距 key 拒绝。R7 的匹配 `Paused`→恰发一次 `CraftResume` 留在 R7 P2。dropped-loot projection/page 子项仅在 R10 P2a owner/visibility metadata provider 与 R3 P4 migration/hydration consumer 均合入后启用:按固定页发送,同一 visibility key 只排序/编码一次,禁止 per-client 无界重建。 +- ⬜ P1 emit builder + scope、craft machinery contracts:builder 上线,vfx/audio/env 挂 scope;跨位面时 env/season 重发。对 A-CS A-01..A-06 的冻结 SHA 实现 proto/Rust mirrors、converter、samples、client encode/send API、bridge/router/store contract pins 和 `craft_emit` **API/stub**。P1 acceptance 只覆盖 R6 自有边界:两种 target、u64 decimal-string、Pause/Resume identity、Cancel、WorkbenchOpen、StateV2 phase/generation 的 wire roundtrip 与 malformed reject;不要求尚未允许存在的 R1 state producer、R4 gate 或 R7 intent producer,不宣称 production reachable。dropped-loot projection/page 只在 master M-14 artifact gate 后启用。 - ⬜ P2 client 桥接层收敛:枚举前缀剥离收敛到单点(含 forge-session 修复);`ServerDataRouter` 注册表整备(分域注册文件,不再单个 1547 行 switch 追加);dropped-loot store 仅在同 revision 全部分片收齐后原子替换,缺页/混 revision 保留旧视图并等待重发。 - ⬜ P3 旁路归一批次:28 channel 逐批收编入 server_data envelope 或登记豁免(资源包/握手类可豁免);删除散装 receiver。 -- ⬜ P4 契约 pin 全量化:R6 P1 落地三种 craft intent 后,以 116 C2S + 144 S2C 每变体至少一条正反 sample 对拍(此前 baseline 为 113;schema 改动连 sample 一起改);emit 迁移长尾;inventory receipt 子批次要求 `InventoryEventV1::Moved`(或等价 accepted receipt)携带 request identity、revision、权威 item view,覆盖 schema/sample/convert/emit API、Fabric handler 与 Python decoder。dropped-loot 样本覆盖空/单页/恰 256/257/末页缺失/混 revision。 +- ⬜ P4 契约 pin 全量化:从当时 authoritative registries 派生 C2S/S2C type sets,对每个实际变体至少一条正反 sample;禁止手写 116/144 充当 freshness 证据。emit 迁移长尾;inventory receipt 与 dropped-loot 样本沿用本 plan 既有 contract。 - ⬜ P5 bot 验收 + 吸收 plan 批量归档。 R10 dropped-loot 契约优先:编码前按 recipient dimension/range/owner 投影,仅同 visibility key 复用;rejected receipt 携带 reason/instance/from/to,并测两 recipient 正反可见性。 @@ -37,18 +37,18 @@ skeleton:vfx-audio-dimension-bleed、q-world-season-dimension-env-resync、for - 独占:server `network/*_emit.rs` 公共模式与新 `network/emit/`、`schema/proto_convert.rs`;client `network/`(ProtoServerDataBridge、ServerDataRouter、BongNetworkHandler 的 channel 注册区段)。 - 不碰:`BongNetworkHandler.clearClientStateOnDisconnect` 区段(R2 域,同文件分区段,merge 前互相 fetch);`client_request_handler.rs`(R4);各 emit 的业务语义。 -- 依赖:Agent `A-CS`(`plan-agent-craft-schema-v1`)先合并并冻结 TypeBox source/schema/dist;R2 先合(同文件低冲突区段)。R1 craft adapter 不得在本轨 P1 的 craft 契约 pins 合入前宣称 pause/resume 可达;R4 handler/gate 随后落地。dropped-loot projection/page 子项另硬依赖 R10 P2a owner/visibility provider 与 R3 P4 migration/hydration consumer。 +- 依赖与 production activation 只引用 master §3/§4.1 及 PR 1902。R6 P1 可在 A-CS A-01..A-06 冻结后交付 machinery contracts;R1 state producer、R4 runtime rejection、R7 intent producer 的 production evidence 分别留给其 owner phase,不构成 R6 P1 completion。dropped-loot projection/page 同理消费 master 列出的真实 provider。 ## bot 验收场景 1. `wire_scope_dimension`:主世界 bot + TSY bot 双开,主世界触发 vfx/audio→断言 TSY bot 收不到(P6 protobuf 深断言配合)。 2. `wire_dimension_transfer_resync`:bot 跨位面→断言 env/season/zone 大气全量重发。 -3. `wire_contract_sweep`:对 144 S2C 变体的 sample 对拍在 CI 常绿(配 proto-breaking 深检,联动 V 轨);`CraftSessionStateV2` 另有 phase 全变体、identity/generation required/forbidden 组合、stale generation 拒绝、proto→bridge→handler→CraftStore roundtrip pins。 -4. `wire_join_snapshot`:重连首包快照集完整(与 R2 的 `reconnect_state_freshness` 同场景);checkpointed craft guarded restore 后必须包含单个权威 `CraftSessionStateV2` hydration,server `Paused`/identity/generation 与 client store 对拍,idle/terminal/delivery-pending 不得被伪装成可 Resume。 +3. `wire_contract_sweep`:对 registry-derived S2C type set 做 sample 对拍;A-06 另覆盖 phase/identity/generation wire roundtrip。runtime stale/rejection 与 producer behavior 引用 owner trace,不在本场景伪造。 +4. `wire_join_snapshot`:重连首包快照集完整;craft hydration 只在 master activation row 完成后接入,并引用 R1 S-10/S-16。 ## 开放问题(pre-P0 收口) 1. 28 旁路的收编/豁免分界(低频大 payload 如资源包显然豁免;npc bubble 这类高频小包是否值得并入 envelope)。 2. join 首包快照集的权威清单放哪维护(emit builder 注册时声明 `replay_on_join` 标志?)。 -3. R7 production `CraftResume` producer 的验收证明不在 R6 P1 提前实现;由 R7 P2 在 R6 wire/bridge、R4 handler/gate 与 R2 store 前置合入后完成,依据总纲 `plan-refactor-master-v1.md §3 Wave 2`。R6 P1 只关闭 identity/phase/schema/bridge/store 的负向契约,避免复制未来 producer。 -4. `CraftSessionStateV2.generation` 的 numeric boundary pins(`0`、`u64::MAX`、`MAX→MAX` overflow refusal、off-by-one)本轮只冻结 wire 类型与基本 roundtrip,不扩展 P1 实施;延期到总纲 `plan-refactor-master-v1.md §3 Wave 2` 的 R6/R7 接缝验收窗口,理由是边界策略需与 producer/store generation-CAS 一起收口。 +3. production `CraftResume`、runtime-key rejection 与 real `craft_emit` activation 不在 R6 P1 验收;分别由 R7/R4/R1 owner phase 按 master atomic cutover row 提供。R6 只验 converter/API/stub contract。 +4. `CraftSessionStateV2.generation` 的 `0`、`u64::MAX`、overflow refusal 属 R6 wire pin;producer/store CAS behavior 属其 owner activation trace,不混入上游 phase。 From e8461f270a3fdefd4d86962f8f664eaec582d7d3 Mon Sep 17 00:00:00 2001 From: server_kizuna Date: Tue, 4 Aug 2026 23:34:05 +0800 Subject: [PATCH 14/14] =?UTF-8?q?=E6=94=B6=E6=95=9B=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E9=87=8D=E5=86=99=E7=9A=84=E9=98=B6=E6=AE=B5=E4=B8=8E=E5=A5=91?= =?UTF-8?q?=E7=BA=A6=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Model: cc-sonnet-high Co-Authored-By: Claude --- docs/plan-refactor-server-session-v1.md | 4 ++-- .../plans-skeleton/plan-agent-craft-schema-v1.md | 16 +++++++++++++--- docs/plans-skeleton/plan-refactor-c2s-gate-v1.md | 3 ++- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/docs/plan-refactor-server-session-v1.md b/docs/plan-refactor-server-session-v1.md index 829665565..a1a5265e4 100644 --- a/docs/plan-refactor-server-session-v1.md +++ b/docs/plan-refactor-server-session-v1.md @@ -149,7 +149,7 @@ pub trait InteractionSession { 3. 只有 O-06 成功取消或 O-16/O-19 成功持久化终结结果才 `-Q`。取消标记或删除失败不能把 quota 留成无主 reservation:O-25 由原 reservation 继续担当 durable owner,O-07 由 `CancelPending` row 担当;scanner/retry 均不得凭 age 直接释放。 4. `payload` 是 canonical serialized bytes;`payload_digest = SHA-256(payload)`。worker 的 `DeliveryRequest` 必须从 claimed payload decode,并在 O-16 transaction 中以 digest/semantic item set 对拍;不存在独立可替换的 caller payload。 5. payload 序列化后恰为 `SESSION_DELIVERY_MAX_PAYLOAD_BYTES` 可接受;`+1` 在新增 escrow/output 被接受前 fail closed。payload 一旦进入 O-08 不可增长。 -6. `ReceiptRetained`/`DispositionRetained` 不是无限历史:在 replay horizon 内保留完整结果;达到 producer/outbox GC watermark 后转 O-22 bounded tombstone,再满足 O-24 才 GC。具体 horizon、tombstone 上限和告警阈值由 R3 implementation P0 冻结,并受独立有界 storage quota 约束;O-26 要求在 deliver 前预留该容量。 +6. `ReceiptRetained`/`DispositionRetained` 不是无限历史:完整结果按 §3.1 的 replay TTL 保留,达到 producer/outbox GC watermark 后转 O-22 bounded tombstone,再按 §3.1 的 tombstone TTL 满足 O-24 才 GC。R1 是 retention 数值与边界的唯一 contract owner;R3 只实现 table/index/compaction scheduling 与 O-26 deliver 前容量预留,不得另定 horizon、row/byte limit 或重启时刷新 age。 7. O-08 成功后 gameplay claim 已由 S-14 释放;O-12/O-13/O-14/O-18 不得恢复 session。DeadLetter 无 runtime claim 但继续占 Q。 ## 5. 接缝与 artifact ledger @@ -201,7 +201,7 @@ volatile adapter 统一释放 target claim;TSY Search/Extract 双向 busy;Ts ## 8. 开放问题 -1. retention 数值已由 §3.1 冻结;R3 P0 只需选择 table/index/compaction scheduling,不得扩大 horizon/上限或在重启时刷新 age。 +1. retention 数值与边界由本 canonical contract §3.1 唯一冻结;R3 P0 只选择 table/index/compaction scheduling 并实现这些常量,不得扩大 horizon/上限或在重启时刷新 age。 2. R3 需选择 `CancelPending` 独立表或 reservation row 内嵌 retry metadata;两者必须实现相同 O-07/O-25 语义。 3. 具体 domain escrow/refund 数值沿用各域既有 plan;不得在本 canonical protocol 新增经济规则。 diff --git a/docs/plans-skeleton/plan-agent-craft-schema-v1.md b/docs/plans-skeleton/plan-agent-craft-schema-v1.md index 71f0c7610..1c4df8d23 100644 --- a/docs/plans-skeleton/plan-agent-craft-schema-v1.md +++ b/docs/plans-skeleton/plan-agent-craft-schema-v1.md @@ -32,13 +32,23 @@ P0 必须枚举每个 row 的 source→export→`ClientRequestV1`/`ServerDataV1` - A-02/A-03 只含 required `session_key + generation`,不得夹带 target 或替代 Cancel。 - A-04 保留显式取消语义与既有 production discriminant;字段 inventory 必须与 proto/Rust live contract 对拍后冻结。 - A-05 `WorkbenchOpen.entity_id` 是 A-01 `workbench_key` 的 producer;TypeBox/JSON 使用 decimal string,并锁 `0/1/u64::MAX`。 -- A-06 覆盖 R1 `Running | Paused | Suspended | HandoffPreparing | Ended` 的 client projection;delivery obligation 状态不伪装成 resumable gameplay phase。逐 phase 锁定 identity/generation required/forbidden 组合。 +- A-06 的五个 phase 都 required `session_key + generation`,且禁止 delivery obligation 字段。`Paused` 是唯一 Resume-eligible phase;`Running` 表示已活动且重复 Resume 不得重启;`Suspended` 必须等待 guarded restore 后由新 `Paused` projection 开放 Resume;`HandoffPreparing` 与 `Ended` 均 terminal/non-resumable。 ## 3. 阶段交付物 ### P1 — TypeBox domain content -在 `agent/packages/schema/src/{craft.ts,client-request.ts,server-data.ts,schema-registry.ts,index.ts}` 落 A-01..A-06,注册/export 到相应 envelope 与 registry。正反样本覆盖 target/key、Pause/Resume identity、Cancel、WorkbenchOpen producer 和 StateV2 phase。P1 只可声明 contract-first,不宣称 producer→consumer production 可达。 +在 `agent/packages/schema/src/{craft.ts,client-request.ts,server-data.ts,schema-registry.ts,index.ts}` 落 A-01..A-06,注册/export 到相应 envelope 与 registry。正反样本覆盖 target/key、Pause/Resume identity、Cancel、WorkbenchOpen producer,并按下表逐行 pin StateV2: + +| phase | identity rule | client intent rule | +|---|---|---| +| `Running` | required matching `session_key + generation` | non-resumable;重复 Resume typed reject/no restart | +| `Paused` | required matching `session_key + generation` | 唯一 Resume-eligible;只允许同 identity/generation 恰一次 Resume | +| `Suspended` | required matching `session_key + generation` | non-resumable;等待 guarded restore 后新的 `Paused` projection | +| `HandoffPreparing` | required matching `session_key + generation` | terminalizing;Resume/Pause/Cancel 均不得重新打开 session | +| `Ended` | required matching `session_key + generation` | terminal;所有 gameplay intent stale reject | + +每个 phase 各有 valid sample,并分别覆盖缺 `session_key`、缺 `generation`、stale generation、mismatched identity;含 obligation phase/字段或未知 phase 必须拒绝。P1 只可声明 contract-first,不宣称 producer→consumer production 可达。 ### P2 — generated / dist @@ -51,7 +61,7 @@ P0 必须枚举每个 row 的 source→export→`ClientRequestV1`/`ServerDataV1` ## 4. 验收与边界 - 必跑 `cd agent/packages/schema && npm test` 与 `cd agent && npm run build -w @bong/schema`。 -- acceptance 逐 A-01..A-06 证明正反 sample、union membership、registry membership、freshness、generated/dist/runtime import;count 断言从 registry 派生,不出现手写 113/116。 +- acceptance 逐 A-01..A-06 证明正反 sample、union membership、registry membership、freshness、generated/dist/runtime import;A-06 必须逐 `Running/Paused/Suspended/HandoffPreparing/Ended` 执行 §3 phase/identity/client-intent 矩阵的全部正反 case,不能用通用样本代替;count 断言从 registry 派生,不出现手写 113/116。 - 不改 proto、Rust、Java、gameplay handler/session;不吸收全量 schema drift plan。若无关 drift 阻断 envelope freshness,记录真实 owner/prerequisite,不擅自扩 scope。 - 跨轨 owner/order/cutover 仅引用 master §3/§4.1 与 PR 1902,不在本 plan 复制依赖箭头。 diff --git a/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md b/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md index 04447eb61..a5b1467de 100644 --- a/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md +++ b/docs/plans-skeleton/plan-refactor-c2s-gate-v1.md @@ -20,7 +20,8 @@ - ⬜ P0 设计收口 + 吸收清单验真:分别普查 production Rust match 与 TypeBox authoritative registry,记录 drift owner;每个 registry-derived C2S variant 标注门禁四元组,冻结 `GateSpec` 与拒绝回执。 - ⬜ P1 门禁中间件落地:gate 层上线,先给已知漏洞簇挂 spec;craft production decode/dispatch 只在 master M-07/M-10 activation 时接入 A-CS A-01..A-04,runtime key negatives 属本阶段 owner evidence。contract-first handler declarations 可先合入,但不以未激活 stub 宣称 live。 -- ⬜ P2 巨石拆分批次 A:巨型 match 拆为按域 handler 注册表(combat/production/world/social/npc 五组),行为不变,bot 场景锁住;pickup handler 的 authorization/txn/receipt 接线只在 master M-14/M-15 与 R5 ledger artifact 可用后实施,不得以 mock 或旧 schema 接线。- ⬜ P3 巨石拆分批次 B + 全量挂 spec + 删旧:authoritative registry-derived C2S type set 全部声明门禁(含显式 `no_gate`);删除重复距离/维度判断。 +- ⬜ P2 巨石拆分批次 A:巨型 match 拆为按域 handler 注册表(combat/production/world/social/npc 五组),行为不变,bot 场景锁住;pickup handler 的 authorization/txn/receipt 接线只在 master M-14/M-15 与 R5 ledger artifact 可用后实施,不得以 mock 或旧 schema 接线。 +- ⬜ P3 巨石拆分批次 B + 全量挂 spec + 删旧:authoritative registry-derived C2S type set 全部声明门禁(含显式 `no_gate`);删除重复距离/维度判断。 - ⬜ P4 bot 验收 + 吸收 plan 批量归档。 ## 吸收清单(短名省略 plan-bughunt- 前缀与 -v1 后缀)