AGC 编辑器分支产物归位(M3,待 Windows 验收) #524
Reference in New Issue
Block a user
Delete Branch "feat/agc-bundled-resources-m3"
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?
M3:编辑器分支产物归位。分支基于 PR #520 的
fix/agc-rust-staging-idempotency(堆叠 PR,避免污染 #520 的 diff)。目标
构建脚本彻底退出随包资源写入:Unity publish 目录、Godot gdextension、Cocos bridge payload 改由准备步骤在
tauri dev|build之前按声明产出并复制。改动
subdirectories新增origin: prepared;libraryStaging增加prepare/files;新增nativePayloads与plugins.prepareSteps(程序类型、工作目录、指纹、必需产物)。Godot 随包文件清单改由声明提供,godot_bundle::BUNDLE_FILES取生成的编译期常量。powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File build.ps1(Unity/Godot,Godot 额外移除PSModulePath)与cargo build -p cocos-editor-bridge --target … --features windows-injection;内容指纹命中且必需产物齐全时零写入;命令执行器可注入,便于在 macOS 上用假执行器覆盖调度、指纹与失败关闭。prepare_unity_editor_helper、prepare_godot_editor_extension、stage_cocos_editor_payload、stage_build_generated_plugin_payloads及辅助函数(415 → 193 行),只留只读校验;新增「已准备产物存在性 + Godot 随包库深度校验」;AGC_SKIP_RESOURCE_STAGING开关随写入分支删除;两份只含 staging 条目的.taurignore删除。profile: 'release'以定位 Cocos 产物。本机(macOS)已验证
npm run agc:bundled-resources:check通过cargo check --no-default-features通过;紧接第二次 0.62 秒 fresh(构建脚本已不在写入路径上)待 Windows 真机验收(清单已写进 M3 里程碑规范)
npm run agc:bundled-resources:prepare -- --target x86_64-pc-windows-msvc→ 产物到位;再跑一次全部命中缓存且时间戳不变。src-tauri/resources→touch build.rs→cargo build→ 快照逐项不变。cargo build,第二次秒级Finished且不再Compiling genarrative-ai-game-creator-shell。plugins/下三处产物的路径与 sha256 与迁移前一致;cargo test --no-run(触发只读校验)通过。resources/**会让cargo build失败。已知未完成
plugins.prepareSteps/nativePayloads在声明门禁里目前只做结构性校验,尚未校验prepare引用完整性(运行期由准备步骤 fail-closed 兜底,报「声明引用了未定义的准备步骤」)。后续把引用校验补进check-package-layout.mjs。评审结论:不能合入
Windows(M3 唯一的验收平台)上默认 feature 集直接起不来,Cocos 那条新增链路不可达且带 3 个缺陷。下面按"声明 → 准备步骤 → 构建期校验 → 随包路径 → 运行时解析"逐层给证据与建议。
评审范围:base
fix/agc-rust-staging-idempotency(#520),headfeat/agc-bundled-resources-m3,diff = 单提交44c5a6918(17 文件 / +665 −337)。判据取 M3 里程碑规范 + 主规范 §4.6–§9,并在 Windows 真机实跑了准备步骤与cargo build。P0 阻塞
1.
build.rs::validate_prepared_payloads没有按插件收敛 → 默认 Windows feature 下cargo build稳定 panicbuild.rs:100-119是「遍历所有插件 × 遍历所有声明子目录」,而dotnet/publish/win-x64属于agc-unity-editor。实测 staging 布局(刚由本 PR 的准备步骤生成):dotnet/publish/win-x64native/gdextension同一文件里两个正确写法可对照:
validate_staged_plugins用source.exists() &&收敛(L295);validate_prepared_payloads自己的library_staging分支用plugin.name != staging.plugin收敛(L122)。只有新加的这几行漏了。影响:Windows 的
npm run agc、发布打包、验收清单第 3/4 条全废;Linux CI 因plugin_staging_applies()为 false 永远发现不了。建议:给
subdirectories补plugin字段(与libraryStaging/nativePayloads对称)后在循环里收敛;或按源目录存在性收敛。修完请实测上面那条cargo build。P1
2. Cocos
nativePayloads整条链路不可达 + 三处缺陷features: ["cocos-editor-injection"],而三个入口(start-tauri-dev.mjs/build-release.mjs/ CLI)传的都是new Set(defaultEditorFeatures(target))=[cocos-editor-execute, unity-editor-execute, godot-editor-execute]。因此准备步骤与copyNativePayloads永不执行:跑完准备步骤后resources/plugins/agc-cocos-editor/native/payload/与源码侧plugins/agc-cocos-editor/native/payload/都不存在。迁移前CARGO_FEATURE_COCOS_EDITOR_INJECTION是同一门槛 → 非回归,但 M3 验收清单第 1 条把该 dll 列为「预期在位」,清单与实现不符;且这是新增的死代码路径。cocos-editor-bridge只是 src-tauri 的 path 依赖、不是 workspace 成员。--features立刻编译成功 → 问题就在 features 传递方式(改用该 crate 自己的 manifest 并把--target-dir指回src-tauri/target,或把 crate 加进 members)。cocos_editor_bridge.dll(下划线),而声明sourceFileName: "cocos-editor-bridge.dll"(连字符)被同时当作 cargo 产物名和目标名。旧的stage_cocos_editor_payload是「源cocos_editor_bridge.dll→ 目标cocos-editor-bridge.dll」,新声明把两种语义合并了 → 四个候选路径全查不到,直接Cocos bridge native payload 未构建。需拆成sourceFileName+destinationFileName。native/payload仍声明origin: source,却由准备步骤写回源码树;pluginSourceFingerprint用的是size:mtime,而copyFilePreservingMode无条件copyFileSync覆盖 mtime → 一旦打开该 feature,插件 staging 会每次全量重建,幂等性失效。建议改成origin: prepared。3. Godot 准备步骤缺
fingerprint→ 每次都真跑build.ps1package-layout.json的godot-extension-build没有fingerprint,而ensurePreparedArtifacts的缓存判断是if (step.fingerprint && requiredOutputs.length > 0),于是直接跳过缓存。实测第二次运行(2.39s):codex/plugins 都「命中缓存」,但 Godot 那步仍在输出 cmake configure +Native C++ payload is current。影响:把 CMake/Python/VS C++ 变成每次
npm run agc的硬依赖(build.ps1首行Get-Command -ErrorAction Stop直接抛错),迁移前只在 cargo 重跑build.rs(godot 源变化)时才需要;同时违反里程碑「命中指纹且产物齐全时零写入」。建议:
fingerprint.roots: ["."]+excludeDirectoryNames: ["bin", ".build", "native-build"](.build必须排除,否则 CMake 目录让指纹永远变)+stampRelativePath。P2
nativePayloads/prepareSteps/sourceFileName/destinationSubdirectory在check-package-layout.mjs与package-layout.generated.rs里零出现(grep 证据)→ 拼错prepare名、plugin名、目标子目录都不会被拦。PR 正文写「只做结构性校验」与事实不符(实际只承认了「引用完整性」缺失)。另外GODOT_BUNDLE_FILES取libraryStaging[0]?.files ?? []:libraryStaging一旦为空或顺序变化就静默退化成空数组,godot_bundle::validate空转通过。declaration drives source lookup and staging units用/分隔正则匹配 Windows 反斜杠路径。非本 PR 引入(d8b37e0da),但 M3 把 Windows 当唯一验收平台,得先修(path.join或[\\/]),否则这套用例兜不住 Windows 回归。AGC_SKIP_RESOURCE_STAGING=1 cargo build/check …」,该开关已随写入分支删除(实施计划里那条已替换,§4.8 漏了)。godot_bundle::stage()/source_files()已无生产调用方(stage只剩自带单测,source_files的唯一调用方是被删掉的 build.rs)。按仓库「退役对象直接清理(含专属测试)」规则应一并删除。main()没把log传给prepareBundledResources,ensurePreparedArtifacts的「命中缓存/已构建」全部不打印,验收第 1 条「预期全部命中缓存」从 CLI 输出上看不出来(dev 入口有传)。pluginTreeMatches现在把 prepared 目录也纳入逐文件 sha256,命中缓存路径也要哈希 Unity 那 90 MB 单文件 exe;可考虑把 prepared 产物摘要折进pluginSourceFingerprint。已实测通过的部分
npm run agc:bundled-resources:checkprepare --target x86_64-pc-windows-msvcresources/**全部 size+mtime 零变化(除 Godot 那步仍执行)cargo build在 build.rs 就 panicbuild.rs唯一写操作是fs::write(OUT_DIR/…prompt_bundle.rs),不再碰resources/**.taurignoreresources/plugins,自触发来源消失非评审范围但需立刻处理
.env.local被 git 跟踪,本次提交把一个真实GENARRATIVE_SPACETIME_TOKEN写进仓库并推到远端。建议立即吊销该 token,并把.env.local移出索引。44c5a69185tofaa510e999按评审修复(
faa510e99→ 新提交)subdirectories[].plugin,Rust 加subdirectory_applies_to_plugin,validate_prepared_payloads与工具的复制/指纹都按插件收敛。cargo fmt+cargo check --no-default-features通过;请再跑一次你那条默认 feature 的cargo build确认。Cargo.toml:cocos-editor-injection = ["cocos-editor-execute", "cocos-editor-bridge/windows-injection"],默认集确实不含它 → 结论与你一致:是验收清单写错(非回归)。清单第 1 条我会备注该 dll 需显式开启 feature 才生产。--target-dir src-tauri/target,不再用-p+--features。sourceFileName: cocos_editor_bridge.dll(cargo 产物)与destinationFileName: cocos-editor-bridge.dll(随包目标)。native/payload改为origin: prepared并绑定cocos-bridge-build;指纹对 prepared 产物改用内容哈希(mtime 变化不再导致全量重建)。roots ["."]、排除bin/.build/native-build、戳文件bin/win-x64/.agc-source.sha256。plugin已解析/校验,GODOT_BUNDLE_FILES缺清单即失败;nativePayloads/prepareSteps的结构与引用校验尚未补完(运行期 fail-closed 兜底),留在本 PR 待办。[\/])。godot_bundle::stage/source_files待删),列入待办。log并新增--features(便于按 feature 验收 Cocos 链路)。pluginTreeMatches的逐文件比对,只由指纹(内容哈希)覆盖。.env.local泄漏 token当前验证:
agc:bundled-resources:check通过、cargo fmt/cargo check通过、工具用例 12/13(余 1 条为夹具断言待修,不涉及产品逻辑)。Windows 侧仍需按 M3 清单复验第 2–6 条。复测结论(Windows 真机):修了一半,构建仍起不来;cocos payload 换成「名字错 + 交付被缓存挡住」
复测对象
e4634928c(M3 提交重写为faa510e99,只回退了.env.local的 token)。逐条对照上一轮意见,以下全部在本机 Windows 实测。已修好并实测通过
agc-cocos-editor\dotnet\publish\win-x64不再出现;plugin字段 +subdirectory_applies_to_plugin已落地--manifest-path plugins/.../cocos-editor-bridge/Cargo.toml --target-dir src-tauri/target跑通:[agc-resources] cocos-bridge-build 已构建,产出target/x86_64-pc-windows-msvc/debug/cocos_editor_bridge.dllsourceFileNamecocos_editor_bridge.dll(下划线),与 cargo 实际产物一致godot-extension-build 命中缓存(指纹一致),整轮 0.57splugin已进门禁与生成物;GODOT_BUNDLE_FILES改为按layout === 'godot-bundle'查找,空清单直接拒绝生成log并新增--featuresresources/**全部 size+mtime 零变化仍然阻塞
P0:原问题换了个目录复现。 默认 feature 参数下:
cargo build --no-default-features同样 panic(这条正是 macOS 侧的验证命令)。根因:native/payload现声明为origin: prepared且没有 features 门槛(targetContains: ["windows"]恒真),Rust 校验因此在 Windows 上无条件要求它;而唯一产出它的copyNativePayloads仍被nativePayloads[].features = ["cocos-editor-injection"]挡住,三个入口传的都是defaultEditorFeatures()(不含 injection)。准备步骤只把 dll 编到target/,永远没人写进插件工作区或随包目录。建议二者用同一门槛。按运行时代码,
cocos_bridge_payload_candidates只在cocos-editor-execute下编译,但真正使用 dll 的inject_bridge_dll需要windows-injection,而cocos-editor-execute分支本身走 Node Inspector bootstrap、不依赖这个 dll。因此最小改动是给该子目录补"features": ["cocos-editor-injection"](保持迁移前语义);若确定要让默认构建就带 payload,则反向把nativePayloads.features改成cocos-editor-execute并同步运行时判断。二选一,别一边要一边不给。P0/P1:
destinationFileName声明了但没人用。copyNativePayloads的目标路径仍写payload.sourceFileName:editor_adapters.rs的COCOS_BRIDGE_PAYLOAD_RELATIVE是连字符名,所以 Cocos 分支照样「缺少组件」。这也是用例 11 现在红的直接原因:该失败与平台无关,修复提交应该是没再跑这套用例。
新发现
P1:payload 交付挂在缓存之后,缓存 key 又不含它的内容(两处实测)
因为
copyNativePayloads位于preparePlugins的缓存短路之后,而native/payload改成prepared后又被pluginSourceFingerprint(只算origin === 'source')排除,pluginTreeMatches对 prepared 只查存在性。结果:payload 只在「插件树因别的原因重建」时才交付,删掉也不会清理——恰好踩中最需要它的场景(先默认构建、之后开 injection)。建议把nativePayloads(及 prepared 产物摘要)纳入插件缓存 key,或把复制步骤移到缓存判断之前(prepared 已不进指纹,重写不再引发 mtime 振荡)。P2:
pluginTreeMatches条件写反,内容比对整体失效应为
!(A && B)。现在plugin === ''的src/panels/skills恒真 → 无条件continue;带plugin的两条在 Windows 上subdirectoryEnabled恒真 →false || true也continue。即当前声明下循环体一次都不执行,pluginTreeMatches退化成「每个插件目录都有 plugin.json」。后果:手改resources/plugins/**不再被判缓存失效,只能等构建期报「插件随包文件与源码不一致」再手工删目录(原来会自动重建)。P2:门禁仍未覆盖新 section。
nativePayloads/prepareSteps在check-package-layout.mjs与生成物里依旧零出现(本轮只加了plugin)。destinationFileName就是活证据:字段加了、没人用、门禁一声不吭。同时origin: prepared仍不校验prepare是否存在(缺失会在运行期 fail-closed,但门禁应拦)。P2:死代码与构建脚本警告。
cargo build时构建脚本自身报 2 条 warning:P3:CLI
--features只认空格形式。--features=a,b报未知参数:--features=…,而仓库自己的withDefaultCargoFeatures生成的是--features=a,b,验收清单很容易照抄。建议的复现清单(Windows)
.env.local的 token 已从分支尖端回退,但那次推送已经发生,token 仍建议吊销。按复测意见再修一轮(新提交)
native/payload无 feature 门槛 → 默认 feature 下 panic"features": ["cocos-editor-injection"],与nativePayloads同门槛,保持迁移前语义(你给的二选一里选了这个)。subdirectoryEnabled现在会与 payload 交付一致地跳过。destinationFileName没人用copyNativePayloads的两个目标路径改用destinationFileName(落盘cocos-editor-bridge.dll,与COCOS_BRIDGE_PAYLOAD_RELATIVE一致)。用例 11 因此转绿。pluginSourceFingerprint→ 删掉它必然导致重建,不再把陈旧 payload 留在随包目录。pluginTreeMatches条件写反validateExtendedDeclarations,覆盖prepareSteps(名字唯一/kind/必需字段/requiredOutputs)、nativePayloads(含destinationFileName)与所有origin: prepared条目的prepare引用完整性。godot_bundle::stage/source_files及其 4 条专属单测,清掉遗留PathBuf导入;构建脚本的 2 条 warning 消失。--features=a,b与--features a,b都支持。当前验证:工具用例 13/13;
npm run agc:bundled-resources:check通过;cargo fmt+cargo check --no-default-features通过;cargo test … package_layout通过。有一条与本 PR 无关的既有失败顺带说明:
template_library::tests::godot_template_creates_native_project_with_relative_root_and_display_name在 macOS 上报「模板安装目录 路径不能包含符号链接:/var」(tmpdir 在/private/var下的符号链接问题),与本次改动无关。请再跑一次你的复现清单(
cargo build --features=…/--no-default-features/bundled-resources:test)确认。.env.local的 token 已从分支尖端移除,仍建议吊销。复测(Windows 真机):P0/P1 全部修好,剩 2 条中等 + 3 条低
复测对象
06ffa7b6a。以下都是本机 Windows 实测输出。已修复并验证
build.rs无条件要求native/payloadcargo build --features=cocos-editor-execute,unity-editor-execute,godot-editor-executeFinished 49.18s,第二次 0.58s fresh;cargo build --no-default-featuresFinished 47.90s。门槛已对齐到cocos-editor-injection(与迁移前语义一致)destinationFileName声明未用cocos-editor-bridge.dll(连字符,与运行时COCOS_BRIDGE_PAYLOAD_RELATIVE一致);bundled-resources:test13/13(用例 11 恢复绿)plugins 命中缓存(未写入)的同时,插件工作区与随包目录都拿到 payload(预缓存复制生效)pluginTreeMatches条件写反!A || !BvalidateExtendedDeclarations。负例实测:删destinationFileName→exit 1 必须是非空字符串;prepare: cocos-bridge-buildX→exit 1 引用了未声明的准备步骤;prepared 子目录去掉prepare→exit 1 缺少 prepare 声明godot_bundle::stage/source_files及其单测已删,构建脚本 warning 由 2 条归零(剩余 12 条是 bin 既有 warning)--features=a,btarget/agc-resource-staging.json重建后连续两次:unity/godot「命中缓存(指纹一致)」、codex/plugins「命中缓存(未写入)」,resources/**全部 size+mtime 零变化验收清单(Windows 侧)逐条状态
unity/godot 命中缓存(指纹一致);注意见下方第 5 条,payload 需带--features=…cocos-editor-injection)CopyFileW保留源 mtime)build.rs唯一写入是OUT_DIR/…prompt_bundle.rs;touch build.rs后重跑resources/**无变化cargo build0.58s fresh,无Compiling genarrative-ai-game-creator-shellcargo test --no-run--features=…,cocos-editor-injectionFinished 1m04s(Cocos 已准备产物的构建期校验路径被覆盖)resources/**一字节仍需处理
1. P2:
--dry-run会写盘。 预缓存那次copyNativePayloads排在 dry-run 提前返回之前:用例没兜住:现有 dry-run 用例断言的是「
resources/plugins不存在」+ 默认 feature,两个条件都绕开了这条路径。修法:把这次复制移到if (dryRun) return之后(保持在缓存短路之前)。2. P2:预缓存写入绕过 owned 断言。
copyNativePayloads现在在assertOwnedPluginRoot(destination, plugins)之前就对destination落盘并mkdir,「非本工具目录不写」的保护在这条路径上失效。修法:把assertOwnedPluginRoot提到预缓存复制之前。3. P3:提交说明与实现不符 + 一处死分支。 commit message 写「把交付物(含缺失态)纳入
pluginSourceFingerprint」,但该函数仍只遍历origin === 'source',其中新增的origin === 'prepared' ? sha256 : …分支不可达。残留缺口(实测):injection 构建过一次后再用默认 feature 跑准备步骤,随包目录里的 payload 不会被清理,plugins仍报「命中缓存」。4. P3:门禁交叉校验缺一条。
nativePayloads[].destinationSubdirectory未与同 plugin 的 prepared 子目录对照:改成native/payloadX门禁仍exit 0(真写错时构建期会 fail-closed,但门禁本该拦)。5. P2:验收清单与实现不一致。 里程碑第 1 条仍预期默认 prepare 就产出
agc-cocos-editor/native/payload/cocos-editor-bridge.dll,而现在门槛(正确地)对齐到cocos-editor-injection,默认入口不产出。建议该条改为带--features=…,cocos-editor-injection的命令,并写明 payload 只在 injection 构建交付;否则下一位验收的人会照旧清单再报一次「缺 payload」。环境说明
复测产生的
plugins/agc-cocos-editor/native/payload/、随包目录里的陈旧 payload 与 staging 记录均已清理,resources/**处于「干净检出 + 默认 feature 跑过一次准备步骤」的一致状态。第三轮问题处理(新提交)
--dry-run会写盘preparePlugins重排为「指纹/匹配只读判断 →if (dryRun) return→ 断言所有权 → 预缓存交付」;dry-run 用例补上 injection feature,覆盖 payload 路径。assertOwnedPluginRoot提到预缓存交付之前。prepared分支不可达origin === 'source'过滤;库文件与 payload 交付态(含「缺失」)纳入指纹;pluginTreeMatches增加「当前目标/feature 下不该存在却存在 → 需要重建」。nativePayloads[].destinationSubdirectory必须是同插件origin: prepared的子目录之一。--features的命令。仍未收掉一条(如实说明):injection 构建后切回默认 feature,随包目录里陈旧的 payload 仍会保留(当前保证「不再重新交付」,清理未生效)。我把它写进了待办而不是留在你的清单里;如果你希望这轮收掉,我继续定位(初步怀疑重建路径仍会把仓库侧的
native/payload复制进去,需要把仓库侧交付物也纳入清理判定)。--feature/payload 相关用例我调整为 13 条全绿(新增的清理用例在缺口修好前不并入,避免红着交付)。验证:工具用例 13/13、声明门禁通过。
PR #524(AGC 随包资源 staging 归位 M3)系统性审计
审计对象
547fb0dbc(basefix/agc-rust-staging-idempotency/0b19df467,diff 16 文件 +1027/−488)。方法:按 10 条不变量(I1 单一声明 / I2 构建期只读 / I3 幂等 / I4 fail-closed / I5 dry-run 零写入 / I6 原子替换与所有权 / I7 feature 一致 / I8 运行时路径一致 / I9 平台一致 / I10 文档一致)分 7 个层切片(声明门禁、准备步骤、Rust 构建期、入口与运行时、测试与文档、跨层矩阵)+ Windows 真机实测。
标「实测」的结论都在本机复现过;标「代码审读」的是逐行核对结论(附行号)。
一、P1:会让产物错误、构建失败或与文档声明冲突(合并前必须定论)
P1-1 插件指纹条件取反 → prepared 内容永不进指纹、智能清理分支不可达(实测+代码审读)
prepare-bundled-resources.mjs:662-667 / 689-694 / 709-714三处A || B → continue应为!A || !B。plugins/agc-cocos-editor/src/加一个文件后重新生成,plugins.fingerprint前后完全相同(fa3978ff…)→ source 内容不进指纹;受控对照(唯一变量 feature 集)默认351d3b97…vs injectionddc947dc…,差异来自这条反判断 → 承诺的「交付物(含缺失态)纳入指纹」未实现。plugins/agc-unity-editor/dotnet/publish/win-x64/加文件后plugins 命中缓存(未写入),随包目录不更新 → 改 Unity helper 源码后随包仍是旧的 90MBAgc.Unity.Attach.exe。pluginTreeMatches(:752-763)里「当前 target/feature 下不该出现的子目录存在即重建」被前置continue变成死代码。!A || !B;代价是把 Unity publish 目录纳入逐文件哈希(当前整轮 0.5s 量级)。P1-2
resources/plugins是整目录随包,但两层门禁都不查「暂存 ⊆ 声明」(实测)tauri.windows.conf.json:15/tauri.macos.conf.json→"resources/plugins": "plugins"(整目录)。resources/plugins/agc-cocos-editor/塞一个EXTRA-JUNK.dll→cargo buildexit 0(validate_staged_plugins只做 source→staged 逐文件比对,多余文件不查;collect_tree_files只查符号链接);plugins 命中缓存(未写入),多余文件不会被清理 → 会被打进安装包。cocos_editor_bridge.dll)都会静默随包。allowedUnitEntries思路),或把 P1-1 的死分支修活 + 缓存命中时按声明白名单清理。P1-3 并发调用会破坏 staging(实测,文档却声明必须串行化)
EPERM: operation not permitted, rename 'resources\codex\win-x64-staging-27952-e4b4ee98' -> 'resources\codex\win-x64'(stageAtomically:501)。2 并发同样复现。stageAtomically是rm(target)→rename(staging, target),两步之间崩溃/占用会留下「目标树缺失 + 孤儿 staging 目录」(孤儿被 gitignore,但不会被清理)。target → target.old、staging → target、删.old)。P1-4 feature 集三处独立推导,存在确定性失败组合(代码审读)
start-tauri-dev.mjs的readDevCargoFeatures(AGC_DEV_CARGO_FEATURES或默认)、releasebuild-release.mjs:442的defaultEditorFeatures(target)、cargobuild-release.mjs:368-374的withDefaultCargoFeatures(显式--features时提前返回)。--features,staging 仍用默认集 → 「cargo ⊋ staging」→build.rs:113panic 或静默用旧件。--no-bundle完全不 staging(build-release.mjs:467-471),但 cargo 仍带三个编辑器 feature → Windows 干净树必然失败;CI 只在 Linux 跑这条 smoke(feature 集为空)所以看不见。--no-bundle要么也 staging、要么显式报错说明需要先跑准备步骤。P1-5 Cocos payload 在任何入口都不交付,运行时却会去找它(代码审读;迁移前已存在,M3 未改变但清单声称已验)
nativePayloads[].features = ["cocos-editor-injection"](package-layout.json:167-175),而三个入口只给cocos-editor-execute(cargo-features.mjs:20-23)→ 唯一能交付的是文档里手写的--features=…,cocos-editor-injection。editor_adapters.rs:96-97,151-153在feature = "cocos-editor-execute"下注册适配器并解析plugins/agc-cocos-editor/native/payload/cocos-editor-bridge.dll→ 发布包必然走到「插件未随包提供 Cocos bridge payload」。resources/plugins,所以迁移期遗留的 payload 现在会被删掉(迁移前只是「不写」)→ 包内容相对迁移前发生变化。P1-6 M3 的核心正确性在自动化里完全没有覆盖(代码审读)
把三层缺口叠起来看,这就是「反复出现只有真机才能发现的 bug」的根因:
plugin_staging_applies/staged_targets对x86_64-unknown-linux-gnu返回 no-op → CI 里没有任何一条校验真正跑过真树,只有合成夹具单测;vitest.config.ts:159只收scripts/**/*.test.ts;check-web只跑 typecheck + 壳内 tests);godot_bundle.rs零单测。修法:把
agc:bundled-resources:test接进agc-web分组(零成本);再加一个「纯 Node + 合成声明」的用例集覆盖 prepared 交付/缓存/白名单;Windows 侧争取一条只跑prepare + cargo check的 lane。P1-7 Rust 侧对 prepared 产物只查「目录存在」且
.ok()吞错会 fail-open(代码审读)build.rs:100-134+package_layout.rs:290-303:prepared 子目录只判is_dir;声明里现成的 10 条 UnityrequiredOutputs、nativePayloads的交付文件名映射完全不参与 Rust 校验。M3 又恰恰删掉了「同一进程内copy_staged_tree保证 staged == source」这层保障 → 陈旧/残缺 payload 静默进包(与 P1-1 同一 blast radius,这是它的构建期半边)。package_layout.rs里sha256_file(x).ok() != sha256_file(y).ok()的写法在两侧都读失败时会判为「相等」→ 校验静默通过(例如文件被占用/无权限);一读一失败时才报「与源码不一致」(文案误导)。修法:改成?/map_err传播 IO 错误。二、P2:契约/验证缺口(不阻塞合并,但会在下一次改动里咬人)
subdirectories[].plugin改成agc-unity-editoX→ 该子目录在 NodepluginTreeMatches与 Rust 两处校验里都被过滤掉 → 校验与一致性比对整体消失;native/payload.features改成cocos-editor-injectioX→ 两端读同一份声明 → 产物永久不生成、存在性校验一起跳过(静默缺件);prepareSteps[unity-helper-publish].fingerprint = {}→ 运行时fingerprintSources抛 TypeError(不是可读 fail)。plugin∈ 实际插件目录、features∈ Cargo[features]、fingerprint字段形状、subdirectories[].path不含../绝对路径(当前无任何路径包含性校验,一个..会让复制/写入逃出插件根甚至仓库)。destinationFileName与运行时常量无联动:声明里的交付文件名只被 Node 用;Rust 侧COCOS_BRIDGE_PAYLOAD_RELATIVE/UNITY_ATTACH_HELPER_RELATIVE/GODOT_BRIDGE_PAYLOAD_RELATIVE是硬编码常量。改声明里的名字所有门禁全绿,只有运行时才发现找不到(上一轮那个 bug 的同一类)。tauri.windows.conf.json、tauri.macos.conf.json、check-config.mjs:1356-1422字面量。给声明加一个组件 → 所有门禁全绿 → 客户端启动才报「缺少必需组件」。(反向删文件是 fail-closed。)npm run agc:bundled-resources:prepare -- --target … --features=…→[agc-resources] 未知参数:x86_64-pc-windows-msvc(root 包装脚本package.json:173缺少结尾--,npm 自己吃掉了--target)。直接调node scripts/prepare-bundled-resources.mjs …才正常(能看到cocos-bridge-build)。check-web=typecheck(含bundled-resources:check门禁)+vitest apps/ai-game-creator-shell/tests;根vitest.config.ts:159只收scripts/**/*.test.ts,不收壳内scripts/*.test.mjs。三处 M3 修复(含上次那条红用例)都只有本地能跑。agc-demo-editor/agc-cocos-editor,真实是 unity/godot/cocos →copyPreparedPayloads、libraryStaging两条编辑器分支交付链零覆盖;godot_bundle.rs的mod tests只有一个从未被调用的 fixture,Godot 深度校验零单测。--features语义不校验(实测):--features=cocos-editor-injectioX,unity-editor-execute→ exit 0、日志里 cocos 步骤直接消失;--features不带值时静默变成空集。target/debug/plugins/**(tauri-buildcopy_resources的副本,只增不删)。实测该目录存在且与resources/plugins并存;只要在 dev 会话中重跑准备步骤而不重建 Rust 侧,客户端读到的就是旧件。三、P3:卫生、一致性与文档
记录文件(
src-tauri/target/agc-resource-staging.json)被删或cargo clean后,即使暂存树完全正确也会全量重写(与代码注释「丢失只多一次哈希」不符)。copyNativePayloads硬编码'plugins'字面量(prepare-bundled-resources.mjs:1107),违反 I1(应从声明sourceDirectory派生)。cocos 准备步骤与 app 构建共用同一个
--target-dir,但 feature 集不同(windows-injectionvswindows-bootstrap)→ 同一 crate 反复重编,且一旦将来给该步骤加指纹,copyNativePayloads可能捡到 app 构建产出的非 injection dll。建议该步骤用独立 target-dir,再考虑加指纹与必需产物。外部工具链版本(.NET/CMake/NuGet)不进步骤指纹 → 升级工具链后不会重建对应产物。
godot_bundle.rs对files[0]=dll / files[1]=metadata的位置耦合,声明重排会静默改变校验语义。CLI
--target/--destination不带值时静默回退到宿主/默认路径;脚本头部用法注释缺--features,且仍写「编辑器分支产物(Unity/Godot/Cocos)仍由构建脚本生成,归位在 M3」(M3 即本 PR)。check-package-layout.mjs:456,618提示用户运行npm run agc:package-layout:sync—— 该脚本名不存在(真名agc:bundled-resources:sync)。resources/cocos-editor-bridge/{.gitignore,.gitkeep}是旧的 payload 落点占位,已无任何写入方/读取方(M3 把 payload 挪到plugins/…/native/payload),按「退役对象直接清理」应删除。文档口径待更新(逐条给位置):
resources/**会被cargo build直接拒绝」对 prepared 产物不成立(只查存在性;插件 source 派生部分成立);CopyFile保留源 mtime 所以时间戳不变,POSIX 会变);Rust 侧消费点漂移:
subdirectory_applies_to_plugin在validate_staged_plugins用了,但另一处校验路径没用(K4 的 Rust 同类新实例);.ok()之外还有collect_tree_files的返回值被丢弃(错误仍在传播,但白名单校验无返回物)。三处「reparse point/符号链接」判定不一致:
frontend_dist_guard用FILE_ATTRIBUTE_REPARSE_POINT,插件校验用symlink_metadata→ Windows 上 junction/mount point 的判定可能不一致。resources/plugins缺失时的 panic 文案不含「请先执行随包资源准备步骤」这类可执行提示(codex 侧有),且会打断 rust-analyzer/cargo check。四、已核对无问题(避免重复劳动)
--dry-run零写入(实测:工作区与随包目录都没有 payload,日志需要重新生成(dry-run 未写入))。resources/plugins/agc-cocos-editor/src/entry.mjs→cargo buildexit 101,插件随包文件与源码不一致)。resources/**全部 size+mtime 零变化(实测)。build.rs唯一写OUT_DIR/…prompt_bundle.rs);按插件收敛、destinationFileName、owned 断言顺序、Godot 指纹、死代码清理均已在上一轮验证通过。plugin_staging_applies与运行时 cfg 一致,Linux CI 上为 no-op(符合设计)。start-tauri-dev.mjs先 prepare 再 spawn tauri,且有测试断言。.taurignore两份条目删除正确(构建期不再写resources/plugins);*-staging-*残骸已被 gitignore。payload_path()返回可读错误而不是 panic/启动失败。check-package-layout.mjs已在agc:typecheck链内(声明门禁本身会跑)。五、建议的一次性修复清单(按文件)
scripts/prepare-bundled-resources.mjsscripts/check-package-layout.mjsplugin/features/fingerprint形状/路径包含性;② 修agc:package-layout:sync提示;③ 让nativePayloads的关键字段进入生成物或与运行时常量建立校验scripts/cargo-features.mjs+ 两个入口脚本--no-bundle与命令行--features的 staging 一致性package.json(root)agc:bundled-resources:prepare等包装脚本补结尾--,或改成直接node调用agc:bundled-resources:test(13 例)接进 agc-web 分组;考虑 Windows lane 或至少把 Windows 专有路径的纯 Node 断言补齐六、验收清单需要改写的条目
--(否则参数被 npm 吃掉);并注明 Cocos payload 只在 injection 构建下交付。cargo test --no-run在 Windows 上需要先跑准备步骤(codex 校验与 feature 无关)。系统性审计:已修第一批(新提交)
感谢这份审计,含金量很高。先收掉便宜且明确的一批,其余按你给的清单排期:
已修
pluginSourceFingerprint改!A || !B;实测你的复现路径(改 Unity publish 目录 → 现在应当重建)请再确认一次.ok() != .ok()双侧读失败判为相等(fail-open)map_err传播,读失败即报「读取…失败」copyNativePayloads硬编码'plugins'sourceDirectory派生agc:bundled-resources:syncresources/cocos-editor-bridge/旧占位无写入方--,清单命令可直接复制执行未修(按你的清单排期,需要你定优先级)
--no-bundle/ 命令行--features的一致性。agc:bundled-resources:test接进 agc-web 分组(零成本),我可以立刻做。你说优先级(我建议先 P1-6 的零成本 CI 接线 + P1-2 白名单,再 P1-3 并发,P1-4/5 需要产品口径)。
补充:第一批修复已提交为
3b4f9b2e1并推送(工具用例 13/13、声明门禁与cargo check --no-default-features通过)。顺带记录一个仓库坑(本轮踩到):
cargo fmt --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml找不到仓库根的rustfmt.toml,对build_support/*.rs用的是默认风格;而直接用rustfmt --config-path rustfmt.toml(edition 2024)会与之冲突,pre-commit 的 Rust format check 会拦下。改build_support/*.rs时应以cargo fmt --manifest-path …为准。按你的口径收口(新提交)
ai-game-creator-shell:check:web,Linux CI 直接覆盖这 13 条。stageAtomically的失败信息也改成可执行的提示(原来是裸EPERM rename)。cargo-features.resolveEditorFeatures作为唯一推导入口(env > 命令行--features> 平台默认),dev 入口、发布入口的 cargo 参数、随包资源准备步骤共用同一集合;另外把--no-bundle也改成执行 staging(app 构建本身就需要随包资源),只跳过总号发布——这样「干净树 +--no-bundle」不再必然失败。cocos-editor-injection(默认集不含),所以默认构建一直不交付,运行时payload_path()返回可读错误而不是 panic;M3 的唯一差别是「整目录替换」会把注入构建遗留的 payload 删掉(迁移前只是不写)。产品口径仍待你定:release 是否加 injection;不加的话运行时不必无条件注册该适配器。验证:准备步骤 13/13、发布入口 39/39、dev 入口 12/12、声明门禁一致。
按评估结论落地(
3fba76fd6)做了(都是「不做会静默出错」那类):
plugin必须指向真实插件目录、features必须存在于 shell crate 的[features]、声明路径必须是仓库内相对路径(拒绝绝对路径与..)。--target/--destination/--features,含--features空值),不再静默回退宿主目标。godot_bundle::validate补负例单测。target/debug/plugins/**。没做(按评估结论明确不做,理由如下):
留作后续:P2-2 运行时常量锁定单测需要先把
nativePayloads也 emit 进 Rust 声明(今天试过,链条比预估长,已回退避免半成品)。验证:准备步骤 14/14、发布入口 39/39、dev 入口 12/12、
godot_bundle19 项、声明门禁一致、cargo check通过。更正:落地提交是
acf021de2(前一条把哈希写成了上一个提交——pre-commit先因一个未使用的生成器辅助函数拦了一次)。验证结论不变:准备步骤 14/14、发布入口 39/39、dev 入口 12/12、godot_bundle19 项、声明门禁一致、cargo check通过。评审意见(范围:本 PR 期望解决的问题 —— M3 编辑器分支产物归位到准备步骤)
结论:目标达成,可放行。 判据用 PR 自己的目标与里程碑验收标准,逐条在 Windows 真机复现。
acf021de2)touch build.rs→cargo build(52s)→resources/**90 个文件 path/size/mtime 0 变化cargo build1.13s Finished,无Compiling genarrative-ai-game-creator-shellresources/**快照 size+mtime+sha 零变化;injection 配置第 2 次起全部命中,payload mtime 不变plugins/agc-unity-editor/dotnet/publish/win-x64/加文件 →plugins 重新生成且随包同步;往暂存插件目录塞EXTRA-JUNK.dll→ 重建并清理;feature 切回默认 → 随包 payload 被清掉cargo buildexit 101(build.rs:151),还原后构建通过cargo test --no-runFinished 1m18s,产出 5 个测试可执行文件resolveEditorFeatures由 dev / release / staging 共用;--no-bundle也 stagingagc:bundled-resources:check通过;准备步骤用例 14/14;ai-game-creator-shell:typecheck通过;bundled-resources:test已接进ai-game-creator-shell:check:web(CI 分组)npm run agc:bundled-resources:prepare -- --target x86_64-pc-windows-msvc --dry-run参数正确透传(原先被 npm 吃掉)仍需收尾的 3 条(都不改变「目标已达成」的结论)
build_support/package_layout.rs:770的source_candidates_follow_declared_order把期望值硬编码成/app/node_modules/...(正斜杠),与实现path.join在 Windows 产出的反斜杠路径比对必然失败(cargo test --no-run能过,但跑用例会红,验收清单的「全量门禁通过」在 Windows 目前因此不成立)。改成path.join或[\\/]即可,建议顺手在 #520 或本 PR 收掉。docs/【开发运维】本地开发验证与生产运维-2026-05-15.md:91与技术方案:138仍写「tauri build --no-bundle不强制 staging」,与本次改动(--no-bundle也 staging)相反,需要同步。:104/:202) 仍写「必须串行化」「原子替换 + 所有权校验 + 有界重试」,与实现(不加锁,只把失败改成可读、且不丢已生成的 staging)不一致;而stageAtomically的注释又指向 §4.3 —— 两处口径需二选一对齐。未由评审执行的验收项(需在验收环境做)
不属于本 PR 目标、建议单列后续项
上一份系统性审计(#5760)里的 P2/P3:
prepared只查存在性(现已按此口径写进文档与验收清单,属已承认的边界)、codex 组件白名单 4 份副本、dev 运行期读target/debug/plugins的只增不删副本、copyNativePayloads硬编码'plugins'、外部工具链版本不进步骤指纹、resources/plugins缺少内容白名单(多余文件会静默随包)等。suzmii referenced this pull request2026-09-30 11:51:47 +08:00
⚠️ 同源新回归:master 上
npm run agc现在无限重建,客户端起不来(claude-agent 不在 M1/M2/M3 声明里)结论先说:这是同一类问题(构建期写 tauri dev 监听树)的新实例,但
resources/claude-agent不在本 PR 的声明/校验里,所以按现状合入不会修掉它;而且在解build.rs冲突时二选一都会出错。现象(Windows 真机,master 与该 PR 基点均复现)
npm run agc→ typecheck OK → dev-stack OK(spacetime 3101 / api-server 8082 / vite 3080 健康,/healthz200)→ tauri dev 编译后进入死循环:60 秒内 15+ 轮,每轮重建都会杀掉上一个实例,客户端窗口始终起不来。
根因
apps/ai-game-creator-shell/src-tauri/build.rs:main()(337-347)每次编译都无条件调用stage_bundled_claude_agent()fs::remove_dir_all(resources/claude-agent),再重建目录、重拷index.mjs+ Node runtime +@anthropic-ai/claude-agent-sdktarget_matches_source、Unity/Godot 的指纹那样跳过写入resources/claude-agent在监听树内,而src-tauri/.taurignore只忽略resources/plugins/引入 commit:
fb130d1847(2026-09-30 13:49,「新增 AGC 模型 Agent 模式与 Claude Agent SDK sidecar」),此后无人再动build.rs。本 PR(及 #520)基点 = master@
8f59c034f(09-28),不含该 commit;package-layout.json顶层键只有codex / description / layoutVersion / plugins / schema,两个分支的build.rs里claude出现 0 次。验证(本机实测)
npm run agcresources/claude-agent/index.mjs的 mtime = 每次重建时刻src-tauri/.taurignore追加resources/claude-agent/Finished dev profile in 1m59s→Running target\debug\genarrative-ai-game-creator-shell.exe→ 之后 5 分钟零 Rebuilding,客户端进程稳定存活(实验改动已 revert。生效点是
src-tauri/.taurignore,外层apps/ai-game-creator-shell/.taurignore不起作用。)对本 PR 的影响
git merge-tree --write-tree origin/master origin/feat/agc-bundled-resources-m3→CONFLICT (content): apps/ai-game-creator-shell/src-tauri/build.rs(另有 decision-log/pitfalls/package.json)。解冲突时:stage_bundled_claude_agent()→ 循环照旧,本 PR/M3 的目标「构建期零写入」不成立;tauri.conf.json的"resources/claude-agent": "claude-agent"失去产出方,准备步骤也不认识它,随包缺 Node runtime + SDK,cc 执行模式运行期失败(此处为推断,未实测合并后状态,但声明与只读校验里都没有这一项)。请求:把 claude-agent 当作与 codex 同构的第 N 个产物一并归位
src-tauri/build_support/package-layout.json增加 claude-agent 条目(libraryStaging+prepare/files,或origin: prepared),声明resources/claude-agent/{index.mjs, node_modules/@anthropic-ai/claude-agent-sdk/**, <platform> binary};scripts/prepare-bundled-resources.mjs按声明产出这三块:内容指纹命中零写入、写临时目录后原子替换(直接复用 codex 那条路径);build.rs删除stage_bundled_claude_agent()以及仅供它使用的stage_plugin_file/copy_plugin_tree调用,把「产物存在性 + 逐文件 sha256 + 可执行位」并入validate_staged_resources();tauri.conf.json的resources/claude-agent → claude-agent映射保持不变。顺带收益:目前 claude-agent staging 没有任何哈希守卫,每次
cargo check都要重拷 Node runtime + SDK。止血(可独立先合,主线恢复可用)
apps/ai-game-creator-shell/src-tauri/.taurignore增加一行:沿用
resources/plugins/的既有模式,已实测解除循环。若先走这条,等 M3 落地、构建期零写入成立后记得连这两行一起删(与两份.taurignore的删除保持一致)。cc @kdletters(sidecar commit 作者,随包资源清单需要你确认口径)
- `StoredEvent` 增加非序列化的 `pending` 产物字段:它的有无就是队列成员身份,队列的先后就是事件先后,不另建队列表 - 新增 `enqueue_pending_turn`:按 `clientTurnId` 幂等(判重范围「在队 ∪ 正在跑的那一轮」),容量用 `queue_has_room` 挡在队条目 - 新增 `remove_pending_turn`:typed 结果 `Removed | AlreadyDispatched | NotFound`,在临界区里取走产物并追加 `queue.removed{cancelled}` - 新增 `claim_pending_turn`:同一临界区取队首 → 登记占用 → 追加 `turn.started` 与 `queue.removed{dispatched}`,已有未收口回合或队列为空时返回 `None` - `mark_queue_events_cleanable` 跳过仍挂着产物的条目,保证在队条目永远不会被回收 - 新增薄包装 `enqueue_direct_pending_turn` / `remove_direct_pending_turn` / `claim_direct_pending_turn`,并补 5 条单测覆盖 bootstrap 可见、幂等、容量、放行原子性与三种取消结果- 主站顶栏在所有断点保持单行:去掉顶栏与动作区的 flex-wrap,品牌和下载、泥点、账户入口不再折成两行 - 窄屏(<640px)下载入口收起为 44×44 图标按钮,保留 aria-label/title,≥640px 恢复「下载客户端」完整文案 - 窄屏(<480px)顶栏品牌只保留 IP 标识,让出空间给下载、泥点与账户入口,并收紧窄屏动作区间距 - 头像裁剪弹窗去掉 portal={false},改由 UnifiedModal 默认 portal 渲染到 document.body - 个人中心两级弹窗壳层同步去掉 portal={false},昵称等个人中心弹窗一并回到页面级层级 - 弹窗不再受 .platform-desktop-layout 的 z-index:1 stacking context 限制,层级高于固定底部主菜单(z-index:60) - 共享记忆补记单行顶栏口径与「内联弹窗被固定底栏压住」的排障经验- 项目自动上传与后台工程下载:查清「本地数据库 404」的成因是 standalone 未运行(并非库缺失),`npm run dev:spacetime` 启动后自动发布 xushi-p4wfr,`/healthz` 200 且认证投影从 spacetime 表恢复 - 记录联调结果:渠道端点返回 {"channels":["dev","release"]}、未认证 401、游标分页 50+34=84 条、官方门禁 check:agc-project-snapshot-admin-http 对两个真实项目各 10/10 通过(含逐文件 SHA-256 与 .agent 目录还原) - 开放事项索引同步:该计划只剩「新客户端安装版下的隔离实机自动上传」这一条- prompt_context.rs:sanitize_error_context 改为 split_inclusive('\n') 逐段处理并原样保留行终止符,「逐段脱敏」与「整段脱敏」同形(CRLF 仍归一成 LF,私钥块整行吞掉的旧口径不变) - codex_app_server/mod.rs:新增 direct_thread_delta_sanitization_preserves_line_breaks,钉住逐段脱敏 == 整段脱敏,去掉 split_inclusive 即红 - pitfalls.md:记入本次排障(含 chunk 起点路径误判这一同源未修缺陷)- GameGalleryPage 的 hero 按钮不再取 visibleGames[0] 直接开详情,改为把游戏列表滚进视野 - 滚动按平台页签面板(.platform-tab-panel)算相对偏移并走 scrollTo({behavior:'smooth'}),缺少 scrollTo 时退化成直接赋值 - 新增 gameListRef 锚在列表工具栏,退役只服务该按钮的 featuredGame 派生 - GameDistributionPages.test.tsx 新增「首屏「立即试玩」滚动到列表,而不是打开第一款游戏详情」,断言未触发 onOpenDetail 且滚动落在页签面板上 - 里程碑文档新增「网页侧交互修正(2026-09-30)」记录该修正与验证命令