合并最新master到埋点开发分支
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled

同步macOS单架构发布及渠道测试修复
保留埋点方案与macOS发布两项共享决策记录
This commit is contained in:
2026-09-21 04:55:52 +00:00
12 changed files with 76 additions and 71 deletions
@@ -9,6 +9,7 @@ import { fileURLToPath } from 'node:url';
import {
generateUpdateManifest,
prepareReleaseVersion,
resolveManifestPlatformKeys,
resolveReleaseContext,
resolveReleasePartition,
runTauriBuild,
@@ -20,10 +21,13 @@ import {
} from './verify-updater-signature.mjs';
/**
* AGC macOS 分区(`<channel>-mac`)发布入口:构建 universal 包 → 双架构 smoke → 生成 universal DMG
* AGC macOS 分区(`<channel>-mac`)发布入口:构建 arm64 单架构包 → arm64 隔离 smoke → 生成 arm64 DMG
* → 生成分区清单 latest.json → 用产物内烘焙的公钥验签 → 按 dry-run 决定是否上传 OSS。
*
* 边界:
* - 只出 Apple Siliconarm64)单架构:清单只登记 `darwin-aarch64`。Intel 侧要可用,前提是随包 Node
* 也能按架构各带一份(`stage-node-runtime.mjs` 对 universal 目标失败关闭);在实现之前**不得**
* 把 arm64 产物登记成 `darwin-x86_64`,否则 Intel 客户端会装到跑不起来的包。
* - Apple 签名与公证暂缺:本入口剥离 `APPLE_*` 凭据让 Tauri 跳过 Apple 签名,但**不能传
* `--no-sign`** —— 该标志同时会跳过 updater 的 minisign 签名,产物就没有 `.sig`
* 未签名 + 未公证必须显式记录而非静默通过;
@@ -94,11 +98,14 @@ process.env.AGC_UPDATE_OSS_BASE_URL ||= `https://${bucket}.${endpoint}/agc`;
const dryRun = readReleaseDryRun();
process.env.CARGO_TARGET_DIR = path.join(appRoot, 'src-tauri/target');
const context = resolveReleaseContext(['--target=universal-apple-darwin']);
// 单架构目标:清单侧 `resolveManifestPlatformKeys` 只为它登记 darwin-aarch64。
const macTarget = 'aarch64-apple-darwin';
const context = resolveReleaseContext([`--target=${macTarget}`]);
const partition = resolveReleasePartition(context.channel, context.target);
const version = await prepareReleaseVersion(context);
// 首装包名必须保持 `<产品名>_<版本>_universal.dmg`:清单侧按该后缀唯一匹配本次产物。
const firstInstallName = `${productName}_${version}_universal.dmg`;
// 首装包名必须让清单侧的单架构分支唯一匹配:`<产品名>_<版本>_<架构>.dmg`
// 架构段用 Tauri 的 aarch64 口径(不是 updater 平台键的 arm64 / x86_64)。
const firstInstallName = `${productName}_${version}_aarch64.dmg`;
// 幂等边界:workspace 会保留上一轮产物。先删掉本次将要写出的对象,否则
// 1) hdiutil 会因同名 DMG 已存在直接失败(首次实跑即命中);
@@ -117,7 +124,7 @@ for (const stale of [
}
const args = [
'--target=universal-apple-darwin',
`--target=${macTarget}`,
'--bundles',
'app',
'--ci',
@@ -132,16 +139,13 @@ const command = (binary, argv, options = {}) =>
runTauriBuild(args, context);
const app = path.join(context.bundleRoot, 'macos', appBundleName);
for (const architecture of ['arm64', 'x86_64']) {
command(process.execPath, [
path.join(appRoot, 'scripts/check-macos-bundle.mjs'),
app,
architecture,
'--universal',
]);
}
command(process.execPath, [
path.join(appRoot, 'scripts/check-macos-bundle.mjs'),
app,
'arm64',
]);
// DMG 放在 bundle 根目录下:渠道清单的首装包选择会扫描该目录,命名必须匹配 `_<version>_universal.dmg`。
// DMG 放在 bundle 根目录下:渠道清单的首装包选择会扫描该目录,命名必须匹配 `_<version>_aarch64.dmg`。
const dmgDirectory = path.join(context.bundleRoot, 'macos');
fs.mkdirSync(dmgDirectory, { recursive: true });
const dmg = path.join(dmgDirectory, firstInstallName);
@@ -170,7 +174,7 @@ const release = await generateUpdateManifest(context);
assert.equal(
path.resolve(release.downloadArtifact),
path.resolve(dmg),
'首装包必须锁定本次生成的 universal DMG',
'首装包必须锁定本次生成的 arm64 DMG',
);
// 上传前门禁:用产物里烘焙的公钥复核更新包签名。验不过就停在这里,绝不写 OSS。
@@ -266,8 +270,9 @@ fs.writeFileSync(
firstInstallSha256: dmgHash,
manifest: 'latest.json',
},
smokes: ['arm64', 'x86_64'],
intelSmoke: process.arch === 'arm64' ? 'Rosetta' : 'native',
// 单架构发布:只跑 arm64 隔离 smokeIntel 未支持(清单里没有 darwin-x86_64 键)。
smokes: ['arm64'],
manifestPlatformKeys: resolveManifestPlatformKeys(context.target),
},
null,
2,
@@ -211,13 +211,13 @@ test('packaged renderer receives the same channel as the updater manifest', () =
});
let spawnOptions;
runTauriBuild([], context, {
// 必须 stub:真实 staging 会用宿主平台(如 macOS 的 darwin/arm64)去对默认的
// Windows 目标做一致性校验,在非 Windows 主机上直接失败——本用例只关心渠道注入。
stageRuntime: () => {},
spawn: (_binary, _args, options) => {
spawnOptions = options;
return { status: 0 };
},
// 该用例只关心渠道环境变量;Node 运行时 staging 与宿主平台绑定,
// 真实 staging 会让同一用例在 macOS/Linux 上对 windows target 失败。
stageRuntime: () => {},
});
assert.equal(spawnOptions?.env?.VITE_AGC_PLATFORM_CHANNEL, 'release');
});
@@ -178,8 +178,12 @@ test('macOS release entry and smoke script derive product names from config and
assert.ok(entry.includes('readProductName'), '入口必须从 Tauri 配置读产品名');
assert.ok(!entry.includes('陶泥儿'), 'macOS 发布入口不得写死产品名');
assert.ok(
entry.includes('_${version}_universal.dmg'),
'首装包名必须保留清单侧唯一匹配所需的后缀',
entry.includes("const macTarget = 'aarch64-apple-darwin'"),
'macOS 发布入口必须固定单架构目标',
);
assert.ok(
entry.includes('_${version}_aarch64.dmg'),
'首装包名必须保留清单侧单架构分支唯一匹配所需的后缀(Tauri 口径 aarch64',
);
const smoke = fs.readFileSync(
@@ -59,27 +59,23 @@ export function readInstalledNodeLicense(
return result.content;
}
export function targetRuntime(
target,
{ platform = process.platform, arch = process.arch } = {},
) {
export function targetRuntime(target) {
const targets = {
'x86_64-pc-windows-msvc': ['win32', 'x64'],
'aarch64-apple-darwin': ['darwin', 'arm64'],
'x86_64-apple-darwin': ['darwin', 'x64'],
};
if (target === 'universal-apple-darwin') {
// Tauri 通用包含 arm64+x86_64 两个 slice,而这里的 Node 便携运行时是
// 「构建宿主架构」的单架构发行版:macOS 构建机(当前为 arm64)只能提供
// 自身架构的官方 Node。真正的通用 Node 需要另行下载另一架构发行版并
// 用 lipo 合并,属未完成项;在此之前 non-darwin 宿主必须失败关闭
if (platform !== 'darwin') {
throw new Error(
`Node 运行时不支持${platform}/${arch} 宿主构建通用 macOS 包:${target}`,
);
}
return { platform: 'darwin', arch };
}
// 随包 Node 是**单架构**官方发行版:一份运行时只服务它自己的架构。
// macOS 发布当前固定为 aarch64-apple-darwin 单架构包(Intel 未支持),
// universal 目标没有正确的运行时来源,必须失败关闭——绝不能退化成
// 「按宿主架构暂存一份 arm64」:那样通用包自检(按 process.arch)能过,
// 但 Intel 机器上这份运行时不可执行,用户拿到的是坏包
if (target === 'universal-apple-darwin')
throw new Error(
'Node 运行时不支持 universal-apple-darwin:随包 Node 只有单架构发行版,' +
'通用包需按架构各带一份(另行下载另一架构官方发行版)之后才能构建;' +
'当前 macOS 发布固定为 aarch64-apple-darwin 单架构',
);
const value = targets[target];
if (!value) throw new Error(`Node 运行时不支持发布目标:${target}`);
return { platform: value[0], arch: value[1] };
@@ -281,20 +281,10 @@ test('native target and macOS dynamic dependency policy reject nonportable Node'
platform: 'darwin',
arch: 'arm64',
});
assert.deepEqual(
targetRuntime('universal-apple-darwin', {
platform: 'darwin',
arch: 'arm64',
}),
{ platform: 'darwin', arch: 'arm64' },
);
// universal 必须失败关闭:只带宿主架构那一份运行时,Intel 上不可执行。
assert.throws(
() =>
targetRuntime('universal-apple-darwin', {
platform: 'linux',
arch: 'x64',
}),
/不支持在 linux\/x64 宿主构建通用 macOS 包/u,
() => targetRuntime('universal-apple-darwin'),
/universal-apple-darwin/u,
);
assertPortableMacNode(
'/node:\n\t/usr/lib/libSystem.B.dylib (compatibility version 1)\n',
@@ -13,7 +13,7 @@
## 范围
- 构建期按渠道生成清单:版本按渠道独立递增,清单包含该渠道平台的下载地址与签名;`dev-mac` 的 universal 包按同一地址与签名同时写入 `darwin-aarch64``darwin-x86_64`
- 构建期按渠道生成清单:版本按渠道独立递增,清单包含该渠道平台的下载地址与签名;macOS 自 2026-09-21 起只出 arm64 单架构,`dev-mac` 只登记 `darwin-aarch64`(原 universal 双键口径已作废:随包 Node 只有单架构官方发行版,需先实现双架构运行时才能恢复 Intel,见 `docs/project-memory/shared-memory/decision-log.md`
- 构建期生成更新产物签名,并在缺少签名私钥或私钥不可用时失败关闭。
- 渠道参数与目标平台绑定校验:Windows 目标只能发布 `dev-win`macOS 目标只能发布 `dev-mac`;未显式指定时按目标平台取默认渠道。
- 上传按渠道落位:安装包与签名进版本目录,清单覆盖写渠道路径的 latest 指针。
@@ -6,6 +6,16 @@
- 已确认本期只做本地事件采集和明文 JSONL 持久化,不做加密与上传;一个项目对应一个目标,事件按业务节点采集,5 分钟封存,7 天或 20 MiB 清理。后续上传周期为 15 分钟。
- 当前尚未实施,参数建议及剩余口径见方案第 11 节;文档入库不表示这些建议已获确认或功能已上线。
## 2026-09-21 macOS 发布改为只出 arm64 单架构(Intel 暂不支持)
- 背景:Mac 发布管线按 `universal-apple-darwin` 构建,但随包 Node 便携运行时只有**单架构官方发行版**(`stage-node-runtime.mjs``process.execPath` 取材),于是 macOS Job #7~#13 连续失败在「Node 运行时不支持发布目标:universal-apple-darwin」。期间出现过一版「按宿主架构放行」的过渡实现,它能骗过通用包自检(`check-macos-bundle.mjs``process.arch` 校验),但 Intel 上那份 arm64 侧车不可执行,并且已发布的 dev-mac 0.1.86 就带着这个缺陷。
- 决策:macOS 固定只构建 `aarch64-apple-darwin`,渠道清单只登记 `darwin-aarch64`(不再登记 `darwin-x86_64`,避免把 arm64 产物发给 Intel 客户端);`targetRuntime('universal-apple-darwin')` 保持失败关闭,入口 `build-macos-ci.mjs` 只跑 arm64 隔离 smoke,首装包命名 `<产品名>_<版本>_aarch64.dmg`
- 原因:要让 Intel 真正可用,必须让发布包按架构各带一份**同版本**运行时(另下载另一架构官方发行版)+ 通用包自检按架构分别校验,这是一条独立且更大的改动;在 DDL 前用「只带宿主架构」糊过去等于把坏包发给 Intel 用户,比暂不支持更糟。单架构同时把构建时间与产物体积减半。
- 验证:`node --test apps/ai-game-creator-shell/scripts/*.test.mjs` 92/92(含 `targetRuntime('universal-apple-darwin')` 必须抛错、入口固定 arm64 目标与 `_aarch64.dmg` 后缀的守卫用例);`npm run check:production-ops``check:encoding``check:doc-index`、prettier、eslint、`git diff --check` 通过;真实端到端由 Jenkins Mac Job 验证(清单只含 `darwin-aarch64`、DMG 与更新包唯一匹配)。
- 影响范围:`apps/ai-game-creator-shell/scripts/{build-macos-ci.mjs,stage-node-runtime.mjs,prepare-macos-codex.test.mjs}``jenkins/Jenkinsfile.ai-game-creator-shell-macos-build`、本仓三份技术/运维文档与共享记忆。未动 Windows 渠道、未动 Rust 侧运行时解析(单架构仍是扁平 `game-runtime/node/`)。
- 恢复 Intel 的路径:先在 staging 支持按架构各带一份同版本运行时并让通用包自检按架构校验,再切回 universal 目标、把 `darwin-x86_64` 键登记回去并补 Intel 真机验收。
- 已知未覆盖:Intel Mac 用户的更新体验(清单缺 `darwin-x86_64` 键,客户端会「无可用更新」,未实测其 UI 文案);Mac 包里仍并列携带两套 Codex 原生依赖(只运行 arm64 切片,可按需瘦身)。
## 2026-09-21 图集切片上限:客户端结果门从 64 对齐到平台契约的 256
- 背景:现场(项目 `gameagent-6e53c9e8`2026-09-21 07:54)「AI 生成图标素材」失败:`platform-generation-result-unknown: 异步生成完成结果无法绑定到 operationIdExternal Editor 旧同步结果的图集切片超过 64 个`。任务账本(`.agent/runtime/asset-generation-tasks/tasks.json`)显示它跑了 99 秒、`assetId` 为空、没有落任何素材;对应的持久化请求(`canvas-generation-requests/manual-canvas-asset-generate/slot-560175669f….json`)是 `sliceMode: connected-components` + `sliceCount: null`(自动切分)。也就是**平台已经生成并切完图了,是客户端在绑定结果这一步把整条结果判失败**,付费产物被丢弃。
@@ -1,8 +1,8 @@
# 踩坑与排障记录
## macOS 通用包里的 Node 便携运行时仍是宿主架构
## macOS 只出 arm64 单架构,universal 必须失败关闭
`stage-node-runtime.mjs` 只把**构建宿主的 Node** 打成便携运行时(`targetRuntime` 只支持 per-arch 目标),而 `build-macos-ci.mjs` 构建的是 `universal-apple-darwin`2026-09-21 macOS Job #13 因此在 `stageNodeRuntime` 直接抛 `Node 运行时不支持发布目标:universal-apple-darwin`。当前按「通用包 + 宿主架构(arm64 构建机 → arm64Node」放行:`check-macos-bundle.mjs``process.arch` 校验,所以本机自检通过,但**Intel Mac 上该侧车不可执行**。要真正通用需要另行下载另一架构官方 Node 发行版并用 `lipo -create` 合并,同时把 bundle 检查改成接受 `universal` 并核验 `lipo -archs node = arm64 x86_64`;在此之前不得宣称 macOS 通用包在 Intel 上可用
`stage-node-runtime.mjs` 只把**构建宿主的 Node**打成便携运行时(官方发行版是单架构,没有 universal 发行版),而 2026-09-21 之前 `build-macos-ci.mjs` 构建的是 `universal-apple-darwin`macOS Job #7~#13 因此在 `stageNodeRuntime` 直接抛Node 运行时不支持发布目标:universal-apple-darwin」。期间出现过一版「按宿主架构放行」的过渡实现(`targetRuntime` 对 universal 返回宿主架构),它能骗过通用包自检(`check-macos-bundle.mjs``process.arch` 校验,但**Intel Mac 上这份 arm64 侧车不可执行**,等于把坏包发出去。当前决策:macOS 固定只构建 `aarch64-apple-darwin`,清单只登记 `darwin-aarch64``targetRuntime('universal-apple-darwin')` 保持失败关闭。恢复 Intel 的正确路径是先在 staging 支持按架构各带一份**同版本**运行时(另下载另一架构官方发行版)并让通用包自检按架构分别校验,再切回 universal 目标、把 `darwin-x86_64` 键登记回去;不得用「只带宿主架构」充数,也不得把 arm64 产物登记成 x86_64 键
## release 冷备空间不足会把生产留在维护态
@@ -98,11 +98,11 @@
| 系统 | 构建目标 | 清单平台键 | 更新包 | 清单地址 |
| --------- | ------------------------ | ---------------------------------------------- | ------------------------ | ------------------------------------ |
| Windows | `x86_64-pc-windows-msvc` | `windows-x86_64` | NSIS `.exe` + `.exe.sig` | `<OSS base>/agc/<channel>-win/latest.json` |
| macOS | `universal-apple-darwin` | `darwin-aarch64` + `darwin-x86_64`(同一对象) | `*.app.tar.gz` + `.sig` | `<OSS base>/agc/<channel>-mac/latest.json` |
| macOS | `aarch64-apple-darwin` | `darwin-aarch64` | `*.app.tar.gz` + `.sig` | `<OSS base>/agc/<channel>-mac/latest.json` |
- 对象布局:清单固定写成 `agc/<channel>-win|mac/latest.json`;安装包与签名写成同一分区的 `<version>/<file>``<file>.sig`
- macOS 正式交付使用 universal 主程序:两个平台键指向同一个 `.app.tar.gz` 与签名,一份产物同时服务 Apple Silicon 与 Intel。单架构目标`aarch64-apple-darwin` / `x86_64-apple-darwin`)只用于本机诊断,不登记正式分区清单——单架构构建不可轮流覆盖同一个 `latest.json` 并宣称双架构均可更新
- universal 主程序同时携带分目录的 arm64/x64 原生 Codex 组件:每个组件保持上游单架构布局与独立 SHA-256 清单,运行中的主程序切片只选择同架构目录,不得把两套原生包的元数据或辅助程序混装。
- macOS 当前只出 Apple Silicon 单架构`aarch64-apple-darwin`),清单只登记 `darwin-aarch64`**不再**登记 `darwin-x86_64`,避免把 arm64 产物发给 Intel 客户端。恢复 Inteluniversal)的前置条件是随包 Node 也能按架构各带一份:`stage-node-runtime.mjs` 对 universal 目标失败关闭,因为一台构建机只能提供宿主架构的官方 Node,只带一份会让 Intel 上该运行时不可执行,而通用包自检按 `process.arch` 校验、抓不到这个问题
- 主程序当前只出 arm64 单架构,但 `tauri.macos.conf.json` 仍并列映射 `darwin-arm64` / `darwin-x64` 两套原生 Codex 组件:每个组件保持上游单架构布局与独立 SHA-256 清单,运行中的主程序切片只选择同架构目录,不得把两套原生包的元数据或辅助程序混装;随包 Node 只有宿主架构那一份(本地自检会按架构核对并实际执行它)
- 构建期要求:打开 `bundle.createUpdaterArtifacts` 以生成 `.sig`;构建环境提供签名私钥与密码(私钥内容不得入库);公钥写入客户端配置。公钥在首个带更新能力的版本发布后不可更换,更换等于放弃自动更新(只能手动重装)。
- 版本递增按渠道及系统分区独立进行:发布脚本读取该分区远端 `latest.json``version`,与本地版本取较高者递增 patch;不同分区的远端版本互不影响。
- 版本高水位:仅 dev 的 Windows 分区在迁移窗口内取「分区清单版本」与「旧协议迁移指针版本」较大值再递增,避免已发布旧客户端版本倒退。迁移窗口结束(旧指针 404)后只读分区清单;release、自定义渠道与所有 Mac 分区均不参与旧指针比较。
@@ -146,7 +146,7 @@
| 条款 | 验收方式 | 证据 |
| ---------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| 渠道与端点映射、渠道校验 | `node --test apps/ai-game-creator-shell/scripts/build-release.test.mjs` | 通过(默认渠道、错配失败关闭、未知渠道失败关闭) |
| macOS universal 清单与双架构依赖 | 定向发布脚本与安装包验证 | 两个平台键同 URL/签名;两套资源独立校验,真机验收另记 |
| macOS 单架构清单与随包资源 | 定向发布脚本与安装包验证 | 清单只有 `darwin-aarch64`DMG/更新包按架构命名并唯一匹配;真机验收另记 |
| 缺签名时失败关闭 | 同上 | 通过 |
| 开发态不检查更新 | `vitest run apps/ai-game-creator-shell/tests/appUpdate.test.ts` | 通过(开关关闭时不请求清单) |
| 旧自研链路整条删除 | 代码检索无残留命令、事件与白名单条目 | 通过(`download_agc_update` / 下载事件 / 清单常量均无残留) |
@@ -168,10 +168,10 @@
已决策:
- macOS 采用 universal 包,两个平台键对应同一更新产物;主程序用 lipo 检查两种架构,Codex 资源分别做原生身份/摘要与启动验证。Rosetta 结果不代替 Intel 真机验收
- macOS 当前只验 arm64主程序用 `lipo` 检查架构,Codex 与随包 Node 做原生身份/摘要与启动验证。恢复 Intel 后必须补回双架构 smoke 与 Intel 真机验收,Rosetta 结果不能替代
- 旧客户端迁移桥:保留一个版本周期。渠道清单上线后,发布管线同时把旧的 `agc/latest.json`sha256 格式)指向 `dev-win` 最新安装包,让已发布客户端自动升级到新协议;下个周期整条删除。
- 签名密钥:由本仓库维护者生成并保管,私钥保存在仓库外(`%USERPROFILE%\.tauri\genarrative-agc-updater.key`),只有公钥进入客户端配置;Jenkins 用受保护凭据 `AgcUpdaterSigningKey``AgcUpdaterSigningKeyPassword` 注入为 Tauri 打包器读取的 `TAURI_SIGNING_PRIVATE_KEY``TAURI_SIGNING_PRIVATE_KEY_PASSWORD`,本机可用 `TAURI_SIGNING_PRIVATE_KEY_PATH` 指向同一私钥。当前密钥不带密码;首次发布前仍可重新生成,首次发布后不可更换。
- macOS 发布方式:已接入专用 macOS Jenkins 节点(label `genarrative-agc-macos`EXCLUSIVE 单 executor),由 `Jenkinsfile.ai-game-creator-shell-macos-build` 执行 `scripts/build-macos-ci.mjs` 完成 universal 构建、双架构隔离 smoke、universal DMG、分区清单生成、更新包验签与 OSS 上传。`AGC_RELEASE_DRY_RUN` 默认为关(与 Windows 渠道对称,即直接发布),只有勾选后才退化为「只打印上传计划、不写 OSS」的演练。
- macOS 发布方式:已接入专用 macOS Jenkins 节点(label `genarrative-agc-macos`EXCLUSIVE 单 executor),由 `Jenkinsfile.ai-game-creator-shell-macos-build` 执行 `scripts/build-macos-ci.mjs` 完成 arm64 单架构构建、arm64 隔离 smoke、arm64 DMG`<产品名>_<版本>_aarch64.dmg`、分区清单生成、更新包验签与 OSS 上传。`AGC_RELEASE_DRY_RUN` 默认为关(与 Windows 渠道对称,即直接发布),只有勾选后才退化为「只打印上传计划、不写 OSS」的演练。
- macOS 代码签名与公证暂缺:产物为未签名 + 未公证,构建入口剥离 `APPLE_*` 凭据跳过 Apple 签名,不传 `--no-sign`(它还会跳过 updater 的 minisign 签名,产物将没有 `.sig`);构建清单实测记录 `appleSigned` 与签名类型,`latest.json` 侧固定记录 `notarized=false`,首装需用户在 Gatekeeper 中手动放行。该限制作为已知未验证项记录,不静默通过;「安装 → 重启接管新版本」的自动更新闭环仍需实机验收。
- 更新包验签门禁:构建完成、上传 OSS 之前,用产物内烘焙的 `plugins.updater.pubkey` 复核 `<更新包>.sig`Tauri 使用 minisign 的 `ED` 预哈希模式)。keyId 不一致或校验失败立即失败关闭,禁止上传——客户端校验失败会直接拒绝安装,且公钥发布后不可更换。
@@ -71,7 +71,7 @@
### 环境与工作流
- 客户端交付配套 Node/npm;发布包从本机已安装且与目标平台/架构一致的工具链制作受校验资源,保留许可并校验内容摘要。安装态不依赖系统 PATH 的 Node;开发态可使用已验证的宿主运行时。不得从项目或相对 PATH 加载伪造运行时。
- 客户端交付配套 Node/npm;发布包从本机已安装且与目标平台/架构一致的工具链制作受校验资源,保留许可并校验内容摘要。安装态不依赖系统 PATH 的 Node;开发态可使用已验证的宿主运行时。不得从项目或相对 PATH 加载伪造运行时。随包运行时是**单架构**官方发行版,因此 macOS 当前只构建 `aarch64-apple-darwin` 单架构包;要出 universal 必须先让 staging 支持按架构各带一份同版本运行时,在此之前 universal 目标失败关闭,不得只带宿主架构那一份糊过去。
- 新建 Web 游戏在生图和大量实现前执行客户端环境预检,检查 Node/npm 的实际版本、浏览器启动和 CDP 可用性。报告只包含安全状态、版本、耗时和错误码。缺失或异常必须尽早返回阻塞,不能指示模型改宿主环境、全盘搜索或自行下载一套运行时。编辑器工程不强制 Web 工具链。
- 预检不安装依赖、不修改项目 revision、不请求平台生成;构建仍执行项目自己的 npm 脚本。Codex 隔离 HOME 与平台凭据边界保持不变,客户端把已验证的运行时加入执行 PATH,不能把宿主凭据目录交给模型。
- 第一轮先明确本次必需玩法、素材和验收项。同批独立读取尽量合并,必需图片一次规划;已有且可用的资产复用。已有目标全部通过后给出交付结果,非阻塞的新点子列为后续工作,不在收尾时主动开启新的生产链。
@@ -579,7 +579,7 @@ Supervisor 认领该回执后,由父 run 自己为每个原 delivery 逐一创
- 调度边界:正式 DAG、manifest、Agent task/session/run 身份、队列、锁、委派、all-join、完成门、Provider lifecycle、持久 retry/handoff 与 `needs-reconciliation` 继续由现有 AGC Runtime 掌控。每个被调度节点在 `codex_cli` 模式下直接启动一次非交互 `codex exec` 充当该节点的推理 AgentCodex 返回当前 Runtime 广告函数的结构化调用,Runtime 仍是唯一 ToolHost,不允许 CLI 自己写项目、执行命令、调用 MCP 或形成第二套 revision / verification 真相。
- 安装包侧车:Windows x64 release 固定随 Tauri resource 打包 `@openai/codex@0.155.1` 的原生 `codex.exe`Rust build script 从 AGC 子包锁定依赖 stage 到 resource,并写入版本与 SHA-256 清单。固定版本只在 `build_support/codex_bundle.rs` 声明一次,构建脚本、宿主补丁执行器身份、逐次审批协议允许列表和模型目录捕获共同引用它,避免多处字面量漂移。Windows 侧车映射只写入 `tauri.windows.conf.json`,通用 `tauri.conf.json` 不得让 Linux / macOS 构建依赖未生成的 Windows 二进制。运行时只在文件摘要和 `codex-cli` 版本同时匹配清单时优先选内置侧车;缺失、损坏或版本漂移时跳过它,按既有 npm 安装、PATH 顺序回退。安装包同时携带 Apache-2.0 第三方声明;API Key、`auth.json`、Cookie、Token、用户 `CODEX_HOME`、用户配置和项目数据绝不打包。
- Windows x64 release 安装包只生成 NSIS,不生成 MSI:`tauri.windows.conf.json``bundle.targets` 固定为 `["nsis"]`,通用配置继续保留其它平台的默认打包目标。安装后的产品名、开始菜单 / 桌面快捷方式和 EXE 产品描述统一由 `tauri.conf.json``productName: "陶泥儿"` 生成;应用 identifier 与内部可执行文件名保持稳定。内置 Codex 资源安装到顶层 `coding-agent/win-x64/`,运行时从同一路径查找 `bin/codex.exe``manifest.json`;仓库 staging 仍使用 `resources/codex/win-x64/`,包内子目录、组件名、版本和完整性校验保持原合同。
- macOS 安装包必须携带锁定版本的原生 Codex、`codex-code-mode-host``rg`、上游 zsh、`codex-package.json` 和第三方声明,保留上游相对布局;构建时按 Cargo 目标选择 npm 原生依赖,缺文件、版本或目标不匹配立即失败,不借用开发机 PATH 里的 Codex。资源只在 `tauri.macos.conf.json` 映射到 `Contents/Resources/coding-agent/mac-native/darwin-arm64/``darwin-x64/`。构建与运行共享平台文件白名单,运行时由当前 `.app/Contents/MacOS` 定位相邻 `Resources`,完整性与版本验证通过后优先使用内置组件;失败沿既有外部安装回退,不能运行未校验的内置文件。universal 主程序同时携带两套独立原生资源,运行切片按 Cargo 目标只选择对应目录;macOS 构建必须预备两套锁定原生依赖,不读取全局 Codex。
- macOS 安装包必须携带锁定版本的原生 Codex、`codex-code-mode-host``rg`、上游 zsh、`codex-package.json` 和第三方声明,保留上游相对布局;构建时按 Cargo 目标选择 npm 原生依赖,缺文件、版本或目标不匹配立即失败,不借用开发机 PATH 里的 Codex。资源只在 `tauri.macos.conf.json` 映射到 `Contents/Resources/coding-agent/mac-native/darwin-arm64/``darwin-x64/`。构建与运行共享平台文件白名单,运行时由当前 `.app/Contents/MacOS` 定位相邻 `Resources`,完整性与版本验证通过后优先使用内置组件;失败沿既有外部安装回退,不能运行未校验的内置文件。macOS 当前只构建 arm64 单架构,但资源映射仍并列携带两套锁定原生 Codex 依赖(运行切片按 Cargo 目标只选择对应目录),恢复 Intel 时无需改动资源布局;随包 Node 只有宿主架构那一份,所以不得构建 universal 包。不读取全局 Codex。
- 内置插件的清单、运行入口与面板同时在 Windows/macOS 随包分发,继续由既有 PluginHost 的应用资源目录扫描入口发现;不携带开发依赖、缓存、测试或私有配置。插件文件随包不等于原生适配器跨平台:Cocos 进程桥接仍受现有 Windows 实现和 feature 门禁约束,macOS 原生桥接另行设计与验收,不复制 Windows DLL 冒充支持。系统 Node、用户 Cocos Creator、账号登录、网络和生成工程的 npm 工具链仍是现有外部前提,不在此次 Codex 侧车补齐中隐式变更。
- macOS 安装包验收必须包括:脱离仓库位置的 `.app` 资源与架构检查、受限 PATH/隔离 HOME 下内置 Codex 启动和 app-server 握手、必需文件缺失/篡改/平台错误的拒绝测试,以及 DMG 完整性检查。真实登录、Provider 对话、GUI 和 Cocos 操作必须独立列出证据,不能用压缩包生成或 `--version` 成功替代。未配置正式签名、公证的本地测试包不得作为公开发行包。
- macOS 安装包的系统下限取主程序和全部原生组件中的最高要求;锁定 Codex 0.155.1 原生依赖中 `codex-resources/zsh/bin/zsh``LC_BUILD_VERSION` 下限为 macOS 15.0`bin/codex``codex-code-mode-host``codex-path/rg` 分别为 11.0 / 10.12),因此 `bundle.macOS.minimumSystemVersion` 明确为 `15.0`。更新原生依赖时重新检查 Mach-O 的系统下限,不能只按 AGC 主程序宣称兼容版本。0.155.1 的 macOS 原生包新增 `codex-resources/voice/`(语音宿主与 GStreamer 动态库);AGC 不启用语音能力,侧车清单只 stage 上述组件,不打包该目录,未来如启用语音需重新评估依赖与许可。
@@ -728,19 +728,19 @@ Pingora current release 自审脚本 `scripts/ops/pingora-current-release-audit.
### AGC macOS 手动构建节点
Mac universal 构建脚本位于 `jenkins/Jenkinsfile.ai-game-creator-shell-macos-build`,只对专用 `genarrative-agc-macos` 标签运行。节点按 EXCLUSIVE、单 executor 配置,Job 禁止并发且不设置 trigger;不接入现有每小时版本调度,不改 Windows 发布职责。它使它只在专用 Agent 目录下构建:默认按约定匹配 `$HOME/Library/Jenkins/agents/<node>/workspace/`(不写死节点名,节点改名后仍成立),也可用 `AGC_AGENT_ROOT` 显式覆盖;禁止指向开发 checkout 或共享其可写 target/node_modules。
Mac 单架构(arm64构建脚本位于 `jenkins/Jenkinsfile.ai-game-creator-shell-macos-build`,只对专用 `genarrative-agc-macos` 标签运行。节点按 EXCLUSIVE、单 executor 配置,Job 禁止并发且不设置 trigger;不接入现有每小时版本调度,不改 Windows 发布职责。它使它只在专用 Agent 目录下构建:默认按约定匹配 `$HOME/Library/Jenkins/agents/<node>/workspace/`(不写死节点名,节点改名后仍成立),也可用 `AGC_AGENT_ROOT` 显式覆盖;禁止指向开发 checkout 或共享其可写 target/node_modules。
Job 名为 `Genarrative-Agc-MacOS-Build`,SCM 直接读取仓库内上述 Jenkinsfile,参数为 `SOURCE_BRANCH``COMMIT_HASH``AGC_UPDATE_CHANNEL``AGC_RELEASE_VERSION``AGC_RELEASE_DRY_RUN``AGC_UPDATE_RELEASE_NOTES``OSSUTIL_BIN``CARGO_BUILD_JOBS`。渠道参数是基础名(不含系统,默认 `dev`),脚本不接受 `dev-mac` 这类系统后缀,写入分区固定推导为 `<channel>-mac`:这与 Windows Job 的 `<channel>-win` 对称,也延续已发布客户端的端点。
该 Job 的职责是构建并发布 `<channel>-mac` 分区更新:执行 `npm ci` 后使用锁文件校验并补齐两种 macOS Codex 原生依赖,再调用 `scripts/build-macos-ci.mjs`AGC 应用目录下)生成 universal app、arm64/x86_64 隔离 smoke、universal DMG 与分区清单 `latest.json`,用产物内烘焙的公钥复核更新包签名(`verify-updater-signature.mjs`),最后按 `AGC_RELEASE_DRY_RUN` 决定是否上传 OSS。签到会同时取 `master`,让渠道清单里上一次发布的 commit 可解析——缺了它更新摘要会退化成「最近客户端改动」(该步失败只降级摘要,不阻断发布)。产物名(`*.app`、updater 归档、DMG、卷名)一律从 Tauri `productName` 推导,校验脚本从包内 `Info.plist` 读取可执行名,改产品名不会让入口静默找错对象;隔离 smoke 用 `ditto --clone` 复制副本(实测整轮 6.8 秒,此前整包复制约 1 分钟),并在构建前删除本次将写出的 DMG/更新包/签名,保证归档产物一定来自本次构建。归档限 `artifacts/` 下的 DMG、SHA-256、`latest.json`、更新包签名、更新摘要、非敏感构建清单和源码 commit;不归档用户 HOME、Jenkins secret、原始工作目录或全量日志。
该 Job 的职责是构建并发布 `<channel>-mac` 分区更新:执行 `npm ci` 后使用锁文件校验并补齐两种 macOS Codex 原生依赖,再调用 `scripts/build-macos-ci.mjs`AGC 应用目录下)生成 arm64 单架构 app、arm64 隔离 smoke、arm64 DMG`<产品名>_<版本>_aarch64.dmg`与分区清单 `latest.json`,用产物内烘焙的公钥复核更新包签名(`verify-updater-signature.mjs`),最后按 `AGC_RELEASE_DRY_RUN` 决定是否上传 OSS。签到会同时取 `master`,让渠道清单里上一次发布的 commit 可解析——缺了它更新摘要会退化成「最近客户端改动」(该步失败只降级摘要,不阻断发布)。产物名(`*.app`、updater 归档、DMG、卷名)一律从 Tauri `productName` 推导,校验脚本从包内 `Info.plist` 读取可执行名,改产品名不会让入口静默找错对象;隔离 smoke 用 `ditto --clone` 复制副本(实测整轮 6.8 秒,此前整包复制约 1 分钟),并在构建前删除本次将写出的 DMG/更新包/签名,保证归档产物一定来自本次构建。归档限 `artifacts/` 下的 DMG、SHA-256、`latest.json`、更新包签名、更新摘要、非敏感构建清单和源码 commit;不归档用户 HOME、Jenkins secret、原始工作目录或全量日志。
发布凭据全部走 Jenkins 全局凭据,并在 `withCredentials` 内注入当前进程:`AgcUpdaterSigningKey`(与 `AgcUpdaterSigningKeyPassword`)映射为 `TAURI_SIGNING_PRIVATE_KEY` / `TAURI_SIGNING_PRIVATE_KEY_PASSWORD``AliyunAccessKeyId` / `AliyunaccessKeySecret` 映射为 `AGC_OSS_ACCESS_KEY_ID` / `AGC_OSS_ACCESS_KEY_SECRET`;私钥与凭据不写入 workspace、日志或归档产物。上传顺序为更新包、签名、首装包,三者全部成功后才覆盖 `agc/<channel>-mac/latest.json` 指针;`AGC_RELEASE_DRY_RUN` 与 Windows 渠道对称:默认关闭即真发布,勾选后才退化为演练(只打印将上传的对象、不写任何 OSS 对象)。本 Job 是正式发布入口,调度器在发号后与 Windows 一起触发它,并额外传 `SKIP_IF_SUPERSEDED=true`——Mac 节点是日常办公机,离线期间排队的旧构建在节点回来后若已被源码分支推进,直接跳过而不发布过期版本。Mac 节点需要 `ossutil`(实测 1.7.19 原生 arm64 可用,装在 `~/.local/bin`,已在 Job 的 PATH 内),可用 `OSSUTIL_BIN` 指定命令名或绝对路径。首次发布建议显式指定 `AGC_RELEASE_VERSION`,避免按渠道高水位递增时出现版本链回退。
产物边界:macOS 代码签名与公证暂缺,构建通过剥离 `APPLE_*` 凭据让 Tauri 跳过 Apple 签名,**不得使用 `--no-sign`**——该标志会连带跳过 updater 的 minisign 签名,产物缺少 `.sig` 会直接卡在验签门禁(首次实跑即命中该坑)。构建清单按 `codesign -dv` 实测记录 `appleSigned` / `appleSignatureKind`(如 `adhoc`),并固定记录 `notarized=false`。用户首次安装需要在 Gatekeeper 中手动放行;更新包校验本身只依赖 minisign 签名,因此未签名不阻断自动更新的校验环节,但「安装 → 重启接管新版本」的实机闭环仍未验证,不得以构建成功替代。
Job 描述与节点描述是 Jenkins 侧元数据,**不随仓库同步**:Jenkinsfile 只回写参数定义,描述必须手工维护,否则会停留在建 Job 时的口径(2026-09-20 就出现过描述还写着「只构建归档、不签名不上传」,而实际已经是含签名、分区清单、验签与 OSS 上传的发布管线)。当前口径:Job 描述说明「构建 universal → 双架构 smoke → DMG → `<channel>-mac` 分区清单 → 更新包验签 → 按 dry-run 决定上传,未做 Apple 签名与公证」;节点描述说明「`genarrative-agc-macos`、EXCLUSIVE 单 executor、仅手动触发、独立 workspace、sccache 在 HOME」。维护手法:该 Job 上 `POST job/<name>/config.xml` 会返回 500,节点侧同接口正常;因此改 Job 描述用脚本接口原地更新(保留构建历史),不要为了改描述删 Job 重建。
Job 描述与节点描述是 Jenkins 侧元数据,**不随仓库同步**:Jenkinsfile 只回写参数定义,描述必须手工维护,否则会停留在建 Job 时的口径(2026-09-20 就出现过描述还写着「只构建归档、不签名不上传」,而实际已经是含签名、分区清单、验签与 OSS 上传的发布管线)。当前口径:Job 描述说明「构建 arm64 单架构 → arm64 smoke → DMG → `<channel>-mac` 分区清单(只登记 `darwin-aarch64`→ 更新包验签 → 按 dry-run 决定上传,未做 Apple 签名与公证」;节点描述说明「`genarrative-agc-macos`、EXCLUSIVE 单 executor、仅手动触发、独立 workspace、sccache 在 HOME」。维护手法:该 Job 上 `POST job/<name>/config.xml` 会返回 500,节点侧同接口正常;因此改 Job 描述用脚本接口原地更新(保留构建历史),不要为了改描述删 Job 重建。
该 Job 的 Rust 编译走 sccache 对象缓存,与 `Genarrative-Api-Build``Genarrative-Stdb-Module-Build` 一致:`RUSTC_WRAPPER=sccache``CARGO_INCREMENTAL=0``SCCACHE_CACHE_SIZE=20G``SCCACHE_DIR` 默认落在稳定缓存根 `~/caches/genarrative-jenkins/agc-macos/sccache`,可用 `GENARRATIVE_AGC_MACOS_CACHE_ROOT` 覆盖;缓存根固定在 HOME 下而不是 `WORKSPACE` 内,避免 workspace 重建后无改动也触发近似冷构建。节点是 8 逻辑核(4P+4E)的共用机器,`CARGO_BUILD_JOBS` 因此做成 Job 参数(默认 `8`,吃满节点)。该值不只是并行 crate 数——cargo 会把它作为 jobserver 令牌上限传给 rustc,主 crate 的 codegen 也受它限制,因此写小会直接拖长整条构建;节点只有 24 GB 内存且是日常办公机,若构建期间出现明显换页再临时调低。首次构建 sccache 必然全未命中(实测 0 命中 / 407 未命中),此后的构建才逐步吃到缓存。节点未安装 sccache 时管线打印提示并回退到真实 `rustc`,不阻断构建。universal 双架构各自独立编译,依赖 crate 的复用收益约为单架构的两倍;命中情况由构建末尾的 `sccache --show-stats` 输出,供判断是否需要预热或调整缓存上限。
该 Job 的 Rust 编译走 sccache 对象缓存,与 `Genarrative-Api-Build``Genarrative-Stdb-Module-Build` 一致:`RUSTC_WRAPPER=sccache``CARGO_INCREMENTAL=0``SCCACHE_CACHE_SIZE=20G``SCCACHE_DIR` 默认落在稳定缓存根 `~/caches/genarrative-jenkins/agc-macos/sccache`,可用 `GENARRATIVE_AGC_MACOS_CACHE_ROOT` 覆盖;缓存根固定在 HOME 下而不是 `WORKSPACE` 内,避免 workspace 重建后无改动也触发近似冷构建。节点是 8 逻辑核(4P+4E)的共用机器,`CARGO_BUILD_JOBS` 因此做成 Job 参数(默认 `8`,吃满节点)。该值不只是并行 crate 数——cargo 会把它作为 jobserver 令牌上限传给 rustc,主 crate 的 codegen 也受它限制,因此写小会直接拖长整条构建;节点只有 24 GB 内存且是日常办公机,若构建期间出现明显换页再临时调低。首次构建 sccache 必然全未命中(实测 0 命中 / 407 未命中),此后的构建才逐步吃到缓存。节点未安装 sccache 时管线打印提示并回退到真实 `rustc`,不阻断构建。单架构发布后依赖 crate 的复用更彻底(此前每次要编 aarch64 与 x86_64 两遍);命中情况由构建末尾的 `sccache --show-stats` 输出,供判断是否需要预热或调整缓存上限。工作区 `target/` 目录跨构建保留(Checkout 只跑 `git clean -fd`,不动 ignored 目录),因此仓库内 path 依赖之外的 crate 只编一次。
首次端到端验证(2026-09-20build #3):`SUCCESS`43 分钟。产出 `陶泥儿_0.1.68_universal.dmg``陶泥儿.app.tar.gz``.sig``latest.json``build-manifest.json`;两个架构的隔离 smokearm64 / Rosetta x86_64)通过,更新包用产物内烘焙公钥复核通过(`alg=ED``keyId=cb883447e3e87c4e`),dry-run 只打印 4 个上传对象、未写 OSS。构建清单实测记录 `appleSigned=false``appleSignatureKind=adhoc``notarized=false`。首次实跑暴露并修掉两个入口缺陷:传 `--no-sign` 会连带跳过 updater 的 minisign 签名(产物无 `.sig`),以及复用 workspace 里残留的同名 DMG 让 `hdiutil create` 直接失败、残留旧 `.sig` 还可能让验签误通过——入口现在会在构建前删除本次将写出的确切产物并要求 `hdiutil -ov`
@@ -10,7 +10,7 @@ pipeline {
timestamps()
}
parameters {
string(name: 'SOURCE_BRANCH', defaultValue: 'master', description: '必须含 universal 双架构依赖实现的受信任分支')
string(name: 'SOURCE_BRANCH', defaultValue: 'master', description: '必须含 macOS 单架构(arm64)发布实现的受信任分支')
string(name: 'COMMIT_HASH', defaultValue: '', description: '可选,固定属于源码分支的提交;不递增应用版本')
string(name: 'AGC_UPDATE_CHANNEL', defaultValue: 'dev', description: 'AGC 发布渠道(基础名,不含系统):dev、release 或自定义小写名称;写入的分区固定为 <channel>-mac')
string(name: 'AGC_RELEASE_VERSION', defaultValue: '', description: '可选三段版本号;留空则按 <channel>-mac 分区清单高水位递增 patch。首次发布建议显式指定,避免版本链回退')
@@ -26,7 +26,7 @@ pipeline {
AGC_OSS_BUCKET = 'agc-dev'
AGC_OSS_ENDPOINT = 'oss-rg-china-mainland.aliyuncs.com'
// 与 Linux 生产管线(api-build / stdb-module-build)对齐:Rust 编译走 sccache 对象缓存。
// 本 Job 是 universal 双架构(aarch64 + x86_64)各自独立编译,缓存收益约为单架构的两倍
// 本 Job 只编译 aarch64-apple-darwin 单架构(Intel 未支持):sccache 与工作区 target 目录跨构建复用
// 缓存根固定在 HOME 下的稳定目录,避免 WORKSPACE 重建导致近似冷构建;可用下面的变量覆盖。
GENARRATIVE_AGC_MACOS_CACHE_ROOT = 'caches/genarrative-jenkins/agc-macos'
RUSTC_WRAPPER = 'sccache'
@@ -143,7 +143,7 @@ pipeline {
sh '''
set -eu
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:${AGC_EXTRA_PATH}"
echo "[agc-macos] 渠道=${AGC_UPDATE_CHANNEL} 分区=${AGC_UPDATE_CHANNEL}-mac 目标=universal-apple-darwin dry-run=${AGC_RELEASE_DRY_RUN}"
echo "[agc-macos] 渠道=${AGC_UPDATE_CHANNEL} 分区=${AGC_UPDATE_CHANNEL}-mac 目标=aarch64-apple-darwin(单架构,Intel 未支持)dry-run=${AGC_RELEASE_DRY_RUN}"
ossutil_bin="${OSSUTIL_BIN:-ossutil}"
if command -v "${ossutil_bin}" >/dev/null 2>&1; then
"${ossutil_bin}" --version | head -1
@@ -194,8 +194,8 @@ pipeline {
}
success {
echo params.AGC_RELEASE_DRY_RUN
? 'macOS universal 渠道演练完成:已构建、验签并生成清单,未写入 OSS;Apple 签名与公证暂缺。'
: "macOS universal 更新包、签名、首装包与 ${params.AGC_UPDATE_CHANNEL}-mac 分区清单已上传 OSS;Apple 签名与公证暂缺,首装需手动放行。"
? 'macOS arm64 渠道演练完成:已构建、验签并生成清单,未写入 OSS;Apple 签名与公证暂缺。'
: "macOS arm64 更新包、签名、首装包与 ${params.AGC_UPDATE_CHANNEL}-mac 分区清单已上传 OSS(清单只登记 darwin-aarch64;Apple 签名与公证暂缺,首装需手动放行。"
}
}
}