Files
ft/CodexOutput/upserver/B3BatchMigration/M2/M2-DP1独立方案审查.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

24 KiB
Raw Blame History

M2 DP1 独立方案审查(双提供方独立 KV 用户服务抽离)

日期:2026-09-29。审查人:原 ZCode(独立侧)。只读审查,未修改任何生产/测试/服务器代码,未调用正式 101/102,未打包。

0. 受审基线核验

  • 18 项受审基线逐项按字节数与 SHA256 重算:18/18 一致,无漂移。
  • 清单自身 SHA256 实测 858C43EF26361EB348D4CE36C7F3AFC8AF4DAF85E754A45F3D960A2D078145FA,与提示词记录一致。
  • 本报告据无漂移基线作出,可作为主会话裁决输入。基线外只读扩展源码调用点(PlayerKVEditorValidationHost.cs、EventFishingDuelPanel.cs、FriendService.cs、LeadboardData 等业务文件、S101LoginWindow.cs、CodexOutput/upserver/KVGameLoadTests/ServiceHost/*)仅用于证据,相关发现单独标注"基线外"。

总裁决

限定通过(需修订后可进入白名单裁决)。方案 C 的最小纵切方向成立:调用图证据显示正式 KV 路径可以在 DP1 内完全脱离 PlayFabUserService 运行时实例与 PlayFab SDK 调用,同时 PF/AGG/受控验证/kv-loop 四条既有通道不需要行为变化。无 P1 阻断。有 3 项 P2 需修订(R1/R2/R3)与 3 项 P3 登记(见 §8),全部是"把方案自身遗留的未决点落成可执行裁决",不是结构性缺陷。


1. 三方案比较是否成立(重点:方案 C 能否真正移除 PlayFabUserService 与 SDK 依赖)

结论:通过(附 R1/R2 修订条件)。

源码证据链:

  • 启动装配:SubBoostrap.cs:37-42 一次捕获 PlayerKVRuntimeSelection;SubBoostrap.cs:103 if(!selection.IsKV) rootCtxGo.AddComponent<PlayFabMgr>() —— KV 模式下 PlayFabMgr 组件根本不创建,PlayFabMgr.HasLiveInstanceForKVLoad 恒假(PlayFabMgr.cs:31)。
  • 登录注册:LoginStage.cs:24 目前无条件 Register<IUserService, PlayFabUserService>(),是正式 KV 依赖旧类的唯一注入源;方案 C 只需在此按冻结选择分支注册新 KV 类。
  • 登录行为:UserService.cs:195-226 LoginCore 中 if(IsFormalKV) return await LoginKVFormalAsync(...)(:200)在 PlayFab SDK 全部步骤(LoginPlayFab/OnYinDunLogin/InitTapJoy/LoginApiServer,:207-225)之前返回;PFLoginAct.cs:94-115 正式 KV 分支在 MailService.Init(); iapService.Init()(:139-140)之前 return。即正式 KV 现有执行路径已零 PlayFab SDK 调用,抽离后把这段逻辑整体搬进新类即可,无需改语义。
  • 正式 KV 编排全部位于 UserService.KVLoad.cs partial(LoginKVFormalAsync :82-147、LogoutFormalKV :67-75、BindFormalPromptServices :148-171、CreateFormalKVHandler :64-65、OnFormalKVApplicationQuitting :66),对旧类其余成员的依赖只有:config/customServerMgr 注入属性(:104-106)、kvControlledInstance/runtimeSelection 字段(:16-17)、logoutGeneration/loginLifetime(UserService.cs:169-172)、模块 31 的 ResetForKVLoad/ReplaceFromKVDecoded/KVIntents(:222-251)。这些均可随迁或在窄接口上重建,见 §3。
  • 旧类保留通道不受损:
    • PF 模式:LoginCore 去掉 IsFormalKV 分支后即恢复纯 PF 语义;LoginStage 非 KV 分支注册不变。
    • AGG:PlayerKVRuntimeSelection.Capture 在 agg+kv 时直接抛异常(PlayerKVRuntimeSelection.cs:29-30),AGG 构建永远走 AggUserService(LoginStage.cs:20-22),不因 DP1 改变。
    • kv-loop:UserService.KVGameplayLoop.cs:537-586 LoginKVGameplayLoopAsync 深度依赖 PF 身份(LoginPlayFab :552、LoginApiServer :553、PlayFabMgr.Instance.StartKVGameplayLoopSync() :576、ForgetAllCredentials :613),它必须也应当留在 PlayFabUserService 上;Bind(this)(:570)在 Bind 改窄接口后仍编译(旧类实现该接口)。
    • 受控验证:PlayerKVEditorValidationHost.cs:236,265-267 手工 new AggUserService(...) 并直接调用 user.ResetForKVLoad/ReplaceFromKVDecoded/KVIntents —— 签名不变即不受影响(基线外证据)。
  • 编译级约束(须写入方案):新 KV 用户服务源文件不得引入 PlayFab.* using、不得引用 PlayFabMgr/PlayFabUserService 具体类型;"双模式证明"不得以能编译冒充,须按 §7 矩阵以注册结果与 HasLiveInstanceForKVLoad 断言。

方案 A/B 的否决理由与既有一致(A 无法满足双提供方目标,B 需要未核的线上合同),维持。

为什么是"附条件":方案把两个决定性问题(IUserService 实现义务、登录/退出阶段的中立面)留作未决点(方案 §"完整评审需集中裁决的未决点" 1、4),本审查已给出确定性答案(见 §2、§6),方案文本需按此落成可执行裁决,否则白名单无法精确冻结。

2. DI/构造路径追踪:DP1 是否必须让独立 KV 类实现当前宽泛 IUserService

结论:必须实现。追据如下(R1 的核心证据)。

  • PlayerKVGameplayBindings.Bind 在正式登录 LoginKVFormalAsync 内执行(UserService.KVLoad.cs:100),Require<GameCore.SmallGameData>()(PlayerKVGameplayBindings.cs:28)触发容器构造 SmallGameData。
  • DataCenter.SmallGame.cs:26-30:public SmallGameData(cfg.Tables tables, IUserService userService, ICustomServerMgr customServerMgr) —— 构造函数注入 IUserService,是 31 个模块中唯一的构造参数级依赖(其余 4 个 DataCenter 文件均为方法内 Resolve<> 或属性注入:DataCenter.PlayerData.cs:599、DataCenter.FishingEventData.cs:514 等、DataCenter.LeadboardData.cs:137)。
  • 模块 14 ClubService 有 [Inject] public IUserService userService { get; set; }(ClubService.cs:86),在 Bind 解析时做属性注入,同样要求容器内已注册 IUserService。
  • PFLoginAct.cs:15 [Inject] public IUserService 是登录页的必经注入。
  • 结论:若 KV 类不实现 IUserService,KV 模式下 SmallGameData 无法构造、Bind 失败。替代路线(把 SmallGameData 构造与 ClubService 属性改为窄契约)需把 DataCenter.SmallGame.cs、ClubService.cs 加入白名单,改动两处业务文件,但 DP1 执行路径上没有任何成员被调用(SmallGameData 构造函数只存引用不调用成员,DataCenter.SmallGame.cs:26-34;受控验证 fixture 甚至传 null,PlayerKVEditorValidationHost.cs:237 基线外证据),因此该替代在 DP1 无收益,不推荐本批做,登记为 DP2+ 可选项。

必须同时给出的成员三分表(否则"显式失败"不可审计):

类别 成员 KV 类处理 依据
真实实现 Login/Logout/LoginLifetime/CustomId 迁入正式 KV 逻辑 PFLoginAct.cs:38,88,96,111,168 是登录页仅有的接口调用;LoginLifetime 用于链接取消
可观察状态(如实反映"未发布") IsLogin/IsAuthorized/UserId/ApiPlayerId/InternalId/entityKey/DisplayName/AvatarUrl 恒 false/null/0,与今日正式 KV 现状逐项一致 UserService.cs:110-115 + LoginKVFormalAsync 从不写这些字段;LogoutFormalKV 置空(UserService.KVLoad.cs:71);不是伪造,是如实反映"身份未发布"
显式失败(throw InvalidOperationException,消息含"formal KV identity/profile contract pending") ChangeDisplayName/UpdateAvatarUrl/UpdateLanguage/LinkAccount/GetLinkState/SetPlayerLv/GetPlayLv/GetPlayerLv/GetPlayInfo/SetPlayInfo/ClearPlayInfos/UpdateProfile/UpdateProfileLv 全部显式失败并登记清账 全部调用点位于 FishingStage 闸后(社交/资料/UI)或 PF 专属链;DP1 执行路径零调用(§6 证据)
登记裁决 CreateTime/LastLoginTime/LoginTime/CreateTotalSeconds/ContinentCode/Language/EAccoutUrl/Dictionary EAccoutUrl EAccoutUrl 按模块 31 置换真实 KV 值(UserService.KVLoad.cs:249);时间/大陆/语言字段维持默认值并在清账清单登记(P3-1) FishingEventData/PlayerShopData 读 CreateTime(基线外,闸后);默认值行为与今日正式 KV 完全一致,不新增伪造

不接受项复核:方案明令禁止静默空实现、假 EntityKey、假 PlayFab ID、KV 失败回退 PF——上表不触碰任何一项;GetDefaultName 不属于上表(见 §4)。

P2 风险 R1:方案 §"未决点 1" 仍以问句留空。按本节证据应改写为确定裁决:"DP1 的独立 KV 类实现 IUserService,成员按上表三分;窄化 SmallGameData 构造依赖延后到 DP2+,登记最终清账"。

3. 第 31 模块的最小中立接收契约

结论:通过。契约成员恰好三个,方案描述准确,可按下述精确化。

事务侧全部消费点(PlayerKVGameplayBindings.cs:55-56):

modules.Add(new PlayerKVLoadModule(31, user.ResetForKVLoad,
    d => user.ReplaceFromKVDecoded(d.Module<PlayerKVLoginState>(31), d.Epoch, d.Raw)));
return new PlayerKVGameLoadTransaction(modules, ..., () => user.KVIntents?.Discard());

即窄契约 = ResetForKVLoad() + ReplaceFromKVDecoded(PlayerKVLoginState, long epoch, IReadOnlyDictionary<string,string> raw) + KVIntents(或折叠为 DiscardKVIntents() 委托源)。三个调用通道:正式(UserService.KVLoad.cs:100)、kv-loop(UserService.KVGameplayLoop.cs:570)、受控验证(PlayerKVEditorValidationHost.cs:265 基线外,手工组模块、直调具体方法、不经接口)。接口只需服务 Bind 一处调用者,两实现类(PlayFabUserService 供验证/kv-loop、新 KV 类供正式),符合 AGENTS.md"实际调用者"要求。

字段/成员归属清单(方案要求"哪些留在旧类、哪些必须迁出",此处给出完整盘点,全部出自 UserService.KVLoad.cs 行号):

归属 成员 行号
迁入新 KV 类(正式专属) kvHandoffEpoch/kvHandoffHandle/kvFormalLoading/kvFormalAttempted/kvFormalPromptsBoundEver/kvFormalClosing :22-26
迁入新 KV 类 IsFormalKV/KVHandoffReady/TryGetCurrentGameHandle(epoch 对账) :27-39
迁入新 KV 类 LoginKVFormalAsync/LogoutFormalKV/TrackFormalClose/BindFormalPromptServices/CreateFormalKVHandler/OnFormalKVApplicationQuitting :64-171
迁入新 KV 类 CaptureSaveBackend/RejectSaveBackend 的 formal 分支(:47-51)——旧类保留 PF/kv-loop/受控分支 :40-55
迁入新 KV 类 ObserveKVQuitAsync 的 formal 观察语义 :194
留在旧类(受控验证/kv-loop 专属) ConfigureKVForValidation/LoadKVForValidationAsync/CloseKVForValidationAsync/KVLoadedForValidation/KVReceiverCountForValidation/EnterKVReceiver/ExitKVReceiver/kvReceiver/kvReceiverUsers/MMTestForKVValidation/IsControlledKVInstance :56-63,172-203,219-220
两边各自实现(窄契约接口) ResetForKVLoad/ReplaceFromKVDecoded/KVIntents/KVLoadingCopy/MMTestValue/KVTapJoyBalance/EAccoutUrl/KVLastError :21,216-218,222-251
留在旧类(旧类自身还需) kvControlledInstance/runtimeSelection(受控实例判定) :16-17

拆分时必须保持的安全语义(方案已列,逐条核过源码成立):31 模块定序与主线程 Apply(PlayerKVGameLoadTransaction.cs:37-45)、失败 ResetAll+discard 回滚(:77-83)、admitted 准入在两个实现里各自收紧(旧类删掉 formal 项后剩 kvControlledInstance || KVLoopLoading;新 KV 类为"formal loading/closing 且无 handle 且 InternalId==0 且无 api 会话")、AggUserService 的 internal override ResetForKVLoad/ReplaceKVLinked(AggUserService.cs:20-26)不受影响。

4. IUserService 身份成员的分提供方语义

结论:需修订(并入 R1 的成员表);DP1 不修改共享接口。

  • entityKey:真实非注释读取全客户端仅 2 处,均在 Duel 事件 UI:EventFishingDuelPanel.cs:634-635(Id/Type)、:748(成员比对)——FishingStage 闸后(基线外证据)。KV 无 Entity 合同,新类 entityKey => null(与今日正式 KV 一致,今日也从未赋值)。Duel 两处属 DP2+ 身份/业务合同批次;null 会在该 UI 触发 NRE,闸未开前无影响,但必须登记到最终门控清账清单(P3-2)。
  • UserId:PF 语义为 PlayFabId;正式 KV 今日恒 null(LoginKVFormalAsync 不写、LogoutFormalKV :71 置空)。社交/缓存键(如 DataCenter.SmallGame.cs:390,434,513)全部闸后。KV 侧最终以哪个 ID(101/102 规范名还是服务端内部 ID)作社交键,属 DP2+ 身份合同,本批不得提前拍板。
  • ApiPlayerId:IsAuthorized ? apiSession.PlayerId : null(UserService.cs:115),正式 KV 无 apiSession 恒 null,新类照实返回 null。
  • InternalId:正式 KV 恒 0(不经过 LoginApiServerCore)。
  • IsLogin/IsAuthorized:正式 KV 现状恒 false(UserService.cs:110-112),新类照实。不得在 KVHandoffReady 后翻转这两个值——那是首场景身份发布(CP2B 后续)的裁决,DP1 的"就绪"只通过 KV 专用状态面暴露(见 §6 R3)。
  • GetDefaultName:id[0..5](UserService.cs:146)对 "101"/"102" 会抛 ArgumentOutOfRangeException(长度 3 < 5)。调用点约 10 处全在闸后(FriendService.cs:164、LeadboardData.cs:239、各社交/伙伴 UI,基线外)。方案已如实记录;登记清账(P3-2),DP1 不改。

临时门控与清账条件:DP1 的门 = LoginStage.RequestNextAct 的 KV 早退(LoginStage.cs:114-115)+ 显式失败成员表;清账条件 = 线上身份/资料合同(DP2+)落地后逐成员改为真实语义或迁移到窄接口,届时重审。

5. 启动开关、冻结、AGG、URL、准入与生命周期安全性

结论:通过。方案继承的时序在源码全部成立,可整体迁入新类。

  • 唯一开关与冻结时点:SubBoostrap.RegisterServices 在任何用户服务注册/登录之前捕获一次(SubBoostrap.cs:37-42);LoginStage 只读 SubBoostrap.RuntimeSelection(:35,114)。会话内不可换:LoginKVFormalAsync 的 kvFormalAttempted 单发 + loginGate 信号量 + generation/requestedId 对账(UserService.cs:172-193、UserService.KVLoad.cs:85-119)阻止二次登录/换号,符合用户已裁决的重启式重登。
  • SaveBackend 再读取:登录时 CaptureSaveBackend 重读配置(UserService.cs:173)并经 RejectSaveBackend 与冻结选择对账(UserService.KVLoad.cs:47-51:formal 下非 "kv" 即 Input 错)。这是对同一事实的防御性复读,不是第二开关;拆分后新 KV 类必须保留等价校验(读到的后端串必须等于 "kv"),旧类保留 PF/kv-loop/受控分支。
  • 编辑器直开:InternalEditorBoost.Init 仅允许 playfab,KV 选择直接抛异常(SubBoostrap.cs:378-392)。不因 DP1 改变。
  • AGG:Capture 在 agg+kv 时抛(PlayerKVRuntimeSelection.cs:29-30),早于 LoginStage 的 #if AGG 注册(LoginStage.cs:20-22),"AGG-KV 启动失败、不静默回退"已由现状保证。
  • 正式 URL:Capture 严格比对 FormalApiUrl(:31-32);LoginKVFormalAsync 再核 server.ApiBaseUri 与同 URL(UserService.KVLoad.cs:92-95)。
  • 准入:TryAdmitPlayer 仅 101/102 且规范化(PlayerKVRuntimeSelection.cs:36-43);"有现存档"由加载三态语义保证(无档≠假空,此前批次已裁定)。
  • KV 无 PF 活动实例:SubBoostrap.cs:103 不挂组件 + LoginKVFormalAsync:94 核 HasLiveInstanceForKVLoad。
  • 生命周期可迁性:401/403 经 server.KVUnauthorized 事件 → 版本对账 → LogoutFormalKV(UserService.KVLoad.cs:105-109、CustomServerMgr.cs:180-184);Application.quitting 订阅(UserService.cs:156)在旧类删除 formal 分支后由新类构造器订阅;Logout/QuitAct 先同步失效再观察关闭(LogoutFormalKV 先 DisableKVBusiness/置空 handle/取消 LoginLifetime,再 TrackFormalClose,:67-75);Close 失败保留 kvFormalClosing 屏障(:76-81);在途旧响应与同账号新 epoch 由 TryGetCurrentGameHandle 的 epoch/引用对账(:30-39)+ CustomServerMgr.SessionVersion(CustomServerMgr.cs:177)拒绝。以上全部位于可整体搬迁的 formal 段,无旧类私有耦合。

6. 跨批依赖与隐藏必经路径

结论:通过(附白名单修订 R2/R3)。

  • DP1 执行路径(SubBoostrap → LoginStage → PFLoginAct → Bind/Apply → 交接 → 停在登录页)上的 IUserService 实际调用面 = Login/Logout/LoginLifetime/CustomId 四个(PFLoginAct.cs:38,88,96,111,168);SmallGameData 构造只存引用(DataCenter.SmallGame.cs:26-34);ClubService 属性注入后在 Reset/Replace 中不触 userService(ClubService.cs M14 段无引用,社交请求全部 IsAuthorized/ApiPlayerId 守卫且闸后,:245-330,384-589)。
  • DP1 执行路径上的 PlayFabMgr.Instance 直调 = 0:组件不创建(SubBoostrap.cs:103),登录页 KV 分支不触 TapJoy/邮件/IAP(PFLoginAct.cs:114 提前 return)。全客户端 225 处 PlayFabMgr.Instance(83 文件)与 213 处 IUserService 文本引用全部位于 FishingStage 闸后或 PF/AGG 模式——本批无须迁移任何一个;打开闸门前(DP2+)必须成组处理,方案已如实声明。
  • 本批必须迁移的最小调用者(与方案候选白名单一致):LoginStage.cs(注册分支)、PFLoginAct.cs(中立检测/状态/输入)、QuitAct.cs(退出观察)、PlayerKVGameplayBindings.cs(Bind 窄接口)、UserService.cs+UserService.KVLoad.cs(formal 段搬出与旧类分支清理)、新增 KV 类。
  • FishingStage 闸保持:LoginStage.RequestNextAct(:114-115)不动。
  • 不把客户端路由字面量或本机服务器代码当合同:本审查未读服务器目录;mail/fetch 等仅为 CP2B-T 的路径示例,不构成线上合同。

R2(P2)— 测试迁移与不改文件的编译耦合核验义务:CodexOutput/upserver/KVGameLoadTests/ServiceHost/Program.cs 与 Stubs.cs 直接 new PlayFabUserService()/new AggUserService() 驱动 formal 登录、401、quitting、双登等用例(:50,64,73,88,105,109,124,153,基线外证据)。formal 成员搬出后这批用例必须改指向新 KV 类;同时 UserService.KVGameplayLoop.cs、AggUserService.cs、PlayerKVEditorValidationHost.cs 不在白名单也确实无须改动,但白名单文件须显式记录"这三个文件依赖窄契约签名不变"的核验义务,防止实施时顺手扩面。方案文本需补这两点,否则"必要合成测试"不可审计。

R3(P2)— 登录/退出阶段的中立面须落成具体机制:方案只说"提供方中立的登录结果或窄 KV 会话能力"。具体缺陷三处:

  1. PFLoginAct.cs:88 playFabUserService.CustomId = configCustomId 与 :167-170 的下转——新类注册后 playFabUserService 为 null 会 NRE;应改 userService.CustomId(接口已有成员,UserService.cs:70),彻底去掉该下转。
  2. formal 检测(:94,:316)应以 SubBoostrap.RuntimeSelection?.IsKV==true 为准,不得以"注入对象可下转到某具体类"为准。
  3. QuitAct.cs:40,61 两个下转在新类下全部失效(validation 退出观察与 formal 退出观察分别落在两个类上)。建议一个 internal 窄接口(例如含 KVHandoffReady、KVLastError、ObserveKVQuitAsync 三成员),PFLoginAct/QuitAct 为其仅有的两个消费者,PF/受控语义由旧类实现、formal 语义由新类实现;或明确采用"新类具体下转 + 旧类下转并存",二选一须在方案定死,避免实施时各写各的。

7. AGENTS.md 简洁性核对

结论:通过(附条件)。

  • 新增抽象仅两件,均有实际调用者:31 模块窄契约(消费者=Bind 一处,实现=旧类[验证/kv-loop]+新类[formal]);KV 状态/退出观察窄接口(消费者=PFLoginAct/QuitAct,实现=两类)。无第二套 Coordinator/Guard/存档管理器;方案明令禁止,源码确认无需。
  • 开关单一:复用 RuntimeSelection 冻结捕获;kv-loop Enabled 是编辑器测试通道许可(permit 文件驱动),CustomServerMgr.kvOnly/kvBusinessReady 是运输层状态,各有不可省职责,DP1 不新增任何开关。
  • 闲置路径清账已在方案 §DP2+ 与约束文件清单中登记;本报告补充:旧类拆分后残留的 formal 分支(ctor :156、EvtAPISvrUnauthorized :159、IsLogin :110、LoginCore :200、Logout :684 的 IsFormalKV 项)必须同批清干净,不得留死代码——这正是 UserService.cs 入白名单的原因,方案候选已含。
  • 最小必测矩阵(建议写入验收):
    1. PF 回归:同构建选 playfab → Resolve<IUserService>() 为 PlayFabUserService(AGG 配置为 AggUserService),现有 PF/kv-loop/受控验证套件原样通过;
    2. KV 正式:选 kv → Resolve<IUserService>() 为新 KV 类 且 PlayFabMgr.HasLiveInstanceForKVLoad==false 且全进程零 PlayFabUserService 构造(以注册/类型断言,不以编译冒充);
    3. 既有 formal 用例迁移后全绿:成功/无档/非 101/102/错误 URL/AGG-KV 拒绝、401/403、双登单发、Logout/Application.quitting、Close 失败屏障、旧响应/新 epoch、31 模块定序与回滚、KVHandoffReady 与 EnableKVBusiness 单次发布;
    4. 显式失败成员逐个断言抛出(防静默空实现回潮);
    5. Unity CLI Loop Editor 编译 0 错误;可取得的真实 Player 编译另记。
  • 不能声称已通过的限制:线上业务路由/响应合同、IsLogin/ApiPlayerId 正式发布、SignalR 真连接、首场景链路、真实 Player 编译(工具受限时)。方案 §DP1 验收已如实声明,维持。

8. 发现清单(严重度汇总)

编号 严重度 内容 最小修订
R1 P2 方案未决点 1(IUserService 实现义务)应按 §2 证据落成确定裁决 + 成员三分表 修订方案文本,白名单裁决时引用本报告 §2 表
R2 P2 既有 formal 合成测试(ServiceHost Program/Stubs)迁移义务与三个"不改但受签名耦合"文件的核验义务未写入方案 补入 DP1 白名单说明与验收
R3 P2 PFLoginAct/QuitAct 中立面机制未定死(CustomId NRE 风险 :88/:167-170;QuitAct 双下转失效 :40/:61) 按 §6 R3 定死窄接口或双下转方案
P3-1 P3 KV 类时间/大陆/语言只读成员维持默认值(与今日一致),CreateTotalSeconds 返回值无意义 登记最终清账;DP2+ 身份合同批次裁决
P3-2 P3 GetDefaultName 短 ID 抛异常;Duel entityKey 两处读取在身份发布后 NRE 登记清账清单,指定 DP2+ 批次
P3-3 P3 基线外观察:S101LoginWindow.cs 编辑器最小认证探针与 IUserService 无关,不构成双提供方影响 无须动作,记录在案

9. 可执行修订建议(供主会话直接裁决)

  1. 方案按 R1 改写未决点 1 为确定裁决(§2 表原样落入方案)。
  2. DP1 白名单定格:生产 = LoginStage.cs、PFLoginAct.cs、QuitAct.cs、PlayerKVGameplayBindings.cs、UserService.cs、UserService.KVLoad.cs、新增 Services/PlayerKVUserService.cs(命名可裁);测试 = KVGameLoadTests/ServiceHost/Program.cs、Stubs.cs 及既有 formal 用例迁移、必要新增断言。SubBoostrap.cs 现状无 DI/冻结缺口,不入白名单(若实施发现缺口须先补审)。
  3. 明确禁止项写入白名单:新类禁 PlayFab using/具体类型引用;不碰 UserService.KVGameplayLoop.cs、AggUserService.cs、PlayerKVEditorValidationHost.cs、DataCenter.SmallGame.cs、ClubService.cs、场景/Prefab/服务器/付费/社交路由。
  4. R3 的中立面机制二选一定死后才可编码;建议窄接口(消费者明确、两类实现语义分离)。
  5. PlayerKV* 改名、Core/KVMgr 物理依赖、公开身份发布、GetDefaultName/时间字段语义——全部留在既有延期清单,本批不动。

再次声明:本报告落盘不等于批准编码。主会话须先重核 18/18 hash(本报告 §0 可复核),再逐项裁决并落精确白名单。