ci: let a merge queue qualify candidates on both required checks - #5297
Conversation
The main ruleset requires an up-to-date branch. Every merge therefore forces each open PR to merge `main` and rerun the full ~45 minute qualification. A GitHub merge queue removes that multiplier, but only if both required checks report on `merge_group` events. - `python-tests.yml` runs on `merge_group`. Queue candidates take the non-PR classification path, which always plans full qualification. Sonar skips temporary queue refs, and the Node forward probe stays on push/dispatch. - `dco.yml` runs on `merge_group`, reads the queue's base ref and head SHA, normalizes the full `refs/heads/` base ref, and fails closed when either coordinate is missing. The trigger stays inert until the ruleset enables a merge queue. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: song <22676124+songoow@users.noreply.github.com>
|
Independent verification of the queue-candidate path, plus one verified nit. 1. The central claim checks out. def candidate(changes, *, pull_request: bool = True):
if not pull_request:
return "full", "main and manual runs retain full qualification"so a 2. Every gating 3. 4. Nit (verified, non-blocking): the assertion on test -n "${BASE_REF}" && test -n "${HEAD_SHA}"does not fail the step under Your new If you want that assertion to be real, make it a statement errexit can see, e.g. if [[ -z "${BASE_REF}" || -z "${HEAD_SHA}" ]]; then
echo "::error::missing base ref or head sha for ${GITHUB_EVENT_NAME}"
exit 1
fiImpact today is low -- both coordinates are always present on Local run caveat (host, not your diff). I applied your head and ran both modified test files in |
Pick up the main fixes from loopx-project#5288 and loopx-project#5292. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: song <22676124+songoow@users.noreply.github.com>
huangruiteng
left a comment
There was a problem hiding this comment.
动机
按 LoopX PR review capability 的 policy revision 12 评审完整 head 0833b9202142353b35723b776911cec572aa6560,基线固定为 5ab23b3f67a2c1098717ebb450572c8158683153。没有阻断性发现,有一项输入诊断的非阻断 refinement。旧工作流不接收 merge-group 事件,即使规则集启用队列也无法给队列候选报告两个必需检查;本 PR 补齐这个入口,是 S12 贡献/合并体验的准备阶段,不是宣称队列已启用或已测得端到端提速。
改动思路
让 Sign-off 和 merge-gate 沿用既有规则检查队列候选,而不是用 PR head 的旧结果代替候选结果。GitHub 官方事件契约 明确区分 merge_group 与 PR/push;必需 workflow 需要额外订阅这个事件。本 PR 只扩展 workflow 入口:是否启用队列、变更规则集或放松 up-to-date 要求,仍由维护者作独立决定。
普通贡献者目前仍走原来的提交、检查失败、修复/重跑路径,不增加必填配置或新权限。启用后的预期路径才是入队 → 完整候选资格验证 → 两个必需结果;失败交还原有 CI/DCO 修复 owner。节点前向探测和 Sonar 不属于这两个必需结果,不应额外阻塞队列。该阶段可独立验证和回滚,真正的 GitHub 队列触发、运行时 provenance 与运行耗时仍需维护者启用后观察。
具体改动
关键代码讲解
.github/workflows/dco.yml:3–6,24–40添加merge_group,通过原 PR 坐标或 queuebase_ref/head_sha取得检查范围;去掉refs/heads/前缀后仍刷新精确目标分支,再用真实git rev-list查贡献提交。基线已有的“较新上游未签署提交不归该 PR 修”的规则保留。dco.yml:47–72的 DCO/平台合并判断没有放宽。贡献提交仍需 trailer;无 trailer 的两父 GitHub integration commit 只有 exact SHA、签名验证、web-flow 身份及有序父节点全部匹配才可走既有例外,其父贡献独立检查。伪造显示名和 API 不可用不能变成成功。python-tests.yml:56–83的分类入口未另造 queue classifier:非 PR 分支调用原review_gate.py classify --non-pr,得到full,不会借 docs-only 或 presentation-only diff 获得队列豁免;最终merge-gate仍always()执行原verify()。:364–369将非阻断 Node 26 probe 精确限定为 push/dispatch;Sonar 上传只在非 merge-group 事件执行。PR、push、manual 原路径条件保持;docs、presentation 与 full 的原分类契约不变。文档同步声明必须使用merge方法及维护者规则集边界。
正向执行:合成 merge-group 携带完整 base ref → 正规化 → refresh 实际 Git remote → 范围内签署贡献通过;非 PR 分类实际写出 full plan → 原 CLI gate 接受全部应成功的 lane。负向执行:未签署贡献、缺失坐标、无法解析的 ref、API/签名/父节点不匹配或应成功 lane 被跳过都拒绝。base/head 使用相同不可变 Git 输入,普通 PR 的签署/未签署结果、上游刷新和完整诊断一致;原分类/merge-gate 的 PR、push、dispatch 三个入口也保持一致。
本地验证:
uv run --extra test python -m pytest -q tests/test_python_ci_workflow.py tests/test_dco_workflow.py
uv run --extra test python -m unittest discover -s scripts/ci -p 'test_*.py'
uv run --extra test python examples/github-actions-runtime-smoke.py
uv run --extra test python -m ruff check tests/test_dco_workflow.py tests/test_python_ci_workflow.py
uv run --extra test loopx --format json canary premerge --from-git-diff --git-diff-base 5ab23b3f67a2c1098717ebb450572c8158683153 --tier standard --no-progress122 pytest、7 CI 单测、runtime smoke、Ruff、4 个 canary 直接检查与 13 个选中检查全部通过,基线对应 pytest 为 117 项。额外探针执行了真实 workflow Bash、真实本地 bare Git remote 及原 classify/verify CLI;签署贡献的 full-ref 正向对照成功,删除 ref 正规化则失败;重新排序 needs 不改变结果,矛盾豁免和非预期 skip 被拒绝。平台 commit metadata 的负向矩阵使用替代 API 返回值,不能视为已从线上队列获取 provenance;没有修改活动规则集或启动队列。
非阻断 refinement:空 base 的 guard 应在输入边界退出
位置:dco.yml:32 的 test -n "$BASE_REF" && test -n "$HEAD_SHA"。在 set -e 下,左侧为空不会在那里退出,随后 Git 以 invalid refspec 拒绝(实际 exit 128);valid base + 空 head 则在右侧退出(实际 exit 1)。因此当前路径没有复现“空 head 被当成 HEAD 并误通过”的风险,也不是放宽 DCO 的 blocker。但空 base 的错误归因不够直接。
最小修整是拆成两个独立检查,分别输出缺少 base/head 的明确错误再退出;不需要重写 DCO owner。回归应提供真实存在的目标分支,并分别覆盖空 base、空 head、两者都空,断言在调用 fetch/rev-list 前拒绝。现有 fixture 已创建 release/next;额外探针也使用真实分支和未签署候选确认拒绝,而非靠“不存在的分支”得到偶然失败。这个建议不影响本次 APPROVE。
语义与 CI 对齐
必需检查从仓库的 docs/development/testing-and-quality.md 解析为 Sign-off/merge-gate,不是从远端临时 check 名猜测。新增的是 GitHub 外部事件坐标,复用 loopx_ci_job_plan_v1 和既有 DCO 验证语义;没有新的 actor 生命周期、写权限、Goal 状态或手动同步标志。默认关闭的是规则集队列,不是发现 trigger 就自动激活。关停队列时 PR/push/dispatch 的条件、classifier、门禁和提示保持原行为;启用时队列按既有 non-PR full 分支处理。
对主干的风险
最强风险是误把队列候选当成可豁免的普通 PR,或把未签署贡献当作平台 merge 放过。实际 CLI 的完整规划、失败/跳过拒绝矩阵及既有 provenance 负向测试覆盖这些分支;API 故障明确 fail closed,恢复 owner 是 CI 重跑/贡献者 DCO 修复,绝不是 bypass 来伪造通过。工作流没有新增 secrets 或写权限,规则集 activation 留在维护者边界;没有产品前端/Lark/持久化变化,无需配套 UI。
本机 Bash 3.2 在未变的 PR 分类 shell 里报 extra[@]: unbound variable,同输入基线/本 head 的完整错误一致;独立执行实际分类 CLI、非 PR shell 和 gate 都通过。这是基线已有的本地 shell 版本限制,不作为该 PR 的 request-changes 理由。本次未查询或等待远端 CI,也未把 mock metadata 或本机结果宣称为 GitHub queue 的线上验收。启用前仍需维护者确认真实 candidate 的两父 merge/provenance 与两个 required check 行为,不能据本评审自动改变 ruleset。
我的整体评价
APPROVE,附上述非阻断诊断建议。96 增/12 删、五个文件交付的是边界清楚的 workflow 准备增量;既有 DCO、分类器和 gate owner 都复用,没有 speculative runner 或平行状态源。未来向的收敛检查已覆盖输入 guard 与共享资格 owner:guard 是可做的小修,通用抽象不需要。本评审不等于队列已上线、耗时承诺、merge-readiness 或合并授权;本次不合并。
English verdict: APPROVE
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: song <22676124+songoow@users.noreply.github.com>
The sonar caller check matched the literal text 'needs: pytest' followed by 'uses:', so adding the merge_group skip between them failed it. Parse the job instead: it still must need this run's pytest job and call the local reusable workflow, and its only condition may be the merge_group skip, so a status function such as always() cannot run analysis without same-run coverage. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: song <22676124+songoow@users.noreply.github.com>
|
Updated to exact head What changed:
Local validation on the new head:
No other test or smoke pins the text of the sonar job or the node-forward condition. The other smokes that match Red checks you can still expect from main: the latest The push dismissed the earlier approval, so I am requesting re-review on this head. |
huangruiteng
left a comment
There was a problem hiding this comment.
English verdict: APPROVE
没有阻塞性发现;有一处可后续改善的 fail-fast 诊断。按 LoopX PR review capability policy 12,对完整当前 PR 及上次评审后的 Sonar 测试修复重新验证。评审 head:aa5b03da7598dfb777cd1e4e898f09b3acab8640;固定基线:649826221289cd4cb3dd8880d016e0afbbaca0fc。批准的是工作流准备,不是启用在线合并队列、放宽 ruleset 或执行合并。
动机
原有 Sign-off 和 Python qualification 只覆盖 PR、main 等事件,不能为 GitHub 的临时 merge-group candidate 返回对应必需结果。该 PR 是一个有用且可逆的 prerequisite:提供候选验证入口,仍由维护者决定是否启用队列。它没有声称本地 workflow 测试已证明线上排队节省时间,也没有把修改工作流等同于 ruleset 生效。GitHub 官方事件文档明确区分 merge_group 与 pull_request,要求相关必需工作流订阅前者。
改动思路
最强反对理由是临时合并提交可能被错当成普通 PR,继而获得 docs exemption,或 DCO 用错 base/head 导致检查了空贡献范围。PR 沿用真实的 classifier、merge-gate 和 DCO owner,只适配外部事件坐标;非 PR 路径已有 full qualification,队列复用它,不增加第二个资格规则。Doing nothing 不能返回候选 SHA 上的必需结果;另建 queue verifier 会复制范围和合格定义;扩大 DCO 豁免则越过贡献认证边界。
现有 Mergify 冲突/签署提示工作流不是这个 candidate qualification 的 owner,两者不重复。普通 PR、push、手动运行的用户路径保持不变。持续运行只有已有超时、取消及失败修复后重新候选的路径,不引入手工同步状态。下一步有明确 owner 和依赖:维护者以 merge 方法启用队列,确认真实候选 SHA、提交来源与两个必需结果,再决定是否放宽分支最新要求。这是独立可验证的完整准备切片,不是整个在线队列已交付。
具体改动
关键代码讲解
- Python Tests merge_group trigger:新事件经已有 non-PR 分支进入 full plan。即使候选的底层修改只是 docs 或 presentation,队列也不享受 PR exemption;已有最终 merge-gate 仍逐一验证所需结果。
- Sign-off contribution coordinates:普通 PR 坐标优先,队列使用 merge_group base_ref/head_sha;先去除
refs/heads/前缀,再刷新真实目标分支、计算贡献范围。签署要求未降低,GitHub 双父集成提交豁免仍依赖精确 SHA、父节点及已验证的 GitHub 签名来源。 - Sonar same-run coverage test:最近修改从脆弱的相邻文本断言改为解析实际 job;必须 needs=pytest、调用本仓库 reusable workflow,且只排除 merge_group。删除条件、改 always、更换 coverage owner 或远端 reusable 的四个突变都使该真实测试失败。工作流不为临时队列引用运行 Sonar;非阻塞 Node forward probe 仍仅在 push/manual 运行。
完整差异六文件、104 行增加与 13 行删除:两个 workflow、已有质量文档及三个耐久测试。没有修改 runtime、权限模型、仓储格式或 UI;不引入新的 caller schema,也不需要 frontend/Lark companion 或第一屏预览。旧评审后的修复只改变 Sonar 断言,但本次结论重新覆盖全部 PR,不继承此前批准。
对主干的风险
实际 Bash 配合隔离 Git remote 验证了普通 PR 与队列:有签署提交通过,未签署提交拒绝;刷新 upstream 后只检查贡献范围。缺失 head、缺失 base、同时缺失坐标均拒绝;真实存在的目标分支下,未签署提交失败,补签后的新提交再通过。完整 ref 正向路径通过,删除前缀归一化会在相同 fixture 的真实 fetch 边界失败。GitHub 提交来源 API 使用合成响应;本次没有制造线上候选或调整在线 ruleset,其真实性 readback 属于启用阶段。
非阻塞建议:DCO 第 32 行的 test base && test head 在 set -e 下不会因左侧 empty-base 立刻退出;目前后面的 Git fetch 拒绝无效 refspec,因此没有放行漏洞。可将两项检查分开,并分别给出缺失坐标的可操作错误,保留本次三个缺失组合的实际 Bash regression。这里要求的是更清晰的故障定位,不要求此 PR 修复其他控制面代码。
本地原生验证:相关 pytest 129 项通过;CI owner unittest 7 项通过;GitHub Actions runtime smoke、Ruff、diff check 通过。标准 premerge canary 首次因磁盘容量不足在创建比较 checkout 时失败,保留记录并回收验证依赖后,同一 head、同一命令重跑的 4 项直接检查和 12 项选中 smoke 全部通过,未缩小范围或放宽预算。真实 classifier/merge-gate 覆盖 PR/push/manual/merge_group 与 docs/presentation/full;缺失或矛盾资格拒绝,修复后重新通过,重排 needs 保持输出。
语义与 CI 对齐
语义选择是适配 GitHub 外部事件、复用本仓库既有 full qualification 与 Sign-off contract。队列触发存在不等于在线激活或授权范围扩大,普通 PR/push/manual 的完整分类、资格诊断和恢复与固定 base 配对保持一致。base/head 的三项既有失败身份及 assertion 详情一致:generated pair 2 != 1,以及两个 read-channel case 的 turn_start_capability_hook_dispatch 差异;只归一化内存地址、耗时,失败 owner 和测试都未改动,归因为 pre_existing_unrelated,不据此 request changes。另保留 base/head 同样的本机 Bash 3.2 nounset 限制。未查询、轮询或等待远端 CI;评审批准不覆盖独立的必需检查/合并就绪约束。
我的整体评价
APPROVE,交付判断为 justified increment。long_horizon 和 user_experience 均保持:已存在的资格、签署、错误修复和重跑流程没有额外确认或手工状态;新的维护者激活步骤提供此前不存在的在线 ruleset 权限,不能省略。未来向重构检查考虑了 classifier、merge-gate、DCO 及 Sonar 的规则 owner,选择保留共同 owner,没有为新事件建立平行验证器;诊断改善可以有界后续处理。残余范围是真实 GitHub 队列候选来源和 hosted 调度,需要维护者启用阶段 readback,而不是把本地 fixture 当成线上验收。没有执行合并。
Problem
The
protect mainruleset setsstrict_required_status_checks_policy: true, and the required checks areSign-offandmerge-gate. Every merge tomaintherefore makes every other open PR out of date. Each of those PRs has to mergemain(locally, to keep DCO sign-off) and rerun the fullPython Testsqualification: a median of about 45 minutes, and about 69 minutes at p90, over recent PR runs. With several dozen open PRs, most CI capacity goes to these reruns, and the extra jobs also add queueing to everyone else's runs.A GitHub merge queue removes that multiplier: a PR is qualified on its own pushes, and the queue qualifies the exact merged candidate once. The queue can only be enabled if both required checks report on
merge_groupevents. Today neither workflow does, so enabling a queue would stall every entry.Change
python-tests.ymladdson: merge_group. Thechangesjob already sends any non-pull_requestevent throughreview_gate.py classify --non-pr, which always plansfull. Queue candidates therefore get no exemption, exactly likemain.sonaris skipped formerge_group, so temporarygh-readonly-queue/*refs do not create SonarCloud branches.node-forward-compatibilitychanges fromevent_name != 'pull_request'topush || workflow_dispatch. The 45-minute non-blocking probe stays off the queue, and its behavior on push and dispatch is unchanged.dco.ymladdson: merge_group. It readsmerge_group.base_refandmerge_group.head_shawhen there is no pull-request payload. It strips therefs/heads/prefix that the queue event carries, and fails closed if either coordinate is empty. The contribution-range logic and the verified-GitHub-merge exemption are unchanged.Default-off: until the ruleset enables a merge queue, GitHub never emits
merge_group.pull_requestbehavior in both workflows is unchanged. Forpushandworkflow_dispatch, the only difference is the forward-probe condition, which was already true for those events.Activation (maintainer ruleset decision, not part of this PR)
protect mainwith merge method merge.Sign-offexempts only verified GitHub-generated two-parent merges; a squash or rebase queue would produce commits it does not recognize, and the check would fail closed.strict_required_status_checks_policy). That is where the rerun savings come from.The first real queue run is the first live observation of GitHub's queue-commit provenance. If
Sign-offrejects the queue merge commit, the queue fails loudly rather than silently passing.Validation
pytest tests/test_dco_workflow.py: 24 passed. The test harness now evaluates the workflow's actual${{ a || b }}env expressions for each event type. New cases cover a merge-queue candidate: signed passes, unsigned fails, a verified platform merge is exempt only with provenance, and a missing base ref or head fails closed. Mutation check: removing therefs/heads/normalization fails 3 of the new tests.pytest tests/test_python_ci_workflow.py: 98 passed. The new test checks thatmerge_groupis a trigger, that onlypull_requestcan take the exemption-classifying branch, that Sonar skips queue refs and thatmerge-gatestill always reports.impact_plan.plan(..., pull_request=False)returnsfull.loopx canary premerge --from-git-diff(tier standard): 13 selected, 13 executed, 0 failures; public-boundary scan clean.self_merge_allowed: false. This changes required-check workflows, so it is left for maintainer merge.merge_groupevent. That needs the ruleset change above.Future-facing pass
Once the queue is active, a possible follow-up is to run some lanes (for example, Windows and Stage 2C) only in the queue rather than on every PR push. That would change the "PRs get full qualification" policy, so it is not proposed here and needs an explicit decision.
Independent of #5295, which moves browser qualification off the critical path. Both touch
python-tests.yml, in different sections.🤖 Generated with Claude Code