合并 origin/master 最新变更
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m50s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 2m8s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m25s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 3m38s
Project CI / Frontend tests (pull_request) Successful in 2m51s
Project CI / Backend tests (pull_request) Successful in 4m31s
Project CI / Repository checks (pull_request) Successful in 2m52s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m46s
Project CI / Native shell tests (pull_request) Successful in 6m16s

合入主分支 73090437f,保留现役认证、发布、线程队列与 UI 工作流
协调桌面接线拆分与共享测试编译边界,迁移取消回合断言
通过配置、壳契约、编码及文档检查;Rust 检查受本地 Claude SDK 随包依赖缺失阻塞
This commit is contained in:
2026-10-01 06:43:54 +00:00
737 changed files with 68024 additions and 32955 deletions
+43 -124
View File
@@ -10,9 +10,6 @@ const nativeShellPlanPath =
'docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md';
const hostBridgeProtocolDocPath =
'docs/【前端架构】宿主壳能力统一协议-2026-06-17.md';
const developmentWorkflowDocPath =
'docs/project-memory/shared-memory/development-workflow.md';
const decisionLogDocPath = 'docs/project-memory/shared-memory/decision-log.md';
const rootPackageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
const mobileShellConfigCheckSource = fs.readFileSync(
'apps/mobile-shell/scripts/check-config.mjs',
@@ -2567,21 +2564,47 @@ function assertAiGameCreatorShellUserDevBoundary() {
!aiGameCreatorClientHttpSource.includes(
"'https://dev.genarrative.world'",
) ||
!aiGameCreatorClientHttpSource.includes("transport: 'tauri-http'") ||
!aiGameCreatorClientHttpSource.includes('target.origin !==')
!aiGameCreatorClientHttpSource.includes(
"'https://www.genarrative.world'",
) ||
/\bfetch\s*\(/u.test(aiGameCreatorClientHttpSource) ||
aiGameCreatorClientHttpSource.includes("transport: 'tauri-http'")
) {
throw new Error(
'AI game creator release dev API transport boundary drifted',
'AI game creator clientHttp must only resolve the server selection, not carry a transport',
);
}
// 渲染层是离线前端:平台接口、OSS 直传、Provider 与更新清单的网络 IO 全部在 Rust 侧
// (`src-tauri/src/account_api.rs` / `auth_session.rs` / `platform_asset_upload.rs` /
// `game_distribution_publish.rs` 等 reqwest facade)。这里对整棵渲染源码加网络原语与
// Tauri HTTP guest 的负向门禁,避免以后再长出第二条传输路径。
const rendererNetworkPrimitivePatterns = [
['fetch(', /\bfetch\s*\(/u],
['XMLHttpRequest', /\bXMLHttpRequest\b/u],
['EventSource', /\bEventSource\b/u],
['sendBeacon(', /\bsendBeacon\s*\(/u],
['new WebSocket(', /\bnew\s+WebSocket\s*\(/u],
['@tauri-apps/plugin-http', /@tauri-apps\/plugin-http/u],
];
for (const rendererFilePath of collectFiles(
'apps/ai-game-creator-shell/src',
(entryPath) => /\.(ts|tsx)$/u.test(entryPath),
)) {
const rendererSource = fs.readFileSync(rendererFilePath, 'utf8');
for (const [label, pattern] of rendererNetworkPrimitivePatterns) {
if (pattern.test(rendererSource)) {
throw new Error(
`AI game creator renderer must stay offline, but ${normalizeScannedFilePath(rendererFilePath)} contains ${label}`,
);
}
}
}
if (
!aiGameCreatorCargoManifestSource.includes(
'tauri-plugin-http = { version = "2.5.9", default-features = false, features = ["charset", "cookies", "http2", "rustls-tls"] }',
) ||
!aiGameCreatorShellTauriSource.includes('tauri_plugin_http::init()')
aiGameCreatorCargoManifestSource.includes('tauri-plugin-http') ||
aiGameCreatorShellTauriSource.includes('tauri_plugin_http::init()')
) {
throw new Error(
'AI game creator release must register the native HTTP plugin without the OS automatic system-proxy feature',
'AI game creator shell must not register a renderer HTTP plugin; native IO goes through the Rust reqwest facades',
);
}
if (
@@ -2591,27 +2614,18 @@ function assertAiGameCreatorShellUserDevBoundary() {
'AI game creator monorepo build must dedupe React runtime packages',
);
}
const httpPermission = (aiGameCreatorMainCapability.permissions ?? []).find(
(permission) =>
typeof permission === 'object' &&
permission?.identifier === 'http:default',
);
if (
!httpPermission ||
JSON.stringify(httpPermission.allow ?? []) !==
JSON.stringify([
{ url: 'https://dev.genarrative.world/api/*' },
{ url: 'https://www.genarrative.world/api/*' },
{ url: 'https://*/api/*' },
{ url: 'http://localhost:*/*' },
{ url: 'http://127.0.0.1:*/*' },
{ url: 'https://*.aliyuncs.com/*' },
])
(aiGameCreatorMainCapability.permissions ?? []).some(
(permission) =>
permission === 'http:default' ||
(typeof permission === 'object' &&
permission?.identifier === 'http:default'),
)
) {
throw new Error(
// 更新清单与安装包下载由 tauri-plugin-updater 在原生侧完成;
// 封面与截图仍通过 webview 的 http 插件向凭证指定的 OSS 地址直传。
'AI game creator native HTTP scope must match the release, dev, custom HTTPS, loopback API, and OSS media upload boundary',
// 客户端窗口只保留剪贴板、图片、资源关闭、opener、updater 与原生对话框权限;
// 更新清单下载由 tauri-plugin-updater 在原生侧完成,素材直传也不再经渲染层。
'AI game creator client capability must not grant the renderer HTTP permission',
);
}
@@ -3760,107 +3774,12 @@ function extractDocumentMethodTable(source) {
);
}
function assertNativeShellScaffoldScanWording(source, label) {
if (
source.includes('三端生产壳临时替身词扫描') ||
source.includes('三端壳生产源码') ||
source.includes('壳生产源码禁替身') ||
!source.includes('H5 HostBridge 真实调用链的临时替身词扫描')
) {
throw new Error(
`${label} must document production scaffold scanning for the H5 HostBridge call chain`,
);
}
}
function assertNativeShellCapabilityPlan() {
const planSource = fs.readFileSync(nativeShellPlanPath, 'utf8');
const hostBridgeProtocolDocSource = fs.readFileSync(
hostBridgeProtocolDocPath,
'utf8',
);
const developmentWorkflowDocSource = fs.readFileSync(
developmentWorkflowDocPath,
'utf8',
);
const decisionLogDocSource = fs.readFileSync(decisionLogDocPath, 'utf8');
if (
planSource.includes('permissions` 必须只包含 `core:default`') ||
planSource.includes(
'permissions=["core:default","allow-host-bridge-request"]',
)
) {
throw new Error(
'native shell plan must not document core:default as a desktop capability permission',
);
}
if (
!planSource.includes('主窗口 capability 只授予 `allow-host-bridge-request`')
) {
throw new Error(
'native shell plan must document the minimal desktop capability permission',
);
}
for (const staleShareWording of [
'深链、系统分享、即时本地通知',
'重复执行支付、登录、系统分享、文件导入导出',
'发布分享弹窗只有声明 `share.open` 时才显示“系统分享”',
'发布分享弹窗只有在宿主声明 `share.open` 时才提供“系统分享”动作',
]) {
if (
planSource.includes(staleShareWording) ||
hostBridgeProtocolDocSource.includes(staleShareWording) ||
decisionLogDocSource.includes(staleShareWording)
) {
throw new Error(
`native shell docs must describe share.open as a host-specific controlled share action: ${staleShareWording}`,
);
}
}
for (const requiredShareWording of [
'深链、受控分享动作、即时本地通知',
'重复 `id` 不得重复执行支付、登录、受控分享动作、文件导入导出',
'发布分享弹窗在 Expo 移动壳声明 `share.open` 时提供“系统分享”动作',
'发布分享弹窗在 Tauri 桌面壳中展示“复制分享文案 / 已复制 / 复制失败”',
'并按 `hostShell` 区分 Expo 系统分享面板和 Tauri 剪贴板复制表达',
]) {
if (
!planSource.includes(requiredShareWording) &&
!hostBridgeProtocolDocSource.includes(requiredShareWording) &&
!decisionLogDocSource.includes(requiredShareWording)
) {
throw new Error(
`native shell docs missing host-specific share.open wording: ${requiredShareWording}`,
);
}
}
for (const staleMobilePermissionText of [
'Android `permissions` 不手写显式权限',
'最终 Expo public config 只允许扫码能力由 `expo-camera` plugin 带入 `android.permission.CAMERA`',
'移动拍摄不请求麦克风权限',
]) {
if (
planSource.includes(staleMobilePermissionText) ||
hostBridgeProtocolDocSource.includes(staleMobilePermissionText)
) {
throw new Error(
`native shell docs must not keep stale mobile permission wording: ${staleMobilePermissionText}`,
);
}
}
assertNativeShellScaffoldScanWording(planSource, 'native shell plan');
assertNativeShellScaffoldScanWording(
hostBridgeProtocolDocSource,
'HostBridge protocol document',
);
assertNativeShellScaffoldScanWording(
developmentWorkflowDocSource,
'development workflow document',
);
assertNativeShellScaffoldScanWording(
decisionLogDocSource,
'decision log document',
);
assertShellLayerLayoutDocumented(planSource, 'native shell plan');
assertShellLayerLayoutDocumented(
hostBridgeProtocolDocSource,