Feat/adapt taonier #661
Reference in New Issue
Block a user
Delete Branch "feat/adapt-taonier-skill"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
注意到现在发布游戏 封面那块用的是画布里的assetid来引用图片,,,,独立出来用oss key
1.
[maintainability·low]apps/ai-game-creator-shell/tests/jsdomLocalStorageSetup.ts:17-34— 内存 Storage 替身重复三份jsdomLocalStorageSetup.ts、appSurface/harness.ts、chatPromptPolish.test.tsx各有一份逻辑几乎一致的内存Storage。length/key/removeItem语义以后要改就得改三处,容易漂。tests/memoryStorage.ts:createMemoryStorage(),三处统一 import(99d5fd1ea)。2.
[bug·high]apps/ai-game-creator-shell/src-tauri/src/game_distribution_publish.rs:448-450— 图片字节直接拼接算摘要会碰撞metadata_digest只hasher.update(&image.bytes),不带长度或分隔符。封面[1,2]+截图[3]与封面[1]+截图[2,3]得到同一摘要;摘要喂给根幂等键,会把改过的媒体当成重放静默丢弃。u64::to_le_bytes()长度再写字节,并补分片不变性断言(3832ca4fa)。3.
[performance·medium]game_distribution_publish.rs:1036-1037— 读草稿图片没有体积上限read_draft_image直接std::fs::read,只在服务端 8MB 处事后拒绝。std::fs::metadata().len(),超过 8MB(对齐服务端单图上限)直接返回错误;补稀疏大文件测试(3ecd4efcb)。4.
[maintainability·low]game_distribution_publish.rs:1124— multipart 文件名一律.pngmultipart_image_part固定叫cover.png/screenshot-{i}.png,但内容类型可能是 JPEG/WebP/GIF/AVIF。multipart_image_file_name,按content_type给扩展名(3d6795978)。5.
[maintainability·low]game_distribution_publish.rs:1148—_package_relative_path已是死参数.export/taonier.zip,命令签名仍要求前端传package_relative_path,helper 里也保留形参。stage_publish_package/publish_local_project_game删除package_relative_path形参,前端publishGameDistribution与useTaonierTab同步不再传,命令契约更新(aa946a930)。6.
[bug·low]apps/ai-game-creator-shell/src/services/gameDistributionPublish.ts:179-180— 空串截图槽位被 filter 掉会错位normalizeMetadata先 trim 再filter(s => s !== '')。map里把null/空串统一归一成null、不再 filter,测试同步改为按序断言(ab9789d11)。7.
[style·low].../tabs/taonier/ArtifactsPane.tsx:124-128—publishLabel嵌套三元30b129fcf)。8.
[style·low]ArtifactsPane.tsx:121-122—online?.nextVersionNumber == null宽松相等!online || online.nextVersionNumber === null(229f0580f)。9.
[style·low].../tabs/taonier/OnlineValuesCard.tsx:79-85— 模板字符串里嵌三元onlineSummary(含版本号后缀),再单点插入(4d72a77ae)。10.
[maintainability·low].../tabs/taonier/ConflictCard.tsx:65—as never关掉类型检查resolved[meta.field] = conflict.current[meta.field] as never。string/string[]字段会静默通过。screenshotPaths走数组分支、其余走字符串分支(500c3fda6)。11.
[bug·medium].../export/draft/taonier/build.rs:185— 结论阶段无体积门禁就整包读入let bytes = std::fs::read(&path).ok();在produced判断之前执行。produced为真时读,且先过metadata.len() <= ARTIFACT_PACKAGE_READ_MAX_BYTES,超线直接判不合格(和 32 同一提交d3d7e5a8c)。12.
[performance·low]build.rs:251—read_package_bytes用Vec::new()metadata.len(),函数却从空 Vec 反复扩容。expected_bytes,改用Vec::with_capacity(4acb0e712)。13.
[style·low].../export/state/useTaonierTab.ts:179—== nullonline.nextVersionNumber === null;online为空已由!online挡住(7b521f6f2)。14.
[bug·medium]useTaonierTab.ts:177-178— 先快照form再await packageAll()publishGame用进入时的form快照拼buildTaonierPublishMetadata,但packageAll()开头会refresh(),可能在用户无未保存编辑时采纳更新的注册表表单。.export/taonier.json媒体路径,文本字段却来自旧快照,理论上会用过时标题/简介发布。useTaonierExport新增getEffectiveForm(),packageAll()落盘成功后记录本次落定的权威表单;publishGame在await packageAll()之后用getEffectiveForm() ?? form合成 metadata,补两条测试(27 passed)。核实结论:packageAll本就会触发 vite 构建与自动落盘,.export/taonier.json是 UI 自己写的持久化副本、不是构建/skill 写的;XHS 导出有同样的构建/自动保存结构但没有发布到广场的路径,因此没有这个陈旧表单问题,其表单也只有name/introduction/iconPath。用户「所有字段都改走 UI、不读磁盘文件」的建议只对一半:去掉磁盘读取既不能消除陈旧,又会丢掉 Rust 侧「UI 槽位数 vs 草稿本地图数」的 fail-closed 校验,最小正确修复即本提交(ab2af60d3)。15.
[bug·medium]useTaonierTab.ts:194-195—packageAllreject 时进度卡在 runningrefresh/persist/buildOnce会把exportUnavailable与未识别失败经throwToErrorPool抛出,这里的await没包 try/catch。publishGamereject,progress停在phase:'running';运行中对话框没有关闭入口,面板一直卡在「正在打包发行包…」。await包 try/catch,失败把进度置failed并带原因(8d74fa9ab)。16.
[maintainability·low].../export/state/taonierDownload.ts:30— 与xhsMinitoolDownload.ts近乎逐字重复export/state/exportArtifactDownload.ts承载保存对话框 +save_local_project_asset_file+ 文件管理器定位,删除taonierDownload.ts/xhsMinitoolDownload.ts,两个ArtifactsPane改用saveExportArtifactToDisk/revealExportArtifactInManager(680503d2a)。17.
[bug·medium]ArtifactsPane.tsx:118-121— 发布按钮没把taonier.running算进禁用publish.publishing,taonier.running未纳入。packageAll,争抢同一个.export/taonier.zip。publishDisabled增加taonier.running(7db1fc4ff)。18.
[style·low].../tabs/taonier/FormCard.tsx:137-138— 字段控件嵌套三元options/multiline/input三态先算inputSlot,并复用同一份fieldClassName,去掉三份重复样式(e4d46609c)。19.
[style·low]FormCard.tsx:215-219— 截图数量文案嵌套三元screenshotSummary(bb7097ce5)。20.
[bug·medium]FormCard.tsx:279-283— 多选截图只取最后一张onImport里const picked = assets.at(-1),只写回最后一张。AssetImporter的本地文件对话框是multiple(上限 100),其余截图被静默丢弃。MAX_AGC_GAME_SCREENSHOTS - 当前张数的剩余名额逐张onAppendScreenshot;测试替身支持返回多张,新增多选导入断言(057b56b24)。21.
[bug·high]game_distribution_publish.rs:1062-1068— AGC 回读链路没跟着硬切到 objectKeyGameDistributionPublicationImageDraft只序列化assetId/previewUrl;build_publication_draft拿到cover_object_key/ 截图object_key只用于换签预览就丢弃,新合同下assetId恒为None。FrozenPublicationMetadata.screenshots仍按对象数组解析,而服务端frozenMetadata.screenshots已是字符串数组。objectKey ?? assetId取沿用键,两者都拿不到 → 沿用线上封面时coverObjectKey变空、截图槽位全null,更新已上架作品会被迫重选封面/截图(或报「请先选择游戏封面」)。GameDistributionPublicationImageDraft增加object_key: Option<String>并按 objectKey 序列化;FrozenPublicationMetadata.screenshots从Vec<FrozenPublicationScreenshot>改为Vec<String>,删除该结构体;build_publication_draft透传normalize_object_key(cover_object_key)与字符串截图数组;前端GameDistributionPublicationImageDraft同步为objectKey;补 Rust 断言frozen_metadata_accepts_string_screenshot_array(800c45680)。22.
[bug·high]server-rs/.../modules/game_distribution.rs:1859-1861— 三条 multipart 路由缺DefaultBodyLimitcreate_game/create_version/update_owner_game_metadata收 multipart(1 封面 + 最多 6 截图 × 8MiB),路由都没有显式DefaultBodyLimit。MAX_PUBLISH_MULTIPART_BYTES = 8MiB × 7 + 2MiB并给三条路由加.layer(DefaultBodyLimit::max(...));MAX_SINGLE_MEDIA_BYTES提为pub(crate),补口径断言(d24a16ad0)。23.
[bug·high]game_distribution.rs:3749-3750— 旧screenshots_json对象数组按Vec<String>解析失败serde_json::from_str::<Vec<String>>对旧[{"assetId","objectKey"}]返回 Err,unwrap_or_default()静默变空列表。24.
[bug·medium]game_distribution.rs:1868-1877— 先上传 OSS 再写 SpacetimeDB,重放/冲突留孤儿对象resolve_publish_media之前先按刚读到的游戏行做 revision CAS,过期直接返回同一 409PUBLICATION_CONFLICT口径;resolve_publish_media拆出 inner 并记录本次新传 objectKey,中途失败即补偿删除;事务失败或幂等重放(replayed=true)时cleanup_uncommitted_publish_media调delete_media_objects清理孤儿。说明:幂等收据只在 reducer 事务内可读,api-server 无法上传前查收据,故重放改为事后补偿,效果同样是不留孤儿(7aeac536e)。25.
[security·medium]game_distribution_media.rs:147-149— 内容类型完全信客户端 multipart 头starts_with("image/"),image/svg+xml可通过并按该类型存储;bytes 端点回显存储类型,没有nosniff/attachment。read_image_field改按文件头嗅探 PNG/JPEG/WebP/GIF/AVIF,声明为 SVG 等主动内容一律拒绝,落桶 Content-Type 与扩展名以嗅探结果为准;proxy_read_bytes新增ObjectReadContentPolicy,游戏媒体只放行栅格类型、其余降级application/octet-stream+Content-Disposition: attachment,并统一加X-Content-Type-Options: nosniff(7aeac536e)。26.
[performance·low]server-rs/.../api-server/src/object_read.rs:70-74— 每次 read-bytes 都新建reqwest::Clientproxy_read_bytes改为接收调用方传入的共享 client,素材域与游戏媒体域都传state.editor_oss_http_client()(进程级复用,connect 10s / total 120s),不再每次reqwest::Client::new()(7aeac536e)。27.
[performance·low]game_distribution_media.rs:133—upload_media_image克隆整段最多 8MiB 字节resolve_publish_media之前算完,上传仍借用并 clone。upload_media_image改为取UploadedImage所有权,body: image.bytes直接移交,去掉整段clone()(7aeac536e)。28.
[bug·high]spacetime-module/.../game_distribution.rs:4156—GameDistributionFrozenMetadata::screenshots从对象数组改为Vec<String>metadata_json会在parse_game_distribution_frozen_metadata反序列化失败,审核通过时重新解析旧版本会报「版本冻结资料格式无效」。metadata_json而不是保留旧形状兼容。29.
[bug·high]spacetime-module/.../game_distribution.rs:4225— 旧screenshots_json行按Vec<String>解析.ok()成Nonecover_object_key本就是字符串不受影响)。30.
[performance·low]spacetime-module/.../game_distribution.rs:4216—game_distribution_media_has_public_read_grant每次媒体读都 O(n) 扫全表screenshots_json。{prefix}{game_id}/{file},先按GAME_DISTRIBUTION_MEDIA_PREFIX反解game_id,再按主键.game_id().find(...)只读那一行核对封面/截图归属,降为 O(1);同时把该前缀抽到shared-contracts供写路径与授权端共用同一常量,补 key 布局单测(08771d39e)。31.
[maintainability·medium]scripts/check-game-distribution-media-e2e.mjs:129-131—publishFormData/imagePart在多个 E2E 脚本重复scripts/game-distribution-e2e-helpers.mjs(COVER_PNG/imagePart/coverImagePart/publishFormData/resolveApiRequest),11 个 E2E 脚本删除各自的重复实现改为 import,multipart 字段名与api()的 FormData 分支只留一份(1f239a96c)。32.
[security·medium].../taonier/build.rs:186-190— 符号链接产物仍被完整读入std::fs::read(&path)发生在produced门禁之前,而produced=false恰恰可能是「这是符号链接」。produced与metadata.len()双重门禁(d3d7e5a8c)。33.
[bug·medium]scripts/check-game-distribution-validation-restart-e2e.mjs:241-244— 创建失败后仍解引用created.data.coverObjectKeycheck()只记失败不中断,created.data为空时下一行直接取属性。TypeError打崩整个脚本,拿不到正常的 check 结论。check后补if (!gameId) process.exit(1);(8e59e11f0)。34.
[bug·medium]scripts/check-game-distribution-web-a11y-e2e.mjs:226— 同上,且在publishFormData实参里提前解引用const gameId = created.data?.id后补if (!gameId) process.exit(1);(152cdc617)。35.
[bug·low]scripts/check-game-distribution-validation-restart-e2e.mjs:438— 旧E2E_STATE_FILE只有coverAssetIdstate.coverObjectKey为undefined会被JSON.stringify丢掉,续跑创建的 v3 没有封面。state.coverObjectKey ?? state.coverAssetId(9c822c098)。36.
[maintainability·low]scripts/check-game-distribution-upload-safety.mjs:125— 同一批 multipart helper 重复(≥11 份)scripts/game-distribution-e2e-helpers.mjs(1f239a96c)。37.
[maintainability·low]scripts/check-game-distribution-media-e2e.mjs:650-662— 断言硬编码内部存储前缀agc/project-snapshots/v1/game-distribution/media/009a772eb)。38.
[test·low]scripts/check-game-distribution-ops-rollback-e2e.mjs:537— 封禁建游请求同时带 objectKey 与 cover 字节coverObjectKey: null+coverpart)不一致。publishFormData({ ...resolvedMetadata, coverObjectKey: null }, { cover })(9184e9277)。39.
[bug·high]src/components/game-distribution/gamePublishMediaDefaults.ts:43-45— 旧{assetId,objectKey}截图数组会让objectKey?.trim()抛错screenshotSource声明为string[],旧数据元素是对象。applyVersionMediaDefaults会TypeError中断,版本续做失败且在线默认值被静默丢弃。typeof item === 'string' ? item : item?.objectKey归一。40.
[performance·medium]gamePublishAssets.ts:83-85— blob 预览 URL 只在显式移除/替换时 revokeapplyVersionMediaDefaults覆盖coverImage/screenshotImages、以及组件卸载时,旧 blob URL 没有URL.revokeObjectURL。applyVersionMediaDefaults在用线上默认值覆盖前先 revoke 被替换的本地预览;GamePublishPage/GameWorkMetadataEditor用 ref 跟踪最新图片,卸载时兜底 revoke;补「覆盖前 revoke」断言测试(5ce5cb006)。41.
[performance·low]GamePublishMediaFieldset.tsx:185— key 退化成${name}-${index}objectKey为 undefined,key 用name-index。GamePublishImage新增稳定id(createGamePublishImageId),本地图与线上沿用图各自铸一次,列表 key 改用image.id,补同名文件 id 互异单测(12d4e8477)。42.
[maintainability·low]GamePublishPage.tsx:310-315— 槽位映射与 File part 组装在发布页/编辑页重复gamePublishAssets新增buildGamePublishMediaSubmission,统一「槽位 objectKey 去空白 / 本地新图只进 parts」;GamePublishPage的 metadata 槽位与 multipart part、GameWorkMetadataEditor的 payload/media 都改用它,补槽位与 part 同源单测(6f0ea0310)。43.
[bug·medium].../export/state/useTaonierExport.ts:548-549— 保存失败先无条件setDirty(false)dirty,再按失败类型分流。exportUnavailable会丢掉未落盘编辑:dirty=false让adoptableRef变真,宿主恢复后的重读会用陈旧注册表表单覆盖用户最新输入。saveConflict/未识别/其余失败清dirty,exportUnavailable保持dirty(075078f35)。44.
[bug·medium]useTaonierExport.ts:381-384— 采纳新表单时无条件setFailure(null)commandFailed/artifactMissing等)静默 dismiss。00448e0b2)。45.
[bug·low].../export/state/taonierFields.ts:100-101— 面板按码点、发布预检按 UTF-16 计数validateTaonierForm用Array.from(trimmed).length(码点),gameDistributionPublish.ts:normalizeMetadata仍用title.length > 40/summary.length > 120(UTF-16 码元)。chars().count()(码点)。normalizeMetadata改用Array.from(...).length,与面板和服务端同口径;补 40 emoji 标题 + 120 emoji 简介测试(42c742df5)。46.
[bug·medium]server-rs/.../modules/game_distribution.rs:1721—update_owner_game_metadata幂等摘要不含game_idpayload,而幂等收据键是(owner_user_id, action, idempotency_key),不按游戏隔离;create_version已经用(game_id, &payload)。Idempotency-Key推送相同资料时,第二次会被当成第一次的重放:跳过写入、返回第二部作品的旧状态(replayed=true),更新被静默吞掉。(game_id.as_str(), &payload);补跨作品摘要区分断言(fede8cc16)。47.
[bug·medium]src/components/game-distribution/GameDetailPage.tsx:121— 作者视角媒体换签走了匿名公开路由GameDetailResolvedAsset只用useGameDistributionMediaReadUrl(objectKey),不带gameId。published+active作品;作者预览自己的未发布/待审/驳回作品时,封面/截图静默退回占位(项目记忆里记过的「作者预览必须走 owner 路由」坑)。gameId,renderAsset传ownerView ? game.id : undefined;测试替身记录换签选项,补作者视角带gameId断言(067f02c85)。1. 【.gitattributes:33】现状:文件末尾同时存在两条完全相同的
apps/ai-game-creator-shell/src/view/project-development/export/generated/** ...规则(原第 33、35 行)。问题:重复规则无功能影响,但污染文件、误导后续编辑。修复:删除重复行。commitbff0006eb。3. 【vite.config.taonier.mjs:55】现状:
assertH5Root只用/\s(?:src|href)="(\/[^"]*)"/匹配双引号属性。问题:单引号属性(src='/app.js')与=两侧空白漏检,误配 base 的产物可能通过校验、上传后整站 404(Vite 一般输出双引号,但手写/插件 HTML 会漏)。修复:正则改为/\s(?:src|href)\s*=\s*(["'])(\/[^"']*)\1/。commit9c2664289。4. 【vite.config.taonier.mjs:130】现状:
defineTaonierConfig先mergeConfig(baseConfig, overrides),再补插件;Vite 的mergeConfig会用 overrides 的标量覆盖build.outDir。问题:overrides 改了outDir后,pack.mjs的--vite-built-dir默认仍是dist-taonier,会打包陈旧/缺失目录且无提示。修复:合并后重新钉死merged.build.outDir = TAONIER_OUT_DIR。commitaf752eaa1。5. 【pack.mjs:162】现状:
writeZipEntries直接openSync(zipPath, 'w')截断落点。问题:写盘中途失败(磁盘满、4 GiB 上限、源文件被删)会进入只删落点的 catch,把落点上原有合法 zip 一起删掉。修复:写同目录临时文件<zip>.tmp-<pid>,成功后renameSync覆盖;失败只清理临时文件,makeZip失败路径不再删落点。commit5b0c41683。6. 【pack.mjs:126】现状:
collectZipEntries只处理isDirectory()与isFile()。问题:FIFO/socket/设备文件既非目录也非普通文件,assertNoSymlinks也不拦,会被静默跳过,产出缺文件的包。修复:其余类型直接抛错。commitbda1bb081。7. 【preview.rs:1111】现状:导出目录从
exports改名为.export时,预览服务的两处受保护组件列表只删了exports、漏加.export。问题:项目根存在index.html时,/.export/taonier.json等注册表 JSON(content-type 非 octet-stream)会被本地预览直接读走。修复:两处列表补回.export(.zip仍因 octet-stream 被挡)。commit5df27afed。8. 【game_distribution_publish.rs:1127】现状:
read_publish_image先std::fs::metadata看体积,再std::fs::read按路径读。问题:两次系统调用之间路径可被换成符号链接或超长文件(TOCTOU),超长文件会先被读进内存。修复:改用OpenOptions+O_NOFOLLOW/FILE_FLAG_OPEN_REPARSE_POINT打开,取同一句柄 metadata 定上限后take(cap+1)限长读取(原 review 里的常量名MAX_DRAFT_MEDIA_BYTES不存在,实际是MAX_PUBLISH_MEDIA_BYTES)。commit951284205。9. 【game_distribution_publish.rs:1267,误报】现状:
ensure_publish_project_stack已不存在,发布前只校验project.export_package权限并读固定.export/taonier.zip。问题:这是b04aceb57有意删除的 phaser/vite 发布门,新门禁在导出/构建步骤(prepare()的BuildScriptMissing),计划文档 D8 与 decision-log 都有记录;按 review 恢复会退回旧产品决策。处理:无需改动。10. 【repository_context.rs:719】现状:
should_ignore_directory顶层组件只匹配memory | logs | .app,exports改名后漏了.export。问题:scan_repository会把生成的.export打包内容纳入 agent 仓库上下文。修复:匹配加入.export。commitad0877219。11. 【FormCard.tsx:14】现状:封面/截图复用小红书 tab 的
IconPreview,其alt="图标预览"、失败 title「用『选择图标』导入…」等文案写死为「图标」。问题:封面/截图的读图失败提示对用户说的是错误对象。修复:IconPreview增加可选labels(缺省保持小红书文案),FormCard 的封面/截图各自传入对应 alt、title、aria。commitcb57e59ec。12. 【FormCard.tsx:274】现状:封面槽只保存一个路径,但复用了多选上限 100 的
SPRITE_IMPORTER_SETTINGS,onImport只取assets.at(-1)。问题:用户多选时其余图片被静默丢弃。修复:新增COVER_IMPORTER_SETTINGS(maxItems: 1),封面导入器改用它(截图仍用多选设置)。commitedaf8896e。13. 【taonier/script.rs:53】现状:
find读取脚本命令时map(str::trim),而project/verification.rs的防漂移比较用actual_command != expected_command(actual 是 package.json 原始值)。问题:脚本值带首尾空格/换行时永远判「脚本已变化」→exportUnavailable。修复:比较时两边都trim()(只比较脚本内容、不比较无意义空白)。commit3a6758095。15. 【taonier/build.rs:192】现状:
conclude里std::fs::read前只看 512 MiB 读入安全线,平台 200 MiB 上限在读完后的ensure_within_platform_package_limit才判。问题:200–512 MiB 产物会先整包读进内存再判不合规。修复:读之前同时按平台 200 MiB 上限过滤。commita3cf98000。16. 【taonier/build.rs:262】现状:
read_artifact_package先用symlink_metadata拒符号链接,再File::open按路径打开read_package_bytes。问题:检查与打开之间被换成同长度符号链接时仍会被跟随(TOCTOU,低危)。修复:改OpenOptions+O_NOFOLLOW/FILE_FLAG_OPEN_REPARSE_POINT。commitba9d70a5b。17. 【vite.config.taonier.mjs:68】现状:
pruneEmptyDirs用statSync判断目录(会跟随符号链接)。问题:链接指向产物目录之外(甚至祖先)时会递归清理/栈溢出。修复:改lstatSync,符号链接一律跳过。commit003f7cfe9。18. 【game-distribution-e2e-helpers.mjs:34】现状:
coverImagePart与COVER_PNG被导出,但仓库内无任何脚本导入(10 个脚本各写本地coverImagePart,2 个 web-publish 脚本各写本地COVER_PNG)。问题:死导出误导后续维护。修复:删除coverImagePart,COVER_PNG收回为模块内部常量。commit0f127dbc7。19. 【useTaonierTab.ts:254】现状:hook 挂在面板根、跨项目不卸载;发布链路在
await packageAll/订阅回调/await publishLocalProjectGame之后无条件setProgress/setPublishResult。问题:发布 A 项目期间切到 B 项目,A 的进度/结果/失败会写进 B 的面板。修复:新增projectPathRef,在各 await 之后与上传进度回调里比对当前项目再写状态。commit5bc52a344。20. 【useTaonierExport.ts:784】现状:
packageAll用 render 闭包里的form做本地校验;refresh()/flush 可能已把更新的落盘表单写进effectiveFormRef。问题:form可能是陈旧快照,会放行盘上的非法值,违背「校验不过不触宿主」。修复:改用effectiveFormRef.current ?? form。commit8e3f0b892。21. 【test-creator-runtime.py:194】现状:fixtures 的
game字典没有price_mud_points。问题:该字段是非Option的u64,SpacetimeDB SATS 反序列化对缺失字段报missing_field;recover_row_with_deleted_fields只忽略未知字段,normalize_migration_row没有game_distribution_game分支,脚本会在导入 fixture 时直接失败。修复:补price_mud_points=0(根因上更彻底的normalize_migration_row回填见第 21 条备注,属数据兼容,未自动做)。commit28be35239。22. 【check-game-distribution-validation-restart-e2e.mjs:413】现状:兼容旧
STATE_FILE时用state.coverObjectKey ?? state.coverAssetId组装元数据。问题:旧版存的coverAssetId是素材对象 ID,不是 OSS objectKey;建版沿用要求它等于游戏当前cover_object_key,因此这个兼容分支永远 400。修复:缺coverObjectKey时先 GET 游戏详情取回真实coverObjectKey。commit11c8c2789。23. 【taonier/commands.rs:171】现状:
run_build在 async 函数里直接调project_root(同步文件 IO,Windows 上还会触发 UAC/ACL 修复)。问题:占着 Tokio worker,拖慢同 runtime 的其它异步任务;同模块其它 IO 都走了offload。修复:project_root挪进offload(spawn_blocking)。commit2ab7717a2。24. 【OnlineValuesCard.tsx:113,已过时】现状:该文件已在
9a653c838(你的提交)中删除,线上值内联进 FormCard 字段行,仓库内无引用。处理:finding 已不适用,无需改动。25. 【check-game-distribution-purchase-e2e.mjs:1470】现状:定价边界用例建游失败时,
boundsGameId为 undefined 仍继续请求/my-games/undefined、/games/undefined/versions。问题:级联报错掩盖根因。修复:记录失败后if (!boundsGameId) process.exit(1)。commit3a0d27574。26. 【useTaonierExport.ts:727】现状:
buildOnce只setFailure(null),不清dismissedFailure;visibleFailure用 JSON 指纹过滤已忽略的失败。问题:点「重新构建」时同一条确定性失败(如固定路径的artifactMissing)会被永久滤掉,弹窗不再出现。修复:setFailure(null)前setDismissedFailure('')。commit86649e50a。28. 【gamePublishMediaDefaults.ts:78】现状:截图换签用
for ... await串行请求(最多 6 次往返)。问题:预览出现慢。修复:改Promise.all并发请求后一次性回填。commit5e5bb19a6。29. 【useGameDistributionMediaReadUrl.ts:43】现状:effect 只用
cancelled标志忽略结果,不取消在途 GET;两个 client 函数也不收AbortSignal。问题:objectKey/gameId 快速切换时旧请求照跑,白占 API 配额。修复:两个 client 函数与resolveGamePublishImagePreview增加可选signal,hook 在清理时controller.abort()。commite830034d0。30. 【gameDistributionClient.ts:408】现状:
readGameDistributionMediaSignedUrl直接payload.read;requestJson对空响应体会返回null as T。问题:空响应触发原始 TypeError,而不是既有的「游戏媒体访问地址缺失」。修复:payload?.read ?? payload与read?.signedUrl。commit3f211f12d。31. 【api-server game_distribution.rs:2060】现状:
create_game先resolve_publish_media(...).await?上传封面/截图,再由cleanup_uncommitted_publish_media兜底清理;normalize_local_project_id(...)?写在create_game_distribution_game实参里、在 cleanup 之前求值。问题:用户传入非法localProjectId时提前?返回,已上传对象成为孤儿。修复:把normalize_local_project_id提到resolve_publish_media之前。commitf4582fe5b。32. 【api-server game_distribution.rs:2139】现状:
create_version同样的顺序问题。问题:同上,上传后提前返回留下孤儿对象。修复:同样提前校验。commitf4582fe5b。37. 【gamePublishMediaDefaults.ts:80】现状:取消后
if (apply.isCancelled() || !preview) continue;仍会推进循环、继续发剩余截图请求。问题:取消语义失效。修复:并发版本在Promise.all后统一if (apply.isCancelled()) return;,取消后不再补写也不再发请求。commit5e5bb19a6。39. 【GamePublishPage.tsx:335】现状:
buildPublishGameMetadata()与buildPublishMediaParts()各自调用一次buildGamePublishMediaSubmission。问题:当前纯函数结果一致,但「单一来源」只是口头约束,未来改动可能让元数据槽位与上传 part 分叉。修复:两个 builder 改为接收调用方算好的submission,两处提交流程各算一次。commit53750ac31。二、逐条处理结果(按你的决定)
readFileSync整读 +deflateRawSync产出一份压缩副本,峰值约 2× 文件大小;注释宣称的「流式」只在归档层面成立。问题:大型 H5 资源(音频/贴图/WASM)可能顶爆内存或导出中断。处理:按你的决定不重写,已在整读+压缩处补TODO(perf),说明峰值与 ZIP data descriptor(通用标志位 bit 3)流式改法。commit7d8b966d1(含 agc-skills manifest 版本/哈希同步)。run_project_verification_with_commit_at的项目侧预检失败(不支持的包管理器、项目.npmrc、脚本超长/含换行、脚本漂移、脚本缺失)过去和沙箱/日志故障一起返回String,被map_err(TaonierExportUnavailable::new)?一律收成exportUnavailable。问题:项目事实问题被当成宿主故障,用户被引导去重试一个只有改项目才能解决的问题。修复:verification.rs拆出「按已解析规格运行」的入口;陶泥儿与小红书构建先单独跑项目侧预检,失败映射为新变体projectUnavailable。前端把该变体归为 code agent 可修、文案写死,ts-rs 绑定与测试同步更新。commit06e6cf852(陶泥儿测试用例的两行覆盖并入了你当时的并发提交61acaf6ce)。serde_json::from_str::<Vec<String>>解析screenshots_json,解析失败.ok()静默变None;旧格式(master4a2b27071写出的[{"assetId","objectKey"}])会解析失败,老作品截图匿名读 404。处理:按你的决定不修——线上还没有这类数据,暂无兼容压力。等真有旧数据时再按持久化兼容流程加双形状 helper 或做一次性行重写。GameDistributionFrozenMetadata.screenshots已是Vec<String>,旧metadata_json里存对象数组时,管理员审批这类历史待审版本会直接失败。处理:按你的决定不修——线上还没有这类数据。等真有历史待审版本时,再给该字段加兼容对象数组的反序列化并补测试。game_payload、game_media_object_keys(292-294)与resolve_publish_media_inner的current_screenshots(4105-4106)都用Vec<String>解析截图,旧格式下会丢图 / 作者读不到 / 「沿用截图」永远不匹配。处理:按你的决定不修——线上还没有这类数据。将来若出现旧格式,与第 27 条一起用同一个双形状 helper 替换这三处。field.bytes().await把整个 multipart part 收成一个Bytes,随后bytes.to_vec()再完整复制一份,峰值约 2× part;单个超大 part 会被完整缓冲后才 413。处理:按你的决定不改写,已在读取处补TODO(perf),说明峰值与field.chunk()循环流式限长改法。commit0119a329a。revokeGamePublishImagePreview(URL.revokeObjectURL)原先在setCover/setScreenshots的 updater 里执行。问题:副作用写进 state updater,StrictMode 双调用/重放可能撤销仍被渲染引用的 blob URL,updater 也不再是纯计算。修复:VersionMediaApplyTarget增加getCover/getScreenshots,在 dispatch 之前先 revoke;两个调用点用各自已有的 ref 提供当前媒体,updater 只做纯计算。commitdca848103。- 新增 GET /api/game-distribution/my-games/{gameId}/media/read-url 与 .../media/read-bytes,需 bearer - 判定 = gameId 属于当前登录主体且 objectKey 命中该作品当前行 cover_object_key/screenshots_json - 抽出 game_media_object_keys 与换签/字节中转共用函数,公开读判定与响应形状不变 - 新增媒体键提取单测与 owner 路由挂载/鉴权断言- resolve_preview_url 改走 /api/game-distribution/my-games/{gameId}/media/read-url,不再走素材库 /api/assets/read-url - 新增 owner_media_read_url,复用 endpoint 的路径段转义并编码 objectKey - 新增路由与编码单测,钉住不再回退素材库 ACL- 在 check('创建游戏成功') 后补 if (!gameId) process.exit(1) - check 只记失败不中断,created.data 为空时继续取 coverObjectKey 会抛未捕获 TypeError 打崩脚本- GameDistributionPublicationImageDraft 增加 objectKey,封面/截图回读时带上沿用键 - FrozenPublicationMetadata.screenshots 改按字符串数组解析,删除只认 {assetId,objectKey} 的旧结构 - 前端草稿类型补 objectKey;补字符串数组解析的 Rust 单测- buildOnce 在 setFailure(null) 前 setDismissedFailure(''),显式重跑失败仍会重新弹出弹窗在本地运行ci通过