Files
ft/CodexOutput/upserver/B3BatchMigration/M2/M2-CP3A原句柄正式业务发布与B01提示接线完整方案-待独立审查.md
Liubing\LB 6813da149a feat(upserver): 保存M2客户端KV迁移至本人统计写检查点
保存独立KV用户服务、固定会话存档与原业务接线,以及C3d资料缓存和C3e本人统计写。
保留原PF/AGG路线、业务公式和时序;榜单读取、好友资料、公告、匹配及首场景闸仍待完成。

验证:ServiceHost两条件各33组646断言;Unity CLI Loop编译0错误269警告。
包含相关方案、评审裁决、测试及状态文档。未操作服务器、正式账号或打包。
2026-09-30 19:19:05 +08:00

74 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# M2 CP3A 原句柄正式业务发布与 B01 提示接线完整方案(待独立审查)
日期:2026-09-29。本文是**编码前方案,不是编码批准**。受审基线为 [`M2-CP3A-受审基线.json`](M2-CP3A-受审基线.json),18 项 SHA256/长度;清单自身 SHA256 `F7311D9D923A61E8F665B021184C9C969C551EC1DDD96D2F23BF2F9CD1C98D49`。独立审查须先重算基线,再逐条判断合同、白名单和验收是否充分;主会话裁决后才可修改生产代码。
## 1. 目的、边界和顺序
CP2A 已有正式 101/102 现存 KV 档的登录与**原** `PlayerKVGameSessionHandle` 内部交接,`LoginStage` 仍阻止进入 `FishingStage`;其 Player 目标真实脚本编译尚未验证,CP2A 未关闭。B01 已在限定路径验证 `ShowNotice`、`IsOpenClubRed` 的固定句柄读写,但普通正式注册中的 `DefaultSettingService` 与 `ClubService` 仍是无句柄实例。CP3A 只把 CP2A 交接的原句柄一次性送达这两个**现有实例**,为后续按业务动作迁移提供一个真实、可拒绝陈旧调用的入口。
本批保持登录阶段停留,不公开 `IsLogin`/`IsAuthorized`,不发布身份事件,不开放 Mail/IAP、普通 API 路由、Survey、排行榜、Friend、AntiCheat,不进入首场景;不声称 101/102 正式游戏可玩。只承认两项提示键 `ShowNotice` 与 `IsOpenClubRed` 的 B01 语义。Club 的 `LureDonateData`、`GetSelfClub` 等其他调用仍未迁移。地址固定 `http://s101.local:18080/`;仅 Unity CLI Loop;不 Play、不操作正式账号、不打包。现有工作树全部保留。
## 2. 源码事实与选择
1. `UserService.KVLoad.cs` 在 `GameLoaded`、同一 epoch、同一原对象核验后才设 `kvHandoffHandle`;注销同步清空它,再启动异步撤销。`PlayerKVGameSessionHandle` 的读写自身会在撤销后返回拒绝;不需要第二次登录或从全局账号重新找 Owner。
2. `SubBoostrap` 在登录前注册并初始化一个 `DefaultSettingService` 与 `LocalNotificationService`。通知服务在登录前执行 `Init()`,未来授权回调会持有设置服务。`LoginStage` 注册一个 `ClubService`,31 模块绑定时会解析并加载这个对象。故登录后**替换容器实例**会遗留旧引用,不能作为接线办法。
3. B01 两个服务的固定句柄字段目前 `readonly`,仅带句柄构造进入 KV 分支;无参正式实例走旧 PF 分支。通知服务的 `promptSettings` 同样只在带句柄构造时设置;将设置实例稍后绑定而不更新通知服务,会触发现有实例一致性拒绝。
4. 采用**同实例、单次、失败关闭绑定**:在已加载原对象上完成只读预检后,固定原 handle;任何部分失败撤销本会话,保留失败证据,不尝试换新实例或继续 PF。此选择只服务 CP3A;不把它扩展成静态当前 Owner、通用服务定位器、全局存储路由或可热换号的绑定接口。
## 3. 时序和合同
### 3.1 正式模式出生即拒绝旧路径
`DefaultSettingService` 创建时从 `SubBoostrap` 已冻结的运行选择捕获是否要求正式 KV。非正式 PF 与 B01 显式带 handle 构造的既有语义不变。正式 KV 的无句柄实例,在 `ShowNotice` 读/写、`CheckRequestPermission` 开始或继续时必须报可诊断的未交接错误,**不调用** `PlayFabMgr.Instance`;本地音乐/语言等 `PlayerPrefs` 设置不属于 KV 提示键,不受此规则改变。
`ClubService` 构造时只读容器中由 `SubBoostrap.RegisterServices` 先行注册的 `PlayerKVRuntimeSelection`,捕获其 `IsKV`;不得依赖后续可变配置或新增 `ClubService → SubBoostrap` 反向引用。其 `SetOpenClubRed` 在正式 KV 尚无句柄时同样失败关闭,不调用 PF,也不先变更红点/内存。31 模块的 `Reset/Replace` 只负责加载数据,绝不自动绑定或重绑句柄。两服务在正式撤销后继续保留原句柄引用,下一次读写由原句柄拒绝,绝不能落入无句柄 PF 分支。
### 3.2 GameLoaded 后一次性交接
`LoginKVFormalAsync` 现有 `KVHandoffReady` 核验成功后、向 `PFLoginAct` 返回 `Done` 前,在 Unity 主线程解析**当前容器中的原** `ISettingService`、`ILocalNotificationService`、`ClubService`。类型及引用不符、通知初始化仍在进行、授权回调正在等待、已有不同句柄绑定、任一提示键读取非成功、Guard 拒绝或 Owner 已陈旧,均失败关闭。预检必须使用 CP2A 的 `kvHandoffHandle` 原对象;不得调用 `TryAcquireGameHandle`、创建新 Context 或重新发 S101 认证。
预检通过后,按同一原句柄一次性固定 Setting、Club 与 Notification 的引用关系。绑定方法只供该登录交接调用,不在业务 UI 中公开“取当前句柄”。已有同一原句柄的重复调用可作检查后幂等;不同句柄一律拒绝。`LocalNotificationService` 沿用登录前已完成的 `Init()` 实例,不重复初始化、订阅或请求系统权限;后续回调必须同时验证原 Setting 实例和其原句柄。绑定过程中不发送 KV 写入或业务 API。
任何绑定或后置一致性检查失败:不得返回登录成功;同步撤销 `kvHandoffHandle`/身份,调用现有 Coordinator 关闭并保留关闭失败屏障。即便 Setting 已绑定而 Club 未绑定,也只能让旧句柄失效,不能把同实例改绑另一个 epoch 或退回 PF。正式同进程重登在已绑定后拒绝,要求新的应用生命周期;不以重新注册服务“修复”部分失败。该限制须进入用户可见错误与测试事实,不能在 `finally` 清掉关闭故障。
### 3.3 存储及副作用
保持 B01 原合同:读区分 `Success`、`Found` 和值;读失败不是缺键。`ShowNotice` 只有值 `"1"` 为真;Club 提示键任何**已存在**的字符串都不重写。写必须检查 `Accepted` 并保留原 `PlayerKVEditResult`、票据和 `LocalCommitMayHaveSucceeded`;失败或撤销后不更新提示缓存、Club 红点,也不自动重试、Flush、回 PF。通知权限回调恢复时仍检查固定 Setting 与 Owner,不能作用于同账号新 epoch 的服务。
## 4. 精确实施白名单与禁止项
在完整独立评审与主会话裁决后,**最多**修改以下既有生产文件的限定位置:
| 文件 | 限定修改 |
|---|---|
| `Services/UserService.KVLoad.cs` | GameLoaded 后原 handle 发布、失败撤销与同进程再绑拒绝;不改 S101 协议/账号名单 |
| `Services/SettingService.cs` | 正式模式出生标志、同实例一次绑定、提示键绑定前/撤销后拒绝;不改其他设置 |
| `Services/ClubService.cs` | 仅 Club 提示键的同实例绑定、绑定前拒绝;不改 Club API/捐赠/签到 |
| `Services/ILocalNotificationService.cs` | 已初始化实例绑定固定 Setting、异步恢复检查;不重复系统权限或事件注册 |
| `SubBoostrap.cs` | 创建 Setting 时传递冻结的正式 KV 选择;不改启动模式选择或 PF 组件策略 |
测试白名单:可在 `KVGameLoadTests`、`KVGameLoadTests/ServiceHost` 与 B01 当前验证项目增添/调整**真实生产源码**的合成验收;B01 的 `ClubSlice.cs` 是历史切片,不能单独代表当前 `ClubService.cs` 已验收。若宿主无法编译真实通知/Club 代码,增加固定 Unity CLI Loop Editor 验证入口须单独列明文件与边界,不能用切片绿测冒充。除表内文件及本批测试外,`KVMgr`、Guard/Owner/Handle、Coordinator、账号、Schema、场景/Prefab、API 后端、cfg、旧 `PlayFabMgr` 均不得改。`LoginStage.RequestNextAct` 的 KV 停留闸必须保留。
## 5. 合成验收与发布门
| 用例 | 必须证明 |
|---|---|
| V01 | PF 缺省和既有 B01 带句柄构造行为不变;正式无句柄提示读/写及授权均拒绝,PF 请求 0、KV 写 0 |
| V02 | CP2A 同一原 handle、epoch、OwnerInstanceId 从 GameLoaded 交到三原实例;没有第二次认证/Get/租约或新 handle |
| V03 | 设置/通知/Club 原容器引用保持;通知 `Init()` 只一次,已有订阅与系统权限请求不重复;在途回调不能跨绑定 |
| V04 | 两键不存在、空串、`"0"`、`"1"`、其他值的 B01 语义、去重、读失败和 Accepted/Unknown/本地不确定结果保持 |
| V05 | 预检失败、两服务间部分绑定失败、logout、Quit、401/403、同账号新 epoch、跨账号回调、Close 故障都先撤销可用状态,后续无 PF 回退或新 Owner 污染 |
| V06 | 旧 B01 专项、CP2A 加载/ServiceHost 必要回归,Unity CLI Loop Editor 0 新错误;真实 Player 脚本编译若仍无不打包的 CLI Loop 方法,继续标未验证,不能宣称 CP2A/CP3A 发布通过 |
| V07 | `LoginStage` 不进入 `FishingStage`;Mail/IAP/Survey/排行榜/Friend/AntiCheat 的正式请求、旧 PF 存档调用仍为 0;真实 101/102 不操作 |
本批代码即使验收通过也只算业务句柄交付能力。首场景需要独立处理直接 PF 排行榜、Survey 读写、Friend/AntiCheat 与动态初始化及精确 API 契约;当前正式 Swagger 路径无可用契约,不可猜测路由响应或通过屏蔽功能开放场景。CP3 其余约 200 个候选仍按实际业务动作/失败语义分批,不由本方案自动批准。多键部分成功后的检测、差异对账和受控恢复仍为正式玩法前置条件。最终 CP4/CP5 必须清理闲置实现、旧 Manager 运行引用与 SDK 依赖,并核流程图。
## 6. 独立审查需明确回答
1. 登录前已初始化的 `LocalNotificationService` 是否允许按第 3 节原实例绑定?需要哪些额外竞态屏障,才能保证在途权限回调不拿到新句柄?
2. `ClubService` 构造时从容器捕获先行注册的 `PlayerKVRuntimeSelection` 是否可靠?若容器构造/解析时序使其不可靠,请指出最小修订及需补审的精确文件,不得在实施中猜。
3. 部分绑定失败后的“原对象保留陈旧句柄 + 本进程拒绝重登”是否完整闭合,或还需把所有实例同时置为不可用?
4. 白名单和 V01–V07 是否足够;真实 `ClubService` 与通知服务如何在 Unity CLI Loop/合成宿主中验证?若任何一项不够,请给阻断项和最小修订边界。
评审报告只读写独立文件;审查者不要改生产源码、共享入口、状态、TODO 或本方案。主会话收到报告后先复算 18/18 hash,再逐条裁决和冻结最终代码白名单,之后才进入连续实施与每检查点日志。