Browse Source

ci: require native Windows aggregate verdict

Tianyi Cui 1 month ago
parent
commit
c92c86492d

+ 2 - 2
.agents/notes/implemented/process/2026-08-08-native-windows-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 .agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md
-2026-08-08-native-windows-pull-request-ci.md: 1f8bf7c9e5249ce218fd0d169ed82008c2dbbd36
-2026-08-08-native-windows-pull-request-ci.zh.md: efe044e601aebc92f5d9446a1a683c935dcd783b
+2026-08-08-native-windows-pull-request-ci.md: 1fbfe6615ac72e5d9ca4017208cbbdf684f4c43e
+2026-08-08-native-windows-pull-request-ci.zh.md: 7d7c5234807daa8c41647aeafb5c93d16eca7f9b

File diff suppressed because it is too large
+ 3 - 3
.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md


File diff suppressed because it is too large
+ 3 - 3
.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.zh.md


+ 6 - 0
.agents/notes/implemented/process/2026-08-22-native-windows-blocks-pull-request-aggregate.i18n.yaml

@@ -0,0 +1,6 @@
+# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
+# 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 .agents/notes/implemented/process/2026-08-22-native-windows-blocks-pull-request-aggregate.md
+2026-08-22-native-windows-blocks-pull-request-aggregate.md: ddec9536cbb350d3792ae547150b175ef21f1b9e
+2026-08-22-native-windows-blocks-pull-request-aggregate.zh.md: 94fa8b3836c15b9c977c39c7539cbb1be5fc882b

+ 31 - 0
.agents/notes/implemented/process/2026-08-22-native-windows-blocks-pull-request-aggregate.md

@@ -0,0 +1,31 @@
+# Agent Note: Native Windows blocks the pull-request aggregate
+
+Status: implemented
+
+English | [中文](2026-08-22-native-windows-blocks-pull-request-aggregate.zh.md)
+
+## Problem
+
+Wine reaches blocking win32 toolchain paths quickly, but it cannot prove behavior that depends on the NT kernel, NTFS, PowerShell, Windows process control, or native addons. An `all checks passed` result that can succeed while the complete native job is pending or failed does not enforce the repository's supported Windows behavior.
+
+The native job runs the complete supported-source coverage denominator and its owning Windows acceptance inventory. Its optimized 16-core hosted run completes within the five-minute target, making that higher-fidelity result short enough for the required pull-request path.
+
+## Decision
+
+The `all-checks-passed` job in [ci.yml](../../../../.github/workflows/ci.yml) lists both `windows` and `windows-native` in `needs`. Its existing `if: always()` verdict treats a failed, cancelled, or skipped native job like any other unsuccessful dependency, so `all checks passed` cannot succeed until the real-Windows job succeeds.
+
+Branch protection continues to require the single stable `all checks passed` context rather than adding the native job name as another protected context. The [dual Windows topology](2026-08-08-native-windows-pull-request-ci.md) owns each job's host, failover selector, and inventory; this note owns their blocking relationship. The aggregate bookkeeping job follows the Linux failover selector for its own runner while `needs` independently waits for the pool selected by `DSH_CI_FAILOVER_WINDOWS`.
+
+## Alternatives considered
+
+**Keep native Windows informational.** This preserves the shortest aggregate path, but permits a merge while the highest-fidelity supported Windows verdict is pending or red.
+
+**Require `windows node 24 / native complete` directly in branch protection.** This duplicates workflow topology in repository settings and makes a job-name change a control-plane migration. The aggregate already provides one stable required context and fails closed over unsuccessful dependencies.
+
+**Remove Wine from the aggregate.** Native Windows provides higher fidelity, but Wine still returns a faster win32 build and production-site signal, preserves the compatibility topology, and gives maintainers earlier failure evidence while the native inventory runs.
+
+## Consequences
+
+Every merge waits for native Windows runner capacity and for the complete native job to finish. A failure, cancellation, or skip in that job makes `all checks passed` fail; a passing Wine job alone is insufficient.
+
+The workflow remains one pull-request Action with one native Windows job, unchanged test coverage, and unchanged gate semantics inside that job. The required aggregate gains the native job's measured duration without adding a separately managed branch-protection context.

+ 31 - 0
.agents/notes/implemented/process/2026-08-22-native-windows-blocks-pull-request-aggregate.zh.md

@@ -0,0 +1,31 @@
+# Agent Note: 原生 Windows 阻断拉取请求聚合流程
+
+Status: implemented
+
+[English](2026-08-22-native-windows-blocks-pull-request-aggregate.md) | 中文
+
+## 问题
+
+Wine 能快速触达阻断性 win32 工具链路径,但无法证明依赖 NT 内核、NTFS、PowerShell、Windows 进程控制或原生插件的行为。如果 `all checks passed` 能在完整原生作业仍处于待处理或失败状态时成功,它就没有强制验证仓库所支持的 Windows 行为。
+
+原生作业会运行完整的受支持源码覆盖率分母及其所属 Windows 验收清单。优化后的 16 核托管运行能在五分钟目标内完成,因此这项保真度更高的结果足够短,可以进入必需的拉取请求路径。
+
+## 决策
+
+[ci.yml](../../../../.github/workflows/ci.yml) 中的 `all-checks-passed` 作业会在 `needs` 中同时列出 `windows` 与 `windows-native`。其现有的 `if: always()` 判定会像处理其他未成功依赖项一样处理失败、取消或跳过的原生作业,因此真实 Windows 作业成功前,`all checks passed` 无法成功。
+
+分支保护继续要求单一且稳定的 `all checks passed` 检查,而不把原生作业名称添加为另一个受保护检查。[Windows 双通道拓扑](2026-08-08-native-windows-pull-request-ci.zh.md)负责每个作业的宿主、故障转移选择器与清单;本文负责二者的阻断关系。聚合记账作业为自身运行器采用 Linux 故障转移选择器,而 `needs` 会独立等待 `DSH_CI_FAILOVER_WINDOWS` 所选池中的作业。
+
+## 曾考虑的替代方案
+
+**让原生 Windows 只提供信息。** 这会保留最短的聚合路径,但也允许在保真度最高的受支持 Windows 判定仍处于待处理或红灯状态时合并。
+
+**在分支保护中直接要求 `windows node 24 / native complete`。** 这会在仓库设置中复制工作流拓扑,并使作业名称变更成为控制面迁移。现有聚合流程已经提供一个稳定的必需检查,并会对未成功的依赖项快速失败。
+
+**从聚合流程移除 Wine。** 原生 Windows 的保真度更高,但 Wine 仍能更快返回 win32 构建与生产网站信号、保留兼容性拓扑,并在原生清单运行期间更早地为维护者提供失败证据。
+
+## 后果
+
+每次合并都会等待原生 Windows 运行器容量与完整原生作业结束。该作业失败、取消或跳过都会使 `all checks passed` 失败;仅 Wine 作业通过并不足够。
+
+工作流仍然是单个拉取请求 Action,并保留一个原生 Windows 作业、不变的测试覆盖率以及该作业内不变的门禁语义。必需聚合流程会增加原生作业的实测时长,但无需新增单独管理的分支保护检查。

+ 18 - 18
.github/workflows/ci.yml

@@ -306,10 +306,10 @@ jobs:
       targets: node24-linux-x64
       ci: true
 
-  # The required pull-request Windows signal: the two blocking win32 surfaces
-  # (workspace build, production site) execute with real, checksum-verified
-  # Windows Node under Wine on standard hosted Linux. The independent
-  # windows-native job below keeps the complete native-kernel inventory —
+  # The pull-request Windows signals cover complementary hosts. The two fast
+  # win32 toolchain surfaces (workspace build, production site) execute with
+  # real, checksum-verified Windows Node under Wine on standard hosted Linux.
+  # The windows-native job below keeps the complete native-kernel inventory —
   # including the observational portability gates this lane does not run —
   # on real Windows. This job only provisions runner state (caches,
   # apt); scripts/wine-windows-gates.sh owns the gate logic and is the same
@@ -396,13 +396,12 @@ jobs:
         if: always()
         run: wineserver -k 2>/dev/null || true
 
-  # Every pull request also gets a real Windows-kernel signal. This job keeps
-  # its own unmasked conclusion but is deliberately absent from
-  # all-checks-passed.needs, so it never delays or changes that required
-  # verdict. Under normal operation it runs on the hosted larger runner; under
-  # Windows failover (DSH_CI_FAILOVER_WINDOWS=selfhosted) it retargets onto the
-  # in-house self-hosted Windows pool. Dependabot PRs are excluded from the
-  # self-hosted pool and stay queued for the hosted runner — see the failover
+  # Every pull request also gets a real Windows-kernel signal. Its unmasked
+  # conclusion is a dependency of all-checks-passed, so failure, cancellation,
+  # or omission blocks the required verdict. Under normal operation it runs on
+  # the hosted larger runner. DSH_CI_FAILOVER_WINDOWS=selfhosted retargets it
+  # onto the in-house self-hosted Windows pool. Dependabot PRs are excluded
+  # from the self-hosted pool and stay queued for the hosted runner — see the failover
   # runbook. This Windows switch is independent of the Linux
   # DSH_CI_FAILOVER_LINUX variable that retargets the three required Linux jobs
   # and the all-checks-passed verdict above.
@@ -461,10 +460,10 @@ jobs:
   # Single stable required check for branch protection: require "all checks
   # passed" instead of enumerating matrix legs whose names change as lanes and
   # node versions evolve. Every blocking job in THIS workflow must be listed in
-  # `needs`. The required Wine job is listed as `windows`; `windows-native` is
-  # deliberately absent so its independent result never delays or changes this
-  # verdict. (`needs` cannot reach across workflow files; the master-only jobs in
-  # ci-master.yml are intentionally not part of this PR verdict.)
+  # `needs`, including both the Wine `windows` job and the real-kernel
+  # `windows-native` job. (`needs` cannot reach across workflow files; the
+  # master-only jobs in ci-master.yml are intentionally not part of this PR
+  # verdict.)
   # `if: always()` is load-bearing: without it a failed dependency
   # would SKIP this job, and GitHub counts a skipped required check as passing
   # — so this job always runs and fails on any non-success result, including
@@ -475,14 +474,15 @@ jobs:
     # provisioning — and under Linux failover it follows the same selector as
     # the worker jobs it aggregates, so a standard-hosted outage cannot strand
     # the branch-protection verdict either. It retargets with the Linux switch
-    # (DSH_CI_FAILOVER_LINUX), not the Windows one, because it aggregates the
-    # required Linux workers and runs on the vm-backup pool.
+    # (DSH_CI_FAILOVER_LINUX), not the Windows one, because this bookkeeping job
+    # itself runs on Linux; the native dependency resolves its Windows pool
+    # independently.
     runs-on: >-
       ${{ vars.DSH_CI_FAILOVER_LINUX == 'selfhosted'
           && github.event.pull_request.user.login != 'dependabot[bot]'
           && fromJSON('["self-hosted", "linux", "x64", "vm-backup"]')
           || 'ubuntu-latest' }}
-    needs: [node-24, node-24-coverage, node-24-consumers, node-compat, python-sdk, python-runtime, windows]
+    needs: [node-24, node-24-coverage, node-24-consumers, node-compat, python-sdk, python-runtime, windows, windows-native]
     if: always() && github.event_name == 'pull_request'
     steps:
       - name: Fail if any needed job did not succeed

+ 4 - 4
scripts/ci-workflow.spec.ts

@@ -36,7 +36,7 @@ describe('CI workflow', () => {
     }
   })
 
-  it('keeps a required Wine Windows job, a non-blocking native Windows job with failover, and a master-only standby', () => {
+  it('keeps required Wine and native Windows jobs with failover, plus a master-only standby', () => {
     const workflow = loadWorkflow('.github/workflows/ci.yml')
     const masterWorkflow = loadWorkflow('.github/workflows/ci-master.yml')
     if (!isRecord(workflow.jobs)
@@ -73,7 +73,7 @@ describe('CI workflow', () => {
     expect(windows.if).toBe("github.event_name == 'pull_request'")
     expect(commandSteps.some(step => step.run.includes('wine-windows-gates.sh'))).toBe(true)
 
-    // windows-native: non-blocking native job with failover, runs windows-complete.
+    // windows-native: blocking native job with failover, runs windows-complete.
     // Its pool is resolved by the Windows-specific switch.
     expect(typeof windowsNative['runs-on']).toBe('string')
     expect(windowsNative['runs-on']).toContain('DSH_CI_FAILOVER_WINDOWS')
@@ -104,9 +104,9 @@ describe('CI workflow', () => {
     expect(serialWindows['runs-on']).toEqual(['self-hosted', 'dsh-win-ci', 'windows'])
     expect(serialWindows.name).toBe('serial / windows (self-hosted standby)')
 
-    // Aggregate: Wine `windows` required, native `windows-native` excluded.
+    // Aggregate: both complementary Windows jobs are required.
     expect(aggregate.needs).toContain('windows')
-    expect(aggregate.needs).not.toContain('windows-native')
+    expect(aggregate.needs).toContain('windows-native')
     expect(aggregate.needs).not.toContain('serial-windows')
 
     // Linux failover is a separate switch: the three required Linux workers

Some files were not shown because too many files changed in this diff