按评审修复 M3:校验按插件收敛、Cocos 链路与 Godot 指纹、门禁与文档收口
Project CI / AI game creator shell Rust crates (pull_request) Failing after 1m26s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m7s
Project CI / Backend tests (pull_request) Successful in 3m46s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 6m9s
Project CI / Frontend tests (pull_request) Successful in 1m52s
Project CI / Native shell tests (pull_request) Successful in 6m4s
Project CI / Repository checks (pull_request) Successful in 2m18s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m40s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 9m42s
Project CI / AI game creator shell Rust crates (pull_request) Failing after 1m26s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m7s
Project CI / Backend tests (pull_request) Successful in 3m46s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 6m9s
Project CI / Frontend tests (pull_request) Successful in 1m52s
Project CI / Native shell tests (pull_request) Successful in 6m4s
Project CI / Repository checks (pull_request) Successful in 2m18s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m40s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 9m42s
- P0:validate_prepared_payloads 遍历所有插件 × 所有子目录 → 与 libraryStaging 分支对称地按插件收敛(声明新增 subdirectories[].plugin,Rust 加 subdirectory_applies_to_plugin,工具复制与指纹同样按插件收敛) - P1 Cocos:cargo 准备步骤改用该 crate 自身 manifest 并把 --target-dir 指回 src-tauri/target(原 -p + --features 被 cargo 以「cannot specify features for packages outside of workspace」拒绝);nativePayloads 拆出 sourceFileName(下划线)与 destinationFileName(连字符);native/payload 改为 origin=prepared 并绑定 cocos-bridge-build - P1 Godot:补 fingerprint(roots ["."],排除 bin/.build/native-build,戳文件 bin/win-x64/.agc-source.sha256),不再每次真跑 build.ps1 - P2:pluginSourceFingerprint 对 prepared 产物改用内容哈希(避免无条件覆盖 mtime 导致每次全量重建)、库文件纳入指纹;pluginTreeMatches 对 prepared 只查存在性(内容由指纹覆盖,顺带缓解 90MB exe 的哈希开销);GODOT_BUNDLE_FILES 缺清单即失败,不再静默退化为空数组;CLI 传 log 并新增 --features;用例正则兼容 Windows 路径分隔符;主规范 §4.8 残留的 AGC_SKIP_RESOURCE_STAGING 已清 - 安全:撤出误提交进 .env.local 的真实 token(还原模板并 force-push 分支尖端) - 验证:npm run agc:bundled-resources:check 通过;cargo fmt + cargo check --no-default-features 通过(含新 plugin 字段与按插件收敛的校验);工具用例 12/13(余 1 条为夹具断言待修)
This commit is contained in:
@@ -216,6 +216,10 @@ function parseDeclaration(raw) {
|
||||
return {
|
||||
path: expectString(parsed.path, `${at}.path`),
|
||||
origin: expectOrigin(parsed.origin, `${at}.origin`),
|
||||
plugin:
|
||||
parsed.plugin === undefined
|
||||
? ''
|
||||
: expectString(parsed.plugin, `${at}.plugin`),
|
||||
targetContains: optionalStringArray(parsed, 'targetContains', at),
|
||||
targets: optionalStringArray(parsed, 'targets', at),
|
||||
features: optionalStringArray(parsed, 'features', at),
|
||||
@@ -365,6 +369,7 @@ function renderSubdirectory(entry) {
|
||||
'Subdirectory',
|
||||
[
|
||||
['path', rustString(entry.path)],
|
||||
['plugin', rustString(entry.plugin ?? '')],
|
||||
['origin', rustString(entry.origin)],
|
||||
['target_contains', rustStrings(entry.targetContains)],
|
||||
['targets', rustStrings(entry.targets)],
|
||||
@@ -390,6 +395,13 @@ function renderLibraryStaging(entry) {
|
||||
|
||||
function renderGenerated(declaration) {
|
||||
const codex = declaration.codex;
|
||||
const godotStaging = declaration.plugins.libraryStaging.find(
|
||||
(entry) => entry.layout === 'godot-bundle',
|
||||
);
|
||||
if (!godotStaging || godotStaging.files.length === 0) {
|
||||
fail('声明缺少 godot-bundle 随包文件清单,拒绝生成空清单');
|
||||
}
|
||||
const godotFiles = godotStaging.files;
|
||||
const plugins = declaration.plugins;
|
||||
const codexStruct = renderStruct('Codex', [
|
||||
[
|
||||
@@ -451,9 +463,7 @@ pub const CODEX_VERSION: &str = ${rustString(declaration.codex.version)};
|
||||
pub const CODEX_CLI_VERSION: &str = ${rustString(declaration.codex.cliVersionPrefix + declaration.codex.version)};
|
||||
pub const CODEX_MANIFEST_SCHEMA: &str = ${rustString(declaration.codex.manifestSchema)};
|
||||
|
||||
pub const GODOT_BUNDLE_FILES: &[&str] = ${rustStrings(
|
||||
declaration.plugins.libraryStaging[0]?.files ?? [],
|
||||
)};
|
||||
pub const GODOT_BUNDLE_FILES: &[&str] = ${rustStrings(godotFiles)};
|
||||
|
||||
pub const CODEX: Codex = ${codexStruct};
|
||||
|
||||
|
||||
@@ -572,6 +572,10 @@ export function pluginDirectories(declaration, repoRoot) {
|
||||
);
|
||||
}
|
||||
|
||||
function subdirectoryAppliesToPlugin(subdirectory, pluginName) {
|
||||
return !subdirectory.plugin || subdirectory.plugin === pluginName;
|
||||
}
|
||||
|
||||
function subdirectoryEnabled(subdirectory, target, features) {
|
||||
const matchesContains =
|
||||
!subdirectory.targetContains?.length ||
|
||||
@@ -663,10 +667,14 @@ function pluginSourceFingerprint(declaration, plugins, target, features) {
|
||||
}
|
||||
const root = path.join(plugin.root, subdirectory.path);
|
||||
for (const file of walkFiles(declaration, root)) {
|
||||
const info = statSync(path.join(root, file));
|
||||
lines.push(
|
||||
`${plugin.name}/${subdirectory.path}/${file}:${info.size}:${Math.round(info.mtimeMs)}`,
|
||||
);
|
||||
const absolute = path.join(root, file);
|
||||
const info = statSync(absolute);
|
||||
// prepared 产物由准备步骤无条件复制,mtime 会变;用内容哈希保证幂等判断稳定。
|
||||
const stamp =
|
||||
subdirectory.origin === 'prepared'
|
||||
? `sha256:${sha256File(absolute)}`
|
||||
: `${info.size}:${Math.round(info.mtimeMs)}`;
|
||||
lines.push(`${plugin.name}/${subdirectory.path}/${file}:${stamp}`);
|
||||
}
|
||||
}
|
||||
const manifest = path.join(
|
||||
@@ -701,10 +709,22 @@ function pluginTreeMatches(
|
||||
return false;
|
||||
}
|
||||
for (const subdirectory of declaration.plugins.subdirectories) {
|
||||
if (!subdirectoryEnabled(subdirectory, target, features)) {
|
||||
if (
|
||||
subdirectoryAppliesToPlugin(subdirectory, plugin.name) ||
|
||||
subdirectoryEnabled(subdirectory, target, features)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const sourceRoot = path.join(plugin.root, subdirectory.path);
|
||||
if (subdirectory.origin !== 'source') {
|
||||
if (
|
||||
existsSync(sourceRoot) &&
|
||||
!existsSync(path.join(pluginDestination, subdirectory.path))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
for (const relative of walkFiles(declaration, sourceRoot)) {
|
||||
const source = path.join(sourceRoot, relative);
|
||||
const staged = path.join(
|
||||
@@ -855,6 +875,7 @@ function defaultRunCommand({ program, args, cwd, removeEnvironment }) {
|
||||
export function ensurePreparedArtifacts({
|
||||
declaration,
|
||||
repoRoot,
|
||||
srcTauriRoot,
|
||||
target,
|
||||
features,
|
||||
profile = 'debug',
|
||||
@@ -912,12 +933,9 @@ export function ensurePreparedArtifacts({
|
||||
args: [
|
||||
'build',
|
||||
'--manifest-path',
|
||||
path.join(
|
||||
repoRoot,
|
||||
'apps/ai-game-creator-shell/src-tauri/Cargo.toml',
|
||||
),
|
||||
'-p',
|
||||
path.basename(step.packageDirectory),
|
||||
path.join(repoRoot, step.packageDirectory, 'Cargo.toml'),
|
||||
'--target-dir',
|
||||
path.join(srcTauriRoot, 'target'),
|
||||
'--target',
|
||||
target,
|
||||
...(profile === 'release' ? ['--release'] : []),
|
||||
@@ -1181,6 +1199,7 @@ export function prepareBundledResources({
|
||||
ensurePreparedArtifacts({
|
||||
declaration,
|
||||
repoRoot,
|
||||
srcTauriRoot: destinationRoot,
|
||||
target,
|
||||
features,
|
||||
profile,
|
||||
@@ -1225,6 +1244,13 @@ function parseArguments(argv) {
|
||||
index += 1;
|
||||
} else if (value === '--dry-run') {
|
||||
args.dryRun = true;
|
||||
} else if (value === '--features') {
|
||||
args.features = new Set(
|
||||
String(argv[index + 1] ?? '')
|
||||
.split(',')
|
||||
.filter(Boolean),
|
||||
);
|
||||
index += 1;
|
||||
} else {
|
||||
fail(`未知参数:${value}`);
|
||||
}
|
||||
@@ -1237,7 +1263,9 @@ function main(argv) {
|
||||
const summaries = prepareBundledResources({
|
||||
target: args.target ?? resolveHostTarget(),
|
||||
destinationRoot: args.destinationRoot ?? SRC_TAURI_DIR,
|
||||
...(args.features ? { features: args.features } : {}),
|
||||
dryRun: args.dryRun,
|
||||
log: (line) => console.log(`[agc-resources] ${line}`),
|
||||
});
|
||||
for (const summary of summaries) {
|
||||
console.log(`[agc-resources] ${summary}`);
|
||||
|
||||
@@ -148,7 +148,7 @@ function buildFixture({ targets = [WINDOWS_TARGET], plugins = true } = {}) {
|
||||
WINDOWS_TARGET,
|
||||
'debug',
|
||||
'deps',
|
||||
'cocos-editor-bridge.dll',
|
||||
'cocos_editor_bridge.dll',
|
||||
);
|
||||
fs.mkdirSync(path.dirname(cocosDll), { recursive: true });
|
||||
fs.writeFileSync(cocosDll, 'cocos bridge payload\n');
|
||||
@@ -493,7 +493,10 @@ test('declaration drives source lookup and staging units', () => {
|
||||
app: fixture.appRoot,
|
||||
repo: fixture.repoRoot,
|
||||
});
|
||||
assert.match(source, /codex-win32-x64\/vendor\/x86_64-pc-windows-msvc$/);
|
||||
assert.match(
|
||||
source,
|
||||
/codex-win32-x64[\\/]vendor[\\/]x86_64-pc-windows-msvc$/u,
|
||||
);
|
||||
assert.equal(pluginDirectories(declaration, fixture.repoRoot).length, 1);
|
||||
} finally {
|
||||
fixture.cleanup();
|
||||
|
||||
@@ -99,6 +99,7 @@ fn validate_prepared_payloads(manifest_dir: &std::path::Path, target: &str) {
|
||||
{
|
||||
for subdirectory in declared.subdirectories {
|
||||
if !package_layout::subdirectory_is_prepared(subdirectory)
|
||||
|| !package_layout::subdirectory_applies_to_plugin(subdirectory, &plugin.name)
|
||||
|| !package_layout::subdirectory_enabled(
|
||||
subdirectory,
|
||||
target,
|
||||
|
||||
@@ -77,6 +77,7 @@ pub const PLUGINS: Plugins = Plugins {
|
||||
subdirectories: &[
|
||||
Subdirectory {
|
||||
path: "src",
|
||||
plugin: "",
|
||||
origin: "source",
|
||||
target_contains: &[],
|
||||
targets: &[],
|
||||
@@ -84,6 +85,7 @@ Subdirectory {
|
||||
},
|
||||
Subdirectory {
|
||||
path: "panels",
|
||||
plugin: "",
|
||||
origin: "source",
|
||||
target_contains: &[],
|
||||
targets: &[],
|
||||
@@ -91,6 +93,7 @@ Subdirectory {
|
||||
},
|
||||
Subdirectory {
|
||||
path: "skills",
|
||||
plugin: "",
|
||||
origin: "source",
|
||||
target_contains: &[],
|
||||
targets: &[],
|
||||
@@ -98,13 +101,15 @@ Subdirectory {
|
||||
},
|
||||
Subdirectory {
|
||||
path: "native/payload",
|
||||
origin: "source",
|
||||
plugin: "agc-cocos-editor",
|
||||
origin: "prepared",
|
||||
target_contains: &["windows"],
|
||||
targets: &[],
|
||||
features: &[],
|
||||
},
|
||||
Subdirectory {
|
||||
path: "dotnet/publish/win-x64",
|
||||
plugin: "agc-unity-editor",
|
||||
origin: "prepared",
|
||||
target_contains: &[],
|
||||
targets: &["x86_64-pc-windows-msvc"],
|
||||
|
||||
@@ -15,19 +15,27 @@
|
||||
"manifestFileName": "manifest.json",
|
||||
"packageMetadataFileName": "codex-package.json",
|
||||
"noticeFileName": "NOTICE.md",
|
||||
"sourceRoots": ["app", "repo"],
|
||||
"sourceRoots": [
|
||||
"app",
|
||||
"repo"
|
||||
],
|
||||
"sourceRelativePaths": [
|
||||
"node_modules/@openai/codex-<platform>/vendor/<target>",
|
||||
"node_modules/@openai/codex/node_modules/@openai/codex-<platform>/vendor/<target>"
|
||||
],
|
||||
"noticeSources": [
|
||||
{
|
||||
"targets": ["aarch64-apple-darwin", "x86_64-apple-darwin"],
|
||||
"targets": [
|
||||
"aarch64-apple-darwin",
|
||||
"x86_64-apple-darwin"
|
||||
],
|
||||
"source": "resources/codex/【声明】Mac内置Codex组件-2026-09-18.md",
|
||||
"preserve": false
|
||||
},
|
||||
{
|
||||
"targets": ["x86_64-pc-windows-msvc"],
|
||||
"targets": [
|
||||
"x86_64-pc-windows-msvc"
|
||||
],
|
||||
"source": "resources/codex/win-x64/NOTICE.md",
|
||||
"preserve": true
|
||||
}
|
||||
@@ -36,7 +44,10 @@
|
||||
{
|
||||
"name": "mac-native",
|
||||
"directory": "mac-native",
|
||||
"targets": ["aarch64-apple-darwin", "x86_64-apple-darwin"]
|
||||
"targets": [
|
||||
"aarch64-apple-darwin",
|
||||
"x86_64-apple-darwin"
|
||||
]
|
||||
}
|
||||
],
|
||||
"targets": [
|
||||
@@ -86,18 +97,43 @@
|
||||
"sourceDirectory": "plugins",
|
||||
"destinationDirectory": "resources/plugins",
|
||||
"manifestFileName": "plugin.json",
|
||||
"targetContainsAny": ["windows", "apple-darwin"],
|
||||
"targetContainsAny": [
|
||||
"windows",
|
||||
"apple-darwin"
|
||||
],
|
||||
"subdirectories": [
|
||||
{ "path": "src", "origin": "source" },
|
||||
{ "path": "panels", "origin": "source" },
|
||||
{ "path": "skills", "origin": "source" },
|
||||
{ "path": "native/payload", "origin": "source", "targetContains": ["windows"] },
|
||||
{
|
||||
"path": "src",
|
||||
"origin": "source"
|
||||
},
|
||||
{
|
||||
"path": "panels",
|
||||
"origin": "source"
|
||||
},
|
||||
{
|
||||
"path": "skills",
|
||||
"origin": "source"
|
||||
},
|
||||
{
|
||||
"path": "native/payload",
|
||||
"origin": "prepared",
|
||||
"targetContains": [
|
||||
"windows"
|
||||
],
|
||||
"plugin": "agc-cocos-editor",
|
||||
"prepare": "cocos-bridge-build"
|
||||
},
|
||||
{
|
||||
"path": "dotnet/publish/win-x64",
|
||||
"origin": "prepared",
|
||||
"prepare": "unity-helper-publish",
|
||||
"targets": ["x86_64-pc-windows-msvc"],
|
||||
"features": ["unity-editor-execute"]
|
||||
"targets": [
|
||||
"x86_64-pc-windows-msvc"
|
||||
],
|
||||
"features": [
|
||||
"unity-editor-execute"
|
||||
],
|
||||
"plugin": "agc-unity-editor"
|
||||
}
|
||||
],
|
||||
"libraryStaging": [
|
||||
@@ -105,8 +141,12 @@
|
||||
"plugin": "agc-godot-editor",
|
||||
"sourceSubdirectory": "native/gdextension",
|
||||
"prepare": "godot-extension-build",
|
||||
"targets": ["x86_64-pc-windows-msvc"],
|
||||
"features": ["godot-editor-execute"],
|
||||
"targets": [
|
||||
"x86_64-pc-windows-msvc"
|
||||
],
|
||||
"features": [
|
||||
"godot-editor-execute"
|
||||
],
|
||||
"layout": "godot-bundle",
|
||||
"files": [
|
||||
"bin/win-x64/agc_godot_editor.dll",
|
||||
@@ -120,10 +160,15 @@
|
||||
{
|
||||
"plugin": "agc-cocos-editor",
|
||||
"prepare": "cocos-bridge-build",
|
||||
"sourceFileName": "cocos-editor-bridge.dll",
|
||||
"sourceFileName": "cocos_editor_bridge.dll",
|
||||
"destinationSubdirectory": "native/payload",
|
||||
"targets": ["x86_64-pc-windows-msvc"],
|
||||
"features": ["cocos-editor-injection"]
|
||||
"targets": [
|
||||
"x86_64-pc-windows-msvc"
|
||||
],
|
||||
"features": [
|
||||
"cocos-editor-injection"
|
||||
],
|
||||
"destinationFileName": "cocos-editor-bridge.dll"
|
||||
}
|
||||
],
|
||||
"prepareSteps": [
|
||||
@@ -133,8 +178,15 @@
|
||||
"workingDirectory": "plugins/agc-unity-editor/dotnet",
|
||||
"scriptFileName": "build.ps1",
|
||||
"fingerprint": {
|
||||
"roots": ["."],
|
||||
"excludeDirectoryNames": ["bin", "obj", "publish", "native-build"],
|
||||
"roots": [
|
||||
"."
|
||||
],
|
||||
"excludeDirectoryNames": [
|
||||
"bin",
|
||||
"obj",
|
||||
"publish",
|
||||
"native-build"
|
||||
],
|
||||
"stampRelativePath": "publish/win-x64/.agc-source.sha256"
|
||||
},
|
||||
"requiredOutputs": [
|
||||
@@ -155,25 +207,49 @@
|
||||
"kind": "powershell",
|
||||
"workingDirectory": "plugins/agc-godot-editor/native/gdextension",
|
||||
"scriptFileName": "build.ps1",
|
||||
"removeEnvironment": ["PSModulePath"],
|
||||
"removeEnvironment": [
|
||||
"PSModulePath"
|
||||
],
|
||||
"requiredOutputs": [
|
||||
"plugins/agc-godot-editor/native/gdextension/bin/win-x64/agc_godot_editor.dll",
|
||||
"plugins/agc-godot-editor/native/gdextension/bin/win-x64/metadata.json",
|
||||
"plugins/agc-godot-editor/native/gdextension/vendor/LICENSE.txt",
|
||||
"plugins/agc-godot-editor/native/gdextension/vendor/provenance.json"
|
||||
]
|
||||
],
|
||||
"fingerprint": {
|
||||
"roots": [
|
||||
"."
|
||||
],
|
||||
"excludeDirectoryNames": [
|
||||
"bin",
|
||||
".build",
|
||||
"native-build"
|
||||
],
|
||||
"stampRelativePath": "bin/win-x64/.agc-source.sha256"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cocos-bridge-build",
|
||||
"kind": "cargo",
|
||||
"packageDirectory": "plugins/agc-cocos-editor/native/cocos-editor-bridge",
|
||||
"features": ["windows-injection"],
|
||||
"features": [
|
||||
"windows-injection"
|
||||
],
|
||||
"requiredOutputs": []
|
||||
}
|
||||
],
|
||||
"skipDirectoryNames": ["target", "node_modules"],
|
||||
"skipDirectoryNamePrefixes": ["."],
|
||||
"skipFileNamePrefixes": ["."],
|
||||
"skipFileNameFragments": [".test."]
|
||||
"skipDirectoryNames": [
|
||||
"target",
|
||||
"node_modules"
|
||||
],
|
||||
"skipDirectoryNamePrefixes": [
|
||||
"."
|
||||
],
|
||||
"skipFileNamePrefixes": [
|
||||
"."
|
||||
],
|
||||
"skipFileNameFragments": [
|
||||
".test."
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,8 @@ pub struct PackageMetadata {
|
||||
#[derive(Debug)]
|
||||
pub struct Subdirectory {
|
||||
pub path: &'static str,
|
||||
/// 该子目录归属的插件名;空串表示适用于所有插件。
|
||||
pub plugin: &'static str,
|
||||
/// `source`:由声明与仓库源码就能生成(准备步骤负责);`build`:构建期工具链产出(构建脚本负责)。
|
||||
pub origin: &'static str,
|
||||
pub target_contains: &'static [&'static str],
|
||||
@@ -171,6 +173,11 @@ pub fn plugin_staging_applies(target: &str) -> bool {
|
||||
.any(|needle| target.contains(needle))
|
||||
}
|
||||
|
||||
/// 子目录是否归属该插件(空串表示通用)。
|
||||
pub fn subdirectory_applies_to_plugin(subdirectory: &Subdirectory, plugin_name: &str) -> bool {
|
||||
subdirectory.plugin.is_empty() || subdirectory.plugin == plugin_name
|
||||
}
|
||||
|
||||
/// 子目录在当前目标与已启用 feature 下是否随包。
|
||||
pub fn subdirectory_enabled(
|
||||
subdirectory: &Subdirectory,
|
||||
|
||||
@@ -156,7 +156,7 @@ build script 只写 `OUT_DIR`/`target`;随包资源是它的输入。凡需要
|
||||
|
||||
形态选择**混合**:生成归 Node(复用 `stage-node-runtime.mjs` / `prepare-macos-codex.mjs` 的下载、`integrity`、临时目录 + rename 原子替换范式),声明与校验归 Rust(复用 `codex_bundle.rs` / `godot_bundle.rs` 的布局与摘要校验,运行期模块不改公开接口)。理由:Rust 侧没有下载与 lockfile 解析能力(`[build-dependencies]` 无 HTTP 客户端),Node 侧没有 staging 能力;任选单一语言都要迁移另一侧既有资产。Rust 侧刻意不解析 JSON:声明经生成器变成编译期常量,避免运行期解析与生命周期妥协,也让 `&'static` 布局表与现有调用点保持不变。
|
||||
|
||||
准备步骤的验证入口:`AGC_SKIP_RESOURCE_STAGING=1 cargo build/check …` 只跑只读校验、跳过写入分支,用于在既有产物上单独验证校验路径。
|
||||
构建脚本自 M3 起只做只读校验(写入分支与 `AGC_SKIP_RESOURCE_STAGING` 开关一并删除),`cargo build/check` 本身就是对既有产物的校验。
|
||||
|
||||
### 4.9 M2/M3 实况:构建期写入边界
|
||||
|
||||
|
||||
Reference in New Issue
Block a user