diff --git a/docs/project-memory/plans/【实施计划】Tripo生成Worker执行链路与API路由-2026-09-21.md b/docs/project-memory/plans/【实施计划】Tripo生成Worker执行链路与API路由-2026-09-21.md index b62b03705..8c835ccd4 100644 --- a/docs/project-memory/plans/【实施计划】Tripo生成Worker执行链路与API路由-2026-09-21.md +++ b/docs/project-memory/plans/【实施计划】Tripo生成Worker执行链路与API路由-2026-09-21.md @@ -40,12 +40,12 @@ Milestone Spec: `docs/project-memory/plans/【里程碑】Tripo生成Worker执 3. **幂等提交路由** - 两个路由要求 Bearer 与 `Idempotency-Key`;复用现有外部生成幂等身份与 payload fingerprint,同键同请求返回原 operation,同键不同请求 409。 - - 执行顺序固定为:解析 JSON → 平台组合校验(含 provider 预检)→ 查价 → 入队;任何一步失败都在扣费与 provider 调用之前返回。 + - 执行顺序固定为:解析 JSON → 平台组合校验(含 provider 预检)→ 图片输入元数据预检(仅 image-to-model)→ 查价 → 入队;任何一步失败都在扣费与 provider 调用之前返回。 - 验收:缺头 400、缺价格段 fail closed、同键重放不新增扣费。 4. **图片输入解析** - - `source` 只接受站内资源或素材 ID;按分支校验归属并解析对象键,读出字节后上传 provider 换 `file_token` 再提交,不接受客户端直给 URL,也不把带签名的临时地址交给第三方。 - - 解析失败按 400 返回,不扣费、不入队。 + - `source` 只接受站内资源或素材 ID。提交时只做元数据预检(按 `source.kind` 定点校验归属、记录类型与对象键),worker 执行时重新确认同一事实,再读出字节并上传 provider 换 `file_token` 提交;不接受客户端直给 URL,也不把带签名的临时地址交给第三方。 + - 预检失败按 400 返回,不扣费、不入队;预检只按主键定点查引用,不拉取完整工程列表或素材库。 - 验收:跨 owner 的资源 / 素材被拒绝。 5. **产物落库** @@ -70,6 +70,8 @@ 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 执行时再重新确认同一事实。 + - 默认配置里的 `model3d` 泥点定价数值仍未确认,缺段时提交按 fail closed 拒绝。 - 没有跑过端到端真实调用(提交 → worker → OSS → 资源登记),现有证据只到 `cargo test -p api-server` 全绿(1085 通过)。 - 新增的 `tripo3d/` 文件里,`storage.rs` 的完整字节写入、`artifacts.rs` 的完整字节读取都留着流式 TODO。 diff --git a/docs/project-memory/plans/【里程碑】Tripo生成Worker执行链路与API路由-2026-09-21.md b/docs/project-memory/plans/【里程碑】Tripo生成Worker执行链路与API路由-2026-09-21.md index 6b00a8925..d800a6e5b 100644 --- a/docs/project-memory/plans/【里程碑】Tripo生成Worker执行链路与API路由-2026-09-21.md +++ b/docs/project-memory/plans/【里程碑】Tripo生成Worker执行链路与API路由-2026-09-21.md @@ -28,7 +28,7 @@ Implementation Plan: `docs/project-memory/plans/【实施计划】Tripo生成Wor ## 验收标准 -主规范“验收标准”第 1 至 11 条全部满足,其中必须包含 text-to-model 与 image-to-model 各一次真实 Provider 调用与产物下载校验。 +主规范“验收标准”第 1 至 12 条全部满足,其中必须包含 text-to-model 与 image-to-model 各一次真实 Provider 调用与产物下载校验。 ## 依赖 diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 2b891309d..ba80ad8ee 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -8901,3 +8901,11 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - 上线依赖(本次未完成):`*.preview.genarrative.world` 通配证书(Let's Encrypt 通配只能走 DNS-01,域名在 DNSPod,certbot 无官方插件,需要 DNSPod API Token 配合 acme.sh)、station 侧按 Host 分发到 `84xx` 端口、dev 通配 vhost 与隧道;控制面本体需在 station 用 `scripts/deploy/preview-deployer-install.sh` 重建发布。 - 验证:`cargo test -p preview-deployer-server`(13 项)、`apps/preview-deployer-web` vitest(13 项,含新增公网地址用例)、`npx tsc --noEmit`、`npm run preview-deployer:web:build`(`PREVIEW_DEPLOYER_WEB_BASE=/build/`)、`npm run check:preview-deployer`、`npm run check:encoding`、`git diff --check` 全部通过。 - 关联文档:[开发运维](../../【开发运维】本地开发验证与生产运维-2026-05-15.md)、[Jenkins容器预览部署控制面技术方案](../../technical/【开发运维】Jenkins容器预览部署控制面技术方案-2026-08-15.md)。 + +## 2026-09-21 Tripo 图生 3D 的站内图片引用:提交时元数据预检,执行时重新确认 + +- 背景:图生 3D 的 `source` 只接受当前账号的画布资源 ID 或素材 ID,provider 需要的图片地址由服务端解析。首版实现把归属校验与解析都放在 worker 里,跨 owner / 未登记的引用会被正常受理、扣费、入队后才失败;且解析走的是「列全部工程 / 读素材库再筛」的宽查询。 +- 决策:引用解析固定分两步,提交与执行互不代替。提交时只做元数据预检:按 `source.kind` 分支要求解析结果与记录类型一致,复用既有定点引用查询 `resolve_editor_reference_record_by_id_for_owner`(SpacetimeDB 单事务按 owner + 主键解析项目资源或素材),不读图片正文、不调用 provider,失败即 400 且不产生 operation、不扣费。worker 执行时重新确认同一事实,通过后才读一次图片正文并上传换 `file_token`。跨 owner、未登记、已删除对外收敛成同一句不可用,不把「他人 ID 是否存在」变成可探测信息;`kind` 与实际记录类型不符单独报错,因为两个值都由调用方给出。 +- 边界:预检只按主键定点查引用,禁止拉取当前用户的完整工程列表或素材库,也禁止在提交路径上执行 provider 副作用(上传 `file_token`、submit)。该口径适用于所有「服务端站内引用 → provider 输入」的生成入口,Tripo 是首个按此收口的类别。 +- 验证:`cargo test -p api-server` 1085 通过(含新增的 `tripo3d::image_source` 源码钉住用例,断言预检与定点解析只调用窄查询、不含列工程 / 列素材库 / 读正文 / 上传);`cargo check -p api-server` 无 tripo3d 警告;`npm run check:encoding`、`node scripts/check-doc-index.mjs`、`git diff --check` 通过。真实 provider 端到端仍未跑。 +- 关联文档:[技术方案 Tripo 3D生成API集成](../../technical/【技术方案】Tripo 3D生成API集成-2026-09-21.md)、[实施计划 Tripo生成Worker执行链路与API路由](../plans/【实施计划】Tripo生成Worker执行链路与API路由-2026-09-21.md)。 diff --git a/docs/technical/【技术方案】Tripo 3D生成API集成-2026-09-21.md b/docs/technical/【技术方案】Tripo 3D生成API集成-2026-09-21.md index 326a12bf4..22e65c7db 100644 --- a/docs/technical/【技术方案】Tripo 3D生成API集成-2026-09-21.md +++ b/docs/technical/【技术方案】Tripo 3D生成API集成-2026-09-21.md @@ -49,6 +49,8 @@ source = { kind: "resource", resourceId } | { kind: "asset", assetId } 服务端按 ID 定点确认归属、解析出对象键后,从私有 OSS 读出字节并**上传 provider 换 `file_token`**,再以显式 `file_token` 提交生成任务:不把带签名的临时地址交给第三方,也不依赖 bucket 对公网可达。签名地址会在异步排队期间过期,且等于把私有桶的临时读取权外泄,因此不作为输入通道。 +引用解析分两步,提交与执行互不代替:**提交时只做元数据预检**——按 `source.kind` 的分支做定点归属校验,确认记录类型与对象键存在,不读图片正文、不调用 provider,因此跨 owner、未登记、已删除与 `kind` 不符的引用都在扣费与入队之前返回 400;跨 owner、未登记与已删除对外收敛成同一句不可用,不泄漏他人 ID 是否存在,`kind` 与解析结果不符则单独报错(两个值都由调用方给出,不涉及探测他人数据);**worker 执行时重新确认同一事实**,通过后才读一次图片正文并上传换 `file_token`。预检只按主键定点查引用,不允许拉取当前用户的完整工程列表或素材库。 + **结果落点**(两个端点都必填)使用 tagged enum: ```text @@ -182,13 +184,14 @@ width/height = 预览图像素尺寸 2. 同一 `Idempotency-Key` 重复提交返回同一 operation,钱包只有一条扣费流水。 3. 定价配置缺任一底价键时服务启动失败;请求命中 add-on 后扣费等于底价加全部命中 add-on;`fast` 与 `standard` 不加价。 4. 组合校验前置于 provider 副作用:`texture=false` + `textureQuality`、`generateParts=true` 与 `texture=true` 的同现请求被拒绝且不扣费。 -5. worker 崩溃后重新 claim 不会产生第二次 submit:已有 checkpoint 的 job 只轮询、下载与落库。 -6. submit 成功但 checkpoint 写入失败的 attempt 终态失败且不自动重提。 -7. provider 成功但落库失败时 job 失败、该 attempt 退款,checkpoint 保留供对账。 -8. 完成结果按端点严格类型返回,只含模型与预览的正式资源引用,不含 provider task ID 与带签名的临时 URL。 -9. `assetLibrary` 分支产出 `assetId`;`projectResource` 分支产出 `resourceId`,且 `assetKind=model3d`、`AssetObject.content_type` 为模型 mime、`content_length` 与实际对象一致。 -10. 真实 Provider smoke 覆盖 text-to-model 与 image-to-model 各一次,下载字节数与 `Content-Length` 一致。 -11. 定向测试、`cargo check`、`npm run check:spacetime-schema`、`npm run check:encoding`、`node scripts/check-doc-index.mjs`、`git diff --check` 全部通过。 +5. image-to-model 的 `source` 元数据预检前置于扣费与入队:跨 owner、未登记、已删除与 `kind` 不符的引用返回 400,不产生 operation、不扣费,也不把图片正文读进内存或调用 provider。 +6. worker 崩溃后重新 claim 不会产生第二次 submit:已有 checkpoint 的 job 只轮询、下载与落库。 +7. submit 成功但 checkpoint 写入失败的 attempt 终态失败且不自动重提。 +8. provider 成功但落库失败时 job 失败、该 attempt 退款,checkpoint 保留供对账。 +9. 完成结果按端点严格类型返回,只含模型与预览的正式资源引用,不含 provider task ID 与带签名的临时 URL。 +10. `assetLibrary` 分支产出 `assetId`;`projectResource` 分支产出 `resourceId`,且 `assetKind=model3d`、`AssetObject.content_type` 为模型 mime、`content_length` 与实际对象一致。 +11. 真实 Provider smoke 覆盖 text-to-model 与 image-to-model 各一次,下载字节数与 `Content-Length` 一致。 +12. 定向测试、`cargo check`、`npm run check:spacetime-schema`、`npm run check:encoding`、`node scripts/check-doc-index.mjs`、`git diff --check` 全部通过。 ## 未决问题