mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): enforce architecture support tiers
This commit is contained in:
@@ -11,6 +11,20 @@
|
||||
|
||||
最新增量证据(2026-08-20):
|
||||
|
||||
- D-371/ADR-0464(已接受并机器化):接受 ADR-0006 的 Node 24 与多架构分层,并把根 `ql3-release.json` 升级为
|
||||
`qinglong/release-identity@v2` 唯一事实源。3.0 Tier 1 精确为 `amd64`、`arm64`;`ppc64le`、`s390x` 是通过固定
|
||||
Node 24 与同等级原生门禁前不可发布的候选;`arm/v7` 在缺少 owner、可重复 toolchain 与设备报告时为 experimental blocked;
|
||||
`arm/v6`、`386` 明确留在 `2.x` legacy line。release candidate 的 OCI platforms、原生 OS runner matrix、release set digest 与
|
||||
version audit 均从该身份派生,新增 Tier 1 却无原生 runner 会失败关闭;发布必需门新增 `architecture-support-tier`。这不新增
|
||||
workspace package、dependency、服务、timer、数据库对象或 Edge 常驻成本,也不把旧设备排除出后续受限 Worker 兼容路径。
|
||||
聚焦 release/version/downstream closure 门为 `55/55`;完整 backend 工作区为
|
||||
`1,503 total / 1,501 pass / 2 conditional skip / 0 fail`(含一条不会提交的既有用户测试;D-371 提交范围为
|
||||
`1,502 total / 1,500 pass / 2 skip`),18-package clean build/逐包测试退出 0。release version、package boundary、
|
||||
cluster dependency、Edge import、cluster deployment 与 image release 六项审计全部 compatible,workspace 仍为 18 packages、
|
||||
无 single/shallow package。14 档 Local artifact audit 串行复核全部 compatible;基础 Edge/Standalone 仍为
|
||||
`2,589,998 / 2,590,076` bytes,Application+AI 为 `4,493,151 / 4,493,283` bytes,MCP 为
|
||||
`7,315,930 / 7,316,038` bytes,证明发布政策未进入低资源部署闭包。
|
||||
|
||||
- D-370/ADR-0463(实现与本地协议门已接受;首份原生 CI 双架构报告待实际 workflow 产生):为显式按需的 Cluster Admin
|
||||
Copilot Console 增加固定 Linux x64/arm64 容量与 assertion 生命周期发布证据。两个原生 Admin image matrix job 分别在
|
||||
`192 MiB / swap 0 / 0.25 CPU / 32 PIDs`、只读 root、非 root、cap-drop ALL、no-new-privileges、默认 seccomp、8 MiB tmpfs 与
|
||||
@@ -4959,9 +4973,9 @@
|
||||
| D-11 | 新领域 API 使用 `/api/v3`,旧 API 通过兼容层继续服务 | Proposed | 允许渐进迁移而非一次性切换 |
|
||||
| D-12 | Workflow 首版只支持有界、可恢复的步骤模型 | Proposed | 避免无界 DAG 和 Agent 循环扩大可靠性风险 |
|
||||
| D-13 | Prompt、模型结果和 Tool 内容采集默认关闭 | Proposed | 降低 Secret 和业务数据泄漏风险 |
|
||||
| D-14 | ql-core 按 Tier 发布明确的架构支持矩阵,不把“现有镜像可构建”等同于“Node 24 可支持” | Proposed | 路由、NAS 和异构 Worker 是核心用户场景,但 Node 24 官方产物当前不能覆盖 ARMv6、ARMv7 和 386 |
|
||||
| D-14 | ql-core 按 Tier 发布明确的架构支持矩阵,不把“现有镜像可构建”等同于“Node 24 可支持” | Accepted(ADR-0006/0464:release identity v2 已机器化当前支持矩阵) | 路由、NAS 和异构 Worker 是核心用户场景,但 Node 24 官方产物当前不能覆盖 ARMv6、ARMv7 和 386 |
|
||||
| D-15 | UI Extension 不进入 3.0 首个稳定版本 | Deferred | 先稳定 Package、Tool 和 Runtime Extension 契约 |
|
||||
| D-16 | 3.0 ql-core 以固定 Node.js 24 LTS 为运行时基线 | Proposed | 使用稳定 Web/SQLite 能力并消除不同发行版浮动 Node 版本造成的行为漂移 |
|
||||
| D-16 | 3.0 ql-core 以固定 Node.js 24 LTS 为运行时基线 | Accepted(ADR-0006/0464:当前精确固定 24.18.0,变更必须升级发布身份并重跑完整矩阵) | 使用稳定 Web/SQLite 能力并消除不同发行版浮动 Node 版本造成的行为漂移 |
|
||||
| D-17 | 3.0 新领域持久化通过 typed schema 与 Repository 端口访问;SQLite 目标为 Drizzle + node:sqlite | Proposed | 避免新 Runtime 继续耦合 Sequelize,同时保留 PostgreSQL adapter 边界和可审查 migration |
|
||||
| D-18 | 取消先持久化为 Run 意图事件,再调用 Executor;实际终态由完成事实或 Reconciler 收敛 | Proposed | 使取消可跨重启恢复,并消除“已发信号但无审计事实”和迟到成功覆盖取消的竞态 |
|
||||
| D-19 | 跨进程取消派发使用独立的 durable lease/fencing 状态,不以 RunEvent 或内存锁代替并发控制 | Proposed | 在 edge SQLite 与 cluster PostgreSQL 上共享一致语义,限制多 Worker 重复 signal 和崩溃重试风暴 |
|
||||
@@ -6392,7 +6406,8 @@ ql-observability
|
||||
|
||||
### 7.9 多架构发布
|
||||
|
||||
QingLong 当前容器发布覆盖 `amd64`、`arm/v6`、`arm/v7`、`arm64`、`ppc64le` 和 `386`。3.0 应维护明确的架构支持矩阵:
|
||||
QingLong 2.x 容器发布曾覆盖 `amd64`、`arm/v6`、`arm/v7`、`arm64`、`ppc64le`、`s390x` 和 `386`。
|
||||
3.0 由 release identity v2 维护明确的架构支持矩阵:
|
||||
|
||||
- `ql-core` 和 edge 镜像优先保持现有多架构覆盖,但每个架构必须标记 `supported`、`experimental` 或 `legacy-only`,不能只凭构建成功宣称支持。
|
||||
- Alpine 与 Debian 发布物必须固定同一 Node.js 24 小版本,不能依赖发行版仓库的浮动 `nodejs`;升级 Node 小版本需要通过完整多架构门禁。
|
||||
@@ -6405,15 +6420,19 @@ QingLong 当前容器发布覆盖 `amd64`、`arm/v6`、`arm/v7`、`arm64`、`ppc
|
||||
|
||||
截至 2026-07-18 的上游产物审计显示,Node.js 24 官方 release binary 提供 x64、arm64、ppc64le 和 s390x,没有 ARMv6、ARMv7 或 386;官方 `node:24` Docker manifest 的 Debian/Alpine 覆盖也小于 QingLong 当前矩阵。Node.js BUILDING 文档把 ARMv7 降为实验支持,不能把自定义构建当作官方维护基线。参考:[Node.js 24 release](https://nodejs.org/en/blog/release/v24.18.0)、[Node.js official image manifest](https://github.com/docker-library/official-images/blob/master/library/node)、[Node.js BUILDING](https://github.com/nodejs/node/blob/main/BUILDING.md)。
|
||||
|
||||
因此 D-14 与 D-16 之间存在必须在首个 Beta 前解决的发布决策,当前建议的候选分层为:
|
||||
D-371/ADR-0464 已接受 D-14 与 D-16 的一致解,当前分层为:
|
||||
|
||||
| Tier | 3.0 候选架构 | 约束 |
|
||||
| --- | --- | --- |
|
||||
| Tier 1 supported | amd64、arm64;ppc64le、s390x 在对应 libc 镜像通过后纳入 | 固定官方 Node 24 patch,完整 migration、任务与恢复门禁 |
|
||||
| Tier 2 experimental | ARMv7 自定义 Node 24 构建 | 必须有可重复 toolchain、设备测试和明确无官方 binary 的提示,不进入默认 `latest` |
|
||||
| legacy-only | ARMv6、386;以及未通过 Node 24 门禁的架构 | 继续使用受维护的 2.x 镜像;不能把 Node 20/22 镜像标为满足 D-16 的完整 3.0 |
|
||||
| Tier 1 supported | amd64、arm64 | 固定 Node 24.18.0;完整原生 OS、OCI、migration、任务、恢复与资源门禁;只进入默认 3.0 manifest |
|
||||
| candidate | ppc64le、s390x | 在固定 Node 24 镜像、原生 runner/设备和同等级门禁全部具备前不得进入 3.0 manifest |
|
||||
| experimental blocked | ARMv7 | 当前没有已接受的 owner、可重复 toolchain 与设备证据;未来若补齐只能进入独立 experimental tag |
|
||||
| legacy-only | ARMv6、386 | 继续使用显式 2.x legacy line;不能把 Node 20/22 镜像标为满足 D-16 的完整 3.0 |
|
||||
|
||||
这个分层是待评审候选,不等于已经决定放弃小设备。若 Maintainers 要求 ARMv6、ARMv7、386 在 3.0 与 Node 24 同时成为正式支持项,则必须先拥有并长期维护对应 Node 24 构建链;否则应明确修改 D-16 或维持 2.x legacy support line,不能发布互相矛盾的承诺。
|
||||
这个分层不等于放弃小设备。2.x legacy line 继续承担约定兼容窗口,旧设备参与 3.0 集群的受限 Worker 协议仍是
|
||||
独立后续决策;但不能因此把没有受支持 Node 24、数据库恢复与原生资源证据的设备标成完整 3.0 ql-core。若
|
||||
Maintainers 要求 ARMv6、ARMv7 或 386 成为 3.0 正式支持项,必须先修改 D-16 或长期维护对应 Node 24 构建链,
|
||||
升级 release identity schema,并通过与现有 Tier 1 同等级的发布门禁。
|
||||
|
||||
### 7.10 资源基准与发布门禁
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# ADR-0006:Node.js 24 与多架构支持分层
|
||||
|
||||
- 状态:Proposed
|
||||
- 状态:Accepted(由 ADR-0464 机器化;新增架构必须先满足对应原生门禁)
|
||||
- 日期:2026-07-18
|
||||
- 决策者:QingLong Maintainers
|
||||
- 关联 RFC:[QL-RFC-0001](../QINGLONG_3_0_ARCHITECTURE_RFC.md)
|
||||
- 关联决策:D-05、D-14、D-16、D-17
|
||||
- Operationalized by:ADR-0464
|
||||
|
||||
官方参考:
|
||||
|
||||
@@ -166,3 +167,15 @@ Node 22 能覆盖更多 32-bit ARM 镜像,但把同一个 3.0 tag 在不同架
|
||||
- 接受 ARMv6、386 默认进入有 EOL 的 2.x legacy line,而不是伪装成 Node 24 3.0。
|
||||
- 接受旧设备可通过受限 Worker 兼容路径参与 3.0,但不拥有完整 ql-core 权限。
|
||||
- 接受任何 Node 22 的 3.0 compatibility runtime 都必须先显式修订 RFC。
|
||||
|
||||
## 9. 2026-08-20 实施状态
|
||||
|
||||
ADR-0464 已把本 ADR 从候选分层转为唯一发布身份中的机器契约。当前 3.0 Tier 1 精确为 `amd64`、`arm64`;
|
||||
`ppc64le`、`s390x` 保持候选,必须在进入默认 manifest 前取得固定 Node 24 镜像与同等级原生门禁;`arm/v7`
|
||||
因没有受维护 toolchain、owner 与设备证据而保持 experimental blocked;`arm/v6`、`386` 留在显式 `2.x`
|
||||
legacy line。该状态不是删除小设备支持:2.x 兼容窗口和未来受限 Worker 路径仍需单独关闭,但不能冒充完整 3.0
|
||||
ql-core 支持。
|
||||
|
||||
根 `ql3-release.json` 的 `qinglong/release-identity@v2` 是唯一事实源;release candidate、原生 OS matrix、OCI
|
||||
platform 列表、release set 和 version audit 都从它派生。任何新增 Tier 1 架构若没有对应原生 runner mapping,
|
||||
发布候选会失败关闭。实现未新增 workspace package、运行时依赖、服务、timer、数据库对象或 Edge 常驻成本。
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
# ADR-0464:机器化 Node 24 架构支持分层
|
||||
|
||||
- 状态:Accepted
|
||||
- 日期:2026-08-20
|
||||
- 关联 RFC:QL-RFC-0001 D-14、D-16、D-371
|
||||
- 关联 ADR:ADR-0006、ADR-0088、ADR-0281、ADR-0329、ADR-0463
|
||||
- Accepts and operationalizes:ADR-0006
|
||||
|
||||
## 上下文
|
||||
|
||||
QingLong 部署跨度从低性能路由设备、NAS 到 Cluster control plane 与 Worker 节点。2.x 历史镜像覆盖的架构数量,
|
||||
不能直接等价为完整 3.0 ql-core 的支持承诺:3.0 固定使用 Node 24 和 `node:sqlite`,而上游官方产物、镜像
|
||||
variant 与原生测试资源没有覆盖 ARMv6、ARMv7 和 386。此前 ADR-0006、D-14 与 D-16 仍是 Proposed,发布脚本
|
||||
虽然实际只生成 amd64/arm64,却没有一个机器可读、可审计的支持分层,因此文档、OCI manifest 与未来 matrix
|
||||
可能独立漂移。
|
||||
|
||||
继续把所有历史架构放进一个默认 manifest 会让低配设备用户收到虚假升级承诺;反过来静默删除这些架构也会
|
||||
损害现有部署。3.0 必须同时诚实声明完整核心的边界,以及 2.x legacy 和未来受限 Worker 兼容路径的边界。
|
||||
|
||||
## 决策
|
||||
|
||||
1. 接受 ADR-0006。3.0 ql-core 的唯一生产运行时基线固定为 Node `24.18.0`,engine 为
|
||||
`>=24.18.0 <25`;不同架构不能在同一个 3.0 tag 下静默回退 Node major。
|
||||
2. 根 `ql3-release.json` 升级为 `qinglong/release-identity@v2`,增加 exact-shape
|
||||
`architectureSupport`,作为版本、Node、workspace 边界和架构政策的单一发布事实源。
|
||||
3. 当前 Tier 1 精确为 `amd64`、`arm64`。只有它们进入默认 3.0 OCI platform 列表与原生 OS vulnerability
|
||||
matrix;每个 image 都必须在对应原生 runner 构建、扫描并绑定 immutable OCI evidence。
|
||||
4. `ppc64le`、`s390x` 为 candidates,不是已支持平台。只有获得固定 Node 24 image variant、原生
|
||||
runner/设备、数据库 migration/backup/restore、任务、资源和发布证据后,才能通过新的 release identity schema
|
||||
变更进入 Tier 1。
|
||||
5. `arm/v7` 当前为 experimental blocked:没有已接受的 maintainer owner、可重复 Node 24 toolchain 与设备门。
|
||||
补齐后也必须使用独立 experimental tag,不自动进入默认 manifest。
|
||||
6. `arm/v6`、`386` 为 legacy-only,显式绑定 `2.x` line。它们不能被标成满足 D-16 的完整 3.0 ql-core;
|
||||
EOL、迁移、备份和受限 Worker 兼容协议分别治理,不能由本 ADR 虚构完成状态。
|
||||
7. `scripts/lib/ql3-release-identity.cjs` 对 v2 字段、顺序和值失败关闭并深冻结架构列表;非 canonical JSON、
|
||||
添加未知字段或把 candidate 擅自加入 Tier 1 都会被拒绝。
|
||||
8. release candidate 从 Tier 1 派生 `linux/<arch>` platforms 和每镜像原生 runner matrix,并加入
|
||||
`architecture-support-tier` 必需门。Tier 1 没有受审 runner mapping 时发布失败,而不是降级为 QEMU 或跳过扫描。
|
||||
9. release identity digest 继续传入 candidate、image record、release set、catalog、publication closure 与
|
||||
deployment lock,支持矩阵变化会自然改变整条发布证据链;version audit 同时输出架构分层。
|
||||
10. 本实现不新增 workspace package、生产 dependency、runtime binary、服务、timer、端口、数据库对象或
|
||||
部署 workload。它只改变离线发布权威和 CI matrix,因此 Edge/Standalone 常驻资源闭包保持不变。
|
||||
|
||||
## 被拒绝的替代方案
|
||||
|
||||
### 保留文档表格,发布脚本继续硬编码 amd64/arm64
|
||||
|
||||
拒绝。两份事实源会在新增架构、Node patch 或 workflow 重构时漂移,且 release set 无法证明自己遵循哪份政策。
|
||||
|
||||
### 为架构政策新增一个 workspace package
|
||||
|
||||
拒绝。该政策只有一个根发布事实源和离线 validator,没有独立生产生命周期或依赖边界;新增 package 会重现
|
||||
单文件微包问题,并让低资源制品审计承担无收益的结构成本。
|
||||
|
||||
### 立即把 ppc64le、s390x 加入 Tier 1
|
||||
|
||||
拒绝。上游可能存在 Node binary 不等于当前 Debian image、数据库、恢复、资源和发布门已经完成;支持承诺必须
|
||||
由同等级证据产生。
|
||||
|
||||
### 把 ARMv7 直接归入 legacy-only 或 Tier 1
|
||||
|
||||
拒绝。它仍保留 experimental 的技术路径,但当前资产不足以发布;既不能掩盖未来可能性,也不能伪造现有支持。
|
||||
|
||||
### 让旧设备在 3.0 tag 下运行 Node 20/22
|
||||
|
||||
拒绝。同一版本跨架构拥有不同 SQLite、Web API、插件 engine 与安全周期,会使 ql-core capability 不可判断。
|
||||
|
||||
## 验证与证据
|
||||
|
||||
- release candidate、version transition、release set、catalog、publication closure 和 deployment lock 聚焦回归为
|
||||
`55/55`,覆盖 v2 schema、精确支持分层、来源派生 platforms/matrix、必需门与 post-create mutation 拒绝。
|
||||
- 架构漂移负向用例把 `ppc64le` 未经 schema 变更加入 Tier 1,release identity audit 必须失败关闭。
|
||||
- 完整 backend 工作区为 `1,503 total / 1,501 pass / 2 conditional skip / 0 fail`;其中包含一条既有、未跟踪且
|
||||
不会提交的用户测试,因此 D-371 提交范围对应 `1,502 total / 1,500 pass / 2 skip`。沙箱首次运行唯一失败为
|
||||
loopback `listen EPERM`,允许 `127.0.0.1` 的宿主环境原样重跑后零失败,未把权限限制误判成代码回归。
|
||||
- 18-package clean build 与逐包测试退出 0。release version、package boundary、cluster dependency、Edge import、
|
||||
cluster deployment 与 image release 六项审计全部 compatible;workspace 保持 18 packages、无 single/shallow
|
||||
package,证明本决策没有重新扩大 package 或依赖树。
|
||||
- 14 档 Local artifact audit 必须串行执行,因为并行 build/pack 会争用相同 package `dist`、产生不可信的短暂
|
||||
字节差;串行复核全部 compatible。基础 Edge/Standalone 为 `2,589,998 / 2,590,076` bytes,Application+AI 为
|
||||
`4,493,151 / 4,493,283` bytes,MCP 为 `7,315,930 / 7,316,038` bytes,与 D-370 稳定基线一致。
|
||||
|
||||
## 后续边界
|
||||
|
||||
- 制定 2.x legacy line 的可见 EOL、备份/迁移说明,不能只在内部 RFC 中声明。
|
||||
- 为旧设备参与 3.0 设计最小 capability、身份、调度限制和 EOL 明确的受限 Worker 协议;legacy Worker 不获得
|
||||
Plugin Host、任意 Tool 或控制面数据库访问权。
|
||||
- ppc64le/s390x 进入 Tier 1 或 ARMv7 进入 experimental 前,必须先提交独立 ADR、runner/toolchain owner 与原生
|
||||
证据;不能只修改数组让 CI 看似扩容。
|
||||
+20
-2
@@ -1,12 +1,30 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"schema": "qinglong/release-identity@v1",
|
||||
"schemaVersion": 2,
|
||||
"schema": "qinglong/release-identity@v2",
|
||||
"product": "qinglong3",
|
||||
"version": "3.0.0-alpha.0",
|
||||
"node": {
|
||||
"version": "24.18.0",
|
||||
"engine": ">=24.18.0 <25"
|
||||
},
|
||||
"architectureSupport": {
|
||||
"tier1": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
],
|
||||
"candidates": [
|
||||
"ppc64le",
|
||||
"s390x"
|
||||
],
|
||||
"experimentalBlocked": [
|
||||
"arm/v7"
|
||||
],
|
||||
"legacyOnly": [
|
||||
"arm/v6",
|
||||
"386"
|
||||
],
|
||||
"legacyLine": "2.x"
|
||||
},
|
||||
"workspacePackageCount": 18,
|
||||
"legacyRootPackageExcluded": true
|
||||
}
|
||||
|
||||
@@ -5,7 +5,14 @@ const path = require('node:path');
|
||||
const semver = require('semver');
|
||||
|
||||
const RELEASE_IDENTITY_PATH = 'ql3-release.json';
|
||||
const RELEASE_IDENTITY_SCHEMA = 'qinglong/release-identity@v1';
|
||||
const RELEASE_IDENTITY_SCHEMA = 'qinglong/release-identity@v2';
|
||||
const ARCHITECTURE_SUPPORT = Object.freeze({
|
||||
tier1: Object.freeze(['amd64', 'arm64']),
|
||||
candidates: Object.freeze(['ppc64le', 's390x']),
|
||||
experimentalBlocked: Object.freeze(['arm/v7']),
|
||||
legacyOnly: Object.freeze(['arm/v6', '386']),
|
||||
legacyLine: '2.x',
|
||||
});
|
||||
const MAX_RELEASE_IDENTITY_BYTES = 4096;
|
||||
const VERSION_PATTERN =
|
||||
/^3\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z]+([.-][0-9A-Za-z]+)*)?$/u;
|
||||
@@ -38,10 +45,11 @@ function normalizeReleaseIdentity(value) {
|
||||
'product',
|
||||
'version',
|
||||
'node',
|
||||
'architectureSupport',
|
||||
'workspacePackageCount',
|
||||
'legacyRootPackageExcluded',
|
||||
]) ||
|
||||
value.schemaVersion !== 1 ||
|
||||
value.schemaVersion !== 2 ||
|
||||
value.schema !== RELEASE_IDENTITY_SCHEMA ||
|
||||
value.product !== 'qinglong3' ||
|
||||
typeof value.version !== 'string' ||
|
||||
@@ -50,6 +58,15 @@ function normalizeReleaseIdentity(value) {
|
||||
!exactKeys(value.node, ['version', 'engine']) ||
|
||||
value.node.version !== '24.18.0' ||
|
||||
value.node.engine !== '>=24.18.0 <25' ||
|
||||
!exactKeys(value.architectureSupport, [
|
||||
'tier1',
|
||||
'candidates',
|
||||
'experimentalBlocked',
|
||||
'legacyOnly',
|
||||
'legacyLine',
|
||||
]) ||
|
||||
JSON.stringify(value.architectureSupport) !==
|
||||
JSON.stringify(ARCHITECTURE_SUPPORT) ||
|
||||
value.workspacePackageCount !== 18 ||
|
||||
value.legacyRootPackageExcluded !== true
|
||||
) {
|
||||
@@ -58,6 +75,15 @@ function normalizeReleaseIdentity(value) {
|
||||
return Object.freeze({
|
||||
...value,
|
||||
node: Object.freeze({ ...value.node }),
|
||||
architectureSupport: Object.freeze({
|
||||
...value.architectureSupport,
|
||||
tier1: Object.freeze([...value.architectureSupport.tier1]),
|
||||
candidates: Object.freeze([...value.architectureSupport.candidates]),
|
||||
experimentalBlocked: Object.freeze([
|
||||
...value.architectureSupport.experimentalBlocked,
|
||||
]),
|
||||
legacyOnly: Object.freeze([...value.architectureSupport.legacyOnly]),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -89,6 +115,7 @@ function readReleaseIdentity(root) {
|
||||
}
|
||||
|
||||
module.exports = Object.freeze({
|
||||
ARCHITECTURE_SUPPORT,
|
||||
MAX_RELEASE_IDENTITY_BYTES,
|
||||
RELEASE_IDENTITY_PATH,
|
||||
RELEASE_IDENTITY_SCHEMA,
|
||||
|
||||
@@ -17,6 +17,18 @@ const PREDICATE_TYPE =
|
||||
'https://qinglong.dev/attestations/release-candidate-contract/v1';
|
||||
const MAX_REPORT_BYTES = 1024 * 1024;
|
||||
const RELEASE_SCOPES = Object.freeze(['all', 'cluster', 'local']);
|
||||
const NATIVE_ARCHITECTURES = Object.freeze({
|
||||
amd64: Object.freeze({
|
||||
runner: 'ubuntu-24.04',
|
||||
node_arch: 'x64',
|
||||
image_arch: 'amd64',
|
||||
}),
|
||||
arm64: Object.freeze({
|
||||
runner: 'ubuntu-24.04-arm',
|
||||
node_arch: 'arm64',
|
||||
image_arch: 'arm64',
|
||||
}),
|
||||
});
|
||||
const LOCAL_IMAGES = Object.freeze([
|
||||
Object.freeze({
|
||||
image: 'local',
|
||||
@@ -92,6 +104,18 @@ function selectedImages(scope) {
|
||||
return [...CLUSTER_IMAGES, ...LOCAL_IMAGES];
|
||||
}
|
||||
|
||||
function nativeArchitectureMatrix(architectureSupport) {
|
||||
return architectureSupport.tier1.map((architecture) => {
|
||||
const native = NATIVE_ARCHITECTURES[architecture];
|
||||
if (!native) {
|
||||
fail(
|
||||
`Tier 1 architecture lacks a native release runner: ${architecture}`,
|
||||
);
|
||||
}
|
||||
return native;
|
||||
});
|
||||
}
|
||||
|
||||
function validateIdentity(options) {
|
||||
if (
|
||||
typeof options.version !== 'string' ||
|
||||
@@ -147,6 +171,9 @@ function createReleaseCandidateContract(options) {
|
||||
})
|
||||
.sort((left, right) => left.name.localeCompare(right.name, 'en'));
|
||||
const images = selectedImages(options.releaseScope);
|
||||
const nativeArchitectures = nativeArchitectureMatrix(
|
||||
releaseIdentity.architectureSupport,
|
||||
);
|
||||
const imageManifests = images.map((image) => {
|
||||
const manifest = readJson(
|
||||
path.join(root, image.runtime_root, 'package.json'),
|
||||
@@ -182,24 +209,14 @@ function createReleaseCandidateContract(options) {
|
||||
});
|
||||
});
|
||||
const publishMatrix = images.map(({ dockerfile, target, ...image }) => image);
|
||||
const osMatrix = images.flatMap((image) => [
|
||||
{
|
||||
const osMatrix = images.flatMap((image) =>
|
||||
nativeArchitectures.map((architecture) => ({
|
||||
image: image.image,
|
||||
runner: 'ubuntu-24.04',
|
||||
node_arch: 'x64',
|
||||
image_arch: 'amd64',
|
||||
...architecture,
|
||||
dockerfile: image.dockerfile,
|
||||
target: image.target,
|
||||
},
|
||||
{
|
||||
image: image.image,
|
||||
runner: 'ubuntu-24.04-arm',
|
||||
node_arch: 'arm64',
|
||||
image_arch: 'arm64',
|
||||
dockerfile: image.dockerfile,
|
||||
target: image.target,
|
||||
},
|
||||
]);
|
||||
})),
|
||||
);
|
||||
const unsigned = {
|
||||
schemaVersion: 1,
|
||||
schema: SCHEMA,
|
||||
@@ -220,7 +237,18 @@ function createReleaseCandidateContract(options) {
|
||||
),
|
||||
nodeVersion: releaseIdentity.node.version,
|
||||
nodeEngine: releaseIdentity.node.engine,
|
||||
platforms: ['linux/amd64', 'linux/arm64'],
|
||||
architectureSupport: {
|
||||
tier1: [...releaseIdentity.architectureSupport.tier1],
|
||||
candidates: [...releaseIdentity.architectureSupport.candidates],
|
||||
experimentalBlocked: [
|
||||
...releaseIdentity.architectureSupport.experimentalBlocked,
|
||||
],
|
||||
legacyOnly: [...releaseIdentity.architectureSupport.legacyOnly],
|
||||
legacyLine: releaseIdentity.architectureSupport.legacyLine,
|
||||
},
|
||||
platforms: releaseIdentity.architectureSupport.tier1.map(
|
||||
(architecture) => `linux/${architecture}`,
|
||||
),
|
||||
},
|
||||
workspace: {
|
||||
packageCount: workspacePackages.length,
|
||||
@@ -248,6 +276,7 @@ function createReleaseCandidateContract(options) {
|
||||
requiredGates: [
|
||||
'package-boundary',
|
||||
'source-tag-version-identity',
|
||||
'architecture-support-tier',
|
||||
'native-os-vulnerability',
|
||||
'multiarch-oci-layout',
|
||||
'production-dependency-audit',
|
||||
|
||||
@@ -284,11 +284,20 @@ function auditReleaseVersionContract(rootInput = DEFAULT_ROOT) {
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
schemaVersion: 1,
|
||||
schemaVersion: identity.schemaVersion,
|
||||
schema: RELEASE_IDENTITY_SCHEMA,
|
||||
version: identity.version,
|
||||
nodeVersion: identity.node.version,
|
||||
nodeEngine: identity.node.engine,
|
||||
architectureSupport: Object.freeze({
|
||||
tier1: Object.freeze([...identity.architectureSupport.tier1]),
|
||||
candidates: Object.freeze([...identity.architectureSupport.candidates]),
|
||||
experimentalBlocked: Object.freeze([
|
||||
...identity.architectureSupport.experimentalBlocked,
|
||||
]),
|
||||
legacyOnly: Object.freeze([...identity.architectureSupport.legacyOnly]),
|
||||
legacyLine: identity.architectureSupport.legacyLine,
|
||||
}),
|
||||
legacyRootPackageVersion: legacyRoot.version,
|
||||
legacyRootExcluded: true,
|
||||
workspacePackageCount: workspacePaths.length,
|
||||
|
||||
@@ -41,13 +41,28 @@ test('freezes an independent low-resource local release family', () => {
|
||||
assert.equal(contract.workspace.packageCount, 18);
|
||||
assert.equal(
|
||||
contract.compatibility.releaseIdentitySchema,
|
||||
'qinglong/release-identity@v1',
|
||||
'qinglong/release-identity@v2',
|
||||
);
|
||||
assert.deepEqual(contract.compatibility.architectureSupport, {
|
||||
tier1: ['amd64', 'arm64'],
|
||||
candidates: ['ppc64le', 's390x'],
|
||||
experimentalBlocked: ['arm/v7'],
|
||||
legacyOnly: ['arm/v6', '386'],
|
||||
legacyLine: '2.x',
|
||||
});
|
||||
assert.deepEqual(contract.compatibility.platforms, [
|
||||
'linux/amd64',
|
||||
'linux/arm64',
|
||||
]);
|
||||
assert.match(
|
||||
contract.compatibility.releaseIdentityDigest,
|
||||
/^sha256:[a-f0-9]{64}$/u,
|
||||
);
|
||||
assert.match(contract.contractDigest, /^sha256:[a-f0-9]{64}$/u);
|
||||
assert.equal(
|
||||
contract.requiredGates.includes('architecture-support-tier'),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
contract.requiredGates.includes('offline-deployment-lock-materialization'),
|
||||
true,
|
||||
@@ -81,6 +96,33 @@ test('closes the cluster release family with the Worker image', () => {
|
||||
);
|
||||
assert.equal(contract.releasePlan.clusterEvidenceRequired, true);
|
||||
assert.equal(contract.releasePlan.osMatrix.length, 8);
|
||||
assert.deepEqual(
|
||||
[
|
||||
...new Set(
|
||||
contract.releasePlan.osMatrix.map((entry) => entry.image_arch),
|
||||
),
|
||||
],
|
||||
['amd64', 'arm64'],
|
||||
);
|
||||
assert.deepEqual(
|
||||
contract.releasePlan.osMatrix.slice(0, 2).map((entry) => ({
|
||||
runner: entry.runner,
|
||||
node_arch: entry.node_arch,
|
||||
image_arch: entry.image_arch,
|
||||
})),
|
||||
[
|
||||
{
|
||||
runner: 'ubuntu-24.04',
|
||||
node_arch: 'x64',
|
||||
image_arch: 'amd64',
|
||||
},
|
||||
{
|
||||
runner: 'ubuntu-24.04-arm',
|
||||
node_arch: 'arm64',
|
||||
image_arch: 'arm64',
|
||||
},
|
||||
],
|
||||
);
|
||||
assert.equal(
|
||||
contract.requiredGates.includes('edge-and-standalone-rollout'),
|
||||
false,
|
||||
|
||||
@@ -86,11 +86,18 @@ function replaceVersion(
|
||||
|
||||
test('audits one source-derived QingLong 3 release identity', () => {
|
||||
assert.deepEqual(auditReleaseVersionContract(root), {
|
||||
schemaVersion: 1,
|
||||
schema: 'qinglong/release-identity@v1',
|
||||
schemaVersion: 2,
|
||||
schema: 'qinglong/release-identity@v2',
|
||||
version: SOURCE_VERSION,
|
||||
nodeVersion: '24.18.0',
|
||||
nodeEngine: '>=24.18.0 <25',
|
||||
architectureSupport: {
|
||||
tier1: ['amd64', 'arm64'],
|
||||
candidates: ['ppc64le', 's390x'],
|
||||
experimentalBlocked: ['arm/v7'],
|
||||
legacyOnly: ['arm/v6', '386'],
|
||||
legacyLine: '2.x',
|
||||
},
|
||||
legacyRootPackageVersion: LEGACY_VERSION,
|
||||
legacyRootExcluded: true,
|
||||
workspacePackageCount: 18,
|
||||
@@ -226,6 +233,19 @@ test('rejects invalid SemVer, downgrade, plan mutation and a symbolic-link ident
|
||||
/identity shape or value is incompatible/,
|
||||
);
|
||||
copyFile(root, fixture, 'ql3-release.json');
|
||||
const architectureDrift = JSON.parse(
|
||||
fs.readFileSync(invalidIdentityPath, 'utf8'),
|
||||
);
|
||||
architectureDrift.architectureSupport.tier1.push('ppc64le');
|
||||
fs.writeFileSync(
|
||||
invalidIdentityPath,
|
||||
`${JSON.stringify(architectureDrift, null, 2)}\n`,
|
||||
);
|
||||
assert.throws(
|
||||
() => auditReleaseVersionContract(fixture),
|
||||
/identity shape or value is incompatible/,
|
||||
);
|
||||
copyFile(root, fixture, 'ql3-release.json');
|
||||
fs.renameSync(
|
||||
path.join(fixture, 'ql3-release.json'),
|
||||
path.join(fixture, 'identity-target.json'),
|
||||
|
||||
Reference in New Issue
Block a user