# ADR-0196:本机镜像证明化多架构发布契约 - 状态:Accepted(共享发布契约、CycloneDX、许可证/漏洞门与 CI 接线已实现; 本机双架构 OCI 和远端 GHCR 发布记录尚未取得) - 日期:2026-07-29 - 关联 RFC:QL-RFC-0001 D-14、D-42、D-61、D-126、D-128、D-175、 D-184、D-185、D-186 - 关联 ADR:ADR-0042、ADR-0090、ADR-0128、ADR-0185、ADR-0194、 ADR-0195 ## 背景 ADR-0195 已证明同一个 AI-excluded image 能在 Edge 128 MiB/64 PID 和 Standalone 256 MiB/256 PID envelope 中完成真实 SQLite 生命周期,但候选 image ID 不是发布 authority。仓库已有 control/admin 的双架构、SBOM、 provenance、签名和远端回读流程;为本机镜像复制一套 privileged workflow 会产生独立 action pin、OIDC identity、漏洞策略和验证漂移。 本机 build manifest 还比 production manifest 多一个只用于 Drizzle schema 类型编译的 `drizzle-orm`。直接套用“两个 manifest dependencies 完全相同”的 Cluster 假设会迫使 build-only dependency 进入 runtime,或让 SBOM 隐藏真实 差异。 ## 决策 ### 1. 三个镜像共用一个发布 authority `.github/workflows/ql3-image-release.yml` 是 QingLong 3.0 唯一 backend image 发布 workflow,精确矩阵固定为: - `control → qinglong3-cluster-control`; - `admin → qinglong3-cluster-admin`; - `local → qinglong3-local-application`。 每项分别绑定 exact Dockerfile、production dependency root 和最终 digest。 不得增加第四个 repository、让三个 profile 共用 digest,或为本机镜像复制 第二套 OIDC workflow。原 `audit:cluster-image-release:ql3` 作为兼容命令保留, 新的主命令是 `audit:image-release:ql3`;这不增加 workspace package。 ADR-0253 已收紧入口:发布只接受显式 dispatch 到与输入 QingLong 3 SemVer 完全一致的 protected `v3` tag, 不再由 tag push 自动发布。私密 Worker management source-aware gate 先在受保护 ephemeral runner 只读执行; registry/OIDC/attestation 写权限只存在于依赖该 gate 的 GitHub-hosted publisher。workflow 没有 `pull_request` trigger;所有第三方 action继续固定完整 commit SHA。 ### 2. 本机 SBOM 明确区分 runtime root 与 build-only root 本机 build/runtime manifest 共享制品身份 `@qinglong/local-application-image@3.0.0-alpha.0`。production root 只允许 `croner@7.0.8`、`semver@7.7.4`;build root 必须精确等于 production root 加 受审的 `drizzle-orm@1.0.0-rc.4`,其他差异失败关闭。 共享 CycloneDX 1.5 生成器只从 production lock 遍历运行时可达图,再在 builder lock 中从相同 production roots 复算并比较。因此 build-only Drizzle、TypeScript、 types 与 AI 均不能进入本机 SBOM。当前本机图固定为: - 2 个外部 component; - 10 个内部 component; - 12 个 component、13 个 dependency node; - root 为 `pkg:npm/%40qinglong/local-application-image@3.0.0-alpha.0`。 实际 read-only image inventory 必须与 12 个 component 精确对账,并继续通过 ADR-0195 的 640 files/5 MiB、无 symlink/special file 门。 ### 3. 许可证、漏洞和容量都必须失败关闭 每个 runtime component 必须携带且只能携带一个受审 SPDX license ID。共享 allowlist 当前为 `0BSD`、`Apache-2.0`、`BSD-2-Clause`、`ISC`、`MIT`、 `Python-2.0`、`Unlicense`;新增或缺失 license 必须先修改 ADR 与变异测试。 本机实际闭包只使用 Apache-2.0、ISC、MIT。 每个 release matrix 项在 build/push 前对自己的 production lock 执行 `npm audit --omit=dev --audit-level=high`。安全公告服务不可用、high 或 critical advisory 都使发布失败。ADR-0254 已补充独立 OS/base image CVE scanner:三个 image 的两个 native architecture 都在 publish 前以 digest-pinned base、Trivy 0.70.0、HIGH/CRITICAL、OS-only、unfixed 不忽略执行, 例外必须经过最多 30 天的 owner/ticket/purl/image-scoped 生命周期。 本机 OCI 每个平台的 compressed layer 总预算固定为 128 MiB;Cluster 既有 512 MiB 门保持不变。该值是 registry/传输门,不是路由器实际闪存容量承诺。 ### 4. 原生 image gate 与 attested OCI gate分离 原生 amd64/arm64 `local-image` job 继续执行候选镜像 identity、12-package inventory、CycloneDX 对账、router stress 以及 Edge/Standalone fresh live contract。共享 `image-oci` job 则以 `control|admin|local` 三项 exact Dockerfile matrix 生成 `linux/amd64,linux/arm64` OCI layout,并要求: - 每个平台 config 固定 Linux、`65532:65532`、唯一 local application entrypoint、`NODE_ENV=production`; - `edge,standalone` 与 `ai=excluded` labels、source revision 和 version 精确匹配; - 每个平台恰有一个 digest-bound attestation manifest; - attestation 同时包含 SPDX-2.3 与 SLSA provenance v1; - SPDX 中 `/opt/qinglong/node_modules` 的 npm purl 与 12-component CycloneDX 完全相同; - provenance 只允许 source revision 和不含 credential 的标准 proxy args。 ### 5. 远端发布只以 digest 为 subject 每个 profile 的 Buildx push digest分别接受: 1. BuildKit SBOM; 2. BuildKit maximum provenance; 3. Cosign GitHub OIDC keyless signature; 4. GitHub SLSA provenance attestation; 5. GitHub CycloneDX attestation。 发布后同一 job 必须从 GHCR 回读 `IMAGE@DIGEST`,复验双平台/双 attestation manifest,随后以精确 `.github/workflows/ql3-image-release.yml@GITHUB_REF` certificate identity 验证 Cosign,并以 repository、workflow、source commit、 source ref 和 OCI bundle 分别验证 SLSA 与 CycloneDX。tag 仍只是发现入口, D-184 Compose 私有输入必须使用 digest。 ## 当前证据 - 本机 CycloneDX:12 components、13 dependency nodes; - 实际 arm64 image `sha256:b99e61e90c84fc113b03d8d8d237ee3f5cefc3f800d73708b30ed591ec4a94b0` 为 `65532:65532`、251,932,346 uncompressed bytes;inventory 为 611 files/4,897,102 bytes,并与 CycloneDX 精确一致; - SBOM/许可证变异测试:10/10; - OCI layout 结构/config/attestation 变异测试:9/9; - release workflow 变异测试:19/19; - D-185 image contract:6/6; - control/admin/local 三份 production lock 的联网 npm audit: 0 vulnerabilities; - 共享 CI/release 静态审计: `images=[control,admin,local]`、amd64/arm64、inventory/attestation 全开启。 本机真实双架构 OCI 本轮未取得。临时 `docker-container` builder 已确认同时支持 amd64/arm64,但 BuildKit 在执行项目 Dockerfile 前,从 Docker Hub OAuth endpoint 获取 `docker/buildkit-syft-scanner:stable-1` token 超时。该外部失败与 ADR-0128 已记录的 admin 证据缺口相同,不能记作 OCI 成功;临时 builder、 BuildKit container、隔离 Docker config 与未完成 OCI 输出均已删除。 ## 尚未完成的 Release Gate - GitHub-hosted `image-oci` 三 profile 的真实成功记录; - GHCR 中可回读的本机双架构 manifest digest; - 对该 digest 的 Cosign、SLSA、CycloneDX 三类远端 verify 记录; - GitHub-hosted 六矩阵 base image/OS package scan 的真实成功记录; - 固定低配路由器的下载大小、解压占用、冷启动、断电与升级/回滚实证; - D-184 service activation controller 使用新 digest 的升级与回退闭环。 ## 验证 - `pnpm sbom:local-image:ql3` - `pnpm audit:image-release:ql3` - `pnpm audit:local-image:ql3` - `node --test test/back/ql3ClusterImageSbom.test.cjs` - `node --test test/back/ql3ClusterOciLayoutAudit.test.cjs` - `node --test test/back/ql3ClusterImageReleaseAudit.test.cjs` - `node --test test/back/ql3LocalImageAudit.test.cjs` - 在实际 image 内执行 `ql3-cluster-image-sbom.cjs --image=local --inventory-root=...` - 在可获取 BuildKit scanner 的环境执行 CI `image-oci` 和受保护 release workflow,并保存最终远端 verify 记录。