13 KiB
阶段3C组合与限定联调方案
日期:2026-09-23。状态:独立审查PASS,F1–F6已全部采纳,已批准实施。 审查与裁决见《阶段3C独立方案审查-2026-09-23.md》和《阶段3C方案审查裁决-2026-09-23.md》;本文定义阶段3C开发边界。
1. 目标和边界
阶段3C负责把已经验收的阶段2协议客户端、3A缓存状态和3B固定会话Owner组合验证,并在严格fixture边界下连接s101。目标是证明这些模块能共同完成安全的正常路径,不把测试扩大为游戏存档迁移。
正式API固定为http://s101.local:18080/,账号只允许既有测试账号101和102。
本阶段完成:
- 离线跨实例/跨进程组合验证。
- 101/102限定正式服正常路径联调。
- manifest、碰撞预检、精确清理和双账号隔离证据。
- 测试宿主生成脱敏诊断JSON。
- Unity兼容编译、源码hash、实施记录和最终审查。
本阶段不完成:
- 不接
UserService、PlayFabMgr、CustomServerMgr、玩法或游戏存档入口。 - 不修改PlayFab读写,不删除SDK。
- 不提供自动解锁、人工解锁或强制恢复按钮。
- 不做真机文件系统、平台断电耐久或最终打包。
- 不在正式服制造超时、断网、401/403/400/500、迟到响应或UnknownOutcome。
2. 已选方案和备选比较
2.1 采用:独立KVStage3CTests宿主
新增独立.NET测试宿主,显式模式运行:
--offline:合成传输、跨进程重启和诊断导出。--live-s101:限定正式服正常路径。--cleanup <manifest>:只按已有manifest清理遗留fixture。
不带模式或参数不合法时直接以非0退出码退出,不允许默认连接正式服或产生任何网络访问。
采用理由:阶段2、3A、3B证据保持独立;3C可以同时固定API、账号、缓存根目录和写入key范围;不需要新增生产入口。
2.2 未采用:拆分离线和正式服两个项目
隔离更强,但会重复manifest、身份校验、脱敏报告和结果模型,增加审查面。
2.3 未采用:扩展既有KVLiveTests
会混淆阶段2协议联调与阶段3缓存/Owner组合验收,并且现有项目使用PlayerData通配符,不适合作为新的阶段边界。
3. 文件边界
方案审查通过后,实施只允许新增:
CodexOutput/upserver/KVStage3CTests/KVStage3CTests.csprojCodexOutput/upserver/KVStage3CTests/Program.cs- 该目录下运行生成的manifest、结果、脱敏诊断、日志和hash文件。
CodexOutput/upserver/阶段3C实施记录-2026-09-23.md
测试项目必须显式链接以下已验收生产源码,禁止PlayerData/*.cs通配符:
PlayerKVModels.csPlayerKVClient.csPlayerKVSyncModels.csPlayerKVCacheStore.csPlayerKVSyncState.csPlayerKVSyncCoordinator.csPlayerKVSessionOwner.csS101AccountSession.cs
默认不允许修改任何生产文件、现有测试Program、现有csproj、包、asmdef或服务器代码。如果现有internal/public接口不足以实现本方案,实施必须停止,在实施记录中给出缺口、最小接口和风险,回到方案差异评审;不得先改生产代码。
共享README、TODO、开发状态和接手入口仍由本会话维护,实施过程不得修改。
4. 安全架构
4.1 Manifest先行
每次运行在任何登录或变更请求前创建manifest。建议前缀:
codex_kv_s3c_YYYYMMDD_<32位guid>
所有后缀加入后仍须满足服务端128字符key上限;key使用Ordinal精确匹配,不测试大小写变体。
manifest至少包含:
- schemaVersion、runId、创建时间。
- 精确API、账号列表
[101,102]。 - 完整测试前缀和预登记key列表。
- 模式、缓存根目录的任务内相对路径。
- collisionPreflight、cleanupAllowed、hadUnknownWrite、completed。
- 每个账号的清理状态和最后错误类别。
manifest可以保存完整测试key以便精确清理,但禁止保存Cookie、Token、测试值、业务key、响应正文或真实存档。
manifest必须原子更新;每个账号开始变更前、出现不确定结果时、每次清理读回后立即落盘。
4.2 受限转发Handler
3B Owner初始化固定调用完整Get。如果直接连接正式服,会把真实业务KV写入测试缓存,违反原阶段3方案。因此正式服模式必须使用仅存在于测试宿主中的受限转发Handler:
- 捕获本次已认证CookieContainer,但不输出或序列化。
- 只允许目标scheme/host/port/path属于固定API的
playerdata/Get|Set|Delete。 - Owner发起初始化Get时,转发为只读取manifest登记key的过滤Get;返回给Owner的数据中不得包含其它key。
- 无
keys的Get必须改写为manifest登记key;Set/Delete只要包含任一不属于manifest的key,就在进入网络前拒绝整个请求;Set空data按no-op放行。 - 不修改值、不重试、不跟随重定向,不把失败转成成功。
- 任何无法确认请求边界的情况按本地验证失败处理,不发送请求。
越界写的Handler单元检查直接断言内部转发计数为0。若通过完整Owner链检查,由于3B保守语义会把变更异常映射为MayHaveCommitted=true,必须使用一次性run目录,并预期“转发计数0且Owner冻结为Unknown”;不得为改变该表象而放宽生产语义。
该Handler只是测试fixture,不进入Unity生产代码。它验证真实PlayerKVSessionOwner、PlayerKVClient、Cookie认证和服务端正常路径,同时保证测试缓存只含fixture数据。
4.3 缓存根目录
所有3C缓存只能位于:
C:/projects/ft/CodexOutput/upserver/KVStage3CTests/runs/<runId>/cache
启动时用规范绝对路径验证目标位于上述run目录内。不得使用Unity真实persistentDataPath,不得读取、移动或删除玩家现有缓存目录。
5. 离线组合验证
离线模式使用实际生产源码和合成Handler,不访问网络。父进程负责生成run目录并启动有界子进程,以证明文件锁和磁盘证据跨进程有效。
O1 基础组合
- 创建101 Owner,完成Get/Reconcile。
- Set、Delete、localOnly和水位Flush走实际3A/3B组合。
- 关闭后重新打开,确认已确认数据、pending和localOnly语义一致。
O2 跨进程pending恢复
- 子进程A创建Owner并接受远端编辑,但不Flush;Close不隐式Flush。
- 子进程B使用同一缓存和合成远端重新创建Owner,确认pending仍存在并可Flush。
- 子进程C再次打开,确认远端观察与本地状态Ready、pending清空。
子进程A必须正常Close并退出后才启动B。所有子进程必须有确定超时;超时强杀只作为失败收尾并保留run目录证据,不得作为正常模拟崩溃步骤。
O3 账号和环境隔离
- 101与102使用同一缓存根但不同身份命名空间,状态和文件互不借用。
- API指纹变化创建不同命名空间;不得把IP与
s101.local视为同环境。 - 同命名空间Owner未Close时第二Owner返回Busy,实际收尾后才可打开。
O4 保守恢复策略组合检查
- 合成Unknown后关闭重开,Get观察等于基线或目标都不得自动解除Unknown,不得重发。
- 主文件损坏或主文件缺失但存在bak/tmp时返回RecoveryRequired;不得用旧备份自动恢复可写。
- 主文件有效时,旧/损坏bak只记诊断,不冻结有效主档。
这些是3A/3B联合路径检查,不复制全部底层故障矩阵。
O5 诊断导出
关闭Owner并释放文件锁后,从测试缓存打开状态、读取Inspect()不可变快照,生成脱敏JSON。诊断失败不能改变缓存状态,也不能删除证据。
6. 限定正式服联调
L0 预检
- 命令必须显式为
--live-s101。 - API逐字符规范化后必须等于
http://s101.local:18080/。 - 账号只允许101和102;登录结果必须匹配输入ID且
NewlyCreated=false。 - manifest在任何可能的变更前落盘。
- 使用过滤Get确认两个账号全部manifest key不存在;任一碰撞立即退出,
cleanupAllowed=false,不得删除碰撞key。 - 碰撞预检全部通过后才设置
cleanupAllowed=true。
L1 101正常路径
- 使用受限Handler创建真实Owner。
- 接受两个测试key的Set和一个Delete目标;Flush并逐票据确认Confirmed。
- Close释放;使用新登录、新Cookie、新Owner和相同测试缓存重新打开。
- 过滤Get/Reconcile确认Set结果存在、Delete目标不存在、缓存Ready且无pending/unknown。
L2 102隔离
- 对相同fixture key名写入不同测试值并确认。
- 重新读取101,证明102操作不改变101;不得读取或比较业务KV。
- 关闭102后新登录重开,确认Cookie/Owner隔离和正常持久化。
L3 精确清理
- 只Delete manifest登记key;禁止空keys清理、前缀扫描或全量字典改写。
--cleanup只接受位于KVStage3CTests/runs内、schema/API/账号/前缀/key集合全部校验通过的manifest;使用新登录和受限直接PlayerKVClient清理,不打开或改写生产缓存状态。- 每个账号用新登录和过滤Get读回为空。
- 再使用第二个新会话复核为空,完成后才把该账号加入
cleanedAccounts。 - 两账号都清理、所有正常断言通过、
hadUnknownWrite=false时才能completed=true。
L4 正式服失败退出规则
- 不主动故障注入,不自动重试变更请求。
- 任何变更返回
MayHaveCommitted=true、Owner进入Unknown、超时或网络中断:立即将hadUnknownWrite=true落盘,停止新增写入,本次运行永久判失败。 - 出现Unknown时不自动执行清理并宣称成功;保留manifest,由操作者确认无测试进程/传输存活后显式运行
--cleanup <manifest>。 --cleanup即使成功也只关闭遗留fixture,不把原运行改成通过,也不解除生产缓存Unknown。--cleanup自身若出现MayHaveCommitted或网络不确定,同样保留manifest、退出非0,不循环重试Delete。- 普通断言失败但所有变更均有确定成功/未发送证据时,可进入正常finally精确清理;清理失败必须保留manifest并退出非0。
7. 脱敏诊断JSON合同
诊断仅由测试宿主生成,不新增运行时或Editor入口。
允许字段:
- schemaVersion、runId、mode、generatedAtUtc。
- playerId、epoch、API的SHA256指纹。
- PlayerKVSyncStatus、revision、nextSequence、hasBaseline、authenticationInvalid。
- baselineCount、localOnlyCount、pendingCount、conflictCount、activeItemCount、unknownItemCount。
- 票据状态计数、最后错误类别、manifest文件名、清理状态。
- manifest测试key的SHA256摘要;不得输出原始key。
禁止字段:
- Cookie、Token、认证头、完整URL查询、请求/响应正文。
- baseline/localOnly/pending/conflict中的值或完整JSON。
- 业务key、测试值、完整本地用户名路径、异常堆栈中可能含敏感请求内容的文本。
诊断序列化前构建专用DTO,不得直接序列化PlayerKVDiagnosticSnapshot。票据状态计数由测试宿主在Owner关闭前,从票据对象、WaitForTicketAsync结果和FlushAsync汇总自行累计;Inspect()不提供票据历史。
8. 证据产物
每次运行至少生成:
<runId>.manifest.json<runId>.result.json<runId>.diagnostic.json<runId>.log(只含测试名称、状态、数量和错误类别)
最终目录另生成:
Latest-offline.logLatest-live.logRegression-summary.logfinal-source-hashes.jsonunity-compile-final.json
结果文件必须明确:synthetic/live、是否访问s101、是否发生任何Unknown、清理是否双会话确认、未验收边界。
除manifest外,result.json、diagnostic.json和日志均不得回显完整fixture key或测试值,只允许runId、key摘要、数量、状态和错误类别。
9. 验收门
阶段3C通过需要同时满足:
- 离线组合和跨进程用例全部通过。
- 正式服101/102正常路径通过,无Unknown、无越界请求。
- manifest登记key在两个账号均经双新会话确认不存在。
- 脱敏诊断结构检查通过,禁止字段扫描为0。
- 阶段2 42、3A 86、3B 29回归保持通过。
- Unity CLI Loop编译0错误,PlayerData新增警告0。
- 生产源码hash与3B终审基线一致;3C默认不修改生产文件。
- 实施记录按任务书格式交付,本会话完成代码审查和文档终审。
任一清理未确认、发生Unknown、正式目标不匹配或生产源码意外变化,均不得标记3C通过。
10. 自审
- 无TBD、TODO或待定字段;API、账号、文件边界和模式均已固定。
- 解决了Owner完整Get与“测试缓存不得包含业务KV”的冲突:正式模式只通过测试宿主受限Handler转发过滤Get。
- 正式服只验证正常路径;Unknown、取消和故障仍由离线替身验证。
- 诊断导出是测试产物,不形成生产恢复工具。
tickets生命周期计数纳入诊断,但不在3C重构票据存储;游戏接线前另评估有界历史。- 独立审查已PASS;F1–F6已全部纳入本文和任务书,主会话已批准实施。