3D 定价后台可编辑补齐实现状态、验收证据与踩坑记录
- 主规范补公开读模型投影口径、覆盖文件缺段补齐、旧形状覆盖文件的发布前置门禁与加价项一致性约束 - 实施计划补实现状态表、逐条判据的验收证据与真实环境待验收项;里程碑标记已实现待验收 - 后端架构文档补 upsert input 的期望版本与事务内 CAS 语义 - pitfalls 补「省略即沿用会静默覆盖」与「公开读模型形状不能跟着存储形状走」两条 - decision-log 补实现期的四个口径:公开投影取文生加价项、CAS 复用 updated_at、覆盖文件缺段按默认补齐、旧形状不兼容改发布门禁
This commit is contained in:
@@ -54,6 +54,37 @@ Milestone Spec: `docs/project-memory/plans/【里程碑】3D生成定价后台
|
||||
- 同步主规范、表目录、`CONTEXT.md`、ADR 0005 的实现状态,必要时补 `pitfalls.md`(例如「不要把省略即沿用重新引入」)。
|
||||
- 手工验收:后台改 3D 价 → 不重启 → 画布 3D 面板价格随之变化;重启后仍是保存值。
|
||||
|
||||
## 实现状态(2026-09-23 完成)
|
||||
|
||||
| 步骤 | 状态 | 提交 / 落点 |
|
||||
| --- | --- | --- |
|
||||
| 1 schema 两列 | 完成 | `774b74d3d`;module 表尾部追加两列与展平行类型,绑定重新生成,`npm run check:spacetime-schema` 通过 |
|
||||
| 2 定价类型与默认文件 | 完成 | `9f4a71d2e`;`Model3dPricingConfig` 两段并列 + `Model3dEndpointPricing`,默认 JSON 同步 |
|
||||
| 3 记录转换与归一化 | 完成 | `5e47f0c41`、`952bc1cca`;新增 `editor_generation_model3d_records` 深模块,覆盖文件降级为种子与兜底 |
|
||||
| 4 后台契约 | 完成 | `ee4a9434f`;GET 带版本、POST 两段必填 + 事务内 CAS → 409,退役 `with_previous_model3d` |
|
||||
| 5 admin-web 3D 区块 | 完成 | `c901f6954`;`AdminEditorGenerationModel3dPricingSection` + `adminEditorGenerationPricing` 纯模块 |
|
||||
| 6 文档与验收 | 完成 | 本文件、主规范、ADR 0005、`pitfalls.md`、`decision-log.md` |
|
||||
|
||||
补充落点(不在原步骤内,但为达成验收标准所必需):
|
||||
|
||||
- `ebd904e19` 公开读模型投影:新增 `Model3dPricingPublicView`,`GET /api/editor/generation-pricing` 的 3D 段保持旧形状;加价项取文生段,两段不一致时告警。
|
||||
- 公开投影与前端零兜底口径一起构成「内部两段 / 公开单表」的短期不一致,前端升级前不要差异化加价项,见主规范「旧形状覆盖文件的处理」。
|
||||
|
||||
## 验收证据
|
||||
|
||||
| 判据 | 证据 |
|
||||
| --- | --- |
|
||||
| 1 后台 GET 带版本、公开 GET 形状不变 | `cargo test -p api-server app::tests::admin_editor_generation_pricing_route_saves_config_and_updates_public_route`、`app::tests::public_editor_generation_pricing_route_returns_default_config` |
|
||||
| 2 缺段 400 / 版本不匹配 409 且不落库 | `app::tests::admin_editor_generation_pricing_route_rejects_missing_model3d_section`、`..._rejects_stale_version`、`state::tests::saving_pricing_with_stale_version_is_rejected` |
|
||||
| 3 保存后公开路由立即返回新价 | 同上第一条用例在保存后重读公开路由断言新价 |
|
||||
| 4 缺键补齐、契约外键剔除告警 | `editor_generation_model3d_records::tests::{missing_keys_and_missing_sections_are_backfilled_from_local_config,out_of_contract_keys_are_dropped,missing_key_without_local_value_fails_closed,half_configured_sections_are_rejected,duplicate_keys_are_rejected,writing_requires_the_complete_contract_key_set}` |
|
||||
| 5 覆盖文件兜底与缺段补齐 | `editor_generation_config::tests::editor_generation_pricing_legacy_override_backfills_missing_model3d_section`、`..._default_path_falls_back_to_legacy_override_seed` |
|
||||
| 6 未配置仍 fail closed | `editor_generation_config::tests::editor_generation_pricing_fails_closed_when_model3d_section_is_absent` |
|
||||
| 7 admin-web 3D 区块渲染 / 校验 / 409 | `npx vitest run apps/admin-web`(210 passed):`AdminEditorGenerationPricingPage.test.tsx`、`adminEditorGenerationPricing.test.ts` |
|
||||
| 8 门禁 | `cargo test --locked -p api-server`(1183 passed,另有一条与本改动无关的时序敏感用例在整包并行下偶发失败、单跑通过)、`cargo test --locked -p spacetime-module editor_generation`、`npm run check:spacetime-schema`、`npm run check:encoding`、`node scripts/check-doc-index.mjs`、`git diff --check` |
|
||||
|
||||
尚未执行(需真实环境):手工验收「后台改 3D 价 → 不重启 → 画布 3D 面板价随之变化 → 重启后仍是保存值」;需要在发布 Stdb 模块与 API 后按开发运维文档执行。
|
||||
|
||||
## 验证命令
|
||||
|
||||
```bash
|
||||
@@ -73,5 +104,6 @@ git diff --check
|
||||
- **归一化补齐的数值来源**:缺键用默认 JSON / 覆盖文件同段值补齐,若现网运营价与默认值不同,升级瞬间会把表内缺失键补成默认价。上线前先核对现网两段价格;必要时把现网值写进覆盖文件作为种子来源,再执行升级。
|
||||
- **覆盖文件的旧形状 3D 段**:处理策略见主规范「未决问题」,确认前不进入实现。
|
||||
- **发布顺序**:必须先发 Stdb 模块再发 API;API 先发时会因记录形状不匹配而报错。Web 面板可以晚于 API 发布。
|
||||
- **旧形状覆盖文件是发布前置门禁**:覆盖文件里若还留着 `model3d.basePrices`,升级后的 API 会在启动读取阶段解析失败。升级前先确认该文件不含 `model3d.basePrices`(缺整段是安全的,会按受控默认价补齐),见主规范「旧形状覆盖文件的处理」。
|
||||
- **回滚点 1(未发布)**:仅回滚代码,两列是纯追加且带默认值,不影响旧行为。
|
||||
- **回滚点 2(已发布 API)**:必须同时回滚 Stdb 与 API(旧 API 会把 3D 权威退回文件),回滚窗口内不要在后台改 3D 价;回滚后重新保存一次定价以确认两段仍在。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 3D 生成定价后台可编辑
|
||||
|
||||
Version: 1.0
|
||||
Status: active
|
||||
Status: 已实现,待真实环境手工验收与发布
|
||||
Date: 2026-09-23
|
||||
Parent Spec: `docs/【编辑器】模型定价配置管理方案-2026-06-22.md`
|
||||
Implementation Plan: `docs/project-memory/plans/【实施计划】3D生成定价后台可编辑-2026-09-23.md`
|
||||
@@ -38,6 +38,12 @@ Related: `docs/adr/【ADR】0005-3D生成定价迁入SpacetimeDB与后台编辑-
|
||||
7. admin-web 3D 区块渲染两个端点各 5 行 × 2 档价与 6 个加价项;存在 0 或空值时禁用保存并给出中文提示;收到 409 时提示重新读取。
|
||||
8. `npm run check:spacetime-schema`、`npm run check:encoding`、`cargo test --locked -p api-server` 定向用例、admin-web 定向 vitest、`node scripts/check-doc-index.mjs`、`git diff --check` 全部通过。
|
||||
|
||||
## 实现与验收记录(2026-09-23)
|
||||
|
||||
- 步骤与提交、逐条判据的证据表见 [`【实施计划】3D生成定价后台可编辑-2026-09-23.md`](./【实施计划】3D生成定价后台可编辑-2026-09-23.md)。
|
||||
- 验收标准 1–7 已有自动化用例覆盖;标准 8 的本地门禁全部通过。
|
||||
- 待办:真实环境手工验收(后台改 3D 价 → 不重启 → 画布 3D 面板价变化 → 重启后仍是保存值)与按 Stdb → API → Web 顺序发布。
|
||||
|
||||
## 依赖
|
||||
|
||||
- 画布 3D 入口已上线,价格只读公开定价查询(ADR 0004),本期不改其契约。
|
||||
|
||||
@@ -9373,3 +9373,13 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
- 代价与取舍:必须做加载期归一化,否则发布新增模型版本时表内缺键会让整段读取失败,且失败点同时打掉后台定价页,形成「进程起不来、后台也补不了价」的死锁;加价项目前按端点各留一份,用少量冗余换未来按端点差异化加价的空间。公开读取保持旧投影,代价是存储形状与公开形状短期内不一致。
|
||||
- 验证方式:文档先行阶段(ADR 0005、主规范、里程碑与实施计划、`CONTEXT.md` 术语);实现与验收证据见 [`【实施计划】3D生成定价后台可编辑-2026-09-23.md`](../plans/【实施计划】3D生成定价后台可编辑-2026-09-23.md)。
|
||||
- 关联文档:[ADR 0005](../../adr/【ADR】0005-3D生成定价迁入SpacetimeDB与后台编辑-2026-09-23.md)、[编辑器模型定价配置管理方案](../../【编辑器】模型定价配置管理方案-2026-06-22.md)、[技术方案 Tripo 3D生成API集成](../../technical/【技术方案】Tripo 3D生成API集成-2026-09-21.md)。
|
||||
|
||||
## 2026-09-23 3D 定价后台可编辑的实现口径:公开投影取文生加价项、CAS 复用行内 updated_at、覆盖文件缺段按默认补齐
|
||||
|
||||
- 背景:设计口径(ADR 0005、主规范、决策记录同日上一条)落实现时,暴露出四个必须当场钉死的选择,否则实现会在细节上分叉成另一种语义。
|
||||
- 决策:① 公开读模型 `GET /api/editor/generation-pricing` 的 3D 段由内部两段投影成迁移前的旧形状(`Model3dPricingPublicView`),加价项只有一张表时取文生 3D 段,两段不一致打告警;前端升级到按端点读取前,两段加价项必须保持一致。② 乐观锁版本复用行内 `updated_at` 微秒值:后台 GET 回 `updatedAtMicros`,POST 带 `expectedUpdatedAtMicros`,比对在 procedure 事务内完成,不一致返回 409;写入的新版本取「当前时间」与「期望版本 + 1」的较大值,保证同一微秒内连续保存也推进版本。③ 覆盖文件整段缺 3D 段时按受控默认 JSON 的两段补齐并告警(与已有的 SFX 模型补齐同一条路径),不把「文件里没有这一段」解释成「3D 未配置」,否则老种子文件会把 3D 生成整段关掉。④ 覆盖文件里的旧形状 3D 段不做兼容分支,改为发布前置门禁:升级 API 前确认现网覆盖文件不含 `model3d.basePrices`。
|
||||
- 原因:**公开形状不能跟着存储形状走**——它是画布唯一的价格真相且前端零兜底,而静态站点与 api-server 独立发布,改形状在错开发布窗口内就是 3D 入口不可用;**版本复用 `updated_at`** 让乐观锁不需要新列、新表或第二套时间语义,且天然是「最后一次成功写入」的标识;**缺段补齐**把「老文件 / 老行」与「运营养未配置」区分开,避免一次发布顺手关掉一个已在收费的入口;**旧形状不兼容**是因为本分支未合并,旧形状只可能出自本分支或人工手写,为它保留分支会让配置文件形状长期存在两种真相。
|
||||
- 代价与取舍:公开投影有损,两段加价项差异化会让画布预估价按文生段计算(实际扣费仍以服务端账单为准),这条限制写到主规范里当前端升级的触发条件;发布前置门禁依赖人工/发布流程执行,暂未加进发布脚本;`models` 与 3D 两段仍共享同一行与同一版本号,一次保存要么全成要么全不成。
|
||||
- 影响面:`server-rs/crates/api-server/src/{tripo3d/pricing.rs,editor_generation_config.rs,editor_generation_model3d_records.rs,state.rs,admin.rs,app.rs,editor_project.rs}`、`server-rs/crates/spacetime-module/src/editor_project_storage.rs`、`server-rs/crates/spacetime-client/src/active/mapper/editor_project.rs` 与生成绑定、`server-rs/crates/shared-contracts/src/editor_generation.rs`、`apps/admin-web/src/{api/adminApiTypes.ts,api/adminApiClient.ts,pages/AdminEditorGenerationPricingPage.tsx,pages/AdminEditorGenerationModel3dPricingSection.tsx,pages/adminEditorGenerationPricing.ts}`、主规范、里程碑与实施计划、`pitfalls.md`。
|
||||
- 验证方式:`cargo test --locked -p api-server`(1183 passed;另有一条与本改动无关的时序敏感用例在整包并行下偶发失败、单跑通过)、`cargo test --locked -p spacetime-module editor_generation`、`npm run check:spacetime-schema`、`npm run check:encoding`、`node scripts/check-doc-index.mjs`、`git diff --check`、`npx vitest run apps/admin-web`(210 passed)、`npx tsc --noEmit -p apps/admin-web/tsconfig.json` 与改动文件 eslint。真实环境手工验收与发布尚未执行。
|
||||
- 关联文档:[ADR 0005](../../adr/【ADR】0005-3D生成定价迁入SpacetimeDB与后台编辑-2026-09-23.md)、[编辑器模型定价配置管理方案](../../【编辑器】模型定价配置管理方案-2026-06-22.md)、[实施计划](../plans/【实施计划】3D生成定价后台可编辑-2026-09-23.md)。
|
||||
|
||||
@@ -6007,3 +6007,21 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
|
||||
- 统计:job 私有 daemon 设置 `SCCACHE_IDLE_TIMEOUT=0`,由 `report` 显式停止;最终测试 bin 的不可缓存编译或测试可能超过一分钟,短 idle timeout 会让 daemon 提前退出,结尾查询启动新 daemon 后误报零次请求。容器销毁仍会回收该 job 的全部进程。
|
||||
- 磁盘:快照构建拒绝含 `/opt/genarrative-ci/rust-cache` 的基础镜像,始终从无对象缓存的镜像重建;容器内删除旧对象不能释放 Docker 底层。对象缓存容量上限不涵盖宿主旧镜像及导出归档,切换验证后按运维文档人工保留当前版和一个回滚版,同时保护运行中 CI 使用的镜像。
|
||||
- 扩展:预热所有 Rust 测试组时保留各自 cwd、profile、features 和锁策略;同一临时 target 的 Cargo fresh 不代表不同 cwd 都已生成缓存键,AGC 提示词契约、分片和 smoke 切换入口前清理预热 target。不要把 workspace 与 spacetime-module 合并成一次编译;Native shell release step 清空双 wrapper,避免将测试缓存扩展成发布缓存。当前 sccache 0.18.0 的 READ_ONLY 在 miss 后仍打包产物并产生 cache write error,不适合用来承诺“未命中无开销”。
|
||||
|
||||
## 2026-09-23 后台定价「省略即沿用」会把别人的改动静默覆盖
|
||||
|
||||
- **现象**:admin-web 把后台读取到的整份定价原样回传保存;当 3D 段只在进程内存(文件配置)里时,陈旧的浏览器标签页保存图片价格会把旧的 3D 段一并写回,价格在无人察觉的情况下回滚。
|
||||
- **成因**:后端把「`model3d` 缺席或为 null」解释成「沿用当前 3D 段」(`with_previous_model3d`),于是 payload 的形状本身携带了两种语义:给值是覆盖,不给是沿用;而前端是整份回传,两种语义在真实浏览器里无法区分。
|
||||
- **处理(现行口径)**:定价保存不再有隐含沿用语义——`models` 与 3D 两段都必须显式给出,缺段即 400;同时用读取时拿到的定价版本做事务内乐观锁,不一致返回 409。3D 定价存进 SpacetimeDB 与 `models` 同事务后,任何一次保存写的都是页面当时看到的完整状态。
|
||||
- **易错点**:① 不要为了「顺手支持只改一个字段」再引入任何「省略 = 沿用」分支,缺段就该报错;② 乐观锁的版本必须由读取回包带出并原样回传,不能在前端自造或缓存;③ 首次种子写入(表为空)显式不带期望版本,表内没有行时不允许凭兜底版本直接创建。
|
||||
- **验证**:`server-rs/crates/api-server/src/app.rs` 的 `admin_editor_generation_pricing_route_rejects_missing_model3d_section` / `..._rejects_stale_version` / `..._saves_config_and_updates_public_route`;`state::tests::saving_pricing_with_stale_version_is_rejected`;module 侧 `editor_generation_pricing_expected_version_must_match_current_row`。
|
||||
- **关联**:`docs/【编辑器】模型定价配置管理方案-2026-06-22.md`、`docs/adr/【ADR】0005-3D生成定价迁入SpacetimeDB与后台编辑-2026-09-23.md`。
|
||||
|
||||
## 2026-09-23 公开读模型改了存储形状就等于在两条发布线上做破坏性变更
|
||||
|
||||
- **现象**:3D 定价内部结构从「端点判别键」改成两段并列后,若直接让 `GET /api/editor/generation-pricing` 输出内部结构,画布 3D 入口会因为读不到 `basePrices` 而判定「定价缺失」,整条 3D 生成不可用。
|
||||
- **成因**:公开读模型是画布唯一的价格真相且前端零兜底(缺段即不可提交),而 admin-web / 主站静态资源与 api-server 是两条独立发布线——形状变更在错开发布窗口内必然有一段时间两侧不匹配。
|
||||
- **处理(现行口径)**:`GET /api/editor/generation-pricing` 的 3D 段由内部两段投影回旧形状(`Model3dPricingPublicView`),前端不需要改动。投影是有损的:加价项只有一张表,取文生 3D 段,两段不一致会打告警,因此前端升级到按端点读取加价项之前,两段加价项必须保持一致。
|
||||
- **易错点**:① 改定价内部结构时,先确认公开读模型的形状有没有被顺手带出去,测试要直接断言公开路由的字段名;② 「两段可以独立定价」不等于「两段可以随便不同」,加价项差异会先影响画布预估价。
|
||||
- **验证**:`app::tests::public_editor_generation_pricing_route_returns_default_config`(断言 `model3d.basePrices` / `model3d.addOnPrices` 与内部 `textToModelPricing` 不出现);`tripo3d::pricing` 与 `editor_generation_model3d_records` 的定向用例。
|
||||
- **关联**:`docs/adr/【ADR】0005-3D生成定价迁入SpacetimeDB与后台编辑-2026-09-23.md`。
|
||||
|
||||
Reference in New Issue
Block a user