渠道清单只读核对补统一总号与线上一致性断言
Project CI / Frontend tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled

- check:agc-update-channel-manifests:读 agc/global-version.json,断言任何渠道清单版本不高于统一总号(渠道落后按 INFO 提示,只有反超才失败),复用 agc-global-version 的 compareVersions
- 渠道化里程碑本轮核对补线上总号证据:总号 0.1.155(dev-win 取号),四个渠道 0.1.154/0.1.150/0.1.142/0.1.139 均不高于总号
- decision-log 记录该线上口径
This commit is contained in:
kdletters
2026-09-28 22:22:31 +08:00
parent 7da6de0b4e
commit 87a26f74df
3 changed files with 41 additions and 0 deletions
@@ -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 构建取证。
@@ -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。
@@ -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} 项跳过)` : ''}`