diff --git a/.agents/notes/implemented/process/2026-07-22-evidence-based-larger-hosted-runners.i18n.yaml b/.agents/notes/implemented/process/2026-07-22-evidence-based-larger-hosted-runners.i18n.yaml index 6dcafc39dd..ca37255dcf 100644 --- a/.agents/notes/implemented/process/2026-07-22-evidence-based-larger-hosted-runners.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-22-evidence-based-larger-hosted-runners.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-22-evidence-based-larger-hosted-runners.md: c292a4ea49320d684c35d2b9986549d693efb914 -2026-07-22-evidence-based-larger-hosted-runners.zh.md: 5e59c787a85bd2093f0c3ceaa8290e7cd42528fa +2026-07-22-evidence-based-larger-hosted-runners.md: 13ecbd5c74bb08d84c8fdf1140a9970235aab826 +2026-07-22-evidence-based-larger-hosted-runners.zh.md: 93d6818fdf5af826980b6f4b938fadc122722b68 diff --git a/.agents/notes/implemented/process/2026-07-22-evidence-based-larger-hosted-runners.md b/.agents/notes/implemented/process/2026-07-22-evidence-based-larger-hosted-runners.md index c292a4ea49..13ecbd5c74 100644 --- a/.agents/notes/implemented/process/2026-07-22-evidence-based-larger-hosted-runners.md +++ b/.agents/notes/implemented/process/2026-07-22-evidence-based-larger-hosted-runners.md @@ -12,12 +12,16 @@ Larger runners make it possible to pay setup once and parallelize inside the rep ## Decision -The organization keeps twelve x64 larger-runner pools in the repo-restricted `dsh-larger-ci` group: Ubuntu 24.04 and Windows 2025 at 4, 8, 16, 32, 64, and 96 cores. Public IPs are disabled. Each pool has an autoscaling ceiling of 256; the ceiling does not allocate idle machines or remove the need to bound workflow demand. +The enterprise keeps repo-restricted x64 larger-runner pools for Ubuntu and Windows. Ordinary pull requests name three 32-core pools directly: Ubuntu 24.04 for exhaustive coverage, Ubuntu latest for the remaining primary Node 24 inventory, and Windows 2025 for blocking Windows contracts. Public IPs are disabled, and workflow concurrency remains bounded because an autoscaling ceiling neither allocates idle machines nor makes repository work scale without limit. -The pools are measurement infrastructure, not a dependency of ordinary pull requests. The [portable required-CI decision](2026-07-23-portable-required-pull-request-ci.md) runs branch-protection jobs on standard GitHub-hosted capacity; `suite=larger-runner-benchmark` compares isolated critical lanes across every provisioned size, and `suite=consolidated-runner-benchmark` compares whole aggregates. Each benchmark reports its observed processor and memory capacity before running repository work. +The required primary path depends on those enterprise pools. Standard GitHub-hosted jobs retain the Node 22.19, Node 26, and Python SDK compatibility contracts, while the [portable recovery boundary](2026-07-23-portable-required-pull-request-ci.md) and [serial reference](2026-07-21-serial-cross-platform-ci-reference.md) keep complete standard-runner evidence available on `master`. `suite=larger-runner-benchmark` compares isolated critical lanes across provisioned sizes, and `suite=consolidated-runner-benchmark` compares whole aggregates. Each benchmark reports its observed processor and memory capacity before running repository work. The former gate-level and coarse primary shard jobs are absent from the workflow. Their static, lint, coverage, snapshot, and scenario shard selectors are also absent from the repository, so an unused diagnostic path cannot preserve a second CI architecture. +Linux primary work uses two independent 32-core jobs. Coverage runs alone with its own worker bound. The other job starts the static scheduler alone; once it reports a successful build, lint, Node 24 runtime compatibility, build-backed snapshots, and all artifact consumers start against that completed tree. Generated NodeNext consumer directories are excluded from ESLint discovery because the artifact check removes them while these processes overlap. The pnpm store and ESLint cache are restored without putting cache uploads on the pull-request critical path. Performance reports use each job's `startedAt` to `completedAt` interval; runner queue delay is capacity evidence, not repository execution time. + +Windows shares one 32-core setup across the blocking build and production site plus observational built-artifact contracts. Linux owns the duplicate lint, coverage, and snapshot inventories because running those observational copies on Windows extends the paid critical path without adding a blocking platform claim. + An [exact-head all-size benchmark](https://github.com/deepseek-harness/deepseek-harness/actions/runs/29908491351) ran the complete unsharded primary Node aggregate on every Linux pool before the eager-build correction: | Complete Linux primary | 4 cores | 8 cores | 16 cores | 32 cores | 64 cores | 96 cores | @@ -42,7 +46,7 @@ Inner and outer worker limits are separate controls. An [exact-head 32-worker ES The process-bound coverage project contains exactly five suite files. Thirty-two forks crashed Node 24's CJS lexer twice, and a later 16-fork run reproduced the worker loss and invalid coverage result. The single Vitest invocation therefore uses threads for the broad inventory and reserves forks for suites that exercise process-global state, `process` APIs, or timing-sensitive process I/O. That narrow fork inventory includes the local bash process-plumbing suite and the pi-ai adapter suite because aggregate contention changed timing observations in both. These failures make deterministic coverage, not advertised cores, the upper bound on worker selection. -Complete serial Linux, macOS, and Windows references run only when `master` moves. Pull requests use the portable required path, while larger-runner suites run only by manual dispatch. +Complete serial Linux, macOS, and Windows references run only when `master` moves. Pull requests use the enterprise required path plus standard-hosted compatibility jobs, while other larger-runner sizes run only by manual dispatch. ## Alternatives considered @@ -54,7 +58,7 @@ Complete serial Linux, macOS, and Windows references run only when `master` move **Keep build behind typecheck.** This orders independent compiler invocations and turns snapshot replay into a three-stage critical chain. Build output has its own success dependency, so only snapshot and publication consumers wait for it. -**Make larger-runner pools the required default.** This offers lower measured latency when allocation works, but a missing entitlement or delayed organization transfer leaves required jobs queued without repository diagnostics. The portable path accepts longer runtime, and manual suites preserve the performance experiment. +**Keep the complete required path on standard GitHub-hosted capacity.** This avoids repository-external runner configuration, but exact-head standard-runner runs remain materially slower and can spend longer queued behind shared capacity. Standard-hosted compatibility and serial references preserve portable evidence without making that slower topology the ordinary primary path. **Keep required and observational Windows checks in separate jobs.** The split preserves status semantics at the workflow level but pays setup twice. `run-gates` preserves the same required versus non-blocking distinction inside one process. @@ -62,10 +66,10 @@ Complete serial Linux, macOS, and Windows references run only when `master` move ## Consequences -The benchmark topology pays one setup wave per measured aggregate and retains no shard selectors. It runs paid larger-runner executions only when manually dispatched instead of charging every pull request. +The required topology pays one setup wave per 32-core lane and retains no shard selectors. Every ordinary pull request consumes paid enterprise Linux and Windows minutes; manual benchmarks add other sizes only when remeasurement is useful. -GitHub rounds each larger-runner execution up to a whole minute, so whole-aggregate measurement exposes both billed time and workflow complexity without making that cost part of branch protection. +GitHub rounds each larger-runner execution up to a whole minute, so complete-job measurement exposes both billed time and workflow complexity. Splitting Linux repeats setup once, but isolates coverage from build, lint, and snapshot contention; consolidating Windows avoids repeating its slower setup. Performance targets are observations, not cancellation deadlines or correctness requirements. Manual all-size and serial suites remain available when image, dependency, scheduler, or pricing changes need remeasurement. -Missing or renamed organization-owned labels leave only manual benchmark jobs queued. All twelve pools remain defined so the benchmark can compare sizes after allocation recovers, while required CI follows the standard-runner fallback. +Missing or renamed enterprise labels leave required primary jobs queued. Standard-hosted compatibility jobs and `master` references still report useful evidence, but they do not substitute for the required aggregate; runner assignment is therefore an operational dependency that repository CI cannot repair. diff --git a/.agents/notes/implemented/process/2026-07-22-evidence-based-larger-hosted-runners.zh.md b/.agents/notes/implemented/process/2026-07-22-evidence-based-larger-hosted-runners.zh.md index 5e59c787a8..93d6818fdf 100644 --- a/.agents/notes/implemented/process/2026-07-22-evidence-based-larger-hosted-runners.zh.md +++ b/.agents/notes/implemented/process/2026-07-22-evidence-based-larger-hosted-runners.zh.md @@ -12,12 +12,16 @@ Status: implemented ## 决策 -组织在仅限本仓库使用的 `dsh-larger-ci` 运行器组中保留 12 个 x64 大型运行器池:Ubuntu 24.04 和 Windows 2025 各设 4、8、16、32、64、96 核规格。公网 IP 已禁用。每个池的自动扩缩容上限为 256;该上限既不会分配闲置机器,也不能免除限制工作流需求的必要性。 +企业保留仅限本仓库使用的 Ubuntu 和 Windows x64 大型运行器池。普通拉取请求直接指定 3 个 32 核运行器池:Ubuntu 24.04 用于完整覆盖率,Ubuntu latest 用于其余主 Node 24 清单,Windows 2025 用于阻塞性 Windows 契约。公网 IP 已禁用;工作流并发仍设有边界,因为自动扩缩容上限既不会分配闲置机器,也不意味着仓库工作可以无限扩展。 -这些运行器池是测量基础设施,不是普通拉取请求的依赖。依据[可移植必需 CI 决策](2026-07-23-portable-required-pull-request-ci.md),分支保护作业在 GitHub 标准托管容量上运行;`suite=larger-runner-benchmark` 比较每种已预配规格上相互独立的关键通道,`suite=consolidated-runner-benchmark` 则比较完整聚合流程。每项基准测试都会先报告实测的处理器和内存容量,再运行仓库工作。 +必需主路径依赖这些企业级运行器池。GitHub 标准托管作业保留 Node 22.19、Node 26 和 Python SDK 兼容性契约,而[可移植恢复边界](2026-07-23-portable-required-pull-request-ci.md)与[串行参考流程](2026-07-21-serial-cross-platform-ci-reference.md)则在 `master` 上持续提供完整的标准运行器证据。`suite=larger-runner-benchmark` 比较已预配规格上相互独立的关键通道,`suite=consolidated-runner-benchmark` 则比较完整聚合流程。每项基准测试都会先报告实测的处理器和内存容量,再运行仓库工作。 原有的门禁级和粗粒度主流程分片作业已从工作流中移除。相应的静态、lint、覆盖率、快照和场景分片选择器也已从仓库中移除,因此未使用的诊断路径无法继续维系第二套 CI 架构。 +Linux 主流程使用两个相互独立的 32 核作业。覆盖率单独运行,并设有自己的工作线程上限。另一个作业先单独启动静态调度器;静态调度器报告构建成功后,lint、Node 24 运行时兼容性、依赖构建产物的快照和所有产物消费方才基于构建完成后的工作树启动。生成的 NodeNext 消费方目录不会纳入 ESLint 的文件发现范围,因为这些进程重叠执行时,产物检查会删除这些目录。pnpm store 和 ESLint 缓存会得到恢复,但缓存上传不会进入拉取请求关键路径。性能报告采用每个作业从 `startedAt` 到 `completedAt` 的区间;运行器排队延迟是容量证据,而非仓库执行时间。 + +Windows 以一次 32 核环境设置同时承载阻塞性构建、生产网站和观测性的构建产物契约。重复的 lint、覆盖率和快照清单由 Linux 承担,因为在 Windows 上运行这些观测性副本会延长付费关键路径,却不会新增任何阻塞性平台契约。 + 一次[分支头精确的全规格基准测试](https://github.com/deepseek-harness/deepseek-harness/actions/runs/29908491351)在修正构建尽早启动逻辑前,对每种 Linux 池都运行了完整且未分片的主 Node 聚合流程: | Linux 完整主流程 | 4 核 | 8 核 | 16 核 | 32 核 | 64 核 | 96 核 | @@ -42,7 +46,7 @@ Windows 仓库工作在超过 16 核后收益很小,但 32 核池可以让完 进程约束的覆盖率项目恰好包含 5 个套件文件。32 个 fork 曾两次导致 Node 24 的 CJS 词法分析器崩溃,后来一次使用 16 个 fork 的运行又复现了工作进程丢失和无效的覆盖率结果。因此,单次 Vitest 调用会对大范围测试清单使用线程,只为涉及进程全局状态、`process` API 或对时间敏感的进程 I/O 的套件保留 fork。这份有限的 fork 清单包括本地 bash 进程通路套件和 pi-ai 适配器套件,因为聚合争用改变了二者的时序观测结果。这些故障表明,选择工作线程数量时,上限取决于能否得到确定的覆盖率结果,而非标称核心数。 -只有在 `master` 移动时,才运行完整的 Linux、macOS 和 Windows 串行参考。拉取请求使用可移植的必需路径,大型运行器套件仅通过手动触发运行。 +只有在 `master` 移动时,才运行完整的 Linux、macOS 和 Windows 串行参考。拉取请求使用企业级运行器必需路径和标准托管兼容性作业,其他大型运行器规格仅通过手动触发运行。 ## 曾考虑的替代方案 @@ -54,7 +58,7 @@ Windows 仓库工作在超过 16 核后收益很小,但 32 核池可以让完 **让构建继续等待类型检查。** 此方案会给相互独立的编译器调用排定先后顺序,并把快照回放变成 3 阶段关键链。构建输出本身有独立的成功依赖关系,因此只有快照和发布消费方需要等待它。 -**将大型运行器池设为必需的默认选择。** 分配成功时,该方案能缩短实测延迟,但缺少使用资格或组织转移延迟都会使必需作业持续排队,且不会产生仓库诊断信息。可移植路径接受更长的运行时间,手动套件则保留性能实验。 +**将完整必需路径保留在 GitHub 标准托管容量上。** 此方案可以避免依赖仓库外部的运行器配置,但标准运行器上的分支头精确运行仍明显更慢,也可能因共享容量而排队更久。标准托管兼容性作业和串行参考流程保留可移植证据,无需让这套较慢的拓扑成为普通主路径。 **将必需的 Windows 检查和观测性 Windows 检查保留在不同作业中。** 这种拆分在工作流层保留状态语义,却需要支付两次设置开销。`run-gates` 在一个进程内保留了相同的必需与非阻塞区别。 @@ -62,10 +66,10 @@ Windows 仓库工作在超过 16 核后收益很小,但 32 核池可以让完 ## 后果 -基准测试拓扑对每个实测聚合流程只承担 1 轮设置开销,且不保留分片选择器。付费大型运行器仅在手动触发时执行,而不会向每个拉取请求收取这项费用。 +必需拓扑中的每个 32 核通道只承担 1 轮设置开销,且不保留分片选择器。每个普通拉取请求都会消耗付费的企业级 Linux 和 Windows 运行器分钟数;只有在重新测量有价值时,手动基准测试才会加入其他规格。 -GitHub 会把每次大型运行器执行向上取整到整分钟计费,因此完整聚合测量能同时呈现计费时长与工作流复杂度,而不会让这项成本进入分支保护路径。 +GitHub 会把每次大型运行器执行向上取整到整分钟计费,因此完整作业测量能同时呈现计费时长与工作流复杂度。拆分 Linux 会重复一次设置,但可将覆盖率同构建、lint 和快照的争用隔离;合并 Windows 则避免重复其耗时更长的设置。 性能目标是观测结果,而非取消截止时间或正确性要求。当映像、依赖、调度器或定价发生变化而需要重新测量时,仍可使用手动全规格和串行套件。 -组织自有标签缺失或改名时,只有手动基准作业会排队。全部 12 个池均保持已定义状态,因此分配恢复后,基准测试仍可比较各规格,而必需 CI 则使用标准运行器后备路径。 +企业级运行器标签缺失或改名时,必需主作业会持续排队。标准托管兼容性作业与 `master` 参考流程仍会报告有用证据,但不能替代必需聚合流程;因此,运行器分配是一项仓库 CI 无法修复的运维依赖。 diff --git a/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.i18n.yaml b/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.i18n.yaml index 463777eacf..f8b54b0ec5 100644 --- a/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-23-portable-required-pull-request-ci.md: a430d43f7cb3dd4df987d35f3a49d130c397f8e3 -2026-07-23-portable-required-pull-request-ci.zh.md: cbd5d150056f77e52105f56c70a1ead74f052f59 +2026-07-23-portable-required-pull-request-ci.md: 9cf8d97016300c5258c075879176aa6abd64e59e +2026-07-23-portable-required-pull-request-ci.zh.md: c6839a133d0c3fe7a699362f6168e17d827a5b61 diff --git a/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.md b/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.md index a430d43f7c..9cf8d97016 100644 --- a/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.md +++ b/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.md @@ -1,4 +1,4 @@ -# Agent Note: Portable required pull-request CI +# Agent Note: Portable pull-request CI recovery boundary Status: implemented @@ -8,28 +8,28 @@ English | [中文](2026-07-23-portable-required-pull-request-ci.zh.md) Required pull-request jobs assigned to organization-owned runner labels remain queued when GitHub cannot allocate those pools. The workflow is valid and standard GitHub-hosted jobs can still pass, but `all checks passed` never starts and an otherwise healthy pull request cannot satisfy branch protection. -Billing health, a runner definition's `Ready` state, and a large autoscaling ceiling do not prove that a named pool can receive a job. Required correctness checks need a portable execution path that does not depend on repository-external runner provisioning. +Billing health, a runner definition's `Ready` state, and a large autoscaling ceiling do not prove that a named pool can receive a job. Required correctness checks need a known portable recovery path even when the ordinary low-latency path depends on repository-external runner provisioning. ## Decision -[CI](../../../../.github/workflows/ci.yml) runs every required pull-request job on GitHub's standard `ubuntu-latest` or `windows-2025` capacity. The primary Node and Windows jobs keep their complete consolidated inventories, while top-level gates, coverage, ESLint, publint, and snapshot replay use one worker on the smaller hosts. Node versions are selected through `actions/setup-node`, and the Windows job enables Developer Mode before installing the symlinked workspace. +[CI](../../../../.github/workflows/ci.yml) runs the required primary Node 24 and Windows jobs on repo-restricted enterprise 32-core pools. Standard `ubuntu-latest` jobs retain Node 22.19, Node 26, and Python SDK compatibility, and `master` runs complete serial Linux, macOS, and Windows references. Those standard-hosted jobs keep the portable execution boundary observable without duplicating the primary inventory on every pull request. -The `node 24 / complete`, Node compatibility, Python SDK, and `windows node 24 / complete` jobs remain dependencies of `all checks passed`; no gate is removed or made observational to recover availability. Branch protection continues to require `e2e` and `all checks passed`. +The two Linux primary jobs, Node compatibility, Python SDK, and `windows node 24 / complete` remain dependencies of `all checks passed`; branch protection continues to require `e2e` and `all checks passed`. There is no automatic fallback when an enterprise label cannot allocate: the standard jobs continue to report their own contracts, but they cannot manufacture the missing required result. -The two manual larger-runner suites and all twelve organization-owned labels remain available for measurement. They do not participate in ordinary pull requests. The [larger-runner measurements](2026-07-22-evidence-based-larger-hosted-runners.md) remain evidence for future performance work, while the [serial cross-platform reference](2026-07-21-serial-cross-platform-ci-reference.md) remains the independent master-push completeness check. +The [larger-runner decision](2026-07-22-evidence-based-larger-hosted-runners.md) owns the current primary topology and its measurements. The [serial cross-platform reference](2026-07-21-serial-cross-platform-ci-reference.md) remains the independent standard-hosted completeness check, and the manual larger-runner suites retain size comparisons without expanding the ordinary required matrix. ## Alternatives considered -**Wait for organization-runner allocation to recover.** A queue with no assigned runner emits no repository diagnostic and can block every pull request indefinitely, so an external recovery is not a correctness path. +**Keep every required job on standard capacity.** This removes the enterprise allocation dependency, but complete standard-runner jobs give materially slower feedback and still experience shared-capacity queues. The current split retains portable compatibility and serial evidence while spending enterprise capacity on the primary critical path. -**Use only the smallest organization-owned pools.** Every named pool crosses the same organization allocation boundary; reducing core count does not remove the dependency that caused the queue. +**Select enterprise size from advertised core count.** Benchmarks show non-monotonic scaling and setup variance, so exact complete-job measurements choose the required pools instead. **Skip or demote checks while capacity is unavailable.** This would make the status green by dropping evidence rather than by running the repository's required contracts. -**Keep larger-host worker limits on standard runners.** Concurrent full-repository gates and their inner worker pools can oversubscribe the smaller memory and CPU allocation, turning an availability repair into contention failures. +**Use one worker policy on every host.** Outer gate concurrency and inner tool workers contend differently on Linux, Windows, and standard runners; measured host-specific bounds avoid turning additional cores into slower execution. ## Consequences -Ordinary pull requests can acquire runners without organization-specific configuration, and a live exact-head run proves the same commands that branch protection consumes. The trade-off is longer elapsed time and more rounded standard-runner minutes than the measured larger-runner topology. +Ordinary pull requests receive lower active runtime at the cost of depending on enterprise configuration and paid rounded minutes. A live exact-head run proves the same commands that branch protection consumes; queue delay is reported separately from each job's `startedAt` to `completedAt` execution interval. -Manual larger-runner benchmarks can remain queued without blocking pull requests. Restoring larger runners to the required path needs a separate evidence-based decision after exact-head jobs receive nonzero runner IDs and complete reliably; changing a definition's status alone is insufficient. +Standard compatibility and serial jobs remain useful when enterprise allocation is degraded, but they do not make a blocked required aggregate green. Recovering availability may require temporarily restoring the complete standard-hosted topology; changing a pool definition's status alone is insufficient evidence that it can receive work. diff --git a/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.zh.md b/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.zh.md index cbd5d15005..c6839a133d 100644 --- a/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.zh.md +++ b/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.zh.md @@ -1,4 +1,4 @@ -# Agent Note: 可移植的拉取请求必需 CI +# Agent Note: 拉取请求 CI 的可移植恢复边界 Status: implemented @@ -8,28 +8,28 @@ Status: implemented 分配到组织自有运行器标签的拉取请求必需作业,在 GitHub 无法为这些池分配运行器时会持续排队。工作流本身有效,GitHub 标准托管作业仍能通过,但 `all checks passed` 始终无法启动,原本健康的拉取请求因此无法满足分支保护要求。 -账单状态正常、运行器定义处于 `Ready` 状态以及较高的自动扩缩容上限,都不能证明指定的运行器池可以接收作业。必需的正确性检查需要一条可移植的执行路径,且该路径不能依赖仓库外部的运行器预配。 +账单状态正常、运行器定义处于 `Ready` 状态以及较高的自动扩缩容上限,都不能证明指定的运行器池可以接收作业。必需的正确性检查需要预先明确一条可移植恢复路径,即使日常低延迟路径依赖仓库外部的运行器预配也不例外。 ## 决策 -[CI](../../../../.github/workflows/ci.yml) 在 GitHub 标准的 `ubuntu-latest` 或 `windows-2025` 容量上运行每项拉取请求必需作业。主 Node 作业和 Windows 作业保留各自完整的合并清单,而顶层门禁、覆盖率、ESLint、publint 和快照回放在这些较小的主机上均使用 1 个工作线程。Node 版本通过 `actions/setup-node` 选择;Windows 作业会在安装采用符号链接的工作区前启用开发人员模式。 +[CI](../../../../.github/workflows/ci.yml) 在仅限本仓库使用的企业级 32 核运行器池上运行必需的主 Node 24 作业和 Windows 作业。标准 `ubuntu-latest` 作业保留 Node 22.19、Node 26 和 Python SDK 兼容性,`master` 则运行完整的 Linux、macOS 和 Windows 串行参考流程。这些标准托管作业让可移植执行边界保持可观测,而不必在每个拉取请求中重复主清单。 -`node 24 / complete`、Node 兼容性、Python SDK 和 `windows node 24 / complete` 作业继续作为 `all checks passed` 的依赖项;为恢复可用性,不会移除任何门禁,也不会将其降为观测性检查。分支保护继续要求 `e2e` 和 `all checks passed`。 +两项 Linux 主作业、Node 兼容性、Python SDK 和 `windows node 24 / complete` 继续作为 `all checks passed` 的依赖项;分支保护继续要求 `e2e` 和 `all checks passed`。企业级运行器标签无法分配运行器时没有自动后备机制:标准作业会继续报告各自的契约,但无法产出缺失的必需结果。 -两项手动大型运行器套件和全部 12 个组织自有标签继续用于测量,但不参与普通拉取请求。[大型运行器测量结果](2026-07-22-evidence-based-larger-hosted-runners.md)继续作为后续性能工作的证据,[跨平台串行参考流程](2026-07-21-serial-cross-platform-ci-reference.md)则继续作为 master 推送时独立的完整性检查。 +当前主拓扑及其测量结果由[大型运行器决策](2026-07-22-evidence-based-larger-hosted-runners.md)记录。[跨平台串行参考流程](2026-07-21-serial-cross-platform-ci-reference.md)继续作为独立的标准托管完整性检查,手动大型运行器套件则保留规格比较,同时不扩大普通必需矩阵。 ## 曾考虑的替代方案 -**等待组织运行器恢复分配。** 未分配运行器的队列不会产生仓库诊断信息,而且可能无限期阻塞每个拉取请求,因此依赖外部恢复不能构成正确性路径。 +**将所有必需作业保留在标准容量上。** 此方案消除了企业级运行器分配依赖,但标准运行器上的完整作业反馈明显更慢,仍会遇到共享容量排队。当前拆分既保留可移植兼容性和串行证据,又将企业级运行器容量用于主关键路径。 -**仅使用最小的组织自有运行器池。** 每个指定的运行器池都需要经过相同的组织分配边界;减少核心数不能消除导致作业排队的依赖。 +**根据标称核心数选择企业规格。** 基准测试表明扩展效果不呈单调变化,设置耗时也存在波动,因此必需运行器池改由完整作业的精确测量结果选定。 **在容量不可用时跳过检查或降低其级别。** 这种方式通过丢弃证据而非执行仓库的必需契约来使状态变绿。 -**在标准运行器上保留大型主机的工作线程上限。** 完整仓库门禁及其内层工作线程池并发运行时,可能超出较小主机的内存和 CPU 配额,使可用性修复变成资源争用故障。 +**在每台主机上使用同一工作线程策略。** 外层门禁并发与内层工具工作线程在 Linux、Windows 和标准运行器上的争用方式不同;按主机实测的上限可以避免新增核心反而拖慢执行。 ## 后果 -普通拉取请求无需组织专有配置即可获得运行器,一次实际的分支头精确运行能够证明分支保护使用的同一组命令。代价是,与实测的大型运行器拓扑相比,总耗时更长,而且按整分钟计费的标准运行器用量更多。 +普通拉取请求获得更短的活动耗时,代价是依赖企业级运行器配置,并消耗按整分钟取整的付费分钟数。一次实际的分支头精确运行能够证明分支保护使用的同一组命令;排队延迟与每个作业从 `startedAt` 到 `completedAt` 的执行区间分开报告。 -手动大型运行器基准测试可以继续排队,而不会阻塞拉取请求。要将大型运行器恢复为必需路径,需要在分支头精确作业获得非零运行器 ID 并可靠完成后,另行作出基于证据的决策;仅改变运行器定义的状态还不够。 +企业级运行器分配能力下降时,标准兼容性作业和串行作业仍能提供有用证据,但无法让受阻的必需聚合流程变绿。恢复可用性时,可能需要暂时恢复完整的标准托管拓扑;仅改变运行器池定义的状态,不足以证明它可以接收作业。 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9d5630d33..8a1cac0e4e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,19 +27,31 @@ env: jobs: - # One enterprise runner pays setup once, then executes the complete - # unsharded primary Node inventory with repository-level concurrency. + # Two enterprise runners split the two longest primary Node paths. The + # static lane starts snapshot and artifact validation as soon as its build + # completes, while exhaustive coverage runs alone on the other runner. node-24: if: github.event_name == 'pull_request' - runs-on: dsh-enterprise-ubuntu-24-04-32core-test - name: node 24 / complete + runs-on: ${{ matrix.runner }} + name: ${{ matrix.name }} env: - DSH_COVERAGE_MAX_WORKERS: '16' + DSH_COVERAGE_MAX_WORKERS: '24' DSH_ESLINT_CACHE: '1' - DSH_ESLINT_CONCURRENCY: '16' - DSH_GATE_CONCURRENCY: '10' - DSH_PUBLINT_CONCURRENCY: '16' - DSH_SNAPSHOT_MAX_CONCURRENCY: '8' + DSH_ESLINT_CONCURRENCY: '8' + DSH_GATE_CONCURRENCY: '8' + DSH_NODE_COMPAT_SKIP_TYPECHECK: '1' + DSH_PUBLINT_CONCURRENCY: '8' + DSH_SNAPSHOT_MAX_CONCURRENCY: '32' + strategy: + fail-fast: false + matrix: + include: + - lane: static-snapshots-artifacts + name: node 24 / static, snapshots, and artifacts + runner: dsh-enterprise-ubuntu-latest-32core-test + - lane: coverage + name: node 24 / coverage + runner: dsh-enterprise-ubuntu-24-04-32core-test steps: - uses: actions/checkout@v6 with: @@ -55,6 +67,7 @@ jobs: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm- - uses: actions/cache/restore@v4 + if: matrix.lane == 'static-snapshots-artifacts' with: path: .cache/eslint key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'tsconfig.json', 'tsconfig.base.json', 'tsconfig.base.client.json', 'tsconfig.host.json', 'tsconfig.client.json', 'packages/*/*/tsconfig.json', 'examples/*/tsconfig.json') }} @@ -79,8 +92,67 @@ jobs: if (( install_status != 0 )); then exit "$install_status"; fi exit "$sandbox_status" - - name: Run complete unsharded primary Node CI concurrently - run: pnpm run check:ci + - name: Run static, compatibility, snapshot, and artifact gates + if: matrix.lane == 'static-snapshots-artifacts' + run: | + static_log="$RUNNER_TEMP/static-gates.log" + : > "$static_log" + pnpm run check:ci:static > >(tee "$static_log") 2>&1 & + static_pid=$! + + until grep -Fq 'run-gates: PASS build ' "$static_log"; do + if ! kill -0 "$static_pid" 2>/dev/null; then + static_status=0 + wait "$static_pid" || static_status=$? + if grep -Fq 'run-gates: PASS build ' "$static_log"; then break; fi + if (( static_status != 0 )); then exit "$static_status"; fi + echo '::error::Static gates exited without completing the build.' + exit 1 + fi + sleep 0.2 + done + + pnpm run check:ci:lint & + lint_pid=$! + pnpm run check:node-compat & + compat_pid=$! + DSH_EXAMPLE_MODE=lib pnpm run test:snapshot & + snapshot_pid=$! + pnpm run publint & + publint_pid=$! + pnpm run verify-node-next-types & + node_next_pid=$! + pnpm run verify-built-package-invariants & + built_invariants_pid=$! + DSH_EXAMPLE_MODE=lib pnpm exec vitest run --config vitest.e2e.config.ts \ + examples/headless-agent/tests/keyless-smoke.e2e.ts \ + examples/tui-agent/tests/tui-keyless-smoke.e2e.ts \ + packages/examples/cli-demo/tests/built-bin.e2e.ts \ + packages/examples/acp-demo/tests/built-bin.e2e.ts \ + packages/ui/jsonrpc/tests/built-scope-carrier.e2e.ts \ + packages/workflow/workflow-workerthread/tests/built-worker.e2e.ts \ + packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts & + built_bin_pid=$! + + final_status=0 + capture_status() { + local child_status=0 + wait "$1" || child_status=$? + if (( final_status == 0 && child_status != 0 )); then + final_status=$child_status + fi + } + for child_pid in \ + "$static_pid" "$lint_pid" "$compat_pid" "$snapshot_pid" \ + "$publint_pid" "$node_next_pid" "$built_invariants_pid" "$built_bin_pid" + do + capture_status "$child_pid" + done + exit "$final_status" + + - name: Run exhaustive coverage + if: matrix.lane == 'coverage' + run: pnpm run check:ci:coverage node-compat: @@ -148,27 +220,19 @@ jobs: run: uv run --python 3.10 --group test --project python/sdk pytest # One Windows box shares setup across the required build/site checks and the - # complete observational portability inventory. run-gates reports failures - # from observational gates without allowing them to fail the required job. + # observational portability inventory. Linux owns duplicate lint, coverage, + # and snapshots so they do not dominate the paid Windows critical path. windows: if: github.event_name == 'pull_request' runs-on: dsh-enterprise-windows-2025-32core-test name: windows node 24 / complete env: DSH_COVERAGE_MAX_WORKERS: '12' - DSH_ESLINT_CACHE: '1' DSH_GATE_CONCURRENCY: '16' DSH_PUBLINT_CONCURRENCY: '16' steps: - uses: actions/checkout@v6 - - uses: actions/cache/restore@v4 - with: - path: .cache/eslint - key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'tsconfig.json', 'tsconfig.base.json', 'tsconfig.base.client.json', 'tsconfig.host.json', 'tsconfig.client.json', 'packages/*/*/tsconfig.json', 'examples/*/tsconfig.json') }} - restore-keys: | - ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full- - - name: Enable Developer Mode (symlink support) shell: pwsh run: >- diff --git a/eslint.config.mjs b/eslint.config.mjs index eec63181df..ef03904390 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -12,6 +12,7 @@ export default tseslint.config( '**/.sessions/**', '.claude/**', // harness-local state (worktrees, skills) — other checkouts, not this one's sources '**/.doc-typecheck-*/**', + '**/.node-next-types-*/**', 'website/.generated/**', 'vendor/**', // vendored source keeps upstream style and idioms 'native/**', // imported landlock-run subtree: self-contained workspace with its own gates (native/README.md) diff --git a/scripts/run-gates.ts b/scripts/run-gates.ts index 896ffacfd1..2f0bd164c9 100644 --- a/scripts/run-gates.ts +++ b/scripts/run-gates.ts @@ -317,14 +317,8 @@ function ciWindowsCompleteGates(): Gate[] { function ciWindowsObservationalGates(): Gate[] { return [ ...ciStaticGates(), - lintGate(), + // Linux owns required lint, coverage, and snapshots; Windows omits those duplicates. pnpmScript('duplication', 'duplication'), - { - ...coverageGate(), - env: { DSH_EXAMPLE_MODE: 'lib' }, - needs: ['build'], - }, - snapshotGate(), pnpmScript('publint', 'publint', { needs: ['build'] }), pnpmScript('node-next-types', 'verify-node-next-types', { label: 'node-next types',