Files
deepseek-harness/.agents/notes/archived/process/2026-07-06-parallel-github-ci-gates.zh.md
2026-07-26 23:26:00 +08:00

7.7 KiB
Raw Blame History

Agent Note: 并行 GitHub CI 门禁

Status: implemented Archived: 2026-07-26

English | 中文

问题

无密钥 GitHub CI 门禁大多相互正交类型检查、lint、文档新鲜度、覆盖率、快照重放、构建、包package的发布卫生检查、demo 冒烟和已构建二进制冒烟会因不同原因失败,也不需要彼此的运行时状态。将它们作为一条有序命令链运行,会使工作流墙钟时间等于所有门禁耗时之和;而把每个短小叶子拆成独立 GitHub job又会反复执行 checkout、Node 设置、pnpm 恢复和安装,直到编排开销成为瓶颈。

随着 workspace 增长原有的宽车道拆分不再满足这一平衡。PRPull Request#404 合并时Linux 的静态、覆盖率、快照和产物 job 分别耗时 148、195、94 和 230 秒Windows 的静态和产物 job 分别耗时 251 和 482 秒。每个包都调用一次包管理器打包主导了两个产物验证器的耗时覆盖率在仅运行源码的套件前无谓地重建输出CPU 密集型门禁则在静态与覆盖率车道内争用资源。

产物边界仍然承载关键约束。publintverify-node-next-types、已编译不变量加载和已构建二进制冒烟测试都需要生成的 lib/ 输出。分片不能让这些消费方抢在构建前运行,也不能用源码执行取代它们对已发布产物的信号。

决策

下述生产拓扑已经成为历史,并由基于证据采用更大的托管 runner 取代。更大 runner 的决策移除了其分片选择器和工作流 job本文保留早期拓扑为何被实现的记录。

CI 将非 Windows job 的一分钟和 Windows job 的三分钟视为观测所得的性能目标,而非取消截止时间。托管 runner 的波动应留下完整计时证据和有用的失败日志,而不是取消本来正确的门禁。串行跨平台 CI 参考会在 Linux、macOS 和 Windows 上独立运行完整、未分片的主 Node 聚合,使优化后的车道清单不会成为自身完整性的唯一判据。

在该拓扑中,scripts/run-gates.ts 是通用的有界调度器GitHub 则为昂贵的门禁族提供显式分片名称。scripts/static-shards.ts 将静态门禁划分为基础、文档类型、API 契约、目录、正文、文档投影和文档构建等归属并拒绝缺失或重复的门禁分配。Linux lint 使用互不重叠的 A-C、D-M、N-S、T-Z 包源码和包测试车道Windows 则使用完整的包源码与包测试车道;两者都包含从 . 开始的仓库补集,使新增顶层目标无法消失在分片之间,并负责唯一一次跨文件重复检查。scripts/coverage-shards.ts 把每个 workspace 包恰好分配给一个源码覆盖率车道。目录过滤器保留尾部分隔符,因为 Vitest 位置过滤器按子字符串匹配,否则会纳入具有同名前缀的相邻项。每个覆盖率车道只包含其拥有的源码文件,重复运行穷尽式伴随拓扑测试,并且不先执行构建,因为从删除了所有生成式 lib/ 的树开始,完整覆盖率套件仍可通过。

快照重放使用两个显式多文件车道,以及大型 ACPAgent Client Protocol文件的八个场景分区。scripts/snapshot-shards.ts 拥有该清单,其测试会发现快照配置允许的每个文件。每个快照 job 在其 Linux runner 准备 Bubblewrap 的同时安装依赖,随后构建已发布运行时,并且只运行分配给它的重放表面。该套件保留五个子进程的有界并发,因为重放的大部分时间都在等待子进程协议 I/O。fixture测试前置数据守卫仍会在每个分区中检查完整 ACP 场景表。

冷启动的独立文档类型检查会重建完整的项目引用图,因此专用文档类型车道只构建一次,再用这些声明检查 Markdown 块。Linux 文档车道使用 VitePress 的 MPA 构建,在观测所得的非 Windows 目标内保留页面渲染与死链接验证;单独的阻塞式 Windows 构建和生产站点车道保留已生成包与已发布站点检查,同时避免把两条关键路径放进同一个 job。

产物使用两个车道:一个元数据车道负责 publint、NodeNext 声明和已编译不变量加载,另一个负责已构建二进制冒烟。每个车道都会在其消费方之前自行构建。重复短时构建会消耗 runner 分钟数,但避免了上传/下载依赖,并使每个 job 的关键路径保持有界。

scripts/publint-all.ts 在进程内针对内存发布视图调用 publint 支持的 API该视图由每份清单声明的文件和 npm 强制元数据文件构成。这样无需生成 103 次包管理器打包命令,也能保留 workspace 文件与已发布文件之间的区别。scripts/verify-built-package-invariants.mjs 在真实包下暂存这些经过结构验证、由清单声明的 lib/ 文件,再通过纯 Node 和 Cordis Loader 规范化导入已编译的自引用。若伴随项触及未声明的运行时分片,仍会失败。

兼容性车道会在每条声明支持的 Node 版本线上运行源码 worker 和 Zstandard 运行时冒烟。TypeScript 在专用的主 Node 24 车道中只检查一次源码图;在运行时兼容性 job 中重复同一编译器分析只会增加耗时,不会提供运行时特有信号。

工作流缓存 pnpm store将每个不可变 ESLint 缓存的键绑定到其所属 lint 分片,为 Windows 测量保留原生 PowerShell并保留一个聚合的 all checks passed 状态用于分支保护。Windows 复用三个穷尽式 lint 分区,并在共享 runner 设置后组合基础/目录/正文门禁与文档类型/API 契约门禁;只有调度方式与 Linux 分区不同。Windows 构建和生产站点验证继续阻塞,而更广泛的 Windows 静态、lint 和产物矩阵仍为观察性检查。

曾考虑的替代方案

  • 保留宽车道:最大限度减少工作流 YAML但会保留观测到的数分钟反馈周期。
  • 让每个叶子门禁分别成为 GitHub job:最大化扇出,但短小的生成器和正文检查准备 runner 的时间会超过检查仓库的时间。
  • 向产物消费方上传一次构建:避免重复编译,但上传/下载和依赖调度会延长墙钟时间;干净构建足够短,可以在有界车道内重复。
  • 在两个发布门禁中保留包管理器打包:把清单选择委托给 pnpm但会重复启动 200 多个包管理器进程。清单结构门禁加发布视图 fixture 使优化后的清单契约显式化,并会在存在磁盘上有但未发布的依赖时失败。
  • 在覆盖率前保留构建:提供源码套件已不再消费的生成输出;干净树覆盖率证明表明这只是纯粹的延迟。
  • 在每个 Node 版本上执行类型检查:重复编译器工作,而兼容性冒烟已经验证实际的 Node 特有加载与压缩行为。

后果

上述分片清单和矩阵 job 不属于当前仓库契约。取而代之的更大 runner 决策在单个进程中保留完整主清单,并以串行套件作为独立完整性判据。

优化后的发布验证器依赖由 verify-package-invariants 强制执行的清单 files 契约。如果发布规则超出该契约,结构门禁和两个暂存视图必须一起变化。

兼容性 job 不再声称 TypeScript 本身已在每个 Node 运行时下执行。它们证明 Node 22、24 和 26 上对运行时敏感的源码加载,而主运行时负责唯一一次源码图类型检查。