# Dada DevelopmentPlan

| 项目 | 内容 |
| --- | --- |
| 文档版本 | 1.1 |
| 适用产品版本 | PRD v1.10，P0-A 本机测试需求冻结稿 |
| 功能索引版本 | FeatureSummary 1.1 |
| 当前交付 | 仅 P0-A Windows 本机测试版 |
| 产品需求权威 | `PRD.md` |
| 输入文件 SHA-256 | PRD `31F93674DF1A90B557FEE3AA9E74FB084E246CA8FE09F6BD4B1DC9D56D606565`；FeatureSummary `6F80E272AAB08A5525B54501D83F16A4F6A7A170596947BBC25F1DA54F2FE844` |
| 文档职责 | 固定工程架构、组件边界、数据与接口、事务、实施顺序、阻塞和验收追踪 |

## 1. 文档定位与工程目标

### 1.1 权威顺序

1. `PRD.md` v1.10 是产品状态、字段含义、业务规则、阶段边界和验收范围的唯一权威。
2. `FeatureSummary.md` 1.1 只用于定位十三个功能模块、状态机、十九个数据契约和 AC 索引，不产生新需求。
3. FeatureSummary 中的“批量生成”和“复制生成”仅用于导航 PRD 已排除的不做范围，不构成新需求或文档冲突。公开模型广场只作为三个模型存在和候选路由的线索，不改变 PRD 的网关契约验证门槛。
4. 本文只作工程展开；若实现或后续文档与 PRD 冲突，先升级 PRD，再修改本文，不能以工程便利改变产品语义。

### 1.2 P0-A 工程交付目标

在项目方当前 Windows 电脑上交付一个免安装的本机系统：用户双击 `Dada.exe` 后，由托盘 Supervisor 启动本地 API 与 Generation Worker，并在发布记录支持的当前稳定版 Chrome 或 Edge 中打开 `http://127.0.0.1:43121`。浏览器支持门禁先于登录、注册及其他产品功能；通过后，系统在同一电脑上完成邀请注册、邮箱验证码登录、AI 生图、项目编辑、自动保存、素材渲染、JPG/PNG 导出、运营后台、审计和本机清理闭环。

交付完成必须同时满足：

- 后端仅绑定 `127.0.0.1:43121`，不绑定局域网网卡、IPv6 任意地址或公网地址；
- 数据只写入 `LocalDataRoot`，素材只从安全配置引用的规范归档原地只读读取；
- API 密钥只保存在当前 Windows 用户的 Windows 凭据管理器；
- P0-A 的适用 AC 全部通过，三个模型、Resend 和高德的外部发布门槛已解除；
- 发布产物为未签名 `win-x64` 便携 ZIP 和对应 SHA-256 文件；
- 本机数据没有自动备份、项目归档、导入、迁移或恢复承诺。
- 非支持浏览器只能加载独立阻断引导页所需的最小静态资源和支持状态，不能调用产品功能 API，也没有继续或绕过入口。

### 1.3 当前边界

P0-A 不实现手机编辑、手机访问、局域网访问、跨设备处理、远程部署、公司系统合并、正式对象存储、备案域名或产品 HTTPS 站点。浏览器与本机后端使用回环 HTTP；本机后端访问 AI 网关、Resend 和高德时必须使用外网 HTTPS。

P0-B 只在第 16 节记录未来开放门槛。AC-08、AC-26、AC-37、AC-54 以及 AC-15、AC-23、AC-36、AC-38 中明确属于 P0-B 的部分，不属于当前组件、里程碑、工作包或完成条件。

P1 的本地底图上传、空白画布、普通贴纸关键词搜索、公开注册准备、高德主体迁移和正式合规条款不进入当前工程。支付/充值/订阅、直发小红书、账号绑定、多页图组、批量画布、批量导出、社区、独立普通文字、用户自定义贴纸、AI 贴纸、动态/GIF/视频导出、文案生成、整版模板、提示词优化、局部重绘/扩图/移除背景、专业调色、实时协作、项目版本历史、编组和图层面板均不实现。

### 1.4 外部产品发布阻塞与可并行范围

| 外部项 | 当前状态 | 阻塞范围 | 可并行范围 | 解除条件 |
| --- | --- | --- | --- | --- |
| AI 网关 | 三个模型标识已确认存在；图片请求、参考图、比例、响应、同步/异步和错误协议未验证 | 三个 Adapter 定版、ModelConfig 路由、AC-40、真实点数结算联调、P0-A 发布 | 编辑器、素材、认证、项目、SQLite 队列、固定 Adapter 接口、模拟网关测试 | 三个模型分别完成第 8.3 节契约矩阵并保存脱敏证据 |
| Resend | 发信域名、免费规则和人工送达尚未完成发布验证 | 正式注册/登录验收、AC-01、AC-33、AC-41、AC-49、P0-A 发布 | 认证状态机、验证码挑战表、配额、模拟邮件、开发测试账号 | SPF/DKIM 通过；免费额度复核；QQ、163、企业邮箱各 20 封至少 19 封在 2 分钟内送达 |
| 高德 | 个人认证 Web 服务 Key 的接口、绑定、配额和限制未验证 | DYN004 自动定位、AC-17、AC-41、AC-47、P0-A 发布 | 手动地点输入、动态贴纸渲染、配额与暂停状态机、模拟地理编码 | 地理/逆地理接口、QPS、白名单、安全限制和每月 1,000 次应用硬上限实测通过 |

外部阻塞不能用猜测消除。阻塞存在时可以完成相应模块的结构、模拟测试和本地独立功能，但不能在状态或里程碑中标记为集成完成。AI 网关、Resend 和高德是三项产品发布外部阻塞。

### 1.5 工程基础设施前置条件

Gitea Actions 是已确定的工程交付组件，不是 PRD 产品依赖，也不计入上述三项外部发布阻塞。当前电脑的 Windows 自托管 Runner 在隔离工作目录注册并跑通无真实凭据流水线之前，本地工程初始化和测试脚本可继续，但正式 CI 和 WP-0 工程完成不得宣告。

## 2. 技术架构与选型

### 2.1 固定技术基线

| 层级 | 固定选择 | 用途与约束 |
| --- | --- | --- |
| Web 前端 | React 19.2.8、Vite 8.1.5、TypeScript 7.0.2 | 单页应用；开发使用 Vite，发布后由 Fastify 同源提供静态文件 |
| 画布 | Fabric.js 7.4.0 | 桌面画布对象模型、变换、选区与客户端导出；业务状态使用 Dada 自有 JSON Schema，不直接持久化 Fabric 内部对象 |
| 后端 | Node.js 24.13.0、Fastify 5.10.0 | REST、SSE、鉴权、文件访问、后台与外部服务调用 |
| 契约 | TypeBox 0.34.52、OpenAPI 3.1、`@fastify/swagger` 9.8.1 | 请求、响应、事件和错误信封的单一 JSON Schema 来源；生成前端类型客户端 |
| 数据库 | SQLite WAL | 单机业务、队列、流水、审计和测量状态；不引入远程数据库 |
| 数据访问 | Drizzle ORM 0.45.2、better-sqlite3 13.0.1 | 迁移、类型化查询和同步事务；金融与任务状态使用 `BEGIN IMMEDIATE` |
| 异步执行 | 独立 Node Generation Worker | 从 `GenerationJob` 持久队列领取任务，执行外部调用、物理清理、保留期清理和测量作业 |
| 五色提取 | `@vibrant/core` 4.0.4、`@vibrant/quantizer-mmcq` 4.0.4 | 浏览器本地确定性 MMCQ；不上传底图，不使用随机采样 |
| Windows Supervisor | .NET 8 WinForms 托盘程序 | 进程监督、单实例、浏览器启动、离线配置和优雅退出；不承载产品页面 |
| 包管理 | pnpm 10.28.2 | monorepo 与唯一锁文件；CI 使用冻结锁文件安装 |
| 浏览器测试 | Playwright | 使用本机安装的当前稳定版 Chrome 和 Edge 通道执行 P0-A 验收 |
| 单元/集成测试 | Vitest | 纯函数、Schema、SQLite、服务和 Worker 测试 |

补丁版本以本文基线和仓库锁文件共同固定。升级任一会影响画布、数据库驱动、接口或运行时的依赖，必须通过依赖升级变更重新运行契约、视觉、性能和发布检查。

WP-0 开始时必须对本表精确版本执行冻结锁文件安装、TypeScript 检查、最小 React/Fabric 渲染、Fastify 启动、better-sqlite3 在随包 Node `win-x64` 运行时的 native 加载以及便携包启动预检。任一失败时停止受影响实现，先正式修订 DevelopmentPlan 和锁文件；开发人员不得在实现中自行替换版本、框架、驱动或运行时。

### 2.2 组件边界

```text
Dada.exe (.NET 8 Tray Supervisor)
  |-- runtime/node.exe -> Fastify API
  |                       |-- React SPA / REST / SSE
  |                       |-- SQLite / LocalDataRoot
  |                       |-- Resend / Amap HTTPS
  |-- runtime/node.exe -> Generation Worker
  |                       |-- SQLite durable queue
  |                       |-- AI Gateway HTTPS
  |                       |-- cleanup / retention / measurement
  |-- Chrome or Edge -> http://127.0.0.1:43121

Read-only asset roots --(resource ID + manifest)--> Fastify API
Asset compiler --(declarative metadata / derived files)--> Git metadata or LocalDataRoot
```

- **Supervisor**：只管理实例、配置、子进程、托盘状态和浏览器，不读取业务表，不持有用户会话。
- **Web**：只保存当前页面内存中的未提交/未保存编辑态；不直接访问文件系统或外部供应商。
- **API**：唯一业务写入口；执行鉴权、配额、状态版本、点数与任务创建事务；提供资源 ID 访问。
- **Worker**：唯一 AI 执行入口；不对浏览器开放端口；通过 SQLite 领取任务并完成结算或释放。
- **SQLite**：业务真相源和持久队列；不存二进制内容和真实密钥。
- **LocalDataRoot**：存数据库、日志和 Dada 管理的私有/派生文件；不包含规范素材全库。
- **素材编译器**：TypeScript CLI，只读解析规范归档，输出声明式目录、校验报告和必要派生文件；不执行 Lua/Prefab。
- **发布组件**：Gitea Actions 生成前端、后端 bundle、托盘程序、随包 Node 运行时、迁移、manifest 和 SHA-256。
- **浏览器支持门禁**：由 Fastify 的最小 gate shell、支持检查接口和产品 API 前置中间件共同执行；不依赖登录态，不把浏览器兼容判断下放给 React 产品路由。

### 2.3 不采用的基础设施

P0-A 不采用 Docker、Redis、远程任务队列、远程数据库、对象存储、公司服务器、外部日志平台、外部指标平台、自动更新服务或自动扩容。GenerationJob 表和 SQLite 租约足以支持当前单机且每账号单任务的容量。

## 3. 运行、环境与发布

### 3.1 便携包布局

```text
Dada-P0A-<app_version>-win-x64/
  Dada.exe
  runtime/node.exe
  server/api.mjs
  server/worker.mjs
  server/native/better_sqlite3.node
  web/assets/...
  migrations/...
  asset-metadata/...
  LICENSES/...
  RELEASE.json
  START-HERE.txt
Dada-P0A-<app_version>-win-x64.zip.sha256
```

ZIP 不包含真实密钥、管理员邮箱、LocalDataRoot、只读素材二进制全库或验收账号。`RELEASE.json` 固定记录 app/schema 版本、构建提交、Windows 验收 build，以及 Chrome/Edge 各自的受支持产品标识、稳定版 major、实际验收 full version 和记录时间；发布门禁只接受记录的 major，patch 更新仍属于同一受支持 major。`START-HERE.txt` 固定说明 SHA-256 核验、未签名状态、SmartScreen 首次运行提示和杀软误报时核对下载制品/Gitea 构建记录的流程；不指导关闭杀软、添加宽泛排除或跳过哈希验证。更新时先从托盘退出，再整体替换程序目录；数据库迁移在下一次启动时事务化执行。P0-A 不支持数据库降级，也不为迁移创建自动备份副本；迁移失败时停止启动并保持原事务未提交状态。

### 3.2 Supervisor 生命周期

1. 获取 Windows 命名互斥量 `Dada.P0A.Instance`。第二次启动通过命名管道通知现有实例打开网页，然后退出。
2. 读取 `%LOCALAPPDATA%\Dada\P0A\config\instance.json` 中的非敏感配置引用，检查 LocalDataRoot 不在程序、Git、项目或下载目录内。已初始化实例的根目录或数据库缺失时停止并报告 data_missing，不自动重建或从残留文件恢复；部署人员只能显式初始化一个新的空实例。
3. 检查 Windows 凭据管理器中必要条目的存在状态。启动子进程时，Supervisor 读取所需条目并通过仅对应子进程继承的匿名管道一次性注入内存；值不进入命令行、环境变量、文件或日志。API 只取得 Resend/高德凭据，Worker 只取得 AI 网关凭据。
4. 检查只读素材根目录、manifest 哈希、LocalDataRoot 可写性和 `127.0.0.1:43121` 端口；端口占用时停止，不动态换端口。
5. 启动 API。API 打开 SQLite、执行事务迁移、完成容量对账并暴露仅回环可访问的健康端点。
6. API ready 后启动 Worker；Worker 注册租约拥有者并执行过期租约恢复。
7. 两个子进程均健康后，Supervisor 读取 `RELEASE.json` 并探测已安装浏览器版本：优先启动受支持 Chrome，其次为受支持 Edge；只有不支持版本时仍可打开 localhost 的阻断引导页，不进入产品。托盘菜单固定提供打开 Dada、用 Chrome 打开、用 Edge 打开、运行诊断、重试受影响组件和退出；选择不支持或无法识别的浏览器只得到阻断引导。
8. 子进程异常退出时按 1 秒、5 秒、15 秒最多自动重启三次；五分钟内仍失败则进入 degraded，不继续循环重启。API degraded 时网页不可用；Worker degraded 时 API 禁止新任务但保留查看、下载、编辑和删除能力。
9. 托盘退出通过受 Supervisor 控制的命名管道要求 Worker 停止领取任务并让 API 停止接收写请求；等待最多 15 秒后终止残留子进程。运行中的任务按第 8.4 节恢复。

### 3.3 离线配置命令

同一个 `Dada.exe` 提供以下部署人员命令；这些命令不是产品后台能力：

- `Dada.exe configure init`：创建非敏感实例配置和默认 LocalDataRoot；
- `Dada.exe configure data-root`：校验并设置 LocalDataRoot；
- `Dada.exe configure asset-root`：按资源类别设置只读根目录引用；
- `Dada.exe secrets set|status|clear`：通过隐藏输入维护 Windows 凭据管理器条目；`status` 只返回已配置/未配置；
- `Dada.exe admin-allowlist add|remove|status`：维护管理员白名单摘要；`status` 只显示数量与配置版本；
- `Dada.exe doctor`：检查端口、目录、manifest、SQLite、浏览器和外部依赖配置状态，不输出密钥或白名单值。

所有修改配置的离线命令必须取得同一个实例互斥量；Dada 正在运行时拒绝修改，要求先从托盘退出。命令先完成语法、路径和必需凭据引用验证，再使用同目录临时文件和原子改名写入非敏感配置；每次成功写入将 `secure_config_revision` 递增 1。离线命令不连接产品数据库，也不直接写产品审计。`configure data-root` 不移动、复制或导入旧数据，只能在首次初始化时设置，或明确建立一个空的新实例；原目录仍由部署人员自行保留或处理，Dada 不提供恢复入口。

管理员白名单使用凭据管理器中的 pepper 生成规范化邮箱 HMAC，配置文件只保存 HMAC 集合。白名单邮箱完成验证码验证后，产品数据库才保存其管理员账号邮箱。API 启动时将配置的 `secure_config_revision` 与数据库 `secure_config_apply_state.applied_revision` 比较：

1. 修订未变时不重复应用或记录审计。
2. 新修订通过完整性、pepper 可用性、HMAC 格式和身份冲突检查后，在单一 `BEGIN IMMEDIATE` 事务中应用管理员访问变化、撤销被移除或失效管理员的全部会话、写入 `actor_type=system`、`actor_ref=backend_secure_config`、`result=succeeded` 的非敏感 AdminOperationLog，并推进 applied revision。事务提交后新配置才生效。
3. 验证或应用失败时回滚所有业务变化且不推进 revision；数据库仍可写时，另起一个事务记录不含邮箱、HMAC、pepper 或配置值的 `result=failed` 审计。API 不进入 ready，由托盘报告安全配置待修正。
4. 成功或失败审计无法按上述规则持久化时，配置不生效且 API 启动失败。离线命令执行者身份永不进入产品审计。

### 3.4 环境关系

| 环境 | 数据根 | 外部服务 | 浏览器 | 用途 |
| --- | --- | --- | --- | --- |
| 本地开发 | `%LOCALAPPDATA%\Dada\P0A-dev\<developer_instance>` | 默认模拟；显式契约验证命令才允许真实调用 | 开发服务器加本机 Chrome/Edge | 日常开发；允许开发初始化脚本预置普通测试账号 |
| CI | `%RUNNER_TEMP%\dada-ci\<run_id>` | 全部模拟；只有脱敏契约 fixture | Gitea Runner 上的 Chrome/Edge | 单元、集成、浏览器、视觉和打包；禁止读取真实 LocalDataRoot、凭据和账号 |
| P0-A 验收 | `%LOCALAPPDATA%\Dada\P0A\data` 或离线配置的合规目录 | 真实 AI 网关、Resend、高德 | 当前稳定版 Chrome 和 Edge | 由项目方控制的 1 个 super_admin 和 1 个普通账号人工验收 |
| P0-A 发布运行 | 与验收实例相同 | 与验收实例相同 | 与验收实例相同 | P0-A 没有独立远程生产环境 |

CI 的真实素材发布检查可只读访问规范素材根目录，但使用独立输出目录，不能读取真实 LocalDataRoot，也不能复制全量素材。日常 CI 使用小型合成 fixture，不扫描 3.57 GB 归档。

CI 的单元/API/Worker 测试使用系统分配的临时回环端口；只有 P0-A 打包边界测试使用 43121。Gitea 对浏览器和打包 job 设置并发数 1，运行前确认真实 Dada 实例未启动，绝不停止或复用真实实例。Chrome/Edge 均使用 `%RUNNER_TEMP%` 下的一次性 user-data-dir，测试结束删除，不读取项目方日常浏览器 profile。

### 3.5 配置分层

- **编译配置**：应用版本、Schema 版本、固定端口、5 GB/150 MB 固定上限、固定错误注册表和发布浏览器支持记录，进入 Git/发布制品且后台不可修改。
- **非敏感运行配置**：`secure_config_revision`、LocalDataRoot 引用、素材根引用、浏览器启动偏好（不含支持品牌/版本）和日志级别，保存在 ACL 限制为当前 Windows 用户的 `instance.json`；路径不返回前端。
- **安全配置**：AI 网关、Resend、高德密钥和管理员白名单 pepper，保存在 Windows 凭据管理器。
- **业务配置**：ModelConfig、邀请码、点数、模板状态、用量和服务状态，保存在 SQLite 并审计。

### 3.6 浏览器支持硬门禁

Fastify 为所有访问先提供独立、最小的 support gate，不直接提供 React 产品 shell。门禁流程固定如下：

1. `GET /`、直接访问 `/app/*` 或 `/admin/*` 时只返回 gate HTML、其独立 CSS/JS 和 `RELEASE.json` 中的非敏感支持摘要；响应声明 `Accept-CH: Sec-CH-UA, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Platform`。阻断页不加载产品 bundle、公开素材 manifest、登录组件或 Service Worker。
2. gate 使用 `navigator.userAgentData.getHighEntropyValues(["fullVersionList", "platform"])` 取得 Chromium User-Agent Client Hints，并与请求头中的 `Sec-CH-UA`、`Sec-CH-UA-Full-Version-List`、`Sec-CH-UA-Platform` 交给 `POST /api/v1/support/check` 交叉校验。只接受 platform 为 Windows，且品牌明确为 `Google Chrome` 或 `Microsoft Edge`，major 与 `RELEASE.json` 对应记录相同；不使用 UA 字符串猜测 Chromium 品牌，UA-CH 缺失、冲突、解析失败或无法排除其他浏览器时统一按不支持处理。
3. 通过后 API 设置签名 HttpOnly、SameSite=Strict、Path=/ 的 `dada_browser_support` Cookie，内容只含 app_version、品牌、major 和签发时间，不含指纹或用户身份。Cookie 最长 24 小时、API 重启后失效；产品 API 中间件每次仍比对当前 `Sec-CH-UA` 的品牌/major，浏览器升级、标识缺失或 app_version 变化时必须重新检查。
4. 除 support gate 静态文件、`POST /api/v1/support/check` 和 Supervisor 健康探针外，全部产品 HTML、静态 bundle 和 `/api/v1` 路由都要求门禁通过。失败统一返回 HTTP `426`、`code=BROWSER_UNSUPPORTED`、`message_key=browser.unsupported`，`details.reason` 只允许 `platform_unsupported`、`brand_unsupported`、`version_unsupported` 或 `identity_unavailable`，并返回受支持品牌/major；不得返回继续令牌或绕过参数。
5. 发布时 WP-7 先从 Chrome/Edge 官方稳定通道安装结果和浏览器可执行文件版本生成候选记录，再在该精确 full version 完成 AC-24/视觉/性能验收后写入 `RELEASE.json`。运行时不联网查询“当前版本”，不接受验收时前一 major，也不允许后台修改支持记录。

### 3.7 辅助系统界面契约

辅助系统界面只呈现本机进程与诊断，不新增产品角色或部署人员 CLI。稳定状态及动作如下：

| 状态 | 产生者与判定 | 用户允许动作 | 产品功能影响 |
| --- | --- | --- | --- |
| `starting` / `ready` | Supervisor 汇总预检、API ready 和 Worker heartbeat | 打开 Dada、选择受支持浏览器、运行诊断、退出 | starting 时产品 API 不开放；ready 时按业务状态开放 |
| `startup_failed` | Supervisor 的配置、凭据存在性、目录、迁移或子进程启动检查失败 | 查看诊断结果、修正外部条件后重试启动、退出 | 全部产品功能阻断 |
| `port_in_use` | Supervisor 无法独占绑定 `127.0.0.1:43121` | 运行诊断、释放端口后重试、退出 | API/Worker 均不启动；不动态换端口 |
| `api_degraded` | API 连续健康失败且有限自动重启耗尽 | 重试 API、运行诊断、退出 | 网页和全部产品 API 阻断；Worker 停止领取新任务 |
| `worker_degraded` | Worker heartbeat 超时或有限自动重启耗尽 | 打开 Dada、重试 Worker、运行诊断、退出 | 禁止新 GenerationJob；查看、编辑、纯 JSON 保存、下载和允许的删除继续 |
| `diagnostics_running` / `diagnostics_ready` | Supervisor 调用本地只读探针并汇总 API/Worker 非敏感结果 | 重新运行、复制脱敏摘要、返回托盘 | 诊断本身不恢复、不停用业务能力，也不修改配置 |

托盘和诊断结果只可读取 `app_version`、`supervisor_state`、`component`、稳定 `check_code`、`result=pass|warning|fail`、`message_key`、`checked_at`、固定端口与 `bind_scope=loopback`、浏览器支持版本记录、目录 `configured/readable/writable` 布尔值、manifest 哈希是否有效、SQLite/migration 状态、API/Worker 状态与 heartbeat age、LocalBackendStorageState 数值摘要，以及外部凭据是否已配置和服务状态。不得返回密钥、白名单值、邮箱、提示词、私有正文、图片、绝对路径、供应商原始错误或崩溃堆栈。UIDesign.md 负责托盘、启动失败、端口占用、degraded 和诊断结果的视觉呈现，但不得增加绕过、动态换端口、自动付费、打开私有目录或新的 CLI 操作。

## 4. LocalDataRoot 与文件访问

### 4.1 固定目录布局

默认根目录为 `%LOCALAPPDATA%\Dada\P0A\data`：

```text
data/
  db/dada.sqlite3
  db/dada.sqlite3-wal
  db/dada.sqlite3-shm
  content/references/<owner_ref>/<asset_id>
  content/generated/<owner_ref>/<asset_id>
  content/exports/<owner_ref>/<project_id>/latest.jpg
  content/exports/<owner_ref>/<project_id>/latest.png
  managed-assets/<release_version>/original/<content_hash>
  managed-assets/<release_version>/thumbnail/<content_hash>
  derived-assets/<release_version>/<content_hash>
  staging/<operation_id>/...
  logs/api/*.jsonl
  logs/worker/*.jsonl
  logs/supervisor/*.jsonl
```

数据库、WAL、审计记录和日志位于 LocalDataRoot，但不计入 `managed_content_bytes`。`content`、`managed-assets` 和 `derived-assets` 中由 Dada 管理的二进制文件计入，其中 `managed-assets` 明确包含后台上传普通贴纸原图和 Dada 生成的缩略图。既有只读素材根、程序文件、浏览器公开缓存和用户下载目录不计入。

目录的写入边界固定如下：

- 约 3.57 GB 规范素材只存在已配置的只读源根目录，不进入 Git、`managed-assets`、`derived-assets` 或 LocalDataRoot 其他目录。
- Git 只保存版本化 manifest、catalog、metadata、Schema、版本号、哈希和文本校验报告，不保存规范素材二进制或其等价副本。
- `managed-assets` 只保存 super_admin 在产品上线后上传的 PNG/WebP 普通贴纸原图及 Dada 生成的必要缩略图；两类文件分别登记 `file_kind=sticker_original|sticker_thumbnail`、实际字节和哈希，均计入 5 GB，不接收现有归档素材的复制。
- `derived-assets` 只保存浏览器和本地后端无法从规范源直接安全服务的最小运行产物；以内容 SHA-256 寻址并跨版本复用，禁止预转换全库、批量等价复制或为便利建立第二套素材库。

### 4.2 文件标识与路径隔离

- 所有私有文件使用随机 UUIDv4 `asset_id`；数据库只保存 `storage_class`、相对对象键、大小、MIME、SHA-256、所有者和状态。
- API 响应只包含资源 ID 与受控 API URL，不包含绝对路径、素材根引用或可推导的对象键。
- API 使用 `Path.GetFullPath` 等价的规范化检查确保最终路径仍在已配置根目录内，拒绝 `..`、符号链接逃逸和不在 manifest 中的资源。
- 浏览器不得使用 `file://`、绝对路径或 File System Access API 读取 LocalDataRoot 或素材根；用户从标准文件选择器选择待提交参考图不受影响。
- `public_release_asset` 使用公开版本化路由；`internal_preview_asset` 和 `private_user_asset` 使用不同路由、缓存头和鉴权中间件，不能共享静态目录。

### 4.3 文件与数据库一致性

所有二进制写入采用“预检 -> staging -> 内容校验 -> 原子改名 -> SQLite 提交 -> 异步补偿”的固定流程：

1. 根据请求声明大小、当前实际字节数和存储预留计算预计写入。准入比较固定为 `managed_content_bytes + active_storage_reservations + projected_write_bytes > hard_limit_bytes` 时拒绝；预计写入后恰好等于硬上限时允许建立预留并继续，不能使用 `>=` 作写前拒绝。
2. 文件流写入同一卷的 `staging/<operation_id>`，同时计算 SHA-256、实际大小和 MIME；禁止把整个文件读入内存。
3. 校验成功后原子改名到内容寻址目标；SQLite 事务登记 `managed_file`、资源关系和实际字节增量。提交后总量恰好等于硬上限时，本次写入成功并在同一事务把 `storage_status` 置为 full。
4. SQLite 事务失败时，将已改名但未引用的文件加入补偿清理；进程崩溃后由启动对账识别孤儿。
5. 数据库先进入 `purged` 或删除身份关系后，文件立即无法通过 API 读取；物理删除由清理队列完成，完成后再减少 `managed_content_bytes`。

成功生成输出写入前使用 ModelConfig 中经契约验证的最大响应字节建立 `storage_reservation`。未验证模型必须保持 `available_for_new_jobs=false`，但不因此改写 configured enabled/default/priority。导出持久化失败或容量 full 时，浏览器仍可下载同一次客户端合成结果，但不更新 `latest_exports`。

`PUT /projects/{id}/state` 是纯 JSON 事务，不调用上述二进制流程。服务端对其中每个资源引用校验已提交、未撤销、属于当前用户且对当前项目可访问；该路由不得隐式上传、复制、替换或派生任何文件。需要新二进制的参考图、素材、派生物和 latest export 必须使用独立路由和容量预检。

### 4.4 LocalBackendStorageState 实现

- 固定 `hard_limit_bytes = 5,368,709,120`。
- `normal`：小于 `4,294,967,296`；`warning`：达到该值且小于 `4,831,838,208`；`critical`：达到该值且小于硬上限。
- API 每次受管文件提交或物理删除均在同一数据库事务更新计数；启动时在开放写能力前做全量文件对账，运行中每 60 秒执行可写性和磁盘余量探测。
- 已提交 `managed_content_bytes >= hard_limit_bytes` 或已承诺的 `managed_content_bytes + active_storage_reservations >= hard_limit_bytes` 时，持久状态为 full 并拒绝新的受管二进制写入。单次写入准入仅在预计总量 `> hard_limit_bytes` 时拒绝并返回容量错误；预计总量 `= hard_limit_bytes` 时允许本次提交，提交后进入 full。因单个过大请求被拒绝但实际量与现有预留仍低于上限时，不伪造持久 full，较小且可容纳的后续请求仍可重新预检。
- 数据库目录、日志目录、LocalDataRoot 不可写，或系统磁盘无可用空间时设为 `unavailable`。API 和 Worker 在该状态停止新增写入与 AI 调用。
- warning/critical 只产生非阻断状态事件，不删除文件。critical 的引导顺序固定为下载成品、永久删除项目或由 super_admin 显式清理符合条件的无引用历史贴纸文件、等待物理清理与重测、恢复后继续生成。
- full 仍允许已有项目编辑和上述纯 JSON 项目状态保存、查看、下载、客户端仅下载导出、永久删除，以及显式提交符合条件的历史贴纸清理；它阻止新 AI 任务、参考图持久化、后台贴纸原图上传、缩略图生成、其他受管/派生二进制新增和 latest export 保存。unavailable 拒绝包括项目 JSON、贴纸上传和缩略图在内的所有后端新增写入，但保留查看、下载、客户端仅下载导出和允许的删除入口；未保存编辑可以留在页面内存但不能显示为已保存。若 SQLite 仍可写，永久删除或显式贴纸清理可先提交逻辑删除/队列；SQLite 自身不可写时明确报告操作未提交并允许恢复后重试。
- 物理删除完成、全量重测低于硬上限且目录可写后，状态才恢复 active。

自动清理只包括 PRD 已要求的 720 小时项目清理、账号注销、180 天保留期和 24 小时以上无引用 staging；不得因容量告警删除仍由用户拥有的内容，也不得自动清理任何后台上传普通贴纸历史原图或缩略图。贴纸历史文件在物理删除完成前始终计入 `managed_content_bytes`。

### 4.5 日志与数据库维护

- API、Worker、Supervisor 使用 JSONL 结构化日志，每文件 10 MiB，每进程最多保留 10 个轮转文件，并删除超过 30 天的诊断日志。
- 日志只包含 correlation ID、内部非敏感对象 ID、状态类别、耗时和错误类别；禁止提示词、邮箱正文、验证码、密钥、会话令牌、图片、绝对路径和供应商原始错误正文。
- Worker 每日执行 180 天保留期清理；每周在无运行任务时执行 WAL checkpoint 和 incremental vacuum。
- CreditLedger、AdminOperationLog 与 PrivateContentAccessLog 通过 SQLite trigger 禁止普通 UPDATE/DELETE。只有 Worker 的保留期清理连接可在注册的 `dada_allow_retention_purge()` 作用域内删除到期审计；账号注销只能在 `dada_allow_privacy_purge()` 作用域中把原点数事件转成 AnonymousRetainedEvent 后删除身份关联流水。
- 迁移、checkpoint、日志写入或审计写入失败均使 `storage_status` 进入 unavailable，并阻止后续 AI 调用。

## 5. 工程数据模型

### 5.1 SQLite 约束

数据库启动固定执行：`journal_mode=WAL`、`foreign_keys=ON`、`synchronous=FULL`、`busy_timeout=5000`。涉及任务、点数、名额、邀请码、配置版本和审计的写事务使用 `BEGIN IMMEDIATE`，避免 API 与 Worker 各自读后写造成竞态。

所有时间以 UTC 毫秒保存，对外使用 ISO 8601；用户界面再按本机时区显示。私有对象使用 UUIDv4。点数使用整数。枚举由数据库 CHECK、TypeBox Schema 和前端生成类型三层一致约束。

### 5.2 十九个产品数据契约映射

| 产品契约 | 工程映射 | 关键约束 |
| --- | --- | --- |
| 17.1 UserProfile | `users`、`user_profiles`、`credit_accounts`、`privacy_consents` | 规范化邮箱唯一；role 仅 user/super_admin；名额由 role、status、counts flag 计算；super_admin 的私有内容告知版本/时间由后端持久化；删除后移除身份字段 |
| 17.2 ModelConfig | `model_config_sets`、`model_config_versions`、`model_config_current`、`model_runtime_availability`、`gateway_route_profiles`、`error_mapping_profiles` | 配置集合不可变并原子切换；每集合恰好一个 enabled default；推荐优先级为集合内唯一正整数；运行时可用性独立且不回写配置；契约字段变化强制 unverified |
| 17.3 Project | `projects`、`project_states`、`latest_exports` | `state_version` 单调递增；比例/尺寸创建后不变；active 上限在事务内检查；purge_at 精确为 720 小时 |
| 17.4 GenerationJob | `generation_jobs`、`generation_job_references`、`job_leases` | 每账号 queued/running 部分唯一索引；配置、成本和参考图关系均快照；终态不可逆；上游不确定性只作内部诊断 |
| 17.5 CanvasElement | `project_states.canvas_json` 中经版本化 JSON Schema 校验的元素数组 | 最多 50；不持久化 Fabric 私有字段；动态值、色卡五色和 DYN004 坐标按产品字段快照 |
| 17.6 TemplateRegistry | `template_registry`、`template_versions`、`test_batches`、`template_batch_members` | 稳定 ID + resource_version 唯一；状态转换受服务校验；full_p0 不能逐批公开 |
| 17.7 StaticStickerCatalog | `static_sticker_catalog`、`static_sticker_versions`、`project_asset_refs`、`managed_files` | part1-25 和顺序稳定；记录 bundled/admin 来源及原图/缩略图文件关系；重复图片保留不同 ID；已发布版本与 active/trashed 项目引用阻止历史文件清理 |
| 17.8 DynamicStickerData | 固定 `dynamic_provider_registry` 代码注册表和 CanvasElement 实例快照 | 只提供模板声明的字段；DYN004 坐标仅在同意后写入；不执行 Lua |
| 17.9 CreditAccount 与 CreditLedger | `credit_accounts`、append-only `credit_ledger` | 余额 CHECK 和事务锁；reserve/commit/release 操作键唯一；管理员调点原因必填 |
| 17.10 PrivateContentAccessLog | append-only `private_content_access_logs` | 打开前先写成功审计；180 天；注销时目标改为不可反推随机标识 |
| 17.11 AnonymousRetainedEvent | `anonymous_retained_events` | 注销事务生成随机主体且不保存映射；无邮箱、IP、正文、资源或原 ID；最迟注销后 180 天删除 |
| 17.12 AssetReleaseManifest | Git 中的 immutable manifest + `asset_releases`、`asset_release_items` | release_version、ID、相对路径、大小、哈希和 access_class 固定；公开导出只含 public |
| 17.13 AssetPreviewGrant | `asset_preview_grants` | active/revoked/expired；每次 manifest 和资源读取重新校验；不改变用户角色 |
| 17.14 ClientCachePolicy | 前端固定配置 + 只含公开资源元数据的 IndexedDB `public_asset_lru` + Cache Storage | 157,286,400 字节 LRU；不存内部预览、私有内容、项目、验证码或会话 |
| 17.15 ExternalServiceUsage | `external_service_usage` | Resend 日/月和高德月度独立行；配额领取事务化；只含四个固定状态；恢复审计 |
| 17.16 AdminOperationLog | append-only `admin_operation_logs` | actor、结果、非敏感前后摘要；180 天；配置生效使用 system/backend_secure_config；贴纸清理请求、引用复核、拒绝/排队和物理结果均留痕 |
| 17.17 GatewayBalanceState | `gateway_balance_states`、`gateway_balance_affected_models` | model/account/unknown 影响范围；真实确认后才能恢复；不保存密钥或原始供应商正文 |
| 17.18 GenerationErrorCategory | 代码和迁移共同种入的九项固定只读注册表 `generation_error_categories` | 后台不可增删改；mapping profile 只能引用固定 ID；稳定 message_key |
| 17.19 LocalBackendStorageState | 单实例 `local_backend_storage_state`、`managed_files`、`storage_reservations` | 名称固定；原图/缩略图计量；预计量大于上限才拒绝、等于上限允许后进入 full；物理删除后重测；不与浏览器 localStorage 混淆 |

### 5.3 支撑表

以下表只支撑冻结产品语义，不形成新产品对象：

- `invite_codes`、`email_challenges`、`sessions`、`auth_rate_limits`：邀请码、10 分钟一次性验证码、60 秒重发限制、30 天会话；验证码只保存带 pepper 的摘要。
- `idempotency_records`：路由、主体、键、请求哈希和响应引用。
- `managed_files`、`file_cleanup_queue`、`storage_reservations`：文件一致性、清理和容量预留；贴纸原图/缩略图分别记录 file_kind，排队删除不提前扣减容量。
- `project_asset_refs`、`asset_cleanup_requests`、`asset_cleanup_request_items`：从每次已提交项目状态提取资源版本引用，并保存管理员显式贴纸清理的候选快照、确认、复核与物理结果；只属工程支撑，不新增产品对象。
- `model_config_sets`、`model_runtime_availability`：前者把三个模型的不可变配置版本作为一个可原子激活的集合，后者只记录逐模型 `available_for_new_jobs`、非敏感 reason、检测与恢复时间。
- `browser_support_release`：从 `RELEASE.json` 导入当前 app_version 的 Windows、Chrome/Edge 品牌、major 和已验收 full version，只供门禁与诊断读取，后台不可编辑。
- `job_leases`、`worker_heartbeats`：Worker 领取和崩溃恢复。
- `outbox_events`：数据库提交后向 SSE 和清理队列发布，不承担跨机器消息。
- `recent_assets`：账号级文字模板和贴纸最近使用，不存未保存项目状态。
- `service_recovery_checks`：辅助服务和网关恢复的非敏感确认结果。
- `secure_config_apply_state`：已生效安全配置修订号、非敏感摘要和应用时间；不保存白名单、HMAC、pepper 或凭据。
- `generation_jobs.upstream_outcome_known` 与 `upstream_cost_reconciliation`：只供后台诊断与本地监控的上游结果/成本核对标记，不是新的 GenerationJob 状态、点数状态或 GenerationErrorCategory。

v1.10 同步迁移固定完成四项变化：为 `user_profiles` 增加可空的 `private_content_notice_version` 与 `private_content_notice_acknowledged_at`；建立不可变 `model_config_sets`、集合约束和独立运行时可用表；为后台贴纸原图/缩略图补齐 `managed_files` 关系、项目资源引用和显式清理表；导入当前发布浏览器支持记录。迁移只向前执行，先回填/校验再切换 Schema 版本，任一校验失败整体回滚并阻止 API ready。

## 6. API 与事件契约

### 6.1 通用协议

- 生产 API 前缀固定为 `/api/v1`，JSON 使用 UTF-8；文件上传使用 multipart 流式处理。
- 生产只接受 Host `127.0.0.1:43121` 和同源 Origin；不开放 CORS。开发环境只允许显式 Vite origin。
- 浏览器支持门禁中间件位于路由解析后的第一层，先于会话、CSRF、角色和业务处理；除 support check 与 Supervisor 健康探针外，未通过门禁的请求不得触发数据库业务读取、验证码发送、资源读取或外部调用。
- OpenAPI 3.1 从 TypeBox Schema 生成；前端客户端由同一 Schema 生成，不能手写漂移类型。
- 每个响应包含 `X-Correlation-Id`；客户端可传入合法 UUID，否则 API 生成。
- 创建型和有金融/文件副作用的请求要求 `Idempotency-Key`。键为客户端随机 128 位以上值，不包含用户数据。
- 列表使用 opaque cursor；不得用连续数据库 ID 暴露数量或顺序。

统一错误信封：

```json
{
  "error": {
    "code": "STABLE_ENGINEERING_CODE",
    "message_key": "stable.ui.message.key",
    "correlation_id": "opaque-id",
    "error_category": "optional-generation-category",
    "details": {}
  }
}
```

`details` 只允许字段级校验、最新版本、容量状态或当前任务引用等白名单信息。禁止供应商原始错误、堆栈、内部 URL、绝对路径和凭据。

### 6.2 API 分组

| 分组 | 主要路由 | 语义 |
| --- | --- | --- |
| 浏览器支持 | `POST /support/check`；产品前缀外的 Supervisor health | 读取 UA-CH 与发布支持记录；仅返回支持/阻断状态和非敏感版本；不建立产品会话 |
| Bootstrap | `GET /bootstrap` | 仅门禁通过后返回应用版本、公开功能状态、模型配置/运行时摘要和非敏感依赖状态 |
| 普通认证 | `/auth/register/*`、`/auth/login/*`、`/auth/session`、`/auth/logout` | 注册/登录分离；发码前校验状态；验证码成功后创建会话 |
| 管理员认证 | `/admin-auth/login/*`、`/admin-auth/session` | 发码前校验 HMAC 白名单；首次验证创建 super_admin；session 返回当前私有内容告知版本、已确认版本和 `notice_acknowledged` |
| 账号 | `/me/profile`、`/me/credits`、`/me/credit-ledger`、`/me/delete` | 资料、点数、注销；注销为不可逆事务 |
| 模型 | `GET /models`、`GET /models/{model_id}`、`PUT /admin/models/configuration` | 读取同时返回 `configured_default_model_id`、逐模型配置/运行时状态和 `recommended_model_id`；后台以完整配置集合原子保存 |
| 生成 | `POST /generations`、`GET /generations/{id}`、`GET /generations/current` | multipart 提交；原子冻结；已有活动任务返回现有任务而非创建第二个 |
| 项目 | `/projects`、`/projects/{id}`、`/projects/{id}/state`、`/projects/{id}/trash|restore|purge` | 所有者鉴权；自动保存使用 If-Match state_version；回收站与清理 |
| 历史与成品 | `/projects/{id}/images`、`/projects/{id}/exports` | 原图下载、历史删除、latest JPG/PNG；资源 ID，不暴露路径 |
| 素材 | `/assets/public/*`、`/assets/preview/*`、`/private-assets/*` | 三类资源使用独立授权和缓存策略 |
| 管理员贴纸清理 | `GET /admin/assets/history-cleanup-candidates`、`POST /admin/assets/history-cleanup-intents`、`POST /admin/assets/history-cleanup-intents/{id}/confirm` | 只返回无引用候选；确认时事务复核全部引用并整体拒绝或排队；物理结果异步回读 |
| 私有内容告知与访问 | `POST /admin/private-content-notice/ack`、`/admin/private-content/*` | ack 按当前 super_admin 和版本持久化；未确认禁止进入区域；每次正文/图片打开仍先写独立访问审计 |
| 管理后台 | `/admin/users`、`/admin/invites`、`/admin/models`、`/admin/assets`、`/admin/preview-grants`、`/admin/services`、`/admin/audit` | 全部要求 active super_admin；敏感修改写 AdminOperationLog |
| 状态事件 | `GET /events` | 同源鉴权 SSE；任务、保存、存储和服务状态；事件不携带私有正文 |

SSE 只作为状态变化提示，事件体固定为 event_id、event_type、entity_ref、state/config version 和 occurred_at。客户端收到后通过 REST 读取真相；断线或 event_id 不连续时执行一次 bootstrap/refetch，不依赖 SSE 保存业务状态或私有正文。模型事件只提示 config set version 或 runtime availability version 变化，客户端重新读取 `/models`，不得把运行时不可用写回配置表。

`GET /models` 顶层固定返回 `configured_default_model_id`、`recommended_model_id`（无运行时可用模型时为 null）和当前 `config_set_version`。每个模型固定返回 `enabled`、`is_default`、`recommendation_priority`、`config_version`、成本/限制、`contract_validation_status`，以及独立的 `runtime_availability={available_for_new_jobs, reason, checked_at}`。`recommended_model_id` 只从 enabled、contract verified、Worker/网关可用于新任务的模型中按 recommendation_priority 数值升序派生；它不形成第二个默认状态，也不更新任何 ModelConfig 字段。

`POST /generations` 的业务字段固定为 prompt、model_id、ratio、model_config_version、confirmed_credit_cost、创建模式（new_project 或 existing_project）以及参考图项；不接受负面提示词、种子、采样器、批量数量或模型专属高级参数。参考图项只能是本次 multipart 新文件，或同一所有者且同一项目中已有任务的 `existing_reference_asset_id`；复用时仍创建当前 GenerationJob 的关系快照，不能修改旧任务关系。

### 6.3 鉴权与会话

- 会话令牌为 256 位随机 opaque 值；数据库只保存 SHA-256 摘要、用户、创建、到期和撤销时间。
- Cookie 固定为 HttpOnly、SameSite=Strict、Path=/，最长 30 天。因 P0-A 使用回环 HTTP，不把 Secure Cookie 当作安全前提。
- 每次页面加载通过会话接口取得一次性 CSRF synchronizer token，仅保存在页面内存；所有写请求同时校验 CSRF、Origin、Host 和会话。
- 用户暂停、注销、管理员停用或移出白名单时，在同一事务撤销全部会话；API 每次请求重新检查账号状态和管理员白名单配置版本。
- 管理员和普通用户使用分离入口与会话 audience；普通账号不能因邮箱加入白名单原地提升。
- `user_profiles.private_content_notice_version` 与确认时间是后端真相；Cookie、session、localStorage、IndexedDB 或页面内存只能缓存本次显示状态，不能作为确认依据。管理员 session/bootstrap 每次从当前后端告知版本和用户记录计算 `notice_acknowledged`。

### 6.4 HTTP 语义

- `400`：字段或确定性业务输入无效；`401`：无会话；`403`：角色、所有者或预览授权不足；`404`：不存在或对当前主体不可见。
- `409`：幂等键请求哈希冲突、活动项目上限、历史上限或不可执行状态；已有活动 GenerationJob 返回 `200` 和现有任务引用。
- `412`：`state_version` 或 `model_config_version` 过期；响应带最新安全版本摘要，客户端必须重新确认。
- `413`：参考图或后台贴纸上传超过已验证限制。
- `426`：浏览器品牌、平台或发布 major 不受支持，或无法可靠识别；只返回阻断引导所需字段。
- `428`：active super_admin 尚未确认当前私有内容告知版本；不返回私有内容区域数据。
- `429`：验证码频率限制；配额硬停止使用服务状态响应而不是伪造发送成功。
- `503`：外部服务 paused、网关契约未验证、Worker degraded；生成类响应附固定 error_category（适用时）。
- `507`：当路由要求新增受管内容且 LocalBackendStorageState 为 full/unavailable，或本次预计写入后将超过硬上限时返回；响应带非敏感容量状态和 remaining bytes。预计总量恰好等于硬上限不得以 507 拒绝。full 不影响已有项目状态保存，unavailable 时才拒绝该保存；查看、下载、客户端仅下载导出和删除按 4.4 节处理。

### 6.5 版本用途

- `config_version`：ModelConfig 每次影响启停、默认、recommendation_priority、成本、比例、参考图、提示词、路由或错误映射时生成新不可变版本；三个当前版本由一个 `config_set_version` 原子激活。生成提交必须同时带 `model_config_version` 和 `confirmed_credit_cost`。
- `state_version`：每次项目保存成功递增。保存请求使用 `If-Match`；不匹配进入 conflicted，只读且不能另存副本。
- `release_version`：每次素材发布生成不可变版本；项目元素保存稳定 ID 和 resource_version，历史渲染不跟随新版本漂移。
- `schema_version`：数据库迁移版本；应用启动只允许向前执行已打包迁移。
- `app_version`：发布包版本；写入 RELEASE.json、日志和诊断，但不改变产品数据语义。

### 6.6 新增稳定错误码

| code | HTTP | 触发与固定动作 |
| --- | ---: | --- |
| `BROWSER_UNSUPPORTED` | 426 | 品牌/版本/平台不支持或身份不可可靠识别；只显示无绕过引导 |
| `MODEL_CONFIG_VERSION_CONFLICT` | 412 | 后台提交的 expected config_set_version 已过期；返回最新非敏感版本摘要并要求重载 |
| `MODEL_DEFAULT_REPLACEMENT_REQUIRED` | 409 | 试图停用当前默认模型但未在同一请求指定另一个 enabled 默认；整体不写入 |
| `MODEL_DEFAULT_REPLACEMENT_INVALID` | 409 | 替代默认不存在、未启用或产生非唯一默认；整体不写入 |
| `MODEL_RECOMMENDATION_PRIORITY_INVALID` | 400 | 缺失、非整数或非正数；返回字段位置，整体不写入 |
| `MODEL_RECOMMENDATION_PRIORITY_CONFLICT` | 409 | 同一候选配置集合内优先级重复；返回冲突模型 ID，不部分写入 |
| `PRIVATE_CONTENT_NOTICE_ACK_REQUIRED` | 428 | 当前 super_admin 未确认当前告知版本；仅返回 current notice version/message_key |
| `ASSET_HISTORY_REFERENCE_CONFLICT` | 409 | 清理确认时任一文件仍被已发布版本、active/trashed 项目或其他有效关系引用；整批不排队 |
| `ASSET_CLEANUP_CANDIDATE_STALE` | 409 | 候选快照/确认令牌过期或文件状态已变化；重新查询候选 |
| `STORAGE_CAPACITY_EXCEEDED` | 507 | 当前已 full/unavailable 或预计写入后大于硬上限；不得创建副作用，等于上限不触发 |

上述错误均使用统一错误信封和稳定 message_key，不新增 GenerationErrorCategory。模型因余额、契约或 Worker 状态不可用于新任务时，生成提交继续使用九类错误中的既有分类；模型读取接口只报告独立运行时原因。

## 7. 幂等、并发与事务

### 7.1 幂等规则

- 相同主体、路由和 `Idempotency-Key` 且请求哈希一致时返回第一次结果；哈希不一致返回 409。
- GenerationJob 的 `client_submission_id`、点数流水 `operation_key`、管理员调点 `adjustment_id`、邀请码核销 `registration_id` 和 export `export_id` 具有永久唯一约束。
- 普通 API 幂等响应缓存保留 24 小时；涉及任务、点数、邀请核销和管理员操作的实体唯一键随实体/审计保留，不依赖 24 小时缓存。
- Worker 结算使用 `generation_id + final_credit_state` 唯一约束，重复回调、轮询或进程恢复不能再次 commit/release。

### 7.2 单任务约束

`generation_jobs` 保存 `owner_id`，并建立 SQLite 部分唯一索引：仅当 status 为 queued 或 running 时，owner_id 唯一。API 在 `BEGIN IMMEDIATE` 事务中先查询已有任务，再尝试插入；即使并发请求同时到达，索引仍保证只存在一个活动任务。命中已有任务时返回该任务，不新增项目、参考图关系、冻结点或上游调用。

### 7.3 生成提交事务

1. 校验会话、账号、项目/历史上限、当前 config set、所选模型 enabled、contract verified、独立 runtime availability、配置版本、比例、提示词、参考图、余额、Worker 健康和存储状态；recommended_model_id 仅用于默认推荐，不替代对用户所选模型的校验。
2. 流式接收并校验参考图到 staging，建立预计容量预留。
3. 在一个 `BEGIN IMMEDIATE` 中再次读取所有可变条件；需要时创建草稿 Project；创建 GenerationJob 和参考图关系；把 available 转入 reserved；写 generation_reserve 流水和 outbox。
4. 提交后移动参考图到私有内容位置并登记 managed file。任一步失败则事务不创建任务、不冻结点；已暂存文件进入补偿清理。
5. Worker 只处理已完整提交的 queued 任务。

### 7.4 结算事务

- 成功：验证图片、写入受管文件后，在同一事务把任务改为 succeeded、写 output_asset、把 reserved 变为已扣点、追加 generation_commit、更新项目历史与 draft_state。
- 失败：在同一事务写 failed/rejected 和固定 error_category、把 reserved 返还 available、追加 generation_release；输出文件不得建立引用。
- 供应商返回成功但文件不能安全落盘时，不把任务标为 succeeded；使用 `unknown_retryable` 失败并只释放一次，后台保留非敏感诊断类别。
- 管理员调点只更新 available；原因必填；`reserved_balance` 不变；流水和 AdminOperationLog 与余额更新同事务提交。

### 7.5 项目保存并发

- 前端在一次已提交操作结束后 1 秒 debounce；同项目最多一个保存请求在途，新变化合并为下一份完整状态。
- API 使用 `UPDATE ... WHERE project_id=? AND owner_id=? AND state_version=?` 原子 compare-and-swap。
- 更新成功时完整 JSON Schema 校验、state_version +1、save_status=saved；失败返回 412，前端立即停止自动保存并进入只读 conflicted。
- conflicted 页面把 `conflict_export_used` 只保存在当前页面内存；第一次本地导出结束后立即禁用再次导出，且始终不调用 latest export API。
- 未保存状态、撤销栈、提示词和私有引用只在页面内存；刷新、关闭或崩溃后只恢复最后一次 saved 状态。

### 7.6 ModelConfig 原子配置与运行时推荐

`model_config_sets` 是工程事务容器，不新增产品对象。每次后台保存都生成一个候选 set，把三个模型各自新的或复用的 immutable config_version 作为 set 成员；Schema 与数据库固定执行：

- `recommendation_priority` 为 JSON/TypeBox integer，数据库 `CHECK (recommendation_priority > 0)`，并以 `UNIQUE(config_set_id, recommendation_priority)` 禁止并列；缺失、浮点、零、负数或重复均拒绝整个请求。
- `UNIQUE(config_set_id) WHERE enabled=1 AND is_default=1` 保证至多一个；`model_config_current` 指针更新 trigger 在切换前检查候选 set 中 enabled default 计数恰好为 1，否则中止事务。因此任何可读取的当前配置始终恰好有一个已启用默认模型。
- `PUT /admin/models/configuration` 必须提交完整三模型集合、`expected_config_set_version` 和 Idempotency-Key。API 在 `BEGIN IMMEDIATE` 中复核当前版本、模型 ID 固定集合、默认模型、优先级、字段约束和契约重验规则，写 immutable 版本、AdminOperationLog/outbox，最后单语句切换 current set 指针；契约相关字段变化时同一事务把对应 runtime availability 置 false/reason=contract_unverified。任一失败回滚全部写入。
- 当前默认由 enabled 变为 disabled 时，请求中的另一个模型必须同时为 enabled + is_default；缺失或非法分别返回固定 replacement 错误。该事务不得先产生无默认或双默认的可见中间态。
- `model_runtime_availability` 不属于 config set。余额、契约、Worker 或外部服务变化只在独立事务更新逐模型运行时可用性、GatewayBalanceState/审计和 runtime version，不创建 config_version，也不修改 enabled、is_default 或 recommendation_priority。
- 契约相关配置变化仍按 PRD 生成新 config_version 并把 `contract_validation_status` 置为 unverified，但新版本必须继承候选集合明确提交的 enabled/is_default/recommendation_priority；契约失效本身不得暗改这三个字段，只通过 runtime availability 禁止新任务。
- 逐模型 runtime reason 固定为 `available`、`configured_disabled`、`contract_unverified`、`contract_blocked`、`gateway_balance_insufficient`、`gateway_paused` 或 `worker_degraded`；读取 API 不透传供应商文本。多个原因同时存在时按 configured disabled、contract、balance/paused、Worker 的顺序返回首要阻断原因，恢复一个原因后仍需重新计算其余原因。
- 读取时先确定当前 config set，再对每个 enabled 模型联结 runtime availability；`recommended_model_id` 按有效候选的 recommendation_priority 升序取第一项，无候选返回 null。configured default 即使运行时不可用仍保持原 ID，前端不得把推荐结果写回后台默认。

初始化迁移种入固定三模型集合且三者 configured enabled=true：`gemini-3.1-flash-image-preview` 为 `is_default=true`、`recommendation_priority=1`；`gemini-3-pro-image-preview` 为 priority 2；`gpt-image-2` 为 priority 3。三者初始 contract 为 unverified、运行时不可用于真实任务；初始化和后续迁移都通过同一集合校验器，禁止专用旁路。

### 7.7 后台贴纸历史文件显式清理

项目保存事务从 Canvas Schema 提取普通贴纸 stable ID/resource_version，重建 `project_asset_refs`；active 和 trashed 项目均保留关系，只有项目进入 purged 后才撤销。`asset_release_items` 对所有仍保留的 immutable 已发布版本保留原图/缩略图关系。候选与清理流程固定为：

1. 候选查询只选择 `asset_origin=admin_uploaded`、已不再供当前新增使用，且不存在任何 `asset_release_items`、active/trashed `project_asset_refs`、待发布集合或其他有效引用的原图/缩略图；返回内部 file ID、stable sticker ID、resource_version、file_kind、bytes、hash prefix、引用计数零和短期 `candidate_snapshot_version`，不返回路径。
2. super_admin 以候选快照创建 cleanup intent；API 要求 Idempotency-Key，写入请求及候选摘要的 immutable AdminOperationLog，并返回必须由同一 active super_admin 明确确认的短期确认令牌。查询候选本身不删除、不排队。
3. 确认接口在 `BEGIN IMMEDIATE` 中锁定 intent 和候选文件，重新检查管理员、快照、文件状态及全部引用。任一文件被任一已发布版本、active 项目、尚可恢复的 trashed 项目、待发布集合或其他有效关系引用时，整批不排队，提交 `result=denied` 的引用复核审计后返回 `ASSET_HISTORY_REFERENCE_CONFLICT`；快照失效返回 stale 错误并要求重查。
4. 全部无引用时，同一事务把文件置 `pending_delete`、撤销新的目录读取、写入 `file_cleanup_queue`/outbox，并记录 `validated` 与 `scheduled` 审计。排队不减少 `managed_content_bytes`，也不提前改变 LocalBackendStorageState。
5. Worker 按 operation/file 唯一键执行物理删除并可幂等重试。每轮完成后重新扫描受管文件实际字节，在单一事务更新文件物理结果、`managed_content_bytes`、notice/status/last_measured_at 和最终 AdminOperationLog；失败或部分完成记录非敏感计数并继续重试，绝不把未物理删除的字节扣除。
6. 只有重计量确认容量低于硬上限且 LocalDataRoot 可写时才恢复 active。系统、容量告警、发布或保留期作业均不得自动创建贴纸历史 cleanup intent。

full 不阻止候选查询、确认和排队。unavailable 时仍可查询；只有 SQLite 可写时才能确认并排队，SQLite 不可写则明确返回操作未提交，恢复后重试。两种状态都不得借清理接口上传替换文件、生成新缩略图或写入其他受管二进制。

### 7.8 私有内容告知确认与逐次访问

当前私有内容告知版本作为编译期非敏感业务配置随 app_version 发布，包含稳定 version、message_key、content SHA-256 和 effective_at；修改文案必须递增 version。`POST /admin/private-content-notice/ack` 要求 active super_admin、CSRF、Idempotency-Key 和客户端看到的 expected notice version，在 `BEGIN IMMEDIATE` 中重新比较当前版本并更新该 super_admin 的 UserProfile 两个字段；版本过期时不写入并要求重新读取。

任何 `/admin/private-content/*` 区域数据接口都在服务端比较 UserProfile 已确认版本与当前版本；不相等先返回 `PRIVATE_CONTENT_NOTICE_ACK_REQUIRED`，不能用 Cookie、session 或浏览器存储绕过。图片/完整提示词打开接口在同一数据库事务再次检查 active super_admin 和当前告知确认，先插入 PrivateContentAccessLog 并提交，随后才发放一次受控读取；审计写入失败时不返回内容。告知升版会使所有旧确认自然失效，但确认记录绝不代替任何一次 PrivateContentAccessLog。

## 8. Generation Worker 与三模型适配

### 8.1 持久队列

Worker 使用 SQLite 原子领取 queued 任务并写入 `lease_owner`、`lease_expires_at`、`heartbeat_at` 和 `attempt_no`，任务转为 running。租约 30 秒，心跳每 10 秒更新；一次只有一个本机 Worker 实例持有命名锁。

Worker 不对供应商请求做自动重新提交。超时、连接中断或未知结果进入相应固定错误分类，由用户决定是否重试；这避免恢复时重复产生真实调用和扣费。

### 8.2 固定 Adapter 接口

三个模型各有独立模块，均实现：

- `validateContract(routeProfile)`：检查当前配置引用已验证证据；
- `start(requestSnapshot)`：返回 completed 或 pending，pending 必须包含不含凭据的 upstream job reference；
- `poll(upstreamJobReference)`：只用于契约已经确认的异步模型；
- `normalizeOutput(response)`：输出统一图片流、MIME、像素、大小和供应商用量摘要；
- `classifyError(error, mappingProfile)`：只返回九类固定分类和非敏感 source category；
- `checkBalanceSignal(response)`：更新 GatewayBalanceState 的模型、账户或未知影响范围。

接口允许封装同步或异步供应商差异，但每个 Adapter 必须依据自身证据实现，不能复制另一个模型的字段或端点。每次请求固定一张输出；Adapter 按已验证协议发送单图约束，网关返回多图或结构不符时不得静默选取，按 gateway_contract_invalid 处理并使该配置重新进入 unverified。

初始数据通过 7.6 的配置集合迁移种入三个 PRD 模型 ID且三者 configured `enabled=true`：`gemini-3.1-flash-image-preview` 为唯一默认且 recommendation_priority=1，`gemini-3-pro-image-preview` 为 priority 2，`gpt-image-2` 为 priority 3，三者 `credit_cost=1`。在契约证据通过前 route profile 不填猜测字段、`contract_validation_status=unverified`、`available_for_new_jobs=false`；configured enabled 与运行时可用字段分离，验证通过并保存证据后才能恢复运行时真实任务能力。

### 8.3 契约验证证据

每个模型的验证记录必须包含：

- 网关文档或平台支持确认的版本/日期；
- 脱敏请求与响应 fixture；
- 纯文字单图、参考图、四种比例；
- 返回 MIME、像素、获取方式和 URL 有效期；
- 同步/异步、任务查询、超时和取消/未知结果语义；
- 技术失败、安全拒绝、非安全参考图无效、余额不足和未知错误；
- error mapping profile 版本和 config_version；
- 证据 SHA-256、验证人和验证时间，不包含密钥、完整私有提示词或真实用户图片。

公开模型广场显示的端点与通用参数表只可作为验证输入，不可写成 verified 证据。任一端点、字段、参考图、响应、异步查询或错误协议改变，保存配置时立即将对应模型设为 unverified，停止其新任务并重新执行 AC-40。

`contract_validation_status` 初始和配置变更后为 unverified；完整矩阵通过后为 verified；平台确认不支持 PRD 必需能力或验证存在无法继续的外部条件时为 blocked。unverified 和 blocked 均禁止真实新任务并返回 gateway_contract_invalid，只有新证据完整通过后才能转为 verified。

### 8.4 崩溃恢复

- 启动时 Worker 扫描租约过期 running 任务。
- 若任务已有经验证可查询的 upstream reference，只恢复 poll，不重新 submit。
- `upstream_outcome_known` 初始为 false；Adapter 取得可验证终态时设为 true。`upstream_cost_reconciliation` 固定使用 `not_applicable`、`not_required`、`pending_manual_review`、`confirmed_charged` 或 `confirmed_not_charged`；该值不改变 Dada 点数处理。
- 若已向上游提交但没有可安全查询的 upstream reference，任务以 `unknown_retryable` 失败并释放冻结点，不推测上游结果，也不自动重试；同时保持 `upstream_outcome_known=false`、设 `upstream_cost_reconciliation=pending_manual_review` 并记录非敏感提交时间/路由引用，供管理员核查网关实际消耗。
- 管理员核对只能将 pending 转为 confirmed_charged 或 confirmed_not_charged 并写 AdminOperationLog；不得因核对结果补扣用户点数、改写任务终态或自动重新提交。
- queued 任务可以由新 Worker 正常领取；页面关闭不改变其状态和点数。
- 清理/结算 outbox 使用实体唯一键重放，保证最终只执行一次。

### 8.5 九类错误统一路径

| category | 创建前 | 创建后终态 | 点数 | 工程处理 |
| --- | --- | --- | --- | --- |
| upstream_timeout | 不适用 | failed | release | Adapter 超时映射；允许原输入重试 |
| upstream_failed | 不适用 | failed | release | 已知临时上游故障；稍后重试 |
| safety_rejected | 不适用 | rejected | release | 仅供应商安全拒绝；要求修改提示词或参考图 |
| model_disabled | 不创建 | 不适用 | no reserve | 配置 enabled=false；余额/契约/Worker 运行时原因由更具体分类优先 |
| gateway_balance_insufficient | 不创建或 failed | failed（若已创建） | no reserve/release | 更新独立运行时影响范围，不改配置；人工确认恢复 |
| gateway_contract_invalid | 不创建或 failed | failed（若已创建） | no reserve/release | contract 非 verified；停止运行时路由但不改配置默认/优先级，要求重新验证 |
| reference_invalid | 不创建或 failed | failed（若已创建） | no reserve/release | 格式、大小、数量或非安全型内容不可用；不得映射为 safety_rejected |
| unknown_retryable | 不适用 | failed | release | 无法细分但可稍后重试；保留非敏感诊断 |
| unknown_non_retryable | 不适用 | failed | release | 不应反复重试；联系管理员 |

点数不足、配置版本变化、已有任务、项目/历史上限和本机 storage full/unavailable 使用确定性业务错误，不扩充 GenerationErrorCategory。

## 9. 核心业务工程方案

### 9.1 邀请注册、验证码、会话和管理员

- 注册发码前和验证码成功创建账号前均在事务中重新校验邀请码、账号状态和 10 人名额。
- 邀请码由密码学安全随机数生成，仅在创建成功响应中显示一次；数据库保存带 pepper 的 HMAC、最大次数、有效期、状态和计数，不保存可再次读取的原码，日志与审计只保存邀请码内部 ID。
- `email_challenges` 只保存验证码 HMAC、用途、过期时间、失败次数和 consumed_at；10 分钟过期、60 秒重发限制；连续失败和高频请求按邮箱摘要与本机会话组合限流。
- Resend 用量领取与 `used_count` 更新使用 `BEGIN IMMEDIATE`。调用尝试一经放行即计数，供应商超时不回退计数，以保证不突破免费硬上限。
- 账号创建、初始 10 点、registration_grant、邀请码使用次数和隐私告知版本同事务完成；失败不消耗邀请码。
- active 与 suspended 普通用户计入 10 人；deleted 释放；super_admin 不计入且不使用邀请码。
- 管理员登录使用独立入口。白名单外邮箱在调用 Resend 前拒绝；允许多个权限相同的 super_admin，不设置最后一个管理员保护。
- 开发初始化脚本只写入 P0A-dev 数据根并在启动 banner 标识 DEV_SEEDED_ACCOUNT；发布构建不暴露入口，正式认证 AC 不可使用该账号作为证据。

### 9.2 项目、历史、失败草稿和回收站

- “新建创作”首次提交创建草稿项目；失败保留 `failed_empty`；重试复用项目并新增任务；“继续生成”只追加任务。
- 默认项目名由 API 在创建事务内一次生成：将首次提示词做 Unicode 空白折叠，取前 24 个字素簇作摘要（空摘要使用固定 `未命名创作`），追加按当前 Windows 时区计算的 `YYYY-MM-DD`。同名允许，身份由 project_id 确定；用户重命名作为普通自动保存操作。
- 项目固定 ratio/size；最多 20 个 active，成功历史最多 10 张。恢复 trashed 项目同样事务检查 active 上限。
- 仅无成功图的 failed_empty 可进入批量移入回收站命令；API 根据数据库状态判断，不能相信客户端 batch_deletable。
- 当前底图不能直接删除；切换底图保留覆盖元素、重置底图调整、重新提取已有色卡，并形成一次撤销/保存操作。
- 自动保存由页面内单串行队列执行：已提交操作后 1 秒 debounce，同时最多一个保存请求，期间新变化合并为下一个最新快照。失败保持 failed/dirty 并在页面存活时指数退避重试，不写浏览器持久存储。
- 站内离开守卫对 dirty/saving/failed 固定提供保存并离开、放弃修改和取消；保存并离开必须等待 saved，失败则不跳转。刷新/关闭使用 `beforeunload` 并发起最后一次 best-effort 保存；放弃、强制刷新、关闭或崩溃只能恢复最后 saved 版本。
- trashed 精确保存 deleted_at/purge_at；到期或主动永久删除后先变 purged 并立即撤销访问，再进入物理清理队列。

### 9.3 画布状态与渲染

- Dada Canvas Schema 保存背景、调整、元素、资源版本和 z_index；加载时转换为 Fabric 对象，保存前再规范化，禁止持久化 Fabric 缓存、DOM 或函数。
- 复杂资源统一编译为 `TemplateRenderModel v1`，由 TypeScript Canvas 2D/Fabric renderer 消费；文字运行、装饰图片、参数槽和动态字段均为声明式节点，不使用 HTML/CSS 布局作为导出真相。编辑预览与导出调用同一 renderer 和资源版本。
- 画布最多 50 个覆盖元素；服务端和客户端同时校验。
- 选择循环、长按候选、Shift/多选模式、框选、层级、吸附、撤销重做由编辑域控制器实现；撤销栈仅页面内存，保存的是撤销完成后的当前状态。
- 字体加载必须等待 `FontFace` ready 后才能测量、生成基准图或导出。字体缺失使对应模板 unavailable；DYN012 明确使用 FONT081，不允许静默系统字体替代。
- 切换文字模板只替换模板资源和模板默认样式，保留用户文本、换行、位置、缩放、旋转和层级；字体覆盖及填充/描边/背景/对齐/行距/字距写入 CanvasElement 的显式样式字段。
- 底图调整使用确定性渲染参数，原图与显示调整分离；色卡算法始终读取未裁剪、未调色、未滤镜的原始底图像素。原图按保持比例缩放到最长边 256px 的离屏 Canvas，固定逐像素扫描，使用 MMCQ 生成五色；按像素占比降序、RGB 数值升序打破并列，并把 `palette_algorithm_version=mmcq-v1` 保存到色卡元素，确保同图同版本结果一致。
- 动态贴纸由固定 TypeScript provider 生成声明式元素。时间取插入时本机值并保存；身份默认复制插入当时的账号资料到 DynamicStickerData，单实例覆盖只改该元素，账号资料后续变化不暗改已保存画布。需要 `@` 的 provider 先去掉用户值前导 `@` 再添加一个；DYN004 在确认后才调用定位并保存原始坐标。

### 9.4 素材面板与公开缓存

- 普通贴纸目录按 part 和稳定顺序分页；前端虚拟列表只保留视口与小缓冲，缩略图懒加载，原图只在加入画布或导出时加载。
- 文字模板只按 catalog 分类、稳定展示顺序和 display_name 搜索；普通贴纸只按 part 浏览，不复用文字搜索索引，也不从哈希文件名推断关键词。
- 最近使用只保存账号、稳定 ID、资源版本和时间，不保存项目内容。
- 公开二进制使用版本化 URL、ETag 和 `Cache-Control: public, max-age=31536000, immutable`。
- 应用主动缓存使用 Cache Storage，但 allowlist 仅包含 public_release_asset 中的公开缩略图、公开模板转换产物和公开字体；普通贴纸原图、生成图、成品和其他 public response 不进入 Dada 主动持久缓存。IndexedDB 只保存上述 allowlist 资源的 resource ID、release_version、bytes、last_accessed_at。写入前按 LRU 淘汰，确保不超过 157,286,400 字节。
- internal preview 和 private response 使用 `Cache-Control: no-store`，Service Worker 不拦截；未保存状态不得进入任何浏览器持久化存储。

### 9.5 客户端导出与 latest exports

- 导出由浏览器按项目真实像素在离屏 Canvas 合成；PNG 无损，JPG 默认 92 且仅允许 80-100；颜色空间固定 sRGB，无水印。
- 未提交文字/样式在用户确认后先形成一个撤销操作，等待字体与图片稳定，再执行一次合成。
- 同一 Blob 一路用于浏览器下载和后端 latest export 上传，避免下载与保存结果不同。
- 上传携带 project_id、state_version、format、SHA-256、像素和 `export_id`；API 校验所有者、格式、像素和容量后原子替换该格式最新文件。
- 项目 conflicted、项目 JSON 保存失败、latest export 持久化失败或 storage unavailable 时仍允许客户端下载，但不更新项目或 latest_exports。storage full 下仍按 4.3/4.4 节允许纯 JSON 项目保存，只禁止 latest export 上传；旧成功成品保持不变。
- 从未导出的项目返回稳定空状态，不自动生成默认成品；P0-A 只提供本机重新下载和原始生成图下载。

### 9.6 运营后台与两类审计

- 后台使用同一 React 应用的独立 `/admin` 路由域和独立 API 权限中间件，不建立第二套服务。
- 管理员 session/bootstrap 返回当前私有内容告知 version/message_key、该身份已确认 version/time 和计算后的 `notice_acknowledged`。私有内容区域数据路由在服务端强制当前版本确认；版本升高后重新阻断，确认不使用浏览器或会话状态代替。
- 列表默认只返回非敏感摘要。完整提示词/图片必须调用专用 private access API；API 再次校验当前告知确认，先插入 PrivateContentAccessLog，再返回资源。告知确认不能替代逐次审计。
- 管理员修改用户、点数、邀请码、模型、普通贴纸、复杂模板、预览授权、辅助服务或余额恢复，以及创建/确认/执行贴纸历史清理时，在对应业务事务内写 AdminOperationLog。
- before/after summary 只保存枚举、数值、版本和内部对象引用；不保存正文、图片、凭据或完整白名单。
- 后台不能编辑复杂模板内部样式、固定错误分类、服务硬上限上调、自动充值或付费通道。

### 9.7 内部预览授权

- 公开 manifest 在构建和响应时双重过滤，只能含 public_release_asset。
- 内部预览 manifest 每次按 active session、active user、grant 状态、有效期和 test_batch_id 动态生成，资源 URL 使用随机 manifest item ID，不能由模板稳定 ID 推导。
- 撤销、到期或暂停账号后立即拒绝后续 manifest/资源请求；已经取得的 response 为 no-store，不进入应用缓存。
- full_p0 可以分批 imported/internal_preview/passed，但后台不提供逐批普通用户 enabled 操作。未来正式切换必须是一次事务化发布命令，并且只在 P0-B 全部门槛通过后存在。

## 10. 素材工程方案

### 10.1 唯一输入

素材编译器只读取 PRD 18.1-18.7 的规范入口：

- `%USERPROFILE%\Desktop\sticker_web_handoff` 的交接说明、总 manifest 和完整性报告；
- 文字模板分类 `catalog.csv` 与 `templates/<ID>/metadata.json`；
- 字体 `font_panel_catalog.csv` 与单字体 metadata；
- `%USERPROFILE%\Desktop\贴纸素材\sticker_part1` 至 `sticker_part25` 的 1,407 张 PNG；
- 色卡 `catalog.csv` 与 `styles/<ID>/metadata.json`；
- 动态贴纸 `catalog.csv` 与 `templates/<ID>/metadata.json`。

历史 PB、截图、录像和对账目录只作人工证据，不进入产品构建或运行时。编译器以只读句柄打开源文件，不修正、删除、重命名或生成旁路文件。

### 10.2 StaticStickerCatalog 构建

1. 按 part1-25 和目录稳定规则枚举 1,407 张 PNG，重复内容不去重目录项。
2. 为每个入口生成稳定 ID、part、顺序、原文件名、相对路径、宽高、MIME、SHA-256 和缩略图引用。
3. 校验计数、PNG 解码、路径唯一、ID 唯一和 manifest 哈希；任一失败阻止发布。
4. Git 保存 catalog、metadata、manifest 和校验报告；PNG 原图仍在源目录原地只读，通过后端 ID 路由提供。

### 10.3 复杂资源包

- 文字、字体、色卡和动态贴纸由 TypeScript 编译器转换为版本化声明式 JSON、字体/图片引用和必要派生文件。
- Lua/Prefab 仅作为转换输入和行为证据；编译器不执行脚本，浏览器和后端运行时也不解释或执行 Lua/Prefab。
- 332 个文字模板、86 个字体面板项、16 个色卡和 35 个动态贴纸全部完成导入和注册；P0-A 普通用户 manifest 只导出 32 个文字白名单、其引用字体并强制 FONT081、4 个色卡和 10 个动态贴纸。色卡和动态贴纸不计入普通贴纸的 25 个 part。
- 浏览器或后端无法从规范源直接安全服务的最小转换产物写入 `derived-assets/<release_version>/<content_hash>` 并计入 LocalBackendStorageState。编译器先查找已有内容哈希并复用；源二进制、可直接服务文件和未被 P0-A/内部预览 release 引用的预转换产物不复制或生成。

### 10.4 后台新增普通贴纸

管理员上传 PNG/WebP 后，API 分别为原图和 Dada 生成缩略图建立容量预留，流式写 staging，验证真实 MIME、解码、尺寸、大小、稳定 ID、part 和顺序；通过后创建新的 immutable release_version，将两类文件作为 `sticker_original`/`sticker_thumbnail` 写入 `managed-assets` 并计入 managed_content_bytes，catalog、metadata 和 manifest 只写版本化元数据。此路由在 storage full/unavailable 时拒绝上传和缩略图生成；预计总量大于上限时拒绝，恰好等于上限时允许提交并进入 full。不得把既有规范归档导入 `managed-assets`。发布事务切换 current release，旧版本只读保留；历史文件只能按 7.7 的候选、确认、引用复核、审计、队列、物理删除和重测流程释放。

后台不能上传 Lua、Prefab、文字模板、色卡渲染器或动态资源包。复杂资源只能走受控离线编译和版本发布。

### 10.5 Manifest 与发布路径

- `release_version` 使用 `asset-YYYYMMDD.<sequence>`；每个 manifest 自身和每个文件均有 SHA-256。
- 只读源对象键为 `root_ref + relative_path`；LocalDataRoot 派生对象键为 `release_version/content_hash`；浏览器只看到稳定资源 ID 和版本化 API URL。
- manifest 一经发布不可修改；修正生成新版本。项目保存稳定 ID + resource_version，旧项目始终按旧版本渲染。
- Gitea CI 对 catalog/schema/hash 使用 fixture 验证；受控 release job 在当前电脑只读校验真实归档并生成 manifest，不将 3.57 GB 二进制或其等价派生副本上传为 CI 制品。

### 10.6 三类资源隔离

| access_class | API | 鉴权 | 缓存 |
| --- | --- | --- | --- |
| public_release_asset | `/api/v1/assets/public/...` | 先通过浏览器 support gate；无用户会话要求；只允许 manifest 白名单资源 | 仅公开缩略图、转换产物和字体可进入 150 MB LRU；immutable |
| internal_preview_asset | `/api/v1/assets/preview/...` | active 普通账号 + 每次校验 AssetPreviewGrant | `no-store`；不得进入公开 manifest |
| private_user_asset | `/api/v1/private-assets/...` | 所有者，或先写审计的 active super_admin | `private, no-store`；不可推导路径 |

### 10.7 素材授权边界

P0-A 只维护规范归档中已有的素材来源引用、manifest、catalog、metadata、版本和 SHA-256，并保持受控内测、三类资源隔离和不自行扩大公开范围。完整商业授权台账不新增为当前 P0-A 或本版已定义 P0-B 的技术/产品完成与发布门槛；若未来进入正式公开注册或商业化，则必须按届时适用的合规发布要求补齐。WP-5 主责 PRIV-05 不得被拆解为当前建设商业授权系统的任务。

## 11. 外部服务硬停止与恢复

### 11.1 Resend

- `external_service_usage` 为每日 80 和自然月 2,400 分别建立计数行。事务领取调用额度；达到最后一个名额时先把服务置 paused_quota，再允许该次已领取调用结束。
- paused_quota、paused_provider 或 disabled 时不调用供应商，不创建验证码假成功；注册和需要新验证码的登录暂停，现有有效会话继续。
- 新免费周期开始时创建新计数行，但保持暂停，直到 super_admin 在后台确认周期和免费额度后执行恢复并审计。
- 供应商恢复同样需要非敏感健康检查结果和 super_admin 人工恢复。免费额度下降只能下调 hard_limit 并审计；不能上调或切换付费通道。
- QQ、163 和企业邮箱的发布前 20 封送达结果作为人工发布证据保存，不写入 ExternalServiceUsage，也不创建逐封 EmailVerificationEvent 或运行时质量状态。

### 11.2 高德

- 每自然月硬上限 1,000 次，配额领取与状态更新事务化；达到上限先 paused_quota。
- paused 状态只关闭 DYN004 自动定位；手动地点文字和其他编辑功能继续。
- 浏览器坐标只在 Dada 自有提示确认后取得，并由 API 发送高德；前端不持有高德密钥。
- 新周期或供应商恢复后由 super_admin 人工恢复并写审计；不购买流量或自动扩容。

### 11.3 AI 网关余额

- Adapter 将非敏感余额信号写入 GatewayBalanceState。model 影响范围只停明确模型；account 或 unknown 停同 gateway_account_ref 全部模型。
- 运行时停用事务只更新 `model_runtime_availability`、GatewayBalanceState 和 AdminOperationLog，并向 SSE 发布 runtime version；不得创建或改写 ModelConfig 版本、enabled、is_default 或 recommendation_priority。
- 已创建失败任务只释放一次；Dada 点数不能覆盖真实网关余额。
- 不自动探测后直接恢复。部署人员/管理员先确认真实余额，记录 confirmed recovery check，再由 super_admin 只恢复运行时可用状态并审计；恢复后重新派生 recommended_model_id，仍不回写配置默认或优先级。

## 12. 安全、隐私与保留

### 12.1 本机安全边界

- Fastify 只监听 `127.0.0.1`，拒绝其他 Host；Windows 防火墙不创建入站规则；不设置端口转发。
- CSP 固定 `default-src 'self'`，图片/字体只允许同源 blob URL 和必要内存 URL；禁止第三方脚本。
- AI 网关、Resend 和高德客户端只允许 HTTPS、正常校验 Windows 信任链与主机名、限制响应大小和超时，并拒绝重定向到未在固定供应商配置中的主机；任何环境都不得关闭证书验证。
- 资源路由执行所有者/授权校验，404 隐藏对象是否存在；下载设置 `X-Content-Type-Options: nosniff` 和安全 Content-Disposition。
- P0-A 不做 Dada 应用层文件加密，依赖当前 Windows 用户登录和 NTFS 权限；页面固定显示数据可丢失、不备份、不迁移提示。

### 12.2 账号注销

注销在一个事务中撤销会话、删除邮箱和资料、释放邮箱唯一约束、删除未使用点数、把项目/任务/资源标为不可访问、生成不含映射的匿名事件、匿名化私有访问日志目标并加入物理清理队列。事务提交后旧内容对任何用户都不可恢复；物理文件随后异步删除。

AnonymousRetainedEvent 最迟在注销后 180 天删除；AdminOperationLog 和 PrivateContentAccessLog 按原事件时间 180 天删除，注销不延长。不可回溯汇总只保存计数，不保留单次作品或主体键。

### 12.3 审计与敏感信息

- PrivateContentAccessLog 专用于打开完整提示词/图片；AdminOperationLog 专用于后台敏感操作，两者不重复保存正文。
- 任何日志、错误信封、manifest、SSE、OpenAPI 示例和测试 fixture 都不得出现真实密钥、验证码、会话、管理员邮箱、完整提示词、私有图片或绝对用户路径。
- Windows 凭据管理器读取只发生在子进程启动/重启时。Supervisor 通过继承的匿名管道按子进程最小权限传递一次，Node 在供应商客户端构造后清空接收缓冲；值不进入进程参数、环境变量、磁盘或日志。
- CI 仅使用格式正确的假密钥和 `.invalid` 邮箱；正式验收凭据由离线命令写入。

## 13. 性能、可访问性与视觉一致性

### 13.1 P0-A 性能预算

在当前 Windows 电脑、当前稳定版 Chrome/Edge、1080p 以上视口、50 个混合覆盖元素的标准画布上：

- 连续拖动/缩放/旋转 10 秒期间，指针到下一次画面更新的 p95 不超过 50 ms，单个主线程长任务不得超过 200 ms，不能出现连续 500 ms 无响应；
- 画布交互帧耗时 p95 不超过 33 ms；吸附和候选计算不得引起控件尺寸跳动；
- 自动保存序列化使用调度切片，单次主线程阻塞 p95 不超过 50 ms；网络/SQLite 保存不占用画布交互线程；
- 已缓存白名单资源的编辑器重新打开至可操作不超过 3 秒；未缓存资源显示稳定占位，不阻塞其他工具；
- 1080 x 1920、50 元素画布导出在 10 秒内完成，浏览器页面峰值额外内存不超过 1 GiB；失败不改变项目和 latest export；
- 普通贴纸面板 DOM 节点数不随 1,407 张总量线性增长；每个 part 只渲染视口和两屏缓冲。

性能测试使用固定数据集和采样脚本；未达到预算阻止对应 AC-27、AC-32 或发布门槛。本文不为 P0-B 手机定义当前实现预算。

### 13.2 视觉一致性

- Chrome/Edge 使用相同转换资产、字体文件、Canvas Schema、DPR 归一化和导出路径。
- 基准运行固定 OS、浏览器、视口、DPR、字体 ready、动画时刻和动态数据；具体矩阵写入 tdd.md。
- 元素边界偏差不超过 2px；颜色通道差异超过 16/255 的显著像素不超过总像素 1%；任一超限必须人工复核。
- DYN012、style_02-16 和已知替代项无论自动差异是否通过均人工复核。

### 13.3 可访问性基础

- 所有关键命令、表单、对话框和画布外工具均可键盘到达；焦点不被托盘或 SSE 更新抢走。
- 状态同时提供文字/图标/ARIA，不只依赖颜色；上传、任务、保存、冲突、存储和错误具有文字状态。
- 浏览器不支持引导页必须在不加载产品 bundle 时仍具备语义标题、键盘可读说明和明确支持版本；WinForms 托盘、启动/端口/degraded 对话框和诊断结果使用 Windows UI Automation 可读名称、状态与动作。
- CI 对关键页面运行 axe，critical/serious 问题为零；画布对象操作提供可聚焦的等价工具控件。最终焦点顺序、图标名称和文案由 UIDesign.md 固定。

## 14. 测试、监控与 AC 追踪

### 14.1 测试层级

| 层级 | 基础设施 | 覆盖目标 |
| --- | --- | --- |
| Schema/单元 | Vitest、TypeBox fixture | 状态转换、哈希、版本、九类错误、模型默认/优先级集合校验、运行时推荐、容量 `>`/`=` 边界、动态格式化、色卡确定性、路径校验 |
| SQLite 集成 | 每测试临时数据库和临时 LocalDataRoot | 事务、部分唯一索引、幂等、点数、名额、模型集合原子切换、告知确认持久化、审计不可变、贴纸引用竞争、清理和容量状态 |
| API 契约 | Fastify inject + OpenAPI snapshot | 浏览器门禁、分组路由、模型读取/保存、告知 ack、贴纸清理、稳定错误信封、鉴权、CSRF、If-Match、multipart、三类资源 |
| Worker 恢复 | 模拟网关、可控时钟、进程终止 harness | 租约、心跳、崩溃、poll 恢复、只结算/释放一次、运行时模型状态、上游结果/成本核对、贴纸物理删除/重测与清理重放 |
| 外部契约 | 三模型脱敏 fixture、Resend/Amap mock；受控真实验证命令 | 三模型分别验证；免费配额和错误分类；真实调用不在普通 CI |
| 浏览器 | Playwright Chrome/Edge 独立 profile + 原始 HTTP 客户端 | 发布记录中的真实 Chrome/Edge 通过；其他品牌、错误 major、缺失/冲突 UA-CH 和直调产品 API 均硬阻断；认证、项目、编辑、同机多标签、导出、越权和后台；Service Worker 不拦截 internal/private，Cache Storage/IndexedDB 只有 allowlist 公开资源元数据 |
| 安全/打包 | Windows 凭据测试 harness、进程检查、制品扫描 | 凭据缺失/清除、API/Worker 重启、最小权限传递；support gate 与辅助诊断最小字段；命令行、环境、文件、日志和崩溃诊断零真实凭据 |
| 视觉/性能 | 固定 Canvas fixture、截图 diff、PerformanceObserver | AC-32、50 元素、字体、动态模板、导出一致性 |
| 人工发布验收 | P0-A 便携包和真实外部依赖 | PRD 第 22、23 节适用门槛 |

DevelopmentPlan 不定义逐场景操作步骤。tdd.md 必须从 `RELEASE.json` 接收并写定具体 Windows build、Chrome/Edge 品牌、major 与已验收 full version、DPR、视口、数据 fixture 和逐步断言；不得使用伪造 UA 字符串代替两种真实稳定浏览器的通过证据。

`reference_invalid` 必须分别覆盖格式/大小/数量和非安全型内容不可用；`safety_rejected` 必须使用供应商明确安全拒绝证据。二者不能通过同一个模拟错误或同一个断言替代。跨普通用户隔离验收使用独立 Chrome/Edge profile，通过正常邀请码、验证码和普通权限链路创建临时第二账号，完成越权检查后走正常注销并等待其内容清理；不得使用管理员直写、开发预置账号或隐藏测试后门替代。Playwright 每个缓存/私有资源场景后必须枚举 Service Worker registration、Cache Storage 键和 IndexedDB 记录，不能只依赖响应头断言。私有内容告知测试必须跨新标签页、API/浏览器重启、重新登录和另一受支持浏览器验证同版本不重复，并在告知 version 递增后验证服务端重新阻断；每次打开正文/图片仍单独断言 PrivateContentAccessLog。

### 14.2 本地监控

后台和诊断接口从 SQLite 聚合并显示：

- GenerationJob queued/running 数、队列最老等待时间、租约过期数、各模型调用量/成功率/失败率/错误分类/耗时，以及 `pending_manual_review` 的上游成本核对数量和最老等待时间；
- configured_default_model_id、config_set_version、逐模型 recommendation_priority/runtime availability、recommended_model_id，以及无可用模型持续时间；运行时指标不得回写配置；
- 点数 reserve/commit/release 数和余额不变量异常；
- managed_content_bytes（分参考图、生成图、成品、派生文件、后台贴纸原图和缩略图）、阈值、storage_status、目录可写性、贴纸 cleanup intent/队列积压、引用冲突数、最近物理删除和重测时间；
- Resend 日/月用量、状态、硬上限和非敏感 pause reason；
- 高德月用量、状态和硬上限；
- GatewayBalanceState、影响范围、运行时不可用模型和最后确认时间；
- 审计到期清理、匿名事件清理、项目 purge、staging 清理和日志轮转结果；
- 素材 manifest 版本、根目录可用状态和校验失败数，不回显绝对路径。

固定本地告警条件：running 租约过期、队列等待超过 2 分钟、连续 5 个生成失败、无运行时可用模型、存在 `pending_manual_review` 上游成本核对、点数不变量失败、storage full/unavailable、贴纸或项目清理任务连续 3 次失败、外部服务 paused、网关余额不足/unknown、素材根校验失败、API/Worker degraded。告警进入后台和托盘状态，不发送外部告警或产生自动费用。

### 14.3 十三个功能模块落点

| FeatureSummary 模块 | 工程组件 | 主工作包 |
| --- | --- | --- |
| 邀请注册与邮箱登录 | Auth API、Resend adapter、session、admin allowlist | WP-1 |
| 点数与原子结算 | Credit service、ledger、SQLite 事务 | WP-2 |
| 三模型 AI 生图 | Generation API、ModelConfig set、runtime availability、Worker、三个 Adapter | WP-2 / WP-3 |
| 项目、历史、保存与冲突 | Project service、Canvas Schema、cleanup | WP-2 |
| 电脑画布与底图编辑 | React/Fabric editor | WP-4 |
| 文字模板与字体 | Asset compiler、Font loader、text renderer | WP-4 / WP-5 |
| 25 个普通贴纸 part | StaticStickerCatalog、虚拟列表、managed original/thumbnail、引用保护与显式清理、asset routes | WP-5 |
| 色卡与动态贴纸 | deterministic palette、dynamic provider registry | WP-4 / WP-5 |
| 导出 | browser compositor、latest export API | WP-4 |
| 运营后台 | Admin API/UI、模型配置集合、私有告知确认、贴纸清理、service state、audit | WP-6 |
| 素材发布与内部预览 | immutable release、grants、three access routes | WP-5 / WP-6 |
| 本机数据与资源访问 | Supervisor、浏览器 gate、辅助系统界面、SQLite、LocalDataRoot、storage state | WP-0 / WP-2 |
| 隐私、审计与免费服务边界 | deletion、retention、audit、quota state | WP-1 / WP-6 |

### 14.4 AC 唯一主责映射

| 主责工作包 | 当前 AC |
| --- | --- |
| WP-0 基础工程与本机边界 | AC-24、AC-46、AC-55、AC-56 |
| WP-1 认证、身份、隐私与审计基础 | AC-01、AC-02、AC-22、AC-33、AC-39、AC-45、AC-49 |
| WP-2 项目、点数与 GenerationJob | AC-03、AC-04、AC-05、AC-06、AC-20、AC-21、AC-28、AC-29、AC-34、AC-35、AC-36 的 P0-A 子集、AC-38 的 P0-A 子集、AC-43、AC-44、AC-51、AC-53 |
| WP-3 三模型契约验证与适配 | AC-30、AC-40 |
| WP-4 桌面编辑器与导出 | AC-07、AC-09、AC-10、AC-11、AC-12、AC-14、AC-16、AC-17、AC-18、AC-19、AC-23 的 P0-A 子集、AC-27、AC-32 |
| WP-5 素材转换、发布与白名单 | AC-13、AC-15 的 P0-A 子集、AC-31、AC-42、AC-48 |
| WP-6 运营后台与外部服务控制 | AC-25、AC-47、AC-50、AC-52 |
| WP-7 P0-A 发布准备 | AC-41 |

AC-08、AC-26、AC-37、AC-54 没有当前主责工作包。交叉 AC 可作为其他工作包的依赖或回归输入，但只能由上表主责工作包宣告完成。

### 14.5 PRD 需求 ID 覆盖索引

| 主责工作包 | PRD 需求 ID |
| --- | --- |
| WP-0 | PRIV-01、PRIV-02、NFR-01、NFR-07、NFR-09 |
| WP-1 | AUTH-01、AUTH-02、AUTH-03、AUTH-04、AUTH-05、AUTH-06、AUTH-07、PRIV-03、PRIV-04、PRIV-06、NFR-04 |
| WP-2 | CREDIT-01、CREDIT-02、CREDIT-03、CREDIT-04、CREDIT-05、CREDIT-06、PROJECT-01、PROJECT-02、PROJECT-03、PROJECT-04、PROJECT-05、PROJECT-06、PROJECT-07、PROJECT-08、PROJECT-09、GEN-03、GEN-04、GEN-05、GEN-06、GEN-11、GEN-12、GEN-14、GEN-16、NFR-05 |
| WP-3 | GEN-01、GEN-02、GEN-07、GEN-08、GEN-09、GEN-10、GEN-13、GEN-15、ADMIN-03 |
| WP-4 | EDITOR-01、EDITOR-02、EDITOR-03、EDITOR-04、EDITOR-05、EDITOR-06、EDITOR-07、EDITOR-08、EDITOR-09、TEXT-01、TEXT-02、TEXT-03、TEXT-04、TEXT-05、TEXT-06、TEXT-07、TEXT-08、TEXT-09、TEXT-10、TEXT-11、TEXT-12、TEXT-13、TEXT-14、COL-01、COL-02、COL-03、COL-04、COL-05、DYN-01、DYN-02、DYN-03、DYN-04、DYN-05、DYN-06、DYN-07、DYN-08、DYN-09、EXPORT-01、EXPORT-02、EXPORT-03、EXPORT-04、EXPORT-05、EXPORT-06、NFR-03、NFR-06 |
| WP-5 | STATIC-01、STATIC-02、STATIC-03、STATIC-04、ADMIN-06、ADMIN-07、PRIV-05、NFR-02 |
| WP-6 | ADMIN-01、ADMIN-02、ADMIN-04、ADMIN-05、ADMIN-08、ADMIN-09、NFR-08 |

GEN-05、GEN-14、GEN-16、AUTH-07、CREDIT-06、ADMIN-06、ADMIN-07、NFR-02、NFR-04、NFR-07 和 PRIV-02 具有跨工作包依赖，但上表给出唯一主责。WP-7 不拥有新的产品需求 ID，只负责汇总全部当前需求和适用 AC 的发布证据。

### 14.6 跨工作包依赖矩阵

本表只定义必须交付的接口和回归关系，不改变 14.4/14.5 的唯一主责。

| 上游工作包/契约 | 下游工作包 | 需求/AC 交叉 | 强制交付边界 |
| --- | --- | --- | --- |
| WP-0 LocalBackendStorageState、纯 JSON/二进制路由分界 | WP-2 项目、删除与清理 | PROJECT-04-09、NFR-07/09、17.19；AC-20、35、36 P0-A、38 P0-A、43、55、56 | WP-2 只通过已提交资源 ID 保存 JSON；full 可保存，unavailable 按可写性拒绝；删除完成后回传重测 |
| WP-0 LocalBackendStorageState、容量预留 | WP-4 导出 | EXPORT-05/06、NFR-03/09；AC-23 P0-A、24、35、38 P0-A、55、56 | 客户端下载始终与 latest export 上传解耦；full/unavailable 不得覆盖旧 latest export |
| WP-0 LocalBackendStorageState、managed/derived 写策略 | WP-5 素材编译与发布 | STATIC-04、ADMIN-06/07、PRIV-05、NFR-02/07；AC-31、42、46、48、55 | 既有归档不复制；上传只进 managed；无法直接服务的最小产物去重后进 derived，两者均受 5 GB 约束 |
| WP-0 存储测量、日志和告警 | WP-6 运营后台 | ADMIN-08/09、17.16/17.19；AC-41、50、52、55 | 后台只读取非敏感容量/健康摘要，不能上调固定上限、强制恢复或回显绝对路径 |
| WP-0 安全配置修订/生效协议 | WP-1 认证、WP-6 审计 | AUTH-07、ADMIN-09、17.16；AC-45、49、50 | 只有 API 原子应用后才生效；离线执行者不成为审计角色；失败不部分生效 |
| WP-2 任务诊断字段 | WP-3 Adapter、WP-6 诊断 | GEN-13/16、ADMIN-04；AC-40、51、52、53 | 未知上游结果进入人工成本核对，不新增任务/错误状态，不补扣用户点数 |
| WP-0 浏览器支持记录、gate 与辅助状态 | WP-1 至 WP-7 全部 Web/API | NFR-01、17.1；AC-24、25、30、31、40、50、51、55 | 未通过品牌/major/平台检查只能读取最小引导；全部产品路由先执行 gate；辅助状态不泄露路径或正文 |
| WP-3 ModelConfig set 与 runtime availability | WP-2 生成、WP-6 模型/服务后台 | GEN-01/13-15、ADMIN-03、17.2/17.17；AC-30、40、51 | 默认/优先级以配置集合原子切换；推荐只从独立运行时状态派生；余额/契约降级不回写配置 |
| WP-1 UserProfile 告知字段 | WP-6 私有内容区域与访问审计 | ADMIN-05、17.1/17.10；AC-25、50 | 后端按身份+版本持久确认；区域服务端门禁；每次打开仍先写 PrivateContentAccessLog |
| WP-2 项目资源引用、WP-5 贴纸文件关系 | WP-6 清理后台、WP-0 容量重测 | STATIC-04、ADMIN-06/09、17.7/17.16/17.19；AC-31、50、55 | active/trashed/发布及其他有效引用阻止整批清理；排队不减容量；物理删除后重测才更新/恢复 |

### 14.7 v1.10 重点 AC 工程闭环

| AC | 数据/状态 | API/事务 | 必须进入 tdd.md 的基础设施断言 |
| --- | --- | --- | --- |
| AC-24 | `browser_support_release`、Supervisor 辅助状态 | support check + 全产品路由前置 gate；426 无绕过 | 真实受支持 Chrome/Edge 通过；其他品牌、错误 major、UA-CH 缺失/冲突、直调 API、局域网/其他设备均阻断 |
| AC-25 | UserProfile 告知 version/time、PrivateContentAccessLog | ack 事务、区域服务端前置校验、打开前审计 | 跨标签/重启/重登/两浏览器同版本不重复；升版重新提示；每次内容读取独立审计 |
| AC-30 | immutable config set、唯一正整数 priority、runtime availability | 完整集合 CAS 保存、默认替换同事务、模型读取派生推荐 | 初始 seed；缺失/非正/重复；无替代停默认；合法原子替换；默认运行时不可用和全不可用 |
| AC-31 | managed original/thumbnail、release/project refs、cleanup intent | 上传/缩略图容量预留；候选、确认、引用复核、排队、物理删除/重测 | 两类字节计入；full/unavailable 阻断；有引用整批拒绝；无引用显式清理；旧项目继续渲染 |
| AC-40 | 三个独立 config/Adapter/contract evidence | 分模型验证与 config/runtime 分离 | 三模型各自端点、输入、参考图、比例、同步/异步、任务查询、九类错误和结算证据，任一变化重置 unverified |
| AC-50 | append-only AdminOperationLog / PrivateContentAccessLog | 模型配置、贴纸清理各阶段和服务恢复事务内审计；私有打开独立审计 | 请求、引用校验、拒绝/排队、物理结果完整且 180 天内不可改删；无敏感正文 |
| AC-51 | GatewayBalanceState + model_runtime_availability | model/account/unknown 运行时影响事务、人工恢复 | 三种影响均不改 enabled/is_default/priority；只释放一次点数；推荐重新派生；恢复审计 |
| AC-55 | LocalBackendStorageState、reservation、managed file kinds | 写前 `>`、等于允许后 full；清理后物理重测 | 精确阈值、等号、超限、原图/缩略图计量、full/unavailable 动作矩阵、清理前不减字节及恢复条件 |

## 15. 实施阶段与依赖顺序

以下是工程阶段，不是 tasks.md。后续 tasks.md 必须在每个工作包内部继续拆分，但不得改变顺序、依赖和完成条件。

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

- **需求/AC**：NFR-01、NFR-07、NFR-09、PRIV-01/02、第 17.19、18.9/18.10、23.4；AC-24、46、55、56。
- **输入**：冻结文档、固定技术栈、Gitea、当前 Windows 电脑。
- **产出**：monorepo、冻结锁文件、版本兼容性预检证据、OpenAPI 基础、SQLite 迁移、LocalDataRoot、storage state、.NET 托盘、API/Worker 生命周期、浏览器 support gate、辅助系统界面状态/诊断契约、日志、隔离 CI、便携 ZIP、`START-HERE.txt` 和 SHA-256。
- **依赖**：无。
- **完成条件**：冻结安装、TypeScript、React/Fabric、Fastify、better-sqlite3 native 和便携包预检全部通过；单实例启动/退出、固定端口、回环绑定、浏览器品牌/major/平台硬门禁、无 UA-CH 阻断、API/Worker degraded、脱敏诊断、Gitea 隔离 CI、容量 `>`/`=` 状态和只读资源 ID 骨架通过集成测试。
- **不能提前宣告**：任一固定依赖未通过预检且未正式修订 DevelopmentPlan，或未完成不可写/full 恢复、日志轮转、无备份/不迁移、局域网拒绝、产品 API 门禁和辅助界面敏感字段扫描时，基础环境不完成；AC-24 最终通过仍须等待 WP-7 写入真实稳定浏览器记录。

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

- **需求/AC**：AUTH-01-07、PRIV-03/04/06、ADMIN-09、第 17.1、17.10、17.11、17.16；AC-01、02、22、33、39、45、49；另向 WP-6 提供 AC-25 的 UserProfile 数据依赖，不取得其主责。
- **输入**：WP-0；Resend mock；Windows 凭据和离线白名单命令。
- **产出**：邀请、验证码挑战、普通/管理员分离会话、10 人名额、多 super_admin、安全配置修订/原子生效协议、开发账号脚本、UserProfile 私有内容告知字段/迁移、注销、匿名保留、两类审计基础。
- **依赖**：WP-0。真实认证验收依赖 Resend 发布门槛。
- **完成条件**：模拟状态机、原子邀请码核销、30 天撤销、全管理员停用/离线恢复、安全配置成功/失败生效审计、告知版本字段跨重启持久化、凭据缺失/清除/重启和全传递链零泄漏、注销不可恢复和 180 天清理通过。
- **不能提前宣告**：开发预置账号或模拟邮件不能宣告 Resend、正式注册、管理员认证或 AC-41 完成。

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

- **需求/AC**：CREDIT-01-06、GEN-03-06/11/12/14-16、PROJECT-01-09、第 17.3-5/9/18；AC-03-06、20、21、28、29、34、35、36 P0-A、38 P0-A、43、44、51、53。
- **输入**：WP-0、WP-1 数据与会话；模拟 Adapter。
- **产出**：项目/历史、failed_empty、自动保存、冲突、回收站、`project_asset_refs`、点数账户与流水、持久队列、单任务索引、模型运行时可用性消费、文件生命周期和九类错误路径。
- **依赖**：WP-0、WP-1。真实完成态联调依赖 WP-3。
- **完成条件**：并发提交、幂等、崩溃恢复、只结算/释放一次、上游不确定成本核对、active/trashed 项目资源引用一致、full 纯 JSON 保存/二进制阻断、同机多标签和本地导出例外均通过 SQLite/API/Worker 测试。
- **不能提前宣告**：模拟成功不能宣告三模型真实结算或 AC-40 完成。

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

- **需求/AC**：GEN-01/02/05/07-10/13-16、ADMIN-03、GatewayBalanceState；AC-30、40。
- **输入**：WP-2 Adapter 接口；平台文档/支持确认；受控凭据与既有额度。
- **产出**：初始三模型 seed、immutable config set/迁移、唯一优先级与默认替换事务、读取 API 的配置默认/运行时状态/recommended_model_id、三个独立 Adapter、三套脱敏证据、route/error mapping profile、比例/参考图矩阵、同步/异步恢复、余额影响与真实点数结算联调。
- **依赖**：WP-2；AI 网关外部阻塞解除。
- **完成条件**：初始化默认/1-2-3 优先级、非法优先级整批拒绝、默认模型合法原子替换、runtime 不回写配置、空推荐、三个模型在最后一次契约配置变化后均 verified，以及 AC-30/40 全矩阵通过。
- **不能提前宣告**：配置集合能产生零/多个 enabled default、优先级可并列/非正、运行时降级改写配置，或任何一个模型未验证/只验证文字/单比例/无参考图/无错误时，WP-3 和真实结算联调不完成。

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

- **需求/AC**：EDITOR-01-09、TEXT-01-14、COL-01-05、DYN-01-09、EXPORT-01-06；AC-07、09-12、14、16-19、23 P0-A、27、32。
- **输入**：WP-2 Project/Canvas API；WP-5 的稳定资源 fixture 可并行替换。
- **产出**：Fabric 编辑器、底图、选择/变换/撤销、文字/字体、色卡、动态 provider、客户端 JPG/PNG、latest export 和性能 harness。
- **依赖**：WP-2。最终视觉依赖 WP-5 白名单资源。
- **完成条件**：50 元素预算、Chrome/Edge 核心白名单一致性、保存失败本地导出和四种比例导出通过。
- **不能提前宣告**：占位字体、未校验模板、只在一个浏览器通过或后端失败时不能下载，均不完成。

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

- **需求/AC**：STATIC-01-04、ADMIN-06/07、PRIV-05、NFR-02/07、第 17.6-8/12-14、第 18 节；AC-13、15 P0-A、31、42、48。
- **输入**：只读规范归档和交接 manifest；WP-0 资源路由；WP-4 renderer contract。
- **产出**：1,407 项 StaticStickerCatalog、全部复杂模板注册、P0-A 白名单 release、immutable manifest、哈希、转换产物、后台原图/缩略图关系、显式历史清理 API/队列/审计、三类路由和 150 MB LRU。
- **依赖**：WP-0；可与 WP-4 前半并行，最终集成依赖 WP-4。
- **完成条件**：源目录零改写、无全库/等价派生副本、managed/derived 分类和内容去重、原图/缩略图计量、full/unavailable 阻断、清理候选/确认/引用竞争/物理删除/重测、计数/哈希、公开 manifest 隔离、授权撤销立即生效和旧版本项目渲染均通过。
- **不能提前宣告**：贴纸历史文件仍有发布或 active/trashed 项目引用、只排队未物理删除、容量提前扣减或存在自动清理时不完成；色卡/动态贴纸不能计入 25 part；白名单外 passed 不能逐批向普通用户 enabled；PRIV-05 不得被扩张为 P0-A/P0-B 商业授权台账阻塞。

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

- **需求/AC**：ADMIN-01-09、AUTH-07、CREDIT-06、GEN-15、ExternalServiceUsage、GatewayBalanceState；AC-25、47、50、52。
- **输入**：WP-1/2/3/5 的服务和审计接口；Resend/Amap mock。
- **产出**：用户、邀请、点数、模型配置/运行时状态、生成、素材/历史清理、预览、服务、存储、指标、审计后台；私有内容告知 ack/区域服务端门禁/逐次访问审计；人工恢复流程。
- **依赖**：WP-1、WP-2、WP-5；模型页最终状态依赖 WP-3。
- **完成条件**：告知确认按身份+版本跨标签/重启/重登持久且升版重置；每次私有内容打开仍有独立日志；模型/贴纸清理等全部敏感操作有不可变审计；硬上限不可上调；恢复前置条件强制执行。
- **不能提前宣告**：以浏览器/session 状态代替告知持久化、确认后省略逐次访问审计、贴纸清理缺少请求/复核/结果日志、后台按钮缺服务端授权/事务，或可回显路径/密钥/正文时不完成。

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

- **需求/AC**：第 22 节全部 P0-A 必验及四个 P0-A 子集、第 23 节；AC-41 主责。
- **输入**：WP-0 至 WP-6 完成；真实外部依赖；当前稳定版 Chrome/Edge；规范素材根。
- **产出**：发布 ZIP、SHA-256、带 Windows/Chrome/Edge 支持记录的 RELEASE.json、`START-HERE.txt`、辅助系统界面验收、验收环境、AC 结果、视觉/性能基准、外部门槛记录，以及页面/设置区持续显示的固定文案“测试数据仅保存在本机，不自动备份，也不会迁移到正式系统。”
- **依赖**：所有当前工作包和三个外部阻塞全部解除。
- **完成条件**：适用 AC 全部通过；真实稳定 Chrome/Edge full version 已验收并固化 major，其他浏览器/错误 major/无法识别/直调 API 无绕过；两个输入文档哈希未变化；`START-HERE.txt` 的未签名、SmartScreen/误报和 SHA-256 核验流程可用；固定数据风险文案持续可见；没有 P0-B 能力混入。
- **不能提前宣告**：任一模型 unverified、模型配置/运行时状态仍混写、Resend 送达不达标、高德未核验、贴纸清理/告知确认/storage/素材/浏览器或辅助诊断门槛失败时不得开始或完成 P0-A 实际验收。

## 16. P0-B 未来开放门槛

P0-B 不进入当前工程实现。未来单独立项时，必须重新定版公司系统合并、正式存储、远程部署、域名、HTTPS、数据隔离、桌面继承和手机横竖屏完整编辑，并执行 AC-08、AC-26、AC-37、AC-54 及通用 AC 的 P0-B 子集。

扩大普通账号上限、开放全部复杂模板和移动端能力是同一发布门槛的一部分，不得把 P0-B 解释为只扩大人数。白名单外 full_p0 模板可以提前内部导入和 passed，但只有全部剩余复杂模板 passed，且所有 P0-B 门槛同时满足后，才可通过一次事务化发布全部 enabled。P0-A 数据不自动或手动迁移到该未来系统。

未来一次性开放前必须同时具备：P0-A 全部适用 AC 通过；332 个文字模板、86 个字体项、1,407 张普通贴纸、16 个色卡和 35 个动态贴纸完成全量验收；AC-26 和 AC-37 通过；白名单外复杂模板全部 passed；普通账号上限配置并验证为 50；公开 manifest 不含 internal_preview_asset；三个模型在最后一次契约配置变化后均 verified；公司系统合并、正式存储、远程部署、域名、HTTPS、数据隔离以及桌面与手机横竖屏能力全部定版并验收。缺少任一条件时仍保持 P0-A 的 10 人上限和普通用户白名单。

## 17. 后续文档接口

### 17.1 UIDesign.md 输入

UIDesign.md 接收页面/入口边界、角色权限、状态枚举、错误 `message_key`、保存/冲突/容量/服务暂停行为、P0-A 设备边界、可访问性要求和资源缓存限制；还必须接收 3.6 的无绕过浏览器阻断页、模型 configured default/runtime availability/recommended 三层显示、私有内容告知版本确认、贴纸历史清理确认/引用冲突/排队/物理结果，以及 3.7 的 Windows 托盘、启动失败、端口占用、API/Worker degraded、诊断入口和脱敏诊断字段。本地测试页或设置区必须持续显示且不得改写固定文案：“测试数据仅保存在本机，不自动备份，也不会迁移到正式系统。”UIDesign.md 决定其布局和可访问呈现，但不能将它降级为仅首次启动、仅发布说明或可关闭后永久消失的提示。其他布局、视觉、图标、焦点顺序和具体中文文案由 UIDesign.md 决定，但不能改变 API 状态机、错误动作、AC、辅助状态允许动作或阶段范围。

### 17.2 tdd.md 输入

tdd.md 接收 OpenAPI 分组、稳定错误码、SQLite 不变量、事务/幂等规则、ModelConfig 集合/运行时推荐、Adapter 契约矩阵、九类错误、文件一致性、三类资源、贴纸显式清理、私有告知确认、工作包 AC 主责和性能/视觉阈值。它还必须接收凭据缺失/清除/重启与泄漏扫描门槛、UA-CH 硬门禁和真实稳定浏览器要求、辅助系统界面脱敏断言、Service Worker/Cache Storage/IndexedDB 持久存储断言、容量 `>`/`=` 与 full 纯 JSON/二进制拒绝矩阵，以及上游成本核对路径。tdd.md 补充 `RELEASE.json` 对应的具体 Windows build、浏览器 full version、DPR、视口、fixture、逐场景步骤和断言；必须把 `reference_invalid` 与 `safety_rejected` 分开。

### 17.3 tasks.md 输入

tasks.md 接收 WP-0 至 WP-7 的顺序、依赖、14.6 跨包矩阵、14.7 重点 AC 闭环、组件边界、产出、完成条件和禁止提前宣告事项，再拆为可执行任务。不得创建 P0-B 当前任务，不得重新选择技术栈，也不得把外部阻塞伪装成已完成开发任务。原型或兼容性验证失败时，必须先正式修订 DevelopmentPlan；实现任务不能把固定端口、进程模型、浏览器门禁、模型配置集合、SQLite 保护、容量等号语义、贴纸清理、日志轮转、凭据传递或依赖版本改成可选实现。

## 18. 风险与工程控制

| 风险 | 控制 |
| --- | --- |
| 网关公开说明与真实图片协议不一致 | 三模型独立证据；未验证即运行时不可用于新任务但不改配置；不从模型名称推断 |
| SQLite API/Worker 并发写 | WAL、busy timeout、BEGIN IMMEDIATE、部分唯一索引、事务重试仅限尚未产生外部副作用的操作 |
| Worker 崩溃造成重复上游调用 | 租约恢复只 poll，不重新 submit；无法查询则失败释放，用户手动重试；上游可能消耗进人工成本核对 |
| 文件与数据库跨事务不一致 | staging、哈希、同卷原子改名、managed_files、outbox 和启动对账 |
| 本机磁盘不足 | 精确 `>` 容量预检、等号提交后 full、storage reservation、full/unavailable 硬停止、物理删除后重测、无自动删除 |
| 默认模型、优先级和运行时状态相互覆盖 | immutable config set 原子切换、数据库集合约束、独立 runtime availability、读取时派生推荐 |
| 贴纸清理与项目/发布并发产生悬空引用 | 候选快照、BEGIN IMMEDIATE 复核、active/trashed/发布引用表、整批拒绝、队列幂等和物理后重测 |
| 私有内容告知被浏览器状态绕过 | UserProfile 身份+版本持久确认、区域服务端门禁、升版失效、每次打开独立访问审计 |
| 不支持浏览器绕过前端检查 | 最小 gate shell、UA-CH 交叉校验、发布 major 记录、全部产品路由前置中间件、426 无绕过 |
| 浏览器私有草稿泄漏 | 私有编辑态仅内存；Cache/IndexedDB 仅公开资源；自动化扫描浏览器存储 |
| 素材复制或源目录污染 | 只读根、manifest allowlist、release job 独立输出、源目录前后哈希/时间检查 |
| 字体与导出漂移 | 固定字体资源、FontFace ready、Canvas Schema、Chrome/Edge视觉阈值和人工复核 |
| 托盘或子进程残留 | 单实例 mutex、命名管道、健康检查、有限重启、15 秒优雅退出 |
| 未签名程序被 Windows 警告或杀软误报 | `START-HERE.txt`、SHA-256、固定 Gitea 构建记录和明确的项目方本机核对流程；不声称已签名，不指导关闭安全防护 |
| P0-A 被误写成远程正式系统 | 固定回环地址、无远程组件、AC-24 网络检查、P0-B 仅保留门槛 |

## 19. 完成定义

DevelopmentPlan 的工程方案落实后，P0-A 只有在以下条件同时满足时可标记完成：

- WP-0 至 WP-7 全部达到各自完成条件；
- FeatureSummary 十三个模块、PRD 十九个数据契约和九类 GenerationErrorCategory 均有实现与测试落点；
- AC-01-07、09-14、16-22、24-25、27-35、39-53、55-56 通过；AC-15、23、36、38 仅 P0-A 子集通过；
- AC-08、26、37、54 没有被实现结果冒充为当前完成条件；
- AI 网关、Resend、高德和本机发布门槛全部解除；
- 普通账号系统上限为 10，`super_admin` 不占普通账号名额；P0-A 人工验收固定使用 1 个 `super_admin` 和 1 个普通账号，跨普通用户隔离仅通过正常认证链临时创建第二普通账号，验收后注销并按规则清理；
- LocalDataRoot 与素材根严格分离，5 GB/80%/90%、150 MB LRU、三类资源隔离和无私有草稿持久化通过；
- 后台上传普通贴纸原图和缩略图均计入 managed_content_bytes；预计写入后超过上限拒绝、恰好等于上限允许后进入 full；历史文件只经 super_admin 显式确认、无有效引用、不可变审计、物理删除和重测后释放容量；
- 初始三模型默认和 recommendation_priority 为 1/2/3；后台配置始终恰好一个 enabled default，运行时不可用不改写 enabled/is_default/priority，recommended_model_id 只按可用模型派生且允许为空；
- 私有内容告知按 super_admin 身份与当前版本后端持久确认，升版后重确认；任何图片或完整提示词读取仍先写独立 PrivateContentAccessLog；
- 发布包只服务当前 Windows 电脑上 RELEASE.json 记录 major 的 Chrome/Edge；其他品牌、不支持版本或无法识别的浏览器只有无绕过引导，局域网、手机和其他设备无法访问；
- Windows 托盘、启动失败、固定端口占用、API/Worker degraded 和诊断结果具有稳定脱敏界面契约，不能泄露密钥、绝对路径或私有正文；
- 发布产物、日志、数据库配置、manifest 和文档中不存在真实密钥、验证码、会话令牌、管理员邮箱或硬编码用户绝对路径；
- 本地测试页或设置区始终明确显示“测试数据仅保存在本机，不自动备份，也不会迁移到正式系统。”；用户只能主动下载原始生成图和 JPG/PNG 成品。
- 所有固定技术决策已通过兼容性/原型门槛；任一失败只能先正式修订 DevelopmentPlan，不得由实现人员自由替换。
