后台模型定价保存去掉版本比对:保存即整段覆盖
- shared-contracts:删掉 `EDITOR_GENERATION_PRICING_VERSION_CONFLICT` 常量 - spacetime-module:`EditorGenerationPricingConfigUpsertInput` 去掉期望版本字段,删掉版本比对方法与它的用例,入库只按请求内容整段覆盖 - spacetime-client:重新生成定价 upsert input 绑定(少一个字段),记录映射同步去掉版本 - api-server:进程内缓存去掉版本字段与 409 冲突错误,`editor_generation_pricing()` / `save_editor_generation_pricing()` 只返回配置本身,`EditorGenerationPricingStore::replace` 注释写明「只是缓存刷新、不做版本校验、不是拦截点」 - api-server admin/app:后台 GET 不再返回 `updatedAtMicros`,POST 不再读 `expectedUpdatedAtMicros`,删掉 409 映射与「旧版本被拒」用例,新增「连续保存两次以后一次为准」 - admin-web:定价类型去掉版本字段,保存请求不再回传版本,删掉 409 分支与「重新读取」提示及其用例 - docs:主规范新增「保存语义」段(整段覆盖、同时只有一个管理员在操作、缓存替换不是拦截点),后端数据契约、ADR 0005、里程碑 / 实施计划、决策记录与 pitfalls 同步为删除版本比对
This commit is contained in:
@@ -1,5 +1,15 @@
|
||||
# 决策记录
|
||||
|
||||
## 2026-09-24 模型定价保存去掉版本比对:后台同时只有一个管理员在操作
|
||||
|
||||
- 背景:2026-09-23 引入的「行内 `updated_at` 版本比对」(后台 GET 回 `updatedAtMicros`、POST 带 `expectedUpdatedAtMicros`、procedure 在事务内比对、不一致返回 409)落地后暴露两件事。一是这个版本号没有真实并发场景支撑:后台定价页同时只有一个管理员在操作,409 的后果只是「刷新一下再保存」,却给前后端都加了一条无从处理的失败路径。二是读内容与读版本号原本是两个分开的动作,在「读数据库失败、退回进程内缓存」这条少见分支上,两次读之间若有人写入,调用方会拿到旧内容配新版本号——本该被拦下的写入会被放行,本该能保存的反而被当成版本冲突。
|
||||
- 决策:整体删除版本比对与版本号。`EditorGenerationPricingConfigUpsertInput` 去掉 `expected_updated_at_micros`;`EditorGenerationPricingStore` 去掉版本字段与对应方法;后台 GET 不再返回 `updatedAtMicros`、POST 不再带 `expectedUpdatedAtMicros`,冲突 409 分支与前端「重新读取」提示一并删除;`save_editor_generation_pricing` 与 `editor_generation_pricing()` 只返回配置本身。保存语义固定为「整段覆盖」:请求里的 `models` 与两段 3D 价格就是新的最终状态,后一次保存整体取代前一次。`EditorGenerationPricingStore::replace` 明确记为「只刷新进程内缓存,不做版本校验、不是拦截点」,写入判断只发生在 procedure 的入库事务里。
|
||||
- 原因:**没有并发就没有版本号的价值**——加它只是把一次普通的覆盖保存变成需要用户处理的中断;**两次读本来就是错的形状**,正确做法是「保存即覆盖」而不是把内容和版本号拆开再拼回去;**拦截点唯一**,写入合法性只在 procedure 的入库事务里判断一次,进程内缓存不该有第二份判断逻辑。
|
||||
- 代价与取舍:放弃「两个人同时编辑时提示冲突」的能力,靠「同时只有一个管理员在操作」这个前提兜住;前提一旦不成立(例如以后开多人同时改价),必须重新设计,而不是把版本号加回来。后台不再能区分「我读到的是不是最新」,页面只能在保存后重新读取确认。
|
||||
- 影响面:`server-rs/crates/shared-contracts/src/editor_generation.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/api-server/src/{editor_generation_config.rs,state.rs,admin.rs,app.rs}`、`apps/admin-web/src/{api/adminApiTypes.ts,pages/AdminEditorGenerationPricingPage.tsx,pages/adminEditorGenerationPricing.ts}` 及对应测试、主规范「保存语义」段、后端数据契约文档、ADR 0005、`pitfalls.md`。
|
||||
- 验证方式:`cargo test --locked -p api-server -- editor_generation_pricing saving_pricing`、`cargo test --locked -p spacetime-module -- editor_generation_pricing`、`npx vitest run apps/admin-web/src/pages/AdminEditorGenerationPricingPage.test.tsx apps/admin-web/src/pages/adminEditorGenerationPricing.test.ts`、`npm run admin-web:typecheck`、`npm run check:encoding`、`git diff --check`。
|
||||
- 关联文档:[ADR 0005](../../adr/【ADR】0005-3D生成定价迁入SpacetimeDB与后台编辑-2026-09-23.md)、[编辑器模型定价配置管理方案](../../【编辑器】模型定价配置管理方案-2026-06-22.md)、[后端架构 server-rs 与 SpacetimeDB 数据契约](../../【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md)。
|
||||
|
||||
## 2026-09-24 3D 参数面板开放模型选择:版本取「本端点有底价」的键,能力规则抄 provider,端点与提交体由类型绑死
|
||||
|
||||
- 背景:3D 面板此前把请求体与报价里的 `model` 一起写死成 `DEFAULT_MODEL3D_MODEL_VERSION`(`v3.1-20260211`),而公开读模型的定价段是按 `endpoint × modelVersion` 展开的;后端加载即校验「每个契约版本 × 两个端点都必须有底价」,于是定价表里已配好的 `v3.0` / `v2.5` / `P1` / `P2` 四个版本在前端永远不可达,用户也看不到任何模型选择入口。同一轮还暴露两个类型层面的口子:提交函数签名是「endpoint 联合 + 提交体联合」两个彼此独立的字段,把图生请求体发到文生地址照样编译通过;`Model3dPricingConfig.basePrices` 内层键写成 `string`,写错的版本号在编译期没有任何反馈。
|
||||
@@ -9389,6 +9399,7 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
- 影响面:`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)。
|
||||
- 后续修订(2026-09-24):本条 ② 的版本比对与 409 已整体删除,保存改为整段覆盖,见同日决策「模型定价保存去掉版本比对」。
|
||||
|
||||
## 2026-09-23 3D 提交落点改为平坦可选字段:`target` tagged enum 作废,服务端按二选一校验
|
||||
|
||||
|
||||
@@ -6004,9 +6004,9 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
|
||||
|
||||
- **现象**: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`。
|
||||
- **处理(现行口径,2026-09-24 修订)**:定价保存不再有隐含沿用语义——`models` 与 3D 两段都必须显式给出,缺段即 400。3D 定价存进 SpacetimeDB 与 `models` 同事务后,任何一次保存写的都是页面当时看到的完整状态;保存本身不做版本比对,写进去的整段直接成为新状态(后台定价同时只有一个管理员在操作,所以不再需要版本号;原先的版本号比对已删除)。
|
||||
- **易错点**:① 不要为了「顺手支持只改一个字段」再引入任何「省略 = 沿用」分支,缺段就该报错;② 不要再把版本号加回来——后台定价假设同时只有一个管理员在线,保存是整段覆盖,读到的旧版本号只会制造 409 这类无从处理的失败;③ 表为空时的种子写入仍由 procedure 的入库事务负责,不靠「先读空、再无脑写」的两步流程。
|
||||
- **验证**:`server-rs/crates/api-server/src/app.rs` 的 `admin_editor_generation_pricing_route_rejects_missing_model3d_section` / `admin_editor_generation_pricing_route_saves_config_and_updates_public_route`;`state::tests::saving_pricing_applies_explicit_model3d` / `saving_pricing_twice_keeps_last_write`(后一次保存直接覆盖前一次)。
|
||||
- **关联**:`docs/【编辑器】模型定价配置管理方案-2026-06-22.md`、`docs/adr/【ADR】0005-3D生成定价迁入SpacetimeDB与后台编辑-2026-09-23.md`。
|
||||
|
||||
## 2026-09-23 公开读模型改了存储形状就等于在两条发布线上做破坏性变更
|
||||
|
||||
Reference in New Issue
Block a user