diff --git a/apps/ai-game-creator-shell/scripts/prepare-bundled-resources.test.mjs b/apps/ai-game-creator-shell/scripts/prepare-bundled-resources.test.mjs index b3cf4479b..ef3425e47 100644 --- a/apps/ai-game-creator-shell/scripts/prepare-bundled-resources.test.mjs +++ b/apps/ai-game-creator-shell/scripts/prepare-bundled-resources.test.mjs @@ -312,6 +312,49 @@ test('copies only whitelisted plugin subdirectories', () => { } }); +test('fails closed when the upstream package metadata drifts from the declaration', () => { + const fixture = buildFixture(); + try { + const declaration = fixture.declaration; + const layout = declaration.codex.targets.find( + (entry) => entry.target === WINDOWS_TARGET, + ); + const metadataFile = path.join( + fixture.appRoot, + `node_modules/@openai/codex-${layout.platform}/vendor/${WINDOWS_TARGET}/codex-package.json`, + ); + const metadata = JSON.parse(fs.readFileSync(metadataFile, 'utf8')); + assert.equal(metadata.version, declaration.codex.version); + for (const [key, value] of [ + ['version', '0.0.0'], + ['layoutVersion', 2], + ['entrypoint', 'bin/other.exe'], + ['resourcesDir', '../private'], + ]) { + fs.writeFileSync( + metadataFile, + `${JSON.stringify({ ...metadata, [key]: value }, null, 2)}\n`, + ); + assert.throws( + () => prepare(fixture), + /上游包元数据与声明不一致/, + `${key} 漂移必须被拒绝`, + ); + } + assert.ok( + !fs.existsSync( + path.join( + fixture.destinationRoot, + 'resources/codex/win-x64/manifest.json', + ), + ), + '拒绝时不得留下产物', + ); + } finally { + fixture.cleanup(); + } +}); + test('fails closed when the upstream package is missing', () => { const fixture = buildFixture(); try { diff --git a/docs/project-memory/plans/【里程碑】AGC随包资源生成接入dev与发布入口-2026-09-26.md b/docs/project-memory/plans/【里程碑】AGC随包资源生成接入dev与发布入口-2026-09-26.md index dfe1a529b..9486a877f 100644 --- a/docs/project-memory/plans/【里程碑】AGC随包资源生成接入dev与发布入口-2026-09-26.md +++ b/docs/project-memory/plans/【里程碑】AGC随包资源生成接入dev与发布入口-2026-09-26.md @@ -37,7 +37,7 @@ - [x] 客户端开发启动一次成功:不再出现因资源变更而触发的重复构建,客户端与运行器进程稳定存活。(证据:清理 `AGC` dev 探针——`tauri dev` 全程 `Rebuilding application` 0 次、`Running DevCommand` 1 次、主 crate 仅编译 1 次,app 起来后持续处理项目;完整 `npm run agc` 在本机被 SpacetimeDB `Pre-publish check`(401 InvalidSignature / 502 Bad Gateway)阻断,属既有本机环境问题。) - [x] 源码不变时连续两次构建,第二次为秒级完成;构建脚本声明的输入中不再出现随包资源路径。(证据:`cargo build --no-default-features` 连续三次 0.69 / 0.22 / 0.22 秒;强制构建脚本重跑后 `resources/codex` 与 `resources/plugins` 快照逐项不变。) - [ ] Windows 与 macOS 打包产物中的随包资源,与迁移前逐项一致(路径、内容摘要、可执行位)。(macOS 侧 `check-macos-bundle.mjs` 待打包验证;Windows 待 M3 归位三处构建期产物后复验。) -- [x] 准备步骤连续执行两次不改变产物内容与时间戳;缺少准备步骤时,打包与启动以明确错误失败,而不是静默产出缺组件的包。(证据:准备步骤 9 条用例含幂等与失败关闭;`AGC_SKIP_RESOURCE_STAGING=1` 在既有产物上只读通过;构建脚本校验缺失组件时 fail closed。) +- [x] 准备步骤连续执行两次不改变产物内容与时间戳;缺少准备步骤时,打包与启动以明确错误失败,而不是静默产出缺组件的包。(证据:准备步骤 10 条用例含幂等、上游缺失、上游元数据漂移与失败关闭;`AGC_SKIP_RESOURCE_STAGING=1` 在既有产物上只读通过;构建脚本校验缺失组件时 fail closed。) - [ ] 本机 Rust 门禁(会触发构建脚本的测试入口)与不打包构建路径仍然可用。(macOS 侧已验;Windows 的 `check:rust:shell` 待真机确认。) ## 证据要求 diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index ab546dd32..bc5846ba7 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -9596,5 +9596,5 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - 决策(写入边界,声明新增 `origin`):`origin: source`(Codex 组件、插件 `src`/`panels`/`skills`/`native/payload`)由准备步骤写;`origin: build`(Unity `dotnet/publish/win-x64`)与外部工具链产物(Godot `native/gdextension`、Cocos payload)由构建脚本在产物生成后写。构建脚本删除 codex 与插件白名单的写入分支及 `stage_plugin_file`/`copy_plugin_tree`/`copy_plugin_file`,改为 `stage_build_generated_plugin_payloads`。 - 决策(契约收口):插件随包工作区改为与仓库源码逐文件比对(清单 + 逐文件 sha256 + 整树符号链接,构建期派生内容只查存在性),实现移入 `build_support/package_layout.rs` 以复用单测;上游原生包元数据(layoutVersion/version/target/entrypoint/resourcesDir/pathDir)改由准备步骤按声明校验,`build_support/codex_package_metadata.rs` 因失去调用方而删除。准备步骤改为同步实现(全部是本地同步 IO),入口可直接调用而无需子进程。 - 影响面:`apps/ai-game-creator-shell/scripts/{prepare-bundled-resources.mjs,prepare-bundled-resources.test.mjs,start-tauri-dev.mjs,build-release.mjs,build-release.test.mjs}`、`apps/ai-game-creator-shell/tests/start-tauri-dev.test.ts`、`src-tauri/build.rs`、`build_support/{package_layout.rs,package-layout.json,package-layout.generated.rs}`(`codex_package_metadata.rs` 删除)、`src/agent/codex_cli.rs`、技术方案 §4.9、M1/M2 里程碑与运维文档。 -- 验证:`cargo build --no-default-features` 连续三次 0.69 / 0.22 / 0.22 秒全程 fresh;强制构建脚本重跑(`touch build.rs`)后 `resources/codex` 与 `resources/plugins` 的快照(相对路径/大小/mtime/sha256)逐项不变;`cargo test --no-default-features … package_layout` 36 passed;`node --test scripts/prepare-bundled-resources.test.mjs` 9 passed;`node --test scripts/build-release.test.mjs` 39 passed(含 `stage → bundled → build` 顺序与 no-bundle 不 staging);`npx vitest run tests/start-tauri-dev.test.ts` 12 passed(含「准备步骤先于 CLI 启动」)。 +- 验证:`cargo build --no-default-features` 连续三次 0.69 / 0.22 / 0.22 秒全程 fresh;强制构建脚本重跑(`touch build.rs`)后 `resources/codex` 与 `resources/plugins` 的快照(相对路径/大小/mtime/sha256)逐项不变;`cargo test --no-default-features … package_layout` 36 passed;`node --test scripts/prepare-bundled-resources.test.mjs` 10 passed(含上游元数据漂移被拒);`node --test scripts/build-release.test.mjs` 39 passed(含 `stage → bundled → build` 顺序与 no-bundle 不 staging);`npx vitest run tests/start-tauri-dev.test.ts` 12 passed(含「准备步骤先于 CLI 启动」)。 - 边界(未验证):Windows 真机未验证,且 Unity publish 目录、Godot gdextension、Cocos payload 仍是构建期写入,Windows 构建新鲜度要等 M3 归位;完整 `npm run agc` 在本机被 SpacetimeDB `Pre-publish check`(先后 401 InvalidSignature 与 502 Bad Gateway,属既有本机环境问题)阻断,未跑通整条 dev 启动链路。