From b42a83260cef10eb47e5b93516d50f87de697782 Mon Sep 17 00:00:00 2001 From: menghao Date: Wed, 5 Aug 2026 14:02:17 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=B7=A8=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E5=8E=9F=E7=94=9F=E5=A3=B3=E4=BA=A7=E7=89=A9=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 统一使用规范化临时目录规避 macOS 符号链接祖先误判 放宽生成恢复 HTTP 夹具的有界读取时间 补全 Mach-O 正反字节序及 fat 文件头白名单 同步桌面壳架构说明与项目排障记忆 --- .../src/agent/generation/canvas_generation.rs | 2 +- .../runtime_actions/provider_request_builders.rs | 5 +++-- .../src/agent/runtime_driver/pending_recovery.rs | 8 ++++---- .../autonomous_completion_contract_tests.rs | 4 ++-- apps/desktop-shell/scripts/check-config.mjs | 16 ++++++++++++++++ .../scripts/stage-release-binary.mjs | 8 ++++++-- docs/project-memory/shared-memory/pitfalls.md | 14 ++++++++++++++ ...】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md | 2 +- scripts/check-native-shells.mjs | 8 ++++++-- 9 files changed, 53 insertions(+), 14 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs index 71a920d9a..5c9613bdd 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs @@ -5708,7 +5708,7 @@ mod canvas_generation_tests { fn read_test_http_request(stream: &mut std::net::TcpStream) -> String { stream - .set_read_timeout(Some(Duration::from_secs(2))) + .set_read_timeout(Some(Duration::from_secs(10))) .expect("set request read timeout"); let mut bytes = Vec::new(); let mut buffer = [0_u8; 4096]; diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/provider_request_builders.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/provider_request_builders.rs index e8e9415a8..756cda481 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/provider_request_builders.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/provider_request_builders.rs @@ -463,7 +463,8 @@ mod tests { root_source: &str, suffix: &str, ) -> String { - let temporary = tempfile::tempdir().expect("temporary project root"); + let temporary = + crate::tests::canonical_test_tempdir(&format!("provider-role-overlay-{suffix}-")); let root = temporary.path().join("project"); init_local_game_project_at(&root, &format!("overlay-{suffix}"), "role overlay test") .expect("project init"); @@ -806,7 +807,7 @@ mod tests { #[test] fn planning_request_advertises_only_native_mcp_functions() { - let directory = tempfile::tempdir().expect("temp project directory"); + let directory = crate::tests::canonical_test_tempdir("native-mcp-prompt-"); let root = directory.path().join("project"); init_local_game_project_at(&root, "project-mcp", "MCP 原生函数说明测试") .expect("project init"); diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/pending_recovery.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/pending_recovery.rs index 246cc05a1..1bc5adaa9 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/pending_recovery.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/pending_recovery.rs @@ -1332,7 +1332,7 @@ mod pending_recovery_tests { #[test] fn observed_unknown_canvas_generation_returns_to_same_approved_action() { - let temporary = tempfile::tempdir().expect("create prepared pending project"); + let temporary = crate::tests::canonical_test_tempdir("prepared-pending-"); let root = temporary.path(); init_local_game_project_at(root, "prepared-pending", "原俄罗斯方块项目") .expect("init prepared pending project"); @@ -1413,7 +1413,7 @@ mod pending_recovery_tests { #[test] fn legacy_executing_canvas_generation_returns_to_same_approved_action() { - let temporary = tempfile::tempdir().expect("create executing prepared project"); + let temporary = crate::tests::canonical_test_tempdir("executing-prepared-"); let root = temporary.path(); init_local_game_project_at(root, "executing-prepared", "旧版俄罗斯方块项目") .expect("init executing prepared project"); @@ -1490,7 +1490,7 @@ mod pending_recovery_tests { #[test] fn observed_postprocessing_failure_resumes_from_accepted_generation() { - let temporary = tempfile::tempdir().expect("create accepted recovery project"); + let temporary = crate::tests::canonical_test_tempdir("accepted-recovery-"); let root = temporary.path(); init_local_game_project_at(root, "accepted-recovery", "俄罗斯方块素材后处理恢复") .expect("init accepted recovery project"); @@ -1560,7 +1560,7 @@ mod pending_recovery_tests { #[test] fn canvas_reconciliation_keeps_the_context_plan_step_active() { - let temporary = tempfile::tempdir().expect("create reconciliation context project"); + let temporary = crate::tests::canonical_test_tempdir("reconciliation-context-"); let root = temporary.path(); init_local_game_project_at(root, "reconciliation-context", "俄罗斯方块恢复上下文") .expect("init reconciliation context project"); diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs index 2cd3b3af9..700ad713a 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs @@ -22,7 +22,7 @@ fn autonomous_fixture_with_source( AgentRuntimeState, AgentRuntimeAutonomousCompletionContract, ) { - let temporary = tempfile::tempdir().expect("create autonomous fixture root"); + let temporary = crate::tests::canonical_test_tempdir("autonomous-fixture-"); let root = temporary.path().join("project"); init_local_game_project_at(&root, "autonomous-project", task).expect("init project"); let session_id = resolve_agent_conversation_session_id_at( @@ -223,7 +223,7 @@ fn autonomous_fixture_with_setup( AgentRuntimeState, AgentRuntimeAutonomousCompletionContract, ) { - let temporary = tempfile::tempdir().expect("create autonomous fixture root"); + let temporary = crate::tests::canonical_test_tempdir("autonomous-setup-fixture-"); let root = temporary.path().join("project"); init_local_game_project_at(&root, "autonomous-project", task).expect("init project"); setup(&root); diff --git a/apps/desktop-shell/scripts/check-config.mjs b/apps/desktop-shell/scripts/check-config.mjs index 26ca9cc53..0765ae9cc 100644 --- a/apps/desktop-shell/scripts/check-config.mjs +++ b/apps/desktop-shell/scripts/check-config.mjs @@ -733,7 +733,14 @@ for (const snippet of [ 'header[3] === 0x46', '(stat.mode & 0o111) === 0', 'header.readUInt32BE(0)', + 'machMagic === 0xcafebabe', + 'machMagic === 0xbebafeca', + 'machMagic === 0xcafebabf', + 'machMagic === 0xbfbafeca', + 'machMagic === 0xfeedface', + 'machMagic === 0xcefaedfe', 'machMagic === 0xfeedfacf', + 'machMagic === 0xcffaedfe', 'header[0] !== 0x4d', 'header[1] !== 0x5a', "console.log('[check:native-shells] desktop-release-binary-artifact')", @@ -755,6 +762,15 @@ for (const snippet of [ "'desktop'", 'fs.copyFileSync(sourcePath, stagedPath)', 'fs.chmodSync(stagedPath, sourceMode & 0o777)', + 'header.readUInt32BE(0)', + 'machMagic === 0xcafebabe', + 'machMagic === 0xbebafeca', + 'machMagic === 0xcafebabf', + 'machMagic === 0xbfbafeca', + 'machMagic === 0xfeedface', + 'machMagic === 0xcefaedfe', + 'machMagic === 0xfeedfacf', + 'machMagic === 0xcffaedfe', "console.log(`[desktop-shell:stage-release-binary] ${stagedPath}`)", ]) { if (!stageReleaseBinarySource.includes(snippet)) { diff --git a/apps/desktop-shell/scripts/stage-release-binary.mjs b/apps/desktop-shell/scripts/stage-release-binary.mjs index 13ce5bac8..44cbc143a 100644 --- a/apps/desktop-shell/scripts/stage-release-binary.mjs +++ b/apps/desktop-shell/scripts/stage-release-binary.mjs @@ -49,9 +49,13 @@ function assertExecutable(filePath, label) { const machMagic = header.readUInt32BE(0); const isMachO = machMagic === 0xcafebabe || - machMagic === 0xcafed00d || + machMagic === 0xbebafeca || + machMagic === 0xcafebabf || + machMagic === 0xbfbafeca || machMagic === 0xfeedface || - machMagic === 0xfeedfacf; + machMagic === 0xcefaedfe || + machMagic === 0xfeedfacf || + machMagic === 0xcffaedfe; if (!isMachO || (stat.mode & 0o111) === 0) { throw new Error(`${label} must be an executable Mach-O file`); } diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index a0afa7e0c..6fc252041 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -4144,3 +4144,17 @@ - 风险:Provider 工具约束不是本地安全边界;特别是 `writes + readOnlyHint=true` 自动放行的工具,schema 外字段可能改变外部副作用而不进入预期确认路径。 - 处理:使用完整 JSON Schema validator 校验原始 catalog schema,不手写 required/type 子集;native parser、fingerprint enrichment 与实际 MCP 调用边界复用同一校验器。enrichment 错误必须映射回 classified `arguments-schema` repair,不能以普通字符串直接终止 run;执行点重验用于阻断升级前已经落盘的 schema 外 pending。关闭网络和文件 `$ref` 解析,schema 无法安全编译时不广告或不执行。`serde` 类型错误会包含实际字符串值,catalog miss 也会包含模型提交的 server/tool,因此这两类错误同样只能返回稳定类别,不能拼接原始错误、参数值或 schema 内容。 - 验证:覆盖 required、additionalProperties、type、enum、本地 `$defs/$ref`、HTTP/file 外部引用、无效 schema、错误脱敏,证明 legacy wrapper 在注入 fingerprint 前进入 repair,并证明带旧有效 fingerprint 的历史 pending 在实际调用前仍被 schema 拒绝。 + +## macOS 安全路径测试必须使用规范化临时目录(2026-08-05) + +- 现象:调用仓库上下文、Runtime context bundle 或 pending recovery 的 Rust 测试在 macOS 报“Repository root and its ancestors must not be symbolic links”,Linux CI 却可能通过;本地 HTTP 恢复夹具在完整串行测试中还可能偶发 `WouldBlock`。 +- 原因:`tempfile::tempdir()` 默认返回 `/var/folders/...`,而 macOS 的 `/var` 是指向 `/private/var` 的符号链接,生产安全校验会按设计拒绝该祖先;恢复测试的服务端读超时若仅为 2 秒,也会与完整测试负载下约 2 秒的首次请求形成窄竞态。 +- 处理:凡测试会进入仓库可信路径校验,统一使用 `crate::tests::canonical_test_tempdir(...)`,不得削弱生产符号链接拒绝规则;loopback 夹具保留有界超时,但为完整 CI 负载留足稳定裕量。 +- 验证:在 macOS 上定向运行 provider request、pending recovery、autonomous continuation 与 generation recovery 用例,再运行完整 `npm run check:native-shells`。 + +## Mach-O 文件头校验必须覆盖反字节序魔数(2026-08-05) + +- 现象:macOS arm64 的 Tauri release 已成功构建且 `file` 明确认定为 Mach-O,产物 staging 仍报“must be an executable Mach-O file”。 +- 原因:脚本用 `Buffer.readUInt32BE(0)` 读取文件头,却只比较 `0xfeedfacf` 等正序数值;arm64 常见头字节是 `cf fa ed fe`,读取结果为 `0xcffaedfe`。 +- 处理:文件头白名单同时覆盖 32/64 位与 fat Mach-O 的正序和反字节序合法魔数,并由桌面配置门禁同时反查 staging 脚本和根级产物检查,不能改成只按扩展名或构建退出码判断。 +- 验证:在 macOS 上构建真实 desktop-shell release,运行 `npm run desktop-shell:stage-release-binary`,再由 `npm run check:native-shells` 校验 staged 产物。 diff --git a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md index f15ef8f9f..8fc01536e 100644 --- a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md +++ b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md @@ -524,7 +524,7 @@ GameBridge 禁止: 2026-06-19 追加:桌面壳 macOS 媒体权限说明进入门禁。Tauri 桌面壳仍不新增摄像头或麦克风 HostBridge method,不把系统媒体能力暴露成桌面命令;同源 H5 页面可继续使用浏览器标准 `getUserMedia` 承接儿童动作热身 Demo 的实时摄像头输入和汪汪声浪正式 runtime 的实时麦克风输入。macOS 分发包必须通过 `bundle.macOS.infoPlist="Info.plist"` 合并受控用途说明:`NSCameraUsageDescription` 只描述同源 H5 实时动作输入,`NSMicrophoneUsageDescription` 只描述同源 H5 实时声音玩法。`apps/desktop-shell/scripts/check-config.mjs` 会校验 plist 路径和两条文案,并把 `Info.plist` 纳入生产壳替身词扫描,防止桌面包缺少系统授权说明、把媒体权限扩写成通用采集能力,或在 macOS 分发配置里留下临时替身文本。 -2026-06-18 追加:桌面壳 release 构建烟测进入统一验收。`npm run check:native-shells` 会在 H5 HostBridge、Expo 壳和 Tauri 单测通过后执行 `npm run desktop-shell:build -- --no-bundle`,确认 Tauri release 入口指向共享公开主站、受控命令白名单、图标和 Rust release 编译可以共同产出桌面二进制;构建后 `desktop-shell:stage-release-binary` 会把当前平台二进制复制到根目录 `build/native/desktop/genarrative-desktop-shell` 或 `build/native/desktop/genarrative-desktop-shell.exe`,该目录沿用根 `build/` 的 gitignore,只作为本机或 CI 可收集产物目录。统一验收必须检查 staged 二进制存在、非空且符合当前平台可执行文件头。`apps/desktop-shell/scripts/check-config.mjs` 会反查根级门禁仍保留 release build smoke、staging 步骤、二进制路径、Linux ELF / macOS Mach-O / Windows PE 文件头和可执行位检查,避免桌面产物验收被改成只看命令退出码。该烟测不生成平台安装包,避免把 Linux 本机缺少的系统打包器误判为 HostBridge 回归。 +2026-06-18 追加:桌面壳 release 构建烟测进入统一验收。`npm run check:native-shells` 会在 H5 HostBridge、Expo 壳和 Tauri 单测通过后执行 `npm run desktop-shell:build -- --no-bundle`,确认 Tauri release 入口指向共享公开主站、受控命令白名单、图标和 Rust release 编译可以共同产出桌面二进制;构建后 `desktop-shell:stage-release-binary` 会把当前平台二进制复制到根目录 `build/native/desktop/genarrative-desktop-shell` 或 `build/native/desktop/genarrative-desktop-shell.exe`,该目录沿用根 `build/` 的 gitignore,只作为本机或 CI 可收集产物目录。统一验收必须检查 staged 二进制存在、非空且符合当前平台可执行文件头;macOS 校验同时接受 32/64 位与 fat Mach-O 的大端、反字节序合法魔数,不得把 arm64 常见的 `cf fa ed fe` 文件头误拒绝。`apps/desktop-shell/scripts/check-config.mjs` 会反查根级门禁仍保留 release build smoke、staging 步骤、二进制路径、Linux ELF / macOS Mach-O / Windows PE 文件头和可执行位检查,避免桌面产物验收被改成只看命令退出码。该烟测不生成平台安装包,避免把 Linux 本机缺少的系统打包器误判为 HostBridge 回归。 2026-06-18 追加:移动壳 Expo managed config 烟测进入统一验收。`npm run check:native-shells` 会执行 `npm run mobile-shell:config`,在 `apps/mobile-shell` 目录内调用 `expo config --type public --json`,校验 Expo CLI 实际解析结果中的包名、scheme、深链、ATS / cleartext / backup / 相机与麦克风权限、启动页、adaptive icon、插件配置和 HostBridge 版本没有漂移。`apps/mobile-shell/scripts/check-config.mjs` 会反查根级门禁仍保留 EAS build profile、Expo config 和 Metro export 三个移动分发烟测,避免移动壳验收退回到只看源码类型检查。 diff --git a/scripts/check-native-shells.mjs b/scripts/check-native-shells.mjs index bbfd91361..c72290867 100644 --- a/scripts/check-native-shells.mjs +++ b/scripts/check-native-shells.mjs @@ -4915,9 +4915,13 @@ function assertDesktopReleaseBinaryArtifact() { const machMagic = header.readUInt32BE(0); const isMachO = machMagic === 0xcafebabe || - machMagic === 0xcafed00d || + machMagic === 0xbebafeca || + machMagic === 0xcafebabf || + machMagic === 0xbfbafeca || machMagic === 0xfeedface || - machMagic === 0xfeedfacf; + machMagic === 0xcefaedfe || + machMagic === 0xfeedfacf || + machMagic === 0xcffaedfe; if (!isMachO || (stat.mode & 0o111) === 0) { throw new Error( 'desktop macOS release binary must be an executable Mach-O file',