# Dada P0-A 测试驱动开发计划

| 文档项 | 内容 |
| --- | --- |
| 文档版本 | 1.5 |
| 编写日期 | 2026-07-27 |
| 适用产品版本 | PRD v1.10，P0-A 本机测试需求冻结稿 |
| 功能索引版本 | FeatureSummary 1.1 |
| 工程计划版本 | DevelopmentPlan 1.2 |
| UI 设计版本 | UIDesign 1.1 |
| 当前交付 | 仅 P0-A Windows 本机测试版 |
| 产品需求唯一权威 | `PRD.md` |
| 文档职责 | 固定测试驱动节奏、测试层级、fixture、工作包门禁、AC 追踪、证据和发布验收方法 |
| 当前状态 | 可用于开发的候选测试基线；`RELEASE.json` 尚未生成，不能据此宣告发布验收完成 |

## 1. 文档定位与冻结输入

### 1.1 权威顺序

1. `PRD.md` v1.10 是产品范围、状态、权限、业务规则、数据语义和验收结果的唯一权威。
2. `FeatureSummary.md` 1.1 只用于定位十三个功能模块、关键状态、十九个数据契约和 AC，不产生测试新需求。
3. `DevelopmentPlan.md` 1.2 固定技术栈、OpenAPI、SQLite、事务、存储、进程、工作包及测试层级；本文件不得改写这些工程决策。
4. `UIDesign.md` 1.1 与低保真资产用于页面、控件、状态、文案、焦点、布局和界面验收；它们不得覆盖 PRD 行为或 DevelopmentPlan 接口。
5. 本文件只定义如何先写失败测试、如何得到最小通过实现、如何回归和留证；不修改产品 API、Schema、状态、稳定错误码或 `message_key`。

若本文件与上游冲突，停止受影响测试和实现，以 PRD 为产品权威重新判断，并按依赖顺序同步 DevelopmentPlan、UIDesign 和本文件。不得只替换哈希后沿用旧结论。

### 1.2 输入哈希与失效作用域

#### 1.2.1 全局冻结来源

| 输入 | SHA-256 |
| --- | --- |
| `PRD.md` | `31F93674DF1A90B557FEE3AA9E74FB084E246CA8FE09F6BD4B1DC9D56D606565` |
| `FeatureSummary.md` | `6F80E272AAB08A5525B54501D83F16A4F6A7A170596947BBC25F1DA54F2FE844` |
| `DevelopmentPlan.md` | `76CCC786E910F3E503921AEF5B9BD22976E364184BA8C0062CDCF1C5F376AC0A` |
| `UIDesign.md` | `40A9EA29B921989877A01253A686F12A0EDE93454F8A23ADD5C0511616FCC35C` |

上述四份 Markdown 是所有测试层的全局来源基线。每次测试运行先校验四项哈希；任一不一致时：

1. 整次运行标记为 `source_changed`，不得继续生成通过证据。
2. 完整重读发生变化的权威来源及全部受影响下游来源。
3. 更新需求、UI、fixture、测试和 AC 映射后重新建立失败基线。
4. 不把变更前的截图、性能数据、外部契约记录或人工确认复用为变更后的通过证据。

#### 1.2.2 UI 参考资产

| 输入 | SHA-256 |
| --- | --- |
| `Dada-P0A-LowFi.pen` | `A352C8F205E9399EB2E6306176253AF6FF65DDDBB9FDCEA2E2FE13E3C3459975` |

低保真预览图清单：

| 文件 | SHA-256 |
| --- | --- |
| `Dada-LowFi-Preview/awt96.png` | `9E0392DBEA2407BC0602E62EDEE755DF85BE085B0A6CD328C69DA8C79832AB2C` |
| `Dada-LowFi-Preview/bHQnJ.png` | `FA9F1CFC664F90BB8CD5DCFE985DA9A1280B3F3EF3C971F9480CB226A6D6DC88` |
| `Dada-LowFi-Preview/d99Lo.png` | `2ADC0DE944290E2BDFFF91083F0A3AB5BBEB0C3E294E0DC39CAE55763EFCF0C0` |
| `Dada-LowFi-Preview/DVPM8.png` | `9B1E7237857089CB3185EC4D3BBC2B3C9293267D7026A4F411A2131175FDEE90` |
| `Dada-LowFi-Preview/e4e2Kz.png` | `9D528A72A4902934B77421FB53EF35457B62AEA0477155BA8664B38DF0833232` |
| `Dada-LowFi-Preview/WbDYT.png` | `703A3D371F716C808CA158700D380B7CAFA25D89A7672B7199362A8A65E8EB76` |
| `Dada-LowFi-Preview/Z0pf8.png` | `ED213DFD7022BC355C7E243CD27484BFB25791DF2C38ED1A498175C563E17B9B` |
| `Dada-LowFi-Preview/Z8p3I.png` | `4714CA2C6D4CC00395B81B175389CF04DDE9EB0C71876BDBE72060DC741A1035` |

`.pen` 和预览 PNG 不是产品需求来源，只约束第 11 节映射到的 UI、视觉和 E2E 证据。相关 job 运行前校验这些哈希；某张 PNG 变化时只把对应页面/状态的 UI、视觉和 E2E case 标记为 `source_changed`，`.pen` 变化时先比较受影响 frame，只失效对应页面证据；若无法确定受影响 frame，则失效全部 UI、视觉和 E2E 证据。UNIT、DB、API、WRK、EXT-MOCK、EXT-REAL 和不消费布局的 PKG-SEC 证据不得仅因低保真资产变化而失效。所有失效项都必须重新读取 UIDesign 与变更资产、更新映射并重建视觉基线，不能只替换哈希。

### 1.3 当前范围

P0-A 只在项目方当前 Windows 电脑上运行。Fastify 只监听 `127.0.0.1:43121`，浏览器只从 localhost 访问本地后端，业务数据只写入 `LocalDataRoot`，约 3.57 GB 规范素材从安全配置引用的原目录只读使用。本地后端访问 AI 网关、Resend 和高德时仍使用外网 HTTPS。

当前测试不得实现或暗示手机、局域网、远程访问、跨设备继续处理、公司服务器、对象存储、正式域名、产品 HTTPS、云备份、项目归档或 P0-A 数据迁移。AC-08、AC-26、AC-37、AC-54 是未来 P0-B 门槛，不进入当前完成统计；AC-15、AC-23、AC-36、AC-38 只执行 PRD 明确的 P0-A 子集。

当前 AC 总数固定为 52：

- P0-A 必验 48 项：AC-01 至 AC-07、AC-09 至 AC-14、AC-16 至 AC-22、AC-24 至 AC-25、AC-27 至 AC-35、AC-39 至 AC-53、AC-55 至 AC-56。
- P0-A 子集 4 项：AC-15、AC-23、AC-36、AC-38。
- 未来且不计入当前完成 4 项：AC-08、AC-26、AC-37、AC-54。

## 2. TDD 执行协议

### 2.1 Red-Green-Refactor

每个能力和缺陷都按以下顺序执行，不能先实现后补测试：

1. **Red**：先写一个只因目标行为尚未实现而失败的最小测试。记录测试 ID、权威需求、fixture、预期失败原因和所有禁止副作用。
2. **确认 Red**：在隔离环境运行该测试，确认失败信息指向目标缺口，而不是环境、随机数据、未安装依赖或其他已知失败。
3. **Green**：只实现使该测试通过的最小产品行为；不得借机增加 PRD 未定义的入口、状态、回退或配置。
4. **回归 Green**：运行同层测试、所属工作包测试和跨工作包依赖测试。金融、文件、权限、审计和外部调用必须同时断言状态与无副作用。
5. **Refactor**：在测试持续通过的前提下消除重复、改善结构；不得改变 OpenAPI、Schema、事务边界和用户行为。
6. **Evidence**：保存脱敏证据清单。没有 Red 证据、最终 Green 结果或必要人工证据的场景不能标记完成。

测试状态只允许：

| 状态 | 含义 |
| --- | --- |
| `not_started` | 尚未建立失败测试 |
| `red` | 已确认测试因目标缺口失败 |
| `green` | 自动断言通过，但尚可能等待工作包或发布门禁 |
| `externally_blocked` | 本地模拟通过，但等待真实 AI 网关、Resend、高德、浏览器或发布环境证据 |
| `passed` | 自动、人工、外部及证据门槛全部满足 |
| `source_changed` | 全局来源或作用域内 UI 参考变化，受影响结果按第 1.2 节失效 |

不设置 `skipped_as_passed`、`expected_failure_as_passed` 或无依据豁免。临时跳过只能保持未完成并写明原因。

### 2.2 测试 ID 与用例记录

测试族 ID 使用 `TDD-WP<0-7>-<AREA>-<NNN>`，例如 `TDD-WP0-STO-002`。一个测试族可以包含参数化子用例，子用例追加稳定后缀，例如 `TDD-WP0-STO-002-equal-limit`。

每个可执行用例必须记录：

| 字段 | 要求 |
| --- | --- |
| `test_id` | 全局唯一且提交后不复用 |
| `work_package` | 唯一主责 WP；交叉依赖另列 supporting WP |
| `requirements` | 一个或多个 PRD 需求 ID、数据契约或 NFR |
| `acceptance_criteria` | AC 编号及 P0-A 子集标记 |
| `layer` | 本文第 3 节固定层级 |
| `fixture_ids` | 本文第 5 节稳定 fixture |
| `red_reason` | 实现前预期失败的唯一原因 |
| `green_assertions` | 状态、响应、持久化、UI 和禁止副作用 |
| `automation` | 按第 2.2.1 节生成的非空有序数组，元素只允许 `automated`、`controlled_real`、`manual_review` |
| `evidence_refs` | 机器可读结果、截图、性能、日志或人工记录 |
| `release_gate` | 按第 2.2.1 节生成的非空门禁数组，明确阻止的工作包和 P0-A 发布 |

#### 2.2.1 测试族执行属性

本文实际定义 89 个测试族；AREA 允许大写字母和数字，因此 `TDD-WP4-A11Y-001` 必须计入。`pnpm validate:tdd-trace` 生成 `artifacts/tdd/<run_id>/family-manifest.json`，逐族按下列规则确定执行属性，不能依赖人工补默认值：

| 字段 | 确定规则 |
| --- | --- |
| `red_reason` | 精确继承父测试族“首个 Red”；case 可追加更窄触发条件，但不得删除或弱化父断言 |
| `automation` | 非空有序数组：父层级含 UNIT/DB/API/WRK/EXT-MOCK/E2E/PKG-SEC/VIS-PERF 时含 `automated`；含 EXT-REAL 时含 `controlled_real`；含 MANUAL 时含 `manual_review`；固定按此顺序去重 |
| `release_gate` | WP-0 至 WP-6 固定为 `["work_package:WP-n","release:P0-A"]`；WP-7 固定为 `["release:P0-A"]` |
| `work_package` | 从测试族 ID 的 `WP<n>` 唯一解析；case 不得覆盖 |
| `layer` | 精确继承父测试族“层级”列；case 结果必须逐层列出执行状态，不得以 E2E 代替 DB/API 或以 mock 代替真实门禁 |

因此第 2.2 节 `automation` 与 `release_gate` 在 `result.json` 中使用上述数组值。任一父族缺“首个 Red”或“层级”、属性数组为空、case 指向未知父族或人工改写派生值时，追踪校验失败。

### 2.3 测试脚本接口

后续工程必须提供以下仓库级脚本；脚本名称属于开发接口，不是产品 API：

| 命令 | 作用 |
| --- | --- |
| `pnpm test:unit` | TypeBox、纯函数、状态转换和前端域控制器 |
| `pnpm test:integration` | SQLite、临时 LocalDataRoot、文件一致性和服务事务 |
| `pnpm test:api` | Fastify inject、OpenAPI、鉴权、错误信封和资源路由 |
| `pnpm test:worker` | 队列、租约、崩溃、结算、清理和重计量 |
| `pnpm test:e2e` | Playwright Chrome/Edge 独立 profile 的产品流程 |
| `pnpm test:visual` | 固定 Canvas 与页面截图、Chrome/Edge 差异和人工复核包 |
| `pnpm test:performance` | 50 元素、自动保存、打开编辑器和导出预算 |
| `pnpm test:security` | 凭据、日志、制品、路径、缓存和私有内容泄漏扫描 |
| `pnpm test:package` | 便携包、随包 Node/native 模块、Supervisor 和固定端口边界 |
| `pnpm validate:tdd-trace` | 校验当前 AC 唯一主责、P0-B 排除项、PRD 需求 ID、UIDesign 页面/状态覆盖和 case 必填字段，防止追踪矩阵手工漂移 |
| `pnpm test:all` | 除真实外部调用外的全部阻断测试 |
| `pnpm validate:external -- --service <service> [--model <id>]` | 显式受控真实验证；普通 CI 禁止运行 |

任一脚本失败必须返回非零退出码。测试脚本不得读取真实 P0-A `LocalDataRoot`、日常浏览器 profile 或未显式授权的 Windows 凭据。

### 2.4 证据清单

每次运行写入 CI 或验收工作目录下的 `artifacts/tdd/<run_id>/evidence.json`。该目录不是产品数据目录，不提交真实私有内容。清单至少包含：

```json
{
  "schema_version": 1,
  "run_id": "opaque-run-id",
  "commit": "git-commit",
  "gitea_repository": "intelligrow/project-repository",
  "remote_branch": "codex/wp0-01",
  "remote_commit": "git-commit",
  "remote_verified_at": "ISO-8601",
  "source_hashes": {},
  "ui_reference_hashes": {},
  "environment": {},
  "release_record_hash": null,
  "tests": [
    {
      "test_id": "TDD-WP0-BRW-001-real-supported",
      "parent_family": "TDD-WP0-BRW-001",
      "work_package": "WP-0",
      "requirements": ["NFR-01"],
      "acceptance_criteria": ["AC-24"],
      "layer": ["E2E", "MANUAL"],
      "fixture_ids": ["final-release-browser-fixture"],
      "red_reason": "真实支持 Chrome/Edge 无法通过门禁",
      "green_assertions": ["release-recorded full version passes"],
      "automation": ["automated", "manual_review"],
      "release_gate": ["work_package:WP-0", "release:P0-A"],
      "status": "green",
      "started_at": "ISO-8601",
      "finished_at": "ISO-8601",
      "evidence_refs": []
    }
  ],
  "redaction_scan": "passed"
}
```

`evidence.json`、截图、trace、日志、OpenAPI 示例和 fixture 均不得包含真实密钥、验证码、会话令牌、管理员真实邮箱、完整私有提示词、真实用户图片、Windows 用户名、绝对本机路径或供应商原始错误正文。扫描失败时整次运行失败。

每个 `TASK-*` 的最终交付记录必须补齐项目指定 Gitea 组织仓库、远端分支、完整本地提交 SHA、远端提交 SHA 和核对时间。远端仓库必须位于 `intelligrow` 组织命名空间，且 `remote_commit` 必须与本地任务提交一致；本地未提交、推送失败、仅推送个人命名空间或哈希不一致时，测试结果最多保持 `green`，不得标记任务 `passed` 或开始下一任务。该远端记录可在提交并推送后作为交付证据补写，不得伪造为测试运行时已存在的结果。

## 3. 固定测试层级

| 层级 | 标识 | 基础设施 | 主要职责 |
| --- | --- | --- | --- |
| Schema/单元 | `UNIT` | Vitest、TypeBox fixture、可控时钟 | 枚举、Schema、格式化、版本、状态转换、路径、容量计算、色卡确定性 |
| SQLite/文件集成 | `DB` | 每测试临时 SQLite、临时 LocalDataRoot、受控文件驱动 | 事务、索引、幂等、审计、引用、预留、物理清理和恢复 |
| API 契约 | `API` | Fastify inject、OpenAPI snapshot、原始 HTTP 客户端 | 路由、错误信封、门禁、鉴权、CSRF、If-Match、multipart、资源隔离 |
| Worker 恢复 | `WRK` | 模拟网关、进程终止 harness、可控租约时钟 | 领取、心跳、恢复、结算一次、成本核对、清理重放 |
| 外部模拟 | `EXT-MOCK` | 三模型、Resend、高德的确定 fixture | 错误、配额、同步/异步和恢复状态；普通 CI 不访问外网 |
| 外部真实 | `EXT-REAL` | 显式命令、Windows 凭据管理器、受控账号 | 三模型独立契约、邮件送达、高德接口和发布门槛 |
| 浏览器流程 | `E2E` | Playwright Chrome/Edge channel、独立 user-data-dir | 认证、项目、编辑、导出、多标签、后台、缓存、权限和门禁 |
| 安全/打包 | `PKG-SEC` | .NET/Node harness、进程检查、ZIP/日志/环境扫描 | 凭据传递、固定端口、便携包、诊断脱敏和零泄漏 |
| 视觉/性能 | `VIS-PERF` | 固定 Canvas、截图 diff、PerformanceObserver | NFR-02、NFR-03、1080p、200% 缩放、50 元素和导出 |
| 人工发布 | `MANUAL` | 真实便携包、真实外部依赖、固定验收账号 | 自动化不能替代的视觉、送达、替代资源和发布确认 |

低层测试发现的数据、状态和权限错误不能只由 E2E 覆盖；E2E 负责证明用户流程接线正确。真实外部测试不能替代模拟失败分支，模拟测试也不能替代真实发布门槛。

### 3.1 OpenAPI 与事件分组覆盖

| 分组 | 主测试族 | 必验契约 |
| --- | --- | --- |
| 浏览器支持 | `TDD-WP0-BRW-001`、`TDD-WP0-BRW-002` | support check、426、最小字段、产品路由前置门禁 |
| Bootstrap | `TDD-WP0-API-001`、`TDD-WP3-MDL-003` | app、公开功能、模型配置/runtime/推荐和非敏感依赖摘要 |
| 普通认证 | `TDD-WP1-AUTH-*` | 注册/登录分离、挑战、会话、限流和稳定错误 |
| 管理员认证 | `TDD-WP1-ADM-001`、`TDD-WP1-CFG-001` | 白名单前置校验、独立 audience、告知版本摘要 |
| 账号 | `TDD-WP1-DEL-001`、`TDD-WP2-CRED-*` | 资料、点数、流水和不可逆注销 |
| 模型 | `TDD-WP3-MDL-*` | 配置集合、版本、runtime availability 和派生推荐 |
| 生成 | `TDD-WP2-GEN-*`、`TDD-WP2-REF-001` | multipart、配置复核、单任务、参考图和任务真相 |
| 项目 | `TDD-WP2-PROJ-*` | 所有者、If-Match、完整 JSON、回收站和 purge |
| 历史与成品 | `TDD-WP2-EXP-*`、`TDD-WP4-EXP-001` | 原图、历史、每格式 latest 与客户端下载分离 |
| 三类素材 | `TDD-WP5-RES-001`、`TDD-WP5-PREV-001` | public/preview/private 分路由、鉴权和缓存 |
| 贴纸历史清理 | `TDD-WP5-CLN-001` | 候选、intent、确认、整批复核、异步结果 |
| 私有告知与访问 | `TDD-WP6-PRIV-*` | 身份+版本 ack、服务端区域门禁、先审计后读取 |
| 管理后台 | `TDD-WP6-ADM-001`、`TDD-WP6-AUD-001` | active super_admin、事务内审计和安全摘要 |
| 状态事件 | `TDD-WP0-EVT-001` | 仅提示 ID/类型/版本/时间；断线或缺号后 REST refetch |

所有 `/api/v1` JSON、multipart、SSE 和错误响应都由 TypeBox/OpenAPI 3.1 的同一 Schema 来源验证。前端生成客户端 snapshot 漂移、未声明枚举、错误 HTTP 状态或错误信封字段时，API 契约测试失败。

## 4. 候选环境与发布冻结

### 4.1 当前候选基线

| 项目 | 候选值 | 用途 |
| --- | --- | --- |
| 操作系统 | Microsoft Windows 11 家庭版 中文版，25H2，`10.0.26200`，build `26200` | 开发视觉、性能、打包和辅助界面候选基线 |
| Chrome | `150.0.7871.184`，major `150` | 当前真实 Chrome channel 候选基线 |
| Edge | `150.0.4078.99`，major `150` | 当前真实 Edge channel 候选基线 |
| 产品视口 | `1920 x 1080` CSS px | UIDesign 首要桌面验收视口 |
| 截图 DPR | `1` | Playwright 确定性页面与 Canvas 基线 |
| 默认缩放 | `100%` | 正常布局、视觉和性能 |
| 可访问性缩放 | `200%` | 文字覆盖、弹层、主动作和滚动可达性 |
| 语言 | `zh-CN` | 固定简体中文文案和排序 |
| 时区 | `Asia/Shanghai` | 界面时间显示；持久化仍为 UTC 毫秒 |

上述浏览器必须使用本机真实 Chrome/Edge channel。Playwright 自带 Chromium 只能用于开发诊断，不能作为 AC-24、AC-32 或发布通过证据。伪造 User-Agent 或 UA-CH 可以证明拒绝分支，但不能证明支持分支。

本节具体版本号只用于当前候选视觉、性能和打包基线，不是产品支持范围本身，也不得写死进产品逻辑；产品运行支持口径以最终 `RELEASE.json` 和浏览器门禁实现为准。

### 4.2 `RELEASE.json` 缺失门禁

当前目录没有 `RELEASE.json`，因此本节环境只能标为 candidate。WP-7 必须：

1. 从实际发布包记录 Windows build、Chrome/Edge 品牌、major、full version 和记录时间。
2. 在记录的两个真实 full version 上重新运行 AC-24、AC-32、全部 E2E、视觉、性能、下载、文件选择和 200% 缩放。
3. 对比本节候选值。任何不同都使候选截图和性能结果失去发布证据资格，但不自动否定与浏览器无关的单元/DB 测试。
4. 把 `RELEASE.json` SHA-256 写入最终 `evidence.json`；缺失、版本不一致或哈希不一致时 WP-7 失败。
5. 运行时只接受 `RELEASE.json` 记录的 major，不联网查询“当前版本”，也不接受前一 major。

## 5. 固定 fixture

### 5.1 主体与认证

全部自动化邮箱使用 `.invalid`，不得使用项目方真实邮箱。

| fixture | 内容 |
| --- | --- |
| `FX-ACTOR-USER-A` | active 普通用户，10 available、0 reserved，拥有项目 A |
| `FX-ACTOR-USER-B` | 通过正常邀请、验证码和注册链临时创建的第二普通用户 |
| `FX-ACTOR-SUSPENDED` | suspended 普通用户，仍占普通账号名额 |
| `FX-ACTOR-DELETED` | 已注销主体，只保留允许的匿名事件 |
| `FX-ACTOR-ADMIN-A` | active super_admin，已在安全白名单 |
| `FX-ACTOR-ADMIN-B` | 第二个独立 active super_admin |
| `FX-ACTOR-DEPLOYER` | 无 UserProfile、无会话、无产品角色的配置维护主体 |
| `FX-INVITE-VALID` | 未过期、启用且有剩余次数 |
| `FX-INVITE-EXPIRED` | 已过期 |
| `FX-INVITE-DISABLED` | 已停用 |
| `FX-INVITE-EXHAUSTED` | 已达最大使用次数 |
| `FX-CHALLENGE` | 10 分钟一次性验证码、60 秒重发限制、带 pepper 摘要 |

跨普通用户隔离的发布验收不得直接插入 `FX-ACTOR-USER-B`。它必须在独立 Chrome/Edge profile 中使用真实可验收的邀请和验证码链创建，完成越权检查后正常注销，并等待内容进入规定清理路径。直接 DB fixture 只用于低层事务测试。

### 5.2 模型、任务与点数

| fixture | 内容 |
| --- | --- |
| `FX-MODEL-SEED` | 三模型 configured enabled；Flash 唯一默认 priority 1，Pro priority 2，GPT priority 3；初始 contract unverified、runtime unavailable |
| `FX-MODEL-VERIFIED` | 三模型分别具有独立 verified 证据和可用 runtime 状态 |
| `FX-MODEL-NO-RUNTIME` | 配置不变，三个模型 runtime 均不可用于新任务 |
| `FX-MODEL-CONFLICT` | 过期 config_set_version、非法默认替代或重复优先级候选集合 |
| `FX-CREDIT-ENOUGH` | available 10、reserved 0、模型成本 1 |
| `FX-CREDIT-INSUFFICIENT` | available 0、reserved 0、模型成本 1 |
| `FX-CREDIT-NEGATIVE` | 仅由 admin_adjustment 产生 available -5，reserved 保持不变 |
| `FX-JOB-ACTIVE` | 一个 queued 或 running 任务及一份 generation_reserve |
| `FX-JOB-TERMINAL` | succeeded、failed、rejected 的不可逆终态组合 |
| `FX-GATEWAY-SYNC` | 同步完成模型响应 |
| `FX-GATEWAY-ASYNC` | start 返回上游引用、poll 返回终态 |
| `FX-GATEWAY-UNKNOWN` | 已提交但无可安全查询引用；`GenerationJob.status=failed`，`error_category=unknown_retryable`，`upstream_cost_reconciliation=pending_manual_review`，`upstream_outcome_known=false`，冻结点释放一次 |

### 5.3 项目、参考图与画布

| fixture | 内容 |
| --- | --- |
| `FX-PROJECT-EMPTY` | failed_empty 草稿，无成功图 |
| `FX-PROJECT-HISTORY-9` | 9 张成功图，可继续生成 |
| `FX-PROJECT-HISTORY-10` | 10 张成功图，阻止追加 |
| `FX-PROJECT-ACTIVE-19` | 19 个 active 项目，可再创建一个 |
| `FX-PROJECT-ACTIVE-20` | 20 个 active 项目，阻止创建和恢复 |
| `FX-PROJECT-CONFLICT` | 两个标签基于同一 state_version 的不同内存状态 |
| `FX-REF-VALID` | 可解码的 JPG、PNG、WebP，分别满足模型限制 |
| `FX-REF-MALFORMED` | 扩展名与内容不符、损坏解码和错误 MIME |
| `FX-REF-OVERSIZE` | 超单文件、总大小或数量限制 |
| `FX-REF-NONSAFETY-INVALID` | 供应商明确判定不可用但不属于安全拒绝的参考图 |
| `FX-REF-SAFETY-REJECTED` | 供应商明确安全拒绝证据；不得与前一 fixture 共用模拟错误 |
| `FX-CANVAS-50` | 12 个文字、24 个普通贴纸、4 个色卡、10 个动态贴纸，共 50 个覆盖元素 |
| `FX-CANVAS-RATIOS` | 3:4、1:1、4:3、9:16 四个固定画布和标准像素 |

### 5.4 时间、位置与视觉

- 固定后端时钟为 `2026-07-27T04:00:00.000Z`，界面期望为 Asia/Shanghai 的 `2026-07-27 12:00:00`。
- DYN004 使用确定测试坐标与固定地理编码响应；拒绝 Dada 自有提示时定位 API 调用次数必须为 0。
- 色卡背景使用固定解码像素和 `mmcq-v1`；测试裁剪、调色、滤镜不改变五色，更换原始底图会改变五色。
- 字体测试等待 `FontFace` ready；字体缺失使模板 unavailable。DYN012 只使用 FONT081 Lexend Deca，并始终标记替代字体。
- 动态值、动画时刻、随机 ID 生成器、列表排序和图片解码均固定 seed；视觉基线不得依赖当前墙钟或网络返回顺序。

### 5.5 存储边界

容量测试通过受控文件驱动、数据库计数和预留模拟精确字节，不实际创建 5 GB 文件。必须参数化以下值：

| 字节值 | 期望 |
| ---: | --- |
| `4,294,967,295` | normal |
| `4,294,967,296` | warning 起点 |
| `4,831,838,207` | warning 末值 |
| `4,831,838,208` | critical 起点 |
| `5,368,709,119` | critical 末值 |
| `5,368,709,120` | full；若由本次写入恰好达到，写入成功后进入 full |
| `5,368,709,121` | 超限请求拒绝；不得创建持久副作用 |

计量分类至少区分 reference、generated、export、derived、`sticker_original`、`sticker_thumbnail`。只读规范素材、应用代码、数据库、日志、审计、浏览器公开缓存和下载目录均不得计入。

## 6. 工作包 TDD 门禁

### 6.1 WP-0 基础工程与本机边界

| 测试族 | 首个 Red | Green 断言 | 层级 |
| --- | --- | --- | --- |
| `TDD-WP0-DEP-001` | 冻结依赖任一无法安装、加载或最小运行 | 精确版本、TypeScript、React/Fabric、Fastify、better-sqlite3 native 和随包 Node 全通过；不私自替换技术栈 | UNIT、PKG-SEC |
| `TDD-WP0-API-001` | TypeBox、OpenAPI、生成客户端或错误信封漂移 | OpenAPI 3.1 snapshot 与运行验证一致；响应有 correlation ID；`details` 只含白名单字段且不泄露内部信息 | UNIT、API |
| `TDD-WP0-BND-001` | API 接受非回环绑定、错误 Host/Origin 或 CORS | 只绑定 `127.0.0.1:43121`；拒绝局域网、其他 Host/Origin，不创建防火墙入站规则 | API、PKG-SEC |
| `TDD-WP0-BRW-001` | 真实支持 Chrome/Edge 无法通过门禁 | `RELEASE.json` 记录的两个真实 full version 通过并建立短期支持 Cookie，产品 API 每次复核品牌/major | E2E、MANUAL |
| `TDD-WP0-BRW-002` | 其他品牌、错误 major、UA-CH 缺失/冲突或直调 API 可进入产品 | 全部返回 426 `BROWSER_UNSUPPORTED`，只有最小引导，无登录、资源、DB 业务读取或外部调用，无绕过令牌 | API、E2E |
| `TDD-WP0-SUP-001` | 第二实例、端口占用、API/Worker 崩溃或退出留下残留进程 | mutex、固定端口、有限重启、degraded 状态、15 秒优雅退出和托盘允许动作符合 DevelopmentPlan | PKG-SEC、MANUAL |
| `TDD-WP0-DATA-001` | LocalDataRoot 位于仓库/项目/下载/素材根，或丢失后自动重建 | 路径校验拒绝违规目录；已初始化根缺失进入 data_missing；浏览器只见资源 ID | DB、API、PKG-SEC |
| `TDD-WP0-DATA-002` | 产品出现自动备份、项目包、业务导入或 P0-B 迁移 | 只允许下载原始生成图和 JPG/PNG；删除根或重装不可恢复；固定本机风险文案持续显示 | API、E2E、MANUAL |
| `TDD-WP0-STO-001` | 任一阈值使用模糊比例或边界错一字节 | 第 5.5 节七个精确字节值产生固定 notice/status | UNIT、DB |
| `TDD-WP0-STO-002` | 写入预计量用 `>=` 拒绝等号，或超限后仍有副作用 | `>` 才拒绝；等于上限提交成功后 full；超限不创建任务、冻结、文件关系或上游调用 | UNIT、DB、API |
| `TDD-WP0-STO-003` | full/unavailable 动作混淆或清理排队即减字节 | full 允许 JSON 保存与客户端仅下载；unavailable 拒绝新增后端写；物理删除和重测前计量不变 | DB、API、WRK、E2E |
| `TDD-WP0-FILE-001` | 文件流、数据库事务或进程崩溃产生可读半文件、孤儿引用或计量漂移 | 预检、staging 流式哈希/MIME、同卷原子改名、SQLite 提交、补偿队列和启动对账全覆盖；路径规范化拒绝 `..`、符号链接逃逸和 manifest 外对象 | DB、API、WRK |
| `TDD-WP0-CACHE-001` | 应用缓存超过 157,286,400 字节或含私有/内部内容 | LRU 在写前淘汰；只含 allowlist 公开资源；逐场景枚举 SW、Cache Storage、IndexedDB | UNIT、E2E |
| `TDD-WP0-EVT-001` | SSE 携带私有正文、被客户端当作业务真相或事件缺号后不恢复 | 事件只含固定非敏感字段；客户端收到后 REST refetch；断线/缺号 bootstrap；配置和 runtime 版本分离 | API、E2E |
| `TDD-WP0-SEC-001` | 凭据进入参数、环境、文件、日志或错误 | Supervisor 通过继承匿名管道最小传递；API/Worker 分离；缺失、清除、重启和扫描全通过 | PKG-SEC |
| `TDD-WP0-LOG-001` | 日志无限增长、含敏感数据或目录不可写后仍调用 AI | 10 MiB/10 文件/30 天轮转；白名单字段；写失败进入 unavailable 并阻断 AI | DB、WRK、PKG-SEC |
| `TDD-WP0-PKG-001` | ZIP 缺组件、包含真实配置或动态换端口 | 便携布局、SHA-256、START-HERE、RELEASE 记录和固定端口全部正确，制品零凭据 | PKG-SEC |

WP-0 Green 不等于 AC-24 发布通过；真实浏览器记录必须在 WP-7 重新冻结。

### 6.2 WP-1 认证、权限、数据模型与审计基础

| 测试族 | 首个 Red | Green 断言 | 层级 |
| --- | --- | --- | --- |
| `TDD-WP1-AUTH-001` | 有效邀请注册任一步部分成功 | 发码前和建号前复核；账号、初始 10 点、registration_grant、告知版本和邀请码次数同事务一次提交 | DB、API、E2E |
| `TDD-WP1-AUTH-002` | 无效/过期/停用/耗尽邀请码仍发码或占用次数 | 不发码、不建号、不赠点，返回稳定原因且无副作用 | DB、API、E2E |
| `TDD-WP1-AUTH-003` | 注册/登录对未注册、已注册、已注销、suspended 邮箱静默切换流程 | 六种状态严格按 PRD 分离入口；已注销邮箱只能新邀请重建且旧数据不恢复 | API、E2E |
| `TDD-WP1-AUTH-004` | 验证码重用、过期仍有效或会话未及时撤销 | 10 分钟一次性、60 秒重发、30 天会话；退出/暂停/注销/白名单移除立即撤销 | DB、API |
| `TDD-WP1-NOTICE-001` | 未勾选、版本缺失或告知遗漏第三方/本机数据处理仍可注册 | 告知含版本和生效日期，覆盖 AI 网关、Resend、高德、本机存储与无备份、管理员访问、DYN004、注销和 180 天规则；未同意不建号，成功时记录版本/时间 | UNIT、API、E2E、MANUAL |
| `TDD-WP1-SLOT-001` | suspended 不占名额、super_admin 占名额或第 11 人可注册 | active+suspended 普通账号最多 10；deleted 释放；super_admin 不占且不用邀请码 | DB、API、E2E |
| `TDD-WP1-ADM-001` | 白名单外发码、普通账号提升或全部管理员停用后自助恢复 | 后台独立 audience；多管理员可用；恢复仅安全配置；部署人员无产品身份 | DB、API、E2E |
| `TDD-WP1-CFG-001` | 安全配置修订部分生效或泄露白名单/HMAC | 完整验证后单一事务生效、撤销会话、system 审计和 revision 推进；失败整体回滚 | DB、PKG-SEC |
| `TDD-WP1-DEL-001` | 注销后仍可访问账号、项目、资源、坐标或会话 | 注销事务立即撤权和断链，物理内容异步清理，邮箱可作为全新账号重新注册 | DB、API、WRK、E2E |
| `TDD-WP1-DEL-002` | 匿名事件可回溯或注销延长访问审计期限 | 只保留允许字段和随机主体；匿名事件不晚于注销后 180 天；访问审计沿原 expires_at | DB、WRK |
| `TDD-WP1-AUD-001` | 普通连接可改删两类审计 | trigger 阻断 UPDATE/DELETE；仅保留期/隐私清理作用域可执行规定删除 | DB |

真实 Resend 认证在 WP-7 才能把相关 AC 从 green 提升为 passed；开发预置账号永远不能成为正式认证证据。

### 6.3 WP-2 项目、点数与 GenerationJob

| 测试族 | 首个 Red | Green 断言 | 层级 |
| --- | --- | --- | --- |
| `TDD-WP2-PROJ-001` | 新建/继续生成/历史上限混淆项目数量或比例 | 每次“新建创作”新项目；继续生成只追加；比例固定；10 张历史阻断追加但不阻断新项目 | DB、API、E2E |
| `TDD-WP2-PROJ-002` | 连续编辑并发保存、失败显示已保存或刷新恢复私有草稿 | 1 秒 debounce、单请求在途、最新快照合并；失败重试；强制退出只恢复 saved，撤销栈为空 | UNIT、API、E2E |
| `TDD-WP2-PROJ-003` | 回收站计时、20 项目恢复或 purge 可恢复边界错误 | purge_at 精确 +720 小时；满 20 保持 trashed；purged 立即不可访问并异步清理 | DB、API、WRK、E2E |
| `TDD-WP2-PROJ-004` | 旧标签覆盖、另存副本或多次冲突导出 | CAS 返回 412；旧页只读；仅一次本地导出且不写 latest；刷新后端最新状态 | API、E2E |
| `TDD-WP2-PROJ-005` | 重试新建项目或成功项目混入 failed_empty 批量删除 | 同一草稿每次重试只新增任务；仅无成功图草稿可批量入回收站 | DB、API、E2E |
| `TDD-WP2-CRED-001` | 点数不足、重复结算或生成流程制造负数 | 不足不建任务/流水/上游；reserve/commit/release 各一次；生成流程 available 不为负 | DB、API、WRK |
| `TDD-WP2-CRED-002` | 管理员调点修改 reserved 或缺原因/审计 | 只改 available，可为负；原因、流水和 AdminOperationLog 同事务 | DB、API、E2E |
| `TDD-WP2-GEN-001` | 并发提交创建两个任务、项目、参考关系或冻结 | `BEGIN IMMEDIATE`+部分唯一索引只保留一个 queued/running；后续返回当前任务 | DB、API |
| `TDD-WP2-GEN-002` | 成功/失败/拒绝与点数、项目历史或重试不一致 | 成功一图并 commit；失败/rejected 无输出并 release；重试同项目新任务 | DB、API、WRK、E2E |
| `TDD-WP2-GEN-003` | 旧 model_config_version 静默按新成本或限制提交 | 返回 412、最新摘要；不建任务、不上传引用、不冻结、不调用上游；重新确认后才提交 | API、E2E |
| `TDD-WP2-REF-001` | 未提交参考图被持久化或重试覆盖旧关系 | 提交前只在内存；每个任务保存独立关系快照；purged 后立即不可读并异步删除 | DB、API、E2E |
| `TDD-WP2-EXP-001` | 项目/成品保存失败撤回下载或覆盖旧 latest | 同一次 Blob 用于下载和上传；下载可成功、latest 保持旧值；刷新只恢复 saved | API、E2E |
| `TDD-WP2-EXP-002` | 从未导出项目自动生成默认成品 | 显示“暂无导出成品”，只提供进入编辑导出和下载原图 | API、E2E |
| `TDD-WP2-BAL-001` | 模型/账户/未知余额影响改写配置或重复释放 | 按影响范围只改 runtime；只 release 一次；未确认恢复前不可恢复 | DB、API、WRK |
| `TDD-WP2-ERR-001` | 九类错误被新增、归并、泄露原文或错误处理点数 | 三模型统一固定 category；提交前无任务，提交后只 release 一次；状态和用户动作符合第 9 节 | UNIT、API、WRK、E2E |

### 6.4 WP-3 三模型契约验证与适配

| 测试族 | 首个 Red | Green 断言 | 层级 |
| --- | --- | --- | --- |
| `TDD-WP3-MDL-001` | 初始集合零/多默认、优先级非法或 runtime 假装 verified | 三模型固定 seed；一个 enabled default；priority 1/2/3；contract unverified 时 runtime 不可用 | DB、API |
| `TDD-WP3-MDL-002` | 停用默认、重复优先级或 CAS 冲突产生部分配置 | 完整三模型集合原子校验和切换；失败原集合、runtime 和审计结果符合协议 | UNIT、DB、API、E2E |
| `TDD-WP3-MDL-003` | runtime 降级改变 enabled/default/priority 或推荐为空时写回配置 | 配置字段不变；推荐只从可用候选按 priority 派生，可为 null | UNIT、DB、API、E2E |
| `TDD-WP3-ADP-001` | 一个模型 fixture 被复用于另两个模型或静默接受多图 | 三个独立 Adapter 分别验证纯文字、参考图、四比例、同步/异步、响应和一图约束 | EXT-MOCK、EXT-REAL |
| `TDD-WP3-ADP-002` | 契约字段变化后仍为 verified | 端点、字段、参考图、响应、异步查询或错误协议任一变化立即 unverified，完整重验后才恢复 | DB、EXT-MOCK、EXT-REAL |
| `TDD-WP3-WRK-001` | Worker 崩溃后重新 submit、重复结算或丢任务 | 租约/心跳；有引用只 poll；无安全引用则 unknown_retryable+release+pending_manual_review | DB、WRK |
| `TDD-WP3-BAL-001` | 余额恢复自动执行或成本核对补扣用户点数 | model/account/unknown 影响正确；人工确认恢复并审计；成本核对不改任务终态或用户点数 | DB、WRK、E2E |

三个模型任一缺少最后一次配置下的真实证据时，WP-3 保持 externally_blocked。

### 6.5 WP-4 桌面编辑器与导出

| 测试族 | 首个 Red | Green 断言 | 层级 |
| --- | --- | --- | --- |
| `TDD-WP4-BG-001` | 换底图丢覆盖层、保留旧调色或色卡不刷新 | 覆盖层位置/尺寸保留；底图参数重置；色卡重提；一次撤销和保存 | UNIT、E2E |
| `TDD-WP4-BG-002` | 填充/适配/裁剪/缩放/定位或亮度、对比度、饱和度、色温、锐度、预设滤镜、恢复原图未保存、不可撤销或未进入导出 | 每项参数可预览、提交、撤销、自动保存并在重开与导出中一致；恢复原图只重置底图处理，不改变覆盖元素 | UNIT、E2E、VIS-PERF |
| `TDD-WP4-CAN-001` | 选择循环、多选、层级、吸附或第 51 元素错误 | 50 元素可编辑保存导出；第 51 个阻断；无图层面板/隐藏/锁定/编组 | UNIT、E2E、VIS-PERF |
| `TDD-WP4-TXT-001` | 多行丢换行、空内容提交或切模板改变变换 | 内容/换行持久；模板切换重置默认样式但保留位置/旋转/整体尺寸，可撤销 | UNIT、E2E、VIS-PERF |
| `TDD-WP4-TXT-002` | 字体静默回退、字号与控制柄不同步、文字裁切、范围步长错误或背景透明度影响文字 | 真实归档字体；字号与缩放同步且内容重新测量不裁切；行距/字距/描边等精确范围；完成为一次撤销；重新打开和导出一致 | UNIT、E2E、VIS-PERF |
| `TDD-WP4-TXT-003` | 未选模板即可添加普通文字，或模板分类、显示名称搜索、catalog 顺序、账号最近使用漂移 | 只能从模板添加；四类、显示名称搜索、稳定顺序和账号级最近使用符合 PRD；普通贴纸不复用该搜索 | UNIT、E2E |
| `TDD-WP4-STK-001` | 普通贴纸变换不进入保存/撤销/导出 | 移动、缩放、旋转、翻转、透明度、复制、层级和多选一致 | UNIT、E2E |
| `TDD-WP4-COL-001` | 色卡随机、读取处理后底图或额外上传图片 | 同图 `mmcq-v1` 五色确定；裁剪/调色/滤镜不刷新；换底图刷新；网络上传为 0 | UNIT、E2E、VIS-PERF |
| `TDD-WP4-DYN-001` | 时间贴纸重开后变成新时间 | 使用插入/确认值并持久化，导出静态不可点击内容 | UNIT、E2E |
| `TDD-WP4-DYN-002` | DYN004 提示前调用定位、拒绝后插入或删除后留坐标 | 同意后才定位并保存坐标；拒绝调用为 0；手动地点回退；删除/注销清理 | UNIT、E2E |
| `TDD-WP4-DYN-003` | 身份贴纸重复 `@@`、覆盖修改账号或 DYN012 静默换字体 | 前缀一个；实例覆盖独立；DYN012 固定 FONT081 且证据标记替代 | UNIT、E2E、MANUAL |
| `TDD-WP4-EXP-001` | 四比例像素、JPG/PNG、未提交修改或同 Blob 规则错误 | 精确像素、sRGB、无水印、PNG 无损、JPG 80-100 默认 92；未提交文字或样式经确认后，提交形成且仅形成一次撤销记录并进入正常自动保存，再用提交后的同一画布状态合成；真正的导出动作不额外创建撤销记录 | UNIT、E2E、VIS-PERF |
| `TDD-WP4-A11Y-001` | 关键动作不可键盘到达、焦点逃逸或状态只靠颜色 | 焦点顺序、ARIA、live region、弹层返回、等价画布工具、axe critical/serious 为 0 | E2E、VIS-PERF |
| `TDD-WP4-VIS-001` | Chrome/Edge 白名单结构或导出漂移 | 固定环境按第 10 节阈值比较；已知替代项人工复核 | VIS-PERF、MANUAL |
| `TDD-WP4-PERF-001` | 50 元素、自动保存、资源面板或导出超过预算 | 第 10.2 节全部预算通过，失败不破坏项目或 latest export | VIS-PERF |

### 6.6 WP-5 素材转换、发布与 P0-A 白名单

| 测试族 | 首个 Red | Green 断言 | 层级 |
| --- | --- | --- | --- |
| `TDD-WP5-CAT-001` | 计数、part、重复项、顺序或懒加载错误 | part1-25 共 1,407 PNG；3 组重复保留；虚拟列表节点不线性增长，原图按需加载 | UNIT、E2E、MANUAL |
| `TDD-WP5-MAN-001` | 编译器读取证据目录、执行 Lua/Prefab、复制全库或改写源 | 只读规范入口；声明式转换；源前后哈希/时间不变；无等价全库副本 | UNIT、PKG-SEC、MANUAL |
| `TDD-WP5-WHITE-001` | 普通用户发现白名单外复杂素材或缺少规定白名单 | 精确 32 文字、实际字体+FONT081、4 色卡、10 动态、1,407 普通贴纸 | API、E2E |
| `TDD-WP5-COL-001` | P0-A 启用非四种色卡或四种布局不消费同一五色 | 只启用 COLOR001/002/008/016，统一五色，刷新规则与 WP-4 一致 | UNIT、E2E、VIS-PERF |
| `TDD-WP5-RES-001` | 公开 manifest 暴露 internal/private、路径可推导或缓存策略混用 | 三类路由、鉴权和 cache header 分离；浏览器永不获得绝对路径 | API、E2E、PKG-SEC |
| `TDD-WP5-UPL-001` | 后台上传未流式校验、原图/缩略图漏计量或 full 仍写入 | PNG/WebP、稳定 ID、part、顺序和 MIME 校验；两类文件分别预留并计量 | DB、API、E2E |
| `TDD-WP5-CLN-001` | 自动清理、有引用仍删除、整批部分排队或排队即减容量 | 候选、同管理员确认、BEGIN IMMEDIATE 复核、整批拒绝/排队、物理删除、重测和全阶段审计 | DB、API、WRK、E2E |
| `TDD-WP5-PREV-001` | 授权改变角色、公开 manifest 泄漏或撤销后仍可取资源 | 每次校验 grant；no-store；撤销立即阻断新访问；已有项目按版本渲染 | DB、API、E2E |
| `TDD-WP5-CACHE-001` | SW 拦截 internal/private 或 IndexedDB 保存私有字段 | 仅公开 allowlist；每场景实际枚举 registration、Cache keys 和 IndexedDB 记录 | E2E |

真实 3.57 GB 归档只在受控 release job 只读校验；日常 CI 使用小型合成 fixture。

### 6.7 WP-6 运营后台与内部运营控制

| 测试族 | 首个 Red | Green 断言 | 层级 |
| --- | --- | --- | --- |
| `TDD-WP6-ADM-001` | 普通/预览账号进入后台、后台显示私有正文或敏感配置 | active super_admin 服务端授权；默认仅安全摘要；角色和会话域严格分离 | API、E2E |
| `TDD-WP6-PRIV-001` | 告知确认只存在 Cookie/session/localStorage 或同版本重复 | UserProfile 按 admin+version 持久；跨标签、浏览器/API 重启、重登和 Chrome/Edge不重复；升版重阻断 | DB、API、E2E |
| `TDD-WP6-PRIV-002` | hover/预取打开内容、确认替代逐次审计、要求填写访问目的/原因或审计失败仍返回内容 | 每个入口原样显示“打开并记录审计”；每次点击先提交独立 PrivateContentAccessLog；UI 和 API 均不要求逐次填写访问目的、原因或二次确认；审计只记录 PRD/DevelopmentPlan 允许的对象、管理员、时间、内容类型等非正文信息；失败不返回；列表/HTML/缓存无正文 | DB、API、E2E |
| `TDD-WP6-SVC-001` | Resend 发第 81 封/超月限、高德发第 1001 次或暂停后伪造成功 | 配额领取事务化；paused_quota/provider；已有会话继续；DYN004 停而手动地点继续 | DB、API、EXT-MOCK、E2E |
| `TDD-WP6-SVC-002` | 后台提高上限、启用收费或自动恢复 | 只允许不扩大能力的下调并审计；提高/付费/自动扩容被拒绝；恢复需前置确认 | DB、API、E2E |
| `TDD-WP6-AUD-001` | 敏感操作无日志、两类日志混用、含正文或 180 天内可删 | 全部规定操作同事务审计；贴纸清理全阶段；两类表分离；到期 Worker 清理 | DB、API、WRK、E2E |
| `TDD-WP6-STATE-001` | 后台把 configured default、runtime availability 和 recommended 合并 | 三列独立；临时异常不改配置；恢复只重新派生推荐 | API、E2E |
| `TDD-WP6-DIAG-001` | 总览/服务/存储/诊断暴露路径、密钥或正文 | 只显示固定非敏感字段、计量和状态；复制诊断通过同一脱敏扫描 | API、E2E、PKG-SEC |

### 6.8 WP-7 P0-A 验收准备与发布

| 测试族 | 首个 Red | Green 断言 | 层级 |
| --- | --- | --- | --- |
| `TDD-WP7-REL-001` | `RELEASE.json` 缺失/漂移、ZIP 哈希或真实浏览器未验收 | 发布记录、制品、START-HERE、Chrome/Edge full version、固定 major 和 Windows build 一致 | PKG-SEC、E2E、MANUAL |
| `TDD-WP7-EXT-001` | 任一模型缺独立真实契约证据 | 三模型各自完成纯文字、参考图、四比例、同步/异步、响应、九类错误和结算证据 | EXT-REAL、MANUAL |
| `TDD-WP7-EXT-002` | Resend 域名/免费规则/送达门槛未证实 | 依据 PRD 第 23.2 节、AC-41、AC-47 与 DevelopmentPlan 第 1.4、11.1 节：SPF/DKIM；QQ、163、企业邮箱各 20 封至少 19 封在 2 分钟内送达；正式账号真实认证 | EXT-REAL、MANUAL |
| `TDD-WP7-EXT-003` | 高德接口、绑定、安全限制或 1,000 次硬上限未证实 | 定位/逆地理、QPS、白名单、安全和应用硬停止实测通过 | EXT-REAL、MANUAL |
| `TDD-WP7-UI-001` | UIDesign 页面/入口或适用状态无实现落点，或 1080p/200% 出现遮挡 | 第 11.1 节页面 × 状态矩阵全部适用单元格有证据；Pencil 仅作布局参考，Canvas Index 不计产品实现；固定文案、焦点、状态、控件和无重叠检查通过 | E2E、VIS-PERF、MANUAL |
| `TDD-WP7-SEC-001` | 制品、日志、DB 配置、manifest 或文档出现真实敏感值/用户路径 | 全制品扫描零命中；凭据只在 Windows Credential Manager；诊断脱敏 | PKG-SEC |
| `TDD-WP7-AC-001` | 52 项 AC、89 个测试族或 117 个规范 case 缺失，重复主责、孤儿 case 或用 P0-B 结果冒充 | 第 12 节矩阵每项一个主责且证据齐全；89/89 父族有 case 且派生属性完整；未来四项不计入；四份全局来源哈希未变，UI 参考变化按第 1.2.2 节局部重验 | UNIT、MANUAL |

任一真实外部门槛失败时不得开始 P0-A 实际验收，也不得把 WP-7 标记 passed。

### 6.9 高风险可执行 case

第 6.1 至 6.8 节的 89 个测试族是覆盖导航，不代替逐场景用例。第 6.9.2 至 6.9.9 节给出重点能力的细分 case，第 6.9.10 至 6.9.17 节为其余父族给出至少一个规范 case；合计 117 个 case，保证 89/89 父测试族均有执行入口。同一测试族的参数分支仍按第 2.2 节展开。实现不得删除 case、合并掉禁止副作用断言，或用一条端到端 happy path 代替 DB/API 分层断言。

#### 6.9.1 通用执行约定

- API case 默认使用已通过浏览器门禁的请求上下文、`Host: 127.0.0.1:43121`、同源 Origin、正确 audience 的隔离 session、页面加载取得的 CSRF token；所有创建型、金融或文件副作用请求使用稳定的独立 `Idempotency-Key`。case 明确测试门禁或鉴权失败时才替换这些默认值。
- `REGISTER_SEND`、`REGISTER_COMPLETE`、`LOGIN_SEND`、`LOGIN_COMPLETE`、`PRIVATE_OPEN` 和 `EXPORT_SAVE` 是测试别名，分别在生成的 OpenAPI 中解析到 `/api/v1/auth/register/*` 发码/完成、`/api/v1/auth/login/*` 发码/完成、`/api/v1/admin/private-content/*` 单对象打开和 `/api/v1/projects/{id}/exports` latest 成品保存 operation。别名解析不到唯一 operation、请求或响应不符合 TypeBox/OpenAPI 时先使 `TDD-WP0-API-001` 失败；不得为满足用例新增产品路由。
- 表中 `case_id` 就是第 2.2 节的可执行 `test_id`。父测试族必须先与第 6.1 至 6.8 节的已知父族集合匹配，再用正则 `^(TDD-WP[0-7]-[A-Z0-9]+-[0-9]{3})-` 解析，且捕获组 1 必须是该已知父族；不得通过删除最后一个命名段或按最后一个连字符截断。`result.json` 必须逐项记录第 2.2 节全部字段，包括父测试族继承的 WP、层级、自动/人工属性和发布门禁；不能只记录测试族汇总结果。
- 每个 case 在临时 SQLite、临时 LocalDataRoot、可控 UTC 毫秒时钟和独立浏览器 profile 中运行。步骤 0 固定保存脱敏 DB/文件基线；最后一步固定复查余额不变量、活动任务唯一索引、审计不可变约束和证据脱敏。
- 证据根固定为 `artifacts/tdd/<run_id>/cases/<case_id>/`。每个目录必须有 `result.json`；API case 增加 `request.json`、`response.json`，DB case 增加 `db-diff.json`，文件 case 增加 `fs-before.json`、`fs-after.json`，Worker case 增加 `worker-events.json`，E2E case 增加 `trace.zip` 和 `screenshots/`。文件只保留 Schema 白名单字段、哈希和逻辑资源 ID，不保留正文、图片、邮箱、验证码、令牌或绝对路径。
- 表中的“响应”同时是 HTTP 与 UI 断言：HTTP 状态、错误信封和字段以冻结 OpenAPI 为准；上游只冻结语义而未冻结具体成功状态码/字段名时，case 断言生成 Schema 所定义的成功响应及列出的业务结果，不在本文件发明字段。

#### 6.9.2 认证

| case_id | 需求 / AC | fixture 与前置 | 编号操作步骤 | 预期响应 / UI | DB / 文件变化 | 禁止副作用 | 证据文件 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `TDD-WP1-AUTH-001-valid-register` | AUTH-01 至 04、AC-01 | `FX-INVITE-VALID`、`FX-CHALLENGE`；占名额普通账号为 9；未注册 `.invalid` 邮箱 | 1. 调 `REGISTER_SEND`；2. 读取模拟 Resend 验证码；3. 以署名、社交 ID、当前告知 version/同意和同一 registration_id 调 `REGISTER_COMPLETE`；4. 调普通 session | 两次均符合成功 Schema；注册后得到普通 audience 的 30 天会话并进入创作，点数显示 available 10/reserved 0 | `users/user_profiles/credit_accounts/privacy_consents` 各新增正确关系；`invite_codes.used_count +1`；challenge 只消费一次；新增 session 只存 token 摘要；初始点数流水一次 | 不创建 super_admin；不保存验证码明文；不重复赠点或核销邀请码；不产生第二会话/第二注册实体 | `request.json`、`response.json`、`db-diff.json`、`trace.zip`、`screenshots/registered.png` |
| `TDD-WP1-AUTH-002-invalid-invite` | AUTH-01、02、AC-02 | 参数化 `FX-INVITE-EXPIRED/DISABLED/EXHAUSTED` 和不存在的邀请码 | 1. 对每个参数调 `REGISTER_SEND`；2. 检查 Resend mock 调用；3. 查询认证相关表 | 返回冻结的非成功错误语义并在邀请码字段显示原因，第二阶段不展开 | `users/user_profiles/credit_accounts/privacy_consents/email_challenges/sessions` 无新增；邀请码计数不变 | Resend 调用为 0；不建号、不赠点、不占名额、不写成功审计或成功提示 | 每参数 `response.json`、`db-diff.json`、`external-calls.json`、`screenshots/error.png` |
| `TDD-WP1-AUTH-001-final-recheck-race` | AUTH-01、NFR-04、AC-01、02、45 | `FX-INVITE-VALID` 已成功发码；随后参数化为邀请码耗尽/停用/过期或第 10 个名额被另一事务占用 | 1. 完成前改变可变条件；2. 调 `REGISTER_COMPLETE`；3. 用相同 Idempotency-Key 重试 | 返回对应冻结错误；不建立 session，不进入创作；相同重试返回同一失败结果 | `BEGIN IMMEDIATE` 后无新 user/profile/credit/consent/session；邀请码次数不增加；名额统计不变 | 不出现账号已建但邀请码未核销、邀请码已核销但账号未建或初始点数孤儿流水 | `response.json`、`db-diff.json`、`transaction-trace.json` |
| `TDD-WP1-AUTH-003-entry-state-matrix` | AUTH-01、02、05、06、AC-33 | 参数化未注册、active 已注册、deleted、suspended 邮箱；注册和登录两个入口 | 1. 每种邮箱分别调注册发码与登录发码；2. 对允许分支完成验证码；3. 检查页面不自动切入口 | 行为严格匹配 PRD AUTH-02 状态矩阵；deleted 仅可新邀请注册；suspended 均提示联系管理员 | 只允许的分支创建 challenge/session/new user；重复注册不核销邀请码；deleted 新建主体与旧主体无关联 | 不静默注册/登录转换；不恢复 deleted 数据；不因 suspended 释放名额；错误不泄漏后台白名单 | 每参数 `response.json`、`db-diff.json`、`trace.zip`、`screenshots/entry-state.png` |
| `TDD-WP1-AUTH-004-challenge-guards` | AUTH-02、04、AC-33 | `FX-CHALLENGE`、可控时钟、active 普通账号 | 1. 成功消费验证码；2. 重放同码；3. 新建 challenge 后前进超过 10 分钟再提交；4. 发码后 60 秒内再发；5. 连续错误触发限流 | 仅首次有效提交成功；重放/过期/错误使用稳定错误；60 秒重发和高频为 429；页面不显示假成功 | 首次 challenge `consumed_at` 只写一次；失败次数/限流记录按规则变化；失败分支不新增 session | 不记录验证码明文；不延长原 challenge；限流失败不调用 Resend、不创建会话 | 每参数 `response.json`、`db-diff.json`、`external-calls.json` |
| `TDD-WP1-AUTH-004-session-revocation` | AUTH-04 至 07、AC-22、33、49 | 每个参数使用独立库：active 普通用户或 active super_admin 各有 2 个 session | 1. 验证正常 session；2. 分别触发普通用户 logout/暂停/注销或管理员 logout/停用/移出白名单；3. 再调受保护 API | 撤销前成功；每种撤销后均为 401 或相应身份入口，已打开页面退出旧数据；两种 audience 不能互用 | 相应主体全部 session `revoked_at` 在同一事务写入；账号/白名单状态按触发操作变化 | 不只撤销当前浏览器；不把普通 session 提升为后台 session；不保留可交互私有响应 | 每参数 `response.json`、`db-diff.json`、`trace.zip`、`screenshots/revoked.png` |

#### 6.9.3 点数

| case_id | 需求 / AC | fixture 与前置 | 编号操作步骤 | 预期响应 / UI | DB / 文件变化 | 禁止副作用 | 证据文件 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `TDD-WP2-CRED-001-reserve` | CREDIT-01 至 05、AC-03 | `FX-CREDIT-ENOUGH`、`FX-MODEL-VERIFIED`，无活动任务 | 1. 记录余额；2. 以成本 1 调 `POST /api/v1/generations`；3. 查询 `/me/credits` 和流水 | 创建任务成功；UI 显示 available 9/reserved 1 和 queued；流水显示 generation_reserve | `credit_accounts` 原子 10/0→9/1；一条 reserve ledger、一个 queued job 和 outbox 同事务 | 不提前生成 commit；不使总点数改变；不出现任务已建但未冻结或冻结无任务 | `request.json`、`response.json`、`db-diff.json` |
| `TDD-WP2-CRED-001-insufficient` | CREDIT-04、AC-28 | `FX-CREDIT-INSUFFICIENT`，成本 1 | 1. UI 检查生成按钮；2. 直接调生成 API 防绕过；3. 查询 DB/网关 mock | UI 禁用并显示 available/reserved/required/联系管理员；API 返回冻结错误语义 | 余额、ledger、job、project、reference、outbox 均无变化 | 上游调用 0；无 storage reservation/staging 文件；不产生负余额 | `response.json`、`db-diff.json`、`external-calls.json`、`screenshots/insufficient.png` |
| `TDD-WP2-GEN-001-concurrent-reserve` | CREDIT-03、GEN-07、AC-05、29 | `FX-CREDIT-ENOUGH`，两个不同 submission/idempotency key 同时开始 | 1. 用 barrier 并发提交两次；2. 等待响应；3. 查询 current job、余额和流水 | 一个响应创建任务，另一个 200 返回同一现有任务引用；UI 提供返回当前任务 | 仅一个 queued/running job、一个 project（new_project 模式）、一条 reserve ledger；余额只转移一次 | 不创建第二项目/参考关系/outbox/upstream call；available 不为负 | 两份 `response.json`、`db-diff.json`、`concurrency-trace.json` |
| `TDD-WP2-CRED-002-admin-adjustment` | CREDIT-05、06、ADMIN-01、09、AC-29、50 | `FX-JOB-ACTIVE`；available 0/reserved 1；`FX-ACTOR-ADMIN-A` | 1. 管理员填写原因扣减 5；2. 用同 adjustment_id 重试；3. 用户读点数 | 首次成功，重试返回同一结果；用户显示 available -5/reserved 1；无充值入口 | available 0→-5，reserved 保持 1；一条 admin_adjustment ledger 和一条 AdminOperationLog 同事务 | 不改活动任务/冻结流水；不重复调点；不允许空原因；不使生成流程自身制造负数 | `response.json`、`db-diff.json`、`screenshots/negative-balance.png` |
| `TDD-WP2-CRED-001-finalize-once` | CREDIT-03、05、GEN-09、10、AC-03、04、29 | 参数化成功、技术失败和安全拒绝的 `FX-JOB-ACTIVE` | 1. Worker 提交终态；2. 重复 callback/poll；3. 模拟 Worker 重启再处理 | 成功为 succeeded；技术失败为 failed；安全拒绝为 rejected；用户余额/动作匹配固定类别 | 每任务恰一条 commit 或 release；reserved 恰减一次；成功才建 output/history，失败/拒绝 available 恢复 | 不双重 commit/release；不在失败建 output；终态不可逆；不保存供应商原文 | `worker-events.json`、`db-diff.json`、`fs-before.json`、`fs-after.json` |

#### 6.9.4 GenerationJob

| case_id | 需求 / AC | fixture 与前置 | 编号操作步骤 | 预期响应 / UI | DB / 文件变化 | 禁止副作用 | 证据文件 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `TDD-WP2-GEN-001-submit-snapshot` | GEN-01 至 08、PROJECT-01、AC-03、34、44 | `FX-MODEL-VERIFIED`、`FX-CREDIT-ENOUGH`、`FX-REF-VALID`，new_project | 1. multipart 提交 prompt/model/ratio/config version/cost/reference；2. 查询任务；3. 检查 staging/正式文件 | 成功响应为 queued，返回同一任务真相；UI 不显示假百分比 | 新 project、job、reference snapshot、reserve ledger、outbox 同事务；提交后 reference 从 staging 进入私有 managed file | 不接受未冻结高级字段；不让 Worker 领取未完整提交任务；不暴露路径；失败补偿不留下可读半文件 | `request.json`、`response.json`、`db-diff.json`、`fs-before.json`、`fs-after.json` |
| `TDD-WP2-GEN-002-success` | GEN-06、08、11、CREDIT-03、AC-03 | 上一 case 的 queued job + `FX-GATEWAY-SYNC` | 1. Worker 领取；2. 调上游并验证单图；3. 落盘；4. 查询任务/项目 | queued→running→succeeded；项目可进入编辑且历史新增一图 | lease/heartbeat、output managed file、output_asset、generation_commit、project history/draft_state 一次提交；容量增加实测字节 | 不在文件安全落盘前 succeeded；不保存多图；不重复扣点；不改变项目比例 | `worker-events.json`、`db-diff.json`、`fs-after.json`、`screenshots/succeeded.png` |
| `TDD-WP2-ERR-001-upstream-timeout` | GEN-09、16、17.18、AC-04、53 | 独立 `upstream_timeout` fixture；已提交任务超时 | 1. 触发超时；2. 检查唯一下一步为“使用原输入重试”；3. 点击该动作；4. 查询原任务、新任务和项目 | 原任务 failed/category=`upstream_timeout`；只提供“使用原输入重试”；动作后同项目新增一个 GenerationJob | 原任务 release 一次且无 output；新任务独立 reserve 并保存相同输入关系快照 | 不改成“稍后重试”；不要求修改输入/选择模型/联系管理员；不复用原任务 ID 或创建第二项目 | `response.json`、`worker-events.json`、`db-diff.json`、`screenshots/upstream-timeout.png` |
| `TDD-WP2-ERR-001-upstream-failed` | GEN-09、16、17.18、AC-04、53 | 独立 `upstream_failed` fixture；已提交任务临时失败 | 1. 触发临时失败；2. 检查唯一下一步为“稍后重试”；3. 在受控条件恢复后点击；4. 查询原任务、新任务和项目 | 原任务 failed/category=`upstream_failed`；只提供“稍后重试”；动作后同项目新增一个 GenerationJob | 原任务 release 一次且无 output；新任务独立 reserve 和关系快照 | 不显示“使用原输入重试”或输入/模型修改动作；不自动重试、不创建第二项目 | `response.json`、`worker-events.json`、`db-diff.json`、`screenshots/upstream-failed.png` |
| `TDD-WP2-ERR-001-safety-rejected` | GEN-10、16、17.18、AC-04、53 | 供应商明确安全拒绝证据；与 reference fixture 完全分离 | 1. 触发安全拒绝；2. 检查唯一下一步为“修改提示词或参考图”；3. 不修改输入尝试继续；4. 修改提示词或参考图后提交 | 原任务 rejected/category=`safety_rejected`；未修改时不能新提交；修改后同项目新增 GenerationJob | 原任务 release 一次、无 output；修改前无新 job/reserve；修改后新任务保存修正快照 | 不提供原样/稍后重试、选择模型或联系管理员；不保存违规输出；不映射为 reference_invalid | `response.json`、`worker-events.json`、`db-diff.json`、`screenshots/safety-rejected.png` |
| `TDD-WP2-ERR-001-model-disabled` | GEN-07、16、17.18、AC-30、53 | `model_disabled` 创建前 fixture；参数化存在/不存在其他 enabled 模型 | 1. 触发创建前阻断；2. 检查下一步仅“选择其他模型或等待”；3. 有可用模型时显式改选并确认；4. 无可用模型时保持页面等待 | 返回 category=`model_disabled` 且不创建任务；改选后才能提交；无模型时保持禁用 | 阻断分支无 job/project/reference/reserve/outbox；改选分支按新模型快照创建 | 不提供联系管理员、原输入重试或稍后重试；不自动切推荐或把 runtime 状态写回配置 | `response.json`、`db-diff.json`、`external-calls.json`、`screenshots/model-disabled.png` |
| `TDD-WP2-ERR-001-gateway-balance` | GEN-15、16、17.17、17.18、AC-40、51、53 | `gateway_balance_insufficient` 创建前/后 fixture；明确未受影响模型集合 | 1. 触发余额不足；2. 检查下一步仅“选择未受影响模型或联系管理员”；3. 显式选择未受影响模型并重新确认；4. 无候选时检查联系管理员 | 返回/保存 category=`gateway_balance_insufficient`；已提交任务 failed；只有改选未受影响模型后才能新提交 | 创建前无 reserve/job；创建后 release 一次；新任务使用改选模型快照；runtime 影响范围按 GEN-15 更新 | 不提供等待、原输入/稍后重试或任意“其他模型”；不自动充值、不用 Dada 点数恢复、不回写 ModelConfig | `response.json`、`db-diff.json`、`external-calls.json`、`screenshots/gateway-balance.png` |
| `TDD-WP2-ERR-001-gateway-contract` | GEN-13、16、17.18、AC-40、53 | `gateway_contract_invalid` 创建前/后 fixture；另一个 contract verified 模型 | 1. 触发契约未验证/失效；2. 检查下一步仅“选择其他模型或联系管理员”；3. 显式改选已验证模型并重新确认；4. 无候选时检查联系管理员 | 返回/保存 category=`gateway_contract_invalid`；已提交任务 failed；改选 verified 模型后才能新提交 | 创建前无 reserve/job；创建后 release 一次；失效模型 contract 状态按契约更新；新任务保存新模型/版本 | 不提供等待、原输入/稍后重试；不让未验证模型提交；不自动切推荐或部分改写配置 | `response.json`、`db-diff.json`、`external-calls.json`、`screenshots/gateway-contract.png` |
| `TDD-WP2-ERR-001-reference-invalid` | GEN-05、16、17.18、AC-04、44、53 | 格式/大小/数量和非安全型内容不可用四组 fixture；不得使用安全拒绝响应 | 1. 分别触发创建前/后无效；2. 检查唯一下一步为“更换或移除参考图”；3. 不改参考图尝试继续；4. 更换/移除后提交 | category=`reference_invalid`；未修改时不创建新任务；更换/移除后同项目新增 GenerationJob | 创建前无 job/reserve；创建后 failed+release 一次；修正后保存新的 reference snapshot | 不提供修改提示词、重试、等待、选模型或联系管理员；不映射为 safety_rejected | 每 fixture `response.json`、`worker-events.json`、`db-diff.json`、`screenshots/reference-invalid.png` |
| `TDD-WP2-ERR-001-unknown-retryable` | GEN-09、16、17.18、AC-04、53 | 独立 `unknown_retryable` fixture；已提交任务 | 1. 触发未知临时错误；2. 检查唯一下一步为“稍后重试”；3. 在受控条件恢复后点击；4. 查询任务和脱敏诊断 | 原任务 failed/category=`unknown_retryable`；只提供“稍后重试”；动作后同项目新增一个 GenerationJob | 原任务 release 一次、无 output；新任务独立 reserve；只保留非敏感诊断 | 不显示“使用原输入重试”、修改输入、选模型或联系管理员；不泄漏原始错误或自动重试 | `response.json`、`worker-events.json`、`db-diff.json`、`screenshots/unknown-retryable.png` |
| `TDD-WP2-ERR-001-unknown-non-retryable` | GEN-09、16、17.18、AC-04、53 | 独立 `unknown_non_retryable` fixture；已提交任务 | 1. 触发未知不可重试错误；2. 检查唯一下一步为“联系管理员”；3. 尝试寻找或调用重复提交动作；4. 查询任务 | 原任务 failed/category=`unknown_non_retryable`；只提供“联系管理员”，无重试或修改入口 | release 一次；无 output；原草稿保留但本 case 不创建新 job | 不自动重试、不循环轮询制造新任务、不降级为 unknown_retryable、不提供其他类别动作 | `response.json`、`worker-events.json`、`db-diff.json`、`screenshots/unknown-non-retryable.png` |
| `TDD-WP2-GEN-003-stale-config` | GEN-15、ADMIN-03、AC-30 | 用户已确认旧 `model_config_version/cost`；管理员原子发布新集合 | 1. 旧页面直接提交；2. 读取 412 摘要；3. 重新 GET models 并明确确认；4. 再提交 | 第一次 412 且要求重新确认；第二次才可创建任务 | 第一次 job/project/reference/reserve/outbox 全无变化；第二次按新快照创建 | 第一次不上游、不持久化参考图、不冻结；不自动替用户切推荐模型 | `response.json`、`db-diff.json`、`external-calls.json`、`trace.zip` |
| `TDD-WP3-WRK-001-recovery` | GEN-08、09、CREDIT-03、AC-04、51 | 参数化 `FX-GATEWAY-ASYNC` 有引用和 `FX-GATEWAY-UNKNOWN` 无安全引用；在 submit 后终止 Worker | 1. 使租约过期；2. 重启 Worker；3. 有引用分支 poll；4. 无引用分支终结；5. 再重启复查 | 有引用不重新 submit，只 poll 到真实终态；无引用为 failed/unknown_retryable，后台显示成本核对待人工 | 租约重新领取；无引用 `upstream_outcome_known=false`、`upstream_cost_reconciliation=pending_manual_review`，release 一次 | 不把 pending_manual_review 写入 job status；不补扣用户点数；不重复上游 submit/结算 | `worker-events.json`、`db-diff.json`、`external-calls.json` |

#### 6.9.5 容量

| case_id | 需求 / AC | fixture 与前置 | 编号操作步骤 | 预期响应 / UI | DB / 文件变化 | 禁止副作用 | 证据文件 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `TDD-WP0-STO-002-equal-limit` | 17.19、NFR-05、AC-55 | 已计量+活动预留=`5,368,709,119`，预计写 1 字节，可写目录 | 1. 调受管二进制写入预检；2. 提交文件/DB；3. 重读 storage state | 不得返回 507；写入成功后 notice/status 为 full | 建立并消费一份 reservation；managed bytes 精确为 `5,368,709,120`；managed file 已提交；last_measured_at 更新 | 不用 `>=` 拒绝；不回滚已成功等号写入；不调用自动清理 | `response.json`、`db-diff.json`、`fs-before.json`、`fs-after.json` |
| `TDD-WP0-STO-002-over-limit` | 17.19、NFR-05、AC-55 | 已计量+活动预留=`5,368,709,119`，预计写 2 字节 | 1. 分别尝试生成/参考图/export/贴纸原图/缩略图；2. 查询所有副作用 | 每次 507 `STORAGE_CAPACITY_EXCEEDED`，返回非敏感状态和 remaining bytes | storage state 保持由实际量决定；无新 reservation/managed file/latest export/job/ledger/outbox | 上游调用 0；无 staging 残留；不把单个超大请求伪造成持久 full（若现有承诺仍低于上限） | 每路由 `response.json`、`db-diff.json`、`fs-after.json`、`external-calls.json` |
| `TDD-WP0-STO-003-full-matrix` | 17.19、PROJECT-04、EXPORT-06、AC-55 | managed bytes 恰为上限，status full，SQLite 可写 | 1. 保存纯 JSON project state；2. 读取/下载已有文件；3. 客户端合成并仅下载；4. 尝试 latest 保存和新生成；5. 永久删除/显式清理 | JSON 保存、读取、下载、仅下载、删除/清理入口可用；latest/新生成/其他二进制写为 507 | JSON state_version 可增加；禁止项不新增 managed file/job/ledger；删除/清理只先逻辑提交/排队 | 不自动删除；客户端下载不更新 latest_exports；排队不提前减少 managed bytes | `response.json`、`db-diff.json`、`fs-before.json`、`fs-after.json`、`trace.zip` |
| `TDD-WP0-STO-003-unavailable-matrix` | 17.19、NFR-05、AC-35、55 | LocalDataRoot 不可写，再参数化 SQLite 仍可写/不可写 | 1. 尝试 project JSON 保存、生成和二进制写；2. 读取/下载/仅下载；3. 尝试删除/清理 | 新增后端写全部失败且 UI 为 failed/unavailable；读取、下载和可行的仅下载保留；SQLite 不可写时删除/排队明确未提交 | 不更新 state_version/managed bytes/latest/job/ledger；SQLite 可写时允许规定逻辑删除/队列，反之无 DB 变化 | 不显示已保存；不上游；不把内存草稿写浏览器持久存储；不伪造清理完成 | `response.json`、`db-diff.json`、`trace.zip`、`screenshots/unavailable.png` |
| `TDD-WP0-STO-003-cleanup-remeasure` | ADMIN-06、17.19、AC-31、55 | full；查询时全部为无引用、可清理的历史贴纸文件 | 1. 查询候选并创建 intent；2. 参数 A 在确认前给其中一项新增 active/trashed 项目、已发布版本或待发布引用，再确认；3. 参数 B 保持全部无引用并确认；4. 读取排队容量；5. Worker 物理删除并重计量 | A 返回 `ASSET_HISTORY_REFERENCE_CONFLICT` 且整批不排队；B 整批排队，排队阶段仍 full，物理删除并重计量后才按真实值恢复 | A 只追加 `result=denied` 审计且 managed bytes 不变；B scheduled 前后 bytes 不变，物理成功后 files/queue/bytes/status/last_measured_at 同事务更新 | 不让查询时已有引用文件进入候选；不部分排队冲突批次；不在物理删除前减量；不自动创建 intent | 每参数 `response.json`、`db-diff.json`、`worker-events.json`、`fs-before.json`、`fs-after.json` |

#### 6.9.6 私有内容访问

| case_id | 需求 / AC | fixture 与前置 | 编号操作步骤 | 预期响应 / UI | DB / 文件变化 | 禁止副作用 | 证据文件 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `TDD-WP6-PRIV-001-ack` | ADMIN-05、17.1、AC-25 | `FX-ACTOR-ADMIN-A` 未确认 current notice v1 | 1. 进入私有区域；2. 得到 428；3. 以 expected v1 调 notice ack；4. 重试区域摘要 | 首次 428 `PRIVATE_CONTENT_NOTICE_ACK_REQUIRED` 只含 version/message_key；ack 成功后区域仅显示安全摘要 | 只更新该 admin 的 notice version/ack time；幂等重试不新增语义变化 | ack 不创建 PrivateContentAccessLog；不返回正文/图片；不写 Cookie/localStorage 作为真相 | `response.json`、`db-diff.json`、`trace.zip`、`screenshots/notice.png` |
| `TDD-WP6-PRIV-001-persistence` | ADMIN-05、AC-25 | admin A 已确认 v1 | 1. 新标签；2. API 重启；3. 浏览器重启/重登；4. 在真实 Chrome 与 Edge 登录同一 admin | 同版本均不重复提示，session/bootstrap 计算 `notice_acknowledged=true` | UserProfile ack 字段不因浏览器/会话改变；每次 session 独立但 ack 不重复写 | Cookie/session/localStorage/IndexedDB 清空不使服务端遗忘；跨管理员不共享 ack | 每环境 `response.json`、`db-diff.json`、`trace.zip`、`screenshots/ack-persisted.png` |
| `TDD-WP6-PRIV-001-version-bump` | ADMIN-05、AC-25 | admin A/B 已确认 v1，应用 current notice 切到 v2 | 1. 两个 admin 分别进入区域；2. 尝试用 expected v1 ack；3. 读取 v2 并确认 | 两者重新 428；旧 expected version 失败且要求重读；各自确认 v2 后可进入摘要 | 旧 ack 保留历史语义但当前字段仅在正确 v2 ack 后更新；A/B 独立 | 不以旧确认放行；不因 A 确认替 B 确认；不返回私有正文 | `response.json`、`db-diff.json`、`trace.zip` |
| `TDD-WP6-PRIV-002-open-after-audit` | ADMIN-05、17.10、AC-25、50 | active admin A 已确认 current；目标含图片和完整提示词 | 1. 列表确认无正文；2. 不带 purpose/reason 调 `PRIVATE_OPEN`；3. 在提交钩子确认审计落库后才释放受控读取；4. 对同目标再打开 | 每个入口原样“打开并记录审计”；每次打开成功且各生成一条访问审计 | 每次先 append 一条含 admin/目标/时间/content_type/expires_at 的 PrivateContentAccessLog；内容本身不入审计 | UI/API 不要求目的、原因或二次确认；不预取/hover 打开；不把正文放列表、HTML、缓存或证据 | `response.json`、`db-diff.json`、`ordering-trace.json`、`trace.zip`、`screenshots/open-entry.png` |
| `TDD-WP6-PRIV-002-deny-without-audit` | ADMIN-05、17.10、AC-25 | 参数化未确认、普通用户/预览账号、停用 admin、审计 insert 失败 | 1. 调区域/`PRIVATE_OPEN`；2. 模拟审计约束失败；3. 枚举页面、SW、Cache、IndexedDB | 未确认 428；其他未授权 401/403/404 按冻结协议；审计失败返回非成功且无内容字节 | 未授权无访问审计；审计失败事务不留下伪成功记录，也不发放读取 | 不返回正文/图片/可推导 URL；不写 AdminOperationLog 替代；不因 notice ack 省略逐次审计 | 每参数 `response.json`、`db-diff.json`、`cache-enumeration.json`、`trace.zip` |

#### 6.9.7 模型配置

| case_id | 需求 / AC | fixture 与前置 | 编号操作步骤 | 预期响应 / UI | DB / 文件变化 | 禁止副作用 | 证据文件 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `TDD-WP3-MDL-001-seed-read` | GEN-01、ADMIN-03、17.2、AC-30 | 全新迁移后的 `FX-MODEL-SEED` | 1. 启动迁移；2. 调 `GET /api/v1/models`；3. 查集合约束 | 返回固定三模型；Flash 唯一 configured default；priority 1/2/3；recommended null；三者 contract unverified/runtime unavailable | 一个 current set，三个 immutable member/version，独立 runtime 行 | 不把 unverified 标为 available；不生成第二默认；不把 recommended 写配置 | `response.json`、`db-diff.json` |
| `TDD-WP3-MDL-002-replace-default` | ADMIN-03、17.2、AC-30 | `FX-MODEL-VERIFIED`，current set version N | 1. 完整提交三模型集合：停用当前默认并指定另一个 enabled default；2. 带 expected N/Idempotency-Key；3. GET models | 保存成功，读取始终恰一个 enabled default，config_set_version=N+1 | 新 immutable set/必要 config versions、AdminOperationLog/outbox 写入，最后原子切 current pointer | 不出现可见零默认/双默认中间态；不逐行部分保存；不改无关 runtime | `request.json`、`response.json`、`db-diff.json`、`transaction-trace.json` |
| `TDD-WP3-MDL-002-invalid-set` | ADMIN-03、17.2、AC-30 | 参数化缺替代、非法替代、priority 缺失/浮点/0/负数/重复、缺模型/未知模型 | 1. 分别 PUT 完整候选；2. 读取稳定错误；3. GET 当前集合 | 返回对应 400/409 稳定 code；UI 聚焦修正字段；当前集合仍为 N | current pointer、versions/runtime/审计成功记录/outbox 均无变化 | 不生成孤儿候选 set；不部分改变 enabled/default/priority；不新增错误分类 | 每参数 `response.json`、`db-diff.json` |
| `TDD-WP3-MDL-002-cas-conflict` | ADMIN-03、AC-30 | 两个 admin 基于 expected N 编辑不同完整集合 | 1. A 保存为 N+1；2. B 仍以 N 保存；3. B 刷新 | A 成功；B 返回 412 `MODEL_CONFIG_VERSION_CONFLICT` 和最新安全摘要 | 仅 A 的 immutable set、pointer、审计/outbox 存在；B 零写入 | B 不覆盖 A、不自动 merge、不复用过期配置继续提交 | 两份 `response.json`、`db-diff.json`、`trace.zip` |
| `TDD-WP3-MDL-002-contract-change` | GEN-13、15、ADMIN-03、AC-30、40 | verified 模型集合 | 1. 修改端点/字段/参考图/响应/异步查询/error mapping 任一契约字段；2. 保存；3. GET models；4. 尝试生成 | 保存成功但对应模型 contract unverified、runtime reason contract_unverified；生成被阻断直到独立重验 | 新 config_version/set；对应 runtime 独立置 false；审计/outbox 同事务 | 不沿用旧 verified 证据；不暗改 enabled/default/priority；不调用真实上游 | 每参数 `response.json`、`db-diff.json`、`external-calls.json` |
| `TDD-WP3-MDL-003-runtime-recommendation` | GEN-15、ADMIN-03、17.2、AC-30、51 | 三模型 verified/available，配置快照和哈希已记录 | 1. 依次模拟默认模型不可用、全部不可用、一个原因恢复但另一原因仍在、全部恢复；2. 每步 GET models | configured default 始终原 ID；recommended 按可用模型 priority 派生或 null；reason 使用固定优先级 | 只更新 `model_runtime_availability`/GatewayBalanceState/runtime version；config set/version/hash 不变 | 不写 enabled/is_default/priority；不自动恢复；不把推荐变成第二默认或用户隐式选择 | 每步 `response.json`、`db-diff.json`、`screenshots/model-states.png` |

#### 6.9.8 自动保存与冲突

| case_id | 需求 / AC | fixture 与前置 | 编号操作步骤 | 预期响应 / UI | DB / 文件变化 | 禁止副作用 | 证据文件 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `TDD-WP2-PROJ-002-debounce` | PROJECT-04、06、AC-20 | saved project version N、可控前端时钟 | 1. 1 秒内完成多次已提交编辑；2. 在首个保存请求在途时继续编辑；3. 释放响应并推进时钟；4. 撤销/重做 | 同时最多一请求；第一份后下一请求为合并后的最新完整状态；状态 saving→saved | 每次成功 state_version +1；最终 project_states 为最新已提交 JSON；撤销/重做也正常保存 | 不逐操作并发；不保存 Fabric 私有字段；不把未提交编辑或撤销栈写浏览器持久存储 | `network-timeline.json`、`response.json`、`db-diff.json`、`trace.zip` |
| `TDD-WP2-PROJ-002-cas-success` | PROJECT-04、17.3、AC-20 | owner project version N，完整合法 Canvas JSON | 1. PUT state with `If-Match: N`；2. GET project；3. 用相同幂等请求重放 | 首次成功并返回 saved/N+1；重放返回第一次结果 | `UPDATE ... state_version=N` 命中一行；project_states 新快照，state_version 仅 +1 | 不隐式上传/复制/派生资源；不泄漏或接受他人资源引用；不因重放 +2 | `request.json`、`response.json`、`db-diff.json` |
| `TDD-WP2-PROJ-004-stale-tab` | PROJECT-07、AC-36 P0-A 子集 | `FX-PROJECT-CONFLICT`，标签 A/B 均基于 N | 1. A 保存到 N+1；2. B 以 N 保存；3. B 尝试编辑/覆盖/另存副本 | B 返回 412 和最新安全版本摘要；立即只读 conflicted，只显示一次本地导出与刷新 | 仅 A 状态写入；B 的 state_version/save_status/latest_exports/project 数量不变 | B 不自动重试覆盖、不 merge、不另存项目、不恢复自动保存 | 两标签 `response.json`、`db-diff.json`、`trace.zip`、`screenshots/conflicted.png` |
| `TDD-WP2-PROJ-002-save-failure-recovery` | PROJECT-04、05、EXPORT-06、AC-20、35 | saved N；页面创建 dirty；参数化网络失败、storage unavailable、强制刷新/崩溃 | 1. 触发保存失败；2. 检查 failed/离开弹层；3. 页面保持时恢复并重试；4. 另一路强制退出后重开；5. 枚举浏览器存储 | 失败不显示 saved；正常恢复可保存；强制退出只恢复后端 N 且撤销栈为空 | 失败期间 DB 不变；成功重试才 N+1；浏览器持久存储无草稿/提示词/私有引用 | 不把失败当成功；不在关闭后恢复内存草稿；不让 SSE 代替 REST 真相 | `response.json`、`db-diff.json`、`cache-enumeration.json`、`trace.zip`、`screenshots/save-failed.png` |

#### 6.9.9 导出

| case_id | 需求 / AC | fixture 与前置 | 编号操作步骤 | 预期响应 / UI | DB / 文件变化 | 禁止副作用 | 证据文件 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `TDD-WP4-EXP-001-confirm-pending-edit` | PROJECT-04、06、EXPORT-03、AC-23 | saved 画布、一次未提交文字/样式编辑、撤销栈深度 U | 1. 打开导出；2. 看到“导出前需要提交当前修改”；3. 确认；4. 等待画布稳定并启动正常自动保存；5. 合成导出；6. 执行一次撤销 | 导出使用确认后的状态；确认使撤销栈 U→U+1 且撤销可回到确认前；导出动作不再增加撤销项 | 确认后的完整状态进入正常保存队列；保存成功按 PROJECT-04 增加 state_version；latest 是否更新由后续保存结果决定 | 不在确认时产生多条撤销；不以导出额外编辑画布；不导出确认前状态；不跳过自动保存流程 | `undo-trace.json`、`network-timeline.json`、`export-hash.json`、`trace.zip`、`screenshots/pending-confirm.png` |
| `TDD-WP4-EXP-001-cancel-pending-edit` | EXPORT-03、AC-23 | 与上一 case 相同 | 1. 打开弹层；2. 取消未提交修改确认；3. 检查画布/撤销/网络 | 弹层关闭或停留在未导出状态；无下载、无成功提示 | state_version、latest_exports、撤销栈、保存队列均不因取消改变 | 不提交当前编辑、不合成、不下载、不调 `EXPORT_SAVE` | `network-timeline.json`、`db-diff.json`、`trace.zip` |
| `TDD-WP4-EXP-001-format-ratio` | EXPORT-01 至 03、AC-23 | `FX-CANVAS-RATIOS`，JPG quality 参数化 80/92/100，PNG | 1. 对四比例分别导出 JPG/PNG；2. 解码像素/ICC；3. 与 Canvas fixture 比较 | 尺寸精确为 1080×1440、1080×1080、1440×1080、1080×1920；sRGB、无水印、PNG 无损、JPG 范围/默认正确 | 纯客户端合成本身无 DB 变化；后续保存只处理同一 Blob | 不缩放到其他尺寸；PNG 时质量控件不可聚焦；不因导出改变画布/撤销栈 | 每组合 `result.json`、`image-metadata.json`、`pixel-diff.json`、`export-hash.json` |
| `TDD-WP2-EXP-001-same-blob-success` | EXPORT-04、AC-23、35 | saved project，已有旧 JPG latest；新客户端 Blob B | 1. 记录 B SHA-256；2. 用 B 触发浏览器下载；3. 同一 B 调 `EXPORT_SAVE`；4. 重读 latest 并下载 | UI 显示“已下载并保存为最新成品”；两个通道 hash 相同 | 原子替换该格式 latest_exports/managed file；旧 JPG 进入规定清理；PNG latest 不变；容量按真实文件更新 | 不重新编码第二份 Blob；不保留完整导出历史；不影响另一格式；幂等重试不重复计量 | `download.json`、`request.json`、`response.json`、`db-diff.json`、`fs-before.json`、`fs-after.json` |
| `TDD-WP2-EXP-001-result-matrix` | EXPORT-05、06、AC-35、55 | 参数化：合成失败、下载失败、project 保存失败、latest 持久化失败、storage full/unavailable | 1. 在 UIDesign 四结果组合及存储状态下导出；2. 记录下载/上传调用顺序；3. 重读项目/latest | 分别显示冻结结果文案；可合成时允许规定本地下载；下载失败可复用同一次合成；后端失败明确 latest 未更新 | 失败分支保留旧 latest；project 保存失败不写新 state；latest 失败不写 latest/managed file；成功分支才更新 | 不撤回已下载文件；不以本地下载覆盖后端记录；不显示双成功假状态；不把 Blob 存浏览器持久缓存 | 每参数 `network-timeline.json`、`response.json`、`db-diff.json`、`trace.zip`、`screenshots/result.png` |
| `TDD-WP2-PROJ-004-conflict-export-once` | PROJECT-07、EXPORT-05、AC-36 P0-A 子集 | conflicted 标签页，`conflict_export_used=false` | 1. 本地导出一次（成功和失败各参数）；2. 再次尝试；3. 刷新最新版本 | 第一次结束后立即禁用再次导出；结果明确“仅下载本页版本，未写入项目”；刷新到后端最新状态 | 所有步骤不调用 latest API，projects/project_states/latest_exports 无变化；flag 只在页面内存 | 不因第一次失败允许第二次；不覆盖后端、不另存副本、不把 flag 持久化 | `network-timeline.json`、`db-diff.json`、`trace.zip`、`screenshots/conflict-export.png` |

#### 6.9.10 WP-0 其余测试族规范 case

| case_id | 需求 / AC | fixture 与前置 | 编号操作步骤 | 预期响应 / UI | DB / 文件变化 | 禁止副作用 | 证据文件 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `TDD-WP0-DEP-001-frozen-toolchain` | NFR-09、DevelopmentPlan 2.1、13.1 | 冻结 lockfile、干净离线依赖缓存、win-x64 runner | 1. 严格按 lockfile 安装；2. 编译 TypeScript；3. 最小加载 React/Fabric/Fastify/better-sqlite3；4. 用随包 Node 运行 native smoke | 所有命令退出 0，版本与 lockfile/DevelopmentPlan 一致 | 只生成声明的构建缓存与报告；业务 DB/LocalDataRoot 无变化 | 不升级/替换依赖、技术栈或 native 模块；不联网漂移解析版本 | `commands.json`、`dependency-tree.json`、`native-smoke.json` |
| `TDD-WP0-API-001-schema-envelope` | NFR-05、DevelopmentPlan 6.1、6.6 | 生成 TypeBox/OpenAPI/客户端 snapshot；合法与非法 fixture | 1. 生成 OpenAPI 3.1；2. 比较 snapshot；3. 对每个 operation 注入合法/非法请求；4. 校验错误信封和 correlation ID | Schema/运行/客户端一致；非法响应使用冻结 HTTP/code/message_key/details 白名单 | 只读场景 DB 无变化；写场景失败按各 operation 事务回滚 | 不出现未声明字段/枚举、供应商原文、堆栈、路径或缺失 correlation ID | `openapi.json`、`snapshot-diff.json`、`response.json`、`redaction.json` |
| `TDD-WP0-BND-001-loopback-origin` | NFR-09、AC-24 | API 未启动；回环、局域网、错误 Host/Origin 和预检请求参数 | 1. 启动产品包；2. 检查监听地址；3. 从允许/拒绝地址请求；4. 发送跨源/CORS 预检 | 只接受 `127.0.0.1:43121` 与同源；其他路径连接失败或按门禁拒绝 | 无业务表、会话、外部用量变化 | 不监听 `0.0.0.0`/IPv6 wildcard，不建防火墙入站规则，不返回 CORS 放行头 | `socket-listeners.json`、`response.json`、`firewall-diff.json` |
| `TDD-WP0-BRW-001-real-supported` | NFR-01、AC-24、41 | 最终 `RELEASE.json`；真实 Chrome/Edge 独立 profile | 1. 分别启动记录的 full version；2. 完成 UA-CH support check；3. 进入 bootstrap/login；4. 复查产品 API | 两浏览器均通过并建立短期支持 cookie，产品 API 每次复核品牌/major | 仅门禁所需非敏感记录/cookie；不建用户 session | 不用 Playwright Chromium、伪 UA 或前一 major 代替；不联网查询当前版本 | 每浏览器 `environment.json`、`response.json`、`trace.zip`、`screenshots/supported.png` |
| `TDD-WP0-BRW-002-hard-block` | NFR-01、AC-24 | 参数化其他品牌、错误 major、非 Windows、UA-CH 缺失/冲突/解析失败和原始 API 客户端 | 1. 请求 support check；2. 请求 HTML/bundle/API；3. 尝试登录/项目/后台深链；4. 查 DB/外部调用 | 全部 426 `BROWSER_UNSUPPORTED`，reason 仅四类，页面只有重新打开说明且无绕过 | 业务 DB 读取/写入计数为 0，session/验证码/资源读取均无变化 | 不返回继续令牌/参数，不发 Resend/AI/高德，不泄露产品数据 | 每参数 `response.json`、`db-access.json`、`external-calls.json`、`screenshots/blocked.png` |
| `TDD-WP0-SUP-001-lifecycle` | NFR-09、AC-24、41 | 真实便携包；参数化第二实例、43121 占用、API/Worker 崩溃、正常退出 | 1. 双击启动；2. 注入各故障；3. 观察托盘/重启次数；4. 退出并等待 15 秒 | 单实例；端口占用明确失败且不换端口；有限重启/degraded/允许动作符合契约；退出后无残留 | 仅规定日志/状态记录；正常退出 flush WAL/队列；不改业务真相 | 不无限重启、不杀无关进程、不弹隐藏控制台、不留下 API/Worker/端口 | `process-tree.json`、`port.json`、`supervisor-events.json`、`screenshots/system-ui.png` |
| `TDD-WP0-DATA-001-root-validation` | NFR-09、17.19、AC-41、55、56 | 参数化 repo/project/download/只读素材根、合规外部目录、已初始化后缺失目录 | 1. 用离线配置设置各路径；2. 启动；3. 删除已初始化根后重启；4. 调资源 API | 违规路径拒绝；合规路径可写；缺失根进入 data_missing 且不自动重建；浏览器只见逻辑资源 ID | 仅合规首次初始化创建固定目录/DB；失败不创建散落目录 | 不硬编码用户名、不回显绝对路径、不在源码/下载/素材根写业务数据 | `config-result.json`、`fs-before.json`、`fs-after.json`、`response.json` |
| `TDD-WP0-DATA-002-no-backup-migration` | AUTH-06、NFR-09、AC-24、56 | 有账号/项目/下载成品的 LocalDataRoot | 1. 搜索 UI/API/包功能；2. 尝试导出项目包/DB/账号/点数；3. 删除根/重装；4. 启动模拟 P0-B 导入 | 仅原图/JPG/PNG 下载可用；固定本机提示持续显示；删除/重装不可恢复；无迁移入口 | 下载目录文件保留；业务数据删除后不被重建或导入 | 不自动备份、不生成迁移包、不把下载图识别为正式迁移数据 | `route-inventory.json`、`package-scan.json`、`trace.zip`、`screenshots/fixed-copy.png` |
| `TDD-WP0-STO-001-byte-thresholds` | 17.19、AC-55 | 第 5.5 节七个精确字节 fixture，可写目录 | 1. 对每个值重计量；2. 读取 storage state/API/UI；3. 重复计算 | normal/warning/critical/full 边界逐字节正确，重复结果确定 | 只更新 measured bytes/notice/status/last_measured_at；无 managed file 变化 | 不用浮点比例导致错一字节，不自动删除，不把 warning/critical 当阻断 | 每值 `result.json`、`db-diff.json`、`screenshots/capacity-bar.png` |
| `TDD-WP0-FILE-001-atomic-commit` | NFR-05、17.19、AC-44、55 | 受控文件驱动；合法/错误 MIME、hash、`..`、符号链接逃逸；各崩溃点 | 1. 流式写 staging；2. 校验/预留；3. 在改名/DB 提交前后终止；4. 重启对账 | 非法输入拒绝；成功对象完整可读；崩溃后恢复为已提交或补偿清理，不出现半对象 | reservation、managed_files、staging/正式文件按提交点一致；启动对账修复计量 | 不接受扩展名猜 MIME、不越根、不产生可读半文件/孤儿引用/重复计量 | `response.json`、`crash-points.json`、`db-diff.json`、`fs-before.json`、`fs-after.json` |
| `TDD-WP0-CACHE-001-public-lru` | NFR-07、17.14、AC-46、48 | 公开资源超过 157,286,400 字节；internal/private/project/提示词 fixture | 1. 按访问顺序填充 public cache；2. 触发写前 LRU；3. 尝试缓存私有类型；4. 枚举 SW/Cache/IndexedDB | 总量不超上限，淘汰最久未用且当前资源可重取；私有类型均不缓存 | `public_asset_lru` 只含公开元数据；业务 DB 无变化 | 不缓存 internal preview、私有内容、项目、验证码、session、未保存编辑；不计用户下载 | `cache-enumeration.json`、`lru-trace.json`、`trace.zip` |
| `TDD-WP0-EVT-001-rest-refetch` | PROJECT-04、GEN-14、NFR-05、DevelopmentPlan 6.2、AC-20、30 | 可控 SSE 序列：正常、断线、event_id 缺号、runtime/config 变化 | 1. 订阅 `/api/v1/events`；2. 注入事件；3. 断线/缺号；4. 观察 REST 请求和 UI | 事件只提示 ID/type/entity/version/time；每次由 REST 取真相；断线/缺号 bootstrap/refetch | SSE 不直接写业务表；REST 读取后前端状态更新 | 不携带正文/图片/凭据，不把 runtime 事件写 ModelConfig，不抢焦点 | `sse-events.json`、`network-timeline.json`、`trace.zip` |
| `TDD-WP0-SEC-001-credential-channel` | PRIV-01、AC-41 | Windows Credential Manager 测试条目；API/Worker 各需不同凭据；缺失/清除参数 | 1. Supervisor 读取；2. 通过对应匿名管道启动子进程；3. 检查命令行/环境/句柄；4. 清除并重启 | 仅目标子进程能在内存读取一次；缺失时服务明确不可用/启动失败 | 不写业务 DB；只写非敏感状态与日志 | 凭据不进参数、环境、文件、日志、崩溃诊断；API 不获网关密钥，Worker 不获 Resend/高德密钥 | `process-env-scan.json`、`pipe-acl.json`、`redaction.json` |
| `TDD-WP0-LOG-001-rotation-redaction` | PRIV-01、02、NFR-09、AC-41、56 | 生成超过 10 MiB/10 文件/30 天日志；注入密钥、邮箱、提示词、路径和写失败 | 1. 写边界日志；2. 触发轮转/保留清理；3. 复制诊断；4. 使目录不可写并尝试 AI | 轮转严格为 10 MiB/10 文件/30 天；诊断只含白名单；写失败进入 unavailable 并阻断 AI | 只更新日志文件和非敏感状态；不改业务对象 | 不记录敏感正文/绝对路径/供应商原文，不在日志失败时继续 AI | `log-manifest.json`、`redaction.json`、`response.json`、`external-calls.json` |
| `TDD-WP0-PKG-001-portable-zip` | NFR-01、09、AC-24、41、56 | release build、空白 Windows 用户目录 | 1. 生成 win-x64 ZIP/哈希/START-HERE/RELEASE；2. 解压非仓库目录；3. 离线启动；4. 扫描制品和端口 | 包含 Dada.exe、随包 Node/native 依赖并固定 43121；说明与 RELEASE 一致 | 仅合规 LocalDataRoot 生成运行数据；制品本身不可变 | 不含真实配置/凭据/用户名/LocalDataRoot，不动态换端口，不要求 Docker/安装器 | `package-manifest.json`、`sha256.txt`、`package-scan.json`、`process-tree.json` |

#### 6.9.11 WP-1 其余测试族规范 case

| case_id | 需求 / AC | fixture 与前置 | 编号操作步骤 | 预期响应 / UI | DB / 文件变化 | 禁止副作用 | 证据文件 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `TDD-WP1-NOTICE-001-registration-consent` | AUTH-03、PRIV-04、06、AC-01、41、56 | 当前告知 version/effective_at/hash；缺字段、未勾选和完整参数 | 1. 展开注册第二阶段；2. 检查全部处理方/用途/本机风险；3. 未同意提交；4. 同意当前 version 注册 | 未同意/版本错误不能注册；完整告知原样可达；成功进入创作 | 失败无 user/consent/invite/credit；成功记录 consent version/time 与账号同事务 | 不省略 AI/Resend/高德、DYN004、管理员访问、注销、180 天、无备份/迁移；不预勾选 | `response.json`、`db-diff.json`、`screenshots/notice-expanded.png` |
| `TDD-WP1-SLOT-001-stage-limit` | AUTH-01、NFR-04、AC-32、45 | 参数化 9/10 个 active+suspended 普通账号、deleted、多个 super_admin | 1. 发码前尝试第 10/11 人；2. 在发码后并发占满名额再完成；3. 删除普通账号再注册 | 第 10 人可成功，第 11 人发码/完成均阻断；deleted 释放；super_admin 不占名额 | 成功时计数/账号/邀请原子；失败零变化；累计统计保留 deleted | 不把 suspended/deleted/super_admin 计错，不通过并发超 10，不让失败占邀请码 | `response.json`、`db-diff.json`、`concurrency-trace.json` |
| `TDD-WP1-ADM-001-admin-auth-boundary` | AUTH-07、AC-49 | 白名单/非白名单邮箱、已存在普通账号冲突、两个 admin、全部停用状态 | 1. 后台发码；2. 首次验证/后续登录；3. 尝试普通 session 访问后台；4. 停用全部并尝试自助恢复 | 非白名单发码前拒绝；白名单首次建 super_admin；audience 严格分离；无最后管理员保护且无自助恢复 | 允许分支建 admin/session/审计；拒绝分支不建 challenge/session；停用撤销 session | 不泄漏白名单、不使用邀请码/普通名额、不原地提升普通账号、不创建部署人员角色 | 每参数 `response.json`、`db-diff.json`、`external-calls.json`、`trace.zip` |
| `TDD-WP1-CFG-001-secure-revision` | AUTH-07、ADMIN-09、AC-49、50 | current applied revision N；参数化完整 N+1、HMAC/凭据缺失、身份冲突 | 1. 离线写候选安全配置；2. API 启动应用；3. 查询 apply state/session；4. 注入失败重试 | 完整配置原子生效并推进 revision；失败保持 N 并阻止受影响入口 | 成功同事务更新 apply state、撤销受影响 session、写 system/backend_secure_config 审计；失败全回滚 | 不写白名单/HMAC/pepper/人员身份/凭据，不出现半生效配置 | `config-result.json`、`db-diff.json`、`redaction.json` |
| `TDD-WP1-DEL-001-account-delete` | AUTH-06、AC-22、39、56 | 用户拥有项目、参考/生成/导出、坐标、点数、多个 session | 1. 确认注销；2. 并发调用受保护 API；3. 尝试旧资源 ID/恢复；4. 用新邀请重新注册同邮箱 | 注销成功后所有旧访问立即 401/404；不可恢复；新注册是全新账号 | 同事务撤销 sessions、删除身份/业务关系/未用点数并创建允许匿名事件；物理文件排队 | 不延迟撤权到物理清理，不恢复旧 ID/点数/资料，不保留邮箱映射到匿名主体 | `response.json`、`db-diff.json`、`worker-events.json`、`trace.zip` |
| `TDD-WP1-DEL-002-anonymous-retention` | AUTH-06、17.10、17.11、AC-22、39 | 注销前有生成/点数事件和私有访问审计；可控 180 天时钟 | 1. 注销；2. 检查匿名字段；3. 在到期前尝试管理删除；4. 到期运行 Worker | 匿名事件只含允许统计；访问审计目标不可反推；到期前不可删，到期后系统清理 | 原身份关联移除；随机匿名主体无映射；expires_at 不因注销延长；到期行删除 | 不保留邮箱/IP/提示词/图片/项目/job 原 ID，不让管理员手删或延长期限 | `db-diff.json`、`retention-events.json`、`redaction.json` |
| `TDD-WP1-AUD-001-immutable-triggers` | ADMIN-05、09、17.10、17.16、AC-50 | 各一条 AdminOperationLog/PrivateContentAccessLog；普通、retention、privacy purge 连接 | 1. 普通连接 UPDATE/DELETE；2. 伪造 purge 函数；3. 合法作用域在到期/注销执行 | 普通和伪造操作被 trigger 阻断；仅注册作用域执行规定清理 | 非法尝试零变化；合法作用域只删除/匿名化允许行并留非敏感结果 | 不允许后台/API 任意修改，不混用两类日志，不把正文写审计 | `sql-results.json`、`db-diff.json`、`trigger-definition.json` |

#### 6.9.12 WP-2 其余测试族规范 case

| case_id | 需求 / AC | fixture 与前置 | 编号操作步骤 | 预期响应 / UI | DB / 文件变化 | 禁止副作用 | 证据文件 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `TDD-WP2-PROJ-001-new-versus-continue` | PROJECT-01 至 03、GEN-04、11、AC-03、06、34 | `FX-PROJECT-ACTIVE-19`、一个 9 图固定比例项目 | 1. 工作台新建两次；2. 在第一个项目继续生成；3. 尝试改比例；4. 补到 10 图再继续 | 两次新建为两个 project；继续只新增 job/history且沿用比例；10 图阻断追加但可另建 | projects +2；继续分支 project 数不变；成功 history 正确关联 | 不默认追加最近项目、不创建第三项目、不改变既有项目比例、不让历史上限阻断其他新建 | `response.json`、`db-diff.json`、`trace.zip` |
| `TDD-WP2-PROJ-003-trash-restore-purge` | PROJECT-03、08、AC-21 | active project、`FX-PROJECT-ACTIVE-20`、可控 720 小时时钟 | 1. 移入回收站；2. 满 20 时恢复；3. 释放名额再恢复；4. 主动永久删除；5. 另一项目推进到 purge_at | 满额恢复阻断且时间不变；释放后可恢复；purge 后立即不可见/不可恢复 | status/deleted_at/purge_at 精确；purged 撤销访问关系并排物理清理 | 不提前物理结果、不因恢复重算 purge_at、不让 purged 资源可读 | `response.json`、`db-diff.json`、`worker-events.json` |
| `TDD-WP2-PROJ-005-failed-draft-retry` | PROJECT-01、GEN-09、AC-04、43 | `FX-PROJECT-EMPTY` 和含成功图项目 | 1. 失败页重试两次；2. 返回工作台主动新建；3. 多选批量删除 failed_empty 与成功项目 | 重试只在原 project 新增 job；主动新建才新 project；批量只接受无成功图草稿 | 原草稿保留 prompt/config；job 数递增；批量合格项 trashed，成功项目不变 | 不把重试变新项目、不把成功项目纳入快捷批量、不丢草稿输入 | `response.json`、`db-diff.json`、`trace.zip` |
| `TDD-WP2-REF-001-reference-lifecycle` | GEN-05、PROJECT-09、AC-44、48 | 新上传、同项目已有 reference、他人/跨项目/撤销/已 purge reference | 1. 提交前在页面选择/移除；2. multipart 新上传；3. 同项目复用；4. 尝试越权/失效；5. 删除项目 | 提交前仅内存；每 job 独立关系快照；仅同 owner+project 可复用；purge 后立即不可读 | 成功建立 job reference/managed file；移除前无文件；purge 撤销关系并排清理 | 不修改旧任务关系、不跨用户/项目复用、不缓存预提交图片、不暴露路径 | 每参数 `response.json`、`db-diff.json`、`fs-after.json`、`cache-enumeration.json` |
| `TDD-WP2-EXP-002-empty-latest` | EXPORT-04、AC-23、38 P0-A 子集 | 从未导出但有成功原图的项目 | 1. 打开项目详情/latest 区；2. 请求 JPG/PNG latest；3. 使用允许动作 | 显示“暂无导出成品”，只提供进入编辑并导出和下载原始生成图 | latest_exports 无行且不因读取创建；原图读取只读 | 不自动合成/保存默认成品，不伪造跨设备下载，不改变项目 | `response.json`、`db-diff.json`、`trace.zip`、`screenshots/no-export.png` |
| `TDD-WP2-BAL-001-balance-impact` | GEN-14、15、17.17、AC-04、51 | 参数化 model/account/unknown 影响；活动任务与空闲模型 | 1. 注入余额不足；2. 结算已提交任务；3. GET models/后台；4. 重复事件；5. 未确认恢复前尝试启用 | 影响范围按规则；任务 failed+release 一次；runtime 不可用；恢复前操作阻断 | GatewayBalanceState/affected models/runtime/审计更新；ModelConfig hash 不变 | 不自动充值、不用 Dada 点数继续调用、不重复 release、不自动恢复或改默认/priority | `response.json`、`db-diff.json`、`worker-events.json`、`external-calls.json` |

#### 6.9.13 WP-3 其余测试族规范 case

| case_id | 需求 / AC | fixture 与前置 | 编号操作步骤 | 预期响应 / UI | DB / 文件变化 | 禁止副作用 | 证据文件 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `TDD-WP3-ADP-001-per-model-contract` | GEN-01、02、05、06、13、16、AC-40 | 三个模型各自独立 mock/真实受控账号和脱敏 fixture | 1. 每模型分别执行纯文字、合法参考图、四比例；2. 覆盖同步/异步与查询；3. 验证 MIME/尺寸/单图；4. 注入九类映射 | 每模型只按自身已证实协议成功；结果恰一图；错误映射到固定 category | 真实验证只写对应 model/config_version 的 evidence 状态；任务/点数按独立用例结算 | 不从一个模型推断另两个、不共享 verified 标记、不接受多图/未知字段、不记录原始响应 | 每模型 `contract-matrix.json`、`response.json`、`external-calls.json`、`redaction.json` |
| `TDD-WP3-ADP-002-invalidate-revalidate` | GEN-13、15、ADMIN-03、AC-30、40 | 三模型 verified；参数化端点/字段/参考图/响应/异步/查询/error protocol 变化 | 1. 改一个契约字段并保存；2. 尝试生成；3. 仅为该模型重跑完整矩阵；4. 再尝试 | 变化后立即 unverified/不可新建；完整独立重验后才恢复 verified | 新 config_version；对应 runtime/evidence 版本更新；其他模型证据不变 | 不沿用旧证据、不局部 smoke 即 verified、不暗改 enabled/default/priority | 每字段 `db-diff.json`、`contract-matrix.json`、`response.json` |
| `TDD-WP3-BAL-001-confirmed-recovery` | GEN-14、15、ADMIN-09、AC-51 | 余额导致 model/account/unknown runtime unavailable；无/有真实恢复确认 | 1. 未确认时点击恢复；2. 执行非敏感健康检查；3. admin 明确确认；4. 重新计算所有阻断原因 | 无确认失败；确认真实恢复后只解除已满足原因，仍有其他原因则继续 unavailable | recovery check、GatewayBalanceState/runtime/AdminOperationLog 同事务更新；config set 不变 | 不自动恢复/充值，不补扣用户点数，不清除仍存在的 contract/Worker 阻断 | `response.json`、`db-diff.json`、`external-calls.json`、`screenshots/recovery.png` |

#### 6.9.14 WP-4 其余测试族规范 case

| case_id | 需求 / AC | fixture 与前置 | 编号操作步骤 | 预期响应 / UI | DB / 文件变化 | 禁止副作用 | 证据文件 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `TDD-WP4-BG-001-switch-background` | GEN-12、EDITOR-01、PROJECT-04、AC-07 | 画布含文字/贴纸/色卡/动态元素和已调底图，项目有第二历史图 | 1. 记录覆盖元素；2. 确认换底图；3. 检查参数/色卡；4. 撤销/重做；5. 重开 | 覆盖层位置尺寸保留；底图处理重置；五色重提；一次撤销且重开一致 | 完整 Canvas state 自动保存并 state_version 递增 | 不删除/缩放覆盖层，不保留旧调色，不产生多次撤销或改项目比例 | `canvas-before.json`、`canvas-after.json`、`db-diff.json`、`trace.zip`、`pixel-diff.json` |
| `TDD-WP4-BG-002-processing-controls` | EDITOR-01 至 03、PROJECT-04、EXPORT-03、AC-07、20、23 | 固定底图和像素基线 | 1. 参数化填充/适配/裁剪/缩放/定位及亮度、对比度、饱和度、色温、锐度、预设滤镜；2. 完成；3. 撤销；4. 恢复原图；5. 导出/重开 | 每项预览与提交值一致；完成一次撤销；导出/重开一致；恢复原图只清底图处理 | 每次提交进入保存的 Canvas JSON；取消无 DB 变化 | 不影响覆盖元素，不持久化未提交预览，不用静默超范围值 | 每参数 `canvas-state.json`、`db-diff.json`、`pixel-diff.json`、`trace.zip` |
| `TDD-WP4-CAN-001-fifty-elements` | EDITOR-04 至 09、17.5、AC-27 | `FX-CANVAS-50`，稳定选择重叠 fixture | 1. 添加到 50；2. 循环选择/长按候选/框选/多选移动；3. 层级/复制/删除；4. 保存/导出；5. 添加第 51 | 前 50 可编辑保存导出；选择顺序确定；第 51 明确阻断且布局不跳 | Canvas 最多 50，成功操作各一次撤销/保存；第 51 无 state change | 不出现图层面板/隐藏/锁定/编组，不因阻断损坏已有元素 | `canvas-state.json`、`db-diff.json`、`trace.zip`、`performance.json` |
| `TDD-WP4-TXT-001-multiline-template-switch` | TEXT-01 至 06、PROJECT-04、AC-09、10 | 多行文字模板实例、固定变换 | 1. 输入含换行文字并完成；2. 切换另一模板；3. 撤销/重做；4. 重开/导出 | 换行持久；空内容按规则拒绝；切模板重置默认样式但保留位置/旋转/整体尺寸 | 每次完成一条撤销并保存最新 element snapshot | 不把切模板重置变换、不裁切内容、不保存取消编辑 | `canvas-state.json`、`db-diff.json`、`pixel-diff.json`、`trace.zip` |
| `TDD-WP4-TXT-002-font-metrics-ranges` | TEXT-07 至 14、NFR-02、AC-11、12 | 真实归档字体/缺失字体、最长文字、属性边界/步长 | 1. 等待 FontFace ready；2. 参数化字号/缩放/行距/字距/描边/颜色/背景透明度；3. 完成；4. 缺字体加载；5. 重开/导出 | 字号与控制柄同步，重新测量不裁切；范围步长精确；缺字体模板 unavailable；背景透明度不影响文字 | 完成一次撤销/保存；缺字体分支无 Canvas 新元素 | 不静默系统字体回退、不把背景透明度应用文字、不超范围取值 | `font-load.json`、`canvas-state.json`、`pixel-diff.json`、`trace.zip` |
| `TDD-WP4-TXT-003-catalog-search-recent` | TEXT-01、17.6、AC-09、32 | P0-A 32 文字模板、四分类、稳定 catalog、两个用户 | 1. 未选模板尝试加文字；2. 按显示名称搜索；3. 检查分类/顺序；4. 使用模板；5. 切换账号读最近使用 | 只能从模板添加；四类/显示名搜索/稳定顺序正确；最近使用按账号隔离 | 只在成功使用后更新 `recent_assets`；未选/搜索不写项目 | 不提供独立普通文字，不复用普通贴纸搜索，不跨账号共享最近项 | `catalog.json`、`response.json`、`db-diff.json`、`trace.zip` |
| `TDD-WP4-STK-001-transform-sticker` | EDITOR-04 至 09、PROJECT-04、AC-13、27 | 普通贴纸元素和重叠选择 fixture | 1. 移动/缩放/旋转/翻转/透明度；2. 复制/层级/多选；3. 撤销重做；4. 保存/导出重开 | 画布、属性控件、导出和重开状态一致；每次连续手势是一项撤销 | Canvas element snapshot/state_version 按提交更新 | 不改变 stable ID/resource_version，不引入隐藏/锁定/编组，不丢选择 | `canvas-state.json`、`db-diff.json`、`pixel-diff.json`、`trace.zip` |
| `TDD-WP4-COL-001-deterministic-palette` | COL-01 至 05、AC-15 P0-A 子集、16 | 固定解码底图、`mmcq-v1`、四个 P0-A 色卡 | 1. 重复提取五色；2. 调裁剪/调色/滤镜；3. 换原始底图；4. 渲染四布局；5. 监控网络 | 同图五色/顺序确定；处理不刷新；换底图刷新；四色卡消费同一五色 | 五色快照只在底图更换提交时保存；无上传文件 | 不读取处理后像素/额外图片，不联网提色，不启用白名单外色卡 | `palette.json`、`network-timeline.json`、`pixel-diff.json`、`db-diff.json` |
| `TDD-WP4-DYN-001-fixed-time` | DYN-01 至 03、AC-14 | 可控时钟、DYN001-003/007/008/011 fixture | 1. 插入/确认动态值；2. 推进墙钟；3. 重开；4. 导出 | 使用插入/确认时快照，重开/导出不随当前时间变化；结果静态不可点击 | 动态实例只保存允许字段和值快照 | 不保存定时器/外部链接，不在重开重新计算，不输出 GIF/视频 | `canvas-state.json`、`clock-trace.json`、`pixel-diff.json` |
| `TDD-WP4-DYN-002-location-consent` | DYN-04、PRIV-03、AC-17、47 | DYN004 固定坐标/高德 mock；同意、拒绝、服务暂停参数 | 1. 插入前检查 Dada 提示；2. 拒绝；3. 同意并定位/逆地理；4. 手动地点回退；5. 删除元素/注销 | 拒绝调用 0 且不插入坐标；同意后正确显示；暂停时自动定位禁用但手动地点可用 | 只在同意后把坐标/文本存实例；删除/注销按规则清理 | 前端不持高德密钥，不把坐标用于其他元素/用户，不在提示前调用 | `external-calls.json`、`canvas-state.json`、`db-diff.json`、`trace.zip` |
| `TDD-WP4-DYN-003-identity-font` | DYN-05 至 09、AC-18、19 | 用户社交 ID 含/不含 `@`；实例覆盖；DYN012/FONT081 | 1. 插入身份贴纸；2. 修改实例覆盖；3. 检查账号资料；4. 插入 DYN012；5. 重开/导出 | 最多一个前导 @；实例覆盖独立且不改账号；DYN012 使用 FONT081 并明确标注替代 | 只保存实例 snapshot；UserProfile 不变 | 不声称 DIN 字体、不静默回退、不执行 Lua/Prefab、不把覆盖写账号 | `canvas-state.json`、`db-diff.json`、`font-load.json`、`pixel-diff.json` |
| `TDD-WP4-A11Y-001-keyboard-focus` | NFR-06、AC-32 | 第 11.1 节全部 page_id，最长文案、弹层和 200% 缩放 | 1. 仅键盘走主流程；2. 检查 skip link/focus order/弹层返回；3. 触发 live region；4. 运行 axe；5. 测画布等价控件 | 所有动作可达，焦点不逃逸/不被 SSE 抢走；critical/serious 为 0；状态非仅颜色 | 无非用户触发业务写；可访问性扫描只产证据 | 不用正 tabindex、不隐藏主动作、不以缩小字体规避 200% 问题 | 每页 `axe.json`、`focus-trace.json`、`trace.zip`、`screenshots/200pct.png` |
| `TDD-WP4-VIS-001-browser-diff` | NFR-02、AC-19、23、32 | 最终 RELEASE Chrome/Edge、固定 Canvas/字体/动态值/DPR | 1. 等待资源稳定；2. 两浏览器截图/导出；3. 计算元素边界/显著像素；4. 人工复核已知替代 | 结构、字体、换行、颜色、描边、装饰在第 10.1 阈值内；替代项明确标记 | 只生成视觉证据；业务状态使用同一 fixture | 不用低保真 PNG 作像素基准，不用阈值掩盖缺字体/空画布，不冒充全 P0-B | 每场景 `pixel-diff.json`、`layout-boxes.json`、`manual-review.json`、截图 |
| `TDD-WP4-PERF-001-budget` | NFR-03、AC-27、32 | `FX-CANVAS-50`、1,407 贴纸目录、四比例导出、最终浏览器 | 1. 预热一次；2. 每预算运行五次；3. 记录 p50/p95/max/memory/DOM；4. 注入导出失败 | 第 10.2 节所有预算通过；失败不破坏 project/latest；资源面板节点不线性增长 | 只生成性能数据；失败分支 DB/latest 不变 | 不提高超时、减少元素/素材或降低尺寸掩盖失败，不用 Chromium 替代真实浏览器 | `performance.json`、`memory.json`、`dom-count.json`、`environment.json` |

#### 6.9.15 WP-5 全部测试族规范 case

| case_id | 需求 / AC | fixture 与前置 | 编号操作步骤 | 预期响应 / UI | DB / 文件变化 | 禁止副作用 | 证据文件 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `TDD-WP5-CAT-001-catalog-1407` | STATIC-01 至 04、AC-13、32 | 规范 catalog/metadata，含 3 组重复图和 part1-25 | 1. 编译/加载目录；2. 校验 ID/part/顺序/计数；3. 滚动全目录；4. 观察原图请求/DOM | 恰 1,407 项，重复图片保留不同稳定 ID；虚拟列表稳定，原图按需加载 | 只读 catalog；不复制或改写源；浏览器仅缓存允许公开项 | 不去重业务 ID、不一次下载全量、不暴露绝对路径、不线性增长 DOM | `catalog-validation.json`、`network-timeline.json`、`dom-count.json`、`source-hashes.json` |
| `TDD-WP5-MAN-001-readonly-compiler` | TEXT-01 至 TEXT-14、COL-01 至 COL-05、DYN-01 至 DYN-09、STATIC-01 至 STATIC-04、AC-32、42 | 只读规范入口与独立输出目录；Lua/Prefab/证据目录 trap | 1. 记录源 hash/mtime；2. 运行声明式转换；3. 注入尝试执行 Lua/Prefab/读取证据目录；4. 复核输出 | 只消费允许 catalog/metadata/资源；非法入口失败；输出 manifest 可重现 | 只写版本化派生输出；源 hash/mtime 零变化 | 不执行 Android runtime、不扫描证据目录、不复制约 3.57 GB 全库、不覆盖源 | `compiler-report.json`、`source-before.json`、`source-after.json`、`output-manifest.json` |
| `TDD-WP5-WHITE-001-p0a-allowlist` | TEXT-01、TEXT-04、COL-01、DYN-01、DYN-08、STATIC-01、ADMIN-07、NFR-02、NFR-07、AC-32、42、48 | 普通用户无 grant、预览用户有单批 grant、注册全量模板 | 1. 分别读取公开/预览 manifest；2. 搜索白名单外 ID；3. 浏览所有面板 | 普通用户仅 32 文字、实际字体+FONT081、4 色卡、10 动态、1,407 贴纸；预览只多授权批次 | 只读；授权不改变 role | 不向普通用户暴露白名单外 passed/internal，不提前开放 P0-B，不遗漏白名单；AC-54 为未来 P0-B，不进入本 case 的当前验收或 passed 关联 | `manifest.json`、`response.json`、`trace.zip`、`screenshots/allowlist.png` |
| `TDD-WP5-COL-001-four-layouts` | COL-01 至 05、AC-15 P0-A 子集、16、32 | COLOR001/002/008/016 与同一五色 fixture | 1. 渲染四卡；2. 改裁剪/滤镜；3. 换底图；4. 尝试访问其他色卡 | 四布局消费完全相同五色；刷新规则一致；其他色卡不可发现 | 只保存稳定 ID/version/五色 snapshot | 不启用其余 12 色卡、不各自重新提色、不联网读取图片 | `palette.json`、`pixel-diff.json`、`manifest.json` |
| `TDD-WP5-RES-001-three-access-classes` | PRIV-02、05、17.12、13、AC-42、48 | public、internal_preview、private 资源及不同主体/grant | 1. 请求三类 manifest/资源；2. 推导 stable ID/路径；3. 撤销 grant 后重取；4. 检查 headers/缓存 | public 可匿名于产品授权范围读取；preview 每次验 grant/no-store；private 仅所有者/受控 admin；越权 403/404 | 读取无业务写，访问审计仅私有完整内容规定入口产生 | 不混路由/cache header，不返回路径/internal manifest，不用公开 ID 推导私有对象 | 每主体 `response.json`、`headers.json`、`cache-enumeration.json` |
| `TDD-WP5-UPL-001-upload-metering` | ADMIN-06、17.7、17.19、AC-31、55 | PNG/WebP、坏 MIME/解码、稳定 ID 冲突；active/full/unavailable；原图+缩略图字节 | 1. multipart 上传并指定 part/order；2. 生成缩略图；3. 注入非法/冲突/容量状态；4. 重计量 | 合法上传成功并可上架；非法/冲突明确失败；full/unavailable 阻断写但已有资源可读 | 原图/缩略图分别 reservation/managed_file 并计量；失败零文件/目录项 | 不用扩展名替代解码、不漏计缩略、不部分覆盖稳定 ID、不在 full 写入 | `response.json`、`db-diff.json`、`fs-before.json`、`fs-after.json` |
| `TDD-WP5-CLN-001-reference-race` | ADMIN-06、09、17.7、17.19、AC-31、50、55 | 查询时整批文件全部无任何发布/项目/待发布引用 | 1. 查询候选并由 admin A 创建 intent；2. 参数化确认前新增各类引用/快照过期/admin 变化；3. A 确认；4. 无竞态分支确认并运行 Worker | 任一新增引用整批 409 conflict+denied 审计；stale 重新查询；无竞态才整批排队/物理删除/重测 | 冲突 bytes/状态/queue 不变；成功排队不减量，物理后同事务更新并记录全阶段审计 | 不让查询时有引用项入候选、不由 admin B 确认、不部分排队、不自动清理或提前减量 | 每参数 `response.json`、`db-diff.json`、`worker-events.json`、`fs-after.json` |
| `TDD-WP5-PREV-001-grant-lifecycle` | ADMIN-07、17.13、AC-42、48 | 普通账号，active/revoked/expired grant，已有项目引用预览版本 | 1. admin 授权批次；2. 用户读取/添加；3. 撤销/到期；4. 再读取/添加；5. 重开已有项目 | active 时只见授权项；撤销/到期立即阻断新读取/添加；已有项目按保存版本渲染 | grant 状态/审计更新；UserProfile.role 不变；项目引用版本不改写 | 不出现后台权限、不把 preview 写 public manifest/cache、不因撤销破坏既有项目 | `response.json`、`db-diff.json`、`cache-enumeration.json`、`trace.zip` |
| `TDD-WP5-CACHE-001-no-private-client-state` | NFR-07、17.14、AC-46、48 | 已访问 public/preview/private、提示词和项目；注册 SW fixture | 1. 完成各访问；2. 离线/刷新；3. 枚举 SW registrations、Cache keys/body、IndexedDB stores/records | 仅 public allowlist 元数据/资源存在；SW 不拦 preview/private；离线不能恢复私有正文/草稿 | 只允许 `public_asset_lru` 记录；业务 DB 无变化 | 不仅凭响应头判断，不缓存 no-store 内容、项目/提示词/session/验证码/未保存编辑 | `cache-enumeration.json`、`service-worker.json`、`trace.zip` |

#### 6.9.16 WP-6 其余测试族规范 case

| case_id | 需求 / AC | fixture 与前置 | 编号操作步骤 | 预期响应 / UI | DB / 文件变化 | 禁止副作用 | 证据文件 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `TDD-WP6-ADM-001-role-and-summary` | ADMIN-01、02、04、08、AC-25、49 | active/suspended super_admin、普通/预览账号，后台安全摘要 fixture | 1. 各主体请求后台页/API；2. 停用 admin 后复用旧页面；3. 浏览列表/详情；4. 检查私有默认 | 仅 active super_admin 可进入；停用立即退出；默认只见脱敏元数据，普通/预览无后台入口 | 读取无业务变化；拒绝分支无审计/私有读取；规定登录审计独立存在 | 不共享普通/后台 session，不因预览 grant 获后台，不默认返回提示词/图片/密钥 | 每主体 `response.json`、`db-access.json`、`trace.zip`、`screenshots/admin-denied.png` |
| `TDD-WP6-SVC-001-hard-quota` | ADMIN-05、08、NFR-08、17.15、AC-47 | Resend 日 79/80、月 2399/2400；高德 999/1000；provider 异常；已有 session | 1. 并发领取最后额度；2. 尝试下一次；3. 注入 provider 故障；4. 检查登录/注册/DYN004/现有 session | 最后一份已领取调用可结束，后续不调用；状态 paused_quota/provider；已有 session 继续；手动地点继续 | `external_service_usage` 事务计数/status/reason 更新；失败不创建验证码假成功 | 不发送第 81/超月/第 1001 次，不影响已有 session，不切付费/备用，不建运行时邮件质量状态 | `response.json`、`db-diff.json`、`external-calls.json`、`concurrency-trace.json` |
| `TDD-WP6-SVC-002-limit-recovery` | ADMIN-08、09、NFR-08、AC-47、52 | paused 服务；参数化下调 hard_limit、提高/付费/自动扩容、新周期/供应商恢复 | 1. 尝试各配置；2. 无确认恢复；3. 建立非敏感恢复检查；4. admin 明确确认 | 只允许不扩大能力的下调并审计；提高/付费/自动扩容拒绝；满足条件+确认后恢复 | 成功下调/恢复与 AdminOperationLog 同事务；非法请求零变化 | 不自动恢复、不保存凭据/供应商原文、不把暂停伪装成功或提高上限 | 每参数 `response.json`、`db-diff.json`、`screenshots/service-state.png` |
| `TDD-WP6-AUD-001-sensitive-operations` | ADMIN-09、17.16、AC-50 | 每类规定敏感操作 fixture，含贴纸清理各阶段和 system 配置生效 | 1. 逐类执行成功/拒绝；2. 查询 AdminOperationLog；3. 查询 PrivateContentAccessLog；4. 尝试改删/越权查看 | 每次操作同事务形成不可变、非敏感 before/after/result；两类审计分离；只有 admin 查看 | AdminOperationLog 条数/actor_type/ref/expires_at 正确；私有打开只写专表 | 不漏失败审计、不重复保存私有正文、不记录白名单/密钥/验证码/路径、不允许人工修改删除 | `operation-matrix.json`、`db-diff.json`、`redaction.json`、`trace.zip` |
| `TDD-WP6-STATE-001-three-model-states` | GEN-15、ADMIN-03、08、AC-30、51 | default 不可用、其他推荐、全不可用、恢复参数 | 1. 打开总览/模型页；2. 注入 runtime 变化；3. SSE 后 REST refetch；4. 恢复一个原因 | configured default、逐模型 runtime、recommended 三列独立；推荐可 null；UI 不暗换用户选择 | 只改 runtime/version；ModelConfig/current set 无变化 | 不合并三种状态、不把 SSE 当真相、不回写默认/enabled/priority | `response.json`、`db-diff.json`、`screenshots/model-columns.png`、`trace.zip` |
| `TDD-WP6-DIAG-001-redacted-diagnostics` | PRIV-01、02、ADMIN-08、NFR-09、AC-41、55 | 总览/服务/存储/诊断包含所有状态；注入密钥、邮箱、路径、正文 trap | 1. 请求各摘要；2. 复制诊断；3. 检查托盘诊断；4. 跑脱敏扫描 | 仅显示固定非敏感字段、逻辑位置、计量和状态；复制内容一致 | 只读；可记录非敏感诊断访问/时间，不改变服务状态 | 不含密钥/HMAC/邮箱/提示词/图片/绝对路径/供应商正文，不提供隐藏修复写入口 | `response.json`、`diagnostic.txt`、`redaction.json`、`screenshots/diagnostics.png` |

#### 6.9.17 WP-7 全部测试族规范 case

| case_id | 需求 / AC | fixture 与前置 | 编号操作步骤 | 预期响应 / UI | DB / 文件变化 | 禁止副作用 | 证据文件 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `TDD-WP7-REL-001-final-release-record` | NFR-01、09、AC-24、41 | 最终便携 ZIP、START-HERE、SHA-256、RELEASE，真实验收电脑 | 1. 校验制品 hash；2. 导入 Windows build/Chrome/Edge 品牌、major/full version；3. 两浏览器跑全部发布 E2E/视觉/性能；4. 比较候选基线 | RELEASE/制品/诊断/门禁值一致；任一漂移使旧视觉/性能失去发布资格并重跑 | 只生成发布记录/证据；业务 fixture 在隔离验收根 | 不缺 RELEASE、不手填假版本、不接受前一 major/Chromium、不动态换端口 | `RELEASE.json`、`environment.json`、`package-manifest.json`、`evidence.json` |
| `TDD-WP7-EXT-001-three-real-models` | GEN-13、AC-40、41 | 三模型各自真实凭据/配置，显式执行确认 | 1. 显示模型/次数/额度；2. 分别执行纯文字、参考图、四比例、sync/async/查询/响应/错误；3. 脱敏；4. 绑定 config_version | 三模型独立完整矩阵通过才解除；任一缺项保持 externally_blocked | 只写对应模型验证证据/runtime 状态和允许审计；测试任务/点数按隔离规则 | 不共享证据、不自动充值、不保留真实输入/图片/原始响应、不在普通 CI 调用 | 每模型 `contract-matrix.json`、`external-calls.json`、`redaction.json`、`manual-review.json` |
| `TDD-WP7-EXT-002-real-resend` | AUTH-01、02、07、AC-01、33、41、47、49 | 已验证发信域名、正式受控普通/admin 邮箱、免费规则记录 | 1. 核 SPF/DKIM/免费规则；2. QQ/163/至少一种企业邮箱各发 20 封；3. 统计 2 分钟送达；4. 真实完成普通/admin 认证 | 每类至少 19/20 在 2 分钟内且两类认证成功；否则阻止发布但不改 runtime service_status | 只保存人工汇总/证据 hash；不建逐封 EmailVerificationEvent | 不用预置账号/mock 冒充，不保存验证码/真实邮箱进证据，不切付费备用 | `delivery-summary.json`、`domain-check.json`、`auth-result.json`、`redaction.json` |
| `TDD-WP7-EXT-003-real-amap` | DYN-04、PRIV-03、AC-17、41、47 | 个人认证高德 Web 服务 Key、安全绑定和显式确认 | 1. 核接口/QPS/白名单/安全/免费规则；2. 执行定位/逆地理；3. 验证 1,000 硬限模拟/受控测试；4. 脱敏坐标证据 | 接口、绑定和应用硬停止通过才解除；失败保持 externally_blocked | 只写非敏感用量/状态/证据；测试坐标不关联真实项目 | 不暴露 Key/真实用户坐标，不发第 1001 次，不启用付费或自动扩容 | `amap-contract.json`、`external-calls.json`、`redaction.json`、`manual-review.json` |
| `TDD-WP7-UI-001-page-state-evidence` | UIDesign 全页、NFR-02、06、AC-24、25、32、41、55、56 | 第 11.1 节 22 page_id 与适用状态、最终 Chrome/Edge、100%/200% | 1. 逐非 N/A 单元运行 case；2. 截图/键盘/axe；3. 验证全局容量条；4. 对照 Pencil 相关 frame；5. 人工复核 | 所有 UIDesign 页面/状态有实现证据且无重叠；Canvas Index 不实现；无 frame 页面不漏测 | 只生成 UI 证据；交互 DB 变化由所属业务 case 证明 | 不用 PNG OCR 推文案、不把 frame 当页面全集、不以 N/A 逃避容量条/错误态 | 每单元 `trace.zip`、`axe.json`、`screenshots/100pct.png`、`screenshots/200pct.png`、`manual-review.json` |
| `TDD-WP7-SEC-001-artifact-leak-scan` | PRIV-01、02、NFR-09、AC-41、48、56 | ZIP、源码、Git、DB 配置、manifest、日志、截图、trace/evidence 全集合；秘密 trap | 1. 解包并扫描文本/二进制/元数据；2. 检查 Windows 用户名/绝对路径；3. 检查缓存/凭据位置；4. 人工复核命中 | 真实敏感值和硬编码用户路径零命中；凭据仅 Credential Manager；诊断通过同一扫描 | 只生成扫描报告；不修改被扫描制品 | 不把 allowlist/HMAC/验证码/session/提示词/图片/原始错误写证据，不用删除报告掩盖命中 | `scan-report.json`、`allowlist.json`、`manual-review.json` |
| `TDD-WP7-AC-001-trace-structure` | NFR-05、52 当前 AC、109 需求 ID、13 模块、19 契约、9 错误 | 冻结 allowlist、89 family 定义、117 case 定义、页面矩阵和全部 evidence manifest | 1. 运行 `pnpm validate:tdd-trace`；2. 注入缺 AC/重复主责/P0-B passed/未知需求/孤儿 family/case 缺字段/错误父族解析；3. 恢复后重跑 | 每种破坏均非零且定位；正确状态返回 0：52 AC、109 唯一 ID、89/89 family 有 case、117 case 字段齐全 | 只生成 family/trace validation manifest；不改业务 DB | 不信任手写合计、不让 AC-08/26/37/54 计 passed、不允许未知父族、删除末段式解析或空 automation/release_gate | `family-manifest.json`、`trace-validation.json`、`negative-fixtures.json` |

## 7. 十九个产品数据契约覆盖

| 产品契约 | 主测试族 | 核心不变量 |
| --- | --- | --- |
| 17.1 UserProfile | `TDD-WP1-AUTH-*`、`TDD-WP6-PRIV-001` | 角色/状态/名额、点数和告知版本字段 |
| 17.2 ModelConfig | `TDD-WP3-MDL-*` | 唯一默认、唯一正整数优先级、配置与 runtime 分离 |
| 17.3 Project | `TDD-WP2-PROJ-*` | 固定比例、版本、上限、草稿、回收站 |
| 17.4 GenerationJob | `TDD-WP2-GEN-*`、`TDD-WP3-WRK-001` | 单活动任务、快照、不可逆终态、一次结算 |
| 17.5 CanvasElement | `TDD-WP4-CAN-001` | 四类型、最多 50、无 Fabric 私有字段 |
| 17.6 TemplateRegistry | `TDD-WP5-MAN-001`、`TDD-WP5-WHITE-001` | 稳定 ID/版本/状态/白名单与 full_p0 边界 |
| 17.7 StaticStickerCatalog | `TDD-WP5-CAT-001`、`TDD-WP5-CLN-001` | part/顺序/重复、来源、文件关系和引用保护 |
| 17.8 DynamicStickerData | `TDD-WP4-DYN-*` | 只提供消费字段、确认值和 DYN004 坐标 |
| 17.9 CreditAccount/CreditLedger | `TDD-WP2-CRED-*` | available/reserved、不可变流水和唯一操作键 |
| 17.10 PrivateContentAccessLog | `TDD-WP6-PRIV-002`、`TDD-WP6-AUD-001` | 每次打开独立记录、先审计后读取、180 天 |
| 17.11 AnonymousRetainedEvent | `TDD-WP1-DEL-002` | 随机断链主体、允许字段、最迟 180 天 |
| 17.12 AssetReleaseManifest | `TDD-WP5-RES-001` | immutable 版本、哈希、access_class、公开过滤 |
| 17.13 AssetPreviewGrant | `TDD-WP5-PREV-001` | 不改角色、每次校验、撤销/到期立即生效 |
| 17.14 ClientCachePolicy | `TDD-WP0-CACHE-001`、`TDD-WP5-CACHE-001` | 150 MB LRU、公开 allowlist、私有禁存 |
| 17.15 ExternalServiceUsage | `TDD-WP6-SVC-*` | 四状态、硬上限、人工恢复、零自动费用 |
| 17.16 AdminOperationLog | `TDD-WP6-AUD-001` | 两种 actor、非敏感摘要、180 天不可改删 |
| 17.17 GatewayBalanceState | `TDD-WP2-BAL-001`、`TDD-WP3-BAL-001` | model/account/unknown 影响、确认恢复、不回写配置 |
| 17.18 GenerationErrorCategory | `TDD-WP2-ERR-001` | 九项只读注册表、稳定动作与点数口径 |
| 17.19 LocalBackendStorageState | `TDD-WP0-STO-*` | 精确阈值、`>` 准入、file kind 计量、物理后重测 |

## 8. 十三个功能模块覆盖

| FeatureSummary 模块 | 主测试族 | 主责 WP |
| --- | --- | --- |
| 邀请注册与邮箱登录 | `TDD-WP1-AUTH-*`、`TDD-WP7-EXT-002` | WP-1 |
| 点数与原子结算 | `TDD-WP2-CRED-*`、`TDD-WP2-GEN-*` | WP-2 |
| 三模型 AI 生图 | `TDD-WP2-GEN-*`、`TDD-WP3-ADP-*` | WP-2 / WP-3 |
| 项目、历史、保存与冲突 | `TDD-WP2-PROJ-*` | WP-2 |
| 电脑画布与底图编辑 | `TDD-WP4-BG-001`、`TDD-WP4-CAN-001` | WP-4 |
| 文字模板与字体 | `TDD-WP4-TXT-*`、`TDD-WP5-WHITE-001` | WP-4 / WP-5 |
| 25 个普通贴纸 part | `TDD-WP5-CAT-001`、`TDD-WP5-UPL-001`、`TDD-WP5-CLN-001` | WP-5 |
| 色卡与动态贴纸 | `TDD-WP4-COL-001`、`TDD-WP4-DYN-*`、`TDD-WP5-COL-001` | WP-4 / WP-5 |
| 导出 | `TDD-WP2-EXP-*`、`TDD-WP4-EXP-001` | WP-4 |
| 运营后台 | `TDD-WP6-*` | WP-6 |
| 素材发布与内部预览 | `TDD-WP5-MAN-001`、`TDD-WP5-RES-001`、`TDD-WP5-PREV-001` | WP-5 / WP-6 |
| 本机数据与资源访问 | `TDD-WP0-*` | WP-0 / WP-2 |
| 隐私、审计与免费服务边界 | `TDD-WP1-DEL-*`、`TDD-WP6-PRIV-*`、`TDD-WP6-SVC-*` | WP-1 / WP-6 |

## 9. 九类生成错误测试矩阵

### 9.1 GenerationErrorCategory

`reference_invalid` 与 `safety_rejected` 必须使用不同 fixture、不同模拟响应和不同断言。供应商安全拒绝才能映射为 `safety_rejected`；格式、大小、数量或非安全型内容不可用映射为 `reference_invalid`。

| category | 创建前断言 | 创建后断言 | 用户动作 | 点数 |
| --- | --- | --- | --- | --- |
| `upstream_timeout` | 不适用 | failed、无输出、保存 category | 使用原输入重试 | release 一次 |
| `upstream_failed` | 不适用 | failed、无输出、保存 category | 稍后重试 | release 一次 |
| `safety_rejected` | 不适用 | rejected、无输出、使用明确安全证据 | 修改提示词或参考图 | release 一次 |
| `model_disabled` | 不创建任务 | 不适用 | 选择其他模型或等待 | 不 reserve |
| `gateway_balance_insufficient` | 可在创建前阻断 | 已创建则 failed，并更新独立 runtime 影响 | 选择未受影响模型或联系管理员 | 无 reserve 或 release 一次 |
| `gateway_contract_invalid` | contract 非 verified 时阻断 | 已创建后契约失效则 failed | 选择其他模型或联系管理员 | 无 reserve 或 release 一次 |
| `reference_invalid` | 格式/大小/数量可在创建前阻断 | 供应商非安全型不可用时 failed | 更换或移除参考图 | 无 reserve 或 release 一次 |
| `unknown_retryable` | 不适用 | failed，保留非敏感诊断 | 稍后重试 | release 一次 |
| `unknown_non_retryable` | 不适用 | failed，不显示重复重试入口 | 联系管理员 | release 一次 |

对每类都必须额外断言：前端、GenerationJob、后台生成记录和统计使用同一 category ID；原始错误码、正文、堆栈和内部地址不进入响应或 UI；queued、running、succeeded 的 `error_category` 为空；后台不能新增、重命名或删除分类。

### 9.2 稳定工程错误码

这些错误不扩充 GenerationErrorCategory。每项都必须验证 HTTP 状态、`error.code`、稳定 `message_key`、correlation ID、允许的 `details`、UI 动作和零敏感泄漏。

| code | HTTP | 主测试族 | 关键断言 |
| --- | ---: | --- | --- |
| `BROWSER_UNSUPPORTED` | 426 | `TDD-WP0-BRW-002` | 四个固定 reason；只有阻断引导，无绕过或业务副作用 |
| `MODEL_CONFIG_VERSION_CONFLICT` | 412 | `TDD-WP3-MDL-002` | 返回最新安全版本摘要，整套配置不写入，UI 刷新后重编 |
| `MODEL_DEFAULT_REPLACEMENT_REQUIRED` | 409 | `TDD-WP3-MDL-002` | 停用当前默认却未指定替代时整次失败 |
| `MODEL_DEFAULT_REPLACEMENT_INVALID` | 409 | `TDD-WP3-MDL-002` | 替代不存在/未启用/产生非唯一默认时整次失败 |
| `MODEL_RECOMMENDATION_PRIORITY_INVALID` | 400 | `TDD-WP3-MDL-002` | 缺失、浮点、零或负数定位到字段且不部分写入 |
| `MODEL_RECOMMENDATION_PRIORITY_CONFLICT` | 409 | `TDD-WP3-MDL-002` | 重复优先级标出冲突模型且不部分写入 |
| `PRIVATE_CONTENT_NOTICE_ACK_REQUIRED` | 428 | `TDD-WP6-PRIV-001` | 只返回当前告知版本/message_key，不返回区域数据 |
| `ASSET_HISTORY_REFERENCE_CONFLICT` | 409 | `TDD-WP5-CLN-001` | 任一引用使整批不排队，容量不变并记录 denied 审计 |
| `ASSET_CLEANUP_CANDIDATE_STALE` | 409 | `TDD-WP5-CLN-001` | 旧候选失效，要求重新查询和确认，不复用令牌 |
| `STORAGE_CAPACITY_EXCEEDED` | 507 | `TDD-WP0-STO-002` | full/unavailable 或预计值 `>` 上限才拒绝；等于上限不返回 507 |

未知 `message_key` 使用 UIDesign 的安全兜底“操作未完成”、correlation ID 和接口允许的重试/返回/诊断动作；不得被映射成成功、已释放点数或允许覆盖。

## 10. 视觉、性能与可访问性

### 10.1 视觉基线

- 页面结构验收使用 `1920 x 1080`、DPR 1、100% 缩放；低保真画板只验证区域、控件、状态、固定文案、焦点和交互，不做像素级 gold image。
- 200% 浏览器缩放逐页验证无文字覆盖、不可关闭弹层、不可达主动作或不连贯重叠；允许滚动，不缩小字号规避。
- Canvas 视觉基线固定 OS、浏览器 full version、视口、DPR、字体 ready、动画时刻、背景图和动态数据。
- Chrome/Edge 元素边界相对基准偏差不超过 2 px。
- 任一颜色通道绝对差异超过 16/255 的像素视为显著差异，显著像素不超过画布总像素 1%。
- 任一阈值超限必须人工复核，不能自动判定通过。
- P0-A 中 COLOR002、COLOR008、COLOR016、DYN012 及其他已知替代/近似项无论自动差异是否通过均人工复核；DYN012 报告必须写明 FONT081 Lexend Deca 替代。

### 10.2 性能预算

在候选或最终冻结的 Windows、Chrome/Edge、1920 x 1080、DPR 1、50 个混合覆盖元素下：

| 指标 | 门槛 |
| --- | --- |
| 连续拖动/缩放/旋转 10 秒的指针到下一画面更新 | p95 不超过 50 ms |
| 画布交互帧耗时 | p95 不超过 33 ms |
| 单个主线程长任务 | 不超过 200 ms |
| 连续无响应 | 不得达到 500 ms |
| 自动保存序列化主线程阻塞 | p95 不超过 50 ms |
| 已缓存白名单资源的编辑器重新打开到可操作 | 不超过 3 秒 |
| 1080 x 1920、50 元素导出 | 不超过 10 秒 |
| 导出页面峰值额外内存 | 不超过 1 GiB |
| 普通贴纸面板 DOM | 仅视口和两屏缓冲，不随 1,407 总数线性增长 |

每项至少运行一次预热和五次测量，报告单次值、p50、p95、最大值、浏览器 full version 和 fixture hash。性能失败不得通过提高超时、减少元素或降低最终导出尺寸掩盖。

### 10.3 可访问性

- 关键页面运行 axe，critical 和 serious 问题均为 0。
- 页面第一个 Tab 为“跳到主要内容”；视觉顺序与焦点顺序一致，不使用正 tabindex。
- 按钮、图标、状态和表单具有可读名称；状态不只依赖颜色。
- 弹层焦点被限制，关闭后回到触发点；破坏性弹层请求期间不能关闭或重复提交。
- saving、queued、running、重计量使用 polite live region；保存失败、冲突、浏览器阻断、full/unavailable 使用 assertive。
- 画布外提供所有关键命令的可聚焦等价控件；焦点或 SSE 更新不得抢走用户当前焦点。

## 11. 页面、状态与低保真映射

### 11.1 UIDesign 页面 × 适用状态矩阵

UIDesign 第“页面与入口地图”及各页面章节是产品页面覆盖全集；Pencil 不定义页面全集。下表的 22 个 coverage unit 包含产品页、身份变体和 Windows 辅助界面。每个非 `N/A` 单元格必须至少关联一个自动或人工 case，并把 `1920 x 1080`、DPR 1、100% 与 200% 缩放截图写入 `artifacts/tdd/<run_id>/ui/<page_id>/<state>/`；`N/A` 必须保留下表理由，不能用缺实现代替。内部预览账号沿用普通页面，只作为授权状态变体，不创建新角色或后台页面。

| page_id / 页面或入口 | 主体与入口 | 正常 | 空 / 加载 | 失败 / 禁用 | 冲突 / 容量 / 特殊状态 | 主测试落点 |
| --- | --- | --- | --- | --- | --- | --- |
| `support-gate` 浏览器不受支持引导 | 所有主体，产品门禁前 | 四种阻断 reason、支持品牌/major、无继续入口 | N/A：引导无业务空态；身份不可判定属于阻断 | bundle/API 均无法绕过；诊断仅最小字段 | N/A：不读取业务状态或容量 | `TDD-WP0-BRW-002`、`TDD-WP7-UI-001` |
| `user-auth` 登录 / 注册 | 未登录普通入口 | 登录、分阶段注册、管理员文字入口、固定本机提示 | 字段初始空；发码/验证/注册加载 | Resend、验证码、邀请、名额、告知未完成；按钮禁用 | N/A：认证不消费存储容量 | `TDD-WP1-AUTH-*`、第 6.9.2 节 |
| `workspace` 创作工作台 | active 普通/内部预览账号 | 生成表单、模型/点数/比例、最近项目 | 无项目艺术带；模型/项目加载 | 服务/参考图/生成错误；模型、点数、单任务、历史/项目上限禁用 | model config 变化重确认；warning/critical 提示；full/unavailable 阻断新增 | `TDD-WP2-GEN-*`、`TDD-WP0-STO-*` |
| `current-task` 当前生成任务 | active 普通/内部预览账号 | 真实状态、结果入口 | 无活动任务；queued/running 无假百分比 | failed/rejected 与九类固定动作；第二任务禁用 | storage 错误按任务真相；pending_manual_review 不作为用户任务状态；继承普通端全局容量条 | `TDD-WP2-GEN-*`、`TDD-WP3-WRK-001` |
| `projects` 项目列表与回收站 | active 普通/内部预览账号 | active/trashed 列表、筛选、多选 failed_empty | 无项目/筛选空；稳定卡片骨架 | 列表失败保留旧数据；20 active、恢复/批量条件禁用 | 多标签版本提示；删除排队不乐观减容量；继承普通端全局容量条 | `TDD-WP2-PROJ-001`、`TDD-WP2-PROJ-003`、`TDD-WP2-PROJ-005` |
| `project-detail` 项目详情 | 项目所有者 | 历史、固定比例、继续生成、编辑、原图/latest 下载 | 无成功图草稿；历史/成品加载 | 10 图上限、当前底图不可删、无 latest、资源读取失败 | 比例不可改；清理/容量状态不伪造资源可用性；继承普通端全局容量条 | `TDD-WP2-PROJ-*`、`TDD-WP2-EXP-002` |
| `editor` 编辑器 | 有成功底图的项目所有者 | 三栏、保存状态、画布、属性与素材 | 画布/素材加载；无成功图不能正常进入 | 保存/素材失败；第 51 元素、不可用模板禁用 | dirty/saving/saved/failed；conflicted 只读；full 可存 JSON，unavailable 保存失败 | `TDD-WP4-*`、第 6.9.8 节 |
| `export` 导出弹层 | 编辑器内项目所有者 | JPG/PNG、质量、尺寸、下载/latest 分结果 | 合成/持久化加载；无业务空态 | 合成、下载、项目保存、latest 保存分别失败；无画布/冲突次数用尽禁用 | 未提交确认；conflicted 仅一次本地导出；full/unavailable 后端失败不撤回下载 | `TDD-WP4-EXP-001`、第 6.9.9 节 |
| `credits` 点数明细 | active 普通/内部预览账号 | available/reserved、流水、关联与时间 | 无流水；分页加载 | 加载失败保留安全余额摘要 | 负 available 如实显示；无购买/充值入口；页面操作不消费容量但仍继承普通端全局容量条 | `TDD-WP2-CRED-*`、第 6.9.3 节 |
| `settings` 设置与账号注销 | active 普通/内部预览账号 | 资料、本机数据、容量摘要、固定文案、注销 | 资料/状态加载 | 保存失败；注销确认/处理中禁用重复提交 | full/unavailable 状态与允许动作；注销后返回登录 | `TDD-WP1-DEL-*`、`TDD-WP0-DATA-002` |
| `preview-user-variant` 内部预览账号普通页变体 | 有有效/撤销/过期 AssetPreviewGrant 的普通账号 | 授权批次素材显示“预览”，其余页面同普通用户 | 无授权/批次空；manifest 加载 | 撤销/过期立即阻断新读取 | 不出现后台入口、调点、邀请码、模型或私有内容；继承普通端全局容量条 | `TDD-WP5-PREV-001`、`TDD-WP5-RES-001` |
| `admin-auth` super_admin 登录 | 未登录后台入口 | 白名单邮箱验证码、独立 audience、返回普通登录 | 字段空；发码/登录中 | 非白名单发码前阻断、停用/移除白名单、Resend 错误 | N/A：不显示普通创作或容量业务 | `TDD-WP1-ADM-001`、`TDD-WP1-CFG-001` |
| `admin-overview` 后台总览 | active super_admin | 名额、任务、模型、点数、服务、容量、审计摘要 | 无近期事件；指标骨架 | 聚合失败保留旧值和时间；敏感动作按状态禁用 | default/runtime/recommended 分列；pending_manual_review 为成本核对；容量持续条 | `TDD-WP6-ADM-001`、`TDD-WP6-STATE-001`、`TDD-WP6-DIAG-001` |
| `admin-users` 用户管理 | active super_admin | 脱敏列表/详情、暂停恢复、调点 | 无用户/筛选空；表格骨架 | 操作失败、原因缺失、状态前置禁用 | 调点可使 available 为负但 reserved 不变；页面操作不消费容量但仍继承后台全局容量条 | `TDD-WP6-ADM-001`、`TDD-WP2-CRED-002` |
| `admin-invites` 邀请码 | active super_admin | 创建、停用、期限/次数、脱敏复制 | 无邀请码；表格骨架 | 名额满、停用/过期/耗尽不可当可用；请求失败 | CAS/幂等结果；页面操作不消费容量但仍继承后台全局容量条 | `TDD-WP1-AUTH-*`、`TDD-WP1-SLOT-001`、`TDD-WP6-AUD-001` |
| `admin-models` 模型 | active super_admin | 完整三模型集合、default/runtime/recommended 三层 | 初始加载骨架；无 recommended 是合法状态 | 合法替代/priority 前置禁用；契约未验证/运行时不可用 | config_set CAS 冲突、完整集合原子回滚；仍继承后台全局容量条 | `TDD-WP3-MDL-*`、第 6.9.7 节 |
| `admin-assets` 素材 | active super_admin | 普通贴纸、资源版本/发布、历史清理 | 无上传素材/无候选；列表/处理加载 | 格式/编译失败；权限、文件状态禁用 | stable ID/version 冲突；full/unavailable 阻断上传；引用冲突/排队/重计量 | `TDD-WP5-UPL-001`、`TDD-WP5-CLN-001`、`TDD-WP5-MAN-001` |
| `admin-preview` 内部预览 | active super_admin | 批次、模板状态、账号授权/撤销/到期 | 无批次/授权；表格骨架 | 非法状态转换、过期、发布校验失败 | 授权不改变角色；P0-B 全量开放不可在 P0-A 出现；继承后台全局容量条 | `TDD-WP5-PREV-001`、`TDD-WP5-WHITE-001` |
| `admin-generations` 生成记录与私有内容 | active super_admin | 默认安全摘要、告知、逐次打开 | 无记录；列表/审计写入加载 | 未确认 428、审计失败不打开、权限禁用 | 告知升版重阻断；每次打开先独立审计；页面读取不消费容量但仍继承后台全局容量条 | `TDD-WP6-PRIV-*`、第 6.9.6 节 |
| `admin-services-storage` 服务与存储 | active super_admin | Resend/高德/网关/Worker/LocalDataRoot 状态与允许动作 | 指标加载；无告警 | paused/degraded/unavailable；不合法恢复/提高上限禁用 | full/unavailable、清理排队和物理重计量；不显示绝对路径 | `TDD-WP6-SVC-001`、`TDD-WP6-SVC-002`、`TDD-WP6-DIAG-001`、`TDD-WP0-STO-*` |
| `admin-audit` 审计 | active super_admin | 后台操作与私有访问两类独立页/标签 | 无记录；游标加载 | 查询失败保留旧结果；无修改/人工删除动作 | 180 天保留；正文、图片、密钥和路径不出现；页面读取不消费容量但仍继承后台全局容量条 | `TDD-WP6-AUD-001`、`TDD-WP6-PRIV-002` |
| `system-ui` Windows 辅助系统界面 | 本机托盘/Supervisor，无产品角色 | 托盘、运行状态、打开 Dada、诊断、退出 | 启动中；N/A 业务空态 | 启动失败、端口占用、API/Worker degraded，动作严格受限 | full/unavailable 只作脱敏告警；固定端口不动态切换 | `TDD-WP0-SUP-001`、`TDD-WP0-PKG-001`、`TDD-WP0-LOG-001`、`TDD-WP7-REL-001` |

`pnpm validate:tdd-trace` 必须把 UIDesign 页面/入口 allowlist 与上述 `page_id` 对照：缺少 UIDesign 页面、重复 page_id、把内部预览误建角色/后台、把 Canvas Index 计为产品页、适用状态无 case/evidence 或 `N/A` 无理由时返回非零退出码。

### 11.2 Pencil 布局参考映射

`.pen` 中共有 19 个顶层画板。画板尺寸 1600 x 900，索引画板为 1440 x 420；预览 PNG 外边框使图像为 1602 x 902 或 1442 x 422。实现验收仍以 UIDesign 的 1920 x 1080 为主，不把预览尺寸改成产品视口。

画板中的版本号、账号、点数、容量和时间均是低保真示例数据。例如辅助界面预览里的 Chrome/Edge 版本不能作为支持记录；自动化必须从当前测试 fixture 读取状态，发布验收必须从 `RELEASE.json` 读取真实版本。若 `.pen` 或预览 PNG 中的示例文案、账号、容量、时间与 UIDesign 固定文案冲突，以 UIDesign 为界面验收依据；自动化不得通过 PNG OCR 反推验收文案。

| frame ID | 画板 | 预览 | 主要测试落点 |
| --- | --- | --- | --- |
| `Z8p3I` | 00 Canvas Index | `Z8p3I.png` | 非产品索引，仅校验设计资产清单；不要求实现，不计页面完成率 |
| `Z0pf8` | 01A 普通用户登录 | `Z0pf8.png` | 登录字段、标签、管理员入口、固定本机提示、焦点 |
| `DVPM8` | 01B 注册展开态 | `DVPM8.png` | 邀请/邮箱只读摘要、资料、告知、注册主动作 |
| `WbDYT` | 02A 空工作台 | `WbDYT.png` | 艺术带、生成表单、推荐模型、比例和空任务 |
| `d99Lo` | 02B 生成中工作台 | `d99Lo.png` | 当前任务、冻结点数、critical 容量条和禁用提交 |
| `zXyG9` | 03A 项目列表 | 无独立 PNG | 项目/回收站、上限、failed_empty、稳定卡片 |
| `v2Qbu` | 03B 项目详情 | 无独立 PNG | 历史、固定比例、编辑/生成/下载、无成品状态 |
| `bHQnJ` | 04A 三栏编辑器 | `bHQnJ.png` | 三栏、保存状态、画布、对象属性、固定底栏 |
| `i9upg7` | 04B 导出弹层 | 无独立 PNG | JPG/PNG、质量、未提交确认、下载/持久化分结果 |
| `YlKzE` | 05A 点数明细 | 无独立 PNG | available/reserved、流水、无购买入口 |
| `Ws5E0` | 05B 设置与本机数据 | 无独立 PNG | 固定文案、容量、文件权限风险、注销 |
| `e4e2Kz` | 06A 后台总览 | `e4e2Kz.png` | 名额、任务、成本核对、服务/容量、最近审计 |
| `YVfVG` | 06B 模型配置 | 无独立 PNG | 默认/推荐/runtime 三列、完整集合和冲突 |
| `A28V3c` | 06C 素材与历史清理 | 无独立 PNG | 上传计量、候选/确认/冲突/排队/重计量 |
| `OnsiT` | 06D 生成记录与私有告知 | 无独立 PNG | 默认隐藏正文、身份+版本告知、逐次入口 |
| `L3lde2` | 06E 服务与存储 | 无独立 PNG | Resend/高德/网关/Worker/storage 状态与动作 |
| `DlCpN` | 06F 私有内容访问审计 | 无独立 PNG | 两类审计分离、不可编辑删除、无正文 |
| `u1adni` | 07A 浏览器硬阻断 | 无独立 PNG | 426、四原因、支持 major、无继续入口 |
| `awt96` | 07B Windows 辅助系统界面 | `awt96.png` | 托盘、启动失败、端口占用、degraded、诊断脱敏 |

除 `Z8p3I` Canvas Index 外，Pencil 产品 frame 只为其映射到的第 11.1 节页面/状态提供布局参考；是否需要截图及状态数量由第 11.1 节决定，不以“18 个产品 frame”替代完整页面覆盖。UIDesign 中没有独立 Pencil frame 的后台用户、邀请码、内部预览、管理员登录等页面仍必须具备完整 UI 证据。预览缺失不阻止测试，`.pen` 不是实现范围清单；不得自行补画或改写设计文件作为测试通过手段。

## 12. 当前 AC 唯一主责矩阵

每项 AC 只有一个主责 WP。支持测试可以跨 WP，但只能由主责 WP 汇总完成，WP-7 只汇总发布证据而不夺取产品需求主责。

`pnpm validate:tdd-trace` 必须从冻结的 PRD 需求/AC allowlist 校验本节、第 13 节和第 6.9 节，失败时返回非零退出码：当前 AC 集合必须恰为 52 项；每项恰有一个唯一主责；AC-08、AC-26、AC-37、AC-54 不得进入当前矩阵或 `passed` 计数；第 13 节范围展开后必须恰为 109 个互不重复的已知 PRD 需求 ID，不得遗漏、重复或新增未知 ID；测试族必须恰为 89 个且 AREA 正则允许数字；117 个规范 case 必须全部按 `^(TDD-WP[0-7]-[A-Z0-9]+-[0-9]{3})-` 捕获已知父族，每个父族至少一个 case；每个 case 的 fixture、步骤、响应、DB/文件变化、禁止副作用、证据文件和第 2.2.1 节派生属性均非空。检查必须读取结构化追踪数据或确定性解析本文，不得信任手写合计值。

| AC | 当前范围 | 唯一主责 | 主测试族 | 必要支持 |
| --- | --- | --- | --- | --- |
| AC-01 | 全部 | WP-1 | `TDD-WP1-AUTH-001` | `TDD-WP1-NOTICE-001`、`TDD-WP7-EXT-002` |
| AC-02 | 全部 | WP-1 | `TDD-WP1-AUTH-002` | 无 |
| AC-03 | 全部 | WP-2 | `TDD-WP2-GEN-002` | `TDD-WP3-ADP-001` |
| AC-04 | 全部 | WP-2 | `TDD-WP2-GEN-002` | `TDD-WP2-ERR-001` |
| AC-05 | 全部 | WP-2 | `TDD-WP2-GEN-001` | `TDD-WP2-CRED-001` |
| AC-06 | 全部 | WP-2 | `TDD-WP2-PROJ-001` | 无 |
| AC-07 | 全部 | WP-4 | `TDD-WP4-BG-001` | `TDD-WP4-BG-002`、`TDD-WP4-COL-001` |
| AC-09 | 全部 | WP-4 | `TDD-WP4-TXT-001` | `TDD-WP4-EXP-001` |
| AC-10 | 全部 | WP-4 | `TDD-WP4-TXT-001` | 无 |
| AC-11 | P0-A 字体子范围 | WP-4 | `TDD-WP4-TXT-002` | `TDD-WP5-WHITE-001` |
| AC-12 | 全部 | WP-4 | `TDD-WP4-TXT-002` | `TDD-WP4-EXP-001` |
| AC-13 | 全部 | WP-5 | `TDD-WP5-CAT-001` | `TDD-WP5-CACHE-001` |
| AC-14 | 全部 | WP-4 | `TDD-WP4-STK-001` | 无 |
| AC-15 | 仅四种 P0-A 色卡 | WP-5 | `TDD-WP5-COL-001` | `TDD-WP4-COL-001` |
| AC-16 | 全部 | WP-4 | `TDD-WP4-DYN-001` | 无 |
| AC-17 | 全部 | WP-4 | `TDD-WP4-DYN-002` | `TDD-WP7-EXT-003` |
| AC-18 | 全部 | WP-4 | `TDD-WP4-DYN-003` | 无 |
| AC-19 | 全部 | WP-4 | `TDD-WP4-DYN-003` | `TDD-WP4-VIS-001` |
| AC-20 | 全部 | WP-2 | `TDD-WP2-PROJ-002` | `TDD-WP0-CACHE-001` |
| AC-21 | 全部 | WP-2 | `TDD-WP2-PROJ-003` | `TDD-WP0-STO-003` |
| AC-22 | 全部 | WP-1 | `TDD-WP1-DEL-001` | `TDD-WP1-DEL-002` |
| AC-23 | 仅 P0-A 四比例与同机下载 | WP-4 | `TDD-WP4-EXP-001` | `TDD-WP2-EXP-001` |
| AC-24 | 全部 | WP-0 | `TDD-WP0-BRW-001`、`TDD-WP0-BRW-002` | `TDD-WP0-BND-001` |
| AC-25 | 全部 | WP-6 | `TDD-WP6-PRIV-001`、`TDD-WP6-PRIV-002` | `TDD-WP6-AUD-001` |
| AC-27 | 全部 | WP-4 | `TDD-WP4-CAN-001` | `TDD-WP4-PERF-001` |
| AC-28 | 全部 | WP-2 | `TDD-WP2-CRED-001` | `TDD-WP2-GEN-003` |
| AC-29 | 全部 | WP-2 | `TDD-WP2-CRED-002` | `TDD-WP2-GEN-001` |
| AC-30 | 全部 | WP-3 | `TDD-WP3-MDL-002`、`TDD-WP3-MDL-003` | `TDD-WP2-GEN-003` |
| AC-31 | 全部 | WP-5 | `TDD-WP5-UPL-001`、`TDD-WP5-CLN-001` | `TDD-WP0-STO-003` |
| AC-32 | 全部 P0-A 白名单 | WP-4 | `TDD-WP4-VIS-001` | `TDD-WP4-TXT-003`、`TDD-WP5-WHITE-001`、`TDD-WP7-REL-001` |
| AC-33 | 全部 | WP-1 | `TDD-WP1-AUTH-003` | `TDD-WP7-EXT-002` |
| AC-34 | 全部 | WP-2 | `TDD-WP2-PROJ-001` | 无 |
| AC-35 | 全部 | WP-2 | `TDD-WP2-EXP-001` | `TDD-WP2-PROJ-002` |
| AC-36 | 仅同机多标签 | WP-2 | `TDD-WP2-PROJ-004` | `TDD-WP2-EXP-001` |
| AC-38 | 仅 P0-A 本机成品空状态 | WP-2 | `TDD-WP2-EXP-002` | 无 |
| AC-39 | 全部 | WP-1 | `TDD-WP1-DEL-002` | `TDD-WP6-AUD-001` |
| AC-40 | 全部 | WP-3 | `TDD-WP3-ADP-001`、`TDD-WP3-ADP-002` | `TDD-WP7-EXT-001` |
| AC-41 | 全部 | WP-7 | `TDD-WP7-REL-001`、`TDD-WP7-EXT-*` | WP-0 至 WP-6 全部 Green |
| AC-42 | 全部 | WP-5 | `TDD-WP5-PREV-001` | `TDD-WP5-RES-001` |
| AC-43 | 全部 | WP-2 | `TDD-WP2-PROJ-005` | `TDD-WP2-GEN-002` |
| AC-44 | 全部 | WP-2 | `TDD-WP2-REF-001` | `TDD-WP0-CACHE-001` |
| AC-45 | 全部 | WP-1 | `TDD-WP1-SLOT-001` | `TDD-WP5-PREV-001` |
| AC-46 | 全部 | WP-0 | `TDD-WP0-CACHE-001` | `TDD-WP5-CACHE-001` |
| AC-47 | 全部 | WP-6 | `TDD-WP6-SVC-001` | `TDD-WP7-EXT-002`、`TDD-WP7-EXT-003` |
| AC-48 | 全部 | WP-5 | `TDD-WP5-RES-001` | `TDD-WP5-PREV-001`、`TDD-WP0-SEC-001` |
| AC-49 | 全部 | WP-1 | `TDD-WP1-ADM-001`、`TDD-WP1-CFG-001` | `TDD-WP7-EXT-002` |
| AC-50 | 全部 | WP-6 | `TDD-WP6-AUD-001` | `TDD-WP5-CLN-001`、`TDD-WP6-PRIV-002` |
| AC-51 | 全部 | WP-2 | `TDD-WP2-BAL-001` | `TDD-WP3-BAL-001` |
| AC-52 | 全部 | WP-6 | `TDD-WP6-SVC-002` | `TDD-WP6-AUD-001` |
| AC-53 | 全部 | WP-2 | `TDD-WP2-ERR-001` | `TDD-WP3-ADP-001` |
| AC-55 | 全部 | WP-0 | `TDD-WP0-STO-001`、`TDD-WP0-STO-002`、`TDD-WP0-STO-003` | `TDD-WP5-UPL-001`、`TDD-WP5-CLN-001` |
| AC-56 | 全部 | WP-0 | `TDD-WP0-DATA-002` | `TDD-WP7-UI-001` |

未来 AC-08、AC-26、AC-37、AC-54 不得出现在当前 `passed` 计数。允许编译器或内部预览为未来素材生成非发布证据，但不得扩大普通用户可见范围或阻止 P0-A 完成。

## 13. 109 个 PRD 需求 ID 覆盖索引

| 主责 WP | 需求 ID | 数量 |
| --- | --- | ---: |
| WP-0 | PRIV-01、PRIV-02、NFR-01、NFR-07、NFR-09 | 5 |
| WP-1 | AUTH-01 至 AUTH-07、PRIV-03、PRIV-04、PRIV-06、NFR-04 | 11 |
| WP-2 | CREDIT-01 至 CREDIT-06、PROJECT-01 至 PROJECT-09、GEN-03 至 GEN-06、GEN-11、GEN-12、GEN-14、GEN-16、NFR-05 | 24 |
| WP-3 | GEN-01、GEN-02、GEN-07 至 GEN-10、GEN-13、GEN-15、ADMIN-03 | 9 |
| WP-4 | EDITOR-01 至 EDITOR-09、TEXT-01 至 TEXT-14、COL-01 至 COL-05、DYN-01 至 DYN-09、EXPORT-01 至 EXPORT-06、NFR-03、NFR-06 | 45 |
| WP-5 | STATIC-01 至 STATIC-04、ADMIN-06、ADMIN-07、PRIV-05、NFR-02 | 8 |
| WP-6 | ADMIN-01、ADMIN-02、ADMIN-04、ADMIN-05、ADMIN-08、ADMIN-09、NFR-08 | 7 |
| 合计 | 不重复的当前 PRD 需求 ID | 109 |

每个具体测试文件必须从本表选择需求 ID，不得创造未经 PRD 定义的新 ID。跨包依赖可以重复引用需求 ID，但唯一主责不变。

## 14. CI、隔离与外部验证

### 14.1 普通 CI

- Gitea Actions 使用冻结 lockfile，全部外部服务为 mock，仅使用脱敏 fixture 和 `.invalid` 邮箱。
- 每个 DB/文件测试使用系统临时目录和独立 SQLite；完成后关闭句柄并删除测试目录。
- 浏览器与打包 job 并发数为 1，使用 `%RUNNER_TEMP%` 下的独立 user-data-dir，不读取或关闭真实 Dada 实例。
- 普通 CI 使用系统分配临时回环端口；临时端口仅限测试 harness。打包、Supervisor、启动入口、诊断和发布验收必须证明产品包固定使用 `127.0.0.1:43121` 且端口占用时失败，不会动态换端口。
- 普通 CI 不读取 Windows Credential Manager 中真实条目，不访问项目方真实 LocalDataRoot，不扫描 3.57 GB 归档。
- flaky 重跑不能把失败改成通过。任何自动重跑都保留首轮失败，并在证据中记录；连续不稳定测试阻止所属 WP 完成。

### 14.2 受控真实验证

- `pnpm validate:external` 只能由项目方在 P0-A 验收电脑显式运行；命令从 Windows Credential Manager 按最小范围读取凭据，不接收命令行密钥。
- 每次真实调用先显示服务、模型、预计调用数量和是否可能消耗既有额度；执行者明确确认后才开始。
- 真实响应立即脱敏，仅保留协议字段、状态类别、耗时、MIME、尺寸、非敏感用量、证据哈希和时间。
- AI 网关三个模型分别生成证据，不能共享“已验证”标记。
- Resend 送达记录只保存人工统计，不建立运行时 EmailVerificationEvent；19/20、2 分钟的阻断门槛直接来自 PRD 第 23.2 节、AC-41、AC-47 和 DevelopmentPlan 第 1.4、11.1 节，不是 TDD 新增指标。
- 高德真实测试不把测试坐标关联到真实用户项目。
- 外部验证失败保持 externally_blocked，不自动切换付费、备用供应商、自动充值或自动扩容。

## 15. 工作包顺序与完成门禁

| WP | 依赖 | 自动完成门禁 | 仍需真实/人工门禁 |
| --- | --- | --- | --- |
| WP-0 | 无 | 单元、API、存储、Supervisor、安全、打包 Green | RELEASE 浏览器记录在 WP-7 冻结 |
| WP-1 | WP-0 | 认证状态机、事务、会话、名额、注销、审计 Green | Resend 正式认证 |
| WP-2 | WP-0、WP-1 | 项目、点数、任务、参考图、冲突、错误 Green | 真实三模型完成态依赖 WP-3 |
| WP-3 | WP-2 | 模型集合、runtime、Adapter mock、Worker 恢复 Green | 三模型独立真实契约 |
| WP-4 | WP-2 | 编辑器、导出、a11y、性能 harness Green | 最终视觉依赖 WP-5 真实白名单 |
| WP-5 | WP-0；与 WP-4 前半可并行 | 编译、目录、隔离、缓存、上传、清理 Green | 真实归档只读校验和人工视觉 |
| WP-6 | WP-1、WP-2、WP-5；模型页依赖 WP-3 | 后台、告知、审计、服务与存储 Green | 真实服务恢复和正式身份流程 |
| WP-7 | WP-0 至 WP-6 | `pnpm test:all`、52 AC 结构检查、制品扫描 Green | RELEASE、Chrome/Edge、AI、Resend、高德及人工验收全部通过 |

任何工作包的“不能提前宣告”条件沿用 DevelopmentPlan。依赖工作包未 Green 时，下游可以编写 Red 测试和 mock 接线，但不能标记完成。

## 16. P0-A 测试完成定义

只有以下条件全部满足，`tdd.md` 所定义的 P0-A 测试才完成：

1. 四份 Markdown 全局来源哈希与第 1.2.1 节一致；第 1.2.2 节 UI 参考资产已匹配当前哈希，或受影响页面已完成局部重读、映射更新和视觉重建，不存在未处理的 `source_changed` case。
2. WP-0 至 WP-7 全部达到第 15 节门禁，第 6.9 节 117 个规范 case 及其参数分支均有可复现 Red 和最终 Green/Passed 证据。
3. `pnpm validate:tdd-trace` 通过；第 12 节 52 项当前 AC 全部 passed，每项恰有一个唯一主责；AC-08、26、37、54 未混入当前完成统计；89/89 测试族至少有一个 case，117 个 case 的必填字段、派生执行属性和证据目录完整。
4. 第 13 节 109 个互不重复且无未知项的需求 ID、十三个功能模块、十九个数据契约和九类错误均有可执行测试落点。
5. `reference_invalid` 与 `safety_rejected` 由不同证据证明；九类错误的任务、点数、用户动作和脱敏断言全部通过。
6. `managed_content_bytes` 的阈值、等号写入、超限无副作用、原图/缩略图计量、full/unavailable 动作和物理后重测全部通过。
7. ModelConfig 始终一个 enabled default、唯一正整数 priority；runtime 降级不回写配置；recommended 可重新派生或为空。
8. 私有告知按 super_admin 身份+版本跨标签、重启、重登和两浏览器持久；每次图片/完整提示词读取仍先写独立访问审计。
9. Playwright 已实际枚举 Service Worker、Cache Storage 和 IndexedDB，确认没有 internal preview、私有内容、项目、提示词或未保存草稿。
10. 跨用户隔离使用正常邀请/验证码/普通权限创建临时第二账号，完成后正常注销并进入规定清理路径。
11. 第 11.1 节 UIDesign 页面 × 状态矩阵的全部适用单元格均有实现和证据；Canvas Index 未计入产品页，UIDesign 中没有独立 Pencil frame 的页面没有漏测；1920 x 1080、200% 缩放、焦点、最长文案、弹层和多状态叠加无不可用重叠。
12. 视觉差异、50 元素性能、四比例导出、Chrome/Edge 下载与文件选择均在最终 RELEASE 版本通过。
13. 三模型、Resend、高德真实门槛通过；未配置自动充值、收费备用或自动扩容。
14. `RELEASE.json`、便携 ZIP、SHA-256、START-HERE 和固定端口边界一致；局域网、其他设备和不支持浏览器无法进入产品。
15. 制品、代码、日志、DB 配置、manifest、截图和证据中不存在真实凭据、验证码、会话令牌、管理员真实邮箱、完整私有内容或硬编码用户绝对路径。
16. 普通用户主框架和设置区持续原样显示：“测试数据仅保存在本机，不自动备份，也不会迁移到正式系统。”

缺少任一项时保持各 case 的实际测试状态，不得标记 P0-A 测试整体完成或发布完成。
