diff --git a/docs/project-memory/plans/【里程碑】AGC更新发布管线渠道化-2026-09-17.md b/docs/project-memory/plans/【里程碑】AGC更新发布管线渠道化-2026-09-17.md index de438329d..b1eddde2a 100644 --- a/docs/project-memory/plans/【里程碑】AGC更新发布管线渠道化-2026-09-17.md +++ b/docs/project-memory/plans/【里程碑】AGC更新发布管线渠道化-2026-09-17.md @@ -70,5 +70,6 @@ - **`dev-win`(v0.1.154,`pub_date=2026-09-28T13:24:53Z`,commit `76cdb96c5`)**:清单 `windows-x86_64` 指向 `agc/dev-win/0.1.154/陶泥儿开发版_0.1.154_x64-setup.exe`(165,984,391 字节,HEAD 200);`.sig` 对象 436 字节且与清单内签名文本一致;下载后字节数与 HEAD 一致,`sha256=7867734e3991…` 与旧协议指针 `agc/latest.json` 的 `sha256`/`size` 完全一致;用公钥验签通过(`alg=ED`,`keyId=cb883447e3e87c4e`)。 - **`dev-mac`(v0.1.142,`pub_date=2026-09-24T11:35:31Z`,commit `c07c10c0c`)**:清单 `darwin-aarch64` 指向 `agc/dev-mac/0.1.142/陶泥儿 Release.app.tar.gz`(315,510,533 字节,HEAD 200);`.sig` 对象 420 字节且与清单签名一致;下载后字节数一致、用同一公钥验签通过。 - 结论:两个渠道的「安装包 ↔ 签名 ↔ 渠道清单」三者一致成立,条目 3 关闭。 + - **统一总号(条目 1 的线上口径)**:同一脚本还会读 `agc/global-version.json`,断言「任何渠道清单版本都不高于统一总号」。本轮实测总号 `0.1.155`(`updatedAt=2026-09-28T14:11:19Z`、`channel=dev-win`、`buildId=95`),四个渠道清单都不高于它(dev-win 0.1.154 / release-win 0.1.150 / dev-mac 0.1.142 / release-mac 0.1.139);渠道低于总号是正常滞后(dev-win 那次取号后构建可能仍在跑),脚本按 INFO 提示而不是判失败,只有「渠道高于总号」才失败关闭。 - **顺带发现的真实缺陷(已在 macOS 里程碑与 decision-log 记录)**:线上 `dev-mac/0.1.142` 清单只登记 `darwin-aarch64` 是符合 2026-09-21 单架构决策的;但它指向的更新包解出来是 **0.1.139 的 release 身份包**(`world.genarrative.ai-game-creator.release` / 陶泥儿 Release)。这一条本条验收没覆盖(本条只看「地址存在 + 签名匹配」),本轮已把「包内版本与渠道身份」加进 `check:agc-update-channel-manifests`,并在 mac 构建入口补上构建期身份断言。 - 条目 5(Jenkins 归档与日志不含私钥)仍是未勾选:静态可证部分已记录在验收行上(`withCredentials` 注入 + 归档 glob 不含私钥文件),真实 Jenkins 运行日志需要一次 CI 构建取证。 diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 1ada97d5e..e6db3d13c 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -9707,6 +9707,7 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - 决策(产物选择器本身也要挡旧版本):`generateUpdateManifest()` 是共享入口,Windows 侧靠 Jenkins 的 `git clean -fdx` 才没踩到同一个坑,所以再补一道与平台无关的守卫——`assertArtifactVersionMatches()` 要求文件名里出现形如 `_0.1.154_` 的版本段时必须等于本轮版本(残留的 `_0.1.153_` 安装包会被挑中并因此失败关闭);macOS 的 `<产品名>.app.tar.gz` 不含版本段,返回 `null`,由 mac 入口的身份断言负责。 - 决策(只读核对也要看包内身份):`check:agc-update-channel-manifests` 在 `AGC_UPDATE_VERIFY_DOWNLOAD=1` 时下载 mac 更新包、解出 `Info.plist` 做同样断言;同时按 2026-09-21 决策断言 mac 渠道只登记 `darwin-aarch64`(不再要求 universal 双键)。 - 决策(跨渠道产物不得同字节):同一次核对里若下载到多个渠道的更新包,脚本会断言它们的 sha256 两两不同——不同渠道的 `productName` / `identifier` 不同,产物就不可能相同。事故取证:`dev-mac/0.1.142` 的更新包与 `release-mac/0.1.139` 的更新包 sha256 都是 `1dfc9deb79f7fa5fd66d8a8a47b80e3a6ff125b8762cabfd32d3e16dec946e4e`,字节级证明 dev 分区放的是 release 渠道那次构建的产物。全量核对用 `AGC_UPDATE_CHANNELS=dev-win,dev-mac,release-win,release-mac AGC_UPDATE_VERIFY_DOWNLOAD=1`(约 950 MiB 下载)。 +- 决策(统一总号也要线上核对):同一脚本读 `agc/global-version.json`,断言「任何渠道清单版本都不高于统一总号」;渠道低于总号按 INFO 提示(可能正在构建),只有渠道高于总号才失败关闭。本轮线上:总号 `0.1.155`(2026-09-28T14:11:19Z 由 dev-win 取号),四个渠道清单分别为 0.1.154 / 0.1.150 / 0.1.142 / 0.1.139,均不高于总号。 - 决策(口径回归):macOS 现行契约是 arm64 单架构(2026-09-21 决策),里程碑里「两个 macOS 平台键指向 universal 产物」的旧文字按现行决策改写;不得据此重新切回 universal,除非按该决策给出的恢复路径补齐按架构的 Node 运行时。 - 影响范围:`apps/ai-game-creator-shell/scripts/build-macos-ci.mjs`、新增 `apps/ai-game-creator-shell/scripts/macos-release-identity.mjs` 与其 `.test.mjs`、`apps/ai-game-creator-shell/scripts/prepare-macos-codex.test.mjs`、`jenkins/Jenkinsfile.ai-game-creator-shell-macos-build`、`scripts/check-agc-update-channel-manifests.mjs`、两份里程碑与本文件、pitfalls。 - 验证:`node --test apps/ai-game-creator-shell/scripts/*.test.mjs` → **110 passed / 0 failed**(其中定向批次 `macos-release-identity / prepare-macos-codex / verify-updater-signature / build-release / cargo-features` 60 passed;新增的 mac 身份回归用例直接喂线上那份 0.1.139 release 身份 plist,必须抛错;新增的产物版本守卫用例喂 `_0.1.153_` 残留安装包,必须抛错);`npm run check:production-ops`、`check:encoding`、`check:doc-index`、prettier、eslint、`git diff --check` 通过;只读核对对线上 `dev-win` 全 PASS(含 158 MiB 产物下载验签与旧协议 sha256 一致),对线上 `dev-mac` 精确报出上面两条 FAIL。 diff --git a/scripts/check-agc-update-channel-manifests.mjs b/scripts/check-agc-update-channel-manifests.mjs index 544e4b554..239d03294 100644 --- a/scripts/check-agc-update-channel-manifests.mjs +++ b/scripts/check-agc-update-channel-manifests.mjs @@ -25,6 +25,7 @@ import { pipeline } from 'node:stream/promises'; import * as tar from 'tar'; +import { compareVersions } from '../apps/ai-game-creator-shell/scripts/agc-global-version.mjs'; import { resolveChannelInstallIdentity } from '../apps/ai-game-creator-shell/scripts/channel-identity.mjs'; import { readMacosAppInfoIdentity } from '../apps/ai-game-creator-shell/scripts/macos-release-identity.mjs'; import { @@ -58,6 +59,8 @@ let failures = 0; let skipped = 0; /** 下载过的更新包摘要:用于「不同渠道不能共用同一份产物字节」这条隔离断言。 */ const downloadedArtifacts = []; +/** 各渠道清单版本:用于和统一总号(`agc/global-version.json`)比对。 */ +const channelVersions = new Map(); function check(name, ok, detail = '') { if (!ok) failures += 1; console.log( @@ -179,6 +182,7 @@ async function verifyChannel(channel, tempDir) { return; } const version = String(manifest.version ?? ''); + channelVersions.set(channel, version); check( `${channel} 清单版本与发布元数据齐备`, /^\d+\.\d+\.\d+$/u.test(version) && @@ -424,6 +428,41 @@ if (!VERIFY_DOWNLOAD) { ); } +// 统一总号:`agc/global-version.json` 是发号唯一事实源,任何渠道清单都不允许高于它; +// 总号高于渠道是正常的(渠道可能正在构建或尚未发布),这里只提示,不判失败。 +{ + const response = await fetch(`${OSS_BASE_URL}/global-version.json`); + check( + '统一总号对象可读', + response.status === 200, + `status=${response.status} ${OSS_BASE_URL}/global-version.json`, + ); + if (response.status === 200) { + const global = await response.json(); + const globalVersion = String(global.version ?? ''); + check( + '统一总号字段齐备', + /^\d+\.\d+\.\d+$/u.test(globalVersion) && + !Number.isNaN(Date.parse(String(global.updatedAt ?? ''))) && + String(global.channel ?? '').trim().length > 0, + `version=${globalVersion} updatedAt=${global.updatedAt ?? ''} channel=${global.channel ?? ''}`, + ); + for (const [channel, version] of channelVersions) { + if (!/^\d+\.\d+\.\d+$/u.test(version)) continue; + check( + `${channel} 清单版本不高于统一总号`, + compareVersions(version, globalVersion) <= 0, + `channel=${version} global=${globalVersion}`, + ); + if (compareVersions(version, globalVersion) < 0) { + console.log( + `INFO ${channel} 还没发到总号版本(渠道 ${version} < 总号 ${globalVersion};总号最近由 ${global.channel} 在 ${global.updatedAt} 取号,构建可能仍在进行)`, + ); + } + } + } +} + console.log( failures === 0 ? `\n全部通过${skipped > 0 ? `(${skipped} 项跳过)` : ''}`