CI & Actions
每个 workflow 都位于 .github/workflows/。下面的部分按触发方式对它们分组:基于 git 事件自动触发,或通过 workflow_dispatch 和计划执行的由维护者调用/建议性 workflow。
自动工作流
质量门禁(ci.yml)
在每个针对 master 的 PR 以及向 master 的可信推送时触发。包含多个矩阵分支的组合任务:
- fmt:
cargo fmt --all -- --check - history-guard:获取完整历史记录,并针对
origin/master检查正在测试的提交;拉取请求使用显式的github.event.pull_request.head.sha,而受信任的推送和合并队列运行使用github.sha。该防护及其测试夹具会拒绝空的git merge-base,防止嫁接出的第二个根提交在合并后导致git blame崩溃 - 代码检查:
cargo clippy --workspace --exclude zeroclaw-desktop --all-targets --features ci-all -- -D warnings,然后执行cargo doc --no-deps --workspace --exclude zeroclaw-desktop(通过.cargo/config.toml中的build.rustdocflags将 rustdoc 警告视为致命错误;排除 desktop 以匹配xtask build_api/ docs-deploy,并避免 lint 运行器上的 GTK/glib-sys),以及注释规范门禁 - build:为
x86_64-unknown-linux-gnu和aarch64-apple-darwin无条件运行矩阵;当包归属选中其中任一命令时,单独的 windows-build 作业会检查x86_64-pc-windows-msvc。 - check:对工作区(不含
zeroclaw-desktop)执行两轮启用警告即失败的检查:一轮不启用默认特性;另一轮启用默认特性并附带--all-targets,这是唯一在默认特性范围内编译测试目标的一轮。全特性范围由 lint 负责编译和类型检查,其 clippy 阶段在同一工作区上运行--features ci-all --all-targets;单独再运行一个全特性的cargo check只会重复编译,不会增加覆盖范围。 - check-32bit:
i686-unknown-linux-gnu,不启用任何默认特性 - check-plugin-backends:由
scripts/ci/plugin_backend_change_filter.sh选中。在每个执行后端下编译zeroclaw-plugins,并在plugins-wasm-cranelift下运行组件、运行时准入和根 CLI 插件测试。单独的plugin artifact smoke任务使用相同的特性构建zeroclaw二进制文件,并针对该二进制文件运行scripts/ci/plugin_artifact_smoke.sh,其使用的软件包来自scripts/ci/build_plugin_smoke_packages.sh。该冒烟测试接受任意zeroclaw二进制文件,使用一次性配置目录和scripts/ci/plugin_smoke_provider.py中的脚本化本地提供方,并驱动操作员自己的命令:plugin install、config set,以及执行该工具的一次智能体对话轮次。它检查以下内容:已安装的插件在操作员启用插件系统、开启发现并批准该工具之前保持未使用状态;为不请求这些值的软件包配置的值不会被下发;摘要错误的载荷、非组件的载荷以及属于其他 WIT world 的组件均被拒绝;严格签名模式拒绝未签名、不受信任或被编辑过的软件包,同时运行受信任的软件包。该任务的最后一步以相反的预期运行冒烟测试,并要求其返回为失败检查保留的退出状态。智能体对话轮次还依赖于路径过滤器未选中的 crate,例如zeroclaw-providers、zeroclaw-tools、zeroclaw-memory和zeroclaw-api,因此这些 crate 的故障会首先在合并队列和master运行中显现,而该任务在这些运行中始终执行。 - bench:基准测试编译检查;仅在推送到
master、合并队列运行以及手动触发时运行。PR 会跳过该检查(门禁将跳过视为非致命),因此基准测试的腐化仍可在发布前被发现,同时无需在每个 PR 上消耗编译资源。 - test:独立的固件协议主机门禁(来自
scripts/ci/firmware_protocol_gate.sh),以及在 Linux 上运行的cargo nextest run --locked --workspace --exclude zeroclaw-desktop,其中包括配置写入隔离和 Fluent 覆盖率(无裸露面向用户字符串)架构守卫。最后一个信息性步骤使用--run-ignored only针对已录制的固件数据,回放网关黄金帧场景(crates/zeroclaw-gateway/tests/golden_frames.rs:WebSocket 聊天、webhook JSON 与 SSE、/api/events以及 ACP)。该步骤复用本作业的构建产物,将任何偏差以警告形式写入步骤摘要,且从不导致作业失败;在网关通过 RPC 与核心通信之前,它将保持信息性质。 - test-tool-features:选择启用的 SaaS、编码 CLI 及外部适配器的测试,Cargo
default已不再构建这些内容:在根目录、config、tools、runtime、channels 和 gateway 库测试以及根目录报告模板集成测试上运行cargo nextest run,并附带--features tools-compat。lint会在ci-all下编译相同的代码,但不运行任何测试。 - memory-postgres-test:
cargo nextest run --locked -p zeroclaw-memory --features memory-postgres,随后针对临时 PostgreSQL 17 服务运行该 crate 的串行忽略(ignored)验收测试 - parallel-runtime-test:来自
scripts/ci/parallel_runtime_test_gate.sh的重复同进程运行时/通道测试,与主测试任务并行运行,适用于相关 PR 路径,并在master推送和合并队列运行中无条件执行 - security:
cargo deny check - nix-eval:评估 NixOS 模块断言(
nixos-module-evalflake 检查) - docs-style: markdown lint、em-dash 书面语检查,以及通过
scripts/ci/docs_quality_gate.sh和scripts/ci/docs_links_gate.sh进行变更行链接门禁
Required gate 中的 Linux 任务运行在 Blacksmith 上,唯一例外是依赖内核的 Landlock 检查。编译密集型机器组使用 blacksmith-8vcpu-ubuntu-2404。运行 fmt、变更检测器、仓库守卫、文档和策略门禁、Nix 检查、容器冒烟测试、security、web-permission-tests 以及 gate 聚合器本身的管理性任务组使用 blacksmith-4vcpu-ubuntu-2404。唯一有意保留的 Linux 例外是 test-landlock,它继续运行在 GitHub 托管的 ubuntu-latest 上,因为 Landlock LSM 检查依赖托管内核。Windows 任务所有者恢复使用 blacksmith-8vcpu-windows-2025;macOS 和其他 Windows 任务保留 GitHub 托管的平台运行器。这种拆分是因为 2026-09-14 GitHub 托管运行器分配在整个组织范围内停滞,而 Blacksmith 仍在正常服务。管理性任务迁移后,托管服务中断只会影响平台特定检查,而不会导致整个 required gate 失败。所有任务都直接声明其运行器标签和缓存提供方输入;tests/architecture/ci_runner_labels.rs 会检查 Linux 清单(编译机器组、管理性机器组和托管 Linux 允许列表),以及仅用于恢复的 Windows 运行器和缓存写入方契约。工作任务不等待 fmt,这是有意做出的权衡:格式化失败的 PR 会额外消耗运行器资源。CI Required Gate 仍直接依赖 fmt 并汇总必需结果,因此格式错误仍会阻止合并。通过标签单独触发的建议性 Windows 工作流不在该门禁范围内,其 Linux 选择器也运行在 Blacksmith 4vcpu 类型上。分支保护规则固定使用复合门禁任务。master 推送运行保持相同的质量信号,同时为后续 PR 运行预热可信的 Rust 缓存。
更改 [workspace.package] version 为稳定版 X.Y.Z 的拉取请求和合并队列条目也会运行 crates-preflight。该任务调用 pub-crates.yml,传入 stage: preflight,不传递任何密钥,并使用编译集群的 runner 标签。crates-preflight-changes 通过 scripts/ci/crates_preflight_trigger.sh 判断是否需要运行。其他所有变更都会跳过这两个任务,而被跳过的任务不会导致门禁失败。由于该任务是没有 runs-on 的可复用工作流调用,tests/architecture/ci_runner_labels.rs 会单独对其进行盘点,并要求其使用编译集群的标签。
运行器清单同时覆盖 ci.yml 和 windows-tests.yml,并使用带工作流限定的作业标识,确保同名作业不会相互遮蔽。Linux/macOS 的 build 矩阵不等待任何检测器。单独的 Windows 编译、服务和恢复作业则等待 Blacksmith windows-required-changes 检测器。该检测器通过 cargo metadata --locked --offline --no-deps 读取包归属关系,不进行注册表解析或下载。非 Rust 的 path-changes 检测器与此包图保持独立。
仅针对 master 分支的推送,计算密集型作业(以及用于判断 Windows clippy 和并行运行时作业是否执行的检测器)会先在 master-debounce 中等待五分钟。master 推送常以突发形式到达,而运行级别的 cancel-in-progress 只会在被取代的推送的编译集群已经启动之后才将其取消。加入等待后,在等待窗口内被取代的推送会在那个轻量作业仍处于休眠时就被取消。该作业在拉取请求和合并队列运行中会被跳过,而被跳过的作业无需占用运行器即可直接完成,因此它们的延迟保持不变。由于 GitHub 会通过隐式的 success() 将被跳过的上游作业的状态向下游传播,master-debounce 的每个下游作业都带有显式的 !cancelled() 条件,以及对每个依赖项的结果检查;若某个作业未如此配置,tests/architecture/ci_runner_labels.rs 将失败。CI Required Gate 依赖 master-debounce,因此防抖失败不会在门禁为绿色的同时留下被跳过的编译作业。代价是 master 运行开始编译前会多出五分钟,同时 master 的 Rust 缓存播种也会相应推迟同样的时长。
Fresh 所需的 CI 通常是 Cargo 实际运行的那些 surface 所共享的证据。对同一 head、target 和 feature set 重新在本地运行相同的 Cargo 命令,只是重复确认,而不是更强的证明。在请求额外的 Cargo 或 Clippy 之前,请将变更的 surface 与当前工作流文件以及 PR 上实际执行的检查进行比较。额外验证应放在所需 gate 不能证明审查对象的地方:
- 某个平台收到了编译检查,但没有收到测试;
- 平台、crate 或路径位于所需的 lint 作业之外;
- 桌面更改没有触发桌面工作流;
- 发布目标位于 PR 矩阵之外,仅由 release/manual 工作流覆盖;
- 过期、已取消、已跳过或不可用的 CI 不是新的证据。
当定义或导入受 feature 条件控制时,请将其 cfg 谓词与每个使用方进行比较。验证启用配置和每个相关的禁用配置:启用 feature 的检查可以证明使用方仍能正常工作,而工作区范围的 no-default-features 检查则能捕获会产生警告的不匹配,例如未使用的私有定义或导入。该检查会在不带 --all-targets 的情况下运行 cargo check,因此从不会编译测试目标:一个受普通 test 条件控制、且其唯一调用方都位于某个 feature 后面的辅助函数,反而会由 default-features/all-targets 检查流程捕获。当所需的两个 CI 配置都没有覆盖已更改的谓词时,仍然需要针对性的 feature 组合。
PostgreSQL 内存任务会在启用 memory-postgres 的情况下运行所有非 ignored 的 zeroclaw-memory 测试,然后将 ZEROCLAW_TEST_POSTGRES_URL 指向其一次性服务,串行运行该 crate 中的 ignored 测试。要在本地复现依赖数据库的那一半测试,请提供一个隔离的 PostgreSQL 数据库,并运行 ZEROCLAW_TEST_POSTGRES_URL=postgres://<user>:<password>@127.0.0.1:5432/<database> cargo test --locked -p zeroclaw-memory --features memory-postgres -- --ignored --test-threads=1。每个验收测试都拥有一个名称唯一的 schema,且只删除该 schema。
Windows 必需编译仍会自动运行,无需标签,但它只检查命令实际能够编译的代码。在 PR 上,scripts/ci/windows_test_scope.py --required-jobs 会复用包根分类和反向依赖闭包。当受影响的闭包包含 zeroclaw 时,根命令运行;当其包含 zeroclaw-channels 时,voice-wake 命令运行。内部路径依赖边会保守地包括可选依赖、目标特定依赖、构建依赖和开发依赖。仅编辑独立的 ZeroCode 时,不会运行这些无关命令。工作区清单、锁文件、工具链、Cargo 配置、工作流/Action 控制项、未知路径,以及缺失或格式错误的证据,都会选中所有必需的 Windows 作业。主分支推送和合并队列构建始终运行所有这些作业。Linux/macOS 编译仍然无条件执行。
服务冒烟测试会构建 windows_service_smoke_fixture,该夹具自行管理其模拟守护进程和参数分发。对根 CLI 文件 src/main.rs 的修改不会自动选中它。服务源码、运行时模块接线、夹具与冒烟脚本、config/spawn 依赖以及相关的清单和构建控制项仍保留覆盖范围。任务所有者恢复作业仍保留直接的控制平面和运行时模块接线触发条件。当包清单和构建脚本的反向依赖闭包触及 runtime 时,它们会选中这些原生作业;无关的网关或独立应用清单则不会。恢复用 shell 入口会委托给同一个包选择器,而不是另行维护第二份归属注册表。
恢复测试在各自的 windows-task-owner-recovery 作业中运行,运行器为 blacksmith-8vcpu-windows-2025。由于其运行时单元测试工具无法复用 cargo check 的输出,该作业与编译并行执行。未被选中的作业会在分配 Windows 运行器之前跳过。CI Required Gate 直接读取检测器以及每个必需的 Windows 作业的结果,拒绝缺失的选择输出,并要求每个被选中的作业都必须成功。被选中但跳过、失败或被取消的作业,不能视为有意跳过。
Recovery 直接调用固定版本的 Swatinem/rust-cache action,并设置 shared-key: windows-recovery-blacksmith,而不是假定 GitHub 构建运行器的缓存具有兼容的 Rust 环境哈希。只有在 refs/heads/master 上成功的 push 运行才会保存这个专用依赖缓存;PR 和 merge-queue 运行仅恢复该缓存。缓存未命中时会正常构建,并且不会跳过任何测试。默认的工作区产物清理保持启用,因此运行时测试框架仍会编译。这会新增一个消耗仓库存储的缓存命名空间;编译和服务冒烟测试保留其现有的运行器和缓存。
带标签门控的建议性 Windows 测试(windows-tests.yml)
使用 ci:windows 的场景是:在 Windows 上执行测试能够弥补仅编译检查留下的重要缺口。典型情况包括进程或服务生命周期变更、PowerShell 执行、文件系统与路径处理、编码、原生依赖,以及 Windows CI 选择或执行逻辑的变更。它并非每个 Rust PR 的默认标签,也不适用于仅修改文档的变更或与 Windows 无关的 Linux 测试失败。
给候选版本足够稳定、可以产出有价值的 Windows 验证证据后添加该标签。在以 Windows 为重点的迭代期间,每次更新都需要再次运行时保留该标签;一次性验证完成后,如果后续推送不再需要该覆盖范围,则移除该标签。无需该标签,必需的 Windows 编译仍会自动执行。每晚平台测试是合并后的兜底措施,不能替代变更需要 Windows 专项验证时的合并前执行。
将 ci:windows 标签添加到以 master 为目标分支的 PR 上,即可选择启用建议性的 Windows 测试。添加该标签会为当前 PR 版本启动工作流。只要标签保持存在,推送(push)和重新打开(reopen)事件都会再次运行该工作流。添加无关标签既不会重新运行测试,也不会取消正在进行的 Windows 运行。移除该标签会停止后续的自动运行,但不会取消已在进行中的运行。该标签是计算成本开关,而非安全审批;GitHub 的 fork 工作流常规审批要求仍然适用。
该工作流使用普通的只读 pull_request 事件,而非 pull_request_target,并测试该事件冻结的合并 SHA。它没有手动触发器(manual dispatch)。将其与 ci.yml 分开,可使添加标签时仅启动建议性工作流,而不会重新启动必需的 CI。必需的门禁不依赖于这些作业。
在引用通过结果之前,请先阅读选择器摘要以及 Windows 作业自身的结论和日志。Windows 作业使用了 continue-on-error,因此仅凭工作流显示为绿色的结果,并不能证明其测试已通过。记录所测试的修订版本、所选范围以及任何失败的执行阶段;利用日志区分测试失败与选择或工作流失败。
Linux 选择器会检查格式,比较 PR 基础 SHA 与已检出的合并修订版本,并生成 Cargo 工作区元数据。它将基线 mode、JSON packages、简要 reason 和 needs_plugin_host 写入作业输出与步骤摘要。该标签仅会启用现有选择器,而不是无条件运行完整测试套件。每夜平台测试仍是合并后自动执行的兜底检查。
skip 表示没有任何已变更路径影响当前工作区测试套件所覆盖的 Rust 编译或测试。scoped 会将 Rust 源码、测试、基准测试、示例以及包内本地清单路径映射到 Cargo 元数据中的包根目录,将该集合沿工作区反向依赖方闭包展开,将根目录 src/ 和 tests/ 映射到根包,对包名去重,并像现有工作区测试套件一样排除 zeroclaw-desktop。当工作区清单或依赖解析、.cargo、Rust 工具链、CI 或测试基础设施、选择器或工作流、未知路径、存在歧义的元数据以及其他无法安全映射的变更时,选择 full。每一处 Cargo.lock 变更都会选择 full,因为工作区范围的依赖解析可能影响直接变更清单之外的包。对根包、gateway 包或 provider 包的直接变更,以及对插件、运行时、插件配置、WIT、根插件激活、插件后端过滤器、依赖、选择器、选择器契约、ci.yml 或 windows-tests.yml 路径的变更,会设置 needs_plugin_host=true。这些控制文件的规则确保工作流修订会执行其所负责的插件宿主路径。变更路径输入格式错误或不可用时,选择基线 full 且为 true。Cargo 元数据缺失或格式错误时,同样选择基线 full 并设置 needs_plugin_host=true,因为无法安全地确定依赖闭包。
对于 scoped,Windows 作业会向 cargo nextest 传入显式的 -p 参数;对于 full,则运行 cargo nextest run --locked --no-fail-fast --workspace --exclude zeroclaw-desktop。当 needs_plugin_host=true 时,该作业会安装 wasm32-wasip2,并追加以下内容:启用了功能特性的 zeroclaw-plugins 组件目标,即 channel_plugin_e2e、tool_plugin_timeout_e2e、reference_plugin、reference_plugin_e2e 和 tool_plugin_e2e;插件库测试;运行时实时配置与准入回归测试;网关库测试;根 CLI 插件测试,包括插件注册表客户端、plugins:: 模块(其中含 egress ceremony 的 Windows shell 证明)、CLI egress ceremony 测试,以及根二进制文件中的 plugin update 测试;运行真实二进制文件的 plugin_update_cli 和 plugin_info_cli 组件测试;以及根目录下的 plugin_channel_runtime_e2e 和 channel_egress_e2e 目标。每个追加的调用即使前面的调用失败也会运行,摘要会保留各阶段状态以及失败清单。该作业分别报告基线、插件宿主和总耗时。普通的 scoped 和 full 选择不会承担插件宿主的编译成本。此 PR 工作流会恢复兼容的 platform-test 缓存条目,但从不保存它们。它检出冻结的 PR 合并 SHA,且不会持久化检出凭据。被跳过的 Windows 作业是有意为之的,并会与选择器结果一同明确显示。
定期平台测试 (platform-tests.yml)
在 macos-15 和 windows-latest 上运行 cargo nextest run --locked --workspace --exclude zeroclaw-desktop --no-fail-fast,运行前会先执行一次成本较低的 Linux 格式检查。这次每晚运行的全工作区测试是基于标签选择的 Windows 测试覆盖的兜底,并通过 --no-fail-fast 汇总所有失败项。矩阵运行如下:
- 修改
platform-tests.yml本身的拉取请求; - 手动调度;以及
- 每晚 03:17 UTC 的计划。
这些 job 使用 continue-on-error,且不参与 CI Required Gate。它们是可移植性验证证据,而不是合并要求。普通代码 PR 不会自动启动这套完整矩阵;维护者可以在需要完整平台验证时,针对某个分支手动触发它。该工作流不会在普通 push 或 merge_group 事件中运行。master 上的每夜运行和手动触发运行可以写入受信任的缓存;拉取请求运行则不能。--no-fail-fast 可确保在一次运行中显示所有平台的失败情况。
每日顾问扫描 (daily-audit.yml)
每日 UTC 时间 09:00 针对依赖树运行 cargo deny check advisories。发现问题时创建 issue。除非报告漏洞,否则不执行任何操作。
每日 npm 审计 (daily-npm-audit.yml)
每天在 09:23 UTC 对 web/package-lock.json 运行 npm audit --audit-level=high。当高严重性 npm 安全公告影响已提交的 web 锁定文件时,打开一个去重的 security + dependencies 问题。
每周 Trivy 镜像扫描 (trivy-scheduled.yml)
每周六扫描已发布的 dist 和 default-features GHCR 镜像,并将 HIGH/CRITICAL 发现项以 SARIF 形式上传到 Security 选项卡。扫描以报告优先(针对发现项设置 exit-code: 0),但如果预期镜像缺失,则会在 Trivy 设置之前使作业失败,并在错误中指出缺失的标签以及所属的发布者工作流。
每周 Scoop Bucket Canary (scoop-bucket-canary.yml)
每周一针对当前稳定版本演练 Scoop 发布路径。它会解析最新的 vX.Y.Z 标签,并使用 dry_run: true 和 credential_canary: true 调用 pub-scoop.yml,因此会使用真实的 SCOOP_BUCKET_TOKEN 针对真实存储桶执行测试,而不会写入任何内容。
credential_canary 是该契约中“失败关闭”的部分:缺少 SCOOP_BUCKET_REPO 或 SCOOP_BUCKET_TOKEN 会导致运行失败,并且配置的凭据必须传递到 git push --dry-run 授权探测。仅使用 dry_run: true 的通用手动 pub-scoop.yml 运行在清单生成方面仍然是宽松的,并且在凭据不可用时可能会跳过该探测;不要将通用模式用作凭据验证的依据。
之所以存在这一机制,是因为 SCOOP_BUCKET_TOKEN 与账户绑定:它会过期,并且当所属身份在存储桶上的协作者授权发生变化时,会悄无声息地失去写入权限。这两种情况都发生过。在 canary 之前,唯一会实际使用该凭据的是发布后的 scoop 作业,因此直到版本已经定稿并发布公告之后,才发现令牌已失效,而存储桶只能手动更新。
金丝雀用于检测凭据失效。它有意不负责确保存储桶状态正确,也未接入 Release Stable:失效的包管理器凭据绝不能阻塞或延迟发布。
Scoop bucket 如何保持正确性
目前,发布器是唯一的自动化写入者:
pub-scoop.yml在发布时推送。 Scoop 用户在此操作成功后会立即看到新版本。它需要跨仓库的SCOOP_BUCKET_TOKEN,这是最容易出问题的部分。- 维护者恢复失败的推送。 轮换或修复令牌,调度 Scoop Bucket Canary 通过故障关闭的
credential_canary路径对其进行验证,使用dry_run: false重新运行发布器,并确认 bucket 清单已写入发布版本。
scoop-zeroclaw#1 中提出了由 bucket 侧 Excavator 执行的方案。该工作流合并后,bucket 仓库授予 Actions 读写工作流的权限,并且维护者的冒烟测试证明它能够提交更新后,它就可以成为不依赖凭据的恢复层。在满足这三个条件之前,不要假定失败的发布器会自动自愈。
checkver 和 autoupdate 块对于计划中的 Excavator 路径已经不可或缺。当前的推送路径还使用 scripts/release/scoop_metadata.sh 从 autoupdate 推导其发布 URL 模板,因此两条路径共用同一个清单契约。不要删除这些块,也不要手动将其从 dist/scoop/zeroclaw.json 中移除。
PR 路径标签器 (pr-path-labeler.yml)
根据更改的文件自动应用路径和范围标签。它会在 PR 打开、重新打开以及每次向 PR 分支推送更新时运行。由于启用了 sync-labels: true,.github/labeler.yml 中定义的标签会根据当前 PR 文件集重新计算。
该工作流不会应用 risk:*、size:*、type:*、贡献者等级、状态、解决结果、stale 或 pickup 标签。如果 PR 缺少路径/范围标签,请检查 .github/labeler.yml 中的路径是否覆盖了这些变更。
Dependabot 在 .github/dependabot.yml 中为其自身的 PR 提供了单独的标签配置。Cargo 更新 PR 以 dependencies 开头;GitHub Actions 和 Docker 更新 PR 以 ci 和 dependencies 开头。
PR 大小标签器 (pr-size-labeler.yml)
应用来自 PR 文件元数据的唯一一个规范 size:* 标签。它会在 PR 打开、重新打开以及每次推送到 PR 分支的更新时运行。分类器会先排除类文档文件和 Cargo.lock,然后统计新增行数加删除行数,并依据 Labels 中的阈值表应用标签。
该工作流在 pull_request_target 中运行,以便能够为来自分支(fork)的 PR 写入标签,但它从可信的工作流/默认分支版本中获取分类器脚本。它不会检出、构建、导入、加载(source)或执行 PR 中的代码。它也不会应用 risk:*、type:*、贡献者等级、状态、解决结果、stale、pickup 或 ProjectV2 字段。
PR 风险报告(pr-risk-labeler.yml)
在 PR 更新和 risk/security 标签变更时通过 pull_request_target 运行,并支持针对单个 PR 的手动触发。它从可信工作流版本中获取分类器和与 JSON 兼容的策略,仅在 #9530 仅测试证明需要时读取 PR 元数据、变更文件、标签以及 API 源文本,并生成可供人阅读的步骤摘要和 JSON 证据。它从不检出、构建、导入、加载(source)或执行拉取请求中的代码。
第二阶段仅用于报告。该工作流具有 contents: read 和 pull-requests: read 权限,不会添加、移除或替换标签,不会创建提交状态,不会强制执行审批人数,也不要求仓库规则集。risk:manual 被报告为未来变更的硬性冻结,而 domain:security 则独立于建议的 risk:* 结果进行报告。
分类器对不确定的证据默认向上升级。文档、测试夹具和已知的机械性元数据可以标记为 risk:low;常规行为变更标记为 risk:medium;涉及已配置的信任、凭据、兼容性、治理、发布权限、安全或工具链基线路径的变更标记为 risk:high。已签入的策略还包含确定性的变更行升级规则,涵盖工作流权限扩展、密钥访问、OIDC、制品发布、发布行为、提升权限的 pull_request_target、工具链安装或容器基线、发布基线变更,以及可能的 WIT 契约。如果上述内容敏感文件发生变更,但补丁不可用或格式错误,报告保持 risk:high,而不是猜测为 risk:medium。#9530 例外情况仅当完整的 Rust 补丁以及匹配的基线/目标源码证明所有高风险 Rust 变更都位于现有的 #[cfg(test)] 代码内时,才可以推荐 risk:medium;缺失、截断、格式错误、混合、条件编译、重命名、新增和删除等证据,均保持 risk:high。
项目仪表板规划器(project-dashboard-plan.yml)
手动运行以处理单个 issue 编号。它会读取 issue 状态和标签,然后写入一条仅供报告的步骤摘要,建议最符合该 issue 的现有 Project Status 值。
该工作流不会在 issue 事件上自动运行,不会写入 ProjectV2 字段、编辑 issue、添加标签、发表评论,也不会重新计算 PR 的 risk:* 或 type:* 标签。若要进行实时 ProjectV2 变更或自动化的 issue 事件规划,需要单独获批的字段映射、触发策略以及项目范围的凭据。
验证 PR 标题(pr-title.yml)
在每次 PR 打开/编辑/同步时运行。运行验证器单元测试(scripts/check-pr-title.test.sh),并依据 Conventional Commits 检查 PR 标题(scripts/check-pr-title.sh)。
将 mdBook 文档部署到 Pages(docs-deploy.yml)
在标签推送时触发(以及 workflow_dispatch);构建并将带版本的文档发布到 gh-pages 分支。有关版本下限和引导规则,请参阅 发布操作手册 → 版本化文档部署。
Docker 镜像 PR 检查(docker-image-pr.yml)
仅当 Docker 镜像、Compose 或发布 Docker 上下文文件发生变更时运行。它会校验合并后的默认与 Alpine Compose 配置。对于超出仅修改 Compose 的变更,它会构建默认和 Debian 预构建冒烟测试镜像以及源 Dockerfile,但不推送。若唯一的 Docker 路径变更是 release-stable-manual.yml,则只构建预构建冒烟测试镜像。发布工作流的 Docker 任务会通过 scripts/ci/prepare_docker_context.sh 打包预构建二进制文件,这些镜像正是用于验证它们,且该任务从不使用源 Dockerfile。如果差异还涉及任何源镜像输入(Dockerfile、Containerfile、Compose 文件、.dockerignore、配置模板、Docker 上下文脚本或本工作流),则仍会构建完整的源镜像矩阵。默认和 Alpine 源镜像面向 linux/amd64 与 linux/arm64 构建,Debian 源镜像面向 linux/amd64 构建。独立的 Alpine 和 Debian linux/amd64 通道会启用 plugins-wasm-runtime-only,使其构建上下文持续验证仓库的 WIT 契约可供启用插件的源码构建使用。
所有功能的 Containerfile 源镜像在该文件或 Docker 工作流变更时,为 linux/amd64 构建。它使用隔离的缓存作用域,且既不加载也不推送。Alpine amd64 通道会运行两个二进制文件,通过合并后的 Compose 配置启动已构建的镜像,并检查网关健康状态和仪表板界面。Alpine arm64 通道仅覆盖编译和镜像组装。它在 blacksmith-8vcpu-ubuntu-2404 上进行交叉构建,该环境属于现有的 32 GB 大内存 Rust 运行器类别,因为其 release 链接阶段在 ubuntu-latest 上耗尽了内存。其他 Docker 作业仍在 ubuntu-latest 上运行;release 优化、构建标志和矩阵规模保持不变。仅涉及 Compose 的变更使用精简的 Alpine amd64 矩阵,以便仍然验证运行时契约,而无需重建无关镜像。所有作业均只具有仓库的只读权限,且没有镜像仓库写入权限。
Docker 发布(docker-publish.yml)
构建、签名并扫描从 dev/ci/docker-tags.toml 生成的四变体矩阵:minimal、default-features、dist 和 all-features。人工创建的 v* 标签会直接启动此工作流。通过 workflow_dispatch 启动的稳定版发布会使用 GITHUB_TOKEN 创建其标签,而这不会触发另一个标签推送事件,因此在规范发布和 Docker 作业成功后,release-stable-manual.yml 会在不可变的发布标签处同步调用 Docker Publish。
此矩阵是对稳定版本预构建的 latest、版本化及 debian 镜像的补充,而非替代。两条路径使用不同的构建输入并发布不同的标签。
Discord 发布(discord-release.yml)
在稳定版发布成功后触发。将发布说明发布到社区 Discord。
推文发布 (tweet-release.yml)
在稳定版发布成功后触发。发布一条公告推文。
每周 AUR 新鲜度检查 (aur-freshness-check.yml)
每周一将已发布的 zeroclawlabs AUR 版本与当前稳定版 GitHub 发布版本进行比较,如果 AUR 版本落后则失败。
发布到 AUR 是一种即发即忘的操作:如果 pub-aur.yml 失败,就不会有任何机制重新检查,因此软件包会悄无声息地落后。v0.8.4 之后发生的正是这种情况。aur.archlinux.org 的维护窗口与发布重叠,唯一一次未重试的克隆操作因 The AUR is down due to maintenance 而失败,软件包在没有任何信号的情况下落后三周。现在,发布器最多允许一个活动中的非试运行发布,并会进行重试以应对短暂中断;在同一并发组中,GitHub 可能会取代较早排队的真实发布,而试运行使用单独的组。每次尝试都会重新克隆权威的软件包状态,并拒绝用较旧的 epoch:pkgver-pkgrel 元组替换较新的元组。重试预算仍无法覆盖所有失败,因此此检查就是后备保障,可将无声遗漏或被取代的运行转化为可见的问题。
如果无法访问 AUR RPC,检查会发出警告并通过,而不是失败。AUR 中断属于上游可用性问题,而不是软件包过时问题,下次计划运行时会重新检查。过时状态是持久的,因此延迟检测可以接受;但每周因他人的维护窗口而触发告警则不可接受。
文档在发布流水线中构建和发布,而非每次推送 master 时构建。翻译是一个仅限本地的工作流,适用于专用翻译缓存 PR、新语言区域及发布翻译流程。常规英文文档 PR 可以推迟大范围生成的 .po 变更。贡献者指南请参阅 Docs & Translations,发布流程请参阅 Release Runbook。
手动和建议性工作流
每月过期扫描(monthly-outdated.yml)
每月 1 日 09:00 UTC 定时扫描。对所有工作区成员运行 cargo outdated --workspace。当发现过期依赖时,创建一个带有 dependencies-标签的 issue。权限:contents: read + issues: write。去重保护可在前一个 issue 仍然打开时防止重复累积。
The issue 预览可能被截断,但其关联的 monthly-outdated-report 工件会保留完整的扫描输出 30 天。
新问题的第一步分诊:检查所报告的过期 crates 是否存在与 semver 不兼容的版本跳变,以及下游 crate 的 API 是否发生了变化。如果版本跳变是琐碎的(patch/minor),则创建一个简短的仅依赖项 PR。如果升级因 semver 破坏而受阻,则关闭该问题,并附上一条说明和阻塞的 crate 名称。
跨平台构建(cross-platform-build-manual.yml)
用于手动触发构建覆盖完整目标矩阵的发布二进制文件:Linux x86_64/aarch64 GNU 和 MUSL,以及 armv7 和 arm 硬浮点,macOS Intel/ARM、Windows x86_64,以及 aarch64-linux-android(使用 NDK 构建)。在打标签之前,使用此功能验证分支能否在非 Linux 目标上顺利编译。
每次调度还会独立于构建运行一个小型发布工具冒烟测试矩阵。当只需要此项验证时,设置 release_tools_only;随后会跳过 web 和 release-build 作业。在受信任的 GitHub 托管 Linux x86_64 上,冒烟测试安装固定版本的 cross 归档,确认 cross 和 cross-util 均可用,并记录 cross --version。在受信任的 GitHub 托管 Windows x86_64 上,它使用与稳定版发布工作流相同的 Rust 版本和 Bash 到 Cargo 的路径形式,然后记录 cargo-tauri.exe --version 和 cargo tauri --version。每个执行分支都会在公开作业摘要中记录确切的测试提交和运行器架构。冒烟测试使用只读仓库权限,并且没有发布作业、环境、机密或制品上传。
MUSL 构建任务也会通过 scripts/ci/install_release_tool.sh 安装 cross;该脚本会下载精确锁定的上游发布资源,并在安装前验证其 SHA-256。必需的 Repository Structure 作业会测试受支持的 runner 到资源的映射以及冒烟工作流契约,且不会发起网络调用。
默认的 qualification_profile: normal 保留完整矩阵、dist-broad 原始二进制冒烟测试路线、标准 dist 归档路线、常规缓存策略,以及现有的 ARM64 内存生命周期和 macOS 180 分钟时限上限。其五个任务在任务作用域内保留 CARGO_INCREMENTAL=0。发布工具冒烟测试会在安装工具之前校验调度输入;常规产品任务则保持其独立的调度。
选择 qualification_profile: stable-gnu-ubuntu22、distribution: dist 和 release_tools_only: false,以执行有限、不发布的资格鉴定。空值、未知值、格式错误、宽泛分发版或仅工具的组合都会被拒绝。四个 GNU 构建行运行在 Ubuntu22 上:x86_64、aarch64、armv7 和 arm。它们调用与 Stable 相同的本地 build-release-payloads 操作,并保留其现有的链接器和 ARMv6 绑定、Rust1.98、锁定的 release 配置文件、显式规范 features、普通 Cargo 作业、每行三个载荷、90 分钟上限,以及含边界的 83,886,080 字节主二进制文件大小上限。只有鉴定器会启用该操作的 features 输出;其清单仅消费这一个解析结果。Stable 保持证据开关关闭。
独立的 web 和 fixture 生产者沿用现有入口点与 Stable 工具链配置。这三个构建角色直接调用固定版本的 Swatinem action,并使用原始的 web、plugin-smoke-packages 和 build 共享键。fixture 前缀以及每个 Ubuntu22 目标前缀均与 Stable 保持一致,默认的 target 恢复功能保持启用。每个限定缓存都使用 save-if: false,即使在 master 分支上也是如此。这些角色省略了 workflow/job 级别的 CARGO_INCREMENTAL 声明,与 Stable 保持一致。固定版本的缓存 action 会在构造缓存配置之前导出 CARGO_INCREMENTAL=0,并将该值保留到后续步骤中。生产者回执要求并记录有效字符串 "0",完整性检查也校验同一个值。保留 action 导出的该值。常规 web 缓存策略保持不变。在接受缓存一致性之前,action 日志必须提供实际的主键、恢复键、命中情况和缓存来源。这些输入既不保证预热缓存,也不保证之后的 Stable 构建耗时。
两个原生消费者分别运行 Ubuntu22 上的 x86 GNU 和 Ubuntu24-arm 上的 ARM64 GNU,各自有 30 分钟的时长上限。每个消费者都会校验并解压同一次运行生成的归档,在五分钟内运行完整的详细模式脚本化 provider 测试套件,并无条件调用感知归档的插件冒烟测试。不存在原始文件回退方案。被选定的完整性检查任务即使在上游失败后仍会运行,并会拒绝每个缺失、跳过、失败或已取消的角色。它使用未修改的校验器检查四个归档,检查十二个输入哈希及其保存的 ELF、编译器/sysroot 和资源观测结果,并检查两个原生证据载体。源提交、树、运行、尝试、profile、web 和 fixture 的哈希将这些回执绑定到同一次分发。
在接受此路线之前,须保留实际的缓存回执,解读全部十二项 ABI 观测结果,并检查每个原生载体(carrier)所指定的十三项 provider 测试和十八项归档控制项。仅有成功的作业或载体清单,不能替代这些结果或维护者审查。证据缺失或无法解读时,应暂缓接受;这不授权自动进行预算、资源、功能或 ABI 策略的变更。Ubuntu24 ARM64 的执行结果不能作为 Ubuntu22 ARM64 运行时证明。ARM32、配套执行、Darwin、MUSL、Android、Windows、Stable 的完整矩阵以及发布就绪状态,各自都需要独立的证据。仅有源代码变更,不能证明上述任何运行时结果。
跨平台 Clippy (cross-platform-clippy.yml)
在 macOS aarch64 和 Windows x86_64 目标上的手动及每周定时建议性 lint 覆盖。它镜像了所需的 PR lint 命令,并为每个平台设置了 --target,但故意不在 PR 上运行,也不属于 CI Required Gate。
必需的 Linux Clippy、建议性的跨平台 Clippy 以及针对性的 Windows Clippy 都调用 scripts/ci/run_clippy.sh。该运行器负责支持的命令形式、Cargo 退出状态的传递,以及共享的耗时、缓存、编译次数和下载次数诊断信息。工作流文件继续负责触发器、运行器、工具链、缓存、超时设置以及必需门禁的纳入。
发布稳定版(release-stable-manual.yml)
手动触发完整发布流水线。构建所有目标,创建 GitHub Release,将预构建的 latest、带版本号的以及 debian Docker 镜像推送到 GHCR,针对发布标签调用生成的 Docker 变体矩阵,触发网站重新部署,并调用分发子工作流(Scoop、AUR、Discord、tweet)。Homebrew Core 通过其自身的 autobump 服务检测新发布版本。两个环境关卡需要维护者在运行过程中批准:github-releases(publish 作业)和 docker。
可下载的资源使用 GitHub 托管的 Build Level 2 证明。离线捆绑包和受信任根材料随附在一个验证归档文件中,两种 SBOM 格式在创建发行版之前都会进行校验和计算和证明。Cosign 仅限用于 GHCR 镜像签名。
每个 64 位归档(实验性 Android 归档除外)都包含 WASM 插件宿主。派发的发布在创建 GitHub Release 及其自身的镜像推送之前会先验证这一点:plugin-smoke-packages 在 Linux 上一次性构建夹具插件包,plugin-smoke 则在与归档平台一致的 runner 上分别下载每个归档,并针对打包后的二进制文件运行 scripts/ci/plugin_artifact_smoke.sh(与拉取请求上 check-plugin-backends 冒烟任务运行的是同一个脚本)。publish 和 docker 会等待所有任务完成。一个 xtask 测试确保任务列表与注册表中各目标的插件策略保持一致,因此某个目标不能在没有对应任务的情况下携带宿主,也不能在仍携带宿主时失去其任务。构建任务会使任何超过尺寸检查步骤所设 80 MiB 上限的二进制文件失败。手动触发的 Cross-Platform Build 工作流会针对其构建的二进制文件重新执行相同的任务,因此分发变更可以通过一次派发完成度量和验证,然后再运行发布。由标签推送启动的发布也会触发其自身 v* 标签触发器下的 Docker Publish,且该运行不会等待冒烟测试。
完整流程请参阅 Release Runbook。
仅用于发布的构建工具不会在每次运行时都从源代码编译。该工作流通过 scripts/ci/install_release_tool.sh 安装固定版本的上游 cross 和 Tauri CLI 发布二进制文件;该脚本会在将二进制文件放入 Cargo 的 bin 目录之前,验证每个运行器专用归档文件对应的仓库 SHA-256。更新任一工具时,都必须同时更新其版本、资源名称和校验和,然后运行 scripts/ci/install_release_tool.test.sh。
软件包发布者
每个工作流都在 workflow_dispatch 触发时运行,并接收一个版本输入参数。此外,它们也会在发布工作流成功发布后被调用。
| 工作流 | 它的作用 |
|---|---|
pub-aur.yml | 更新 Arch 用户仓库 PKGBUILD 并推送到 AUR |
pub-crates.yml | 打包并验证协调的工作区发布版本,然后在 crates-io 环境门禁下按依赖顺序将其发布到 crates.io |
pub-scoop.yml | 更新 Windows 的 Scoop 清单 |
Homebrew Core 的官方 autobump 服务会发现稳定的 GitHub 发布版本,并独立发起 formula 版本更新。不要恢复项目自有的 Homebrew 发布器或 fork token;这会重复 Homebrew 的权威自动化。
必需的密钥
| 秘密 | 用于 |
|---|---|
AUR_SSH_KEY | pub-aur.yml |
CARGO_REGISTRY_TOKEN | 仓库机密被显式传递给 pub-crates.yml,且仅由其受保护的发布作业引用;v0.8.5 需要使用 publish-new 发布 zerorelay、zeroclaw-relay-proto 和 zeroclaw-tls,而后续的协调更新需要使用 publish-update |
DISCORD_WEBHOOK_URL | discord-release.yml |
TWITTER_ACCESS_TOKEN、TWITTER_ACCESS_TOKEN_SECRET、TWITTER_CONSUMER_API_KEY、TWITTER_CONSUMER_API_SECRET_KEY | tweet-release.yml |
SCOOP_BUCKET_TOKEN | pub-scoop.yml、release-stable-manual.yml、scoop-bucket-canary.yml;仅限于 zeroclaw-labs/scoop-zeroclaw 且具有 Contents 读写权限的细粒度 PAT |
WEBSITE_REPO_PAT | release-stable-manual.yml(触发网站仓库重新部署) |
GITHUB_TOKEN(自动) | 所有推送提交、创建 PR 或将镜像推送到 GHCR 的工作流 |
Docker 镜像使用自动生成的 GITHUB_TOKEN 推送到 GHCR;不存在单独的注册表令牌。将 CARGO_REGISTRY_TOKEN 存储为仓库机密,并仅将这个指定的机密映射到可复用的发布器中。被调用的工作流仅在不可逆的发布步骤中引用它;该步骤所属的作业需要通过 crates-io 环境审批;无令牌的预检既不引用也不导出它。审批者启动发布作业之前,预检会对同一个不可变的发布提交进行打包。
发布 Stable 通过其 stage 输入调用发布程序两次。crates-preflight 作业与二进制构建并行运行 stage: preflight,且不使用任何密钥。手动派发的发布尚无标签,因此它会直接校验本次运行的提交。GitHub Release 的 publish 作业依赖该作业,因此 crates 失败会在任何内容公开之前终止发布。创建 GitHub Release 之后,crates 作业使用令牌运行 stage: publish。该阶段要求新标签解析到同一提交,跳过重新校验,并上传 preflight 阶段已记录摘要的仪表板包。手动派发 pub-crates.yml 仍会在一次调用中运行两个阶段。
两个作业都会从第二个稀疏检出的 scripts/release/(位于 .release-tooling,并从打包目录树中排除)运行发布脚本,并通过 PUBLISH_SOURCE_ROOT 传递源码树。在发布运行中,该检出即为发布提交。针对已有标签、从 master 发起的手动派发,会使用 master 的脚本处理已标记的源码树,因此发布脚本的修复无需移动标签即可应用到某个发布版本中。resolve_crates_release.sh 会拒绝任何其他工具提交。
根 crate 将生成的仪表板随附在 web/dist 中。预检会构建一次该仪表板,记录每个文件路径及其内容的摘要,使用该目录树验证软件包,并将其作为 crates-io-web-dist 构件上传。发布作业不会重新构建仪表板,而是恢复该构件。publish-crates.sh 仅当恢复的目录树与记录的摘要一致时才会向 crates.io 发布,否则拒绝发布。该构件保留 30 天,以便审批延迟或发布作业失败后重新运行时仍能找到它。
协调发布集中的大多数 crate 已经存在,并且可以使用 crates.io 的可信发布功能。v0.8.5 版本还会创建 zerorelay、zeroclaw-relay-proto 和 zeroclaw-tls,因此其引导令牌必须包含 publish-new。在每个 crate 都为此工作流配置可信发布者条目之前,环境令牌仍是引导路径。配置这些条目后,迁移该作业,使 GitHub 将 OIDC 身份交换为短期令牌,而不是继续保留 CARGO_REGISTRY_TOKEN。
该组织当前在 Scoop bucket 上禁用了部署密钥,而自动生成的 GITHUB_TOKEN 无法写入另一个仓库。请将 SCOOP_BUCKET_TOKEN 的权限范围严格限制在该 bucket 内;不要重复使用维护者的宽泛 CLI 令牌。发布器会通过 git push --dry-run 检查写入权限,然后使用相同的 Git 传输通道执行实际更新。
轮换 SCOOP_BUCKET_TOKEN
由于部署密钥不可用,此凭据是个人访问令牌,因此存在两种相互独立的失效模式,而这两种模式都曾导致一次发布出现问题:
- 令牌会过期。 细粒度 PAT 的有效期有上限,因此无论其他内容是否发生变化,此问题都会按固定周期再次出现。
- 所有者身份失去了对存储桶的写入权限。 即使令牌仍然有效,其背后的账户也可能只是一个
read协作者。这会产生remote: Permission to zeroclaw-labs/scoop-zeroclaw.git denied to <account>和 HTTP 403,而不是身份验证错误,因此看起来像是代码问题,实际却是权限问题。
使用 ZeroClaw-Bot 账户持有令牌,绝不要使用个人账户,这样发布流程就不会依赖某一位维护者的凭据。要轮换令牌:
- 以
ZeroClaw-Bot身份创建一个细粒度 PAT,资源所有者 为zeroclaw-labs,仓库访问权限 仅限于单个仓库zeroclaw-labs/scoop-zeroclaw,并设置 仓库权限 → 内容:读取和写入。除此之外不设置任何权限。 - 确认组织已批准该令牌。针对组织资源所有者的细粒度 PAT 在获得批准前会一直处于待处理状态,待处理令牌可以进行身份验证,但无法推送。
- 确认
ZeroClaw-Bot仍对该存储桶具有write权限:gh api repos/zeroclaw-labs/scoop-zeroclaw/collaborators/ZeroClaw-Bot/permission --jq '.role_name'。第 1 步不会授予仓库访问权限;它只会限定令牌可以使用的权限范围。令牌的权限不能超过其所有者已有的权限。 - 设置机密:
gh secret set SCOOP_BUCKET_TOKEN --repo zeroclaw-labs/zeroclaw。 - 无需接触存储桶,通过触发 Scoop Bucket Canary 进行验证。运行成功表明新令牌具备推送权限。
轮换时,将过期日期记录在某个持久的位置。无论如何,canary 会在一周内发现过期的令牌,但那时它已经失效了。
AUR 软件包所有权
项目所拥有的软件包目前是 zeroclawlabs,由 zeroclaw-bot 维护。规范名称为 zeroclaw 的软件包是第三方软件包,无法通过轮换 AUR_SSH_KEY 来接管。如果该维护者仍处于不活跃状态,请先遵循 AUR 孤儿请求流程,再更改 pkgname 或工作流的克隆目标。所有权转移后,请在一项经过审查的变更中协调软件包重命名或合并。
构建缓存行为
ci.yml 中大多数 Rust 密集型作业都通过本地 ./.github/actions/rust-cache 复合操作进行缓存。该操作接收来自 Blacksmith 编译调用方的 use-blacksmith: 'true',与这些作业直接指定的运行器标签一致。这两个值都是 ci.yml 中已提交的常量,而不是 Actions 变量。在源自 Fork 的 pull_request 运行中无法读取 vars,因此原先的 CI_USE_BLACKSMITH 开关会静默地将每个 Fork PR(占本仓库 PR 的 90%)发送到 GitHub 托管运行器,并使用冷缓存。原因是 master 迁移到 Blacksmith 后,已停止为 GitHub Actions 缓存预先填充数据。使用这些常量后,这些 Fork PR 作业会与对应的 master 作业一样,在 Blacksmith 运行器上使用 useblacksmith/rust-cache(Blacksmith NVMe 粘性磁盘)。若要将该组运行器迁回 ubuntu-latest + Swatinem/rust-cache,只需在一次经过审查的提交中修改 Linux 编译作业上的运行器标签和提供方输入即可,tests/architecture/ci_runner_labels.rs 会保持这些修改同步一致。无论如何选择,两个 action 引用都保留在复合操作中,因此两者都必须保留在允许列表中。Windows 任务负责人恢复流程是 Blacksmith 上的例外情况,它直接使用 Swatinem/rust-cache,并拥有自己由 master 预先填充的依赖缓存。macOS 和 Windows 构建分支继续使用 Swatinem/rust-cache 访问 GitHub Actions 缓存。fmt、nix-eval 和 docs-style 作业(均不编译工作区)不使用 Rust 缓存。codeql.yml 为其 Rust 扫描固定了相同的 Blacksmith 标签,因此现在没有任何工作流会读取 CI_USE_BLACKSMITH 变量;可以从仓库设置中删除该变量。以下行为在排查缓存相关的偶发故障时值得留意:
- Matrix 各分支需要不同的缓存键。 缓存键由作业 id(或
shared-key)、运行器操作系统和架构、工具链、CARGO*/RUST*环境变量以及 lockfile 派生而来。仅在功能标志或 cargo 参数上有所不同的 Matrix 分支会解析为相同的缓存键;先保存的分支占有该键,其他分支则恢复到不匹配的依赖集。在采用每个分支独立缓存键之前,cranelift 插件后端分支每次运行(包括 master 分支)都要重新编译约 620 个依赖 crate(约四分钟),因为它与 pulley 和仅运行时分支共用同一个键。因此,check和check-plugin-backends为每个分支提供一个不含逗号的cache-keyMatrix 字段,并将其作为shared-key传递;依赖集确实相同的分支可以共享同一个键。新的 Rust Matrix 作业也应采用相同做法。 - 缓存写入仅限 master 分支。
save-if的条件为github.ref == 'refs/heads/master',因此 PR 运行会读取由 master 填充的缓存,但绝不会更新它。PR 分支无法用特定于分支的产物污染共享缓存。master上的push触发器使得该工作流在合并后拥有一次可信的缓存写入运行。 - 恢复步骤仅在成功时保存缓存。 其
cache-on-failure: 'false'设置可防止失败的恢复运行写入缓存。其他复合缓存编译任务保留cache-on-failure: true,因此部分完成的 master 运行仍可为其下一次尝试提供预热缓存。 - 已启用 Windows 构建缓存。 Windows 构建环节运行与 Linux 和 macOS 相同的固定版本 Rust 缓存 action。如果 Windows 缓存行为不稳定或出现回归,请还原此工作流更改,并在缓存 issue 中记录失败的 restore/save 证据。
- 增量编译已禁用。 工作流级别设置了
CARGO_INCREMENTAL: 0。增量构建会增大缓存体积,并在部分陈旧条件下产生不可复现的构建产物。 cargo-deny和cargo-nextest每次运行都会重新安装。security作业运行cargo install cargo-deny --locked;Linux 的test作业以及两个定时的platform-tests.yml任务会从get.nexte.st拉取相应平台的cargo-nextest二进制文件。高级 PR 的 Windows 作业则下载其固定版本的 nextest 发行包,并在解压前校验硬编码的 SHA-256 值,因为该作业会运行在数量更多的不可信 PR 上。两者都未被缓存,因此每次安装都会为对应作业增加固定的开销。改用taiki-e/install-action可以实现缓存,但该操作目前不在允许列表中。
当闸门变红时
| 症状 | 首先要检查的事项 |
|---|---|
Release Stable(发布稳定版)在 uses: 引用变更后于 startup_failure 处中止,且没有任何作业 | 检查运行摘要和仓库的 Actions 策略。如果 GitHub 报告选择的操作遭到拒绝,请将变更后的 ref 与允许列表进行比较,仅添加被拒绝的模式,等待设置生效,然后重新触发一次运行。否则,请调查工作流定义或其他仓库策略;仅凭 startup_failure 无法确定原因 |
CI Required Gate 红色 | 先 fmt,然后 lint,接着 test,最后 build |
发布 validate 失败 | Cargo.toml 版本与工作流输入不匹配,或者标签已存在 |
| 发布构建阶段失败 | 特定目标的作业日志。Android 为 experimental,并启用 continue-on-error |
| 环境门限超时 | 从工作流运行页面重新运行超时的作业 |
| 分发发布者失败 | 首先,使用 dry_run: true 手动重新运行相应的子工作流 |
允许的操作
该仓库以 selected 模式运行 Actions,只有此允许列表中的操作才可运行。允许列表必须保持精简;新增的第三方操作在添加前需要维护者明确批准。
所有第三方引用都固定到完整的提交 SHA,并附带尾部版本注释;下面的版本列记录的就是该注释。
| 操作 | 用于 | 目的 |
|---|---|---|
actions/checkout (v6.0.2) | 大多数工作流 | 检出仓库 |
actions/cache (v4.2.3, v5.0.5) | docker-image-pr.yml, tweet-release.yml | 通用依赖和 Trivy 数据库缓存 |
actions/setup-node (v7.0.0) | ci-sbom.yml, ci.yml, cross-platform-build-manual.yml, daily-npm-audit.yml, pub-crates.yml, release-stable-manual.yml | 用于 npm SBOM 生成、Web 测试/审计以及 Web/桌面构建的 Node 工具链 |
actions/upload-artifact(v7.0.1) | release-stable-manual.yml, cross-platform-build-manual.yml, docker-publish.yml, trivy-scheduled.yml | 上传构建产物和 Trivy SARIF 交接产物 |
actions/download-artifact(v8.0.1) | release-stable-manual.yml、cross-platform-build-manual.yml、docker-publish.yml | 下载构建产物和 Trivy SARIF 交接产物 |
actions/attest (v4.2.2) | release-stable-manual.yml | 为发布资产生成 GitHub 托管的构建级别 2 来源证明 |
actions/labeler (v6.1.0) | pr-path-labeler.yml | 从 .github/labeler.yml 应用路径/作用域标签 |
dtolnay/rust-toolchain (stable, v1) | ci.yml, platform-tests.yml, pub-crates.yml, release-stable-manual.yml, cross-platform-build-manual.yml, cross-platform-clippy.yml, daily-audit.yml, docs-deploy.yml, codeql.yml | 安装 Rust 工具链 |
Swatinem/rust-cache (v2.9.2) | ci.yml(GitHub 托管的复合路径与直接 Blacksmith Windows 恢复)、platform-tests.yml、pub-crates.yml、release-stable-manual.yml、cross-platform-build-manual.yml、cross-platform-clippy.yml、docs-deploy.yml | Cargo 构建/依赖缓存在 GitHub 托管运行器上的使用,以及 Blacksmith Windows 的恢复 |
useblacksmith/rust-cache (v3.0.1) | ci.yml(Blacksmith 的路径 ./.github/actions/rust-cache) | Cargo 构建/依赖缓存位于 Blacksmith sticky disk 上;由每个 Blacksmith 编译作业传递给复合操作的 use-blacksmith: 'true' 输入选择(一个已提交的常量) |
docker/setup-buildx-action (v3.11.1, v4.0.0) | release-stable-manual.yml, docker-publish.yml | Docker Buildx 设置 |
docker/login-action(v3.4.0、v4.1.0) | release-stable-manual.yml、docker-publish.yml、trivy-scheduled.yml | GHCR 身份验证 |
docker/build-push-action (v6.18.0, v7.1.0) | release-stable-manual.yml, docker-publish.yml | 多平台镜像构建与推送 |
sigstore/cosign-installer (v3.8.1) | release-stable-manual.yml, docker-publish.yml | 安装 cosign 以实现无密钥 GHCR 容器镜像签名 |
anchore/sbom-action (v0.24.0) | release-stable-manual.yml | 为每个发布生成 SPDX + CycloneDX SBOM。 |
aquasecurity/trivy-action (v0.36.0) | docker-image-pr.yml、docker-publish.yml、trivy-scheduled.yml | 仅报告模式的容器漏洞扫描 |
github/codeql-action/upload-sarif (v3.36.2) | docker-publish.yml, trivy-scheduled.yml, ci-code-analysis.yml | 将 Trivy 和 Semgrep SARIF 报告上传到安全选项卡 |
github/codeql-action/init (v4.37.8) | codeql.yml | 初始化 CodeQL 分析(Rust 和 JS/TS) |
github/codeql-action/analyze (v4.37.8) | codeql.yml | 上传 CodeQL SARIF 到安全选项卡 |
GitHub Release 本身是在 publish 作业内通过 gh release create 创建的,而不是通过 release action。
等效的白名单模式(故意保持狭窄):
actions/*
dtolnay/rust-toolchain@*
Swatinem/rust-cache@*
useblacksmith/rust-cache@*
docker/*
sigstore/cosign-installer@*
anchore/sbom-action@*
aquasecurity/trivy-action@*
github/codeql-action/upload-sarif@*
github/codeql-action/init@*
github/codeql-action/analyze@*
导出当前生效的策略:
sh
gh api repos/zeroclaw-labs/zeroclaw/actions/permissions
gh api repos/zeroclaw-labs/zeroclaw/actions/permissions/selected-actions
任何添加或更改 uses: 操作来源的 PR 都必须在正文中包含一个白名单影响说明。避免使用宽泛的通配符例外;仅针对已验证缺失的操作扩展白名单。
维护规则
- 保持
CI Required Gate的确定性和简洁性。向该门禁添加作业需要有明确的质量依据。 - 所有第三方操作引用必须固定到完整的提交 SHA(根据上述白名单策略)。
- 保持
ci.yml、dev/ci.sh和.githooks/pre-push一致。共享的检查项必须放在scripts/ci/中;每个调用方应调用该辅助脚本,而不是复制其命令。对于独立的固件协议检查项,文档中记录的本地入口点是./dev/ci.sh firmware-protocol。 - 保持
scripts/ci/prepare_docker_context.sh、docker-image-pr.yml以及release-stable-manual.yml中的 Docker 作业相互一致,以便 PR 验证所使用的上下文结构与发布工作流所发布的保持相同。 - 在更改发布证明、校验和、SBOM 或验证存档序列后,运行
python3 scripts/ci/release_attestation_contract_test.py。 docs-stylegate 作业运行bash scripts/ci/docs_quality_gate.sh(markdown lint + em-dash 文案检查)和bash scripts/ci/docs_links_gate.sh(变更行链接检查)。在推送 docs 变更之前,请先在本地运行这两个脚本。
紧急回滚
如果白名单在事件处理过程中锁定了关键操作:
- 临时将 Actions 策略恢复为
all。 - 在识别出缺失的条目后,恢复
selected的白名单。 - 记录事件和最终的白名单差异。
这是通往 all 模式的唯一合理路径,并且它的存续时间绝不应超过该事件本身。