fix(ci): 两处聚合门禁改数逐分片正面凭据 —— declared = enforced (#6082) - #6194
Merged
Conversation
`test-gate` / `dogfood-gate` 原本只读一个 `needs.<matrix>.result`:命中 `success|skipped|cancelled` 放行,其余落 `*)` 判红。一个标量装不下三个分片 的判决,实测两个缺陷: - run 31120902911:runner 饥饿下分片被逐个丢弃(runner_id 0、无 steps、零 测试执行),聚合读到未文档化的 `abandoned` ⇒ 假红,31 秒后 PR #6010 被踢 出合并队列。直接把 `abandoned` 加进白名单(方案 A)已被维护者否决:该 run 并非 moot,队列仍在实时消费它的判决,放绿等于对零测试执行发布 `Test Core: success` —— 正是 #4928 要挡的形状。 - run 31114735713:分片 `Test Core (3/3)` job 级 conclusion=failure,同 run 聚合门禁却读到 `abandoned` —— 聚合里的任何生命周期态都会吞掉兄弟分片的真 实判决。所以问题从来不是「少了一个词」。 改为数正面凭据:每个真实分片 job 以「无 `if:` 的最后两步」发布 `shard-attest-<job>-<n>-of-<N>` 制品(「我跑了且我通过了」),门禁下载本 run 的同名制品并要求凭据集合 == 矩阵声明的花名册。从未被调度的分片发不出凭据, 失败的分片同样发不出 —— 吞噬面在结构上消失。`cancelled`(#3668 生命周期态, 真败对其有支配性)与 filter-`skipped`(#4928,期望 N 归零)语义原样保留; 被声明的 `failure` 仍是一票否决,因为 post-step 可以在凭据发布之后翻红。 选制品而非 job `outputs`:GitHub 文档明确矩阵 outputs 互相覆盖(消费者看到 最后完成的那条腿),靠「空值不覆盖」这一未文档化合并行为搭门禁,等于重犯本 单要修的错。同 run 下载制品不需要 `contents: read` 之外的权限,门禁权限块 未变;`actions/cache` 这条同类通道按本仓自身理由否决(10 GB 池已被 turbo 种 子紧张占用)。 可判定的核心抽到 `scripts/check-shard-attestation.mjs`(家族惯例: `--self-test` + 静态扫描),49 条断言含 #3668 形的支配性实验四向、本单两条 实测反例各一钉、#4928 守卫不回退。静态扫描把门禁花名册钉死在矩阵声明上,并 强制凭据两步永远是 job 的最后两步且不带 `if:`。 分片 job 名与 `Test Core` / `Dogfood Regression Gate` 两个必需检查上下文名、 两处 `if: always()` 全部逐字未动。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
`Dogfood Regression Gate gate satisfied` —— 门禁名本身以 Gate 结尾,模板再拼 一个 "gate" 就重了。改为 `<门禁名>: satisfied — …`。纯日志文案,判据未动; 49 条自测与八向支配性实验重跑全绿。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #6082
按 2026-08-07 02:07Z 维护者裁决(comment 5211251003)实施 方案 C:两处聚合门禁改为数逐分片正面凭据(declared = enforced)。⛔ 方案 A(白名单加
abandoned)已被否决,本 PR 不含它;方案 D 是管家台账面,与本 PR 无码面交集。一、被替换的缺陷
test-gate/dogfood-gate原本只读一个标量needs.*.result(矩阵聚合读数),命中success|skipped|cancelled放行、其余落*)判红。一个标量装不下三个分片的判决,前一轮 dev 的实测(comment 5208599210)给出两条反例:runner_id: 0、started_at == created_at、steps数组根本不存在 ⇒ 零测试执行),聚合读到未文档化的abandoned⇒ 假红,31 秒后 #6010 被踢出队列。该 run 并非 moot —— 队列仍在实时消费它的判决,所以放绿 = 对零测试执行发布Test Core: success,正是 #4928 要挡的形状。Test Core (3/3)job 级conclusion: failure在场,同 run 聚合门禁却读到abandoned。聚合里的任何生命周期态都会吞掉兄弟分片的真实判决 —— 问题从来不是「少了一个词」。二、机制选型:制品(artifacts),而非 job
outputs每个真实分片 job 以无
if:的最后两步发布凭据;门禁下载本 run 的同名制品并要求凭据集合 == 矩阵声明的花名册。outputs:GitHub 明文记载矩阵 job 的 outputs 互相覆盖(消费者看到的是最后完成的那条腿),needs.*.outputs.*在结构上装不下三条腿的凭据。社区常用的「空值不覆盖」是未文档化的合并副作用 —— 把合并判决押在未指定的 runner 细节上,恰恰是本单要修的那类错误(abandoned本身就是未文档化枚举漏出)。⇒ 否决。actions/cache:同属「每条腿可写、下游可读」的通道,但按本仓自身的理由否决 —— 10 GB 缓存池已被精打细算(见 ci.ymlRestore Turbo cache注释:PR 侧写入曾驱逐 main 的 turbo 种子,Build Core 51s → 4m30s 是实测事故)。actions: read只用于跨 run / 跨仓),已核对actions/download-artifact@v7的 README 与action.yml(pattern/merge-multiple/github-token的语义);两个门禁的permissions: contents: read原样未动。凭据两步为什么必须是最后两步且不带
if::不带if:的步骤只在此前每一步都成功时才运行,而它们之后没有任何步骤能在凭据已计入通过之后再把 job 翻红。check:shard-attestation静态强制这两条性质。三、期望 N 的推导(#4928 语义不回退)
逐腿推导,不读
filter的 output:skippedfilter自身 success(或 cancelled)时放行 —— #4928 守卫逐字保留。cancelledcancelled有支配性(run 30271824408 实测),放绿不掩盖回归。success/abandoned/ 任何未来漏出值)即「filter 说跳过 ⇒ 期望 N 相应归零、skipped-by-filter 仍是通过」被保留,且这条推导比读 output 更硬:它不可能与实际发生的事情不一致。
被声明的
failure仍是一票否决,即便花名册齐全 —— 一条腿可以先发凭据、再被 post-step(缓存保存的 post action 之类)翻红。计数是必要条件,不是充分条件。其余取值一律只由计数决定,这正是它对「下一个未文档化生命周期值」免疫、而不需要再往词表里加词的原因。四、支配性实验(#3668 先例形)
可判定的核心抽到
scripts/check-shard-attestation.mjs(家族惯例:--self-test+ 静态扫描;check:stall-guard是同形先例 —— 既在 CI 里真跑、又自带自测)。纯 YAML + 不可测 shell 通不过本 PR 自己在实施的「declared = enforced」标准,故抽脚本。四向实验通过真实
--verifyCLI(不是纯函数)驱动,方向在跑之前先行预测:八向全部与预测一致。同样的四向 + 本单两条反例钉 + #4928 守卫三种坏 filter 取值,已作为命名用例进
--self-test(共 49 条断言),脚本内注释直接引 #6082 与两个 run 号。逆向的诚实说明:本 PR 不会让 #6010 那类被踢的 PR 转绿 —— 分片确实零执行,判红是正确的真负例,这一点裁决已明确(治驱逐是已批的 D,归管家台账)。C 的收益是另外两件:①「兄弟分片被丢弃 ⇒ 聚合吞任意红分片」这个更宽缺陷在结构上消失;②「分片没跑却报绿」从「靠词表没写对」变成「凭据不存在,数不出来」。
五、静态漂移守卫(
pnpm check:shard-attestation,已接入 lint.yml)GitHub 无法在
strategy.matrix与下游 job 的步骤之间共享同一个字面量(env上下文在strategy中不可用),所以两处字面量在这里对账,而不是靠记。扫描.github/workflows/ci.yml断言:--leg JOB/N里的 N 等于该 job 声明的strategy.matrix.shard长度(非矩阵 job 为 1);if:;shard-attest-JOB-n-of-N且被门禁的pattern覆盖;if:必须是always()、needs必须含filter(否则filterjob 一旦失败,Test Core / Build Core / Dogfood 会全部 skipped 而分支保护判为通过 —— 隐式 success() 今天已第三次咬人 #4928 守卫无从读取);test-gate/dogfood-gate两个必需上下文承载者必须仍在数凭据;发凭据却无人计数的 job = declared but not enforced ⇒ 红。jobs:/ 解析失败各有一条自测。六、必需检查名稳定 + always-report 存活(#5617 / #3622)
git diff中name:与if:两个键一行未动;解析后复核:test-gate => name: "Test Core" | if: "always()"、dogfood-gate => name: "Dogfood Regression Gate" | if: "always()",分片 job 名Test Core (${{ matrix.shard }}/3)/Dogfood Regression Gate (${{ matrix.shard }}/3)/Dogfood Verify CLI同样逐字保留。if: always()的纯 job(未改成if: !cancelled()),因此在每个 PR 与 merge_group 上必定发布上下文。新增的 checkout / download 步骤只可能让该上下文变红(fail-closed),不可能让它消失 —— 而 chore(ci): cut PR wall-clock ~9.5min → ~5-6min — dedupe dogfood, shard it, cache lint's build #3622 的死锁来自上下文缺席,不是来自它是红的。静态守卫把if: always()本身也钉住了。七、merge_group 一致性
设计中没有任何一处依赖
github.event_name:凭据步骤、上传/下载、门禁判据在pull_request/merge_group/push上完全同形。唯一与事件相关的既有步骤是Save Turbo cache (main only)(if: always() && github.event_name == 'push'),它位于凭据两步之前;在 PR / merge_group 上它是 skipped(不是 failed),而隐式success()只关心「此前有没有步骤失败」,所以凭据步骤照常运行。制品是 run 级资源,与触发事件无关。八、验证
check:workflow-status-functions与本改动无交集:其脚本头明确把needs.*.result/needs.*.conclusion划在范围外(「Those ARE status reads … This gate is about the silent compression of status into data」),而门禁读的正是result;本 PR 也没有新增任何 job 级if:读needs.*.outputs.*。九、无 changeset
仅
.github/workflows/**+scripts/**+ 根package.json(private: true)脚本一行,不发布任何包 ⇒ 打skip-changeset标签,不写空 changeset。Generated by Claude Code