扣费路径的校验问题 #147

Closed
opened 2026-08-07 16:36:56 +08:00 by lhk229 · 1 comment
Owner
<html> <html><head></head>

看了代码确认过关键事实,说明如下。不改代码。

机制

计费入口 execute_billable_asset_operation_with_cost 的形状是「先扣、再跑、失败退」:

124  let points_consumed = consume_asset_operation_points(...)  ← 扣费
135  let mut refund_on_drop = AssetOperationRefundOnDrop::new(...)
146  match operation.await {                                    ← 整条生成链路都在这个闭包里
151      Err(error) => { if points_consumed && should_refund... { refund_now() } }

should_refund_asset_operation_error 无条件返回 true——任何失败都退款。

问题在于闭包里的执行顺序。以 SFX 为例(generation.rs:328 起):

扣泥点 → LLM 翻译(最多 2 轮)→ ElevenLabs 生成 → MP3 校验
       → OSS 上传 → confirm_asset_object + bind_asset_object_to_entity
       → create_editor_asset  ← 这里才第一次校验 folder 归属

assetFolderId 从 HTTP 入站到这一步没有任何校验,连格式校验都没有。normalize_generated_asset_folder_id 只是把 "project" 和旧版 "folder-*" 重写成默认文件夹 ID,其余值原样透传,不查库。唯一的检查是 SpacetimeDB reducer 里的 require_owned_asset_folder

.ok_or_else(|| "素材文件夹不存在".to_string())?;
if folder.owner_user_id != owner_user_id {
    return Err("无权访问该素材文件夹".to_string());
}

这个 Err 一路 ? 冒泡回计费闭包,触发退款。用户的泥点全额退回,但翻译的 LLM 调用、ElevenLabs 的生成调用、OSS 的写入已经全部发生且不可逆。

攻击面

登录用户拿自己的合法 projectId,配一个随便编的 assetFolderId(不存在,或属于别人),反复提交。每次:

  • 用户成本:0 泥点(退了)
  • 平台成本:1 次 LLM 翻译 + 1 次 ElevenLabs 计费生成 + 1 次 OSS 写入

不需要任何越权——projectId 是自己的,鉴权全过。这是报告里说的"免费消耗 provider 的入口",措辞准确。

需要补一句我核查的边界:我在 app.rs 没找到针对这些生成路由的按账号调用配额(只 grep 了常见关键字),仓库里有 backpressure.rs 这类整体限流但我没读。如果要拿"有没有天然限流"当决策依据,这一点得单独确认。

另外还有一个我没有验证的次生问题:confirm_asset_objectbind_asset_object_to_entitycreate_editor_asset 之前就已经成功了,失败后大概率留下没有 editor_asset 行的孤儿 OSS 对象。有没有清理机制我没查,先记为待确认。

波及范围

所有走 execute_billable_asset_operation_with_cost 且把 asset_folder_id 一路透传到 create_editor_asset 的路径,形状完全一致:

路径 计费入口
图片生成 / 改图 editor_project.rs:4300、:4320
视频生成 editor_project.rs:6515
UI 素材提取 editor_project.rs:7690
角色动画 character_animation_assets.rs:749、:1104
SFX generation.rs:328
BGM 同文件 BGM 分支

所以这不是 SFX V2 引入的回归,是这套计费/落库骨架从一开始就有的形状。SFX 只是最新一条沿用它的路径。

唯一的例外,以及它为什么不算先例

完美像素路径确实做了前置只读预检——preflight_editor_pixel_art_result 是仓库里唯一一个"先只读校验、后写副作用"的独立 procedure,里面明确调了 folder 归属校验,还有一条源码扫描测试盯着它保持只读。

但它不扣泥点、不调付费 provider——它是本地像素规整。也就是说这个模板证明的是"架构上做得到",而不是"扣费场景已经这么做了"。扣费场景里一个采用的都没有。

不改的代价

  • 已知可被廉价滥用的 provider 消耗入口,成本落在平台侧,用户侧零成本。
  • 正常用户几乎碰不到:前端唯一构造点只会发自己素材库里真实存在的 folder id,或者干脆不带。所以这是故意构造才能触发的问题,不是日常故障。
  • 真出事时账面是"大量失败任务 + 退款完成",钱包账本自洽,从计费数据上看不出异常,只能从 provider 账单和失败率的背离发现。这是它最难受的地方。

真要修的时候

两条路,都不用改 schema:

  1. :扣费前用现成的只读 get_editor_asset_library(owner_user_id) 拉快照,本地比对 folder id 是否在其中。零 SpacetimeDB 改动,但多一次往返。
  2. :仿 preflight_editor_pixel_art_result 加一个只读预检 procedure,在 execute_billable_asset_operation_with_cost 之前调用。跟已有模板一致,也能顺带覆盖 projectId

两种都必须和 normalize_generated_asset_folder_id 的默认文件夹语义严格对齐("project" / "folder-*" / 默认 folder 三种特殊值),否则会把合法请求误拒——validate_editor_pixel_art_preflight_asset_folder 里已经写过这段对齐逻辑,可以直接抄。

要我把这份说明写进 docs/project-memory/shared-memory/pitfalls.md 或者单独一份遗留问题文档吗?分支上已经有"记录调试输出敏感配置遗留问题"这种只记录不修的先例,格式可以照着来。

</html> </html>看了代码确认过关键事实,说明如下。**不改代码。**

机制

计费入口 [execute_billable_asset_operation_with_cost](https://claude.ai/epitaxy/server-rs/crates/api-server/src/asset_billing.rs:96) 的形状是「先扣、再跑、失败退」:

124  let points_consumed = consume_asset_operation_points(...)  ← 扣费
135  let mut refund_on_drop = AssetOperationRefundOnDrop::new(...)
146  match operation.await {                                    ← 整条生成链路都在这个闭包里
151      Err(error) => { if points_consumed && should_refund... { refund_now() } }

[should_refund_asset_operation_error](https://claude.ai/epitaxy/server-rs/crates/api-server/src/asset_billing.rs:274) 无条件返回 true——任何失败都退款。

问题在于闭包里的执行顺序。以 SFX 为例([generation.rs:328](server-rs/crates/api-server/src/vector_engine_audio_generation/generation.rs:328) 起):

扣泥点 → LLM 翻译(最多 2 轮)→ ElevenLabs 生成 → MP3 校验
       → OSS 上传 → confirm_asset_object + bind_asset_object_to_entity
       → create_editor_asset  ← 这里才第一次校验 folder 归属

assetFolderId 从 HTTP 入站到这一步没有任何校验,连格式校验都没有。normalize_generated_asset_folder_id 只是把 "project" 和旧版 "folder-*" 重写成默认文件夹 ID,其余值原样透传,不查库。唯一的检查是 SpacetimeDB reducer 里的 [require_owned_asset_folder](https://claude.ai/epitaxy/server-rs/crates/spacetime-module/src/editor_project_storage.rs:5345)

.ok_or_else(|| "素材文件夹不存在".to_string())?;
if folder.owner_user_id != owner_user_id {
    return Err("无权访问该素材文件夹".to_string());
}

这个 Err 一路 ? 冒泡回计费闭包,触发退款。用户的泥点全额退回,但翻译的 LLM 调用、ElevenLabs 的生成调用、OSS 的写入已经全部发生且不可逆。

攻击面

登录用户拿自己的合法 projectId,配一个随便编的 assetFolderId(不存在,或属于别人),反复提交。每次:

  • 用户成本:0 泥点(退了)
  • 平台成本:1 次 LLM 翻译 + 1 次 ElevenLabs 计费生成 + 1 次 OSS 写入

不需要任何越权——projectId 是自己的,鉴权全过。这是报告里说的"免费消耗 provider 的入口",措辞准确。

需要补一句我核查的边界:我在 app.rs 没找到针对这些生成路由的按账号调用配额(只 grep 了常见关键字),仓库里有 backpressure.rs 这类整体限流但我没读。如果要拿"有没有天然限流"当决策依据,这一点得单独确认。

另外还有一个我没有验证的次生问题:confirm_asset_objectbind_asset_object_to_entitycreate_editor_asset 之前就已经成功了,失败后大概率留下没有 editor_asset 行的孤儿 OSS 对象。有没有清理机制我没查,先记为待确认。

波及范围

所有走 execute_billable_asset_operation_with_cost 且把 asset_folder_id 一路透传到 create_editor_asset 的路径,形状完全一致:

路径 计费入口
图片生成 / 改图 [editor_project.rs:4300](server-rs/crates/api-server/src/editor_project.rs:4300)、[:4320](server-rs/crates/api-server/src/editor_project.rs:4320)
视频生成 [editor_project.rs:6515](server-rs/crates/api-server/src/editor_project.rs:6515)
UI 素材提取 [editor_project.rs:7690](server-rs/crates/api-server/src/editor_project.rs:7690)
角色动画 [character_animation_assets.rs:749](server-rs/crates/api-server/src/character_animation_assets.rs:749)、[:1104](server-rs/crates/api-server/src/character_animation_assets.rs:1104)
SFX [generation.rs:328](server-rs/crates/api-server/src/vector_engine_audio_generation/generation.rs:328)
BGM 同文件 BGM 分支

所以这不是 SFX V2 引入的回归,是这套计费/落库骨架从一开始就有的形状。SFX 只是最新一条沿用它的路径。

唯一的例外,以及它为什么不算先例

完美像素路径确实做了前置只读预检——[preflight_editor_pixel_art_result](https://claude.ai/epitaxy/server-rs/crates/spacetime-module/src/editor_project_storage.rs:2795) 是仓库里唯一一个"先只读校验、后写副作用"的独立 procedure,里面明确调了 folder 归属校验,还有一条源码扫描测试盯着它保持只读。

但它不扣泥点、不调付费 provider——它是本地像素规整。也就是说这个模板证明的是"架构上做得到",而不是"扣费场景已经这么做了"。扣费场景里一个采用的都没有。

不改的代价

  • 已知可被廉价滥用的 provider 消耗入口,成本落在平台侧,用户侧零成本。
  • 正常用户几乎碰不到:前端唯一构造点只会发自己素材库里真实存在的 folder id,或者干脆不带。所以这是故意构造才能触发的问题,不是日常故障。
  • 真出事时账面是"大量失败任务 + 退款完成",钱包账本自洽,从计费数据上看不出异常,只能从 provider 账单和失败率的背离发现。这是它最难受的地方。

真要修的时候

两条路,都不用改 schema:

  1. :扣费前用现成的只读 get_editor_asset_library(owner_user_id) 拉快照,本地比对 folder id 是否在其中。零 SpacetimeDB 改动,但多一次往返。
  2. :仿 preflight_editor_pixel_art_result 加一个只读预检 procedure,在 execute_billable_asset_operation_with_cost 之前调用。跟已有模板一致,也能顺带覆盖 projectId

两种都必须和 normalize_generated_asset_folder_id 的默认文件夹语义严格对齐("project" / "folder-*" / 默认 folder 三种特殊值),否则会把合法请求误拒——validate_editor_pixel_art_preflight_asset_folder 里已经写过这段对齐逻辑,可以直接抄。

要我把这份说明写进 docs/project-memory/shared-memory/pitfalls.md 或者单独一份遗留问题文档吗?分支上已经有"记录调试输出敏感配置遗留问题"这种只记录不修的先例,格式可以照着来。

<html> <body> <!--StartFragment--><html><head></head><body><p>看了代码确认过关键事实,说明如下。<strong>不改代码。</strong></p> <h2>机制</h2> <p>计费入口 <a href="https://claude.ai/epitaxy/server-rs/crates/api-server/src/asset_billing.rs:96"><code>execute_billable_asset_operation_with_cost</code></a> 的形状是「先扣、再跑、失败退」:</p> <pre><code>124 let points_consumed = consume_asset_operation_points(...) ← 扣费 135 let mut refund_on_drop = AssetOperationRefundOnDrop::new(...) 146 match operation.await { ← 整条生成链路都在这个闭包里 151 Err(error) =&gt; { if points_consumed &amp;&amp; should_refund... { refund_now() } } </code></pre> <p>而 <a href="https://claude.ai/epitaxy/server-rs/crates/api-server/src/asset_billing.rs:274"><code>should_refund_asset_operation_error</code></a> 无条件返回 <code>true</code>——<strong>任何</strong>失败都退款。</p> <p>问题在于闭包里的执行顺序。以 SFX 为例(<a href="https://claude.ai/epitaxy/server-rs/crates/api-server/src/vector_engine_audio_generation/generation.rs:328">generation.rs:328</a> 起):</p> <pre><code>扣泥点 → LLM 翻译(最多 2 轮)→ ElevenLabs 生成 → MP3 校验 → OSS 上传 → confirm_asset_object + bind_asset_object_to_entity → create_editor_asset ← 这里才第一次校验 folder 归属 </code></pre> <p><code>assetFolderId</code> 从 HTTP 入站到这一步<strong>没有任何校验</strong>,连格式校验都没有。<code>normalize_generated_asset_folder_id</code> 只是把 <code>"project"</code> 和旧版 <code>"folder-*"</code> 重写成默认文件夹 ID,其余值原样透传,不查库。唯一的检查是 SpacetimeDB reducer 里的 <a href="https://claude.ai/epitaxy/server-rs/crates/spacetime-module/src/editor_project_storage.rs:5345"><code>require_owned_asset_folder</code></a>:</p> <pre><code class="language-rust">.ok_or_else(|| "素材文件夹不存在".to_string())?; if folder.owner_user_id != owner_user_id { return Err("无权访问该素材文件夹".to_string()); } </code></pre> <p>这个 <code>Err</code> 一路 <code>?</code> 冒泡回计费闭包,触发退款。<strong>用户的泥点全额退回,但翻译的 LLM 调用、ElevenLabs 的生成调用、OSS 的写入已经全部发生且不可逆。</strong></p> <h2>攻击面</h2> <p>登录用户拿自己的合法 <code>projectId</code>,配一个随便编的 <code>assetFolderId</code>(不存在,或属于别人),反复提交。每次:</p> <ul> <li>用户成本:0 泥点(退了)</li> <li>平台成本:1 次 LLM 翻译 + 1 次 ElevenLabs 计费生成 + 1 次 OSS 写入</li> </ul> <p>不需要任何越权——<code>projectId</code> 是自己的,鉴权全过。这是报告里说的"免费消耗 provider 的入口",措辞准确。</p> <p>需要补一句我核查的边界:我在 <code>app.rs</code> 没找到针对这些生成路由的按账号调用配额(只 grep 了常见关键字),仓库里有 <code>backpressure.rs</code> 这类整体限流但我没读。如果要拿"有没有天然限流"当决策依据,这一点得单独确认。</p> <p>另外还有一个我<strong>没有验证</strong>的次生问题:<code>confirm_asset_object</code> 和 <code>bind_asset_object_to_entity</code> 在 <code>create_editor_asset</code> 之前就已经成功了,失败后大概率留下没有 <code>editor_asset</code> 行的孤儿 OSS 对象。有没有清理机制我没查,先记为待确认。</p> <h2>波及范围</h2> <p>所有走 <code>execute_billable_asset_operation_with_cost</code> 且把 <code>asset_folder_id</code> 一路透传到 <code>create_editor_asset</code> 的路径,形状完全一致:</p> 路径 | 计费入口 -- | -- 图片生成 / 改图 | editor_project.rs:4300、:4320 视频生成 | editor_project.rs:6515 UI 素材提取 | editor_project.rs:7690 角色动画 | character_animation_assets.rs:749、:1104 SFX | generation.rs:328 BGM | 同文件 BGM 分支 <p>所以这不是 SFX V2 引入的回归,是这套计费/落库骨架从一开始就有的形状。SFX 只是最新一条沿用它的路径。</p> <h2>唯一的例外,以及它为什么不算先例</h2> <p>完美像素路径确实做了前置只读预检——<a href="https://claude.ai/epitaxy/server-rs/crates/spacetime-module/src/editor_project_storage.rs:2795"><code>preflight_editor_pixel_art_result</code></a> 是仓库里唯一一个"先只读校验、后写副作用"的独立 procedure,里面明确调了 folder 归属校验,还有一条源码扫描测试盯着它保持只读。</p> <p>但它<strong>不扣泥点、不调付费 provider</strong>——它是本地像素规整。也就是说这个模板证明的是"架构上做得到",而不是"扣费场景已经这么做了"。扣费场景里一个采用的都没有。</p> <h2>不改的代价</h2> <ul> <li>已知可被廉价滥用的 provider 消耗入口,成本落在平台侧,用户侧零成本。</li> <li>正常用户几乎碰不到:前端唯一构造点只会发自己素材库里真实存在的 folder id,或者干脆不带。所以这是<strong>故意构造才能触发</strong>的问题,不是日常故障。</li> <li>真出事时账面是"大量失败任务 + 退款完成",钱包账本自洽,<strong>从计费数据上看不出异常</strong>,只能从 provider 账单和失败率的背离发现。这是它最难受的地方。</li> </ul> <h2>真要修的时候</h2> <p>两条路,都不用改 schema:</p> <ol> <li><strong>轻</strong>:扣费前用现成的只读 <code>get_editor_asset_library(owner_user_id)</code> 拉快照,本地比对 folder id 是否在其中。零 SpacetimeDB 改动,但多一次往返。</li> <li><strong>正</strong>:仿 <code>preflight_editor_pixel_art_result</code> 加一个只读预检 procedure,在 <code>execute_billable_asset_operation_with_cost</code> 之前调用。跟已有模板一致,也能顺带覆盖 <code>projectId</code>。</li> </ol> <p>两种都必须和 <code>normalize_generated_asset_folder_id</code> 的默认文件夹语义严格对齐(<code>"project"</code> / <code>"folder-*"</code> / 默认 folder 三种特殊值),否则会把合法请求误拒——<code>validate_editor_pixel_art_preflight_asset_folder</code> 里已经写过这段对齐逻辑,可以直接抄。</p> <p>要我把这份说明写进 <code>docs/project-memory/shared-memory/pitfalls.md</code> 或者单独一份遗留问题文档吗?分支上已经有"记录调试输出敏感配置遗留问题"这种只记录不修的先例,格式可以照着来。</p></body></html><!--EndFragment--> </body> </html>看了代码确认过关键事实,说明如下。**不改代码。** ## 机制 计费入口 [`[execute_billable_asset_operation_with_cost](https://claude.ai/epitaxy/server-rs/crates/api-server/src/asset_billing.rs:96)`](server-rs/crates/api-server/src/asset_billing.rs:96) 的形状是「先扣、再跑、失败退」: ``` 124 let points_consumed = consume_asset_operation_points(...) ← 扣费 135 let mut refund_on_drop = AssetOperationRefundOnDrop::new(...) 146 match operation.await { ← 整条生成链路都在这个闭包里 151 Err(error) => { if points_consumed && should_refund... { refund_now() } } ``` 而 [`[should_refund_asset_operation_error](https://claude.ai/epitaxy/server-rs/crates/api-server/src/asset_billing.rs:274)`](server-rs/crates/api-server/src/asset_billing.rs:274) 无条件返回 `true`——**任何**失败都退款。 问题在于闭包里的执行顺序。以 SFX 为例([[generation.rs:328](https://claude.ai/epitaxy/server-rs/crates/api-server/src/vector_engine_audio_generation/generation.rs:328)](server-rs/crates/api-server/src/vector_engine_audio_generation/generation.rs:328) 起): ``` 扣泥点 → LLM 翻译(最多 2 轮)→ ElevenLabs 生成 → MP3 校验 → OSS 上传 → confirm_asset_object + bind_asset_object_to_entity → create_editor_asset ← 这里才第一次校验 folder 归属 ``` `assetFolderId` 从 HTTP 入站到这一步**没有任何校验**,连格式校验都没有。`normalize_generated_asset_folder_id` 只是把 `"project"` 和旧版 `"folder-*"` 重写成默认文件夹 ID,其余值原样透传,不查库。唯一的检查是 SpacetimeDB reducer 里的 [`[require_owned_asset_folder](https://claude.ai/epitaxy/server-rs/crates/spacetime-module/src/editor_project_storage.rs:5345)`](server-rs/crates/spacetime-module/src/editor_project_storage.rs:5345): ```rust .ok_or_else(|| "素材文件夹不存在".to_string())?; if folder.owner_user_id != owner_user_id { return Err("无权访问该素材文件夹".to_string()); } ``` 这个 `Err` 一路 `?` 冒泡回计费闭包,触发退款。**用户的泥点全额退回,但翻译的 LLM 调用、ElevenLabs 的生成调用、OSS 的写入已经全部发生且不可逆。** ## 攻击面 登录用户拿自己的合法 `projectId`,配一个随便编的 `assetFolderId`(不存在,或属于别人),反复提交。每次: - 用户成本:0 泥点(退了) - 平台成本:1 次 LLM 翻译 + 1 次 ElevenLabs 计费生成 + 1 次 OSS 写入 不需要任何越权——`projectId` 是自己的,鉴权全过。这是报告里说的"免费消耗 provider 的入口",措辞准确。 需要补一句我核查的边界:我在 `app.rs` 没找到针对这些生成路由的按账号调用配额(只 grep 了常见关键字),仓库里有 `backpressure.rs` 这类整体限流但我没读。如果要拿"有没有天然限流"当决策依据,这一点得单独确认。 另外还有一个我**没有验证**的次生问题:`confirm_asset_object` 和 `bind_asset_object_to_entity` 在 `create_editor_asset` 之前就已经成功了,失败后大概率留下没有 `editor_asset` 行的孤儿 OSS 对象。有没有清理机制我没查,先记为待确认。 ## 波及范围 所有走 `execute_billable_asset_operation_with_cost` 且把 `asset_folder_id` 一路透传到 `create_editor_asset` 的路径,形状完全一致: | 路径 | 计费入口 | |---|---| | 图片生成 / 改图 | [[editor_project.rs:4300](https://claude.ai/epitaxy/server-rs/crates/api-server/src/editor_project.rs:4300)](server-rs/crates/api-server/src/editor_project.rs:4300)、[[:4320](https://claude.ai/epitaxy/server-rs/crates/api-server/src/editor_project.rs:4320)](server-rs/crates/api-server/src/editor_project.rs:4320) | | 视频生成 | [[editor_project.rs:6515](https://claude.ai/epitaxy/server-rs/crates/api-server/src/editor_project.rs:6515)](server-rs/crates/api-server/src/editor_project.rs:6515) | | UI 素材提取 | [[editor_project.rs:7690](https://claude.ai/epitaxy/server-rs/crates/api-server/src/editor_project.rs:7690)](server-rs/crates/api-server/src/editor_project.rs:7690) | | 角色动画 | [[character_animation_assets.rs:749](https://claude.ai/epitaxy/server-rs/crates/api-server/src/character_animation_assets.rs:749)](server-rs/crates/api-server/src/character_animation_assets.rs:749)、[[:1104](https://claude.ai/epitaxy/server-rs/crates/api-server/src/character_animation_assets.rs:1104)](server-rs/crates/api-server/src/character_animation_assets.rs:1104) | | SFX | [[generation.rs:328](https://claude.ai/epitaxy/server-rs/crates/api-server/src/vector_engine_audio_generation/generation.rs:328)](server-rs/crates/api-server/src/vector_engine_audio_generation/generation.rs:328) | | BGM | 同文件 BGM 分支 | 所以这不是 SFX V2 引入的回归,是这套计费/落库骨架从一开始就有的形状。SFX 只是最新一条沿用它的路径。 ## 唯一的例外,以及它为什么不算先例 完美像素路径确实做了前置只读预检——[`[preflight_editor_pixel_art_result](https://claude.ai/epitaxy/server-rs/crates/spacetime-module/src/editor_project_storage.rs:2795)`](server-rs/crates/spacetime-module/src/editor_project_storage.rs:2795) 是仓库里唯一一个"先只读校验、后写副作用"的独立 procedure,里面明确调了 folder 归属校验,还有一条源码扫描测试盯着它保持只读。 但它**不扣泥点、不调付费 provider**——它是本地像素规整。也就是说这个模板证明的是"架构上做得到",而不是"扣费场景已经这么做了"。扣费场景里一个采用的都没有。 ## 不改的代价 - 已知可被廉价滥用的 provider 消耗入口,成本落在平台侧,用户侧零成本。 - 正常用户几乎碰不到:前端唯一构造点只会发自己素材库里真实存在的 folder id,或者干脆不带。所以这是**故意构造才能触发**的问题,不是日常故障。 - 真出事时账面是"大量失败任务 + 退款完成",钱包账本自洽,**从计费数据上看不出异常**,只能从 provider 账单和失败率的背离发现。这是它最难受的地方。 ## 真要修的时候 两条路,都不用改 schema: 1. **轻**:扣费前用现成的只读 `get_editor_asset_library(owner_user_id)` 拉快照,本地比对 folder id 是否在其中。零 SpacetimeDB 改动,但多一次往返。 2. **正**:仿 `preflight_editor_pixel_art_result` 加一个只读预检 procedure,在 `execute_billable_asset_operation_with_cost` 之前调用。跟已有模板一致,也能顺带覆盖 `projectId`。 两种都必须和 `normalize_generated_asset_folder_id` 的默认文件夹语义严格对齐(`"project"` / `"folder-*"` / 默认 folder 三种特殊值),否则会把合法请求误拒——`validate_editor_pixel_art_preflight_asset_folder` 里已经写过这段对齐逻辑,可以直接抄。 要我把这份说明写进 `docs/project-memory/shared-memory/pitfalls.md` 或者单独一份遗留问题文档吗?分支上已经有"记录调试输出敏感配置遗留问题"这种只记录不修的先例,格式可以照着来。
Author
Owner
@kdletters
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: GenarrativeAI/Genarrative#147