Files
ft/CodexOutput/upserver/阶段3C组合与限定联调方案-2026-09-23.md

13 KiB
Raw Blame History

阶段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.csproj
  • CodexOutput/upserver/KVStage3CTests/Program.cs
  • 该目录下运行生成的manifest、结果、脱敏诊断、日志和hash文件。
  • CodexOutput/upserver/阶段3C实施记录-2026-09-23.md

测试项目必须显式链接以下已验收生产源码,禁止PlayerData/*.cs通配符:

  • PlayerKVModels.cs
  • PlayerKVClient.cs
  • PlayerKVSyncModels.cs
  • PlayerKVCacheStore.cs
  • PlayerKVSyncState.cs
  • PlayerKVSyncCoordinator.cs
  • PlayerKVSessionOwner.cs
  • S101AccountSession.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:

  1. 捕获本次已认证CookieContainer,但不输出或序列化。
  2. 只允许目标scheme/host/port/path属于固定API的playerdata/Get|Set|Delete。
  3. Owner发起初始化Get时,转发为只读取manifest登记key的过滤Get;返回给Owner的数据中不得包含其它key。
  4. 无keys的Get必须改写为manifest登记key;Set/Delete只要包含任一不属于manifest的key,就在进入网络前拒绝整个请求;Set空data按no-op放行。
  5. 不修改值、不重试、不跟随重定向,不把失败转成成功。
  6. 任何无法确认请求边界的情况按本地验证失败处理,不发送请求。

越界写的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恢复

  1. 子进程A创建Owner并接受远端编辑,但不Flush;Close不隐式Flush。
  2. 子进程B使用同一缓存和合成远端重新创建Owner,确认pending仍存在并可Flush。
  3. 子进程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.log
  • Latest-live.log
  • Regression-summary.log
  • final-source-hashes.json
  • unity-compile-final.json

结果文件必须明确:synthetic/live、是否访问s101、是否发生任何Unknown、清理是否双会话确认、未验收边界。

除manifest外,result.json、diagnostic.json和日志均不得回显完整fixture key或测试值,只允许runId、key摘要、数量、状态和错误类别。

9. 验收门

阶段3C通过需要同时满足:

  1. 离线组合和跨进程用例全部通过。
  2. 正式服101/102正常路径通过,无Unknown、无越界请求。
  3. manifest登记key在两个账号均经双新会话确认不存在。
  4. 脱敏诊断结构检查通过,禁止字段扫描为0。
  5. 阶段2 42、3A 86、3B 29回归保持通过。
  6. Unity CLI Loop编译0错误,PlayerData新增警告0。
  7. 生产源码hash与3B终审基线一致;3C默认不修改生产文件。
  8. 实施记录按任务书格式交付,本会话完成代码审查和文档终审。

任一清理未确认、发生Unknown、正式目标不匹配或生产源码意外变化,均不得标记3C通过。

10. 自审

  • 无TBD、TODO或待定字段;API、账号、文件边界和模式均已固定。
  • 解决了Owner完整Get与“测试缓存不得包含业务KV”的冲突:正式模式只通过测试宿主受限Handler转发过滤Get。
  • 正式服只验证正常路径;Unknown、取消和故障仍由离线替身验证。
  • 诊断导出是测试产物,不形成生产恢复工具。
  • tickets生命周期计数纳入诊断,但不在3C重构票据存储;游戏接线前另评估有界历史。
  • 独立审查已PASS;F1–F6已全部纳入本文和任务书,主会话已批准实施。