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

261 lines
13 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.
# 阶段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已全部纳入本文和任务书,主会话已批准实施。