八角色并行开工分析,合计 7448 行;另出 00 汇总页(跨角色收敛结论、 13 项待拍板、6 项待仲裁分歧、未取证项汇总),挂第四迭代导航最前。 mkdocs build --strict 通过。 基线实测修正(文档与实况不符): - api 测试 381(releases.md 记 379,成因待仲裁) - 埋点白名单 41(四份文档记 42,experiment_exposed 重复计数) - 真机验证挂起 10 项(转述链 4→6→8→10 每跳丢项) - E2E 断言机械可数 226(声称 234 无可复核来源) - v0.4.0 实际发布 09-14 11:17;CI 非红,三仓五上下文全绿 多方独立收敛(无需拍板): - 队列用 Postgres SKIP LOCKED + 租约列,不引入 Redis/MQ - 服务端零对象写能力(ObjectStorage 无 put/get),M4 立足点缺地基 - 「四模块字节级快照锁 CI」不存在,实际门禁仅结构断言 - 定稿模型 input_asset_id NOT NULL,即图生图不支持文生图 - 跨 schema 外键补回是 V5 自身指令,裁剪理由已不成立 阻塞项与安全缺口: - AI provider BLOCKED:零 SDK/endpoint/额度,正典种子即 fixture - 分支保护必需上下文选错触发器:(push) 限定 branches:[dev], 致「推 dev 即满足门禁」且「非 dev 分支 PR 永久无法合并」 - check-secrets.sh 对 sk-/sk-ant- 零覆盖,须先于任何 AI key 落地 - 北极星 09-21 窗口已于 09-13 关闭,补救无从下手,建议改事件驱动 本批核心教训:13 处文档/注释与代码相反且多已被下游采信,其中 5 处造成实际规模误判(widthPx M→S、数据模型早已定稿 L→M、 社区侧 purpose 校验实际不存在等)。汇总页 §0 立转述纪律。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
121 KiB
Patbond 第四迭代任务分解(M4 AI 创作)
作者:Senior Project Manager 日期:2026-09-14 依据:
docs/development/development-plan.md(第 7 节 M4、第 4/6 节规范、第 9/10 节质量门禁与 DoD)、iterations/iteration-3/29-m3-summary.md§4、iterations/iteration-3.5/06-wave2-closure.md§7、docs/architecture/backend-modules.md、docs/architecture/decisions.md(ADR-001~022)、docs/database/patbond_postgresql.sql(creationschema 3 表,已评审定稿)、docs/api/openapi.yamlv1.4.0(32 路径 / 45 操作 / 75 schema,冻结中) 编号约定:本迭代工单以T4-前缀编号,决策以D4-前缀编号。 范围声明:严格限定为 M4 AI 创作。本地服务与预约(M5)、通知推送与交付加固(M6)不在本迭代范围;posts.region_id仍仅作预留。范围外需求一律记 backlog。
结论摘要:工单 28 个(其中条件单 3 个)分 4 波;待用户拍板决策 14 项,头号三项为 AI 提供方选型、模块归属与 Worker 形态、生成输入图是否必填。开工审计推翻或修正了 8 处既有文档结论,其中两处直接改变规模预估:creation schema 的完整设计早已评审定稿(V6 是提取而非设计,且零跨 schema 外键需裁剪),而「输出写入媒体表」一句话背后隐藏着「服务端目前根本不能写对象存储」这一真实工作量。
1. 范围界定与依据
1.1 开发计划 M4 原文(正典依据)
开发计划第 7 节 M4(development-plan.md:246-255)逐字引用:
- 目标:「替换上传和生成延时模拟。」(:248)
- 「实现模型/风格目录、生成任务创建、查询和取消。」(:250)
- 「Worker 通过队列消费任务,使用租约、重试和幂等键防止重复生成。」(:251)
- 「输出写入媒体表,成功后可一键创建社区草稿。」(:252)
- 「Flutter 展示排队、生成进度、失败重试和取消状态。」(:253)
- 验收标准:「任务状态流转合法;Worker 重启不丢任务;相同幂等请求不重复扣费或生成;失败原因可追踪。」(:255)
四条验收标准与工单的映射:
| 验收标准 | 主责工单 | 取证方式 |
|---|---|---|
| 任务状态流转合法 | T4-06 / T4-07 / T4-08 | ck_generation_jobs_state 数据库层强制五态字段组合(目标模型 patbond_postgresql.sql:667-692),非法迁移在库层即被拒;应用层再补六类路径测试 + T4-25 E2E |
| Worker 重启不丢任务 | T4-08 | 租约过期回收(lease_expires_at + ix_generation_jobs_running partial 索引);T4-25 E2E 在 running 态重启容器后断言任务被重新领取并完成 |
| 相同幂等请求不重复扣费或生成 | T4-06 | UNIQUE (user_id, idempotency_key) + uq_generation_jobs_provider_request;配额按 generation_jobs 行数计(见 D4-7),幂等命中不新建行即天然不重复扣费 |
| 失败原因可追踪 | T4-07 / T4-08 | error_code / error_message / attempt_count 三列经查询端点外露;provider 原始失败经日志(不含密钥)留痕 |
1.2 其他正典约束(M4 必须遵守)
- 业务域→schema 映射已定:
development-plan.md:67「AI 创作 = 模型、风格、异步任务、重试、生成结果 →creationschema」;:64「媒体域含 AI 输入输出」。 - 写接口幂等强制:
development-plan.md:173「创建帖子、生成任务和预约等写接口必须支持Idempotency-Key」。 - 可观测性硬要求:
development-plan.md:329「技术指标须含队列积压和 Worker 失败率」;:330 产品漏斗须含「AI 任务成功」。 patbond-common纪律:development-plan.md:79「不应让所有服务被动引入 RabbitMQ、Feign 等依赖」——这条对 D4-3 队列载体选型有直接约束力。- Flutter feature 拆分已预留
creation:development-plan.md:96明列auth/pets/community/creation/marketplace。 - 单迁移链:
backend-modules.md:41「Flyway 迁移链唯一持有者……其他模块不得携带 Flyway」——V6 必须进patbond-user,与模块归属决策(D4-2)解耦。 - 结构性变更须经 ADR:
backend-modules.md:3-5「新增/拆分模块须经 ADR 决策并同步更新本页」。M4 是 ADR-023 起的落点(现存最高 ADR-022)。 - AI 提供方在正典中仍为未决项:
development-plan.md:358「AI 提供方、对象存储、天气/地图供应商和通知渠道尚未确定」、:365 待确认事项同列。对象存储已由 ADR-016 解决,AI 提供方是 M4 唯一从头拍板的供应商项(D4-1)。
2. 开工前的关键事实(PM 逐项核实原始 SQL / 契约 / 源码)
纪律说明:M3.5 的教训(
iteration-3.5/06-wave2-closure.md:42「转述不可采信」——一份报告写「无 nickname 字段」实指「契约未暴露」,被当成缺列,规模高估一整档)在本节被严格执行。下列每一条「存在 / 不存在」的判断都注明了核实的原始文件与行号,未经原文核实的一律标注「未取证」。
2.1 creation schema 的完整设计早已评审定稿——V6 是提取而非设计(推翻「M4 需从零设计数据模型」的隐含预期)
docs/database/patbond_postgresql.sql(评审定稿的目标模型)已含 creation schema 全部三张表:
| 对象 | 行号 | 关键内容 |
|---|---|---|
CREATE SCHEMA creation |
:60 | 七个 schema 之一(platform/identity/media/pet_health/community/creation/marketplace) |
creation.generation_models |
:556-573 | code/display_name/provider_code/provider_model_name/media_kind/enabled/sort_order;UNIQUE (code, media_kind) + UNIQUE (id, media_kind);索引 :575-577(partial WHERE enabled) |
creation.generation_styles |
:580-598 | code/title/subtitle/media_kind/preview_asset_id(FK→media.assets ON DELETE SET NULL)/enabled/sort_order;索引 :600-602 |
creation.generation_jobs |
:605-696 | 40 列,含完整队列语义(见 2.2) |
| 队列索引 | :711-715 | ix_generation_jobs_queue (priority DESC, next_attempt_at, created_at, id) WHERE status='queued'、ix_generation_jobs_running (lease_expires_at, id) WHERE status='running' |
| provider 幂等唯一索引 | :699-700 | uq_generation_jobs_provider_request (provider_code_snapshot, provider_request_id) WHERE provider_request_id IS NOT NULL |
updated_at 触发器 |
:1245-1249 | 三张表均复用 platform.set_updated_at()(V1 已建) |
| 开发种子 | :1495-1523 | 2 模型(image+video)/ 5 风格 / 1 已完成任务;provider_code = 'fixture' |
规模含义:T4-01 的性质与 M3 的 T3-01 完全同构(「从 bootstrap SQL 提取」,iteration-3/01:52),是抄录 + 裁剪判断,不是建模。据此定为 M 而非 L。
2.2 M4 的迁移零跨 schema 外键需要裁剪——与 V3/V5 的先例相反
V3 剪了 4 条 marketplace 外键、V5 剪了 2 条(V5__community_baseline.sql:15-25 原文)。M4 的 V6 一条都不用剪:
generation_jobs的三条跨 schema 外键目标全部已存在:identity.users(V1:60)、pet_health.pets(V3:35)、media.assets(V1:205)。generation_styles.preview_asset_id → media.assets同理。generation_jobs内部两条复合外键(→generation_models(id, media_kind)、→generation_styles(id, media_kind),:644-647)同一迁移内建表即可满足。- 反向的一条要补回:
community.posts.generation_job_id目前是裸列无外键——V5__community_baseline.sql:47行内注释原文-- generation_job_id: bare nullable uuid, FK to creation.generation_jobs stripped (M4 补回),列定义 :48generation_job_id uuid(nullable),索引ix_posts_generation_job已在 :95 建好。V5 文件头 :17-19 亦言明「the M4 migration that creates the creation schema re-adds this constraint」。
2.3 队列语义在库层已完备——不需要引入任何新中间件
generation_jobs 已含全套 DB-as-queue 列(patbond_postgresql.sql 行号):status(:625)、progress(:626)、priority(:627)、attempt_count/max_attempts(:628-629)、provider_request_id(:630)、error_code/error_message(:631-632)、idempotency_key/request_hash(:633-634 均 NOT NULL)、next_attempt_at(:635)、lease_owner/lease_expires_at(:636-637)、started_at/completed_at(:640-641)、version(:642)。
配合 :711-715 的两条 partial 索引,SELECT … FOR UPDATE SKIP LOCKED 即构成带优先级、退避与租约回收的完整队列。
与开发计划的偏离提示:development-plan.md:205 写「RabbitMQ 在异步任务阶段启用」。核实基础设施现状后(见 2.4),PM 建议本迭代不引入 RabbitMQ,改用 DB 队列——这属于对正典的建议性偏离,须经 D4-3 拍板并落 ADR。
2.4 现有异步基础设施:几乎为零(这是 T4-08 的真实起点)
| 事实 | 核实位置 |
|---|---|
| 无 Redis / RabbitMQ / Kafka(依赖与容器均无) | docker-compose.yml 六服务:postgres:13 / minio:34 / user:49 / auth:79 / pet:97 / community:125;各模块 pom 依赖清单(如 patbond-user/pom.xml:28-104)无任何 MQ/Redis 坐标 |
@EnableScheduling 仅一处 |
patbond-user/.../UserApplication.java:7 |
@Scheduled 唯一使用点 |
patbond-user/.../session/SessionCleanupJob.java:32-41(fixedDelayString = "${patbond.session.cleanup-interval:PT6H}"),配置 patbond-user/src/main/resources/application.yml:24-26 |
@Async / @EnableAsync / TaskExecutor / ThreadPoolTaskScheduler |
全仓零命中(主代码与测试) |
| 队列消费循环 / MQ 监听器 / outbox 发布器 | 不存在。platform schema 注释(V1:24)承诺了 "reliable outbox",但表与消费代码均未落地 |
| 无限流 | 全仓无 RateLimit/bucket4j/429;patbond-common/.../error/ErrorCode.java 无 429 段(最接近的 LOGIN_LOCKED(42300, 423, …):36 是账号锁定不是限流) |
| 无 feature flag 设施 | 全仓零实现(详见 2.9) |
结论:M4 的 Worker 只有一个先例可循(SessionCleanupJob 式的 @Scheduled + DB 幂等操作,Spring 默认单线程 scheduler、多实例并跑无锁)。这既是 D4-2/D4-3 的现实约束,也说明 T4-08 必须自带线程池与并发度配置。
2.5 「输出写入媒体表」一句话背后:服务端目前根本不能写对象存储(本迭代最易被低估的工作量)
ObjectStorage 接口只有四个方法(patbond-user/.../media/ObjectStorage.java:15-37):ensureBucket():18、presignPut(...):25、stat(...):28、presignGet(...):31。实现 S3ObjectStorage.java 中 S3Client 实际只用于 headBucket/createBucket(:66-79) 与 headObject(:102);putObject 仅出现在预签名构造里(:83-86 presigner.presignPutObject(...))。
即:现有媒体链路是「客户端直传,服务端只签名与校验」。AI 输出的字节由 provider 返回、必须由服务端落盘,因此 M4 必须:
- 在
ObjectStorage上新增写方法 +S3ObjectStorage实现 +MediaStorageConfig.java:33-59的UnconfiguredObjectStorage补桩; - 解决「谁来写
media.assets行」——ADR-017(decisions.md:156)定死「media 上传流程实现在patbond-user」,且media写入侧代码全在patbond-user/.../media/(MediaAssetRepository.insertUploading:32-41、markReady:81-86);pet/community侧只有MediaUrlSigner(本地 SigV4 签名,不持S3Client)与只读MediaAssetGateway。
这直接决定 D4-2 的子问题:新模块如何把生成结果落进 media 域。已单列为工单 T4-03。
2.6 media.assets 的尺寸列早已存在——widthPx/heightPx 恒 null 不是缺列(M3.5 同型教训的复现)
width_px integer/height_px integer均在V1__identity_media_baseline.sql:217-218,约束仅ck_media_dimensions(:241-245,正数校验),nullable。- 契约侧唯一关于回填的原文在
openapi.yaml:3536:description: complete 后回填,可空;但POST /api/v1/media/uploads/{assetId}/complete(:1256-1299)没有 requestBody(:1274 直接进 parameters),也没有任何字段承载尺寸;MediaAsset.required=[id, kind, purpose, mimeType, status, createdAt](:3516),两个尺寸字段不在其中。 - 故此项的真实缺口是「服务端无尺寸探测」+「契约声明了来源不存在的回填」,规模为 S(配合 T4-03 新增的对象读能力),不是「加列迁移」。处置方案见 D4-9。
2.7 ai_creation 分类在写侧被双重封死(代码 + 契约)
- 数据库允许:
V5__community_baseline.sql:68CONSTRAINT ck_posts_category CHECK (category IN ('general', 'help', 'ai_creation'))。 - 后端写侧拒绝:
patbond-community/.../dto/CreatePostRequest.java:26@Pattern(regexp = "general|help", message = "category 仅支持 general/help")。 - 契约写侧拒绝:
openapi.yaml:3657CreatePostRequest.categoryenum[general, help],:3659 注明「ai_creation为 M4 预留值,M3 不开放写入(提交 400/40000)」;读侧Post.category(:3747-3748) 与FeedCard.category(:3826) 已含三值。 generationJob在契约里没有字段本体——openapi.yaml:136与 :3717 只是说明文字,PostResponse.java:12亦注明该字段「do not appear at all」。- 落库通路也缺:
patbond-community/.../repository/PostRepository.java:56-58的insertPost(...)签名与 :60-64 的 INSERT 列清单均无generation_job_id。
即 T4-10 需同时改:Java 校验、Repository INSERT、契约 enum、契约新增 generationJob 字段。
2.8 目标模型内部存在一处矛盾,必须在 V6 前裁决
generation_jobs.model_version_snapshot varchar(64) **NOT NULL**(patbond_postgresql.sql:622 区块,:620-623 三个 snapshot 列)——但 creation.generation_models(:556-573)没有任何 version 列,种子数据把它硬编码为 '1.0'(:1514)。这是评审定稿模型里的一处自相矛盾,V6 必须选边(D4-6)。
另一处需注意的约束后果(可满足但不直观):ck_generation_jobs_state(:667-692)要求 queued 态必须 started_at IS NULL AND progress = 0 AND lease_* IS NULL。因此租约过期回收/重试时必须把 started_at 清回 NULL,首次尝试的开始时间会丢失。T4-08 需在实现说明中显式记录这一点。
2.9 A/B 前置「6 绿 1 半」不成立——实测为 5 绿 3 半
iteration-3/06-experiment-tracking-plan.md:336-351 的八项前置表逐项对代码核实后:
| # | 前置 | 文档判定 | 代码核实 |
|---|---|---|---|
| 4 | 稳定分流组件 | 绿(:345) | 部分绿:anonymousId 持久化已落地(analytics_service.dart:51、:177-190),但 hash(userId, salt) % buckets 分流组件全仓零实现 |
| 5 | 曝光事件 | 绿(:346) | 部分绿:后端字典已有(EventDictionary.java:103,props {experimentKey, variant}),Flutter 侧 grep -rn experiment lib test integration_test 零命中 |
| 7 | 护栏监控与回滚 | 部分绿(:348,「feature flag 随社区功能发布开关顺带落地」) | 回滚也不绿:feature flag / 社区发布开关在两仓中完全不存在,feature-checklist.md 连该条目都未登记 |
另:#2「指标基线」判绿,但北极星出数 SQL 仅以 Markdown 代码块存在于 iteration-2/06-experiment-tracking-plan.md:200-229,三仓 scripts/ 下无任何可执行巡检/看板脚本(只有 check-secrets.sh 与 hooks/)。
含义:「M4 可启首个实验」这一结论的前提比文档描述的弱。若要在 M4 启动实验,需先补分流 + 曝光 + 开关三件(工单 T4-23,条件单,随 D4-11)。
2.10 埋点白名单实测 41 条,文档三处写 42(experiment_exposed 被重复计数)
- 权威定义在代码常量:
patbond-user/.../analytics/EventDictionary.java:42-104,grep -c 'Map.entry("'= 41。无字典表(V4 只 seedbreeds与vaccine_catalog),DB 侧仅正则约束V2:21。 - 文档写 42 的三处:
iteration-3/22-event-whitelist-v3.md:4,14、iteration-3/29-m3-summary.md:20、feature-checklist.md:218。 - 根因:设计源文档自己写的是 19 个新事件(
iteration-3/06-experiment-tracking-plan.md:12,149),22(v2)+ 19 = 41;22 号报告把experiment_exposed既算进 19 又单列一次。 - 无门禁能拦:
EventDictionaryTest.java无任何总量断言(无hasSize/size())。T4-22 顺手补一条总量断言即可永久钉住。
2.11 真机验证挂起实为 10 项(不是 8 项),M2 两项带 2026-09-21 硬时限
device-verification.md 逐项清点:M2 2 项(:42 验证一 Android 事件落库、:63 验证二 SessionTracker 30 分钟换会话)+ M3 4 项(:114/:124/:134/:144)+ M3.5 4 项(:176/:190/:200/:209)= 10 项,三个执行记录(:106/:168/:221)全为「(待补)」,即 10 项全部未执行。
M2 两项的时限来自 iteration-3/29-m3-summary.md:52(埋点角色建议 2026-09-21 北极星首次出数日前完成,否则首批读数只能标未验收)与 iteration-2/06-experiment-tracking-plan.md:308-312(出数日历:2026-09-21 周一首次出数)。今日 09-14,仅剩 7 天——已按硬时限单独排为 T4-24 并置于第一波。
2.12 发布流程与回归门禁的实况(含一处文档自相矛盾)
main已受分支保护禁直推,发布必须走 Gitea PR:releases.md:126-138五步流程;状态检查上下文CI / backend-test (push)(api)与CI / flutter-gates (push)(flutter),见releases.md:110-114。api 的ci.yml:15-18确认pull_request触发器在位、job 名backend-test。- 回归门禁已由两份改为四份:
releases.md:56原文「回归清单由两份改为四份……原则是『每个引入对外端点的迭代都应有对应的 E2E 脚本,并在此后每次发布回归』」;v0.4.0 实测 42/42 场景、234 断言(releases.md:36)。 - 文档自相矛盾(须在 M4 收官修正):同一文件
releases.md:131的「发布后生效的纪律」第 1 步仍写「E2E 双份回归 PASS」,与 :36/:56 的四份口径冲突;且该原则从未固化进git-workflow.md(该文件 :56-64 的门禁表只列三仓的 format/analyze/test/mkdocs 四命令,无 E2E 条目)。M4 引入生成域对外端点,按 :56 原则须新增第五份 E2E 脚本(T4-25),并把「双份」改为「五份」+ 把该原则写进git-workflow.md(T4-27)。 - 现有四份脚本实为纯 Dart CLI(
dart run,非flutter test,不计入 597):test_e2e_manual.dart(M1,7 场景)、test_e2e_m2_manual.dart(11)、test_e2e_m3_manual.dart(14)、test_e2e_m35_manual.dart(10);四份同构骨架(fail()/check()/_passed计数/Resp包装/通用call()/脚本内现注册账号取 token/token 与预签名 URL 脱敏),T4-25 照抄即可。
2.13 契约快照锁的实际强度低于文档口径(「四模块字节级快照锁 CI」不成立)
- 正典
docs/api/openapi.yaml与四份快照(patbond-{auth,user,pet,community}/src/test/resources/contract/openapi-v1.4.0.yaml)实测 md5 五处一致(a7081fb84f1207eef579ab94025f5801),字节级同步这一事实成立。 - 但CI 里没有任何 md5/checksum 校验:
patbond-api/.gitea/workflows/ci.yml只有三步(secret scan :41-42、装 JDK17 :46、./mvnw -B clean test:55);scripts/下只有check-secrets.sh。md5 比对只出现在人工冻结记录里(iteration-3.5/04-contract-freeze-v140.md:77、03-backend-profile-avatar.md:287「字节级复制正典 + md5 逐一比对」= 人工步骤)。 - CI 实际锁住的是每模块一条守卫测试的「版本 + 三个计数 + tag 分组」:
AuthContractConformanceTest.java:399-407、MediaContractConformanceTest.java:246-251、petContractConformanceTest.java:755-760、CommunityContractConformanceTest.java:522-527,断言值1.4.0 / 32 / 45 / 75。 - 缺口:任何「计数守恒的字节修改」(改 description、改 enum 取值、改 min/max、同时增删各一字段)都能悄悄通过。M4 契约面显著扩大(新增生成域 + 可能第五个模块快照),建议顺手补一条真校验和断言(T4-14,S)。
- 第二道门禁在位且有效:
@Order(99) everyDeclaredResponseCellIsExercised()(auth :414-425 / pet :771 / community :538 / user :261),v1.4.0 共 181 格、豁免 1 格。
2.14 Flutter create 页现状:563 行、零测试、全假
lib/features/create/create_page.dart(563 行,lib/features/create/ 下仅此一个文件);grep -rn "CreatePage" test integration_test 零命中——这页没有任何 widget 测试。页首注释 :10-15 自述「AI 生成模拟(700/650/500ms 假延时、风格/模型/分辨率设置、结果卡)属 M4 范围,T3-17 原样保留」。
| 模拟点 | 位置 | 实况 |
|---|---|---|
| 假上传 | :55-64 simulateUpload() |
Future.delayed(700ms);展示的图直接取 demo 宠物头像 appState.pet.avatarUrl(:162) |
| 假生成 | :66-92 generate() |
4 步进度,650ms × 3 + 500ms |
| 结果图 | :86 |
selectedStyle.image,即 lib/data/demo_data.dart:206-234 的 4 个 unsplash 硬编码 URL |
| 结果文案 | :87-90 |
硬编码「豆豆的{风格}冒险」等 |
| 发布 | :122-129 publish() |
纯占位 SnackBar「AI 作品发布随 AI 创作能力上线(M4)」 |
| 模型下拉 | :179 |
硬编码 ['Patbond-V1','Pet-Art Pro','Cute Motion'] |
| 风格选择器 | :218-292 |
横向 150×125 卡 + 渐变遮罩 + 选中描边,数据源 creationStyles(demo) |
| 进度条 | :294,定义 :532-563 |
LinearProgressIndicator(value: step/4) + 4 条硬编码 label |
| 结果区 | :312-386 |
16:10 图 + 标题/正文 TextField + 话题 Chip + 硬编码位置「北京市 · 朝阳区」 |
| 真实入口(保留) | :136,定义 :394-419 |
_ComposeEntryCard → push 真实 PostComposePage |
可复用的 UI 骨架:模式分段器、风格横滑卡、进度条、结果编辑区四块布局可整体保留、只换数据源与状态源——这降低了 T4-17/T4-18 的视觉返工风险(M3 的 R10 风险在此不复现)。
顺带命中一项主题债:该页 :138 的 SegmentedButton 选中态粉底根因是主题里没有 segmentedButtonTheme(lib/core/theme/app_theme.dart:94-196 只定制了 card/filledButton/inputDecoration/snackBar/datePicker/navigationBar),因此吃了 ColorScheme.fromSeed(:88-92)的派生色。已审计色对表在 app_theme.dart:199-214,落地范本是 _datePickerTheme(:215-296)。M4 既然要重做该控件,此债搭车成本为 S(详见 §8)。
2.15 Flutter 架构与可复用资产
- 分层:feature 内平铺
page/controller/repository/models/display/analytics/exceptions,约定原文lib/features/community/community_controller.dart:14-20(Page/Widget → Controller → Repository → ApiClient)。 - 状态管理:无 Riverpod/Bloc/provider——原生
ChangeNotifier+ListenableBuilder,手写 DI 全在lib/app/app.dart:80-147。 - 路由:无 go_router、无路由表;
MaterialApp只给home:(app.dart:308-318),跳转靠散落的Navigator.push;路由名仅服务埋点(lib/analytics/analytics_page_name.dart:7-25)。新增 AI 页面需自建AnalyticsPageName枚举值,否则fromRouteName返回 null 不上报。 - API Client:dio;四条 baseUrl 由
--dart-define注入(lib/core/network/api_client.dart:8-34)。若 D4-2 新建模块,须新增第五条PATBOND_CREATION_API_BASE_URL,并同步四份 E2E 脚本的硬编码常量与真机清单的flutter run参数说明。 - 429 已有粗分支、无 Retry-After:
api_client.dart:164-167(status == 429 → ApiRateLimitException);lib/analytics/analytics_service.dart:268-272注释明写「Retry-After 分支待其落地后一并做」。T4-11 落地后此处可一并收口。 CursorPage已有:lib/core/models/cursor_page.dart:3-27。但没有可复用的分页列表容器——四态骨架各页各写一份;可照抄的两个范本:Feed(community_controller.dart:9,12,146-200+home_page.dart:456-523,536-580)与页面级自持游标(lib/features/pets/health_events_page.dart:19,71-127,同款_ListPhase已复制 4 份)。MediaUploader可直接复用于 AI 输入图:lib/features/community/media_uploader.dart:127-144,purpose由调用方注入(:154-161),已被发布页(9 图并发 2)与头像 sheet(1 图并发 1)两处复用。
2.16 收藏与草稿两页:后端与仓库层全就绪,只缺页面
listMyBookmarks()→GET /api/v1/me/bookmarks:community_repository.dart:52、实现 :274-283,全仓无调用点。listMyPosts()→GET /api/v1/me/posts?status=:community_repository.dart:26-30、:179-193;唯一调用点是发布页恢复最新一条草稿(post_compose_page.dart:196-199,limit:1)。- 契约侧路径名须注意:不存在
/me/drafts与/me/favorites;正确路径是openapi.yaml:1462(/api/v1/me/posts,含 inlinestatusenum[draft, published]:1476-1482)与 :1749(/api/v1/me/bookmarks,项形态为FeedCard)。全域术语是 bookmark,无 favorite。 - 入口现为 demo SnackBar:
lib/features/profile/profile_page.dart:49菜单项 + :235showDemoMessage,注释 :43-46 已自认「后端能力已就位,列表页本单未做」。 - 故此项规模确为 M(两页 + 导航 + 测试,照抄
_ListPhase范本),与 M4 契约零耦合,可在第一波并行消化。
2.17 基线数字的口径澄清
| 指标 | 数字 | 口径说明 |
|---|---|---|
| patbond-api 测试 | 379(surefire 运行数) / 381(@Test+@ParameterizedTest+@RepeatedTest 注解静态计数,62 个测试类) |
文档登记的 379 来自 releases.md:17,35 的 surefire 汇总;两者差异属参数化用例展开口径,不是回归。M4 报告统一以 ./mvnw -B clean test 输出为准并注明口径 |
| patbond-flutter 测试 | 597 通过 + 2 skip | flutter test 输出原文 00:30 +597 ~2: All tests passed!(iteration-3.5/08-release-e2e-regression.md:277);2 skip 是环境门控冒烟(test/smoke/detail_interactions_smoke_test.dart:173、media_upload_smoke_test.dart:133),不含 integration_test/(4 份桌面实测)与仓库根四份 E2E |
| 埋点事件白名单 | 41(非 42) | 见 2.10 |
| 真机挂起项 | 10(非 8) | 见 2.11 |
| 契约 | v1.4.0 / 32 路径 / 45 操作 / 75 schema / 181 格矩阵 | 自行计数复核一致(openapi.yaml:4 版本,路径与操作按缩进计数,schemas 自 :2082 起) |
| Flyway | V1~V5,最高 V5;flyway_schema_history 单一,归属 patbond-user |
patbond-user/src/main/resources/application.yml:11-12;pet/community 仅 test 作用域带 Flyway(patbond-pet/pom.xml:79-100、patbond-community/pom.xml:91-113);auth 无库 |
| ADR | 最高 ADR-022,M4 新决策自 ADR-023 起 | docs/architecture/decisions.md:184-195 |
| 错误码 | 27 个,最大段位见 ErrorCode.java |
下一可用:404 段 40407、403 段 40302、422 段 42206、429 段全新 |
2.18 未取证事项(明确声明)
- AI 提供方的可用性与价格:本机为 AMD Radeon Vega 集显、无
nvidia-smi(即无 CUDA),自托管 SD/ComfyUI 在当前工作机不可行;生产侧腾讯云轻量服务器是否有 GPU 未取证(需用户确认)。云 API 的具体额度与单价未取证(需用户提供账号或授权调研)。 - 实验设计模板与样本量规则文档(A/B 前置 #3/#6,文档判绿):两仓中未定位到该文档实体,仅在
iteration-3/06表格里被声明为已交付。 patbond-doc无仓库级.gitignore:site/目前仅靠用户全局~/.gitignore_global:183排除(实测未被 tracked)。换机器或 CI 检出时mkdocs build产物可被误提交,与git-workflow.md:62的纪律冲突。已并入 T4-27(一行修复)。
3. 本迭代 MVP 范围
PM 建议口径,待 §6 拍板确认。
纳入:
creationschema 三表落 V6 + 补回posts.generation_job_id外键(T4-01)- 模型/风格只读目录 + 种子(T4-04)
- provider 适配层 + 至少一个可跑通全链路的 provider 实现(T4-05,形态随 D4-1)
- 生成任务创建(
Idempotency-Key强制)/ 查询 / 列表 / 取消(T4-06、T4-07) - Worker 队列消费:DB 队列 + 租约 + 指数退避重试 + provider 幂等(T4-08)
- 输出落
media.assets(含服务端对象写入能力)与真实尺寸回填(T4-03、T4-09) - 一键建社区草稿:开放
category='ai_creation'写侧 +generationJobId落库与外露(T4-10) - 生成域配额与限流(429 +
Retry-After,契约首个响应头)(T4-11) - Flutter:
creationfeature 分层、create 页真实化、排队/进度/失败重试/取消四态、结果页与一键建草稿(T4-16~T4-19) - 契约 v1.5.0 扩展与冻结(T4-13)+ 快照锁补强(T4-14)
- 埋点字典 v4(AI 创作漏斗)+
eventVersion口径定型 + 白名单总量断言(T4-22) - 历史遗留搭车五项:
widthPx/heightPx、429 限流、uploading 超时清理、收藏与草稿两页、真机 M2 两项(详见 §8) - 第五份 E2E 脚本 + 五份回归 + v0.5.0 走 PR 发布(T4-25、T4-26)
待拍板裁剪项(默认建议见 §6):
- 视频生成(D4-5,建议剪出,仅保留
media_kind列与枚举) - 文生图(无输入图,D4-4,建议剪出,图生图先行)
- 我的生成记录列表页(T4-20,条件单,建议纳入——排查失败任务的唯一入口)
- A/B 首个实验(T4-23,条件单,随 D4-11)
- 文档站访问控制(T4-28,条件单,随 D4-12)
默认剪出:真实计费与支付(无计费表,以配额替代)、生成结果的审核与水印、生成完成推送通知(M6)、多模型并行对比、逐帖曝光与服务端排序实验日志(ADR-020 backlog)、完整可观测性栈(M6)——逐条理由见 §7。
4. 工单列表
预估规模口径沿用前三迭代:S ≈ 半天内,M ≈ 1-2 天,L ≈ 3-5 天(含测试与文档)。
A 组:数据、地基与工程基础(后端)
T4-01 Flyway V6:creation schema 提取与 posts.generation_job_id 外键补回
- 仓库:patbond-api(迁移进
patbond-user/src/main/resources/db/migration/,单迁移链纪律backend-modules.md:41),patbond-doc(迁移说明) - 描述:从目标模型
docs/database/patbond_postgresql.sql:556-716提取creationschema 三表(generation_models/generation_styles/generation_jobs)为V6__creation_baseline.sql,含全部 CHECK、复合外键、11 条索引(两条队列 partial 索引 :711-715 必须逐字照抄)与三个updated_at触发器(:1245-1249,复用 V1 的platform.set_updated_at());补回community.posts.generation_job_id → creation.generation_jobs(id) ON DELETE SET NULL(V5:47 承诺的 M4 义务,索引ix_posts_generation_job已存在无需重建)。按 D4-4 决定input_asset_id是否保持NOT NULL、按 D4-6 处置model_version_snapshot的来源矛盾——两处偏离目标模型的地方必须在迁移文件头注释里逐条写明理由(沿 V5:1-33 的写法)。种子数据独立为不进生产链的 dev 脚本(沿db/dev/afterMigrate__dev_seed.sql先例)。 - 验收标准:
- 全新 postgres:18(Testcontainers)上 V1→V6 全量迁移一次成功;表结构与目标模型逐列比对一致(偏离项除外,差异入迁移说明)。
- 存量库实证:在已迁至 V5 的库上单独跑 V6 成功(沿 v0.4.0 的零迁移实证惯例
releases.md:36),flyway_schema_history只增一行。 - 新增迁移测试断言:
creationschema 存在、三表列数与关键 CHECK 名在位、posts上出现指向creation.generation_jobs的外键约束(与CommunityMigrationIntegrationTest.java:79-85现有的「不存在该外键」断言互为镜像,必须同步反转,否则该测试必红)。 ./mvnw -B clean test全绿(既有 379/381 测试不回归)。
- 依赖:D4-4、D4-6 拍板(第一波首项,两项决策不定则 V6 无法定稿)。
- 规模:M
T4-02 生成域模块落位与鉴权接入(形态随 D4-2)
- 仓库:patbond-api,patbond-doc(
architecture/backend-modules.md与 ADR-023 随收口) - 描述:按 D4-2 拍板结果落位生成域。PM 建议方案:沿 ADR-009/ADR-017 先例新建 Maven 模块
patbond-creation(:8085),复用 JWT 资源侧校验与当前用户解析(照抄patbond-community的security/与config/SecurityConfig.java结构),只读写creationschema,test 作用域引入 Flyway(照抄patbond-community/pom.xml:91-113的注释与依赖,生产侧不携带 Flyway);application.yml走.sample模式(pet/community 的主 yml 被.gitignore:10-11排除,compose 直接挂载.sample);compose 编排纳入第七个容器(depends_on: postgres service_healthy+user service_started)。若 D4-2 选择并入patbond-user,本单退化为「新增creation包 + 端点前缀 + 属性类」,规模降为 S。 - 验收标准:
- 模块编译入构建链,
./mvnw -B clean test全绿;无 token / 过期 token 返回 401 + 既有40100系错误码逐字节一致。 - compose 起七容器(postgres + minio + auth + user + pet + community + creation)全部 healthy;跨服务接线以 compose 实测验证(M3 的 T3-13 曾抓出「media 端点挂错服务,单测无法覆盖」,
iteration-3/29:45)。 backend-modules.md模块表与端口表同步更新;ADR-023 记录模块归属与 Worker 形态决策。
- 模块编译入构建链,
- 依赖:D4-2 拍板(骨架期变更成本最低,可先按建议方案搭);T4-01。
- 规模:M(若并入 user 则 S)
T4-03 对象存储服务端写入能力与媒体内部登记端点
- 仓库:patbond-api(
patbond-user/.../media/),patbond-doc(媒体链路说明补章) - 描述:本迭代最易被低估的一单,见 §2.5。现有
ObjectStorage(patbond-user/.../media/ObjectStorage.java:15-37)只有ensureBucket/presignPut/stat/presignGet,服务端无法写对象。本单:① 在ObjectStorage上新增写方法(建议void put(String objectKey, byte[] bytes, String contentType)或流式重载)与读方法(Optional<byte[]> get(...)或仅取图片头部字节,供 T4-09 探测尺寸),在S3ObjectStorage实现并给MediaStorageConfig.java:33-59的UnconfiguredObjectStorage补桩(保持未配置即 500 的既有语义);② 新增/internal媒体登记端点(建议POST /internal/media/assets),一次调用完成「写对象 + 插入media.assets行并直接置ready」,供生成域落盘输出——这样 ADR-017「media 上传流程实现在 patbond-user」的边界不被打破;③MediaProperties.allowedPurposes(MediaProperties.java:66,当前post_image,user_avatar,pet_avatar;YAMLapplication.yml:45)与 mime 白名单按 D4-14 扩充新用途(纯配置变更,media.assets.purpose无 CHECK 约束,无需迁移——ADR-022 已验证同一路径)。若 D4-2 选择并入 user,②可退化为内部服务方法调用。 - 验收标准:
- MinIO Testcontainer 全链路:服务端 put →
media.assets行status='ready'且ready_at非空 → 预签名 GET 可取回同一字节(sha256 比对)。 /internal/media/assets无X-Internal-Token/ 错误 token 返回 401 +TOKEN_INVALID(复用InternalAuthFilter.java:28,54-56的常量时间比较,未配置密钥时 fail closed 语义不变)。ck_media_location/ck_media_ready/uq_media_object (bucket, object_key)四条数据库约束与应用层校验一致,各有测试。- 未配置对象存储时行为与既有一致(500,不静默成功)。
- MinIO Testcontainer 全链路:服务端 put →
- 依赖:T4-01(不强依赖,可与之并行);D4-2 决定②的形态;D4-14 决定新 purpose 命名。
- 规模:M
T4-04 模型/风格只读目录端点与种子数据
- 仓库:patbond-api,patbond-doc(契约草案)
- 描述:实现模型与风格的只读目录(建议
GET /api/v1/creation/models、GET /api/v1/creation/styles,均支持mediaKind过滤,enabled=true过滤走 partial 索引ix_generation_models_kind_order/ix_generation_styles_kind_order,按sort_order, id排序,不分页——沿 care-reminders/breeds/vaccine-catalog 的既有例外先例openapi.yaml:98)。风格响应含previewUrl(preview_asset_id现签预签名 GET,与Pet.avatarUrl/AuthorSummary.avatarUrl同口径:三种情况为 null、键恒在)。种子按 D4-5 只入 image 模型与风格(目标模型 :1495-1508 的 5 条风格里 4 条为 image、1 条为 video);provider_code取值随 D4-1。风格预览图本身需要真实资产:种子引用的preview_asset_id在目标模型里指向 fixture asset,本单需明确预览图来源(建议随种子上传 4 张占位图并记录 assetId,或首版允许previewUrl为 null 由客户端回落纯色卡)。 - 验收标准:目录端点返回按
sort_order稳定排序;enabled=false的行不出现;mediaKind非法值 400/40000;previewUrl为 null 时键仍在;六类测试路径中适用的四类(成功/参数错/无权限/不存在)覆盖。 - 依赖:T4-01;T4-02。
- 规模:S
T4-05 provider 适配层与首个 provider 实现
- 仓库:patbond-api,patbond-doc(provider 接入说明 + ADR-023)
- 描述:依赖 D4-1 拍板,是关键路径起点。定义 provider 抽象接口(提交生成请求 → 返回
providerRequestId;轮询或回调获取状态与结果字节/URL;显式区分可重试失败与永久失败,后者不再消耗max_attempts);把供应商差异全部收敛在适配层(沿 ADR-016 对象存储适配层的同一手法:「代码经存储适配层隔离供应商」)。按 D4-1 实现首个 provider。PM 建议先实现 fixture provider:可配置延时与进度、返回内置图片字节、可注入失败/超时/慢响应用于测试——它同时是 CI 与 E2E 的唯一可用 provider(无外部依赖、无密钥、无成本)。若 D4-1 同时批准接入云 API,则云 provider 作为第二实现另计(见 T4-05b 说明:不单列工单,按 D4-1 结果并入本单并把规模提为 L)。 - 验收标准:
- 适配层接口下至少一个实现全链路可跑;provider 密钥一律经
.env注入、.sample占位,scripts/check-secrets.sh --all零命中(凭证防泄漏两层门禁在位,git-workflow.md:36-54)。 - fixture provider 可注入四类故障:可重试失败、永久失败、超时、返回损坏字节;各有测试。
- 日志不含 provider 密钥与完整 prompt 明文(
development-plan.md第 6.1 节日志纪律)。
- 适配层接口下至少一个实现全链路可跑;provider 密钥一律经
- 依赖:D4-1 拍板(未拍板期间可先写接口与 fixture 实现,明确止损线:适配层以上不写任何供应商特定代码)。
- 规模:M(若同时接云 API 则 L)
B 组:后端生成纵切
T4-06 生成任务创建:幂等键、快照与配额
- 仓库:patbond-api
- 描述:
POST /api/v1/creation/jobs,Idempotency-Key必带(development-plan.md:173强制;沿POST /api/v1/posts的既有形态openapi.yaml:1320与IdempotencyKeyRequiredHeader:1928-1944,同键异 payload → 409/40905)。落UNIQUE (user_id, idempotency_key)+request_hash(规范化请求体的 sha256,32 字节,ck_generation_jobs_idempotency校验)。校验链:模型/风格存在且enabled且media_kind一致(复合外键(model_id, media_kind)天然兜底)、输入 asset 属本人且status='ready'(复用 community 的MediaAssetGateway只读模式;非 ready → 422/42203 沿用既有MEDIA_NOT_READY)、宠物归属校验(若带petId)、尺寸在ck_generation_jobs_dimensions的 64~8192 内。四个 snapshot 列在创建时一次性冻结(provider_code_snapshot/provider_model_snapshot/model_version_snapshot/style_code_snapshot)——这是「失败原因可追踪」的基础:目录改了不影响历史任务的可复现性。配额检查按 D4-7(PM 建议直接COUNT本人窗口内的generation_jobs行,不新建计数表:幂等命中不新建行,故天然满足「相同幂等请求不重复扣费」)。任务落库即status='queued'(满足ck_generation_jobs_state的 queued 分支:progress=0、started_at/completed_at/output_asset_id/error_code/lease_*全 NULL)。 - 验收标准:
- 六类测试路径全覆盖(成功 / 参数错 / 资源不存在 / 无权限 / 并发冲突 / 幂等重试),走真实 PostgreSQL。
- 相同
Idempotency-Key并发 N 次只产生一行,且响应为同一jobId(多线程真并发测试,沿LikeBookmarkIntegrationTest.java:141,167的既有手法);同键异 payload 返回 409/40905。 - 引用他人 / 非 ready asset 被拒且错误码稳定;
enabled=false的模型被拒。 - 超配额返回 429 +
Retry-After(依赖 T4-11);幂等重试不消耗配额,有专项测试。 - snapshot 四列非空且与创建时刻的目录值一致;事后改目录不改历史行,有测试。
- 依赖:T4-01、T4-02、T4-04、T4-05(接口即可,不需 provider 实现完成);D4-7 拍板配额口径。
- 规模:L
T4-07 生成任务查询、列表与取消
- 仓库:patbond-api
- 描述:
GET /api/v1/creation/jobs/{jobId}(详情,含status/progress/errorCode/errorMessage/attemptCount/outputAsset现签 URL)、GET /api/v1/creation/jobs(我的生成记录,cursor 分页走ix_generation_jobs_user_created (user_id, created_at DESC, id DESC),禁 OFFSET,支持status白名单过滤)、DELETE或POST .../cancel(取消,语义随 D4-8 子项)。取消的状态机后果必须逐条对齐ck_generation_jobs_state(:667-692):cancelled要求completed_at IS NOT NULL且lease_*全 NULL——因此取消queued任务需同时写completed_at;取消running任务需清租约(此时 provider 侧可能仍在跑,须明确「取消是尽力而为,已产生的 provider 消耗不退配额」并写入契约描述);succeeded/failed/cancelled的重复取消为幂等 no-op 还是 422,随 D4-8 定型。他人任务一律 404(防枚举,沿POST_NOT_FOUND的既有做法openapi.yaml:45)。新增错误码建议:GENERATION_JOB_NOT_FOUND(40407, 404)、GENERATION_STATE_INVALID(42206, 422)。 - 验收标准:
- 五个状态各自的详情响应形态定型并入契约;
errorCode/errorMessage在 failed 态必非空、在其他态必为 null(与库层 CHECK 互证)。 - 分页不丢不重:以
limit=7多页与limit=100单页两次全量翻页,有序 id 列表逐位相等(沿 M3 T3-05 的取证手法iteration-3/29:39)。 - 取消四种源状态(queued / running / 已终态 / 他人任务)各有测试;取消后库层 CHECK 不被违反。
- 六类测试路径覆盖。
- 五个状态各自的详情响应形态定型并入契约;
- 依赖:T4-06。可与 T4-08 并行。
- 规模:M
T4-08 Worker 队列消费:租约、退避重试与幂等
- 仓库:patbond-api,patbond-doc(Worker 运行手册 + ADR-023)
- 描述:M4 的技术核心,两条验收标准(状态流转合法、Worker 重启不丢任务)的主责单。按 D4-3 实现 DB 队列消费(PM 建议):
- 领取:
SELECT … FROM creation.generation_jobs WHERE status='queued' AND next_attempt_at <= now() ORDER BY priority DESC, next_attempt_at, created_at, id LIMIT :n FOR UPDATE SKIP LOCKED(正对ix_generation_jobs_queue:711-713),随即写status='running'+started_at+lease_owner(实例标识)+lease_expires_at。 - 续租:长任务周期性延长
lease_expires_at并更新progress。 - 回收:独立扫描
status='running' AND lease_expires_at < now()(正对ix_generation_jobs_running:714-715),重置为queued。⚠️ 实现陷阱(§2.8):ck_generation_jobs_state的 queued 分支要求started_at IS NULL AND progress = 0,故回收时必须把started_at与progress清零——首次尝试的开始时间会丢失,须在代码注释与迁移说明中记录该取舍。 - 重试:
attempt_count + 1;未达max_attempts且属可重试失败 → 回queued并按指数退避写next_attempt_at;达上限或永久失败 →failed+error_code/error_message+completed_at。 - provider 幂等:
provider_request_id落库并受uq_generation_jobs_provider_request保护(:699-700),重试时优先查询已有 provider 请求状态而非重新提交——这是「相同幂等请求不重复扣费或生成」在 provider 侧的落点。 - 调度基座:
@EnableScheduling+ 显式TaskExecutor(全仓当前无任何线程池配置,§2.4),并发度、租约时长、退避参数、轮询间隔全部配置化。Worker 形态(同进程 vs 独立容器)随 D4-2。
- 领取:
- 验收标准:
- Worker 重启不丢任务:集成测试在
running态强杀 Worker(或直接令租约过期)后,任务被重新领取并最终succeeded;T4-25 E2E 用docker compose restart做真容器取证。 - 多 Worker 并发领取同一批任务,同一任务不被两个实例同时持有(
SKIP LOCKED+ 租约双重保证),有并发测试。 - 四类 provider 故障(可重试 / 永久 / 超时 / 损坏输出,由 T4-05 fixture 注入)各自的终态与
attempt_count正确;退避间隔递增有测试。 - 全部状态迁移穷举测试:每条合法迁移成功、每条非法迁移被应用层拒绝且被库层 CHECK 兜底(两层各有断言)。
- 队列积压与 Worker 失败率可查(
development-plan.md:329的最小兑现:SQL 可查 + 结构化日志留痕;完整指标栈 M6,见 §7)。
- Worker 重启不丢任务:集成测试在
- 依赖:T4-06、T4-05;T4-03(输出落盘经 T4-09);D4-2、D4-3 拍板。
- 规模:L
T4-09 生成输出落媒体表与真实尺寸回填(含 widthPx/heightPx 遗留清偿)
- 仓库:patbond-api,patbond-doc(契约描述修订)
- 描述:Worker 成功后把 provider 输出经 T4-03 的写能力落
media.assets(purpose随 D4-14、owner_user_id= 请求用户、storage_type='object'、status='ready'+ready_at),回填generation_jobs.output_asset_id并置succeeded(ck_generation_jobs_state要求 succeeded 时progress=100、started_at/completed_at/output_asset_id非空、error_code/lease_*为 NULL——五个条件同事务写齐)。AI 输出的width_px/height_px天然已知(请求参数 + provider 返回),直接写入。同时按 D4-9 处置 M3 遗留:PM 建议在POST /api/v1/media/uploads/{assetId}/complete里补服务端尺寸探测(读对象头部字节解析 JPEG/PNG/WebP 尺寸),使契约openapi.yaml:3536的「complete 后回填」名副其实;同时把PostMediaItem.widthPx/heightPx(:3605-3610,当前无 description)补上口径说明。本单不做:视频时长探测(D4-5 剪出视频)。 - 验收标准:
- 生成成功后
media.assets行status='ready'且width_px/height_px与请求尺寸一致;预签名 GET 可取回字节。 - 五个 succeeded 字段条件同事务写齐;中途失败不留「有 output 但非 succeeded」的中间态,有测试。
- 用户上传路径的尺寸回填:jpeg/png/webp 三种 mime 各有测试,探测失败时回落 null 且不影响
ready(向后兼容,MediaAsset.required不含尺寸字段)。 - 单图帖不再一律回落 4:3(M3 观察项 2 闭环,
iteration-3/29:56)——客户端侧验收在 T4-19/T4-21。
- 生成成功后
- 依赖:T4-03、T4-08;D4-9 拍板。
- 规模:M
T4-10 一键建社区草稿:ai_creation 写侧开放与 generationJobId 打通
- 仓库:patbond-api(
patbond-community),patbond-doc(契约) - 描述:打通「生成成功 → 社区草稿」。改动面(§2.7 已逐处定位):①
CreatePostRequest.java:26的@Pattern(regexp = "general|help")放开为含ai_creation;②CreatePostRequest新增generationJobId;③PostRepository.insertPost(:56-58 签名、:60-64 INSERT 列清单)与PostService(:88 category 默认值、:98/:102 canonicalize 与插入、:151/:178 更新路径)补该列;④PostResponse.java与FeedCardResponse按 D4-8 决定是否外露generationJob(当前PostResponse.java:12注明该字段完全不出现)。归属与防伪校验:generationJobId必须属于当前用户且status='succeeded',否则 404/40407;按 D4-8 定型「category='ai_creation'是否强制要求带generationJobId」(PM 建议强制,防止用户伪造 AI 分类)。草稿创建沿用既有两步语义(status='draft'创建 →PATCH status='published'发布,openapi.yaml:3660-3664、:3701-3707),本单不新增发布路径。 - 验收标准:
- 生成任务 → 建草稿 → 发布 → Feed 可见全链路走真实 PostgreSQL;
posts.generation_job_id落库正确且外键约束生效(引用不存在的 jobId 被库层拒绝)。 - 他人 jobId / 未成功 jobId / 无 jobId 但
category='ai_creation'三种非法组合各有测试与稳定错误码。 ai_creation帖在 Feed 与详情的读侧形态与general一致(FeedCard.category枚举早已含三值openapi.yaml:3826,无需改读侧枚举)。- 既有 107 个 community 测试不回归;契约矩阵新增格全部被真实触发(
everyDeclaredResponseCellIsExercised门禁)。
- 生成任务 → 建草稿 → 发布 → Feed 可见全链路走真实 PostgreSQL;
- 依赖:T4-01(外键)、T4-06/T4-09(succeeded 任务);D4-8 拍板。
- 规模:M
T4-11 生成域配额与限流:429 + Retry-After(清偿 M3 遗留)
- 仓库:patbond-api(
patbond-common加错误码 + 生成域实现),patbond-doc(契约首个响应头) - 描述:契约史上第一个响应头声明——
openapi.yaml全文当前零处headers:键(§2.13 核实)。新增RATE_LIMITED(42900, 429, …)到ErrorCode.java(该文件当前无 429 段),按 D4-7 实现配额与限流:建议 per-user 并发上限(COUNT本人queued|running行)+ 每日次数上限(COUNT本人当日created_at行),均直接查generation_jobs不新建表;超限返回 429/42900 +Retry-After(秒数,指向下一次可提交时间)。无 Redis 的现实约束(§2.4)决定了计数只能落 PG 或进程内——建议落 PG(多实例正确,代价是每次创建多一次 COUNT,走ix_generation_jobs_user_created)。同时清偿客户端侧遗留:lib/core/network/api_client.dart:164-167已有 429 粗分支但无Retry-After解析,lib/analytics/analytics_service.dart:268-272注释明写等待后端落地——本单交付后由 T4-16 收口客户端分支。本单不做:全站通用限流(属 M6「限流、审计、结构化日志」范围),只做生成域。 - 验收标准:
- 超并发 / 超日限两种超限各返回 429 +
Retry-After,头值可解析为正整数秒;有集成测试。 - 幂等重试不计入配额(同
Idempotency-Key重试在超限后仍返回原任务而非 429),有专项测试——这是「相同幂等请求不重复扣费」的一半取证。 - 契约新增 429 响应与
Retry-After头声明;契约测试覆盖该格(新增矩阵格必须被真实触发)。 - 客户端 429 分支与
Retry-After尊重在 T4-16 有测试。
- 超并发 / 超日限两种超限各返回 429 +
- 依赖:T4-06;D4-7 拍板具体数值。
- 规模:M
T4-12 uploading 超时未确认 asset 清理定时任务(清偿 M3 遗留)
- 仓库:patbond-api(
patbond-user),patbond-doc(feature-checklist 转绿) - 描述:M3 T3-13 已有方案未实现(
iteration-3/29:58、feature-checklist.md:193)。本单实现@Scheduled清理:把超过阈值仍为status='uploading'的 asset 置failed(或按方案置deleted+deleted_at,须满足ck_media_deleted),并按需删除孤儿对象。搭车理由:M4 本就要建 Worker 调度基座(T4-08),且@Scheduled已有先例可照抄(SessionCleanupJob.java:32-41的幂等、可多实例并跑写法 +application.yml:24-26的配置形态);同时 AI 输入图会放大孤儿资产量(每次生成都要先传一张图),此项从「可选清理」升为「成本控制项」。索引已就绪:ix_media_uploading_created(V1:255 区域)。 - 验收标准:阈值内的 uploading 不被误清;超阈值被置终态且
identity.users.avatar_asset_id/pet_health.pets.avatar_asset_id/post_media/generation_jobs.input_asset_id等引用方不出现悬空引用(外键为 RESTRICT 的路径须验证清理不会失败);任务幂等、多实例并跑无害,有测试。 - 依赖:T4-02(或直接在 user 模块内);与主线解耦,可任意波次插入。
- 规模:S
C 组:契约与测试
T4-13 OpenAPI v1.5.0 扩展与冻结(本波闸门)
- 仓库:patbond-doc(
docs/api/openapi.yaml),patbond-api(四份或五份快照字节级同步 + 守卫测试期望值更新) - 描述:沿用三个迭代验证过的迭代式契约冻结:第一波按 §1.1 与目标模型出草案(放在
iterations/iteration-4/openapi-creation-draft.yaml,沿 M2/M3 的openapi-pets-draft.yaml/openapi-community-draft.yaml先例),以TODO-FREEZE标注四处待定型点——① 生成任务响应的五态字段形态、②Retry-After头与 429 的声明形态(契约首个响应头)、③generationJob在Post/FeedCard的外露程度、④ 新purpose枚举值命名;随 T4-06/07/09/10/11 实现定型回填 → 拍板 → 冻结合入 + api 侧快照同步升版。 变更清单(对 v1.4.0 应为纯增量,延续 v1.4.0 写入info.description的承诺):新增生成域路径与 schema;CreateMediaUploadRequest.purposeenum 增值(:3458);CreatePostRequest.categoryenum 增ai_creation(:3657,并删去 :3659 那句「M3 不开放写入」);CreatePostRequest/Post新增generationJobId/generationJob;MediaAsset.widthPxdescription 修订(:3536);新增错误码40407/42206/42900入info.description错误码表(:35-62,现 27 个码);新增 429 响应与Retry-After头。 同步义务(缺一 CI 必红):四份守卫测试的期望值1.4.0 / 32 / 45 / 75必须整体更新(AuthContractConformanceTest.java:399-407、MediaContractConformanceTest.java:246-251、petContractConformanceTest.java:755-760、CommunityContractConformanceTest.java:522-527),四份OpenApiContract.java的RESOURCE常量(pet:35 / auth:39 / user:39 / community:39)须指向openapi-v1.5.0.yaml;若 D4-2 新建模块则为五份。 - 验收标准:契约评审通过;四/五份快照 md5 与正典完全一致;守卫测试与
everyDeclaredResponseCellIsExercised双门禁全绿、矩阵零漂移;新增格全部被真实触发(不得豁免);mkdocs build --strict零 warning;冻结后任何变更须显著上报两端同步。 - 依赖:草案仅依赖目标模型;冻结须 T4-06/07 状态形态 + T4-09 输出形态 + T4-10 权限语义 + T4-11 限流形态定型。不冻结不放行第三波两端联调。
- 规模:M
T4-14 契约快照锁补强:从「计数守恒」到真校验和
- 仓库:patbond-api(四/五模块测试),patbond-doc(冻结纪律补章)
- 描述:§2.13 核实的缺口——CI 只锁
info.version+ 3 个计数 + tag 分组,md5 比对是人工冻结步骤,任何「计数守恒的字节修改」(改 description、改 enum 取值、改 min/max、同时增删各一字段)都能悄悄通过。本单在每模块守卫测试中补一条快照文件校验和断言(对src/test/resources/contract/openapi-v1.x.y.yaml算 sha256 与硬编码期望值比对),把「字节级快照锁」从人工纪律变成 CI 门禁。注意:期望值需在 T4-13 冻结后填入,故本单分两步——先在 v1.4.0 上落机制并验证能抓出注毒(可先行于第一波),再随 T4-13 更新期望值。 - 验收标准:定向注毒自证——对快照做一处「计数守恒」的字节修改(如改一句 description),断言必红;恢复后转绿。四/五模块各有该断言。文档纪律更新为「升版须同步快照 + 更新校验和」。
- 依赖:无(机制可先落);期望值随 T4-13。
- 规模:S
T4-15 后端集成测试滚动补齐与 CI(横切单)
- 仓库:patbond-api
- 描述:随 B 组滚动补齐 Testcontainers 集成测试与契约一致性测试(机制复用既有三层结构:纯单测 / 契约一致性 / Testcontainers postgres:18 + 一处 MinIO 容器)。本迭代专项矩阵:① 状态机穷举(五态两两迁移的合法与非法各一格);② 队列并发(多 Worker 领取不重叠、
SKIP LOCKED有效性);③ 租约过期回收(含started_at清零后仍满足 CHECK);④ 幂等三层(应用层键、DB 唯一约束、provider 请求 id);⑤ 配额边界(超限 / 幂等重试不计数 / 跨日窗口翻转)。MinIO 容器使用面因 T4-03/T4-09 扩大,记录 CI 时长;若超阈值按模块分层执行,但不降低「提交前全绿」标准。 - 验收标准:每个新业务接口覆盖六类路径;Gitea Actions 全绿(以 commit status API 实查为准,沿 M2/M3 惯例——
iteration-3/29:47记录过「一个 agent 识破 Monitor 的假 success 事件」的教训);CI 时长记录在案。 - 依赖:随 T4-04~T4-12 滚动。
- 规模:M(分摊在各单内)
D 组:Flutter 客户端
T4-16 creation feature 分层与 API Client
- 仓库:patbond-flutter
- 描述:按
development-plan.md:96已预留的creationfeature 拆分(lib/features/creation/),对齐 community/pets 的既有结构(Page → Controller → Repository → ApiClient,约定原文community_controller.dart:14-20;状态用原生ChangeNotifier,DI 手写进lib/app/app.dart:80-147)。依 T4-13 冻结契约实现 DTO 与 Client(模型/风格目录、任务创建/详情/列表/取消)。若 D4-2 新建模块,须新增第五条 baseUrl:patbondCreationApiBaseUrl+PATBOND_CREATION_API_BASE_URL(照抄api_client.dart:8-34的四条现有写法),并同步lib/app/app.dart装配、四份 E2E 脚本的地址常量、device-verification.md通用前置的flutter run参数说明(当前写「四个 base URL 全传」,须改为五个)。新增AnalyticsPageName枚举值(lib/analytics/analytics_page_name.dart:7-25;不在枚举内fromRouteName返回 null 即不上报)。顺带收口 429:api_client.dart:164-167现有的ApiRateLimitException补Retry-After解析与承载(T4-11 交付后),并按注释analytics_service.dart:268-272的承诺一并处理埋点上传侧的Retry-After分支。 - 验收标准:DTO 映射(含五种 status、null 尺寸、null previewUrl)有单元测试;错误映射类型化(新增
40407/42206/42900各有分支);Retry-After解析(有头 / 无头 / 非法值)有测试;UI 无关骨架可先行。 - 依赖:T4-13 冻结(骨架部分可提前与后端并行)。
- 规模:M
T4-17 create 页替换:真实目录、输入与提交
- 仓库:patbond-flutter
- 描述:替换
lib/features/create/create_page.dart(563 行、零测试、全假,逐处清单见 §2.14)。保留可用的四块视觉骨架(模式分段器 / 风格横滑卡 / 进度条 / 结果编辑区),只换数据源与状态源:模型下拉与风格卡改读 T4-04 目录(删掉:179硬编码模型数组与demo_data.dart:206-234的 unsplash URL 依赖);假上传(:55-64)改接MediaUploader(purpose用 T4-03 新增的 AI 输入用途,单图单并发,照抄头像 sheet 的maxImages:1, maxConcurrentUploads:1用法);prompt 输入、尺寸与高清增强开关映射到真实请求字段(prompt/negativePrompt/widthPx/heightPx/upscale/parameters);提交携带客户端生成并在重试间保持的Idempotency-Key(照抄发布页post_compose_page.dart:267的_idempotencyKey ??= _uuid.v4()手法)。视频模式按 D4-5 处理(建议:分段器保留但 video 项禁用并给「即将上线」提示,而非删除——避免 UI 大改,也与media_kind保留枚举一致)。搭车主题债:给主题补segmentedButtonTheme,选中态改用app_theme.dart:199-214已审计色对表中的色对(_datePickerTheme:215-296 是落地范本),一次修复全部 6 处SegmentedButton使用点(create/home/services/pet_form×2/vaccination_form)。 - 验收标准:
- 页面不再读任何 demo 常量(
demo_data.dart、AppState.pet依赖清零);grep断言无Future.delayed假延时残留。 - 目录加载四态(loading/empty/error/retry)齐备并有 widget 测试——该页当前零测试,本单是从 0 建测试基线。
- 提交校验与错误提示:非法尺寸、未选风格、未传图、超配额 429(含
Retry-After文案)各有测试。 SegmentedButton选中态对比度达 WCAG AA 并在已审计色对表中登记新增行;6 处使用点视觉一致。
- 页面不再读任何 demo 常量(
- 依赖:T4-16;T4-04/T4-06 联调。
- 规模:L
T4-18 生成状态机 UI:排队、进度、失败重试与取消
- 仓库:patbond-flutter
- 描述:
development-plan.md:253的直接落点(「Flutter 展示排队、生成进度、失败重试和取消状态」)。实现任务状态轮询与展示:queued(排队中 + 队列位置或预计时间,视 T4-07 是否外露)、running(真实progress驱动进度条,替换:532-563现有的step/4假进度与 4 条硬编码 label)、succeeded(转 T4-19)、failed(errorCode/errorMessage可读化 + 重试按钮,重试语义随 D4-8:是新建任务还是复用同一任务须定型)、cancelled。轮询策略需明确:间隔、退避、页面不可见时暂停、离页后是否继续(建议离页即停 + 回页重取,避免后台耗电);尊重Retry-After。断网与超时的四态齐备。 - 验收标准:
- 五种状态各有 widget 测试;状态迁移驱动的 UI 变化(queued→running→succeeded / →failed)有测试。
- 轮询在页面不可见时暂停、回前台恢复,有测试(M3 曾用 widget 测试抓出「回前台不开新曝光段」的真 bug,
iteration-3/29:46——同类风险在此复现,须专项覆盖)。 - 失败重试与取消的乐观/悲观策略明确且不假成功(离线操作提示明确,沿 M3 T3-17 的既有纪律)。
progress为真实值(非一跳 100%),有测试。
- 依赖:T4-16、T4-17;T4-07/T4-08 联调。
- 规模:L
T4-19 结果页与一键建社区草稿
- 仓库:patbond-flutter
- 描述:生成成功后的结果展示与去向。复用现有结果区布局(
create_page.dart:312-386)但换真实数据:输出图用outputAsset.url(预签名 GET,缓存 key 须剥X-Amz-*签名参数——沿用已有的presignedImageCacheKey机制,否则每次刷新重下,该机制的回归判据已写进device-verification.md:190第 2 项);按真实widthPx/heightPx渲染宽高比(T4-09 交付后不再一律 4:3);删掉硬编码位置「北京市 · 朝阳区」(:369-374)与硬编码文案(:87-90)。「发布到社区」从占位 SnackBar(:122-129)改为真实调用 T4-10:建草稿并跳转PostComposePage预填(图与generationJobId已挂),由用户在既有发布页完成文案与发布——不在本页做完整发布流程(避免与发布页的草稿/幂等/媒体逻辑重复实现)。 - 验收标准:结果图真实渲染且宽高比正确;预签名 URL 不被持久化(纪律 R2)、过期可重取;一键建草稿 → 发布页预填 → 发布 → Feed 出现全链路真实后端;失败四态齐备;有 widget 测试。
- 依赖:T4-16、T4-18;T4-09/T4-10 联调。
- 规模:M
T4-20 我的生成记录列表(条件单,随 D4-11 之外的独立取舍)
- 仓库:patbond-flutter
- 描述:若纳入:接 T4-07 的列表端点做「我的作品/生成记录」页(cursor 分页四态,照抄
lib/features/pets/health_events_page.dart:19,71-127的_ListPhase范本),支持按 status 过滤、点击进详情、失败任务可重试、可删除记录(随 D4-14)。PM 倾向纳入:它是用户排查失败任务与找回历史输出的唯一入口,缺它则一次生成失败后用户无处可查(「失败原因可追踪」在客户端侧没有落点)。若周期紧张可后置,但须在收官总结记范围缺口。 - 验收标准:分页不丢不重(widget 测试模拟游标);四态齐备;状态与详情页同源一致。
- 依赖:T4-16、T4-18。
- 规模:S
T4-21 我的收藏与草稿两页(M3.5 遗留搭车)
- 仓库:patbond-flutter
- 描述:清偿
iteration-3.5/06:71遗留(「后端与仓库层均已就位,缺两个页面 + 导航」)。两页均照抄_ListPhase+ cursor 分页范本:我的收藏接listMyBookmarks()(community_repository.dart:52,274-283,当前全仓零调用点,项形态为FeedCard);我的草稿接listMyPosts(status: draft)(:26-30,179-193,当前唯一调用点是发布页取limit:1恢复最新草稿)。入口替换lib/features/profile/profile_page.dart:49菜单项的 demo SnackBar(:235showDemoMessage);顺带把「我的作品」统计数字(:307-310,当前不可点)接成可点入草稿/已发布列表。本单不做:草稿自动保存(见 §7)。 - 验收标准:两页分页不丢不重、四态齐备、有 widget 测试;收藏页的取消收藏与 Feed/详情状态同源一致(复用既有
toggle_sync.dart乐观更新机制);profile 页不再有该项 demo SnackBar(有反向断言)。 - 依赖:无(与 M4 契约零耦合,第一波即可并行消化)。
- 规模:M
E 组:埋点、实验、遗留与收口
T4-22 埋点字典 v4:AI 创作漏斗 + eventVersion 口径定型 + 白名单计数纠偏
- 仓库:patbond-flutter(挂接)、patbond-api(白名单扩充)、patbond-doc(字典)
- 描述:事件定义以 Experiment Tracker 的 M4 埋点方案为准(本单不自造字典;沿用「结果编码进事件名」惯例与 ADR-013 纪律)。预期覆盖
development-plan.md:330要求的「AI 任务成功」漏斗:创作页进入 → 输入图上传(复用既有post_media_upload_*三段还是新增独立事件,由埋点角色定)→ 任务提交成功/失败 → 生成成功/失败/取消 → 建草稿 → 发布。隐私红线:props 不含 prompt 明文、不含 assetId/jobId、不含精确字节数(沿 M3 红线,服务端EventDictionary.java:38-39,114-116+ 客户端analytics_service.dart:288-295双层过滤在位)。 搭车三项修正:①eventVersion口径定型(M3 观察项 2,iteration-3/29:57、feature-checklist.md:222)——契约描述openapi.yaml:2341-2344可两读、服务端零取值校验(TrackEventsRequest.java:38-39仅@NotNull无@Min,唯一取值校验在 DB 的V2:22)、客户端硬编码 1(analytics_service.dart:139);定型为「事件 schema 版本」、写入字典纪律、并决定是否补@Min(1)与契约minimum。② 白名单计数纠偏:实测 41 条而文档三处写 42(§2.10),修正iteration-3/22-event-whitelist-v3.md:4,14、iteration-3/29-m3-summary.md:20、feature-checklist.md:218,并在EventDictionaryTest.java补一条总量断言(当前无任何hasSize)永久钉住。③ 新增事件同步进 v4 字典文档与EventDictionary.java,并对刻意不做的事件名(如逐帖曝光类)维持「不在白名单即 unknown」的锁死机制。 - 验收标准:关键动作事件端到端落库(compose 实测或 curl 造真实 payload,沿 M3 的 §5c 取证惯例);白名单与字典文档条数一致且有总量断言;
eventVersion定型结论写入字典纪律与契约描述;三处文档计数修正;不回归既有 597 前端测试。 - 依赖:Experiment Tracker 方案;随 T4-17~T4-19 滚动挂接。
- 规模:M
T4-23 A/B 最小分流设施与首个实验(条件单,随 D4-11)
- 仓库:patbond-flutter、patbond-api、patbond-doc
- 描述:若 D4-11 拍板启动首个实验,须先补 §2.9 核实的三处零实现:① 稳定分流组件(前置 #4 的缺失半边)——
hash(userId 或 anonymousId, experimentSalt) % buckets,建议纯客户端确定性哈希(anonymousId持久化已在analytics_service.dart:51,177-190就位),避免为一个实验搭服务端下发设施;② Flutter 曝光封装(前置 #5 的缺失半边)——experiment_exposed事件的强类型封装(服务端字典EventDictionary.java:103已就位、props{experimentKey, variant},客户端零命中),触发时机按iteration-3/06:145的既定口径「用户实际到达实验触点时(渲染了变体 UI),非分配时」;③ 开关与回滚(前置 #7 的「回滚绿」实无依据)——最小实现为客户端可远程或配置关闭实验、回落对照组。首个实验对象建议取低风险、纯展示层的项(如 create 页风格卡的默认排序,或结果页「发布到社区」的引导文案),不实验后端生成逻辑(成本与状态机风险)。 - 验收标准:同一用户多次进入分到同一变体(确定性有测试);
experiment_exposed落库且 props 键集与字典一致;关闭开关后全量回落对照组,有测试;实验设计与样本量按既有模板评审(注意:该模板文档在两仓中未定位到实体,见 §2.18,须先补或现场约定)。 - 依赖:D4-11 拍板;T4-17(触点所在页面);T4-22(字典)。
- 规模:M
T4-24 真机验证 M2 两项(硬时限 2026-09-21)
- 仓库:patbond-doc(执行记录回填)
- 描述:本迭代唯一带外部硬时限的工单。执行
device-verification.md:42(验证一:Android 事件真实落库,~10 分钟)与:63(验证二:SessionTracker 30 分钟后台换会话,~45 分钟含等待),回填:104的「M2 项执行记录」。时限来自北极星首次出数日 2026-09-21(iteration-2/06-experiment-tracking-plan.md:308-312)与埋点角色的提醒(iteration-3/29:52:未完成则首批读数只能标未验收)。今日 09-14,仅剩 7 天,本单必须置于第一波首日。 降级方案(做不到时):① 首批北极星读数(W37 队列)在数据报告中显式标注「埋点落库未经 Android 真机验收,读数仅作趋势参考」;② 把复评点推到下一个成熟窗口(约 H14,即再迟 7 天);③ 用桌面/模拟器覆盖能覆盖的部分并明确记录不可替代的缺口——注意device-verification.md:144已言明 Linux 桌面的platform=linux不在契约枚举内、整批 400 被拒,故事件落库这一项桌面根本无法替代,降级只能降到「标注未验收」,不能降到「换环境验证」。 - 验收标准:两项的通过标准(
:59-61、:82-83)逐条勾选并回填执行记录(日期、机型/Android 版本、psql 输出脱敏摘录、执行人);若未执行,则降级方案的三项动作全部落地并在收官总结显式记录。 - 依赖:Android 真机到位(外部依赖,PM 无法消除,故列 D4-13 请用户拍板)。
- 规模:S
T4-25 第五份 E2E 脚本:test_e2e_m4_manual.dart
- 仓库:patbond-flutter(脚本)、patbond-api(compose 环境)、patbond-doc(证据归档)
- 描述:按
releases.md:56的既定原则(「每个引入对外端点的迭代都应有对应的 E2E 脚本,并在此后每次发布回归」),M4 引入生成域对外端点,须新增第五份。照抄现有四份的同构骨架(纯 Dart CLIdart run、fail()/check()、_passed计数、Resp包装、通用call()、脚本内现注册账号取 token、token 截断与预签名 URL 签名<SIGNATURE_REDACTED>脱敏、内联小图 fixture——注意iteration-3.5/06:56的教训:1×1 极小 PNG 能传能下但 Flutter 解码器拒绝,夹具须 ≥16×16)。场景对齐 M4 四条验收标准:- 目录读取(模型/风格,含
enabled过滤与排序) - 传图 → 提交生成任务 → 轮询至
succeeded→ 输出 asset 可预签名取回且widthPx/heightPx非空 - 相同
Idempotency-Key重提 → 同一 jobId、不新增行、不消耗配额 - Worker 重启不丢任务:任务处
running时docker compose restart生成域容器 → 任务被重新领取并最终成功 - 非法状态迁移被拒(对 succeeded 任务取消、对 queued 任务重复取消等)
- provider 失败 → 重试 → 达上限 →
failed且errorCode/errorMessage/attemptCount可查 - 取消 queued 与取消 running 各一
- 超配额 → 429 +
Retry-After - 一键建草稿 →
posts.generation_job_id落库 → 发布 → 另一账号 Feed 可见(category='ai_creation') - 他人 jobId / 未成功 jobId 建草稿被拒 另需在脚本头补上第五条 baseUrl 常量(若 D4-2 新建模块)。
- 目录读取(模型/风格,含
- 验收标准:全场景绿;契约偏差 0;M4 四条验收标准逐条有证据(HTTP transcript 脱敏 + psql 库层交叉核对,沿 M3 T3-21 的双源取证强度);连跑 3 轮零 flake。
- 依赖:T4-06~T4-11、T4-19。
- 规模:M
T4-26 五份 E2E 回归与 v0.5.0 发布(PR 流程)
- 仓库:三仓
- 描述:按
releases.md:126-138的新流程发布(v0.4.0 已实测过一轮):① 三仓 CI 绿 + 五份 E2E 同环境串行回归 PASS(M1 7 + M2 11 + M3 14 + M3.5 10 + M4 约 10 = 约 52 场景);② Gitea 建 PRdev → main(标题写版本号,正文贴门禁证据链接);③ 等状态检查转绿(CI / backend-test、CI / flutter-gates,releases.md:110-114);④ 合并(须为快进,若显示分叉先查明原因不得用合并提交掩盖);⑤ 打 tag + 登记发布记录。版本号建议 v0.5.0(功能性里程碑,随 M4 收官)。 - 验收标准:五份回归全绿并出回归报告(沿
iteration-3.5/08-release-e2e-regression.md格式);PR 快进合并、历史线性、零合并提交;三仓 tag 一致;releases.md追加记录含三仓 tag/哈希、门禁证据、已知遗留。 - 依赖:T4-25 及全部交付单。
- 规模:M
T4-27 文档与迭代收口
- 仓库:patbond-doc
- 描述:OpenAPI v1.5.0 归档;
architecture/backend-modules.md更新(模块表/端口表/生成域归属与 media 写入边界);architecture/decisions.md新增 ADR-023 起(AI 提供方、模块与 Worker 形态、队列载体、输入图必填与视频剪出、配额与限流口径——每条拍板决策一条 ADR 或合并为一条复合 ADR);feature-checklist.md新增 M4 章节(当前 AI 创作零条目,仅 :183/:208 两处提及)并把清偿项转绿(:193 清理任务、:194 429 限流、:211 widthPx/heightPx、:222 eventVersion、:243 收藏与草稿页)、把已过期条目纠正(:145「auth 域契约测试补齐 ⬜」实际 M3 T3-19 已交付);device-verification.md新增 M4 节(真机专属项预登记)并回填 M2 执行记录;迭代报告归档与收官总结。 三项文档纪律修正:①releases.md:131的「E2E 双份回归」与 :36/:56 的四份口径自相矛盾,须改为五份;② 把「每个引入对外端点的迭代都应有对应 E2E 脚本并在每次发布回归」这条原则固化进git-workflow.md(该文件 :56-64 的门禁表当前无 E2E 条目,原则只存在于releases.md:56的一句叙述里);③patbond-doc补仓库级.gitignore(当前无该文件,site/仅靠用户全局~/.gitignore_global:183排除,换机器或 CI 检出即可被误提交,与git-workflow.md:62冲突)。iteration-4目录的 mkdocs.yml 导航由文档维护者收口提交统一添加(本拆解报告不改 mkdocs.yml)——参考 iteration-3.5 的挂法(mkdocs.yml:100-108,节点名脱离「第 N 迭代」序列、无index.md进展看板行),插入位置在 :108 之后、:109- API:之前。 - 验收标准:
mkdocs build --strict零 warning;报告索引完整;三项文档纪律修正各有对应 diff;ADR 编号连续无冲突。 - 依赖:各波交付。
- 规模:S
T4-28 文档站访问控制(条件单,随 D4-12)
- 仓库:服务器侧配置 + patbond-doc(
server-exposure.md状态更新) - 描述:若 D4-12 拍板纳入:给文档站加 basic auth 或 IP 白名单。登记原文
server-exposure.md:32:「✅ 运行;⚠️ 公开可访问,待评估是否加 basic auth 或 IP 白名单(无凭证内容,但暴露内部架构细节)」。实现面为 nginx 配置(sites-enabled/已按域名分流,:31)+ 凭据经.env/密码文件不入库(沿 2026-09-11 安全事件后固化的服务器侧纪律,iteration-3.5/07)。本迭代的新论据:M4 报告将暴露 AI 提供方选型、密钥管理方式、配额策略与队列实现细节,公开可读的价值收益低于信息暴露成本。 - 验收标准:未授权访问返回 401/403;授权后文档站功能不变;
server-exposure.md:32状态位更新为已处置并记录核对命令;凭据不入库(check-secrets.sh --all零命中)。 - 依赖:D4-12 拍板。不占关键路径(纯运维项)。
- 规模:S
5. 波次划分与关键路径
沿用已验证模式:波次并行 + 迭代式契约冻结 + 同仓串行跨仓并行 + 每波 compose 实测。
第一波(并行开工)
| 并行线 | 工单 | 说明 |
|---|---|---|
| 硬时限 | T4-24 真机 M2 两项 | 首日执行,2026-09-21 前必须闭环或启动降级;不占技术关键路径但占人 |
| 数据与骨架 | T4-01 → T4-02 | V6 + 生成域落位,一人连续负责;需 D4-2/D4-4/D4-6 开工前拍板 |
| 存储写能力 | T4-03 | 与 T4-01 并行;是 T4-09 的前置,不要等到第二波才开 |
| provider 适配 | T4-05 | 需 D4-1 拍板;未拍板时先写接口 + fixture,止损线:适配层以上不写供应商代码 |
| 目录 | T4-04 | T4-01 后即可 |
| 契约草案 | T4-13(起草态) | 四处 TODO-FREEZE 标注 |
| 门禁补强 | T4-14(机制部分) | 先在 v1.4.0 上落校验和断言并注毒自证 |
| 前端遗留 | T4-21 我的收藏与草稿 | 与 M4 契约零耦合,第一波并行消化 |
| 清理任务 | T4-12 | 与主线解耦,可插任意空档 |
| UI 设计 | 生成状态五态与结果页设计稿 | 供 T4-17~T4-19,不占关键路径 |
第二波(后端纵切,契约收敛)
| 并行线 | 工单 | 说明 |
|---|---|---|
| 后端主线 | T4-06 → T4-08 / T4-07(06 后两线并行) | T4-06 是全部生成单的前置 |
| 输出落地 | T4-09 | 依 T4-03 + T4-08 |
| 草稿打通 | T4-10 | 依 T4-01 外键 + succeeded 任务 |
| 限流 | T4-11 | 依 T4-06;契约首个响应头,须早于冻结定型 |
| 前端骨架 | T4-16(不依赖契约部分) | Repository/DTO 骨架先行 |
| 测试滚动 | T4-15 | 即测即绿即提交 |
波末闸门:T4-13 契约冻结 v1.5.0(条件:T4-06/07 状态形态 + T4-09 输出形态 + T4-10 权限语义 + T4-11 限流与 Retry-After 形态四处全部定型;四/五份快照同步升版 + 守卫测试期望值整体更新 + T4-14 校验和期望值填入)。不冻结不放行第三波两端联调。
第三波(冻结契约下两端并行)
| 并行线 | 工单 | 说明 |
|---|---|---|
| 前端主线 | T4-16(完成)→ T4-17 → T4-18 → T4-19;条件单 T4-20 | 创作页先通,状态机与结果页才有意义 |
| 埋点/实验 | T4-22;条件单 T4-23 | 随页面落地滚动挂接 |
| 后端旁路 | 契约测试补齐、队列并发压测、provider 故障注入矩阵 | 不占关键路径 |
第四波(收官)
T4-25 第五份 E2E → T4-26 五份回归与 v0.5.0 发布 → T4-27 文档收口;条件单 T4-28 可并行。
关键路径
[D4-1/D4-2/D4-3/D4-4 拍板]
→ T4-01(M) → T4-03(M) → T4-05(M) → T4-06(L) → T4-08(L) → T4-09(M)
→ [T4-13 契约冻结闸门]
→ T4-16(M) → T4-17(L) → T4-18(L) → T4-19(M) → T4-25(M) → T4-26(M)
四个 L 工单串在关键路径上(T4-06 / T4-08 / T4-17 / T4-18),后端队列侧与客户端状态机侧各占其二——与 M3 的「媒体双端占其二」结构同型。
压缩手段:
- D4-1~D4-4 置顶开工前裁决(四项都在关键路径起点,其中 D4-1 阻塞 T4-05、D4-4 阻塞 V6 定稿)。
- T4-03 前移到第一波:它不依赖任何生成域代码,却是 T4-09 的硬前置;若拖到第二波会把 T4-09 挤到冻结闸门之后。
- T4-05 fixture 优先:fixture provider 让 T4-06/T4-08 不必等真实供应商接通即可开发与测试。
- T4-13 草案与 T4-16 骨架前移(M2/M3 两度验证有效)。
- 旁路化:T4-07、T4-10、T4-11、T4-12、T4-20、T4-21、T4-22 均不在关键路径上,可灵活填空档。
- T4-24 首日执行:它只占 ~1 小时机时但有 7 天外部时限,越早越好。
6. 需要用户拍板的决策清单
以下决策 PM 只给建议,不替用户拍板。D4-1 是头号(阻塞关键路径起点且正典自认未决:development-plan.md:358,365);D4-1~D4-4 建议开工前裁决(四项都影响 V6 定稿或关键路径起点)。
| # | 决策事项 | 影响 | PM 建议(仅供参考) |
|---|---|---|---|
| D4-1 | AI 提供方选型(正典三度登记为未决:development-plan.md:358「AI 提供方……尚未确定」、:365 待确认事项)。候选:A. fixture / 本地 stub provider(可配置延时与进度、返回内置图、可注入四类故障;零密钥零成本零外部依赖,CI 与 E2E 唯一可用);B. 云 AI API(通义万相 / 即梦 / Replicate / OpenAI 等:能出真图,但引入账号、密钥、按次成本、网络不可达风险与 CI 外部依赖);C. 自托管 SD / ComfyUI(已核实不可行:本工作机为 AMD Radeon Vega 集显、无 nvidia-smi 即无 CUDA;生产侧腾讯云轻量服务器是否有 GPU 未取证,按 ADR-016 的同类背景推断为无) |
阻塞 T4-05(关键路径起点)、T4-06/T4-08 的开发与测试;决定 provider 抽象的形态(同步返回 vs 轮询 vs 回调)、密钥管理、CI 可跑性、以及验收标准「不重复扣费」中「扣费」是真金钱还是配额 | A 起步 + 适配层,B 作为末期可选加挂:M4 的四条验收标准(状态流转合法 / Worker 重启不丢 / 幂等不重复 / 失败可追踪)全部与图好不好看无关,fixture 能 100% 取证且能注入真实供应商难以复现的故障;同时把云 API 的接入点留在适配层,若用户愿意提供密钥与预算,第三波末加挂一个云 provider 做一次「真图」验证并入报告。不建议 C(无 GPU) |
| D4-2 | 模块归属与 Worker 形态:① 生成域——新建 patbond-creation(:8085,七容器) vs 并入 patbond-user(后者已持 Flyway、已持唯一的 ObjectStorage 写入方、已有唯一的 @EnableScheduling 先例);② Worker 形态——与 API 同进程 @Scheduled(省容器)vs 同一 jar 不同 profile 起独立容器(八容器,「重启不丢任务」的取证更干净、长任务不与在线请求抢线程) |
决定 T4-02/T4-03 骨架、compose 容器数(6/7/8)、CI 时长、客户端是否需要第五条 baseUrl(连带四份 E2E 脚本与真机清单的参数说明)、以及 ADR-023 的内容 | ①新建 patbond-creation(:8085):沿 ADR-009/ADR-017 两次先例(数据所有权独立、模块边界即未来服务边界,backend-modules.md:68-72);且 Worker 是长任务,与在线 API 同模块会互相影响。媒体写入不下放——生成输出经 T4-03 新增的 POST /internal/media/assets 由 patbond-user 落盘,ADR-017「media 上传流程实现在 patbond-user」的边界不破。②同进程 @Scheduled + 配置开关(patbond.creation.worker.enabled):DB 队列 + 租约天然支持多实例,需要拆时改配置即可拆;「重启不丢任务」用重启该容器取证同样成立。七容器已是双人团队的运维上限,不建议直接上八 |
| D4-3 | 队列载体:A. DB 队列(FOR UPDATE SKIP LOCKED + 目标模型已设计好的 lease_owner/lease_expires_at/next_attempt_at/priority 四列 + 两条 partial 索引)vs B. 引入 RabbitMQ(development-plan.md:205 原文「RabbitMQ 在异步任务阶段启用」,选 A 即是对正典的建议性偏离)vs C. 引入 Redis |
决定是否新增第 7/8 个容器与新依赖;决定 T4-08 的实现复杂度;影响 patbond-common 纪律(development-plan.md:79「不应让所有服务被动引入 RabbitMQ、Feign 等依赖」) |
A(DB 队列),理由三条:① 目标模型的 generation_jobs 已把租约、退避、优先级、幂等、provider 请求去重全部设计在表里,两条 partial 索引(patbond_postgresql.sql:711-715)就是为 SKIP LOCKED 队列写的——选 B 等于把已定稿的设计弃用一半;② 零新增基础设施(现状无 MQ 无 Redis,§2.4),运维面不增;③ 单库事务内「领任务 + 改状态」天然原子,比「MQ 消息 + DB 状态」的双写一致性问题简单一个数量级。偏离正典须落 ADR-023 并注明:M6「事务 Outbox 发布器」阶段若真需要 MQ,届时再引入,届时 DB 队列可作为 outbox 的对照实现 |
| D4-4 | 生成输入是否必须有图(决定 V6 能否照抄目标模型):目标模型 generation_jobs.input_asset_id uuid **NOT NULL**(patbond_postgresql.sql:610)——即评审定稿的模型不支持纯文生图。选项:A. 沿用 NOT NULL,图生图先行;B. 改为 nullable 以支持文生图(V6 偏离目标模型,须写入迁移说明) |
决定 V6 是否偏离目标模型;决定 create 页整个交互(A 则「先传宠物照片」是硬门槛,现有假上传卡直接转真实;B 则需要「有图/无图」两条分支 UI);决定配额与滥用面(A 天然收窄) | A(沿用 NOT NULL,图生图先行):① 产品定位是宠物 App,核心价值是「用我的宠物照片生成」,纯文生图与定位弱相关且与通用 AI 画图工具直接竞争;② 现有 create 页的上传卡(create_page.dart:159-168)本就是必经步骤,改造成本最低;③ NOT NULL 让每次生成都有一张已归属校验的输入资产,滥用面与成本可控。若产品坚持文生图,改 nullable 的成本本身很小(V6 一处),但连带 UI 分支与滥用防护为 +1M |
| D4-5 | 媒体形态:A. 仅 image(media_kind 列与枚举保留,种子只入 image)vs B. image + video 同期 |
视频涉及转码、封面帧、时长、存储量级台阶;duration_ms 列已在模型里;现有 create 页已有「AI 视频」分段项(create_page.dart:138-157)与 video 时长 ChoiceChip(:191-196);媒体上传契约的 kind enum 当前是 [image](openapi.yaml:3454,注明 video/document 为向后新增预留) |
决定 T4-04 种子、T4-09 是否需时长探测、T4-17 分段器处置、存储成本量级 |
| D4-6 | model_version_snapshot 的来源矛盾(目标模型内部自相矛盾,V6 前必须裁决):generation_jobs.model_version_snapshot varchar(64) **NOT NULL**,但 generation_models(:556-573)无任何 version 列,种子把它硬编码为 '1.0'(:1514)。选项:A. V6 给 generation_models 加 version varchar(64) NOT NULL DEFAULT '1.0'(最小增列偏离);B. 把 snapshot 列改为 nullable(弱化可追踪性);C. 让 snapshot 存 provider_model_name(语义重复,两列同值) |
决定 V6 的一处偏离;影响「失败原因可追踪」与「历史任务可复现」的强度 | A(给目录加 version 列):snapshot 四列的全部价值就是「目录改了不影响历史任务的可复现性」,B 会让这条价值在最关键的模型版本维度上失效;C 造成两列恒同值的冗余。A 的成本是 V6 里一行加列 + 一行迁移说明。偏离须写入迁移文件头注释(沿 V5:1-33 写法) |
| D4-7 | 配额与限流口径(决定「不重复扣费」中「扣费」的定义):目标模型没有任何计费/额度表,故 M4 的「扣费」只能理解为「消耗配额」。待定:① 每用户并发上限(建议 1);② 每用户每日次数上限(建议 20);③ 计数落在哪(建议直接 COUNT generation_jobs 行,不新建表);④ 429 的 Retry-After 取值口径(下一次可提交的秒数) |
决定 T4-06/T4-11 实现;决定 T4-25 的第 3、8 场景;直接决定成本上限(若 D4-1 选云 API,日限 × 单价 = 每日成本天花板) | 并发 1 + 日限 20 + 直接 COUNT 不建表:① 并发 1 让排队语义对用户可解释、也让 Worker 并发度调优不受用户维度干扰;② 20 次/日对个人用户足够宽松、对成本足够安全;③ 不建表的关键理由——幂等键已保证重复请求不新建行,所以「按行数计配额」天然满足『相同幂等请求不重复扣费』,这是最省事又最正确的实现;④ Retry-After 取「最早一个 running 任务的预计完成时间」或「次日零点」的秒数,取小者。四个数字均请用户确认,尤其若 D4-1 选 B(云 API),日限直接等于每日成本上限 |
| D4-8 | 生成任务与草稿的语义定型(四个子项):① 「一键建草稿」是用户点按钮建还是成功后自动建;② category='ai_creation' 是否强制要求带 generationJobId;③ generationJob 在 Post/FeedCard 读侧外露到什么程度(完全不露 / 只露 id / 露模型与风格名);④ 失败任务的「重试」是新建任务还是复用同一任务;⑤ 对已终态任务重复取消是幂等 200 还是 422 |
决定 T4-07/T4-10/T4-18/T4-19 的实现与契约形态;③ 直接决定契约新增字段的规模;④ 影响幂等键语义(新建任务须换 key,否则命中旧任务) | ①用户点按钮建(自动建会产生草稿垃圾,且用户可能只想看看不想发);②强制带 generationJobId(否则用户可伪造 AI 分类,读侧 ai_creation 就失去可信度);③只露 id + 模型与风格的展示名(够做「由 Patbond-V1 · 治愈动画 生成」的角标,不露 prompt——prompt 可能含隐私);④新建任务(复用同一行会破坏 attempt_count <= max_attempts 与 snapshot 语义;客户端须生成新 Idempotency-Key,并在 UI 明确「重试会消耗一次配额」);⑤幂等 200 no-op(沿 UpdatePostRequest.status 对已发布帖重复提交为幂等 no-op 的既有先例 openapi.yaml:3701-3707) |
| D4-9 | widthPx/heightPx 恒 null 的处置(M3 观察项 2,跨两个迭代未决):A. 服务端在 complete 时探测尺寸(需 T4-03 顺带加对象读能力,读图片头部字节解析 jpeg/png/webp);B. 只改契约描述(把 openapi.yaml:3536 的「complete 后回填」改为「仅 AI 输出回填」,用户上传路径承认恒 null);C. 契约新增 complete 请求体由客户端申报尺寸(客户端已解码过图,成本最低,但客户端申报值不可信) |
决定单图帖能否按真实宽高比渲染(现一律回落 4:3);决定 T4-09 的规模;A 会让 T4-03 多一个方法 | A(服务端探测):① M4 本就要给 ObjectStorage 加写能力,顺带加读只是一个方法,边际成本 S;② AI 输出路径无论如何都要写真实尺寸(尺寸是生成请求参数,服务端已知),若用户上传路径仍恒 null,就会出现「AI 图有宽高比、用户图没有」的分裂体验;③ C 的客户端申报值不可信(可被篡改,且与 ck_media_dimensions 的库层校验形成两个事实源)。探测失败时回落 null 且不阻塞 ready(MediaAsset.required 不含尺寸字段,向后兼容) |
| D4-10 | 契约版本与快照锁补强:① 版本号 v1.5.0(若确认对 v1.4.0 为纯增量);② 是否顺手把快照锁从「计数守恒」补强为真校验和断言(§2.13:CI 当前只锁 version + 3 计数 + tag 分组,md5 比对是人工步骤,任何计数守恒的字节改动都能溜过) | ① 决定四/五份快照文件名与守卫测试期望值;② T4-14 是否成立 | ①v1.5.0,且维持「对既有集成方纯增量」的承诺:核对全部变更(新增生成域路径、两处 enum 增值、新增字段、一处 description 修订、新增错误码与 429 响应)——无字段删改、无类型变更、无必填收紧,纯增量成立,可继续写入 info.description。②纳入(T4-14,S):M4 契约面显著扩大(新增域 + 可能第五份快照),且已有一次真实教训——契约测试累计抓修 3 处真问题(iteration-3/29:44),其中「校验器对 nullable + allOf 静默跳过」正是这类「看不见的漂移」 |
| D4-11 | A/B 首个实验是否本迭代启动(ADR-012 原定「M4 首实验」,decisions.md:130):A. 启动,但极小面(补分流哈希 + Flutter 曝光封装 + 最小开关三件,实验对象取纯展示层项);B. 后置到 M5/M6(等监控设施选型落地一并做) |
决定 T4-23 是否成立(M);决定 ADR-012 的承诺是否兑现 | A,但请用户知情前提被高估了:§2.9 核实后前置实为 5 绿 3 半(#4 分流组件、#5 Flutter 曝光封装、#7 feature flag 三者代码零实现),文档写的「6 绿 1 半」偏乐观。启动首个实验需先补这三件(约 1M),建议用纯客户端确定性哈希(anonymousId 持久化已就位)避免为一个实验搭服务端下发设施;实验对象取 create 页风格卡默认排序或结果页引导文案这类纯展示层项,不实验后端生成逻辑。若用户认为 M4 周期已满,选 B 亦合理,但须在收官总结显式记录 ADR-012 承诺顺延,并把「6 绿 1 半」的口径更正为「5 绿 3 半」 |
| D4-12 | 文档站是否加访问控制(server-exposure.md:32 登记的待评估项):A. 本迭代纳入(S 独立工单 T4-28);B. 继续挂起 |
纯运维项,不占关键路径;影响内部架构信息的暴露面 | A(纳入,S):本迭代的新论据是 M4 报告将写入 AI 提供方选型、密钥管理方式、配额策略与队列实现细节——公开可读的收益低于暴露成本;且 2026-09-11 安全事件后服务器侧纪律已固化,趁热做成本最低。实现为 nginx basic auth 或 IP 白名单,凭据不入库。若用户偏好继续挂起亦无技术风险,但建议至少把「M4 迭代报告」这一目录先行限制 |
| D4-13 | 真机 M2 两项的 09-21 硬时限如何应对(外部依赖,PM 无法消除):A. 7 天内投入 Android 真机(借用或采购)并首日执行 T4-24;B. 接受降级——首批北极星读数标注「未经真机验收」+ 复评点顺延至下一成熟窗口 | 决定 2026-09-21 首批北极星读数(W37 队列)是否可作为正式基线;连带影响 A/B 前置 #1「数据质量验收」的成色(该项判绿但拦路项正是真机) | A(投入真机):① 两项合计仅约 1 小时机时(device-verification.md:42 ~10 分钟 + :63 ~45 分钟含等待),投入产出比极高;② 桌面无法替代——device-verification.md:144 已言明 Linux 桌面 platform=linux 不在契约枚举内、整批 400 被拒,故「事件落库」这一项只能在 Android 上验;③ 这两项是 10 项挂起中唯一带外部时限的,其余 8 项无时限可继续挂起。若确实无设备,B 的三项降级动作(标注 / 顺延 / 记录不可替代缺口)须全部落地 |
| D4-14 | AI 资产的用途命名、归属与生命周期:① 新 purpose 枚举值命名(建议 ai_input / ai_output,vs 复用 post_image);② 输出 asset 的 owner_user_id 是否为请求用户(建议是);③ 失败/取消任务的输入图是否清理、生成记录是否可删、删除记录是否连带删对象;④ 生成图被建成帖子后,删帖是否影响该资产 |
决定 T4-03 的配置扩充、T4-09 的写入字段、T4-12 的清理范围、T4-20 的删除能力;直接影响长期存储成本(每次生成 ≥1 输入 + 1 输出) | ①新增 ai_input / ai_output 两个值(不复用 post_image:用途是审计与清理策略的依据,混用后无法区分「用户主动发的图」与「生成中间产物」)——纯配置变更无需迁移(MediaProperties.java:66 + application.yml:45,ADR-022 已验证同一路径);②是(便于按用户清理与配额审计);③输入图随任务终态保留、不主动清理(重试与申诉都需要它;靠 T4-12 只清理未确认的 uploading 孤儿);生成记录允许软删但首版不删对象(对象清理属 M6 存储治理);④删帖不影响资产(posts.generation_job_id 是 ON DELETE SET NULL、post_media 与 assets 的引用关系独立,沿既有语义不动) |
决策依赖关系提示:D4-1 → T4-05;D4-2 → T4-02/T4-03②/T4-16 第五条 baseUrl;D4-3 → T4-08;D4-4 + D4-6 → V6 定稿(T4-01 的开工前提);D4-5 → T4-04 种子 + T4-17 分段器;D4-7 → T4-06/T4-11;D4-8 → T4-07/T4-10/T4-18/T4-19 + 契约;D4-9 → T4-03/T4-09;D4-10 → T4-13/T4-14;D4-11 → T4-23 成立与否;D4-12 → T4-28 成立与否;D4-13 → T4-24 的执行 vs 降级;D4-14 → T4-03 配置 + T4-09 + T4-12 + T4-20。
7. 刻意不做的事项及理由
以下均为主动裁剪,不是遗漏。逐条给出理由,供收官总结引用与产品知情。
| # | 不做的事 | 理由 |
|---|---|---|
| 1 | 视频生成(随 D4-5) | 台阶式复杂度:转码 + 封面帧 + 时长探测 + 存储与带宽量级。ADR-016 已把「单机公网带宽」列为接受的限制(自托管 MinIO 在腾讯云轻量服务器),视频会立刻击穿它。media_kind 列与枚举保留,UI 分段项保留但禁用 |
| 2 | 纯文生图(随 D4-4) | 评审定稿的目标模型 input_asset_id NOT NULL(patbond_postgresql.sql:610)本就不支持;且与「用我的宠物照片生成」的产品定位弱相关。改 nullable 的技术成本很小,连带的 UI 分支与滥用防护不小 |
| 3 | 真实计费与支付 | 目标模型零计费/额度表。「不重复扣费」以「不重复消耗配额」取证(D4-7),配额直接 COUNT 任务行。支付属独立产品决策,不在 M0~M6 任何里程碑原文中 |
| 4 | 引入 RabbitMQ / Redis(随 D4-3) | generation_jobs 已把租约、退避、优先级、幂等、provider 去重全部设计在表里 + 两条为 SKIP LOCKED 而建的 partial 索引;引入 MQ 反而带来「消息 + DB 状态」双写一致性问题。development-plan.md:205 的 RabbitMQ 承诺顺延到 M6「事务 Outbox」阶段,须落 ADR 记录偏离 |
| 5 | 生成内容审核、敏感词、举报、水印 | 无运营后台(M3 D3-7 已定「hidden/archived 保留字段不开放端点」)。但须显式声明敞口:AI 生图的 UGC 风险高于普通图文(可生成不宜内容且难以事后归因),数据库侧可手工 hidden 应急;审核与水印入 backlog,随 M6 或运营后台一起设计 |
| 6 | 生成完成的推送通知 | M6 原文「通知、事务 Outbox 发布器和失败重试」(development-plan.md:271)。M4 内客户端靠轮询(T4-18),离页即停、回页重取 |
| 7 | 完整可观测性栈(指标、Trace、告警、准实时护栏监控) | M6 原文「限流、审计、结构化日志、指标、Trace 和告警」(:272);A/B 前置 #7 的监控半边也已明确「留 M4」实指「依赖监控设施选型」。M4 只做 development-plan.md:329 的最小兑现:队列积压与 Worker 失败率 SQL 可查 + 结构化日志留痕,不建看板不接告警 |
| 8 | 全站通用限流 | T4-11 只做生成域限流(成本刚需)。全站限流属 M6 :272 范围。注意这意味着 M3 遗留的 429 只被部分清偿——埋点上报端点(POST /api/v1/events)仍无限流,须在 feature-checklist 里如实标注为部分完成 |
| 9 | 草稿自动保存 | T4-21 只做「我的草稿」列表页。自动保存(防抖 Timer + 冲突处理)是独立交互设计问题,且现有埋点枚举已明确「自动保存不埋」(post_analytics.dart:30-41 只有 manual/on_exit)。继续挂起 |
| 10 | 多模型并行生成 / 一次出多图 / 生图质量 A-B 对比 | 目标模型一个任务对一个 output_asset_id(单值列),多图需改模型(新增输出表或数组列)。属产品功能扩展,不在 M4 原文 |
| 11 | 逐帖曝光与服务端排序实验日志 | ADR-020 已否决逐卡曝光并把它记为「M4+ backlog 待服务端下发日志」(decisions.md:169)。服务端日志下发依赖 M6 的日志设施,本迭代不启 |
| 12 | 话题、关注列表、作者主页 | ADR-018 裁剪项,与 AI 创作零耦合。继续挂起(详见 §8) |
| 13 | access token 黑名单、/internal 改 mTLS |
跨迭代技术债。但须记录权重上升:T4-03 新增 POST /internal/media/assets(且承载对象写入),/internal 面在本迭代扩大,mTLS 的优先级应在 M6 加固时上调 |
| 14 | 月份网格选择器、大图下滑关闭手势、SegmentedButton 之外的主题债 |
与 M4 零功能耦合。大图下滑关闭需引入 photo_view 新依赖,不为一个手势引入依赖;月份网格的实测痛点已被年份网格 + 手输 + 「今天」覆盖(iteration-3.5/06:72) |
| 15 | 真机 M3 四项与 M3.5 四项 | 无设备且无时限(只有 M2 两项带 09-21 硬时限)。M4 会再新增真机专属登记项,届时一并执行更经济 |
8. 历史遗留处置:搭车 vs 挂起
清点范围为 M3(iteration-3/29-m3-summary.md:52-62)与 M3.5(iteration-3.5/06-wave2-closure.md:69-76)的全部遗留,加上本次审计新发现的文档/门禁缺口。
8.1 搭车进 M4(11 项)
| 遗留项 | 判定理由 | 插入位置 |
|---|---|---|
widthPx/heightPx 恒 null(M3 观察项 2) |
AI 输出的尺寸是生成请求参数,服务端必然已知,不写就自相矛盾;若只写 AI 路径会造成「AI 图有宽高比、用户图没有」的分裂。且 T4-03 本就要给 ObjectStorage 加方法,顺带加读只是边际 S。核实纠正:不是缺列(V1:217-218 早已存在),规模 S 非 M |
T4-09(随 D4-9) |
429 限流 + 客户端 Retry-After 分支 |
AI 生成有真实成本,配额与限流是刚需而非可选;客户端 api_client.dart:164-167 已有 429 粗分支、analytics_service.dart:268-272 的注释明写在等后端落地 |
T4-11(后端)+ T4-16(客户端收口)。注意只清偿生成域,埋点端点限流仍挂起 |
| uploading 超时未确认 asset 清理任务 | M4 本就要建 @Scheduled 调度基座;且每次生成都要先传一张输入图,孤儿资产量被放大,此项从「可选清理」升为「成本控制项」 |
T4-12 |
eventVersion 口径未定型 |
字典 v4 本就要动 EventDictionary 与契约描述,顺手定型边际成本近零 |
T4-22 |
| 「我的收藏与草稿」两页 | 后端与仓库层全就绪、零调用点(listMyBookmarks 全仓无调用),与 M4 契约零耦合,可在第一波并行消化,不占关键路径 |
T4-21 |
| 真机 M2 两项 | 唯一带外部硬时限的遗留(2026-09-21),仅约 1 小时机时 | T4-24(第一波首日,随 D4-13) |
SegmentedButton 粉底(M2 主题债) |
本次审计改判为搭车:该控件的使用点之一正是 M4 要重做的 create 页模式分段器(create_page.dart:138),根因已定位(主题缺 segmentedButtonTheme,app_theme.dart:94-196)、已审计色对表与落地范本俱在(:199-214、:215-296),一次修复 6 处使用点,边际成本 S |
T4-17 |
| A/B 前置 #4/#5/#7 的缺失半边 | ADR-012 承诺「M4 首实验」;且文档「6 绿 1 半」经核实实为「5 绿 3 半」,若不补则实验无法启动 | T4-23(条件单,随 D4-11) |
| 契约快照锁强度不足(本次新发现) | CI 只锁计数不锁字节;M4 契约面显著扩大(新增域 + 可能第五份快照),此时补机制成本最低 | T4-14 |
| 埋点白名单计数 42→41 纠偏 + 缺总量断言(本次新发现) | 字典 v4 本就要动白名单;补一条总量断言即永久钉住,边际成本近零 | T4-22 |
三项文档纪律缺口(本次新发现):releases.md:131 仍写「E2E 双份」与 :36/:56 的四份矛盾;E2E 回归原则从未固化进 git-workflow.md;patbond-doc 无仓库级 .gitignore(site/ 仅靠全局 gitignore 排除) |
M4 本就要把回归清单改为五份并新增第五份脚本,三项一并修正;.gitignore 是一行 |
T4-27 |
8.2 继续挂起(10 项)
| 遗留项 | 挂起理由 | 建议落点 |
|---|---|---|
| 真机 M3 四项 + M3.5 四项(共 8 项) | 无设备且无外部时限;M4 会再新增真机专属登记项,届时一并执行更经济。步骤已在 device-verification.md:110-168、:172-217 备齐 |
设备到位即插入,不阻塞任何工单(约 1 天) |
| 完整草稿列表的自动保存 | 独立交互设计问题;现有埋点枚举已明确「自动保存不埋」。T4-21 只交付列表页 | M5 或独立客户端体验单 |
| 月份网格选择器 | 实测痛点已被年份网格 + 手输 + 「今天」覆盖(iteration-3.5/06:72) |
独立客户端体验单 |
| 大图「下滑关闭」手势 | 需引入 photo_view 新依赖,不为一个手势引入依赖 |
独立客户端体验单(与依赖评估一并做) |
| 话题功能(ADR-018 剪出) | 与 AI 创作零耦合。注意一处历史推测已被本次审计削弱:M3 曾建议「topics 端点随 AI 创作分类需求一起做(ai_creation category 天然关联)」(iteration-3/01:285),但实际 ai_creation 只是 category 的第三个枚举值、与 topics 表无任何字段关联,该关联不成立,不构成搭车理由 |
M5+ 或独立社区增量单 |
| 关注列表 / 作者主页(ADR-018 裁剪) | 与 AI 创作零耦合 | M5+ |
| 逐帖 Feed 曝光 | ADR-020 已否决,明确「待 M4+ 服务端下发日志」,而日志设施属 M6 | M6 后重评 |
| access token 黑名单 | 跨迭代技术债,无 M4 耦合 | M6 加固 |
/internal 改 mTLS |
跨迭代技术债。权重上升须记录:T4-03 新增 /internal/media/assets 且承载对象写入,/internal 面在本迭代扩大 |
M6 加固(优先级上调) |
| 全站通用限流(生成域之外) | T4-11 只做生成域;埋点上报端点仍无限流 | M6 :272 |
9. 风险清单
| # | 风险 | 影响 | 缓解措施 |
|---|---|---|---|
| R1 | AI 提供方未决且正典自认未决(development-plan.md:358,365):若拖延,T4-05 空转,连带 T4-06/T4-08 无法测试;若选云 API,还叠加密钥、成本、网络不可达与 CI 外部依赖四重不确定 |
关键路径起点空转 | D4-1 置顶开工前裁决;未拍板期间先写适配层接口 + fixture 实现,止损线明确:适配层以上不写任何供应商特定代码;fixture 无论如何都要做(CI 与 E2E 唯一可用 provider) |
| R2 | 异步基础设施从零起步(§2.4:无 MQ、无 Redis、无线程池、@Scheduled 唯一先例是删会话行):租约、退避、并发领取、多实例安全全是新代码,且 ck_generation_jobs_state 的五态字段组合约束严格(回收时必须清 started_at 与 progress,否则库层直接拒绝) |
T4-08(L)估算失准直接拖垮迭代;状态机 bug 密集区 | 选 DB 队列(D4-3)让实现收敛在一条 SQL 模式内;T4-15 的状态机穷举矩阵作为 DoD 硬项(每条合法迁移成功 + 每条非法迁移被应用层拒且被库层 CHECK 兜底,两层各有断言);started_at 清零的取舍写进代码注释与迁移说明 |
| R3 | 「输出写入媒体表」被一句话低估(§2.5):服务端目前根本不能写对象存储,且 ADR-017 把 media 写入边界钉在 patbond-user |
T4-03 若拖到第二波会把 T4-09 挤到契约冻结之后,连锁推迟第三波 | T4-03 前移到第一波(它不依赖任何生成域代码);用 /internal/media/assets 保住 ADR-017 边界不破;MinIO Testcontainer 覆盖服务端 put 全链路 |
| R4 | 契约冻结面比 M3 更宽:四处待定型点(五态响应形态、429 + Retry-After 这一契约史上首个响应头、generationJob 外露程度、新 purpose 命名),且若 D4-2 新建模块则快照从四份变五份、守卫测试期望值需五处同步更新 |
冻结延迟连锁推迟第三波两端联调 | 四处待定型点第一波即在草案中显式 TODO-FREEZE 并限期第二波中期收敛;闸门纪律不放松;T4-14 的校验和断言让「漏同步一份快照」在 CI 立即暴露 |
| R5 | AI 生成 UGC 的内容风险高于普通图文:可生成不宜内容,且无审核、无水印、无举报、无运营后台 | 内容风险敞口(虽 MVP 用户面小) | §7 第 5 条已显式声明敞口并要求产品知情;hidden 字段可手工应急;prompt 不入埋点明文(隐私);审核与水印入 backlog 并在收官总结列明 |
| R6 | 成本失控(仅在 D4-1 选云 API 时成立):无配额则单用户可无限提交 | 真金钱损失 | D4-7 的日限即每日成本天花板,必须与 D4-1 同时拍板;T4-11 的配额是 T4-06 的准入条件而非可选后置项;fixture provider 让开发与 CI 阶段零成本 |
| R7 | create 页从零建测试基线(§2.14:563 行、grep "CreatePage" test 零命中) |
T4-17/T4-18 是「改代码 + 建测试」双份工作量;假延时改真轮询是状态 bug 高发区 | 保留四块视觉骨架只换数据源(降低视觉返工,M3 的 R10 不复现);轮询的「页面不可见暂停 / 回前台恢复」列为 DoD 硬项——M3 曾用 widget 测试抓出「回前台不开新曝光段」的同类真 bug(iteration-3/29:46) |
| R8 | 09-21 硬时限只剩 7 天且依赖外部设备(PM 无法消除) | 首批北极星读数(W37)只能标未验收,连带 A/B 前置 #1 的成色 | D4-13 请用户即刻拍板;T4-24 排在第一波首日;降级方案三项动作已写入工单,且明确桌面无法替代(platform=linux 不在契约枚举、整批 400) |
| R9 | 容器数与 CI 时长继续增长:六 → 七(或八)容器;MinIO 容器使用面因 T4-03/T4-09 扩大;测试数从 379/597 继续上量 | 门禁反馈变慢被绕过 | D4-2 建议同进程 Worker 以省一个容器;T4-15 记录每波 CI 时长,超阈值按模块分层执行,不降低「提交前全绿」标准 |
| R10 | 文档结论与代码实况的系统性偏差(本次审计一次性发现 8 处,见 §11):白名单计数、快照锁强度、A/B 前置成色、真机项数、E2E 份数口径…… 说明「转述被当成事实」不是 M3.5 的偶发事故 | 规模预估失准、承诺无法兑现、门禁虚假安全感 | 本报告全部结论标注核实文件与行号;把「可机验断言」作为纠偏机制(T4-22 补白名单总量断言、T4-14 补契约校验和断言——两者都是把人工纪律变成 CI 门禁);收官总结须逐条回填修正后的口径 |
| R11 | 未提交/未推送风险(历次惯例项) | 工作量全损 | 每波每单交付即提交即推送(ADR-011/ADR-021:只推 dev,main 走 PR);PM 每波核对三仓 git status 与远端同步 |
10. 质量要求(对全部工单生效)
- 遵守开发计划第 10 节 DoD 八条:契约/迁移/代码一致;不依赖 demo 常量;权限、校验、幂等、并发已处理;四态齐备;日志可定位且不泄敏;干净环境可复现。
- 契约规范沿用:
camelCase、UUID 字符串、ISO 8601 +timestamptz、统一信封{code, message, data}、稳定错误码(生成域新码段定死:建议40407/42206/42900,永不复用或改号)、cursor 分页(禁 OFFSET)、生成任务创建强制Idempotency-Key(development-plan.md:173)、version乐观锁。 - 契约冻结后 api 侧快照同步升版,四/五份 md5 与正典一致;守卫测试期望值(version + 3 计数 + tag 分组)与 T4-14 的校验和断言同步更新;
everyDeclaredResponseCellIsExercised新增格全部真实触发,不得豁免。 - 迁移纪律:V6 进
patbond-user(单迁移链);不修改已推送的 V1~V5;任何偏离目标模型之处在迁移文件头注释逐条写明理由(沿 V5:1-33 写法);存量库实证 + 全新库全量迁移双向验证。 - 集成测试一律 Testcontainers
postgres:18(ADR-006/008),媒体与生成输出测试用 MinIO 容器;每单交付JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw clean test全绿、dart format --set-exit-if-changed+flutter analyze+flutter test全绿、mkdocs build --strict零 warning(门禁表git-workflow.md:56-64,门禁不绿不提交)。 - 每个业务接口覆盖六类路径:成功 / 参数错 / 资源不存在 / 无权限 / 并发冲突 / 幂等重试。
- 所有网络页面四态(loading/empty/error/retry)齐备 + 离线提示;图片位另加占位/失败态;预签名 URL 不得持久化、缓存 key 须剥
X-Amz-*。 - 凭证纪律:不提交任何密码、token、对象存储密钥、AI provider 密钥(
.env/.sample模式);两层check-secrets.sh+ CI 兜底;埋点不含 prompt 明文、不含 assetId/jobId、不含精确字节数。 - 本迭代不实现预约、通知推送、运营后台的任何接口或页面;
posts.region_id仅预留;范围外需求记 backlog。
11. 工单统计
- 工单总数:28(A 数据与地基 5 + B 后端生成纵切 7 + C 契约与测试 3 + D Flutter 6 + E 埋点遗留收口 7),其中 3 个条件单(T4-20 我的生成记录、T4-23 A/B 首实验、T4-28 文档站访问控制)
- 规模分布(核心 25 单):S × 5(T4-04 / T4-12 / T4-14 / T4-24 / T4-27)、M × 16、L × 4(T4-06 / T4-08 / T4-17 / T4-18);条件单另计 S × 2、M × 1
- 波次:4 波(第一波并行开工 10 线 / 第二波后端纵切 6 线 + 契约冻结闸门 / 第三波两端并行 3 线 / 第四波收官 3 单)
- 关键路径:D4-1~D4-4 拍板 → T4-01 → T4-03 → T4-05 → T4-06 → T4-08 → T4-09 → T4-13 冻结闸门 → T4-16 → T4-17 → T4-18 → T4-19 → T4-25 → T4-26;L × 4 在链上,后端队列与客户端状态机各占其二
- 待拍板决策:14 项(D4-1
D4-14);D4-1 头号(阻塞关键路径起点),**D4-1D4-4 建议开工前裁决**(D4-4 与 D4-6 是 V6 定稿的前提) - 遗留处置:搭车 11 项、继续挂起 10 项
- 刻意不做:15 项,逐条附理由
11.1 本报告推翻或修正的既有文档结论(8 处)
| # | 既有文档结论 | 核实后的实况 | 影响 |
|---|---|---|---|
| 1 | 「事件白名单 42 条」(iteration-3/22:4,14、iteration-3/29:20、feature-checklist.md:218) |
41 条。EventDictionary.java:42-104 实测 Map.entry 41 个;根因是 experiment_exposed 被重复计数(设计源文档 iteration-3/06:12,149 自己写的是 19 个新事件,22 + 19 = 41)。无任何门禁能拦(EventDictionaryTest 无总量断言) |
三处文档纠正 + 补总量断言(T4-22) |
| 2 | 「四模块字节级快照锁 CI」 | CI 里没有任何 md5/checksum 校验。ci.yml 只有三步(secret scan / 装 JDK / mvnw clean test);md5 比对只是人工冻结步骤。CI 实际锁的是「version + 3 计数 + tag 分组」,任何计数守恒的字节修改都能溜过 |
新增 T4-14(S)把纪律变门禁 |
| 3 | 「A/B 前置 6 绿 1 半,M4 可启首个实验」(iteration-3/29:68) |
实为 5 绿 3 半。#4 分流哈希组件、#5 Flutter 曝光封装、#7 feature flag / 社区发布开关三者代码零实现(后两者 grep 全仓零命中,#7 的「回滚绿」无任何依据,连 feature-checklist 都未登记) | D4-11 需在知情前提下拍板;T4-23 规模上调 |
| 4 | 「真机挂起四项」/ 主会话口径「共 8 项(M3.5 两项)」 | 共 10 项:M2 2 + M3 4 + M3.5 4(device-verification.md:176/:190/:200/:209,M3.5 收口报告 06:75 自己写的也是「4 项 + 1 备注」)。三个执行记录全为「待补」 |
§8.2 按 10 项登记;T4-24 只抢 M2 两项 |
| 5 | 「widthPx/heightPx 恒 null」被隐含理解为能力缺口 |
列早已存在(V1:217-218);真实缺口是「服务端无尺寸探测」+「契约声明了不存在的回填入口」(complete 端点 openapi.yaml:1256-1299 无 requestBody)。这是 M3.5「转述不可采信」教训的同型复现 |
规模由 M 降为 S,搭车 T4-09 |
| 6 | 「M4 需设计 AI 创作数据模型」(隐含预期) | 完整设计早已评审定稿:docs/database/patbond_postgresql.sql:556-716 含三表 40 列、全套租约/退避/优先级/幂等列、11 条索引(两条为 SKIP LOCKED 队列而建)、三个触发器、开发种子。且零跨 schema 外键需裁剪(V3 剪 4 条、V5 剪 2 条的先例在 M4 不重演,因 identity/pet_health/media 三个 schema 均已存在) |
T4-01 由 L 降为 M;D4-3 有了「不引入 MQ」的硬证据 |
| 7 | 「输出写入媒体表」(development-plan.md:252 一句话) |
服务端目前根本不能写对象存储:ObjectStorage.java:15-37 只有 ensureBucket/presignPut/stat/presignGet,putObject 仅用于构造预签名。需新增写方法 + 实现 + 未配置桩 + 跨模块落库通路(受 ADR-017 边界约束) |
新增独立工单 T4-03(M),并前移到第一波 |
| 8 | 「回归清单已改为四份」与「发布流程」 | 口径自相矛盾且未固化:releases.md:36/:56 写四份,同一文件 :131 的纪律条目仍写「E2E 双份回归」;且「每个引入对外端点的迭代都应有 E2E 脚本」这条原则从未写进 git-workflow.md(该文件门禁表无 E2E 条目)。另 feature-checklist.md:145「auth 域契约测试补齐 ⬜」已过期(M3 T3-19 已交付) |
三项文档修正并入 T4-27;第五份脚本单列 T4-25 |
附带修正两处口径(非推翻):① patbond-api 测试数 379(surefire 运行数,releases.md:17,35)与 381(注解静态计数,62 个测试类)的差异属参数化展开口径,不是回归;② patbond-flutter 是 597 通过 + 2 skip(环境门控冒烟),且不含 integration_test/ 四份桌面实测与仓库根四份 E2E 脚本。
11.2 尚未取证、需用户或后续角色补齐的三项
- AI 提供方的可用性与价格:已核实本工作机无 CUDA(AMD Vega 集显、无
nvidia-smi)故自托管不可行;生产服务器是否有 GPU、云 API 的额度与单价均未取证,需用户确认或授权调研(阻塞 D4-1)。 - A/B 实验设计模板与样本量规则文档(前置 #3/#6,文档判绿):两仓中未定位到文档实体,仅在
iteration-3/06表格里被声明为已交付(影响 T4-23 的验收标准可执行性)。 - 北极星出数与对账 SQL 无可执行载体:SQL 仅以 Markdown 代码块存在(
iteration-2/06:200-229、:364-470),三仓scripts/下只有check-secrets.sh与hooks/。2026-09-21 首次出数须人工执行,建议后续沉淀为脚本(不在 M4 范围,记 backlog)。