共享记忆补记本轮 3D 收口的三条决策与一条踩坑

- decision-log:视图输入显式化并改按文档 view-key 形态发出、派生来源取自请求 source、失败态提交开新一代
- pitfalls:失败态沿用同一尝试代次会让主按钮只是重现同一次失败,以及新增提交入口时的判断口径
This commit is contained in:
2026-09-23 18:59:54 +08:00
parent f198b7a6d0
commit 5ee9a1e2e2
2 changed files with 18 additions and 0 deletions
@@ -9452,3 +9452,12 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
- 代价与取舍:重下会丢掉已读字节并重新传输(换连接、重新取响应头),比断点续传多花流量,但代码与状态都更少,也避开了签名地址过期的问题;`download_model` / `download_rendered_image` 的签名加了 `max_bytes` 并改为返回完整字节,属于 `platform-tripo` 的公共 API 变更。
- 影响面:`server-rs/crates/platform-tripo/src/common/{config,client,types,error}.rs``server-rs/crates/api-server/src/tripo3d/{artifacts,worker,errors,provider}.rs``platform-tripo` smoke 示例、[技术方案 Tripo 3D模型Provider集成](../../technical/【技术方案】Tripo 3D模型Provider集成-2026-09-18.md)。
- 验证方式:`cargo test --locked -p platform-tripo`16 passed,含 `artifact_download_retries_the_whole_body_after_a_broken_transfer``artifact_download_rejects_bodies_over_the_caller_limit``zero_timeout_and_blank_credentials_are_rejected`)、`cargo test --locked -p api-server tripo3d`57 passed)、`cargo check -p platform-tripo --examples`
## 2026-09-23 3D 收口第二轮:视图输入显式化、派生来源取自请求、失败态提交开新一代
- 背景:3D review 收尾时确认三处口径:① multiview 的 `views` 契约里 `front` / `left` / `back` / `right` 是裸 `String``platform-tripo` 直接走 SDK 的 `FileInput::from(&str)`(裸字符串变体),服务端只能按前缀猜它是 URL、file_token 还是 task_id —— 生产路径上传拿到的是 file_token,最坏情况会被当成 task idreview #77);② worker 落库时 `source_resource_id: has_resource.then(|| resource_id.clone())` 填的是本次新生成的输出资源 id,既不是派生来源,也让「只落素材库」那条路径彻底丢掉溯源(review #75);③ 3D 失败态下主按钮仍可点且沿用同一个 `model3dAttemptNonce`,而幂等键 = 代次 + 内容指纹,同键同请求只会拿回原来那个终态 operation,点「生成 3D 模型」只是重现同一次失败(review #106)。
- 决策:① 视图输入改成显式契约 `Model3dViewInput``url` / `fileToken`,带 `kind` 的 tagged enum),线上 `inputs` 按 Tripo 文档推荐的 view-key 形态构造 `[{"front":{"url":…}},{"left":{"file_token":…}}]`;该字段由 `platform-tripo` 自己构造后经 SDK params 的 `extra` 透传(`MultiviewToModelParams::from_views` 只能发位置数组 `["<裸字符串>", "", …]`,表达不了这个形态),视图校验(front 必填、至少两张、空白按未提供)与 `inputs` 构造合并成同一条路径。② 派生来源改为从请求的 `source` 取:`Model3dJobRequest::source_resource_id()` 只认 `resource { resourceId }`,素材库来源与文生 3D 为 `None`;资源行记该来源,素材行优先指向本项目的资源行、只落素材库时退回该来源。③ 失败态下的任何提交(主按钮与「重试」)都先 `refreshModel3dAttemptNonce` 再提交,主按钮保持可点。
- 原因:① 契约是唯一的种类来源,写明是 URL 还是 file_token 之后,链路上没有任何一方需要猜前缀;view-key 是文档明确「推荐」的形态,且允许把值写成嵌套 `{url}` / `{file_token}`,正好对上契约里的显式取值。② `source_resource_id` 记的是派生来源,拿产物自己的 id 去填等于自证来源。③ 「再生成一次」必须开新一代:幂等键的用途是把重复点击折成同一次请求,不是把已失败的尝试永久锁死。
- 代价与取舍:① multiview 请求契约改形状(无现役调用方,只有冒烟示例与契约用例,未加兼容层),且线上形态最终以真实 provider 冒烟跑通为准——仓库单测钉的是发出的 JSON 形状。② `platform-tripo``inputs` 不再是 SDK 全权构造:失去 SDK `from_views` 的客户端校验,但同样的规则本来就由平台自己再校验一遍。③ 失败态下主按钮与「重试」行为一致,两个入口都开新一代。
- 影响面:`server-rs/crates/shared-contracts/src/model3d/multiview_to_model/{request,mod}.rs``packages/shared/src/contracts/model3d/multiview-to-model/{Model3dMultiviewInputs,Model3dViewInput}.ts``index.ts``server-rs/crates/platform-tripo/src/multiview_to_model/client.rs`、smoke 示例、`server-rs/crates/api-server/src/tripo3d/{job,worker}.rs``src/components/image-editor/model3d-generation/Model3dGenerationModal.tsx`、[技术方案 Tripo 3D模型Provider集成](../../technical/【技术方案】Tripo 3D模型Provider集成-2026-09-18.md)。
- 验证方式:`cargo test --locked -p shared-contracts`119 + 各契约用例全过,含 `multiview_views_require_typed_inputs_without_unknown_fields`)、`cargo test --locked -p platform-tripo`26 passed,含 `views_are_sent_as_view_key_entries_with_explicit_values` / `blank_views_are_treated_as_missing` / `views_require_front_and_at_least_two_entries` / `task_id_reuse_is_validated_and_wrapped`)、`cargo test --locked -p api-server tripo3d::`59 passed,含 `source_resource_id_comes_from_the_request_source_only`)、`npx vitest run src/components/image-editor/model3d-generation/`4 files / 50 passed)、`npm run check:encoding`5284 files)。
@@ -6025,3 +6025,12 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- **易错点**:① 改定价内部结构时,先确认公开读模型的形状有没有被顺手带出去,测试要直接断言公开路由的字段名;② 「两段可以独立定价」不等于「两段可以随便不同」,加价项差异会先影响画布预估价。
- **验证**`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`
## 2026-09-23 失败态沿用同一个 3D 尝试代次,主按钮点下去只是重现同一次失败
- **现象**:3D 生成失败后,用户不改任何参数直接点「生成 3D 模型」,界面没有任何新的尝试,拿回来的还是原来那个失败操作。
- **成因**:幂等键 = 尝试代次(`model3dAttemptNonce`)+ 请求内容指纹,服务端按「键 + payload 指纹」dedupe。参数一改本来就会换代号次(`applyModel3dPromptChange` / `applyModel3dTierChange` / `applyModel3dSwitchChange`),但「什么都不改再点一次」既不换代次也不改内容,于是命中同一次已终态的 operation。
- **处理(现行口径)**:失败态下的任何提交入口都先 `refreshModel3dAttemptNonce` 再提交(主按钮与「重试」共用同一份重置后的 dialog)。幂等键的用途是把重复点击折成同一次请求,不是把已失败的尝试永久锁死。
- **易错点**:① 新增提交入口时要问「这次提交属于新的一代还是同一次尝试」,只看 `canSubmit`(价格 / 输入 / 上传中)挡不住这一类;② 别把失败态的主按钮改成禁用就算修好——参数改过再点主按钮是用户的自然路径,禁用会把这条路堵上。
- **验证**`src/components/image-editor/model3d-generation/Model3dGenerationModal.test.tsx` 的「失败态下主按钮也开新一代」与「失败后展示原因与重试」两条用例;`Model3dGenerationFormModel.test.ts` 钉住参数改动换代次。
- **关联**`src/components/image-editor/model3d-generation/Model3dGenerationSubmission.ts``resolveModel3dRequestKey`)。