# M2 CP2B-T 正式 KV 普通业务请求恢复完整方案(待独立审查) 日期:2026-09-29。**本文只批准评审输入,不是编码批准。** 用户明确:`kvOnly` 对普通请求的拒绝是移植期临时处理,正式 KV 应像原业务流程一样走正常请求。按“同合同一次批级前审、批内连续实施、一次批末复核”减少审核往返;受审 hash 见同目录基线清单,报告落盘后主会话复算、逐项裁决、冻结白名单,再动生产代码。现有工作树保留,本机服务器源码不访问或修改。 ## 1. 已核事实与批次边界 旧 `f40a816cc` 的 `CustomServerMgr.CustomServerPost/GetStringAsync` 以一个 `HttpClient` 发送普通业务请求。迁移提交 `0eb43b2b4` 加入 `kvOnly`,把普通 `SendAsync` 拒绝在入口;当前 `BindKVForGame` 复用此模式,`PlayerKVAccountLease.SendAsync` 只接受 `__kv_validation/ok` 和 `__kv_validation/unauthorized`。这证明拒绝是迁移门,不是线上 API 的产品合同。 正式 KV 登录已经通过同一 `S101AccountSession` 的 `CookieContainer` 创建互不共享的认证、KV、租约 handler;租约归 `PlayerKVLoginContext`,有 epoch/current、活动请求排水及 Close 屏障。租约在 31 模块 Apply **之前**就绑定到 `CustomServerMgr`;因此不能在 `BindKVForGame` 时立刻开放普通请求。CP3A 的原 handle 与 Setting/Club/Notification 交接完成之后,才有可作为本批“运输就绪”的时点。 本批目标:在正式 KV 已 GameLoaded、原 handle 和三原实例交接均成功时,现有 `ICustomServerMgr` 的 `GetStringAsync`、`CustomServerPost` 及 `Account/Event/Club/Friend/...Request` 包装方法可以通过**同一个账号租约和 Cookie**发送规范相对路径,并沿用现有“HTTP 200 返回正文,否则空结果/错误事件”的公共合同;旧会话、退出和 401/403 必须同步失效。非 KV/PF 分支行为不变。验证租约仍只准两个验证路由。**本批不开放 `FishingStage`,不调用正式 101/102,不声称任何业务 route 已在线上验证,也不迁 Survey/排行榜/首场景身份。** 运输可用与某个业务接口在当前线上存在是两个验收结论。 ## 2. 路线比较与选择 | 路线 | 收益 | 问题 | 裁决建议 | |---|---|---|---| | A. 去掉 `kvOnly`,给旧 `client` 填登录 Cookie | 改动少 | 租约与第二 `HttpClient` 分离,退出排水/epoch/handler 所有权无法沿用;Apply 中可过早发送 | 不采用 | | B. 扩展既有账号租约的**正式模式**,由普通包装方法转发 | 同一次认证与 Cookie、已有关闭屏障及会话版本;无第二登录 | 需区分正式/验证路由、运输就绪与公开授权,并补退场/401 竞态测试 | **推荐** | | C. 为业务另建第四 transport/租约 | 职责可分 | 新 handler 所有权、Cookie/Close/版本并发面和基线回归增加;当前无必要 | 暂不采用 | ## 3. 合同与时序 1. `PlayerKVControlledLoadCapability` 已有 formal/validation 不可变模式。在现有 `PlayerKVAccountLease` 内据此区分路由:**验证模式保持原精确两路由不变**;正式模式接受 GET/POST 的规范相对业务路径,拒绝空、绝对/跨源 URL、以 `/` 或 `\\` 开头、`//`、`?`、`#`、`%` 编码、控制字符、`.`/`..` 段及反斜线。正式模式也拒绝 `__kv_validation/*`、`account/Login` 和 `playerdata/*`,避免重认证、绕过专用 KV 传输。路由大小写保持客户端原值,不自动改名。不能以“只列 Mail/Friend”再造一个临时全局禁用门;所有合规普通路由走相同运输合同,**业务语义另按批审**。 2. `CustomServerMgr.BindKVForGame` 仍在 Apply 前绑定并废弃旧空闲 PF transport,但将正式普通请求标记为**未就绪**。只在 `LoginKVFormalAsync` 核同 epoch 的 GameLoaded、CP3A 三原实例绑定成功、原 handle 仍当前后,由 `UserService.KVLoad` 一次调用内部 `EnableKVBusiness(epoch)`;管理器只核自身 formal 租约、固定 API、相同 epoch、尚未关闭及单次发布,不反向依赖 UserService/Coordinator。验证实例从未启用。`PFLoginAct` 仍停在登录页,现阶段不会自动发业务请求。 3. `CustomServerMgr.SendAsync` 在 KV 就绪时捕获原 lease 与 `SessionVersion`,通过其 `SendAsync` 发请求;HTTP 200 且响应仍属同租约/版本/epoch 才返回正文。旧响应、取消、退出与会话替换返回空,不更新业务状态。401/403 用当前版本专用 KV 未授权事件使 `PlayFabUserService` **先同步撤销就绪与公开身份/原 handle,再交 Coordinator 关闭**;不走 PF 自动重登。其他状态/超时保留现有空结果和受控错误日志,不把响应体、Cookie 或存档写入日志。`CustomServerMgr.UseAccountSession` 在 KV 模式继续拒绝第二次认证。 4. `LogoutFormalKV`、Quit、加载/绑定失败与 `ClearKVForGame` 在任何 await 前同步禁用普通请求、递增 `SessionVersion` 并触发 `SessionChanged`,随后租约排水关闭;旧回包不能发布未授权或覆盖新身份。`PlayerKVAccountLease.CloseAsync` 仍负责活动请求取消/排水;失败保留关闭屏障。PF 分支的 transport 与事件语义不变。 5. `ICustomServerMgr.Cookie` 是现有 `RTService.Connect()` 的会话输入。正式 KV 运输就绪且租约仍当前时可返回**同一账号 CookieContainer**;未就绪/已撤销仍拒绝。`SessionChanged` 与 `RTService` 已有 `SessionVersion` 检查必须使旧 SignalR 连接失效。本批不声称 SignalR 真实连接完成,G5 另验;但不能留下“普通 HTTP 已开而现有 `Cookie` 永远抛异常”的隐藏冻结。 ## 4. 精确实施边界 | 生产文件 | 允许的修改 | |---|---| | `Client/Assets/Scripts/Services/PlayerData/PlayerKVLoginContext.cs` | 在既有 capability/租约中区分正式与验证模式、规范路由检查、仅向当前就绪租约交接原 CookieContainer 引用;不改变登录、KV cache/owner、验证两路由、Close 先后 | | `Client/Assets/Scripts/Core/CustomServerMgr.cs` | KV 普通请求在明确就绪后转发同一租约;同步禁用、版本和 Cookie/401 处理;PF 旧分支不改 | | `Client/Assets/Scripts/Services/UserService.KVLoad.cs` | CP3A 成功交接后一次启用、KV 未授权与 Logout/失败先失效再关闭;不公开旧 `UserId` 或跳场景 | 测试仅可增改 `CodexOutput/upserver/KVGameLoadTests` 与固定 Unity CLI Loop Editor 验证入口中实际必要的合成用例;不得顺手改 `LoginStage`、`FishingStage`、`PFLoginAct`、`UserService.cs`、KVMgr/Guard/Owner/Coordinator、Schema、场景/Prefab、服务端或配置。若实施证明白名单不够,先补审精确增量,不以测试绿灯倒推代码许可。 ## 5. 验收与批末复核 合成 handler 必须证明:一次账号认证、三 handler 同 Cookie 且不同实例;正式就绪前普通 GET/POST 零发送,就绪后 `mail/fetch`、`friend/GetInvitedListCount`、`club/GetSelfClub` 等**仅作为路径/正文示例**由原租约发送且保持 JSON/Content-Type;验证租约仍只认两个验证路由。异常/绝对/编码路径、专用认证及 KV 路由零发送。200/非200、401/403、取消、退出中的旧响应、同账号新 epoch、账号切换、Close 失败逐项验证无 PF 回退、无旧结果/身份污染;`Cookie` 与 `SessionVersion` 只在当前就绪租约有效。再跑既有 CP2A/CP3A/B01/B02 回归和 Unity CLI Loop Editor 编译,记录正式请求 0;真实 Player 编译仍按工具限制标未验。批末封存 hash、一次独立复核和主会话裁决,每检查点更新日志。 `FishingStage` 仍保持 [LoginStage 阶段闸](../../../../Client/Assets/Scripts/Stages/LoginStage.cs),直到 Mail/Survey/排行榜/Friend/AntiCheat/活动、公开身份/授权和真正线上响应合同另批完成,并按[最终门控清单](M2-最终通关前临时门控与忽略路径清理清单-2026-09-29.md)销账。固定正式地址 `http://s101.local:18080/`;仅 Unity CLI Loop,不用 UnitySkills,暂不打包。