图生 3D 图片输入的失败语义同步到文档

- 实施计划写明预检失败的状态码口径:400 只覆盖跨 owner / 未登记 / 已删除 / kind 不符,403 / 409 / 5xx 保留原状态码,缺对象键 502
- 决策记录新增同日条目,说明收窄收敛范围与缺对象键改判 502 的原因、代价与验证方式
This commit is contained in:
2026-09-23 14:59:52 +08:00
parent 11d1324857
commit 1fa413a352
2 changed files with 12 additions and 2 deletions
@@ -45,7 +45,7 @@ Milestone Spec: `docs/project-memory/plans/【里程碑】Tripo生成Worker执
4. **图片输入解析**
- `source` 只接受站内资源或素材 ID。提交时只做元数据预检(按 `source.kind` 定点校验归属、记录类型与对象键),worker 执行时重新确认同一事实,再读出字节并上传 provider 换 `file_token` 提交;不接受客户端直给 URL,也不把带签名的临时地址交给第三方。
- 预检失败按 400 返回,不扣费、不入队;预检只按主键定点查引用,不拉取完整工程列表或素材库。
- 预检失败不扣费、不入队:跨 owner / 未登记 / 已删除 / `kind` 不符按 400`403`(服务身份校验失败)、`409`(版本冲突)与 5xx 保留原状态码,记录已解析但缺对象键按 502;预检只按主键定点查引用,不拉取完整工程列表或素材库。
- 验收:跨 owner 的资源 / 素材被拒绝。
5. **产物落库**
@@ -70,7 +70,7 @@ Milestone Spec: `docs/project-memory/plans/【里程碑】Tripo生成Worker执
步骤 1 至 6 已落地:配置与路由、组合校验、定价、入队、图片输入解析、产物 OSS 写入与 worker 执行链路均已提交;步骤 7 的接线(`app.rs` merge、worker 分支、长任务超时)与文档同步也已完成。尚未完成的是需要真实环境的项:
- 图片输入在提交时已补元数据预检(`tripo3d::image_source::preflight_image_source`):按 `source.kind` 分支配对记录类型并复用既有定点引用查询 `resolve_editor_reference_record_by_id_for_owner`,取代原先“列工程 / 列素材库再筛”的宽查询;跨 owner、未登记、已删除与 `kind` 不符都在扣费与入队前返回 400,worker 执行时再重新确认同一事实。
- 图片输入在提交时已补元数据预检(`tripo3d::image_source::preflight_image_source`):按 `source.kind` 分支配对记录类型并复用既有定点引用查询 `resolve_editor_reference_record_by_id_for_owner`,取代原先“列工程 / 列素材库再筛”的宽查询;跨 owner、未登记、已删除与 `kind` 不符都在扣费与入队前返回 400改判口径见同日决策记录(403 / 409 / 5xx 保留原状态码,缺对象键 502),worker 执行时再重新确认同一事实。
- 提交路由已补应用级验收用例:走真实 `build_router` 断言两个端点未鉴权 401、缺 `Idempotency-Key` 400、组合校验与定价必填字段冲突在定价与 provider 之前 400,因此路由 merge、鉴权中间件与校验顺序都有可执行证据,不依赖 provider 与 SpacetimeDB 环境。
- `model3d` 泥点定价已写入受控默认配置:换算口径 `ceil(0.8 × Tripo 官方 credit)`H 系列文生 `10 / 20`、图生 `20 / 30`P1 文生 `30 / 40`、图生 `40 / 50`P2 两端均 `100 / 110`add-on 六项按同一系数换算;`editor_generation_config` 的定向用例按 credit 表反算默认价,段缺失仍按 fail closed 拒绝。
@@ -9393,3 +9393,13 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
- 影响面:`server-rs/crates/shared-contracts/src/model3d/{text_to_model/request.rs,image_to_model/request.rs,common/mod.rs}`(删除 `common/generation_target.rs`)、`server-rs/crates/api-server/src/tripo3d/{job.rs,validation.rs,target.rs,routes.rs,worker.rs}``server-rs/crates/api-server/src/editor_project.rs`(删除 Standard 的 `result` 透传)、`packages/shared/src/contracts/model3d/`(删除 `common/Model3dGenerationTarget.ts` 与 barrel 导出)、`src/components/image-editor/model3d-generation/{Model3dGenerationSubmission.ts,useModel3dGenerationTask.test.tsx}``src/services/image-editor/editorProjectClient.ts`、技术方案 / 里程碑 / 实施计划与共享记忆。
- 验证方式:`cargo test -p shared-contracts``cargo test -p api-server`1193 passed)、`cargo test -p api-server tripo3d::`55 passed,含 `target_requires_exactly_one_flat_locator``flat_locator_maps_to_point_lookup_without_rewriting_ids``flat_target_is_normalized_before_enqueue`)、`npx vitest run src/components/image-editor/model3d-generation src/components/image-editor/model3d-preview`63 passed)、`npm run typecheck``npm run contracts:model3d:generate``npm run check:encoding``npm run check:rustfmt``git diff --check`
- 关联文档:[技术方案 Tripo 3D生成API集成](../../technical/【技术方案】Tripo 3D生成API集成-2026-09-21.md)、[实施计划 Tripo生成API契约与数据模型](../plans/【实施计划】Tripo生成API契约与数据模型-2026-09-21.md)。
## 2026-09-23 图生 3D 的图片输入失败语义:只收敛「引用不可用」,缺对象键改判 502
- 背景:`tripo3d/image_source.rs` 原先用 `is_client_error()` 把 resolver 的所有 4xx 都收敛成 `400 model3d-image-source-unavailable`(文案「图片输入必须是当前账号已登记的画布资源或素材。」),同时把「记录已解析、属于调用方、但没有对象键」也归进同一句 400。逐条核对后确认前者会把 `403``require_editor_generation_runtime_service_identity``map_editor_project_error` 的「无权」)与 `409`(版本冲突 / 幂等)说成用户引用写错,后者则把一个「DB 行存在但格式异常」的运维信号说成客户端问题。
- 决策:① 收敛范围收窄为 `400` / `404`,其余状态码(含 `403``409`、5xx)一律按原状态码与原文案上报;② 「记录已解析但缺对象键」单独判 `502 model3d-image-source-object-key-missing`(带 `field: source`),口径与 `editor_project::validate_editor_reference_id_for_owner``preflight_editor_icon_spec_reference_metadata``BAD_GATEWAY` 一致;③ 预检失败仍然不扣费、不入队。
- 原因:**错误语义要指向用户能改的东西**——`403` 是服务身份配置问题、`409` 是并发写入、缺对象键是数据完整性,三者都不是「你的图片没登记」。原实现的模块注释本身写着「只有基础设施失败保留原状态码」,但代码用 `is_client_error()` 覆盖了 `403` / `409`,注释与实现互相矛盾,按注释收口才自洽。
- 代价与取舍:`502``400` 的差异对外可见(前端会把 5xx 当可重试的服务端错误、把 400 当参数错误),这是有意的;token 与 ID 都不出响应体。缺对象键的场景按「上游数据异常」处理,客户端重试不会自愈,需要人工修数据行。
- 影响面:`server-rs/crates/api-server/src/tripo3d/image_source.rs`(收敛分支、新增 `image_source_object_key_missing`、模块文档与定向用例)、[实施计划 Tripo生成Worker执行链路与API路由](../plans/【实施计划】Tripo生成Worker执行链路与API路由-2026-09-21.md)。
- 验证方式:`cargo test --locked -p api-server tripo3d::image_source`4 passed,含 `image_source_resolution_only_collapses_unavailable_references` 覆盖 400 / 404 收敛与 403 / 409 / 500 保留、`image_source_resolution_reports_missing_object_key_as_server_side_failure` 断言 502 与 reason)、`rustfmt --check`
- 关联文档:[技术方案 Tripo 3D生成API集成](../../technical/【技术方案】Tripo 3D生成API集成-2026-09-21.md)、[实施计划 Tripo生成Worker执行链路与API路由](../plans/【实施计划】Tripo生成Worker执行链路与API路由-2026-09-21.md)。